@agentaily/design-system 0.1.0 → 0.2.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/DESIGN.md CHANGED
@@ -88,7 +88,7 @@ Examples — ✓ "Rate limited. Retry in 18s." ✗ "Oops! Something went wrong
88
88
  | `guidelines/` | 17 specimen cards (Colors ×4, Type ×4, Spacing ×5, Brand ×4) |
89
89
  | `states/` | 5 interaction-state matrices (buttons, form controls, selection/nav, loading/streaming, status/feedback) — force-rendered hover/focus/active/disabled |
90
90
  | `components/buttons/` | Button, IconButton, **ButtonGroup** |
91
- | `components/inputs/` | Input, Textarea, Select, Switch, Checkbox, Label, RadioGroup, Slider, Toggle, ToggleGroup, **Field, FieldGroup, InputGroup, InputOTP, Combobox, Calendar, DatePicker** |
91
+ | `components/inputs/` | Input, Textarea, Select, Switch, Checkbox, Label, RadioGroup, Slider, Toggle, ToggleGroup, **Field, FieldGroup, InputGroup, InputOTP, Combobox, Calendar, DatePicker, Form (+FormActions, Form.useForm)** |
92
92
  | `components/display/` | Card, Badge, Avatar, Tabs, Kbd, Separator, Skeleton, Progress, Accordion, Breadcrumb, Table, Pagination, Empty, **Collapsible, Item, Carousel, Chart (BarChart/LineChart), DataTable, Typography (Prose/Text)** |
93
93
  | `components/feedback/` | Spinner, Toast, Tooltip, Dialog, Alert |
94
94
  | `components/overlay/` | Popover, DropdownMenu, Command, Sheet, **HoverCard, ContextMenu, Menubar, NavigationMenu, AlertDialog** |
@@ -104,7 +104,9 @@ Examples — ✓ "Rate limited. Retry in 18s." ✗ "Oops! Something went wrong
104
104
  | `ui_kits/docs/` | Documentation site (interactive) |
105
105
  | `SKILL.md` | Agent-skill entry point |
106
106
 
107
- Every component ships `<Name>.jsx` + `<Name>.d.ts` (props) + `<Name>.prompt.md` (usage) — **116 component exports across 11 categories** (buttons, inputs, display, feedback, overlay, layout, chat, ai, code, voice, workflow, utilities). This is a deliberately exhaustive set covering shadcn/ui primitives + AI-native surfaces (reasoning, tools, agents, voice, workflow graphs). Consume via the compiled bundle: `window.AxiomDesignSystem_7fc962`. Read each component's `.prompt.md` for copy-paste usage.
107
+ Every component ships `<Name>.jsx` + `<Name>.d.ts` (props) + `<Name>.prompt.md` (usage) — **118 component exports across 11 categories** (buttons, inputs, display, feedback, overlay, layout, chat, ai, code, voice, workflow, utilities). This is a deliberately exhaustive set covering shadcn/ui primitives + AI-native surfaces (reasoning, tools, agents, voice, workflow graphs). Consume via the compiled bundle: `window.AxiomDesignSystem_7fc962`. Read each component's `.prompt.md` for copy-paste usage.
108
+
109
+ **Forms are layered, not monolithic.** Presentational controls (Input/Select/Field…) own layout and never depend on a form engine. `Form` + `FormActions` add pure structure. `Form.useForm` is an **optional**, zero-dependency orchestration hook (values/errors/touched/validate/submit) exposed off the capitalized `Form` export — drop it for react-hook-form or TanStack and the controls still work. Errors surface only after blur or submit; spread `form.field(name)` onto any value control, or `form.field(name, {type:"checkbox"})` for boolean ones.
108
110
 
109
111
  ## CAVEATS
110
112
  - **Fonts are CDN substitutions:** Space Grotesk + JetBrains Mono via Google Fonts `@import` (no binaries shipped). Replace `tokens/fonts.css` with real `@font-face` + files for offline/production use.
package/README.md CHANGED
@@ -55,7 +55,11 @@ import { Button, Composer, Reasoning } from "@agentaily/design-system";
55
55
 
56
56
  ### ③ Agent Skill
57
57
 
58
- 仓库根目录的 `SKILL.md` 让整个目录可作为 Claude Code / Agent Skill 分发,供 AI Agentaily 品牌产出设计稿或生产代码。
58
+ `skill/SKILL.md` 是一个自包含的 Claude Code / Agent Skill —— 面向**消费者**的使用指南(品牌规则 + 如何用 `@agentaily/design-system` 包 + Storybook 目录链接),不含仓库内部实现。把 `skill/` 软链到 `~/.claude/skills/agentaily-design` 即可全局调用:
59
+
60
+ ```bash
61
+ ln -s "$PWD/skill" ~/.claude/skills/agentaily-design
62
+ ```
59
63
 
60
64
  ## 结构
61
65
 
@@ -0,0 +1,164 @@
1
+ import * as React from "react";
2
+
3
+ /** Structural <form> wrapper. Pure layout — pass form.handleSubmit as onSubmit. */
4
+ export interface FormProps extends React.FormHTMLAttributes<HTMLFormElement> {
5
+ /** Vertical rhythm between children. @default "normal" */
6
+ gap?: "tight" | "normal" | "loose";
7
+ onSubmit?: (e: React.FormEvent<HTMLFormElement>) => void;
8
+ children?: React.ReactNode;
9
+ }
10
+
11
+ /** Footer row for submit/cancel buttons. */
12
+ export interface FormActionsProps extends React.HTMLAttributes<HTMLDivElement> {
13
+ /** Horizontal alignment of the buttons. @default "end" */
14
+ align?: "start" | "end" | "between";
15
+ /** Draw a separator line above the row. @default false */
16
+ bordered?: boolean;
17
+ /** Stretch buttons to fill the row equally. @default false */
18
+ full?: boolean;
19
+ children?: React.ReactNode;
20
+ }
21
+
22
+ /** A `{ value, message }` pair, or the bare value (message falls back to a default). */
23
+ export type Rule<T> = T | { value: T; message: string };
24
+
25
+ /** Built-in per-field validation rules, RHF-aligned. Pass as the 2nd arg to register/field. */
26
+ export interface FieldRules<V = Record<string, any>> {
27
+ /** Non-empty (and, for checkboxes, checked). String = custom message. */
28
+ required?: boolean | string;
29
+ minLength?: Rule<number>;
30
+ maxLength?: Rule<number>;
31
+ min?: Rule<number>;
32
+ max?: Rule<number>;
33
+ pattern?: RegExp | { value: RegExp; message: string };
34
+ /** Custom validator(s). Return `true` (ok), a string (error message), or `false`. May be async. */
35
+ validate?:
36
+ | ((value: any, values: V) => boolean | string | Promise<boolean | string>)
37
+ | Record<string, (value: any, values: V) => boolean | string | Promise<boolean | string>>;
38
+ /** Set "checkbox" so register/field bind `checked` instead of `value`. */
39
+ type?: "checkbox";
40
+ }
41
+
42
+ export interface RegisterReturn {
43
+ name: string;
44
+ value?: any;
45
+ checked?: boolean;
46
+ onChange: (e: any) => void;
47
+ onBlur: () => void;
48
+ }
49
+ export interface FieldReturn extends RegisterReturn {
50
+ /** Current error for this field (only set once it has been validated). */
51
+ error?: string;
52
+ }
53
+
54
+ export interface FormState<V> {
55
+ errors: Record<string, string>;
56
+ touched: Record<string, boolean>;
57
+ dirtyFields: Record<string, boolean>;
58
+ isDirty: boolean;
59
+ /** True when the last validation run produced no errors. */
60
+ isValid: boolean;
61
+ isValidating: boolean;
62
+ isSubmitting: boolean;
63
+ isSubmitted: boolean;
64
+ isSubmitSuccessful: boolean;
65
+ submitCount: number;
66
+ }
67
+
68
+ export interface FormBag<V = Record<string, any>> extends FormState<V> {
69
+ values: V;
70
+ /** Alias of isSubmitted, kept for back-compat. */
71
+ submitted: boolean;
72
+ formState: FormState<V>;
73
+
74
+ /** RHF-compatible binding: `<input {...form.register("email", { required: true })} />`. */
75
+ register: (name: string, rules?: FieldRules<V>) => RegisterReturn;
76
+ /** Like register but also injects `error` for DS controls: `<Input {...form.field("email")} />`. */
77
+ field: (name: string, rules?: FieldRules<V>) => FieldReturn;
78
+
79
+ setValue: (
80
+ name: string,
81
+ value: any,
82
+ opts?: { shouldValidate?: boolean; shouldTouch?: boolean; shouldDirty?: boolean },
83
+ ) => void;
84
+ setValues: (values: V | ((prev: V) => V)) => void;
85
+ getValues: (name?: string) => any;
86
+ /** Read field value(s) reactively (re-renders on change). */
87
+ watch: (name?: string) => any;
88
+ /** Set a field error manually (e.g. a server-side error). */
89
+ setError: (name: string, message: string) => void;
90
+ clearErrors: (name?: string) => void;
91
+ /** Imperatively validate the form or one field. Resolves to validity. */
92
+ trigger: (name?: string) => Promise<boolean>;
93
+
94
+ /**
95
+ * Two call styles:
96
+ * - `onSubmit={form.handleSubmit}` (uses config.onSubmit) — works as a direct event/onClick handler.
97
+ * - `onSubmit={form.handleSubmit(onValid, onInvalid?)}` — RHF style, returns a handler.
98
+ * Always preventDefaults, validates, marks all fields touched, focuses the first error.
99
+ */
100
+ handleSubmit: {
101
+ (e?: React.FormEvent | React.MouseEvent): void;
102
+ (
103
+ onValid: (values: V) => void | Promise<any>,
104
+ onInvalid?: (errors: Record<string, string>) => void,
105
+ ): (e?: React.SyntheticEvent) => void;
106
+ };
107
+ reset: (
108
+ next?: V,
109
+ opts?: {
110
+ keepErrors?: boolean;
111
+ keepTouched?: boolean;
112
+ keepDirty?: boolean;
113
+ keepIsSubmitted?: boolean;
114
+ keepDefaultValues?: boolean;
115
+ },
116
+ ) => void;
117
+ }
118
+
119
+ export interface UseFormConfig<V = Record<string, any>> {
120
+ initialValues?: V;
121
+ /** Alias of initialValues (RHF naming). */
122
+ defaultValues?: V;
123
+ /** Schema-style validator: `(values) => ({ field: message })`. Sync or async. Composes with per-field rules. */
124
+ validate?: (values: V) => Record<string, string> | Promise<Record<string, string>> | undefined;
125
+ /** When validation first fires. @default "onSubmit" */
126
+ mode?: "onSubmit" | "onBlur" | "onChange" | "onTouched" | "all";
127
+ /** When to re-validate after the first submit. @default "onChange" */
128
+ reValidateMode?: "onChange" | "onBlur";
129
+ /** Called with values on a clean submit. May return a promise → toggles isSubmitting. */
130
+ onSubmit?: (values: V) => void | Promise<any>;
131
+ /** Called with the errors map when submit is attempted while invalid. */
132
+ onInvalid?: (errors: Record<string, string>) => void;
133
+ /** Focus the first errored `[name]` control on a failed submit. @default true */
134
+ shouldFocusError?: boolean;
135
+ }
136
+
137
+ export interface UseFieldArrayConfig<V = Record<string, any>> {
138
+ form: FormBag<V>;
139
+ /** Name of the array field on the form's values. */
140
+ name: string;
141
+ }
142
+ export interface FieldArrayReturn<T = any> {
143
+ /** Current rows, each with a stable `id`. */
144
+ fields: Array<T & { id: number }>;
145
+ append: (item: T) => void;
146
+ prepend: (item: T) => void;
147
+ remove: (index: number) => void;
148
+ insert: (index: number, item: T) => void;
149
+ move: (from: number, to: number) => void;
150
+ replace: (items: T[]) => void;
151
+ update: (index: number, item: T) => void;
152
+ }
153
+
154
+ export declare function Form(props: FormProps): JSX.Element;
155
+ export declare namespace Form {
156
+ /** Production controlled form hook — RHF-aligned. Optional; drop it for RHF/TanStack. */
157
+ function useForm<V = Record<string, any>>(config?: UseFormConfig<V>): FormBag<V>;
158
+ /** Dynamic list fields. */
159
+ function useFieldArray<T = any, V = Record<string, any>>(
160
+ config: UseFieldArrayConfig<V>,
161
+ ): FieldArrayReturn<T>;
162
+ function Actions(props: FormActionsProps): JSX.Element;
163
+ }
164
+ export declare function FormActions(props: FormActionsProps): JSX.Element;
@@ -0,0 +1,484 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useState, useRef, useCallback, useMemo } from "react";
3
+ const AX_FORM_CSS = `
4
+ .ax-form { display: flex; flex-direction: column; gap: var(--space-5, 20px); }
5
+ .ax-form--tight { gap: var(--space-3, 12px); }
6
+ .ax-form--loose { gap: var(--space-6, 28px); }
7
+ .ax-form__actions {
8
+ display: flex; align-items: center; gap: var(--space-3, 12px);
9
+ padding-top: var(--space-2, 8px);
10
+ }
11
+ .ax-form__actions--end { justify-content: flex-end; }
12
+ .ax-form__actions--start { justify-content: flex-start; }
13
+ .ax-form__actions--between { justify-content: space-between; }
14
+ .ax-form__actions--full > * { flex: 1; }
15
+ .ax-form__actions--bordered {
16
+ border-top: 1px solid var(--border-default);
17
+ margin-top: var(--space-2, 8px); padding-top: var(--space-5, 20px);
18
+ }
19
+ `;
20
+ if (typeof document !== "undefined" && !document.getElementById("ax-form-css")) {
21
+ const s = document.createElement("style");
22
+ s.id = "ax-form-css";
23
+ s.textContent = AX_FORM_CSS;
24
+ document.head.appendChild(s);
25
+ }
26
+ function Form({ gap = "normal", onSubmit, children, className = "", ...rest }) {
27
+ const cls = [
28
+ "ax-form",
29
+ gap === "tight" ? "ax-form--tight" : gap === "loose" ? "ax-form--loose" : "",
30
+ className
31
+ ].filter(Boolean).join(" ");
32
+ return /* @__PURE__ */ jsx("form", { className: cls, onSubmit, noValidate: true, ...rest, children });
33
+ }
34
+ function FormActions({
35
+ align = "end",
36
+ bordered = false,
37
+ full = false,
38
+ children,
39
+ className = "",
40
+ ...rest
41
+ }) {
42
+ const cls = [
43
+ "ax-form__actions",
44
+ "ax-form__actions--" + align,
45
+ bordered ? "ax-form__actions--bordered" : "",
46
+ full ? "ax-form__actions--full" : "",
47
+ className
48
+ ].filter(Boolean).join(" ");
49
+ return /* @__PURE__ */ jsx("div", { className: cls, ...rest, children });
50
+ }
51
+ const isEmpty = (v) => v === void 0 || v === null || v === "" || Array.isArray(v) && v.length === 0;
52
+ async function evalRules(rules, value, values) {
53
+ if (!rules) return void 0;
54
+ const { required, minLength, maxLength, min, max, pattern, validate } = rules;
55
+ if (required && (isEmpty(value) || value === false)) {
56
+ return typeof required === "string" ? required : "This field is required.";
57
+ }
58
+ if (isEmpty(value)) return void 0;
59
+ const num = (r) => typeof r === "object" ? r : { value: r, message: void 0 };
60
+ if (minLength != null) {
61
+ const { value: n, message } = num(minLength);
62
+ if (String(value).length < n) return message || `Must be at least ${n} characters.`;
63
+ }
64
+ if (maxLength != null) {
65
+ const { value: n, message } = num(maxLength);
66
+ if (String(value).length > n) return message || `Must be at most ${n} characters.`;
67
+ }
68
+ if (min != null) {
69
+ const { value: n, message } = num(min);
70
+ if (Number(value) < n) return message || `Must be ${n} or more.`;
71
+ }
72
+ if (max != null) {
73
+ const { value: n, message } = num(max);
74
+ if (Number(value) > n) return message || `Must be ${n} or less.`;
75
+ }
76
+ if (pattern) {
77
+ const re = pattern instanceof RegExp ? pattern : pattern.value;
78
+ const message = pattern instanceof RegExp ? void 0 : pattern.message;
79
+ if (re && !re.test(String(value))) return message || "Invalid format.";
80
+ }
81
+ if (validate) {
82
+ const fns = typeof validate === "function" ? { _: validate } : validate;
83
+ for (const key of Object.keys(fns)) {
84
+ const res = await fns[key](value, values);
85
+ if (res === false) return "Invalid value.";
86
+ if (typeof res === "string") return res;
87
+ }
88
+ }
89
+ return void 0;
90
+ }
91
+ function useForm(config = {}) {
92
+ const {
93
+ initialValues = {},
94
+ defaultValues,
95
+ // RHF alias
96
+ validate,
97
+ // schema-style: (values) => ({ field: message }) | Promise
98
+ mode = "onSubmit",
99
+ // onSubmit | onBlur | onChange | onTouched | all
100
+ reValidateMode = "onChange",
101
+ // onChange | onBlur (after first submit)
102
+ onSubmit: onSubmitCfg,
103
+ onInvalid: onInvalidCfg,
104
+ shouldFocusError = true
105
+ } = config;
106
+ const defaults = defaultValues || initialValues;
107
+ const [values, setValuesState] = useState(defaults);
108
+ const [errors, setErrors] = useState({});
109
+ const [touched, setTouched] = useState({});
110
+ const [dirtyFields, setDirtyFields] = useState({});
111
+ const [isSubmitting, setSubmitting] = useState(false);
112
+ const [isSubmitted, setSubmitted] = useState(false);
113
+ const [isSubmitSuccessful, setSubmitSuccessful] = useState(false);
114
+ const [submitCount, setSubmitCount] = useState(0);
115
+ const [isValidating, setValidating] = useState(false);
116
+ const valuesRef = useRef(values);
117
+ valuesRef.current = values;
118
+ const errorsRef = useRef(errors);
119
+ errorsRef.current = errors;
120
+ const rulesRef = useRef({});
121
+ const defaultsRef = useRef(defaults);
122
+ const formElRef = useRef(null);
123
+ const writeValues = useCallback((updater) => {
124
+ setValuesState((prev) => {
125
+ const next = typeof updater === "function" ? updater(prev) : updater;
126
+ valuesRef.current = next;
127
+ return next;
128
+ });
129
+ }, []);
130
+ const runValidation = useCallback(
131
+ async (only) => {
132
+ const vals = valuesRef.current;
133
+ const out = {};
134
+ const names = only ? [only] : Object.keys(rulesRef.current);
135
+ for (const name of names) {
136
+ const msg = await evalRules(rulesRef.current[name], vals[name], vals);
137
+ if (msg) out[name] = msg;
138
+ }
139
+ if (validate) {
140
+ const schema = await validate(vals) || {};
141
+ for (const k of Object.keys(schema)) {
142
+ if (only && k !== only) continue;
143
+ if (!out[k] && schema[k]) out[k] = schema[k];
144
+ }
145
+ }
146
+ return out;
147
+ },
148
+ [validate]
149
+ );
150
+ const revalidateField = useCallback(
151
+ async (name) => {
152
+ setValidating(true);
153
+ const partial = await runValidation(name);
154
+ setErrors((prev) => {
155
+ const next = { ...prev };
156
+ if (partial[name]) next[name] = partial[name];
157
+ else delete next[name];
158
+ errorsRef.current = next;
159
+ return next;
160
+ });
161
+ setValidating(false);
162
+ },
163
+ [runValidation]
164
+ );
165
+ const shouldValidateOn = useCallback(
166
+ (event, name) => {
167
+ if (isSubmitted) return reValidateMode === event;
168
+ switch (mode) {
169
+ case "all":
170
+ return true;
171
+ case "onChange":
172
+ return event === "change";
173
+ case "onBlur":
174
+ return event === "blur";
175
+ case "onTouched":
176
+ return event === "blur" || event === "change" && touched[name];
177
+ case "onSubmit":
178
+ default:
179
+ return false;
180
+ }
181
+ },
182
+ [isSubmitted, reValidateMode, mode, touched]
183
+ );
184
+ const setValue = useCallback(
185
+ (name, value, opts = {}) => {
186
+ writeValues((prev) => ({ ...prev, [name]: value }));
187
+ if (opts.shouldDirty !== false) {
188
+ setDirtyFields((p) => defaultsRef.current[name] === value ? p : { ...p, [name]: true });
189
+ }
190
+ if (opts.shouldTouch) setTouched((p) => ({ ...p, [name]: true }));
191
+ if (opts.shouldValidate) revalidateField(name);
192
+ },
193
+ [writeValues, revalidateField]
194
+ );
195
+ const register = useCallback(
196
+ (name, rules) => {
197
+ if (rules) rulesRef.current[name] = rules;
198
+ else if (!(name in rulesRef.current)) rulesRef.current[name] = void 0;
199
+ const isCheckbox = rules && rules.type === "checkbox";
200
+ const onChange = (e) => {
201
+ const t = e && e.target;
202
+ const next = t ? t.type === "checkbox" ? t.checked : t.value : e;
203
+ writeValues((prev) => ({ ...prev, [name]: next }));
204
+ setDirtyFields((p) => ({ ...p, [name]: true }));
205
+ if (shouldValidateOn("change", name)) revalidateField(name);
206
+ };
207
+ const onBlur = () => {
208
+ setTouched((p) => ({ ...p, [name]: true }));
209
+ if (shouldValidateOn("blur", name)) revalidateField(name);
210
+ };
211
+ const base = { name, onChange, onBlur };
212
+ if (isCheckbox) base.checked = !!values[name];
213
+ else base.value = values[name] ?? "";
214
+ return base;
215
+ },
216
+ [values, writeValues, shouldValidateOn, revalidateField]
217
+ );
218
+ const field = useCallback(
219
+ (name, opts) => {
220
+ const props = register(name, opts);
221
+ props.error = errors[name];
222
+ return props;
223
+ },
224
+ [register, errors]
225
+ );
226
+ const trigger = useCallback(
227
+ async (name) => {
228
+ setValidating(true);
229
+ const errs = await runValidation(name);
230
+ if (name) {
231
+ setErrors((prev) => {
232
+ const next = { ...prev };
233
+ if (errs[name]) next[name] = errs[name];
234
+ else delete next[name];
235
+ errorsRef.current = next;
236
+ return next;
237
+ });
238
+ setValidating(false);
239
+ return !errs[name];
240
+ }
241
+ setErrors(errs);
242
+ errorsRef.current = errs;
243
+ setValidating(false);
244
+ return Object.keys(errs).length === 0;
245
+ },
246
+ [runValidation]
247
+ );
248
+ const setError = useCallback((name, message) => {
249
+ setErrors((prev) => {
250
+ const n = { ...prev, [name]: message };
251
+ errorsRef.current = n;
252
+ return n;
253
+ });
254
+ }, []);
255
+ const clearErrors = useCallback((name) => {
256
+ setErrors((prev) => {
257
+ if (!name) {
258
+ errorsRef.current = {};
259
+ return {};
260
+ }
261
+ const next = { ...prev };
262
+ delete next[name];
263
+ errorsRef.current = next;
264
+ return next;
265
+ });
266
+ }, []);
267
+ const getValues = useCallback((name) => name ? valuesRef.current[name] : valuesRef.current, []);
268
+ const watch = useCallback((name) => name ? values[name] : values, [values]);
269
+ const focusError = useCallback(
270
+ (errs) => {
271
+ if (!shouldFocusError || typeof document === "undefined") return;
272
+ const first = Object.keys(errs)[0];
273
+ if (!first) return;
274
+ const el = (formElRef.current || document).querySelector(`[name="${first}"]`);
275
+ if (el && el.focus) el.focus();
276
+ },
277
+ [shouldFocusError]
278
+ );
279
+ const handleSubmit = useCallback(
280
+ (onValidArg, onInvalidArg) => {
281
+ const runner = async (e) => {
282
+ if (e && e.preventDefault) e.preventDefault();
283
+ if (e && e.currentTarget && e.currentTarget.tagName === "FORM")
284
+ formElRef.current = e.currentTarget;
285
+ setSubmitting(true);
286
+ setValidating(true);
287
+ const errs = await runValidation();
288
+ setErrors(errs);
289
+ errorsRef.current = errs;
290
+ setTouched(() => {
291
+ const all = {};
292
+ [...Object.keys(rulesRef.current), ...Object.keys(valuesRef.current)].forEach(
293
+ (k) => all[k] = true
294
+ );
295
+ return all;
296
+ });
297
+ setSubmitted(true);
298
+ setSubmitCount((c) => c + 1);
299
+ setValidating(false);
300
+ const valid = Object.keys(errs).length === 0;
301
+ if (valid) {
302
+ try {
303
+ await (typeof onValidArg === "function" ? onValidArg : onSubmitCfg)?.(
304
+ valuesRef.current
305
+ );
306
+ setSubmitSuccessful(true);
307
+ } catch (err) {
308
+ setSubmitSuccessful(false);
309
+ throw err;
310
+ } finally {
311
+ setSubmitting(false);
312
+ }
313
+ } else {
314
+ setSubmitSuccessful(false);
315
+ setSubmitting(false);
316
+ (onInvalidArg || onInvalidCfg)?.(errs);
317
+ focusError(errs);
318
+ }
319
+ };
320
+ if (typeof onValidArg === "function") return runner;
321
+ return runner(onValidArg);
322
+ },
323
+ [runValidation, onSubmitCfg, onInvalidCfg, focusError]
324
+ );
325
+ const reset = useCallback(
326
+ (next, opts = {}) => {
327
+ const target = next || defaultsRef.current;
328
+ if (opts.keepDefaultValues !== true) defaultsRef.current = target;
329
+ writeValues(target);
330
+ if (!opts.keepErrors) {
331
+ setErrors({});
332
+ errorsRef.current = {};
333
+ }
334
+ if (!opts.keepTouched) setTouched({});
335
+ if (!opts.keepDirty) setDirtyFields({});
336
+ if (!opts.keepIsSubmitted) {
337
+ setSubmitted(false);
338
+ setSubmitSuccessful(false);
339
+ }
340
+ },
341
+ [writeValues]
342
+ );
343
+ const isDirty = Object.keys(dirtyFields).length > 0;
344
+ const isValid = Object.keys(errors).length === 0;
345
+ const formState = useMemo(
346
+ () => ({
347
+ errors,
348
+ touched,
349
+ dirtyFields,
350
+ isDirty,
351
+ isValid,
352
+ isValidating,
353
+ isSubmitting,
354
+ isSubmitted,
355
+ isSubmitSuccessful,
356
+ submitCount
357
+ }),
358
+ [
359
+ errors,
360
+ touched,
361
+ dirtyFields,
362
+ isDirty,
363
+ isValid,
364
+ isValidating,
365
+ isSubmitting,
366
+ isSubmitted,
367
+ isSubmitSuccessful,
368
+ submitCount
369
+ ]
370
+ );
371
+ return {
372
+ // state
373
+ values,
374
+ errors,
375
+ touched,
376
+ dirtyFields,
377
+ isDirty,
378
+ isValid,
379
+ isValidating,
380
+ isSubmitting,
381
+ isSubmitted,
382
+ isSubmitSuccessful,
383
+ submitCount,
384
+ submitted: isSubmitted,
385
+ formState,
386
+ // binding
387
+ register,
388
+ field,
389
+ // imperative
390
+ setValue,
391
+ setValues: writeValues,
392
+ getValues,
393
+ watch,
394
+ setError,
395
+ clearErrors,
396
+ trigger,
397
+ handleSubmit,
398
+ reset,
399
+ // internal-ish (handy for custom layouts)
400
+ _formElRef: formElRef
401
+ };
402
+ }
403
+ let _faKey = 0;
404
+ function useFieldArray({ form, name }) {
405
+ const keysRef = useRef([]);
406
+ const list = form.getValues(name) || [];
407
+ while (keysRef.current.length < list.length) keysRef.current.push(++_faKey);
408
+ if (keysRef.current.length > list.length) keysRef.current = keysRef.current.slice(0, list.length);
409
+ const fields = list.map((item, i) => ({ ...item, id: keysRef.current[i] }));
410
+ const commit = (next, keys) => {
411
+ keysRef.current = keys;
412
+ form.setValue(name, next, { shouldDirty: true });
413
+ };
414
+ const append = useCallback(
415
+ (item) => {
416
+ const cur = form.getValues(name) || [];
417
+ commit([...cur, item], [...keysRef.current, ++_faKey]);
418
+ },
419
+ [form, name]
420
+ );
421
+ const prepend = useCallback(
422
+ (item) => {
423
+ const cur = form.getValues(name) || [];
424
+ commit([item, ...cur], [++_faKey, ...keysRef.current]);
425
+ },
426
+ [form, name]
427
+ );
428
+ const remove = useCallback(
429
+ (index) => {
430
+ const cur = form.getValues(name) || [];
431
+ commit(
432
+ cur.filter((_, i) => i !== index),
433
+ keysRef.current.filter((_, i) => i !== index)
434
+ );
435
+ },
436
+ [form, name]
437
+ );
438
+ const insert = useCallback(
439
+ (index, item) => {
440
+ const cur = (form.getValues(name) || []).slice();
441
+ const keys = keysRef.current.slice();
442
+ cur.splice(index, 0, item);
443
+ keys.splice(index, 0, ++_faKey);
444
+ commit(cur, keys);
445
+ },
446
+ [form, name]
447
+ );
448
+ const move = useCallback(
449
+ (from, to) => {
450
+ const cur = (form.getValues(name) || []).slice();
451
+ const keys = keysRef.current.slice();
452
+ cur.splice(to, 0, cur.splice(from, 1)[0]);
453
+ keys.splice(to, 0, keys.splice(from, 1)[0]);
454
+ commit(cur, keys);
455
+ },
456
+ [form, name]
457
+ );
458
+ const replace = useCallback(
459
+ (items) => {
460
+ commit(
461
+ items,
462
+ items.map(() => ++_faKey)
463
+ );
464
+ },
465
+ [form, name]
466
+ );
467
+ const update = useCallback(
468
+ (index, item) => {
469
+ const cur = (form.getValues(name) || []).slice();
470
+ cur[index] = item;
471
+ commit(cur, keysRef.current.slice());
472
+ },
473
+ [form, name]
474
+ );
475
+ return { fields, append, prepend, remove, insert, move, replace, update };
476
+ }
477
+ Form.useForm = useForm;
478
+ Form.useFieldArray = useFieldArray;
479
+ Form.Actions = FormActions;
480
+ export {
481
+ Form,
482
+ FormActions
483
+ };
484
+ //# sourceMappingURL=Form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Form.js","sources":["../../../src/components/inputs/Form.jsx"],"sourcesContent":["import React, { useState, useCallback, useRef, useMemo } from \"react\";\n\nconst AX_FORM_CSS = `\n.ax-form { display: flex; flex-direction: column; gap: var(--space-5, 20px); }\n.ax-form--tight { gap: var(--space-3, 12px); }\n.ax-form--loose { gap: var(--space-6, 28px); }\n.ax-form__actions {\n display: flex; align-items: center; gap: var(--space-3, 12px);\n padding-top: var(--space-2, 8px);\n}\n.ax-form__actions--end { justify-content: flex-end; }\n.ax-form__actions--start { justify-content: flex-start; }\n.ax-form__actions--between { justify-content: space-between; }\n.ax-form__actions--full > * { flex: 1; }\n.ax-form__actions--bordered {\n border-top: 1px solid var(--border-default);\n margin-top: var(--space-2, 8px); padding-top: var(--space-5, 20px);\n}\n`;\n\nif (typeof document !== \"undefined\" && !document.getElementById(\"ax-form-css\")) {\n const s = document.createElement(\"style\");\n s.id = \"ax-form-css\";\n s.textContent = AX_FORM_CSS;\n document.head.appendChild(s);\n}\n\n/**\n * Form — a thin structural <form> wrapper. Pure layout, no state.\n * Pass form.handleSubmit (from Form.useForm) as onSubmit, then compose controls.\n */\nexport function Form({ gap = \"normal\", onSubmit, children, className = \"\", ...rest }) {\n const cls = [\n \"ax-form\",\n gap === \"tight\" ? \"ax-form--tight\" : gap === \"loose\" ? \"ax-form--loose\" : \"\",\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n return (\n <form className={cls} onSubmit={onSubmit} noValidate {...rest}>\n {children}\n </form>\n );\n}\n\n/** FormActions — right-aligned footer row for submit/cancel buttons. */\nexport function FormActions({\n align = \"end\",\n bordered = false,\n full = false,\n children,\n className = \"\",\n ...rest\n}) {\n const cls = [\n \"ax-form__actions\",\n \"ax-form__actions--\" + align,\n bordered ? \"ax-form__actions--bordered\" : \"\",\n full ? \"ax-form__actions--full\" : \"\",\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n return (\n <div className={cls} {...rest}>\n {children}\n </div>\n );\n}\n\n/* ============================================================\n useForm — production controlled form hook (RHF-aligned API).\n Exposed as Form.useForm. Drop it for react-hook-form / TanStack;\n the presentational components never depend on it.\n ============================================================ */\n\nconst isEmpty = (v) =>\n v === undefined || v === null || v === \"\" || (Array.isArray(v) && v.length === 0);\n\n// Evaluate one field's built-in rules. Returns a message string or undefined.\nasync function evalRules(rules, value, values) {\n if (!rules) return undefined;\n const { required, minLength, maxLength, min, max, pattern, validate } = rules;\n\n if (required && (isEmpty(value) || value === false)) {\n return typeof required === \"string\" ? required : \"This field is required.\";\n }\n if (isEmpty(value)) return undefined; // other rules don't apply to empty optional fields\n\n const num = (r) => (typeof r === \"object\" ? r : { value: r, message: undefined });\n\n if (minLength != null) {\n const { value: n, message } = num(minLength);\n if (String(value).length < n) return message || `Must be at least ${n} characters.`;\n }\n if (maxLength != null) {\n const { value: n, message } = num(maxLength);\n if (String(value).length > n) return message || `Must be at most ${n} characters.`;\n }\n if (min != null) {\n const { value: n, message } = num(min);\n if (Number(value) < n) return message || `Must be ${n} or more.`;\n }\n if (max != null) {\n const { value: n, message } = num(max);\n if (Number(value) > n) return message || `Must be ${n} or less.`;\n }\n if (pattern) {\n const re = pattern instanceof RegExp ? pattern : pattern.value;\n const message = pattern instanceof RegExp ? undefined : pattern.message;\n if (re && !re.test(String(value))) return message || \"Invalid format.\";\n }\n if (validate) {\n const fns = typeof validate === \"function\" ? { _: validate } : validate;\n for (const key of Object.keys(fns)) {\n const res = await fns[key](value, values);\n if (res === false) return \"Invalid value.\";\n if (typeof res === \"string\") return res;\n }\n }\n return undefined;\n}\n\nfunction useForm(config = {}) {\n const {\n initialValues = {},\n defaultValues, // RHF alias\n validate, // schema-style: (values) => ({ field: message }) | Promise\n mode = \"onSubmit\", // onSubmit | onBlur | onChange | onTouched | all\n reValidateMode = \"onChange\", // onChange | onBlur (after first submit)\n onSubmit: onSubmitCfg,\n onInvalid: onInvalidCfg,\n shouldFocusError = true,\n } = config;\n\n const defaults = defaultValues || initialValues;\n const [values, setValuesState] = useState(defaults);\n const [errors, setErrors] = useState({});\n const [touched, setTouched] = useState({});\n const [dirtyFields, setDirtyFields] = useState({});\n const [isSubmitting, setSubmitting] = useState(false);\n const [isSubmitted, setSubmitted] = useState(false);\n const [isSubmitSuccessful, setSubmitSuccessful] = useState(false);\n const [submitCount, setSubmitCount] = useState(0);\n const [isValidating, setValidating] = useState(false);\n\n const valuesRef = useRef(values);\n valuesRef.current = values;\n const errorsRef = useRef(errors);\n errorsRef.current = errors;\n const rulesRef = useRef({}); // name -> rules registered via register/field\n const defaultsRef = useRef(defaults);\n const formElRef = useRef(null);\n\n const writeValues = useCallback((updater) => {\n setValuesState((prev) => {\n const next = typeof updater === \"function\" ? updater(prev) : updater;\n valuesRef.current = next;\n return next;\n });\n }, []);\n\n // Validate the whole form (or a single field). Returns a fresh errors map.\n const runValidation = useCallback(\n async (only) => {\n const vals = valuesRef.current;\n const out = {};\n const names = only ? [only] : Object.keys(rulesRef.current);\n for (const name of names) {\n const msg = await evalRules(rulesRef.current[name], vals[name], vals);\n if (msg) out[name] = msg;\n }\n if (validate) {\n const schema = (await validate(vals)) || {};\n for (const k of Object.keys(schema)) {\n if (only && k !== only) continue;\n if (!out[k] && schema[k]) out[k] = schema[k];\n }\n }\n return out;\n },\n [validate],\n );\n\n // Recompute one field's error and merge into state (preserving others).\n const revalidateField = useCallback(\n async (name) => {\n setValidating(true);\n const partial = await runValidation(name);\n setErrors((prev) => {\n const next = { ...prev };\n if (partial[name]) next[name] = partial[name];\n else delete next[name];\n errorsRef.current = next;\n return next;\n });\n setValidating(false);\n },\n [runValidation],\n );\n\n const shouldValidateOn = useCallback(\n (event, name) => {\n if (isSubmitted) return reValidateMode === event;\n switch (mode) {\n case \"all\":\n return true;\n case \"onChange\":\n return event === \"change\";\n case \"onBlur\":\n return event === \"blur\";\n case \"onTouched\":\n return event === \"blur\" || (event === \"change\" && touched[name]);\n case \"onSubmit\":\n default:\n return false;\n }\n },\n [isSubmitted, reValidateMode, mode, touched],\n );\n\n const setValue = useCallback(\n (name, value, opts = {}) => {\n writeValues((prev) => ({ ...prev, [name]: value }));\n if (opts.shouldDirty !== false) {\n setDirtyFields((p) => (defaultsRef.current[name] === value ? p : { ...p, [name]: true }));\n }\n if (opts.shouldTouch) setTouched((p) => ({ ...p, [name]: true }));\n if (opts.shouldValidate) revalidateField(name);\n },\n [writeValues, revalidateField],\n );\n\n // Build the props for a control. `register` is RHF-compatible; `field` adds `error`.\n const register = useCallback(\n (name, rules) => {\n if (rules) rulesRef.current[name] = rules;\n else if (!(name in rulesRef.current)) rulesRef.current[name] = undefined;\n const isCheckbox = rules && rules.type === \"checkbox\";\n const onChange = (e) => {\n const t = e && e.target;\n const next = t ? (t.type === \"checkbox\" ? t.checked : t.value) : e;\n writeValues((prev) => ({ ...prev, [name]: next }));\n setDirtyFields((p) => ({ ...p, [name]: true }));\n if (shouldValidateOn(\"change\", name)) revalidateField(name);\n };\n const onBlur = () => {\n setTouched((p) => ({ ...p, [name]: true }));\n if (shouldValidateOn(\"blur\", name)) revalidateField(name);\n };\n const base = { name, onChange, onBlur };\n if (isCheckbox) base.checked = !!values[name];\n else base.value = values[name] ?? \"\";\n return base;\n },\n [values, writeValues, shouldValidateOn, revalidateField],\n );\n\n const field = useCallback(\n (name, opts) => {\n const props = register(name, opts);\n props.error = errors[name];\n return props;\n },\n [register, errors],\n );\n\n const trigger = useCallback(\n async (name) => {\n setValidating(true);\n const errs = await runValidation(name);\n if (name) {\n setErrors((prev) => {\n const next = { ...prev };\n if (errs[name]) next[name] = errs[name];\n else delete next[name];\n errorsRef.current = next;\n return next;\n });\n setValidating(false);\n return !errs[name];\n }\n setErrors(errs);\n errorsRef.current = errs;\n setValidating(false);\n return Object.keys(errs).length === 0;\n },\n [runValidation],\n );\n\n const setError = useCallback((name, message) => {\n setErrors((prev) => {\n const n = { ...prev, [name]: message };\n errorsRef.current = n;\n return n;\n });\n }, []);\n\n const clearErrors = useCallback((name) => {\n setErrors((prev) => {\n if (!name) {\n errorsRef.current = {};\n return {};\n }\n const next = { ...prev };\n delete next[name];\n errorsRef.current = next;\n return next;\n });\n }, []);\n\n const getValues = useCallback((name) => (name ? valuesRef.current[name] : valuesRef.current), []);\n const watch = useCallback((name) => (name ? values[name] : values), [values]);\n\n const focusError = useCallback(\n (errs) => {\n if (!shouldFocusError || typeof document === \"undefined\") return;\n const first = Object.keys(errs)[0];\n if (!first) return;\n const el = (formElRef.current || document).querySelector(`[name=\"${first}\"]`);\n if (el && el.focus) el.focus();\n },\n [shouldFocusError],\n );\n\n const handleSubmit = useCallback(\n (onValidArg, onInvalidArg) => {\n const runner = async (e) => {\n if (e && e.preventDefault) e.preventDefault();\n if (e && e.currentTarget && e.currentTarget.tagName === \"FORM\")\n formElRef.current = e.currentTarget;\n setSubmitting(true);\n setValidating(true);\n const errs = await runValidation();\n setErrors(errs);\n errorsRef.current = errs;\n setTouched(() => {\n const all = {};\n [...Object.keys(rulesRef.current), ...Object.keys(valuesRef.current)].forEach(\n (k) => (all[k] = true),\n );\n return all;\n });\n setSubmitted(true);\n setSubmitCount((c) => c + 1);\n setValidating(false);\n const valid = Object.keys(errs).length === 0;\n if (valid) {\n try {\n await (typeof onValidArg === \"function\" ? onValidArg : onSubmitCfg)?.(\n valuesRef.current,\n );\n setSubmitSuccessful(true);\n } catch (err) {\n setSubmitSuccessful(false);\n throw err;\n } finally {\n setSubmitting(false);\n }\n } else {\n setSubmitSuccessful(false);\n setSubmitting(false);\n (onInvalidArg || onInvalidCfg)?.(errs);\n focusError(errs);\n }\n };\n // Dual API: handleSubmit(onValid[, onInvalid]) returns a handler (RHF style);\n // handleSubmit(event) runs immediately (use as onSubmit/onClick directly).\n if (typeof onValidArg === \"function\") return runner;\n return runner(onValidArg);\n },\n [runValidation, onSubmitCfg, onInvalidCfg, focusError],\n );\n\n const reset = useCallback(\n (next, opts = {}) => {\n const target = next || defaultsRef.current;\n if (opts.keepDefaultValues !== true) defaultsRef.current = target;\n writeValues(target);\n if (!opts.keepErrors) {\n setErrors({});\n errorsRef.current = {};\n }\n if (!opts.keepTouched) setTouched({});\n if (!opts.keepDirty) setDirtyFields({});\n if (!opts.keepIsSubmitted) {\n setSubmitted(false);\n setSubmitSuccessful(false);\n }\n },\n [writeValues],\n );\n\n const isDirty = Object.keys(dirtyFields).length > 0;\n const isValid = Object.keys(errors).length === 0;\n\n const formState = useMemo(\n () => ({\n errors,\n touched,\n dirtyFields,\n isDirty,\n isValid,\n isValidating,\n isSubmitting,\n isSubmitted,\n isSubmitSuccessful,\n submitCount,\n }),\n [\n errors,\n touched,\n dirtyFields,\n isDirty,\n isValid,\n isValidating,\n isSubmitting,\n isSubmitted,\n isSubmitSuccessful,\n submitCount,\n ],\n );\n\n return {\n // state\n values,\n errors,\n touched,\n dirtyFields,\n isDirty,\n isValid,\n isValidating,\n isSubmitting,\n isSubmitted,\n isSubmitSuccessful,\n submitCount,\n submitted: isSubmitted,\n formState,\n // binding\n register,\n field,\n // imperative\n setValue,\n setValues: writeValues,\n getValues,\n watch,\n setError,\n clearErrors,\n trigger,\n handleSubmit,\n reset,\n // internal-ish (handy for custom layouts)\n _formElRef: formElRef,\n };\n}\n\n/* ============================================================\n useFieldArray — dynamic list fields, RHF-aligned.\n const ta = Form.useFieldArray({ form, name: \"members\" });\n ============================================================ */\nlet _faKey = 0;\nfunction useFieldArray({ form, name }) {\n const keysRef = useRef([]);\n const list = form.getValues(name) || [];\n // keep a stable key per row\n while (keysRef.current.length < list.length) keysRef.current.push(++_faKey);\n if (keysRef.current.length > list.length) keysRef.current = keysRef.current.slice(0, list.length);\n\n const fields = list.map((item, i) => ({ ...item, id: keysRef.current[i] }));\n\n const commit = (next, keys) => {\n keysRef.current = keys;\n form.setValue(name, next, { shouldDirty: true });\n };\n\n const append = useCallback(\n (item) => {\n const cur = form.getValues(name) || [];\n commit([...cur, item], [...keysRef.current, ++_faKey]);\n },\n [form, name],\n );\n const prepend = useCallback(\n (item) => {\n const cur = form.getValues(name) || [];\n commit([item, ...cur], [++_faKey, ...keysRef.current]);\n },\n [form, name],\n );\n const remove = useCallback(\n (index) => {\n const cur = form.getValues(name) || [];\n commit(\n cur.filter((_, i) => i !== index),\n keysRef.current.filter((_, i) => i !== index),\n );\n },\n [form, name],\n );\n const insert = useCallback(\n (index, item) => {\n const cur = (form.getValues(name) || []).slice();\n const keys = keysRef.current.slice();\n cur.splice(index, 0, item);\n keys.splice(index, 0, ++_faKey);\n commit(cur, keys);\n },\n [form, name],\n );\n const move = useCallback(\n (from, to) => {\n const cur = (form.getValues(name) || []).slice();\n const keys = keysRef.current.slice();\n cur.splice(to, 0, cur.splice(from, 1)[0]);\n keys.splice(to, 0, keys.splice(from, 1)[0]);\n commit(cur, keys);\n },\n [form, name],\n );\n const replace = useCallback(\n (items) => {\n commit(\n items,\n items.map(() => ++_faKey),\n );\n },\n [form, name],\n );\n const update = useCallback(\n (index, item) => {\n const cur = (form.getValues(name) || []).slice();\n cur[index] = item;\n commit(cur, keysRef.current.slice());\n },\n [form, name],\n );\n\n return { fields, append, prepend, remove, insert, move, replace, update };\n}\n\nForm.useForm = useForm;\nForm.useFieldArray = useFieldArray;\nForm.Actions = FormActions;\n"],"names":[],"mappings":";;AAEA,MAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkBpB,IAAI,OAAO,aAAa,eAAe,CAAC,SAAS,eAAe,aAAa,GAAG;AAC9E,QAAM,IAAI,SAAS,cAAc,OAAO;AACxC,IAAE,KAAK;AACP,IAAE,cAAc;AAChB,WAAS,KAAK,YAAY,CAAC;AAC7B;AAMO,SAAS,KAAK,EAAE,MAAM,UAAU,UAAU,UAAU,YAAY,IAAI,GAAG,QAAQ;AACpF,QAAM,MAAM;AAAA,IACV;AAAA,IACA,QAAQ,UAAU,mBAAmB,QAAQ,UAAU,mBAAmB;AAAA,IAC1E;AAAA,EAAA,EAEC,OAAO,OAAO,EACd,KAAK,GAAG;AACX,SACE,oBAAC,UAAK,WAAW,KAAK,UAAoB,YAAU,MAAE,GAAG,MACtD,SAAA,CACH;AAEJ;AAGO,SAAS,YAAY;AAAA,EAC1B,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,OAAO;AAAA,EACP;AAAA,EACA,YAAY;AAAA,EACZ,GAAG;AACL,GAAG;AACD,QAAM,MAAM;AAAA,IACV;AAAA,IACA,uBAAuB;AAAA,IACvB,WAAW,+BAA+B;AAAA,IAC1C,OAAO,2BAA2B;AAAA,IAClC;AAAA,EAAA,EAEC,OAAO,OAAO,EACd,KAAK,GAAG;AACX,6BACG,OAAA,EAAI,WAAW,KAAM,GAAG,MACtB,UACH;AAEJ;AAQA,MAAM,UAAU,CAAC,MACf,MAAM,UAAa,MAAM,QAAQ,MAAM,MAAO,MAAM,QAAQ,CAAC,KAAK,EAAE,WAAW;AAGjF,eAAe,UAAU,OAAO,OAAO,QAAQ;AAC7C,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,EAAE,UAAU,WAAW,WAAW,KAAK,KAAK,SAAS,aAAa;AAExE,MAAI,aAAa,QAAQ,KAAK,KAAK,UAAU,QAAQ;AACnD,WAAO,OAAO,aAAa,WAAW,WAAW;AAAA,EACnD;AACA,MAAI,QAAQ,KAAK,EAAG,QAAO;AAE3B,QAAM,MAAM,CAAC,MAAO,OAAO,MAAM,WAAW,IAAI,EAAE,OAAO,GAAG,SAAS,OAAA;AAErE,MAAI,aAAa,MAAM;AACrB,UAAM,EAAE,OAAO,GAAG,QAAA,IAAY,IAAI,SAAS;AAC3C,QAAI,OAAO,KAAK,EAAE,SAAS,EAAG,QAAO,WAAW,oBAAoB,CAAC;AAAA,EACvE;AACA,MAAI,aAAa,MAAM;AACrB,UAAM,EAAE,OAAO,GAAG,QAAA,IAAY,IAAI,SAAS;AAC3C,QAAI,OAAO,KAAK,EAAE,SAAS,EAAG,QAAO,WAAW,mBAAmB,CAAC;AAAA,EACtE;AACA,MAAI,OAAO,MAAM;AACf,UAAM,EAAE,OAAO,GAAG,QAAA,IAAY,IAAI,GAAG;AACrC,QAAI,OAAO,KAAK,IAAI,EAAG,QAAO,WAAW,WAAW,CAAC;AAAA,EACvD;AACA,MAAI,OAAO,MAAM;AACf,UAAM,EAAE,OAAO,GAAG,QAAA,IAAY,IAAI,GAAG;AACrC,QAAI,OAAO,KAAK,IAAI,EAAG,QAAO,WAAW,WAAW,CAAC;AAAA,EACvD;AACA,MAAI,SAAS;AACX,UAAM,KAAK,mBAAmB,SAAS,UAAU,QAAQ;AACzD,UAAM,UAAU,mBAAmB,SAAS,SAAY,QAAQ;AAChE,QAAI,MAAM,CAAC,GAAG,KAAK,OAAO,KAAK,CAAC,EAAG,QAAO,WAAW;AAAA,EACvD;AACA,MAAI,UAAU;AACZ,UAAM,MAAM,OAAO,aAAa,aAAa,EAAE,GAAG,aAAa;AAC/D,eAAW,OAAO,OAAO,KAAK,GAAG,GAAG;AAClC,YAAM,MAAM,MAAM,IAAI,GAAG,EAAE,OAAO,MAAM;AACxC,UAAI,QAAQ,MAAO,QAAO;AAC1B,UAAI,OAAO,QAAQ,SAAU,QAAO;AAAA,IACtC;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,QAAQ,SAAS,IAAI;AAC5B,QAAM;AAAA,IACJ,gBAAgB,CAAA;AAAA,IAChB;AAAA;AAAA,IACA;AAAA;AAAA,IACA,OAAO;AAAA;AAAA,IACP,iBAAiB;AAAA;AAAA,IACjB,UAAU;AAAA,IACV,WAAW;AAAA,IACX,mBAAmB;AAAA,EAAA,IACjB;AAEJ,QAAM,WAAW,iBAAiB;AAClC,QAAM,CAAC,QAAQ,cAAc,IAAI,SAAS,QAAQ;AAClD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,CAAA,CAAE;AACvC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,CAAA,CAAE;AACzC,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,CAAA,CAAE;AACjD,QAAM,CAAC,cAAc,aAAa,IAAI,SAAS,KAAK;AACpD,QAAM,CAAC,aAAa,YAAY,IAAI,SAAS,KAAK;AAClD,QAAM,CAAC,oBAAoB,mBAAmB,IAAI,SAAS,KAAK;AAChE,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,CAAC;AAChD,QAAM,CAAC,cAAc,aAAa,IAAI,SAAS,KAAK;AAEpD,QAAM,YAAY,OAAO,MAAM;AAC/B,YAAU,UAAU;AACpB,QAAM,YAAY,OAAO,MAAM;AAC/B,YAAU,UAAU;AACpB,QAAM,WAAW,OAAO,EAAE;AAC1B,QAAM,cAAc,OAAO,QAAQ;AACnC,QAAM,YAAY,OAAO,IAAI;AAE7B,QAAM,cAAc,YAAY,CAAC,YAAY;AAC3C,mBAAe,CAAC,SAAS;AACvB,YAAM,OAAO,OAAO,YAAY,aAAa,QAAQ,IAAI,IAAI;AAC7D,gBAAU,UAAU;AACpB,aAAO;AAAA,IACT,CAAC;AAAA,EACH,GAAG,CAAA,CAAE;AAGL,QAAM,gBAAgB;AAAA,IACpB,OAAO,SAAS;AACd,YAAM,OAAO,UAAU;AACvB,YAAM,MAAM,CAAA;AACZ,YAAM,QAAQ,OAAO,CAAC,IAAI,IAAI,OAAO,KAAK,SAAS,OAAO;AAC1D,iBAAW,QAAQ,OAAO;AACxB,cAAM,MAAM,MAAM,UAAU,SAAS,QAAQ,IAAI,GAAG,KAAK,IAAI,GAAG,IAAI;AACpE,YAAI,IAAK,KAAI,IAAI,IAAI;AAAA,MACvB;AACA,UAAI,UAAU;AACZ,cAAM,SAAU,MAAM,SAAS,IAAI,KAAM,CAAA;AACzC,mBAAW,KAAK,OAAO,KAAK,MAAM,GAAG;AACnC,cAAI,QAAQ,MAAM,KAAM;AACxB,cAAI,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,EAAG,KAAI,CAAC,IAAI,OAAO,CAAC;AAAA,QAC7C;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IACA,CAAC,QAAQ;AAAA,EAAA;AAIX,QAAM,kBAAkB;AAAA,IACtB,OAAO,SAAS;AACd,oBAAc,IAAI;AAClB,YAAM,UAAU,MAAM,cAAc,IAAI;AACxC,gBAAU,CAAC,SAAS;AAClB,cAAM,OAAO,EAAE,GAAG,KAAA;AAClB,YAAI,QAAQ,IAAI,QAAQ,IAAI,IAAI,QAAQ,IAAI;AAAA,YACvC,QAAO,KAAK,IAAI;AACrB,kBAAU,UAAU;AACpB,eAAO;AAAA,MACT,CAAC;AACD,oBAAc,KAAK;AAAA,IACrB;AAAA,IACA,CAAC,aAAa;AAAA,EAAA;AAGhB,QAAM,mBAAmB;AAAA,IACvB,CAAC,OAAO,SAAS;AACf,UAAI,oBAAoB,mBAAmB;AAC3C,cAAQ,MAAA;AAAA,QACN,KAAK;AACH,iBAAO;AAAA,QACT,KAAK;AACH,iBAAO,UAAU;AAAA,QACnB,KAAK;AACH,iBAAO,UAAU;AAAA,QACnB,KAAK;AACH,iBAAO,UAAU,UAAW,UAAU,YAAY,QAAQ,IAAI;AAAA,QAChE,KAAK;AAAA,QACL;AACE,iBAAO;AAAA,MAAA;AAAA,IAEb;AAAA,IACA,CAAC,aAAa,gBAAgB,MAAM,OAAO;AAAA,EAAA;AAG7C,QAAM,WAAW;AAAA,IACf,CAAC,MAAM,OAAO,OAAO,OAAO;AAC1B,kBAAY,CAAC,UAAU,EAAE,GAAG,MAAM,CAAC,IAAI,GAAG,MAAA,EAAQ;AAClD,UAAI,KAAK,gBAAgB,OAAO;AAC9B,uBAAe,CAAC,MAAO,YAAY,QAAQ,IAAI,MAAM,QAAQ,IAAI,EAAE,GAAG,GAAG,CAAC,IAAI,GAAG,MAAO;AAAA,MAC1F;AACA,UAAI,KAAK,YAAa,YAAW,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,IAAI,GAAG,KAAA,EAAO;AAChE,UAAI,KAAK,eAAgB,iBAAgB,IAAI;AAAA,IAC/C;AAAA,IACA,CAAC,aAAa,eAAe;AAAA,EAAA;AAI/B,QAAM,WAAW;AAAA,IACf,CAAC,MAAM,UAAU;AACf,UAAI,MAAO,UAAS,QAAQ,IAAI,IAAI;AAAA,eAC3B,EAAE,QAAQ,SAAS,SAAU,UAAS,QAAQ,IAAI,IAAI;AAC/D,YAAM,aAAa,SAAS,MAAM,SAAS;AAC3C,YAAM,WAAW,CAAC,MAAM;AACtB,cAAM,IAAI,KAAK,EAAE;AACjB,cAAM,OAAO,IAAK,EAAE,SAAS,aAAa,EAAE,UAAU,EAAE,QAAS;AACjE,oBAAY,CAAC,UAAU,EAAE,GAAG,MAAM,CAAC,IAAI,GAAG,KAAA,EAAO;AACjD,uBAAe,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,IAAI,GAAG,KAAA,EAAO;AAC9C,YAAI,iBAAiB,UAAU,IAAI,mBAAmB,IAAI;AAAA,MAC5D;AACA,YAAM,SAAS,MAAM;AACnB,mBAAW,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,IAAI,GAAG,KAAA,EAAO;AAC1C,YAAI,iBAAiB,QAAQ,IAAI,mBAAmB,IAAI;AAAA,MAC1D;AACA,YAAM,OAAO,EAAE,MAAM,UAAU,OAAA;AAC/B,UAAI,WAAY,MAAK,UAAU,CAAC,CAAC,OAAO,IAAI;AAAA,UACvC,MAAK,QAAQ,OAAO,IAAI,KAAK;AAClC,aAAO;AAAA,IACT;AAAA,IACA,CAAC,QAAQ,aAAa,kBAAkB,eAAe;AAAA,EAAA;AAGzD,QAAM,QAAQ;AAAA,IACZ,CAAC,MAAM,SAAS;AACd,YAAM,QAAQ,SAAS,MAAM,IAAI;AACjC,YAAM,QAAQ,OAAO,IAAI;AACzB,aAAO;AAAA,IACT;AAAA,IACA,CAAC,UAAU,MAAM;AAAA,EAAA;AAGnB,QAAM,UAAU;AAAA,IACd,OAAO,SAAS;AACd,oBAAc,IAAI;AAClB,YAAM,OAAO,MAAM,cAAc,IAAI;AACrC,UAAI,MAAM;AACR,kBAAU,CAAC,SAAS;AAClB,gBAAM,OAAO,EAAE,GAAG,KAAA;AAClB,cAAI,KAAK,IAAI,QAAQ,IAAI,IAAI,KAAK,IAAI;AAAA,cACjC,QAAO,KAAK,IAAI;AACrB,oBAAU,UAAU;AACpB,iBAAO;AAAA,QACT,CAAC;AACD,sBAAc,KAAK;AACnB,eAAO,CAAC,KAAK,IAAI;AAAA,MACnB;AACA,gBAAU,IAAI;AACd,gBAAU,UAAU;AACpB,oBAAc,KAAK;AACnB,aAAO,OAAO,KAAK,IAAI,EAAE,WAAW;AAAA,IACtC;AAAA,IACA,CAAC,aAAa;AAAA,EAAA;AAGhB,QAAM,WAAW,YAAY,CAAC,MAAM,YAAY;AAC9C,cAAU,CAAC,SAAS;AAClB,YAAM,IAAI,EAAE,GAAG,MAAM,CAAC,IAAI,GAAG,QAAA;AAC7B,gBAAU,UAAU;AACpB,aAAO;AAAA,IACT,CAAC;AAAA,EACH,GAAG,CAAA,CAAE;AAEL,QAAM,cAAc,YAAY,CAAC,SAAS;AACxC,cAAU,CAAC,SAAS;AAClB,UAAI,CAAC,MAAM;AACT,kBAAU,UAAU,CAAA;AACpB,eAAO,CAAA;AAAA,MACT;AACA,YAAM,OAAO,EAAE,GAAG,KAAA;AAClB,aAAO,KAAK,IAAI;AAChB,gBAAU,UAAU;AACpB,aAAO;AAAA,IACT,CAAC;AAAA,EACH,GAAG,CAAA,CAAE;AAEL,QAAM,YAAY,YAAY,CAAC,SAAU,OAAO,UAAU,QAAQ,IAAI,IAAI,UAAU,SAAU,CAAA,CAAE;AAChG,QAAM,QAAQ,YAAY,CAAC,SAAU,OAAO,OAAO,IAAI,IAAI,QAAS,CAAC,MAAM,CAAC;AAE5E,QAAM,aAAa;AAAA,IACjB,CAAC,SAAS;AACR,UAAI,CAAC,oBAAoB,OAAO,aAAa,YAAa;AAC1D,YAAM,QAAQ,OAAO,KAAK,IAAI,EAAE,CAAC;AACjC,UAAI,CAAC,MAAO;AACZ,YAAM,MAAM,UAAU,WAAW,UAAU,cAAc,UAAU,KAAK,IAAI;AAC5E,UAAI,MAAM,GAAG,MAAO,IAAG,MAAA;AAAA,IACzB;AAAA,IACA,CAAC,gBAAgB;AAAA,EAAA;AAGnB,QAAM,eAAe;AAAA,IACnB,CAAC,YAAY,iBAAiB;AAC5B,YAAM,SAAS,OAAO,MAAM;AAC1B,YAAI,KAAK,EAAE,eAAgB,GAAE,eAAA;AAC7B,YAAI,KAAK,EAAE,iBAAiB,EAAE,cAAc,YAAY;AACtD,oBAAU,UAAU,EAAE;AACxB,sBAAc,IAAI;AAClB,sBAAc,IAAI;AAClB,cAAM,OAAO,MAAM,cAAA;AACnB,kBAAU,IAAI;AACd,kBAAU,UAAU;AACpB,mBAAW,MAAM;AACf,gBAAM,MAAM,CAAA;AACZ,WAAC,GAAG,OAAO,KAAK,SAAS,OAAO,GAAG,GAAG,OAAO,KAAK,UAAU,OAAO,CAAC,EAAE;AAAA,YACpE,CAAC,MAAO,IAAI,CAAC,IAAI;AAAA,UAAA;AAEnB,iBAAO;AAAA,QACT,CAAC;AACD,qBAAa,IAAI;AACjB,uBAAe,CAAC,MAAM,IAAI,CAAC;AAC3B,sBAAc,KAAK;AACnB,cAAM,QAAQ,OAAO,KAAK,IAAI,EAAE,WAAW;AAC3C,YAAI,OAAO;AACT,cAAI;AACF,mBAAO,OAAO,eAAe,aAAa,aAAa;AAAA,cACrD,UAAU;AAAA,YAAA;AAEZ,gCAAoB,IAAI;AAAA,UAC1B,SAAS,KAAK;AACZ,gCAAoB,KAAK;AACzB,kBAAM;AAAA,UACR,UAAA;AACE,0BAAc,KAAK;AAAA,UACrB;AAAA,QACF,OAAO;AACL,8BAAoB,KAAK;AACzB,wBAAc,KAAK;AACnB,WAAC,gBAAgB,gBAAgB,IAAI;AACrC,qBAAW,IAAI;AAAA,QACjB;AAAA,MACF;AAGA,UAAI,OAAO,eAAe,WAAY,QAAO;AAC7C,aAAO,OAAO,UAAU;AAAA,IAC1B;AAAA,IACA,CAAC,eAAe,aAAa,cAAc,UAAU;AAAA,EAAA;AAGvD,QAAM,QAAQ;AAAA,IACZ,CAAC,MAAM,OAAO,OAAO;AACnB,YAAM,SAAS,QAAQ,YAAY;AACnC,UAAI,KAAK,sBAAsB,KAAM,aAAY,UAAU;AAC3D,kBAAY,MAAM;AAClB,UAAI,CAAC,KAAK,YAAY;AACpB,kBAAU,CAAA,CAAE;AACZ,kBAAU,UAAU,CAAA;AAAA,MACtB;AACA,UAAI,CAAC,KAAK,YAAa,YAAW,CAAA,CAAE;AACpC,UAAI,CAAC,KAAK,UAAW,gBAAe,CAAA,CAAE;AACtC,UAAI,CAAC,KAAK,iBAAiB;AACzB,qBAAa,KAAK;AAClB,4BAAoB,KAAK;AAAA,MAC3B;AAAA,IACF;AAAA,IACA,CAAC,WAAW;AAAA,EAAA;AAGd,QAAM,UAAU,OAAO,KAAK,WAAW,EAAE,SAAS;AAClD,QAAM,UAAU,OAAO,KAAK,MAAM,EAAE,WAAW;AAE/C,QAAM,YAAY;AAAA,IAChB,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,IAEF;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EACF;AAGF,SAAO;AAAA;AAAA,IAEL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX;AAAA;AAAA,IAEA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAEA,YAAY;AAAA,EAAA;AAEhB;AAMA,IAAI,SAAS;AACb,SAAS,cAAc,EAAE,MAAM,QAAQ;AACrC,QAAM,UAAU,OAAO,EAAE;AACzB,QAAM,OAAO,KAAK,UAAU,IAAI,KAAK,CAAA;AAErC,SAAO,QAAQ,QAAQ,SAAS,KAAK,OAAQ,SAAQ,QAAQ,KAAK,EAAE,MAAM;AAC1E,MAAI,QAAQ,QAAQ,SAAS,KAAK,OAAQ,SAAQ,UAAU,QAAQ,QAAQ,MAAM,GAAG,KAAK,MAAM;AAEhG,QAAM,SAAS,KAAK,IAAI,CAAC,MAAM,OAAO,EAAE,GAAG,MAAM,IAAI,QAAQ,QAAQ,CAAC,IAAI;AAE1E,QAAM,SAAS,CAAC,MAAM,SAAS;AAC7B,YAAQ,UAAU;AAClB,SAAK,SAAS,MAAM,MAAM,EAAE,aAAa,MAAM;AAAA,EACjD;AAEA,QAAM,SAAS;AAAA,IACb,CAAC,SAAS;AACR,YAAM,MAAM,KAAK,UAAU,IAAI,KAAK,CAAA;AACpC,aAAO,CAAC,GAAG,KAAK,IAAI,GAAG,CAAC,GAAG,QAAQ,SAAS,EAAE,MAAM,CAAC;AAAA,IACvD;AAAA,IACA,CAAC,MAAM,IAAI;AAAA,EAAA;AAEb,QAAM,UAAU;AAAA,IACd,CAAC,SAAS;AACR,YAAM,MAAM,KAAK,UAAU,IAAI,KAAK,CAAA;AACpC,aAAO,CAAC,MAAM,GAAG,GAAG,GAAG,CAAC,EAAE,QAAQ,GAAG,QAAQ,OAAO,CAAC;AAAA,IACvD;AAAA,IACA,CAAC,MAAM,IAAI;AAAA,EAAA;AAEb,QAAM,SAAS;AAAA,IACb,CAAC,UAAU;AACT,YAAM,MAAM,KAAK,UAAU,IAAI,KAAK,CAAA;AACpC;AAAA,QACE,IAAI,OAAO,CAAC,GAAG,MAAM,MAAM,KAAK;AAAA,QAChC,QAAQ,QAAQ,OAAO,CAAC,GAAG,MAAM,MAAM,KAAK;AAAA,MAAA;AAAA,IAEhD;AAAA,IACA,CAAC,MAAM,IAAI;AAAA,EAAA;AAEb,QAAM,SAAS;AAAA,IACb,CAAC,OAAO,SAAS;AACf,YAAM,OAAO,KAAK,UAAU,IAAI,KAAK,CAAA,GAAI,MAAA;AACzC,YAAM,OAAO,QAAQ,QAAQ,MAAA;AAC7B,UAAI,OAAO,OAAO,GAAG,IAAI;AACzB,WAAK,OAAO,OAAO,GAAG,EAAE,MAAM;AAC9B,aAAO,KAAK,IAAI;AAAA,IAClB;AAAA,IACA,CAAC,MAAM,IAAI;AAAA,EAAA;AAEb,QAAM,OAAO;AAAA,IACX,CAAC,MAAM,OAAO;AACZ,YAAM,OAAO,KAAK,UAAU,IAAI,KAAK,CAAA,GAAI,MAAA;AACzC,YAAM,OAAO,QAAQ,QAAQ,MAAA;AAC7B,UAAI,OAAO,IAAI,GAAG,IAAI,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC;AACxC,WAAK,OAAO,IAAI,GAAG,KAAK,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC;AAC1C,aAAO,KAAK,IAAI;AAAA,IAClB;AAAA,IACA,CAAC,MAAM,IAAI;AAAA,EAAA;AAEb,QAAM,UAAU;AAAA,IACd,CAAC,UAAU;AACT;AAAA,QACE;AAAA,QACA,MAAM,IAAI,MAAM,EAAE,MAAM;AAAA,MAAA;AAAA,IAE5B;AAAA,IACA,CAAC,MAAM,IAAI;AAAA,EAAA;AAEb,QAAM,SAAS;AAAA,IACb,CAAC,OAAO,SAAS;AACf,YAAM,OAAO,KAAK,UAAU,IAAI,KAAK,CAAA,GAAI,MAAA;AACzC,UAAI,KAAK,IAAI;AACb,aAAO,KAAK,QAAQ,QAAQ,MAAA,CAAO;AAAA,IACrC;AAAA,IACA,CAAC,MAAM,IAAI;AAAA,EAAA;AAGb,SAAO,EAAE,QAAQ,QAAQ,SAAS,QAAQ,QAAQ,MAAM,SAAS,OAAA;AACnE;AAEA,KAAK,UAAU;AACf,KAAK,gBAAgB;AACrB,KAAK,UAAU;"}
@@ -8,6 +8,8 @@ export interface InputProps {
8
8
  hint?: string;
9
9
  /** Error message; also paints the border red. */
10
10
  error?: string;
11
+ /** Marks the field required: appends a red asterisk to the label and sets the input's required attribute. @default false */
12
+ required?: boolean;
11
13
  /** Use the mono font for the value (ids, keys, code). @default false */
12
14
  mono?: boolean;
13
15
  placeholder?: string;
@@ -3,10 +3,12 @@ import "react";
3
3
  const AX_FIELD_CSS = `
4
4
  .ax-field { display: flex; flex-direction: column; gap: 6px; }
5
5
  .ax-field__label {
6
+ display: inline-flex; align-items: center; gap: 6px;
6
7
  font-family: var(--font-mono); font-size: var(--text-xs);
7
8
  font-weight: var(--weight-medium); letter-spacing: var(--tracking-label);
8
9
  text-transform: uppercase; color: var(--text-faint);
9
10
  }
11
+ .ax-field__req { color: var(--danger); }
10
12
  /* State priority is declared explicitly via layer order, low -> high.
11
13
  This frees each state rule from fighting specificity, so no :not() chains
12
14
  are needed and source order is no longer load-bearing. Add a new state by
@@ -31,8 +33,11 @@ const AX_FIELD_CSS = `
31
33
  .ax-input:focus { outline: none; border-color: var(--fg-2); box-shadow: 0 0 0 3px var(--focus-soft); }
32
34
  }
33
35
  @layer ax-error {
34
- .ax-input--error { border-color: var(--danger); }
35
- .ax-input--error:focus { box-shadow: 0 0 0 3px var(--danger-dim); }
36
+ /* Outline (not just border-color) so the error ring is robust: it survives
37
+ overlay/instrumentation layers that repaint control borders, and never
38
+ shifts layout. Offset -1px parks it exactly on the border edge. */
39
+ .ax-input--error { border-color: var(--danger); outline: 1px solid var(--danger); outline-offset: -1px; }
40
+ .ax-input--error:focus { box-shadow: 0 0 0 3px var(--danger-dim); outline-color: var(--danger); }
36
41
  }
37
42
  @layer ax-disabled {
38
43
  .ax-input:disabled { opacity: 0.4; cursor: not-allowed; border-color: var(--border-default); }
@@ -50,19 +55,18 @@ function Input({
50
55
  label,
51
56
  hint,
52
57
  error,
58
+ required = false,
53
59
  mono = false,
54
60
  className = "",
55
61
  ...rest
56
62
  }) {
57
- const cls = [
58
- "ax-input",
59
- mono ? "ax-input--mono" : "",
60
- error ? "ax-input--error" : "",
61
- className
62
- ].filter(Boolean).join(" ");
63
+ const cls = ["ax-input", mono ? "ax-input--mono" : "", error ? "ax-input--error" : "", className].filter(Boolean).join(" ");
63
64
  return /* @__PURE__ */ jsxs("label", { className: "ax-field", children: [
64
- label ? /* @__PURE__ */ jsx("span", { className: "ax-field__label", children: label }) : null,
65
- /* @__PURE__ */ jsx("input", { className: cls, ...rest }),
65
+ label ? /* @__PURE__ */ jsxs("span", { className: "ax-field__label", children: [
66
+ label,
67
+ required ? /* @__PURE__ */ jsx("span", { className: "ax-field__req", children: "*" }) : null
68
+ ] }) : null,
69
+ /* @__PURE__ */ jsx("input", { className: cls, required, ...rest }),
66
70
  error ? /* @__PURE__ */ jsx("span", { className: "ax-field__hint ax-field__hint--error", children: error }) : hint ? /* @__PURE__ */ jsx("span", { className: "ax-field__hint", children: hint }) : null
67
71
  ] });
68
72
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Input.js","sources":["../../../src/components/inputs/Input.jsx"],"sourcesContent":["import React from \"react\";\n\nconst AX_FIELD_CSS = `\n.ax-field { display: flex; flex-direction: column; gap: 6px; }\n.ax-field__label {\n font-family: var(--font-mono); font-size: var(--text-xs);\n font-weight: var(--weight-medium); letter-spacing: var(--tracking-label);\n text-transform: uppercase; color: var(--text-faint);\n}\n/* State priority is declared explicitly via layer order, low -> high.\n This frees each state rule from fighting specificity, so no :not() chains\n are needed and source order is no longer load-bearing. Add a new state by\n slotting a layer, not by patching every other rule. */\n@layer ax-base, ax-hover, ax-focus, ax-error, ax-disabled;\n\n@layer ax-base {\n .ax-input {\n height: 36px; padding: 0 12px; width: 100%;\n background: var(--surface-card); color: var(--text-body);\n border: 1px solid var(--border-default); border-radius: var(--radius-2);\n font-family: var(--font-body); font-size: var(--text-sm);\n transition: border-color var(--dur-1) var(--ease-out), box-shadow var(--dur-1) var(--ease-out), background var(--dur-1) var(--ease-out);\n }\n .ax-input::placeholder { color: var(--text-faint); }\n .ax-input--mono { font-family: var(--font-mono); }\n}\n@layer ax-hover {\n .ax-input:hover { border-color: var(--border-strong); }\n}\n@layer ax-focus {\n .ax-input:focus { outline: none; border-color: var(--fg-2); box-shadow: 0 0 0 3px var(--focus-soft); }\n}\n@layer ax-error {\n .ax-input--error { border-color: var(--danger); }\n .ax-input--error:focus { box-shadow: 0 0 0 3px var(--danger-dim); }\n}\n@layer ax-disabled {\n .ax-input:disabled { opacity: 0.4; cursor: not-allowed; border-color: var(--border-default); }\n}\n.ax-field__hint { font-size: var(--text-xs); color: var(--text-faint); }\n.ax-field__hint--error { color: var(--danger); }\n`;\n\nif (typeof document !== \"undefined\" && !document.getElementById(\"ax-field-css\")) {\n const s = document.createElement(\"style\");\n s.id = \"ax-field-css\";\n s.textContent = AX_FIELD_CSS;\n document.head.appendChild(s);\n}\n\nexport function Input({\n label,\n hint,\n error,\n mono = false,\n className = \"\",\n ...rest\n}) {\n const cls = [\n \"ax-input\",\n mono ? \"ax-input--mono\" : \"\",\n error ? \"ax-input--error\" : \"\",\n className,\n ].filter(Boolean).join(\" \");\n return (\n <label className=\"ax-field\">\n {label ? <span className=\"ax-field__label\">{label}</span> : null}\n <input className={cls} {...rest} />\n {error ? <span className=\"ax-field__hint ax-field__hint--error\">{error}</span> :\n hint ? <span className=\"ax-field__hint\">{hint}</span> : null}\n </label>\n );\n}\n"],"names":[],"mappings":";;AAEA,MAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyCrB,IAAI,OAAO,aAAa,eAAe,CAAC,SAAS,eAAe,cAAc,GAAG;AAC/E,QAAM,IAAI,SAAS,cAAc,OAAO;AACxC,IAAE,KAAK;AACP,IAAE,cAAc;AAChB,WAAS,KAAK,YAAY,CAAC;AAC7B;AAEO,SAAS,MAAM;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,GAAG;AACL,GAAG;AACD,QAAM,MAAM;AAAA,IACV;AAAA,IACA,OAAO,mBAAmB;AAAA,IAC1B,QAAQ,oBAAoB;AAAA,IAC5B;AAAA,EAAA,EACA,OAAO,OAAO,EAAE,KAAK,GAAG;AAC1B,SACE,qBAAC,SAAA,EAAM,WAAU,YACd,UAAA;AAAA,IAAA,QAAQ,oBAAC,QAAA,EAAK,WAAU,mBAAmB,iBAAM,IAAU;AAAA,IAC5D,oBAAC,SAAA,EAAM,WAAW,KAAM,GAAG,KAAA,CAAM;AAAA,IAChC,QAAQ,oBAAC,QAAA,EAAK,WAAU,wCAAwC,UAAA,MAAA,CAAM,IACrE,OAAO,oBAAC,QAAA,EAAK,WAAU,kBAAkB,gBAAK,IAAU;AAAA,EAAA,GAC5D;AAEJ;"}
1
+ {"version":3,"file":"Input.js","sources":["../../../src/components/inputs/Input.jsx"],"sourcesContent":["import React from \"react\";\n\nconst AX_FIELD_CSS = `\n.ax-field { display: flex; flex-direction: column; gap: 6px; }\n.ax-field__label {\n display: inline-flex; align-items: center; gap: 6px;\n font-family: var(--font-mono); font-size: var(--text-xs);\n font-weight: var(--weight-medium); letter-spacing: var(--tracking-label);\n text-transform: uppercase; color: var(--text-faint);\n}\n.ax-field__req { color: var(--danger); }\n/* State priority is declared explicitly via layer order, low -> high.\n This frees each state rule from fighting specificity, so no :not() chains\n are needed and source order is no longer load-bearing. Add a new state by\n slotting a layer, not by patching every other rule. */\n@layer ax-base, ax-hover, ax-focus, ax-error, ax-disabled;\n\n@layer ax-base {\n .ax-input {\n height: 36px; padding: 0 12px; width: 100%;\n background: var(--surface-card); color: var(--text-body);\n border: 1px solid var(--border-default); border-radius: var(--radius-2);\n font-family: var(--font-body); font-size: var(--text-sm);\n transition: border-color var(--dur-1) var(--ease-out), box-shadow var(--dur-1) var(--ease-out), background var(--dur-1) var(--ease-out);\n }\n .ax-input::placeholder { color: var(--text-faint); }\n .ax-input--mono { font-family: var(--font-mono); }\n}\n@layer ax-hover {\n .ax-input:hover { border-color: var(--border-strong); }\n}\n@layer ax-focus {\n .ax-input:focus { outline: none; border-color: var(--fg-2); box-shadow: 0 0 0 3px var(--focus-soft); }\n}\n@layer ax-error {\n /* Outline (not just border-color) so the error ring is robust: it survives\n overlay/instrumentation layers that repaint control borders, and never\n shifts layout. Offset -1px parks it exactly on the border edge. */\n .ax-input--error { border-color: var(--danger); outline: 1px solid var(--danger); outline-offset: -1px; }\n .ax-input--error:focus { box-shadow: 0 0 0 3px var(--danger-dim); outline-color: var(--danger); }\n}\n@layer ax-disabled {\n .ax-input:disabled { opacity: 0.4; cursor: not-allowed; border-color: var(--border-default); }\n}\n.ax-field__hint { font-size: var(--text-xs); color: var(--text-faint); }\n.ax-field__hint--error { color: var(--danger); }\n`;\n\nif (typeof document !== \"undefined\" && !document.getElementById(\"ax-field-css\")) {\n const s = document.createElement(\"style\");\n s.id = \"ax-field-css\";\n s.textContent = AX_FIELD_CSS;\n document.head.appendChild(s);\n}\n\nexport function Input({\n label,\n hint,\n error,\n required = false,\n mono = false,\n className = \"\",\n ...rest\n}) {\n const cls = [\"ax-input\", mono ? \"ax-input--mono\" : \"\", error ? \"ax-input--error\" : \"\", className]\n .filter(Boolean)\n .join(\" \");\n return (\n <label className=\"ax-field\">\n {label ? (\n <span className=\"ax-field__label\">\n {label}\n {required ? <span className=\"ax-field__req\">*</span> : null}\n </span>\n ) : null}\n <input className={cls} required={required} {...rest} />\n {error ? (\n <span className=\"ax-field__hint ax-field__hint--error\">{error}</span>\n ) : hint ? (\n <span className=\"ax-field__hint\">{hint}</span>\n ) : null}\n </label>\n );\n}\n"],"names":[],"mappings":";;AAEA,MAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8CrB,IAAI,OAAO,aAAa,eAAe,CAAC,SAAS,eAAe,cAAc,GAAG;AAC/E,QAAM,IAAI,SAAS,cAAc,OAAO;AACxC,IAAE,KAAK;AACP,IAAE,cAAc;AAChB,WAAS,KAAK,YAAY,CAAC;AAC7B;AAEO,SAAS,MAAM;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,GAAG;AACL,GAAG;AACD,QAAM,MAAM,CAAC,YAAY,OAAO,mBAAmB,IAAI,QAAQ,oBAAoB,IAAI,SAAS,EAC7F,OAAO,OAAO,EACd,KAAK,GAAG;AACX,SACE,qBAAC,SAAA,EAAM,WAAU,YACd,UAAA;AAAA,IAAA,QACC,qBAAC,QAAA,EAAK,WAAU,mBACb,UAAA;AAAA,MAAA;AAAA,MACA,WAAW,oBAAC,QAAA,EAAK,WAAU,iBAAgB,eAAC,IAAU;AAAA,IAAA,EAAA,CACzD,IACE;AAAA,wBACH,SAAA,EAAM,WAAW,KAAK,UAAqB,GAAG,MAAM;AAAA,IACpD,QACC,oBAAC,QAAA,EAAK,WAAU,wCAAwC,UAAA,MAAA,CAAM,IAC5D,OACF,oBAAC,QAAA,EAAK,WAAU,kBAAkB,gBAAK,IACrC;AAAA,EAAA,GACN;AAEJ;"}
package/dist/index.d.ts CHANGED
@@ -76,6 +76,7 @@ export * from "./components/inputs/Checkbox";
76
76
  export * from "./components/inputs/Combobox";
77
77
  export * from "./components/inputs/DatePicker";
78
78
  export * from "./components/inputs/Field";
79
+ export * from "./components/inputs/Form";
79
80
  export * from "./components/inputs/Input";
80
81
  export * from "./components/inputs/InputGroup";
81
82
  export * from "./components/inputs/InputOTP";
package/dist/index.js CHANGED
@@ -61,6 +61,7 @@ import { Checkbox } from "./components/inputs/Checkbox.js";
61
61
  import { Combobox } from "./components/inputs/Combobox.js";
62
62
  import { DatePicker } from "./components/inputs/DatePicker.js";
63
63
  import { Field, FieldGroup } from "./components/inputs/Field.js";
64
+ import { Form, FormActions } from "./components/inputs/Form.js";
64
65
  import { Input } from "./components/inputs/Input.js";
65
66
  import { InputGroup } from "./components/inputs/InputGroup.js";
66
67
  import { InputOTP } from "./components/inputs/InputOTP.js";
@@ -139,6 +140,8 @@ export {
139
140
  FieldGroup,
140
141
  FileTree,
141
142
  Flow,
143
+ Form,
144
+ FormActions,
142
145
  HoverCard,
143
146
  IconButton,
144
147
  Image,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentaily/design-system",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Agentaily design system — dark-first monochrome React component library (116 components) + Storybook.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",