Update design of account search dropdown in collection editor (#38739)

This commit is contained in:
diondiondion authored and GitHub committed 2026-04-17 15:13:18 +00:00
1 parent ea33d7fba6
commit 05a1c170c2
8 files changed
+192 -66

No files matched your search

@@ -25,7 +25,7 @@
.popover {
z-index: 9999;
box-sizing: border-box;
max-height: max(200px, 30dvh);
max-height: min(320px, 50dvh);
padding: 4px;
border-radius: 4px;
color: var(--color-text-primary);
@@ -63,13 +63,7 @@
user-select: none;
&[data-highlighted='true'] {
color: var(--color-text-on-brand-base);
background: var(--color-bg-brand-base);
&[aria-disabled='true'] {
color: var(--color-text-on-disabled);
background: var(--color-bg-disabled);
}
background: var(--color-bg-overlay-highlight);
}
&[aria-disabled='true'] {
@@ -82,11 +82,12 @@ interface ComboboxProps<
* Customise the rendering of group titles.
* The `titleId` must be attached to the element that provides the
* accessible name for the group.
* Return `null` to omit rendering the group title.
*/
renderGroupTitle?: (
groupKey: GroupKey,
titleId: string,
) => React.ReactElement | string;
) => React.ReactElement | null;
/**
* The main selection handler, called when an option is selected or deselected.
*/
@@ -182,7 +183,12 @@ const ComboboxWithRef = <Item extends ComboboxItem, GroupKey extends string>(
const hasGroups = !Array.isArray(items);
const flatItems = useMemo(
() => (hasGroups ? (Object.values(items).flat() as Item[]) : items),
() =>
hasGroups
? (Object.values(items)
.flat()
.filter((i) => !!i) as Item[])
: items,
[hasGroups, items],
);
@@ -478,10 +484,11 @@ const ComboboxWithRef = <Item extends ComboboxItem, GroupKey extends string>(
{(Object.keys(items) as GroupKey[]).map((groupKey) => {
const groupItems = items[groupKey];
const groupTitleId = `${listId}-group-${groupKey}`;
const groupTitle = renderGroupTitle?.(
const customGroupTitle = renderGroupTitle?.(
groupKey,
groupTitleId,
) ?? <span id={groupTitleId}>{groupKey}</span>;
);
const hasTitle = customGroupTitle !== null;
if (!groupItems?.length) return null;
@@ -489,11 +496,18 @@ const ComboboxWithRef = <Item extends ComboboxItem, GroupKey extends string>(
<ul
key={groupKey}
role='group'
aria-labelledby={groupTitleId}
aria-labelledby={hasTitle ? groupTitleId : undefined}
>
<li role='presentation' className={classes.groupLabel}>
{groupTitle}
</li>
{hasTitle && (
<li
role='presentation'
className={classes.groupLabel}
>
{customGroupTitle ?? (
<span id={groupTitleId}>{groupKey}</span>
)}
</li>
)}
{renderItems(groupItems)}
</ul>
);
@@ -14,8 +14,9 @@ interface WrapperProps extends Omit<
/**
* A basic list item component that can be used as a base for more bespoke list items.
*
* Depending on functionality, use `ListItemButton` or `ListItemLink` as a child of the
* wrapper component.
* Choose the child of the wrapper component based on needed interactivity:
* `ListItemContent` for a non-interactive item, `ListItemButton` or `ListItemLink`
* for interactive items.
*/
export const ListItemWrapper: React.FC<WrapperProps> = ({
icon,
@@ -33,10 +34,31 @@ export const ListItemWrapper: React.FC<WrapperProps> = ({
);
};
interface LinkProps extends React.ComponentPropsWithoutRef<typeof Link> {
interface WithSubtitle {
subtitle?: React.ReactNode;
}
interface ContentProps
extends React.ComponentPropsWithoutRef<'h3'>, WithSubtitle {}
export const ListItemContent: React.FC<ContentProps> = ({
subtitle,
children,
...otherProps
}) => {
return (
<>
<h3 className={classes.title} {...otherProps}>
{children}
</h3>
{subtitle && <div className={classes.subtitle}>{subtitle}</div>}
</>
);
};
interface LinkProps
extends React.ComponentPropsWithoutRef<typeof Link>, WithSubtitle {}
export const ListItemLink: React.FC<LinkProps> = ({
subtitle,
children,
@@ -44,20 +66,16 @@ export const ListItemLink: React.FC<LinkProps> = ({
...otherProps
}) => {
return (
<>
<h3 className={classes.title}>
<Link className={classNames(className, 'focusable')} {...otherProps}>
{children}
</Link>
</h3>
{subtitle && <div className={classes.subtitle}>{subtitle}</div>}
</>
<ListItemContent subtitle={subtitle}>
<Link className={classNames(className, 'focusable')} {...otherProps}>
{children}
</Link>
</ListItemContent>
);
};
interface ButtonProps extends React.ComponentPropsWithoutRef<'button'> {
subtitle?: React.ReactNode;
}
interface ButtonProps
extends React.ComponentPropsWithoutRef<'button'>, WithSubtitle {}
export const ListItemButton: React.FC<ButtonProps> = ({
subtitle,
@@ -66,17 +84,14 @@ export const ListItemButton: React.FC<ButtonProps> = ({
...otherProps
}) => {
return (
<>
<h3 className={classes.title}>
<button
type='button'
className={classNames(className, 'focusable')}
{...otherProps}
>
{children}
</button>
</h3>
{subtitle && <div className={classes.subtitle}>{subtitle}</div>}
</>
<ListItemContent subtitle={subtitle}>
<button
type='button'
className={classNames(className, 'focusable')}
{...otherProps}
>
{children}
</button>
</ListItemContent>
);
};
@@ -7,18 +7,31 @@ import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?reac
import { Icon } from '../icon';
import { ListItemWrapper, ListItemButton, ListItemLink } from './index';
import {
ListItemWrapper,
ListItemContent,
ListItemButton,
ListItemLink,
} from './index';
const meta = {
title: 'Components/ListItem',
component: ListItemWrapper,
subcomponents: { ListItemButton, ListItemLink },
subcomponents: { ListItemContent, ListItemButton, ListItemLink },
} satisfies Meta<typeof ListItemWrapper>;
export default meta;
type Story = StoryObj<typeof meta>;
export const NonInteractive: Story = {
render: () => (
<ListItemWrapper icon={<Icon icon={VisibilityIcon} id='visibility' />}>
<ListItemContent>View more</ListItemContent>
</ListItemWrapper>
),
};
export const WithButton: Story = {
render: () => (
<ListItemWrapper