From 325e2444194e743ad110e71275a7a91c9cce064d Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 20 Dec 2024 17:09:03 +0100 Subject: [PATCH 1/5] Menu: migrate Storybook examples to CSF3 --- .../src/menu/stories/index.story.tsx | 952 +++++++++--------- 1 file changed, 496 insertions(+), 456 deletions(-) diff --git a/packages/components/src/menu/stories/index.story.tsx b/packages/components/src/menu/stories/index.story.tsx index dcd890370a1e0a..384fd676933487 100644 --- a/packages/components/src/menu/stories/index.story.tsx +++ b/packages/components/src/menu/stories/index.story.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import type { Meta, StoryFn } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react'; import { css } from '@emotion/react'; /** @@ -20,9 +20,8 @@ import Button from '../../button'; import Modal from '../../modal'; import { createSlotFill, Provider as SlotFillProvider } from '../../slot-fill'; import { ContextSystemProvider } from '../../context'; -import type { MenuProps } from '../types'; -const meta: Meta< typeof Menu > = { +const meta = { id: 'components-experimental-menu', title: 'Components (Experimental)/Actions/Menu', component: Menu, @@ -64,300 +63,337 @@ const meta: Meta< typeof Menu > = { source: { excludeDecorators: true }, }, }, -}; +} satisfies Meta< typeof Menu >; export default meta; -export const Default: StoryFn< typeof Menu > = ( props: MenuProps ) => ( - - } - > - Open menu - - - - Label - - - Label - Help text - - - Label - - The menu item help text is automatically truncated when - there are more than two lines of text - - - - Label - - This item doesn't close the menu on click - - - Disabled item - - - Group label - }> - With prefix - - With suffix - } - suffix="⌥⌘T" - > - - Disabled with prefix and suffix - - And help text - - - - -); -Default.args = {}; +type Story = StoryObj< typeof meta >; -export const WithSubmenu: StoryFn< typeof Menu > = ( props: MenuProps ) => ( - - } - > - Open menu - - - Level 1 item - - - - Submenu trigger item with a long label - - +export const Default: Story = { + args: { + children: ( + <> + + } + > + Open menu + - Level 2 item + Label + + + Label + Help text - Level 2 item + Label + + The menu item help text is automatically truncated + when there are more than two lines of text + + + + Label + + This item doesn't close the menu on click + + Disabled item + + + Group label + } + > + With prefix + + With suffix + + } + suffix="⌥⌘T" + > + + Disabled with prefix and suffix + + And help text + + + + + ), + }, +}; + +export const WithSubmenu: Story = { + args: { + children: ( + <> + + } + > + Open menu + + + Level 1 item - - Submenu trigger + + + Submenu trigger item with a long label + - Level 3 item + Level 2 item - Level 3 item + Level 2 item + + + + Submenu trigger + + + + + + Level 3 item + + + + + Level 3 item + + + + - - - -); -WithSubmenu.args = { - ...Default.args, + + ), + }, }; -export const WithCheckboxes: StoryFn< typeof Menu > = ( props: MenuProps ) => { - const [ isAChecked, setAChecked ] = useState( false ); - const [ isBChecked, setBChecked ] = useState( true ); - const [ multipleCheckboxesValue, setMultipleCheckboxesValue ] = useState< - string[] - >( [ 'b' ] ); +export const WithCheckboxes: Story = { + render: function WithCheckboxes( props ) { + const [ isAChecked, setAChecked ] = useState( false ); + const [ isBChecked, setBChecked ] = useState( true ); + const [ multipleCheckboxesValue, setMultipleCheckboxesValue ] = + useState< string[] >( [ 'b' ] ); - const onMultipleCheckboxesCheckedChange: React.ComponentProps< - typeof Menu.CheckboxItem - >[ 'onChange' ] = ( e ) => { - setMultipleCheckboxesValue( ( prevValues ) => { - if ( prevValues.includes( e.target.value ) ) { - return prevValues.filter( ( val ) => val !== e.target.value ); - } - return [ ...prevValues, e.target.value ]; - } ); - }; + const onMultipleCheckboxesCheckedChange: React.ComponentProps< + typeof Menu.CheckboxItem + >[ 'onChange' ] = ( e ) => { + setMultipleCheckboxesValue( ( prevValues ) => { + if ( prevValues.includes( e.target.value ) ) { + return prevValues.filter( + ( val ) => val !== e.target.value + ); + } + return [ ...prevValues, e.target.value ]; + } ); + }; - return ( - - } - > - Open menu - - - - - Single selection, uncontrolled - - - Checkbox item A - - Initially unchecked - - - - Checkbox item B - Initially checked - - - - - - Single selection, controlled - - { - setAChecked( e.target.checked ); - } } - > - Checkbox item A - - Initially unchecked - - - setBChecked( e.target.checked ) } - > - Checkbox item B - Initially checked - - - - - - Multiple selection, uncontrolled - - - Checkbox item A - - Initially unchecked - - - - Checkbox item B - Initially checked - - - - - - Multiple selection, controlled - - - Checkbox item A - - Initially unchecked - - - - Checkbox item B - Initially checked - - - - - ); -}; -WithCheckboxes.args = { - ...Default.args, + return ( + + + } + > + Open menu + + + + + Single selection, uncontrolled + + + Checkbox item A + + Initially unchecked + + + + Checkbox item B + + Initially checked + + + + + + + Single selection, controlled + + { + setAChecked( e.target.checked ); + } } + > + Checkbox item A + + Initially unchecked + + + + setBChecked( e.target.checked ) + } + > + Checkbox item B + + Initially checked + + + + + + + Multiple selection, uncontrolled + + + Checkbox item A + + Initially unchecked + + + + Checkbox item B + + Initially checked + + + + + + + Multiple selection, controlled + + + Checkbox item A + + Initially unchecked + + + + Checkbox item B + + Initially checked + + + + + + ); + }, }; -export const WithRadios: StoryFn< typeof Menu > = ( props: MenuProps ) => { - const [ radioValue, setRadioValue ] = useState( 'two' ); - const onRadioChange: React.ComponentProps< - typeof Menu.RadioItem - >[ 'onChange' ] = ( e ) => setRadioValue( e.target.value ); +export const WithRadios: Story = { + render: function WithRadios( props ) { + const [ radioValue, setRadioValue ] = useState( 'two' ); + const onRadioChange: React.ComponentProps< + typeof Menu.RadioItem + >[ 'onChange' ] = ( e ) => setRadioValue( e.target.value ); - return ( - - } - > - Open menu - - - - Uncontrolled - - Radio item 1 - - Initially unchecked - - - - Radio item 2 - Initially checked - - - - - Controlled - - Radio item 1 - - Initially unchecked - - - - Radio item 2 - Initially checked - - - - - ); -}; -WithRadios.args = { - ...Default.args, + return ( + + + } + > + Open menu + + + + Uncontrolled + + Radio item 1 + + Initially unchecked + + + + Radio item 2 + + Initially checked + + + + + + Controlled + + Radio item 1 + + Initially unchecked + + + + Radio item 2 + + Initially checked + + + + + + ); + }, }; const modalOnTopOfMenuPopover = css` @@ -367,67 +403,68 @@ const modalOnTopOfMenuPopover = css` `; // For more examples with `Modal`, check https://ariakit.org/examples/menu-wordpress-modal -export const WithModals: StoryFn< typeof Menu > = ( props: MenuProps ) => { - const [ isOuterModalOpen, setOuterModalOpen ] = useState( false ); - const [ isInnerModalOpen, setInnerModalOpen ] = useState( false ); +export const WithModals: Story = { + render: function WithModals( props ) { + const [ isOuterModalOpen, setOuterModalOpen ] = useState( false ); + const [ isInnerModalOpen, setInnerModalOpen ] = useState( false ); - const cx = useCx(); - const modalOverlayClassName = cx( modalOnTopOfMenuPopover ); + const cx = useCx(); + const modalOverlayClassName = cx( modalOnTopOfMenuPopover ); - return ( - <> - - - } - > - Open menu - - - setOuterModalOpen( true ) } - hideOnClick={ false } - > - Open outer modal - - setInnerModalOpen( true ) } - hideOnClick={ false } + return ( + <> + + + } > - Open inner modal - - { isInnerModalOpen && ( - setInnerModalOpen( false ) } - overlayClassName={ modalOverlayClassName } + Open menu + + + setOuterModalOpen( true ) } + hideOnClick={ false } + > + Open outer modal + + setInnerModalOpen( true ) } + hideOnClick={ false } > - Modal's contents - - - ) } - - - { isOuterModalOpen && ( - setOuterModalOpen( false ) } - overlayClassName={ modalOverlayClassName } - > - Modal's contents - - - ) } - - ); -}; -WithModals.args = { - ...Default.args, + Modal's contents + + + ) } + + + { isOuterModalOpen && ( + setOuterModalOpen( false ) } + overlayClassName={ modalOverlayClassName } + > + Modal's contents + + + ) } + + ); + }, }; const ExampleSlotFill = createSlotFill( 'Example' ); @@ -478,47 +515,46 @@ const Fill = ( { children }: { children: React.ReactNode } ) => { ); }; -export const WithSlotFill: StoryFn< typeof Menu > = ( props: MenuProps ) => { - return ( - - - - } - > - Open menu - - - - Item - - - - - - - - Item from fill - - - - Submenu from fill - +export const WithSlotFill: Story = { + render: ( props ) => { + return ( + + + + } + > + Open menu + - - Submenu item from fill - + Item + - - - ); -}; -WithSlotFill.args = { - ...Default.args, + + + + Item from fill + + + + Submenu from fill + + + + + Submenu item from fill + + + + + + + ); + }, }; const toolbarVariantContextValue = { @@ -526,105 +562,109 @@ const toolbarVariantContextValue = { variant: 'toolbar', }, }; -export const ToolbarVariant: StoryFn< typeof Menu > = ( props: MenuProps ) => ( - // TODO: add toolbar - - - } - > - Open menu - - - - Level 1 item - - - Level 1 item - - - - - Submenu trigger - - - - Level 2 item - - - - - - -); -ToolbarVariant.args = { - ...Default.args, -}; - -export const InsideModal: StoryFn< typeof Menu > = ( props: MenuProps ) => { - const [ isModalOpen, setModalOpen ] = useState( false ); - return ( - <> - - { isModalOpen && ( - setModalOpen( false ) } - title="Menu inside modal" +export const ToolbarVariant: Story = { + render: ( props ) => ( + // TODO: add toolbar + + + + } > - - - } - > - Open menu - + Open menu + + + + Level 1 item + + + Level 1 item + + + + + Submenu trigger + - Level 1 item - - - Level 1 item + Level 2 item - - - + + + + + + ), +}; + +export const InsideModal: Story = { + render: function InsideModal( props ) { + const [ isModalOpen, setModalOpen ] = useState( false ); + return ( + <> + + { isModalOpen && ( + setModalOpen( false ) } + title="Menu inside modal" + > + + + } + > + Open menu + + + - Submenu trigger + Level 1 item - - - + + + + Level 1 item + + + + + - Level 2 item + Submenu trigger - - - - - - - - ) } - - ); -}; -InsideModal.args = { - ...Default.args, -}; -InsideModal.parameters = { - docs: { - source: { type: 'code' }, + + + + + Level 2 item + + + + + + + + + ) } + + ); + }, + parameters: { + docs: { + source: { type: 'code' }, + }, }, }; From 8df6efee994af5e328d8684d9ba623bf248cd125 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 20 Dec 2024 17:26:18 +0100 Subject: [PATCH 2/5] Add explicit menu props for better TypeScript perf --- packages/components/src/menu/stories/index.story.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/components/src/menu/stories/index.story.tsx b/packages/components/src/menu/stories/index.story.tsx index 384fd676933487..7796b41c3003fe 100644 --- a/packages/components/src/menu/stories/index.story.tsx +++ b/packages/components/src/menu/stories/index.story.tsx @@ -20,6 +20,7 @@ import Button from '../../button'; import Modal from '../../modal'; import { createSlotFill, Provider as SlotFillProvider } from '../../slot-fill'; import { ContextSystemProvider } from '../../context'; +import type { MenuProps } from '../types'; const meta = { id: 'components-experimental-menu', @@ -183,7 +184,7 @@ export const WithSubmenu: Story = { }; export const WithCheckboxes: Story = { - render: function WithCheckboxes( props ) { + render: function WithCheckboxes( props: MenuProps ) { const [ isAChecked, setAChecked ] = useState( false ); const [ isBChecked, setBChecked ] = useState( true ); const [ multipleCheckboxesValue, setMultipleCheckboxesValue ] = @@ -329,7 +330,7 @@ export const WithCheckboxes: Story = { }; export const WithRadios: Story = { - render: function WithRadios( props ) { + render: function WithRadios( props: MenuProps ) { const [ radioValue, setRadioValue ] = useState( 'two' ); const onRadioChange: React.ComponentProps< typeof Menu.RadioItem @@ -404,7 +405,7 @@ const modalOnTopOfMenuPopover = css` // For more examples with `Modal`, check https://ariakit.org/examples/menu-wordpress-modal export const WithModals: Story = { - render: function WithModals( props ) { + render: function WithModals( props: MenuProps ) { const [ isOuterModalOpen, setOuterModalOpen ] = useState( false ); const [ isInnerModalOpen, setInnerModalOpen ] = useState( false ); From 7d4921b5a9acd73936df9c34dcb708c930d93e69 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 20 Dec 2024 17:45:56 +0100 Subject: [PATCH 3/5] Try explicit menu props again --- packages/components/src/menu/stories/index.story.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/menu/stories/index.story.tsx b/packages/components/src/menu/stories/index.story.tsx index 7796b41c3003fe..2d0864a36505c7 100644 --- a/packages/components/src/menu/stories/index.story.tsx +++ b/packages/components/src/menu/stories/index.story.tsx @@ -67,7 +67,7 @@ const meta = { } satisfies Meta< typeof Menu >; export default meta; -type Story = StoryObj< typeof meta >; +type Story = StoryObj< MenuProps >; export const Default: Story = { args: { From fd4b11860e1251c2a03a32f95871922c05f60c14 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sun, 22 Dec 2024 15:10:11 +0100 Subject: [PATCH 4/5] Apply cfs2 to vsf3 migration tool, keep named functions --- .../src/menu/stories/index.story.tsx | 259 ++++++++++-------- 1 file changed, 139 insertions(+), 120 deletions(-) diff --git a/packages/components/src/menu/stories/index.story.tsx b/packages/components/src/menu/stories/index.story.tsx index 2d0864a36505c7..5fb2f34d9122da 100644 --- a/packages/components/src/menu/stories/index.story.tsx +++ b/packages/components/src/menu/stories/index.story.tsx @@ -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: ( - <> - - } - > - Open menu - - - - Label - - - Label - Help text - - - Label - - The menu item help text is automatically truncated - when there are more than two lines of text - +export const Default: StoryObj< typeof Menu > = { + render: ( props: MenuProps ) => ( + + } + > + Open menu + + + + Label + + + Label + Help text + + + Label + + The menu item help text is automatically truncated when + there are more than two lines of text + + + + Label + + This item doesn't close the menu on click + + + Disabled item + + + Group label + } + > + With prefix - - Label - - This item doesn't close the menu on click - + With suffix + + } + suffix="⌥⌘T" + > + + Disabled with prefix and suffix + + And help text - Disabled item - - - Group label - } - > - With prefix - - With suffix - - } - suffix="⌥⌘T" - > - - Disabled with prefix and suffix - - And help text - - - - - ), - }, + + + + ), + + args: {}, }; -export const WithSubmenu: Story = { - args: { - children: ( - <> - - } - > - Open menu - - - Level 1 item - - - - Submenu trigger item with a long label - - - - - Level 2 item - - - Level 2 item - - - +export const WithSubmenu: StoryObj< typeof Menu > = { + render: ( props: MenuProps ) => ( + + } + > + Open menu + + + Level 1 item + + + + Submenu trigger item with a long label + + + + + Level 2 item + + + Level 2 item + + + + Submenu trigger + + + - Submenu trigger + Level 3 item - - - - - Level 3 item - - - - - Level 3 item - - - - - - - - - ), + + + + Level 3 item + + + + + + + + + ), + + args: { + ...Default.args, }, }; -export const WithCheckboxes: Story = { +export const WithCheckboxes: StoryObj< typeof Menu > = { render: function WithCheckboxes( props: MenuProps ) { const [ isAChecked, setAChecked ] = useState( false ); const [ isBChecked, setBChecked ] = useState( true ); @@ -327,9 +321,13 @@ export const WithCheckboxes: Story = { ); }, + + 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 = { ); }, + + 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 ( @@ -556,6 +561,10 @@ export const WithSlotFill: Story = { ); }, + + args: { + ...Default.args, + }, }; const toolbarVariantContextValue = { @@ -563,8 +572,9 @@ const toolbarVariantContextValue = { variant: 'toolbar', }, }; -export const ToolbarVariant: Story = { - render: ( props ) => ( + +export const ToolbarVariant: StoryObj< typeof Menu > = { + render: ( props: MenuProps ) => ( // TODO: add toolbar @@ -597,10 +607,14 @@ export const ToolbarVariant: Story = { ), + + 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' }, From 980c20e1cd38a906f8e8021b2ae3ed850d98f0f1 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sun, 22 Dec 2024 15:18:54 +0100 Subject: [PATCH 5/5] Use `args.children` when possible --- .../src/menu/stories/index.story.tsx | 203 +++++++++--------- 1 file changed, 104 insertions(+), 99 deletions(-) diff --git a/packages/components/src/menu/stories/index.story.tsx b/packages/components/src/menu/stories/index.story.tsx index 5fb2f34d9122da..37ebb6f905dc84 100644 --- a/packages/components/src/menu/stories/index.story.tsx +++ b/packages/components/src/menu/stories/index.story.tsx @@ -68,112 +68,117 @@ const meta: Meta< typeof Menu > = { export default meta; export const Default: StoryObj< typeof Menu > = { - render: ( props: MenuProps ) => ( - - } - > - Open menu - - - - Label - - - Label - Help text - - - Label - - The menu item help text is automatically truncated when - there are more than two lines of text - - - - Label - - This item doesn't close the menu on click - - - Disabled item - - - Group label - } - > - With prefix + args: { + children: ( + <> + + } + > + Open menu + + + + Label - With suffix - - } - suffix="⌥⌘T" - > - - Disabled with prefix and suffix - - And help text + + Label + Help text - - - - ), - - args: {}, -}; - -export const WithSubmenu: StoryObj< typeof Menu > = { - render: ( props: MenuProps ) => ( - - } - > - Open menu - - - Level 1 item - - - - Submenu trigger item with a long label - - - - - Level 2 item + + Label + + The menu item help text is automatically truncated + when there are more than two lines of text + + + + Label + + This item doesn't close the menu on click + + + Disabled item + + + Group label + } + > + With prefix - - Level 2 item + With suffix + + } + suffix="⌥⌘T" + > + + Disabled with prefix and suffix + + And help text - - - Submenu trigger - - - - - Level 3 item - - - - - Level 3 item - - - - - - - - - ), + + + + ), + }, +}; +export const WithSubmenu: StoryObj< typeof Menu > = { args: { ...Default.args, + children: ( + <> + + } + > + Open menu + + + Level 1 item + + + + Submenu trigger item with a long label + + + + + Level 2 item + + + Level 2 item + + + + + Submenu trigger + + + + + + Level 3 item + + + + + Level 3 item + + + + + + + + + ), }, };