Skip to content

Commit ddfa101

Browse files
reidbarbersnowystingerdevongovett
authored
Add docs for useTagGroup (#4154)
* add docs for useTagGroup * update tailwind preview * add useTag to API section * update anatomy diagram * update to reference latest name changes * address review comments * update example descriptions * include remove button in grid cell * update anatomy diagram * move to Status category * minor style/wording change * remove isFocused from useTagGroup * update clear to remove --------- Co-authored-by: Robert Snow <[email protected]> Co-authored-by: Devon Govett <[email protected]>
1 parent 9d1ba9b commit ddfa101

File tree

3 files changed

+362
-0
lines changed

3 files changed

+362
-0
lines changed
Lines changed: 65 additions & 0 deletions
Loading
6.37 KB
Loading
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
{/* Copyright 2020 Adobe. All rights reserved.
2+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
3+
you may not use this file except in compliance with the License. You may obtain a copy
4+
of the License at http://www.apache.org/licenses/LICENSE-2.0
5+
Unless required by applicable law or agreed to in writing, software distributed under
6+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
7+
OF ANY KIND, either express or implied. See the License for the specific language
8+
governing permissions and limitations under the License. */}
9+
10+
import {Layout} from '@react-spectrum/docs';
11+
export default Layout;
12+
13+
import docs from 'docs:@react-aria/tag';
14+
import typesDocs from 'docs:@react-types/shared/src/events.d.ts';
15+
import {HeaderInfo, FunctionAPI, TypeContext, InterfaceType, TypeLink, PageDescription} from '@react-spectrum/docs';
16+
import {Keyboard} from '@react-spectrum/text';
17+
import packageData from '@react-aria/tag/package.json';
18+
import ChevronRight from '@spectrum-icons/workflow/ChevronRight';
19+
import tailwindExample from 'url:./tailwind.png';
20+
import {ExampleCard} from '@react-spectrum/docs/src/ExampleCard';
21+
import Anatomy from './anatomy.svg';
22+
23+
---
24+
category: Status
25+
keywords: [tag, aria]
26+
---
27+
28+
# useTagGroup
29+
30+
<PageDescription>{docs.exports.useTagGroup.description}</PageDescription>
31+
32+
<HeaderInfo
33+
packageData={packageData}
34+
componentNames={['useTagGroup']} />
35+
36+
## API
37+
38+
<FunctionAPI function={docs.exports.useTagGroup} links={docs.links} />
39+
<FunctionAPI function={docs.exports.useTag} links={docs.links} />
40+
41+
## Features
42+
43+
* Exposed to assistive technology as a grid using ARIA
44+
* Keyboard navigation support including arrow keys, home/end, page up/down, and delete
45+
* Keyboard focus management and cross browser normalization
46+
* Labeling support for accessibility
47+
* Support for mouse, touch, and keyboard interactions
48+
49+
## Anatomy
50+
51+
<Anatomy />
52+
53+
A tag group consists of a list of tags.
54+
If a visual label is not provided, then an `aria-label` or
55+
`aria-labelledby` prop must be passed to identify the tag group to assistive technology.
56+
57+
Individual tags should include a visual label, and may optionally include icons or a remove button.
58+
59+
`useTagGroup` returns props for the group and its label, which you should spread
60+
onto the appropriate element:
61+
62+
<TypeContext.Provider value={docs.links}>
63+
<InterfaceType properties={docs.links[docs.exports.useTagGroup.return.id].properties} />
64+
</TypeContext.Provider>
65+
66+
`useTag` returns props for an individual tag:
67+
68+
<TypeContext.Provider value={docs.links}>
69+
<InterfaceType properties={docs.links[docs.exports.useTag.return.id].properties} />
70+
</TypeContext.Provider>
71+
72+
In order to be correctly identified to assistive technologies and enable proper keyboard navigation, the tag group should use `gridProps` on its outer container.
73+
74+
Each individual tag should use `rowProps` on its outer container, and use `gridCellProps` on an inner container.
75+
76+
## Example
77+
78+
```tsx example export=true
79+
import {useTag, useTagGroup} from '@react-aria/tag';
80+
import {useTagGroupState} from '@react-stately/tag';
81+
import {Item} from '@react-stately/collections';
82+
import {useFocusRing} from '@react-aria/focus';
83+
84+
// Reuse the Button from your component library. See below for details.
85+
import {Button} from 'your-component-library';
86+
87+
function TagGroup(props) {
88+
let { label, description, errorMessage, validationState, allowsRemoving, onRemove } = props;
89+
let ref = React.useRef(null);
90+
91+
let state = useTagGroupState(props);
92+
let {
93+
gridProps,
94+
labelProps,
95+
descriptionProps,
96+
errorMessageProps
97+
} = useTagGroup(props, state, ref);
98+
99+
return (
100+
<div ref={ref} className="tag-group">
101+
<div {...labelProps}>{label}</div>
102+
<div {...gridProps}>
103+
{[...state.collection].map((item) => (
104+
<Tag
105+
{...item.props}
106+
key={item.key}
107+
item={item}
108+
state={state}
109+
allowsRemoving={allowsRemoving}
110+
onRemove={onRemove}
111+
>
112+
{item.rendered}
113+
</Tag>
114+
))}
115+
</div>
116+
{description && (
117+
<div {...descriptionProps} className="description">
118+
{description}
119+
</div>
120+
)}
121+
{errorMessage && validationState === "invalid" && (
122+
<div {...errorMessageProps} className="error-message">
123+
{errorMessage}
124+
</div>
125+
)}
126+
</div>
127+
);
128+
}
129+
130+
function Tag(props) {
131+
let { item, state, allowsRemoving, onRemove } = props;
132+
let ref = React.useRef(null);
133+
let {focusProps} = useFocusRing({within: true});
134+
135+
let { rowProps, gridCellProps, labelProps, removeButtonProps } = useTag({
136+
...props,
137+
item,
138+
allowsRemoving,
139+
onRemove
140+
}, state, ref);
141+
142+
return (
143+
<span
144+
ref={ref}
145+
{...rowProps}
146+
{...focusProps}
147+
>
148+
<div {...gridCellProps}>
149+
<span {...labelProps}>{item.rendered}</span>
150+
{allowsRemoving && <Button {...removeButtonProps}>❎</Button>}
151+
</div>
152+
</span>
153+
);
154+
}
155+
156+
<TagGroup label="Categories">
157+
<Item key="news">News</Item>
158+
<Item key="travel">Travel</Item>
159+
<Item key="gaming">Gaming</Item>
160+
<Item key="shopping">Shopping</Item>
161+
</TagGroup>
162+
```
163+
164+
<details>
165+
<summary style={{fontWeight: 'bold'}}><ChevronRight size="S" /> Show CSS</summary>
166+
167+
```css
168+
/* css */
169+
.tag-group {
170+
display: flex;
171+
flex-direction: column;
172+
}
173+
174+
.tag-group div {
175+
margin: 5px 0;
176+
}
177+
178+
.tag-group [role="grid"] {
179+
display: flex;
180+
flex-wrap: wrap;
181+
}
182+
183+
.tag-group [role="row"] {
184+
display: flex;
185+
align-items: center;
186+
border: 1px solid gray;
187+
border-radius: 4px;
188+
padding: 2px 5px;
189+
margin: 2px;
190+
}
191+
192+
.tag-group [role="gridcell"] {
193+
margin: 0 5px;
194+
}
195+
196+
.tag-group [role="row"] button {
197+
background: none;
198+
border: none;
199+
padding-right: 0;
200+
}
201+
202+
.tag-group .description {
203+
font-size: 12px;
204+
}
205+
206+
.tag-group .error-message {
207+
color: red;
208+
font-size: 12px;
209+
}
210+
```
211+
212+
</details>
213+
214+
### Button
215+
216+
The `Button` component is used in the above example to remove a tag. It is built using the [useButton](useButton.html) hook, and can be shared with many other components.
217+
218+
<details>
219+
<summary style={{fontWeight: 'bold'}}><ChevronRight size="S" /> Show code</summary>
220+
221+
```tsx example export=true render=false
222+
import {useButton} from '@react-aria/button';
223+
224+
function Button(props) {
225+
let ref = React.useRef(null);
226+
let {buttonProps} = useButton(props, ref);
227+
return <button {...buttonProps} ref={ref}>{props.children}</button>;
228+
}
229+
```
230+
231+
</details>
232+
233+
234+
## Styled examples
235+
236+
<ExampleCard
237+
url="https://codesandbox.io/s/usetaggroup-with-tailwind-css-zxxrpv"
238+
preview={tailwindExample}
239+
title="Tailwind CSS"
240+
description="A TagGroup built with Tailwind." />
241+
242+
## Usage
243+
244+
### Remove tags
245+
246+
The `onRemove` handler and `allowsRemoving` props can be used to include a remove button which can be used to remove a tag. This allows the user to press the remove button, or press the backspace key while the tag is focused to remove the tag from the group.
247+
248+
```tsx example
249+
import {useListData} from '@react-stately/data';
250+
251+
function Example() {
252+
let list = useListData({
253+
initialItems: [
254+
{ id: 1, name: "News" },
255+
{ id: 2, name: "Travel" },
256+
{ id: 3, name: "Gaming" },
257+
{ id: 4, name: "Shopping" }
258+
]
259+
});
260+
261+
return (
262+
<TagGroup
263+
label="Categories"
264+
items={list.items}
265+
onRemove={list.remove}
266+
allowsRemoving>
267+
{(item) => <Item>{item.name}</Item>}
268+
</TagGroup>
269+
);
270+
}
271+
```
272+
273+
### Description
274+
275+
The `description` prop can be used to associate additional help text with a tag group.
276+
277+
```tsx example
278+
<TagGroup label="Categories" description="Your selected categories.">
279+
<Item key="news">News</Item>
280+
<Item key="travel">Travel</Item>
281+
<Item key="gaming">Gaming</Item>
282+
<Item key="shopping">Shopping</Item>
283+
</TagGroup>
284+
```
285+
286+
### Error message
287+
288+
The `errorMessage` prop can be used to help the user fix a validation error. It should be combined with the `validationState` prop.
289+
290+
```tsx example
291+
<TagGroup label="Categories" errorMessage="Invalid set of categories." validationState="invalid">
292+
<Item key="news">News</Item>
293+
<Item key="travel">Travel</Item>
294+
<Item key="gaming">Gaming</Item>
295+
<Item key="shopping">Shopping</Item>
296+
</TagGroup>
297+
```

0 commit comments

Comments
 (0)