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
10 changes: 3 additions & 7 deletions packages/@react-spectrum/s2/src/TableView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,7 @@ function CellFocusRing() {
className={style({
...cellFocus,
position: 'absolute',
top: 'var(--topFocusRing)',
top: 'var(--topFocusRing, 0)',
bottom: 0,
insetStart: 0,
insetEnd: 0,
Expand Down Expand Up @@ -1216,12 +1216,8 @@ export const TableHeader = /*#__PURE__*/ (forwardRef as forwardRefType)(function
className={selectAllCheckboxColumn({isQuiet})}>
{({isFocusVisible}) => (
<>
{selectionMode === 'single' && (
<>
{isFocusVisible && <CellFocusRing />}
<VisuallyHiddenSelectAllLabel />
</>
)}
{isFocusVisible && <CellFocusRing />}
{selectionMode === 'single' && <VisuallyHiddenSelectAllLabel />}
{selectionMode === 'multiple' && (
<Checkbox styles={selectAllCheckbox} slot="selection" />
)}
Expand Down
1 change: 1 addition & 0 deletions packages/@react-spectrum/s2/src/TreeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export interface TreeViewProps<T>
| 'selectionBehavior'
| 'onScroll'
| 'onCellAction'
| 'keyboardNavigationBehavior'
| keyof GlobalDOMAttributes
>,
UnsafeStyles,
Expand Down
42 changes: 36 additions & 6 deletions packages/@react-spectrum/s2/stories/CardView.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {MenuItem} from '../src/Menu';
import type {Meta, StoryObj} from '@storybook/react';
import {SkeletonCollection} from '../src/SkeletonCollection';
import {style} from '../style/spectrum-theme' with {type: 'macro'};
import {TextField} from '../src/TextField';
import {useAsyncList} from 'react-stately/useAsyncList';

const meta: Meta<typeof CardView> = {
Expand Down Expand Up @@ -72,7 +73,15 @@ const avatarSize = {
XL: 32
} as const;

export function PhotoCard({item, layout}: {item: Item; layout: string}) {
export function PhotoCard({
item,
layout,
interactive
}: {
item: Item;
layout: string;
interactive?: React.ReactNode;
}) {
return (
<Card id={item.id} textValue={item.description || item.alt_description}>
{({size}) => (
Expand Down Expand Up @@ -112,12 +121,20 @@ export function PhotoCard({item, layout}: {item: Item; layout: string}) {
<div
className={style({
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
gap: 8,
gridArea: 'description'
})}>
<Avatar src={item.user.profile_image.small} size={avatarSize[size]} />
<Text slot="description">{item.user.name}</Text>
<div
className={style({
display: 'flex',
alignItems: 'center',
gap: 8
})}>
<Avatar src={item.user.profile_image.small} size={avatarSize[size]} />
<Text slot="description">{item.user.name}</Text>
</div>
{interactive}
</div>
</Content>
</>
Expand All @@ -126,7 +143,7 @@ export function PhotoCard({item, layout}: {item: Item; layout: string}) {
);
}

export const ExampleRender = (args: CardViewProps<any>) => {
export const ExampleRender = (args: CardViewProps<any> & {interactive?: React.ReactNode}) => {
let list = useAsyncList<Item, number | null>({
async load({signal, cursor, items}) {
let page = cursor || 1;
Expand Down Expand Up @@ -155,7 +172,9 @@ export const ExampleRender = (args: CardViewProps<any>) => {
onLoadMore={args.loadingState === 'idle' ? list.loadMore : undefined}
styles={cardViewStyles}>
<Collection items={items} dependencies={[args.layout]}>
{item => <PhotoCard item={item} layout={args.layout || 'grid'} />}
{item => (
<PhotoCard interactive={args.interactive} item={item} layout={args.layout || 'grid'} />
)}
</Collection>
{(loadingState === 'loading' || loadingState === 'loadingMore') && (
<SkeletonCollection>
Expand Down Expand Up @@ -288,3 +307,14 @@ export const CollectionCards: Story = {
onAction: undefined
}
};

export const CardViewWithTextField: Story = {
render: args => (
<ExampleRender {...args} interactive={<TextField aria-label="search photos" />} />
),
args: {
loadingState: 'idle',
onAction: undefined,
selectionMode: 'multiple'
}
};
52 changes: 51 additions & 1 deletion packages/dev/s2-docs/pages/react-aria/GridList.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@ function Example(props) {

Use the `layout` and `orientation` props to arrange items in horizontal and vertical stacks and grids. This affects keyboard navigation and drag and drop behavior.

```tsx render docs={docs.exports.GridList} links={docs.links} props={['layout', 'orientation', 'keyboardNavigationBehavior']} initialProps={{layout: 'grid', orientation: 'horizontal', keyboardNavigationBehavior: 'tab'}} wide
```tsx render docs={docs.exports.GridList} links={docs.links} props={['layout', 'orientation']} initialProps={{layout: 'grid', orientation: 'horizontal'}} wide
"use client";
import {GridList, GridListItem, Text} from 'vanilla-starter/GridList';

Expand Down Expand Up @@ -704,6 +704,56 @@ let photos = [
</GridList>
```

## Keyboard navigation

By default, GridList uses arrow key navigation to move focus into rows. Set `keyboardNavigationBehavior="tab"` to have <Keyboard>Tab</Keyboard> move focus in and out of a row.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should include a blurb about when you'd be likely to want to change the keyboard navigation behavior, ie you have something like a textfield or something else that would conflict with arrow key navigation

Use this when rows contain interactive elements such as text fields, where arrow keys and typing in the field should not trigger grid navigation or selection.

```tsx render
"use client";
import {GridList, GridListItem, Text} from 'vanilla-starter/GridList';
import {ComboBox, ComboBoxItem} from 'vanilla-starter/ComboBox';

///- begin collapse -///
///- begin collapse -///
let photos = [
{id: 1, title: 'Desert Sunset', description: 'PNG • 2/3/2024', src: 'https://images.unsplash.com/photo-1705034598432-1694e203cdf3?q=80&w=600&auto=format&fit=crop'},
{id: 2, title: 'Hiking Trail', description: 'JPEG • 1/10/2022', src: 'https://images.unsplash.com/photo-1722233987129-61dc344db8b6?q=80&w=600&auto=format&fit=crop'},
{id: 3, title: 'Lion', description: 'JPEG • 8/28/2021', src: 'https://images.unsplash.com/photo-1629812456605-4a044aa38fbc?q=80&w=600&auto=format&fit=crop'},
{id: 4, title: 'Mountain Sunrise', description: 'PNG • 3/15/2015', src: 'https://images.unsplash.com/photo-1722172118908-1a97c312ce8c?q=80&w=600&auto=format&fit=crop'},
{id: 5, title: 'Giraffe tongue', description: 'PNG • 11/27/2019', src: 'https://images.unsplash.com/photo-1574870111867-089730e5a72b?q=80&w=600&auto=format&fit=crop'},
{id: 6, title: 'Golden Hour', description: 'WEBP • 7/24/2024', src: 'https://images.unsplash.com/photo-1718378037953-ab21bf2cf771?q=80&w=600&auto=format&fit=crop'},
];

function PermissionPicker({label}) {
return (
<ComboBox style={{paddingInlineStart: 12, width: '80%'}} aria-label={label} defaultSelectedKey="view" placeholder="Permission">
<ComboBoxItem id="view">Can view</ComboBoxItem>
<ComboBoxItem id="comment">Can comment</ComboBoxItem>
<ComboBoxItem id="edit">Can edit</ComboBoxItem>
</ComboBox>
);
}
///- end collapse -///

<GridList
/*- begin highlight -*/
keyboardNavigationBehavior="tab"
/*- end highlight -*/
items={photos}
selectionMode="multiple"
aria-label="Shared files">
{item => (
<GridListItem textValue={item.title}>
<img src={item.src} alt="" />
<Text>{item.title}</Text>
<Text slot="description">{item.description}</Text>
<PermissionPicker label={`${item.title} permission`} />
</GridListItem>
)}
</GridList>
```

## Drag and drop

GridList supports drag and drop interactions when the `dragAndDropHooks` prop is provided using the <TypeLink links={docs.links} type={docs.exports.useDragAndDrop} /> hook. Users can drop data on the list as a whole, on individual items, insert new items between existing ones, or reorder items. React Aria supports drag and drop via mouse, touch, keyboard, and screen reader interactions. See the [drag and drop guide](dnd?component=GridList) to learn more.
Expand Down
63 changes: 62 additions & 1 deletion packages/dev/s2-docs/pages/react-aria/Table.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ function FileTable() {
<Row columns={visibleColumns}>
{column => (
<Cell>
{column.id === 'price'
{column.id === 'price'
? item.price.toLocaleString('en-US', {style: 'currency', currency: 'USD', maximumFractionDigits: 0})
: item[column.id]}
</Cell>
Expand Down Expand Up @@ -725,6 +725,67 @@ function subscribe(fn) {
}
```

## Keyboard navigation

By default, Table uses arrow key navigation to move focus into cells. Set `keyboardNavigationBehavior="tab"` to have <Keyboard>Tab</Keyboard> move focus in and out of a cell.
Use this when cells contain interactive elements such as text fields, where arrow keys and typing in the field should not trigger grid navigation or selection.

```tsx render
"use client";
import {Table, TableHeader, Column, Row, TableBody, Cell} from 'vanilla-starter/Table';
import {ComboBox, ComboBoxItem} from 'vanilla-starter/ComboBox';

function PermissionPicker({label}) {
return (
<ComboBox aria-label={label} defaultSelectedKey="view" placeholder="Permission">
<ComboBoxItem id="view">Can view</ComboBoxItem>
<ComboBoxItem id="comment">Can comment</ComboBoxItem>
<ComboBoxItem id="edit">Can edit</ComboBoxItem>
</ComboBox>
);
}

<Table
/*- begin highlight -*/
keyboardNavigationBehavior="tab"
/*- end highlight -*/
selectionMode="multiple"
aria-label="Shared files">
<TableHeader>
<Column id="name" isRowHeader>Name</Column>
<Column id="type">Type</Column>
<Column id="date">Date Modified</Column>
<Column id="permission">Permission</Column>
</TableHeader>
<TableBody>
<Row id="games">
<Cell>Games</Cell>
<Cell>Folder</Cell>
<Cell>6/7/2023</Cell>
<Cell><PermissionPicker label="Games permission" /></Cell>
</Row>
<Row id="apps">
<Cell>Applications</Cell>
<Cell>Folder</Cell>
<Cell>4/7/2025</Cell>
<Cell><PermissionPicker label="Applications permission" /></Cell>
</Row>
<Row id="report">
<Cell>2024 Financial Report</Cell>
<Cell>PDF Document</Cell>
<Cell>12/30/2024</Cell>
<Cell><PermissionPicker label="2024 Financial Report permission" /></Cell>
</Row>
<Row id="job">
<Cell>Job Posting</Cell>
<Cell>Text Document</Cell>
<Cell>1/18/2025</Cell>
<Cell><PermissionPicker label="Job Posting permission" /></Cell>
</Row>
</TableBody>
</Table>
```

## Drag and drop

Table supports drag and drop interactions when the `dragAndDropHooks` prop is provided using the <TypeLink links={docs.links} type={docs.exports.useDragAndDrop} /> hook. Users can drop data on the table as a whole, on individual rows, insert new rows between existing ones, or reorder rows. React Aria supports drag and drop via mouse, touch, keyboard, and screen reader interactions. See the [drag and drop guide](dnd?component=Table) to learn more.
Expand Down
57 changes: 56 additions & 1 deletion packages/dev/s2-docs/pages/react-aria/Tree.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ import {Tree, TreeHeader, TreeItem, TreeSection} from 'vanilla-starter/Tree';
<TreeItem id="shared-photos" title="Shared Photos">
<TreeItem id="shared-photo-1" title="Shared Photo 1" />
<TreeItem id="shared-photo-2" title="Shared Photo 2" />
</TreeItem>
</TreeItem>
</TreeSection>
<TreeSection>
<TreeHeader>Documents</TreeHeader>
Expand Down Expand Up @@ -322,6 +322,61 @@ function Example(props) {
}
```

## Keyboard navigation

By default, Tree uses arrow key navigation to move focus into rows. Set `keyboardNavigationBehavior="tab"` to have <Keyboard>Option</Keyboard> move focus in and out of a row.
Use this when rows contain interactive elements such as text fields, where arrow keys and typing in the field should not trigger grid navigation or selection.

```tsx render
"use client";
import {Tree, TreeItem, TreeItemContent} from 'vanilla-starter/Tree';
import {ComboBox, ComboBoxItem} from 'vanilla-starter/ComboBox';

///- begin collapse -///
function PermissionPicker({label}) {
return (
<ComboBox style={{marginInlineStart: 'auto', flexShrink: 0}} aria-label={label} defaultSelectedKey="view" placeholder="Permission">
<ComboBoxItem id="view">Can view</ComboBoxItem>
<ComboBoxItem id="comment">Can comment</ComboBoxItem>
<ComboBoxItem id="edit">Can edit</ComboBoxItem>
</ComboBox>
);
}
///- end collapse -///

<Tree
/*- begin highlight -*/
keyboardNavigationBehavior="tab"
/*- end highlight -*/
selectionMode="multiple"
defaultExpandedKeys={['documents', 'photos']}
aria-label="Shared files"
style={{width: 420}}>
<TreeItem id="documents" title="Documents">
<TreeItem id="weekly" textValue="Weekly Report.pdf">
<TreeItemContent>
Weekly Report.pdf
<PermissionPicker label="Weekly Report.pdf permission" />
</TreeItemContent>
</TreeItem>
<TreeItem id="budget" textValue="Budget.xlsx">
<TreeItemContent>
Budget.xlsx
<PermissionPicker label="Budget.xlsx permission" />
</TreeItemContent>
</TreeItem>
</TreeItem>
<TreeItem id="photos" title="Photos">
<TreeItem id="sunset" textValue="Sunset.jpg">
<TreeItemContent>
Sunset.jpg
<PermissionPicker label="Sunset.jpg permission" />
</TreeItemContent>
</TreeItem>
</TreeItem>
</Tree>
```

## Drag and drop

Tree supports drag and drop interactions when the `dragAndDropHooks` prop is provided using the <TypeLink links={docs.links} type={docs.exports.useDragAndDrop} /> hook. Users can drop data on the list as a whole, on individual items, insert new items between existing ones, or reorder items. React Aria supports drag and drop via mouse, touch, keyboard, and screen reader interactions. See the [drag and drop guide](dnd?component=Tree) to learn more.
Expand Down
62 changes: 62 additions & 0 deletions packages/dev/s2-docs/pages/s2/TableView.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,68 @@ function subscribe(fn) {
}
```

## Keyboard navigation

By default, TableView uses arrow key navigation to move focus into cells. Set `keyboardNavigationBehavior="tab"` to have <Keyboard>Tab</Keyboard> move focus in and out of a cell.

```tsx render type="s2"
"use client";
import {TableView, TableHeader, Column, TableBody, Row, Cell} from '@react-spectrum/s2/TableView';
import {ComboBox, ComboBoxItem} from '@react-spectrum/s2/ComboBox';
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};

function PermissionPicker({label}) {
return (
<ComboBox aria-label={label} defaultSelectedKey="view" placeholder="Permission">
<ComboBoxItem id="view">Can view</ComboBoxItem>
<ComboBoxItem id="comment">Can comment</ComboBoxItem>
<ComboBoxItem id="edit">Can edit</ComboBoxItem>
</ComboBox>
);
}

<TableView
/*- begin highlight -*/
keyboardNavigationBehavior="tab"
/*- end highlight -*/
selectionMode="multiple"
aria-label="Shared files"
styles={style({width: 'full'})}>
<TableHeader>
<Column id="name" isRowHeader>Name</Column>
<Column id="type">Type</Column>
<Column id="date">Date Modified</Column>
<Column id="permission">Permission</Column>
</TableHeader>
<TableBody>
<Row id="games">
<Cell>Games</Cell>
<Cell>Folder</Cell>
<Cell>6/7/2023</Cell>
<Cell><PermissionPicker label="Games permission" /></Cell>
</Row>
<Row id="apps">
<Cell>Applications</Cell>
<Cell>Folder</Cell>
<Cell>4/7/2025</Cell>
<Cell><PermissionPicker label="Applications permission" /></Cell>
</Row>
<Row id="report">
<Cell>2024 Financial Report</Cell>
<Cell>PDF Document</Cell>
<Cell>12/30/2024</Cell>
<Cell><PermissionPicker label="2024 Financial Report permission" /></Cell>
</Row>
<Row id="job">
<Cell>Job Posting</Cell>
<Cell>Text Document</Cell>
<Cell>1/18/2025</Cell>
<Cell><PermissionPicker label="Job Posting permission" /></Cell>
</Row>
</TableBody>
</TableView>
```

## Drag and drop

Table supports drag and drop interactions when the `dragAndDropHooks` prop is provided using the <TypeLink links={docs.links} type={docs.exports.useDragAndDrop} /> hook. Users can drop data on the table as a whole, on individual rows, insert new rows between existing ones, or reorder rows. See the [drag and drop guide](dnd?component=TableView) to learn more.
Expand Down
Loading