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
18 changes: 18 additions & 0 deletions .changeset/card-content-padding-none.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
"@tailor-platform/app-shell": minor
---

Add `padding="none"` to `Card.Content` for edge-to-edge content such as a `Table`, `DataTable` or divided list.

```tsx
<Card.Root>
<Card.Header title="Line items" />
<Card.Content padding="none">
<DataTable.Root value={table}>
<DataTable.Table />
</DataTable.Root>
</Card.Content>
</Card.Root>
```

Cells keep their own 24px inset, so the first column lines up with the card title. Row backgrounds stay inside the card's rounded corners, and a nested `DataTable` drops its own border. This replaces the `className="px-0!"` workaround.
22 changes: 11 additions & 11 deletions docs-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -241,10 +241,10 @@
"symbols": ["Card"],
"hashes": {
"typeSurface": "6ce6f0fdf2450cec",
"outline": "794fbe3b6440047a",
"snapshot": "98e09e5019fa1368",
"outputMd": "29b147ccc0c5ab88",
"examples": "b7b2d28f20fe0a18"
"outline": "a8c217856c9c367a",
"snapshot": "31ad44b3faa1da06",
"outputMd": "675dc665e5082ff0",
"examples": "b20054836086ced0"
}
},
"checkbox": {
Expand Down Expand Up @@ -621,9 +621,9 @@
"symbols": [],
"hashes": {
"typeSurface": null,
"outline": "41d9abcae59b27b4",
"outline": "ead7d94daf36e8da",
"snapshot": null,
"outputMd": "c3fb2588a0699c26",
"outputMd": "717ef4e7ae835aff",
"examples": null
}
},
Expand Down Expand Up @@ -1284,9 +1284,9 @@
"symbols": [],
"hashes": {
"typeSurface": null,
"outline": "c8a79ca086341d8c",
"outline": "a16d07a7eae92139",
"snapshot": null,
"outputMd": "1277e06bb82432e6",
"outputMd": "9ee8eba5945a9db9",
"examples": "d3cca347b055a503"
}
},
Expand Down Expand Up @@ -1656,7 +1656,7 @@
"packages/core/skills/app-shell-patterns/references/concepts/modules-and-resources.md": "bf15b5460f22d165",
"packages/core/skills/app-shell-patterns/references/concepts/routing-navigation.md": "4becc2090217bfd2",
"packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "dc6b3afc049b4148",
"packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "1277e06bb82432e6",
"packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "9ee8eba5945a9db9",
"packages/core/skills/app-shell-patterns/references/components/action-panel.md": "71794d4b561e4d4b",
"packages/core/skills/app-shell-patterns/references/components/activity-card.md": "4f386e49380fb0b0",
"packages/core/skills/app-shell-patterns/references/components/ai-chat.md": "824d1aa36bb7f194",
Expand All @@ -1668,7 +1668,7 @@
"packages/core/skills/app-shell-patterns/references/components/avatar.md": "a874b74c176bcb47",
"packages/core/skills/app-shell-patterns/references/components/badge.md": "0cb27fbce6f76e2e",
"packages/core/skills/app-shell-patterns/references/components/button.md": "bce0e2fada1d1638",
"packages/core/skills/app-shell-patterns/references/components/card.md": "29b147ccc0c5ab88",
"packages/core/skills/app-shell-patterns/references/components/card.md": "675dc665e5082ff0",
"packages/core/skills/app-shell-patterns/references/components/checkbox.md": "14f7ba23f441df38",
"packages/core/skills/app-shell-patterns/references/components/combobox.md": "dfb775c7c4307409",
"packages/core/skills/app-shell-patterns/references/components/command-palette.md": "7e5bc675a593791a",
Expand Down Expand Up @@ -1734,7 +1734,7 @@
"packages/core/skills/app-shell-patterns/references/patterns/interaction-multi-select.md": "09e99e5035da6e81",
"packages/core/skills/app-shell-patterns/references/patterns/interaction-toast.md": "f68bdab855730f48",
"packages/core/skills/app-shell-patterns/references/patterns/list-dense-scan.md": "8062ea0df9403fbb",
"packages/core/skills/app-shell-patterns/references/pages/document-detail.md": "c3fb2588a0699c26",
"packages/core/skills/app-shell-patterns/references/pages/document-detail.md": "717ef4e7ae835aff",
"packages/core/skills/app-shell-patterns/references/migrations.md": "013c3fef2303b096",
"packages/core/skills/app-shell-patterns/SKILL.md": "814325f4ea06ffae"
}
Expand Down
30 changes: 29 additions & 1 deletion docs-src/components/card.docs.examples.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Card } from "@tailor-platform/app-shell";
import { Card, Table } from "@tailor-platform/app-shell";

export function BasicUsage() {
return (
Expand All @@ -10,3 +10,31 @@ export function BasicUsage() {
</Card.Root>
);
}

export function EdgeToEdgeTable() {
return (
<Card.Root>
<Card.Header title="Line items" />
<Card.Content padding="none">
<Table.Root>
<Table.Header>
<Table.Row>
<Table.Head>Item</Table.Head>
<Table.Head align="right">Qty</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>Steel bracket</Table.Cell>
<Table.Cell align="right">12</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>Hex bolt M8</Table.Cell>
<Table.Cell align="right">40</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
</Card.Content>
</Card.Root>
);
}
56 changes: 49 additions & 7 deletions docs-src/components/card.docs.outline.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { Card } from "@tailor-platform/app-shell";
| -------------- | ---------------------------------------------------------------- |
| `Card.Root` | Outer container. Renders a `<div>` with surface styling applied. |
| `Card.Header` | Top section with an optional title and description. |
| `Card.Content` | Body section with consistent horizontal and bottom padding. |
| `Card.Content` | Body section with consistent padding, or edge-to-edge. |

## Card.Root Props

Expand All @@ -51,10 +51,52 @@ import { Card } from "@tailor-platform/app-shell";

`Card.Content` accepts all standard HTML `<div>` props.

| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | ---------------------- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Card body content |
| Prop | Type | Default | Description |
| ----------- | ----------------------- | ----------- | ---------------------------------------------------------------------------------------------- |
| `padding` | `"default"` \| `"none"` | `"default"` | `"none"` renders the content edge-to-edge — see [Edge-to-edge content](#edge-to-edge-content). |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Card body content |

## Edge-to-edge content

Use `padding="none"` when a `Table`, `DataTable` or divided list should meet the card's border instead of floating inside it:

<!-- example: edge-to-edge-table -->

Don't add padding back on the table. `Table.Head` and `Table.Cell` already inset their first and last cells by 24px, which lines the first column up with the card title.

Edge-to-edge content also:

- **Keeps row backgrounds inside the card's rounded corners.** A direct `Table` or `DataTable` child clips its row hover and pinned-cell backgrounds to the card's corners.
- **Removes a nested `DataTable`'s own border**, so it doesn't draw a box inside the card. Its toolbar and footer stay as they are:

```tsx
<Card.Root>
<Card.Header title="Line items" />
<Card.Content padding="none">
<DataTable.Root value={table}>
<DataTable.Table />
<DataTable.Footer>
<DataTable.Pagination />
</DataTable.Footer>
</DataTable.Root>
</Card.Content>
</Card.Root>
```

For a scrollable list, put the overflow on `Card.Content`. Rows supply their own `px-6`, and dividers run to the card edges:

```tsx
<Card.Content padding="none" className="max-h-60 overflow-y-auto">
<ul className="divide-y divide-border border-t border-border">
{items.map((item) => (
<li key={item.id} className="px-6 py-3">
{item.name}
</li>
))}
</ul>
</Card.Content>
```

## Examples

Expand Down Expand Up @@ -85,8 +127,8 @@ import { Card } from "@tailor-platform/app-shell";
View all
</Button>
</Card.Header>
<Card.Content>
<Table>{/* ... */}</Table>
<Card.Content padding="none">
<Table.Root>{/* ... */}</Table.Root>
</Card.Content>
</Card.Root>
```
Expand Down
6 changes: 4 additions & 2 deletions docs-src/concepts/styling-theming.docs.outline.md
Original file line number Diff line number Diff line change
Expand Up @@ -465,12 +465,12 @@ Write **plain Tailwind utilities** in your application code. Three rules cover e
</div>
```

When no prop exists and you genuinely have to override a value a component sets, add Tailwind's [importance modifier](https://tailwindcss.com/docs/styling-with-utility-classes#important-modifier) — a trailing `!`. It is the last resort, not the first: a plain utility silently loses (see below), and `!` is what the pattern catalogue uses for the one documented case, zeroing a card's padding so a table can sit flush inside it:
When no prop exists and you genuinely have to override a value a component sets, add Tailwind's [importance modifier](https://tailwindcss.com/docs/styling-with-utility-classes#important-modifier) — a trailing `!`. It is the last resort, not the first: a plain utility silently loses (see below). Check for a prop before reaching for it. Sitting a table flush inside a card, for example, used to need `className="px-0!"`; it is now a prop:

```tsx
<Card.Root>
<Card.Header title="Line items" />
<Card.Content className="px-0!">
<Card.Content padding="none">
<Table.Root>{/* … */}</Table.Root>
</Card.Content>
</Card.Root>
Expand All @@ -497,6 +497,8 @@ This is the reason rule 3 sends you to props rather than to a more specific clas
<Card.Content className="px-0" />
```

(For this particular case, use `padding="none"`.)

State variants make it worse rather than better: `Button`'s `ghost` variant sets a hover color, and a `:hover` rule outranks an unprefixed utility on **specificity**, not merely order — so a plain `text-destructive` on a ghost button is red only until the pointer reaches it. `variant="destructive"` is the supported way to express that; where no such prop exists, `!` wins in every state because importance beats specificity.

### Styling by state (data attributes)
Expand Down
12 changes: 6 additions & 6 deletions docs-src/pages/document-detail.docs.outline.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ time, and the record is unblocked only once every one is released.
### 4. Line items

`Card.Root` → `Card.Header` (title, plus a one-line description where the
columns need explaining) → `Card.Content className="px-0!"` → `Table.Root`.
columns need explaining) → `Card.Content padding="none"` → `Table.Root`.

Fetch the line items with the record, sorted explicitly so the order is stable
across reloads, and render them as a plain `Table`. For the typical document —
Expand Down Expand Up @@ -799,7 +799,7 @@ export default function PurchaseOrderDetailPage({
would have been a link field on the summary instead. */}
<Card.Root>
<Card.Header title="Source requisitions" />
<Card.Content className="px-0!">
<Card.Content padding="none">
<Table.Root>
<Table.Header>
<Table.Row>
Expand Down Expand Up @@ -834,7 +834,7 @@ export default function PurchaseOrderDetailPage({
enough to page. No container padding; the cells inset themselves. */}
<Card.Root>
<Card.Header title="Line items" />
<Card.Content className="px-0!">
<Card.Content padding="none">
{order.lineItems.length === 0 ? (
<p className="px-6 text-sm text-muted-foreground">No line items on this order.</p>
) : (
Expand Down Expand Up @@ -901,7 +901,7 @@ export default function PurchaseOrderDetailPage({
relationship is possible, so the reader learns it exists. */}
<Card.Root>
<Card.Header title="Goods receipts" />
<Card.Content className="px-0!">
<Card.Content padding="none">
{order.goodsReceipts.length === 0 ? (
<p className="px-6 text-sm text-muted-foreground">
No goods receipts linked to this order.
Expand Down Expand Up @@ -1023,7 +1023,7 @@ export default function PurchaseOrderDetailPage({
title="Journal entry"
description="Booked when this order was posted."
/>
<Card.Content className="px-0!">
<Card.Content padding="none">
<Table.Root>
<Table.Header>
<Table.Row>
Expand Down Expand Up @@ -1122,7 +1122,7 @@ enforced across every detail screen once settled.
## Constraints

- **Every main-column section sits in a `Card.Root`** — except `DescriptionCard`, which contains itself, and `Alert`, which is a banner. No bare `<div>` sections.
- **A table in a card needs ONE geometry change, not two.** Zero the card's padding (`Card.Content className="px-0!"`, or drop `Card.Content`) and leave the table container alone. `Table.Head` and `Table.Cell` already inset their own first and last cells by 24px; padding on the table container stacks on top of that and pushes the first column 24px right of the card title.
- **A table in a card goes in `Card.Content padding="none"`, and nothing else changes.** Don't drop `Card.Content` or zero its padding with a class instead: the prop also keeps row backgrounds inside the card's rounded corners and removes a nested `DataTable`'s own border. Leave the table container alone too. `Table.Head` and `Table.Cell` already inset their own first and last cells by 24px; padding on the table container stacks on top of that and pushes the first column 24px right of the card title.
- **Bare `YYYY-MM-DD` dates must not use `type: "date"`.** `DescriptionCard` hands the value to `new Date(...)`, which reads a date-only string as UTC midnight and renders the previous day west of Greenwich. Pre-format those as text via `render`. Real timestamps keep `type: "date"`, with `emptyBehavior: "hide"` when nullable.
- **`ActionPanel` is workflow-only.** No navigation, no "view related record" — those are links in the cards that hold them.
- **Never rely on a query cap to bound the line-items table.** If the cap can be reached, page the lines.
Expand Down
86 changes: 79 additions & 7 deletions docs/components/card.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading