From 6c224ead1347cc5738082dd12637814491faab41 Mon Sep 17 00:00:00 2001 From: Robert Snow Date: Wed, 30 Jul 2025 14:32:09 +1000 Subject: [PATCH 1/3] chore: s2 docs taggroup --- packages/@react-spectrum/s2/src/TagGroup.tsx | 110 ++++++++---- packages/dev/s2-docs/pages/s2/TagGroup.mdx | 170 +++++++++++++++++++ 2 files changed, 244 insertions(+), 36 deletions(-) create mode 100644 packages/dev/s2-docs/pages/s2/TagGroup.mdx diff --git a/packages/@react-spectrum/s2/src/TagGroup.tsx b/packages/@react-spectrum/s2/src/TagGroup.tsx index b1616897b39..353e2224bf1 100644 --- a/packages/@react-spectrum/s2/src/TagGroup.tsx +++ b/packages/@react-spectrum/s2/src/TagGroup.tsx @@ -323,7 +323,45 @@ function TagGroupInner({ style={item.props.UNSAFE_style} key={item.key} className={item.props.className({size, allowsRemoving: Boolean(onRemove)})}> - {item.props.children({size, allowsRemoving: Boolean(onRemove), isInCtx: true})} +
+ + {item.props.children({size, allowsRemoving: Boolean(onRemove), isInCtx: true})} + +
+ {Boolean(onRemove) && ( + + )} ); })} @@ -516,41 +554,41 @@ function TagWrapper({children, isDisabled, allowsRemoving, isInRealDOM, isEmphas return ( <> {isInRealDOM && ( -
- - {children} - -
- )} +
+ + {children} + +
+ )} {!isInRealDOM && children} {allowsRemoving && isInRealDOM && ( {docs.exports.TagGroup.description} + +```tsx render docs={docs.exports.TagGroup} links={docs.links} props={['size', 'labelPosition', 'labelAlign', 'errorMessage', 'maxRows', 'selectionMode', 'description', 'label', 'disallowEmptySelection', 'selectionBehavior', 'isEmphasized', 'isInvalid', ]} initialProps={{label: 'Ice cream flavors', selectionMode: 'multiple', maxRows: 2}} type="s2" +import {TagGroup, Tag} from '@react-spectrum/s2'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + + + Chocolate + Mint + Strawberry + Vanilla + Chocolate Chip Cookie Dough + Rocky Road + Butter Pecan + Neapolitan + Salted Caramel + Mint Chocolate Chip + Tonight Dough + Lemon Cookie + Cookies and Cream + Phish Food + Peanut Butter Cup + Coffee + Pistachio + Cherry + +``` + +## Content + +`TagGroup` follows the **Collection Components API**, accepting both static and dynamic collections. +In this example, both the tags and the rows are provided to the table via a render function, enabling the user to hide and show columns and add additional rows. + +```tsx render type="s2" +"use client"; +import {TagGroup, Tag} from '@react-spectrum/s2'; +import {useState} from 'react'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + +///- begin collapse -/// +const initialTags = [ + {name: 'Landscape', id: 'landscape'}, + {name: 'Portrait', id: 'portrait'}, + {name: 'Macro', id: 'macro'}, + {name: 'Night', id: 'night'}, + {name: 'Dual', id: 'dual'}, + {name: 'Golden Hour', id: 'golden-hour'}, + {name: 'Animal', id: 'animal'}, + {name: 'Plant', id: 'plant'}, + {name: 'Travel', id: 'travel'} +]; +///- end collapse -/// + +function PhotoCategories() { + let [tags, setTags] = useState(initialTags); + + return ( + + {tag => {tag.name}} + + ); +} +``` + +### Removing tags + +To show a remove button on the tag, you can use the `onRemove` prop. + +```tsx render type="s2" +"use client"; +import {Button, TagGroup, Tag, TextField} from '@react-spectrum/s2'; +import {useState} from 'react'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + +///- begin collapse -/// +const initialTags = [ + {name: 'Landscape', id: 'landscape'}, + {name: 'Portrait', id: 'portrait'}, + {name: 'Macro', id: 'macro'}, + {name: 'Night', id: 'night'}, + {name: 'Dual', id: 'dual'}, + {name: 'Golden Hour', id: 'golden-hour'}, + {name: 'Animal', id: 'animal'}, + {name: 'Plant', id: 'plant'}, + {name: 'Travel', id: 'travel'} +]; +///- end collapse -/// + +function PhotoCategories() { + let [tags, setTags] = useState(initialTags); + let [newTag, setNewTag] = useState(''); + + return ( +
+
+ + +
+ setTags((prev) => prev.filter(t => !keys.has(t.id)))} + label="Photo categories" + selectionMode="multiple" + maxRows={2} + items={tags} + styles={style({width: 320})} + isEmphasized + > + {tag => {tag.name}} + +
+ ); +} +``` + +### 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. Link interactions vary depending on the selection behavior. See the [selection guide](selection.html) for more details. + +```tsx render type="s2" +"use client"; +import {TagGroup, Tag} from '@react-spectrum/s2'; +import {useState} from 'react'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + + + Landscape + Portrait + Macro + Night + Dual + Golden Hour + +``` + +### Empty state + +If the collection is empty, the `renderEmptyState` prop will be called. + +```tsx render type="s2" +"use client"; +import {TagGroup, Tag} from '@react-spectrum/s2'; +import {useState} from 'react'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + + 'No categories'} label="Photo categories" styles={style({width: 320})}> + {[]} + +``` + +## Props + +### TagGroup + + + +### Tag + + From d4e7d437ca28f78aa5515eda3ad1d494a9d662ff Mon Sep 17 00:00:00 2001 From: Robert Snow Date: Wed, 30 Jul 2025 17:13:37 +1000 Subject: [PATCH 2/3] make examples better and add group actions --- packages/dev/s2-docs/pages/s2/TagGroup.mdx | 78 +++++++++++++++++----- 1 file changed, 61 insertions(+), 17 deletions(-) diff --git a/packages/dev/s2-docs/pages/s2/TagGroup.mdx b/packages/dev/s2-docs/pages/s2/TagGroup.mdx index 6cc59ce4fdf..f0e86a15e04 100644 --- a/packages/dev/s2-docs/pages/s2/TagGroup.mdx +++ b/packages/dev/s2-docs/pages/s2/TagGroup.mdx @@ -40,21 +40,23 @@ In this example, both the tags and the rows are provided to the table via a rend ```tsx render type="s2" "use client"; -import {TagGroup, Tag} from '@react-spectrum/s2'; +import {TagGroup, Tag, Text} from '@react-spectrum/s2'; import {useState} from 'react'; import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; +import OrientationLandscape from '@react-spectrum/s2/icons/OrientationLandscape'; +import OrientationPortrait from '@react-spectrum/s2/icons/OrientationPortrait'; +import Star from '@react-spectrum/s2/icons/Star'; +import CameraProperties from '@react-spectrum/s2/icons/CameraProperties'; +import Lighten from '@react-spectrum/s2/icons/Lighten'; +import Cloud from '@react-spectrum/s2/icons/Cloud'; ///- begin collapse -/// const initialTags = [ - {name: 'Landscape', id: 'landscape'}, - {name: 'Portrait', id: 'portrait'}, - {name: 'Macro', id: 'macro'}, - {name: 'Night', id: 'night'}, - {name: 'Dual', id: 'dual'}, - {name: 'Golden Hour', id: 'golden-hour'}, - {name: 'Animal', id: 'animal'}, - {name: 'Plant', id: 'plant'}, - {name: 'Travel', id: 'travel'} + {name: 'Landscape', id: 'landscape', Icon: OrientationLandscape}, + {name: 'Portrait', id: 'portrait', Icon: OrientationPortrait}, + {name: 'Night', id: 'night', Icon: Star}, + {name: 'Dual', id: 'dual', Icon: CameraProperties}, + {name: 'Golden Hour', id: 'golden-hour', Icon: Lighten} ]; ///- end collapse -/// @@ -63,7 +65,7 @@ function PhotoCategories() { return ( - {tag => {tag.name}} + {({name, Icon}) => {name}} ); } @@ -135,12 +137,12 @@ import {useState} from 'react'; import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; - Landscape - Portrait - Macro - Night - Dual - Golden Hour + Landscape + Portrait + Macro + Night + Dual + Golden Hour ``` @@ -159,6 +161,48 @@ import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; ``` +### Group actions + +You can add a group action to the tag group by providing a `groupActionLabel` and `onGroupAction` prop. + +```tsx render type="s2" +"use client"; +import {TagGroup, Tag} from '@react-spectrum/s2'; +import {useState} from 'react'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + +///- begin collapse -/// +const initialTags = [ + {name: 'Landscape', id: 'landscape'}, + {name: 'Portrait', id: 'portrait'}, + {name: 'Macro', id: 'macro'}, + {name: 'Night', id: 'night'}, + {name: 'Dual', id: 'dual'}, + {name: 'Golden Hour', id: 'golden-hour'}, +]; +///- end collapse -/// + +function CopyAll() { + let [tags, setTags] = useState(initialTags); + let [selectedTags, setSelectedTags] = useState([]); + return ( + { + alert([...selectedTags].map(tag => tags.find(t => t.id === tag)?.name).join(', ')); + }} + selectionMode="multiple" + selectedKeys={selectedTags} + onSelectionChange={setSelectedTags} + items={tags} + label="Photo categories" + styles={style({width: 320})}> + {tag => {tag.name}} + + ) +} +``` + ## Props ### TagGroup From 66d992226af8cb8c4378651f14aeb46e7153b9bd Mon Sep 17 00:00:00 2001 From: Robert Snow Date: Fri, 1 Aug 2025 12:48:25 +1000 Subject: [PATCH 3/3] cleanup examples --- .../dev/s2-docs/pages/react-aria/TagGroup.mdx | 5 +- packages/dev/s2-docs/pages/s2/TagGroup.mdx | 130 +++++++++--------- 2 files changed, 70 insertions(+), 65 deletions(-) diff --git a/packages/dev/s2-docs/pages/react-aria/TagGroup.mdx b/packages/dev/s2-docs/pages/react-aria/TagGroup.mdx index 5e877c90ac7..fed5c973b9d 100644 --- a/packages/dev/s2-docs/pages/react-aria/TagGroup.mdx +++ b/packages/dev/s2-docs/pages/react-aria/TagGroup.mdx @@ -65,7 +65,9 @@ import {TagGroup, TagList, Tag, Label, Button, Text} from 'react-aria-components ## Content -`TagGroup` 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. Items can be removed via the `onRemove` event. +`TagGroup` 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. +Items can be removed via the `onRemove` event. ```tsx render "use client"; @@ -91,7 +93,6 @@ function Example() { {/*- end highlight -*/} {(item) => {item.name}} - /*- end highlight -*/ ); } ``` diff --git a/packages/dev/s2-docs/pages/s2/TagGroup.mdx b/packages/dev/s2-docs/pages/s2/TagGroup.mdx index f0e86a15e04..c60e068740f 100644 --- a/packages/dev/s2-docs/pages/s2/TagGroup.mdx +++ b/packages/dev/s2-docs/pages/s2/TagGroup.mdx @@ -7,7 +7,7 @@ import docs from 'docs:@react-spectrum/s2'; {docs.exports.TagGroup.description} -```tsx render docs={docs.exports.TagGroup} links={docs.links} props={['size', 'labelPosition', 'labelAlign', 'errorMessage', 'maxRows', 'selectionMode', 'description', 'label', 'disallowEmptySelection', 'selectionBehavior', 'isEmphasized', 'isInvalid', ]} initialProps={{label: 'Ice cream flavors', selectionMode: 'multiple', maxRows: 2}} type="s2" +```tsx render docs={docs.exports.TagGroup} links={docs.links} props={['size', 'labelPosition', 'labelAlign', 'errorMessage', 'maxRows', 'description', 'label', 'isEmphasized', 'isInvalid']} initialProps={{label: 'Ice cream flavors', selectionMode: 'multiple', maxRows: 2}} type="s2" import {TagGroup, Tag} from '@react-spectrum/s2'; import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; @@ -36,90 +36,52 @@ import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; ## Content `TagGroup` follows the **Collection Components API**, accepting both static and dynamic collections. -In this example, both the tags and the rows are provided to the table via a render function, enabling the user to hide and show columns and add additional rows. +This example shows a dynamic collection, passing a list of objects to the `items` prop, and a function to render the children. +Items can be removed via the `onRemove` event. ```tsx render type="s2" "use client"; -import {TagGroup, Tag, Text} from '@react-spectrum/s2'; +import {TagGroup, Tag, TextField, Button} from '@react-spectrum/s2'; +import {useListData} from 'react-stately'; import {useState} from 'react'; import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; -import OrientationLandscape from '@react-spectrum/s2/icons/OrientationLandscape'; -import OrientationPortrait from '@react-spectrum/s2/icons/OrientationPortrait'; -import Star from '@react-spectrum/s2/icons/Star'; -import CameraProperties from '@react-spectrum/s2/icons/CameraProperties'; -import Lighten from '@react-spectrum/s2/icons/Lighten'; -import Cloud from '@react-spectrum/s2/icons/Cloud'; ///- begin collapse -/// -const initialTags = [ - {name: 'Landscape', id: 'landscape', Icon: OrientationLandscape}, - {name: 'Portrait', id: 'portrait', Icon: OrientationPortrait}, - {name: 'Night', id: 'night', Icon: Star}, - {name: 'Dual', id: 'dual', Icon: CameraProperties}, - {name: 'Golden Hour', id: 'golden-hour', Icon: Lighten} -]; -///- end collapse -/// - -function PhotoCategories() { - let [tags, setTags] = useState(initialTags); - - return ( - - {({name, Icon}) => {name}} - - ); -} -``` - -### Removing tags - -To show a remove button on the tag, you can use the `onRemove` prop. - -```tsx render type="s2" -"use client"; -import {Button, TagGroup, Tag, TextField} from '@react-spectrum/s2'; -import {useState} from 'react'; -import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; - -///- begin collapse -/// -const initialTags = [ +const initialItems = [ {name: 'Landscape', id: 'landscape'}, {name: 'Portrait', id: 'portrait'}, - {name: 'Macro', id: 'macro'}, {name: 'Night', id: 'night'}, {name: 'Dual', id: 'dual'}, - {name: 'Golden Hour', id: 'golden-hour'}, - {name: 'Animal', id: 'animal'}, - {name: 'Plant', id: 'plant'}, - {name: 'Travel', id: 'travel'} + {name: 'Golden Hour', id: 'golden-hour'} ]; ///- end collapse -/// function PhotoCategories() { - let [tags, setTags] = useState(initialTags); + let list = useListData({initialItems}); let [newTag, setNewTag] = useState(''); return ( -
-
+
+ {/*- begin collapse -*/} +
+ {/*- end collapse -*/} setTags((prev) => prev.filter(t => !keys.has(t.id)))} label="Photo categories" - selectionMode="multiple" - maxRows={2} - items={tags} styles={style({width: 320})} - isEmphasized + ///- begin highlight -/// + items={list.items} + onRemove={(keys) => list.remove(...keys)} + ///- end highlight -/// > - {tag => {tag.name}} + {(item) => {item.name}}
); @@ -133,11 +95,12 @@ Use the `href` prop on a `` to create a link. See the **client side routing ```tsx render type="s2" "use client"; import {TagGroup, Tag} from '@react-spectrum/s2'; -import {useState} from 'react'; import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + {/*- begin highlight -*/} Landscape + {/*- end highlight -*/} Portrait Macro Night @@ -152,15 +115,53 @@ If the collection is empty, the `renderEmptyState` prop will be called. ```tsx render type="s2" "use client"; +import {TagGroup} from '@react-spectrum/s2'; + + 'No categories'}> + {[]} + +``` + +### Selection + +Use the `selectionMode` prop to enable single or multiple selection. +The selected items can be controlled via the `selectedKeys` prop, matching the `id` prop of the items. +Items can be disabled with the `isDisabled` prop. See the [selection guide](selection.html) for more details. + +```tsx render docs={docs.exports.TagGroup} links={docs.links} props={['selectionMode', 'selectionBehavior', 'disallowEmptySelection']} initialProps={{selectionMode: 'multiple'}} wide +"use client"; import {TagGroup, Tag} from '@react-spectrum/s2'; import {useState} from 'react'; import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; - 'No categories'} label="Photo categories" styles={style({width: 320})}> - {[]} - +function Example(props) { + let [selected, setSelected] = useState(new Set()); + + return ( +
+ + Laundry + Fitness center + Parking + Swimming pool + Breakfast + +

Current selection: {selected === 'all' ? 'all' : [...selected].join(', ')}

+
+ ); +} ``` + ### Group actions You can add a group action to the tag group by providing a `groupActionLabel` and `onGroupAction` prop. @@ -182,15 +183,18 @@ const initialTags = [ ]; ///- end collapse -/// -function CopyAll() { +function CopyAll(props) { let [tags, setTags] = useState(initialTags); let [selectedTags, setSelectedTags] = useState([]); return ( { alert([...selectedTags].map(tag => tags.find(t => t.id === tag)?.name).join(', ')); }} + ///- end highlight -/// selectionMode="multiple" selectedKeys={selectedTags} onSelectionChange={setSelectedTags} @@ -207,8 +211,8 @@ function CopyAll() { ### TagGroup - + ### Tag - +