cleanplate 0.2.2 → 0.2.3
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/dist/components/form-controls/Checkbox.d.ts +54 -6
- package/dist/components/form-controls/Checkbox.d.ts.map +1 -1
- package/dist/components/form-controls/Date.d.ts +3 -0
- package/dist/components/form-controls/Date.d.ts.map +1 -1
- package/dist/components/form-controls/File.d.ts +30 -4
- package/dist/components/form-controls/File.d.ts.map +1 -1
- package/dist/components/form-controls/Input.d.ts +36 -0
- package/dist/components/form-controls/Input.d.ts.map +1 -1
- package/dist/components/form-controls/Radio.d.ts +54 -6
- package/dist/components/form-controls/Radio.d.ts.map +1 -1
- package/dist/components/form-controls/Select.d.ts +3 -0
- package/dist/components/form-controls/Select.d.ts.map +1 -1
- package/dist/components/form-controls/Stepper.d.ts +1 -0
- package/dist/components/form-controls/Stepper.d.ts.map +1 -1
- package/dist/components/form-controls/TextArea.d.ts +1 -0
- package/dist/components/form-controls/TextArea.d.ts.map +1 -1
- package/dist/components/form-controls/Toggle.d.ts +5 -3
- package/dist/components/form-controls/Toggle.d.ts.map +1 -1
- package/dist/components/form-controls/index.d.ts +3 -3
- package/dist/components/form-controls/index.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.es.css +1 -1
- package/dist/index.es.js +3 -3
- package/dist/index.js +3 -3
- package/docs/FormControls.md +188 -11
- package/llms.txt +2 -2
- package/package.json +1 -1
package/docs/FormControls.md
CHANGED
|
@@ -10,10 +10,10 @@ FormControls is a set of form primitives exported as a namespace: `FormControls.
|
|
|
10
10
|
| TextArea | Multi-line text | placeholder, value, onChange(e) |
|
|
11
11
|
| Select | Single or multi select dropdown | options ({ label, value }[]), value, onChange(option \| option[]), isMulti, placeholder |
|
|
12
12
|
| Date | Day/month/year picker (DD-MMM-YYYY) | defaultValue, onChange(dateValue: string) |
|
|
13
|
-
| Checkbox |
|
|
14
|
-
| Radio | Radio
|
|
15
|
-
| File | File
|
|
16
|
-
| Toggle |
|
|
13
|
+
| Checkbox | Checkbox group (array-based, multi-select) | name, label, options, value (CheckboxValue[]), defaultValue, onChange(values, e), orientation, variant |
|
|
14
|
+
| Radio | Radio group (array-based) | name, label, options, value, defaultValue, onChange(value, e), orientation, variant |
|
|
15
|
+
| File | File picker with `button` / `card` variants, drag-and-drop, and a removable file list | name, label, variant, multiple, accept, value (File[]), onChange(files, e), buttonLabel, dropZoneText |
|
|
16
|
+
| Toggle | On/off switch | checked, defaultChecked, onChange(checked: boolean) |
|
|
17
17
|
| Stepper | Text input for step flows | placeholder, value, onChange(e) |
|
|
18
18
|
|
|
19
19
|
## Types
|
|
@@ -53,6 +53,7 @@ interface InputProps {
|
|
|
53
53
|
id?: string;
|
|
54
54
|
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
55
55
|
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
56
|
+
onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;
|
|
56
57
|
value?: string;
|
|
57
58
|
defaultValue?: string;
|
|
58
59
|
label?: string;
|
|
@@ -63,14 +64,36 @@ interface InputProps {
|
|
|
63
64
|
isFluid?: boolean;
|
|
64
65
|
className?: string;
|
|
65
66
|
error?: string;
|
|
67
|
+
dataTestId?: string;
|
|
68
|
+
/** Native `autocomplete` attribute. */
|
|
69
|
+
autoComplete?: string;
|
|
70
|
+
/** Hard cap on the number of characters the user can type. */
|
|
71
|
+
maxLength?: number;
|
|
72
|
+
/** Numeric lower bound (clamped on blur for `type="number"`). */
|
|
73
|
+
min?: number | string;
|
|
74
|
+
/** Numeric upper bound (clamped on blur for `type="number"`). */
|
|
75
|
+
max?: number | string;
|
|
76
|
+
/** Inline leading affix (currency, country code, …). Soft-capped at 4 chars. */
|
|
77
|
+
prefix?: string;
|
|
78
|
+
/** Inline trailing affix (unit, TLD, …). Soft-capped at 4 chars. */
|
|
79
|
+
suffix?: string;
|
|
80
|
+
/** Spoken label for screen readers when `prefix` is a symbol/abbreviation. */
|
|
81
|
+
prefixA11yLabel?: string;
|
|
82
|
+
/** Spoken label for screen readers when `suffix` is a symbol/abbreviation. */
|
|
83
|
+
suffixA11yLabel?: string;
|
|
66
84
|
}
|
|
67
85
|
```
|
|
68
86
|
|
|
69
87
|
### Other control types
|
|
70
|
-
- **TextAreaProps
|
|
71
|
-
- **
|
|
72
|
-
- **
|
|
73
|
-
- **
|
|
88
|
+
- **TextAreaProps**: label, value/defaultValue, onChange, isDisabled, isRequired, isFluid, className, error, dataTestId.
|
|
89
|
+
- **FileProps**: `name`, `label`, `variant` (`"button" | "card"`, default `"button"`), `multiple`, `accept`, `value: File[]` (controlled), `defaultValue: File[]` (uncontrolled initial visual list), `onChange(files: File[], e?)`, `buttonLabel` (default `"Browse file"`), `dropZoneText` (default `"Drag files to upload"`, card variant only), plus the common `isDisabled`, `isRequired`, `isFluid`, `className`, `error`, `dataTestId`. The card variant supports drag-and-drop. **FileVariant** = `"button" | "card"`.
|
|
90
|
+
- **RadioProps**: `options` (non-empty `RadioOption[]`), `name`, `label` (group `<legend>`), optional `id`, `value`, `defaultValue`, `onChange(value, e)`, `orientation` (`"vertical" | "horizontal"`), `variant` (`"default" | "card"`), `isDisabled`, `isRequired`, `isFluid`, `className`, `error`, `dataTestId`.
|
|
91
|
+
- **RadioOption**: `{ label, value, isDisabled?, description?, icon?, dataTestId?, id? }`. `description` is rendered under the option label as muted secondary text and linked via `aria-describedby`. `icon` accepts any `ReactNode` (e.g. `<Icon />`, `<img />`, custom SVG) and renders to the left of the label/description.
|
|
92
|
+
- **ToggleProps**: checked, defaultChecked, onChange(checked: boolean), label, isDisabled, isRequired, isFluid, className, error, dataTestId.
|
|
93
|
+
- **CheckboxProps**: `options` (non-empty `CheckboxOption[]`), `name`, `label` (group `<legend>`), optional `id`, `value` (`CheckboxValue[]`), `defaultValue` (`CheckboxValue[]`), `onChange(values, e)`, `orientation` (`"vertical" | "horizontal"`), `variant` (`"default" | "card"`), `isDisabled`, `isRequired`, `isFluid`, `className`, `error`, `dataTestId`.
|
|
94
|
+
- **CheckboxOption**: `{ label, value, isDisabled?, description?, icon?, dataTestId?, id? }`. `description` is rendered under the option label as muted secondary text and linked via `aria-describedby`. `icon` accepts any `ReactNode` (e.g. `<Icon />`, `<img />`, custom SVG) and renders to the left of the label/description. `CheckboxValue = string | number`.
|
|
95
|
+
- **DateProps**: optional `id`, `label`, `defaultValue` (`"dd-mm-yyyy"`), `onChange(dateValue: string)`, plus `isDisabled`, `isRequired`, `isFluid`, `className`, `error`, `dataTestId`.
|
|
96
|
+
- **FormControlsStepperProps**: label, placeholder, value/defaultValue, onChange(e), type, isDisabled, isRequired, isFluid, className, error, dataTestId.
|
|
74
97
|
|
|
75
98
|
## Usage Examples
|
|
76
99
|
|
|
@@ -88,23 +111,177 @@ import { FormControls } from "cleanplate";
|
|
|
88
111
|
/>
|
|
89
112
|
```
|
|
90
113
|
|
|
91
|
-
###
|
|
114
|
+
### Input with prefix / suffix
|
|
115
|
+
|
|
116
|
+
```jsx
|
|
117
|
+
<FormControls.Input label="Amount" type="number" prefix="$" suffix="USD" placeholder="0.00" />
|
|
118
|
+
<FormControls.Input label="Phone" type="tel" prefix="+91" placeholder="98765 43210" />
|
|
119
|
+
<FormControls.Input label="Weight" type="number" suffix="kg" placeholder="0" />
|
|
120
|
+
<FormControls.Input label="Discount" type="number" suffix="%" placeholder="0" />
|
|
121
|
+
<FormControls.Input label="Website" type="url" suffix=".com" placeholder="acme" />
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### TextArea and Date
|
|
92
125
|
|
|
93
126
|
```jsx
|
|
94
127
|
<FormControls.TextArea label="Message" placeholder="Hello" />
|
|
95
|
-
<FormControls.Checkbox label="Accept terms?" value={checked} onChange={setChecked} />
|
|
96
128
|
<FormControls.Date label="DOB" defaultValue="31-05-1992" onChange={(v) => {}} />
|
|
97
129
|
```
|
|
98
130
|
|
|
131
|
+
### Checkbox group
|
|
132
|
+
|
|
133
|
+
Pass an `options` array. The component renders the entire group inside a `<fieldset>` + `<legend>` and emits `onChange(values, event)` with the next array of selected values. The required `*` is rendered on the group label, not on individual options.
|
|
134
|
+
|
|
135
|
+
```jsx
|
|
136
|
+
const [interests, setInterests] = useState(["product"]);
|
|
137
|
+
<FormControls.Checkbox
|
|
138
|
+
label="Email me about"
|
|
139
|
+
name="interests"
|
|
140
|
+
value={interests}
|
|
141
|
+
onChange={(v) => setInterests(v)}
|
|
142
|
+
options={[
|
|
143
|
+
{ label: "Newsletters", value: "newsletter", description: "Weekly digest" },
|
|
144
|
+
{ label: "Product updates", value: "product", description: "Release notes for features you use" },
|
|
145
|
+
{ label: "Promotions", value: "promo", isDisabled: true },
|
|
146
|
+
]}
|
|
147
|
+
/>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
For a single checkbox (consent / opt-in), pass an array with one entry — `value` is still an array; an empty array means unchecked:
|
|
151
|
+
|
|
152
|
+
```jsx
|
|
153
|
+
const [accepted, setAccepted] = useState([]);
|
|
154
|
+
<FormControls.Checkbox
|
|
155
|
+
label="Terms and conditions"
|
|
156
|
+
name="accept"
|
|
157
|
+
isRequired
|
|
158
|
+
value={accepted}
|
|
159
|
+
onChange={(v) => setAccepted(v)}
|
|
160
|
+
options={[{ label: "I accept the terms and conditions", value: "yes" }]}
|
|
161
|
+
/>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Checkbox (card variant with icons)
|
|
165
|
+
|
|
166
|
+
```jsx
|
|
167
|
+
import { FormControls, Icon } from "cleanplate";
|
|
168
|
+
|
|
169
|
+
<FormControls.Checkbox
|
|
170
|
+
label="Add-ons"
|
|
171
|
+
name="addons"
|
|
172
|
+
variant="card"
|
|
173
|
+
orientation="horizontal"
|
|
174
|
+
isFluid
|
|
175
|
+
value={addons}
|
|
176
|
+
onChange={(v) => setAddons(v)}
|
|
177
|
+
options={[
|
|
178
|
+
{ label: "Analytics", value: "analytics", description: "Real-time dashboards", icon: <Icon name="bar_chart" /> },
|
|
179
|
+
{ label: "Automation", value: "automation", description: "Trigger on events", icon: <Icon name="bolt" /> },
|
|
180
|
+
{ label: "Collaboration", value: "collab", description: "Roles and comments", icon: <Icon name="groups" /> },
|
|
181
|
+
]}
|
|
182
|
+
/>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Radio group
|
|
186
|
+
|
|
187
|
+
Pass an `options` array. The component renders the entire group inside a `<fieldset>` + `<legend>` and emits `onChange(value, event)`. The required `*` is rendered on the group label, not on individual options.
|
|
188
|
+
|
|
189
|
+
```jsx
|
|
190
|
+
const [plan, setPlan] = useState("std");
|
|
191
|
+
<FormControls.Radio
|
|
192
|
+
label="Shipping"
|
|
193
|
+
name="ship"
|
|
194
|
+
value={plan}
|
|
195
|
+
onChange={(v) => setPlan(String(v))}
|
|
196
|
+
isRequired
|
|
197
|
+
options={[
|
|
198
|
+
{ label: "Standard", value: "std" },
|
|
199
|
+
{ label: "Express", value: "exp", description: "1–2 business days" },
|
|
200
|
+
{ label: "Overnight", value: "ovn", isDisabled: true },
|
|
201
|
+
]}
|
|
202
|
+
/>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
For a single radio, just pass an array with one entry:
|
|
206
|
+
|
|
207
|
+
```jsx
|
|
208
|
+
<FormControls.Radio
|
|
209
|
+
label="Subscription"
|
|
210
|
+
name="subscribe"
|
|
211
|
+
value={subscribed ? "yes" : ""}
|
|
212
|
+
onChange={(v) => setSubscribed(v === "yes")}
|
|
213
|
+
options={[{ label: "Subscribe to weekly digest", value: "yes" }]}
|
|
214
|
+
/>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Radio (card variant with icons)
|
|
218
|
+
|
|
219
|
+
```jsx
|
|
220
|
+
import { FormControls, Icon } from "cleanplate";
|
|
221
|
+
|
|
222
|
+
<FormControls.Radio
|
|
223
|
+
label="Delivery method"
|
|
224
|
+
name="delivery"
|
|
225
|
+
variant="card"
|
|
226
|
+
orientation="horizontal"
|
|
227
|
+
isFluid
|
|
228
|
+
value={method}
|
|
229
|
+
onChange={(v) => setMethod(String(v))}
|
|
230
|
+
options={[
|
|
231
|
+
{ label: "Standard", value: "std", description: "4–10 business days · $5.00", icon: <Icon name="local_shipping" /> },
|
|
232
|
+
{ label: "Express", value: "exp", description: "2–5 business days · $16.00", icon: <Icon name="bolt" /> },
|
|
233
|
+
{ label: "Super Fast", value: "fast", description: "1 business day · $25.00", icon: <Icon name="rocket_launch" /> },
|
|
234
|
+
]}
|
|
235
|
+
/>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### File (button variant)
|
|
239
|
+
|
|
240
|
+
Compact trigger that looks like a primary button. Selected files render below the trigger as small cards with a type-specific thumbnail icon, name, size, and a remove button.
|
|
241
|
+
|
|
242
|
+
```jsx
|
|
243
|
+
const [files, setFiles] = useState([]);
|
|
244
|
+
<FormControls.File
|
|
245
|
+
label="Upload file"
|
|
246
|
+
name="upload"
|
|
247
|
+
value={files}
|
|
248
|
+
onChange={(next) => setFiles(next)}
|
|
249
|
+
/>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### File (card / drop-zone variant)
|
|
253
|
+
|
|
254
|
+
Drop-zone with dashed border, helper text, and a `Browse file` CTA. Click anywhere in the zone to open the picker, or drag files in. Combine with `multiple` and `accept` to constrain selection.
|
|
255
|
+
|
|
256
|
+
```jsx
|
|
257
|
+
const [files, setFiles] = useState([]);
|
|
258
|
+
<FormControls.File
|
|
259
|
+
label="File Upload"
|
|
260
|
+
name="upload"
|
|
261
|
+
variant="card"
|
|
262
|
+
multiple
|
|
263
|
+
accept="image/*,application/pdf"
|
|
264
|
+
value={files}
|
|
265
|
+
onChange={(next) => setFiles(next)}
|
|
266
|
+
/>
|
|
267
|
+
```
|
|
268
|
+
|
|
99
269
|
### Used by other components
|
|
100
270
|
|
|
101
271
|
Pagination uses `FormControls.Select` for rows-per-page. Pills uses `FormControls.Input` in edit mode.
|
|
102
272
|
|
|
103
273
|
## Behavior Notes
|
|
104
274
|
|
|
275
|
+
- **Input (`type="number"`):** Renders as `<input type="text" inputmode="numeric" pattern="[0-9]*">` so the field shows the numeric keypad on mobile, validates digit-only input via HTML5 pattern, and avoids the well-known UX issues of native `type="number"` (scroll-wheel mutates value, spinner buttons, accepts `e`/`+`/`-`). Consumers still pass `type="number"` at the API boundary; for decimals or signed numbers, use `type="text"` and add a custom `inputMode`/validation.
|
|
276
|
+
- **Input (`type="search"`):** Keeps `type="search"` semantics (mobile search keyboard, autosuggest history) but hides the browser's native cancel button and renders a leading `search` icon plus a custom `close` clear button from the icon library. The clear button shows only when the input has content, focuses the input on click, and emits a synthetic `onChange` with an empty value so both controlled (`value`/`onChange`) and uncontrolled (`defaultValue`) usage stay in sync.
|
|
277
|
+
- **Input (`prefix` / `suffix`):** Inline leading/trailing text affix for currency (`$`), country code (`+91`), unit (`kg`, `%`), TLD (`.com`), etc. Soft-capped at 4 characters so the layout stays predictable; longer strings are truncated. When set, the field's outer wrapper takes over the visible border / padding / focus ring so the affixes read as part of the same input. Affixes are linked to the input via `aria-describedby`, so screen readers announce e.g. "Amount, dollars, $500" when the visible affix is `$`. For symbols/abbreviations that don't read well, pass `prefixA11yLabel` / `suffixA11yLabel` (e.g. `prefix="$"`, `prefixA11yLabel="dollars"`). Ignored when `type="search"` (search already uses both edges) — for any other `type`, including `number`, affixes work as expected.
|
|
278
|
+
- **Input (validation / constraints):** `maxLength` is passed straight to the native attribute (works for any `type`). `min` / `max` are passed to the native attribute (HTML5 form-validation hints) and, for `type="number"` only, also clamped on `blur` — the user can finish typing freely and the value snaps to the bound when they leave the field.
|
|
279
|
+
- **Input (`autoComplete` / `onBlur`):** `autoComplete` maps to the native attribute (`"email"`, `"current-password"`, `"off"`, …). `onBlur` runs after any internal numeric clamp so consumers see the final value.
|
|
105
280
|
- **Select:** options are `{ label, value }`; single select passes one option to onChange, multi passes an array. value can be option or array for multi.
|
|
106
281
|
- **Date:** Returns string "dd-mm-yyyy" to onChange; uses internal day/month/year Selects.
|
|
107
|
-
- **
|
|
282
|
+
- **Radio:** Group-first API — pass `options: RadioOption[]`. Renders `<fieldset>` + `<legend>` with a single `value` and `onChange(value, e)`. `isRequired` puts `*` on the legend and adds `required`/`aria-required` to the first enabled option (HTML5 only requires one input in the group to carry it). Custom ring/dot follows the native `:checked` state so uncontrolled groups stay visually correct. Pass `variant="card"` for tile-style options (ring in top-right, optional `icon` on the left, primary-brand border + tint when selected).
|
|
283
|
+
- **Checkbox:** Group-first API — pass `options: CheckboxOption[]`. Renders `<fieldset>` + `<legend>` with a `value: CheckboxValue[]` and `onChange(values, e)`. `isRequired` puts `*` on the legend and sets `aria-required` on the group; native HTML5 doesn't enforce "at least one" for checkbox groups, so add custom validation at the form layer. Custom box/tick follows the native `:checked` state. Pass `variant="card"` for tile-style options (box in top-right, optional `icon` on the left, primary-brand border + tint when checked). For a single checkbox, pass a one-element `options` array — `value=[]` is unchecked, `value=[opt.value]` is checked.
|
|
284
|
+
- **File:** Native `<input type="file">` is visually hidden but stays in the a11y tree. Manages a `File[]` selection internally; `onChange(files, e)` fires for picker selections, drops, and removals (the underlying event is `undefined` for non-picker triggers). With `multiple`, subsequent picks/drops append; without, the new selection replaces the old. The card variant supports drag-and-drop and tints primary-brand on hover. Removing a file resets the native input so re-selecting the same file still emits a change. `defaultValue` seeds the visual list only — browsers don't allow programmatic pre-population of file inputs.
|
|
108
285
|
- **isFluid:** Full-width field wrapper.
|
|
109
286
|
|
|
110
287
|
## Related Components / Links
|
package/llms.txt
CHANGED
|
@@ -177,8 +177,8 @@ All component documentation is located in the `docs/` folder. The following docu
|
|
|
177
177
|
### FormControls
|
|
178
178
|
- File: `docs/FormControls.md`
|
|
179
179
|
- Purpose: Set of form primitives: Input, TextArea, Select, Date, Checkbox, Radio, File, Toggle, Stepper. Access via FormControls.Input, FormControls.Select, etc.
|
|
180
|
-
- Key Features: Input (text), TextArea, Select (single/multi, options { label, value }), Date (dd-mm-yyyy), Checkbox (onChange(
|
|
181
|
-
- Types: InputProps, SelectProps, SelectOption, TextAreaProps, CheckboxProps, FileProps, RadioProps, ToggleProps, DateProps, FormControlsStepperProps
|
|
180
|
+
- Key Features: Input (supports text/search/number + prefix/suffix; number maps to numeric text input and clamps via `min`/`max` on blur; search adds icon + clear button), TextArea, Select (single/multi, options { label, value }, supports `name`/`id`, ARIA wiring, and hidden input for native form submission), Date (dd-mm-yyyy), Checkbox (group-first: options[], CheckboxValue[] for multi-select, onChange(values, e); each option supports `description` and `icon`; `variant="card"` for tile-style options), Radio (group-first: options[], single value, onChange(value, e); each option supports `description` and `icon`; `variant="card"` for tile-style options), File (`variant="button" | "card"`; card variant has dashed drop zone with drag-and-drop; `value: File[]` + `onChange(files, e)`; renders a removable file list with type-aware thumbnail, name, and size), Toggle (switch semantics via checkbox + role="switch"), Stepper; common props label, isRequired, isFluid, error
|
|
181
|
+
- Types: InputProps, SelectProps, SelectOption, TextAreaProps, CheckboxProps, CheckboxOption, CheckboxValue, FileProps, FileVariant, RadioProps, RadioOption, RadioValue, ToggleProps, DateProps, FormControlsStepperProps
|
|
182
182
|
- Related Components: Pills (Input), Pagination (Select), Container, Button
|
|
183
183
|
|
|
184
184
|
### Toast Component
|