[C07] Data Table (component)
Summary
C07 is a component spec, not a screen — distinct from every other entry in this catalogue. It is the canonical generic Data Table for the admin portal: the single source of truth for tabular UI, designed so every list page (E02 Event Participants, future Members, Affiliates, Results, Audit log, T02–T05 Number Stock, etc.) mounts <DataTable> rather than re-implementing rows, selection, and bulk chrome.
The bulk-engaged shell — "variant E" from the bulk toolbar exploration — is locked: 1.5px indigo border + 4px outer glow on the wrapper, plus a bonded DTBulkBar welded to the table’s top edge. Operators can never miss the actions; the actions are spatially welded to the rows they act on. Page chrome above the table (page header, stats strip, filter chips, utilities row) is never modified by the table — no layout jitter on enter/exit.
This .adoc is the persistent contract; the design at :design-url: is the visual reference. Implementation lands as a shared component in the admin-portal SPA (eventual home: @ems/shared-ui per WS4 of the admin-portal greenfield plan).
Actor & Context
Actor: indirect — operators interact with C07 via every list-page consumer; the component itself has no actor of its own.
Frequency: every list-page render.
Precondition: the consumer has a row dataset + a column declaration; the backend provides server-side sort/paginate where applicable (C07 is display-only at the substrate level).
Entry point: mounted by the consumer screen as <DataTable> (or whichever stack-appropriate name lands in the implementation — the prop contract is the canonical part).
API Surface
Column shape
| Field | Type | Purpose |
|---|---|---|
|
string |
Unique column id. |
|
string |
Header text. |
|
number |
Pixels (no flex-grow yet). |
|
|
Sort indicator only — display-only at the C07 level. Caller owns sort logic. |
|
boolean (optional) |
Right-align numeric content. |
|
boolean (optional) |
Allow text wrap. |
|
boolean (optional, default |
Cell suppresses |
|
|
Cell renderer. When omitted, the cell renders |
DataTable props
| Prop | Type | Purpose |
|---|---|---|
|
|
Column declaration (above). |
|
|
Row data — each row must carry an |
|
|
Selected row ids. Omit to disable selection entirely. |
|
|
Row checkbox click. |
|
|
Header checkbox click. |
|
id (optional) |
Dim siblings to focus on this row. |
|
|
Row click handler. Pointer cursor when set. |
|
|
Strikethrough + faint treatment. |
|
boolean |
When |
|
string (optional) |
Free-form context label, e.g. |
|
number (optional) |
Drives the Select all N pill when current page selection < total. |
|
|
|
|
|
Bulk-bar Clear button. |
|
ReactNode (optional) |
Rendered when |
Click-to-act mechanism
Cell-level clicks override the row-level click via the dt-stop class and stopRowClick flag:
-
Cells with a
renderfunction default tostopRowClick: true(most common case — the renderer wires its own click handler). -
Cells with NO
renderfunction default tostopRowClick: false(the row click bubbles). -
A column may explicitly set
stopRowClick: falseto opt back into row-click bubbling even when it has a renderer.
Internally, cells emit a dt-stop className that the row’s click handler checks via e.target.closest('.dt-stop') before firing onRowClick.
Visual Treatment
Default mode (no bulk chrome)
-
Wrapper: 1px neutral border, no glow.
-
Header row: 36px tall, off-white background
#fbfbfc, uppercase label, 10.5px font, indigo chevron on sortable columns. -
Row: 38px min-height, single-pixel divider between rows.
-
Hover: pointer cursor when
onRowClickis set; row tint on hover. -
Focused row: indigo-soft background
UI.accentSoft; siblings dimmed. -
Selected row: light-indigo background
#f8f7ff. -
Inactive row (per
isRowInactive): strikethrough text + faint colour; treatment applies across all cells.
Bulk-engaged mode
-
Wrapper: 1.5px indigo border + 4px outer glow
0 0 0 4px rgba(79,70,229,0.10). -
DTBulkBarbonded to the table’s top edge: indigo background, white type, ~11px x 14px padding.-
Indeterminate checkbox (white-stroked) on the left.
-
<count> selectedlabel, plus optional context label (· across page 1). -
Select all N pill (background
rgba(255,255,255,0.10)) whencount < bulkTotal. -
Action buttons right-aligned: white text,
rgba(255,255,255,0.10)background, 1px translucent-white border. Danger actions outlined in soft redrgba(255,180,180,0.55)instead. -
Clear button (
Clear ✕) at the far right, button-less, separated by a thin vertical divider.
-
-
Header row + body rows: unchanged from default mode.
-
Selected rows: tint stays
#f8f7ff(lighter than the bar) — the bar provides the selection-mode signal; row tint is gentle.
Out of Scope (today)
-
Column resize.
-
Column hide/show.
-
Sticky headers.
-
Server-side sort + paginate adapter (C07 is display-only at the substrate level; caller owns sort logic + renders pagination chrome outside the wrapper).
-
Side panels and modals — these are summoned by the caller via
onRowClick, never owned by C07. -
Touch / mobile responsive treatment.
-
Keyboard interaction (Space/Enter to select, Shift+click for range, arrow-key navigation).
These are tracked as Future Use Cases below where they have a known v2 trigger; otherwise they defer until a real consumer surfaces the need.
Design Anchors
-
design-journal/2026-04/jhipster-filterable-data-table.adoc— table substrate pattern (filter / sort / pagination shared layer); C07 is the visual substrate, the headless adapter is the data substrate. Eventual composition. -
design-journal/2026-04/admin-portal-screen-design-prompt-iteration.adoc— broader handoff workflow. -
E02 Event Participants — first canonical consumer; the design prototype was driven by E02’s column set.
-
C04 Reassignment Dialog — modal pattern that calling screens summon via
onRowClick. -
_explore-bulk-toolbar/Bulk Toolbar Variants.html(in the design bundle) — the placement exploration that picked variant E.
Design Decisions
-
Component, not screen (2026-04-30). C07 is the only entry in this catalogue that is a component spec rather than a screen use-case. The C-prefix is allocated because the catalogue’s namespace covers "anything cross-cutting"; component contracts qualify. Distinct from every other Cxx in two ways: no actor flow, no
:design-url:of its own when accessed via a consumer screen. -
Locked bulk treatment — variant E (2026-04-30). 1.5px indigo border + 4px outer glow + bonded
DTBulkBar. Five placements were compared (header swap, neutral bar above filters, indigo bar above filters, floating dock, table-attached glow); E was chosen for the strongest spatial link between actions and rows. -
Mode toggle =
bulkModeprop (2026-04-30). Caller-controlled, not derived from row count. Page chrome above the table is never modified by C07 — no layout jitter on enter/exit. Calling screens decide when to engage bulk mode based on their own selection-count rules (e.g. E02 engages at ≥2 selected; another consumer might engage at ≥1). -
Click-to-act cells are caller-owned (2026-04-30). Cell renderers handle their own clicks; C07 provides the
dt-stopmechanism so row-click bubbling is suppressed cleanly. E02's four click levels (row → side panel; Category → category dialog; Primary Number → C04; Order chip → C06 modal/popover) are all expressible without C07 coupling. -
Sort + pagination are display-only (2026-04-30). C07 renders the sort indicator chevron; the consumer owns sort state + the data fetch. C07 renders no pagination chrome at all; the consumer renders pagination outside the wrapper. Server-side adapters are deferred until a consumer needs them.
-
Select all Npill is caller-driven (2026-04-30). C07 renders the pill whenbulkTotalis provided ANDcount < bulkTotal. The action behind the pill (calling a "select-all-matching" backend endpoint, expanding the selection set, etc.) is the consumer’s job. C07 just exposes the affordance. -
Selected row tint distinct from bar (2026-04-30). Selected rows tint to
#f8f7ff— lighter than the bonded indigo BulkBar#4f46e5. The bar provides the unmistakable mode signal; row tint is gentle so the table stays scannable. -
Inactive treatment is row-wide (2026-04-30).
isRowInactivereturningtruestrikes through and fades the entire row, every cell. Click handlers (row click, cell-render handlers) stay wired — operators may need to interact with cancelled-order chips on inactive EPs, etc.
Consumers
| Consumer | Status |
|---|---|
First canonical consumer. The C07 design used E02’s column set as its prototype. E02 implementation (US #610 under Feature #608) will mount |
|
Future — list of races + results per race. |
|
Future. |
|
Future tenant number/tag inventory — natural C07 consumers. |
|
Future Audit Log screen |
Future. |
Future Affiliations list |
Future. |
Future Use Cases
|
Forward-looking. Not in v1. Captured here so the design intent isn’t lost and so v1 stubs in the right places (or doesn’t, where the absence is the intent). |
FU-1 — Sticky table header
Long lists (E02 with 10k+ participants, E04 with thousands of results) need a sticky header row so the column titles stay visible during scroll. Out of v1 scope per the design. Trigger: first consumer that hits a "I lost track of what column I’m looking at" UX problem on real production data.
FU-2 — Server-side sort + paginate adapter
Today C07 is display-only on sort + has no pagination chrome at all. Consumers wire their own. A v2 adapter — declarative sort prop with caller-provided (column, direction) ⇒ void callback, plus a pagination slot or built-in chrome — would reduce per-consumer plumbing.
The headless filter substrate at design-journal/2026-04/jhipster-filterable-data-table.adoc already prototypes the JhipsterQueryAdapter (toApiQueryParams / toUrlQueryParams / fromRoute) with 35 unit tests. C07 v2’s server-side adapter should compose with that adapter rather than reinvent it.
FU-3 — Operator-controlled column visibility settings
Mirrors E02's FU-7 (captured there because E02 is the first screen to surface the need). Implementation belongs at the C07 level so every consumer benefits — settings live with the component substrate, not per-screen.
A "Column settings" affordance (gear icon / table header utility) lets the operator hide/reveal columns from the canonical set. Persisted per-user, per-screen.
Distinct from the structural displayListField mechanic (which hides a column when, e.g., no event has team-based registrations) — that’s structure-driven; FU-3 is operator-driven.
FU-4 — Column resize + column reorder
Power-user affordance. Hide behind a settings flag if/when surfaced.
FU-5 — Keyboard interaction
Space/Enter to select a focused row; Shift+click for range select; arrow keys to navigate rows. Accessibility-grade UX — defer until a screen-reader pass is on the roadmap.
Notes
|
Active design iteration in progress. v1 of C07 is locked at the component-and-bulk-treatment level. v2 changes are listed under Future Use Cases above (and partially overlap with E02 FU-7). See admin-portal Screen Design Prompt Iteration for the broader handoff workflow. |
v2 prompt — what to change in the next Claude Design pass
Captured here so the next iteration on C07 can pull these forward without re-deriving from chat memory:
-
E02 column-set in the canonical prototype is stale. The C07 design’s
participantColumnsexample renders a Secondary Number column. E02 v2 (post-2026-04-30 review) explicitly drops Secondary Number until US #478 schema pivot lands. v2 prompt should either drop the column from C07’s canonical example OR note the discrepancy with a comment. The C07 component is unaffected; only the example data binding shifts. -
Sort indicator — needs both directions. Today the design shows
chevronDownon sortable columns. To express the "currently sorted column + direction" state, we need a chevronUp/chevronDown pair (matching the column’ssort: 'asc' | 'desc'value) plus a way for the consumer to communicate which column is currently active. v2 prompt should specify the active-sort visual treatment. -
Select all Nsemantics need a backend coordination call-out. The design says clicking the pill "selects every row matching current filters". Today’s/api/event-participantsis paginated; the bulk action has to operate on a much larger set. v2 prompt should call out the contract: the consumer must provide a "fetch all matching IDs" path (likely a new lightweight/api/…?ids-only=trueendpoint family or equivalent), AND the bulk action must accept either a Set of IDs OR a filter expression. C07 doesn’t need to know — but the spec needs to say so. -
Sticky header trigger (FU-1) — the design defers this. v2 prompt should pick a row-count threshold (e.g. > 50 rows visible) above which sticky activates by default, OR a
stickyHeaderprop the consumer sets. Whichever ships, document the rationale. -
Filter/sort/pagination composition with the headless substrate. The journal entry on
jhipster-filterable-data-table.adocdefines theColumnConfig<T>shape with declarative filter+sort+visibility facets. C07’s column shape is currently{key, label, width, sort?, num?, wrap?, stopRowClick?, render?}— no filter dimension. v2 prompt should propose either: (a) extend C07’s column shape to include filter facet, OR (b) introduce a higher-level<FilterableDataTable>wrapper that bridges the headless adapter into C07’s prop surface. Pick one and explain. -
Column visibility settings UI (FU-3) — gear-icon affordance design needed: where does the gear sit, what does the panel look like, how does the operator set "default visible vs hidden", etc. This was raised on E02 FU-7 but the proper home is C07.
-
Bulk-action overflow (FU-7) — when > 5 bulk actions, the inline layout breaks. v2 prompt should design the ⋯ overflow menu treatment.
-
Keyboard + accessibility pass (FU-5) — Space/Enter selection, Shift+click range, arrow-key navigation, ARIA roles on row+cell. v2 prompt should run a focused a11y review.
-
Empty state variants — today the
emptyState: ReactNodeis a single slot. The consumer-side reality has multiple distinct empties: "no data yet" (e.g. event with zero participants), "filter result empty" (data exists but filters narrowed it to nothing), "permission-empty" (rows exist but the operator can see none). Different copy, different CTAs (Import / Clear filters / contact admin). v2 prompt should specify either a richer slot (emptyStateForReason) or document that callers handle the discrimination upstream. -
Touch / mobile treatment (FU-6) — defer or design? Trigger likely arrives when the operator-on-tablet event-day flow is real.
These items are captured here rather than in the design-journal entry because they’re component-spec concerns, not iteration-process concerns. The design-journal entry’s Open Questions list pulls a one-line summary of the most active items when the next session is kicked off.