diff --git a/packages/@react-spectrum/s2/chromatic/Tabs.stories.tsx b/packages/@react-spectrum/s2/chromatic/Tabs.stories.tsx index 4850028a80c..163ae081cc3 100644 --- a/packages/@react-spectrum/s2/chromatic/Tabs.stories.tsx +++ b/packages/@react-spectrum/s2/chromatic/Tabs.stories.tsx @@ -11,13 +11,14 @@ */ import Bell from '../s2wf-icons/S2_Icon_Bell_20_N.svg'; +import {Button, Tab, TabList, TabPanel, Tabs} from '../src'; +import {Collection, Text} from '@react-spectrum/s2'; import Edit from '../s2wf-icons/S2_Icon_Edit_20_N.svg'; import Heart from '../s2wf-icons/S2_Icon_Heart_20_N.svg'; import type {Meta, StoryObj} from '@storybook/react'; import {style} from '../style/spectrum-theme' with { type: 'macro' }; -import {Tab, TabList, TabPanel, Tabs} from '../src/Tabs'; -import {Text} from '@react-spectrum/s2'; import {userEvent} from '@storybook/test'; +import {useState} from 'react'; const meta: Meta = { component: Tabs, @@ -186,3 +187,62 @@ export const Collasped = { await userEvent.keyboard('{Enter}'); } }; + +function AddRemoveExample(props) { + let [tabs, setTabs] = useState([ + {id: 1, title: 'Tab 1', content: 'Tab body 1'}, + {id: 2, title: 'Tab 2', content: 'Tab body 2'}, + {id: 3, title: 'Tab 3', content: 'Tab body 3'}, + {id: 4, title: 'Tab 4', content: 'Tab body 4'}, + {id: 5, title: 'Tab 5', content: 'Tab body 5'}, + {id: 6, title: 'Tab 6', content: 'Tab body 6'}, + {id: 7, title: 'Tab 7', content: 'Tab body 7'}, + {id: 8, title: 'Tab 8', content: 'Tab body 8'}, + {id: 9, title: 'Tab 9', content: 'Tab body 9'} + ]); + + let addTab = () => { + setTabs(tabs => [ + ...tabs, + { + id: tabs.length + 1, + title: `Tab ${tabs.length + 1}`, + content: `Tab body ${tabs.length + 1}` + } + ]); + }; + + let removeTab = () => { + if (tabs.length > 1) { + setTabs(tabs => tabs.slice(0, -1)); + } + }; + + return ( +
+ +
+ + {tab => {tab.title}} + +
+ + +
+
+ + {tab => ( + + {tab.content} + + )} + +
+
+ ); +} + +export const CustomizedLayout = { + render: (args: any) => ( + ) +}; diff --git a/packages/@react-spectrum/s2/src/Tabs.tsx b/packages/@react-spectrum/s2/src/Tabs.tsx index 733013b49a3..992bd15cac4 100644 --- a/packages/@react-spectrum/s2/src/Tabs.tsx +++ b/packages/@react-spectrum/s2/src/Tabs.tsx @@ -82,10 +82,23 @@ const InternalTabsContext = createContext & { prevRef?: RefObject, selectedKey?: Key | null }>({}); -const CollapseContext = createContext({ + +interface CollapseContextType { + showTabs: boolean, + menuId: string, + valueId: string, + ariaLabel?: string | undefined, + ariaDescribedBy?: string | undefined, + tabs: Array>, + listRef?: RefObject, + onSelectionChange?: (key: Key) => void +} + +const CollapseContext = createContext({ showTabs: true, menuId: '', - valueId: '' + valueId: '', + tabs: [] }); const tabs = style({ @@ -198,35 +211,57 @@ const tablist = style({ minWidth: 'min' }); +const tablistWrapper = style({ + position: 'relative', + minWidth: 'min', + flexShrink: 0, + flexGrow: 0 +}, getAllowedOverrides()); + export function TabList(props: TabListProps): ReactNode | null { - let {showTabs} = useContext(CollapseContext) ?? {}; + let {showTabs, menuId, valueId, tabs, listRef, onSelectionChange, ariaLabel, ariaDescribedBy} = useContext(CollapseContext) ?? {}; + let {density, orientation, labelBehavior} = useContext(InternalTabsContext); if (showTabs) { return ; } - return null; + + return ( +
+ {listRef &&
+ +
} + +
+ ); } function TabListInner(props: TabListProps) { let { tablistRef, + orientation, density, labelBehavior, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy } = useContext(InternalTabsContext) ?? {}; + let {tabs, listRef} = useContext(CollapseContext) ?? {}; return (
+ tablistWrapper(null, props.styles)}> + {listRef &&
+ +
} >, onSelectionCha }; let CollapsingTabs = ({collection, containerRef, ...props}: {collection: Collection>, containerRef: any} & TabsProps) => { - let {density = 'regular', orientation = 'horizontal', labelBehavior = 'show', onSelectionChange} = props; + let {orientation = 'horizontal', onSelectionChange} = props; let [showItems, _setShowItems] = useState(true); showItems = orientation === 'vertical' ? true : showItems; let setShowItems = useCallback((value: boolean) => { @@ -683,14 +718,7 @@ let CollapsingTabs = ({collection, containerRef, ...props}: {collection: Collect } else { contents = ( <> - - + {props.children} @@ -699,10 +727,7 @@ let CollapsingTabs = ({collection, containerRef, ...props}: {collection: Collect return (
-
- -
- + {contents}
diff --git a/packages/@react-spectrum/s2/stories/Tabs.stories.tsx b/packages/@react-spectrum/s2/stories/Tabs.stories.tsx index 2e68ba5d3e7..da54f4b77b2 100644 --- a/packages/@react-spectrum/s2/stories/Tabs.stories.tsx +++ b/packages/@react-spectrum/s2/stories/Tabs.stories.tsx @@ -11,14 +11,14 @@ */ import Bell from '../s2wf-icons/S2_Icon_Bell_20_N.svg'; +import {Button, Tab, TabList, TabPanel, Tabs, TabsProps} from '../src'; import {Collection, Text} from '@react-spectrum/s2'; import Edit from '../s2wf-icons/S2_Icon_Edit_20_N.svg'; import {fn} from '@storybook/test'; import Heart from '../s2wf-icons/S2_Icon_Heart_20_N.svg'; import type {Meta, StoryObj} from '@storybook/react'; -import {ReactElement} from 'react'; +import React, {ReactElement} from 'react'; import {style} from '../style' with { type: 'macro' }; -import {Tab, TabList, TabPanel, Tabs, TabsProps} from '../src'; const meta: Meta = { component: Tabs, @@ -148,3 +148,62 @@ export const Dynamic: Story = {
) }; + +function AddRemoveTabsExample(props) { + let [tabs, setTabs] = React.useState([ + {id: 1, title: 'Tab 1', content: 'Tab body 1'}, + {id: 2, title: 'Tab 2', content: 'Tab body 2'}, + {id: 3, title: 'Tab 3', content: 'Tab body 3'}, + {id: 4, title: 'Tab 4', content: 'Tab body 4'}, + {id: 5, title: 'Tab 5', content: 'Tab body 5'}, + {id: 6, title: 'Tab 6', content: 'Tab body 6'}, + {id: 7, title: 'Tab 7', content: 'Tab body 7'}, + {id: 8, title: 'Tab 8', content: 'Tab body 8'}, + {id: 9, title: 'Tab 9', content: 'Tab body 9'} + ]); + + let addTab = () => { + setTabs(tabs => [ + ...tabs, + { + id: tabs.length + 1, + title: `Tab ${tabs.length + 1}`, + content: `Tab body ${tabs.length + 1}` + } + ]); + }; + + let removeTab = () => { + if (tabs.length > 1) { + setTabs(tabs => tabs.slice(0, -1)); + } + }; + + return ( +
+ +
+ + {tab => {tab.title}} + +
+ + +
+
+ + {tab => ( + + {tab.content} + + )} + +
+
+ ); +} + +export const CustomizedLayout: Story = { + render: (args) => , + tags: ['!autodocs'] +};