feat(DataTable): add truncate prop to Column with auto title tooltip#249
Merged
Conversation
Adds `truncate?: boolean` to `Column<TRow>`. Text overflow handling is universal across list tables; today callers wire CSS truncate + a `title` attr inside their custom `render` to surface the full value on hover. This pushes both concerns to the column level: cells get `truncate max-w-0` when truncate is true, and a `title` is set automatically from `accessor` whenever it returns a string or number. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
Contributor
|
@copilot does this PR implement behaviour to show tooltip as the description says? |
Contributor
Yes — |
IzumiSy
reviewed
May 13, 2026
Brings in #247 (type-aware cell renderers), #248 (column align), #251 (oxlint upgrade), #253 (accessor narrowing docs), and #254 (inferColumns no longer carries an accessor). Conflicts: - types.ts: dropped the duplicate `accessor` declaration from `ColumnBase` (it now lives per-branch in `ColumnTypeBranch` per #247) and kept both new fields — `align` (from #248) and `truncate` (from this branch). Updated `truncate`'s JSDoc to describe the Tooltip wiring rather than the `title` attribute. - field-helpers.ts: kept main's spread-based `column()` so the discriminated union survives. - data-table.tsx: combined `align` and `truncate` into the same cell classes; rebuilt `content` via `col.render ?? renderTypedCell(...)` (from #247). - data-table.test.tsx / docs: kept both feature describe blocks and combined doc tables. Per @IzumiSy's review (#249), the truncate tooltip now uses the app-shell `<Tooltip>` component (with a `Tooltip.Provider` mounted at `DataTable.Root`) instead of the browser `title` attribute. The cell is wrapped in `Tooltip.Trigger` only when `accessor` returns a stringifiable primitive — objects / arrays / no accessor still apply the truncate CSS but skip the tooltip wiring. Tests: 1010 passing (was 992 + 8 new truncate tests). Lint, fmt, workspace typecheck all clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
commit: |
Adds a `description` field to the Product mock with varied-length marketing copy across the 20 rows (most ~140-200 chars, a few short ~30-60 chars). The demo now includes a Description column with `type: "text"`, `truncate: true`, and an explicit `accessor` — clicking through the demo shows the ellipsis behavior on long rows, the `<Tooltip>` reveal on hover, and the no-tooltip path for short rows that happen to fit. The short-description rows aren't a special case in the renderer (they're still wired into a Tooltip — the tooltip just happens to show the same text the cell already shows). They keep the demo honest about the current limitation: the wiring fires based on whether `accessor` returns a primitive, not whether the cell is actually overflowing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…or is omitted
Route the truncate tooltip through `getCellValue` (now exported from
cell-renderers.tsx) so it shares the same precedence rule the built-in
typed renderers already use: explicit `accessor` first, then
`row[col.id]` when `id` is set.
Pair this with a small change to `inferColumns` — pin `id` to the
metadata field name in the returned column. With both in place,
`column({ ...infer("description"), truncate: true })` now wires the
tooltip automatically: the cell's `getCellValue` falls through from
the absent accessor to `row["description"]`, which is the same value
the inferred `render` reads.
The string/number type guard on `tooltipLabel` stays — objects, arrays,
and absent values still skip the tooltip rather than rendering ugly
content. The demo drops the explicit `accessor: (row) => row.description`
to demonstrate the simpler shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
IzumiSy
reviewed
May 14, 2026
| // `render` for those cases. | ||
| let tooltipLabel: string | undefined; | ||
| if (col.truncate) { | ||
| const raw = getCellValue(row, col); |
Contributor
There was a problem hiding this comment.
Using getCellValue here makes sense!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
truncate: booleantoColumn<TRow>. Text overflow is universal across list tables — today callers wire CSS truncate + atitleattribute inside their customrenderto surface the full value on hover. This pushes both concerns to the column level.truncate: true, body cells gettruncate max-w-0(so they stay shrinkable inside the row), and the full value is auto-revealed in an app-shell<Tooltip>on hover when the resolved cell value is a stringifiable primitive.renderfor those cases.How the tooltip resolves its value
The tooltip goes through the same precedence rule the built-in
typerenderers use —getCellValue(now exported fromcell-renderers.tsx):col.accessor(row)if defined,row[col.id]ifidis set,Routing both the typed renderers and the truncate tooltip through one helper means consumers don't have to learn two different "what does this cell read?" rules. As a bonus,
inferColumnsnow pinsidto the metadata field name, so the spread pattern works without an explicitaccessor:Accessibility considerations
titleattribute: properrole="tooltip"+aria-describedbysemantics from Base UI; screen readers get the full text from the cell DOM regardless of CSS truncation.title): the<td>isn't focusable, so sighted keyboard-only users can't trigger the tooltip. AddingtabIndex={0}to tooltip-wired cells is a possible follow-up; SR users are unaffected because cell text is intact.Why this is part of a three-PR series
This is 3 / 3 in a series porting patterns from a downstream consumer's
ReconciliationListinto the platformDataTable. PR 1 (#247) addstypefor built-in cell renderers; PR 2 (#248) addsalign. Each PR is cut frommainand is independent — they can land in any order.Notes for reviewers
max-w-0is what makes the truncation actually take effect inside table layout. Without it, the cell sizes to its content and the ellipsis never fires. Documented in the JSDoc onColumn.truncateso callers know to anchor row width viawidthon neighboring columns (or fixed-size columns like selection / row actions).Tooltip.Provider(delay 300ms) is mounted atDataTable.Root, so all truncate tooltips in a single table share hover-delay state. Benign no-op for tables that don't usetruncate.inferColumnsnow setsid: fieldNamein addition tolabel. Previously consumers callingtoggleColumn(label)on inferred columns would have used the label string; nowtoggleColumn(fieldName)works (and is the more stable identifier). The label-based form continues to resolve via theid ?? labelfallback.Descriptioncolumn with varied-length copy (most 140–200 chars, a few 30–60 chars) to exercise the ellipsis + tooltip behavior end-to-end viacolumn({ ...infer("description"), type: "text", truncate: true }).Test plan
pnpm --filter @tailor-platform/app-shell type-check— cleanpnpm -r type-check— workspace clean (vite + nextjs demos typecheck)pnpm --filter @tailor-platform/app-shell test— 1011 passed (6 new truncate tests: classes, string/number accessor wiring, object accessor skipped, no-accessor skipped,row[col.id]fallback wired)pnpm --filter @tailor-platform/app-shell lint— cleanpnpm --filter @tailor-platform/app-shell build— dist.d.tsreflects the newidon inferred columnspnpm fmt:check— clean across 346 filestruncate: trueon a column withaccessor(or an inferred column) indata-table-demo, narrow the column, confirm the ellipsis appears and hover shows the full value via the app-shell<Tooltip>🤖 Generated with Claude Code