@tailor-platform/app-shell 1.15.0 → 1.16.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 +208 -114
- package/dist/app-shell.js +3449 -3446
- package/dist/{appshell-DLVBFo5J.js → appshell-B0wh5Lq0.js} +625 -512
- package/dist/style-entry.d.ts +116 -96
- package/dist/testing.d.ts +116 -96
- package/dist/testing.js +1 -1
- package/dist/vite-plugin.d.ts +116 -96
- package/package.json +8 -8
- package/skills/app-shell-patterns/SKILL.md +101 -14
- package/skills/app-shell-patterns/references/api/create-ai-gateway-client.md +115 -0
- package/skills/app-shell-patterns/references/api/create-typed-paths.md +117 -0
- package/skills/app-shell-patterns/references/api/define-i18n-labels.md +143 -0
- package/skills/app-shell-patterns/references/api/define-module.md +247 -0
- package/skills/app-shell-patterns/references/api/define-resource.md +307 -0
- package/skills/app-shell-patterns/references/api/guards/hidden.md +202 -0
- package/skills/app-shell-patterns/references/api/guards/overview.md +354 -0
- package/skills/app-shell-patterns/references/api/guards/pass.md +129 -0
- package/skills/app-shell-patterns/references/api/guards/redirect-to.md +307 -0
- package/skills/app-shell-patterns/references/api/router/use-location.md +240 -0
- package/skills/app-shell-patterns/references/api/router/use-navigate.md +138 -0
- package/skills/app-shell-patterns/references/api/router/use-params.md +218 -0
- package/skills/app-shell-patterns/references/api/use-ai-chat.md +110 -0
- package/skills/app-shell-patterns/references/api/use-app-shell-config.md +126 -0
- package/skills/app-shell-patterns/references/api/use-app-shell-data.md +146 -0
- package/skills/app-shell-patterns/references/api/use-app-shell.md +143 -0
- package/skills/app-shell-patterns/references/api/use-auth.md +145 -0
- package/skills/app-shell-patterns/references/api/use-override-breadcrumb.md +74 -0
- package/skills/app-shell-patterns/references/api/use-page-meta.md +96 -0
- package/skills/app-shell-patterns/references/api/use-register-command-palette-actions.md +90 -0
- package/skills/app-shell-patterns/references/api/use-resolved-locale.md +67 -0
- package/skills/app-shell-patterns/references/api/use-route-error.md +214 -0
- package/skills/app-shell-patterns/references/api/use-theme.md +108 -0
- package/skills/app-shell-patterns/references/api/use-time-zone.md +80 -0
- package/skills/app-shell-patterns/references/api/use-toast.md +107 -0
- package/skills/app-shell-patterns/references/components/action-panel.md +168 -0
- package/skills/app-shell-patterns/references/components/activity-card.md +142 -0
- package/skills/app-shell-patterns/references/components/ai-chat.md +315 -0
- package/skills/app-shell-patterns/references/components/alert.md +202 -0
- package/skills/app-shell-patterns/references/components/app-shell.md +530 -0
- package/skills/app-shell-patterns/references/components/appearance-switcher.md +87 -0
- package/skills/app-shell-patterns/references/components/attachment.md +138 -0
- package/skills/app-shell-patterns/references/components/autocomplete.md +278 -0
- package/skills/app-shell-patterns/references/components/avatar.md +80 -0
- package/skills/app-shell-patterns/references/components/badge.md +309 -0
- package/skills/app-shell-patterns/references/components/button.md +164 -0
- package/skills/app-shell-patterns/references/components/card.md +109 -0
- package/skills/app-shell-patterns/references/components/checkbox.md +169 -0
- package/skills/app-shell-patterns/references/components/combobox.md +318 -0
- package/skills/app-shell-patterns/references/components/command-palette.md +413 -0
- package/skills/app-shell-patterns/references/components/csv-importer.md +214 -0
- package/skills/app-shell-patterns/references/components/data-table.md +953 -0
- package/skills/app-shell-patterns/references/components/date-picker.md +248 -0
- package/skills/app-shell-patterns/references/components/default-header.md +75 -0
- package/skills/app-shell-patterns/references/components/default-sidebar.md +136 -0
- package/skills/app-shell-patterns/references/components/description-card.md +569 -0
- package/skills/app-shell-patterns/references/components/dialog.md +164 -0
- package/skills/app-shell-patterns/references/components/document-progress-card.md +154 -0
- package/skills/app-shell-patterns/references/components/form.md +378 -0
- package/skills/app-shell-patterns/references/components/global-header-layout.md +132 -0
- package/skills/app-shell-patterns/references/components/grid.md +184 -0
- package/skills/app-shell-patterns/references/components/input.md +83 -0
- package/skills/app-shell-patterns/references/components/layout.md +539 -0
- package/skills/app-shell-patterns/references/components/menu.md +218 -0
- package/skills/app-shell-patterns/references/components/metric-card.md +94 -0
- package/skills/app-shell-patterns/references/components/select.md +283 -0
- package/skills/app-shell-patterns/references/components/sheet.md +237 -0
- package/skills/app-shell-patterns/references/components/sidebar-group.md +466 -0
- package/skills/app-shell-patterns/references/components/sidebar-item.md +458 -0
- package/skills/app-shell-patterns/references/components/sidebar-layout.md +258 -0
- package/skills/app-shell-patterns/references/components/spinner.md +81 -0
- package/skills/app-shell-patterns/references/components/table.md +188 -0
- package/skills/app-shell-patterns/references/components/tabs.md +158 -0
- package/skills/app-shell-patterns/references/components/textarea.md +122 -0
- package/skills/app-shell-patterns/references/components/timeline.md +317 -0
- package/skills/app-shell-patterns/references/components/toolbar.md +138 -0
- package/skills/app-shell-patterns/references/components/tooltip.md +144 -0
- package/skills/app-shell-patterns/references/components/with-guard.md +445 -0
- package/skills/app-shell-patterns/references/concepts/authentication.md +301 -0
- package/skills/app-shell-patterns/references/concepts/custom-components.md +59 -0
- package/skills/app-shell-patterns/references/concepts/file-based-routing.md +350 -0
- package/skills/app-shell-patterns/references/{fundamental → concepts}/graphql.md +11 -4
- package/skills/app-shell-patterns/references/concepts/modules-and-resources.md +229 -0
- package/skills/app-shell-patterns/references/concepts/routing-navigation.md +387 -0
- package/skills/app-shell-patterns/references/concepts/sidebar-navigation.md +388 -0
- package/skills/app-shell-patterns/references/{fundamental/design-system.md → concepts/styling-theming.md} +258 -110
- package/skills/app-shell-patterns/references/migrations.md +34 -0
- package/skills/app-shell-patterns/references/pages/{detail.md → document-detail.md} +4 -27
- package/skills/app-shell-patterns/references/patterns/form-composer.md +43 -45
- package/skills/app-shell-patterns/references/patterns/form-modal.md +39 -100
- package/skills/app-shell-patterns/references/patterns/form-sectioned.md +12 -38
- package/skills/app-shell-patterns/references/patterns/form-single-page.md +11 -32
- package/skills/app-shell-patterns/references/patterns/form-wizard.md +12 -53
- package/skills/app-shell-patterns/references/patterns/interaction-confirm.md +11 -27
- package/skills/app-shell-patterns/references/patterns/interaction-multi-select.md +13 -27
- package/skills/app-shell-patterns/references/patterns/interaction-toast.md +10 -34
- package/skills/app-shell-patterns/references/patterns/list-dense-scan.md +11 -86
- package/skills/app-shell-patterns/references/fundamental/components.md +0 -1009
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Form, Field, and Fieldset
|
|
3
|
+
description: Components for building validated forms with automatic accessibility wiring, validation state management, and optional React Hook Form integration
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- GENERATED by docs-kit from docs-src/components/form.docs.outline.md — do not edit by hand. -->
|
|
7
|
+
|
|
8
|
+
# Form, Field, and Fieldset
|
|
9
|
+
|
|
10
|
+
`Form`, `Field`, and `Fieldset` are compound components for building accessible, validated forms. They handle label association, `aria-describedby`, validation state, and external error routing automatically.
|
|
11
|
+
|
|
12
|
+
## Import
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { Form, Field, Fieldset } from "@tailor-platform/app-shell";
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Basic Usage
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
<Form onFormSubmit={(values) => save(values)}>
|
|
22
|
+
<Field.Root name="email">
|
|
23
|
+
<Field.Label>Email</Field.Label>
|
|
24
|
+
<Field.Control type="email" required />
|
|
25
|
+
<Field.Description>We'll never share your email.</Field.Description>
|
|
26
|
+
<Field.Error match="valueMissing">Email is required.</Field.Error>
|
|
27
|
+
<Field.Error match="typeMismatch">Enter a valid email address.</Field.Error>
|
|
28
|
+
</Field.Root>
|
|
29
|
+
<button type="submit">Save</button>
|
|
30
|
+
</Form>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Form
|
|
36
|
+
|
|
37
|
+
A form element with consolidated error handling and validation. Wraps every child `Field.Root` in a shared validation context.
|
|
38
|
+
|
|
39
|
+
### Form Props
|
|
40
|
+
|
|
41
|
+
| Prop | Type | Default | Description |
|
|
42
|
+
| ---------------- | --------------------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
43
|
+
| `children` | `React.ReactNode` | **Required** | Form contents |
|
|
44
|
+
| `onFormSubmit` | `(values: FormValues, details: FormSubmitEventDetails) => void` | - | Called after successful validation with parsed form values. Prefer this for simple forms. Use a type argument for type-safe values. |
|
|
45
|
+
| `onSubmit` | `React.FormEventHandler` | - | Low-level native submit handler. Use when integrating with React Hook Form's `handleSubmit`. |
|
|
46
|
+
| `errors` | `Record<string, string>` | - | External errors keyed by field `name` (e.g. from an API response). Automatically routed to matching `Field.Error` components. |
|
|
47
|
+
| `validationMode` | `"onSubmit" \| "onBlur" \| "onChange"` | `"onSubmit"` | Controls when field validation fires. |
|
|
48
|
+
| `noValidate` | `boolean` | - | Disables native browser validation UI (recommended — AppShell renders its own). |
|
|
49
|
+
| `actionsRef` | `React.Ref<{ validate: () => void }>` | - | Ref to imperatively trigger validation from outside the submit flow. |
|
|
50
|
+
| `id` | `string` | - | Applied to the `<form>` element. Lets a submit button outside the form target it via the native `form` attribute. |
|
|
51
|
+
| `className` | `string` | - | Additional CSS classes for the `<form>` element. |
|
|
52
|
+
|
|
53
|
+
### External Errors
|
|
54
|
+
|
|
55
|
+
Feed API validation errors back into the form via the `errors` prop. Errors are keyed by field `name` and cleared automatically when the user edits the corresponding field.
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
const [errors, setErrors] = React.useState({});
|
|
59
|
+
|
|
60
|
+
async function handleSubmit(values) {
|
|
61
|
+
const res = await api.save(values);
|
|
62
|
+
if (res.errors) setErrors(res.errors);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
<Form errors={errors} onFormSubmit={handleSubmit}>
|
|
66
|
+
<Field.Root name="url">
|
|
67
|
+
<Field.Label>Homepage</Field.Label>
|
|
68
|
+
<Field.Control type="url" required />
|
|
69
|
+
<Field.Error />
|
|
70
|
+
</Field.Root>
|
|
71
|
+
<button type="submit">Submit</button>
|
|
72
|
+
</Form>;
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Programmatic Validation
|
|
76
|
+
|
|
77
|
+
Use `actionsRef` to imperatively trigger validation (e.g. in a multi-step wizard).
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
const actions = React.useRef(null);
|
|
81
|
+
|
|
82
|
+
<Form actionsRef={actions}>
|
|
83
|
+
<Field.Root name="name">
|
|
84
|
+
<Field.Label>Name</Field.Label>
|
|
85
|
+
<Field.Control required />
|
|
86
|
+
<Field.Error>Name is required.</Field.Error>
|
|
87
|
+
</Field.Root>
|
|
88
|
+
</Form>;
|
|
89
|
+
|
|
90
|
+
<button onClick={() => actions.current?.validate()}>Check</button>;
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Field
|
|
96
|
+
|
|
97
|
+
A compound component that groups all parts of a form field and manages its validation state.
|
|
98
|
+
|
|
99
|
+
`Field.Root` creates a context boundary. All child sub-components and any Base UI-backed AppShell component (e.g. `Select`, `Combobox`, `Autocomplete`) placed inside `Field.Root` automatically connect to this context — inheriting label association (`htmlFor`), `aria-describedby`, `disabled` state, and validation state.
|
|
100
|
+
|
|
101
|
+
### Sub-components
|
|
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>`. Can be omitted when using a Base UI-backed AppShell component as the 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. |
|
|
111
|
+
|
|
112
|
+
### Field.Root Props
|
|
113
|
+
|
|
114
|
+
| Prop | Type | Default | Description |
|
|
115
|
+
| ------------------------ | ---------------------------------------- | ------- | --------------------------------------------------------------------------------------------------- |
|
|
116
|
+
| `name` | `string` | - | Field name; used for form value extraction and error routing. |
|
|
117
|
+
| `children` | `React.ReactNode` | - | Field sub-components and input controls. |
|
|
118
|
+
| `disabled` | `boolean` | - | Disables all controls within the field. |
|
|
119
|
+
| `isTouched` | `boolean` | - | Whether the field has been blurred. Maps to React Hook Form's `fieldState.isTouched`. |
|
|
120
|
+
| `isDirty` | `boolean` | - | Whether the field value differs from its default. Maps to React Hook Form's `fieldState.isDirty`. |
|
|
121
|
+
| `invalid` | `boolean` | - | Marks the field as invalid (shows error styling). |
|
|
122
|
+
| `error` | `{ message?: string }` | - | Error object from React Hook Form's `fieldState.error`. Sets `invalid` automatically when provided. |
|
|
123
|
+
| `validate` | `(value: string) => string \| undefined` | - | Custom validation function; return an error message string to mark the field invalid. |
|
|
124
|
+
| `validationMode` | `"onSubmit" \| "onBlur" \| "onChange"` | - | Overrides the parent Form's `validationMode` for this field. |
|
|
125
|
+
| `validationDebounceTime` | `number` | - | Debounce delay (ms) for `"onChange"` validation mode. |
|
|
126
|
+
| `className` | `string` | - | Additional CSS classes for the field wrapper. |
|
|
127
|
+
|
|
128
|
+
### Field.Error Props
|
|
129
|
+
|
|
130
|
+
| Prop | Type | Default | Description |
|
|
131
|
+
| ---------- | ---------------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------- |
|
|
132
|
+
| `match` | `keyof ValidityState \| boolean \| ((value: string) => boolean)` | - | Validity state key to match (e.g. `"valueMissing"`, `"typeMismatch"`). Omit for a catch-all error message. |
|
|
133
|
+
| `children` | `React.ReactNode` | - | Error message content. If omitted, the browser's native validation message is shown. |
|
|
134
|
+
|
|
135
|
+
### Using Another AppShell Component as the Control
|
|
136
|
+
|
|
137
|
+
`Field.Control` can be omitted when using a Base UI-backed AppShell component (e.g. `Select`, `Combobox`). The component registers itself with the `Field` context automatically, inheriting label association and validation state.
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
const [country, setCountry] = React.useState<string | null>(null);
|
|
141
|
+
|
|
142
|
+
<Field.Root name="country">
|
|
143
|
+
<Field.Label>Country</Field.Label>
|
|
144
|
+
<Select
|
|
145
|
+
items={["Japan", "United States"]}
|
|
146
|
+
value={country}
|
|
147
|
+
onValueChange={setCountry}
|
|
148
|
+
placeholder="Select a country"
|
|
149
|
+
/>
|
|
150
|
+
<Field.Error>Please select a country.</Field.Error>
|
|
151
|
+
</Field.Root>;
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### How values reach `onFormSubmit`
|
|
155
|
+
|
|
156
|
+
`onFormSubmit` does **not** read `FormData`. It collects values from the `Field.Root`s registered
|
|
157
|
+
inside the `Form`, keyed by each field's `name`. Every AppShell control works this way once wrapped
|
|
158
|
+
in a `Field.Root` — `Select`, `Combobox`, and `Autocomplete` included. They need no `name` of their
|
|
159
|
+
own and usually no React state. Mirroring a field into `useState` just to submit it is normally an
|
|
160
|
+
anti-pattern; the exception is a value the component must read during render, such as a composer body
|
|
161
|
+
that gates the submit button or swaps its placeholder.
|
|
162
|
+
|
|
163
|
+
Non-string items are serialised into the submitted value:
|
|
164
|
+
|
|
165
|
+
| Item shape | Value in `onFormSubmit` |
|
|
166
|
+
| -------------------------------------- | ------------------------------------ |
|
|
167
|
+
| `string` | the string |
|
|
168
|
+
| `{ value, label }` | `value`, automatically |
|
|
169
|
+
| any other object | **JSON string** — usually not wanted |
|
|
170
|
+
| any other object + `itemToStringValue` | whatever that function returns |
|
|
171
|
+
|
|
172
|
+
Multi-select submits an array. For arbitrary objects, pass `itemToStringValue` to choose what gets
|
|
173
|
+
submitted — it is distinct from `mapItem`, which controls what the user sees:
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
<Field.Root name="warehouse">
|
|
177
|
+
<Field.Label>Warehouse</Field.Label>
|
|
178
|
+
<Combobox
|
|
179
|
+
items={warehouses}
|
|
180
|
+
mapItem={(w) => ({ label: w.name, key: String(w.id) })}
|
|
181
|
+
itemToStringValue={(w) => String(w.id)}
|
|
182
|
+
/>
|
|
183
|
+
</Field.Root>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Composer-style textareas
|
|
187
|
+
|
|
188
|
+
Most forms should let `Field.Root` own the submitted value. A composer is the notable exception:
|
|
189
|
+
the current draft is often read during render to disable Send on whitespace-only input, swap the
|
|
190
|
+
placeholder, or show live UI around the text. In that case, keep the `Textarea` controlled, but
|
|
191
|
+
still make it a real field inside `Form` so validation and server errors land beside the draft.
|
|
192
|
+
|
|
193
|
+
Use a visually hidden `Field.Label` rather than only `aria-label` so the field keeps the same
|
|
194
|
+
label, description, and error wiring as any other form control. If the submit can fail, route the
|
|
195
|
+
server rejection through `Form`'s `errors` prop and clear the draft only after success.
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
function ReplyComposer() {
|
|
199
|
+
const [body, setBody] = React.useState("");
|
|
200
|
+
const [internal, setInternal] = React.useState(false);
|
|
201
|
+
const [errors, setErrors] = React.useState<Record<string, string>>({});
|
|
202
|
+
|
|
203
|
+
async function handleSubmit() {
|
|
204
|
+
const result = await saveReply({ body, internal });
|
|
205
|
+
if (result?.error) {
|
|
206
|
+
setErrors({ body: result.error });
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
setErrors({});
|
|
211
|
+
setBody("");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<Form noValidate errors={errors} onFormSubmit={handleSubmit}>
|
|
216
|
+
<Field.Root name="body">
|
|
217
|
+
<Field.Label className="sr-only">Reply</Field.Label>
|
|
218
|
+
<Textarea
|
|
219
|
+
required
|
|
220
|
+
rows={4}
|
|
221
|
+
value={body}
|
|
222
|
+
onChange={(event) => setBody(event.target.value)}
|
|
223
|
+
placeholder={internal ? "Internal context for teammates..." : "Reply to customer..."}
|
|
224
|
+
/>
|
|
225
|
+
<Field.Error />
|
|
226
|
+
</Field.Root>
|
|
227
|
+
|
|
228
|
+
<Checkbox
|
|
229
|
+
label="Internal note (not sent to customer)"
|
|
230
|
+
checked={internal}
|
|
231
|
+
onCheckedChange={setInternal}
|
|
232
|
+
/>
|
|
233
|
+
|
|
234
|
+
<Button type="submit" disabled={body.trim().length === 0}>
|
|
235
|
+
Send
|
|
236
|
+
</Button>
|
|
237
|
+
</Form>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Native form submission
|
|
243
|
+
|
|
244
|
+
Separately from `onFormSubmit`, controls can participate in **native** submission — a plain
|
|
245
|
+
`<form>`, `new FormData(form)`, or a server action. That path reads the DOM, so each control needs
|
|
246
|
+
its own `name`. `Select`, `Combobox`, and `Autocomplete` accept `name` (plus `form`, `required`,
|
|
247
|
+
`inputRef`) from 1.13.0; before that they contributed nothing to a native payload.
|
|
248
|
+
|
|
249
|
+
### Submitting from outside the form
|
|
250
|
+
|
|
251
|
+
Give the `Form` an `id` and point a detached submit button at it with the native `form` attribute —
|
|
252
|
+
this is how a page-header Save reaches a form in the page body (1.13.0+).
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
<Layout.Header
|
|
256
|
+
title="Create product"
|
|
257
|
+
actions={[
|
|
258
|
+
<Button key="save" type="submit" form="product-form">
|
|
259
|
+
Save
|
|
260
|
+
</Button>,
|
|
261
|
+
]}
|
|
262
|
+
/>
|
|
263
|
+
<Form id="product-form" onFormSubmit={(values) => save(values)}>
|
|
264
|
+
{/* … */}
|
|
265
|
+
</Form>
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Custom Validation UI with Field.Validity
|
|
269
|
+
|
|
270
|
+
`Field.Validity` exposes the field's `ValidityState` via a render callback, enabling fully custom validation UI.
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
<Field.Root name="password">
|
|
274
|
+
<Field.Label>Password</Field.Label>
|
|
275
|
+
<Field.Control type="password" required minLength={8} />
|
|
276
|
+
<Field.Validity>
|
|
277
|
+
{(state) => (
|
|
278
|
+
<ul>
|
|
279
|
+
<li>{state.validity.valueMissing ? "❌" : "✅"} Required</li>
|
|
280
|
+
<li>{state.validity.tooShort ? "❌" : "✅"} At least 8 characters</li>
|
|
281
|
+
</ul>
|
|
282
|
+
)}
|
|
283
|
+
</Field.Validity>
|
|
284
|
+
</Field.Root>
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Fieldset
|
|
290
|
+
|
|
291
|
+
A compound component (`Fieldset.Root`, `Fieldset.Legend`) for grouping related fields with a shared legend for accessible form sectioning.
|
|
292
|
+
|
|
293
|
+
### Sub-components
|
|
294
|
+
|
|
295
|
+
| Sub-component | Description |
|
|
296
|
+
| ----------------- | -------------------------------------------------------------- |
|
|
297
|
+
| `Fieldset.Root` | Renders a `<fieldset>`. Propagates `disabled` to all children. |
|
|
298
|
+
| `Fieldset.Legend` | Accessible `<legend>` for the group. |
|
|
299
|
+
|
|
300
|
+
### Fieldset.Root Props
|
|
301
|
+
|
|
302
|
+
| Prop | Type | Default | Description |
|
|
303
|
+
| ----------- | ----------------- | ------- | ------------------------------------------ |
|
|
304
|
+
| `children` | `React.ReactNode` | - | Field components to group. |
|
|
305
|
+
| `disabled` | `boolean` | - | Disables all controls within the fieldset. |
|
|
306
|
+
| `className` | `string` | - | Additional CSS classes. |
|
|
307
|
+
|
|
308
|
+
### Example
|
|
309
|
+
|
|
310
|
+
```tsx
|
|
311
|
+
<Fieldset.Root>
|
|
312
|
+
<Fieldset.Legend>Billing details</Fieldset.Legend>
|
|
313
|
+
<Field.Root name="company">
|
|
314
|
+
<Field.Label>Company</Field.Label>
|
|
315
|
+
<Field.Control />
|
|
316
|
+
</Field.Root>
|
|
317
|
+
<Field.Root name="taxId">
|
|
318
|
+
<Field.Label>Tax ID</Field.Label>
|
|
319
|
+
<Field.Control />
|
|
320
|
+
</Field.Root>
|
|
321
|
+
</Fieldset.Root>
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## React Hook Form Integration
|
|
327
|
+
|
|
328
|
+
React Hook Form is **optional and not an AppShell dependency** — install it in your own app. `Form` +
|
|
329
|
+
`Field` is the default stack and covers most forms via `onFormSubmit`. Reach for RHF only when a form
|
|
330
|
+
genuinely needs cross-field validation, field arrays, or a schema resolver (Zod). The two compose
|
|
331
|
+
rather than compete: `Field` handles accessibility wiring and visual state, RHF handles values and
|
|
332
|
+
the validation lifecycle.
|
|
333
|
+
|
|
334
|
+
`Field.Root` accepts `isTouched`, `isDirty`, `invalid`, and `error` props that align with React Hook Form's `fieldState` shape, so you can spread `fieldState` directly. Use `Form`'s `onSubmit` prop to connect RHF's `handleSubmit`.
|
|
335
|
+
|
|
336
|
+
```tsx
|
|
337
|
+
import { useForm, Controller } from "react-hook-form";
|
|
338
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
339
|
+
import { z } from "zod";
|
|
340
|
+
|
|
341
|
+
const schema = z.object({ email: z.string().email() });
|
|
342
|
+
|
|
343
|
+
function MyForm() {
|
|
344
|
+
const { control, handleSubmit } = useForm({
|
|
345
|
+
resolver: zodResolver(schema),
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
return (
|
|
349
|
+
<Form onSubmit={handleSubmit((data) => save(data))}>
|
|
350
|
+
<Controller
|
|
351
|
+
name="email"
|
|
352
|
+
control={control}
|
|
353
|
+
render={({ field, fieldState }) => (
|
|
354
|
+
<Field.Root {...fieldState}>
|
|
355
|
+
<Field.Label>Email</Field.Label>
|
|
356
|
+
<Field.Control {...field} type="email" />
|
|
357
|
+
<Field.Error>{fieldState.error?.message}</Field.Error>
|
|
358
|
+
</Field.Root>
|
|
359
|
+
)}
|
|
360
|
+
/>
|
|
361
|
+
<button type="submit">Save</button>
|
|
362
|
+
</Form>
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
The `Field` context handles only accessibility wiring (`htmlFor`, `aria-describedby`) and visual state (`data-invalid`, `data-dirty`, `data-touched`) — it does not interfere with RHF's value management or validation lifecycle.
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Related
|
|
372
|
+
|
|
373
|
+
- [Input](./input.md) — Standalone styled input, useful outside of forms.
|
|
374
|
+
- [Textarea](./textarea.md) — Multi-line text control that integrates automatically with `Field`.
|
|
375
|
+
- [Checkbox](./checkbox.md) — Boolean control that integrates automatically with `Field`.
|
|
376
|
+
- [Select](./select.md) — Dropdown that integrates automatically with `Field`.
|
|
377
|
+
- [Combobox](./combobox.md) — Searchable combobox that integrates automatically with `Field`.
|
|
378
|
+
- [Autocomplete](./autocomplete.md) — Free-text input with suggestions that integrates automatically with `Field`.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: GlobalHeaderLayout
|
|
3
|
+
description: Opinionated layout with an app-wide header above the whole shell and a sidebar that collapses to a persistent icon rail
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- GENERATED by docs-kit from docs-src/components/global-header-layout.docs.outline.md — do not edit by hand. -->
|
|
7
|
+
|
|
8
|
+
# GlobalHeaderLayout
|
|
9
|
+
|
|
10
|
+
`GlobalHeaderLayout` is AppShell's opinionated "global top bar" layout. It is a thin wrapper over [`SidebarLayout`](./sidebar-layout.md) that wires together:
|
|
11
|
+
|
|
12
|
+
- an app-wide header above both the sidebar and the content area
|
|
13
|
+
- a default sidebar with its own header removed
|
|
14
|
+
- icon-rail collapse behavior
|
|
15
|
+
- the stock content container and outlet
|
|
16
|
+
|
|
17
|
+
Use it when you want the whole app to share one top bar. Reach for `SidebarLayout` directly when you need the more flexible primitive.
|
|
18
|
+
|
|
19
|
+
## Import
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { GlobalHeaderLayout } from "@tailor-platform/app-shell";
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Basic usage
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import {
|
|
29
|
+
AppShell,
|
|
30
|
+
AppearanceSwitcher,
|
|
31
|
+
GlobalHeaderLayout,
|
|
32
|
+
SidebarGroup,
|
|
33
|
+
SidebarItem,
|
|
34
|
+
} from "@tailor-platform/app-shell";
|
|
35
|
+
import { LayersIcon } from "lucide-react";
|
|
36
|
+
|
|
37
|
+
function App() {
|
|
38
|
+
return (
|
|
39
|
+
<AppShell title="Operations" modules={modules}>
|
|
40
|
+
<GlobalHeaderLayout
|
|
41
|
+
header={
|
|
42
|
+
<GlobalHeaderLayout.DefaultHeader actions={[<AppearanceSwitcher key="appearance" />]} />
|
|
43
|
+
}
|
|
44
|
+
sidebar={
|
|
45
|
+
<GlobalHeaderLayout.DefaultSidebar>
|
|
46
|
+
<SidebarItem to="/" />
|
|
47
|
+
<SidebarGroup title="Main" icon={<LayersIcon className="size-4" />}>
|
|
48
|
+
<SidebarItem to="/dashboard" />
|
|
49
|
+
<SidebarItem to="/orders" />
|
|
50
|
+
</SidebarGroup>
|
|
51
|
+
</GlobalHeaderLayout.DefaultSidebar>
|
|
52
|
+
}
|
|
53
|
+
/>
|
|
54
|
+
</AppShell>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Props
|
|
60
|
+
|
|
61
|
+
| Prop | Type | Default | Description |
|
|
62
|
+
| ------------- | --------------------------------------------------------------- | --------------------------------------- | ---------------------------------------------------------------- |
|
|
63
|
+
| `header` | `React.ReactNode` | `<GlobalHeaderLayout.DefaultHeader />` | Replaces the full-width app header. |
|
|
64
|
+
| `sidebar` | `React.ReactNode` | `<GlobalHeaderLayout.DefaultSidebar />` | Replaces the primary sidebar. |
|
|
65
|
+
| `defaultOpen` | `boolean` | `true` | Whether the sidebar starts expanded on desktop. |
|
|
66
|
+
| `collapsible` | `boolean` | `true` | Whether the sidebar can be collapsed. |
|
|
67
|
+
| `children` | `(props: { Outlet: () => React.ReactNode }) => React.ReactNode` | current route outlet | Custom content renderer for the stock content container. |
|
|
68
|
+
| `body` | `React.ReactNode` | - | Escape hatch that replaces the entire region beside the sidebar. |
|
|
69
|
+
|
|
70
|
+
`children` and `body` are mutually exclusive. Use `body` when you need to add your own side columns around the main content.
|
|
71
|
+
|
|
72
|
+
## Built-in parts
|
|
73
|
+
|
|
74
|
+
`GlobalHeaderLayout` exposes the same composition pieces as `SidebarLayout`, plus the opinionated header and sidebar defaults:
|
|
75
|
+
|
|
76
|
+
| Export | Description |
|
|
77
|
+
| ------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
78
|
+
| `GlobalHeaderLayout.DefaultHeader` | The app-wide header (`GlobalHeader`) with app title + breadcrumb on the left and `actions` on the right. |
|
|
79
|
+
| `GlobalHeaderLayout.DefaultSidebar` | `DefaultSidebar` with `hideHeader` and `iconRail` forced on. |
|
|
80
|
+
| `GlobalHeaderLayout.ContentContainer` | The stock main content column. |
|
|
81
|
+
| `GlobalHeaderLayout.Outlet` | The current route outlet. |
|
|
82
|
+
| `GlobalHeaderLayout.Trigger` | The built-in sidebar toggle button. |
|
|
83
|
+
| `GlobalHeaderLayout.Breadcrumb` | The route-driven breadcrumb component (`DynamicBreadcrumb`). |
|
|
84
|
+
|
|
85
|
+
## `GlobalHeader`
|
|
86
|
+
|
|
87
|
+
The layout's default header is also exported as a top-level `GlobalHeader` component. It renders:
|
|
88
|
+
|
|
89
|
+
- the AppShell `title` and optional `icon`
|
|
90
|
+
- the route-driven breadcrumb
|
|
91
|
+
- a right-aligned `actions` cluster
|
|
92
|
+
|
|
93
|
+
`actions` behaves the same way as on `SidebarLayout.DefaultHeader`: omitting it renders the built-in [`AppearanceSwitcher`](./appearance-switcher.md), and passing it replaces the entire right-hand cluster.
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import { AppearanceSwitcher, GlobalHeader } from "@tailor-platform/app-shell";
|
|
97
|
+
|
|
98
|
+
<GlobalHeader actions={[<AppearanceSwitcher key="appearance" />]} />;
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Owning the body region
|
|
102
|
+
|
|
103
|
+
Use `body` when you want custom columns beside the content while still keeping the global header and icon-rail sidebar.
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
<GlobalHeaderLayout
|
|
107
|
+
body={
|
|
108
|
+
<>
|
|
109
|
+
<GlobalHeaderLayout.ContentContainer>
|
|
110
|
+
<GlobalHeaderLayout.Outlet />
|
|
111
|
+
</GlobalHeaderLayout.ContentContainer>
|
|
112
|
+
<aside className="w-96 shrink-0 overflow-y-auto border-l">
|
|
113
|
+
<AssistantPanel />
|
|
114
|
+
</aside>
|
|
115
|
+
</>
|
|
116
|
+
}
|
|
117
|
+
/>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## When to use which layout
|
|
121
|
+
|
|
122
|
+
| Goal | Use |
|
|
123
|
+
| ------------------------------------------------------ | ------------------------------------------------------------- |
|
|
124
|
+
| Built-in sidebar + content header | [`SidebarLayout`](./sidebar-layout.md) |
|
|
125
|
+
| Full-width app header above the whole shell | `GlobalHeaderLayout` |
|
|
126
|
+
| Completely custom page structure with AppShell routing | `SidebarLayout body={...}` or `GlobalHeaderLayout body={...}` |
|
|
127
|
+
|
|
128
|
+
## Related
|
|
129
|
+
|
|
130
|
+
- [SidebarLayout](./sidebar-layout.md) - lower-level primitive that GlobalHeaderLayout wraps
|
|
131
|
+
- [DefaultSidebar](./default-sidebar.md) - underlying sidebar component used by `GlobalHeaderLayout.DefaultSidebar`
|
|
132
|
+
- [DefaultHeader](./default-header.md) - content-column header for `SidebarLayout`
|