-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Menu: migrate Storybook examples to CSF3 #68204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
325e244
8df6efe
7d4921b
fd4b118
980c20e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,7 +1,7 @@ | ||||||
| /** | ||||||
| * External dependencies | ||||||
| */ | ||||||
| import type { Meta, StoryObj } from '@storybook/react'; | ||||||
| import type { StoryObj, Meta } from '@storybook/react'; | ||||||
| import { css } from '@emotion/react'; | ||||||
|
|
||||||
| /** | ||||||
|
|
@@ -22,7 +22,7 @@ import { createSlotFill, Provider as SlotFillProvider } from '../../slot-fill'; | |||||
| import { ContextSystemProvider } from '../../context'; | ||||||
| import type { MenuProps } from '../types'; | ||||||
|
|
||||||
| const meta = { | ||||||
| const meta: Meta< typeof Menu > = { | ||||||
| id: 'components-experimental-menu', | ||||||
| title: 'Components (Experimental)/Actions/Menu', | ||||||
| component: Menu, | ||||||
|
|
@@ -64,126 +64,120 @@ const meta = { | |||||
| source: { excludeDecorators: true }, | ||||||
| }, | ||||||
| }, | ||||||
| } satisfies Meta< typeof Menu >; | ||||||
| }; | ||||||
| export default meta; | ||||||
|
|
||||||
| type Story = StoryObj< MenuProps >; | ||||||
|
|
||||||
| export const Default: Story = { | ||||||
| args: { | ||||||
| children: ( | ||||||
| <> | ||||||
| <Menu.TriggerButton | ||||||
| render={ | ||||||
| <Button __next40pxDefaultSize variant="secondary" /> | ||||||
| } | ||||||
| > | ||||||
| Open menu | ||||||
| </Menu.TriggerButton> | ||||||
| <Menu.Popover> | ||||||
| <Menu.Item> | ||||||
| <Menu.ItemLabel>Label</Menu.ItemLabel> | ||||||
| </Menu.Item> | ||||||
| <Menu.Item> | ||||||
| <Menu.ItemLabel>Label</Menu.ItemLabel> | ||||||
| <Menu.ItemHelpText>Help text</Menu.ItemHelpText> | ||||||
| </Menu.Item> | ||||||
| <Menu.Item> | ||||||
| <Menu.ItemLabel>Label</Menu.ItemLabel> | ||||||
| <Menu.ItemHelpText> | ||||||
| The menu item help text is automatically truncated | ||||||
| when there are more than two lines of text | ||||||
| </Menu.ItemHelpText> | ||||||
| export const Default: StoryObj< typeof Menu > = { | ||||||
| render: ( props: MenuProps ) => ( | ||||||
| <Menu { ...props }> | ||||||
| <Menu.TriggerButton | ||||||
| render={ <Button __next40pxDefaultSize variant="secondary" /> } | ||||||
| > | ||||||
| Open menu | ||||||
| </Menu.TriggerButton> | ||||||
| <Menu.Popover> | ||||||
| <Menu.Item> | ||||||
| <Menu.ItemLabel>Label</Menu.ItemLabel> | ||||||
| </Menu.Item> | ||||||
| <Menu.Item> | ||||||
| <Menu.ItemLabel>Label</Menu.ItemLabel> | ||||||
| <Menu.ItemHelpText>Help text</Menu.ItemHelpText> | ||||||
| </Menu.Item> | ||||||
| <Menu.Item> | ||||||
| <Menu.ItemLabel>Label</Menu.ItemLabel> | ||||||
| <Menu.ItemHelpText> | ||||||
| The menu item help text is automatically truncated when | ||||||
| there are more than two lines of text | ||||||
| </Menu.ItemHelpText> | ||||||
| </Menu.Item> | ||||||
| <Menu.Item hideOnClick={ false }> | ||||||
| <Menu.ItemLabel>Label</Menu.ItemLabel> | ||||||
| <Menu.ItemHelpText> | ||||||
| This item doesn't close the menu on click | ||||||
| </Menu.ItemHelpText> | ||||||
| </Menu.Item> | ||||||
| <Menu.Item disabled>Disabled item</Menu.Item> | ||||||
| <Menu.Separator /> | ||||||
| <Menu.Group> | ||||||
| <Menu.GroupLabel>Group label</Menu.GroupLabel> | ||||||
| <Menu.Item | ||||||
| prefix={ <Icon icon={ customLink } size={ 24 } /> } | ||||||
| > | ||||||
| <Menu.ItemLabel>With prefix</Menu.ItemLabel> | ||||||
| </Menu.Item> | ||||||
| <Menu.Item hideOnClick={ false }> | ||||||
| <Menu.ItemLabel>Label</Menu.ItemLabel> | ||||||
| <Menu.ItemHelpText> | ||||||
| This item doesn't close the menu on click | ||||||
| </Menu.ItemHelpText> | ||||||
| <Menu.Item suffix="⌘S">With suffix</Menu.Item> | ||||||
| <Menu.Item | ||||||
| disabled | ||||||
| prefix={ | ||||||
| <Icon icon={ formatCapitalize } size={ 24 } /> | ||||||
| } | ||||||
| suffix="⌥⌘T" | ||||||
| > | ||||||
| <Menu.ItemLabel> | ||||||
| Disabled with prefix and suffix | ||||||
| </Menu.ItemLabel> | ||||||
| <Menu.ItemHelpText>And help text</Menu.ItemHelpText> | ||||||
| </Menu.Item> | ||||||
| <Menu.Item disabled>Disabled item</Menu.Item> | ||||||
| <Menu.Separator /> | ||||||
| <Menu.Group> | ||||||
| <Menu.GroupLabel>Group label</Menu.GroupLabel> | ||||||
| <Menu.Item | ||||||
| prefix={ <Icon icon={ customLink } size={ 24 } /> } | ||||||
| > | ||||||
| <Menu.ItemLabel>With prefix</Menu.ItemLabel> | ||||||
| </Menu.Item> | ||||||
| <Menu.Item suffix="⌘S">With suffix</Menu.Item> | ||||||
| <Menu.Item | ||||||
| disabled | ||||||
| prefix={ | ||||||
| <Icon icon={ formatCapitalize } size={ 24 } /> | ||||||
| } | ||||||
| suffix="⌥⌘T" | ||||||
| > | ||||||
| <Menu.ItemLabel> | ||||||
| Disabled with prefix and suffix | ||||||
| </Menu.ItemLabel> | ||||||
| <Menu.ItemHelpText>And help text</Menu.ItemHelpText> | ||||||
| </Menu.Item> | ||||||
| </Menu.Group> | ||||||
| </Menu.Popover> | ||||||
| </> | ||||||
| ), | ||||||
| }, | ||||||
| </Menu.Group> | ||||||
| </Menu.Popover> | ||||||
| </Menu> | ||||||
| ), | ||||||
|
|
||||||
| args: {}, | ||||||
| }; | ||||||
|
|
||||||
| export const WithSubmenu: Story = { | ||||||
| args: { | ||||||
| children: ( | ||||||
| <> | ||||||
| <Menu.TriggerButton | ||||||
| render={ | ||||||
| <Button __next40pxDefaultSize variant="secondary" /> | ||||||
| } | ||||||
| > | ||||||
| Open menu | ||||||
| </Menu.TriggerButton> | ||||||
| <Menu.Popover> | ||||||
| <Menu.Item>Level 1 item</Menu.Item> | ||||||
| <Menu> | ||||||
| <Menu.SubmenuTriggerItem suffix="Suffix"> | ||||||
| <Menu.ItemLabel> | ||||||
| Submenu trigger item with a long label | ||||||
| </Menu.ItemLabel> | ||||||
| </Menu.SubmenuTriggerItem> | ||||||
| <Menu.Popover> | ||||||
| <Menu.Item> | ||||||
| <Menu.ItemLabel>Level 2 item</Menu.ItemLabel> | ||||||
| </Menu.Item> | ||||||
| <Menu.Item> | ||||||
| <Menu.ItemLabel>Level 2 item</Menu.ItemLabel> | ||||||
| </Menu.Item> | ||||||
| <Menu> | ||||||
| <Menu.SubmenuTriggerItem> | ||||||
| export const WithSubmenu: StoryObj< typeof Menu > = { | ||||||
| render: ( props: MenuProps ) => ( | ||||||
| <Menu { ...props }> | ||||||
| <Menu.TriggerButton | ||||||
| render={ <Button __next40pxDefaultSize variant="secondary" /> } | ||||||
| > | ||||||
| Open menu | ||||||
| </Menu.TriggerButton> | ||||||
| <Menu.Popover> | ||||||
| <Menu.Item>Level 1 item</Menu.Item> | ||||||
| <Menu> | ||||||
| <Menu.SubmenuTriggerItem suffix="Suffix"> | ||||||
| <Menu.ItemLabel> | ||||||
| Submenu trigger item with a long label | ||||||
| </Menu.ItemLabel> | ||||||
| </Menu.SubmenuTriggerItem> | ||||||
| <Menu.Popover> | ||||||
| <Menu.Item> | ||||||
| <Menu.ItemLabel>Level 2 item</Menu.ItemLabel> | ||||||
| </Menu.Item> | ||||||
| <Menu.Item> | ||||||
| <Menu.ItemLabel>Level 2 item</Menu.ItemLabel> | ||||||
| </Menu.Item> | ||||||
| <Menu> | ||||||
| <Menu.SubmenuTriggerItem> | ||||||
| <Menu.ItemLabel>Submenu trigger</Menu.ItemLabel> | ||||||
| </Menu.SubmenuTriggerItem> | ||||||
| <Menu.Popover> | ||||||
| <Menu.Item> | ||||||
| <Menu.ItemLabel> | ||||||
| Submenu trigger | ||||||
| Level 3 item | ||||||
| </Menu.ItemLabel> | ||||||
| </Menu.SubmenuTriggerItem> | ||||||
| <Menu.Popover> | ||||||
| <Menu.Item> | ||||||
| <Menu.ItemLabel> | ||||||
| Level 3 item | ||||||
| </Menu.ItemLabel> | ||||||
| </Menu.Item> | ||||||
| <Menu.Item> | ||||||
| <Menu.ItemLabel> | ||||||
| Level 3 item | ||||||
| </Menu.ItemLabel> | ||||||
| </Menu.Item> | ||||||
| </Menu.Popover> | ||||||
| </Menu> | ||||||
| </Menu.Popover> | ||||||
| </Menu> | ||||||
| </Menu.Popover> | ||||||
| </> | ||||||
| ), | ||||||
| </Menu.Item> | ||||||
| <Menu.Item> | ||||||
| <Menu.ItemLabel> | ||||||
| Level 3 item | ||||||
| </Menu.ItemLabel> | ||||||
| </Menu.Item> | ||||||
| </Menu.Popover> | ||||||
| </Menu> | ||||||
| </Menu.Popover> | ||||||
| </Menu> | ||||||
| </Menu.Popover> | ||||||
| </Menu> | ||||||
| ), | ||||||
|
|
||||||
| args: { | ||||||
| ...Default.args, | ||||||
| }, | ||||||
| }; | ||||||
|
|
||||||
| export const WithCheckboxes: Story = { | ||||||
| export const WithCheckboxes: StoryObj< typeof Menu > = { | ||||||
| render: function WithCheckboxes( props: MenuProps ) { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to provide a named function here? Can't we just do:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I used named functions to prevent warnings about using hooks outside of a component.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. The rules of hooks are broken because React doesn't know how to statically treat hooks inside a dynamically assigned function component like this - it just can't possibly guarantee if To me that's just another reason not to use this Alternatively, we could move the component declaration outside of I'm leaving this to your judgment, as I don't have particularly strong feelings.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My understanding was that the hooks linter simply can't know that What we're doing for // this is fine
Menu.Submenu = () => { /* something with hooks */ };
// this is not fine because lowercase
Menu.submenu = () => { /* something with hooks */ };
// would've been fine if the `render` field had been uppercase like this 😅
Default.Render = () => { /* something with hooks */ };So while I can't say that this isn't a "hack", I don't think it's hacky in a way that's unsafe?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've elaborated on it here, and it's definitely not unsafe, and we can move forward with it for Storybook.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These parameter types shouldn't be necessary, since that is already handled through the
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did that initially, but then I added back the explicit props because it seems to improve TypeScript performance (TS doesn't need to compute the prop types since they are explicitly declared)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We ran some TS profiling here, and it turned out to make a big difference. Results were similar to here: As I discussed with @ciampo over DM earlier, I think this has something to do with how Storybook infers component props for stories when working with compound components, and maybe the inference is not optimized to work with subcomponents and does extra cycles, resulting in infinite loop. It's worth debugging with a dev Storybook setup and an isolated story test case, IMO.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Huh, that's unfortunate! One ideological issue I have with
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's try it out and measure it, but I think it should be fine, as long as we don't let Storybook infer the prop types.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about We can test it out in a separate PR, if needed |
||||||
| const [ isAChecked, setAChecked ] = useState( false ); | ||||||
| const [ isBChecked, setBChecked ] = useState( true ); | ||||||
|
|
@@ -327,9 +321,13 @@ export const WithCheckboxes: Story = { | |||||
| </Menu> | ||||||
| ); | ||||||
| }, | ||||||
|
|
||||||
| args: { | ||||||
| ...Default.args, | ||||||
| }, | ||||||
| }; | ||||||
|
|
||||||
| export const WithRadios: Story = { | ||||||
| export const WithRadios: StoryObj< typeof Menu > = { | ||||||
| render: function WithRadios( props: MenuProps ) { | ||||||
| const [ radioValue, setRadioValue ] = useState( 'two' ); | ||||||
| const onRadioChange: React.ComponentProps< | ||||||
|
|
@@ -395,6 +393,10 @@ export const WithRadios: Story = { | |||||
| </Menu> | ||||||
| ); | ||||||
| }, | ||||||
|
|
||||||
| args: { | ||||||
| ...Default.args, | ||||||
| }, | ||||||
| }; | ||||||
|
|
||||||
| const modalOnTopOfMenuPopover = css` | ||||||
|
|
@@ -403,8 +405,7 @@ const modalOnTopOfMenuPopover = css` | |||||
| } | ||||||
| `; | ||||||
|
|
||||||
| // For more examples with `Modal`, check https://ariakit.org/examples/menu-wordpress-modal | ||||||
| export const WithModals: Story = { | ||||||
| export const WithModals: StoryObj< typeof Menu > = { | ||||||
| render: function WithModals( props: MenuProps ) { | ||||||
| const [ isOuterModalOpen, setOuterModalOpen ] = useState( false ); | ||||||
| const [ isInnerModalOpen, setInnerModalOpen ] = useState( false ); | ||||||
|
|
@@ -466,6 +467,10 @@ export const WithModals: Story = { | |||||
| </> | ||||||
| ); | ||||||
| }, | ||||||
|
|
||||||
| args: { | ||||||
| ...Default.args, | ||||||
| }, | ||||||
| }; | ||||||
|
|
||||||
| const ExampleSlotFill = createSlotFill( 'Example' ); | ||||||
|
|
@@ -516,8 +521,8 @@ const Fill = ( { children }: { children: React.ReactNode } ) => { | |||||
| ); | ||||||
| }; | ||||||
|
|
||||||
| export const WithSlotFill: Story = { | ||||||
| render: ( props ) => { | ||||||
| export const WithSlotFill: StoryObj< typeof Menu > = { | ||||||
| render: ( props: MenuProps ) => { | ||||||
| return ( | ||||||
| <SlotFillProvider> | ||||||
| <Menu { ...props }> | ||||||
|
|
@@ -556,15 +561,20 @@ export const WithSlotFill: Story = { | |||||
| </SlotFillProvider> | ||||||
| ); | ||||||
| }, | ||||||
|
|
||||||
| args: { | ||||||
| ...Default.args, | ||||||
| }, | ||||||
| }; | ||||||
|
|
||||||
| const toolbarVariantContextValue = { | ||||||
| Menu: { | ||||||
| variant: 'toolbar', | ||||||
| }, | ||||||
| }; | ||||||
| export const ToolbarVariant: Story = { | ||||||
| render: ( props ) => ( | ||||||
|
|
||||||
| export const ToolbarVariant: StoryObj< typeof Menu > = { | ||||||
| render: ( props: MenuProps ) => ( | ||||||
| // TODO: add toolbar | ||||||
| <ContextSystemProvider value={ toolbarVariantContextValue }> | ||||||
| <Menu { ...props }> | ||||||
|
|
@@ -597,10 +607,14 @@ export const ToolbarVariant: Story = { | |||||
| </Menu> | ||||||
| </ContextSystemProvider> | ||||||
| ), | ||||||
|
|
||||||
| args: { | ||||||
| ...Default.args, | ||||||
| }, | ||||||
| }; | ||||||
|
|
||||||
| export const InsideModal: Story = { | ||||||
| render: function InsideModal( props ) { | ||||||
| export const InsideModal: StoryObj< typeof Menu > = { | ||||||
| render: function InsideModal( props: MenuProps ) { | ||||||
| const [ isModalOpen, setModalOpen ] = useState( false ); | ||||||
| return ( | ||||||
| <> | ||||||
|
|
@@ -663,6 +677,11 @@ export const InsideModal: Story = { | |||||
| </> | ||||||
| ); | ||||||
| }, | ||||||
|
|
||||||
| args: { | ||||||
| ...Default.args, | ||||||
| }, | ||||||
|
|
||||||
| parameters: { | ||||||
| docs: { | ||||||
| source: { type: 'code' }, | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hiding whitespace changes removes a lot of noise while reviewing