Skip to content

feat(DataTable): add truncate prop to Column with auto title tooltip#249

Merged
erickteowarang merged 4 commits into
mainfrom
feat/data-table/column-truncate
May 14, 2026
Merged

feat(DataTable): add truncate prop to Column with auto title tooltip#249
erickteowarang merged 4 commits into
mainfrom
feat/data-table/column-truncate

Conversation

@erickteowarang
Copy link
Copy Markdown
Contributor

@erickteowarang erickteowarang commented May 13, 2026

Summary

  • Adds truncate: boolean to Column<TRow>. Text overflow is universal across list tables — today callers wire CSS truncate + a title attribute inside their custom render to surface the full value on hover. This pushes both concerns to the column level.
  • When truncate: true, body cells get truncate 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.
  • Non-stringifiable values (objects, arrays) and absent values still apply the truncate CSS but skip the tooltip wiring — callers can render their own tooltip inside render for those cases.

How the tooltip resolves its value

The tooltip goes through the same precedence rule the built-in type renderers use — getCellValue (now exported from cell-renderers.tsx):

  1. col.accessor(row) if defined,
  2. otherwise row[col.id] if id is set,
  3. otherwise no tooltip.

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, inferColumns now pins id to the metadata field name, so the spread pattern works without an explicit accessor:

// Explicit accessor (any column type):
column({
  label: "Description",
  accessor: (row) => row.description,
  truncate: true,
});

// Inferred — id resolves via `row[col.id]`, no accessor needed:
column({ ...infer("description"), truncate: true });

Accessibility considerations

  • Wins over a title attribute: proper role="tooltip" + aria-describedby semantics from Base UI; screen readers get the full text from the cell DOM regardless of CSS truncation.
  • Known gap (not regressing from title): the <td> isn't focusable, so sighted keyboard-only users can't trigger the tooltip. Adding tabIndex={0} to tooltip-wired cells is a possible follow-up; SR users are unaffected because cell text is intact.
  • Tooltip fires regardless of actual overflow — short values that fit also show a tooltip on hover. Detecting real overflow needs JS DOM measurement; out of scope here.
  • Touch — Base UI tooltips don't surface on tap; touch users see the truncated value only. Standard limitation.

Why this is part of a three-PR series

This is 3 / 3 in a series porting patterns from a downstream consumer's ReconciliationList into the platform DataTable. PR 1 (#247) adds type for built-in cell renderers; PR 2 (#248) adds align. Each PR is cut from main and is independent — they can land in any order.

Notes for reviewers

  • max-w-0 is 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 on Column.truncate so callers know to anchor row width via width on neighboring columns (or fixed-size columns like selection / row actions).
  • A Tooltip.Provider (delay 300ms) is mounted at DataTable.Root, so all truncate tooltips in a single table share hover-delay state. Benign no-op for tables that don't use truncate.
  • inferColumns now sets id: fieldName in addition to label. Previously consumers calling toggleColumn(label) on inferred columns would have used the label string; now toggleColumn(fieldName) works (and is the more stable identifier). The label-based form continues to resolve via the id ?? label fallback.
  • Demo update: the data-table demo now includes a Description column with varied-length copy (most 140–200 chars, a few 30–60 chars) to exercise the ellipsis + tooltip behavior end-to-end via column({ ...infer("description"), type: "text", truncate: true }).

Test plan

  • pnpm --filter @tailor-platform/app-shell type-check — clean
  • pnpm -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 — clean
  • pnpm --filter @tailor-platform/app-shell build — dist .d.ts reflects the new id on inferred columns
  • pnpm fmt:check — clean across 346 files
  • Reviewer smoke-test: set truncate: true on a column with accessor (or an inferred column) in data-table-demo, narrow the column, confirm the ellipsis appears and hover shows the full value via the app-shell <Tooltip>

🤖 Generated with Claude Code

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>
@IzumiSy
Copy link
Copy Markdown
Contributor

IzumiSy commented May 13, 2026

@copilot does this PR implement behaviour to show tooltip as the description says?

Copy link
Copy Markdown
Contributor

Copilot AI commented May 13, 2026

@copilot does this PR implement behaviour to show tooltip as the description says?

Yes — d0ef9be implements it as a native browser hover tooltip via the cell’s title attribute when truncate: true and accessor returns a string or number (packages/core/src/components/data-table/data-table.tsx). The behavior is covered in packages/core/src/components/data-table/data-table.test.tsx. It does not render the app-shell Tooltip component; it relies on the browser’s default tooltip.

Copilot AI requested a review from IzumiSy May 13, 2026 07:26
Comment thread packages/core/src/components/data-table/data-table.tsx Outdated
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>
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 14, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@tailor-platform/app-shell@249
npm i https://pkg.pr.new/@tailor-platform/app-shell-sdk-plugin@249
npm i https://pkg.pr.new/@tailor-platform/app-shell-vite-plugin@249

commit: e92a4e1

Erick Teowarang and others added 2 commits May 14, 2026 17:42
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>
Copy link
Copy Markdown
Contributor

@IzumiSy IzumiSy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

// `render` for those cases.
let tooltipLabel: string | undefined;
if (col.truncate) {
const raw = getCellValue(row, col);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using getCellValue here makes sense!

@erickteowarang erickteowarang merged commit b644bdb into main May 14, 2026
5 checks passed
@erickteowarang erickteowarang deleted the feat/data-table/column-truncate branch May 14, 2026 23:06
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.

3 participants