cleanplate 0.2.3 → 0.2.5
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/Select.d.ts +75 -5
- package/dist/components/form-controls/Select.d.ts.map +1 -1
- package/dist/components/form-controls/Stepper.d.ts +6 -0
- package/dist/components/form-controls/Stepper.d.ts.map +1 -1
- package/dist/components/form-controls/index.d.ts +1 -1
- package/dist/components/form-controls/index.d.ts.map +1 -1
- package/dist/components/pagination/Pagination.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.es.css +1 -1
- package/dist/index.es.js +7 -3
- package/dist/index.js +7 -3
- package/docs/FormControls.md +130 -9
- package/docs/superpowers/plans/2026-05-10-select-floating-ui.md +122 -0
- package/llms.txt +3 -2
- package/package.json +4 -2
package/docs/FormControls.md
CHANGED
|
@@ -8,7 +8,7 @@ FormControls is a set of form primitives exported as a namespace: `FormControls.
|
|
|
8
8
|
| --- | --- | --- |
|
|
9
9
|
| Input | Single-line text | placeholder, value, onChange(e), type |
|
|
10
10
|
| TextArea | Multi-line text | placeholder, value, onChange(e) |
|
|
11
|
-
| Select |
|
|
11
|
+
| Select | Floating UI combobox: desktop portalled list; **≤768px** bottom sheet; sync or async options, search, groups, multi chips + cap | `mode` / `isMulti`, `options`, `onSearch`, `groups`, `maxSelect`, `triggerMaxItems`, `name`, `placeholder`, `error` |
|
|
12
12
|
| Date | Day/month/year picker (DD-MMM-YYYY) | defaultValue, onChange(dateValue: string) |
|
|
13
13
|
| Checkbox | Checkbox group (array-based, multi-select) | name, label, options, value (CheckboxValue[]), defaultValue, onChange(values, e), orientation, variant |
|
|
14
14
|
| Radio | Radio group (array-based) | name, label, options, value, defaultValue, onChange(value, e), orientation, variant |
|
|
@@ -18,23 +18,54 @@ FormControls is a set of form primitives exported as a namespace: `FormControls.
|
|
|
18
18
|
|
|
19
19
|
## Types
|
|
20
20
|
|
|
21
|
-
### SelectOption
|
|
21
|
+
### Option (and SelectOption)
|
|
22
|
+
|
|
23
|
+
`Option` is the canonical option shape. **`SelectOption` is a deprecated alias** — use `Option` in new code.
|
|
24
|
+
|
|
22
25
|
```typescript
|
|
23
|
-
interface
|
|
24
|
-
label: string;
|
|
26
|
+
interface Option {
|
|
25
27
|
value: string | number;
|
|
28
|
+
label: string;
|
|
29
|
+
/** Contiguous rows with the same non-empty string get a sticky group heading when `groups` is true. */
|
|
30
|
+
group?: string;
|
|
31
|
+
/** Material Symbols icon name (row-leading). */
|
|
32
|
+
icon?: string;
|
|
33
|
+
/** Image URL for a circular avatar (row-leading). */
|
|
34
|
+
avatar?: string;
|
|
35
|
+
/** Muted secondary line (e.g. subtitle). */
|
|
36
|
+
meta?: string;
|
|
37
|
+
disabled?: boolean;
|
|
26
38
|
}
|
|
27
39
|
```
|
|
28
40
|
|
|
29
41
|
### SelectProps
|
|
42
|
+
|
|
30
43
|
```typescript
|
|
44
|
+
type SelectValue = Option | Option[] | null;
|
|
45
|
+
|
|
31
46
|
interface SelectProps {
|
|
32
|
-
|
|
33
|
-
|
|
47
|
+
name?: string;
|
|
48
|
+
id?: string;
|
|
34
49
|
label?: string;
|
|
35
|
-
|
|
50
|
+
/** Controlled selection: one option, array (multi), or `null` when cleared (single). Multi clear uses `[]`. */
|
|
51
|
+
value?: SelectValue;
|
|
52
|
+
onChange?: (option: Option | Option[] | null) => void;
|
|
53
|
+
/**
|
|
54
|
+
* Static list, or `null` with `onSearch` for async/search-backed data.
|
|
55
|
+
* `[]` or missing = empty sync list.
|
|
56
|
+
*/
|
|
57
|
+
options?: Option[] | null;
|
|
58
|
+
/** Required when `options` is `null`. Debounced by `searchDebounce`; empty query runs immediately. */
|
|
59
|
+
onSearch?: (query: string) => Promise<Option[]>;
|
|
60
|
+
/** Debounce for `onSearch` only (ms). @default 300 */
|
|
61
|
+
searchDebounce?: number;
|
|
62
|
+
/** Panel search field placeholder. @default "Search" */
|
|
63
|
+
searchPlaceholder?: string;
|
|
64
|
+
/** When search text matches nothing, optional “add” callback receives trimmed string. */
|
|
65
|
+
onAddOption?: (value: string) => void;
|
|
66
|
+
/** @default true */
|
|
67
|
+
closeOnAddOption?: boolean;
|
|
36
68
|
placeholder?: string;
|
|
37
|
-
isMulti?: boolean;
|
|
38
69
|
isRequired?: boolean;
|
|
39
70
|
isDisabled?: boolean;
|
|
40
71
|
isFluid?: boolean;
|
|
@@ -43,6 +74,19 @@ interface SelectProps {
|
|
|
43
74
|
triggerClassName?: string;
|
|
44
75
|
triggerActiveClassName?: string;
|
|
45
76
|
contentsClassName?: string;
|
|
77
|
+
dataTestId?: string;
|
|
78
|
+
/** Selection mode. @default 'single'. Prefer over legacy `isMulti`. */
|
|
79
|
+
mode?: "single" | "multi";
|
|
80
|
+
/** @deprecated Use `mode="multi"`. */
|
|
81
|
+
isMulti?: boolean;
|
|
82
|
+
/** Multi: max chips before a "+N" overflow badge. @default 2 */
|
|
83
|
+
triggerMaxItems?: number;
|
|
84
|
+
/** Multi: show clear control on trigger when allowed. @default true */
|
|
85
|
+
clearable?: boolean;
|
|
86
|
+
/** Group headings for contiguous same-`group` options. @default false */
|
|
87
|
+
groups?: boolean;
|
|
88
|
+
/** Multi only: max selectable options; “Select all” respects cap. No effect in single mode. */
|
|
89
|
+
maxSelect?: number;
|
|
46
90
|
}
|
|
47
91
|
```
|
|
48
92
|
|
|
@@ -111,6 +155,41 @@ import { FormControls } from "cleanplate";
|
|
|
111
155
|
/>
|
|
112
156
|
```
|
|
113
157
|
|
|
158
|
+
### Select — multi, groups, async
|
|
159
|
+
|
|
160
|
+
**Multi** with `mode="multi"` (or legacy `isMulti`). **`triggerMaxItems`** limits visible chips; extra selections show a **`+N`** badge with an accessible label. **`maxSelect`** caps how many options can be chosen (optional).
|
|
161
|
+
|
|
162
|
+
```jsx
|
|
163
|
+
const [tags, setTags] = useState([{ label: "A", value: "a" }]);
|
|
164
|
+
<FormControls.Select
|
|
165
|
+
label="Tags"
|
|
166
|
+
mode="multi"
|
|
167
|
+
placeholder="Choose"
|
|
168
|
+
triggerMaxItems={2}
|
|
169
|
+
maxSelect={5}
|
|
170
|
+
value={tags}
|
|
171
|
+
onChange={(next) => setTags(Array.isArray(next) ? next : [])}
|
|
172
|
+
options={[
|
|
173
|
+
{ label: "Apple", value: "apple", group: "Fruit" },
|
|
174
|
+
{ label: "Carrot", value: "carrot", group: "Veg" },
|
|
175
|
+
]}
|
|
176
|
+
groups
|
|
177
|
+
/>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Async / search-backed list:** pass **`options={null}`** and implement **`onSearch(query)`** returning a `Promise<Option[]>`. The panel search field debounces calls (**`searchDebounce`**, default 300ms); an empty query runs immediately. For a fixed in-memory list, pass **`options={items}`** — the same search field **filters** options client-side.
|
|
181
|
+
|
|
182
|
+
```jsx
|
|
183
|
+
<FormControls.Select
|
|
184
|
+
label="City"
|
|
185
|
+
options={null}
|
|
186
|
+
onSearch={async (q) => fetchCities(q)} // return Option[]
|
|
187
|
+
searchPlaceholder="Type to search"
|
|
188
|
+
/>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Mobile:** At **viewport width ≤768px**, the panel opens as a **bottom sheet** (fixed to the lower viewport) with dialog semantics when a **label** is present, instead of a floating anchored list.
|
|
192
|
+
|
|
114
193
|
### Input with prefix / suffix
|
|
115
194
|
|
|
116
195
|
```jsx
|
|
@@ -277,13 +356,55 @@ Pagination uses `FormControls.Select` for rows-per-page. Pills uses `FormControl
|
|
|
277
356
|
- **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
357
|
- **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
358
|
- **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.
|
|
280
|
-
- **Select:**
|
|
359
|
+
- **Select:** Built on **Floating UI** — desktop uses a **portalled** panel with flip/shift to stay in the viewport; **≤768px** uses a **bottom sheet** (`role="dialog"`, `aria-modal`, `aria-labelledby` to the field label when the label exists). **Option** shape supports `group`, `icon`, `avatar`, `meta`, `disabled`. **`mode`** (`'single' | 'multi'`) replaces **`isMulti`** (still supported, deprecated). Single mode: **`onChange(Option | null)`** — `null` when cleared. Multi mode: **`onChange(Option[])`** — use **`[]`** for clear. **`name` + hidden `<input>`:** native form submit posts the selected **`value`**(s); **multi** joins with **commas** — avoid comma characters inside `value` if you rely on `FormData`, or parse manually. **`options={null}` + `onSearch`:** async loading; show loading/empty/error states in the panel. **`groups`:** sticky headings for shared `Option.group`. **`maxSelect`:** multi only; **`triggerMaxItems`:** chip overflow **`+N`**. **`aria-controls`** on the combobox trigger and panel search point at the listbox **only while open**. **`aria-invalid`** reflects **`error`** on trigger, search field, and listbox. Validation message uses **`role="alert"`** (via shared field error pattern).
|
|
281
360
|
- **Date:** Returns string "dd-mm-yyyy" to onChange; uses internal day/month/year Selects.
|
|
282
361
|
- **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
362
|
- **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
363
|
- **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.
|
|
285
364
|
- **isFluid:** Full-width field wrapper.
|
|
286
365
|
|
|
366
|
+
## Theming
|
|
367
|
+
|
|
368
|
+
CleanPlate exposes a thin layer of CSS custom properties on `:root` so consumer apps can retheme the form-control surface without forking styles or wrestling specificity. Override these in your own stylesheet **after** the `cleanplate/dist/index.css` import.
|
|
369
|
+
|
|
370
|
+
| Token | Default | What it controls |
|
|
371
|
+
| --- | --- | --- |
|
|
372
|
+
| `--cp-form-control-radius` | `var(--radius-large)` (12px) | Corner radius for `Input`, `TextArea`, `Stepper`, `Select` trigger + open dropdown corners, `Date` day/month/year segments, `File` (outline trigger, drop zone, in-card CTA, file list rows), and `Radio` / `Checkbox` `variant="card"` option tiles. |
|
|
373
|
+
|
|
374
|
+
### Recipes
|
|
375
|
+
|
|
376
|
+
```css
|
|
377
|
+
/* Square-ish form fields across the whole app, while leaving badges, cards, */
|
|
378
|
+
/* and other --radius-large surfaces alone. */
|
|
379
|
+
:root {
|
|
380
|
+
--cp-form-control-radius: 4px;
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
```css
|
|
385
|
+
/* Scope the override to one section — CSS custom properties cascade, so any */
|
|
386
|
+
/* wrapper works as the boundary. */
|
|
387
|
+
.checkout-form {
|
|
388
|
+
--cp-form-control-radius: 0;
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
```jsx
|
|
393
|
+
// Per-instance override — same token, scoped to one element via the style prop.
|
|
394
|
+
<FormControls.Input
|
|
395
|
+
name="zip"
|
|
396
|
+
label="ZIP"
|
|
397
|
+
style={{ "--cp-form-control-radius": "20px" }}
|
|
398
|
+
/>
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### When to override what
|
|
402
|
+
|
|
403
|
+
- **`--cp-form-control-radius`** when you want to retheme just the form-field family (Input, Select trigger, Date, Stepper, TextArea, File triggers and card CTA, file list rows, Radio/Checkbox card tiles). Recommended path.
|
|
404
|
+
- **`--radius-large`** (the underlying design token) when you want every "large radius" surface in CleanPlate — form fields *and* anything else that opts into the same scale — to move together. Coarser, but useful for whole-product rebrands.
|
|
405
|
+
|
|
406
|
+
The component-level token is the public, supported override. Underlying design tokens (`--radius-small`, `--radius-medium`, `--radius-large`, …) are exposed but treated as the lower tier — overriding them is allowed, but expect broader visual impact.
|
|
407
|
+
|
|
287
408
|
## Related Components / Links
|
|
288
409
|
|
|
289
410
|
- Pills (uses FormControls.Input in edit mode)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Select rebuild — Frozen requirements & implementation plan
|
|
2
|
+
|
|
3
|
+
> **Frozen as of:** 2026-05-10
|
|
4
|
+
> **Process:** Implement one numbered phase per session (or PR slice); pause for Storybook review before starting the next. Work stays on the current feature branch.
|
|
5
|
+
|
|
6
|
+
**Goal:** Replace `Select` with a Floating UI–based control: desktop portalled dropdown with collision-aware positioning; mobile-first bottom sheet for the panel; parity with your written spec plus the decisions below.
|
|
7
|
+
|
|
8
|
+
**Tech stack:** React 18, existing SCSS (`FormControls.module.scss`), `@floating-ui/react` (must be a **runtime dependency** for the published package—not only devDependency).
|
|
9
|
+
|
|
10
|
+
**Architecture (short):** Reference element = composite trigger (`button`/`div` pattern matching a11y plan). Floating panel = `FloatingPortal` → `FloatingFocusManager` for desktop; overlay + sheet panel for narrow viewports sharing the same inner list/search shell where possible.
|
|
11
|
+
|
|
12
|
+
**Testing gate:** Existing Storybook form-controls **Select** story (expand with variants as phases land).
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Frozen requirements
|
|
17
|
+
|
|
18
|
+
### Locked from your original spec
|
|
19
|
+
|
|
20
|
+
- **Modes:** `mode: 'single' | 'multi'` (multi shows removable chips when selected; overflow `+N` after `triggerMaxItems`).
|
|
21
|
+
- **Data:** Static `Option[]`, or async when `options === null` and `onSearch` is provided.
|
|
22
|
+
- **Options:** Shape as in your doc (`value`, `label`, optional `group`, `icon`, `avatar`, `meta`, `disabled`).
|
|
23
|
+
- **Portal:** Dropdown content renders through a portal (`document.body` or `#root`-safe target if we add optional `portalRoot` prop—**frozen default:** `document.body`).
|
|
24
|
+
- **Clear:** Trigger shows × whenever there is a selection; clears all and closes panel.
|
|
25
|
+
- **Search:** Debounced `searchDebounce` (async); search row at top of panel (clear control on search input when non-empty)—matches your inspirations.
|
|
26
|
+
- **Panel (multi):** Select all row with checked / unchecked / **indeterminate** when some visible options are selected; respects disabled options and `maxSelect`.
|
|
27
|
+
- **`maxSelect`:** Enforced in multi only; capped state is visibly disabled before tap; single mode **no-op** (frozen).
|
|
28
|
+
- **Keyboard (desktop dropdown):** ↓ / ↑ move active option; Enter toggles selection; Escape closes and returns focus to trigger; Tab closes (match spec table).
|
|
29
|
+
- **States:** Idle, open (+ search focus behavior per phase notes), loading, error, empty, disabled.
|
|
30
|
+
- **Forms:** Preserve hidden/native submission story where applicable (`name` prop); extend as needed for multi value encoding.
|
|
31
|
+
|
|
32
|
+
### Former “open items” — now decided
|
|
33
|
+
|
|
34
|
+
| Item | Decision |
|
|
35
|
+
|------|----------|
|
|
36
|
+
| `maxSelect` in single mode | **No-op** (ignored). |
|
|
37
|
+
| Bottom sheet animation | **CSS-only:** translateY from 100% → 0, **240ms**, `cubic-bezier(0.22, 1, 0.36, 1)`; backdrop fades **160ms**. (Tunable in SCSS only.) |
|
|
38
|
+
| Flip / collision (desktop) | **Yes:** `flip` + `shift` + **`size`** so max height clamps to viewport; scroll inside list. |
|
|
39
|
+
| Error retry | **Manual “Retry”** only after failure (invokes same `onSearch` with last query). No automatic retry loops. |
|
|
40
|
+
| Uncontrolled mode | **Out of scope for v1.** API is controlled: `value` + `onChange`. (Optional `defaultValue` is a future enhancement, not part of this rebuild.) |
|
|
41
|
+
|
|
42
|
+
### Visual / UX defaults (aligned to your screenshots)
|
|
43
|
+
|
|
44
|
+
- Multi trigger: chips with per-chip dismiss; comma-joined label text is replaced by chips + overflow.
|
|
45
|
+
- Single trigger: plain text label of selected option (no chip), or placeholder.
|
|
46
|
+
- Checkbox-style row affordance for multi list; highlight row on hover/focus-visible.
|
|
47
|
+
- Icons: **`icon`** uses **Material Symbols names** like the rest of CleanPlate **`Icon`** (your doc said Tabler—**frozen correction:** match existing **`Icon`** / `cleanplate` pattern in this repo, unless you revert this in review).
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Dependencies
|
|
52
|
+
|
|
53
|
+
- Move **`@floating-ui/react`** from `devDependencies` to **`dependencies`** in `package.json` before shipping the component (can land in Phase 1 PR).
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Implementation phases (one todo each — review after)
|
|
58
|
+
|
|
59
|
+
### Phase 1 — Floating UI desktop shell
|
|
60
|
+
|
|
61
|
+
- Portal + `useFloating` with `flip`, `shift`, `offset`, `size`, `whileElementsMounted` / `autoUpdate`.
|
|
62
|
+
- Replace in-flow dropdown positioning; dismiss on outside press and Escape (`useDismiss`).
|
|
63
|
+
- **Scope limit:** Static options, existing simple single + multi behaviour, no chips yet beyond current approximation if multi still uses text summary briefly.
|
|
64
|
+
- Storybook: Select still runnable; note known gaps.
|
|
65
|
+
|
|
66
|
+
### Phase 2 — API & types freeze
|
|
67
|
+
|
|
68
|
+
- Export `Option` type; deprecate or alias `SelectOption` → `Option` for compat.
|
|
69
|
+
- Add `mode` prop; **`isMulti` maps to `mode`** for one release or deprecate `isMulti` with console warning once (pick minimal churn—frozen approach: **`mode` canonical**, keep `isMulti` as undocumented alias forwarding to `mode` OR remove if you prefer breaking change—default **canonical `mode`** + **`isMulti` deprecated** shim).
|
|
70
|
+
- Storybook controls updated.
|
|
71
|
+
|
|
72
|
+
### Phase 3 — Multi trigger chips + overflow
|
|
73
|
+
|
|
74
|
+
- Render first `triggerMaxItems` selections as removable chips; `+N` for remainder.
|
|
75
|
+
- Clear (×) and chevron behaviours per spec.
|
|
76
|
+
|
|
77
|
+
### Phase 4 — Search (sync + async wiring)
|
|
78
|
+
|
|
79
|
+
- Search field in panel; **sync:** filters `options` locally, no debounce on static path (debounce still applies only to `onSearch` path per your table).
|
|
80
|
+
- **Async:** when `options === null`, debounced `onSearch(q)`.
|
|
81
|
+
|
|
82
|
+
### Phase 5 — Async states
|
|
83
|
+
|
|
84
|
+
- Loading / error / empty UI in list slot; Retry on error only.
|
|
85
|
+
|
|
86
|
+
### Phase 6 — Groups + rich options
|
|
87
|
+
|
|
88
|
+
- `groups` sections; icons/avatars/meta; disabled options skipped in keyboard nav and not selectable.
|
|
89
|
+
|
|
90
|
+
### Phase 7 — Panel bulk actions & `maxSelect`
|
|
91
|
+
|
|
92
|
+
- Select all / clear all in panel (multi); indeterminate logic for “visible” selectable set respect `disabled` rows.
|
|
93
|
+
- Cap styling and blocked clicks.
|
|
94
|
+
|
|
95
|
+
### Phase 8 — Keyboard completion
|
|
96
|
+
|
|
97
|
+
- Arrow navigation through options; Enter to toggle/select; Tab/Escape per frozen table; roving tabindex or Floating UI list patterns.
|
|
98
|
+
|
|
99
|
+
### Phase 9 — Mobile bottom sheet
|
|
100
|
+
|
|
101
|
+
- Breakpoint: **`max-width: 768px`** (match common `md` boundary; overrides if you expose `breakpoints` prop later—**frozen: internal constant** first).
|
|
102
|
+
- Sheet uses focus trap compatible with Floating UI dismissal; reuse inner list markup.
|
|
103
|
+
|
|
104
|
+
### Phase 10 — A11y + Storybook showcases
|
|
105
|
+
|
|
106
|
+
- `aria-expanded`, `aria-controls`, listbox/combobox roles consistent across modes.
|
|
107
|
+
- Hidden input / form submission validated for multi.
|
|
108
|
+
- Stories: static single/multi, async, groups, chips overflow, maxSelect, error, empty, sheet (viewport toggle).
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Review checkpoint protocol
|
|
113
|
+
|
|
114
|
+
After each phase: run **Storybook** (`npm run storybook`), exercise **Select** story (and phase-specific knobs), confirm no regressions in **All controls showcase**, then approve next phase.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Out of scope (explicit)
|
|
119
|
+
|
|
120
|
+
- List virtualization (`react-window`, etc.).
|
|
121
|
+
- Uncontrolled/defaultValue API.
|
|
122
|
+
- Auto-retry on fetch errors.
|
package/llms.txt
CHANGED
|
@@ -177,8 +177,9 @@ 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 (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 (
|
|
181
|
-
- Types: InputProps,
|
|
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** (Floating UI: portalled combobox on desktop with flip/shift positioning; **viewport ≤768px** uses a **bottom sheet** shell with `role="dialog"` / `aria-modal` when a label exists; **sync** `options` with in-panel **search filter**, or **async** via `options={null}` + `onSearch` (debounced); **groups** (`Option.group` sticky headers); **multi** chips with `triggerMaxItems` **+N** overflow, **Select all / Clear all**, **`maxSelect`** cap; optional **`onAddOption`**; combobox + listbox ARIA, **`aria-invalid`** on error, **`aria-controls`** on trigger/search only while open; `name` + hidden input for forms — multi posts comma-joined **`value`s**), 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, **Option** (preferred), **SelectOption** (deprecated alias of Option), **SelectValue**, SelectProps, TextAreaProps, CheckboxProps, CheckboxOption, CheckboxValue, FileProps, FileVariant, RadioProps, RadioOption, RadioValue, ToggleProps, DateProps, FormControlsStepperProps
|
|
182
|
+
- Theming: Public CSS custom property `--cp-form-control-radius` (default `var(--radius-large)` = 12px) controls corner radius for Input, TextArea, Stepper, Select trigger + open dropdown corners, Date day/month/year segments, File (outline trigger, drop zone, in-card CTA span, file list rows), and Radio/Checkbox `variant="card"` tiles. Override on `:root` (or any wrapper / inline `style`) after importing `cleanplate/dist/index.css` to retheme just form fields. Prefer this over overriding the underlying `--radius-large` design token, which affects every "large radius" surface in the framework.
|
|
182
183
|
- Related Components: Pills (Input), Pagination (Select), Container, Button
|
|
183
184
|
|
|
184
185
|
### Toast Component
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cleanplate",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "CleanPlate - A Headless React UI Framework",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -35,10 +35,12 @@
|
|
|
35
35
|
"url": "https://github.com/sivadass/cleanplate/issues"
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://cleanplate.sivadass.in",
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@floating-ui/react": "^0.27.16"
|
|
40
|
+
},
|
|
38
41
|
"devDependencies": {
|
|
39
42
|
"@babel/preset-react": "^7.23.3",
|
|
40
43
|
"@babel/preset-typescript": "^7.28.5",
|
|
41
|
-
"@floating-ui/react": "^0.27.16",
|
|
42
44
|
"@fluentui/storybook-llms-extractor": "^0.0.2",
|
|
43
45
|
"@rollup/plugin-babel": "^6.0.4",
|
|
44
46
|
"@rollup/plugin-commonjs": "^25.0.7",
|