DataTable: Add current-page row selection - #8415
rickyzhangca wants to merge 4 commits into
Conversation
Add controlled and uncontrolled selection for flat and grouped tables with accessible selection controls and regression coverage.\n\nRefs github/primer#6718 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🦋 Changeset detectedLatest commit: 76eb548 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
|
Integration test results from github/github-ui PR:
CI check runs linting, type checking, and unit tests. Check the workflow logs for specific failures. Need help? If you believe this failure is unrelated to your changes, please reach out to the Primer team for assistance. |
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
A moderate accessibility issue remains unresolved in row checkbox fallback naming.
Review tier: Lite
Findings: None
What changed in this PR
Adds opt-in current-page row selection to DataTable, including controlled state, grouped rows, accessibility support, documentation, and tests.
Changes:
- Added stable-ID selection APIs and select-all behavior.
- Added composable selection components.
- Added examples, accessibility documentation, browser tests, and a minor changeset.
| File | Summary |
|---|---|
packages/react/src/experimental/index.ts |
Exports selection prop types. |
packages/react/src/DataTable/useTable.ts |
Manages selection state and derived row status. |
packages/react/src/DataTable/Table.tsx |
Adds composable selection parts. |
packages/react/src/DataTable/Table.module.css |
Styles selection controls. |
packages/react/src/DataTable/Table.features.stories.tsx |
Demonstrates composed selection. |
packages/react/src/DataTable/index.ts |
Exports selection components and types. |
packages/react/src/DataTable/DataTable.types.test.tsx |
Validates selection typings. |
packages/react/src/DataTable/DataTable.tsx |
Integrates selection into DataTable. |
packages/react/src/DataTable/DataTable.features.stories.tsx |
Adds selection examples. |
packages/react/src/DataTable/DataTable.docs.json |
Documents selection APIs. |
packages/react/src/DataTable/DataTable.accessibility.md |
Documents selection semantics. |
packages/react/src/DataTable/__tests__/DataTableSelection.test.tsx |
Tests selection behavior and hydration. |
packages/react/src/__tests__/__snapshots__/exports.test.ts.snap |
Updates export snapshots. |
e2e/components/Table.test.ts |
Tests composed-selection interactions and accessibility. |
e2e/components/DataTable.test.ts |
Tests DataTable selection scenarios. |
.changeset/selectable-data-table-rows.md |
Declares the minor release. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
joshblack
left a comment
There was a problem hiding this comment.
Looking great so far! Thanks for putting this up. The config-based API makes a lot of sense to me 💯 Left a couple of comments/questions and then also had some broader things:
- One though, do you think it'd be nice for data table to start to break down the sections more? We don't really do this for other components but I bet for data table it's worth it
- For select all, are we including the "select all" bit or is that in the batch actions stuff you mentioned is coming later on? It'd be nice to include that with pagination because without it the example is kind of a bummer 😞
- I was curious about some of the demo controls (like the buttons to refresh pages), what are they for? Would they be better as dev stories?
- For the selectable standalone and grouped repo story I was a little confused since I couldn't select the standalone one, what is the intended behavior?
Thanks again for putting this up! Hope these all make sense, can't wait to hear what you think 👀
| { | ||
| title: 'With Row Selection', | ||
| id: 'experimental-components-datatable-features--with-row-selection', | ||
| aat: true, |
There was a problem hiding this comment.
I guess I missed this in the last one but why did we need an aat flag for stories? 👀
There was a problem hiding this comment.
it's storybook's automated a11y testing, somehow I thought we always want it, removed!
| ] | ||
|
|
||
| test.describe('DataTable', () => { | ||
| test('selection scope is owned by pagination and filtering @aat', async ({page}) => { |
There was a problem hiding this comment.
Organization-wise this should probably live after the loop tests we have (just as the convention we have for other ones)
| ] | ||
|
|
||
| test.describe('DataTable', () => { | ||
| test('selection scope is owned by pagination and filtering @aat', async ({page}) => { |
There was a problem hiding this comment.
Separately, what are we looking to test with this in Playwright? I wasn't quite sure so wanted to check. Is it that we want to run axe at the end?
There was a problem hiding this comment.
not just for axe, mainly for testing the interactions
this end to end one is too long to follow, they are covered by the dev story anyway, removed!
| 'experimental-components-datatable-features--with-grouped-row-selection', | ||
| 'experimental-components-datatable-features--with-mixed-row-selection', | ||
| ]) { | ||
| test(`controlled selection round-trip ${id} @aat`, async ({page}) => { |
There was a problem hiding this comment.
Had a similar question with this one, just wanted to see what we wanted to capture with these tests in playwright 👀
There was a problem hiding this comment.
checking checkboxes are clickable in no grouping + all rows are in groups + some rows are standalone and some are in groups
There was a problem hiding this comment.
it's a bit hard to read, updated it to be testing all 3 cases in one some rows are standalone and some are in groups setting!
| { | ||
| "name": "checked", | ||
| "type": "boolean", | ||
| "required": true, | ||
| "description": "Whether every selectable row is selected." | ||
| }, | ||
| { | ||
| "name": "indeterminate", | ||
| "type": "boolean", | ||
| "description": "Whether some, but not all, selectable rows are selected." | ||
| }, |
There was a problem hiding this comment.
Would it help at all to collapse these into one prop? e.g. selection="all|some|none" just to make it more semantic and remove the ambiguity if checked and indeterminate are applied
| { | ||
| "name": "onChange", | ||
| "type": "React.ChangeEventHandler<HTMLInputElement>", | ||
| "description": "Handle changes to the native selection checkbox." | ||
| }, |
There was a problem hiding this comment.
Do we need to expose the underlying checkbox even for this or could it be like an onToggleSelect kind of thing?
There was a problem hiding this comment.
changed to onToggleSelect?: () => void!
| { | ||
| "name": "indeterminate", | ||
| "type": "boolean", | ||
| "description": "Whether the row is in a mixed selection state." | ||
| }, |
There was a problem hiding this comment.
I wasn't sure what this one would mean with mixed row selection, could you share more about when this would show up? I assume for groups, potentially? 👀 The mixed row selection story didn't make it clear to me what was going on
There was a problem hiding this comment.
yea you right, individual rows shouldn't have intermediate state, removed
There was a problem hiding this comment.
“Mixed” referred to standalone rows alongside groups, not mixed row selection, fixed the name so it's clear, my bad
| ] | ||
| }, | ||
| { | ||
| "name": "Table.RowSelection", |
There was a problem hiding this comment.
Similar prop-style questions to the header one above, curious what you think!
| ] | ||
|
|
||
| export const WithRowSelection = () => { | ||
| const [selectedRows, setSelectedRows] = React.useState<ReadonlySet<string | number>>(() => new Set([2])) |
There was a problem hiding this comment.
More of a general question, should we make the row id type consumable instead of doing string | number everywhere?
| ...rest | ||
| }: TableSelectionHeaderProps) { | ||
| return ( | ||
| <th |
There was a problem hiding this comment.
Random, is there a reason we couldn't use the TableHeader component directly and then add on top of it? My thought was that maybe it didn't allow us to override something that we wanted but wanted to check-in to see 👀
totally, i also found
sorry I didn't follow this, do you mean the ability to select all and do batch actions? this pr will enable selecting all, later on we add bulk actions
they are for demoing certain behaviours, for example the refresh button demos the selection will persist between data refreshes
yea that can be confusing, remove that disabled row |
Address review feedback with semantic selection props and a shared row ID type. Simplify examples and browser coverage without changing current-page selection ownership. Refs github/primer#6718 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Refs github/primer#6718
Implements the row-selection stage for DataTable, building on the grouping support merged in #8386. This PR targets
maindirectly and does not close the tracking issue: bulk actions remain a follow-up.CleanShot.2026-09-14.at.10.28.16.AM.mp4
Changelog
New
rowSelection,selectedRows,defaultSelectedRows,onSelectionChange, andisRowSelectableprops, using the existinggetRowIdfor stable identity.Table.SelectionHeaderandTable.RowSelectionparts using Primer Checkbox.Changed
Removed
Rollout strategy
Includes a minor changeset. Selection is opt-in; existing consumers do not need to migrate.
Testing & Reviewing
State ownership and scope
selectedRows/defaultSelectedRowsacceptReadonlySet<string | number>;onSelectionChangereceives{selectedRows: Set<string | number>}.Automated validation on this HEAD
Reviewer focus / outstanding validation
WithRowSelection,WithGroupedRowSelection, andWithMixedRowSelectionunder DataTable / Features.Kept as draft pending review and manual accessibility validation.