@tailor-platform/app-shell 1.18.0 → 1.20.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 +2 -2
- package/dist/app-shell.d.ts +1108 -190
- package/dist/app-shell.js +13090 -10783
- package/dist/{appshell-Y1AYCuVJ.js → appshell-B4QkQvXF.js} +519 -392
- package/dist/fonts/inter.css +7 -7
- package/dist/fonts/noto-sans-jp.css +116 -116
- package/dist/style-entry.d.ts +1 -656
- package/dist/testing.d.ts +132 -97
- package/dist/testing.js +1 -1
- package/dist/vite-plugin.d.ts +132 -97
- package/package.json +15 -10
- package/skills/app-shell-patterns/SKILL.md +38 -27
- package/skills/app-shell-patterns/references/api/aggregations.md +131 -0
- package/skills/app-shell-patterns/references/api/create-ai-gateway-client.md +32 -2
- package/skills/app-shell-patterns/references/api/define-module.md +5 -5
- package/skills/app-shell-patterns/references/api/router/use-params.md +2 -2
- package/skills/app-shell-patterns/references/api/use-ai-chat.md +46 -8
- package/skills/app-shell-patterns/references/api/use-app-shell-config.md +4 -4
- package/skills/app-shell-patterns/references/api/use-app-shell-data.md +5 -5
- package/skills/app-shell-patterns/references/api/use-app-shell.md +2 -2
- package/skills/app-shell-patterns/references/api/use-auth.md +3 -3
- package/skills/app-shell-patterns/references/api/use-route-error.md +12 -12
- package/skills/app-shell-patterns/references/components/action-panel.md +57 -23
- package/skills/app-shell-patterns/references/components/activity-card.md +3 -3
- package/skills/app-shell-patterns/references/components/ai-chat.md +37 -3
- package/skills/app-shell-patterns/references/components/app-shell.md +6 -5
- package/skills/app-shell-patterns/references/components/badge.md +7 -3
- package/skills/app-shell-patterns/references/components/card.md +2 -2
- package/skills/app-shell-patterns/references/components/data-table.md +514 -77
- package/skills/app-shell-patterns/references/components/date-picker.md +2 -2
- package/skills/app-shell-patterns/references/components/default-sidebar.md +2 -2
- package/skills/app-shell-patterns/references/components/dialog.md +8 -0
- package/skills/app-shell-patterns/references/components/filter-rail.md +10 -3
- package/skills/app-shell-patterns/references/components/form.md +19 -9
- package/skills/app-shell-patterns/references/components/global-header-layout.md +31 -8
- package/skills/app-shell-patterns/references/components/item-list.md +191 -0
- package/skills/app-shell-patterns/references/components/layout.md +24 -17
- package/skills/app-shell-patterns/references/components/select.md +18 -0
- package/skills/app-shell-patterns/references/components/sidebar-group.md +9 -7
- package/skills/app-shell-patterns/references/components/sidebar-layout.md +74 -11
- package/skills/app-shell-patterns/references/components/sidebar-section.md +8 -8
- package/skills/app-shell-patterns/references/components/tabs.md +32 -2
- package/skills/app-shell-patterns/references/components/text.md +75 -38
- package/skills/app-shell-patterns/references/components/tree.md +342 -0
- package/skills/app-shell-patterns/references/components/with-guard.md +5 -5
- package/skills/app-shell-patterns/references/concepts/authentication.md +1 -1
- package/skills/app-shell-patterns/references/concepts/routing-navigation.md +3 -3
- package/skills/app-shell-patterns/references/concepts/sidebar-navigation.md +16 -14
- package/skills/app-shell-patterns/references/concepts/styling-theming.md +28 -20
- package/skills/app-shell-patterns/references/migrations.md +22 -2
- package/skills/app-shell-patterns/references/pages/document-detail.md +32 -25
- package/skills/app-shell-patterns/references/patterns/form-modal.md +4 -1
- package/skills/app-shell-patterns/references/patterns/interaction-multi-select.md +1 -1
- package/dist/semantic-contrast.test.ts +0 -227
- package/dist/theme-bridge.test.ts +0 -182
|
@@ -48,12 +48,13 @@ This gives you:
|
|
|
48
48
|
|
|
49
49
|
### Common props
|
|
50
50
|
|
|
51
|
-
| Prop
|
|
52
|
-
|
|
|
53
|
-
| `sidebar`
|
|
54
|
-
| `defaultOpen`
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
51
|
+
| Prop | Type | Default | Description |
|
|
52
|
+
| ------------------ | ------------------------------------------------------------ | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
53
|
+
| `sidebar` | `React.ReactNode` | `<SidebarLayout.DefaultSidebar />` | Replaces the entire sidebar region. |
|
|
54
|
+
| `defaultOpen` | `boolean` | `true` | Whether the sidebar starts expanded on desktop. Ignored in `"fixed"` and `"resize"` modes. |
|
|
55
|
+
| `sidebarBehaviour` | `"fixed" \| "collapse" \| "resize" \| "collapse-and-resize"` | `"collapse-and-resize"` | Selects the permitted desktop operations. Mobile/tablet drawer access is independent of this setting. |
|
|
56
|
+
| `collapsible` | `boolean` | - | Deprecated compatibility prop: explicit `false` maps to `"fixed"` and explicit `true` to `"collapse"`. Cannot be combined with `sidebarBehaviour`. |
|
|
57
|
+
| `topBar` | `React.ReactNode` | - | Full-width bar above both the sidebar and the content region. |
|
|
57
58
|
|
|
58
59
|
### Default content mode
|
|
59
60
|
|
|
@@ -74,6 +75,61 @@ Use this when you need to compose your own columns beside the sidebar.
|
|
|
74
75
|
|
|
75
76
|
`body` is mutually exclusive with `header` and `children`, because it replaces the region those props normally describe.
|
|
76
77
|
|
|
78
|
+
## Resizing the sidebar
|
|
79
|
+
|
|
80
|
+
`sidebarBehaviour` selects the desktop operations. It defaults to `"collapse-and-resize"`:
|
|
81
|
+
|
|
82
|
+
| Mode | Collapse | Resize |
|
|
83
|
+
| ----------------------- | -------- | ------ |
|
|
84
|
+
| `"fixed"` | No | No |
|
|
85
|
+
| `"collapse"` | Yes | No |
|
|
86
|
+
| `"resize"` | No | Yes |
|
|
87
|
+
| `"collapse-and-resize"` | Yes | Yes |
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
<SidebarLayout />; // Both operations are enabled by default
|
|
91
|
+
<SidebarLayout sidebarBehaviour="fixed" />;
|
|
92
|
+
<SidebarLayout sidebarBehaviour="collapse" />;
|
|
93
|
+
<SidebarLayout sidebarBehaviour="resize" />;
|
|
94
|
+
<SidebarLayout sidebarBehaviour="collapse-and-resize" />;
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`"fixed"` and `"resize"` always keep the desktop sidebar expanded, ignore `defaultOpen`, and make `useAppShellSidebar().setOpen()` inert. They hide desktop collapse controls and do not intercept the desktop toggle shortcut. `"collapse"` has toggle controls but no resize handle.
|
|
98
|
+
|
|
99
|
+
All modes keep the layout's normal mobile/tablet drawer and icon-rail overlay presentation. The trigger, `toggle()`, and shortcut can still open the full menu when desktop collapsing is disabled. These responsive visibility states are independent of desktop expansion.
|
|
100
|
+
|
|
101
|
+
### Deprecated `collapsible`
|
|
102
|
+
|
|
103
|
+
The existing boolean remains supported but is deprecated. Explicit `false` maps to `"fixed"`; explicit `true` maps to `"collapse"`. Omitting both props uses the new `"collapse-and-resize"` default. TypeScript rejects specifying both props together.
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
// Before
|
|
107
|
+
<SidebarLayout collapsible={false} />;
|
|
108
|
+
|
|
109
|
+
// After
|
|
110
|
+
<SidebarLayout sidebarBehaviour="fixed" />;
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
This intentionally changes the responsive presentation of legacy `collapsible={false}`: it no longer forces a fixed sidebar on mobile/tablet. It keeps desktop fixed while using the normal responsive drawer/overlay. A completely custom `sidebar` still owns its own presentation and sizing.
|
|
114
|
+
|
|
115
|
+
### Resize interaction
|
|
116
|
+
|
|
117
|
+
The initial width is 256px. In `"resize"` and `"collapse-and-resize"` modes, the whole outer edge of the expanded desktop sidebar is a resize handle. Hovering it shows the resize cursor and a small pill beside the border that follows the pointer. Drag the edge, or focus the handle and use Left/Right arrows (16px steps), Home, or End (480px). Double-click restores the default 256px width.
|
|
118
|
+
|
|
119
|
+
In `"resize"` mode, dragging follows the pointer and clamps to 192–480px without collapse resistance. Home and Left at the minimum keep the sidebar at 192px; double-clicking at 256px does not collapse it.
|
|
120
|
+
|
|
121
|
+
In `"collapse-and-resize"` mode, Home or Left at the minimum collapses the sidebar, and double-clicking at 256px collapses it. The collapsed desktop sidebar keeps its handle. Double-clicking it or using Enter/Space restores 256px; a single pointer click does nothing.
|
|
122
|
+
|
|
123
|
+
In `"collapse-and-resize"` mode, dragging uses a position-based resistance curve, not a delayed CSS transition. Between the collapsed width and the minimum expanded width (192px), most of the interval uses a 25% movement gain: moving the pointer 10px moves the edge only 2.5px. In the final quarter near the opposite boundary, a smooth position-based blend catches up to the pointer. The edge stops when the pointer stops; it does not catch up over time. At 192px and above it follows the pointer 1:1, up to 480px. Shrinking through the same range resists toward the collapsed position. It collapses slightly early, when the pointer enters the final 20% of that interval, with the curve already meeting the collapsed width at that point. The same pointer stays captured across both transitions, without a jump to 256px.
|
|
124
|
+
|
|
125
|
+
With collapsing enabled, releasing inside the resistance range settles back to the current mode's boundary (collapsed width or 192px).
|
|
126
|
+
|
|
127
|
+
Live drag pixels are applied through CSS custom properties rather than React state on every pointer movement. React commits the width at mode changes and when the gesture ends; ARIA values stay current during dragging. Width changes still require browser layout because the content column resizes live.
|
|
128
|
+
|
|
129
|
+
Width is not persisted across remounts or reloads. The icon rail keeps its existing collapsed width. Mobile drawers and tablet overlays keep their existing widths and do not show handles.
|
|
130
|
+
|
|
131
|
+
This applies to the built-in sidebar primitives, including `SidebarLayout.DefaultSidebar`. An arbitrary custom `sidebar` element still owns its own sizing.
|
|
132
|
+
|
|
77
133
|
## Extending the built-in header
|
|
78
134
|
|
|
79
135
|
The supported "small customization" path is to pass `SidebarLayout.DefaultHeader` into the `header` slot and fill its `actions` cluster.
|
|
@@ -162,26 +218,33 @@ Use `body` when the stock content column is too restrictive and you need your ow
|
|
|
162
218
|
|
|
163
219
|
## Reading and controlling the sidebar
|
|
164
220
|
|
|
165
|
-
Use `useAppShellSidebar()` to read and control the sidebar state instead of observing internal DOM attributes or clicking the trigger through the DOM.
|
|
221
|
+
Use `useAppShellSidebar()` to read and control the sidebar state instead of observing internal DOM attributes or clicking the trigger through the DOM. It also exposes the effective `sidebarBehaviour`, already normalized from either public prop, so custom sidebars do not need to handle the deprecated boolean themselves.
|
|
222
|
+
|
|
223
|
+
`open` and `setOpen()` describe desktop expansion. Drawer visibility is separate: `toggle()` selects the mobile drawer, tablet overlay, or desktop state according to the viewport. The four existing hook fields (`open`, `isMobile`, `setOpen`, and `toggle`) remain available.
|
|
166
224
|
|
|
167
225
|
```tsx
|
|
168
226
|
import { useAppShellSidebar } from "@tailor-platform/app-shell";
|
|
169
227
|
|
|
170
228
|
function CustomHeader() {
|
|
171
|
-
const { open, isMobile, toggle } = useAppShellSidebar();
|
|
229
|
+
const { sidebarBehaviour, open, isMobile, toggle } = useAppShellSidebar();
|
|
172
230
|
|
|
173
231
|
return (
|
|
174
232
|
<div className="flex items-center justify-between border-b px-4 py-3">
|
|
175
233
|
<button type="button" onClick={toggle}>
|
|
176
|
-
|
|
234
|
+
Toggle navigation
|
|
177
235
|
</button>
|
|
178
|
-
<span>
|
|
236
|
+
<span>
|
|
237
|
+
{sidebarBehaviour}: {open ? "Desktop expanded" : "Desktop collapsed"}
|
|
238
|
+
</span>
|
|
239
|
+
<span>{isMobile ? "Mobile" : "Non-mobile"}</span>
|
|
179
240
|
</div>
|
|
180
241
|
);
|
|
181
242
|
}
|
|
182
243
|
```
|
|
183
244
|
|
|
184
|
-
Outside `SidebarLayout`, the hook returns a safe fallback (`open: true`, `isMobile: false`, no-op
|
|
245
|
+
Outside `SidebarLayout`, the hook returns a safe fallback (`sidebarBehaviour: "fixed"`, `open: true`, `isMobile: false`, no-op actions) rather than throwing. This fallback describes the absence of a managed sidebar; it is not the layout's default mode.
|
|
246
|
+
|
|
247
|
+
The desktop capabilities can be derived from the reported mode: `"collapse"` and `"collapse-and-resize"` permit collapse, while `"resize"` and `"collapse-and-resize"` permit resizing. Reading this setting does not automatically add resize behavior to arbitrary custom markup.
|
|
185
248
|
|
|
186
249
|
## Reusing the built-in breadcrumb
|
|
187
250
|
|
|
@@ -90,14 +90,14 @@ const labels = defineI18nLabels({
|
|
|
90
90
|
- The items belong under a parent with its own icon (or landing page via `to`)
|
|
91
91
|
- You need nesting
|
|
92
92
|
|
|
93
|
-
| Feature | SidebarSection | SidebarGroup
|
|
94
|
-
| ------------------- | ----------------- |
|
|
95
|
-
| **Collapsible** | ❌ No | ✅ Yes
|
|
96
|
-
| **Heading is link** | ❌ No | Optional (`to` prop)
|
|
97
|
-
| **Icon** | ❌ No | Optional
|
|
98
|
-
| **Child styling** | Same as top level | Indented submenu
|
|
99
|
-
| **Icon rail** | Heading hidden | Group icon opens a flyout |
|
|
100
|
-
| **Nesting** | Not intended | Can contain groups
|
|
93
|
+
| Feature | SidebarSection | SidebarGroup |
|
|
94
|
+
| ------------------- | ----------------- | -------------------------------------------------- |
|
|
95
|
+
| **Collapsible** | ❌ No | ✅ Yes |
|
|
96
|
+
| **Heading is link** | ❌ No | Optional (`to` prop) |
|
|
97
|
+
| **Icon** | ❌ No | Optional |
|
|
98
|
+
| **Child styling** | Same as top level | Indented submenu |
|
|
99
|
+
| **Icon rail** | Heading hidden | Group icon opens a flyout (hover, or tap on touch) |
|
|
100
|
+
| **Nesting** | Not intended | Can contain groups |
|
|
101
101
|
|
|
102
102
|
## Accessibility
|
|
103
103
|
|
|
@@ -41,7 +41,7 @@ function BasicUsage() {
|
|
|
41
41
|
| Sub-component | Description |
|
|
42
42
|
| ------------- | -------------------------------------------------------------- |
|
|
43
43
|
| `Tabs.Root` | Manages tab selection state |
|
|
44
|
-
| `Tabs.List` | Groups the individual tab buttons
|
|
44
|
+
| `Tabs.List` | Groups the individual tab buttons on one scrolling row |
|
|
45
45
|
| `Tabs.Tab` | An interactive tab button that toggles the corresponding panel |
|
|
46
46
|
| `Tabs.Panel` | A panel displayed when the corresponding tab is active |
|
|
47
47
|
|
|
@@ -61,7 +61,9 @@ function BasicUsage() {
|
|
|
61
61
|
|
|
62
62
|
### Tabs.List Props
|
|
63
63
|
|
|
64
|
-
Accepts `className` and all standard HTML `<div>` props
|
|
64
|
+
Accepts `className` and all standard HTML `<div>` props except `render`. The
|
|
65
|
+
HTML props apply to the `tablist` element, which is also the scroll container;
|
|
66
|
+
`className` applies to the visual track inside it (see [Overflow](#overflow)).
|
|
65
67
|
|
|
66
68
|
### Tabs.Tab Props
|
|
67
69
|
|
|
@@ -96,6 +98,34 @@ const [activeTab, setActiveTab] = useState("overview");
|
|
|
96
98
|
</Tabs.Root>;
|
|
97
99
|
```
|
|
98
100
|
|
|
101
|
+
## Overflow
|
|
102
|
+
|
|
103
|
+
Tabs stay on one row. When they are wider than the space available, the list
|
|
104
|
+
scrolls sideways instead of wrapping or growing the page. The scrollbar is
|
|
105
|
+
hidden: touch, a trackpad, Shift + mouse wheel, and the arrow keys scroll it,
|
|
106
|
+
and the tab cut off at the edge shows that there is more. Base UI scrolls the
|
|
107
|
+
active tab into view when the tabs mount, and scrolls the tab that the arrow
|
|
108
|
+
keys, Home or End move focus to into view, so keyboard navigation never lands
|
|
109
|
+
on a hidden tab. Nothing changes while the tabs fit.
|
|
110
|
+
|
|
111
|
+
To make this work, the `tablist` element (`data-slot="tabs-list"`) is the
|
|
112
|
+
scroll container, and the tabs sit in a visual track inside it
|
|
113
|
+
(`data-slot="tabs-list-track"`) that carries the variant styles and your
|
|
114
|
+
`className`. The scroll container has 4px of padding, a matching negative
|
|
115
|
+
margin and the same `scroll-padding`, so the tabs' focus ring is not clipped
|
|
116
|
+
and the list keeps its footprint. A `Tabs.List` placed next to inline content
|
|
117
|
+
starts on its own line.
|
|
118
|
+
|
|
119
|
+
Two cases are not scrolled into view automatically: a tab selected by changing
|
|
120
|
+
a controlled `value` from outside (a deep link, for example), and a list that
|
|
121
|
+
mounts while hidden, such as inside a closed dialog. Call
|
|
122
|
+
`scrollIntoView({ inline: "nearest", block: "nearest" })` on the tab if you
|
|
123
|
+
need either.
|
|
124
|
+
|
|
125
|
+
Scrolling keeps every tab reachable, but it does not shorten the list. If a
|
|
126
|
+
list regularly overflows at the widths your users have, prefer fewer tabs or
|
|
127
|
+
another control, such as a `Select`, over relying on the scroll.
|
|
128
|
+
|
|
99
129
|
## Examples
|
|
100
130
|
|
|
101
131
|
### With Disabled Tab
|
|
@@ -7,7 +7,7 @@ description: Text with a typography role, rendered as an element you choose
|
|
|
7
7
|
|
|
8
8
|
# Text
|
|
9
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.
|
|
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
11
|
|
|
12
12
|
## Import
|
|
13
13
|
|
|
@@ -23,8 +23,8 @@ import { Text } from "@tailor-platform/app-shell";
|
|
|
23
23
|
function BasicUsage() {
|
|
24
24
|
return (
|
|
25
25
|
<div className="flex flex-col gap-1">
|
|
26
|
-
<Text
|
|
27
|
-
<Text className="text-muted-foreground">Orders sent to suppliers this month.</Text>
|
|
26
|
+
<Text.h2>Purchase orders</Text.h2>
|
|
27
|
+
<Text.p className="text-muted-foreground">Orders sent to suppliers this month.</Text.p>
|
|
28
28
|
</div>
|
|
29
29
|
);
|
|
30
30
|
}
|
|
@@ -36,18 +36,57 @@ _Generated from the type surface — first-party props only._
|
|
|
36
36
|
|
|
37
37
|
### TextProps
|
|
38
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
|
|
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.
|
|
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
44
|
|
|
45
45
|
## Exports
|
|
46
46
|
|
|
47
|
-
- `Text` —
|
|
47
|
+
- `Text` — const
|
|
48
48
|
- `TextProps` — type
|
|
49
49
|
|
|
50
|
-
|
|
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:
|
|
51
90
|
|
|
52
91
|
```tsx
|
|
53
92
|
<Text variant="label-md" render={<label htmlFor="quantity" />}>
|
|
@@ -59,7 +98,7 @@ Some lint rules report this example; see [Lint](#lint).
|
|
|
59
98
|
|
|
60
99
|
## Roles
|
|
61
100
|
|
|
62
|
-
`variant` takes one of the ten roles.
|
|
101
|
+
`variant` takes one of the ten roles. On `Text`, each role renders a default element when you do not pass `render`:
|
|
63
102
|
|
|
64
103
|
| `variant` | Default element | Use |
|
|
65
104
|
| ----------------- | --------------- | ----------------------------------------------- |
|
|
@@ -70,7 +109,7 @@ Some lint rules report this example; see [Lint](#lint).
|
|
|
70
109
|
| `body-sm` | `p` | Caption, timestamp, metadata |
|
|
71
110
|
| `body-md-relaxed` | `p` | Long text, such as a chat message or a document |
|
|
72
111
|
| `body-sm-relaxed` | `p` | Long text at the small size |
|
|
73
|
-
| `label-md` | `span` | Field label, short UI text
|
|
112
|
+
| `label-md` | `span` | Field label, short UI text |
|
|
74
113
|
| `label-sm` | `span` | Small label, badge text |
|
|
75
114
|
| `code-sm` | `span` | ID and code. `Text` adds `font-mono` for you |
|
|
76
115
|
|
|
@@ -81,15 +120,9 @@ function Roles() {
|
|
|
81
120
|
return (
|
|
82
121
|
<div className="flex flex-col gap-3">
|
|
83
122
|
{/* The headings render a div so that this specimen is not a document heading. */}
|
|
84
|
-
<Text variant="heading-lg"
|
|
85
|
-
|
|
86
|
-
</Text>
|
|
87
|
-
<Text variant="heading-md" render={<div />}>
|
|
88
|
-
heading-md: Line items
|
|
89
|
-
</Text>
|
|
90
|
-
<Text variant="heading-sm" render={<div />}>
|
|
91
|
-
heading-sm: Shipping address
|
|
92
|
-
</Text>
|
|
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>
|
|
93
126
|
<Text variant="body-md">body-md: Orders sent to suppliers this month.</Text>
|
|
94
127
|
<Text variant="body-sm">body-sm: Updated 2 hours ago</Text>
|
|
95
128
|
<Text variant="label-md">label-md: Total amount</Text>
|
|
@@ -104,30 +137,28 @@ The sizes and line heights are in the [Typography section of the styling guide](
|
|
|
104
137
|
|
|
105
138
|
## Heading Level
|
|
106
139
|
|
|
107
|
-
|
|
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`:
|
|
108
141
|
|
|
109
142
|
```tsx
|
|
110
|
-
<Text variant="heading-md"
|
|
111
|
-
Line items
|
|
112
|
-
</Text>
|
|
143
|
+
<Text.h3 variant="heading-md">Line items</Text.h3>
|
|
113
144
|
```
|
|
114
145
|
|
|
115
|
-
To use a heading role for text that is not a heading,
|
|
146
|
+
To use a heading role for text that is not a heading, use `Text.div` or `Text.span`:
|
|
116
147
|
|
|
117
148
|
```tsx
|
|
118
|
-
<Text variant="heading-md"
|
|
119
|
-
1,240 orders
|
|
120
|
-
</Text>
|
|
149
|
+
<Text.div variant="heading-md">1,240 orders</Text.div>
|
|
121
150
|
```
|
|
122
151
|
|
|
123
152
|
## Lint
|
|
124
153
|
|
|
125
|
-
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.
|
|
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:
|
|
126
157
|
|
|
127
158
|
```tsx
|
|
128
|
-
// eslint-disable-next-line jsx-a11y/
|
|
129
|
-
<Text variant="
|
|
130
|
-
|
|
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
|
|
131
162
|
</Text>
|
|
132
163
|
```
|
|
133
164
|
|
|
@@ -156,9 +187,9 @@ function ColorAndModifiers() {
|
|
|
156
187
|
<Text variant="body-md" className="text-muted-foreground">
|
|
157
188
|
Orders sent this month.
|
|
158
189
|
</Text>
|
|
159
|
-
<Text variant="heading-md"
|
|
190
|
+
<Text.h3 variant="heading-md" className="leading-none">
|
|
160
191
|
Card title
|
|
161
|
-
</Text>
|
|
192
|
+
</Text.h3>
|
|
162
193
|
<Text variant="label-md" className="tabular-nums">
|
|
163
194
|
1,240.00
|
|
164
195
|
</Text>
|
|
@@ -180,11 +211,17 @@ Each role is also a plain utility, for example `text-heading-md`. Use the utilit
|
|
|
180
211
|
import { type TextProps } from "@tailor-platform/app-shell";
|
|
181
212
|
```
|
|
182
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
|
+
|
|
183
220
|
## Accessibility
|
|
184
221
|
|
|
185
|
-
- A heading role renders a real heading (`h2`) by default, so screen readers list it with the other headings. Set the level with `
|
|
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.
|
|
186
223
|
- Do not choose a heading level for its size. `Text` keeps the size and the level separate.
|
|
187
|
-
- `body-*` roles render `p`. Inside another `p`,
|
|
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.
|
|
188
225
|
|
|
189
226
|
## Palettes
|
|
190
227
|
|
|
@@ -195,4 +232,4 @@ The `cream` and `bloom` palettes set the line height and letter spacing of `h1`
|
|
|
195
232
|
- [Card](./card.md) - The table above lists `heading-md` for card titles
|
|
196
233
|
- [Layout](./layout.md) - The table above lists `heading-lg` for the page title
|
|
197
234
|
|
|
198
|
-
AppShell's own components, including these two,
|
|
235
|
+
AppShell's own components, including these two, render their titles and descriptions with `Text`.
|