@you-agent-factory/components 0.0.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/README.md +223 -0
- package/docs/README.md +83 -0
- package/docs/button.md +119 -0
- package/docs/charts.md +325 -0
- package/docs/data-display-code-panel.md +120 -0
- package/docs/feedback-alert-panel.md +117 -0
- package/docs/feedback-skeleton.md +54 -0
- package/docs/forms-form-field.md +490 -0
- package/docs/forms-input-primitives.md +311 -0
- package/docs/forms-select-primitives.md +362 -0
- package/docs/graphs.md +250 -0
- package/docs/layout-display-primitives.md +211 -0
- package/docs/overlays.md +414 -0
- package/docs/table-data-table.md +331 -0
- package/docs/typography-roles.md +182 -0
- package/docs/widget-frame-recipes.md +273 -0
- package/package.json +161 -0
- package/src/category-paths.ts +20 -0
- package/src/charts/chart-state-panel.tsx +103 -0
- package/src/charts/chart.tsx +287 -0
- package/src/charts/index.ts +21 -0
- package/src/data-display/code-panel.tsx +67 -0
- package/src/data-display/data-table.tsx +180 -0
- package/src/data-display/description-list.tsx +24 -0
- package/src/data-display/index.ts +34 -0
- package/src/data-display/table-layout.ts +12 -0
- package/src/data-display/table.tsx +131 -0
- package/src/feedback/alert-panel-semantics.ts +75 -0
- package/src/feedback/alert-panel.tsx +187 -0
- package/src/feedback/index.ts +21 -0
- package/src/feedback/skeleton.tsx +16 -0
- package/src/forms/index.ts +73 -0
- package/src/forms/package-checkbox.tsx +50 -0
- package/src/forms/package-enum-select.tsx +185 -0
- package/src/forms/package-file-input.tsx +25 -0
- package/src/forms/package-form-field.tsx +202 -0
- package/src/forms/package-input.tsx +25 -0
- package/src/forms/package-native-select.tsx +25 -0
- package/src/forms/package-select.tsx +209 -0
- package/src/forms/package-textarea.tsx +35 -0
- package/src/forms/select-icons.tsx +35 -0
- package/src/graphs/graph-edge-path.ts +241 -0
- package/src/graphs/graph-edge.tsx +130 -0
- package/src/graphs/graph-interactive-example.tsx +187 -0
- package/src/graphs/graph-node-button.tsx +51 -0
- package/src/graphs/graph-node-handle-badge.tsx +108 -0
- package/src/graphs/graph-node-handle.ts +28 -0
- package/src/graphs/graph-node-shell.tsx +111 -0
- package/src/graphs/graph-node-state-indicator.tsx +47 -0
- package/src/graphs/graph-node-state.ts +111 -0
- package/src/graphs/graph-viewport-surface.tsx +27 -0
- package/src/graphs/index.ts +41 -0
- package/src/icons/index.ts +4 -0
- package/src/index.ts +209 -0
- package/src/layout/action-row.tsx +53 -0
- package/src/layout/index.ts +9 -0
- package/src/layout/surface-panel.tsx +91 -0
- package/src/navigation/index.ts +4 -0
- package/src/overlays/collapsible.tsx +22 -0
- package/src/overlays/dialog.tsx +148 -0
- package/src/overlays/index.ts +31 -0
- package/src/overlays/overlay-layout.ts +9 -0
- package/src/overlays/popover.tsx +29 -0
- package/src/overlays/scroll-area.tsx +78 -0
- package/src/primitives/button-link.tsx +22 -0
- package/src/primitives/button.tsx +258 -0
- package/src/primitives/icon-button-shell.tsx +49 -0
- package/src/primitives/index.ts +31 -0
- package/src/primitives/package-text.tsx +29 -0
- package/src/primitives/typography-roles.ts +12 -0
- package/src/primitives/typography.tsx +165 -0
- package/src/recipes/index.ts +61 -0
- package/src/recipes/widget-frame-content.tsx +114 -0
- package/src/recipes/widget-frame-disclosure.tsx +161 -0
- package/src/recipes/widget-frame-layout.ts +40 -0
- package/src/recipes/widget-frame-skeleton.tsx +16 -0
- package/src/recipes/widget-frame-states.tsx +99 -0
- package/src/recipes/widget-frame-typography.ts +19 -0
- package/src/recipes/widget-frame.tsx +84 -0
- package/src/styles/color-palette-presets.css +167 -0
- package/src/styles/color-role-tokens.css +163 -0
- package/src/styles/layout-role-tokens.css +27 -0
- package/src/styles/text-color-role-tokens.css +29 -0
- package/src/styles/typography-role-tokens.css +72 -0
- package/src/styles/typography-role-utilities.css +73 -0
- package/src/styles/typography-roles.css +53 -0
- package/src/styles.css +32 -0
- package/src/testing/index.ts +13 -0
- package/src/testing/render.tsx +16 -0
- package/src/tokens/index.ts +4 -0
- package/src/utilities/cn.ts +5 -0
- package/src/utilities/index.ts +6 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
# Form field structure and messaging
|
|
2
|
+
|
|
3
|
+
Domain-free form-field layout and messaging components for reusable forms. Use
|
|
4
|
+
these components when you need consistent label, description, helper text,
|
|
5
|
+
warning, error, and success presentation with package typography — without
|
|
6
|
+
importing dashboard features, API clients, generated OpenAPI types, or app
|
|
7
|
+
state.
|
|
8
|
+
|
|
9
|
+
Form-field components are **presentation-only**. They render styled labels and
|
|
10
|
+
message regions and forward standard React props. They do not fetch data,
|
|
11
|
+
perform validation decisions, submit forms, or own durable application state.
|
|
12
|
+
|
|
13
|
+
## Required setup
|
|
14
|
+
|
|
15
|
+
Import package styles once in your host application before rendering form fields:
|
|
16
|
+
|
|
17
|
+
```css
|
|
18
|
+
@import "@you-agent-factory/components/styles.css";
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Form-field messaging depends on package role tokens and Tailwind utility classes
|
|
22
|
+
compiled from those tokens. It does not require dashboard `styles.css`, dashboard
|
|
23
|
+
providers, generated OpenAPI types, React Query, Zustand, or app localization
|
|
24
|
+
context.
|
|
25
|
+
|
|
26
|
+
## Import paths
|
|
27
|
+
|
|
28
|
+
Prefer the forms category entrypoint for tree-shaking and explicit category
|
|
29
|
+
boundaries:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import {
|
|
33
|
+
buildFormFieldAriaDescribedBy,
|
|
34
|
+
FormDescription,
|
|
35
|
+
FormError,
|
|
36
|
+
FormField,
|
|
37
|
+
FormFieldGroup,
|
|
38
|
+
FormFieldGroupLabel,
|
|
39
|
+
FormHelperText,
|
|
40
|
+
FormLabel,
|
|
41
|
+
FormSuccess,
|
|
42
|
+
FormWarning,
|
|
43
|
+
type FormFieldMessageIds,
|
|
44
|
+
} from "@you-agent-factory/components/forms";
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The same exports are also available from the package root:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import {
|
|
51
|
+
FormField,
|
|
52
|
+
FormLabel,
|
|
53
|
+
buildFormFieldAriaDescribedBy,
|
|
54
|
+
} from "@you-agent-factory/components";
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Pair form-field messaging with package input primitives (`PackageInput`,
|
|
58
|
+
`PackageTextarea`, `PackageCheckbox`, `NativeSelect`, `Select`, and related
|
|
59
|
+
controls). See [Form input primitives](./forms-input-primitives.md) and
|
|
60
|
+
[Form select primitives](./forms-select-primitives.md) for control-specific
|
|
61
|
+
behavior.
|
|
62
|
+
|
|
63
|
+
## Component overview
|
|
64
|
+
|
|
65
|
+
| Component | Role |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| `FormField` | Vertical spacing wrapper for a single control and its messaging |
|
|
68
|
+
| `FormLabel` | Visible label associated with a control via `htmlFor` |
|
|
69
|
+
| `FormDescription` | Supporting copy below the label (body or supporting typography) |
|
|
70
|
+
| `FormHelperText` | Persistent guidance below the control |
|
|
71
|
+
| `FormWarning` | Cautionary message — no default `role`; pass `status` or `alert` when live-region announcement is desired |
|
|
72
|
+
| `FormError` | Validation error message (defaults to `role="alert"`) |
|
|
73
|
+
| `FormSuccess` | Positive confirmation (defaults to `role="status"`) |
|
|
74
|
+
| `FormFieldGroup` | `fieldset` wrapper for grouped controls |
|
|
75
|
+
| `FormFieldGroupLabel` | `legend` for a grouped field |
|
|
76
|
+
| `buildFormFieldAriaDescribedBy` | Joins host-supplied message element ids into one `aria-describedby` value |
|
|
77
|
+
|
|
78
|
+
## Host ownership
|
|
79
|
+
|
|
80
|
+
The package owns presentation markup, typography tokens, and message-region
|
|
81
|
+
semantics. Host applications own everything domain-specific:
|
|
82
|
+
|
|
83
|
+
| Concern | Owner |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| Visible labels, descriptions, and placeholder copy | Host |
|
|
86
|
+
| Helper text, warning text, error text, and success text | Host |
|
|
87
|
+
| `id`, `name`, `required`, `disabled`, and `aria-invalid` on controls | Host |
|
|
88
|
+
| `aria-describedby`, `aria-errormessage`, and `aria-labelledby` wiring | Host |
|
|
89
|
+
| Controlled values, checked state, and change handlers | Host |
|
|
90
|
+
| Validation rules, validation state transitions, and error mapping | Host |
|
|
91
|
+
| Form submission, persistence, and side effects | Host |
|
|
92
|
+
| Required affordances in label copy (for example `*` or “required”) | Host |
|
|
93
|
+
| Data fetching, caching, and durable cross-route state | Host |
|
|
94
|
+
|
|
95
|
+
Pass values, handlers, message copy, and accessibility attributes into
|
|
96
|
+
components as props. Do not expect form-field components to read from your
|
|
97
|
+
routers, stores, or API layers.
|
|
98
|
+
|
|
99
|
+
## Accessibility relationships
|
|
100
|
+
|
|
101
|
+
Form-field components render message regions with stable `id` values that hosts
|
|
102
|
+
wire to controls. The package does **not** automatically set `aria-describedby`
|
|
103
|
+
or `aria-errormessage` on controls — hosts must pass those attributes to input
|
|
104
|
+
primitives.
|
|
105
|
+
|
|
106
|
+
### Label and accessible name
|
|
107
|
+
|
|
108
|
+
Associate a visible label with the control:
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
<FormLabel htmlFor={controlId}>Display name</FormLabel>
|
|
112
|
+
<PackageInput id={controlId} />
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Assistive technology reads the accessible name from the label text. For controls
|
|
116
|
+
without a visible label, supply `aria-label` or `aria-labelledby` on the control
|
|
117
|
+
instead.
|
|
118
|
+
|
|
119
|
+
### Description, helper, warning, and success
|
|
120
|
+
|
|
121
|
+
Include message element ids in `aria-describedby` on the control when those
|
|
122
|
+
messages are present. Use `buildFormFieldAriaDescribedBy` to join ids:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
const ariaDescribedBy = buildFormFieldAriaDescribedBy({
|
|
126
|
+
descriptionId,
|
|
127
|
+
helperId,
|
|
128
|
+
warningId,
|
|
129
|
+
successId,
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Omit ids for messages that are not rendered. When no messages are supplied,
|
|
134
|
+
omit `aria-describedby` entirely.
|
|
135
|
+
|
|
136
|
+
| Message type | Typical `role` | Included in `aria-describedby` |
|
|
137
|
+
| --- | --- | --- |
|
|
138
|
+
| `FormDescription` | (none) | Yes, when rendered |
|
|
139
|
+
| `FormHelperText` | (none) | Yes, when rendered |
|
|
140
|
+
| `FormWarning` | `status` or `alert` (no default — host supplies when needed) | Yes, when rendered |
|
|
141
|
+
| `FormSuccess` | `status` (default) | Yes, when rendered |
|
|
142
|
+
| `FormError` | `alert` (default) | Host choice — see error section |
|
|
143
|
+
|
|
144
|
+
### Error text and invalid state
|
|
145
|
+
|
|
146
|
+
Expose validation failures without relying on color alone:
|
|
147
|
+
|
|
148
|
+
1. Set `aria-invalid={true}` on the control when the field fails validation.
|
|
149
|
+
2. Render error copy in `FormError` with a stable `id`.
|
|
150
|
+
3. Connect the error to the control with **either**:
|
|
151
|
+
- `aria-errormessage={errorId}` (recommended when error text should not
|
|
152
|
+
duplicate the accessible description), or
|
|
153
|
+
- include `errorId` in `aria-describedby` via `buildFormFieldAriaDescribedBy`.
|
|
154
|
+
|
|
155
|
+
When using `aria-errormessage`, omit `errorId` from
|
|
156
|
+
`buildFormFieldAriaDescribedBy` so error copy is not repeated in the accessible
|
|
157
|
+
description.
|
|
158
|
+
|
|
159
|
+
`FormError` defaults to `role="alert"` so assistive technology announces new
|
|
160
|
+
validation failures.
|
|
161
|
+
|
|
162
|
+
### Warning text role
|
|
163
|
+
|
|
164
|
+
`FormWarning` does not set a default `role`. Pass `role="status"` for
|
|
165
|
+
persistent cautionary copy that should be discoverable through
|
|
166
|
+
`aria-describedby`, or `role="alert"` when assistive technology should announce
|
|
167
|
+
new warning text immediately. Omit `role` when the warning is purely visual
|
|
168
|
+
supplement to description or helper text.
|
|
169
|
+
|
|
170
|
+
### Required and disabled state
|
|
171
|
+
|
|
172
|
+
| State | Host responsibility | Package behavior |
|
|
173
|
+
| --- | --- | --- |
|
|
174
|
+
| Required | Set `required` on the native control; add visible required affordance in label copy when your UX requires it | `FormLabel` renders host-supplied label content only |
|
|
175
|
+
| Disabled | Set `disabled` on the native control | Message components remain readable; disabled styling applies on the control |
|
|
176
|
+
| Invalid | Set `aria-invalid={true}` when validation fails | `FormError` uses error-container typography tokens |
|
|
177
|
+
|
|
178
|
+
Required affordances such as asterisks or “(required)” are host-supplied label
|
|
179
|
+
content. Native `required` and `disabled` attributes on controls expose state to
|
|
180
|
+
assistive technology.
|
|
181
|
+
|
|
182
|
+
### Grouped controls
|
|
183
|
+
|
|
184
|
+
Use `FormFieldGroup` and `FormFieldGroupLabel` for related controls that share
|
|
185
|
+
group context:
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
<FormFieldGroup aria-describedby={groupDescriptionId}>
|
|
189
|
+
<FormFieldGroupLabel>Notification preferences</FormFieldGroupLabel>
|
|
190
|
+
<FormDescription id={groupDescriptionId}>
|
|
191
|
+
Choose how you want to receive updates.
|
|
192
|
+
</FormDescription>
|
|
193
|
+
<FormField>
|
|
194
|
+
<FormLabel htmlFor={emailId}>Email</FormLabel>
|
|
195
|
+
<PackageCheckbox id={emailId} />
|
|
196
|
+
</FormField>
|
|
197
|
+
<FormField>
|
|
198
|
+
<FormLabel htmlFor={smsId}>Text message</FormLabel>
|
|
199
|
+
<PackageCheckbox id={smsId} />
|
|
200
|
+
</FormField>
|
|
201
|
+
</FormFieldGroup>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
`FormFieldGroup` renders a borderless `fieldset`. The group label renders as a
|
|
205
|
+
`legend`. Per-control labels still use `FormLabel` with `htmlFor` pointing at
|
|
206
|
+
each control id. Shared group description ids can be referenced from
|
|
207
|
+
`aria-describedby` on the `fieldset`.
|
|
208
|
+
|
|
209
|
+
## Minimal examples
|
|
210
|
+
|
|
211
|
+
### Text field with label and helper text
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
import { useId } from "react";
|
|
215
|
+
import {
|
|
216
|
+
buildFormFieldAriaDescribedBy,
|
|
217
|
+
FormField,
|
|
218
|
+
FormHelperText,
|
|
219
|
+
FormLabel,
|
|
220
|
+
} from "@you-agent-factory/components/forms";
|
|
221
|
+
import { PackageInput } from "@you-agent-factory/components/forms";
|
|
222
|
+
|
|
223
|
+
export function DisplayNameField() {
|
|
224
|
+
const reactId = useId();
|
|
225
|
+
const controlId = `${reactId}-control`;
|
|
226
|
+
const helperId = `${reactId}-helper`;
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<FormField>
|
|
230
|
+
<FormLabel htmlFor={controlId}>Display name</FormLabel>
|
|
231
|
+
<PackageInput
|
|
232
|
+
aria-describedby={buildFormFieldAriaDescribedBy({ helperId })}
|
|
233
|
+
id={controlId}
|
|
234
|
+
/>
|
|
235
|
+
<FormHelperText id={helperId}>Use 3 to 40 characters.</FormHelperText>
|
|
236
|
+
</FormField>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Required disabled field
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
import { useId } from "react";
|
|
245
|
+
import {
|
|
246
|
+
FormField,
|
|
247
|
+
FormLabel,
|
|
248
|
+
} from "@you-agent-factory/components/forms";
|
|
249
|
+
import { PackageInput } from "@you-agent-factory/components/forms";
|
|
250
|
+
|
|
251
|
+
export function LockedDisplayNameField() {
|
|
252
|
+
const reactId = useId();
|
|
253
|
+
const controlId = `${reactId}-control`;
|
|
254
|
+
|
|
255
|
+
return (
|
|
256
|
+
<FormField>
|
|
257
|
+
<FormLabel htmlFor={controlId}>
|
|
258
|
+
Display name
|
|
259
|
+
<span className="text-on-error-container"> *</span>
|
|
260
|
+
</FormLabel>
|
|
261
|
+
<PackageInput disabled id={controlId} required value="Example value" />
|
|
262
|
+
</FormField>
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Error field with `aria-errormessage`
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
import { useId } from "react";
|
|
271
|
+
import {
|
|
272
|
+
buildFormFieldAriaDescribedBy,
|
|
273
|
+
FormError,
|
|
274
|
+
FormField,
|
|
275
|
+
FormLabel,
|
|
276
|
+
} from "@you-agent-factory/components/forms";
|
|
277
|
+
import { PackageInput } from "@you-agent-factory/components/forms";
|
|
278
|
+
|
|
279
|
+
export function InvalidDisplayNameField({ errorText }: { errorText: string }) {
|
|
280
|
+
const reactId = useId();
|
|
281
|
+
const controlId = `${reactId}-control`;
|
|
282
|
+
const errorId = `${reactId}-error`;
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<FormField>
|
|
286
|
+
<FormLabel htmlFor={controlId}>Display name</FormLabel>
|
|
287
|
+
<PackageInput
|
|
288
|
+
aria-describedby={buildFormFieldAriaDescribedBy({})}
|
|
289
|
+
aria-errormessage={errorId}
|
|
290
|
+
aria-invalid
|
|
291
|
+
id={controlId}
|
|
292
|
+
/>
|
|
293
|
+
<FormError id={errorId}>{errorText}</FormError>
|
|
294
|
+
</FormField>
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Warning or success field
|
|
300
|
+
|
|
301
|
+
```tsx
|
|
302
|
+
import { useId } from "react";
|
|
303
|
+
import {
|
|
304
|
+
buildFormFieldAriaDescribedBy,
|
|
305
|
+
FormField,
|
|
306
|
+
FormLabel,
|
|
307
|
+
FormSuccess,
|
|
308
|
+
FormWarning,
|
|
309
|
+
} from "@you-agent-factory/components/forms";
|
|
310
|
+
import { PackageInput } from "@you-agent-factory/components/forms";
|
|
311
|
+
|
|
312
|
+
export function StatusDisplayNameField({
|
|
313
|
+
successText,
|
|
314
|
+
warningText,
|
|
315
|
+
}: {
|
|
316
|
+
successText?: string;
|
|
317
|
+
warningText?: string;
|
|
318
|
+
}) {
|
|
319
|
+
const reactId = useId();
|
|
320
|
+
const controlId = `${reactId}-control`;
|
|
321
|
+
const warningId = warningText ? `${reactId}-warning` : undefined;
|
|
322
|
+
const successId = successText ? `${reactId}-success` : undefined;
|
|
323
|
+
|
|
324
|
+
return (
|
|
325
|
+
<FormField>
|
|
326
|
+
<FormLabel htmlFor={controlId}>Display name</FormLabel>
|
|
327
|
+
<PackageInput
|
|
328
|
+
aria-describedby={buildFormFieldAriaDescribedBy({
|
|
329
|
+
warningId,
|
|
330
|
+
successId,
|
|
331
|
+
})}
|
|
332
|
+
id={controlId}
|
|
333
|
+
/>
|
|
334
|
+
{warningText ? (
|
|
335
|
+
<FormWarning id={warningId} role="status">
|
|
336
|
+
{warningText}
|
|
337
|
+
</FormWarning>
|
|
338
|
+
) : null}
|
|
339
|
+
{successText ? (
|
|
340
|
+
<FormSuccess id={successId} role="status">
|
|
341
|
+
{successText}
|
|
342
|
+
</FormSuccess>
|
|
343
|
+
) : null}
|
|
344
|
+
</FormField>
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Grouped-control field
|
|
350
|
+
|
|
351
|
+
```tsx
|
|
352
|
+
import { useId } from "react";
|
|
353
|
+
import {
|
|
354
|
+
FormDescription,
|
|
355
|
+
FormField,
|
|
356
|
+
FormFieldGroup,
|
|
357
|
+
FormFieldGroupLabel,
|
|
358
|
+
FormLabel,
|
|
359
|
+
} from "@you-agent-factory/components/forms";
|
|
360
|
+
import { PackageCheckbox } from "@you-agent-factory/components/forms";
|
|
361
|
+
|
|
362
|
+
export function NotificationPreferencesField() {
|
|
363
|
+
const reactId = useId();
|
|
364
|
+
const descriptionId = `${reactId}-description`;
|
|
365
|
+
const emailId = `${reactId}-email`;
|
|
366
|
+
const smsId = `${reactId}-sms`;
|
|
367
|
+
|
|
368
|
+
return (
|
|
369
|
+
<FormFieldGroup aria-describedby={descriptionId}>
|
|
370
|
+
<FormFieldGroupLabel>Notification preferences</FormFieldGroupLabel>
|
|
371
|
+
<FormDescription id={descriptionId}>
|
|
372
|
+
Choose how you want to receive updates.
|
|
373
|
+
</FormDescription>
|
|
374
|
+
<FormField>
|
|
375
|
+
<FormLabel htmlFor={emailId}>Email</FormLabel>
|
|
376
|
+
<PackageCheckbox defaultChecked id={emailId} />
|
|
377
|
+
</FormField>
|
|
378
|
+
<FormField>
|
|
379
|
+
<FormLabel htmlFor={smsId}>Text message</FormLabel>
|
|
380
|
+
<PackageCheckbox id={smsId} />
|
|
381
|
+
</FormField>
|
|
382
|
+
</FormFieldGroup>
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Host-owned validation updates
|
|
388
|
+
|
|
389
|
+
Hosts update validation state and message props after user interaction. The
|
|
390
|
+
rendered accessible relationships must follow the new state:
|
|
391
|
+
|
|
392
|
+
```tsx
|
|
393
|
+
import { useId, useState } from "react";
|
|
394
|
+
import {
|
|
395
|
+
buildFormFieldAriaDescribedBy,
|
|
396
|
+
FormError,
|
|
397
|
+
FormField,
|
|
398
|
+
FormLabel,
|
|
399
|
+
} from "@you-agent-factory/components/forms";
|
|
400
|
+
import { PackageInput } from "@you-agent-factory/components/forms";
|
|
401
|
+
|
|
402
|
+
export function ValidatedDisplayNameField() {
|
|
403
|
+
const reactId = useId();
|
|
404
|
+
const controlId = `${reactId}-control`;
|
|
405
|
+
const errorId = `${reactId}-error`;
|
|
406
|
+
const [value, setValue] = useState("");
|
|
407
|
+
const [errorText, setErrorText] = useState<string | undefined>();
|
|
408
|
+
|
|
409
|
+
return (
|
|
410
|
+
<FormField>
|
|
411
|
+
<FormLabel htmlFor={controlId}>Display name</FormLabel>
|
|
412
|
+
<PackageInput
|
|
413
|
+
aria-describedby={buildFormFieldAriaDescribedBy({})}
|
|
414
|
+
aria-errormessage={errorText ? errorId : undefined}
|
|
415
|
+
aria-invalid={errorText ? true : undefined}
|
|
416
|
+
id={controlId}
|
|
417
|
+
onChange={(event) => {
|
|
418
|
+
const nextValue = event.target.value;
|
|
419
|
+
setValue(nextValue);
|
|
420
|
+
setErrorText(
|
|
421
|
+
nextValue.trim().length === 0 ? "Display name is required." : undefined,
|
|
422
|
+
);
|
|
423
|
+
}}
|
|
424
|
+
value={value}
|
|
425
|
+
/>
|
|
426
|
+
{errorText ? <FormError id={errorId}>{errorText}</FormError> : null}
|
|
427
|
+
</FormField>
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Form-field components render caller-provided message text exactly as supplied.
|
|
433
|
+
They do not generate validation copy or decide when a field is valid.
|
|
434
|
+
|
|
435
|
+
## Storybook visual reference
|
|
436
|
+
|
|
437
|
+
Package Storybook lives under `Forms/PackageFormField`. Stories use package
|
|
438
|
+
imports and domain-neutral copy only — no dashboard providers.
|
|
439
|
+
|
|
440
|
+
| Story | What it shows |
|
|
441
|
+
| --- | --- |
|
|
442
|
+
| `Default` | Label and control only |
|
|
443
|
+
| `Required` | Required affordance in label copy |
|
|
444
|
+
| `Disabled` | Disabled control with label |
|
|
445
|
+
| `HelperText` | Helper text in accessible description |
|
|
446
|
+
| `Warning` | Warning message styling and description wiring |
|
|
447
|
+
| `ErrorState` | `aria-invalid`, error copy, and `aria-errormessage` |
|
|
448
|
+
| `Success` | Success message styling |
|
|
449
|
+
| `LongMessage` | Long helper text at narrow widths |
|
|
450
|
+
| `GroupedControl` | Fieldset, legend, shared description, per-control labels |
|
|
451
|
+
| `Focus` | Keyboard focus treatment on the control |
|
|
452
|
+
| `MobileWidth` | 320px viewport layout |
|
|
453
|
+
|
|
454
|
+
Run package Storybook locally:
|
|
455
|
+
|
|
456
|
+
```bash
|
|
457
|
+
cd ui/packages/components
|
|
458
|
+
bun run storybook
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
Browser verification for form-field responsive and focus stories:
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
cd ui/packages/components
|
|
465
|
+
bun run verify:form-field-storybook-browser
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## Allowed dependencies
|
|
469
|
+
|
|
470
|
+
Form-field source may import:
|
|
471
|
+
|
|
472
|
+
- Package utilities (`cn` from `@you-agent-factory/components/utilities`)
|
|
473
|
+
- Package token CSS via the host `styles.css` import
|
|
474
|
+
- React and `react-dom` peer dependencies
|
|
475
|
+
|
|
476
|
+
Form-field source must **not** import:
|
|
477
|
+
|
|
478
|
+
- Dashboard feature modules, routes, or providers
|
|
479
|
+
- Generated OpenAPI clients or dashboard API adapters
|
|
480
|
+
- React Query, Zustand, Monaco, Sonner, or dashboard i18n/session providers
|
|
481
|
+
- Factory, work, session, or provider domain types
|
|
482
|
+
|
|
483
|
+
`check:package-boundary` enforces these rules in CI.
|
|
484
|
+
|
|
485
|
+
## Dashboard integration note
|
|
486
|
+
|
|
487
|
+
The dashboard re-exports form-field messaging from `components/ui` so existing
|
|
488
|
+
feature imports keep working while resolving to package implementations. New host
|
|
489
|
+
applications should import directly from `@you-agent-factory/components` or
|
|
490
|
+
`@you-agent-factory/components/forms`.
|