@tailor-platform/app-shell 1.17.0 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-shell-core.css +1 -1
- package/dist/app-shell.d.ts +503 -137
- package/dist/app-shell.js +8847 -7378
- package/dist/{appshell-CahhZy7v.js → appshell-DQTz-QwK.js} +489 -379
- package/dist/style-entry.d.ts +1 -656
- package/dist/testing.d.ts +118 -113
- package/dist/testing.js +1 -1
- package/dist/theme.bridge.css +127 -40
- package/dist/theme.css +4 -3
- package/dist/themes/_template.css +5 -6
- package/dist/themes/bloom.css +0 -2
- package/dist/themes/cream.css +0 -2
- package/dist/themes/default.css +112 -77
- package/dist/vite-plugin.d.ts +118 -113
- package/package.json +8 -5
- package/skills/app-shell-patterns/SKILL.md +51 -32
- package/skills/app-shell-patterns/references/api/aggregations.md +131 -0
- package/skills/app-shell-patterns/references/api/define-i18n-labels.md +123 -91
- package/skills/app-shell-patterns/references/api/define-module.md +2 -4
- package/skills/app-shell-patterns/references/components/action-panel.md +55 -23
- package/skills/app-shell-patterns/references/components/badge.md +7 -3
- package/skills/app-shell-patterns/references/components/data-table.md +724 -36
- package/skills/app-shell-patterns/references/components/form.md +19 -9
- package/skills/app-shell-patterns/references/components/item-list.md +191 -0
- package/skills/app-shell-patterns/references/components/select.md +10 -0
- package/skills/app-shell-patterns/references/components/sidebar-group.md +6 -6
- package/skills/app-shell-patterns/references/components/text.md +235 -0
- package/skills/app-shell-patterns/references/concepts/sidebar-navigation.md +16 -14
- package/skills/app-shell-patterns/references/concepts/styling-theming.md +46 -22
- package/skills/app-shell-patterns/references/migrations.md +23 -2
- package/skills/app-shell-patterns/references/pages/document-detail.md +36 -25
- package/skills/app-shell-patterns/references/patterns/form-modal.md +4 -1
- package/skills/app-shell-patterns/references/patterns/i18n-labels.md +139 -0
- package/skills/app-shell-patterns/references/patterns/list-dense-scan.md +3 -2
- package/dist/semantic-contrast.test.ts +0 -154
- package/dist/theme-bridge.test.ts +0 -159
|
@@ -100,14 +100,14 @@ A compound component that groups all parts of a form field and manages its valid
|
|
|
100
100
|
|
|
101
101
|
### Sub-components
|
|
102
102
|
|
|
103
|
-
| Sub-component | Description
|
|
104
|
-
| ------------------- |
|
|
105
|
-
| `Field.Root` | Context boundary for a single field. Manages validation state.
|
|
106
|
-
| `Field.Label` | Accessible label; `htmlFor` is resolved automatically via context.
|
|
107
|
-
| `Field.Control` | Styled `<input>`.
|
|
108
|
-
| `Field.Description` | Supplementary help text; automatically linked via `aria-describedby`.
|
|
109
|
-
| `Field.Error` | Validation error message. Use `match` to target specific validity states.
|
|
110
|
-
| `Field.Validity` | Render-prop access to the field's `ValidityState` for fully custom validation UI.
|
|
103
|
+
| Sub-component | Description |
|
|
104
|
+
| ------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
105
|
+
| `Field.Root` | Context boundary for a single field. Manages validation state. |
|
|
106
|
+
| `Field.Label` | Accessible label; `htmlFor` is resolved automatically via context. |
|
|
107
|
+
| `Field.Control` | Styled text `<input>`. `Select`, `Combobox`, and the other self-wired controls go directly under `Field.Root` instead. |
|
|
108
|
+
| `Field.Description` | Supplementary help text; automatically linked via `aria-describedby`. |
|
|
109
|
+
| `Field.Error` | Validation error message. Use `match` to target specific validity states. |
|
|
110
|
+
| `Field.Validity` | Render-prop access to the field's `ValidityState` for fully custom validation UI. |
|
|
111
111
|
|
|
112
112
|
### Field.Root Props
|
|
113
113
|
|
|
@@ -134,7 +134,17 @@ A compound component that groups all parts of a form field and manages its valid
|
|
|
134
134
|
|
|
135
135
|
### Using Another AppShell Component as the Control
|
|
136
136
|
|
|
137
|
-
`Field.Control`
|
|
137
|
+
`Field.Control` is a text input. It is the control for plain text-like fields, and you can pass it a native element such as `render={<textarea />}`. The controls in the last row of the table below go **directly under `Field.Root`**, as a sibling of `Field.Label`. Each one registers itself with the `Field` context, so it gets label association, `aria-describedby`, `disabled`, and validation state without `Field.Control`.
|
|
138
|
+
|
|
139
|
+
| Control | Where it goes |
|
|
140
|
+
| ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
|
|
141
|
+
| Plain text, email, number, password, … input | `<Field.Control type="…" />` |
|
|
142
|
+
| Native element (`<textarea>`, `<input type="…">`) | `<Field.Control render={<textarea />} />` |
|
|
143
|
+
| `Select`, `Combobox`, `Autocomplete`, `Checkbox`, `Textarea`, `DateField`, `DatePicker`, `DateRangePicker` (and their `.Async`) | Directly under `Field.Root` — not `Field.Control` |
|
|
144
|
+
|
|
145
|
+
`Input` is the exception. It is a plain `<input>` that does not connect to `Field.Root`, so inside a field use `<Field.Control />` in its place.
|
|
146
|
+
|
|
147
|
+
Do not pass the self-wired controls to `Field.Control`'s `render` prop. They draw their own frame, so `<Field.Control render={<Select />} />` renders a second border around the select. `Field.Control` also passes them `<input>` props they don't take. `Field.Control` logs a console warning when it is given one of these controls directly as an element. A wrapper component or the function form of `render` is not detected.
|
|
138
148
|
|
|
139
149
|
```tsx
|
|
140
150
|
const [country, setCountry] = React.useState<string | null>(null);
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: ItemList
|
|
3
|
+
description: Compact icon-and-label lists for consistent navigation inside and outside the Sidebar
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- GENERATED by docs-kit from docs-src/components/item-list.docs.outline.md — do not edit by hand. -->
|
|
7
|
+
|
|
8
|
+
# ItemList
|
|
9
|
+
|
|
10
|
+
`ItemList` presents compact icon-and-label rows for navigation and actions. Use it for in-content navigation, tables of contents, or other lists that should share the Sidebar's list presentation without depending on its layout or providers.
|
|
11
|
+
|
|
12
|
+
It does not require `AppShell`, a router, or a Sidebar provider. Resource metadata, translations, permissions, and active-route matching remain the caller's responsibility.
|
|
13
|
+
|
|
14
|
+
## Import
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { ItemList } from "@tailor-platform/app-shell";
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Basic Usage
|
|
21
|
+
|
|
22
|
+
<example-preview name="basic-usage"></example-preview>
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
function BasicUsage() {
|
|
26
|
+
const [view, setView] = useState("Overview");
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<ItemList.Root aria-label="Workspace">
|
|
30
|
+
<ItemList.Item
|
|
31
|
+
icon={<Home />}
|
|
32
|
+
isActive={view === "Overview"}
|
|
33
|
+
onClick={() => setView("Overview")}
|
|
34
|
+
>
|
|
35
|
+
Overview
|
|
36
|
+
</ItemList.Item>
|
|
37
|
+
<ItemList.Separator />
|
|
38
|
+
<ItemList.Section title="Business">
|
|
39
|
+
<ItemList.Item icon={<ShoppingCart />} onClick={() => setView("Orders")}>
|
|
40
|
+
Orders
|
|
41
|
+
</ItemList.Item>
|
|
42
|
+
<ItemList.Group title="Products" icon={<Package />}>
|
|
43
|
+
<ItemList.Item onClick={() => setView("All products")}>All products</ItemList.Item>
|
|
44
|
+
<ItemList.Item onClick={() => setView("Categories")}>Categories</ItemList.Item>
|
|
45
|
+
</ItemList.Group>
|
|
46
|
+
</ItemList.Section>
|
|
47
|
+
</ItemList.Root>
|
|
48
|
+
<output>Current view: {view}</output>
|
|
49
|
+
</>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Sub-components
|
|
55
|
+
|
|
56
|
+
| Sub-component | Description |
|
|
57
|
+
| -------------------- | -------------------------------------------------------------------------------- |
|
|
58
|
+
| `ItemList.Root` | A semantic `ul`; accepts standard list props |
|
|
59
|
+
| `ItemList.Item` | A compact icon-and-label row containing a button or supplied interactive element |
|
|
60
|
+
| `ItemList.Group` | A collapsible, indented child list; open by default |
|
|
61
|
+
| `ItemList.Section` | A labeled, non-collapsible list whose items retain their normal indentation |
|
|
62
|
+
| `ItemList.Separator` | A decorative divider that remains a valid list child |
|
|
63
|
+
|
|
64
|
+
## Items, Links, and Actions
|
|
65
|
+
|
|
66
|
+
`ItemList.Item` is for links and actions, not static rows. It requires `onClick`, `render`, or both; omitting both is a type error, including for a disabled Item. With `onClick`, it renders a `button` with `type="button"` by default. Supply an anchor or application link through `render` for navigation.
|
|
67
|
+
|
|
68
|
+
`render` replaces the interactive element inside the row, not the containing `li`. `children` supplies its label, and `icon` supplies an optional decorative leading icon. Standard button props, classes, attributes, and refs apply to the inner operation. A disabled Item is skipped by Tab and cancels clicks, including navigation through a supplied link.
|
|
69
|
+
|
|
70
|
+
<example-preview name="links-and-actions"></example-preview>
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
function LinksAndActions() {
|
|
74
|
+
const [checks, setChecks] = useState(0);
|
|
75
|
+
const handleCheckNotifications = () => {
|
|
76
|
+
setChecks((count) => count + 1);
|
|
77
|
+
};
|
|
78
|
+
return (
|
|
79
|
+
<>
|
|
80
|
+
<ItemList.Root aria-label="Quick access">
|
|
81
|
+
<ItemList.Item icon={<Home />} render={<a href="https://example.com/help" />}>
|
|
82
|
+
Help center
|
|
83
|
+
</ItemList.Item>
|
|
84
|
+
<ItemList.Item icon={<Bell />} onClick={handleCheckNotifications}>
|
|
85
|
+
Check notifications
|
|
86
|
+
</ItemList.Item>
|
|
87
|
+
</ItemList.Root>
|
|
88
|
+
<output>Notification checks: {checks}</output>
|
|
89
|
+
</>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
For application navigation, use `Link` or `NavLink` from `@tailor-platform/app-shell` inside an AppShell-managed page. Do not import routing APIs directly from `react-router` or create a separate router around the list. The routes in this example belong to the documentation app; use your application's route paths in your own pages.
|
|
95
|
+
|
|
96
|
+
<example-preview name="app-shell-routing"></example-preview>
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
function AppShellRouting() {
|
|
100
|
+
return (
|
|
101
|
+
<ItemList.Root aria-label="Documentation pages">
|
|
102
|
+
<ItemList.Item render={<Link to="/components/button" />}>Button documentation</ItemList.Item>
|
|
103
|
+
<ItemList.Item render={<NavLink to="/components/item-list" end />}>
|
|
104
|
+
ItemList documentation
|
|
105
|
+
</ItemList.Item>
|
|
106
|
+
</ItemList.Root>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`NavLink` owns route matching and supplies `aria-current="page"` when matched; ItemList styles that attribute with the same emphasis as `isActive`. Use `end` when only the exact destination should be current. With a plain link, the caller supplies `aria-current="page"` when appropriate.
|
|
112
|
+
|
|
113
|
+
`isActive` remains a boolean for explicit visual emphasis, including local actions. It does not imply `aria-current`, selection state, a `listbox` role, or `aria-selected`. ItemList does not perform route matching.
|
|
114
|
+
|
|
115
|
+
### Item Props
|
|
116
|
+
|
|
117
|
+
| Prop | Type | Default | Description |
|
|
118
|
+
| ----------- | -------------------------------------- | ------- | ------------------------------------------------------------------ |
|
|
119
|
+
| `icon` | `ReactNode` | — | Optional decorative leading icon |
|
|
120
|
+
| `children` | `ReactNode` | — | Compact row label |
|
|
121
|
+
| `render` | `ReactElement` | — | Replaces the inner button; required unless `onClick` is supplied |
|
|
122
|
+
| `onClick` | `MouseEventHandler<HTMLButtonElement>` | — | Action handler; required unless `render` is supplied |
|
|
123
|
+
| `isActive` | `boolean` | `false` | Explicit visual emphasis without route matching or selection state |
|
|
124
|
+
| `disabled` | `boolean` | — | Disables clicks and removes the operation from the tab order |
|
|
125
|
+
| `className` | `string` | — | Classes applied to the inner operation |
|
|
126
|
+
|
|
127
|
+
Other standard button props, such as `title` and ARIA attributes, are forwarded to the inner operation. Put link-specific props such as `href`, `target`, and `rel` on the element passed to `render`.
|
|
128
|
+
|
|
129
|
+
Rows without an `icon` do not reserve icon space. To align a mixed list, explicitly pass `icon={<span />}` on a row that needs an empty icon slot. Lists with no icons keep their compact indentation.
|
|
130
|
+
|
|
131
|
+
## Groups and Heading Hierarchies
|
|
132
|
+
|
|
133
|
+
`expandOnClick` defaults to `true`: clicking a group's heading toggles its child list, whether or not `render` is supplied. `render` only replaces the heading's main operation; it does not determine the expansion behavior. `defaultOpen` defaults to `true`; set it to `false` to start collapsed. Groups can be nested.
|
|
134
|
+
|
|
135
|
+
Set `expandOnClick={false}` to separate the heading's main operation from expansion. This requires `onClick` or `render` so the heading does not become an inert button. A separate button, named by the group title, expands and collapses the group. Use this for linked headings so navigation does not toggle the list.
|
|
136
|
+
|
|
137
|
+
`onClick` applies only to the heading's main operation. Clicking a child Item or the separate expand button does not invoke it. `isActive` and `aria-current="page"` control emphasis on the main heading operation, not the separate expand button.
|
|
138
|
+
|
|
139
|
+
For a table of contents, use a linked Group for a parent heading and Items for its child headings. Keep the list data aligned with the actual heading hierarchy and anchor IDs in the document.
|
|
140
|
+
|
|
141
|
+
<example-preview name="linked-group"></example-preview>
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
function LinkedGroup() {
|
|
145
|
+
return (
|
|
146
|
+
<ItemList.Root aria-label="Contents">
|
|
147
|
+
<ItemList.Group
|
|
148
|
+
title="Installing"
|
|
149
|
+
render={<a href="#installing" aria-label="Installing" />}
|
|
150
|
+
expandOnClick={false}
|
|
151
|
+
>
|
|
152
|
+
<ItemList.Item render={<a href="#configuration" aria-label="Configuration" />}>
|
|
153
|
+
Configuration
|
|
154
|
+
</ItemList.Item>
|
|
155
|
+
</ItemList.Group>
|
|
156
|
+
</ItemList.Root>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Group Props
|
|
162
|
+
|
|
163
|
+
| Prop | Type | Default | Description |
|
|
164
|
+
| --------------- | -------------------------------------- | ------- | ------------------------------------------------------------------------------------------------- |
|
|
165
|
+
| `title` | `ReactNode` | — | Accessible group label, including text and optional decorations |
|
|
166
|
+
| `icon` | `ReactNode` | — | Optional decorative leading icon |
|
|
167
|
+
| `render` | `ReactElement` | — | Replaces the main heading operation without changing expansion behavior |
|
|
168
|
+
| `onClick` | `MouseEventHandler<HTMLButtonElement>` | — | Handler for the main heading operation only |
|
|
169
|
+
| `expandOnClick` | `boolean` | `true` | Toggle from the heading; `false` requires `onClick` or `render` and adds a separate expand button |
|
|
170
|
+
| `isActive` | `boolean` | `false` | Explicit visual emphasis for the heading operation |
|
|
171
|
+
| `defaultOpen` | `boolean` | `true` | Initial uncontrolled expanded state |
|
|
172
|
+
| `children` | `ReactNode` | — | Nested Items, Groups, Sections, or Separators |
|
|
173
|
+
|
|
174
|
+
Other standard `li` props, including `className`, apply to the containing group item, not its heading operation. `onClick` is the exception described above.
|
|
175
|
+
|
|
176
|
+
A Group's rendered label names its heading operation, separate expand button, and child list. Keep meaningful text in a rich `title`; ReactNode content is not converted into a string. Meaningful details such as counts can be read with the label, while purely decorative elements should use `aria-hidden="true"`. An icon-only title needs screen-reader text. Do not put links or buttons inside the title, because the heading itself is interactive.
|
|
177
|
+
|
|
178
|
+
## Sections and Separators
|
|
179
|
+
|
|
180
|
+
Use `Group` for hierarchy and `Section` for flat classification. A Section accepts a resolved string `title` and `children`, keeps child rows at their normal indentation, and does not add an interactive heading. Other standard `li` props apply to its outer item.
|
|
181
|
+
|
|
182
|
+
Neither Group nor Section requires wrapping its children in another `ItemList.Root`. Use `ItemList.Separator` between logical runs of items; it is decorative and renders a list item rather than an invalid direct `div` child of `ul`.
|
|
183
|
+
|
|
184
|
+
## Styling and Accessibility
|
|
185
|
+
|
|
186
|
+
- Keep labels concise; they truncate in narrow containers.
|
|
187
|
+
- Use `aria-label` or `aria-labelledby` on Root when a list needs an accessible name.
|
|
188
|
+
- Native links and buttons retain their keyboard interactions. Group buttons announce their expanded state and support Enter and Space.
|
|
189
|
+
- Resolve translations before passing labels; ItemList does not translate them. Group titles can contain ReactNode content, while Section titles remain strings.
|
|
190
|
+
- ItemList is an ordinary list, not a menu or selection widget. It does not provide arrow-key selection, search, reordering, or Sidebar icon-rail behavior.
|
|
191
|
+
- Existing Sidebar components retain route metadata resolution, rail tooltips and flyouts, and drawer dismissal while sharing the underlying list presentation.
|
|
@@ -171,6 +171,16 @@ const { Root, Trigger, Value, Content, Item, Group, GroupLabel, Separator } = Se
|
|
|
171
171
|
|
|
172
172
|
## Form submission
|
|
173
173
|
|
|
174
|
+
Inside a form, place `Select` directly under `Field.Root`, not inside `Field.Control`. `Select` draws
|
|
175
|
+
its own trigger and registers with the field itself.
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
<Field.Root name="country">
|
|
179
|
+
<Field.Label>Country</Field.Label>
|
|
180
|
+
<Select items={["Japan", "United States"]} placeholder="Select a country" />
|
|
181
|
+
</Field.Root>
|
|
182
|
+
```
|
|
183
|
+
|
|
174
184
|
Inside a `Field.Root`, the field's `name` identifies the value and these props are unnecessary —
|
|
175
185
|
see [Form](./form.md). They matter for **native** submission: a plain `<form>`, `new FormData(form)`,
|
|
176
186
|
or a server action.
|
|
@@ -124,13 +124,13 @@ Titles support i18n with `defineI18nLabels`:
|
|
|
124
124
|
import { defineI18nLabels } from "@tailor-platform/app-shell";
|
|
125
125
|
|
|
126
126
|
const labels = defineI18nLabels({
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
en: {
|
|
128
|
+
products: "Products",
|
|
129
|
+
settings: "Settings",
|
|
130
130
|
},
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
ja: {
|
|
132
|
+
products: "商品",
|
|
133
|
+
settings: "設定",
|
|
134
134
|
},
|
|
135
135
|
});
|
|
136
136
|
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Text
|
|
3
|
+
description: Text with a typography role, rendered as an element you choose
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- GENERATED by docs-kit from docs-src/components/text.docs.outline.md — do not edit by hand. -->
|
|
7
|
+
|
|
8
|
+
# Text
|
|
9
|
+
|
|
10
|
+
The `Text` component applies a [typography role](../concepts/styling-theming.md#typography) to its content. A role sets font size, line height, font weight, and letter spacing together. The role does not set the color, and it does not choose the HTML element. To choose the element, use an element component such as `Text.h1` or `Text.p`, or use the `render` prop (Base UI render pattern) to compose with another component.
|
|
11
|
+
|
|
12
|
+
## Import
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { Text } from "@tailor-platform/app-shell";
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Basic Usage
|
|
19
|
+
|
|
20
|
+
<example-preview name="basic-usage"></example-preview>
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
function BasicUsage() {
|
|
24
|
+
return (
|
|
25
|
+
<div className="flex flex-col gap-1">
|
|
26
|
+
<Text.h2>Purchase orders</Text.h2>
|
|
27
|
+
<Text.p className="text-muted-foreground">Orders sent to suppliers this month.</Text.p>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Props
|
|
34
|
+
|
|
35
|
+
_Generated from the type surface — first-party props only._
|
|
36
|
+
|
|
37
|
+
### TextProps
|
|
38
|
+
|
|
39
|
+
| Prop | Type | Default | Description |
|
|
40
|
+
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
41
|
+
| `ref?` | `React.Ref<HTMLElement>` | - | Ref to the rendered element. |
|
|
42
|
+
| `render?` | `React.ReactElement` | - | Custom element to render as (Base UI render prop). Use it to compose with another component. For a plain HTML element, use `Text.h1` to `Text.h6`, `Text.p`, `Text.span`, or `Text.div`. |
|
|
43
|
+
| `variant?` | `"heading-lg" \| "heading-md" \| "heading-sm" \| "body-md" \| "body-sm" \| "label-md" \| "label-sm" \| "code-sm" \| "body-md-relaxed" \| "body-sm-relaxed" \| null` | `"body-md"` | Typography role. Sets font size, line height, font weight, and letter spacing together. |
|
|
44
|
+
|
|
45
|
+
## Exports
|
|
46
|
+
|
|
47
|
+
- `Text` — const
|
|
48
|
+
- `TextProps` — type
|
|
49
|
+
|
|
50
|
+
## Element Components
|
|
51
|
+
|
|
52
|
+
`Text.h1` to `Text.h6`, `Text.p`, `Text.span`, and `Text.div` always render their named HTML element. Each one applies a default role, so you do not repeat `variant` for ordinary text:
|
|
53
|
+
|
|
54
|
+
<example-preview name="element-components"></example-preview>
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
function ElementComponents() {
|
|
58
|
+
return (
|
|
59
|
+
<div className="flex flex-col gap-1">
|
|
60
|
+
<Text.h3>Shipping address</Text.h3>
|
|
61
|
+
<Text.p className="text-muted-foreground">Orders ship from the main warehouse.</Text.p>
|
|
62
|
+
<Text.span className="tabular-nums">3 packages</Text.span>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
| Component | Default `variant` |
|
|
69
|
+
| ------------------- | ----------------- |
|
|
70
|
+
| `Text.h1` | `heading-lg` |
|
|
71
|
+
| `Text.h2` | `heading-md` |
|
|
72
|
+
| `Text.h3`–`Text.h6` | `heading-sm` |
|
|
73
|
+
| `Text.p` | `body-md` |
|
|
74
|
+
| `Text.span` | `label-md` |
|
|
75
|
+
| `Text.div` | `body-md` |
|
|
76
|
+
|
|
77
|
+
The element sets the meaning and `variant` sets the appearance. The default is only a starting point, and any role works on any element:
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
<Text.h1 variant="heading-sm">A compact page title</Text.h1>
|
|
81
|
+
<Text.h3 variant="heading-lg">A visually prominent subsection</Text.h3>
|
|
82
|
+
<Text.span variant="body-sm">Updated recently</Text.span>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The element components take `variant`, `className`, the element's own attributes, and a ref typed for the element (`HTMLHeadingElement` for `Text.h2`). They do not take `render`. For any other element, or to compose with another component, use `Text` with `render`.
|
|
86
|
+
|
|
87
|
+
## Using `render`
|
|
88
|
+
|
|
89
|
+
All standard HTML attributes are also accepted by `Text`. Put an attribute that belongs to one element, such as `htmlFor` on a `label`, on the `render` element:
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
<Text variant="label-md" render={<label htmlFor="quantity" />}>
|
|
93
|
+
Quantity
|
|
94
|
+
</Text>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Some lint rules report this example; see [Lint](#lint).
|
|
98
|
+
|
|
99
|
+
## Roles
|
|
100
|
+
|
|
101
|
+
`variant` takes one of the ten roles. On `Text`, each role renders a default element when you do not pass `render`:
|
|
102
|
+
|
|
103
|
+
| `variant` | Default element | Use |
|
|
104
|
+
| ----------------- | --------------- | ----------------------------------------------- |
|
|
105
|
+
| `heading-lg` | `h2` | Page title (`Layout.Header`) |
|
|
106
|
+
| `heading-md` | `h2` | Section heading, card title |
|
|
107
|
+
| `heading-sm` | `h2` | Sub-section heading |
|
|
108
|
+
| `body-md` | `p` | Body copy, descriptions (default role) |
|
|
109
|
+
| `body-sm` | `p` | Caption, timestamp, metadata |
|
|
110
|
+
| `body-md-relaxed` | `p` | Long text, such as a chat message or a document |
|
|
111
|
+
| `body-sm-relaxed` | `p` | Long text at the small size |
|
|
112
|
+
| `label-md` | `span` | Field label, short UI text |
|
|
113
|
+
| `label-sm` | `span` | Small label, badge text |
|
|
114
|
+
| `code-sm` | `span` | ID and code. `Text` adds `font-mono` for you |
|
|
115
|
+
|
|
116
|
+
<example-preview name="roles"></example-preview>
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
function Roles() {
|
|
120
|
+
return (
|
|
121
|
+
<div className="flex flex-col gap-3">
|
|
122
|
+
{/* The headings render a div so that this specimen is not a document heading. */}
|
|
123
|
+
<Text.div variant="heading-lg">heading-lg: Purchase orders</Text.div>
|
|
124
|
+
<Text.div variant="heading-md">heading-md: Line items</Text.div>
|
|
125
|
+
<Text.div variant="heading-sm">heading-sm: Shipping address</Text.div>
|
|
126
|
+
<Text variant="body-md">body-md: Orders sent to suppliers this month.</Text>
|
|
127
|
+
<Text variant="body-sm">body-sm: Updated 2 hours ago</Text>
|
|
128
|
+
<Text variant="label-md">label-md: Total amount</Text>
|
|
129
|
+
<Text variant="label-sm">label-sm: Draft</Text>
|
|
130
|
+
<Text variant="code-sm">code-sm: PO-2026-0001</Text>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The sizes and line heights are in the [Typography section of the styling guide](../concepts/styling-theming.md#typography).
|
|
137
|
+
|
|
138
|
+
## Heading Level
|
|
139
|
+
|
|
140
|
+
On `Text`, all three heading roles render `h2` by default. The heading level comes from the document structure, not from the visual size, so set it with `Text.h1` to `Text.h6`:
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
<Text.h3 variant="heading-md">Line items</Text.h3>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
To use a heading role for text that is not a heading, use `Text.div` or `Text.span`:
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
<Text.div variant="heading-md">1,240 orders</Text.div>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Lint
|
|
153
|
+
|
|
154
|
+
Some `jsx-a11y` rules check that an element has text inside it, for example `heading-has-content` for `<h3 />` and `label-has-associated-control` for `<label />`. These rules report the element in `render` as empty, because they do not see that `Text` passes its children to that element.
|
|
155
|
+
|
|
156
|
+
For headings, use `Text.h1` to `Text.h6`. The rules do not report them. For other elements, such as `label`, use `render`, and if your app enables the rule, disable it on that line. ESLint and oxlint both read this comment:
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
// eslint-disable-next-line jsx-a11y/label-has-associated-control -- Text renders the children
|
|
160
|
+
<Text variant="label-md" render={<label htmlFor="quantity" />}>
|
|
161
|
+
Quantity
|
|
162
|
+
</Text>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Color and Modifiers
|
|
166
|
+
|
|
167
|
+
Set the color with a color token in `className`. Four modifiers may also be combined with a role: `leading-none` for single-line text, `tabular-nums`, `font-semibold` for emphasis, and `uppercase`.
|
|
168
|
+
|
|
169
|
+
<example-preview name="color-and-modifiers"></example-preview>
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
function ColorAndModifiers() {
|
|
173
|
+
return (
|
|
174
|
+
<div className="flex flex-col gap-3">
|
|
175
|
+
<Text variant="body-md" className="text-muted-foreground">
|
|
176
|
+
Orders sent this month.
|
|
177
|
+
</Text>
|
|
178
|
+
<Text variant="label-md" className="tabular-nums">
|
|
179
|
+
1,240.00
|
|
180
|
+
</Text>
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
<Text variant="body-md" className="text-muted-foreground">
|
|
188
|
+
Orders sent this month.
|
|
189
|
+
</Text>
|
|
190
|
+
<Text.h3 variant="heading-md" className="leading-none">
|
|
191
|
+
Card title
|
|
192
|
+
</Text.h3>
|
|
193
|
+
<Text variant="label-md" className="tabular-nums">
|
|
194
|
+
1,240.00
|
|
195
|
+
</Text>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Do not combine a role with other `leading-*`, `tracking-*`, or `text-*` size values. Pick a different `variant` instead. A `text-*` size class such as `text-sm` has no effect on `Text`: AppShell's stylesheet loads after yours, so the role's size wins.
|
|
199
|
+
|
|
200
|
+
## Using Roles Without Text
|
|
201
|
+
|
|
202
|
+
Each role is also a plain utility, for example `text-heading-md`. Use the utility when you style your own element and do not need `Text`:
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
<th className="text-label-md text-muted-foreground">Amount</th>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## TypeScript
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import { type TextProps } from "@tailor-platform/app-shell";
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The element components have no exported props type. Read it from the component:
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
type SectionTitleProps = React.ComponentProps<typeof Text.h2>;
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Accessibility
|
|
221
|
+
|
|
222
|
+
- A heading role on `Text` renders a real heading (`h2`) by default, so screen readers list it with the other headings. Set the level with `Text.h1` to `Text.h6` so the levels follow the page structure.
|
|
223
|
+
- Do not choose a heading level for its size. `Text` keeps the size and the level separate.
|
|
224
|
+
- `body-*` roles render `p` on `Text`. Inside another `p`, use `Text.span` instead (`<Text.span variant="body-md">`): the browser closes the outer `p` before the inner one, so React warns and server rendering fails to hydrate. Inside inline elements such as `button`, `label` or `span`, a `p` is invalid HTML too, so render a `span` there as well.
|
|
225
|
+
|
|
226
|
+
## Palettes
|
|
227
|
+
|
|
228
|
+
The `cream` and `bloom` palettes set the line height and letter spacing of `h1` to `h6` elements. Under these palettes, a heading role on a heading element uses the palette values (line height 1.2, letter spacing `-0.03em`), including when you add `leading-none`. The same role on a `div` or `span` uses the role values. See the [Typography section of the styling guide](../concepts/styling-theming.md#typography).
|
|
229
|
+
|
|
230
|
+
## Related Components
|
|
231
|
+
|
|
232
|
+
- [Card](./card.md) - The table above lists `heading-md` for card titles
|
|
233
|
+
- [Layout](./layout.md) - The table above lists `heading-lg` for the page title
|
|
234
|
+
|
|
235
|
+
AppShell's own components, including these two, render their titles and descriptions with `Text`.
|
|
@@ -69,16 +69,18 @@ const App = () => (
|
|
|
69
69
|
### SidebarItem
|
|
70
70
|
|
|
71
71
|
A navigation item that automatically resolves title and icon from resource definitions.
|
|
72
|
+
Active internal sidebar links expose `aria-current="page"` on the link itself, including
|
|
73
|
+
items with custom `render` content. Inactive and external links do not expose it.
|
|
72
74
|
|
|
73
75
|
#### Props
|
|
74
76
|
|
|
75
|
-
| Prop | Type | Required | Description
|
|
76
|
-
| ---------- | ----------------- | -------- |
|
|
77
|
-
| `to` | `string` | Yes | Target URL. External URLs (http://...) are rendered as external links
|
|
78
|
-
| `title` | `LocalizedString` | No | Override title (i18n supported). When omitted, auto-resolved from resource meta
|
|
79
|
-
| `icon` | `ReactNode` | No | Override icon. When omitted, auto-resolved from resource meta
|
|
80
|
-
| `external` | `boolean` | No | Opens link in new tab with external link icon
|
|
81
|
-
| `render` | `function` | No | Custom render function for full UI control
|
|
77
|
+
| Prop | Type | Required | Description |
|
|
78
|
+
| ---------- | ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
79
|
+
| `to` | `string` | Yes | Target URL. External URLs (http://...) are rendered as external links |
|
|
80
|
+
| `title` | `LocalizedString` | No | Override title (i18n supported). When omitted, auto-resolved from resource meta |
|
|
81
|
+
| `icon` | `ReactNode` | No | Override icon. When omitted, auto-resolved from resource meta; with no icon at all, the icon rail shows the title's first letter |
|
|
82
|
+
| `external` | `boolean` | No | Opens link in new tab with external link icon |
|
|
83
|
+
| `render` | `function` | No | Custom render function for full UI control |
|
|
82
84
|
|
|
83
85
|
#### Examples
|
|
84
86
|
|
|
@@ -124,13 +126,13 @@ A collapsible group for organizing navigation items.
|
|
|
124
126
|
|
|
125
127
|
#### Props
|
|
126
128
|
|
|
127
|
-
| Prop | Type | Required | Description
|
|
128
|
-
| ------------- | ----------------- | -------- |
|
|
129
|
-
| `title` | `LocalizedString` | Yes | Group title (supports i18n)
|
|
130
|
-
| `icon` | `ReactNode` | No | Group icon
|
|
131
|
-
| `to` | `string` | No | When specified, title becomes a clickable link
|
|
132
|
-
| `defaultOpen` | `boolean` | No | Initial expanded state. Default: `true`
|
|
133
|
-
| `children` | `ReactNode` | Yes | Child items (SidebarItem, SidebarGroup, etc.)
|
|
129
|
+
| Prop | Type | Required | Description |
|
|
130
|
+
| ------------- | ----------------- | -------- | ---------------------------------------------------------------------- |
|
|
131
|
+
| `title` | `LocalizedString` | Yes | Group title (supports i18n) |
|
|
132
|
+
| `icon` | `ReactNode` | No | Group icon. When omitted, the icon rail shows the title's first letter |
|
|
133
|
+
| `to` | `string` | No | When specified, title becomes a clickable link |
|
|
134
|
+
| `defaultOpen` | `boolean` | No | Initial expanded state. Default: `true` |
|
|
135
|
+
| `children` | `ReactNode` | Yes | Child items (SidebarItem, SidebarGroup, etc.) |
|
|
134
136
|
|
|
135
137
|
#### Examples
|
|
136
138
|
|