Skip to content

Commit abf72c9

Browse files
ciampotyxla
andauthored
Composite: add context-forwarding with SlotFill example (#65051)
* Composite: add context-forwarding with SlotFill example * Add custom code snippet * CHANGELOG --- Co-authored-by: ciampo <[email protected]> Co-authored-by: tyxla <[email protected]>
1 parent 626c3fa commit abf72c9

File tree

2 files changed

+135
-1
lines changed

2 files changed

+135
-1
lines changed

packages/components/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
- `DropdownMenu` v2: add `GroupLabel` subcomponent ([#64854](https://github.com/WordPress/gutenberg/pull/64854)).
7575
- `DropdownMenuV2`: update animation ([#64868](https://github.com/WordPress/gutenberg/pull/64868)).
7676
- `Composite` V2: fix Storybook docgen ([#64682](https://github.com/WordPress/gutenberg/pull/64682)).
77+
- `Composite` V2: add "With Slot Fill" example ([#65051](https://github.com/WordPress/gutenberg/pull/65051)).
7778
- `Composite` V2: accept store props on top-level component ([#64832](https://github.com/WordPress/gutenberg/pull/64832)).
7879

7980
## 28.6.0 (2024-08-21)

packages/components/src/composite/stories/index.story.tsx

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
/**
22
* External dependencies
33
*/
4-
import type { Meta, StoryFn } from '@storybook/react';
4+
import type { Meta, StoryFn, StoryContext } from '@storybook/react';
55

66
/**
77
* WordPress dependencies
88
*/
99
import { isRTL } from '@wordpress/i18n';
10+
import { useContext, useMemo } from '@wordpress/element';
1011

1112
/**
1213
* Internal dependencies
1314
*/
15+
import { createSlotFill, Provider as SlotFillProvider } from '../../slot-fill';
1416
import { Composite } from '..';
1517
import { useCompositeStore } from '../store';
1618
import { UseCompositeStorePlaceholder, transform } from './utils';
@@ -204,3 +206,134 @@ Typeahead.parameters = {
204206
},
205207
},
206208
};
209+
210+
const ExampleSlotFill = createSlotFill( 'Example' );
211+
212+
const Slot = () => {
213+
const compositeContext = useContext( Composite.Context );
214+
215+
// Forward the Slot's composite context to the Fill via fillProps, so that
216+
// Composite components rendered inside the Fill can work as expected.
217+
const fillProps = useMemo(
218+
() => ( {
219+
forwardedContext: [
220+
[ Composite.Context.Provider, { value: compositeContext } ],
221+
],
222+
} ),
223+
[ compositeContext ]
224+
);
225+
226+
return (
227+
<ExampleSlotFill.Slot
228+
fillProps={ fillProps }
229+
bubblesVirtually
230+
style={ { display: 'contents' } }
231+
/>
232+
);
233+
};
234+
235+
type ForwardedContextTuple< P = {} > = [
236+
React.ComponentType< React.PropsWithChildren< P > >,
237+
P,
238+
];
239+
240+
const Fill = ( { children }: { children: React.ReactNode } ) => {
241+
const innerMarkup = <>{ children }</>;
242+
243+
return (
244+
<ExampleSlotFill.Fill>
245+
{ ( fillProps: { forwardedContext?: ForwardedContextTuple[] } ) => {
246+
const { forwardedContext = [] } = fillProps;
247+
248+
// Render all context providers forwarded by the Slot via fillProps.
249+
return forwardedContext.reduce(
250+
( inner: JSX.Element, [ Provider, props ] ) => (
251+
<Provider { ...props }>{ inner }</Provider>
252+
),
253+
innerMarkup
254+
);
255+
} }
256+
</ExampleSlotFill.Fill>
257+
);
258+
};
259+
260+
export const WithSlotFill: StoryFn< typeof UseCompositeStorePlaceholder > = (
261+
props
262+
) => {
263+
return (
264+
<SlotFillProvider>
265+
<Composite { ...props }>
266+
<Composite.Item>Item one (direct child)</Composite.Item>
267+
<Slot />
268+
<Composite.Item>Item four (direct child)</Composite.Item>
269+
</Composite>
270+
271+
<Fill>
272+
<Composite.Item>Item two (from slot fill)</Composite.Item>
273+
<Composite.Item>Item three (from slot fill)</Composite.Item>
274+
</Fill>
275+
</SlotFillProvider>
276+
);
277+
};
278+
WithSlotFill.args = {
279+
...Default.args,
280+
};
281+
WithSlotFill.parameters = {
282+
docs: {
283+
description: {
284+
story: 'When rendering Composite components across a SlotFill, the Composite.Context should be manually forwarded from the Slot to the Fill component.',
285+
},
286+
source: {
287+
transform: ( code: string, storyContext: StoryContext ) => {
288+
return `const ExampleSlotFill = createSlotFill( 'Example' );
289+
290+
const Slot = () => {
291+
const compositeContext = useContext( Composite.Context );
292+
293+
// Forward the Slot's composite context to the Fill via fillProps, so that
294+
// Composite components rendered inside the Fill can work as expected.
295+
const fillProps = useMemo(
296+
() => ( {
297+
forwardedContext: [
298+
[ Composite.Context.Provider, { value: compositeContext } ],
299+
],
300+
} ),
301+
[ compositeContext ]
302+
);
303+
304+
return (
305+
<ExampleSlotFill.Slot
306+
fillProps={ fillProps }
307+
bubblesVirtually
308+
style={ { display: 'contents' } }
309+
/>
310+
);
311+
};
312+
313+
const Fill = ( { children } ) => {
314+
const innerMarkup = <>{ children }</>;
315+
316+
return (
317+
<ExampleSlotFill.Fill>
318+
{ ( fillProps ) => {
319+
const { forwardedContext = [] } = fillProps;
320+
321+
// Render all context providers forwarded by the Slot via fillProps.
322+
return forwardedContext.reduce(
323+
( inner, [ Provider, props ] ) => (
324+
<Provider { ...props }>{ inner }</Provider>
325+
),
326+
innerMarkup
327+
);
328+
} }
329+
</ExampleSlotFill.Fill>
330+
);
331+
};
332+
333+
// In a separate component:
334+
335+
${ transform( code, storyContext ) }`;
336+
},
337+
},
338+
},
339+
};

0 commit comments

Comments
 (0)