Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a9a5443
Use public react-aria-components API for virtualization
emmatown Aug 5, 2026
161f3be
Use React Aria Components for buttons and links
emmatown Aug 6, 2026
67d970e
Use React Aria Components for basic controls
emmatown Aug 6, 2026
6cf7be2
Use React Aria Components for choice fields
emmatown Aug 6, 2026
84ba321
Use React Aria Components for text entry fields
emmatown Aug 6, 2026
2d3555f
Compose picker fields with React Aria Components
emmatown Aug 6, 2026
0409d82
Use React Aria Components for segmented date fields
emmatown Aug 6, 2026
55765e4
Use React Aria Components for calendars
emmatown Aug 6, 2026
cf7e7f6
Use React Aria Components for date pickers
emmatown Aug 6, 2026
9d4afac
Use React Aria Components for tooltips
emmatown Aug 6, 2026
125b531
Use React Aria Components for menu triggers
emmatown Aug 6, 2026
19e9176
Use React Aria Components for overlays and dialogs
emmatown Aug 6, 2026
55777a8
Use React Aria Components for editor toolbars
emmatown Aug 6, 2026
0dcc171
Use a React Aria Components toolbar in action bars
emmatown Aug 6, 2026
f11ee0d
Use React Aria Components for toast regions
emmatown Aug 6, 2026
61ade5a
Finish React Aria Components compatibility cleanup
emmatown Aug 6, 2026
1a6b999
Use React Aria Components for standalone action groups
emmatown Aug 6, 2026
4e3a11e
Improve React Aria Components action groups
emmatown Aug 10, 2026
4455686
Add collapsing React Aria Components breadcrumbs
emmatown Aug 10, 2026
61e8781
Finish React Aria Components compatibility cleanup
emmatown Aug 10, 2026
4db631b
fixes
emmatown Aug 11, 2026
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
7 changes: 7 additions & 0 deletions .changeset/modern-collections.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@keystar/ui': minor
---

Replace the legacy collection compatibility APIs with explicit React Aria Components collection composition across list boxes, list views, tables, menus, tabs, tags, pickers, combo boxes, navigation trees, action groups, and editor autocomplete.

Collection children now use component-specific names such as `MenuItem`, `ListBoxItem`, `TableRow`, `Tab`, and `Tag`, with explicit collection, section, header, and load-more components where applicable.
6 changes: 3 additions & 3 deletions design-system/docs/components/theme-switcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { monitorIcon } from '@keystar/ui/icon/icons/monitorIcon';
import { moonIcon } from '@keystar/ui/icon/icons/moonIcon';
import { sunIcon } from '@keystar/ui/icon/icons/sunIcon';
import { Icon } from '@keystar/ui/icon';
import { MenuTrigger, Menu, Item } from '@keystar/ui/menu';
import { MenuTrigger, Menu, MenuItem } from '@keystar/ui/menu';
import { useRootColorScheme } from '@keystar/ui/next';

import { SCHEME_AUTO, SCHEME_DARK, SCHEME_LIGHT } from '@keystar/ui/primitives';
Expand Down Expand Up @@ -48,10 +48,10 @@ export function ColorSchemeMenu() {
selectionMode="single"
>
{item => (
<Item key={item.key} textValue={item.label}>
<MenuItem id={item.key} textValue={item.label}>
<Icon src={item.icon} />
<Text>{item.label}</Text>
</Item>
</MenuItem>
)}
</Menu>
</MenuTrigger>
Expand Down
8 changes: 5 additions & 3 deletions design-system/pkg/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1482,14 +1482,15 @@
"@emotion/sheet": "^1.4.0",
"@emotion/utils": "^1.4.2",
"@floating-ui/react": "^0.24.0",
"@internationalized/date": "^3.12.2",
"@internationalized/date": "^3.12.3",
"@internationalized/number": "^3.6.7",
"@internationalized/string": "^3.2.9",
"@react-types/shared": "^3.36.0",
"@internationalized/string": "^3.2.10",
"@react-types/shared": "^3.36.1",
"@types/react": "^19.0.8",
"emery": "^1.4.1",
"facepaint": "^1.2.1",
"react-aria": "catalog:",
"react-aria-components": "catalog:",
"react-stately": "catalog:"
},
"devDependencies": {
Expand All @@ -1515,6 +1516,7 @@
"next": ">=14",
"react": "^18.2.0 || ^19.0.0",
"react-aria": "catalog:",
"react-aria-components": "catalog:",
"react-dom": "^18.2.0 || ^19.0.0",
"react-stately": "catalog:"
},
Expand Down
37 changes: 20 additions & 17 deletions design-system/pkg/src/action-bar/ActionBar.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { FocusScope } from 'react-aria/FocusScope';
import { announce } from 'react-aria/private/live-announcer/LiveAnnouncer';
import { useLocalizedStringFormatter } from 'react-aria/useLocalizedStringFormatter';
import { useKeyboard } from 'react-aria/useKeyboard';
import { filterDOMProps } from 'react-aria/filterDOMProps';
import { useObjectRef } from 'react-aria/useObjectRef';
import { Toolbar } from 'react-aria-components/Toolbar';
import React, {
ForwardedRef,
KeyboardEvent,
ReactElement,
Ref,
useEffect,
Expand Down Expand Up @@ -46,11 +47,11 @@ function ActionBar<T extends object>(
);
}

interface ActionBarInnerProps<T> extends ActionBarProps<T> {
interface ActionBarInnerProps<T extends object> extends ActionBarProps<T> {
isOpen?: boolean;
}

function ActionBarInner<T>(
function ActionBarInner<T extends object>(
props: ActionBarInnerProps<T>,
ref: Ref<HTMLDivElement>
) {
Expand Down Expand Up @@ -78,14 +79,12 @@ function ActionBarInner<T>(
setLastCount(selectedItemCount);
}

let { keyboardProps } = useKeyboard({
onKeyDown(e) {
if (e.key === 'Escape') {
e.preventDefault();
onClearSelection();
}
},
});
let onKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
event.preventDefault();
onClearSelection();
}
};

// Announce "actions available" on mount.
let isInitial = useRef(true);
Expand All @@ -102,7 +101,7 @@ function ActionBarInner<T>(
<div
{...filterDOMProps(props)}
{...styleProps}
{...keyboardProps}
onKeyDown={onKeyDown}
data-open={isOpen}
ref={ref}
className={classNames(
Expand All @@ -124,7 +123,8 @@ function ActionBarInner<T>(
styleProps.className
)}
>
<div
<Toolbar
aria-label={stringFormatter.format('actions')}
data-open={isOpen}
className={classNames(
css({
Expand Down Expand Up @@ -155,10 +155,11 @@ function ActionBarInner<T>(
)}
>
<ActionGroup
role="group"
items={items}
aria-label={stringFormatter.format('actions')}
prominence="low"
overflowMode="collapse"
overflowMode={items ? 'collapse' : 'wrap'}
buttonLabelBehavior={buttonLabelBehavior}
onAction={onAction}
gridArea="actiongroup"
Expand All @@ -183,21 +184,23 @@ function ActionBarInner<T>(
stringFormatter.format('selected', { count: lastCount })
*/}
</Text>
</div>
</Toolbar>
</div>
</FocusScope>
);
}

const ActionBarInnerWithRef = React.forwardRef(ActionBarInner) as <T>(
const ActionBarInnerWithRef = React.forwardRef(ActionBarInner) as <
T extends object,
>(
props: ActionBarInnerProps<T> & { ref?: Ref<HTMLDivElement> }
) => ReactElement;

/**
* Action bars are used for single and bulk selection patterns when a user needs
* to perform actions on one or more items at the same time.
*/
const _ActionBar: <T>(
const _ActionBar: <T extends object>(
props: ActionBarProps<T> & { ref?: ForwardedRef<HTMLDivElement> }
) => ReactElement = React.forwardRef(ActionBar as any) as any;

Expand Down
2 changes: 1 addition & 1 deletion design-system/pkg/src/action-bar/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

export { Item } from 'react-stately/Item';
export { ActionGroupItem as ActionBarItem } from '@keystar/ui/action-group';

export { ActionBar } from './ActionBar';
export { ActionBarContainer } from './ActionBarContainer';
Expand Down
25 changes: 15 additions & 10 deletions design-system/pkg/src/action-bar/stories/ListExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ import { Icon } from '@keystar/ui/icon';
import { copyIcon } from '@keystar/ui/icon/icons/copyIcon';
import { pencilIcon } from '@keystar/ui/icon/icons/pencilIcon';
import { trash2Icon } from '@keystar/ui/icon/icons/trash2Icon';
import { Item, ListView } from '@keystar/ui/list-view';
import { ListViewItem, ListView } from '@keystar/ui/list-view';
import { Text } from '@keystar/ui/typography';
import { Selection } from '@react-types/shared';
import React, { useState } from 'react';

import { ActionBar, ActionBarContainer, ActionBarProps } from '../index';
import {
ActionBar,
ActionBarContainer,
ActionBarItem,
ActionBarProps,
} from '../index';

export const ListExample = (props: Partial<ActionBarProps<any>>) => {
let [selectedKeys, setSelectedKeys] = useState<Selection>(new Set());
Expand All @@ -22,28 +27,28 @@ export const ListExample = (props: Partial<ActionBarProps<any>>) => {
selectedKeys={selectedKeys}
>
{(item: any) => (
<Item key={item.key} textValue={item.name}>
<ListViewItem id={item.key} textValue={item.name}>
<Text>{item.name}</Text>
</Item>
</ListViewItem>
)}
</ListView>
<ActionBar
selectedItemCount={selectedKeys === 'all' ? 'all' : selectedKeys.size}
onClearSelection={() => setSelectedKeys(new Set())}
{...props}
>
<Item key="edit" textValue="Edit">
<ActionBarItem id="edit" textValue="Edit">
<Icon src={pencilIcon} />
<Text>Edit</Text>
</Item>
<Item key="copy" textValue="Copy">
</ActionBarItem>
<ActionBarItem id="copy" textValue="Copy">
<Icon src={copyIcon} />
<Text>Copy</Text>
</Item>
<Item key="delete" textValue="Delete">
</ActionBarItem>
<ActionBarItem id="delete" textValue="Delete">
<Icon src={trash2Icon} />
<Text>Delete</Text>
</Item>
</ActionBarItem>
</ActionBar>
</ActionBarContainer>
);
Expand Down
2 changes: 1 addition & 1 deletion design-system/pkg/src/action-bar/test/ActionBar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe('action-bar/ActionBar', () => {
expect(toolbar).toHaveAttribute('aria-label', 'Actions');

let buttons = within(toolbar).getAllByRole('button');
expect(buttons).toHaveLength(3);
expect(buttons).toHaveLength(4);

expect(tree.getByText('1 selected')).toBeInTheDocument();

Expand Down
11 changes: 6 additions & 5 deletions design-system/pkg/src/action-bar/types.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { BaseStyleProps } from '@keystar/ui/style';
import { DOMProps, ItemProps, Key } from '@react-types/shared';
import { DOMProps, Key } from '@react-types/shared';
import { ReactElement, ReactNode } from 'react';
import type { ActionGroupItemProps } from '@keystar/ui/action-group';

type ItemElement<T> = ReactElement<ItemProps<T>> | null;
type ItemElement = ReactElement<ActionGroupItemProps> | null;

export type ActionBarProps<T> = {
children: ItemElement<T> | ItemElement<T>[] | ((item: T) => ItemElement<T>);
export type ActionBarProps<T extends object> = {
children: ItemElement | ItemElement[] | ((item: T) => ItemElement);
items?: Iterable<T>;
disabledKeys?: Iterable<Key>;
selectedItemCount: number | 'all';
onClearSelection: () => void;
isEmphasized?: boolean;
onAction?: (key: Key) => void;
buttonLabelBehavior?: 'show' | 'collapse' | 'hide';
buttonLabelBehavior?: 'show' | 'hide';
} & DOMProps &
BaseStyleProps;

Expand Down
Loading
Loading