Skip to content

Commit 1c1fbe5

Browse files
authored
Support for Avatar in ListBox, Picker, ComboBox, and SearchAutocomplete (#5431)
* support Avatar in ListBox, Picker, and ComboBox * address review comments * update docs * lint * update stories * fix listbox chromatic * reference accessibility secion in docs
1 parent fa00e0d commit 1c1fbe5

File tree

14 files changed

+293
-5
lines changed

14 files changed

+293
-5
lines changed

packages/@adobe/spectrum-css-temp/components/dropdown/index.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ governing permissions and limitations under the License.
9191
margin-inline-start: var(--spectrum-selectlist-thumbnail-image-padding-x);
9292
}
9393

94+
.spectrum-Dropdown-avatar + .spectrum-Dropdown-label {
95+
margin-inline-start: var(--spectrum-selectlist-thumbnail-image-padding-x);
96+
}
97+
9498
/* Only apply margin if there's a label */
9599
.spectrum-Dropdown-label ~ .spectrum-Dropdown-chevron {
96100
margin-inline-start: var(--spectrum-dropdown-icon-margin-left);

packages/@adobe/spectrum-css-temp/components/menu/index.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,12 @@ governing permissions and limitations under the License.
218218
padding-block-start: var(--spectrum-global-dimension-size-10);
219219
padding-inline-end: var(--spectrum-global-dimension-size-100);
220220
}
221+
.spectrum-Menu-avatar {
222+
/* Matches icon. */
223+
grid-area: icon;
224+
margin-inline-start: var(--spectrum-global-dimension-size-10);
225+
margin-inline-end: var(--spectrum-global-dimension-size-100);
226+
}
221227
.spectrum-Menu-description {
222228
grid-area: description;
223229
line-height: var(--spectrum-global-font-line-height-small);

packages/@react-spectrum/autocomplete/docs/SearchAutocomplete.mdx

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ import packageData from '@react-spectrum/autocomplete/package.json';
1717
```jsx import
1818
import {SearchAutocomplete, Section, Item} from '@react-spectrum/autocomplete';
1919
import {useFilter} from '@react-aria/i18n';
20+
import Email from '@spectrum-icons/workflow/Email';
21+
import Document from '@spectrum-icons/workflow/Document';
22+
import WebPages from '@spectrum-icons/workflow/WebPages';
23+
import SocialNetwork from '@spectrum-icons/workflow/SocialNetwork';
24+
import ShoppingCart from '@spectrum-icons/workflow/ShoppingCart';
25+
import Folder from '@spectrum-icons/workflow/Folder';
26+
import {Text} from '@react-spectrum/text';
27+
import {Avatar} from "@react-spectrum/avatar";
2028
```
2129

2230
---
@@ -215,6 +223,73 @@ function Example() {
215223
}
216224
```
217225

226+
## Complex items
227+
Items within SearchAutocomplete also allow for additional content used to better communicate options. Icons, avatars, and descriptions can be added to the `children` of `Item` as shown in the example below.
228+
If a description is added, the prop `slot="description"` must be used to distinguish the different `<Text>` elements.
229+
See Icon's [labeling](workflow-icons.html#labeling) section and Avatar's [accessibility](Avatar.html#accessibility) section for more information on how to label these elements.
230+
231+
```tsx example
232+
<SearchAutocomplete label="Search apps">
233+
<Section title="Productivity">
234+
<Item textValue="Mail">
235+
<Email size="S" />
236+
<Text>Mail</Text>
237+
<Text slot="description">Send and recieve emails</Text>
238+
</Item>
239+
<Item textValue="File Explorer">
240+
<Folder size="S" />
241+
<Text>File Explorer</Text>
242+
<Text slot="description">Navigate directories and open files</Text>
243+
</Item>
244+
<Item textValue="Document Editor">
245+
<Document size="S" />
246+
<Text>Document Editor</Text>
247+
<Text slot="description">Edit documents</Text>
248+
</Item>
249+
</Section>
250+
<Section title="Internet">
251+
<Item textValue="Web Browser">
252+
<WebPages size="S" />
253+
<Text>Web Browser</Text>
254+
<Text slot="description">Browse the internet</Text>
255+
</Item>
256+
<Item textValue="Social Media">
257+
<SocialNetwork size="S" />
258+
<Text>Social Media</Text>
259+
<Text slot="description">Connect with friends</Text>
260+
</Item>
261+
<Item textValue="Shopping">
262+
<ShoppingCart size="S" />
263+
<Text>Shopping</Text>
264+
<Text slot="description">Shop online</Text>
265+
</Item>
266+
</Section>
267+
</SearchAutocomplete>
268+
```
269+
270+
### With avatars
271+
272+
```tsx example
273+
<SearchAutocomplete label="Search users">
274+
<Item textValue="User 1">
275+
<Avatar src="https://i.imgur.com/kJOwAdv.png" />
276+
<Text>User 1</Text>
277+
</Item>
278+
<Item textValue="User 2">
279+
<Avatar src="https://i.imgur.com/kJOwAdv.png" />
280+
<Text>User 2</Text>
281+
</Item>
282+
<Item textValue="User 3">
283+
<Avatar src="https://i.imgur.com/kJOwAdv.png" />
284+
<Text>User 3</Text>
285+
</Item>
286+
<Item textValue="User 4">
287+
<Avatar src="https://i.imgur.com/kJOwAdv.png" />
288+
<Text>User 4</Text>
289+
</Item>
290+
</SearchAutocomplete>
291+
```
292+
218293
## Asynchronous loading
219294

220295
SearchAutocomplete supports loading data asynchronously, and will display a progress circle reflecting the current load state,

packages/@react-spectrum/autocomplete/stories/SearchAutocomplete.stories.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212
*/
1313

1414
import {action} from '@storybook/addon-actions';
15+
import {Avatar} from '@react-spectrum/avatar';
1516
import {ComponentMeta, ComponentStoryObj} from '@storybook/react';
1617
import Filter from '@spectrum-icons/workflow/Filter';
1718
import {Flex} from '@react-spectrum/layout';
1819
import {Item, SearchAutocomplete} from '@react-spectrum/autocomplete';
1920
import {mergeProps} from '@react-aria/utils';
2021
import React from 'react';
22+
import {Text} from '@react-spectrum/text';
2123

2224
type SearchAutocompleteStory = ComponentStoryObj<typeof SearchAutocomplete>;
2325

@@ -271,3 +273,27 @@ export const iconNull: SearchAutocompleteStory = {
271273
args: {icon: null},
272274
name: 'icon: null'
273275
};
276+
277+
export const WithAvatars: SearchAutocompleteStory = {
278+
args: {label: 'Search users'},
279+
render: (args) => (
280+
<SearchAutocomplete {...args}>
281+
<Item textValue="User 1">
282+
<Avatar src="https://i.imgur.com/kJOwAdv.png" />
283+
<Text>User 1</Text>
284+
</Item>
285+
<Item textValue="User 2">
286+
<Avatar src="https://i.imgur.com/kJOwAdv.png" />
287+
<Text>User 2</Text>
288+
</Item>
289+
<Item textValue="User 3">
290+
<Avatar src="https://i.imgur.com/kJOwAdv.png" />
291+
<Text>User 3</Text>
292+
</Item>
293+
<Item textValue="User 4">
294+
<Avatar src="https://i.imgur.com/kJOwAdv.png" />
295+
<Text>User 4</Text>
296+
</Item>
297+
</SearchAutocomplete>
298+
)
299+
};

packages/@react-spectrum/combobox/docs/ComboBox.mdx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import Draw from '@spectrum-icons/workflow/Draw';
2323
import {Flex} from '@react-spectrum/layout';
2424
import {Text} from '@react-spectrum/text';
2525
import {useFilter} from '@react-aria/i18n';
26+
import {Avatar} from "@react-spectrum/avatar";
2627
```
2728

2829
---
@@ -501,8 +502,9 @@ function Example() {
501502
```
502503

503504
## Complex items
504-
Items within ComboBox also allow for additional content used to better communicate options. Icons and descriptions can be added to the `children` of `Item` as shown in the example below.
505+
Items within ComboBox also allow for additional content used to better communicate options. Icons, avatars, and descriptions can be added to the `children` of `Item` as shown in the example below.
505506
If a description is added, the prop `slot="description"` must be used to distinguish the different `<Text>` elements.
507+
See Icon's [labeling](workflow-icons.html#labeling) section and Avatar's [accessibility](Avatar.html#accessibility) section for more information on how to label these elements.
506508

507509
```tsx example
508510
<ComboBox label="Select action">
@@ -529,6 +531,29 @@ If a description is added, the prop `slot="description"` must be used to disting
529531
</ComboBox>
530532
```
531533

534+
### With avatars
535+
536+
```tsx example
537+
<ComboBox label="Select a user">
538+
<Item textValue="User 1">
539+
<Avatar src="https://i.imgur.com/kJOwAdv.png" />
540+
<Text>User 1</Text>
541+
</Item>
542+
<Item textValue="User 2">
543+
<Avatar src="https://i.imgur.com/kJOwAdv.png" />
544+
<Text>User 2</Text>
545+
</Item>
546+
<Item textValue="User 3">
547+
<Avatar src="https://i.imgur.com/kJOwAdv.png" />
548+
<Text>User 3</Text>
549+
</Item>
550+
<Item textValue="User 4">
551+
<Avatar src="https://i.imgur.com/kJOwAdv.png" />
552+
<Text>User 4</Text>
553+
</Item>
554+
</ComboBox>
555+
```
556+
532557
## Asynchronous loading
533558

534559
ComboBox supports loading data asynchronously, and will display a progress circle reflecting the current load state,

packages/@react-spectrum/combobox/stories/ComboBox.stories.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {action} from '@storybook/addon-actions';
1414
import {ActionButton, Button} from '@react-spectrum/button';
1515
import Add from '@spectrum-icons/workflow/Add';
1616
import Alert from '@spectrum-icons/workflow/Alert';
17+
import {Avatar} from '@react-spectrum/avatar';
1718
import Bell from '@spectrum-icons/workflow/Bell';
1819
import {ButtonGroup} from '@react-spectrum/buttongroup';
1920
import {chain} from '@react-aria/utils';
@@ -286,6 +287,30 @@ export const ComplexItems: ComboBoxStory = {
286287
)
287288
};
288289

290+
export const WithAvatars: ComboBoxStory = {
291+
args: {label: 'Select a user'},
292+
render: (args) => (
293+
<ComboBox {...args}>
294+
<Item textValue="User 1">
295+
<Avatar src="https://i.imgur.com/kJOwAdv.png" />
296+
<Text>User 1</Text>
297+
</Item>
298+
<Item textValue="User 2">
299+
<Avatar src="https://i.imgur.com/kJOwAdv.png" />
300+
<Text>User 2</Text>
301+
</Item>
302+
<Item textValue="User 3">
303+
<Avatar src="https://i.imgur.com/kJOwAdv.png" />
304+
<Text>User 3</Text>
305+
</Item>
306+
<Item textValue="User 4">
307+
<Avatar src="https://i.imgur.com/kJOwAdv.png" />
308+
<Text>User 4</Text>
309+
</Item>
310+
</ComboBox>
311+
)
312+
};
313+
289314
export const UserProvidedLabel: ComboBoxStory = {
290315
args: {label: 'Select action'},
291316
render: (args) => (

packages/@react-spectrum/listbox/chromatic/ListBox.chromatic.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import AlignCenter from '@spectrum-icons/workflow/AlignCenter';
1414
import AlignLeft from '@spectrum-icons/workflow/AlignLeft';
1515
import AlignRight from '@spectrum-icons/workflow/AlignRight';
16+
import {Avatar} from '@react-spectrum/avatar';
1617
import Blower from '@spectrum-icons/workflow/Blower';
1718
import Book from '@spectrum-icons/workflow/Book';
1819
import Copy from '@spectrum-icons/workflow/Copy';
@@ -136,6 +137,17 @@ const TemplateComplex = (args: SpectrumListBoxProps<object>) => (
136137
</ListBox>
137138
);
138139

140+
const TemplateAvatars = (args: SpectrumListBoxProps<object>) => (
141+
<ListBox {...args} flexGrow={1} items={flatOptions}>
142+
{(item) => (
143+
<Item key={item.name}>
144+
<Avatar src="https://i.imgur.com/kJOwAdv.png" alt="default Adobe avatar" />
145+
<Text>{item.name}</Text>
146+
</Item>
147+
)}
148+
</ListBox>
149+
);
150+
139151
export const Default = {
140152
render: Template,
141153
name: 'flat list with selection',
@@ -159,3 +171,8 @@ export const ComplexItems = {
159171
name: 'complex items',
160172
args: {selectedKeys: ['Puppy', 'Cut'], disabledKeys: ['Paste'], selectionMode: 'multiple'}
161173
};
174+
175+
export const WithAvatar = {
176+
render: TemplateAvatars,
177+
name: 'with avatar'
178+
};

packages/@react-spectrum/listbox/docs/ListBox.mdx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import Book from '@spectrum-icons/workflow/Book';
2121
import BulkEditUsers from '@spectrum-icons/workflow/BulkEditUsers';
2222
import Draw from '@spectrum-icons/workflow/Draw';
2323
import {Text} from '@react-spectrum/text';
24+
import {Avatar} from "@react-spectrum/avatar";
2425
```
2526

2627
---
@@ -267,7 +268,9 @@ function DynamicExample() {
267268
```
268269

269270
## Complex Items
270-
Items within ListBox also allow for additional content used to better communicate options. Icons and descriptions can be added to the `children` of `Item` as shown in the example below. If a description is added, the prop `slot="description"` must be used to distinguish the different `<Text>` elements.
271+
Items within ListBox also allow for additional content used to better communicate options. Icons, avatars, and descriptions can be added to the `children` of `Item` as shown in the example below.
272+
If a description is added, the prop `slot="description"` must be used to distinguish the different `<Text>` elements.
273+
See Icon's [labeling](workflow-icons.html#labeling) section and Avatar's [accessibility](Avatar.html#accessibility) section for more information on how to label these elements.
271274

272275
```tsx example
273276
<ListBox width="size-2400" aria-label="Options" selectionMode="single">
@@ -290,6 +293,32 @@ Items within ListBox also allow for additional content used to better communicat
290293
</Section>
291294
</ListBox>
292295
```
296+
297+
### With avatars
298+
299+
```tsx example
300+
<ListBox width="size-2400" aria-label="Options" selectionMode="single">
301+
<Section title="Users">
302+
<Item textValue="User 1">
303+
<Avatar src="https://i.imgur.com/kJOwAdv.png" />
304+
<Text>User 1</Text>
305+
</Item>
306+
<Item textValue="User 2">
307+
<Avatar src="https://i.imgur.com/kJOwAdv.png" />
308+
<Text>User 2</Text>
309+
</Item>
310+
<Item textValue="User 3">
311+
<Avatar src="https://i.imgur.com/kJOwAdv.png" />
312+
<Text>User 3</Text>
313+
</Item>
314+
<Item textValue="User 4">
315+
<Avatar src="https://i.imgur.com/kJOwAdv.png" />
316+
<Text>User 4</Text>
317+
</Item>
318+
</Section>
319+
</ListBox>
320+
```
321+
293322
## Asynchronous loading
294323

295324
ListBox supports loading data asynchronously, and will display a progress circle when the `isLoading` prop is set.

packages/@react-spectrum/listbox/src/ListBoxOption.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ export function ListBoxOption<T>(props: OptionProps<T>) {
101101
slots={{
102102
text: {UNSAFE_className: styles['spectrum-Menu-itemLabel'], ...labelProps},
103103
icon: {size: 'S', UNSAFE_className: styles['spectrum-Menu-icon']},
104+
avatar: {size: 'avatar-size-100', UNSAFE_className: styles['spectrum-Menu-avatar']},
104105
description: {UNSAFE_className: styles['spectrum-Menu-description'], ...descriptionProps}
105106
}}>
106107
{contents}

packages/@react-spectrum/listbox/stories/ListBox.stories.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*/
1212

1313
import {action} from '@storybook/addon-actions';
14-
import {ActionGroup, AlertDialog, Button, DialogContainer, Flex} from '@adobe/react-spectrum';
14+
import {ActionGroup, AlertDialog, Avatar, Button, DialogContainer, Flex, Text} from '@adobe/react-spectrum';
1515
import AlignCenter from '@spectrum-icons/workflow/AlignCenter';
1616
import AlignLeft from '@spectrum-icons/workflow/AlignLeft';
1717
import AlignRight from '@spectrum-icons/workflow/AlignRight';
@@ -25,7 +25,6 @@ import {Item, ListBox, Section} from '../';
2525
import {Label} from '@react-spectrum/label';
2626
import Paste from '@spectrum-icons/workflow/Paste';
2727
import React, {useRef, useState} from 'react';
28-
import {Text} from '@react-spectrum/text';
2928
import {TranslateListBox} from './../chromatic/ListBoxLanguages.chromatic';
3029
import {useAsyncList, useTreeData} from '@react-stately/data';
3130

@@ -1019,3 +1018,27 @@ Links.story = {
10191018
}
10201019
}
10211020
};
1021+
1022+
export const WithAvatars = {
1023+
render: () => (
1024+
<ListBox aria-label="Listbox with avatars" width="350px">
1025+
<Item textValue="Person 1">
1026+
<Text>Person 1</Text>
1027+
<Avatar src="https://i.imgur.com/kJOwAdv.png" alt="default Adobe avatar" />
1028+
</Item>
1029+
<Item textValue="Person 1">
1030+
<Text>Person 2</Text>
1031+
<Avatar src="https://i.imgur.com/kJOwAdv.png" alt="default Adobe avatar" />
1032+
</Item>
1033+
<Item textValue="Person 1">
1034+
<Text>Person 3</Text>
1035+
<Avatar src="https://i.imgur.com/kJOwAdv.png" alt="default Adobe avatar" />
1036+
</Item>
1037+
</ListBox>
1038+
),
1039+
decorators: [(Story) => (
1040+
<StoryDecorator>
1041+
<Story />
1042+
</StoryDecorator>
1043+
)]
1044+
};

0 commit comments

Comments
 (0)