, "className" | "style"> &
+ TableCellVariantProps;
/** A data cell (``). Borders follow the `mode`. */
-export function TableCell({ mode, pinned, padding = "cell", render, ...props }: TableCellProps) {
+export function TableCell({ mode, pinned, padding, render, ...props }: TableCellProps) {
const defaultProps: useRender.ElementProps<"td"> = {
- className: tableCellVariants({ surface: mode, pinned: pinned ?? "none", padding }),
+ className: tableCellVariants({ mode, pinned, padding }),
};
return useRender({ defaultTagName: "td", render, props: mergeProps(defaultProps, props) });
}
diff --git a/packages/propel/src/ui/table/table-head.tsx b/packages/propel/src/ui/table/table-head.tsx
index a83ca51e..5bd77945 100644
--- a/packages/propel/src/ui/table/table-head.tsx
+++ b/packages/propel/src/ui/table/table-head.tsx
@@ -1,14 +1,10 @@
import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render";
-import { type TablePinned, type TableMode, tableHeadVariants } from "./variants";
+import { type TableHeadVariantProps, tableHeadVariants } from "./variants";
-export type TableHeadProps = Omit, "className" | "style"> & {
- /** The surrounding table's look, matching the `Table` root. */
- mode: TableMode;
- /** Pin this header to the inline-start/end edge when the table scrolls sideways. */
- pinned?: TablePinned;
-};
+export type TableHeadProps = Omit, "className" | "style"> &
+ TableHeadVariantProps;
/**
* A header cell (``). Borders follow the `mode`. Holds a `TableHeadTitle` (or, when
@@ -17,7 +13,7 @@ export type TableHeadProps = Omit, "className" |
export function TableHead({ mode, pinned, render, ...props }: TableHeadProps) {
const defaultProps: useRender.ElementProps<"th"> = {
scope: "col",
- className: tableHeadVariants({ surface: mode, pinned: pinned ?? "none" }),
+ className: tableHeadVariants({ mode, pinned }),
};
return useRender({ defaultTagName: "th", render, props: mergeProps(defaultProps, props) });
}
diff --git a/packages/propel/src/ui/table/table.stories.tsx b/packages/propel/src/ui/table/table.stories.tsx
index 2e7b4238..e56f8f3e 100644
--- a/packages/propel/src/ui/table/table.stories.tsx
+++ b/packages/propel/src/ui/table/table.stories.tsx
@@ -62,7 +62,7 @@ export const Default: Story = {
{COLUMNS.map((c) => (
-
+
{c}
))}
@@ -71,7 +71,7 @@ export const Default: Story = {
{PEOPLE.map((person) => (
-
+
@@ -81,17 +81,17 @@ export const Default: Story = {
{person.name}
-
+
{person.display}
-
+
{person.email}
-
+
{person.role}
@@ -115,7 +115,7 @@ export const Spreadsheet: Story = {
{COLUMNS.map((c) => (
-
+
{c}
))}
@@ -124,22 +124,22 @@ export const Spreadsheet: Story = {
{PEOPLE.map((person) => (
-
+
{person.name}
-
+
{person.display}
-
+
{person.email}
-
+
{person.role}
@@ -169,7 +169,7 @@ export const Sortable: Story = {
-
+
Name
@@ -177,7 +177,7 @@ export const Sortable: Story = {
-
+
Email
@@ -185,12 +185,12 @@ export const Sortable: Story = {
{PEOPLE.map((person) => (
-
+
{person.name}
-
+
{person.email}
@@ -226,13 +226,13 @@ export const PinnedColumn: Story = {
Name
-
+
Display name
-
+
Email
-
+
Account type
@@ -240,7 +240,7 @@ export const PinnedColumn: Story = {
{PEOPLE.map((person) => (
-
+
@@ -250,17 +250,17 @@ export const PinnedColumn: Story = {
{person.name}
-
+
{person.display}
-
+
{person.email}
-
+
{person.role}
diff --git a/packages/propel/src/ui/table/variants.ts b/packages/propel/src/ui/table/variants.ts
index 244d924d..a02160b9 100644
--- a/packages/propel/src/ui/table/variants.ts
+++ b/packages/propel/src/ui/table/variants.ts
@@ -1,12 +1,7 @@
-/** The two table looks: `table` (row dividers only) and `spreadsheet` (full grid). */
-export type TableMode = "table" | "spreadsheet";
-
-/** Which inline edge a header/cell pins to while the table scrolls sideways. */
-export type TablePinned = "start" | "end";
-
-import { cva, cx } from "class-variance-authority";
+import { cva, cx, type VariantProps } from "class-variance-authority";
import { nodeSlotClass } from "../../internal/node-slot";
+import { type StrictVariantProps } from "../../internal/variant-props";
// Table is a structural data primitive. The designer locked two layout looks (Figma
// "Table" vs "Spreadsheet") as the only `mode` axis, and baked everything else
@@ -51,7 +46,7 @@ export const tableHeadVariants = cva(
{
variants: {
// The surrounding table look, which decides this cell's borders.
- surface: {
+ mode: {
table: "border-b border-subtle",
spreadsheet: "border-e-[0.5px] border-b-[0.5px] border-subtle last:border-e-0",
},
@@ -65,11 +60,11 @@ export const tableHeadVariants = cva(
},
);
-// A data cell (`| `). `surface` decides its borders; `pinned` makes it stick to an
+// A data cell (` | `). `mode` decides its borders; `pinned` makes it stick to an
// inline edge (carrying its own background so scrolled content does not show through).
export const tableCellVariants = cva("h-11 align-middle", {
variants: {
- surface: {
+ mode: {
table: "border-b-[0.5px] border-subtle group-last/body-row:border-b-0",
spreadsheet:
"border-e-[0.5px] border-b-[0.5px] border-subtle group-last/body-row:border-b-0 last:border-e-0",
@@ -89,6 +84,23 @@ export const tableCellVariants = cva("h-11 align-middle", {
},
});
+// Per-axis types derive from the cvas (the single source of truth). `mode`/`pinned` are shared by
+// head + cell; `padding` is cell-only. The `VariantProps` bundles stay PRIVATE — a part
+// imports its own for `Props` (rule 10) and never re-exports it.
+type TableCellVariantConfig = VariantProps;
+
+/** The two table looks: `table` (row dividers only) and `spreadsheet` (full grid). */
+export type TableMode = NonNullable | | |