Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .changeset/bright-tables-wave.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
"@tailor-platform/app-shell": minor
---

Add `align?: "left" | "right"` to `Table.Head` and `Table.Cell` so raw table primitives can align numeric columns without relying on consumer Tailwind utility overrides.
Add `align?: "left" | "center" | "right"` to `Table.Head` and `Table.Cell` so raw table primitives can align numeric and status columns without relying on consumer Tailwind utility overrides.

```tsx
<Table.Head align="right">Amount</Table.Head>
<Table.Cell align="right">123</Table.Cell>

<Table.Head align="center">Status</Table.Head>
<Table.Cell align="center">Active</Table.Cell>
```
21 changes: 20 additions & 1 deletion docs/components/table.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ All other standard HTML `<table>` props are accepted.

All other sub-components (`Table.Header`, `Table.Body`, `Table.Footer`, `Table.Row`, `Table.Head`, `Table.Cell`, `Table.Caption`) accept `className` and their corresponding standard HTML element props.

`Table.Head` and `Table.Cell` also accept `align?: "left" | "right"` for semantic text alignment without relying on utility-class overrides.
`Table.Head` and `Table.Cell` also accept `align?: "left" | "center" | "right"` for semantic text alignment without relying on utility-class overrides.

## Examples

Expand Down Expand Up @@ -135,6 +135,25 @@ All other sub-components (`Table.Header`, `Table.Body`, `Table.Footer`, `Table.R
</Table.Root>
```

### Center-aligned status column

```tsx
<Table.Root>
<Table.Header>
<Table.Row>
<Table.Head>Item</Table.Head>
<Table.Head align="center">Status</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>Widget A</Table.Cell>
<Table.Cell align="center">Active</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
```

### Constrained Height with Scroll

```tsx
Expand Down
35 changes: 35 additions & 0 deletions packages/core/src/components/table.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@ describe("Table", () => {
const head = container.querySelector('[data-slot="table-head"]');
expect(head?.className).toContain("astw:text-right");
expect(head?.className).not.toContain("astw:text-left");
expect(head?.className).not.toContain("astw:text-center");
});

it("center-aligns header cells via align prop", () => {
const { container } = render(
<Table.Root>
<Table.Header>
<Table.Row>
<Table.Head align="center">Status</Table.Head>
</Table.Row>
</Table.Header>
</Table.Root>,
);
const head = container.querySelector('[data-slot="table-head"]');
expect(head?.className).toContain("astw:text-center");
expect(head?.className).not.toContain("astw:text-left");
expect(head?.className).not.toContain("astw:text-right");
});

it("right-aligns data cells via align prop", () => {
Expand All @@ -36,6 +53,23 @@ describe("Table", () => {
const cell = container.querySelector('[data-slot="table-cell"]');
expect(cell?.className).toContain("astw:text-right");
expect(cell?.className).not.toContain("astw:text-left");
expect(cell?.className).not.toContain("astw:text-center");
});

it("center-aligns data cells via align prop", () => {
const { container } = render(
<Table.Root>
<Table.Body>
<Table.Row>
<Table.Cell align="center">Active</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>,
);
const cell = container.querySelector('[data-slot="table-cell"]');
expect(cell?.className).toContain("astw:text-center");
expect(cell?.className).not.toContain("astw:text-left");
expect(cell?.className).not.toContain("astw:text-right");
});

it("left-aligns data cells by default", () => {
Expand All @@ -51,6 +85,7 @@ describe("Table", () => {
const cell = container.querySelector('[data-slot="table-cell"]');
expect(cell?.className).toContain("astw:text-left");
expect(cell?.className).not.toContain("astw:text-right");
expect(cell?.className).not.toContain("astw:text-center");
});
});

Expand Down
12 changes: 9 additions & 3 deletions packages/core/src/components/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@ type RootProps = React.ComponentProps<"table"> & {
containerClassName?: string;
};

type CellAlign = "left" | "right";
type CellAlign = "left" | "center" | "right";
type HeadProps = React.ComponentProps<"th"> & { align?: CellAlign };
type CellProps = React.ComponentProps<"td"> & { align?: CellAlign };

const cellAlignClassName = {
left: "astw:text-left",
center: "astw:text-center",
right: "astw:text-right",
} satisfies Record<CellAlign, string>;

/**
* The root table element with a horizontally scrollable container.
*
Expand Down Expand Up @@ -111,7 +117,7 @@ function Head({ className, align = "left", ...props }: HeadProps) {
data-slot="table-head"
className={cn(
"astw:text-foreground astw:h-10 astw:px-2 astw:first:pl-6 astw:last:pr-6 astw:align-middle astw:font-medium astw:whitespace-nowrap astw:[&:has([role=checkbox])]:pr-0",
align === "right" ? "astw:text-right" : "astw:text-left",
cellAlignClassName[align],
className,
)}
{...props}
Expand All @@ -127,7 +133,7 @@ function Cell({ className, align = "left", ...props }: CellProps) {
data-slot="table-cell"
className={cn(
"astw:px-2 astw:py-2 astw:first:pl-6 astw:last:pr-6 astw:align-middle astw:whitespace-nowrap astw:[&:has([role=checkbox])]:pr-0",
align === "right" ? "astw:text-right" : "astw:text-left",
cellAlignClassName[align],
className,
)}
{...props}
Expand Down
Loading