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
21 changes: 21 additions & 0 deletions packages/@adobe/react-spectrum/docs/table/TableView.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,27 @@ The example below applies `isRowHeader` to the "First Name" and "Last Name" colu
</TableView>
```

When `selectionMode` is `"multiple"`, the select all checkbox in the table header defaults to an accessible label of "Select All". Use the `selectAllLabel` prop to provide a more descriptive label, which is especially useful when a page has multiple TableViews and the default label wouldn't let assistive technology users distinguish between them.

```tsx example
<TableView aria-label="Items" selectionMode="multiple" selectAllLabel="Select all items">
<TableHeader>
<Column isRowHeader>Name</Column>
<Column align="end">Price</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>Keyboard</Cell>
<Cell>$50</Cell>
</Row>
<Row>
<Cell>Mouse</Cell>
<Cell>$25</Cell>
</Row>
</TableBody>
</TableView>
```

## Asynchronous loading
TableView supports loading data asynchronously, and will display a progress circle reflecting the current load state,
set by the `loadingState` prop. It also supports infinite scrolling to load more data on demand as the user scrolls, via the `onLoadMore` prop.
Expand Down
5 changes: 5 additions & 0 deletions packages/@adobe/react-spectrum/src/table/TableView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ export interface SpectrumTableProps<T>
isQuiet?: boolean;
/** Sets what the TableView should render when there is no content to display. */
renderEmptyState?: () => JSX.Element;
/**
* A custom accessibility label for the select all checkbox in the table header.
* If not provided, defaults to the standard localized "Select All" label.
*/
selectAllLabel?: string;
/**
* Whether `disabledKeys` applies to all interactions, or only selection.
*
Expand Down
9 changes: 6 additions & 3 deletions packages/@adobe/react-spectrum/src/table/TableViewBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export interface TableContextValue<T> {
headerMenuOpen: boolean;
setHeaderMenuOpen: (val: boolean) => void;
renderEmptyState?: () => ReactElement;
selectAllLabel?: string;
}

export const TableContext = React.createContext<TableContextValue<unknown> | null>(null);
Expand Down Expand Up @@ -193,6 +194,7 @@ function TableViewBase<T extends object>(props: TableBaseProps<T>, ref: DOMRef<H
onResizeStart: propsOnResizeStart,
onResizeEnd: propsOnResizeEnd,
dragAndDropHooks,
selectAllLabel,
state
} = props;
let isTableDraggable = !!dragAndDropHooks?.useDraggableCollectionState;
Expand Down Expand Up @@ -525,7 +527,8 @@ function TableViewBase<T extends object>(props: TableBaseProps<T>, ref: DOMRef<H
onFocusedResizer,
headerMenuOpen,
setHeaderMenuOpen,
renderEmptyState: props.renderEmptyState
renderEmptyState: props.renderEmptyState,
selectAllLabel
}}>
<TableVirtualizer
{...mergedProps}
Expand Down Expand Up @@ -1148,7 +1151,7 @@ function ResizableTableColumnHeader(props) {

function TableSelectAllCell({column}) {
let ref = useRef<HTMLDivElement | null>(null);
let {state} = useTableContext();
let {state, selectAllLabel} = useTableContext();
let isSingleSelectionMode = state.selectionManager.selectionMode === 'single';
let {columnHeaderProps} = useTableColumnHeader(
{
Expand All @@ -1159,7 +1162,7 @@ function TableSelectAllCell({column}) {
ref
);

let {checkboxProps} = useTableSelectAllCheckbox(state);
let {checkboxProps} = useTableSelectAllCheckbox(state, {'aria-label': selectAllLabel});
let {hoverProps, isHovered} = useHover({});

return (
Expand Down
38 changes: 38 additions & 0 deletions packages/@adobe/react-spectrum/stories/table/Table.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,44 @@ export const CustomRowHeaderLabeling: TableStory = {
}
};

export const CustomSelectAllLabel: TableStory = {
args: {
'aria-label': 'Items',
selectionMode: 'multiple',
selectAllLabel: 'Select all items',
width: 500,
height: 200
},
render: args => (
<TableView {...args}>
<TableHeader>
<Column key="foo">Foo</Column>
<Column key="bar">Bar</Column>
<Column key="baz">Baz</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>One</Cell>
<Cell>Two</Cell>
<Cell>Three</Cell>
</Row>
<Row>
<Cell>One</Cell>
<Cell>Two</Cell>
<Cell>Three</Cell>
</Row>
</TableBody>
</TableView>
),
name: 'custom select all labeling',
parameters: {
description: {
content:
'Overrides the accessible label of the select all checkbox in the table header via selectAllLabel.'
}
}
};

export const CRUD: TableStory = {
render: args => <CRUDExample {...args} />,
name: 'CRUD'
Expand Down
36 changes: 36 additions & 0 deletions packages/@adobe/react-spectrum/test/table/TableTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,42 @@ export let tableTests = () => {
expect(cells[5]).toHaveAttribute('aria-colindex', '4');
});

it('defaults the select all checkbox aria-label to "Select All" when selectAllLabel is not provided', function () {
let {getAllByRole} = render(
<TableView aria-label="Items" selectionMode="multiple">
<TableHeader>
<Column>Foo</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>Foo 1</Cell>
</Row>
</TableBody>
</TableView>
);

let checkbox = getAllByRole('checkbox')[0];
expect(checkbox).toHaveAttribute('aria-label', 'Select All');
});

it('allows the select all checkbox aria-label to be overridden via selectAllLabel', function () {
let {getAllByRole} = render(
<TableView aria-label="Items" selectAllLabel="Select all items" selectionMode="multiple">
<TableHeader>
<Column>Foo</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>Foo 1</Cell>
</Row>
</TableBody>
</TableView>
);

let checkbox = getAllByRole('checkbox')[0];
expect(checkbox).toHaveAttribute('aria-label', 'Select all items');
});

it('accepts a UNSAFE_className', function () {
let {getByRole} = render(
<TableView
Expand Down
1 change: 1 addition & 0 deletions packages/@react-aria/table/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type {
AriaTableCellProps,
TableCellAria,
TableHeaderRowAria,
AriaTableSelectAllCheckboxProps,
AriaTableSelectionCheckboxProps,
TableSelectionCheckboxAria,
TableSelectAllCheckboxAria,
Expand Down
1 change: 1 addition & 0 deletions packages/react-aria/exports/useTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export type {
export type {AriaTableCellProps, TableCellAria} from '../src/table/useTableCell';
export type {TableHeaderRowAria} from '../src/table/useTableHeaderRow';
export type {
AriaTableSelectAllCheckboxProps,
AriaTableSelectionCheckboxProps,
TableSelectionCheckboxAria,
TableSelectAllCheckboxAria
Expand Down
17 changes: 15 additions & 2 deletions packages/react-aria/src/table/useTableSelectionCheckbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ export interface TableSelectionCheckboxAria {
checkboxProps: AriaCheckboxProps;
}

export interface AriaTableSelectAllCheckboxProps {
/**
* A custom aria-label for the select all checkbox. Overrides the default
* localized "Select All" label.
*/
'aria-label'?: string;
}

export interface TableSelectAllCheckboxAria {
/** Props for the select all checkbox element. */
checkboxProps: AriaCheckboxProps;
Expand Down Expand Up @@ -60,13 +68,18 @@ export function useTableSelectionCheckbox<T>(
* @param props - Props for the select all checkbox.
* @param state - State of the table, as returned by `useTableState`.
*/
export function useTableSelectAllCheckbox<T>(state: TableState<T>): TableSelectAllCheckboxAria {
export function useTableSelectAllCheckbox<T>(
state: TableState<T>,
props: AriaTableSelectAllCheckboxProps = {}
): TableSelectAllCheckboxAria {
let {isEmpty, isSelectAll, selectionMode} = state.selectionManager;
const stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/table');

return {
checkboxProps: {
'aria-label': stringFormatter.format(selectionMode === 'single' ? 'select' : 'selectAll'),
'aria-label':
props['aria-label'] ??
stringFormatter.format(selectionMode === 'single' ? 'select' : 'selectAll'),
isSelected: isSelectAll,
isDisabled:
selectionMode !== 'multiple' ||
Expand Down