@timeax/form-palette 0.0.24 → 0.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +347 -340
- package/dist/index.d.mts +23 -8
- package/dist/index.d.ts +23 -8
- package/dist/index.js +1219 -36890
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1216 -36890
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/index.css +0 -1379
- package/dist/index.css.map +0 -1
package/Readme.md
CHANGED
|
@@ -1,428 +1,435 @@
|
|
|
1
|
-
### Form Palette:
|
|
1
|
+
### Form Palette: Build Forms with InputField (Consumer Guide)
|
|
2
2
|
|
|
3
|
-
This guide
|
|
3
|
+
This guide shows how to build forms with @timeax/form-palette. It focuses on InputField usage, variants, and practical recipes taken from the playground’s App.tsx.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- Core runtime and hooks: `src/core/*`
|
|
7
|
-
- Adapters schema and registry: `src/schema/adapter.ts`, `src/core/adapter-registry.ts`, `src/adapters/*`
|
|
8
|
-
- Field and input composition: `src/schema/field.ts`, `src/input/input-field.tsx`
|
|
9
|
-
- Variant typing and registry: `src/schema/variant.ts`, `src/variants/*`, `src/presets/shadcn-variants/*`
|
|
5
|
+
If you just want to build forms, start here. Internals and source file locations are intentionally de‑emphasized.
|
|
10
6
|
|
|
11
7
|
---
|
|
12
8
|
|
|
13
|
-
###
|
|
9
|
+
### Quick start
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
The core coordinates form state, validation, submission, and UI control state. It exposes a `CoreContext` you access through hooks, and it manages fields and buttons that register into the form lifecycle.
|
|
17
|
-
|
|
18
|
-
Primary entry points:
|
|
19
|
-
- `CoreProvider` — wraps a form subtree with a live core instance.
|
|
20
|
-
- `useCore()` / `useCoreContext()` — access the `CoreContext` API.
|
|
21
|
-
- `useField()` — register a field with the core and get a typed, ergonomic API for value, errors, disabled/readOnly, and validation.
|
|
22
|
-
- `input/input-field.tsx` — high-level field UI composition that binds a variant (visual control) to core state with field layout.
|
|
23
|
-
|
|
24
|
-
Important types:
|
|
25
|
-
- `CoreContext` in `src/schema/core.ts` — defines the public form API.
|
|
26
|
-
- `Field` in `src/schema/field.ts` — runtime representation of a single form field.
|
|
11
|
+
Install (from npm):
|
|
27
12
|
|
|
13
|
+
```bash
|
|
14
|
+
npm install @timeax/form-palette
|
|
15
|
+
```
|
|
28
16
|
|
|
29
|
-
|
|
30
|
-
- React context is declared in `src/core/context.ts` as `CoreContextReact`.
|
|
31
|
-
- You must wrap your form with `CoreProvider` (see `src/core/core-provider.tsx`). Consumers then call `useCore()` or `useCoreContext()`.
|
|
17
|
+
Minimal form:
|
|
32
18
|
|
|
33
|
-
Minimal setup:
|
|
34
19
|
```tsx
|
|
35
|
-
import {
|
|
36
|
-
|
|
20
|
+
import { Form, InputField } from "@timeax/form-palette";
|
|
21
|
+
|
|
22
|
+
export default function Example() {
|
|
23
|
+
function onSubmit(e: any) {
|
|
24
|
+
// e.form gives you programmatic access
|
|
25
|
+
// e.formData is the values snapshot
|
|
26
|
+
console.log("Submitted", e.formData);
|
|
27
|
+
}
|
|
37
28
|
|
|
38
|
-
export function MyForm() {
|
|
39
29
|
return (
|
|
40
|
-
<
|
|
41
|
-
<
|
|
42
|
-
|
|
30
|
+
<Form wrapped gap={12} onSubmit={onSubmit}>
|
|
31
|
+
<InputField name="email" label="Email" variant="text" required />
|
|
32
|
+
<InputField name="password" label="Password" variant="password" required />
|
|
33
|
+
<button type="submit">Submit</button>
|
|
34
|
+
</Form>
|
|
43
35
|
);
|
|
44
36
|
}
|
|
45
|
-
|
|
46
|
-
function Inner() {
|
|
47
|
-
const core = useCore();
|
|
48
|
-
// core.values(), core.submit(), etc.
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
37
|
```
|
|
52
38
|
|
|
39
|
+
---
|
|
53
40
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
- `value`, `setValue(next, variant?)`
|
|
72
|
-
- `error`, `setError(message)`
|
|
73
|
-
- `required`, `disabled`, `readOnly` and their setters
|
|
74
|
-
- `loading`, `setLoading(loading)`
|
|
75
|
-
- identity and binding metadata (`name`, `bindId`, `bind`, `shared`, `alias`, `groupId`, `main`, `ignore`)
|
|
76
|
-
- `validate(report?)` delegating to field-level validation when provided
|
|
77
|
-
- Coordinates with core-level `onChange` and per-field `onChange` based on core props such as `changeBefore` (see around lines 431–452 of `use-field.ts`).
|
|
78
|
-
- Ensures the core can toggle the active submit button’s disabled state (`form.controlButton()`).
|
|
79
|
-
|
|
80
|
-
Validation at field-level:
|
|
81
|
-
- `UseFieldOptions<T>['validate']` returns `true | false | string` (or array in some higher-level paths).
|
|
82
|
-
- Any returned message(s) are normalized for display.
|
|
83
|
-
|
|
84
|
-
Identity and binding (`UseFieldOptions`):
|
|
85
|
-
- `name?: string` — primary key in values and error bags.
|
|
86
|
-
- `bindId?: string` / `bind?: string` — internal/external binding identifiers; used for advanced grouping and data flows.
|
|
87
|
-
- `shared?: string` — nest values under a shared namespace (e.g., `profile.first_name`).
|
|
88
|
-
- `groupId?: string`, `main?: boolean` — group controls (e.g., radios) and indicate a primary member.
|
|
89
|
-
- `alias?: string` — map errors/names to a different label when desired.
|
|
90
|
-
- `ignore?: boolean` — participate in registry without contributing to snapshots/validation when ignored.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
#### 1.5 Input composition: `input/input-field.tsx`
|
|
94
|
-
`InputField` is a rich compositor that:
|
|
95
|
-
- Looks up a variant by `variant` key (via `getVariant` from `src/variants`).
|
|
96
|
-
- Builds the field layout using `FieldLayoutConfig` and optional variant-level `resolveLayout`.
|
|
97
|
-
- Wires helper slots (label, sublabel, description, error placement) via primitives from `src/presets/ui/field`.
|
|
98
|
-
- Normalizes validation results (`normalizeValidateResult`).
|
|
99
|
-
|
|
100
|
-
Typical usage:
|
|
101
|
-
```tsx
|
|
102
|
-
import { InputField } from "@/input/input-field";
|
|
41
|
+
### InputField basics
|
|
42
|
+
|
|
43
|
+
Use one component for all input types by switching the variant key. InputField wires the value, validation, and a consistent label/description/error layout for you.
|
|
44
|
+
|
|
45
|
+
Common props (apply to most variants):
|
|
46
|
+
- name: unique field key
|
|
47
|
+
- variant: which control to render (see Variant reference below)
|
|
48
|
+
- label, sublabel: title and a small inline hint
|
|
49
|
+
- description/helpText: helper copy under the control
|
|
50
|
+
- errorText: force an error message (or rely on validation)
|
|
51
|
+
- required, disabled, readOnly
|
|
52
|
+
- icon, prefix, suffix, leadingControl, trailingControl: decorate the input content
|
|
53
|
+
- contain: force the input and label to share a tile-like container
|
|
54
|
+
- validate(value, report): return true | false | string for simple inline validation
|
|
55
|
+
- onChange(detail): detail.value holds the new value; prevent default if you need to override
|
|
56
|
+
|
|
57
|
+
Example with decorations and validation:
|
|
103
58
|
|
|
59
|
+
```tsx
|
|
104
60
|
<InputField
|
|
105
|
-
name="
|
|
61
|
+
name="username"
|
|
106
62
|
variant="text"
|
|
107
|
-
label="
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
63
|
+
label="Username"
|
|
64
|
+
sublabel="public handle"
|
|
65
|
+
prefix="@"
|
|
66
|
+
validate={(value, report) => {
|
|
67
|
+
if (!value) return report ? "Required" : false;
|
|
68
|
+
if (value.length < 3) return report ? "Min 3 characters" : false;
|
|
69
|
+
return true;
|
|
70
|
+
}}
|
|
71
|
+
onChange={(e) => console.log("username:", e.value)}
|
|
111
72
|
/>
|
|
112
73
|
```
|
|
113
74
|
|
|
75
|
+
Programmatic control during submit:
|
|
114
76
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
77
|
+
```tsx
|
|
78
|
+
function onSubmit(e: any) {
|
|
79
|
+
// Programmatically set a value
|
|
80
|
+
e.form.inputs.getByName("email").setValue("demo@example.com");
|
|
81
|
+
console.log(e.formData);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
121
84
|
|
|
85
|
+
---
|
|
122
86
|
|
|
123
|
-
|
|
124
|
-
- Field-level validation is provided via `useField` and variant-level validation can also be built in.
|
|
125
|
-
- Form-level validation (`core.validate(report?)`) runs all participating fields and aggregates into an error bag.
|
|
126
|
-
- Error rendering within `InputField` uses `FieldError` and layout slots.
|
|
87
|
+
### Variant reference (what value they hold and unique props)
|
|
127
88
|
|
|
89
|
+
Below are the built‑in variants with the props you’ll use most often. Examples mirror the playground App.tsx.
|
|
128
90
|
|
|
129
|
-
|
|
91
|
+
Note on options: selection controls accept options as primitives ("US") or objects ({ label, value, ...extra }).
|
|
130
92
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
- Registry: `src/core/adapter-registry.ts`
|
|
138
|
-
- Built-ins: `src/adapters/axios.ts`, `src/adapters/inertia.ts`, index helpers in `src/adapters/index.ts`
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
#### 2.1 Adapter Types (`src/schema/adapter.ts`)
|
|
142
|
-
Core type concepts:
|
|
143
|
-
- `AdapterCallbacks` — lifecycle hooks the adapter can invoke:
|
|
144
|
-
- `onSuccess(response)`
|
|
145
|
-
- `onError(error, updateRef)` (may ‘edit’ error/response context)
|
|
146
|
-
- `onFinish()`
|
|
147
|
-
- `AdapterResult<Body>` — the runtime object an adapter factory returns with three operations:
|
|
148
|
-
- `submit(options?)` — trigger a submission side-effect
|
|
149
|
-
- `send(options?)` — return a promise resolved with an `ok`-typed payload
|
|
150
|
-
- `run(options?)` — a flexible operation; many adapters map it to `submit` or a hybrid
|
|
151
|
-
- `AdapterFactory<Body>` / `NamedAdapterFactory<K>` — functions producing an `AdapterResult`
|
|
152
|
-
- `Adapters` interface — the type-level registry that hosts/presets can augment with declaration merging to add their own keys and ok/error/payload shapes.
|
|
153
|
-
|
|
154
|
-
Convenience type utilities:
|
|
155
|
-
- `AdapterKey` — union of all adapter keys (e.g., `'local' | 'axios' | 'inertia' | ...'`).
|
|
156
|
-
- `AdapterOk<K>`, `AdapterError<K>` — map adapter key to success/error payload types.
|
|
157
|
-
- `AdapterSubmit<K>` — the type of what `onSubmitted(form, payload, resolve)` receives for a given adapter key.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
#### 2.2 Adapter Registry (`src/core/adapter-registry.ts`)
|
|
161
|
-
- Maintains a simple in-memory registry of adapter factories keyed by `AdapterKey`.
|
|
162
|
-
- Ships with a built-in `'local'` adapter (`localAdapter`) that:
|
|
163
|
-
- `send()` resolves `{ data: config.data }`
|
|
164
|
-
- `submit()`/`run()` are no-ops (core decides how to flow).
|
|
165
|
-
- Public functions:
|
|
166
|
-
- `registerAdapter(key, factory)` — register/override an adapter.
|
|
167
|
-
- `getAdapter(key)` — retrieve a factory (or `undefined`).
|
|
168
|
-
- `hasAdapter(key)` — check presence.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
#### 2.3 Built-in Adapters and Helpers (`src/adapters/*`)
|
|
172
|
-
- `axios` adapter factory: `createAxiosAdapter` (see `src/adapters/axios.ts`).
|
|
173
|
-
- `inertia` adapter factory: `createInertiaAdapter` (see `src/adapters/inertia.ts`).
|
|
174
|
-
- Helpers in `src/adapters/index.ts`:
|
|
175
|
-
- `registerAxiosAdapter()` — runtime check for `axios` and registers under key `'axios'`.
|
|
176
|
-
- `registerInertiaAdapter()` — dynamic import of `@inertiajs/react`, checks router `.visit`, registers under key `'inertia'`.
|
|
177
|
-
- `registerKnownAdapter(key: AdapterKey)` — switch over known keys.
|
|
178
|
-
- `registerAllAdapters()` — registers both axios and inertia.
|
|
179
|
-
|
|
180
|
-
Registering at app bootstrap:
|
|
181
|
-
```ts
|
|
182
|
-
import { registerAxiosAdapter, registerInertiaAdapter } from "@/adapters";
|
|
183
|
-
|
|
184
|
-
registerAxiosAdapter();
|
|
185
|
-
await registerInertiaAdapter();
|
|
93
|
+
1) text
|
|
94
|
+
- Value: string | undefined
|
|
95
|
+
- Nice extras: mask, slotChar, unmask, autoClear (phone-like masking); icon/prefix/suffix; searchable isn’t applicable here
|
|
96
|
+
- Example:
|
|
97
|
+
```tsx
|
|
98
|
+
<InputField name="email" label="Email" variant="text" />
|
|
186
99
|
```
|
|
187
100
|
|
|
188
|
-
|
|
189
|
-
-
|
|
190
|
-
-
|
|
101
|
+
2) number
|
|
102
|
+
- Value: number | undefined
|
|
103
|
+
- Props: min, max, step, showButtons
|
|
104
|
+
- Example:
|
|
105
|
+
```tsx
|
|
106
|
+
<InputField name="age" label="Age" variant="number" min={0} max={120} step={1} showButtons />
|
|
107
|
+
```
|
|
191
108
|
|
|
192
|
-
|
|
193
|
-
-
|
|
109
|
+
3) password
|
|
110
|
+
- Value: string | undefined
|
|
111
|
+
- Props: showToggle; strengthMeter; meterStyle="rules" | "bar" (depending on preset)
|
|
112
|
+
- Example:
|
|
113
|
+
```tsx
|
|
114
|
+
<InputField name="pwd" label="Password" variant="password" showToggle strengthMeter meterStyle="rules" />
|
|
115
|
+
```
|
|
194
116
|
|
|
117
|
+
4) color
|
|
118
|
+
- Value: string | undefined (hex or css color)
|
|
119
|
+
- Props: showPreview, previewButtonClassName
|
|
120
|
+
- Example:
|
|
121
|
+
```tsx
|
|
122
|
+
<InputField name="color" label="Favorite colour" variant="color" showPreview />
|
|
123
|
+
```
|
|
195
124
|
|
|
196
|
-
|
|
125
|
+
5) phone
|
|
126
|
+
- Value: string | undefined
|
|
127
|
+
- Typical usage uses masking controls the same way as text: mask, slotChar, unmask, autoClear
|
|
128
|
+
- Example (from playground labeled "Phone"):
|
|
129
|
+
```tsx
|
|
130
|
+
<InputField
|
|
131
|
+
name="phone"
|
|
132
|
+
label="Phone"
|
|
133
|
+
variant="text" // or a dedicated phone variant if enabled in your build
|
|
134
|
+
mask="+99 99 999 999? x999"
|
|
135
|
+
slotChar="_"
|
|
136
|
+
autoClear
|
|
137
|
+
/>
|
|
138
|
+
```
|
|
197
139
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
- `src/presets/shadcn-variants/*` — concrete Shadcn-based components for each variant.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
#### 3.1 Variant Types (`src/schema/variant.ts`)
|
|
208
|
-
- `interface Variants` maps variant keys to `{ value, props }` pairs. This registry is used by:
|
|
209
|
-
- `InputFieldProps<K>` — to infer the correct `value` and `props` for a given variant key.
|
|
210
|
-
- `VariantModule<K>` — to type the runtime module providing a `Variant` component and optional layout resolution.
|
|
211
|
-
- Built-in keys include: `text`, `number`, `phone`, `color`, `password`, `date`, `chips`, `textarea`, `toggle`, `toggle-group`, `radio`, `checkbox`, `select`, `multi-select`, `slider`, `keyvalue`, `custom`, `treeselect`, `file`.
|
|
212
|
-
- All Shadcn prop types are re-exported by their files in `src/presets/shadcn-variants/*` and then aggregated into the `Variants` interface.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
#### 3.2 Variant Modules and Presets
|
|
216
|
-
- A variant module is declared per key in `src/variants/core/*`. Example: `checkbox.tsx` wires the Shadcn checkbox preset and supplies defaults/layout:
|
|
217
|
-
```ts
|
|
218
|
-
export const checkboxModule: VariantModuleFor<"checkbox"> = {
|
|
219
|
-
variant: "checkbox",
|
|
220
|
-
Variant: ShadcnCheckboxVariant as unknown as React.ComponentType<
|
|
221
|
-
VariantBaseProps<CheckboxVariantPublicValue> & ShadcnCheckboxVariantPublicProps
|
|
222
|
-
>,
|
|
223
|
-
resolveLayout({ props }) {
|
|
224
|
-
if (props.single) return toggleLayoutDefaults;
|
|
225
|
-
return {};
|
|
226
|
-
},
|
|
227
|
-
meta: { label: "Checkbox", description: "...", tags: ["checkbox", "group", "boolean", "tri-state"] },
|
|
228
|
-
};
|
|
140
|
+
6) textarea
|
|
141
|
+
- Value: string | undefined
|
|
142
|
+
- Usual textarea props like placeholder, rows, etc.
|
|
143
|
+
- Example:
|
|
144
|
+
```tsx
|
|
145
|
+
<InputField name="bio" label="Bio" variant="textarea" description="Tell us about you" />
|
|
229
146
|
```
|
|
230
|
-
- The `Variant` in a module is a React component receiving `VariantBaseProps<TValue>` plus the variant’s public props. The base props include:
|
|
231
|
-
- `field` (result of `useField`) with `value`, `setValue`, etc.
|
|
232
|
-
- `onChange(detail: ChangeDetail<TValue>)`
|
|
233
|
-
- `disabled`, `readOnly`, `required`, error, etc. (according to `VariantBaseProps` in `src/variants/shared`)
|
|
234
|
-
- Presets implement the actual control UX (e.g., Shadcn Checkbox, Radio, Multiselect, File, TreeSelect, etc.) and are placed under `src/presets/shadcn-variants/*`.
|
|
235
147
|
|
|
148
|
+
7) toggle
|
|
149
|
+
- Value: boolean | undefined
|
|
150
|
+
- Example:
|
|
151
|
+
```tsx
|
|
152
|
+
<InputField name="tos" variant="toggle" label="Accept Terms" required />
|
|
153
|
+
```
|
|
236
154
|
|
|
237
|
-
|
|
238
|
-
|
|
155
|
+
8) toggle-group
|
|
156
|
+
- Value: string | number | undefined (selected item)
|
|
157
|
+
- Props: options (primitives or objects), layout/density sizing depending on preset
|
|
158
|
+
|
|
159
|
+
9) radio
|
|
160
|
+
- Value: string | number | undefined
|
|
161
|
+
- Props:
|
|
162
|
+
- options: primitives or objects with { value, label, description?, disabled? }
|
|
163
|
+
- layout?: "list" | "grid" (default "list"); columns?: number (when layout="grid")
|
|
164
|
+
- size?: "sm" | "md" | "lg"; density?: "compact" | "comfortable" | "loose"
|
|
165
|
+
- You can also map custom item shapes via optionValue/optionLabel style mappers depending on preset
|
|
166
|
+
- Example:
|
|
167
|
+
```tsx
|
|
168
|
+
<InputField
|
|
169
|
+
name="role"
|
|
170
|
+
label="Role"
|
|
171
|
+
variant="radio"
|
|
172
|
+
options={[
|
|
173
|
+
{ value: "reader", label: "Reader" },
|
|
174
|
+
{ value: "editor", label: "Editor" },
|
|
175
|
+
]}
|
|
176
|
+
/>
|
|
177
|
+
```
|
|
239
178
|
|
|
240
|
-
|
|
179
|
+
10) checkbox
|
|
180
|
+
- Value: boolean | string[] | number[] depending on single vs group usage
|
|
181
|
+
- Single boolean checkbox:
|
|
241
182
|
```tsx
|
|
242
|
-
<InputField
|
|
183
|
+
<InputField variant="checkbox" label="Remember me" />
|
|
184
|
+
```
|
|
185
|
+
- Group example:
|
|
186
|
+
```tsx
|
|
187
|
+
<InputField
|
|
188
|
+
name="perms"
|
|
189
|
+
variant="checkbox"
|
|
190
|
+
label="Permissions"
|
|
191
|
+
options={[
|
|
192
|
+
{ value: "read", label: "Read content" },
|
|
193
|
+
{ value: "write", label: "Write content" },
|
|
194
|
+
{ value: "delete", label: "Delete content" },
|
|
195
|
+
]}
|
|
196
|
+
/>
|
|
197
|
+
```
|
|
243
198
|
|
|
199
|
+
- Extras:
|
|
200
|
+
- single?: boolean switches to single‑checkbox mode (value becomes boolean | undefined)
|
|
201
|
+
- tristate?: boolean enables an indeterminate state for single or per‑item
|
|
202
|
+
- layout?: "list" | "grid"; columns?: number (grid mode)
|
|
203
|
+
- size?: "sm" | "md" | "lg"; density?: "compact" | "comfortable" | "loose"
|
|
204
|
+
|
|
205
|
+
11) select
|
|
206
|
+
- Value: string | number | undefined
|
|
207
|
+
- Props (high‑use):
|
|
208
|
+
- options: (string|number)[] | { label?, value?, description?, disabled?, icon?, ... }[]
|
|
209
|
+
- searchable?: boolean (inline search box)
|
|
210
|
+
- searchPlaceholder?: string (placeholder inside the search box)
|
|
211
|
+
- clearable?: boolean (show clear button)
|
|
212
|
+
- placeholder?: string
|
|
213
|
+
- autoCap?: boolean (capitalise label text)
|
|
214
|
+
- emptyLabel?: React.ReactNode (shown when there are no options)
|
|
215
|
+
- emptySearchText?: React.ReactNode (shown when search returns no results)
|
|
216
|
+
- optionLabel, optionValue, optionDescription, optionDisabled, optionIcon, optionKey: map/compute option pieces
|
|
217
|
+
- renderOption?: (ctx) => ReactNode (custom row rendering; per‑option render overrides this)
|
|
218
|
+
- Example:
|
|
219
|
+
```tsx
|
|
244
220
|
<InputField
|
|
245
221
|
name="country"
|
|
246
222
|
variant="select"
|
|
247
223
|
label="Country"
|
|
248
224
|
options={[{ label: "USA", value: "US" }, { label: "Canada", value: "CA" }]}
|
|
249
225
|
placeholder="Select a country"
|
|
226
|
+
searchable
|
|
227
|
+
clearable
|
|
250
228
|
/>
|
|
251
229
|
```
|
|
252
230
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
```
|
|
263
|
-
2) Create a preset component (e.g., in your own folder or mimic Shadcn preset style):
|
|
231
|
+
12) multi-select
|
|
232
|
+
- Value: (string|number)[] | undefined
|
|
233
|
+
- Props: same mapping props as select (optionLabel, optionValue, optionDescription, optionDisabled, optionIcon, optionKey)
|
|
234
|
+
- searchable?: boolean; searchPlaceholder?: string
|
|
235
|
+
- clearable?: boolean; placeholder?: React.ReactNode
|
|
236
|
+
- autoCap?: boolean
|
|
237
|
+
- emptyLabel?: React.ReactNode; emptySearchText?: React.ReactNode
|
|
238
|
+
- renderOption?: (ctx) => ReactNode (custom row rendering; per‑option render overrides this)
|
|
239
|
+
- Example:
|
|
264
240
|
```tsx
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
3) Provide a variant module under `src/variants/core/mycontrol.tsx`:
|
|
272
|
-
```ts
|
|
273
|
-
export const mycontrolModule: VariantModuleFor<"mycontrol"> = {
|
|
274
|
-
variant: "mycontrol",
|
|
275
|
-
Variant: MyControlVariant,
|
|
276
|
-
resolveLayout({ defaults, overrides, props }) {
|
|
277
|
-
return { ...defaults, ...overrides };
|
|
278
|
-
},
|
|
279
|
-
meta: { label: "My Control", description: "Custom control.", tags: ["custom"] },
|
|
280
|
-
};
|
|
241
|
+
<InputField
|
|
242
|
+
name="tags"
|
|
243
|
+
label="Tags"
|
|
244
|
+
variant="multi-select"
|
|
245
|
+
options={["red", "green", "blue"]}
|
|
246
|
+
/>
|
|
281
247
|
```
|
|
282
|
-
4) Register the module with the variant registry (see how existing modules are exported/aggregated in `src/variants`). After registration, you can use `<InputField variant="mycontrol" ... />`.
|
|
283
248
|
|
|
249
|
+
13) chips
|
|
250
|
+
- Value: string[] | number[] | undefined
|
|
251
|
+
- Free‑form or from options; often used to add/remove tokens
|
|
284
252
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
3. Core prepares an adapter instance (key `local` by default or your configured key) using `getAdapter(key)`.
|
|
292
|
-
4. Core calls `adapter.submit()` or `adapter.send()`. Adapters notify the core through `AdapterCallbacks`:
|
|
293
|
-
- `onSuccess(ok)` — core then calls your `onSubmitted<K>(form, payload, resolve)` from `CoreProps` with typed `AdapterSubmit<K>`.
|
|
294
|
-
- `onError(err, update)` — transform/record error; the core can populate error bags via `core.error(...)`.
|
|
295
|
-
- `onFinish()` — always runs; core toggles button states via `controlButton()`.
|
|
296
|
-
|
|
297
|
-
Hosts can implement `onSubmitted<K extends AdapterKey>(form, payload, resolve)` to centralize success handling. The `resolve()` callback is provided to let you complete any external flows before the core finalizes.
|
|
253
|
+
14) treeselect
|
|
254
|
+
- Value: (string|number)[] | string | number | undefined (single or multiple tree selection)
|
|
255
|
+
- Option type: TreeSelectOption = { label, value, icon?, description?, children?: TreeSelectOption[] }
|
|
256
|
+
- Example:
|
|
257
|
+
```tsx
|
|
258
|
+
import { TreeSelectOption } from "@timeax/form-palette/presets/shadcn-variants/tree-select-types";
|
|
298
259
|
|
|
260
|
+
const regionOptions: TreeSelectOption[] = [
|
|
261
|
+
{ label: "Africa", value: "africa", children: [{ label: "Nigeria", value: "ng" }] },
|
|
262
|
+
{ label: "Europe", value: "europe" },
|
|
263
|
+
];
|
|
299
264
|
|
|
300
|
-
|
|
265
|
+
<InputField
|
|
266
|
+
name="regions"
|
|
267
|
+
label="Regions"
|
|
268
|
+
variant="treeselect"
|
|
269
|
+
options={regionOptions}
|
|
270
|
+
/>
|
|
271
|
+
```
|
|
301
272
|
|
|
302
|
-
|
|
273
|
+
- Props (high‑use):
|
|
274
|
+
- multiple?: boolean (default true). If false, single‑select behaviour.
|
|
275
|
+
- searchable?: boolean; searchPlaceholder?: string
|
|
276
|
+
- clearable?: boolean; placeholder?: React.ReactNode
|
|
277
|
+
- autoCap?: boolean
|
|
278
|
+
- optionLabel, optionValue, optionDescription, optionDisabled, optionIcon, optionKey
|
|
279
|
+
- emptyLabel?: React.ReactNode; emptySearchText?: React.ReactNode
|
|
280
|
+
- renderOption?: ({ item, selected, option, click }) => ReactNode
|
|
281
|
+
- renderValue?: ({ selectedItems, placeholder }) => ReactNode (custom trigger content)
|
|
282
|
+
- expandAll?: boolean; defaultExpandedValues?: (string|number)[]
|
|
283
|
+
- leafOnly?: boolean (only leaf nodes are selectable)
|
|
284
|
+
- mode?: "default" | "button"; when "button", you can provide a custom trigger and show a selected‑count badge
|
|
285
|
+
|
|
286
|
+
15) slider
|
|
287
|
+
- Value: number | [number, number] depending on range mode
|
|
288
|
+
- Props: min, max, step; possibly range/multiple depending on preset
|
|
289
|
+
|
|
290
|
+
16) file
|
|
291
|
+
- Value: File | File[] | Custom file shape depending on configuration
|
|
292
|
+
- Types: FileItem, CustomFileLoader, FileLike are exported from the preset if you need advanced control
|
|
293
|
+
- Example (simple):
|
|
294
|
+
```tsx
|
|
295
|
+
<InputField name="avatar" label="Avatar" variant="file" />
|
|
296
|
+
```
|
|
303
297
|
|
|
304
|
-
|
|
298
|
+
17) keyvalue
|
|
299
|
+
- Value: Record<string, string> | undefined
|
|
300
|
+
- Use to capture arbitrary key/value pairs
|
|
301
|
+
|
|
302
|
+
18) editor
|
|
303
|
+
- Value: string | undefined (HTML or Markdown)
|
|
304
|
+
- Requires host CSS import once in your app:
|
|
305
|
+
- import "@toast-ui/editor/dist/toastui-editor.css";
|
|
306
|
+
- Props (high‑use):
|
|
307
|
+
- format?: "html" | "markdown" (stored value format; default "html")
|
|
308
|
+
- toolbar?: "default" | "none" | ToastToolbarItem[][]
|
|
309
|
+
- height?: string (e.g., "400px"), placeholder?: string
|
|
310
|
+
- editType?: "wysiwyg" | "markdown"; previewStyle?: "vertical" | "tab"
|
|
311
|
+
- pastePlainText?: boolean (force plain text on paste)
|
|
312
|
+
- Example:
|
|
305
313
|
```tsx
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
314
|
+
<InputField
|
|
315
|
+
name="content"
|
|
316
|
+
label="Content"
|
|
317
|
+
variant="editor"
|
|
318
|
+
format="markdown"
|
|
319
|
+
toolbar="default"
|
|
320
|
+
height="400px"
|
|
321
|
+
/>
|
|
322
|
+
```
|
|
309
323
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
<CoreProvider
|
|
313
|
-
// Optional: onSubmitted<K>(form, payload, resolve) {}
|
|
314
|
-
// Optional: onChange(form, values) {}
|
|
315
|
-
>
|
|
316
|
-
<Fields />
|
|
317
|
-
</CoreProvider>
|
|
318
|
-
);
|
|
319
|
-
}
|
|
324
|
+
19) custom
|
|
325
|
+
- Bring your own control but still benefit from InputField’s layout and validation chrome
|
|
320
326
|
|
|
321
|
-
|
|
322
|
-
const core = useCore();
|
|
327
|
+
---
|
|
323
328
|
|
|
324
|
-
|
|
325
|
-
<form onSubmit={(e) => { e.preventDefault(); core.submit(); }}>
|
|
326
|
-
<InputField name="email" variant="text" label="Email" required />
|
|
327
|
-
<InputField name="password" variant="password" label="Password" required />
|
|
329
|
+
### Recipes from the playground
|
|
328
330
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
331
|
+
Masking a phone‑like input:
|
|
332
|
+
```tsx
|
|
333
|
+
<InputField
|
|
334
|
+
name="phone"
|
|
335
|
+
label="Phone"
|
|
336
|
+
variant="text"
|
|
337
|
+
mask="+99 99 999 999? x999"
|
|
338
|
+
slotChar="_"
|
|
339
|
+
autoClear
|
|
340
|
+
leadingControl={<span>Leading control</span>}
|
|
341
|
+
prefix="number: "
|
|
342
|
+
/>
|
|
333
343
|
```
|
|
334
344
|
|
|
335
|
-
|
|
336
|
-
```ts
|
|
337
|
-
// app bootstrap
|
|
338
|
-
import { registerAxiosAdapter } from "@/adapters";
|
|
339
|
-
registerAxiosAdapter();
|
|
340
|
-
```
|
|
345
|
+
Password with strength meter:
|
|
341
346
|
```tsx
|
|
342
|
-
<
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
347
|
+
<InputField
|
|
348
|
+
name="password"
|
|
349
|
+
label="Password"
|
|
350
|
+
variant="password"
|
|
351
|
+
placeholder="Enter your password"
|
|
352
|
+
strengthMeter
|
|
353
|
+
meterStyle="rules"
|
|
354
|
+
/>
|
|
350
355
|
```
|
|
351
|
-
Adapter selection is typically part of the provider/config you pass when preparing a submit; see `CoreProps` and your submit orchestration where you choose the adapter key.
|
|
352
356
|
|
|
353
|
-
|
|
357
|
+
Selects and multi‑selects:
|
|
354
358
|
```tsx
|
|
355
359
|
<InputField
|
|
356
|
-
name="
|
|
357
|
-
variant="
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
360
|
+
name="country"
|
|
361
|
+
variant="select"
|
|
362
|
+
label="Country"
|
|
363
|
+
options={[{ label: "USA", value: "US" }, { label: "Canada", value: "CA" }]}
|
|
364
|
+
placeholder="Select a country"
|
|
365
|
+
searchable
|
|
366
|
+
/>
|
|
367
|
+
|
|
368
|
+
<InputField
|
|
369
|
+
name="languages"
|
|
370
|
+
variant="multi-select"
|
|
371
|
+
label="Languages"
|
|
372
|
+
options={["English", "French", "German"]}
|
|
364
373
|
/>
|
|
365
374
|
```
|
|
366
375
|
|
|
367
|
-
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
376
|
+
Tree select with icons and descriptions:
|
|
377
|
+
```tsx
|
|
378
|
+
<InputField name="regions" label="Regions" variant="treeselect" options={regionOptions} />
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Single checkbox:
|
|
382
|
+
```tsx
|
|
383
|
+
<InputField variant="checkbox" label="Remember me" />
|
|
372
384
|
```
|
|
373
385
|
|
|
386
|
+
Number with buttons:
|
|
387
|
+
```tsx
|
|
388
|
+
<InputField name="age" label="Age" variant="number" min={0} max={120} step={1} showButtons />
|
|
389
|
+
```
|
|
374
390
|
|
|
375
391
|
---
|
|
376
392
|
|
|
377
|
-
###
|
|
378
|
-
- Always wrap consumers inside `<CoreProvider>`; `useCoreContext()` throws otherwise.
|
|
379
|
-
- When integrating new adapters, register them at bootstrap (`registerAdapter(...)`).
|
|
380
|
-
- Use `shared` to nest values under a parent key (e.g., `shared="profile"` with `name="first_name"` → `values.profile.first_name`).
|
|
381
|
-
- Prefer `InputField` for consistent Shadcn layout and error rendering. If you build your own control, use `useField` directly and replicate the field chrome from `src/presets/ui/field`.
|
|
382
|
-
- For grouped controls (radios, segmented toggles), use `groupId` and set `main` on the primary member when needed.
|
|
383
|
-
- For adapter flows, handle `onSubmitted` in the provider to centralize success navigation or notifications.
|
|
384
|
-
- The built-in `local` adapter is a no-op side-effect adapter that simply echoes `data`; useful for local validation or preview flows.
|
|
393
|
+
### Submitting the form
|
|
385
394
|
|
|
395
|
+
Form wraps your fields and provides a submit event that carries both the values and utilities.
|
|
386
396
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
- Create a variant module (with `meta`, optional `resolveLayout`).
|
|
398
|
-
- Export/register the module with the variant registry and use via `InputField`.
|
|
397
|
+
```tsx
|
|
398
|
+
import { Form, InputField } from "@timeax/form-palette";
|
|
399
|
+
|
|
400
|
+
function Example() {
|
|
401
|
+
function handleSubmit(e: any) {
|
|
402
|
+
// values snapshot
|
|
403
|
+
console.log(e.formData);
|
|
404
|
+
// programmatic API
|
|
405
|
+
e.form.inputs.getByName("email").setValue("this is nice");
|
|
406
|
+
}
|
|
399
407
|
|
|
408
|
+
return (
|
|
409
|
+
<Form wrapped onSubmit={handleSubmit}>
|
|
410
|
+
<InputField name="email" label="Email" variant="text" />
|
|
411
|
+
<button type="submit">Submit</button>
|
|
412
|
+
</Form>
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
```
|
|
400
416
|
|
|
401
417
|
---
|
|
402
418
|
|
|
403
|
-
###
|
|
404
|
-
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
- Adapters:
|
|
410
|
-
- `src/schema/adapter.ts` — adapter types and registry contracts.
|
|
411
|
-
- `src/core/adapter-registry.ts` — runtime registry + `localAdapter`.
|
|
412
|
-
- `src/adapters/index.ts` — axios/inertia registration helpers.
|
|
413
|
-
- `src/adapters/axios.ts`, `src/adapters/inertia.ts` — concrete factories.
|
|
414
|
-
- Variants and presets:
|
|
415
|
-
- `src/schema/variant.ts` — variant key registry and types.
|
|
416
|
-
- `src/variants/core/*` — modules wiring preset → registry with defaults.
|
|
417
|
-
- `src/presets/shadcn-variants/*` — UI implementations.
|
|
418
|
-
- `src/presets/ui/*` — field chrome primitives (label, description, error, group, etc.).
|
|
419
|
-
|
|
419
|
+
### Tips and best practices
|
|
420
|
+
- Prefer InputField over wiring controls by hand; it gives you consistent labels, descriptions, and error placement.
|
|
421
|
+
- Use options as primitives for quick setups, or objects when you need description/disabled/icon per item.
|
|
422
|
+
- Use validate for quick client checks; you can also set errorText manually.
|
|
423
|
+
- For grouped controls (radios/checkbox groups), pass options; for a single boolean, omit options.
|
|
424
|
+
- Keep labels short and place longer helper copy into description/helpText.
|
|
420
425
|
|
|
421
426
|
---
|
|
422
427
|
|
|
423
|
-
###
|
|
424
|
-
-
|
|
425
|
-
-
|
|
426
|
-
-
|
|
428
|
+
### FAQ
|
|
429
|
+
- Can I access values without submitting? Yes, via the programmatic API exposed in events like onChange at the form level, or by reading e.form.values() from handlers inside the form.
|
|
430
|
+
- Can I bring my own input component? Yes. Use the "custom" variant or build a dedicated preset and still place it inside InputField to reuse layout and validation.
|
|
431
|
+
- Do I need to register adapters? Not for basic local use. Adapters matter when you integrate with external routers or clients; see the package’s adapters folder if needed.
|
|
432
|
+
|
|
433
|
+
---
|
|
427
434
|
|
|
428
|
-
|
|
435
|
+
This document intentionally centers on how to use the package to build forms. For deeper internals and extension points, browse the source or the developer docs in the repository.
|