diff --git a/packages/dev/s2-docs/pages/s2/Tabs.mdx b/packages/dev/s2-docs/pages/s2/Tabs.mdx new file mode 100644 index 00000000000..33a82e34bb8 --- /dev/null +++ b/packages/dev/s2-docs/pages/s2/Tabs.mdx @@ -0,0 +1,294 @@ +import {Layout} from '../../src/Layout'; +export default Layout; +import docs from 'docs:@react-spectrum/s2'; + +# Tabs + +{docs.exports.Tabs.description} +```tsx render docs={docs.exports.Tabs} props={['density', 'isDisabled', 'orientation', 'keyboardActivation']} initialProps={{'aria-label': 'Tabs'}} links={docs.links} expanded type="s2" +import {Tabs, TabList, TabPanel, Tab} from '@react-spectrum/s2'; + + + Explore + Shop + Profile + + + Content for Explore tab. + + + Content for Shop tab. + + + Content for Profile tab. + + +``` + +## Content + +`TabList` follows the **Collection Components API**, accepting both static and dynamic collections. This example shows a dynamic collection, passing a list of objects to the `items` prop, and a function to render the children. +```tsx render +"use client"; +import {ActionButton, Tabs, TabList, Tab, TabPanel, Button, ButtonGroup} from '@react-spectrum/s2'; +import {Collection} from 'react-aria-components'; +import {useState} from 'react'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; +interface Item { + id: number, + title: string, + description: string +} + +function Example() { + ///- begin collapse -/// + let [tabs, setTabs] = useState([ + {id: 1, title: 'Tab 1', description: 'Tab 1'}, + {id: 2, title: 'Tab 2', description: 'Tab 2'}, + {id: 3, title: 'Tab 3', description: 'Tab 3'} + ]); + ///- end collapse -/// + + ///- begin collapse -/// + let addTab = () => { + setTabs(tabs => [ + ...tabs, + { + id: tabs.length + 1, + title: `Tab ${tabs.length + 1}`, + description: `Tab body ${tabs.length + 1}` + } + ]); + }; + ///- end collapse -/// + + ///- begin collapse -/// + let removeTab = () => { + if (tabs.length > 1) { + setTabs(tabs => tabs.slice(0, -1)); + } + }; + ///- end collapse -/// + + return ( +
+ +
+ {/*- begin highlight -*/} + + {item => {item.title}} + + {/*- end highlight -*/} +
+ Add tab + Remove tab +
+
+ {/*- begin highlight -*/} + + {item => {item.description}} + + {/*- end highlight -*/} +
+
+ ) +} +``` +### Links + +Use the `href` prop on a `` to create a link. See the **client side routing guide** to learn how to integrate with your framework. This example uses a simple hash-based router to sync the selected tab to the URL. +```tsx render +"use client"; +import {Tabs, TabList, Tab, TabPanel} from '@react-spectrum/s2'; +import {useSyncExternalStore} from 'react'; + +export default function Example() { + let hash = useSyncExternalStore(subscribe, getHash, getHashServer); + return ( + + + {/*- begin highlight -*/} + Home + {/*- end highlight -*/} + Shared + Deleted + + Home + Shared + Deleted + + ); +} +function getHash() { + return location.hash.startsWith('#/') ? location.hash : '#/'; +} +function getHashServer() { + return '#/'; +} +function subscribe(fn) { + addEventListener('hashchange', fn); + return () => removeEventListener('hashchange', fn); +} +``` +### Icons +Icons can also be used in Tabs in addition to a text label. +```tsx render +"use client" +import {Tabs, TabList, Tab, TabPanel, Text} from '@react-spectrum/s2'; +import Edit from '@react-spectrum/s2/icons/Edit'; +import Bell from '@react-spectrum/s2/icons/Bell'; +import Heart from '@react-spectrum/s2/icons/Heart'; + +function Example() { + return ( +
+ + + {/*- begin highlight -*/} + Edit + Notifications + Likes + {/*- end highlight -*/} + + + Review your edits + + + Check your notifications + + + See your likes + + +
+ ) +} +``` +### Internationalization +To internationalize Tabs, a localized string should be passed as children to the TabList ``. Any text content within the Tab's panel should also be localized accordingly. For languages that are read right-to-left (e.g. Hebrew and Arabic), the layout of Tabs is automatically flipped. + +### Accessibility +While an `aria-label` is not explicitly required for a tab list, Tabs must be labeled using a aria-label in the absence of an ancestor landmark. This will prevent screen readers from announcing non-focused tabs, allowing for a more focused experience. + +## Selection + +Use the `defaultSelectedKey` or `selectedKey` prop to set the selected tab. The selected key corresponds to the `id` prop of a ``. Tabs can be disabled with the `isDisabled` prop. See the [selection guide](selection.html) for more details. +```tsx render +"use client"; +import type {Key} from 'react-aria-components'; +import {Tabs, TabList, Tab, TabPanel} from '@react-spectrum/s2'; +import Home from '@react-spectrum/s2/illustrations/gradient/generic2/Home'; +import Folder from '@react-spectrum/s2/illustrations/gradient/generic2/FolderOpen'; +import Search from '@react-spectrum/s2/illustrations/gradient/generic2/Search'; +import Settings from '@react-spectrum/s2/illustrations/gradient/generic1/GearSetting'; +import {useState} from 'react'; +function Example() { + let [tab, setTab] = useState("files"); + return ( +
+ + + Home + Files + Search + Settings + + + + + + + + + + + + + + +

Selected tab: {tab}

+
+ ); +} +``` + +## Collaspe behavior + +If there isn't enough horizontal room to render every tab on a single line, the component will collapse all tabs into a `Picker`. Note that this does not apply to vertical Tabs. +Try the example below to see the above behavior. +```tsx render +"use client"; +import {ToggleButton, Tabs, TabList, Tab, TabPanel} from '@react-spectrum/s2'; +import {useState} from 'react'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + +function Example() { + let [isCollasped, setCollaspe] = useState(false); + return ( +
+ + + Home + Profile + Contact + About + + +
+ Welcome home +
+
+ +
+ View your profile +
+
+ +
+ Find your contacts +
+
+ +
+ Learn more +
+
+
+ setCollaspe((isCollasped) => !isCollasped)}> + Toggle tab container size + +
+ ); +} +``` +## API + +### Tabs + + +### TabList + + +### Tab + + +### TabPanel + \ No newline at end of file