Skip to content
Open
5 changes: 5 additions & 0 deletions packages/react-aria-components/src/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,11 @@ export interface TableProps
* the Table.
*/
dragAndDropHooks?: DragAndDropHooks;
/**
* An explicit id to use for the select all checkbox so it remains stable across renders and
* refreshes.
*/
rowHeaderCustomColumnId?: string;
}

/**
Expand Down
6 changes: 4 additions & 2 deletions packages/react-aria/src/grid/useGridSelectionCheckbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {useLocalizedStringFormatter} from '../i18n/useLocalizedStringFormatter';
export interface AriaGridSelectionCheckboxProps {
/** A unique key for the checkbox. */
key: Key;
/** An explicit id to use for the checkbox element. */
id?: string;
}

export interface GridSelectionCheckboxAria {
Expand All @@ -26,7 +28,7 @@ export function useGridSelectionCheckbox<T, C extends GridCollection<T>>(
props: AriaGridSelectionCheckboxProps,
state: GridState<T, C>
): GridSelectionCheckboxAria {
let {key} = props;
let {key, id} = props;

let manager = state.selectionManager;
let checkboxId = useId();
Expand All @@ -40,7 +42,7 @@ export function useGridSelectionCheckbox<T, C extends GridCollection<T>>(

return {
checkboxProps: {
id: checkboxId,
id: id ?? checkboxId,
'aria-label': stringFormatter.format('select'),
isSelected,
isDisabled,
Expand Down
9 changes: 7 additions & 2 deletions packages/react-aria/src/table/useTableSelectionCheckbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {useLocalizedStringFormatter} from '../i18n/useLocalizedStringFormatter';
export interface AriaTableSelectionCheckboxProps {
/** A unique key for the checkbox. */
key: Key;
/** An explicit id to use for the checkbox element. */
id?: string;
}

export interface TableSelectionCheckboxAria {
Expand All @@ -43,8 +45,8 @@ export function useTableSelectionCheckbox<T>(
props: AriaTableSelectionCheckboxProps,
state: TableState<T>
): TableSelectionCheckboxAria {
let {key} = props;
const {checkboxProps} = useGridSelectionCheckbox(props, state);
let {key, id} = props;
const {checkboxProps} = useGridSelectionCheckbox({...props, id}, state);

return {
checkboxProps: {
Expand All @@ -64,8 +66,11 @@ export function useTableSelectAllCheckbox<T>(state: TableState<T>): TableSelectA
let {isEmpty, isSelectAll, selectionMode} = state.selectionManager;
const stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/table');

let selectionColumn = state.collection.columns.find(column => column.props?.isSelectionCell);

return {
checkboxProps: {
id: selectionColumn?.key?.toString(),
'aria-label': stringFormatter.format(selectionMode === 'single' ? 'select' : 'selectAll'),
isSelected: isSelectAll,
isDisabled:
Expand Down
18 changes: 18 additions & 0 deletions packages/react-aria/test/table/useTable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,5 +157,23 @@ describe('useTable', () => {
);
expect(getByTestId('test-id').id).toEqual('table-id');
});

it('uses a custom id for the select all checkbox', () => {
let {container} = render(
<Table
aria-label="Table with select all id"
selectionMode="multiple"
rowHeaderCustomColumnId="custom-select-all-id">
<TableHeader columns={columns}>
{column => <Column key={column.uid}>{column.name}</Column>}
</TableHeader>
<TableBody items={rows}>
{item => <Row>{columnKey => <Cell>{item[columnKey]}</Cell>}</Row>}
</TableBody>
</Table>
);

expect(container.querySelector('#custom-select-all-id')).toBeTruthy();
});
});
});
3 changes: 2 additions & 1 deletion packages/react-stately/src/table/TableCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface ITableCollection<T> extends IGridCollection<T> {
interface GridCollectionOptions {
showSelectionCheckboxes?: boolean;
showDragButtons?: boolean;
rowHeaderCustomColumnId?: string;
}

const ROW_HEADER_COLUMN_KEY = 'row-header-column-' + Math.random().toString(36).slice(2);
Expand Down Expand Up @@ -218,7 +219,7 @@ export class TableCollection<T> extends GridCollection<T> implements ITableColle
if (opts?.showSelectionCheckboxes) {
let rowHeaderColumn: GridNode<T> = {
type: 'column',
key: ROW_HEADER_COLUMN_KEY,
key: opts?.rowHeaderCustomColumnId || ROW_HEADER_COLUMN_KEY,
value: null,
textValue: '',
level: 0,
Expand Down
18 changes: 14 additions & 4 deletions packages/react-stately/src/table/useTableState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ import {useControlledState} from '../utils/useControlledState';
export interface TableProps<T> extends MultipleSelection, Sortable, Expandable {
/** The elements that make up the table. Includes the TableHeader, TableBody, Columns, and Rows. */
children: [ReactElement<TableHeaderProps<T>>, ReactElement<TableBodyProps<T>>];
/**
* An explicit id to use for the select all checkbox so it remains stable across renders and
* refreshes.
*/
rowHeaderCustomColumnId?: string;
/** A list of row keys to disable. */
disabledKeys?: Iterable<Key>;
/**
Expand Down Expand Up @@ -90,6 +95,7 @@ export interface CollectionBuilderContext<T> {
showDragButtons: boolean;
selectionMode: SelectionMode;
columns: Node<T>[];
rowHeaderCustomColumnId?: string;
}

export interface TableStateProps<T> extends MultipleSelectionStateProps, Expandable, Sortable {
Expand All @@ -107,6 +113,11 @@ export interface TableStateProps<T> extends MultipleSelectionStateProps, Expanda
showDragButtons?: boolean;
/** @private - Do not use unless you know what you're doing. */
UNSAFE_selectionState?: MultipleSelectionState;
/**
* An explicit id to use for the select all checkbox so it remains stable across renders and
* refreshes.
*/
rowHeaderCustomColumnId?: string;
/** The id of the column that displays hierarchical data. */
treeColumn?: Key;
}
Expand All @@ -129,11 +140,10 @@ export function useTableState<T extends object>(props: TableStateProps<T>): Tabl
showSelectionCheckboxes: showSelectionCheckboxes && selectionMode !== 'none',
showDragButtons: showDragButtons,
selectionMode,
columns: []
columns: [],
rowHeaderCustomColumnId: props.rowHeaderCustomColumnId
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
// oxlint-disable-next-line react/react-compiler, react-hooks/exhaustive-deps
[props.children, showSelectionCheckboxes, selectionMode, showDragButtons]
[props.children, showSelectionCheckboxes, selectionMode, showDragButtons, props.rowHeaderCustomColumnId]
);

let collection = useCollection<T, ITableCollection<T>>(
Expand Down