@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 +4 -2
- package/README.md +5 -1
- package/dist/components/inputs/Form.d.ts +164 -0
- package/dist/components/inputs/Form.js +484 -0
- package/dist/components/inputs/Form.js.map +1 -0
- package/dist/components/inputs/Input.d.ts +2 -0
- package/dist/components/inputs/Input.js +14 -10
- package/dist/components/inputs/Input.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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) — **
|
|
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
|
-
|
|
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":"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"}
|
|
@@ -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
|
-
|
|
35
|
-
|
|
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__ */
|
|
65
|
-
|
|
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 = [\
|
|
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