diff --git a/packages/demo/src/components/demo/table.tsx b/packages/demo/src/components/demo/table.tsx index a0d0bdd..26437b8 100644 --- a/packages/demo/src/components/demo/table.tsx +++ b/packages/demo/src/components/demo/table.tsx @@ -11,7 +11,7 @@ import { TableHeader, TableRow, } from "@eqtylab/equality"; -import { useState } from "react"; +import { type CSSProperties, useState } from "react"; interface TableDemoProps { variant?: @@ -24,12 +24,22 @@ interface TableDemoProps { | "column-sizing" | "truncation" | "responsive" - | "sticky-header"; + | "sticky-header" + | "sticky-header-page" + | "sticky-header-page-offset"; elevation?: Elevation; } const defaultCols = "1fr 1fr auto auto auto"; +const roles = ["Admin", "User", "Viewer"] as const; +const longTableData = Array.from({ length: 10 }, (_, i) => ({ + name: `User ${i + 1}`, + email: `user${i + 1}@example.com`, + role: roles[i % roles.length], + active: i % 4 !== 0, +})); + export const TableDemo = ({ variant = "default", elevation, @@ -216,7 +226,7 @@ export const TableDemo = ({ columns={defaultCols} border > - + Name Email @@ -295,6 +305,53 @@ export const TableDemo = ({ ); } + if ( + variant === "sticky-header-page" || + variant === "sticky-header-page-offset" + ) { + const offset = variant === "sticky-header-page-offset"; + return ( + + + + Name + Email + Role + Status + + + + + {/* sticky="page" keeps the header pinned to the viewport as the page scrolls */} + {longTableData.map((user) => ( + + {user.name} + {user.email} + {user.role} + + + {user.active ? "Active" : "Inactive"} + + + + + + + ))} + + + ); + } + if (variant === "with-sorter") { return ( diff --git a/packages/demo/src/content/components/table.mdx b/packages/demo/src/content/components/table.mdx index 0b0c67a..dc7ded9 100644 --- a/packages/demo/src/content/components/table.mdx +++ b/packages/demo/src/content/components/table.mdx @@ -149,22 +149,45 @@ The empty state cell accepts any `ReactNode`, so you can use a custom component ### Sticky Header -Use the `sticky` prop on `` to keep column headers visible while scrolling. The height of the `` must be constrained for this to work as expected. +Use the `sticky` prop on `` to keep column headers visible while scrolling. It accepts two modes: + +- **`container`** — the header sticks within the table's own scroll area. The `` must have a constrained height (e.g. `max-h-*`), which makes it scroll internally. +- **`page`** — the header sticks against the document as the whole page scrolls. No height constraint is needed; use this for full-page data tables. Pin offset can be adjusted with the `--table-sticky-top` CSS variable to clear fixed app chrome. + +#### On container +#### On page + + + #### Usage ```tsx +{ + /* Sticks within a height-constrained, internally scrolling table */ +} - + Name Email {/* rows */} - +; + +{ + /* Sticks against the document as the page scrolls */ +} + + {/* … */} + {/* rows */} +; ``` ## Column Sizing @@ -309,9 +332,9 @@ Use [container queries](https://tailwindcss.com/docs/responsive-design#what-are- ### TableHeader -| Name | Description | Type | Default | Required | -| -------- | ------------------------------------------------ | --------- | ------- | -------- | -| `sticky` | Keeps the header visible while the table scrolls | `boolean` | `false` | ❌ | +| Name | Description | Type | Default | Required | +| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | ------- | -------- | +| `sticky` | Pins the header while scrolling. `container` sticks within a height-constrained table; `page` sticks against the document as the page scrolls. | `false \| 'container' \| 'page'` | `false` | ❌ | ### TableRow diff --git a/packages/ui/src/components/table/table-components.module.css b/packages/ui/src/components/table/table-components.module.css index 7762c83..fc21ac3 100644 --- a/packages/ui/src/components/table/table-components.module.css +++ b/packages/ui/src/components/table/table-components.module.css @@ -1,7 +1,11 @@ @reference '../../theme/theme.module.css'; .table { - @apply w-full overflow-auto; + @apply w-full; +} + +.table:has(.table-header--sticky-container) { + @apply overflow-auto; } .table-inner { @@ -17,10 +21,17 @@ grid-column: 1 / -1; } -.table-header--sticky { +/* Sticks to the top of the scrollable container (requires a constrained height). */ +.table-header--sticky-container { @apply sticky top-0 z-10; } +/* Sticks to the document/viewport as the page scrolls. */ +.table-header--sticky-page { + @apply sticky z-10; + top: var(--table-sticky-top, 0); +} + .table-body { @apply [&_tr:last-child]:border-0; @apply grid; @@ -88,7 +99,7 @@ } .table-row--linked:has(.table-row-link:focus-visible) { - @apply outline-brand-primary outline outline-2 outline-offset-[-2px]; + @apply outline-brand-primary outline-2 outline-offset-[-2px]; } .table-body .table-row--clickable[data-state='selected'] { @@ -105,8 +116,7 @@ /* BORDER */ .table-border { - @apply border; - @apply overflow-hidden rounded-md; + @apply overflow-clip rounded-md border; } /* ELEVATION */ diff --git a/packages/ui/src/components/table/table-components.tsx b/packages/ui/src/components/table/table-components.tsx index 5fc4789..6c96b5b 100644 --- a/packages/ui/src/components/table/table-components.tsx +++ b/packages/ui/src/components/table/table-components.tsx @@ -31,11 +31,18 @@ TableContainer.displayName = 'Table'; const TableHeader = React.forwardRef< HTMLTableSectionElement, - React.HTMLAttributes & { sticky?: boolean } ->(({ className, sticky, ...props }, ref) => ( + React.HTMLAttributes & { + sticky?: false | 'container' | 'page'; + } +>(({ className, sticky = false, ...props }, ref) => ( ));