Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
9 changes: 1 addition & 8 deletions apps/www/src/components/linear-menu-demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -207,14 +207,6 @@ export default function LinearMenuDemo() {
const renderMenu = (items: MenuItem[], query: string) => {
const filteredItems = filterMenuItems(items, query);

if (searchQuery && filteredItems.length === 0) {
return (
<div>
<Text>No results</Text>
</div>
);
}

return filteredItems.map((item, index) => {
switch (item.type) {
case 'group':
Expand Down Expand Up @@ -274,6 +266,7 @@ export default function LinearMenuDemo() {
<Menu.Trigger render={<Button />}>Actions</Menu.Trigger>
<Menu.Content searchPlaceholder='Search'>
{renderMenu(menuData, searchQuery)}
<Menu.EmptyState>No results</Menu.EmptyState>
</Menu.Content>
</Menu>
</Flex>
Expand Down
24 changes: 24 additions & 0 deletions apps/www/src/content/docs/(overview)/upgrading.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,30 @@ One section per release, newest first, with only the changes that need action
from you. The full record of every release, features and fixes included, is
on [GitHub releases](https://github.com/raystack/apsara/releases).

## Unreleased: menu search uses Base UI filtering

Search in `Menu`, `ContextMenu` and `Menubar` uses Base UI's menu filtering.
Props, slots and exported types do not change.

### 1. Update tests that query roles

In a searchable menu, items keep their menu roles: `menuitem`,
`menuitemcheckbox` and `menuitemradio`. They used to be `option`. The trigger
has `aria-haspopup="dialog"`, the popup is a `dialog`, and the input is a
`searchbox`. Update queries such as `getAllByRole('option')` to
`getAllByRole('menuitem')`.

### 2. Check items that have a `value`

An item with a `value` matches its `value` and its `children` text, not the
text in its icons. A `RadioItem` matches by its text, not its `value`.

### 3. Render `EmptyState` as a direct child of `Content`

In a searchable menu, `EmptyState` shows only when no item matches. Render it
as a direct child of `Content`, or inside a fragment there, so it sits next to
the list instead of inside it.

## Unreleased: date filters compare whole days

`DataView`, `DataTable` and `FilterChip` read and compare dates as calendar
Expand Down
1 change: 1 addition & 0 deletions apps/www/src/content/docs/components/context-menu/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export const autocompleteDemo = {
<Text>Right click here</Text>
</ContextMenu.Trigger>
<ContextMenu.Content searchPlaceholder="Search">
<ContextMenu.EmptyState>No results found</ContextMenu.EmptyState>
<ContextMenu.Group>
<ContextMenu.Label>Heading</ContextMenu.Label>
<ContextMenu.Item>Assign member...</ContextMenu.Item>
Expand Down
25 changes: 14 additions & 11 deletions apps/www/src/content/docs/components/context-menu/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Use `ContextMenu.Submenu`, `ContextMenu.SubmenuTrigger`, and `ContextMenu.Submen

Pass `autocomplete` to the ContextMenu root. Each menu manages its own autocomplete state.

- **`autocompleteMode="auto"`** is the default. Items filter as the user types, matched against each item's `value` prop or its children text.
- **`autocompleteMode="auto"`** is the default. Items filter as the user types. Each item matches by its text and, when set, its `value` prop. A `ContextMenu.RadioItem` matches by its text only, because its `value` is the selected value. Matching ignores case and accents.
- **Submenus** opt in separately: pass `autocomplete` to `ContextMenu.Submenu`.

<Demo data={autocompleteDemo} />
Expand Down Expand Up @@ -107,7 +107,7 @@ The container that holds the menu items. When autocomplete is enabled, renders a

Individual clickable options within the menu. Built on top of [Base UI ContextMenu.Item](https://base-ui.com/react/components/context-menu).

Renders as a `role='option'` when used in an autocomplete menu. By default, the item's `children` text content is used for matching, which can be overridden by passing a `value` prop.
In a searchable menu, the item's text is used for matching, including text in `leadingIcon` and `trailingIcon`. Pass a `value` prop to also match other text, such as a keyword. With a `value`, text in the icons no longer matches.

<auto-type-table path="./props.ts" name="ContextMenuItemProps" />

Expand All @@ -125,7 +125,7 @@ Groups related radio items and holds the selected value.

### RadioItem

A menu item that works like a radio button in a `ContextMenu.RadioGroup`. Renders a dot before the label when checked. The `value` prop is also used for search matching when it is a string.
A menu item that works like a radio button in a `ContextMenu.RadioGroup`. Renders a dot before the label when checked.

<auto-type-table path="./props.ts" name="ContextMenuRadioItemProps" />

Expand All @@ -137,13 +137,13 @@ A menu item that renders an `<a>` element. Pass a router link to `render` to use

### Group

A way to group related menu items together. When filtering is active, the group label is hidden.
A way to group related menu items together. When filtering is active, a group with no matching items is hidden.

<auto-type-table path="./props.ts" name="ContextMenuGroupProps" />

### Label

Renders a label in a menu group. This component should be wrapped with ContextMenu.Group so the `aria-labelledby` is correctly set on the group element. Hidden when filtering is active.
Renders a label in a menu group. This component should be wrapped with ContextMenu.Group so the `aria-labelledby` is correctly set on the group element.

<auto-type-table path="./props.ts" name="ContextMenuLabelProps" />

Expand All @@ -155,7 +155,7 @@ Visual divider between menu items or groups. Hidden when filtering is active.

### EmptyState

Placeholder content when there are no menu items to display.
Placeholder content when there are no menu items to display. In a searchable menu, it shows only when no item matches the search, and screen readers announce it. Render it as a direct child of `ContextMenu.Content`.

<auto-type-table path="./props.ts" name="ContextMenuEmptyStateProps" />

Expand All @@ -171,7 +171,7 @@ Supports its own `autocomplete` prop to enable search functionality within the s

The trigger item for a submenu. Renders with a trailing chevron icon by default. Accepts `leadingIcon` and `trailingIcon` props.

When inside a searchable parent menu, the `value` prop can be used for autocomplete matching.
When inside a searchable parent menu, the trigger matches by its text and, when set, its `value` prop. The root search does not search inside submenus.

<auto-type-table path="./props.ts" name="ContextMenuSubTriggerProps" />

Expand Down Expand Up @@ -217,10 +217,13 @@ Every rendered part carries a stable `data-slot` attribute for [styling and test
- Arrow keys move through items, Enter activates the highlighted item, and
ArrowRight/ArrowLeft open and close submenus.
- With `autocomplete`, the popup becomes a `role="dialog"` containing a search
input, items render as `role="option"`, and focus stays in the input while
arrow keys move the highlight, and Escape first closes an open submenu.
input (`role="searchbox"`, labelled by `searchPlaceholder`) and a
`role="menu"` list. Items keep their menu roles. Focus stays in the input
while arrow keys move the highlight. Escape in a submenu closes it and
returns focus to the parent input.
- `ContextMenu.Label` must be wrapped in `ContextMenu.Group` so the group is
labelled via `aria-labelledby`.
- Checkbox items use `role="menuitemcheckbox"` and radio items use
`role="menuitemradio"`, both with `aria-checked`. In a searchable menu they
render as `role="option"` and keep `aria-checked`.
`role="menuitemradio"`, both with `aria-checked`.
- In a searchable menu, `ContextMenu.EmptyState` is a `role="status"` live
region.
14 changes: 7 additions & 7 deletions apps/www/src/content/docs/components/context-menu/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export interface ContextMenuTriggerProps {
}

export interface ContextMenuContentProps {
/** Placeholder text for the autocomplete search input
/** Placeholder text and accessible name for the autocomplete search input
* @default "Search..."
*/
searchPlaceholder?: string;
Expand Down Expand Up @@ -102,7 +102,7 @@ export interface ContextMenuItemProps {
/** Whether the item is disabled */
disabled?: boolean;

/** Value of the item used for autocomplete matching. If not provided, `children` text content is used. */
/** Extra text to match in a searchable menu, along with the `children` text. */
value?: string;

/** Additional CSS class names */
Expand Down Expand Up @@ -153,7 +153,7 @@ export interface ContextMenuCheckboxItemProps {
/** Whether the item is disabled */
disabled?: boolean;

/** Value of the item used for autocomplete matching. If not provided, `children` text content is used. */
/** Extra text to match in a searchable menu, along with the `children` text. */
value?: string;

/** Additional CSS class names */
Expand Down Expand Up @@ -183,7 +183,7 @@ export interface ContextMenuRadioGroupProps {
}

export interface ContextMenuRadioItemProps {
/** Value of the radio item. Also used for autocomplete matching when it is a string. */
/** Value of the radio item. */
value: unknown;

/** Whether to close the menu when the item is clicked
Expand Down Expand Up @@ -222,7 +222,7 @@ export interface ContextMenuLinkItemProps {
/** Icon element to display after item text */
trailingIcon?: ReactNode;

/** Value of the item used for autocomplete matching. If not provided, `children` text content is used. */
/** Extra text to match in a searchable menu, along with the `children` text. */
value?: string;

/** Additional CSS class names */
Expand Down Expand Up @@ -283,12 +283,12 @@ export interface ContextMenuSubTriggerProps {
/** Icon element to display after trigger text. Defaults to a chevron right icon. */
trailingIcon?: ReactNode;

/** Value used for autocomplete matching when inside a searchable parent menu */
/** Extra text to match in a searchable parent menu, along with the `children` text. */
value?: string;
}

export interface ContextMenuSubContentProps {
/** Placeholder text for the autocomplete search input
/** Placeholder text and accessible name for the autocomplete search input
* @default "Search..."
*/
searchPlaceholder?: string;
Expand Down
6 changes: 2 additions & 4 deletions apps/www/src/content/docs/components/menu/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ export const autocompleteDemo = {
Default Autocomplete
</Menu.Trigger>
<Menu.Content searchPlaceholder="Search">
<Menu.EmptyState>No results found</Menu.EmptyState>
<Menu.Group>
<Menu.Label>Heading</Menu.Label>
<Menu.Item>Assign member...</Menu.Item>
Expand Down Expand Up @@ -274,10 +275,6 @@ export const linearDemo = {
const renderMenu = (items: MenuItem[], query: string) => {
const filteredItems = filterMenuItems(items, query);

if (searchQuery && filteredItems.length === 0) {
return <div className="no-results">No results</div>;
}

return filteredItems.map((item, index) => {
switch (item.type) {
case "group":
Expand Down Expand Up @@ -335,6 +332,7 @@ export const linearDemo = {
</Menu.Trigger>
<Menu.Content searchPlaceholder="Search">
{renderMenu(menuData, searchQuery)}
<Menu.EmptyState>No results</Menu.EmptyState>
</Menu.Content>
</Menu>
);
Expand Down
32 changes: 17 additions & 15 deletions apps/www/src/content/docs/components/menu/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ To use a router link, pass it to `render`. The item keeps its layout and icons.

Pass `autocomplete` to the Menu root. Each menu manages its own autocomplete state.

- **`autocompleteMode="auto"`** is the default. Items filter as the user types, matched against each item's `value` prop or its children text.
- **`autocompleteMode="auto"`** is the default. Items filter as the user types. Each item matches by its text and, when set, its `value` prop. A `Menu.RadioItem` matches by its text only, because its `value` is the selected value. Matching ignores case and accents.
- **`autocompleteMode="manual"`** leaves filtering to you. Do it in `onInputValueChange`.
- **Submenus** opt in separately: pass `autocomplete` to `Menu.Submenu`.

Expand Down Expand Up @@ -141,7 +141,7 @@ The container that holds the menu items. When autocomplete is enabled, renders a

Individual clickable options within the menu. Built on top of [Base UI Menu.Item](https://base-ui.com/react/components/menu).

Renders as a `role='option'` when used in an autocomplete menu. By default, the item's `children` text content is used for matching, which can be overridden by passing a `value` prop.
In a searchable menu, the item's text is used for matching, including text in `leadingIcon` and `trailingIcon`. Pass a `value` prop to also match other text, such as a keyword. With a `value`, text in the icons no longer matches.

<auto-type-table path="./props.ts" name="MenuItemProps" />

Expand All @@ -159,7 +159,7 @@ Groups related radio items and holds the selected value.

### RadioItem

A menu item that works like a radio button in a `Menu.RadioGroup`. Renders a dot before the label when checked. The `value` prop is also used for search matching when it is a string.
A menu item that works like a radio button in a `Menu.RadioGroup`. Renders a dot before the label when checked.

<auto-type-table path="./props.ts" name="MenuRadioItemProps" />

Expand All @@ -171,13 +171,13 @@ A menu item that renders an `<a>` element. Built on [Base UI Menu.LinkItem](http

### Group

A way to group related menu items together. When filtering is active, the group label is hidden.
A way to group related menu items together. When filtering is active, a group with no matching items is hidden.

<auto-type-table path="./props.ts" name="MenuGroupProps" />

### Label

Renders a label in a menu group. This component should be wrapped with Menu.Group so the `aria-labelledby` is correctly set on the group element. Hidden when filtering is active.
Renders a label in a menu group. This component should be wrapped with Menu.Group so the `aria-labelledby` is correctly set on the group element.

<auto-type-table path="./props.ts" name="MenuLabelProps" />

Expand All @@ -189,7 +189,7 @@ Visual divider between menu items or groups. Hidden when filtering is active.

### EmptyState

Placeholder content when there are no menu items to display.
Placeholder content when there are no menu items to display. In a searchable menu, it shows only when no item matches the search, and screen readers announce it. Render it as a direct child of `Menu.Content`.

<auto-type-table path="./props.ts" name="MenuEmptyStateProps" />

Expand All @@ -205,7 +205,7 @@ Supports its own `autocomplete` prop to enable search functionality within the s

The trigger item for a submenu. Renders with a trailing chevron icon by default. Accepts `leadingIcon` and `trailingIcon` props.

When inside a searchable parent menu, the `value` prop can be used for autocomplete matching.
When inside a searchable parent menu, the trigger matches by its text and, when set, its `value` prop. The root search does not search inside submenus.

<auto-type-table path="./props.ts" name="MenuSubTriggerProps" />

Expand Down Expand Up @@ -250,14 +250,16 @@ Every rendered part carries a stable `data-slot` attribute for [styling and test
- Arrow keys move through items, Enter activates the highlighted item, Escape
closes the menu and returns focus to the trigger, and ArrowRight/ArrowLeft
open and close submenus.
- With `autocomplete`, the popup becomes a `role="dialog"` containing a search
input, items render as `role="option"`, and focus stays in the input while
arrow keys move the highlight, and ArrowRight or Enter opens a highlighted
submenu, and Escape first closes an open submenu.
- With `autocomplete`, the trigger has `aria-haspopup="dialog"` and the popup
becomes a `role="dialog"` containing a search input (`role="searchbox"`,
labelled by `searchPlaceholder`) and a `role="menu"` list. Items keep their
menu roles. Focus stays in the input while arrow keys move the highlight.
ArrowRight or Enter opens a highlighted submenu and moves focus to its first
item, or to its own search input when it is searchable. Escape closes the
submenu and returns focus to the parent input.
- `Menu.Label` must be wrapped in `Menu.Group` so the group is labelled via
`aria-labelledby`.
- `Menu.CheckboxItem` uses `role="menuitemcheckbox"` and `Menu.RadioItem` uses
`role="menuitemradio"`, both with `aria-checked`. In a searchable menu they
render as `role="option"` and keep `aria-checked`.
- `Menu.LinkItem` renders an `<a>` with `role="menuitem"`, or `role="option"`
in a searchable menu.
`role="menuitemradio"`, both with `aria-checked`.
- `Menu.LinkItem` renders an `<a>` with `role="menuitem"`.
- In a searchable menu, `Menu.EmptyState` is a `role="status"` live region.
14 changes: 7 additions & 7 deletions apps/www/src/content/docs/components/menu/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export interface MenuTriggerProps {
}

export interface MenuContentProps {
/** Placeholder text for the autocomplete search input
/** Placeholder text and accessible name for the autocomplete search input
* @default "Search..."
*/
searchPlaceholder?: string;
Expand Down Expand Up @@ -104,7 +104,7 @@ export interface MenuItemProps {
/** Whether the item is disabled */
disabled?: boolean;

/** Value of the item used for autocomplete matching. If not provided, `children` text content is used. */
/** Extra text to match in a searchable menu, along with the `children` text. */
value?: string;

/** Additional CSS class names */
Expand Down Expand Up @@ -140,7 +140,7 @@ export interface MenuCheckboxItemProps {
/** Whether the item is disabled */
disabled?: boolean;

/** Value of the item used for autocomplete matching. If not provided, `children` text content is used. */
/** Extra text to match in a searchable menu, along with the `children` text. */
value?: string;

/** Additional CSS class names */
Expand Down Expand Up @@ -170,7 +170,7 @@ export interface MenuRadioGroupProps {
}

export interface MenuRadioItemProps {
/** Value of the radio item. Also used for autocomplete matching when it is a string. */
/** Value of the radio item. */
value: unknown;

/** Whether to close the menu when the item is clicked
Expand Down Expand Up @@ -209,7 +209,7 @@ export interface MenuLinkItemProps {
/** Icon element to display after item text */
trailingIcon?: ReactNode;

/** Value of the item used for autocomplete matching. If not provided, `children` text content is used. */
/** Extra text to match in a searchable menu, along with the `children` text. */
value?: string;

/** Additional CSS class names */
Expand Down Expand Up @@ -285,12 +285,12 @@ export interface MenuSubTriggerProps {
/** Icon element to display after trigger text. Defaults to a chevron right icon. */
trailingIcon?: ReactNode;

/** Value used for autocomplete matching when inside a searchable parent menu */
/** Extra text to match in a searchable parent menu, along with the `children` text. */
value?: string;
}

export interface MenuSubContentProps {
/** Placeholder text for the autocomplete search input
/** Placeholder text and accessible name for the autocomplete search input
* @default "Search..."
*/
searchPlaceholder?: string;
Expand Down
1 change: 1 addition & 0 deletions apps/www/src/content/docs/components/menubar/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export const autocompleteDemo = {
<Menu autocomplete>
<Menu.Trigger>Actions</Menu.Trigger>
<Menu.Content searchPlaceholder="Search actions...">
<Menu.EmptyState>No results found</Menu.EmptyState>
<Menu.Group>
<Menu.Label>General</Menu.Label>
<Menu.Item>Assign member...</Menu.Item>
Expand Down
Loading