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
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;

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.

I don't think we'll introduce this to TableView in v3, only in S2 and it's already possible in RAC

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

we haven't migrated our project to S2 yet, and this came out of an accessibility audit that requires us to fix it in the near future β€” is there any chance you'd reconsider adding it to v3 too?

/**
* 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>(

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.

This is already possible in RAC, so I don't think we need any props for the hooks
https://stackblitz.com/edit/nysi3thd?file=src%2FTable.tsx

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

yes, RAC already supports this via but v3's TableView renders that checkbox internally with no way for consumers to reach it β€” that's why I scoped this here.

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