-
Notifications
You must be signed in to change notification settings - Fork 1.3k
feat: add Avatar support for ComboBoxItem and PickerItem #8931
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
For the new `avatar` style macro, I copied a subset of ComboBox's `image` style macro. Avatar controls size via its `size` prop, so I didn't include that in the style macro.
```tsx render | ||
"use client"; | ||
import {Avatar, ComboBox, ComboBoxItem, Text} from '@react-spectrum/s2'; | ||
|
||
const users = Array.from({length: 10}, (_, i) => ({ | ||
id: `user${i + 1}`, | ||
name: `User ${i + 1}`, | ||
email: `user${i + 1}@example.com`, | ||
avatar: 'https://i.imgur.com/kJOwAdv.png' | ||
})); | ||
|
||
<ComboBox label="Share" items={users}> | ||
{(item) => ( | ||
<ComboBoxItem id={item.id} textValue={item.name}> | ||
{/*- begin highlight -*/} | ||
<Avatar slot="avatar" src={item.avatar} /> | ||
{/*- end highlight -*/} | ||
<Text slot="label">{item.name}</Text> | ||
<Text slot="description">{item.email}</Text> | ||
</ComboBoxItem> | ||
)} | ||
</ComboBox> | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this doc example is good enough for the site, or if it's okay to have two tsx render
blocks one after another like I did here. Same thoughts for the Picker docs I added
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it could be moved up into the previous example, which would then render two comboboxes next to each other, one with icons, one with avatars (since they probably shouldn't be mixed)
It'll be hidden by the "expand" button, but should be obvious with the highlights once expanded
I'm sure we'll have some other opinions so happy to do this ourselves later if we can't come to an immediate consensus
context={AvatarContext} | ||
value={{slots: { | ||
avatar: {size: avatarSize[size], styles: avatar} | ||
}}}> | ||
<DefaultProvider |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the right approach to add another child DefaultProvider
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like it says
// A Context.Provider that only sets a value if not inside SelectValue.
function DefaultProvider({context, value, children}: {context: React.Con...
I haven't spent time thinking about it yet (hopefully can soon), is this what you want?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks that helps, I just wanted to make sure that sandwiching this between the DefaultProviders for IconContext and TextContent is okay, since this is diff than the usual Provider that takes an array of contexts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this should be fine since those contexts won't collide since they are different contexts after all. Technically you could use a standard Provider I believe since the styles passed down are the same between the context provided at the SelectValue level and the one at the PickerItem level in this case but its probably safer to use DefaultProvider here so those can diverge if need be
Build successful! 🎉 |
Build successful! 🎉 |
context={AvatarContext} | ||
value={{slots: { | ||
avatar: {size: avatarSize[size], styles: avatar} | ||
}}}> | ||
<DefaultProvider |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like it says
// A Context.Provider that only sets a value if not inside SelectValue.
function DefaultProvider({context, value, children}: {context: React.Con...
I haven't spent time thinking about it yet (hopefully can soon), is this what you want?
```tsx render | ||
"use client"; | ||
import {Avatar, ComboBox, ComboBoxItem, Text} from '@react-spectrum/s2'; | ||
|
||
const users = Array.from({length: 10}, (_, i) => ({ | ||
id: `user${i + 1}`, | ||
name: `User ${i + 1}`, | ||
email: `user${i + 1}@example.com`, | ||
avatar: 'https://i.imgur.com/kJOwAdv.png' | ||
})); | ||
|
||
<ComboBox label="Share" items={users}> | ||
{(item) => ( | ||
<ComboBoxItem id={item.id} textValue={item.name}> | ||
{/*- begin highlight -*/} | ||
<Avatar slot="avatar" src={item.avatar} /> | ||
{/*- end highlight -*/} | ||
<Text slot="label">{item.name}</Text> | ||
<Text slot="description">{item.email}</Text> | ||
</ComboBoxItem> | ||
)} | ||
</ComboBox> | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it could be moved up into the previous example, which would then render two comboboxes next to each other, one with icons, one with avatars (since they probably shouldn't be mixed)
It'll be hidden by the "expand" button, but should be obvious with the highlights once expanded
I'm sure we'll have some other opinions so happy to do this ourselves later if we can't come to an immediate consensus
https://www.chromatic.com/build?appId=5f0dd5ad2b5fc10022a2e320&number=1045, I wouldn't worry about the other diffs, they are from main |
Build successful! 🎉 |
|
||
export const AvatarContext = createContext<ContextValue<Partial<AvatarProps>, DOMRefValue<HTMLImageElement>>>(null); | ||
export interface AvatarContextValue extends AvatarProps { | ||
render?: (avatar: ReactNode) => ReactNode |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think rather than adding this prop we could just make Avatar have CenterBaseline by default. I can't really think of a case where you'd want to align text with it at the bottom. We may be able to avoid the extra wrapper div as well since Image already has a wrapper div. We could potentially put the ::before
directly on that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for fixing this!
Build successful! 🎉 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should run chromatic on this, I'm in the middle of editing dependencies, so if someone else beats me to it, that's fine
Build successful! 🎉 |
Build successful! 🎉 |
Build successful! 🎉 |
context={AvatarContext} | ||
value={{slots: { | ||
avatar: {size: avatarSize[size], styles: avatar} | ||
}}}> | ||
<DefaultProvider |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this should be fine since those contexts won't collide since they are different contexts after all. Technically you could use a standard Provider I believe since the styles passed down are the same between the context provided at the SelectValue level and the one at the PickerItem level in this case but its probably safer to use DefaultProvider here so those can diverge if need be
For the new
avatar
style macro exported from Menu, I copied a subset of ComboBox'simage
style macro.Avatar controls size via its
size
prop, so I didn't include that in the style macro.Closes: N/A
Combobox:
Picker:
✅ Pull Request Checklist:
📝 Test Instructions:
🧢 Your Project:
Adobe