Skip to content

feat(data-table): bulk selection actions in the footer - #496

Merged
interacsean merged 13 commits into
mainfrom
feat/data-table-footer-selection-actions
Oct 9, 2026
Merged

interacsean merged 13 commits into
mainfrom
feat/data-table-footer-selection-actions

Conversation

@itsprade

@itsprade itsprade commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

What this does

Adds bulk actions to DataTable (tailor-inc/platform-planning#1738). Pass selectionActions to useDataTable and, while rows are selected, DataTable.Footer becomes a bulk-action bar: the selection count, your actions, and Clear, with pagination kept alongside. When nothing is selected, the footer looks exactly as it does today.

┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ ☑ V2001  Tyrell Parts SA   Raw material   C. Lindqvist   EMEA   Inactive   US$238,429.82    │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ 5 of 240 selected │ ▷ Activate (2)  ⏸ Deactivate (3)  ⤓ Export  ⋯ │ Clear   Rows per page 25  Page 1 / 10  « ‹ › » │
└─────────────────────────────────────────────────────────────────────────────────────────────┘

This PR started as the prototype and open question from 3 Sep. The direction was settled with Sean in #pf-app-shell and at the 18 Sep board planning: build it into DataTable, place it in the footer, and let the consumer control which actions appear. Sean's two review comments are addressed in 6b749f47 and 4382774a.

API

It is one option, shaped like rowActions. There is nothing new to compose in JSX.

const table = useDataTable({
  columns,
  data,
  control,
  selectionActions: [
    {
      id: "activate",
      label: "Activate",
      icon: <Play />,
      canApply: (vendor) => vendor.status === "inactive", // "Activate (2)", disabled at 0
      // Only the eligible rows, incl. ones selected on other pages. Returning the
      // promise: the bar waits for it, then clears the selection.
      onClick: (vendors) => activate(vendors),
    },
    {
      id: "export",
      label: "Export",
      keepSelection: true, // doesn't change the rows
      onClick: (vendors) => exportCsv(vendors),
    },
  ],
});

<DataTable.Root value={table}>
  <DataTable.Table />
  <DataTable.Footer>
    <DataTable.Pagination />
  </DataTable.Footer>
</DataTable.Root>;

SelectionAction and RowAction both extend a new base, DataTableAction, so one definition can be spread into both arrays and only onClick differs.

id, label, icon, variant Shared (DataTableAction). variant: "destructive" is styling only; the app still confirms destructive actions itself (interaction/confirm).
canApply(row) Shared and optional. For a selection action, it shows the eligible count, disables the action at zero, and passes only those rows to onClick. A row action is disabled where it returns false.
onClick(rows, { clearSelection, run }) Selection action. A returned promise is waited on (see below). An action that confirms first keeps run and passes it the request from the dialog's confirm button — run(deleteRows(rows)) — for the same handling. A synchronous handler that never calls run owns the selection.
keepSelection Selection action. Keep the selection after the promise resolves.

New exports: SelectionAction, SelectionActionHelpers and DataTableAction. New on the return value and context: selectedRows (each selected row as last loaded, across pages) and deselectAllRows (page-scoped).

Behaviour

  • Opt-in. The bar needs a non-empty selectionActions, a DataTable.Footer, and at least one selected row. selectionActions also turns on the checkbox column by itself, so onSelectionChange becomes optional.
  • Async actions. While a returned promise (or one passed to run) is pending, every action, the ⋯ trigger and Clear are disabled. The pending state is kept by the table rather than the bar, so it survives the bar unmounting and remounting mid-request. Meanwhile the running action shows a spinner, and the bar is aria-busy, so a slow request can't be fired twice. When the promise resolves, the selection clears: those rows may have changed, and copies remembered from other pages can't refresh. That fixes the stale counts and deleted-but-still-selected rows from the review. If the promise rejects, the selection stays for a retry, and a [DataTable] Selection action "…" failed error is logged.
  • Overflow. Three actions stay inline. The rest move into a More actions menu, which opens upward.
  • Pop-out. In <Layout fill> the footer is already pinned. On a page-scrolling table, the bar is position: sticky while rows are selected, so it rides the bottom of the viewport and settles back into place at the end of the table. There is no JS. It sticks to the nearest scrolling container: inside a scrolling drawer it rides that drawer, and inside a wrapper with overflow: hidden it stays in the footer. The docs say so.
  • Built on the generic Toolbar (feat: add generic toolbar #559). The bar is a Toolbar.Row, so it gets role="toolbar" and Arrow/Home/End navigation.
  • Accessibility. A persistent polite live region announces the count from the first selection. When the bar closes while focus is inside it, focus returns to the header checkbox.
  • Tone. The bar uses accent, which stays soft in all three themes and in both light and dark mode. Hover and secondary text are re-pointed so they stay legible on the tinted bar.
  • Pagination. While the bar is up, Pagination hides its own "N of M row(s) selected" text and shares the line with the bar. It wraps to a second line only when there isn't room.

Compatibility

  • API: additive. Tables without selectionActions render exactly as before, and a test asserts this. New context members are optional. selectedRows is required on UseDataTableReturn, like expandedIds was, so only a hand-built return value would need it.
  • RowAction.isDisabled is deprecated in favour of canApply, which reads the other way round. It still works: the action is disabled if either one says so.
  • Behaviour change: the header checkbox is now page-scoped in both directions. Checking it used to replace the selection with the current page, and unchecking it cleared every page. That silently dropped rows picked on other pages: with 5 rows selected on page 1, "select all" on page 2 gave 25, not 30. It now adds or removes only the current page, and clearSelection still empties everything. Tables whose selection stays on one page see no difference. The fix is its own commit (dcc9e770) so it can be split out if preferred, and the changeset calls it out.
  • clearSelection is now a no-op when nothing is selected, so an action that clears itself and then resolves doesn't fire onSelectionChange([]) twice.
  • @IzumiSy, one change to feat: add generic toolbar #559. The DataTable root goes from overflow-hidden to overflow-clip. Both clip the toolbar to the rounded frame, but hidden also makes the root a scroll container, which pinned the sticky footer inside the table. clip keeps your clipping without that side effect.

Commits

  1. fix(data-table): the header checkbox keeps other pages' rows.
  2. feat(data-table): selectionActions and remembered selected rows. Selection becomes an ordered map of id → row as last seen, and the current page's copy wins.
  3. feat(data-table): the footer bar (selection-bar.tsx), plus the Pagination and Footer changes and en/ja labels.
  4. feat(examples): /showcase/data-table-selection moves onto the real API, and /dashboard/products replaces its "Selected: …" line with bulk actions.
  5. docs(data-table): docs-src sections; interaction/multi-select is rewritten around selectionActions; list/dense-scan is updated; decisions/data-table-selection-footer-actions.md is now Decided; and a minor changeset.
  6. feat(data-table), from review: canApply and DataTableAction, RowAction.canApply, promise-aware actions with keepSelection, and matching docs, pattern, demo and tests.
  7. feat(data-table), from the second review: the pending state moves into useDataTable (pendingActionId / runSelectionAction); Clear is disabled while pending; run(request) is added to the helpers (SelectionActionHelpers) so a confirm dialog can hand its request back. The demo delete, pattern example and docs use it.

The rest are the original prototype commits and two merges of main. The latest merge, after 1.16.0, regenerated docs-manifest.json, and that also refreshes snapshot hashes for dialog/menu/select/sheet, which #555 had left stale on main.

Worth discussing

  • canApply(row) replaces the consumer-supplied count from the first sketch. Selection spans pages, and an app with server pagination can't count rows on other pages without its own cache; the table already sees every row the user selects. It was first written appliesTo and was renamed in review before shipping.
  • Overlap with refactor(core): align state ownership with React Compiler #525. It rewrites selection state and adds controlled rowSelection. With a controlled selection, rows that haven't loaded yet are counted once they load; this is documented. Whichever lands second adapts, and the row memory just goes wherever selection is written.
  • Safari corners. In Safari, the table's rounded corners rendered incorrectly in our testing. We haven't confirmed the cause yet: it could be the themes' corner-shape: squircle (since feat(themes): theme foundation with mode/palette axes and AppearanceSwitcher #306) or overflow: clip. It's being looked into separately, and if it's overflow: clip, it gets fixed here before merge.
  • Toasts. Bulk actions usually end in a toast, and the default bottom-right toast covers the footer's pagination for a few seconds.

Follow-ups (not in this PR)

  • Width-aware overflow (review point 1). Actions should move into "⋯" as space runs out rather than at a fixed three. This belongs in the generic Toolbar, so every toolbar gets it; to be done with Seiya.
  • "Select all N" across pages.
  • A placeable DataTable.SelectionActions for custom placement, following the "option = default placement, sub-component = custom placement" rule from tailor-inc/platform-planning#1699.
  • Tooltips explaining a disabled action, and Escape to clear.

Try it

pnpm install && pnpm build && pnpm dev
  • /showcase/data-table-selection: 240 vendors with fake slow requests. Tick rows and click Activate to see the pending state and auto-clear, and Export to see keepSelection. Page with a selection open, open ⋯ → Delete for the confirm flow, and use the Pinned footer / Page scroll switch.
  • /dashboard/products: one DataTableAction ("Delete") serves both the row menu and the bulk bar.

Verification

  • Tests. selection-bar.test.tsx has 22 tests, 9 of them for async actions: pending state, clear-on-success, keepSelection, rejection, sync handlers, a single onSelectionChange([]), Clear disabled while pending, the pending state surviving a remount, and run from a confirm handler. There are also row-action tests (canApply, deprecated isDisabled, a shared definition), hook tests (cross-page selection, selectedRows, selectionActions) and a header-checkbox test. 2,058 core tests pass after merging the latest main.

  • Checks. pnpm fmt:check, pnpm exec turbo run lint test type-check check-dts (28/28), pnpm docs:check, and docs examples compile against core.

  • In the browser, on the example app's theme (bloom) in light and dark mode, at 1440px and a narrower ~1000px window:

    • the bar, counts and overflow menu;
    • the pending spinner with actions disabled, then auto-clear (Activate) and a kept selection (Export);
    • the confirm flow;
    • Clear moving focus to the header checkbox;
    • cross-page selection and the header checkbox;
    • sticky pop-out in page-scroll mode;
    • the Products row-menu Delete disabled through the shared canApply.

    Not yet checked on the default and cream themes.

🤖 Generated with Claude Code

Adds `/data-table-selection` to the vite example: 240 vendor records with a
checkbox column, and a footer that swaps its row-count text for a bulk-action
bar while a selection is open.

The bar reuses the same `DataTable.Pagination` in both states, so the
right-hand cluster is identical whether or not rows are selected. Inversion is
done by re-pointing the surface tokens on a wrapper inside the footer, so the
pagination buttons, the page-size Select and the action Buttons re-theme
themselves without any class overrides.

Prototype only — nothing under packages/** changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

Code Metrics Report

main (ecc3f93) #496 (75cb5da) +/-
Coverage 87.1% 87.1% 0.0%
Test Execution Time 2m7s 2m3s -4s
Details
  |                     | main (ecc3f93) | #496 (75cb5da) | +/-  |
  |---------------------|----------------|----------------|------|
  | Coverage            |          87.1% |          87.1% | 0.0% |
  |   Files             |            178 |            178 |    0 |
  |   Lines             |           5467 |           5467 |    0 |
  |   Covered           |           4764 |           4764 |    0 |
+ | Test Execution Time |           2m7s |           2m3s |  -4s |

Reported by octocov

…t or as a pattern

Records the footer direction for multi-select bulk actions, notes that
`catalogue/src/pattern/interaction/multi-select` currently prescribes a floating
bottom bar (built on raw Table.Root, predating DataTable selection) and so needs
rewriting either way, and frames the open question for a team call: build the bar
into DataTable via `selectionActions`, or keep it a documented pattern.

No decision taken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@itsprade
itsprade force-pushed the feat/data-table-footer-selection-actions branch from a473622 to 9a6439b Compare September 3, 2026 14:56
itsprade and others added 6 commits September 29, 2026 12:28
Brings the prototype up to date with main (58 commits, incl. the generic
Toolbar #559 and the docs-src pipeline #396). main moved every demo page
under pages/showcase/ (#513), so the prototype moves with them, unchanged:
/data-table-selection -> /showcase/data-table-selection, listed in the
sidebar's Showcase group.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…kbox

Selection persists across pages, but the header checkbox did not respect
that: checking it replaced the selection with the current page's rows, and
unchecking it cleared every page. With 5 rows picked on page 1, select-all on
page 2 gave 25 selected instead of 30.

The header checkbox is now page-scoped in both directions. selectAllRows adds
the page's rows to the selection, and a new deselectAllRows removes only the
page's rows; clearSelection still empties everything. deselectAllRows is
optional on DataTableContextValue, and a hand-built context without it falls
back to clearSelection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oss pages

Adds the data half of footer bulk actions (#1738): a selectionActions option
on useDataTable, shaped like rowActions, and the selected rows it acts on.
The footer bar that renders these actions follows in the next commit.

- SelectionAction: id, label, icon, variant, and an optional appliesTo(row)
  predicate that narrows an action to the selected rows it applies to.
  onClick receives those rows plus a clearSelection helper.
- A non-empty selectionActions array enables row selection on its own, so
  onSelectionChange stays optional.
- selectedRows: selection is now an ordered map of id -> row as last seen, so
  rows picked on other pages can still be counted and handed to an action.
  Rows on the current page always win over the remembered copy.

selectedRows is required on UseDataTableReturn, like expandedIds, and
optional on DataTableContextValue, which is documented as hand-constructible.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rows are selected

With selectionActions set and at least one row selected, the footer becomes
the bulk-action bar (#1738): the count, the actions, and Clear, with the
footer's own children (usually Pagination) kept alongside. Tables without
selectionActions render exactly as before.

- Built on the generic Toolbar (#559): the bar is a Toolbar.Row, so it gets
  role=toolbar and Arrow/Home/End navigation between its buttons.
- The first three actions render as buttons; the rest collapse into a
  "More actions" menu that opens upward. An action with appliesTo shows its
  eligible count ("Activate (6)") and disables at zero.
- Surface: bg-accent, which stays soft in every theme and both modes. A
  display:contents wrapper re-points --accent and --muted-foreground so hover
  states and secondary text stay visible on the tinted bar.
- Sticky while open, so on a page-scrolling table the bar rides the bottom of
  the viewport until the table's end scrolls into view. The DataTable root
  moves from overflow-hidden to overflow-clip for this: both clip to the
  rounded frame, but hidden also makes the root a scroll container, which
  trapped the sticky footer inside the table.
- Pagination hides its own "N selected" text while the bar is up and shares
  the line with it, dropping to a second line only when it doesn't fit.
- A persistent polite live region announces the count from the first tick;
  when the bar closes with focus inside it, focus returns to the header
  checkbox. Labels in en and ja.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/showcase/data-table-selection was the #496 prototype: a hand-built footer bar
on useDataTableContext with a primary/neutral tone toggle. It now uses the
real API, so the page shows exactly what the core component does:

- selectionActions with appliesTo counts, Activate / Deactivate / Export
  inline and Delete in the More actions menu behind a confirm dialog
  (interaction/confirm)
- actions mutate local state, so counts and badges update after an action
- the generic Toolbar scaffold on top, matching the other DataTable demos
- a Pinned footer / Page scroll switch to compare <Layout fill> with a page-
  scrolling table, where the bar rides the bottom of the window

dashboard/products drops its ad-hoc "Selected: …" line for Publish / Archive
/ Delete bulk actions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lect pattern

- DataTable docs: a Selection actions section (with accessibility notes), a
  SelectionAction reference table, the selectionActions option, and the
  footer/pagination and useDataTableContext notes that go with it.
- interaction/multi-select: rewritten around selectionActions. The floating
  bar on a hand-built Table.Root with native checkboxes is gone, along with
  its dangling source marker; the example is a DataTable with an appliesTo
  action, an overflowed destructive action, and a confirm dialog.
- list/dense-scan: bulk actions point at the footer bar.
- decisions: #496's open question is now a decided record — built into
  DataTable, what changed from the first sketch (appliesTo instead of count,
  rows instead of ids, accent instead of primary/neutral), and follow-ups.
- changeset: minor, calling out the page-scoped header checkbox.

Generated docs/ and the manifest come from pnpm docs:sync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@itsprade itsprade changed the title docs(data-table): bulk actions in the footer — direction + prototype feat(data-table): bulk selection actions in the footer Sep 29, 2026
@itsprade
itsprade marked this pull request as ready for review September 29, 2026 12:02
@itsprade
itsprade requested a review from a team as a code owner September 29, 2026 12:02
@interacsean

Copy link
Copy Markdown
Contributor

A few things from testing this locally, using the showcase page with simulated async paging:

1. Collapse actions into "⋯" based on width
Right now MAX_INLINE_ACTIONS = 3 is fixed (selection-bar.tsx). On a narrow table the bar wraps instead of moving actions into the menu. Could we look at width-aware overflow? That would mean a ResizeObserver on the row that measures the buttons and moves the trailing ones that don't fit into "More actions". It's probably best built into the generic Toolbar so other toolbars get it too, rather than kept specific to the selection bar.

2. A common base type for rowActions and selectionActions
RowAction and SelectionAction share id / label / icon / variant, but they drift apart elsewhere:

  • The gate is flipped: isDisabled(row) on one, appliesTo(row) on the other.
  • The click signatures differ.

Consumers will end up writing the same "Activate" action twice with inverted predicates. The callbacks will reasonably differ, since one record and many records need different handling. But the display fields and the per-row eligibility check (canApply(row)) could come from a shared base that both extend. That would also leave room to derive one from the other later. We're fine taking this as a follow-up, but it's worth settling the naming and polarity before appliesTo ships as public API.

3. Scroll behaviour from overflow-hidden → overflow-clip
At a high level, the sticky pop-out now depends on the nearest scroll ancestor outside the table. If a consumer wraps the table in any overflow-hidden or overflow-auto container (cards, tab panels, drawers), that wrapper becomes the sticky boundary and the pop-out quietly stops working. clip also blocks programmatic scrolling of the root, so focus can no longer scroll clipped content into view. And clip doesn't give the implicit min-height: 0 that hidden did; it only works here because min-h-0 is set explicitly. It may be worth a note in the docs, plus a quick check in Safari 16.x, which has had bugs with clip combined with border-radius.

4. Post-action state, unless the consumer clears the selection themselves

  • Actions can run concurrently. onClick is fire-and-forget. While an async action is still running, every button stays enabled, so the user can start other actions against the same selection. A pending state is listed as a follow-up, but without it this is easy to hit in real apps.

  • Off-page rows go stale. Rows selected on other pages keep the version from when they were last loaded. Once an action's request resolves, those copies don't update, so appliesTo counts and the rows passed to later actions are based on stale data. Repro with the demo's clearSelection() calls removed:

    1. Tick an inactive row on page 1 and an inactive row on page 2.
    2. Click Activate (2) from page 2.
    3. The bar shows Activate (1) Deactivate (1) on both pages. The correct result is (0)/(2), and clicking Activate again re-sends a row that's already active.

    Deleted rows also stay selected indefinitely, because their page never reloads to replace them.

    The demo hides both problems by always clearing. Options: clear after onClick by default with an opt-out, let onClick return updated rows that get written back into the selection, or at least document "clear after mutating" as a requirement.

itsprade and others added 2 commits October 1, 2026 16:25
…election actions

Addresses Sean's review of #496.

Naming and polarity (review point 2), settled before it ships:
- appliesTo is renamed canApply and moves into DataTableAction, a new base
  type with id / label / icon / variant / canApply that both RowAction and
  SelectionAction extend. One definition can be spread into rowActions and
  selectionActions; only onClick differs.
- RowAction gains canApply, so a row action is disabled where it returns
  false. isDisabled, which reads the other way round, is deprecated but still
  honoured: either one can switch the action off.

Post-action state (review point 4):
- An onClick that returns a promise is waited on. While it is pending, every
  action and the More actions trigger are disabled, the running action shows
  a Spinner, and the bar is aria-busy, so a slow request can't be fired twice
  or overlapped.
- When it resolves, the selection clears. The rows may have changed, and
  copies remembered from other pages can't refresh, so keeping them handed
  stale rows to the next action and left deleted rows selected. Actions that
  don't change rows opt out with keepSelection. A rejection keeps the
  selection for a retry and logs a [DataTable] error rather than rethrowing,
  which would surface as an unhandled rejection even when the app had already
  handled it.
- A synchronous onClick, such as one that opens a confirm dialog, still owns
  the selection.
- clearSelection is a no-op on an empty selection, so an action that clears
  itself and then resolves doesn't fire onSelectionChange([]) twice.

Docs (review point 3): the Selection actions section says where the pop-out
works (nearest scrolling container; it stays put inside a clipping wrapper)
and covers the new behaviour. There are DataTableAction / SelectionAction /
RowAction tables, and the multi-select pattern and its example use canApply
and returned promises. The decision record and changeset are updated, and the
demo now uses slow fake requests so the pending state is visible.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in 12 commits from main, including the 1.16.0 release and the docs-kit
change that moves the root guides into docs-src/guides (#561).

The only conflict was docs-manifest.json, a generated file. It was taken from
main and regenerated with pnpm docs:sync. That also refreshes the snapshot
hashes for dialog, menu, select and sheet, which were stale on main: #555
updated their test snapshots after the manifest was last synced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@itsprade

itsprade commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Thanks Sean, really useful testing. Points 2–4 are addressed in 6b749f47. Point 1 I'd like to do in Toolbar:

1. Width-aware overflow. Agreed, it belongs in the generic Toolbar so every toolbar gets it. Toolbar items will need a way to describe their menu form, since rows can hold inputs and selects, not just buttons. I'll pick it up with Seiya as a follow-up; this PR keeps the fixed three.

2. Shared base and naming. Done before it ships. appliesTo is now canApply, on a DataTableAction base that RowAction and SelectionAction both extend, so one definition spreads into both arrays and only onClick differs. RowAction gets canApply too; isDisabled is deprecated but still honoured (the action is disabled if either says so).

3. overflow-clip. I've added a docs note. The pop-out sticks to the nearest scrolling container: it rides a scrolling drawer, and it stays put inside an overflow: hidden wrapper. min-h-0 stays explicit on the root. On Safari, the rounded corners render incorrectly in my testing. I haven't confirmed yet whether that's the themes' corner-shape: squircle or clip. I'm looking into it separately, and if it's clip, it gets fixed here before merge.

4. Post-action state. onClick can now return a promise.

  • While it's pending, every action is disabled, with a spinner on the running one and aria-busy on the bar.
  • On success the selection clears, so the stale counts and lingering deleted rows from your repro go away.
  • keepSelection: true opts out, e.g. for export.
  • A rejection keeps the selection for a retry and logs a [DataTable] error.
  • Sync handlers, such as one that opens a confirm dialog, still own the selection.

The showcase now uses slow fake requests, so the pending state is visible.

@interacsean

Copy link
Copy Markdown
Contributor

Thanks, 2 and 3 look good. Two remaining gaps in the pending handling:

Clear stays active while an action is pending. We should disable the Clear button while an async action is pending. Right now it stays active, and pressing it undoes the disabled state on the action buttons. The cause is that the pending state lives in the bar: when the selection becomes empty the bar unmounts, and a remounted bar has forgotten the pending action. Emptying the selection by unticking rows has the same effect, which is probably an edge case we can live with, but we should close off the obvious route.

The confirm-dialog path gets none of the new handling. For delete, onClick only opens the dialog, so from the bar's point of view it's synchronous. The real request runs later from the dialog's confirm button, outside the bar, so there's no pending state and no clear on success. This isn't specific to the demo: the docs tell consumers to confirm destructive and bulk actions this way (interaction/confirm), so it's the path real apps will take. Could the helpers expose something like run(promise) so a confirm handler can pass its request back to the bar?

itsprade and others added 2 commits October 8, 2026 11:11
docs-manifest.json was the only conflict. It was taken from main and
regenerated with pnpm docs:sync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dd run()

Addresses Sean's second review comment on #496.

- The in-flight action now lives in useDataTable (pendingActionId /
  runSelectionAction) instead of the footer bar. Emptying the selection
  unmounts the bar, and a remounted bar used to forget the pending request
  and re-enable its actions. The busy state now survives that.
- Clear is disabled while a request is pending, which closes the obvious way
  to empty the selection mid-request.
- onClick's helpers gain run(request), exported as SelectionActionHelpers. An
  action that only opens a confirm dialog keeps run and passes it the request
  from the dialog's confirm button. The bar then gives it the same pending
  state and clear-on-success as a returned promise. The showcase delete, the
  multi-select pattern example, the docs and the changeset use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@itsprade

itsprade commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

Thanks Sean, both are in 4382774a:

Clear while pending. Clear is now disabled while a request is pending. I also moved the pending state out of the bar and into useDataTable, so it survives the bar unmounting and remounting. Unticking every row and ticking again mid-request keeps the actions locked until the request settles, so the edge case is covered too, not just the Clear route.

Confirm-dialog path. Went with your run suggestion. The helpers are now { clearSelection, run }, exported as SelectionActionHelpers. An action that confirms first keeps run, and the dialog's confirm button calls run(deleteRows(rows)). From then on it gets exactly the same handling as a returned promise: the bar is busy with a spinner and Clear disabled, and the selection clears on success (or is kept, with keepSelection). run returns the request, so the dialog can still await it. The showcase delete, the interaction/multi-select example and the docs all use it now.

Also merged the latest main. Ready for another look.

Width-aware overflow in Toolbar remains a follow-up with @IzumiSy.

Resolve docs-manifest.json conflict: keep the PR's data-table hash and
main's date-picker hash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@interacsean
interacsean merged commit 94f6a5a into main Oct 9, 2026
8 checks passed
@interacsean
interacsean deleted the feat/data-table-footer-selection-actions branch October 9, 2026 04:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants