@timeax/form-palette 0.1.2 → 0.1.4
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 +2280 -0
- package/dist/{variant-BGkGWsSy.d.mts → core-EVtvQ-ma.d.mts} +3522 -3488
- package/dist/{variant-B_L0opEi.d.ts → core-ZuVzbe2m.d.ts} +3522 -3488
- package/dist/extra.d.mts +134 -163
- package/dist/extra.d.ts +134 -163
- package/dist/extra.js +7838 -5940
- package/dist/extra.js.map +1 -1
- package/dist/extra.mjs +7763 -5862
- package/dist/extra.mjs.map +1 -1
- package/dist/index.d.mts +14 -3
- package/dist/index.d.ts +14 -3
- package/dist/index.js +3429 -3655
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3359 -3585
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -2
- package/Readme.md +0 -1380
package/README.md
ADDED
|
@@ -0,0 +1,2280 @@
|
|
|
1
|
+
# Index
|
|
2
|
+
Included Sections: 119
|
|
3
|
+
- [Form Palette](#form-palette) 123-343
|
|
4
|
+
- [Quick start](#quick-start) 133-161
|
|
5
|
+
- [Installation:](#installation) 135-161
|
|
6
|
+
- [Form](#form) 163-237
|
|
7
|
+
- [Minimal “local” form](#minimal-local-form) 167-200
|
|
8
|
+
- [Axios adapter](#axios-adapter) 202-217
|
|
9
|
+
- [Inertia adapter](#inertia-adapter) 219-237
|
|
10
|
+
- [InputField](#inputfield) 239-294
|
|
11
|
+
- [Basic usage](#basic-usage) 252-263
|
|
12
|
+
- [Helper slots](#helper-slots) 265-277
|
|
13
|
+
- [Standalone mode](#standalone-mode) 279-294
|
|
14
|
+
- [Adapters](#adapters) 296-331
|
|
15
|
+
- [Built-in adapter keys](#built-in-adapter-keys) 300-304
|
|
16
|
+
- [Registering adapters](#registering-adapters) 306-325
|
|
17
|
+
- [Adapter-specific props](#adapter-specific-props) 327-331
|
|
18
|
+
- [Recommended boot order](#recommended-boot-order) 333-343
|
|
19
|
+
- [Variant props + InputField usage](#variant-props-inputfield-usage) 346-1707
|
|
20
|
+
- [text](#text) 377-434
|
|
21
|
+
- [Variant props](#variant-props) 379-399
|
|
22
|
+
- [Sample usage (InputField)](#sample-usage-inputfield) 401-434
|
|
23
|
+
- [textarea](#textarea) 436-465
|
|
24
|
+
- [Variant props](#variant-props-1) 438-442
|
|
25
|
+
- [Sample usage (InputField)](#sample-usage-inputfield-1) 444-465
|
|
26
|
+
- [toggle-group](#toggle-group) 467-529
|
|
27
|
+
- [Variant props](#variant-props-2) 469-499
|
|
28
|
+
- [Sample usage (InputField)](#sample-usage-inputfield-2) 501-529
|
|
29
|
+
- [number](#number) 531-559
|
|
30
|
+
- [password](#password) 561-599
|
|
31
|
+
- [phone](#phone) 601-638
|
|
32
|
+
- [Slider (`slider`)](#slider-slider) 640-699
|
|
33
|
+
- [Props](#props) 644-679
|
|
34
|
+
- [Example](#example) 681-699
|
|
35
|
+
- [Toggle (`toggle`)](#toggle-toggle) 701-734
|
|
36
|
+
- [Props](#props-1) 705-719
|
|
37
|
+
- [Example](#example-1) 721-734
|
|
38
|
+
- [TreeSelect (`treeselect`)](#treeselect-treeselect) 736-849
|
|
39
|
+
- [Base props](#base-props) 740-773
|
|
40
|
+
- [Mode: default (`mode` omitted or "default")](#mode-default-mode-omitted-or-default) 775-793
|
|
41
|
+
- [Mode: button (`mode="button"`)](#mode-button-modebutton) 795-811
|
|
42
|
+
- [Example (default mode)](#example-default-mode) 813-833
|
|
43
|
+
- [Example (multiple + button mode)](#example-multiple-button-mode) 835-849
|
|
44
|
+
- [multi-select](#multi-select) 851-938
|
|
45
|
+
- [Variant props](#variant-props-3) 853-884
|
|
46
|
+
- [Mode and trigger props](#mode-and-trigger-props) 886-908
|
|
47
|
+
- [Sample usage](#sample-usage) 910-938
|
|
48
|
+
- [radio](#radio) 940-999
|
|
49
|
+
- [Variant props](#variant-props-4) 942-967
|
|
50
|
+
- [Supported option shapes](#supported-option-shapes) 969-972
|
|
51
|
+
- [Sample usage](#sample-usage-1) 974-999
|
|
52
|
+
- [select](#select) 1001-1077
|
|
53
|
+
- [Variant props](#variant-props-5) 1003-1048
|
|
54
|
+
- [Sample usage](#sample-usage-2) 1050-1077
|
|
55
|
+
- [checkbox](#checkbox) 1079-1147
|
|
56
|
+
- [chips](#chips) 1149-1202
|
|
57
|
+
- [color](#color) 1204-1234
|
|
58
|
+
- [date](#date) 1236-1283
|
|
59
|
+
- [Variant props](#variant-props-6) 1238-1256
|
|
60
|
+
- [Sample usage](#sample-usage-3) 1258-1283
|
|
61
|
+
- [keyvalue](#keyvalue) 1285-1335
|
|
62
|
+
- [Variant props](#variant-props-7) 1287-1307
|
|
63
|
+
- [Sample usage](#sample-usage-4) 1309-1335
|
|
64
|
+
- [editor](#editor) 1337-1381
|
|
65
|
+
- [Variant props](#variant-props-8) 1339-1356
|
|
66
|
+
- [Sample usage](#sample-usage-5) 1358-1381
|
|
67
|
+
- [file](#file) 1383-1460
|
|
68
|
+
- [Variant props](#variant-props-9) 1385-1411
|
|
69
|
+
- [Mode and trigger props](#mode-and-trigger-props-1) 1413-1435
|
|
70
|
+
- [Sample usage](#sample-usage-6) 1437-1460
|
|
71
|
+
- [json-editor](#json-editor) 1462-1542
|
|
72
|
+
- [Wrapper / trigger props](#wrapper-trigger-props) 1464-1482
|
|
73
|
+
- [Editor props (passed into the JSON editor)](#editor-props-passed-into-the-json-editor) 1484-1504
|
|
74
|
+
- [Sample usage](#sample-usage-7) 1506-1542
|
|
75
|
+
- [lister](#lister) 1544-1644
|
|
76
|
+
- [Data + mapping props](#data-mapping-props) 1546-1563
|
|
77
|
+
- [Selection + behaviour props](#selection-behaviour-props) 1565-1584
|
|
78
|
+
- [Trigger styling + container props](#trigger-styling-container-props) 1586-1614
|
|
79
|
+
- [Sample usage](#sample-usage-8) 1616-1644
|
|
80
|
+
- [custom](#custom) 1646-1707
|
|
81
|
+
- [Variant props](#variant-props-10) 1648-1663
|
|
82
|
+
- [Sample usage](#sample-usage-9) 1665-1707
|
|
83
|
+
- [Form Palette — `extra` entrypoint (v2)](#form-palette-extra-entrypoint-v2) 1709-1723
|
|
84
|
+
- [1) Lister (runtime)](#1-lister-runtime) 1725-1849
|
|
85
|
+
- [What is Lister?](#what-is-lister) 1727-1738
|
|
86
|
+
- [Building blocks (what you actually mount/call)](#building-blocks-what-you-actually-mountcall) 1740-1771
|
|
87
|
+
- [✅ `ListerProvider`](#listerprovider) 1742-1747
|
|
88
|
+
- [✅ `ListerUI`](#listerui) 1749-1752
|
|
89
|
+
- [✅ `useLister()`](#uselister) 1754-1764
|
|
90
|
+
- [✅ `useData()`](#usedata) 1766-1771
|
|
91
|
+
- [Quick start (recommended)](#quick-start-recommended) 1773-1818
|
|
92
|
+
- [Step 1 — Mount provider + UI once](#step-1-mount-provider-ui-once) 1775-1794
|
|
93
|
+
- [Step 2 — Open a picker imperatively](#step-2-open-a-picker-imperatively) 1796-1818
|
|
94
|
+
- [`ListerProvider` API](#listerprovider-api) 1820-1849
|
|
95
|
+
- [`ListerProviderHost`](#listerproviderhost) 1832-1842
|
|
96
|
+
- [Practical usage](#practical-usage) 1844-1849
|
|
97
|
+
- [2) `useData()` — deep dive (extremely important)](#2-usedata-deep-dive-extremely-important) 1851-2247
|
|
98
|
+
- [What `useData()` returns (mental model)](#what-usedata-returns-mental-model) 1865-1877
|
|
99
|
+
- [`UseDataOptions` (inputs)](#usedataoptions-inputs) 1879-1910
|
|
100
|
+
- [Selection config (`selection`)](#selection-config-selection) 1898-1910
|
|
101
|
+
- [Search modes: remote vs local vs hybrid](#search-modes-remote-vs-local-vs-hybrid) 1912-1935
|
|
102
|
+
- [✅ `remote` (default)](#remote-default) 1914-1919
|
|
103
|
+
- [✅ `local`](#local) 1921-1926
|
|
104
|
+
- [✅ `hybrid`](#hybrid) 1928-1935
|
|
105
|
+
- [Search targeting (`searchTarget`)](#search-targeting-searchtarget) 1937-1947
|
|
106
|
+
- [Core returned API (what you’ll use most)](#core-returned-api-what-youll-use-most) 1949-1984
|
|
107
|
+
- [Data + status](#data-status) 1951-1954
|
|
108
|
+
- [Search](#search) 1956-1960
|
|
109
|
+
- [Filters](#filters) 1962-1966
|
|
110
|
+
- [Fetch](#fetch) 1968-1971
|
|
111
|
+
- [Selection (when enabled)](#selection-when-enabled) 1973-1984
|
|
112
|
+
- [`useData()` — practical use cases (full examples)](#usedata-practical-use-cases-full-examples) 1986-2238
|
|
113
|
+
- [Use case A — Remote search list (simple)](#use-case-a-remote-search-list-simple) 1988-2030
|
|
114
|
+
- [Use case B — Local mode (fetch once, instant client filtering)](#use-case-b-local-mode-fetch-once-instant-client-filtering) 2032-2066
|
|
115
|
+
- [Use case C — Filters with `patchFilters` (remote/hybrid auto-fetch)](#use-case-c-filters-with-patchfilters-remotehybrid-auto-fetch) 2068-2121
|
|
116
|
+
- [Use case D — Constrain to a known allow-list (`searchTarget: mode="only"`)](#use-case-d-constrain-to-a-known-allow-list-searchtarget-modeonly) 2123-2159
|
|
117
|
+
- [Use case E — Custom multi-select UI (selection enabled)](#use-case-e-custom-multi-select-ui-selection-enabled) 2161-2209
|
|
118
|
+
- [Use case F — Advanced request shaping (`buildRequest`)](#use-case-f-advanced-request-shaping-buildrequest) 2211-2238
|
|
119
|
+
- [Practical tips](#practical-tips) 2240-2247
|
|
120
|
+
- [3) JsonEditor (overview)](#3-jsoneditor-overview) 2249-2280
|
|
121
|
+
- [Standalone usage](#standalone-usage) 2259-2280
|
|
122
|
+
|
|
123
|
+
# Form Palette
|
|
124
|
+
|
|
125
|
+
A small but powerful React form runtime built around three ideas:
|
|
126
|
+
|
|
127
|
+
1. **A single `<Form />` shell** that wires up state, submission and validation.
|
|
128
|
+
2. **`<InputField />`** as the universal “field wrapper” that renders a registered **variant** (text, number, select, json-editor, etc.) and handles label / description / errors / layout.
|
|
129
|
+
3. **Adapters** that decide what “submit” means (`local`, `axios`, `inertia`, or your own).
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Quick start
|
|
134
|
+
|
|
135
|
+
#### Installation:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npm install @timeax/form-palette
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
import * as React from "react";
|
|
145
|
+
import {
|
|
146
|
+
Form,
|
|
147
|
+
InputField,
|
|
148
|
+
registerCoreVariants,
|
|
149
|
+
registerAxiosAdapter,
|
|
150
|
+
registerInertiaAdapter,
|
|
151
|
+
} from "@timeax/form-palette";
|
|
152
|
+
|
|
153
|
+
// App boot (once)
|
|
154
|
+
registerCoreVariants();
|
|
155
|
+
registerAxiosAdapter();
|
|
156
|
+
await registerInertiaAdapter();
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
> If you only use one adapter, only register the one you need.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Form
|
|
164
|
+
|
|
165
|
+
`Form` is the main form component exported from the package entrypoint (it is `CoreShell`, re-exported as `Form`).
|
|
166
|
+
|
|
167
|
+
### Minimal “local” form
|
|
168
|
+
|
|
169
|
+
Use `adapter="local"` when you want submission to be handled purely in JS.
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
function Example() {
|
|
173
|
+
return (
|
|
174
|
+
<Form
|
|
175
|
+
name="profile"
|
|
176
|
+
adapter="local"
|
|
177
|
+
onSubmit={(e) => {
|
|
178
|
+
// Current outbound snapshot
|
|
179
|
+
console.log(e.formData);
|
|
180
|
+
// You can also mutate outbound data via e.editData(...)
|
|
181
|
+
}}
|
|
182
|
+
>
|
|
183
|
+
<InputField
|
|
184
|
+
name="email"
|
|
185
|
+
variant="text"
|
|
186
|
+
label="Email"
|
|
187
|
+
required
|
|
188
|
+
/>
|
|
189
|
+
|
|
190
|
+
<InputField
|
|
191
|
+
name="age"
|
|
192
|
+
variant="number"
|
|
193
|
+
label="Age"
|
|
194
|
+
/>
|
|
195
|
+
|
|
196
|
+
<button type="submit">Save</button>
|
|
197
|
+
</Form>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Axios adapter
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
<Form
|
|
206
|
+
name="profile"
|
|
207
|
+
adapter="axios"
|
|
208
|
+
url="/api/profile"
|
|
209
|
+
method="post"
|
|
210
|
+
onSubmitted={(form, payload) => {
|
|
211
|
+
console.log(payload);
|
|
212
|
+
}}
|
|
213
|
+
>
|
|
214
|
+
<InputField name="email" variant="text" label="Email" required />
|
|
215
|
+
<button type="submit">Save</button>
|
|
216
|
+
</Form>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Inertia adapter
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
<Form
|
|
223
|
+
name="profile"
|
|
224
|
+
adapter="inertia"
|
|
225
|
+
url="/profile"
|
|
226
|
+
method="post"
|
|
227
|
+
onSubmitted={(form, payload) => {
|
|
228
|
+
// payload is the resolved inertia Page (or normalized error on failure)
|
|
229
|
+
console.log(payload);
|
|
230
|
+
}}
|
|
231
|
+
>
|
|
232
|
+
<InputField name="email" variant="text" label="Email" required />
|
|
233
|
+
<button type="submit">Save</button>
|
|
234
|
+
</Form>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## InputField
|
|
240
|
+
|
|
241
|
+
`InputField` is the form runtime’s “field wrapper”. It:
|
|
242
|
+
|
|
243
|
+
* Pulls the chosen `variant` from the variant registry and renders it.
|
|
244
|
+
* Connects to form state when used inside `<Form />` (by `name`).
|
|
245
|
+
* Computes layout (label placement, helper slots, spacing, etc.) by combining:
|
|
246
|
+
|
|
247
|
+
* variant defaults
|
|
248
|
+
* host overrides
|
|
249
|
+
* optional `variant.resolveLayout(...)`
|
|
250
|
+
* Normalizes validation results into a consistent list of errors.
|
|
251
|
+
|
|
252
|
+
### Basic usage
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
<InputField
|
|
256
|
+
name="username"
|
|
257
|
+
variant="text"
|
|
258
|
+
label="Username"
|
|
259
|
+
description="Public handle"
|
|
260
|
+
required
|
|
261
|
+
placeholder="@davy"
|
|
262
|
+
/>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Helper slots
|
|
266
|
+
|
|
267
|
+
Most helper UI (description, help text, error text, tags, etc.) is rendered through a layout graph.
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
<InputField
|
|
271
|
+
name="bio"
|
|
272
|
+
variant="textarea"
|
|
273
|
+
label="Bio"
|
|
274
|
+
helpText="Keep it short"
|
|
275
|
+
errorText=""
|
|
276
|
+
/>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Standalone mode
|
|
280
|
+
|
|
281
|
+
`InputField` can run without a surrounding `<Form />` (it will fall back to a self-managed field state).
|
|
282
|
+
|
|
283
|
+
```tsx
|
|
284
|
+
<InputField
|
|
285
|
+
variant="text"
|
|
286
|
+
label="Standalone"
|
|
287
|
+
defaultValue="Hello"
|
|
288
|
+
onChange={({ value }) => {
|
|
289
|
+
console.log(value);
|
|
290
|
+
}}
|
|
291
|
+
/>
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## Adapters
|
|
297
|
+
|
|
298
|
+
Adapters define how the form submits.
|
|
299
|
+
|
|
300
|
+
### Built-in adapter keys
|
|
301
|
+
|
|
302
|
+
* `local` – no network; calls your callbacks.
|
|
303
|
+
* `axios` – HTTP submit via Axios.
|
|
304
|
+
* `inertia` – submit via Inertia.
|
|
305
|
+
|
|
306
|
+
### Registering adapters
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
import { registerAdapter } from "@timeax/form-palette";
|
|
310
|
+
|
|
311
|
+
registerAdapter("my-adapter", (config) => {
|
|
312
|
+
return {
|
|
313
|
+
submit() {
|
|
314
|
+
// fire-and-forget
|
|
315
|
+
},
|
|
316
|
+
async send() {
|
|
317
|
+
// resolve a result shape that matches AdapterOk<"my-adapter">
|
|
318
|
+
return { data: config.data } as any;
|
|
319
|
+
},
|
|
320
|
+
run() {
|
|
321
|
+
this.submit();
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Adapter-specific props
|
|
328
|
+
|
|
329
|
+
Some adapters expose additional props on `<Form />` (e.g. `url`, `method`, `config`).
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Recommended boot order
|
|
334
|
+
|
|
335
|
+
1. Register variants (so InputField can resolve `variant` → component).
|
|
336
|
+
2. Register the adapters you will use.
|
|
337
|
+
3. Render forms.
|
|
338
|
+
|
|
339
|
+
```ts
|
|
340
|
+
registerCoreVariants();
|
|
341
|
+
registerAxiosAdapter();
|
|
342
|
+
await registerInertiaAdapter();
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
# Variant props + InputField usage
|
|
347
|
+
|
|
348
|
+
Below are the **variant-specific props** you can pass to `<InputField />` for:
|
|
349
|
+
|
|
350
|
+
* `text`
|
|
351
|
+
* `textarea`
|
|
352
|
+
* `toggle-group`
|
|
353
|
+
* `number`
|
|
354
|
+
* `phone`
|
|
355
|
+
* `password`
|
|
356
|
+
* `slider`
|
|
357
|
+
* `toggle`
|
|
358
|
+
* `treeselect`
|
|
359
|
+
* `multi-select`
|
|
360
|
+
* `select`
|
|
361
|
+
* `radio`
|
|
362
|
+
* `checkbox`
|
|
363
|
+
* `chips`
|
|
364
|
+
* `color`
|
|
365
|
+
* `date`
|
|
366
|
+
* `keyvalue`
|
|
367
|
+
* `editor`
|
|
368
|
+
* `json-editor`
|
|
369
|
+
* `file`
|
|
370
|
+
* `lister`
|
|
371
|
+
* `custom`
|
|
372
|
+
|
|
373
|
+
> Note: Some props like `value`, `onValue`, `error`, `disabled`, `readOnly`, `size`, `density` are typically **injected by the core runtime/InputField**. The tables focus on the *props you usually configure*.
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## text
|
|
378
|
+
|
|
379
|
+
### Variant props
|
|
380
|
+
|
|
381
|
+
| Prop | Description |
|
|
382
|
+
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
383
|
+
| `trim?: boolean` | **boolean** — If `true`, the value is trimmed **before validation** (visual input stays as typed). |
|
|
384
|
+
| `minLength?: number` | **number** — Minimum allowed length (after optional trimming). |
|
|
385
|
+
| `maxLength?: number` | **number** — Maximum allowed length (after optional trimming). |
|
|
386
|
+
| `joinControls?: boolean` | **boolean** — If `true` and there are controls, the input + controls share one box (border/radius/focus). |
|
|
387
|
+
| `extendBoxToControls?: boolean` | **boolean** — When `joinControls` is true, controls are either visually “inside” the same box (`true`) or separate (`false`). |
|
|
388
|
+
| `inputClassName?: string` | **string** — Extra classes for the **inner** `<input>` element (not the wrapper). |
|
|
389
|
+
| `prefix?: string` | **string** — Fixed prefix rendered as part of the visible input string (e.g. `₦`, `ID: `). |
|
|
390
|
+
| `suffix?: string` | **string** — Fixed suffix rendered as part of the visible input string (e.g. `%`, `kg`). |
|
|
391
|
+
| `stripPrefix?: boolean` | **boolean** — If `true` (default), the prefix is stripped from the emitted model value before calling `onValue` internally. |
|
|
392
|
+
| `stripSuffix?: boolean` | **boolean** — If `true` (default), the suffix is stripped from the emitted model value before calling `onValue` internally. |
|
|
393
|
+
| `mask?: string` | **string** — Mask pattern (PrimeReact style), e.g. `"99/99/9999"`, `"(999) 999-9999"`. |
|
|
394
|
+
| `maskDefinitions?: Record<string, RegExp>` | **Record** — Per-symbol slot definitions (kept for future custom engine; unused by current implementation). |
|
|
395
|
+
| `slotChar?: string` | **string** — Placeholder slot character (default `_`). |
|
|
396
|
+
| `autoClear?: boolean` | **boolean** — If `true`, “empty” masked values emit `""` instead of a fully-masked placeholder string. |
|
|
397
|
+
| `unmask?: "raw" \| "masked" \| boolean` | **union** — Controls whether the **model value** is raw vs masked. (`"raw"`/`true` ⇒ emit unmasked; `"masked"`/`false`/`undefined` ⇒ emit masked). |
|
|
398
|
+
| `maskInsertMode?: "stream" \| "caret"` | **union** — Reserved for future caret-mode logic (currently unused; kept for API compatibility). |
|
|
399
|
+
| `...inputProps` | All other standard `React.InputHTMLAttributes<HTMLInputElement>` (except `value`, `defaultValue`, `onChange`, `size`) are forwarded. |
|
|
400
|
+
|
|
401
|
+
### Sample usage (InputField)
|
|
402
|
+
|
|
403
|
+
```tsx
|
|
404
|
+
import { InputField } from "@timeax/form-palette";
|
|
405
|
+
|
|
406
|
+
export function ExampleText() {
|
|
407
|
+
return (
|
|
408
|
+
<InputField
|
|
409
|
+
variant="text"
|
|
410
|
+
name="phone"
|
|
411
|
+
label="Phone number"
|
|
412
|
+
description="We’ll use this for account recovery."
|
|
413
|
+
|
|
414
|
+
// semantic validation flags (core layer)
|
|
415
|
+
trim
|
|
416
|
+
minLength={11}
|
|
417
|
+
maxLength={11}
|
|
418
|
+
|
|
419
|
+
// mask + UI props (preset layer)
|
|
420
|
+
prefix="+234 "
|
|
421
|
+
mask="999 999 9999"
|
|
422
|
+
unmask="raw"
|
|
423
|
+
autoClear
|
|
424
|
+
|
|
425
|
+
// regular input attributes
|
|
426
|
+
type="tel"
|
|
427
|
+
inputMode="tel"
|
|
428
|
+
placeholder="803 123 4567"
|
|
429
|
+
/>
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
## textarea
|
|
437
|
+
|
|
438
|
+
### Variant props
|
|
439
|
+
|
|
440
|
+
| Prop | Description |
|
|
441
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
442
|
+
| `...textareaProps` | The textarea variant primarily forwards props from the underlying UI `Textarea` component (`UiTextareaProps`), excluding `value`, `defaultValue`, and `onChange` because the variant emits changes via the form runtime. |
|
|
443
|
+
|
|
444
|
+
### Sample usage (InputField)
|
|
445
|
+
|
|
446
|
+
```tsx
|
|
447
|
+
import { InputField } from "@timeax/form-palette";
|
|
448
|
+
|
|
449
|
+
export function ExampleTextarea() {
|
|
450
|
+
return (
|
|
451
|
+
<InputField
|
|
452
|
+
variant="textarea"
|
|
453
|
+
name="bio"
|
|
454
|
+
label="About you"
|
|
455
|
+
helpText="Keep it short and clear."
|
|
456
|
+
|
|
457
|
+
// typical textarea attributes (usually supported via UiTextareaProps)
|
|
458
|
+
rows={4}
|
|
459
|
+
placeholder="Tell us a little about yourself..."
|
|
460
|
+
/>
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## toggle-group
|
|
468
|
+
|
|
469
|
+
### Variant props
|
|
470
|
+
|
|
471
|
+
| Prop | Description |
|
|
472
|
+
| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
473
|
+
| `options: (ToggleOption \| string \| number \| boolean)[]` | Options for the toggle group. You can pass full option objects or primitive shorthand (primitives are normalized to `{ value: String(x), label: String(x) }`). |
|
|
474
|
+
| `multiple?: boolean` | **boolean** — If `true`, enables multi-select (value becomes an array of strings internally). |
|
|
475
|
+
| `variant?: "default" \| "outline"` | **union** — Visual style passed to the underlying ToggleGroup. |
|
|
476
|
+
| `layout?: "horizontal" \| "vertical" \| "grid"` | **union** — Layout mode. |
|
|
477
|
+
| `gridCols?: number` | **number** — Column count when `layout="grid"` (defaults to `2` in the component). |
|
|
478
|
+
| `fillWidth?: boolean` | **boolean** — If `true`, makes the group/items stretch to fill available width (adds `w-full` and related item sizing). |
|
|
479
|
+
| `optionValue?: string` | **string** — When `options` are custom objects, the property name to read `value` from (fallback: `obj.value`). |
|
|
480
|
+
| `optionLabel?: string` | **string** — When `options` are custom objects, the property name to read `label` from (fallback: `obj.label` or `String(value)`). |
|
|
481
|
+
| `optionIcon?: string` | **string** — When `options` are custom objects, the property name to read `icon` from (fallback: `obj.icon`). |
|
|
482
|
+
| `optionDisabled?: string` | **string** — When `options` are custom objects, the property name to read disabled flag from (fallback: `obj.disabled`). |
|
|
483
|
+
| `optionTooltip?: string` | **string** — When `options` are custom objects, the property name to read tooltip content from (fallback: `obj.tooltip`). |
|
|
484
|
+
| `optionMeta?: string` | **string** — When `options` are custom objects, the property name to read meta from (fallback: `obj.meta`). |
|
|
485
|
+
| `renderOption?: (option, isSelected) => React.ReactNode` | Custom renderer per option (receives normalized option + selected state). |
|
|
486
|
+
| `className?: string` | Class for the toggle group container. |
|
|
487
|
+
| `itemClassName?: string` | Base class applied to **all** toggle items. |
|
|
488
|
+
| `activeClassName?: string` | Class applied **only** to selected items (merged with default active styles). |
|
|
489
|
+
| `autoCap?: boolean` | If `true`, capitalizes the first letter of string labels. |
|
|
490
|
+
| `gap?: number` | Gap between buttons in **pixels** (applies to flex + grid layouts). |
|
|
491
|
+
|
|
492
|
+
**ToggleOption shape** (when not using primitive shorthand):
|
|
493
|
+
|
|
494
|
+
* `label: React.ReactNode`
|
|
495
|
+
* `value: string`
|
|
496
|
+
* `icon?: React.ReactNode`
|
|
497
|
+
* `disabled?: boolean`
|
|
498
|
+
* `tooltip?: React.ReactNode`
|
|
499
|
+
* `meta?: any`
|
|
500
|
+
|
|
501
|
+
### Sample usage (InputField)
|
|
502
|
+
|
|
503
|
+
```tsx
|
|
504
|
+
import { InputField } from "@timeax/form-palette";
|
|
505
|
+
|
|
506
|
+
export function ExampleToggleGroup() {
|
|
507
|
+
return (
|
|
508
|
+
<InputField
|
|
509
|
+
variant="toggle-group"
|
|
510
|
+
name="plan"
|
|
511
|
+
label="Choose a plan"
|
|
512
|
+
required
|
|
513
|
+
|
|
514
|
+
options={[
|
|
515
|
+
{ value: "basic", label: "Basic" },
|
|
516
|
+
{ value: "pro", label: "Pro" },
|
|
517
|
+
{ value: "team", label: "Team", disabled: true, tooltip: "Coming soon" },
|
|
518
|
+
]}
|
|
519
|
+
layout="horizontal"
|
|
520
|
+
variant="outline"
|
|
521
|
+
fillWidth
|
|
522
|
+
gap={8}
|
|
523
|
+
activeClassName="ring-1 ring-primary"
|
|
524
|
+
/>
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
## number
|
|
532
|
+
|
|
533
|
+
| Prop | Description |
|
|
534
|
+
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
535
|
+
| `showButtons` | When `true`, renders built-in step controls (±) alongside the number input. |
|
|
536
|
+
| `buttonLayout` | Layout for the step controls when `showButtons` is enabled. Supported layouts: `"stacked"` (vertical on the right) and `"inline"` (`-` left, `+` right). |
|
|
537
|
+
| `step` | Step amount used by the built-in controls and stepping logic (forwarded to the underlying number input). |
|
|
538
|
+
| `min` | Minimum numeric value constraint (used by the stepping logic and forwarded to the underlying number input). |
|
|
539
|
+
| `max` | Maximum numeric value constraint (used by the stepping logic and forwarded to the underlying number input). |
|
|
540
|
+
|
|
541
|
+
> Also accepts the rest of the underlying `InputNumberProps` (they’re forwarded to the number input).
|
|
542
|
+
|
|
543
|
+
**Sample**
|
|
544
|
+
|
|
545
|
+
```tsx
|
|
546
|
+
<InputField
|
|
547
|
+
variant="number"
|
|
548
|
+
name="quantity"
|
|
549
|
+
label="Quantity"
|
|
550
|
+
description="How many items?"
|
|
551
|
+
min={1}
|
|
552
|
+
max={99}
|
|
553
|
+
step={1}
|
|
554
|
+
showButtons
|
|
555
|
+
buttonLayout="inline"
|
|
556
|
+
/>
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
## password
|
|
562
|
+
|
|
563
|
+
| Prop | Description |
|
|
564
|
+
| ----------------------- | --------------------------------------------------------- |
|
|
565
|
+
| `autoComplete` | Sets the input `autoComplete` hint for password managers. |
|
|
566
|
+
| `minLength` | Minimum allowed length (HTML constraint). |
|
|
567
|
+
| `maxLength` | Maximum allowed length (HTML constraint). |
|
|
568
|
+
| `revealToggle` | Show / hide the reveal toggle button. |
|
|
569
|
+
| `defaultRevealed` | Initial revealed state (defaults to hidden). |
|
|
570
|
+
| `onRevealChange` | Called when revealed state changes. |
|
|
571
|
+
| `renderToggleIcon` | Custom renderer for the toggle icon. |
|
|
572
|
+
| `toggleAriaLabel` | ARIA label for the toggle button. |
|
|
573
|
+
| `toggleButtonClassName` | ClassName hook for the toggle button. |
|
|
574
|
+
| `strengthMeter` | Enable the strength meter UI. |
|
|
575
|
+
| `ruleDefinitions` | Custom rule definitions used by the strength meter. |
|
|
576
|
+
| `ruleUses` | Which rules should be considered when computing strength. |
|
|
577
|
+
| `meterStyle` | Visual style of the strength meter. |
|
|
578
|
+
| `renderMeter` | Custom renderer for the full meter block. |
|
|
579
|
+
| `meterWrapperClassName` | ClassName hook for the meter wrapper. |
|
|
580
|
+
|
|
581
|
+
> Password inherits the *visual* props from the `text` variant (it reuses the text UI), but controls `type`, value wiring, and trailing controls internally.
|
|
582
|
+
|
|
583
|
+
**Sample**
|
|
584
|
+
|
|
585
|
+
```tsx
|
|
586
|
+
<InputField
|
|
587
|
+
variant="password"
|
|
588
|
+
name="password"
|
|
589
|
+
label="Password"
|
|
590
|
+
required
|
|
591
|
+
minLength={8}
|
|
592
|
+
autoComplete="new-password"
|
|
593
|
+
revealToggle
|
|
594
|
+
strengthMeter
|
|
595
|
+
ruleUses={["minLen", "upper", "lower", "number", "symbol"]}
|
|
596
|
+
/>
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
601
|
+
## phone
|
|
602
|
+
|
|
603
|
+
| Prop | Description |
|
|
604
|
+
| ---------------------- | ------------------------------------------------------------------------------- |
|
|
605
|
+
| `countries` | List of allowed countries (and their dial codes) shown in the country selector. |
|
|
606
|
+
| `defaultCountryCode` | The default selected country code (e.g. `"NG"`). |
|
|
607
|
+
| `allowCountrySearch` | Enable searching in the country list. |
|
|
608
|
+
| `allowCountryClear` | Allow clearing the selected country. |
|
|
609
|
+
| `countryPlaceholder` | Placeholder text for the country selector. |
|
|
610
|
+
| `showFlag` | Show the flag in the country selector. |
|
|
611
|
+
| `showCountryName` | Show the country name in the selector / list. |
|
|
612
|
+
| `showDialCode` | Show dial codes in the country list. |
|
|
613
|
+
| `showSelectedDialCode` | Show the selected dial code next to the input. |
|
|
614
|
+
| `dialCodeDelimiter` | Delimiter between dial code and the input number (e.g. `" "`, `"-"`). |
|
|
615
|
+
| `valueMode` | Controls how the field value is emitted (e.g. E.164 vs local formats). |
|
|
616
|
+
| `mask` | Optional input mask (string or resolver function). |
|
|
617
|
+
| `lazy` | IMask “lazy” mode (placeholder chars hidden until typed). |
|
|
618
|
+
| `keepCharPositions` | IMask option to keep character positions stable. |
|
|
619
|
+
| `unmask` | How the underlying mask value is emitted (IMask option). |
|
|
620
|
+
|
|
621
|
+
> Phone inherits the *visual* props from the `text` variant, but controls value parsing/formatting and the country selector internally.
|
|
622
|
+
|
|
623
|
+
**Sample**
|
|
624
|
+
|
|
625
|
+
```tsx
|
|
626
|
+
<InputField
|
|
627
|
+
variant="phone"
|
|
628
|
+
name="phone"
|
|
629
|
+
label="Phone number"
|
|
630
|
+
defaultCountryCode="NG"
|
|
631
|
+
allowCountrySearch
|
|
632
|
+
showSelectedDialCode
|
|
633
|
+
dialCodeDelimiter=" "
|
|
634
|
+
valueMode="e164"
|
|
635
|
+
/>
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
---
|
|
639
|
+
|
|
640
|
+
## Slider (`slider`)
|
|
641
|
+
|
|
642
|
+
Value type: `number | undefined`
|
|
643
|
+
|
|
644
|
+
### Props
|
|
645
|
+
|
|
646
|
+
| Prop | Description |
|
|
647
|
+
| -------------------------- | ------------------------------------------------------ |
|
|
648
|
+
| `value` | Current slider value (number). |
|
|
649
|
+
| `onValue` | Called when the value changes. |
|
|
650
|
+
| `error` | Validation/error message for the field. |
|
|
651
|
+
| `disabled` | Disables interaction. |
|
|
652
|
+
| `readOnly` | Prevents changes but still displays the value. |
|
|
653
|
+
| `size` | Sizing preset for the control. |
|
|
654
|
+
| `density` | Density preset for the control (spacing). |
|
|
655
|
+
| `min` | Minimum value (default 0). |
|
|
656
|
+
| `max` | Maximum value (default 100). |
|
|
657
|
+
| `step` | Step size (default 1). |
|
|
658
|
+
| `showValue` | Show the current numeric value next to the slider. |
|
|
659
|
+
| `valuePlacement` | Where to render the value when `showValue` is enabled. |
|
|
660
|
+
| `formatValue` | Format the displayed value. |
|
|
661
|
+
| `className` | Root wrapper className. |
|
|
662
|
+
| `sliderClassName` | Slider track/handle className. |
|
|
663
|
+
| `valueClassName` | Value label className. |
|
|
664
|
+
| `leadingIcons` | Icons rendered before the slider/value. |
|
|
665
|
+
| `trailingIcons` | Icons rendered after the slider/value. |
|
|
666
|
+
| `icon` | Single icon (shorthand). |
|
|
667
|
+
| `iconGap` | Gap between icon(s) and content. |
|
|
668
|
+
| `leadingIconSpacing` | Spacing between multiple leading icons. |
|
|
669
|
+
| `trailingIconSpacing` | Spacing between multiple trailing icons. |
|
|
670
|
+
| `leadingControl` | Optional control element rendered before the slider. |
|
|
671
|
+
| `trailingControl` | Optional control element rendered after the slider. |
|
|
672
|
+
| `leadingControlClassName` | Wrapper className for the leading control. |
|
|
673
|
+
| `trailingControlClassName` | Wrapper className for the trailing control. |
|
|
674
|
+
| `joinControls` | Join controls visually to the slider box. |
|
|
675
|
+
| `extendBoxToControls` | Extend slider “box” background behind controls. |
|
|
676
|
+
| `controlVariant` | Variant for the +/- controls (if shown). |
|
|
677
|
+
| `controlStep` | Step used by +/- controls (falls back to `step`). |
|
|
678
|
+
| `controlDecrementIcon` | Custom icon node for decrement control. |
|
|
679
|
+
| `controlIncrementIcon` | Custom icon node for increment control. |
|
|
680
|
+
|
|
681
|
+
### Example
|
|
682
|
+
|
|
683
|
+
```tsx
|
|
684
|
+
<InputField
|
|
685
|
+
name="rating"
|
|
686
|
+
label="Rating"
|
|
687
|
+
variant="slider"
|
|
688
|
+
min={0}
|
|
689
|
+
max={100}
|
|
690
|
+
step={5}
|
|
691
|
+
showValue
|
|
692
|
+
valuePlacement="right"
|
|
693
|
+
formatValue={(v) => `${v}%`}
|
|
694
|
+
controlVariant="ghost"
|
|
695
|
+
controlStep={5}
|
|
696
|
+
/>
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
## Toggle (`toggle`)
|
|
702
|
+
|
|
703
|
+
Value type: `boolean | undefined`
|
|
704
|
+
|
|
705
|
+
### Props
|
|
706
|
+
|
|
707
|
+
| Prop | Description |
|
|
708
|
+
| ---------------------- | ------------------------------------------ |
|
|
709
|
+
| `value` | Current toggle value (boolean). |
|
|
710
|
+
| `onValue` | Called when the value changes. |
|
|
711
|
+
| `error` | Validation/error message for the field. |
|
|
712
|
+
| `size` | Visual size of the switch. |
|
|
713
|
+
| `density` | Spacing density for the wrapper. |
|
|
714
|
+
| `onText` | Text shown when the value is `true`. |
|
|
715
|
+
| `offText` | Text shown when the value is `false`. |
|
|
716
|
+
| `label` | Optional label rendered beside the switch. |
|
|
717
|
+
| `containerClassName` | Wrapper className. |
|
|
718
|
+
| `switchRootClassName` | ClassName for the Switch root element. |
|
|
719
|
+
| `switchThumbClassName` | ClassName for the Switch thumb. |
|
|
720
|
+
|
|
721
|
+
### Example
|
|
722
|
+
|
|
723
|
+
```tsx
|
|
724
|
+
<InputField
|
|
725
|
+
name="enabled"
|
|
726
|
+
label="Enabled"
|
|
727
|
+
variant="toggle"
|
|
728
|
+
onText="On"
|
|
729
|
+
offText="Off"
|
|
730
|
+
density="sm"
|
|
731
|
+
/>
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
---
|
|
735
|
+
|
|
736
|
+
## TreeSelect (`treeselect`)
|
|
737
|
+
|
|
738
|
+
Value type: `TreeKey | TreeKey[] | undefined` (where `TreeKey` is `string | number`)
|
|
739
|
+
|
|
740
|
+
### Base props
|
|
741
|
+
|
|
742
|
+
| Prop | Description |
|
|
743
|
+
| ----------------------- | ------------------------------------------------------------------------------ |
|
|
744
|
+
| `value` | Selected key(s). Single value is a key; multi is an array of keys. |
|
|
745
|
+
| `onValue` | Called when selection changes. |
|
|
746
|
+
| `error` | Validation/error message for the field. |
|
|
747
|
+
| `disabled` | Disables interaction. |
|
|
748
|
+
| `readOnly` | Prevents changes but still displays the selection. |
|
|
749
|
+
| `size` | Sizing preset for trigger/list rows. |
|
|
750
|
+
| `density` | Density preset for trigger/list rows. |
|
|
751
|
+
| `options` | Tree of options to render. |
|
|
752
|
+
| `multiple` | Allow selecting multiple keys (returns `TreeKey[]`). |
|
|
753
|
+
| `autoCap` | (Option mapping helper) Auto-capitalize generated labels when mapping options. |
|
|
754
|
+
| `optionLabel` | (Option mapping helper) Label accessor (key name or function). |
|
|
755
|
+
| `optionValue` | (Option mapping helper) Value accessor (key name or function). |
|
|
756
|
+
| `optionDescription` | (Option mapping helper) Description accessor. |
|
|
757
|
+
| `optionDisabled` | (Option mapping helper) Disabled accessor. |
|
|
758
|
+
| `optionIcon` | (Option mapping helper) Icon accessor. |
|
|
759
|
+
| `optionKey` | (Option mapping helper) Key accessor. |
|
|
760
|
+
| `searchable` | Enable search input in the dropdown. |
|
|
761
|
+
| `searchPlaceholder` | Placeholder text for the search input. |
|
|
762
|
+
| `emptyLabel` | Content shown when there are no options. |
|
|
763
|
+
| `emptySearchText` | Content shown when search returns no matches. |
|
|
764
|
+
| `clearable` | Show a clear/reset action. |
|
|
765
|
+
| `placeholder` | Text shown when nothing is selected. |
|
|
766
|
+
| `className` | Wrapper className for the whole field. |
|
|
767
|
+
| `triggerClassName` | ClassName for the trigger/button area. |
|
|
768
|
+
| `contentClassName` | ClassName for the dropdown content. |
|
|
769
|
+
| `renderOption` | Custom renderer for an option row. |
|
|
770
|
+
| `renderValue` | Custom renderer for the trigger's current value display. |
|
|
771
|
+
| `expandAll` | Expand all nodes by default. |
|
|
772
|
+
| `defaultExpandedValues` | Keys that should start expanded by default. |
|
|
773
|
+
| `leafOnly` | Restrict selection to leaf nodes only. |
|
|
774
|
+
|
|
775
|
+
### Mode: default (`mode` omitted or "default")
|
|
776
|
+
|
|
777
|
+
| Prop | Description |
|
|
778
|
+
| -------------------------- | --------------------------------------------------------------- |
|
|
779
|
+
| `mode` | Omit or set to `'default'` to use the standard field trigger. |
|
|
780
|
+
| `button` | Optional custom trigger button renderer. |
|
|
781
|
+
| `selectedBadge` | Optional selected-count badge renderer. |
|
|
782
|
+
| `icon` | Single icon rendered near the trigger value. |
|
|
783
|
+
| `iconGap` | Gap between icon and content. |
|
|
784
|
+
| `leadingIcons` | One or more icons before the value. |
|
|
785
|
+
| `trailingIcons` | One or more icons after the value. |
|
|
786
|
+
| `leadingControl` | Custom control element before the trigger (e.g., clear button). |
|
|
787
|
+
| `trailingControl` | Custom control element after the trigger. |
|
|
788
|
+
| `leadingControlClassName` | ClassName for leading control wrapper. |
|
|
789
|
+
| `trailingControlClassName` | ClassName for trailing control wrapper. |
|
|
790
|
+
| `joinControls` | Visually join controls to the trigger box (shared border). |
|
|
791
|
+
| `extendBoxToControls` | Extend trigger background behind controls. |
|
|
792
|
+
| `rootClassName` | Wrapper className around controls + trigger. |
|
|
793
|
+
| `triggerInnerClassName` | ClassName for the trigger’s inner content. |
|
|
794
|
+
|
|
795
|
+
### Mode: button (`mode="button"`)
|
|
796
|
+
|
|
797
|
+
| Prop | Description |
|
|
798
|
+
| -------------------------- | --------------------------------------------------------------- |
|
|
799
|
+
| `mode` | Set to `'button'` to render a button-style trigger. |
|
|
800
|
+
| `button` | (mode='button') If provided, this is the trigger renderer. |
|
|
801
|
+
| `selectedBadge` | (mode='button') Selected-count badge renderer. |
|
|
802
|
+
| `icon` | Single icon rendered near the trigger value. |
|
|
803
|
+
| `iconGap` | Gap between icon and content. |
|
|
804
|
+
| `leadingIcons` | One or more icons before the value. |
|
|
805
|
+
| `trailingIcons` | One or more icons after the value. |
|
|
806
|
+
| `leadingControl` | Custom control element before the trigger (e.g., clear button). |
|
|
807
|
+
| `trailingControl` | Custom control element after the trigger. |
|
|
808
|
+
| `leadingControlClassName` | ClassName for leading control wrapper. |
|
|
809
|
+
| `trailingControlClassName` | ClassName for trailing control wrapper. |
|
|
810
|
+
| `joinControls` | Visually join controls to the trigger box (shared border). |
|
|
811
|
+
| `extendBoxToControls` | Extend trigger background behind controls. |
|
|
812
|
+
|
|
813
|
+
### Example (default mode)
|
|
814
|
+
|
|
815
|
+
```tsx
|
|
816
|
+
<InputField
|
|
817
|
+
name="category"
|
|
818
|
+
label="Category"
|
|
819
|
+
variant="treeselect"
|
|
820
|
+
options={[
|
|
821
|
+
{
|
|
822
|
+
key: "social",
|
|
823
|
+
label: "Social",
|
|
824
|
+
children: [
|
|
825
|
+
{ key: "twitter", label: "Twitter" },
|
|
826
|
+
{ key: "instagram", label: "Instagram" },
|
|
827
|
+
],
|
|
828
|
+
},
|
|
829
|
+
]}
|
|
830
|
+
searchable
|
|
831
|
+
placeholder="Pick one…"
|
|
832
|
+
/>
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
### Example (multiple + button mode)
|
|
836
|
+
|
|
837
|
+
```tsx
|
|
838
|
+
<InputField
|
|
839
|
+
name="tags"
|
|
840
|
+
variant="treeselect"
|
|
841
|
+
mode="button"
|
|
842
|
+
multiple
|
|
843
|
+
options={[
|
|
844
|
+
{ key: 1, label: "Starter" },
|
|
845
|
+
{ key: 2, label: "Pro" },
|
|
846
|
+
{ key: 3, label: "Enterprise" },
|
|
847
|
+
]}
|
|
848
|
+
/>
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
## multi-select
|
|
852
|
+
|
|
853
|
+
### Variant props
|
|
854
|
+
|
|
855
|
+
| Prop | Description |
|
|
856
|
+
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
857
|
+
| `options` | Options for the multi-select. Accepts primitives or objects. |
|
|
858
|
+
| `autoCap` | Capitalise the first letter of the label (when the resolved label is a string). |
|
|
859
|
+
| `optionLabel` | How to read the label from each option (string key or mapper function). If omitted: uses `label`, else `String(value)`. |
|
|
860
|
+
| `optionValue` | How to read the value from each option (string key or mapper function). If omitted: primitives are used directly, else `value`. |
|
|
861
|
+
| `optionDescription` | How to read the description from each option (string key or mapper function). If omitted: uses `description`. |
|
|
862
|
+
| `optionIcon` | How to read the icon from each option (string key or mapper function). If omitted: uses `icon`. |
|
|
863
|
+
| `optionDisabled` | How to detect disabled options (string key or mapper function). If omitted: uses `disabled`. |
|
|
864
|
+
| `optionKey` | How to compute stable keys for items (string key or mapper function). If omitted: uses index. |
|
|
865
|
+
| `searchable` | Enable search field in the list. |
|
|
866
|
+
| `searchPlaceholder` | Placeholder for the search field. |
|
|
867
|
+
| `emptySearchText` | Text when there are no matches for the current search. |
|
|
868
|
+
| `showSelectAll` | Show a “Select all” row. |
|
|
869
|
+
| `selectAllLabel` | Label for the “Select all” row. |
|
|
870
|
+
| `selectAllPosition` | Where to render the “Select all” row. |
|
|
871
|
+
| `clearable` | Show a clear action when there is at least one selection. |
|
|
872
|
+
| `placeholder` | Placeholder when nothing is selected. |
|
|
873
|
+
| `renderOption` | Optional global renderer for an option row. (An option may also provide its own per-option `render`.) |
|
|
874
|
+
| `renderCheckbox` | Optional renderer for the checkbox element used by each option row. |
|
|
875
|
+
| `renderValue` | Custom renderer for the trigger summary (selected values). |
|
|
876
|
+
| `maxListHeight` | Max height for the list (px). |
|
|
877
|
+
| `className` | Wrapper class for the whole variant. |
|
|
878
|
+
| `triggerClassName` | Class for the trigger button. |
|
|
879
|
+
| `contentClassName` | Class for the popover content container. |
|
|
880
|
+
|
|
881
|
+
**Options can be passed as:**
|
|
882
|
+
|
|
883
|
+
* primitives: `['ng', 'gh', 'ke']`
|
|
884
|
+
* objects: `[{ label, value, ...extra }]`
|
|
885
|
+
|
|
886
|
+
### Mode and trigger props
|
|
887
|
+
|
|
888
|
+
| Prop | Description |
|
|
889
|
+
| ----------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
890
|
+
| `mode` | Choose trigger style: `"default"` (standard input-like trigger) or `"button"` (custom trigger node). |
|
|
891
|
+
| `leadingIcons` | Icons shown before the summary text inside the trigger (default mode). |
|
|
892
|
+
| `trailingIcons` | Icons shown after the summary / clear button inside the trigger. |
|
|
893
|
+
| `icon` | Single icon shorthand (falls into `leadingIcons`). |
|
|
894
|
+
| `iconGap` | Base gap (px) used between icon groups and text. |
|
|
895
|
+
| `leadingIconSpacing` | Override spacing (px) between leading icons and text. |
|
|
896
|
+
| `trailingIconSpacing` | Override spacing (px) between trailing icons and the right-side controls. |
|
|
897
|
+
| `leadingControl` | Custom node rendered on the far-left *outside* the trigger (e.g., a compact action button). |
|
|
898
|
+
| `trailingControl` | Custom node rendered on the far-right *outside* the trigger. |
|
|
899
|
+
| `leadingControlClassName` | ClassName for the leading control wrapper. |
|
|
900
|
+
| `trailingControlClassName` | ClassName for the trailing control wrapper. |
|
|
901
|
+
| `joinControls` | Visually joins leading/trailing controls with the trigger (no gaps). |
|
|
902
|
+
| `extendBoxToControls` | Extends the input box styling (border/background) around the joined controls. |
|
|
903
|
+
| `button` | Used when `mode="button"`. If provided, this is the trigger. If not provided, `children` is used. |
|
|
904
|
+
| `children` | When `mode="button"` and `button` is not provided, `children` is used as the trigger content. |
|
|
905
|
+
| `selectedBadge` | Selected-count badge (mode="button" only). |
|
|
906
|
+
| `selectedBadgeHiddenWhenZero` | Hide the badge when selected count is 0 (mode="button"). |
|
|
907
|
+
| `selectedBadgeClassName` | ClassName for the selected-count badge. |
|
|
908
|
+
| `selectedBadgePlacement` | Where to place the badge relative to the trigger content (mode="button"). |
|
|
909
|
+
|
|
910
|
+
### Sample usage
|
|
911
|
+
|
|
912
|
+
```tsx
|
|
913
|
+
import { InputField } from "@timeax/form-palette"; // adjust import to your project
|
|
914
|
+
|
|
915
|
+
export function MultiSelectExample() {
|
|
916
|
+
return (
|
|
917
|
+
<InputField
|
|
918
|
+
variant="multi-select"
|
|
919
|
+
name="countries"
|
|
920
|
+
label="Countries"
|
|
921
|
+
description="Pick one or more countries."
|
|
922
|
+
options={[
|
|
923
|
+
{ label: "Nigeria", value: "ng" },
|
|
924
|
+
{ label: "Ghana", value: "gh" },
|
|
925
|
+
{ label: "Kenya", value: "ke" },
|
|
926
|
+
]}
|
|
927
|
+
searchable
|
|
928
|
+
searchPlaceholder="Search countries..."
|
|
929
|
+
showSelectAll
|
|
930
|
+
selectAllLabel="Select all"
|
|
931
|
+
clearable
|
|
932
|
+
placeholder="Select countries..."
|
|
933
|
+
/>
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
---
|
|
939
|
+
|
|
940
|
+
## radio
|
|
941
|
+
|
|
942
|
+
### Variant props
|
|
943
|
+
|
|
944
|
+
| Prop | Description |
|
|
945
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
946
|
+
| `items` | Alias of `options` (list of items to render). |
|
|
947
|
+
| `options` | Options to render. Supports `RadioItem` objects or custom items via mappers. |
|
|
948
|
+
| `mappers` | Mapping functions for `TItem → value/label/description/disabled/key/render`. Takes precedence over `option*` props. |
|
|
949
|
+
| `optionValue` | Shortcut mapping for **value** (used only if `mappers` is not provided). |
|
|
950
|
+
| `optionLabel` | Shortcut mapping for **label** (used only if `mappers` is not provided). |
|
|
951
|
+
| `renderOption` | Global option renderer (can be overridden per item via `item.render`). |
|
|
952
|
+
| `layout` | Layout mode: `"stack"` or `"grid"`. |
|
|
953
|
+
| `columns` | Number of columns when `layout="grid"`. |
|
|
954
|
+
| `itemGapPx` | Gap (px) between items. |
|
|
955
|
+
| `size` | Variant size override for the radio control. |
|
|
956
|
+
| `density` | Variant density override for spacing. |
|
|
957
|
+
| `autoCap` | Auto-capitalise labels when the resolved label is a string. |
|
|
958
|
+
| `aria-label` | ARIA label forwarded to the radio group wrapper. |
|
|
959
|
+
| `aria-labelledby` | ARIA `aria-labelledby` forwarded to the radio group wrapper. |
|
|
960
|
+
| `aria-describedby` | ARIA `aria-describedby` forwarded to the radio group wrapper. |
|
|
961
|
+
| `groupClassName` | ClassName for the group wrapper. |
|
|
962
|
+
| `optionClassName` | ClassName for each option container. |
|
|
963
|
+
| `labelClassName` | ClassName for the option label. |
|
|
964
|
+
| `descriptionClassName` | ClassName for the option description. |
|
|
965
|
+
| `id` | Optional `id` for the group wrapper. |
|
|
966
|
+
| `name` | HTML `name` attribute to group radio inputs. |
|
|
967
|
+
| `className` | Alias for `groupClassName`. |
|
|
968
|
+
|
|
969
|
+
### Supported option shapes
|
|
970
|
+
|
|
971
|
+
* `RadioItem<TValue>`: `{ value, label, description?, disabled?, key?, render? }`
|
|
972
|
+
* Any `TItem` shape, as long as you provide `mappers` (or `optionLabel`/`optionValue` shortcuts)
|
|
973
|
+
|
|
974
|
+
### Sample usage
|
|
975
|
+
|
|
976
|
+
```tsx
|
|
977
|
+
import { InputField } from "@timeax/form-palette"; // adjust import to your project
|
|
978
|
+
|
|
979
|
+
export function RadioExample() {
|
|
980
|
+
return (
|
|
981
|
+
<InputField
|
|
982
|
+
variant="radio"
|
|
983
|
+
name="plan"
|
|
984
|
+
label="Plan"
|
|
985
|
+
description="Choose a plan."
|
|
986
|
+
items={[
|
|
987
|
+
{ value: "free", label: "Free", description: "Basic features" },
|
|
988
|
+
{ value: "pro", label: "Pro", description: "Everything included" },
|
|
989
|
+
{ value: "team", label: "Team", description: "For small teams" },
|
|
990
|
+
]}
|
|
991
|
+
layout="grid"
|
|
992
|
+
columns={3}
|
|
993
|
+
autoCap
|
|
994
|
+
/>
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
---
|
|
1000
|
+
|
|
1001
|
+
## select
|
|
1002
|
+
|
|
1003
|
+
### Variant props
|
|
1004
|
+
|
|
1005
|
+
| Prop | Description |
|
|
1006
|
+
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
1007
|
+
| `options` | Options for the select. Accepts primitives or objects. |
|
|
1008
|
+
| `autoCap` | Capitalise the first letter of the label (when the resolved label is a string). |
|
|
1009
|
+
| `optionLabel` | How to read the label from each option (string key or mapper function). If omitted: uses `label`, else `String(value)`. |
|
|
1010
|
+
| `optionValue` | How to read the value from each option (string key or mapper function). If omitted: primitives are used directly, else `value`. |
|
|
1011
|
+
| `optionDescription` | How to read the description from each option (string key or mapper function). If omitted: uses `description`. |
|
|
1012
|
+
| `optionIcon` | How to read the icon from each option (string key or mapper function). If omitted: uses `icon`. |
|
|
1013
|
+
| `optionDisabled` | How to detect disabled options (string key or mapper function). If omitted: uses `disabled`. |
|
|
1014
|
+
| `optionKey` | How to compute stable keys for items (string key or mapper function). If omitted: uses index. |
|
|
1015
|
+
| `searchable` | Enable search field in the list. |
|
|
1016
|
+
| `searchPlaceholder` | Placeholder for the search field. |
|
|
1017
|
+
| `emptySearchText` | Text shown when there are no matches for the current search. |
|
|
1018
|
+
| `clearable` | Show a clear action (x) when a value is selected. |
|
|
1019
|
+
| `emptyLabel` | Label to show when no value is selected (acts like “none”). |
|
|
1020
|
+
| `placeholder` | Placeholder when no value is selected (and `emptyLabel` not shown). |
|
|
1021
|
+
| `renderOption` | Optional global renderer for a list option. (An option may also provide its own per-option `render`.) |
|
|
1022
|
+
| `renderValue` | Custom renderer for the trigger display (selected option). |
|
|
1023
|
+
| `virtualScroll` | Enable virtual scrolling for large option lists. |
|
|
1024
|
+
| `virtualScrollThreshold` | Number of items after which virtual scroll is enabled (when `virtualScroll=true`). |
|
|
1025
|
+
| `virtualScrollPageSize` | How many items to render per virtual page/chunk. |
|
|
1026
|
+
| `leadingControl` | Custom node rendered on the far-left *outside* the trigger. |
|
|
1027
|
+
| `leadingControlClassName` | ClassName for the leading control wrapper. |
|
|
1028
|
+
| `leadingIconSpacing` | Override spacing (px) between leading icons and the selected value. |
|
|
1029
|
+
| `trailingIcons` | Icons shown on the right side of the trigger. |
|
|
1030
|
+
| `trailingControl` | Custom node rendered on the far-right *outside* the trigger. |
|
|
1031
|
+
| `trailingControlClassName` | ClassName for the trailing control wrapper. |
|
|
1032
|
+
| `trailingIconSpacing` | Override spacing (px) between selected value and trailing icons. |
|
|
1033
|
+
| `joinControls` | Visually joins leading/trailing controls with the trigger (no gaps). |
|
|
1034
|
+
| `extendBoxToControls` | Extends the input box styling (border/background) around the joined controls. |
|
|
1035
|
+
| `icon` | Single icon shorthand (used in default mode). |
|
|
1036
|
+
| `iconGap` | Base gap (px) used between icon and text. |
|
|
1037
|
+
| `className` | Wrapper class for the whole variant. |
|
|
1038
|
+
| `triggerClassName` | Class for the trigger button. |
|
|
1039
|
+
| `contentClassName` | Class for the popover content container. |
|
|
1040
|
+
| `mode` | Choose trigger style: `"default"` (normal select trigger) or `"button"` (custom trigger node). |
|
|
1041
|
+
| `leadingIcons` | Icons shown before the selected value inside the trigger (default mode). |
|
|
1042
|
+
| `button` | Used when `mode="button"`. If provided, this is the trigger. If not provided, `children` is used. |
|
|
1043
|
+
| `children` | When `mode="button"` and `button` is not provided, `children` is used as the trigger content. |
|
|
1044
|
+
|
|
1045
|
+
**Options can be passed as:**
|
|
1046
|
+
|
|
1047
|
+
* primitives: `['ng', 'gh', 'ke']`
|
|
1048
|
+
* objects: `[{ label, value, ...extra }]`
|
|
1049
|
+
|
|
1050
|
+
### Sample usage
|
|
1051
|
+
|
|
1052
|
+
```tsx
|
|
1053
|
+
import { InputField } from "@timeax/form-palette"; // adjust import to your project
|
|
1054
|
+
|
|
1055
|
+
export function SelectExample() {
|
|
1056
|
+
return (
|
|
1057
|
+
<InputField
|
|
1058
|
+
variant="select"
|
|
1059
|
+
name="country"
|
|
1060
|
+
label="Country"
|
|
1061
|
+
options={[
|
|
1062
|
+
{ label: "Nigeria", value: "ng", description: "NG" },
|
|
1063
|
+
{ label: "Ghana", value: "gh", description: "GH" },
|
|
1064
|
+
{ label: "Kenya", value: "ke", description: "KE" },
|
|
1065
|
+
]}
|
|
1066
|
+
searchable
|
|
1067
|
+
searchPlaceholder="Search..."
|
|
1068
|
+
clearable
|
|
1069
|
+
emptyLabel="No selection"
|
|
1070
|
+
placeholder="Select a country..."
|
|
1071
|
+
virtualScroll
|
|
1072
|
+
virtualScrollThreshold={80}
|
|
1073
|
+
virtualScrollPageSize={30}
|
|
1074
|
+
/>
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
```
|
|
1078
|
+
|
|
1079
|
+
## checkbox
|
|
1080
|
+
|
|
1081
|
+
| Prop | Description | | |
|
|
1082
|
+
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | ---------------------- |
|
|
1083
|
+
| `single` | Render a single boolean checkbox instead of an option group. | | |
|
|
1084
|
+
| `singleLabel` | Label text shown next to the checkbox in single mode. | | |
|
|
1085
|
+
| `singleDescription` | Optional helper text shown under the single checkbox label. | | |
|
|
1086
|
+
| `options` | Option list for group mode. Accepts primitives or objects (normalized to `{ value, label, description?, disabled?, key? }`). | | |
|
|
1087
|
+
| `items` | Alias for `options` (alternate naming). | | |
|
|
1088
|
+
| `mappers` | Override normalization: `{ mapValue?, mapLabel?, mapDescription?, mapDisabled?, mapKey? }`. | | |
|
|
1089
|
+
| `optionValue` | Map item → option value (overrides `mappers.mapValue`). | | |
|
|
1090
|
+
| `optionLabel` | Map item → option label (overrides `mappers.mapLabel`). | | |
|
|
1091
|
+
| `optionDescription` | Map item → option description (overrides `mappers.mapDescription`). | | |
|
|
1092
|
+
| `optionDisabled` | Map item → disabled boolean (overrides `mappers.mapDisabled`). | | |
|
|
1093
|
+
| `optionKey` | Map item → stable React key (overrides `mappers.mapKey`). | | |
|
|
1094
|
+
| `renderOption` | Custom option renderer (gets `{ item, index, state, effectiveTristate, disabled, size, density, checkboxId, click(), checkbox }`). | | |
|
|
1095
|
+
| `tristate` | Enable tri-state cycling for group options (`none → true → false → none`). | | |
|
|
1096
|
+
| `layout` | Group layout: `"list"` or `"grid"`. | | |
|
|
1097
|
+
| `columns` | Grid columns when `layout="grid"` (default: `2`). | | |
|
|
1098
|
+
| `itemGapPx` | Gap between options in px (defaults vary by layout). | | |
|
|
1099
|
+
| `size` | Checkbox size: `"sm" | "md" | "lg"`(default:`"md"`). |
|
|
1100
|
+
| `density` | Spacing preset: `"compact" | "normal"`(default:`"normal"`). | |
|
|
1101
|
+
| `autoCap` | Auto-capitalize option labels. | | |
|
|
1102
|
+
| `groupClassName` | Class applied to the options wrapper. | | |
|
|
1103
|
+
| `className` | Alias for `groupClassName`. | | |
|
|
1104
|
+
| `optionClassName` | Class applied to each option container. | | |
|
|
1105
|
+
| `labelClassName` | Class applied to label text (single + option labels). | | |
|
|
1106
|
+
| `optionLabelClassName` | Extra class applied to each option label. | | |
|
|
1107
|
+
| `descriptionClassName` | Extra class applied to option descriptions. | | |
|
|
1108
|
+
| `id` | Wrapper id. | | |
|
|
1109
|
+
| `name` | Base `name` used for hidden inputs in group mode. | | |
|
|
1110
|
+
| `aria-label` | Accessibility label for the group wrapper. | | |
|
|
1111
|
+
| `aria-labelledby` | Id of an element that labels the group wrapper. | | |
|
|
1112
|
+
| `aria-describedby` | Id of an element that describes the group wrapper. | | |
|
|
1113
|
+
|
|
1114
|
+
**Value shape notes**
|
|
1115
|
+
|
|
1116
|
+
* **Single mode:** `boolean | undefined`
|
|
1117
|
+
* **Group mode:** `CheckboxGroupEntry[] | undefined`, where each entry is `{ value, state }`.
|
|
1118
|
+
|
|
1119
|
+
* `"none"` is an internal state only (it never appears in the public value).
|
|
1120
|
+
|
|
1121
|
+
**Sample usage**
|
|
1122
|
+
|
|
1123
|
+
```tsx
|
|
1124
|
+
// single (boolean)
|
|
1125
|
+
<InputField
|
|
1126
|
+
name="agree_tos"
|
|
1127
|
+
label="Terms"
|
|
1128
|
+
variant="checkbox"
|
|
1129
|
+
single
|
|
1130
|
+
singleLabel="I agree to the Terms of Service"
|
|
1131
|
+
/>
|
|
1132
|
+
|
|
1133
|
+
// group (with tri-state)
|
|
1134
|
+
<InputField
|
|
1135
|
+
name="notify_prefs"
|
|
1136
|
+
label="Notify me"
|
|
1137
|
+
variant="checkbox"
|
|
1138
|
+
tristate
|
|
1139
|
+
layout="grid"
|
|
1140
|
+
columns={2}
|
|
1141
|
+
options={[
|
|
1142
|
+
{ label: "Email", value: "email", description: "Marketing + account alerts" },
|
|
1143
|
+
{ label: "SMS", value: "sms" },
|
|
1144
|
+
{ label: "Push", value: "push" },
|
|
1145
|
+
]}
|
|
1146
|
+
/>
|
|
1147
|
+
```
|
|
1148
|
+
|
|
1149
|
+
## chips
|
|
1150
|
+
|
|
1151
|
+
| Prop | Description | |
|
|
1152
|
+
| ---------------------- | ------------------------------------------------------------------------ | ----------------------------- |
|
|
1153
|
+
| `placeholder` | Placeholder shown when there are no chips and input is empty. | |
|
|
1154
|
+
| `separators` | Separators used to split raw input into chips. Default: `[",", ";"]`. | |
|
|
1155
|
+
| `addOnEnter` | Commit chips on **Enter**. Default: `true`. | |
|
|
1156
|
+
| `addOnTab` | Commit chips on **Tab**. Default: `true`. | |
|
|
1157
|
+
| `addOnBlur` | Commit chips on **blur**. Default: `true`. | |
|
|
1158
|
+
| `allowDuplicates` | When `false`, duplicate chips are ignored. Default: `false`. | |
|
|
1159
|
+
| `maxChips` | Maximum number of chips allowed (`undefined` → unlimited). | |
|
|
1160
|
+
| `backspaceRemovesLast` | Remove last chip on Backspace when input is empty. Default: `true`. | |
|
|
1161
|
+
| `clearable` | Show a clear-all button. Default: `false`. | |
|
|
1162
|
+
| `onAddChips` | Callback: `(added, next) => void` after chips are added. | |
|
|
1163
|
+
| `onRemoveChips` | Callback: `(removed, next) => void` after chips are removed. | |
|
|
1164
|
+
| `renderChip` | Custom chip renderer. You handle remove UI by calling `onRemove(index)`. | |
|
|
1165
|
+
| `renderOverflowChip` | Custom renderer for the overflow chip (when some chips are hidden). | |
|
|
1166
|
+
| `maxVisibleChips` | Maximum number of chips visible before showing an overflow chip. | |
|
|
1167
|
+
| `maxChipChars` | Soft cap for chip display label length (UI-only). | |
|
|
1168
|
+
| `maxChipWidth` | Soft cap for chip width (UI-only). | |
|
|
1169
|
+
| `textareaMode` | Use textarea input instead of single-line input. Default: `false`. | |
|
|
1170
|
+
| `placement` | Chips placement: `"inline" | "below"`(default:`"inline"`). |
|
|
1171
|
+
| `className` | Class applied to the main wrapper. | |
|
|
1172
|
+
| `chipsClassName` | Class applied to the chips container. | |
|
|
1173
|
+
| `chipClassName` | Class applied to each chip wrapper. | |
|
|
1174
|
+
| `chipLabelClassName` | Class applied to each chip label text. | |
|
|
1175
|
+
| `chipRemoveClassName` | Class applied to each chip remove button. | |
|
|
1176
|
+
| `inputClassName` | Class applied to the input/textarea element. | |
|
|
1177
|
+
|
|
1178
|
+
> **Also supports** most `ShadcnTextVariantProps` (size, density, icons, etc.).
|
|
1179
|
+
|
|
1180
|
+
**Sample usage**
|
|
1181
|
+
|
|
1182
|
+
```tsx
|
|
1183
|
+
<InputField
|
|
1184
|
+
name="tags"
|
|
1185
|
+
label="Tags"
|
|
1186
|
+
variant="chips"
|
|
1187
|
+
placeholder="Add tags…"
|
|
1188
|
+
separators={[",", ";"]}
|
|
1189
|
+
maxChips={10}
|
|
1190
|
+
clearable
|
|
1191
|
+
/>
|
|
1192
|
+
|
|
1193
|
+
<InputField
|
|
1194
|
+
name="emails"
|
|
1195
|
+
label="Allowed emails"
|
|
1196
|
+
variant="chips"
|
|
1197
|
+
textareaMode
|
|
1198
|
+
placement="below"
|
|
1199
|
+
addOnEnter
|
|
1200
|
+
addOnBlur
|
|
1201
|
+
/>
|
|
1202
|
+
```
|
|
1203
|
+
|
|
1204
|
+
## color
|
|
1205
|
+
|
|
1206
|
+
| Prop | Description |
|
|
1207
|
+
| ------------------------ | -------------------------------------------------------------------------- |
|
|
1208
|
+
| `showPreview` | Show a color preview button / swatch. Default: `true`. |
|
|
1209
|
+
| `showPickerToggle` | Show the small picker toggle icon. Default: `true`. |
|
|
1210
|
+
| `previewSize` | Preview swatch size in px. Default: `16`. |
|
|
1211
|
+
| `wrapperClassName` | Class applied to the wrapper. |
|
|
1212
|
+
| `previewButtonClassName` | Class applied to the preview button. |
|
|
1213
|
+
| `previewSwatchClassName` | Class applied to the swatch element inside the preview. |
|
|
1214
|
+
| `pickerInputClassName` | Class applied to the picker input wrapper. |
|
|
1215
|
+
| `pickerToggleIcon` | Icon component for the toggle (a `React.ElementType`). Default: `Palette`. |
|
|
1216
|
+
|
|
1217
|
+
> **Also supports** `ShadcnTextVariantProps`, minus: `type`, `inputMode`, `autoComplete`, `autoCap`, `leadingIcon`, `trailingIcon`, `invalid`, `mono`.
|
|
1218
|
+
|
|
1219
|
+
**Sample usage**
|
|
1220
|
+
|
|
1221
|
+
```tsx
|
|
1222
|
+
import { Palette } from "lucide-react";
|
|
1223
|
+
|
|
1224
|
+
<InputField
|
|
1225
|
+
name="brand_color"
|
|
1226
|
+
label="Brand color"
|
|
1227
|
+
variant="color"
|
|
1228
|
+
placeholder="#1d4ed8"
|
|
1229
|
+
showPreview
|
|
1230
|
+
previewSize={18}
|
|
1231
|
+
showPickerToggle
|
|
1232
|
+
pickerToggleIcon={Palette}
|
|
1233
|
+
/>
|
|
1234
|
+
```
|
|
1235
|
+
|
|
1236
|
+
## date
|
|
1237
|
+
|
|
1238
|
+
### Variant props
|
|
1239
|
+
|
|
1240
|
+
| Prop | Description | |
|
|
1241
|
+
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
|
|
1242
|
+
| `mode` | Selection mode: `"single"` or `"range"`. | |
|
|
1243
|
+
| `placeholder` | Placeholder content shown when there’s no selection. | |
|
|
1244
|
+
| `clearable` | If `true`, shows a clear action when a value is set. | |
|
|
1245
|
+
| `minDate` | Minimum selectable date (inclusive). | |
|
|
1246
|
+
| `maxDate` | Maximum selectable date (inclusive). | |
|
|
1247
|
+
| `disabledDays` | Disabled-day matcher forwarded to the calendar wrapper (`Calendar["disabled"]`). | |
|
|
1248
|
+
| `formatSingle` | Display pattern for single values. Supports tokens: `yyyy`, `MM`, `dd`, `HH`, `mm`. Defaults depend on `kind`. | |
|
|
1249
|
+
| `formatRange` | Range display formatter: either a pattern string (same tokens as `formatSingle`) or a custom `(range) => string` formatter. | |
|
|
1250
|
+
| `rangeSeparator` | Separator used between `from` and `to` when `formatRange` is a string pattern. Default: `" – "`. | |
|
|
1251
|
+
| `stayOpenOnSelect` | If `true`, keeps the popover open after selecting. In range mode, stays open until both ends are chosen. | |
|
|
1252
|
+
| `open` | Controlled open state for the popover. | |
|
|
1253
|
+
| `onOpenChange` | Called when the popover open state changes. | |
|
|
1254
|
+
| `kind` | Temporal kind that drives default mask + parsing/formatting (e.g. `"date"`, `"datetime"`, `"time"`, `"hour"`, `"monthYear"`, `"year"`). Default: `"date"`. | |
|
|
1255
|
+
| `inputMask` | Optional explicit mask pattern for the input (`9` = digit, `a` = letter, `*` = alphanumeric). If omitted, a default based on `kind` is used. | |
|
|
1256
|
+
| `showCalendar` | Whether to render the calendar popover. Defaults: `true` for `kind="date" | "datetime"`, `false` for time-only kinds. |
|
|
1257
|
+
|
|
1258
|
+
### Sample usage
|
|
1259
|
+
|
|
1260
|
+
```tsx
|
|
1261
|
+
import { InputField } from "@timeax/form-palette"; // adjust import to your project
|
|
1262
|
+
|
|
1263
|
+
export function DateRangeExample() {
|
|
1264
|
+
return (
|
|
1265
|
+
<InputField
|
|
1266
|
+
variant="date"
|
|
1267
|
+
name="period"
|
|
1268
|
+
label="Billing period"
|
|
1269
|
+
description="Select a date range."
|
|
1270
|
+
mode="range"
|
|
1271
|
+
kind="date"
|
|
1272
|
+
placeholder="YYYY-MM-DD – YYYY-MM-DD"
|
|
1273
|
+
clearable
|
|
1274
|
+
minDate={new Date(2025, 0, 1)}
|
|
1275
|
+
maxDate={new Date(2025, 11, 31)}
|
|
1276
|
+
// defaultValue can be a Date or { from?: Date; to?: Date }
|
|
1277
|
+
defaultValue={{ from: new Date(2025, 0, 1), to: new Date(2025, 0, 31) }}
|
|
1278
|
+
/>
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
```
|
|
1282
|
+
|
|
1283
|
+
---
|
|
1284
|
+
|
|
1285
|
+
## keyvalue
|
|
1286
|
+
|
|
1287
|
+
### Variant props
|
|
1288
|
+
|
|
1289
|
+
| Prop | Description |
|
|
1290
|
+
| ---------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
1291
|
+
| `min` | Minimum number of pairs allowed (enforced by the UI controls). |
|
|
1292
|
+
| `max` | Maximum number of pairs allowed. |
|
|
1293
|
+
| `minVisible` | Minimum number of chips to show before collapsing into a “more” indicator. |
|
|
1294
|
+
| `maxVisible` | Maximum number of chips to show before collapsing into a “more” indicator. |
|
|
1295
|
+
| `showAddButton` | Toggle visibility of the “Add” action. |
|
|
1296
|
+
| `showMenuButton` | Toggle visibility of the overflow/menu action (if supported by the preset). |
|
|
1297
|
+
| `placeholder` | Placeholder shown when there are no items. |
|
|
1298
|
+
| `dialogTitle` | Title for the edit/add dialog UI. |
|
|
1299
|
+
| `keyLabel` | Label used for the “key” input. |
|
|
1300
|
+
| `valueLabel` | Label used for the “value” input. |
|
|
1301
|
+
| `submitLabel` | Text for the dialog submit button. |
|
|
1302
|
+
| `moreLabel` | Label renderer for the collapsed “more” indicator: `(count) => ReactNode`. |
|
|
1303
|
+
| `emptyLabel` | Label shown when there are no entries (fallback text). |
|
|
1304
|
+
| `className` | Wrapper class for the whole variant. |
|
|
1305
|
+
| `chipsClassName` | Class for the chips container. |
|
|
1306
|
+
| `chipClassName` | Class for each chip. |
|
|
1307
|
+
| `renderChip` | Custom chip renderer. Receives `{ pair, index, onEdit, onRemove, defaultChip }` and should return a `ReactNode`. |
|
|
1308
|
+
|
|
1309
|
+
### Sample usage
|
|
1310
|
+
|
|
1311
|
+
```tsx
|
|
1312
|
+
import { InputField } from "@timeax/form-palette"; // adjust import to your project
|
|
1313
|
+
|
|
1314
|
+
export function KeyValueExample() {
|
|
1315
|
+
return (
|
|
1316
|
+
<InputField
|
|
1317
|
+
variant="keyvalue"
|
|
1318
|
+
name="headers"
|
|
1319
|
+
label="Headers"
|
|
1320
|
+
description="Key-value headers to include in requests."
|
|
1321
|
+
placeholder="No headers"
|
|
1322
|
+
dialogTitle="Edit headers"
|
|
1323
|
+
keyLabel="Header"
|
|
1324
|
+
valueLabel="Value"
|
|
1325
|
+
submitLabel="Save"
|
|
1326
|
+
moreLabel={(count) => `+${count} more`}
|
|
1327
|
+
min={0}
|
|
1328
|
+
max={20}
|
|
1329
|
+
defaultValue={{ "X-Client": "timeax", "X-Mode": "dev" }}
|
|
1330
|
+
/>
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
```
|
|
1334
|
+
|
|
1335
|
+
---
|
|
1336
|
+
|
|
1337
|
+
## editor
|
|
1338
|
+
|
|
1339
|
+
### Variant props
|
|
1340
|
+
|
|
1341
|
+
| Prop | Description |
|
|
1342
|
+
| ------------------ | ----------------------------------------------------------------- |
|
|
1343
|
+
| `placeholder` | Placeholder content when the editor is empty. |
|
|
1344
|
+
| `minHeight` | Minimum height (px) for the editor surface. |
|
|
1345
|
+
| `maxHeight` | Maximum height (px) for the editor surface (scrolls beyond this). |
|
|
1346
|
+
| `rows` | Row hint for initial sizing (when using a textarea-like layout). |
|
|
1347
|
+
| `toolbar` | Toolbar preset/variant (e.g. `"minimal"`, `"default"`, `"full"`). |
|
|
1348
|
+
| `allowLinks` | Whether links are allowed/enabled. |
|
|
1349
|
+
| `allowImages` | Whether images are allowed/enabled. |
|
|
1350
|
+
| `allowTables` | Whether tables are allowed/enabled. |
|
|
1351
|
+
| `allowLists` | Whether lists are allowed/enabled. |
|
|
1352
|
+
| `allowCode` | Whether code blocks/inline code are allowed/enabled. |
|
|
1353
|
+
| `sanitizeHtml` | If `true`, sanitizes HTML before emitting/storing it. |
|
|
1354
|
+
| `className` | Wrapper class for the whole variant. |
|
|
1355
|
+
| `editorClassName` | Class for the editor surface. |
|
|
1356
|
+
| `toolbarClassName` | Class for the toolbar wrapper. |
|
|
1357
|
+
|
|
1358
|
+
### Sample usage
|
|
1359
|
+
|
|
1360
|
+
```tsx
|
|
1361
|
+
import { InputField } from "@timeax/form-palette"; // adjust import to your project
|
|
1362
|
+
|
|
1363
|
+
export function EditorExample() {
|
|
1364
|
+
return (
|
|
1365
|
+
<InputField
|
|
1366
|
+
variant="editor"
|
|
1367
|
+
name="bio"
|
|
1368
|
+
label="Bio"
|
|
1369
|
+
description="Write a short bio."
|
|
1370
|
+
placeholder="Start typing..."
|
|
1371
|
+
rows={8}
|
|
1372
|
+
minHeight={160}
|
|
1373
|
+
maxHeight={420}
|
|
1374
|
+
toolbar="default"
|
|
1375
|
+
allowLinks
|
|
1376
|
+
allowLists
|
|
1377
|
+
sanitizeHtml
|
|
1378
|
+
/>
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1381
|
+
```
|
|
1382
|
+
|
|
1383
|
+
## file
|
|
1384
|
+
|
|
1385
|
+
### Variant props
|
|
1386
|
+
|
|
1387
|
+
| Prop | Description |
|
|
1388
|
+
| ------------------- | -------------------------------------------------------------------- |
|
|
1389
|
+
| `multiple` | Allow selecting multiple files. |
|
|
1390
|
+
| `accept` | Accepted file types (input accept string / list). |
|
|
1391
|
+
| `maxFiles` | Max number of files allowed. |
|
|
1392
|
+
| `maxTotalSize` | Max total size allowed for all files (bytes). |
|
|
1393
|
+
| `showDropArea` | Show the drop-area UI section. |
|
|
1394
|
+
| `dropIcon` | Optional icon shown in the drop area. |
|
|
1395
|
+
| `dropTitle` | Title text shown in the drop area. |
|
|
1396
|
+
| `dropDescription` | Helper text shown in the drop area. |
|
|
1397
|
+
| `custom` | Use a fully custom “picker” UI instead of the built-in drop/trigger. |
|
|
1398
|
+
| `asRaw` | Treat values as raw `File` objects (native picker flow). |
|
|
1399
|
+
| `renderDropArea` | Custom renderer for the drop area section. |
|
|
1400
|
+
| `renderFileItem` | Custom renderer for each file item row. |
|
|
1401
|
+
| `showCheckboxes` | Show checkboxes next to file items (when supported by the UI). |
|
|
1402
|
+
| `onFilesAdded` | Callback fired when files are added. |
|
|
1403
|
+
| `customLoader` | Provide your own file loader (e.g. resolve URLs → metadata). |
|
|
1404
|
+
| `mergeMode` | Merge strategy when adding files (e.g. append/replace/dedupe). |
|
|
1405
|
+
| `formatFileName` | Custom formatter for displaying a file name. |
|
|
1406
|
+
| `formatFileSize` | Custom formatter for displaying a file size. |
|
|
1407
|
+
| `placeholder` | Placeholder text when nothing is selected. |
|
|
1408
|
+
| `className` | Wrapper class for the whole variant. |
|
|
1409
|
+
| `dropAreaClassName` | ClassName for the drop-area wrapper. |
|
|
1410
|
+
| `listClassName` | ClassName for the file list container. |
|
|
1411
|
+
| `triggerClassName` | ClassName for the trigger (when using the built-in trigger). |
|
|
1412
|
+
|
|
1413
|
+
### Mode and trigger props
|
|
1414
|
+
|
|
1415
|
+
| Prop | Description |
|
|
1416
|
+
| ----------------------------- | ----------------------------------------------------------------------- |
|
|
1417
|
+
| `mode` | Trigger style: `"default"` (input-like) or `"button"` (custom trigger). |
|
|
1418
|
+
| `leadingIcons` | Icons shown before the summary (default mode). |
|
|
1419
|
+
| `trailingIcons` | Icons shown after the summary / clear action. |
|
|
1420
|
+
| `icon` | Single icon shorthand (falls into leading icons). |
|
|
1421
|
+
| `iconGap` | Base gap (px) between icon groups and text. |
|
|
1422
|
+
| `leadingIconSpacing` | Override spacing (px) between leading icons and text. |
|
|
1423
|
+
| `trailingIconSpacing` | Override spacing (px) between summary and trailing controls. |
|
|
1424
|
+
| `leadingControl` | Custom node on the far-left *outside* the trigger. |
|
|
1425
|
+
| `trailingControl` | Custom node on the far-right *outside* the trigger. |
|
|
1426
|
+
| `leadingControlClassName` | ClassName for the leading control wrapper. |
|
|
1427
|
+
| `trailingControlClassName` | ClassName for the trailing control wrapper. |
|
|
1428
|
+
| `joinControls` | Visually “joins” leading/trailing controls with the trigger. |
|
|
1429
|
+
| `extendBoxToControls` | Extends the input box styling around joined controls. |
|
|
1430
|
+
| `button` | When `mode="button"`: explicit trigger node. |
|
|
1431
|
+
| `children` | When `mode="button"` and `button` is not provided: trigger content. |
|
|
1432
|
+
| `selectedBadge` | Selected-count badge (button mode). |
|
|
1433
|
+
| `selectedBadgeHiddenWhenZero` | Hide badge when selected count is 0. |
|
|
1434
|
+
| `selectedBadgeClassName` | ClassName for the selected-count badge. |
|
|
1435
|
+
| `selectedBadgePlacement` | Where to place the badge relative to the trigger content. |
|
|
1436
|
+
|
|
1437
|
+
### Sample usage
|
|
1438
|
+
|
|
1439
|
+
```tsx
|
|
1440
|
+
import { InputField } from "@timeax/form-palette"; // adjust import to your project
|
|
1441
|
+
|
|
1442
|
+
export function FileExample() {
|
|
1443
|
+
return (
|
|
1444
|
+
<InputField
|
|
1445
|
+
variant="file"
|
|
1446
|
+
name="attachments"
|
|
1447
|
+
label="Attachments"
|
|
1448
|
+
description="Upload up to 3 files (max 10MB total)."
|
|
1449
|
+
multiple
|
|
1450
|
+
accept={["image/*", "application/pdf"]}
|
|
1451
|
+
maxFiles={3}
|
|
1452
|
+
maxTotalSize={10 * 1024 * 1024}
|
|
1453
|
+
showDropArea
|
|
1454
|
+
placeholder="Choose files..."
|
|
1455
|
+
/>
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
```
|
|
1459
|
+
|
|
1460
|
+
---
|
|
1461
|
+
|
|
1462
|
+
## json-editor
|
|
1463
|
+
|
|
1464
|
+
### Wrapper / trigger props
|
|
1465
|
+
|
|
1466
|
+
| Prop | Description |
|
|
1467
|
+
| ------------------ | --------------------------------------------------------- |
|
|
1468
|
+
| `mode` | Display mode: `"popover"` or `"accordion"`. |
|
|
1469
|
+
| `trigger` | Custom trigger node (popover mode). |
|
|
1470
|
+
| `triggerLabel` | Default trigger label text (popover mode). |
|
|
1471
|
+
| `triggerVariant` | Visual variant for the trigger button. |
|
|
1472
|
+
| `triggerSize` | Size for the trigger button. |
|
|
1473
|
+
| `openLabel` | Label text when opening the popover. |
|
|
1474
|
+
| `closeLabel` | Label text when closing the popover. |
|
|
1475
|
+
| `open` | Controlled open state (popover mode). |
|
|
1476
|
+
| `onOpenChange` | Open-state change callback. |
|
|
1477
|
+
| `onClose` | Callback fired when the popover closes. |
|
|
1478
|
+
| `id` | Optional id for accessibility wiring. |
|
|
1479
|
+
| `describedBy` | Optional `aria-describedby` target id. |
|
|
1480
|
+
| `wrapperClassName` | ClassName for the outer wrapper. |
|
|
1481
|
+
| `popoverClassName` | ClassName for the popover content wrapper (popover mode). |
|
|
1482
|
+
| `panelClassName` | ClassName for the editor panel wrapper. |
|
|
1483
|
+
|
|
1484
|
+
### Editor props (passed into the JSON editor)
|
|
1485
|
+
|
|
1486
|
+
| Prop | Description |
|
|
1487
|
+
| ------------------ | ----------------------------------------------------------------------------------- |
|
|
1488
|
+
| `title` | Title displayed in the editor header. |
|
|
1489
|
+
| `fieldMap` | Field mapping rules (wildcards supported) → picks a field variant + props per path. |
|
|
1490
|
+
| `layout` | Layout rules (grid/rows + route/page rules). |
|
|
1491
|
+
| `defaults` | Default values / behaviours for missing keys and created fields. |
|
|
1492
|
+
| `filters` | Include/exclude filters for routes/fields. |
|
|
1493
|
+
| `permissions` | Permissions (add/delete/view/edit raw, etc.). |
|
|
1494
|
+
| `callbacks` | Hooks for events like add/delete/edit / route changes. |
|
|
1495
|
+
| `route` | Controlled “page route” (e.g. `"config.headers"`). |
|
|
1496
|
+
| `defaultRoute` | Starting route when uncontrolled. |
|
|
1497
|
+
| `onRouteChange` | Route change callback. |
|
|
1498
|
+
| `viewMode` | Controlled view mode (e.g. raw vs structured UI). |
|
|
1499
|
+
| `defaultViewMode` | Default view mode when uncontrolled. |
|
|
1500
|
+
| `onViewModeChange` | View mode change callback. |
|
|
1501
|
+
| `className` | Root class for the editor. |
|
|
1502
|
+
| `contentClassName` | Class for the main content region. |
|
|
1503
|
+
| `navClassName` | Class for the navigation region. |
|
|
1504
|
+
| `bodyClassName` | Class for the body region. |
|
|
1505
|
+
|
|
1506
|
+
### Sample usage
|
|
1507
|
+
|
|
1508
|
+
```tsx
|
|
1509
|
+
import { InputField } from "@timeax/form-palette"; // adjust import to your project
|
|
1510
|
+
|
|
1511
|
+
export function JsonEditorExample() {
|
|
1512
|
+
return (
|
|
1513
|
+
<InputField
|
|
1514
|
+
variant="json-editor"
|
|
1515
|
+
name="settings"
|
|
1516
|
+
label="Settings"
|
|
1517
|
+
description="Edit advanced settings as a structured UI."
|
|
1518
|
+
mode="popover"
|
|
1519
|
+
triggerLabel="Edit settings"
|
|
1520
|
+
title="Settings"
|
|
1521
|
+
defaultValue={{
|
|
1522
|
+
projectName: "",
|
|
1523
|
+
config: { apiUrl: "", enabled: true },
|
|
1524
|
+
}}
|
|
1525
|
+
fieldMap={{
|
|
1526
|
+
projectName: { variant: "text", props: { label: "Project name" } },
|
|
1527
|
+
"config.apiUrl": { variant: "text", props: { label: "API URL" } },
|
|
1528
|
+
"config.enabled": { variant: "toggle", props: { label: "Enabled" } },
|
|
1529
|
+
"**.*token*": { variant: "password", props: { label: "Token" } },
|
|
1530
|
+
}}
|
|
1531
|
+
permissions={{
|
|
1532
|
+
canViewRaw: true,
|
|
1533
|
+
canEditRaw: false,
|
|
1534
|
+
canAdd: true,
|
|
1535
|
+
canDelete: true,
|
|
1536
|
+
}}
|
|
1537
|
+
/>
|
|
1538
|
+
);
|
|
1539
|
+
}
|
|
1540
|
+
```
|
|
1541
|
+
|
|
1542
|
+
---
|
|
1543
|
+
|
|
1544
|
+
## lister
|
|
1545
|
+
|
|
1546
|
+
### Data + mapping props
|
|
1547
|
+
|
|
1548
|
+
| Prop | Description |
|
|
1549
|
+
| ------------------- | ----------------------------------------------------------------------- |
|
|
1550
|
+
| `def` | Base lister definition (columns, source, mapping, etc.). |
|
|
1551
|
+
| `endpoint` | Inline source endpoint (standalone inline mode). |
|
|
1552
|
+
| `method` | Inline HTTP method: `"GET"` or `"POST"`. |
|
|
1553
|
+
| `buildRequest` | Custom request builder (params/body/headers). |
|
|
1554
|
+
| `selector` | How to extract the array from the response (function or selector path). |
|
|
1555
|
+
| `optionValue` | How to map a raw row → option value (key or function). |
|
|
1556
|
+
| `optionLabel` | How to map a raw row → label. |
|
|
1557
|
+
| `optionIcon` | How to map a raw row → icon. |
|
|
1558
|
+
| `optionDescription` | How to map a raw row → description. |
|
|
1559
|
+
| `optionDisabled` | How to map a raw row → disabled. |
|
|
1560
|
+
| `optionGroup` | How to map a raw row → group label. |
|
|
1561
|
+
| `optionMeta` | How to map a raw row → meta payload. |
|
|
1562
|
+
| `search` | Search override (inline). |
|
|
1563
|
+
| `searchTarget` | Search target override (inline). |
|
|
1564
|
+
|
|
1565
|
+
### Selection + behaviour props
|
|
1566
|
+
|
|
1567
|
+
| Prop | Description |
|
|
1568
|
+
| ------------------ | ---------------------------------------------------------- |
|
|
1569
|
+
| `filters` | Filters payload used by the lister source. |
|
|
1570
|
+
| `confirm` | Optional confirm behaviour (e.g. confirm selection). |
|
|
1571
|
+
| `permissions` | Permissions object used by the lister UI (actions, views). |
|
|
1572
|
+
| `placeholder` | Placeholder text when nothing is selected. |
|
|
1573
|
+
| `maxDisplayItems` | Max chips/labels to show before collapsing into “+N”. |
|
|
1574
|
+
| `renderTrigger` | Custom trigger renderer. |
|
|
1575
|
+
| `title` | Title displayed when opening the lister UI. |
|
|
1576
|
+
| `searchMode` | Search mode for the open UI. |
|
|
1577
|
+
| `initialQuery` | Initial query state. |
|
|
1578
|
+
| `showRefresh` | Show refresh button. |
|
|
1579
|
+
| `refreshMode` | Refresh behaviour/mode. |
|
|
1580
|
+
| `filtersSpec` | Filters spec for the open UI. |
|
|
1581
|
+
| `renderOption` | Custom renderer for option rows. |
|
|
1582
|
+
| `host` | Override lister host implementation. |
|
|
1583
|
+
| `presets` | Override preset map used by lister internals. |
|
|
1584
|
+
| `remoteDebounceMs` | Debounce (ms) for remote search requests. |
|
|
1585
|
+
|
|
1586
|
+
### Trigger styling + container props
|
|
1587
|
+
|
|
1588
|
+
| Prop | Description |
|
|
1589
|
+
| ----------------------------- | ----------------------------------------------------------------------- |
|
|
1590
|
+
| `mode` | Trigger style: `"default"` (input-like) or `"button"` (custom trigger). |
|
|
1591
|
+
| `clearable` | Show clear action when a selection exists. |
|
|
1592
|
+
| `leadingIcons` | Icons shown before the summary (default mode). |
|
|
1593
|
+
| `trailingIcons` | Icons shown after the summary / clear action. |
|
|
1594
|
+
| `icon` | Single icon shorthand. |
|
|
1595
|
+
| `iconGap` | Base gap (px) between icon groups and text. |
|
|
1596
|
+
| `leadingIconSpacing` | Override spacing (px) between leading icons and text. |
|
|
1597
|
+
| `trailingIconSpacing` | Override spacing (px) between summary and trailing controls. |
|
|
1598
|
+
| `leadingControl` | Custom node on the far-left *outside* the trigger. |
|
|
1599
|
+
| `trailingControl` | Custom node on the far-right *outside* the trigger. |
|
|
1600
|
+
| `leadingControlClassName` | ClassName for the leading control wrapper. |
|
|
1601
|
+
| `trailingControlClassName` | ClassName for the trailing control wrapper. |
|
|
1602
|
+
| `joinControls` | Visually “joins” leading/trailing controls with the trigger. |
|
|
1603
|
+
| `extendBoxToControls` | Extends the input box styling around joined controls. |
|
|
1604
|
+
| `maxListHeight` | Max height for the open list/panel (px). |
|
|
1605
|
+
| `className` | Wrapper class for the whole variant. |
|
|
1606
|
+
| `triggerClassName` | ClassName for the trigger. |
|
|
1607
|
+
| `contentClassName` | ClassName for the popover/content container. |
|
|
1608
|
+
| `panelClassName` | ClassName for the panel surface wrapper. |
|
|
1609
|
+
| `button` | When `mode="button"`: explicit trigger node. |
|
|
1610
|
+
| `children` | When `mode="button"` and `button` is not provided: trigger content. |
|
|
1611
|
+
| `selectedBadge` | Selected-count badge (button mode). |
|
|
1612
|
+
| `selectedBadgeHiddenWhenZero` | Hide badge when selected count is 0. |
|
|
1613
|
+
| `selectedBadgeClassName` | ClassName for the selected-count badge. |
|
|
1614
|
+
| `selectedBadgePlacement` | Where to place the badge relative to the trigger content. |
|
|
1615
|
+
|
|
1616
|
+
### Sample usage
|
|
1617
|
+
|
|
1618
|
+
```tsx
|
|
1619
|
+
import { InputField } from "@timeax/form-palette"; // adjust import to your project
|
|
1620
|
+
|
|
1621
|
+
export function ListerExample() {
|
|
1622
|
+
return (
|
|
1623
|
+
<InputField
|
|
1624
|
+
variant="lister"
|
|
1625
|
+
name="user_id"
|
|
1626
|
+
label="User"
|
|
1627
|
+
description="Pick a user from a remote list."
|
|
1628
|
+
|
|
1629
|
+
// standalone inline source (no base `def` required)
|
|
1630
|
+
endpoint="/api/admin/users"
|
|
1631
|
+
method="GET"
|
|
1632
|
+
selector="data" // or (res) => res.data
|
|
1633
|
+
optionValue="id"
|
|
1634
|
+
optionLabel={(u: any) => u.name}
|
|
1635
|
+
optionDescription={(u: any) => u.email}
|
|
1636
|
+
|
|
1637
|
+
searchable
|
|
1638
|
+
clearable
|
|
1639
|
+
placeholder="Select a user..."
|
|
1640
|
+
title="Select user"
|
|
1641
|
+
/>
|
|
1642
|
+
);
|
|
1643
|
+
}
|
|
1644
|
+
```
|
|
1645
|
+
|
|
1646
|
+
## custom
|
|
1647
|
+
|
|
1648
|
+
### Variant props
|
|
1649
|
+
|
|
1650
|
+
| Prop | Description |
|
|
1651
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1652
|
+
| `component` | React component to render. **Required.** |
|
|
1653
|
+
| `valueProp` | Prop name that receives the current value. Default: `"value"`. |
|
|
1654
|
+
| `changeProp` | Prop name for the change handler the component calls. Default: `"onChange"`. The component is expected to call `props[changeProp](nextValue, ...args)`; the **first** argument is treated as the next value. |
|
|
1655
|
+
| `disabledProp` | Prop name for disabled state. Default: `"disabled"`. |
|
|
1656
|
+
| `readOnlyProp` | Prop name for read-only state. Default: `"readOnly"`. |
|
|
1657
|
+
| `errorProp` | Optional prop name to pass the field `error` into the component (if it cares). |
|
|
1658
|
+
| `idProp` | Prop name for the `id` attribute. Default: `"id"`. |
|
|
1659
|
+
| `nameProp` | Prop name for the `name` attribute. Default: `"name"`. |
|
|
1660
|
+
| `placeholderProp` | Prop name for the `placeholder` attribute. Default: `"placeholder"`. |
|
|
1661
|
+
| `mapValue` | Optional transform for the raw next value before it hits the field: `(raw, ...args) => TValue`. |
|
|
1662
|
+
| `mapDetail` | Optional builder for `ChangeDetail` given the raw next value: `(raw, ...args) => ChangeDetail`. If omitted, a default `{ source: "variant", raw }` detail is used. |
|
|
1663
|
+
| `...rest` | Any other props are forwarded to your `component`. |
|
|
1664
|
+
|
|
1665
|
+
### Sample usage
|
|
1666
|
+
|
|
1667
|
+
```tsx
|
|
1668
|
+
import * as React from "react";
|
|
1669
|
+
import { InputField } from "@timeax/form-palette";
|
|
1670
|
+
|
|
1671
|
+
// Example custom component (Radix-like API)
|
|
1672
|
+
function MyToggle(props: {
|
|
1673
|
+
checked?: boolean;
|
|
1674
|
+
onCheckedChange?: (next: boolean) => void;
|
|
1675
|
+
disabled?: boolean;
|
|
1676
|
+
}) {
|
|
1677
|
+
const { checked, onCheckedChange, disabled } = props;
|
|
1678
|
+
|
|
1679
|
+
return (
|
|
1680
|
+
<button
|
|
1681
|
+
type="button"
|
|
1682
|
+
disabled={disabled}
|
|
1683
|
+
aria-pressed={checked}
|
|
1684
|
+
onClick={() => onCheckedChange?.(!checked)}
|
|
1685
|
+
className="rounded border px-3 py-1"
|
|
1686
|
+
>
|
|
1687
|
+
{checked ? "On" : "Off"}
|
|
1688
|
+
</button>
|
|
1689
|
+
);
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
export function CustomExample() {
|
|
1693
|
+
return (
|
|
1694
|
+
<InputField
|
|
1695
|
+
variant="custom"
|
|
1696
|
+
name="marketing_opt_in"
|
|
1697
|
+
label="Marketing emails"
|
|
1698
|
+
description="Toggle to opt in."
|
|
1699
|
+
component={MyToggle}
|
|
1700
|
+
valueProp="checked"
|
|
1701
|
+
changeProp="onCheckedChange"
|
|
1702
|
+
/>
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
```
|
|
1706
|
+
|
|
1707
|
+
---
|
|
1708
|
+
|
|
1709
|
+
# Form Palette — `extra` entrypoint (v2)
|
|
1710
|
+
|
|
1711
|
+
The `extra` entrypoint exposes two “power tools” that sit beside the normal **Form / InputField** flow:
|
|
1712
|
+
|
|
1713
|
+
1. **Lister runtime** (provider + global UI + hooks) — a reusable, app-wide **picker** system for single/multi selection with search/filter + **remote / local / hybrid** data.
|
|
1714
|
+
2. **JsonEditor** — an interactive JSON editor UI (also used by the `json-editor` InputField variant).
|
|
1715
|
+
|
|
1716
|
+
> This README is written against the `extra.ts` export surface:
|
|
1717
|
+
>
|
|
1718
|
+
> ```ts
|
|
1719
|
+
> export * from "@/presets/lister/index";
|
|
1720
|
+
> export { default as JsonEditor } from "@/presets/shadcn-variants/json-editor";
|
|
1721
|
+
> ```
|
|
1722
|
+
|
|
1723
|
+
---
|
|
1724
|
+
|
|
1725
|
+
# 1) Lister (runtime)
|
|
1726
|
+
|
|
1727
|
+
## What is Lister?
|
|
1728
|
+
|
|
1729
|
+
Lister is a small runtime that lets you open a **picker UI** from anywhere in your app:
|
|
1730
|
+
|
|
1731
|
+
* **Single** selection (“choose one user”) or **multi** selection (“choose many tags”).
|
|
1732
|
+
* **Local**, **remote**, or **hybrid** search.
|
|
1733
|
+
* A consistent session model: **open → search/filter → select → apply/cancel**.
|
|
1734
|
+
* App-level integration via a provider and a single UI renderer.
|
|
1735
|
+
|
|
1736
|
+
If you use the `lister` InputField variant, it’s powered by this same runtime.
|
|
1737
|
+
|
|
1738
|
+
---
|
|
1739
|
+
|
|
1740
|
+
## Building blocks (what you actually mount/call)
|
|
1741
|
+
|
|
1742
|
+
### ✅ `ListerProvider`
|
|
1743
|
+
|
|
1744
|
+
* Holds the Lister store/context.
|
|
1745
|
+
* Receives the **host** (permission checks + logging).
|
|
1746
|
+
* Can register a **presets map** (reusable picker definitions).
|
|
1747
|
+
* Supports provider-side remote debounce via `remoteDebounceMs` (default **300ms**).
|
|
1748
|
+
|
|
1749
|
+
### ✅ `ListerUI`
|
|
1750
|
+
|
|
1751
|
+
* Renders any **open sessions** (popovers) from the provider store.
|
|
1752
|
+
* Mount this **once** under the provider.
|
|
1753
|
+
|
|
1754
|
+
### ✅ `useLister()`
|
|
1755
|
+
|
|
1756
|
+
* Imperative controller + access to store.
|
|
1757
|
+
|
|
1758
|
+
Provides:
|
|
1759
|
+
|
|
1760
|
+
* `api.open(...)` / `api.fetch(...)` (def/preset-based)
|
|
1761
|
+
* session actions (apply/cancel/close)
|
|
1762
|
+
* selection actions (toggle/select/deselect/clear)
|
|
1763
|
+
* search actions (setQuery/searchLocal/searchRemote)
|
|
1764
|
+
* filter helpers
|
|
1765
|
+
|
|
1766
|
+
### ✅ `useData()`
|
|
1767
|
+
|
|
1768
|
+
* Lower-level hook used for **fetching + searching + filters + selection state**.
|
|
1769
|
+
* Exported so you can build custom list UIs that still behave like Lister.
|
|
1770
|
+
|
|
1771
|
+
---
|
|
1772
|
+
|
|
1773
|
+
## Quick start (recommended)
|
|
1774
|
+
|
|
1775
|
+
### Step 1 — Mount provider + UI once
|
|
1776
|
+
|
|
1777
|
+
```tsx
|
|
1778
|
+
import * as React from "react";
|
|
1779
|
+
import { ListerProvider, ListerUI } from "@timeax/form-palette/extra";
|
|
1780
|
+
|
|
1781
|
+
const host = {
|
|
1782
|
+
can: (permissions: string[], ctx: any) => true,
|
|
1783
|
+
log: (entry: any) => console.log("[lister]", entry),
|
|
1784
|
+
};
|
|
1785
|
+
|
|
1786
|
+
export function AppShell({ children }: { children: React.ReactNode }) {
|
|
1787
|
+
return (
|
|
1788
|
+
<ListerProvider host={host}>
|
|
1789
|
+
{children}
|
|
1790
|
+
<ListerUI />
|
|
1791
|
+
</ListerProvider>
|
|
1792
|
+
);
|
|
1793
|
+
}
|
|
1794
|
+
```
|
|
1795
|
+
|
|
1796
|
+
### Step 2 — Open a picker imperatively
|
|
1797
|
+
|
|
1798
|
+
```tsx
|
|
1799
|
+
import * as React from "react";
|
|
1800
|
+
import { useLister } from "@timeax/form-palette/extra";
|
|
1801
|
+
|
|
1802
|
+
export function PickUserButton() {
|
|
1803
|
+
const { api } = useLister<any>();
|
|
1804
|
+
|
|
1805
|
+
return (
|
|
1806
|
+
<button
|
|
1807
|
+
onClick={() => {
|
|
1808
|
+
// "users" can be a preset key, or you can pass a definition inline
|
|
1809
|
+
api.open("users", { status: "active" }, { title: "Select a user" });
|
|
1810
|
+
}}
|
|
1811
|
+
>
|
|
1812
|
+
Pick user
|
|
1813
|
+
</button>
|
|
1814
|
+
);
|
|
1815
|
+
}
|
|
1816
|
+
```
|
|
1817
|
+
|
|
1818
|
+
---
|
|
1819
|
+
|
|
1820
|
+
## `ListerProvider` API
|
|
1821
|
+
|
|
1822
|
+
```ts
|
|
1823
|
+
export function ListerProvider(props: {
|
|
1824
|
+
host: ListerProviderHost;
|
|
1825
|
+
presets?: PresetMap;
|
|
1826
|
+
http?: ListerHttpClient;
|
|
1827
|
+
remoteDebounceMs?: number; // default 300
|
|
1828
|
+
children: React.ReactNode;
|
|
1829
|
+
})
|
|
1830
|
+
```
|
|
1831
|
+
|
|
1832
|
+
### `ListerProviderHost`
|
|
1833
|
+
|
|
1834
|
+
```ts
|
|
1835
|
+
export interface ListerProviderHost {
|
|
1836
|
+
/** Host decides permission logic. Mandatory permissions end with '!' */
|
|
1837
|
+
can: (permissions: string[], ctx: ListerPermissionCtx) => boolean;
|
|
1838
|
+
|
|
1839
|
+
/** Host decides notification/diagnostic surface */
|
|
1840
|
+
log: (entry: ListerLogEntry) => void;
|
|
1841
|
+
}
|
|
1842
|
+
```
|
|
1843
|
+
|
|
1844
|
+
### Practical usage
|
|
1845
|
+
|
|
1846
|
+
* Use `can()` to gate actions like **open**, **refresh**, or **apply**.
|
|
1847
|
+
* Use `log()` to send diagnostics to console, Sentry, or an in-app toast.
|
|
1848
|
+
|
|
1849
|
+
---
|
|
1850
|
+
|
|
1851
|
+
# 2) `useData()` — deep dive (extremely important)
|
|
1852
|
+
|
|
1853
|
+
`useData()` is the engine behind Lister-style **data fetching + searching + filtering + selection**.
|
|
1854
|
+
|
|
1855
|
+
Use it when:
|
|
1856
|
+
|
|
1857
|
+
* You want a **custom picker UI** (your own layout, rows, pagination).
|
|
1858
|
+
* You want a **data-backed selection** UX but not the standard Lister popover.
|
|
1859
|
+
* You want Lister’s semantics (remote/local/hybrid search + filters) in other UIs.
|
|
1860
|
+
|
|
1861
|
+
> It works only under `<ListerProvider />` because it uses the provider’s fetch engine.
|
|
1862
|
+
|
|
1863
|
+
---
|
|
1864
|
+
|
|
1865
|
+
## What `useData()` returns (mental model)
|
|
1866
|
+
|
|
1867
|
+
`useData()` returns two lists:
|
|
1868
|
+
|
|
1869
|
+
* `data`: the raw fetched list from the provider.
|
|
1870
|
+
* `visible`: what your UI should render.
|
|
1871
|
+
|
|
1872
|
+
* In **remote** mode: `visible === data`.
|
|
1873
|
+
* In **local / hybrid**: `visible` is client-filtered using `query + searchTarget`.
|
|
1874
|
+
|
|
1875
|
+
It also returns **selection state** (optional) and **fetch/search/filter helpers**.
|
|
1876
|
+
|
|
1877
|
+
---
|
|
1878
|
+
|
|
1879
|
+
## `UseDataOptions` (inputs)
|
|
1880
|
+
|
|
1881
|
+
| Option | Description |
|
|
1882
|
+
| -------------------------- | ---------------------------------------------------------------------------- |
|
|
1883
|
+
| `id?` | Optional identifier used when building the inline def. |
|
|
1884
|
+
| `endpoint` | URL/path to fetch items from. |
|
|
1885
|
+
| `method?` | HTTP method (default: `"GET"`). |
|
|
1886
|
+
| `selector?` | How to extract the list from your response (string path or mapper function). |
|
|
1887
|
+
| `buildRequest?` | Build `{ params?, body?, headers? }` from `{ filters, query, cursor }`. |
|
|
1888
|
+
| `search?` | `{ default?: string }` sets a default `searchTarget` subject key. |
|
|
1889
|
+
| `filters?` | Initial filters object. |
|
|
1890
|
+
| `initial?` | Initial items to avoid immediate fetch. |
|
|
1891
|
+
| `enabled?` | Disable all fetching/effects when false (default: `true`). |
|
|
1892
|
+
| `fetchOnMount?` | Auto-fetch on mount (default: `!initial`). |
|
|
1893
|
+
| `searchMode?` | `"remote"` (default) or `"local"` or `"hybrid"`. |
|
|
1894
|
+
| `debounceMs?` | Debounce for query/target changes in remote/hybrid (default: **300ms**). |
|
|
1895
|
+
| `autoFetchOnFilterChange?` | In remote/hybrid: auto-fetch when filters change (default: `true`). |
|
|
1896
|
+
| `selection?` | Enable selection helpers: `{ mode, key?, prune? }`. |
|
|
1897
|
+
|
|
1898
|
+
### Selection config (`selection`)
|
|
1899
|
+
|
|
1900
|
+
| Key | Meaning |
|
|
1901
|
+
| ------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
1902
|
+
| `mode` | `"single"` or `"multiple"` (omit = no selection). |
|
|
1903
|
+
| `key` | How to compute the item ID. String key (e.g. `"id"`) or `(item) => id`. |
|
|
1904
|
+
| `prune` | `"never"` (default) keeps selection across new fetches. `"missing"` removes selected IDs not present in the latest list. |
|
|
1905
|
+
|
|
1906
|
+
**Defaults**
|
|
1907
|
+
|
|
1908
|
+
* Default `key` behavior is effectively: `item.id ?? item.value`.
|
|
1909
|
+
|
|
1910
|
+
---
|
|
1911
|
+
|
|
1912
|
+
## Search modes: remote vs local vs hybrid
|
|
1913
|
+
|
|
1914
|
+
### ✅ `remote` (default)
|
|
1915
|
+
|
|
1916
|
+
* Query changes trigger a **debounced fetch**.
|
|
1917
|
+
* The server is responsible for searching.
|
|
1918
|
+
|
|
1919
|
+
Best for: **large datasets**, server ranking, true search endpoints.
|
|
1920
|
+
|
|
1921
|
+
### ✅ `local`
|
|
1922
|
+
|
|
1923
|
+
* Switching to local fetches a **base list once** using an empty query.
|
|
1924
|
+
* After that, `visible` is filtered client-side.
|
|
1925
|
+
|
|
1926
|
+
Best for: **small-medium datasets** you can cache (countries, categories, roles).
|
|
1927
|
+
|
|
1928
|
+
### ✅ `hybrid`
|
|
1929
|
+
|
|
1930
|
+
* Query changes still fetch remotely.
|
|
1931
|
+
* But `visible` also applies the local filtering rules.
|
|
1932
|
+
|
|
1933
|
+
Best for: “server list + extra client constraints” (e.g. `searchOnly`).
|
|
1934
|
+
|
|
1935
|
+
---
|
|
1936
|
+
|
|
1937
|
+
## Search targeting (`searchTarget`)
|
|
1938
|
+
|
|
1939
|
+
`useData()` supports Lister-style targeting via `searchTarget`:
|
|
1940
|
+
|
|
1941
|
+
* `mode: "all"` → search across everything
|
|
1942
|
+
* `mode: "subject"` → search only against one subject field (e.g. `name`)
|
|
1943
|
+
* `mode: "only"` → constrain results to a known list of IDs
|
|
1944
|
+
|
|
1945
|
+
If you pass `search={{ default: "name" }}`, the default target becomes `mode:"subject"` on that key.
|
|
1946
|
+
|
|
1947
|
+
---
|
|
1948
|
+
|
|
1949
|
+
## Core returned API (what you’ll use most)
|
|
1950
|
+
|
|
1951
|
+
### Data + status
|
|
1952
|
+
|
|
1953
|
+
* `data`, `visible`
|
|
1954
|
+
* `loading`, `error`
|
|
1955
|
+
|
|
1956
|
+
### Search
|
|
1957
|
+
|
|
1958
|
+
* `query`, `setQuery(query)`
|
|
1959
|
+
* `searchMode`, `setSearchMode(mode)`
|
|
1960
|
+
* `searchTarget`, `setSearchTarget(target)`
|
|
1961
|
+
|
|
1962
|
+
### Filters
|
|
1963
|
+
|
|
1964
|
+
* `filters`, `setFilters(next)`
|
|
1965
|
+
* `patchFilters(partial)`
|
|
1966
|
+
* `clearFilters()`
|
|
1967
|
+
|
|
1968
|
+
### Fetch
|
|
1969
|
+
|
|
1970
|
+
* `refresh()` — refetch using current query/filters/target
|
|
1971
|
+
* `fetch({ query?, filters?, searchTarget?, search? })` — manual override fetch
|
|
1972
|
+
|
|
1973
|
+
### Selection (when enabled)
|
|
1974
|
+
|
|
1975
|
+
* `selectionMode` (`none | single | multiple`)
|
|
1976
|
+
* `selectedIds` (array)
|
|
1977
|
+
* `selected` (array of objects resolved from cache)
|
|
1978
|
+
* `select(id)`, `deselect(id)`, `toggle(id)`, `clearSelection()`
|
|
1979
|
+
* `isSelected(id)`
|
|
1980
|
+
* `getSelection()` — returns the best current selection shape
|
|
1981
|
+
|
|
1982
|
+
> Important: selection maintains an internal cache so selected objects can be returned even when the current page/list no longer contains them.
|
|
1983
|
+
|
|
1984
|
+
---
|
|
1985
|
+
|
|
1986
|
+
## `useData()` — practical use cases (full examples)
|
|
1987
|
+
|
|
1988
|
+
### Use case A — Remote search list (simple)
|
|
1989
|
+
|
|
1990
|
+
```tsx
|
|
1991
|
+
import * as React from "react";
|
|
1992
|
+
import { useData } from "@timeax/form-palette/extra";
|
|
1993
|
+
|
|
1994
|
+
type User = { id: string; name: string; email: string };
|
|
1995
|
+
|
|
1996
|
+
export function RemoteUserSearch() {
|
|
1997
|
+
const { visible, loading, error, query, setQuery, refresh } = useData<User>({
|
|
1998
|
+
endpoint: "/api/users",
|
|
1999
|
+
method: "GET",
|
|
2000
|
+
selector: "data", // or (res) => res.data
|
|
2001
|
+
search: { default: "name" },
|
|
2002
|
+
});
|
|
2003
|
+
|
|
2004
|
+
return (
|
|
2005
|
+
<div>
|
|
2006
|
+
<input
|
|
2007
|
+
value={query}
|
|
2008
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
2009
|
+
placeholder="Search users…"
|
|
2010
|
+
/>
|
|
2011
|
+
<button onClick={refresh} disabled={loading}>
|
|
2012
|
+
Refresh
|
|
2013
|
+
</button>
|
|
2014
|
+
|
|
2015
|
+
{loading && <p>Loading…</p>}
|
|
2016
|
+
{error && <p style={{ color: "crimson" }}>{String(error)}</p>}
|
|
2017
|
+
|
|
2018
|
+
<ul>
|
|
2019
|
+
{visible.map((u) => (
|
|
2020
|
+
<li key={u.id}>
|
|
2021
|
+
<b>{u.name}</b> <small>{u.email}</small>
|
|
2022
|
+
</li>
|
|
2023
|
+
))}
|
|
2024
|
+
</ul>
|
|
2025
|
+
</div>
|
|
2026
|
+
);
|
|
2027
|
+
}
|
|
2028
|
+
```
|
|
2029
|
+
|
|
2030
|
+
---
|
|
2031
|
+
|
|
2032
|
+
### Use case B — Local mode (fetch once, instant client filtering)
|
|
2033
|
+
|
|
2034
|
+
```tsx
|
|
2035
|
+
import * as React from "react";
|
|
2036
|
+
import { useData } from "@timeax/form-palette/extra";
|
|
2037
|
+
|
|
2038
|
+
type Country = { code: string; name: string };
|
|
2039
|
+
|
|
2040
|
+
export function CountryPickerLocal() {
|
|
2041
|
+
const { visible, query, setQuery } = useData<Country>({
|
|
2042
|
+
endpoint: "/api/countries",
|
|
2043
|
+
method: "GET",
|
|
2044
|
+
selector: "data",
|
|
2045
|
+
search: { default: "name" },
|
|
2046
|
+
searchMode: "local",
|
|
2047
|
+
});
|
|
2048
|
+
|
|
2049
|
+
return (
|
|
2050
|
+
<div>
|
|
2051
|
+
<input
|
|
2052
|
+
value={query}
|
|
2053
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
2054
|
+
placeholder="Search countries…"
|
|
2055
|
+
/>
|
|
2056
|
+
<ul>
|
|
2057
|
+
{visible.map((c) => (
|
|
2058
|
+
<li key={c.code}>{c.name}</li>
|
|
2059
|
+
))}
|
|
2060
|
+
</ul>
|
|
2061
|
+
</div>
|
|
2062
|
+
);
|
|
2063
|
+
}
|
|
2064
|
+
```
|
|
2065
|
+
|
|
2066
|
+
---
|
|
2067
|
+
|
|
2068
|
+
### Use case C — Filters with `patchFilters` (remote/hybrid auto-fetch)
|
|
2069
|
+
|
|
2070
|
+
```tsx
|
|
2071
|
+
import * as React from "react";
|
|
2072
|
+
import { useData } from "@timeax/form-palette/extra";
|
|
2073
|
+
|
|
2074
|
+
type Filters = { status?: string; role?: string };
|
|
2075
|
+
|
|
2076
|
+
export function FilteredUsers() {
|
|
2077
|
+
const { visible, filters, patchFilters, clearFilters, loading } = useData<any, Filters>({
|
|
2078
|
+
endpoint: "/api/users",
|
|
2079
|
+
method: "GET",
|
|
2080
|
+
selector: "data",
|
|
2081
|
+
filters: { status: "active" },
|
|
2082
|
+
autoFetchOnFilterChange: true,
|
|
2083
|
+
});
|
|
2084
|
+
|
|
2085
|
+
return (
|
|
2086
|
+
<div>
|
|
2087
|
+
<div style={{ display: "flex", gap: 8 }}>
|
|
2088
|
+
<select
|
|
2089
|
+
value={filters?.status ?? ""}
|
|
2090
|
+
onChange={(e) => patchFilters({ status: e.target.value || undefined })}
|
|
2091
|
+
>
|
|
2092
|
+
<option value="">Any status</option>
|
|
2093
|
+
<option value="active">Active</option>
|
|
2094
|
+
<option value="blocked">Blocked</option>
|
|
2095
|
+
</select>
|
|
2096
|
+
|
|
2097
|
+
<select
|
|
2098
|
+
value={filters?.role ?? ""}
|
|
2099
|
+
onChange={(e) => patchFilters({ role: e.target.value || undefined })}
|
|
2100
|
+
>
|
|
2101
|
+
<option value="">Any role</option>
|
|
2102
|
+
<option value="admin">Admin</option>
|
|
2103
|
+
<option value="user">User</option>
|
|
2104
|
+
</select>
|
|
2105
|
+
|
|
2106
|
+
<button onClick={clearFilters} disabled={loading}>
|
|
2107
|
+
Clear
|
|
2108
|
+
</button>
|
|
2109
|
+
</div>
|
|
2110
|
+
|
|
2111
|
+
<ul>
|
|
2112
|
+
{visible.map((u: any) => (
|
|
2113
|
+
<li key={u.id}>{u.name}</li>
|
|
2114
|
+
))}
|
|
2115
|
+
</ul>
|
|
2116
|
+
</div>
|
|
2117
|
+
);
|
|
2118
|
+
}
|
|
2119
|
+
```
|
|
2120
|
+
|
|
2121
|
+
---
|
|
2122
|
+
|
|
2123
|
+
### Use case D — Constrain to a known allow-list (`searchTarget: mode="only"`)
|
|
2124
|
+
|
|
2125
|
+
```tsx
|
|
2126
|
+
import * as React from "react";
|
|
2127
|
+
import { useData } from "@timeax/form-palette/extra";
|
|
2128
|
+
|
|
2129
|
+
export function AllowedIdsOnly() {
|
|
2130
|
+
const allowed = React.useMemo(() => ["u_1", "u_8", "u_12"], []);
|
|
2131
|
+
|
|
2132
|
+
const { visible, setSearchTarget, searchMode, setSearchMode } = useData<any>({
|
|
2133
|
+
endpoint: "/api/users",
|
|
2134
|
+
method: "GET",
|
|
2135
|
+
selector: "data",
|
|
2136
|
+
searchMode: "local",
|
|
2137
|
+
});
|
|
2138
|
+
|
|
2139
|
+
React.useEffect(() => {
|
|
2140
|
+
setSearchTarget({ mode: "only", only: allowed, subject: null });
|
|
2141
|
+
}, [allowed, setSearchTarget]);
|
|
2142
|
+
|
|
2143
|
+
return (
|
|
2144
|
+
<div>
|
|
2145
|
+
<small>mode: {searchMode}</small>
|
|
2146
|
+
<button onClick={() => setSearchMode("local")}>Local</button>
|
|
2147
|
+
<button onClick={() => setSearchMode("remote")}>Remote</button>
|
|
2148
|
+
|
|
2149
|
+
<ul>
|
|
2150
|
+
{visible.map((u: any) => (
|
|
2151
|
+
<li key={u.id}>{u.name}</li>
|
|
2152
|
+
))}
|
|
2153
|
+
</ul>
|
|
2154
|
+
</div>
|
|
2155
|
+
);
|
|
2156
|
+
}
|
|
2157
|
+
```
|
|
2158
|
+
|
|
2159
|
+
---
|
|
2160
|
+
|
|
2161
|
+
### Use case E — Custom multi-select UI (selection enabled)
|
|
2162
|
+
|
|
2163
|
+
```tsx
|
|
2164
|
+
import * as React from "react";
|
|
2165
|
+
import { useData } from "@timeax/form-palette/extra";
|
|
2166
|
+
|
|
2167
|
+
type Tag = { id: string; name: string };
|
|
2168
|
+
|
|
2169
|
+
export function TagMultiSelect() {
|
|
2170
|
+
const { visible, isSelected, toggle, selectedIds, selected } = useData<Tag>({
|
|
2171
|
+
endpoint: "/api/tags",
|
|
2172
|
+
method: "GET",
|
|
2173
|
+
selector: "data",
|
|
2174
|
+
searchMode: "remote",
|
|
2175
|
+
selection: {
|
|
2176
|
+
mode: "multiple",
|
|
2177
|
+
key: "id",
|
|
2178
|
+
prune: "never", // keep selection stable while remote results change
|
|
2179
|
+
},
|
|
2180
|
+
});
|
|
2181
|
+
|
|
2182
|
+
return (
|
|
2183
|
+
<div>
|
|
2184
|
+
<ul>
|
|
2185
|
+
{visible.map((t) => (
|
|
2186
|
+
<li key={t.id}>
|
|
2187
|
+
<label style={{ display: "flex", gap: 8, alignItems: "center" }}>
|
|
2188
|
+
<input
|
|
2189
|
+
type="checkbox"
|
|
2190
|
+
checked={isSelected(t.id)}
|
|
2191
|
+
onChange={() => toggle(t.id)}
|
|
2192
|
+
/>
|
|
2193
|
+
{t.name}
|
|
2194
|
+
</label>
|
|
2195
|
+
</li>
|
|
2196
|
+
))}
|
|
2197
|
+
</ul>
|
|
2198
|
+
|
|
2199
|
+
<pre style={{ marginTop: 12 }}>
|
|
2200
|
+
selectedIds: {JSON.stringify(selectedIds, null, 2)}
|
|
2201
|
+
|
|
2202
|
+
selected objects: {JSON.stringify(selected, null, 2)}
|
|
2203
|
+
</pre>
|
|
2204
|
+
</div>
|
|
2205
|
+
);
|
|
2206
|
+
}
|
|
2207
|
+
```
|
|
2208
|
+
|
|
2209
|
+
---
|
|
2210
|
+
|
|
2211
|
+
### Use case F — Advanced request shaping (`buildRequest`)
|
|
2212
|
+
|
|
2213
|
+
```tsx
|
|
2214
|
+
import { useData } from "@timeax/form-palette/extra";
|
|
2215
|
+
|
|
2216
|
+
export function CustomPayloadExample() {
|
|
2217
|
+
const { visible } = useData<any, { status?: string }>({
|
|
2218
|
+
endpoint: "/api/users/search",
|
|
2219
|
+
method: "POST",
|
|
2220
|
+
selector: "items",
|
|
2221
|
+
|
|
2222
|
+
buildRequest: ({ query, filters, cursor }) => ({
|
|
2223
|
+
body: {
|
|
2224
|
+
q: query,
|
|
2225
|
+
filters,
|
|
2226
|
+
cursor,
|
|
2227
|
+
},
|
|
2228
|
+
headers: {
|
|
2229
|
+
"X-Search-Mode": "users",
|
|
2230
|
+
},
|
|
2231
|
+
}),
|
|
2232
|
+
});
|
|
2233
|
+
|
|
2234
|
+
return <pre>{JSON.stringify(visible, null, 2)}</pre>;
|
|
2235
|
+
}
|
|
2236
|
+
```
|
|
2237
|
+
|
|
2238
|
+
---
|
|
2239
|
+
|
|
2240
|
+
## Practical tips
|
|
2241
|
+
|
|
2242
|
+
* Want instant search UX and your dataset is small → `searchMode: "local"`.
|
|
2243
|
+
* Want selection to persist while users search remotely → `selection.prune = "never"`.
|
|
2244
|
+
* Want selection to strictly match what’s visible in the current list → `selection.prune = "missing"`.
|
|
2245
|
+
* Want lazy fetch (open a modal first) → `enabled: false`, then call `fetch()` when needed.
|
|
2246
|
+
|
|
2247
|
+
---
|
|
2248
|
+
|
|
2249
|
+
# 3) JsonEditor (overview)
|
|
2250
|
+
|
|
2251
|
+
JsonEditor is exported from `extra` as:
|
|
2252
|
+
|
|
2253
|
+
```ts
|
|
2254
|
+
import { JsonEditor } from "@timeax/form-palette/extra";
|
|
2255
|
+
```
|
|
2256
|
+
|
|
2257
|
+
Use it when you want a structured JSON editing experience (often used behind the `json-editor` InputField variant).
|
|
2258
|
+
|
|
2259
|
+
## Standalone usage
|
|
2260
|
+
|
|
2261
|
+
```tsx
|
|
2262
|
+
import * as React from "react";
|
|
2263
|
+
import { JsonEditor } from "@timeax/form-palette/extra";
|
|
2264
|
+
|
|
2265
|
+
export function JsonEditorStandalone() {
|
|
2266
|
+
const [root, setRoot] = React.useState({
|
|
2267
|
+
user: { name: "Ada", roles: ["admin"] },
|
|
2268
|
+
});
|
|
2269
|
+
|
|
2270
|
+
return (
|
|
2271
|
+
<JsonEditor
|
|
2272
|
+
root={root}
|
|
2273
|
+
onRoot={setRoot}
|
|
2274
|
+
open
|
|
2275
|
+
onClose={() => console.log("closed")}
|
|
2276
|
+
title="User JSON"
|
|
2277
|
+
/>
|
|
2278
|
+
);
|
|
2279
|
+
}
|
|
2280
|
+
```
|