From fa3136cb94c84029ec404de330b13ed8c7048844 Mon Sep 17 00:00:00 2001 From: Henry Wilkinson Date: Mon, 30 Mar 2026 01:37:08 -0400 Subject: [PATCH 1/4] Refactor table components to use CSS grid for layout --- packages/demo/src/components/demo/table.tsx | 90 +++++++------ .../demo/src/content/components/table.mdx | 125 ++++++++---------- packages/ui/src/components/index.ts | 1 - .../table/table-components.module.css | 29 ++-- .../src/components/table/table-components.tsx | 30 ++--- .../ui/src/components/table/table.module.css | 44 ------ packages/ui/src/components/table/table.tsx | 109 --------------- 7 files changed, 136 insertions(+), 292 deletions(-) delete mode 100644 packages/ui/src/components/table/table.module.css delete mode 100644 packages/ui/src/components/table/table.tsx diff --git a/packages/demo/src/components/demo/table.tsx b/packages/demo/src/components/demo/table.tsx index 92d9f21c..6795a725 100644 --- a/packages/demo/src/components/demo/table.tsx +++ b/packages/demo/src/components/demo/table.tsx @@ -27,20 +27,22 @@ interface TableDemoProps { elevation?: Elevation; } +const defaultCols = "1fr 1fr auto auto auto"; + export const TableDemo = ({ variant = "default", elevation, }: TableDemoProps) => { if (variant === "column-sizing") { return ( - + - Name - Email - Role - Status - + Name + Email + Role + Status + @@ -84,22 +86,23 @@ export const TableDemo = ({ if (variant === "truncation") { return ( - + - Name - - Email - - Role - Status - + Name + Email + Role + Status + Alice Cooper - + alice.cooper.very.long.email.address@example.com Admin @@ -112,7 +115,7 @@ export const TableDemo = ({ Bob Smith - bob@example.com + bob@example.com User Active @@ -123,7 +126,7 @@ export const TableDemo = ({ Charlie Brown - + charlie.brown.another.really.long.address@longdomain.example.com Viewer @@ -142,23 +145,27 @@ export const TableDemo = ({ if (variant === "responsive") { return (
- + Name - Email - Role + Email + Role Status - + Alice Cooper - + alice@example.com - Admin + Admin Active @@ -168,10 +175,10 @@ export const TableDemo = ({ Bob Smith - + bob@example.com - User + User Active @@ -181,10 +188,10 @@ export const TableDemo = ({ Charlie Brown - + charlie@example.com - Viewer + Viewer Inactive @@ -200,14 +207,19 @@ export const TableDemo = ({ if (variant === "sticky-header") { return ( - + Name Email Role Status - + @@ -282,7 +294,7 @@ export const TableDemo = ({ if (variant === "with-sorter") { return ( - + @@ -325,7 +337,7 @@ export const TableDemo = ({ Status - + @@ -369,14 +381,14 @@ export const TableDemo = ({ if (variant === "empty-state") { return ( - + Name Email Role Status - + @@ -395,14 +407,14 @@ export const TableDemo = ({ if (variant === "empty-state-custom") { return ( - + Name Email Role Status - + @@ -423,14 +435,18 @@ export const TableDemo = ({ } return ( - + Name Email Role Status - + diff --git a/packages/demo/src/content/components/table.mdx b/packages/demo/src/content/components/table.mdx index ec080189..e42d6673 100644 --- a/packages/demo/src/content/components/table.mdx +++ b/packages/demo/src/content/components/table.mdx @@ -13,11 +13,13 @@ import { ELEVATION } from "@eqtylab/equality"; Tables are built from compositional primitives that map directly to [HTML table elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/table). Each primitive is a styled wrapper that accepts all native HTML attributes, giving you full control over layout, sizing, and responsiveness. -- **TableContainer:** Wraps the `` in a scrollable container with elevation styling. +Rows use CSS Grid with [subgrid](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout/Subgrid) for column sizing. Define columns once via the `columns` prop on `` — all rows share the same column tracks. + +- **TableContainer:** Wraps the `
` in a scrollable container with elevation styling. Accepts a `columns` prop for CSS Grid column sizing. - **TableHeader / TableBody / TableFooter:** Semantic section wrappers (``, ``, ``). -- **TableRow:** A table row (``) that accepts `onClick` for clickable rows. -- **TableHead:** A column header cell (``) that inherits column tracks via subgrid. +- **TableHead:** A column header cell (`
`) with a `truncate` prop for overflow control. -- **TableCell:** A data cell (``) with a `truncate` prop for overflow control. +- **TableRow:** A table row (`
`). +- **TableCell:** A data cell (``). Supports `colSpan` which auto-converts to `grid-column`. ## Usage @@ -39,17 +41,19 @@ import { Basic usage: ```tsx - + Name Email + Role Alice Cooper alice@example.com + Admin @@ -86,7 +90,7 @@ import { TableHead, } from "@eqtylab/equality"; - + @@ -113,7 +117,7 @@ Use the `border` prop on `TableContainer` to apply an elevation-aware border wit #### Usage ```tsx - + Name @@ -126,7 +130,7 @@ Use the `border` prop on `TableContainer` to apply an elevation-aware border wit ### Empty State -When there are no rows, render empty state content in a `` that spans all columns with `colSpan`. +When there are no rows, render empty state content in a `` that spans all columns with `colSpan`. The `colSpan` value is automatically converted to `grid-column` for CSS Grid compatibility. @@ -145,7 +149,7 @@ Use the `sticky` prop on `` to keep column headers visible while sc #### Usage ```tsx - + Name @@ -160,80 +164,68 @@ Use the `sticky` prop on `` to keep column headers visible while sc --- -Use the `tableLayout` prop on `TableContainer` to control how column widths are calculated. Set explicit widths on `` cells using Tailwind classes. +The `columns` prop accepts a CSS [`grid-template-columns`](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns) value. All rows share the same column tracks via CSS subgrid. -### Fixed Layout +### Fixed and Flexible Columns -With `tableLayout="fixed"`, columns respect explicit widths exactly. This is the recommended approach when you need predictable column sizing. +Mix `fr` units for flexible columns with fixed pixel values for predictable sizing. #### Usage ```tsx - + - Name - Email - Role - Status - + Name + Email + Role + Status + ``` -### Min and Max Width - -Use `min-w-` or `max-w-` Tailwind classes on `` to constrain column sizes. `min-w-` works in both `auto` and `fixed` layouts. `max-w-` works best with the default `auto` layout. - -```tsx -{ - /* Column won't shrink below 150px */ -} -Description; - -{ - /* Column won't grow beyond 300px — pair with truncate */ -} - - Email -; -``` - -### Shrink to Content +### Content-Sized Columns -In the default `auto` layout, use `w-[1%]` to minimize a column to fit its content. The browser's table algorithm ensures the column still renders at least as wide as its content, while giving all remaining space to other columns. This is useful for action columns or icon-only columns. +Use `auto` for columns that should shrink to fit their content. This is useful for action columns or icon-only columns. ```tsx -{/* Actions */} + ``` ## Truncation --- -Use the `truncate` prop on `` and `` to clip overflowing text with an ellipsis. This works best with `tableLayout="fixed"` and an explicit column width so the cell has a defined boundary to truncate against. +Use Tailwind's `truncate min-w-0` classes directly on `` and `` to clip overflowing text with an ellipsis. For truncation to work, the column must use `minmax(0, *)` in the `columns` definition so cells can shrink below their content size. #### Usage ```tsx - + - Name - - Email - + Name + Email + Role + Status + Alice Cooper - alice.cooper.very.long.email@example.com + + alice.cooper.very.long.email@example.com + + Admin + Active + @@ -243,7 +235,7 @@ Use the `truncate` prop on `` and `` to clip overflowing t --- -Use [container queries](https://tailwindcss.com/docs/responsive-design#what-are-container-queries) to show or hide columns based on the table's container width. Wrap the table in a `@container` element and apply `hidden @md:table-cell` (or similar) to both the `` and `` for columns that should collapse. +Use [container queries](https://tailwindcss.com/docs/responsive-design#what-are-container-queries) to override the `--table-columns` CSS variable at breakpoints. Apply `hidden`/`@md:block` on cells for columns that should collapse. @@ -251,25 +243,28 @@ Use [container queries](https://tailwindcss.com/docs/responsive-design#what-are- ```tsx
- + Name - Email - Role + Email + Role Status + Alice Cooper - - alice@example.com - - Admin + alice@example.com + Admin Active + @@ -302,11 +297,11 @@ Use [container queries](https://tailwindcss.com/docs/responsive-design#what-are- ### TableContainer -| Name | Description | Type | Default | Required | -| ------------- | ------------------------------------------------------ | ------------------------------------- | -------- | -------- | -| `elevation` | Controls the shadow and background styling | `sunken`, `base`, `raised`, `overlay` | `raised` | ❌ | -| `tableLayout` | Controls the CSS table-layout algorithm | `auto`, `fixed` | `auto` | ❌ | -| `border` | Applies an elevation-aware border with rounded corners | `boolean` | `false` | ❌ | +| Name | Description | Type | Default | Required | +| ----------- | ------------------------------------------------------ | ------------------------------------- | -------- | -------- | +| `columns` | CSS `grid-template-columns` value for column sizing | `string` | — | ❌ | +| `elevation` | Controls the shadow and background styling | `sunken`, `base`, `raised`, `overlay` | `raised` | ❌ | +| `border` | Applies an elevation-aware border with rounded corners | `boolean` | `false` | ❌ | ### TableHeader @@ -319,15 +314,3 @@ Use [container queries](https://tailwindcss.com/docs/responsive-design#what-are- | Name | Description | Type | Default | Required | | ----------- | ------------------------------------------- | --------- | ------- | -------- | | `clickable` | Applies hover and cursor interaction styles | `boolean` | `false` | ❌ | - -### TableHead - -| Name | Description | Type | Default | Required | -| ---------- | ------------------------------------------ | --------- | ------- | -------- | -| `truncate` | Clips overflowing content with an ellipsis | `boolean` | `false` | ❌ | - -### TableCell - -| Name | Description | Type | Default | Required | -| ---------- | ------------------------------------------ | --------- | ------- | -------- | -| `truncate` | Clips overflowing content with an ellipsis | `boolean` | `false` | ❌ | diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index 8a772c33..7b519ab5 100644 --- a/packages/ui/src/components/index.ts +++ b/packages/ui/src/components/index.ts @@ -47,7 +47,6 @@ export * from './spinner/spinner'; export * from './switch/switch'; export * from './sort-button/sort-button'; export * from './table/table-components'; -export * from './table/table'; export * from './tabs/tabs-components'; export * from './tabs/tabs'; export * from './textarea/textarea'; diff --git a/packages/ui/src/components/table/table-components.module.css b/packages/ui/src/components/table/table-components.module.css index fe67914f..abdd6941 100644 --- a/packages/ui/src/components/table/table-components.module.css +++ b/packages/ui/src/components/table/table-components.module.css @@ -5,12 +5,16 @@ } .table-inner { - @apply w-full; + @apply grid w-full; @apply caption-bottom text-sm; + grid-template-columns: var(--table-columns); } .table-header { @apply [&_tr]:border-b; + @apply grid; + grid-template-columns: subgrid; + grid-column: 1 / -1; } .table-header--sticky { @@ -19,26 +23,36 @@ .table-body { @apply [&_tr:last-child]:border-0; + @apply grid; + grid-template-columns: subgrid; + grid-column: 1 / -1; } .table-footer { @apply border-t last:[&>tr]:border-b-0; - @apply font-medium; + @apply grid font-medium; + grid-template-columns: subgrid; + grid-column: 1 / -1; } .table-head { @apply h-10; - @apply px-4 align-middle; + @apply px-4; @apply text-text-primary text-left font-medium transition-colors; @apply [&:has([role=checkbox])]:pr-0; } .table-cell { @apply px-4 py-2; - @apply align-middle; @apply [&:has([role=checkbox])]:pr-0; } +.table-row { + @apply grid items-center; + grid-template-columns: subgrid; + grid-column: 1 / -1; +} + /* Clickable Variant */ .table-row--clickable { @@ -50,13 +64,6 @@ @apply data-[state=selected]:bg-mixed-light dark:data-[state=selected]:bg-mixed-dark; } -/* Truncate Variants */ - -.table-head--truncate, -.table-cell--truncate { - @apply max-w-0 overflow-hidden text-ellipsis whitespace-nowrap; -} - .table-caption { @apply text-text-secondary mt-4 text-sm; } diff --git a/packages/ui/src/components/table/table-components.tsx b/packages/ui/src/components/table/table-components.tsx index 72d233f3..2dcb3358 100644 --- a/packages/ui/src/components/table/table-components.tsx +++ b/packages/ui/src/components/table/table-components.tsx @@ -11,10 +11,10 @@ const TableContainer = React.forwardRef< HTMLTableElement, React.HTMLAttributes & VariantProps & { - tableLayout?: 'auto' | 'fixed'; border?: boolean; + columns?: string; } ->(({ className, style, elevation = ELEVATION.RAISED, tableLayout, border, ...props }, ref) => ( +>(({ className, style, elevation = ELEVATION.RAISED, border, columns, ...props }, ref) => (
- +
)); TableContainer.displayName = 'Table'; @@ -76,23 +71,20 @@ TableRow.displayName = 'TableRow'; const TableHead = React.forwardRef< HTMLTableCellElement, - React.ThHTMLAttributes & { truncate?: boolean } ->(({ className, truncate, ...props }, ref) => ( - `, ``). - **TableRow:** A table row (``) that inherits column tracks via subgrid. - **TableHead:** A column header cell (`
+ React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( + )); TableHead.displayName = 'TableHead'; const TableCell = React.forwardRef< HTMLTableCellElement, - React.TdHTMLAttributes & { truncate?: boolean } ->(({ className, truncate, ...props }, ref) => ( + React.TdHTMLAttributes +>(({ className, colSpan, style, ...props }, ref) => ( )); diff --git a/packages/ui/src/components/table/table.module.css b/packages/ui/src/components/table/table.module.css deleted file mode 100644 index aa05c791..00000000 --- a/packages/ui/src/components/table/table.module.css +++ /dev/null @@ -1,44 +0,0 @@ -@reference '../../theme/theme.module.css'; - -.table { - @apply shadow-sm; -} - -.table-border { - @apply border; - @apply overflow-hidden rounded-md; -} - -.table-empty-state { - @apply text-text-secondary py-8 text-center; -} - -/* ELEVATION */ - -.table--sunken { - @apply shadow-shadow-sunken; -} -.table--sunken.table-border { - @apply border-border-sunken; -} - -.table--base { - @apply shadow-shadow-base; -} -.table--base.table-border { - @apply border-border-base; -} - -.table--raised { - @apply shadow-shadow-raised; -} -.table--raised.table-border { - @apply border-border-raised; -} - -.table--overlay { - @apply shadow-shadow-overlay; -} -.table--overlay.table-border { - @apply border-border-overlay; -} diff --git a/packages/ui/src/components/table/table.tsx b/packages/ui/src/components/table/table.tsx deleted file mode 100644 index 61221355..00000000 --- a/packages/ui/src/components/table/table.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import * as React from 'react'; -import { VariantProps } from 'class-variance-authority'; - -import { - TableBody, - TableCell, - TableContainer, - TableHead, - TableHeader, - TableRow, -} from '@/components/table/table-components'; -import { ELEVATION, generateElevationVariants } from '@/lib/elevations'; -import { cn } from '@/lib/utils'; - -import styles from './table.module.css'; - -type TableColumn = { - key: string; - content: React.ReactNode; - className?: string; -}; - -type TableRowData = { - key: string; - cells: TableCellData[]; - onClick?: () => void; - className?: string; -}; - -type TableCellData = { - key: string; - content: React.ReactNode; - className?: string; -}; - -interface TableProps extends VariantProps { - columns: TableColumn[]; - rows: TableRowData[]; - className?: string; - border?: boolean; - emptyState?: React.ReactNode; - tableLayout?: 'auto' | 'fixed'; -} - -const tableElevationVariants = generateElevationVariants(styles, 'table', ELEVATION.BASE); - -/** @deprecated Use the compositional table primitives (`TableContainer`, `TableHeader`, `TableBody`, `TableRow`, `TableHead`, `TableCell`) instead. */ -const Table = ({ - columns, - rows, - className, - border = false, - elevation = ELEVATION.BASE, - emptyState, - tableLayout, -}: TableProps) => { - const isEmpty = rows.length === 0; - - return ( -
- - - - {columns.map((column) => ( - - {column.content} - - ))} - - - {isEmpty && emptyState ? ( - - - - {emptyState} - - - - ) : ( - - {rows.map((row) => ( - - {row.cells.map((cell) => ( - - {cell.content} - - ))} - - ))} - - )} - -
- ); -}; - -export { Table }; From c35c6c150d2caaa02e0c049c36b714082208adb4 Mon Sep 17 00:00:00 2001 From: Henry Wilkinson Date: Mon, 30 Mar 2026 02:17:58 -0400 Subject: [PATCH 2/4] Fix responsive columns Co-Authored-By: Claude --- packages/demo/src/components/demo/table.tsx | 3 +-- packages/demo/src/content/components/table.mdx | 7 ++----- .../ui/src/components/table/table-components.module.css | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/demo/src/components/demo/table.tsx b/packages/demo/src/components/demo/table.tsx index 6795a725..faf4f620 100644 --- a/packages/demo/src/components/demo/table.tsx +++ b/packages/demo/src/components/demo/table.tsx @@ -147,8 +147,7 @@ export const TableDemo = ({
diff --git a/packages/demo/src/content/components/table.mdx b/packages/demo/src/content/components/table.mdx index e42d6673..21e59c37 100644 --- a/packages/demo/src/content/components/table.mdx +++ b/packages/demo/src/content/components/table.mdx @@ -235,7 +235,7 @@ Use Tailwind's `truncate min-w-0` classes directly on `` and `
`). -- **TableCell:** A data cell (``). Supports `colSpan` which auto-converts to `grid-column`. +- **TableCell:** A data cell (``). ## Usage @@ -130,7 +130,7 @@ Use the `border` prop on `TableContainer` to apply an elevation-aware border wit ### Empty State -When there are no rows, render empty state content in a `` that spans all columns with `colSpan`. The `colSpan` value is automatically converted to `grid-column` for CSS Grid compatibility. +When there are no rows, render empty state content in a `` that spans all columns with `style={{ gridColumn: '1 / -1' }}`. diff --git a/packages/ui/src/components/table/table-components.tsx b/packages/ui/src/components/table/table-components.tsx index 2dcb3358..41837b39 100644 --- a/packages/ui/src/components/table/table-components.tsx +++ b/packages/ui/src/components/table/table-components.tsx @@ -80,13 +80,8 @@ TableHead.displayName = 'TableHead'; const TableCell = React.forwardRef< HTMLTableCellElement, React.TdHTMLAttributes ->(({ className, colSpan, style, ...props }, ref) => ( - +>(({ className, ...props }, ref) => ( + )); TableCell.displayName = 'TableCell'; From 35f6d9829286d6fa5b96750b8b876401f4afd49c Mon Sep 17 00:00:00 2001 From: Henry Wilkinson Date: Mon, 30 Mar 2026 02:29:10 -0400 Subject: [PATCH 4/4] Update package.json --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index b1aadbe2..d79c4747 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -2,7 +2,7 @@ "name": "@eqtylab/equality", "description": "EQTYLab's component and token-based design system", "homepage": "https://equality.eqtylab.io/", - "version": "1.6.1", + "version": "1.7.0", "license": "Apache-2.0", "keywords": [ "component library",