Skip to content

Commit

Permalink
Composite: add context-forwarding with SlotFill example (#65051)
Browse files Browse the repository at this point in the history
* Composite: add context-forwarding with SlotFill example

* Add custom code snippet

* CHANGELOG

---

Co-authored-by: ciampo <[email protected]>
Co-authored-by: tyxla <[email protected]>
  • Loading branch information
3 people authored Sep 4, 2024
1 parent 626c3fa commit abf72c9
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
- `DropdownMenu` v2: add `GroupLabel` subcomponent ([#64854](https://github.com/WordPress/gutenberg/pull/64854)).
- `DropdownMenuV2`: update animation ([#64868](https://github.com/WordPress/gutenberg/pull/64868)).
- `Composite` V2: fix Storybook docgen ([#64682](https://github.com/WordPress/gutenberg/pull/64682)).
- `Composite` V2: add "With Slot Fill" example ([#65051](https://github.com/WordPress/gutenberg/pull/65051)).
- `Composite` V2: accept store props on top-level component ([#64832](https://github.com/WordPress/gutenberg/pull/64832)).

## 28.6.0 (2024-08-21)
Expand Down
135 changes: 134 additions & 1 deletion packages/components/src/composite/stories/index.story.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
/**
* External dependencies
*/
import type { Meta, StoryFn } from '@storybook/react';
import type { Meta, StoryFn, StoryContext } from '@storybook/react';

/**
* WordPress dependencies
*/
import { isRTL } from '@wordpress/i18n';
import { useContext, useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import { createSlotFill, Provider as SlotFillProvider } from '../../slot-fill';
import { Composite } from '..';
import { useCompositeStore } from '../store';
import { UseCompositeStorePlaceholder, transform } from './utils';
Expand Down Expand Up @@ -204,3 +206,134 @@ Typeahead.parameters = {
},
},
};

const ExampleSlotFill = createSlotFill( 'Example' );

const Slot = () => {
const compositeContext = useContext( Composite.Context );

// Forward the Slot's composite context to the Fill via fillProps, so that
// Composite components rendered inside the Fill can work as expected.
const fillProps = useMemo(
() => ( {
forwardedContext: [
[ Composite.Context.Provider, { value: compositeContext } ],
],
} ),
[ compositeContext ]
);

return (
<ExampleSlotFill.Slot
fillProps={ fillProps }
bubblesVirtually
style={ { display: 'contents' } }
/>
);
};

type ForwardedContextTuple< P = {} > = [
React.ComponentType< React.PropsWithChildren< P > >,
P,
];

const Fill = ( { children }: { children: React.ReactNode } ) => {
const innerMarkup = <>{ children }</>;

return (
<ExampleSlotFill.Fill>
{ ( fillProps: { forwardedContext?: ForwardedContextTuple[] } ) => {
const { forwardedContext = [] } = fillProps;

// Render all context providers forwarded by the Slot via fillProps.
return forwardedContext.reduce(
( inner: JSX.Element, [ Provider, props ] ) => (
<Provider { ...props }>{ inner }</Provider>
),
innerMarkup
);
} }
</ExampleSlotFill.Fill>
);
};

export const WithSlotFill: StoryFn< typeof UseCompositeStorePlaceholder > = (
props
) => {
return (
<SlotFillProvider>
<Composite { ...props }>
<Composite.Item>Item one (direct child)</Composite.Item>
<Slot />
<Composite.Item>Item four (direct child)</Composite.Item>
</Composite>

<Fill>
<Composite.Item>Item two (from slot fill)</Composite.Item>
<Composite.Item>Item three (from slot fill)</Composite.Item>
</Fill>
</SlotFillProvider>
);
};
WithSlotFill.args = {
...Default.args,
};
WithSlotFill.parameters = {
docs: {
description: {
story: 'When rendering Composite components across a SlotFill, the Composite.Context should be manually forwarded from the Slot to the Fill component.',
},
source: {
transform: ( code: string, storyContext: StoryContext ) => {
return `const ExampleSlotFill = createSlotFill( 'Example' );
const Slot = () => {
const compositeContext = useContext( Composite.Context );
// Forward the Slot's composite context to the Fill via fillProps, so that
// Composite components rendered inside the Fill can work as expected.
const fillProps = useMemo(
() => ( {
forwardedContext: [
[ Composite.Context.Provider, { value: compositeContext } ],
],
} ),
[ compositeContext ]
);
return (
<ExampleSlotFill.Slot
fillProps={ fillProps }
bubblesVirtually
style={ { display: 'contents' } }
/>
);
};
const Fill = ( { children } ) => {
const innerMarkup = <>{ children }</>;
return (
<ExampleSlotFill.Fill>
{ ( fillProps ) => {
const { forwardedContext = [] } = fillProps;
// Render all context providers forwarded by the Slot via fillProps.
return forwardedContext.reduce(
( inner, [ Provider, props ] ) => (
<Provider { ...props }>{ inner }</Provider>
),
innerMarkup
);
} }
</ExampleSlotFill.Fill>
);
};
// In a separate component:
${ transform( code, storyContext ) }`;
},
},
},
};

0 comments on commit abf72c9

Please sign in to comment.