Skip to content

Commit 5c62b4d

Browse files
authored
docs: S2 Guides (#9083)
* docs: add S2 Selection guide * add S2 Collections docs * add Forms guide * add Client Side Routing guide
1 parent 63ed5b1 commit 5c62b4d

File tree

4 files changed

+2613
-11
lines changed

4 files changed

+2613
-11
lines changed

packages/dev/s2-docs/pages/s2/collections.mdx

Lines changed: 318 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,323 @@ export const description = 'Implementing collections in React Spectrum';
1010

1111
<PageDescription>Many components display a collection of items, and provide functionality such as keyboard navigation, selection, and more. React Spectrum has a consistent, compositional API to define the items displayed in these components.</PageDescription>
1212

13+
## Static collections
14+
15+
A **static collection** is a collection that does not change over time (e.g. hard coded). This is common for components like menus where the items are built into the application rather than user data.
16+
17+
```tsx render type="s2"
18+
"use client";
19+
import {Menu, MenuTrigger, MenuItem, ActionButton} from '@react-spectrum/s2';
20+
21+
<MenuTrigger>
22+
<ActionButton>Menu</ActionButton>
23+
<Menu>
24+
<MenuItem>Open</MenuItem>
25+
<MenuItem>Edit</MenuItem>
26+
<MenuItem>Delete</MenuItem>
27+
</Menu>
28+
</MenuTrigger>
29+
```
30+
31+
### Sections
32+
33+
Sections or groups of items can be constructed by wrapping the items in a section element. A `<Header>` can also be rendered within a section to provide a section title.
34+
35+
<ExampleSwitcher type="component" examples={['Menu', 'Picker']}>
36+
37+
```tsx render type="s2"
38+
"use client";
39+
import {Menu, MenuTrigger, MenuItem, MenuSection, ActionButton, Header, Heading} from '@react-spectrum/s2';
40+
41+
<MenuTrigger>
42+
<ActionButton>Menu</ActionButton>
43+
<Menu>
44+
{/*- begin highlight -*/}
45+
<MenuSection>
46+
<Header>
47+
<Heading>Styles</Heading>
48+
</Header>
49+
{/*- end highlight -*/}
50+
<MenuItem>Bold</MenuItem>
51+
<MenuItem>Underline</MenuItem>
52+
</MenuSection>
53+
<MenuSection>
54+
<Header>
55+
<Heading>Align</Heading>
56+
</Header>
57+
<MenuItem>Left</MenuItem>
58+
<MenuItem>Middle</MenuItem>
59+
<MenuItem>Right</MenuItem>
60+
</MenuSection>
61+
</Menu>
62+
</MenuTrigger>
63+
```
64+
65+
```tsx render type="s2"
66+
"use client";
67+
import {Picker, PickerSection, PickerItem, Header, Heading} from '@react-spectrum/s2';
68+
69+
<Picker label="Text style">
70+
{/*- begin highlight -*/}
71+
<PickerSection>
72+
<Header>
73+
<Heading>Styles</Heading>
74+
</Header>
75+
{/*- end highlight -*/}
76+
<PickerItem id="bold">Bold</PickerItem>
77+
<PickerItem id="underline">Underline</PickerItem>
78+
</PickerSection>
79+
<PickerSection>
80+
<Header>
81+
<Heading>Align</Heading>
82+
</Header>
83+
<PickerItem id="left">Left</PickerItem>
84+
<PickerItem id="middle">Middle</PickerItem>
85+
<PickerItem id="right">Right</PickerItem>
86+
</PickerSection>
87+
</Picker>
88+
```
89+
90+
</ExampleSwitcher>
91+
92+
## Dynamic collections
93+
94+
A **dynamic collection** is a collection that is based on external data, for example from an API. In addition, it may change over time as items are added, updated, or removed from the collection by a user.
95+
96+
Use the `items` prop to provide an array of objects, and a function to render each item as the `children`. This is similar to using `array.map` to render the children, but automatically memoizes the rendering of each item to improve performance.
97+
98+
```tsx render type="s2"
99+
"use client";
100+
import {TagGroup, Tag, ActionButton, Text} from '@react-spectrum/s2';
101+
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
102+
import AddIcon from '@react-spectrum/s2/icons/Add';
103+
import {useState} from 'react';
104+
105+
function Example() {
106+
let [animals, setAnimals] = useState([
107+
{id: 1, name: 'Aardvark'},
108+
{id: 2, name: 'Kangaroo'},
109+
{id: 3, name: 'Snake'}
110+
]);
111+
112+
let addItem = () => {
113+
setAnimals([
114+
...animals,
115+
{id: animals.length + 1, name: 'Lion'}
116+
]);
117+
};
118+
119+
return (
120+
<div className={style({display: 'flex', flexDirection: 'column', gap: 32, width: 320})}>
121+
{/*- begin highlight -*/}
122+
<TagGroup label="Animals" items={animals}>
123+
{item => <Tag>{item.name}</Tag>}
124+
</TagGroup>
125+
{/*- end highlight -*/}
126+
<ActionButton onPress={addItem} styles={style({alignSelf: 'start'})}>
127+
<AddIcon />
128+
<Text>Add item</Text>
129+
</ActionButton>
130+
</div>
131+
);
132+
}
133+
```
134+
135+
<InlineAlert variant="informative">
136+
<Heading>useListData</Heading>
137+
<Content>For convenience, React Spectrum provides a built-in [useListData](https://react-spectrum.adobe.com/react-stately/useListData.html) hook to manage state for an immutable list of items. It includes methods to add, remove, update, and re-order items, and manage corresponding selection state. See the docs for more details.</Content>
138+
</InlineAlert>
139+
140+
### Unique ids
141+
142+
All items in a collection must have a unique id, which is used for [selection](selection.html) and to track item updates. By default, React Spectrum looks for an `id` property on each object in the `items` array. You can also specify an `id` prop when rendering each item. This example uses `item.name` as the `id`.
143+
144+
```tsx
145+
let animals = [
146+
{name: 'Aardvark'},
147+
{name: 'Kangaroo'},
148+
{name: 'Snake'}
149+
];
150+
151+
<Picker label="Animals" items={animals}>
152+
{item => (
153+
/*- begin highlight -*/
154+
<PickerItem id={item.name}>
155+
{/*- end highlight -*/}
156+
{item.name}
157+
</PickerItem>
158+
)}
159+
</Picker>
160+
```
161+
13162
<InlineAlert variant="notice">
14-
<Heading>Coming soon</Heading>
15-
<Content>
16-
This page will cover collection patterns in Spectrum 2.
17-
</Content>
163+
<Heading>React keys</Heading>
164+
<Content>React Spectrum automatically sets the React `key` using `id`. If using `array.map`, you'll need to set both `key` and `id`.</Content>
18165
</InlineAlert>
166+
167+
### Dependencies
168+
169+
Dynamic collections are automatically memoized to improve performance. Rendered item elements are cached based on the object identity of the list item. If rendering an item depends on additional external state, the `dependencies` prop must be provided. This invalidates rendered elements similar to dependencies in React's `useMemo` hook.
170+
171+
```tsx render type="s2"
172+
"use client";
173+
import {CardView, AssetCard, CardPreview, Image, Content, Text} from '@react-spectrum/s2';
174+
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
175+
import {ToggleButtonGroup, ToggleButton} from '@react-spectrum/s2';
176+
import {useState} from 'react';
177+
178+
const items = [
179+
{id: 1, name: 'Charizard', type: 'Fire, Flying', image: 'https://img.pokemondb.net/sprites/home/normal/2x/avif/charizard.avif'},
180+
{id: 2, name: 'Blastoise', type: 'Water', image: 'https://img.pokemondb.net/sprites/home/normal/2x/avif/blastoise.avif'},
181+
{id: 3, name: 'Venusaur', type: 'Grass, Poison', image: 'https://img.pokemondb.net/sprites/home/normal/2x/avif/venusaur.avif'},
182+
{id: 4, name: 'Pikachu', type: 'Electric', image: 'https://img.pokemondb.net/sprites/home/normal/2x/avif/pikachu.avif'}
183+
];
184+
185+
export default function Example() {
186+
let [showType, setShowType] = useState(false);
187+
188+
return (
189+
<div className={style({display: 'flex', flexDirection: 'column', gap: 16, width: 320, alignItems: 'center'})}>
190+
<ToggleButtonGroup
191+
aria-label="Display options"
192+
selectionMode="multiple"
193+
selectedKeys={showType ? ['type'] : []}
194+
onSelectionChange={keys => setShowType(keys.has('type'))}
195+
>
196+
<ToggleButton id="type">Show type</ToggleButton>
197+
</ToggleButtonGroup>
198+
<CardView
199+
aria-label="Pokemon"
200+
items={items}
201+
selectionMode="multiple"
202+
/*- begin highlight -*/
203+
dependencies={[showType]}
204+
/*- end highlight -*/
205+
styles={style({width: 'full', height: 420})}
206+
>
207+
{item => (
208+
<AssetCard textValue={item.name}>
209+
<CardPreview>
210+
<Image src={item.image} />
211+
</CardPreview>
212+
<Content>
213+
<Text slot="title">{item.name}</Text>
214+
{/*- begin highlight -*/}
215+
{showType && <Text slot="description">{item.type}</Text>}
216+
{/*- end highlight -*/}
217+
</Content>
218+
</AssetCard>
219+
)}
220+
</CardView>
221+
</div>
222+
);
223+
}
224+
```
225+
226+
Note that adding dependencies will result in the _entire_ list being invalidated when a dependency changes. To avoid this and invalidate only an individual item, update the item object itself rather than accessing external state.
227+
228+
### Combining collections
229+
230+
To combine multiple sources of data, or mix static and dynamic items, use the `<Collection>` component.
231+
232+
```tsx render type="s2"
233+
"use client";
234+
import {Picker, PickerSection, PickerItem, Header, Heading, Collection} from '@react-spectrum/s2';
235+
236+
let animals = [
237+
{id: 1, species: 'Aardvark'},
238+
{id: 2, species: 'Kangaroo'},
239+
{id: 3, species: 'Snake'}
240+
];
241+
242+
let people = [
243+
{id: 4, name: 'David'},
244+
{id: 5, name: 'Mike'},
245+
{id: 6, name: 'Jane'}
246+
];
247+
248+
<Picker label="Select an item">
249+
<PickerSection>
250+
<Header>
251+
<Heading>Animals</Heading>
252+
</Header>
253+
{/*- begin highlight -*/}
254+
<Collection items={animals}>
255+
{item => <PickerItem id={item.species}>{item.species}</PickerItem>}
256+
</Collection>
257+
{/*- end highlight -*/}
258+
</PickerSection>
259+
<PickerSection>
260+
<Header>
261+
<Heading>People</Heading>
262+
</Header>
263+
<Collection items={people}>
264+
{item => <PickerItem id={item.name}>{item.name}</PickerItem>}
265+
</Collection>
266+
</PickerSection>
267+
</Picker>
268+
```
269+
270+
<InlineAlert variant="notice">
271+
<Heading>Globally unique ids</Heading>
272+
<Content>Unlike React keys which must only be unique within each element, the `id` prop must be globally unique across all sections. In the above example, the ids of `animals` and `people` do not conflict.</Content>
273+
</InlineAlert>
274+
275+
## Asynchronous loading
276+
277+
Data can be loaded asynchronously using any data fetching library. [useAsyncList](https://react-spectrum.adobe.com/react-stately/useAsyncList.html) is a built-in option.
278+
279+
Many components support infinite scrolling via the `loadingState` and `onLoadMore` props. These trigger loading of additional pages of items automatically as the user scrolls.
280+
281+
```tsx render type="s2"
282+
"use client";
283+
import {TableView, TableHeader, Column, TableBody, Row, Cell} from '@react-spectrum/s2';
284+
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
285+
import {useAsyncList} from 'react-stately';
286+
287+
interface Character {
288+
name: string
289+
}
290+
291+
function AsyncLoadingExample() {
292+
/*- begin focus -*/
293+
let list = useAsyncList<Character>({
294+
async load({signal, cursor}) {
295+
let res = await fetch(
296+
cursor || `https://pokeapi.co/api/v2/pokemon`,
297+
{signal}
298+
);
299+
let json = await res.json();
300+
301+
return {
302+
items: json.results,
303+
cursor: json.next
304+
};
305+
}
306+
});
307+
/*- end focus -*/
308+
309+
return (
310+
<TableView
311+
aria-label="Pokemon"
312+
selectionMode="single"
313+
///- begin highlight -///
314+
loadingState={list.loadingState}
315+
onLoadMore={list.loadMore}
316+
///- end highlight -///
317+
styles={style({width: 'full', height: 320})}
318+
>
319+
<TableHeader>
320+
<Column isRowHeader>Name</Column>
321+
</TableHeader>
322+
<TableBody items={list.items}>
323+
{(item) => (
324+
<Row id={item.name}>
325+
<Cell>{item.name}</Cell>
326+
</Row>
327+
)}
328+
</TableBody>
329+
</TableView>
330+
);
331+
}
332+
```

0 commit comments

Comments
 (0)