@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,311 @@
|
|
|
1
|
+
# Form input primitives
|
|
2
|
+
|
|
3
|
+
Domain-free text input, textarea, checkbox, and file input controls for reusable
|
|
4
|
+
forms. Use these primitives when you need accessible native form semantics with
|
|
5
|
+
package styling, without importing dashboard features, API clients, or app state.
|
|
6
|
+
|
|
7
|
+
These components are **presentation controls**. They render styled native inputs
|
|
8
|
+
and forward standard React form props. They do not fetch data, perform validation,
|
|
9
|
+
upload files, or own durable application state.
|
|
10
|
+
|
|
11
|
+
## Required setup
|
|
12
|
+
|
|
13
|
+
Import package styles once in your host application before rendering primitives:
|
|
14
|
+
|
|
15
|
+
```css
|
|
16
|
+
@import "@you-agent-factory/components/styles.css";
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Primitives depend on package role tokens and Tailwind utility classes compiled
|
|
20
|
+
from those tokens. They do not require dashboard `styles.css`, dashboard providers,
|
|
21
|
+
generated OpenAPI types, React Query, Zustand, or app localization context.
|
|
22
|
+
|
|
23
|
+
## Import paths
|
|
24
|
+
|
|
25
|
+
Prefer the forms category entrypoint for tree-shaking and explicit category
|
|
26
|
+
boundaries:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import {
|
|
30
|
+
PackageCheckbox,
|
|
31
|
+
PackageFileInput,
|
|
32
|
+
PackageInput,
|
|
33
|
+
PackageTextarea,
|
|
34
|
+
inputVariants,
|
|
35
|
+
textareaVariants,
|
|
36
|
+
} from "@you-agent-factory/components/forms";
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The same exports are also available from the package root:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { PackageInput } from "@you-agent-factory/components";
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Host ownership
|
|
46
|
+
|
|
47
|
+
The package owns presentation markup, focus rings, disabled styling, and native
|
|
48
|
+
control semantics. Host applications own everything domain-specific:
|
|
49
|
+
|
|
50
|
+
| Concern | Owner |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| Visible labels and placeholder copy | Host |
|
|
53
|
+
| Helper text and validation error messages | Host |
|
|
54
|
+
| `id`, `name`, `required`, and `disabled` values | Host |
|
|
55
|
+
| `aria-describedby`, `aria-invalid`, and `aria-label` wiring | Host |
|
|
56
|
+
| Controlled value or checked state | Host (when using controlled mode) |
|
|
57
|
+
| Form submission, validation rules, and error mapping | Host |
|
|
58
|
+
| File upload, persistence, and storage side effects | Host |
|
|
59
|
+
| Selected-file display copy (for example helper text) | Host |
|
|
60
|
+
| Data fetching, caching, and durable cross-route state | Host |
|
|
61
|
+
|
|
62
|
+
Pass values, handlers, and accessibility attributes into primitives as props.
|
|
63
|
+
Do not expect primitives to read from your routers, stores, or API layers.
|
|
64
|
+
|
|
65
|
+
## Controlled versus uncontrolled values
|
|
66
|
+
|
|
67
|
+
All four primitives forward native React form props. Choose the mode that matches
|
|
68
|
+
how your host form manages state.
|
|
69
|
+
|
|
70
|
+
### Text input (`PackageInput`)
|
|
71
|
+
|
|
72
|
+
| Mode | When to use | Key props |
|
|
73
|
+
| --- | --- | --- |
|
|
74
|
+
| Controlled | Host state must mirror every keystroke (live validation, dependent fields, reset) | `value` + `onChange` |
|
|
75
|
+
| Uncontrolled | Native form submission or refs are enough; host does not need per-keystroke state | `defaultValue` + optional `ref` |
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { useState } from "react";
|
|
79
|
+
import { PackageInput } from "@you-agent-factory/components/forms";
|
|
80
|
+
|
|
81
|
+
export function ControlledNameField() {
|
|
82
|
+
const [name, setName] = useState("");
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<PackageInput
|
|
86
|
+
aria-label="Display name"
|
|
87
|
+
onChange={(event) => {
|
|
88
|
+
setName(event.target.value);
|
|
89
|
+
}}
|
|
90
|
+
value={name}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function UncontrolledNameField() {
|
|
96
|
+
return (
|
|
97
|
+
<PackageInput
|
|
98
|
+
aria-label="Display name"
|
|
99
|
+
defaultValue="Initial name"
|
|
100
|
+
name="displayName"
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Do not pass both `value` and `defaultValue` on the same control.
|
|
107
|
+
|
|
108
|
+
### Textarea (`PackageTextarea`)
|
|
109
|
+
|
|
110
|
+
Same controlled/uncontrolled rules as text input. `PackageTextarea` also accepts
|
|
111
|
+
`variant`:
|
|
112
|
+
|
|
113
|
+
- `field` (default) — bordered field shell with scrollable body (`textareaVariants`).
|
|
114
|
+
- `plain` — borderless transparent textarea for embedding inside custom shells.
|
|
115
|
+
|
|
116
|
+
### Checkbox (`PackageCheckbox`)
|
|
117
|
+
|
|
118
|
+
| Mode | When to use | Key props |
|
|
119
|
+
| --- | --- | --- |
|
|
120
|
+
| Controlled | Host must react to every toggle (conditional sections, select-all logic) | `checked` + `onChange` |
|
|
121
|
+
| Uncontrolled | Checkbox state can live in the native form until submit | `defaultChecked` + optional `ref` |
|
|
122
|
+
|
|
123
|
+
The visible check indicator is decorative (`aria-hidden`). Assistive technology
|
|
124
|
+
reads the native `input type="checkbox"` inside the component. Always provide an
|
|
125
|
+
accessible name with a visible `<label htmlFor={id}>`, `aria-label`, or
|
|
126
|
+
`aria-labelledby`.
|
|
127
|
+
|
|
128
|
+
### File input (`PackageFileInput`)
|
|
129
|
+
|
|
130
|
+
| Mode | When to use | Key props |
|
|
131
|
+
| --- | --- | --- |
|
|
132
|
+
| Controlled selection tracking | Host must show selected-file feedback or gate submit buttons | Read `event.target.files` in `onChange`; optionally clear with `value=""` on re-render |
|
|
133
|
+
| Uncontrolled | File selection is handled entirely by native form submit | `name` on the input; read files from `FormData` on submit |
|
|
134
|
+
|
|
135
|
+
The primitive opens the native file picker and exposes selected `File` objects
|
|
136
|
+
through standard `onChange` events. It does **not** upload, persist, or validate
|
|
137
|
+
file contents. Display selected file names in host-owned helper text or adjacent
|
|
138
|
+
copy when your UX requires feedback.
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
import { useState } from "react";
|
|
142
|
+
import { PackageFileInput } from "@you-agent-factory/components/forms";
|
|
143
|
+
|
|
144
|
+
export function CoverImageField() {
|
|
145
|
+
const [selectedName, setSelectedName] = useState<string | undefined>();
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<>
|
|
149
|
+
<PackageFileInput
|
|
150
|
+
accept="image/png,image/jpeg"
|
|
151
|
+
aria-describedby="cover-image-helper"
|
|
152
|
+
id="cover-image"
|
|
153
|
+
onChange={(event) => {
|
|
154
|
+
setSelectedName(event.target.files?.[0]?.name);
|
|
155
|
+
}}
|
|
156
|
+
/>
|
|
157
|
+
<p id="cover-image-helper">
|
|
158
|
+
{selectedName ? `Selected: ${selectedName}` : "PNG or JPEG up to 2 MB."}
|
|
159
|
+
</p>
|
|
160
|
+
</>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Labels, descriptions, and error relationships
|
|
166
|
+
|
|
167
|
+
Primitives render the control only. Host applications wire labels and supporting
|
|
168
|
+
text:
|
|
169
|
+
|
|
170
|
+
1. Generate stable `id` values (for example `useId()`).
|
|
171
|
+
2. Associate visible labels with `<label htmlFor={controlId}>`.
|
|
172
|
+
3. Render helper text in an element with its own `id`.
|
|
173
|
+
4. Render error text in an element with `role="alert"` and its own `id`.
|
|
174
|
+
5. Pass `aria-describedby` listing helper and/or error element ids.
|
|
175
|
+
6. Pass `aria-invalid={true}` when the field fails validation.
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
import { useId } from "react";
|
|
179
|
+
import { PackageInput } from "@you-agent-factory/components/forms";
|
|
180
|
+
|
|
181
|
+
export function NameField({
|
|
182
|
+
errorText,
|
|
183
|
+
helperText,
|
|
184
|
+
invalid,
|
|
185
|
+
value,
|
|
186
|
+
onChange,
|
|
187
|
+
}: {
|
|
188
|
+
errorText?: string;
|
|
189
|
+
helperText?: string;
|
|
190
|
+
invalid?: boolean;
|
|
191
|
+
value: string;
|
|
192
|
+
onChange: (value: string) => void;
|
|
193
|
+
}) {
|
|
194
|
+
const reactId = useId();
|
|
195
|
+
const controlId = `${reactId}-control`;
|
|
196
|
+
const helperId = helperText ? `${reactId}-helper` : undefined;
|
|
197
|
+
const errorId = errorText ? `${reactId}-error` : undefined;
|
|
198
|
+
const describedBy =
|
|
199
|
+
[helperId, errorId].filter(Boolean).join(" ") || undefined;
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<div>
|
|
203
|
+
<label htmlFor={controlId}>Display name</label>
|
|
204
|
+
<PackageInput
|
|
205
|
+
aria-describedby={describedBy}
|
|
206
|
+
aria-invalid={invalid || Boolean(errorText) || undefined}
|
|
207
|
+
id={controlId}
|
|
208
|
+
onChange={(event) => {
|
|
209
|
+
onChange(event.target.value);
|
|
210
|
+
}}
|
|
211
|
+
value={value}
|
|
212
|
+
/>
|
|
213
|
+
{helperText ? <p id={helperId}>{helperText}</p> : null}
|
|
214
|
+
{errorText ? (
|
|
215
|
+
<p id={errorId} role="alert">
|
|
216
|
+
{errorText}
|
|
217
|
+
</p>
|
|
218
|
+
) : null}
|
|
219
|
+
</div>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Package Storybook uses `PackageFormStoryField` for the same wiring pattern in
|
|
225
|
+
demos. That helper is story support only; host applications should compose
|
|
226
|
+
labels and messages in their own form-field layer.
|
|
227
|
+
|
|
228
|
+
## Required, disabled, and invalid state
|
|
229
|
+
|
|
230
|
+
| State | Host responsibility | Primitive behavior |
|
|
231
|
+
| --- | --- | --- |
|
|
232
|
+
| `required` | Set `required` when the field must be submitted | Native constraint validation semantics |
|
|
233
|
+
| `disabled` | Set `disabled` to block interaction | Pointer and keyboard changes are ignored; disabled styling and semantics apply |
|
|
234
|
+
| Invalid | Set `aria-invalid={true}` when validation fails | Danger border and focus ring via `aria-invalid` styles |
|
|
235
|
+
| Error copy | Render error text and link with `aria-describedby` | Does not invent error messages |
|
|
236
|
+
|
|
237
|
+
Disabled file inputs ignore `onChange` and keep an empty `files` list until
|
|
238
|
+
re-enabled.
|
|
239
|
+
|
|
240
|
+
## Accessibility expectations
|
|
241
|
+
|
|
242
|
+
- Text input, textarea, and file input expose visible `focus-visible` rings for
|
|
243
|
+
keyboard users.
|
|
244
|
+
- Checkbox keyboard focus targets the native input; the styled indicator shows a
|
|
245
|
+
focus ring via `peer-focus-visible` styles.
|
|
246
|
+
- Invalid states use `aria-invalid` styling on the native control (checkbox uses
|
|
247
|
+
`peer-aria-invalid` on the indicator).
|
|
248
|
+
- Host-supplied header actions, labels, and error text must use accessible names
|
|
249
|
+
and programmatic relationships (`htmlFor`, `aria-describedby`, `role="alert"`).
|
|
250
|
+
- Do not rely on placeholder text as the only accessible name.
|
|
251
|
+
|
|
252
|
+
## Variant helpers
|
|
253
|
+
|
|
254
|
+
`inputVariants` and `textareaVariants` export the shared field class strings for
|
|
255
|
+
host compositions that need matching shells around sibling elements:
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
import { inputVariants, textareaVariants } from "@you-agent-factory/components/forms";
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Storybook visual reference
|
|
262
|
+
|
|
263
|
+
Package Storybook lives under `Forms/PackageInput`, `Forms/PackageTextarea`,
|
|
264
|
+
`Forms/PackageCheckbox`, and `Forms/PackageFileInput`. Stories use package
|
|
265
|
+
imports and package token decorators only — no dashboard providers.
|
|
266
|
+
|
|
267
|
+
| Primitive | Example stories |
|
|
268
|
+
| --- | --- |
|
|
269
|
+
| `PackageInput` | Controlled, Uncontrolled, Disabled, Invalid, ErrorState, Focus, HelperText, MobileWidth |
|
|
270
|
+
| `PackageTextarea` | Controlled, Uncontrolled, Disabled, Invalid, ErrorState, Focus, HelperText, MobileWidth |
|
|
271
|
+
| `PackageCheckbox` | Controlled, Uncontrolled, Disabled, Invalid, ErrorState, Focus, HelperText, MobileWidth |
|
|
272
|
+
| `PackageFileInput` | Default, Disabled, Invalid, ErrorState, Focus, HelperText, SelectedFile, MobileWidth |
|
|
273
|
+
|
|
274
|
+
Run package Storybook locally:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
cd ui/packages/components
|
|
278
|
+
bun run storybook
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Browser verification for responsive and focus stories:
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
cd ui/packages/components
|
|
285
|
+
bun run verify:storybook-browser
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Allowed dependencies
|
|
289
|
+
|
|
290
|
+
Primitive source may import:
|
|
291
|
+
|
|
292
|
+
- Package utilities (`cn` from `@you-agent-factory/components/utilities`)
|
|
293
|
+
- Package token CSS via the host `styles.css` import
|
|
294
|
+
- React and `react-dom` peer dependencies
|
|
295
|
+
|
|
296
|
+
Primitive source must **not** import:
|
|
297
|
+
|
|
298
|
+
- Dashboard feature modules, routes, or providers
|
|
299
|
+
- Generated OpenAPI clients or dashboard API adapters
|
|
300
|
+
- React Query, Zustand, Monaco, Sonner, or dashboard i18n/session providers
|
|
301
|
+
- Factory, work, session, or provider domain types
|
|
302
|
+
|
|
303
|
+
`check:package-boundary` enforces these rules in CI.
|
|
304
|
+
|
|
305
|
+
## Dashboard integration note
|
|
306
|
+
|
|
307
|
+
The dashboard re-exports these primitives from `components/ui` (`Input`,
|
|
308
|
+
`Textarea`, `Checkbox`, `FileInput`) so existing feature imports keep working
|
|
309
|
+
while resolving to package implementations. New host applications should import
|
|
310
|
+
directly from `@you-agent-factory/components` or
|
|
311
|
+
`@you-agent-factory/components/forms`.
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
# Form select primitives
|
|
2
|
+
|
|
3
|
+
Domain-free select controls for reusable forms. Use these primitives when you need
|
|
4
|
+
accessible combobox or native `<select>` semantics with package styling, without
|
|
5
|
+
importing dashboard features, API clients, generated OpenAPI types, or app
|
|
6
|
+
localization providers.
|
|
7
|
+
|
|
8
|
+
The package ships three layers:
|
|
9
|
+
|
|
10
|
+
| Component | Role |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| `Select` primitives | Radix-backed combobox building blocks (`Select`, `SelectTrigger`, `SelectContent`, `SelectItem`, and helpers). |
|
|
13
|
+
| `NativeSelect` | Styled native `<select>` for simple option lists and progressive enhancement. |
|
|
14
|
+
| `EnumSelect` helpers | Opinionated wrappers that map host-supplied option arrays into `Select` markup. |
|
|
15
|
+
|
|
16
|
+
These components are **presentation controls**. They render styled select UI and
|
|
17
|
+
forward standard React props. They do not fetch options, perform validation,
|
|
18
|
+
localize labels, or own durable application state.
|
|
19
|
+
|
|
20
|
+
## Required setup
|
|
21
|
+
|
|
22
|
+
Import package styles once in your host application before rendering selects:
|
|
23
|
+
|
|
24
|
+
```css
|
|
25
|
+
@import "@you-agent-factory/components/styles.css";
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Select primitives depend on package role tokens and Tailwind utility classes
|
|
29
|
+
compiled from those tokens. They do not require dashboard `styles.css`, dashboard
|
|
30
|
+
providers, generated OpenAPI types, React Query, Zustand, or app localization
|
|
31
|
+
context.
|
|
32
|
+
|
|
33
|
+
## Import paths
|
|
34
|
+
|
|
35
|
+
Prefer the forms category entrypoint for tree-shaking and explicit category
|
|
36
|
+
boundaries:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import {
|
|
40
|
+
EnumSelect,
|
|
41
|
+
ENUM_SELECT_EMPTY_VALUE,
|
|
42
|
+
NativeSelect,
|
|
43
|
+
OptionalEnumSelect,
|
|
44
|
+
ResetEnumSelect,
|
|
45
|
+
Select,
|
|
46
|
+
SELECT_EMPTY_STATE_VALUE,
|
|
47
|
+
SelectContent,
|
|
48
|
+
SelectEmpty,
|
|
49
|
+
SelectField,
|
|
50
|
+
SelectItem,
|
|
51
|
+
SelectTrigger,
|
|
52
|
+
SelectValue,
|
|
53
|
+
type EnumSelectOption,
|
|
54
|
+
} from "@you-agent-factory/components/forms";
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The same exports are also available from the package root:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import {
|
|
61
|
+
EnumSelect,
|
|
62
|
+
NativeSelect,
|
|
63
|
+
Select,
|
|
64
|
+
} from "@you-agent-factory/components";
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Host ownership
|
|
68
|
+
|
|
69
|
+
The package owns presentation markup, focus rings, disabled styling, keyboard
|
|
70
|
+
interaction, and combobox semantics. Host applications own everything
|
|
71
|
+
domain-specific:
|
|
72
|
+
|
|
73
|
+
| Concern | Owner |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| Option values and visible labels | Host |
|
|
76
|
+
| Placeholder, empty, and loading copy | Host |
|
|
77
|
+
| Visible labels, helper text, and validation errors | Host |
|
|
78
|
+
| `id`, `disabled`, and `aria-*` wiring | Host |
|
|
79
|
+
| Controlled `value` and `open` state | Host (when using controlled mode) |
|
|
80
|
+
| `onValueChange` and `onOpenChange` handlers | Host |
|
|
81
|
+
| Fetching, caching, and refreshing option lists | Host |
|
|
82
|
+
| Localization and enum label mapping | Host |
|
|
83
|
+
| Form submission, validation rules, and error mapping | Host |
|
|
84
|
+
|
|
85
|
+
Pass data and callbacks into select components as props. Do not expect the
|
|
86
|
+
package to read from your routers, stores, or API layers.
|
|
87
|
+
|
|
88
|
+
## Option contract
|
|
89
|
+
|
|
90
|
+
### `EnumSelectOption`
|
|
91
|
+
|
|
92
|
+
`EnumSelect`, `OptionalEnumSelect`, and `ResetEnumSelect` accept a readonly
|
|
93
|
+
array of domain-free options:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
type EnumSelectOption = {
|
|
97
|
+
value: string;
|
|
98
|
+
label: string;
|
|
99
|
+
disabled?: boolean;
|
|
100
|
+
};
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
| Field | Contract |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| `value` | Stable string identifier used in `onValueChange`. Must be unique within the option list. |
|
|
106
|
+
| `label` | Host-supplied visible text. The package does not translate or format enum values. |
|
|
107
|
+
| `disabled` | When `true`, the option is not selectable by pointer or keyboard and exposes disabled semantics to assistive technology. |
|
|
108
|
+
|
|
109
|
+
### Empty option lists
|
|
110
|
+
|
|
111
|
+
When `options` is empty and `loading` is `false`, `EnumSelect` renders a
|
|
112
|
+
non-selectable `SelectEmpty` row using `emptyOptionsLabel` (default:
|
|
113
|
+
`"No options available"`). The row is a disabled menu item backed by
|
|
114
|
+
`SELECT_EMPTY_STATE_VALUE`, so users cannot accidentally select a fake value.
|
|
115
|
+
|
|
116
|
+
For custom Radix compositions, render `SelectEmpty` inside `SelectContent`
|
|
117
|
+
instead of mapping interactive `SelectItem` rows over an empty array.
|
|
118
|
+
|
|
119
|
+
### Optional and reset helpers
|
|
120
|
+
|
|
121
|
+
| Helper | Use when |
|
|
122
|
+
| --- | --- |
|
|
123
|
+
| `OptionalEnumSelect` | The host needs a nullable selection. Uses `ENUM_SELECT_EMPTY_VALUE` as a dedicated empty row and maps it back to `null` in `onValueChange`. |
|
|
124
|
+
| `ResetEnumSelect` | The host needs a select that always returns to the placeholder after each selection (for example one-shot filters). Remounts internally after each change. |
|
|
125
|
+
|
|
126
|
+
## Controlled value and open state
|
|
127
|
+
|
|
128
|
+
`Select` forwards Radix `Select.Root` props. Choose controlled or uncontrolled
|
|
129
|
+
mode based on how your host form manages state.
|
|
130
|
+
|
|
131
|
+
### Controlled value
|
|
132
|
+
|
|
133
|
+
Use controlled value when host state must mirror the current selection (dependent
|
|
134
|
+
fields, validation, reset, or async reconciliation):
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
import { useState } from "react";
|
|
138
|
+
import {
|
|
139
|
+
EnumSelect,
|
|
140
|
+
type EnumSelectOption,
|
|
141
|
+
} from "@you-agent-factory/components/forms";
|
|
142
|
+
|
|
143
|
+
const options: EnumSelectOption[] = [
|
|
144
|
+
{ label: "Story", value: "story" },
|
|
145
|
+
{ label: "Task", value: "task" },
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
function WorkTypeField() {
|
|
149
|
+
const [value, setValue] = useState("story");
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<EnumSelect
|
|
153
|
+
id="work-type"
|
|
154
|
+
onValueChange={setValue}
|
|
155
|
+
options={options}
|
|
156
|
+
placeholder="Select a work type"
|
|
157
|
+
value={value}
|
|
158
|
+
/>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Host responsibilities for controlled value:
|
|
164
|
+
|
|
165
|
+
- Provide the current `value` on every render.
|
|
166
|
+
- Update host state inside `onValueChange` when the user selects a different
|
|
167
|
+
**enabled** option.
|
|
168
|
+
- Expect `onValueChange` not to fire when the user re-selects the current value
|
|
169
|
+
or activates a disabled option.
|
|
170
|
+
|
|
171
|
+
### Controlled open
|
|
172
|
+
|
|
173
|
+
Use controlled open when the host must coordinate menu visibility with other UI
|
|
174
|
+
(for example closing on route change or synchronizing with a parent disclosure):
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
import { useState } from "react";
|
|
178
|
+
import {
|
|
179
|
+
Select,
|
|
180
|
+
SelectContent,
|
|
181
|
+
SelectItem,
|
|
182
|
+
SelectTrigger,
|
|
183
|
+
SelectValue,
|
|
184
|
+
} from "@you-agent-factory/components/forms";
|
|
185
|
+
|
|
186
|
+
function ControlledOpenExample() {
|
|
187
|
+
const [open, setOpen] = useState(false);
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<Select onOpenChange={setOpen} open={open} value="story">
|
|
191
|
+
<SelectTrigger aria-label="Work type">
|
|
192
|
+
<SelectValue placeholder="Select a work type" />
|
|
193
|
+
</SelectTrigger>
|
|
194
|
+
<SelectContent>
|
|
195
|
+
<SelectItem value="story">Story</SelectItem>
|
|
196
|
+
</SelectContent>
|
|
197
|
+
</Select>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Host responsibilities for controlled open:
|
|
203
|
+
|
|
204
|
+
- Provide the current `open` boolean on every render.
|
|
205
|
+
- Update host state inside `onOpenChange` when the user opens or closes the
|
|
206
|
+
menu.
|
|
207
|
+
|
|
208
|
+
### Uncontrolled usage
|
|
209
|
+
|
|
210
|
+
Omit `value` and `open` to let Radix manage internal state. `defaultValue` still
|
|
211
|
+
sets the initial selection. Prefer controlled mode when host validation or
|
|
212
|
+
cross-field logic depends on the current selection.
|
|
213
|
+
|
|
214
|
+
## Keyboard behavior and focus
|
|
215
|
+
|
|
216
|
+
Radix select primitives provide combobox semantics on the trigger:
|
|
217
|
+
|
|
218
|
+
| Key | Behavior |
|
|
219
|
+
| --- | --- |
|
|
220
|
+
| `Tab` / `Shift+Tab` | Move focus to and from the trigger. |
|
|
221
|
+
| `Space`, `Enter`, `ArrowDown`, `ArrowUp` | Open the menu when closed. |
|
|
222
|
+
| `ArrowDown` / `ArrowUp` | Move highlight across enabled options while open. |
|
|
223
|
+
| `Enter` / `Space` | Select the highlighted enabled option and close the menu. |
|
|
224
|
+
| `Escape` | Close the menu without changing the selection. |
|
|
225
|
+
| `Home` / `End` | Jump to the first or last enabled option while open. |
|
|
226
|
+
|
|
227
|
+
After a successful selection, focus returns to the trigger. Package styles keep
|
|
228
|
+
`focus-visible` rings on the trigger and highlighted menu items during keyboard
|
|
229
|
+
interaction.
|
|
230
|
+
|
|
231
|
+
`NativeSelect` uses native platform keyboard behavior for `<select>` elements.
|
|
232
|
+
|
|
233
|
+
## Disabled behavior
|
|
234
|
+
|
|
235
|
+
| Surface | Behavior |
|
|
236
|
+
| --- | --- |
|
|
237
|
+
| Disabled field (`disabled` on `Select` or `EnumSelect`) | Trigger is not interactive, exposes disabled semantics, and cannot open the menu. |
|
|
238
|
+
| Disabled option (`disabled` on `SelectItem` or `EnumSelectOption`) | Skipped by keyboard navigation and not selectable by pointer. |
|
|
239
|
+
| Loading `EnumSelect` (`loading`) | Trigger is disabled, sets `aria-busy`, and shows `loadingLabel` as the placeholder. Controlled value is routed through `SELECT_EMPTY_STATE_VALUE` so the loading label stays visible. |
|
|
240
|
+
|
|
241
|
+
## Loading and empty states
|
|
242
|
+
|
|
243
|
+
`EnumSelect` supports asynchronous option loading without dashboard wiring:
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
<EnumSelect
|
|
247
|
+
id="work-type"
|
|
248
|
+
loading={isLoading}
|
|
249
|
+
loadingLabel="Loading work types..."
|
|
250
|
+
emptyOptionsLabel="No work types available"
|
|
251
|
+
onValueChange={setValue}
|
|
252
|
+
options={options}
|
|
253
|
+
value={value}
|
|
254
|
+
/>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
While `loading` is `true`:
|
|
258
|
+
|
|
259
|
+
- The trigger is disabled and marked busy.
|
|
260
|
+
- The menu shows a non-selectable `SelectEmpty` row with `loadingLabel`.
|
|
261
|
+
- Host `value` is not shown in the trigger; the loading label takes precedence.
|
|
262
|
+
|
|
263
|
+
When loading completes and `options` is still empty, `emptyOptionsLabel` is used
|
|
264
|
+
for both the menu affordance and the visible empty state.
|
|
265
|
+
|
|
266
|
+
## Error and description relationships
|
|
267
|
+
|
|
268
|
+
Use host-owned helper and error text with explicit ARIA relationships on the
|
|
269
|
+
trigger. `SelectField` is a small layout helper when you want label, description,
|
|
270
|
+
and error regions colocated with the control:
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
import {
|
|
274
|
+
Select,
|
|
275
|
+
SelectContent,
|
|
276
|
+
SelectField,
|
|
277
|
+
SelectItem,
|
|
278
|
+
SelectTrigger,
|
|
279
|
+
SelectValue,
|
|
280
|
+
} from "@you-agent-factory/components/forms";
|
|
281
|
+
|
|
282
|
+
function WorkTypeField({ error }: { error?: string }) {
|
|
283
|
+
const controlId = "work-type";
|
|
284
|
+
const helperId = "work-type-helper";
|
|
285
|
+
const errorId = "work-type-error";
|
|
286
|
+
|
|
287
|
+
return (
|
|
288
|
+
<SelectField
|
|
289
|
+
description="Choose the work type for this request."
|
|
290
|
+
descriptionId={helperId}
|
|
291
|
+
error={error}
|
|
292
|
+
errorId={errorId}
|
|
293
|
+
inputId={controlId}
|
|
294
|
+
label="Work type"
|
|
295
|
+
>
|
|
296
|
+
<Select value="story">
|
|
297
|
+
<SelectTrigger
|
|
298
|
+
aria-describedby={`${helperId} ${errorId}`}
|
|
299
|
+
aria-invalid={Boolean(error)}
|
|
300
|
+
id={controlId}
|
|
301
|
+
>
|
|
302
|
+
<SelectValue placeholder="Select a work type" />
|
|
303
|
+
</SelectTrigger>
|
|
304
|
+
<SelectContent>
|
|
305
|
+
<SelectItem value="story">Story</SelectItem>
|
|
306
|
+
</SelectContent>
|
|
307
|
+
</Select>
|
|
308
|
+
</SelectField>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Error text renders with `role="alert"` so assistive technology announces
|
|
314
|
+
validation failures without relying on color alone. Wire `aria-describedby` to
|
|
315
|
+
both helper and error element ids when both are present.
|
|
316
|
+
|
|
317
|
+
## Long labels and compact layouts
|
|
318
|
+
|
|
319
|
+
`SelectTrigger` clamps the selected value with `line-clamp-1`. `SelectItem` text
|
|
320
|
+
uses `line-clamp-2` with `break-words` so long option labels stay inside the
|
|
321
|
+
menu without overlapping neighboring UI. Prefer concise labels in product copy,
|
|
322
|
+
but the package constrains overflow for narrow containers and mobile widths.
|
|
323
|
+
|
|
324
|
+
## NativeSelect
|
|
325
|
+
|
|
326
|
+
Use `NativeSelect` when a native `<select>` is acceptable and you only need
|
|
327
|
+
package field styling:
|
|
328
|
+
|
|
329
|
+
```tsx
|
|
330
|
+
import { NativeSelect } from "@you-agent-factory/components/forms";
|
|
331
|
+
|
|
332
|
+
function PriorityField() {
|
|
333
|
+
return (
|
|
334
|
+
<NativeSelect aria-label="Priority" defaultValue="medium" id="priority">
|
|
335
|
+
<option value="low">Low</option>
|
|
336
|
+
<option value="medium">Medium</option>
|
|
337
|
+
<option value="high">High</option>
|
|
338
|
+
</NativeSelect>
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
`NativeSelect` forwards standard `<select>` props. Host applications still own
|
|
344
|
+
`<option>` children, labels, validation messaging, and change handlers.
|
|
345
|
+
|
|
346
|
+
## Storybook examples
|
|
347
|
+
|
|
348
|
+
Package Storybook stories demonstrate expected states with package imports only.
|
|
349
|
+
Run `bun run storybook` from `ui/packages/components` or inspect the static
|
|
350
|
+
build with `bun run build-storybook`.
|
|
351
|
+
|
|
352
|
+
| Story | What it shows |
|
|
353
|
+
| --- | --- |
|
|
354
|
+
| `Forms/PackageSelect/ControlledValue` | Host-controlled selection |
|
|
355
|
+
| `Forms/PackageSelect/ControlledOpen` | Host-controlled menu open state |
|
|
356
|
+
| `Forms/PackageSelect/KeyboardInteraction` | Keyboard open, navigate, select, and focus return |
|
|
357
|
+
| `Forms/PackageSelect/DisabledField` | Disabled trigger |
|
|
358
|
+
| `Forms/PackageSelect/EmptyOptions` | Non-selectable empty menu affordance |
|
|
359
|
+
| `Forms/PackageSelect/LoadingOptions` | Loading trigger and menu state |
|
|
360
|
+
| `Forms/PackageSelect/ErrorState` | `aria-invalid` and visible error text |
|
|
361
|
+
| `Forms/PackageSelect/LongLabel` | Long labels at desktop width |
|
|
362
|
+
| `Forms/PackageSelect/LongLabelMobile` | Long labels at mobile width |
|