"use client" import * as React from "react" import type { ComponentDocSpec } from "@/lib/design-system/component-doc-types" import { TableBulkActionBarPreview, TableColumnsPreview, TablePreview, TableToolbarActionsPreview, } from "@/components/design-system/data-display-previews" import { TableInlineEditPreview } from "@/components/design-system/inline-edit-previews" function ex( section: Omit, children: React.ReactNode, ) { return { ...section, children } } export const tableComponentDoc: ComponentDocSpec = { slug: "table", summary: "Three layers for tabular data: static Table chrome for read-only matrices, DataTable for the full sortable grid, and HubTable for list hubs with toolbar, Properties, and view renderers.", sections: [ ex({ id: "column-types", title: "HubTable column types" }, ), ex({ id: "toolbar-actions", title: "Toolbar actions" }, ), ex({ id: "bulk-actions", title: "Bulk action bar" }, ), ex({ id: "inline-edit", title: "Inline input and select cells" }, ), ex({ id: "primitive", title: "Static table" }, ), ], anatomy: [ { part: "Table", description: "Root chrome: rounded border, gridlines, header background tokens." }, { part: "TableHeader / TableBody", description: "Semantic sections for column labels and data rows." }, { part: "DataTable", description: "Sortable, filterable grid primitive. Owns toolbar inset and grid chrome." }, { part: "HubTable", description: "Canonical hub wrapper: useTableState, Properties drawer, view renderers." }, { part: "TableViewMoreFooter", description: "Embedded-surface footer when row count exceeds the preview cap." }, { part: "table-cells", description: "Named cell primitives (ProgressCell, CurrencyCell, PersonIdentityCell, …)." }, { part: "InlineInputCell / InlineSelectCell", description: "Explicit draft editing for short table values." }, ], features: [ { group: "Columns and layout", icon: "fa-table-columns", items: [ { part: "Column resize", icon: "fa-arrows-left-right", description: "Drag the trailing edge of any non-locked header to resize. Widths persist via useTableState.", }, { part: "Drag to reorder", icon: "fa-grip-dots-vertical", description: "Drag a free (unpinned) header to reorder columns. Pinned columns stay fixed.", }, { part: "Pin left / pin right", icon: "fa-thumbtack", description: "Per-column header menu: pin left, pin right, or unpin. Select and Actions columns ship pinned.", }, { part: "Auto pin on overflow", icon: "fa-table-cells-large", description: "When the grid overflows horizontally, pinned columns stay visible with edge fade chrome.", }, { part: "Wrap text", icon: "fa-text-wrap", description: "Per-column header menu toggles wrap on long cell content.", }, { part: "Hidden columns", icon: "fa-eye-slash", description: "Hide or show columns from Table properties. Hidden keys stay in state for restore.", }, ], }, { group: "Sort and search", icon: "fa-arrow-down-arrow-up", items: [ { part: "Per-column sort", icon: "fa-sort", description: "Every sortable column exposes Sort ascending / descending in the header menu with aria-sort.", }, { part: "Multi-column sort", icon: "fa-list-ol", description: "Properties drawer Sort panel stacks multiple sort rules with drag reorder.", }, { part: "Toolbar search", icon: "fa-magnifying-glass", description: "Global query across searchable columns. Optional recent queries when persistKey is set.", }, { part: "Per-column quick search", icon: "fa-magnifying-glass-plus", description: "Header menu search scopes to one column without opening Properties.", }, ], }, { group: "Filters", icon: "fa-filter", items: [ { part: "cellKind filters", icon: "fa-tags", description: "ColumnDef.cellKind drives filter icon, type, and default options (person, date, rating, …).", }, { part: "Filter pills", icon: "fa-filter-list", description: "Active filters render as inline chips in the toolbar with inline editors.", }, { part: "Add filter menu", icon: "fa-plus", description: "Toolbar Add filter lists every filterable column derived from ColumnDef.", }, { part: "Filter types", icon: "fa-sliders", description: "Text, select, person picker, date, date-range, and numeric range filters.", }, { part: "filterFieldContext", icon: "fa-database", description: "Runtime options from row data (unique people, min/max range) stay in sync with Properties.", }, { part: "Conditional rules", icon: "fa-palette", description: "Properties conditional formatting applies background tints to matching cells.", }, { part: "Highlight color source", icon: "fa-eye-dropper", description: "A rule's tint comes from the palette, from a status badge fill so a highlighted row and its chip read as one color, or from a color the workspace brings. Status keeps the token rather than a resolved value, so the fill flips with the theme instead of stranding light ink on a light background. A custom color is stored as a tint at the palette's strength, which is what keeps a single cell ink above the contrast floor in every theme.", }, { part: "Applied indicators", icon: "fa-circle-info", description: "A column marked with a filter, group, or conditional-rule glyph in its header. Narrowing happens in Properties, several columns away from the cells it changes, so without the mark the only way to tell which column is filtered is to reopen the drawer. Derived from state, never declared per column, so a filter cleared from a toolbar chip clears the glyph too.", }, ], }, { group: "Rows and selection", icon: "fa-check-double", items: [ { part: "Row selection", icon: "fa-square-check", description: "Checkbox column with getRowSelectionLabel for accessible bulk-action labels.", }, { part: "Bulk action bar", icon: "fa-layer-group", description: "Floating bar when rows are selected. HubTable accepts bulkActions or bulkActionsSlot.", }, { part: "Toolbar actions", icon: "fa-bolt", description: "Hub commands after Properties. Three on the row, rest under More; labels drop to glyphs as it narrows.", }, { part: "Group by", icon: "fa-object-group", description: "Collapse rows under group headers when a groupable column is chosen in Properties.", }, { part: "Group divider", icon: "fa-grip-lines", description: "The band that heads each batch, in the grouped column's own language. It renders the option's chip when the column declares filter options, and it speaks the column's cell kind otherwise: a person column heads its group with an avatar and name, so the divider names the value the way the rows beneath it do. The fill is the option's declared status tone, or a categorical chart hue hashed off the value, so a grouping by owner or term still gives each batch a colour and keeps it across sorts and reloads. A neutral status takes a hue too, because one grey band among coloured ones reads as a divider that failed to paint. Forced colours drop the whole band to the system canvas.", }, { part: "Group selection", icon: "fa-object-ungroup", description: "A tri-state checkbox leading each group's divider bar, in the same column as the row checkboxes beneath it. Grouping turns one long list into the batches a coordinator acts on, and the action that follows is almost always a bulk one; without this the only way to take a batch is to tick its rows one at a time. It adds and removes only its own group's rows, unlike the header checkbox which replaces the whole selection, so a batch already taken from another group survives. The pinned clone carries a non-interactive mirror, leaving the real bar as the keyboard and screen-reader target.", }, { part: "Row click", icon: "fa-arrow-pointer", description: "onRowClick on DataTable / HubTable for navigation or detail affordances.", }, { part: "Lazy loading", icon: "fa-spinner", description: "Incremental row render for very large client-side datasets (optional lazyLoading prop).", }, ], }, { group: "Design tokens", icon: "fa-swatchbook", items: [ { part: "--dt-header-bg", icon: "fa-table-columns", description: "Column header background. Use bg-dt-header-bg on thead.", }, { part: "--dt-row-bg / hover / selected", icon: "fa-table-rows", description: "Row surfaces — opaque fills required for pinned columns.", }, { part: "--dt-group-bg", icon: "fa-object-group", description: "Collapsible group header row tint.", }, { part: "DATA_TABLE_*_INSET", icon: "fa-arrows-left-right-to-line", description: "Toolbar px-4 lg:px-6 and grid mx-4 lg:mx-6 — omit when parent pads.", }, { part: "--interactive-hover-*", icon: "fa-hand-pointer", description: "Ghost buttons, toolbar hovers, and icon-button chrome.", }, { part: "--sticky-edge-fade", icon: "fa-table-cells-large", description: "Pinned column edge gradient on horizontal overflow.", }, ], }, { group: "HubTable and views", icon: "fa-table", items: [ { part: "useTableState", icon: "fa-database", description: "Single state bag for sort, filters, columns, pins, search, pagination, and display options.", }, { part: "Table properties drawer", icon: "fa-sliders", description: "Columns, filters, sort, display, conditional rules, and view-type tiles in one sheet.", }, { part: "View renderers", icon: "fa-grid-2", description: "table, list, board, dashboard, folder, panel, and tree bodies share the same state.", }, { part: "Pagination", icon: "fa-table-list", description: "Optional PaginationBar on primary hubs. Toggle from Properties display options.", }, { part: "embeddedPreview", icon: "fa-ellipsis", description: "Five-row cap with View more footer on embedded surfaces. View more navigates via onEmbeddedPreviewViewMore; edgeInset follows page padding.", }, { part: "persistKey", icon: "fa-floppy-disk", description: "Product-namespaced localStorage restore for filters, sort, column layout, and display options.", }, ], }, ], api: [ { prop: "columns", type: "ColumnDef[]", description: "HubTable / DataTable — keys, labels, cell renderers, filter, cellKind." }, { prop: "cellKind", type: "string", description: "Drives filter preset and Properties field type for the column." }, { prop: "filter", type: "FilterConfig", description: "Override preset when enum options or date-range UX must differ from cellKind." }, { prop: "defaultSort", type: "{ key, dir }", description: "Initial sort column and direction." }, { prop: "state", type: "useTableState", description: "Shared table state across table, list, board, and dashboard views." }, { prop: "selectable", type: "boolean", defaultValue: "true", description: "Row checkbox column and bulk-action bar." }, { prop: "groupable", type: "boolean", defaultValue: "false", description: "Enable group-by rows in Properties." }, { prop: "pagination", type: "boolean", defaultValue: "false", description: "HubTable-owned PaginationBar. Incompatible with embeddedPreview." }, { prop: "embeddedPreview", type: "boolean", description: "Cap at five rows with View more footer on embedded surfaces." }, { prop: "edgeInset", type: "boolean", defaultValue: "true", description: "DataTable toolbar/grid mx/px inset. false when parent owns page padding." }, { prop: "persistKey", type: "string", description: "Product-namespaced key for restored table state (use productPersistKey)." }, { prop: "toolbarActions", type: "ResponsiveAction[]", description: "Hub commands after Properties. Max three on the row, rest under More. Labels drop to glyphs when the filter chips run out of line. Each needs a shortcut." }, { prop: "bulkActions", type: "BulkAction[]", description: "Commands on selected rows. Render in the selection bar, not the toolbar." }, { prop: "renderers", type: "HubTableRenderers", description: "Per-view bodies; omit table to use default DataTable renderer." }, { prop: "displayOptionsInit", type: "object", description: "Initial toolbar search, column labels, gridlines, row height, pagination toggle." }, ], extraImports: [ { label: "DataTable", path: "@/components/data-table" }, { label: "HubTable", path: "@/components/data-views" }, { label: "Table cells", path: "@/components/data-views/table-cells" }, { label: "useTableState", path: "@/components/data-table/use-table-state" }, { label: "Embedded row limit", path: "@exxatdesignux/ui/lib/hub-table-embedded-preview" }, ], guidelines: { do: [ "Use HubTable inside ListPageTemplate for every primary list hub and showcase that mounts a data grid.", "Set cellKind on each ColumnDef and import named cells from table-cells instead of inline formatters.", "Pass embeddedPreview on dashboard sections and design-system demos so tables show five rows with View more.", "Wire onEmbeddedPreviewViewMore to navigate to the full hub route — do not rely on inline expand in docs.", "Use ui/Table only for tiny inline matrices inside a preview (chart legend, 2×2 demos).", "Catalog API sections use DataTable via ComponentDocApiTable — resizable columns, sort, header menus.", "Let auto pin handle horizontal overflow; pin Select and Actions columns deliberately.", "Pass toolbarActions for hub commands (Add, Import, Export) so they inherit the three-action cap and the More overflow.", "Trust the row to sell its own labels once filters fill the line — an action reads fine as a glyph plus a Tip, a chip's value does not.", "Put the page's one primary here when it acts on the list (Library's New question sits a row above the questions it adds to), or in PageHeader when it acts on the record — never both.", "Use InlineInputCell and InlineSelectCell for short reversible edits. Include row identity in each label.", ], dont: [ "Do not hand-roll a button row above the grid — toolbarActions already handles sizing, the cap, and narrow widths.", "Do not put a filled primary in both PageHeader.actionItems and toolbarActions — a page gets one.", "Do not put selection commands in toolbarActions; they belong in bulkActions where they can read the selection.", "Do not build product list pages with ui/Table alone or third-party grids.", "Do not mount raw DataTable inside ListPageTemplate — users lose filters and Properties.", "Do not stack horizontal padding on a parent that already wraps HubTable (double gutter regression).", "Do not enable embeddedPreview on primary hubs that need full pagination and row counts.", "Do not hand-roll filter inputs above the grid — use cellKind + filter pills + Properties.", "Do not use inline cells for destructive changes, long text, or values that need complex validation.", ], }, accessibility: [ "Sortable headers expose aria-sort and a visible direction glyph.", "Icon-only header controls and row actions require aria-label.", "Row checkboxes use getRowSelectionLabel for bulk-action announcements.", "Resize handles use role=separator with an accessible name.", "View more uses the ghost Button variant with resting fill for affordance.", "Inline cells keep row activation and Enter or Escape handling local to the editor.", ], relatedSlugs: [ "data-table", "hub-table", "table-cells", "table-properties-drawer", "selection-tile-grid", "list-page-template", "view-segmented-control", "inline-edit", ], }