Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/@react-spectrum/s2/chromatic/Combobox.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* governing permissions and limitations under the License.
*/

import {AsyncComboBoxStory, AsyncComboBoxStoryType, ContextualHelpExample, CustomWidth, Dynamic, EmptyCombobox, Example, Sections, WithIcons} from '../stories/ComboBox.stories';
import {AsyncComboBoxStory, AsyncComboBoxStoryType, ContextualHelpExample, CustomWidth, Dynamic, EmptyCombobox, Example, Sections, WithAvatars, WithIcons} from '../stories/ComboBox.stories';
import {ComboBox} from '../src';
import {expect} from '@storybook/jest';
import type {Meta, StoryObj} from '@storybook/react';
Expand Down Expand Up @@ -58,6 +58,12 @@ export const Icons: Story = {
play: Static.play
};

export const Avatars: Story = {
...WithAvatars,
name: 'With Avatars',
play: Static.play
};

export const ContextualHelp: Story = {
...ContextualHelpExample,
play: async ({canvasElement}) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ const meta: Meta<typeof ComboBox<any>> = {
};

export default meta;
export {Static, WithSections, WithDynamic, Icons, ContextualHelp, WithCustomWidth} from './Combobox.stories';
export {Static, WithSections, WithDynamic, Icons, Avatars, ContextualHelp, WithCustomWidth} from './Combobox.stories';
22 changes: 18 additions & 4 deletions packages/@react-spectrum/s2/src/ComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,20 @@ import {
Virtualizer
} from 'react-aria-components';
import {AsyncLoadable, GlobalDOMAttributes, HelpTextProps, LoadingState, SpectrumLabelableProps} from '@react-types/shared';
import {BaseCollection, CollectionNode, createLeafComponent} from '@react-aria/collections';
import {baseColor, edgeToText, focusRing, space, style} from '../style' with {type: 'macro'};
import {centerBaseline} from './CenterBaseline';
import {centerPadding, control, controlBorderRadius, controlFont, controlSize, field, fieldInput, getAllowedOverrides, StyleProps} from './style-utils' with {type: 'macro'};
import {
avatar,
checkmark,
description,
icon,
iconCenterWrapper,
label,
sectionHeading
} from './Menu';
import {AvatarContext} from './Avatar';
import {BaseCollection, CollectionNode, createLeafComponent} from '@react-aria/collections';
import {baseColor, edgeToText, focusRing, space, style} from '../style' with {type: 'macro'};
import {centerBaseline} from './CenterBaseline';
import {centerPadding, control, controlBorderRadius, controlFont, controlSize, field, fieldInput, getAllowedOverrides, StyleProps} from './style-utils' with {type: 'macro'};
import CheckmarkIcon from '../ui-icons/Checkmark';
import ChevronIcon from '../ui-icons/Chevron';
import {createContext, CSSProperties, ForwardedRef, forwardRef, ReactNode, Ref, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
Expand Down Expand Up @@ -363,6 +365,13 @@ export interface ComboBoxItemProps extends Omit<ListBoxItemProps, 'children' | '
children: ReactNode
}

const avatarSize = {
S: 16,
M: 20,
L: 22,
XL: 26
} as const;

const checkmarkIconSize = {
S: 'XS',
M: 'M',
Expand Down Expand Up @@ -392,6 +401,11 @@ export function ComboBoxItem(props: ComboBoxItemProps): ReactNode {
icon: {render: centerBaseline({slot: 'icon', styles: iconCenterWrapper}), styles: icon}
}
}],
[AvatarContext, {
slots: {
avatar: {size: avatarSize[size], styles: avatar}
}
}],
[TextContext, {
slots: {
label: {styles: label({size})},
Expand Down
7 changes: 7 additions & 0 deletions packages/@react-spectrum/s2/src/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,13 @@ let image = style({
objectFit: 'contain'
});

export let avatar = style({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure we're going to have avatar support in a menu, best to define this over in ComboBox and Picker as needed

gridArea: 'icon',
marginEnd: 'text-to-visual',
marginTop: fontRelative(6), // made up, need feedback
alignSelf: 'center'
});

export let label = style<{size: string}>({
gridArea: 'label',
font: controlFont(),
Expand Down
29 changes: 28 additions & 1 deletion packages/@react-spectrum/s2/stories/ComboBox.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* governing permissions and limitations under the License.
*/

import {Button, ComboBox, ComboBoxItem, ComboBoxSection, Content, ContextualHelp, Footer, Form, Header, Heading, Link, Text} from '../src';
import {Avatar, Button, ComboBox, ComboBoxItem, ComboBoxSection, Content, ContextualHelp, Footer, Form, Header, Heading, Link, Text} from '../src';
import {categorizeArgTypes, getActionArgs} from './utils';
import {ComboBoxProps} from 'react-aria-components';
import DeviceDesktopIcon from '../s2wf-icons/S2_Icon_DeviceDesktop_20_N.svg';
Expand Down Expand Up @@ -150,6 +150,33 @@ export const WithIcons: Story = {
}
};

const SRC_URL_1 = 'https://i.imgur.com/xIe7Wlb.png';
const SRC_URL_2 = 'https://mir-s3-cdn-cf.behance.net/project_modules/disp/690bc6105945313.5f84bfc9de488.png';

export const WithAvatars: Story = {
render: (args) => (
<ComboBox {...args}>
<ComboBoxItem textValue="User One">
<Avatar src={SRC_URL_1} slot="avatar" />
<Text slot="label">User One</Text>
<Text slot="description">[email protected]</Text>
</ComboBoxItem>
<ComboBoxItem textValue="User Two">
<Avatar src={SRC_URL_2} slot="avatar" />
<Text slot="label">User Two</Text>
<Text slot="description">[email protected]<br />123-456-7890</Text>
</ComboBoxItem>
<ComboBoxItem textValue="User Three">
<Avatar src={SRC_URL_2} slot="avatar" />
<Text slot="label">User Three</Text>
</ComboBoxItem>
</ComboBox>
),
args: {
label: 'Share'
}
};

export const Validation: Story = {
render: (args) => (
<Form>
Expand Down
20 changes: 19 additions & 1 deletion packages/dev/s2-docs/pages/s2/ComboBox.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function Example() {

### Slots

`ComboBoxItem` supports icons, and `label` and `description` text slots.
`ComboBoxItem` supports icons, avatars, and `label` and `description` text slots.

```tsx render
"use client";
Expand Down Expand Up @@ -83,6 +83,24 @@ import UserSettings from '@react-spectrum/s2/icons/UserSettings';
</ComboBox>
```

```tsx render
"use client";
import {Avatar, ComboBox, ComboBoxItem, Text} from '@react-spectrum/s2';

const users = Array.from({length: 10}, (_, i) => ({name: `User ${i + 1}`}));

<ComboBox label="Share" items={users}>
{(item) => (
<ComboBoxItem textValue={item.name}>
{/*- begin highlight -*/}
<Avatar src="https://i.imgur.com/kJOwAdv.png" slot="avatar" />
{/*- end highlight -*/}
<Text slot="label">{item.name}</Text>
</ComboBoxItem>
)}
</ComboBox>
```

<InlineAlert variant="notice">
<Heading>Accessibility</Heading>
<Content>Interactive elements (e.g. buttons) within picker items are not allowed. This will break keyboard and screen reader navigation. Only add textual or decorative graphics (e.g. icons) as children.</Content>
Expand Down