@devslab/dds-table-solid

中文文档
0.33.0发布于 2026/11/12SEE LICENSE IN LICENSETypeScriptES Module构建来源已验证近 30 天下载 2477

Declarative data table for the DevsLab Design System

原始使用文档
文档目录8 节
  1. DataTable props
  2. Column<T>
  3. Sorting: sort
  4. Optional workspace interactions
  5. Paging: page
  6. Folding: fold
  7. Labels are required, not defaulted
  8. What this package does not export

@devslab/dds-table-solid

Declarative data table for DDS, built on TanStack Table (pinned to the exact version 9.2.4 — see docs/decisions.md D-021 for why an exact pin). One component, DataTable, plus the Column<T> and DataTableLabels types used to describe it. The table itself decides nothing about words, routes, or what "next page" means — every string a visitor can read is a prop.

pnpm add @devslab/dds-table-solid

This package ships no stylesheet of its own — import @devslab/dds-css's table styles once in the application shell, alongside the tokens CSS it depends on:

import "@devslab/dds-tokens/tokens.css";
import "@devslab/dds-css/components/table.css";

Without it, <DataTable> renders an unstyled table: fold does nothing (folding is entirely the CSS media query in table.css), and the visually-hidden caption and actions-column header render visible.

import { DataTable, type Column, type DataTableLabels } from "@devslab/dds-table-solid";

type Job = { id: string; name: string; count: number };

const columns: Column<Job>[] = [
  { id: "name", label: "Name", cell: (row) => row.name, sortBy: (row) => row.name, rowHeader: true },
  { id: "count", label: "Count", cell: (row) => String(row.count), sortBy: (row) => row.count, numeric: true, fold: true },
];

const labels: DataTableLabels = {
  sortBy: "Sort by {column}",
  actions: "Actions",
  nextPage: "Next page",
};

<DataTable rows={jobs} columns={columns} caption="Jobs" labels={labels} sort="client" />;

DataTable props

PropTypeRequired / defaultMeaning
rowsreadonly T[]requiredThe data, in the order to render (see Sorting for who is responsible for that order).
columnsreadonly Column<T>[]requiredColumn declarations — see Column<T> below.
captionstringrequiredThe table's accessible name, rendered visually hidden (<caption class="dds-visually-hidden">).
labelsDataTableLabelsrequiredEvery word the component can render — see "Labels are required" below.
sortSortModeoptional, no sort buttons or order text when omittedClient sorting, a server-order description, or controlled server sorting.
resizingColumnResizingoptional, disabled by defaultHeader resize handles, bounds, accessible label, and commit/reset callback.
selectionRowSelection<T>optional, disabled by defaultStable row identity, controlled selection, and selection callback.
density"comfortable" | "dense"optional, default "comfortable" (40px rows)"dense" adds .dds-table--dense (36px rows, tighter cell padding). Either way, row actions and small buttons in cells are held to the row height (32px buttons in 40px rows, 24px in 36px rows; 44px on touch) — see docs/components.md Table.
scroll"auto" | "tall"optional, default "auto""tall" adds .dds-table-wrap--tall: a max-block-size: 70vh scrolling body with a sticky header, for a long in-page list that shouldn't push the rest of the page down.
minWidthstring (a CSS length)optionalSets the <table>'s min-inline-size, so narrow viewports scroll the table horizontally (inside .dds-table-wrap) instead of crushing its columns.
actions(row: T) => JSX.ElementoptionalRenders a trailing cell per row (e.g. row-level buttons); adds a visually-hidden header cell from labels.actions.
detail(row: T) => JSX.ElementoptionalRenders an expandable row under a given row. Returning null (e.g. for a row that is not expanded) renders no detail <tr> for that row at all — not an empty one.
page{ nextHref?: string }optionalSee Paging below.
emptyJSX.ElementoptionalRendered instead of the <table> when rows is empty. The stated-order text and the pagination link (if any) still render — an empty page of a cursor-paginated list still has an order and still needs a way forward.

Column<T>

FieldTypeMeaning
idstringStable column identity — used for the sort key and the data-fold/data-column attributes.
labelstringHeader text (and the sort button's visible label).
cell(row: T) => JSX.ElementRenders the cell body. Visual markup stays yours; the column def TanStack sees carries nothing but the sort accessor.
sortBy?(row: T) => string | number | nullPresent → the column is sortable (in sort="client" mode only) and gets a sort button. Absent → a plain header, never a button. Return null for "no value"; those rows sort last (sortUndefined: "last"), never first.
width?stringA CSS width. Setting it on any column switches the whole table to table-layout: fixed and emits a <colgroup>.
resizable?booleanSet to false to omit this column's handle when resizing is enabled.
fold?booleanMarks the column droppable on narrow viewports (see Folding below).
numeric?booleanRight-aligns the column and applies tabular figures.
rowHeader?booleanRenders this column's cell as <th scope="row"> instead of <td> — exactly one column per table should set it.

Sorting: sort

SortMode is exported and accepts three shapes:

"client" | { statedOrder: string } | { value: SortValue; onChange: (value: SortValue) => void; statedOrder?: string }
  • sort="client" — the table holds its whole list in memory. Columns with sortBy render a sort button; clicking one asks TanStack Table to reorder the rows already in hand. This is safe precisely because "all the rows" and "the rows shown" are the same set.
  • sort={{ statedOrder: "…" }} — for a table that shows a server-ordered, cursor-paged slice. No column renders a sort button in this mode, even if its sortBy is set: a client-side sort could only reorder the rows on the current page, while the table's real order is whatever the backend query produced across pages the browser has never seen. A button that did that would look like it sorted the data and would actually misreport it. Instead the table prints the actual order as text above itself (.dds-table__order) — statedOrder is that sentence, e.g. "Newest first", supplied by the consumer because DDS does not know the backend's ordering vocabulary.

There is no third mode that lets a server-ordered table opt into client sorting for "just this column" — the failure mode (silently reporting a per-page order as the whole list's order) is the same regardless of how many columns are involved.

Optional workspace interactions

All interactions below are opt-in; existing tables retain their behavior.

const [sort, setSort] = createSignal<SortValue>(null);
const [selectedId, setSelectedId] = createSignal<string | null>(null);
<DataTable rows={rows} columns={columns} caption="Jobs" labels={labels}
  sort={{ value: sort(), onChange: setSort }}
  resizing={{ label: "Resize {column}", minWidth: 80, maxWidth: 1200,
    onChange: (widths, reason) => recordLayoutChange(widths, reason) }}
  selection={{ rowId: row => row.id, selectedId: selectedId(),
    onSelect: row => setSelectedId(row.id) }} />;

Import SortValue, ColumnResizing, and RowSelection from this package for typed application adapters. Controlled sorting cycles ascending, descending, then null. It only requests a change; the application must fetch and supply the correctly ordered server result. It never sorts the current page locally. The existing { statedOrder } mode remains a description without sort buttons.

resizing.label is the application-owned accessible label ({column} is replaced with the column name). Drag a header separator to change its width. Left/Right change width by 12px (Shift: 50px); Home/End use the bounds. Double-click or Escape clears all width overrides, restoring declared column widths and the original layout. Set column.resizable = false to omit an individual handle. Pointer cancellation restores the pre-drag layout; unmounting removes pending pointer listeners. Resize callbacks fire on commit or reset, rather than every pointer move. Width overrides last for this mounted table only; persistence and application analytics remain application-owned.

Selection is controlled by a stable row ID. Click, Enter, and Space select a row; embedded buttons, links and form controls keep their own behavior. Selection does not navigate, open dialogs, or change business data by itself.

Paging: page

page?: { nextHref?: string }

nextHref renders the next-page control as <a class="dds-btn dds-btn--secondary" href="…">, wrapped in a labeled <nav> — never a <button onClick>. A cursor-paginated list is just a URL with a different query string, and a real link keeps that working with JavaScript disabled, on a slow connection before hydration, and in a browser's native "open in new tab". Omitting nextHref (or the whole page prop) hides the pagination <nav> entirely rather than rendering a disabled control.

Folding: fold

Columns marked fold: true disappear below a fixed breakpoint (@media (max-width: 56.25rem) in dds-css's table.css) via a plain CSS rule on [data-fold] — there is no JavaScript column-visibility state, no resize observer, and no per-viewport re-render. The column is always present in the DOM; the browser decides whether to paint it. This means folding survives SSR (a server-rendered narrow viewport already hides the column, correctly, before any script runs) and costs nothing to keep in sync with sorting or paging state, because there is no state to keep in sync.

Labels are required, not defaulted

DataTableLabels has no optional fields and DataTable ships no fallback strings:

type DataTableLabels = {
  sortBy: string;   // "{column}" is replaced with the column's label
  actions: string;  // header of the trailing actions column (visually hidden)
  nextPage: string; // accessible name and text of the next-page link
};

DDS does not choose the language a consumer's users read in, so it does not ship English (or any other) copy for a screen reader to announce. A consumer that forgets a label gets a TypeScript error, not a silently English or silently blank control.

What this package does not export

DataTable is the only component. The row/cell primitives TanStack Table's own docs build tables from (a Th, a Td, a useDataTable hook) are deliberately unexported — there is exactly one consumer today, and a second API surface to keep stable is a cost paid before anything needs it. When a second consumer needs a table shape DataTable's props cannot express, export the primitives then.

Client-side pagination and filtering (TanStack Table's getPaginationRowModel et al.) are similarly not wired in: this component's callers are always either a small in-memory list (sort="client") or an already-paged server response (page.nextHref), so neither feature would ever fire — it would only sit in every consumer's bundle.

版本与发布记录

@devslab/dds-table-solid 共发布 19 个版本,首次发布于 2026/10/17;过去 12 个月发布了 19 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.33.0latest2026/11/12今天
0.32.22026/11/111 天前
0.32.12026/11/102 天前
0.32.02026/11/102 天前
0.31.02026/11/092 天前
0.30.02026/11/084 天前

关于 @devslab/dds-table-solid 的常见问题

@devslab/dds-table-solid 是什么?

Declarative data table for the DevsLab Design System

如何安装 @devslab/dds-table-solid?

在项目目录运行 npm install @devslab/dds-table-solid,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:solid-js(1.9.15)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@devslab/dds-table-solid 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@devslab/dds-table-solid 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@devslab/dds-table-solid 可以商用吗?

@devslab/dds-table-solid 使用 SEE LICENSE IN LICENSE 许可。许可条款写在包内的独立文件中,请阅读该文件确认可否商用。以上为概括,不构成法律意见,请以许可原文为准。

@devslab/dds-table-solid 的下载量有多少?

根据 npm 官方下载统计,@devslab/dds-table-solid 在 2026/10/12 – 2026/11/10 这 30 天内被下载 2,477 次,日均约 83 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@devslab/dds-table-solid 还在维护吗?

最新版本 0.33.0 发布于 2026/11/12(今天);至今共发布 19 个版本,其中过去 12 个月发布了 19 个;首次发布于 2026/10/17。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。