diff --git a/packages/demo/src/components/demo/table.tsx b/packages/demo/src/components/demo/table.tsx index 92d9f21..0a8bfa5 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,26 @@ export const TableDemo = ({ if (variant === "responsive") { return (
- + Name - Email - Role + Email + Role Status - + Alice Cooper - + alice@example.com - Admin + Admin Active @@ -168,10 +174,10 @@ export const TableDemo = ({ Bob Smith - + bob@example.com - User + User Active @@ -181,10 +187,10 @@ export const TableDemo = ({ Charlie Brown - + charlie@example.com - Viewer + Viewer Inactive @@ -200,14 +206,19 @@ export const TableDemo = ({ if (variant === "sticky-header") { return ( - + Name Email Role Status - + @@ -282,7 +293,7 @@ export const TableDemo = ({ if (variant === "with-sorter") { return ( - + @@ -325,7 +336,7 @@ export const TableDemo = ({ Status - + @@ -369,20 +380,20 @@ export const TableDemo = ({ if (variant === "empty-state") { return ( - + Name Email Role Status - + No data available @@ -395,19 +406,19 @@ export const TableDemo = ({ if (variant === "empty-state-custom") { return ( - + Name Email Role Status - + - + + Name Email Role Status - + diff --git a/packages/demo/src/content/components/table.mdx b/packages/demo/src/content/components/table.mdx index ec08018..13d4542 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 (``). ## 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 `style={{ gridColumn: '1 / -1' }}`. @@ -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 each breakpoint, matching the number of visible columns. Apply `hidden`/`@md:block` on cells for columns that should collapse. Hidden cells are removed from the grid flow, and the remaining visible cells auto-place into the available tracks. Use `className` instead of the `columns` prop so that responsive overrides aren't blocked by inline style specificity. @@ -251,25 +243,25 @@ 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 +294,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 +311,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/package.json b/packages/ui/package.json index b1aadbe..d79c474 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", diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index 8a772c3..7b519ab 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 fe67914..a6d1ec1 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 content-center 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 72d233f..41837b3 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,25 +71,17 @@ TableRow.displayName = 'TableRow'; const TableHead = React.forwardRef< HTMLTableCellElement, - React.ThHTMLAttributes & { truncate?: boolean } ->(({ className, truncate, ...props }, ref) => ( -
+ React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( + )); TableHead.displayName = 'TableHead'; const TableCell = React.forwardRef< HTMLTableCellElement, - React.TdHTMLAttributes & { truncate?: boolean } ->(({ className, truncate, ...props }, ref) => ( - + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + )); TableCell.displayName = 'TableCell'; 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 aa05c79..0000000 --- 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 6122135..0000000 --- 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 };