@edux-design/forms 0.0.5 → 0.0.7

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 ADDED
@@ -0,0 +1,179 @@
1
+ # @edux-design/forms
2
+
3
+ React form primitives that share a common context so every label, control, and validation message stays connected and accessible. The package powers the `Field`, `Label`, `Input`, `Textarea`, `Checkbox`, `Radio`, `Switch`, and `Feedback` components used across the Edux design system.
4
+
5
+ - **Accessible by default** – the `FieldProvider` wires `htmlFor`, `aria-describedby`, and live regions automatically.
6
+ - **Composable** – mix any primitives inside `<Field>` and re-export `Field.Feedback` for colocated messaging.
7
+ - **Design-token aware** – components rely on the shared utility `cx` helper plus the Edux tokens for borders, colors, spacing, and focus rings.
8
+ - **Feature complete inputs** – variants, start/end icons, clear buttons, and controlled/uncontrolled support ship in every text control.
9
+
10
+ ---
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install @edux-design/forms @edux-design/utils @edux-design/icons
16
+ # or
17
+ pnpm add @edux-design/forms @edux-design/utils @edux-design/icons
18
+ ```
19
+
20
+ Peer dependencies:
21
+
22
+ - `react` `^19.1.0`
23
+ - `react-dom` `^19.1.0`
24
+
25
+ After installing, ensure your build pipeline can consume both ESM and CJS outputs (generated via `tsup`). The published bundle exposes `/dist/index.{js,mjs}` plus type declarations.
26
+
27
+ ---
28
+
29
+ ## Quick Start
30
+
31
+ ```jsx
32
+ import { Field, Label, Input, Textarea, Checkbox, Radio } from "@edux-design/forms";
33
+
34
+ export function SignupForm() {
35
+ return (
36
+ <form className="flex flex-col gap-8 w-full max-w-md">
37
+ <Field>
38
+ <Label hint="required" description="We’ll only email account alerts.">
39
+ Email address
40
+ </Label>
41
+ <Input
42
+ type="email"
43
+ placeholder="you@example.com"
44
+ variant="primary"
45
+ clearable
46
+ />
47
+ <Field.Feedback tone="error">
48
+ This email is already registered.
49
+ </Field.Feedback>
50
+ </Field>
51
+
52
+ <Field>
53
+ <Label hint="optional">Bio</Label>
54
+ <Textarea rows={5} placeholder="Tell us about yourself…" />
55
+ </Field>
56
+
57
+ <Field>
58
+ <Label>Contact me</Label>
59
+ <Radio />
60
+ </Field>
61
+
62
+ <Field>
63
+ <Label hint="required">Terms</Label>
64
+ <Checkbox aria-label="Agree to the terms and conditions" />
65
+ <Field.Feedback tone="corrective">
66
+ Agreeing is required before continuing.
67
+ </Field.Feedback>
68
+ </Field>
69
+ </form>
70
+ );
71
+ }
72
+ ```
73
+
74
+ Every `Field` instance mounts a internal context. `Label`, `Input`, `Textarea`, `Radio`, and `Field.Feedback` consume that context so they stay synchronized even if rendered in separate components.
75
+
76
+ ---
77
+
78
+ ## Components
79
+
80
+ ### `<Field>`
81
+
82
+ - Provides layout (`flex`, `gap-8`) and context wiring.
83
+ - Accepts any children; typically wraps `Label`, a control (`Input`, `Textarea`, `Radio`), and optional `Field.Feedback`.
84
+ - Set `orientation="horizontal"` when you need inline layouts (e.g., Switch + label) without affecting other form groups.
85
+ - Re-exports `Field.Feedback` for ergonomic composition.
86
+
87
+ ### `<Label>`
88
+
89
+ - Consumes the field context and sets `htmlFor`.
90
+ - Supports `hint="required" | "optional"` and a `description` prop rendered beneath the main label.
91
+ - Pass `hidden` to visually hide the label while keeping it accessible.
92
+
93
+ ### `<Input>`
94
+
95
+ - Extends standard `<input>` props and forwards refs.
96
+ - Variants: `primary`, `error`, `corrective`, `success`, `inactive`.
97
+ - Optional `startIcon`, `endIcon`, and `clearable` button (uses the `Close` icon).
98
+ - When `clearable` is set, clicking the clear button dispatches `onChange` with an empty value, so controlled inputs update seamlessly.
99
+ - Honors `aria-invalid`, `aria-disabled`, and disables itself for the `inactive` variant.
100
+
101
+ ### `<Textarea>`
102
+
103
+ - Mirrors the Input API while targeting multiline content.
104
+ - Supports `rows`, `startIcon`, `endIcon`, `clearable`, and the same variants.
105
+ - Uses `resize-y` by default; pass inline styles to opt into both-axis resizing.
106
+
107
+ ### `<Radio>`
108
+
109
+ - Minimal radio control bound to the surrounding `Field` context.
110
+ - Implements hover, focus, and checked states with Tailwind utility classes.
111
+ - Currently exposes a single style; future work will add “active” token colors (see FIXME in source).
112
+
113
+ ### `<Checkbox>`
114
+
115
+ - Square control that mirrors native checkbox behavior while layering on design-token driven states (hover, focus, pressed, inactive, and error).
116
+ - Supports the same variants as text inputs plus an `indeterminate` appearance for tri-state flows.
117
+ - Automatically shares IDs and `aria-describedby` references with the wrapping `Field`, so helper text and validation copy are announced.
118
+ - Pairs well with `<Label>` for field titles; pass `aria-label` or wrap inline helper text next to the component when you need per-option copy.
119
+
120
+ ### `<Switch>`
121
+
122
+ - Toggle-style checkbox that maps the native input to Edux track and thumb visuals, complete with iconography that switches between the Close and Check glyphs.
123
+ - Variants mirror the checkbox control (`primary`, `success`, `corrective`, `error`, `inactive`) so validation states stay consistent.
124
+ - Integrates with the `Field` context to automatically sync `id`, focus management, and `aria-describedby` strings built from registered feedback messages.
125
+ - Disables itself automatically when `variant="inactive"` and exposes hover, focus, pressed, and checked states for every other variant.
126
+
127
+ ### `<Field.Feedback>`
128
+
129
+ - Semantic messaging component with tones: `info`, `success`, `warning`, `error`, `corrective`.
130
+ - Registers its `id` with the parent `Field`, so any controls automatically receive `aria-describedby`.
131
+ - `tone="error"` switches to `role="alert"` / `aria-live="assertive"`; other tones stay `aria-live="polite"`.
132
+ - Includes a circular icon container that colors itself based on the selected tone.
133
+
134
+ ---
135
+
136
+ ## Accessibility Model
137
+
138
+ 1. **Context-generated IDs** – `FieldProvider` assigns a unique `labelHTMLForId`; the same ID drives `<label htmlFor>` and the child control’s `id`.
139
+ 2. **Description registry** – `Field.Feedback` registers/unregisters its identifier so inputs build the `aria-describedby` string automatically, even if multiple feedback blocks mount.
140
+ 3. **Live regions** – error messages announce immediately via `role="alert"`, while neutral/success tones fall back to polite announcements.
141
+ 4. **Focus styling** – every interactive element shares the branded `focus:shadow-focus` utility, ensuring visible focus outlines without extra work.
142
+
143
+ To enforce the contract, hooks throw if you render any primitive outside `<Field>`, making misconfigurations obvious during development.
144
+
145
+ ---
146
+
147
+ ## Theming & Variants
148
+
149
+ Variants map directly to semantic tokens defined in `@edux-design/design-tokens` and consumed via Tailwind utility classes:
150
+
151
+ | Variant | Border / Background intent |
152
+ | ------------ | -------------------------------------------------------------- |
153
+ | `primary` | Default border; highlights on hover/focus with primary color. |
154
+ | `error` | Danger border + subtle danger background for immediate errors. |
155
+ | `corrective` | Soft highlight for “nudge” messages without blocking input. |
156
+ | `success` | Success border for confirmed entries. |
157
+ | `inactive` | Muted border/background, disables the control automatically. |
158
+
159
+ Apply variants per control to reflect validation state. Combine with `Field.Feedback` to reinforce messaging.
160
+
161
+ ---
162
+
163
+ ## Building & Contributing
164
+
165
+ - `pnpm build` (or `npm run build`) bundles the package via `tsup` into `dist/`.
166
+ - `pnpm dev` starts `tsup` in watch mode.
167
+ - `pnpm lint` and `pnpm check-types` keep code quality high.
168
+
169
+ Stories live under `src/demos/*.stories.jsx`; use them as references for new component states and to manually verify visual behavior.
170
+
171
+ ---
172
+
173
+ ## Roadmap / Known Gaps
174
+
175
+ - Radio buttons need dedicated “active” token colors (tracked via FIXME in `Radio.jsx`).
176
+ - Feedback icons currently reuse the `Close` glyph for all tones; alternative glyphs per tone may improve clarity.
177
+ - Clear button animations exist, but the components still rely on manually passing `value` for controlled usage—add tests to prevent regressions.
178
+
179
+ Contributions should follow the existing Tailwind utility conventions and rely on the shared tokens/utilities package to stay consistent with the broader system.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as react from 'react';
2
3
 
3
4
  declare function Label({ children, hint, description, ...props }: {
4
5
  children: React.ReactNode;
@@ -58,6 +59,20 @@ type InputProps = React.InputHTMLAttributes<HTMLInputElement> & {
58
59
 
59
60
  declare function Radio(): react_jsx_runtime.JSX.Element;
60
61
 
62
+ declare const Checkbox: react.ForwardRefExoticComponent<react.RefAttributes<any>>;
63
+
64
+ /**
65
+ * Switch component.
66
+ *
67
+ * Renders an accessible checkbox styled as a switch/toggle that plugs into the
68
+ * `Field` context so labeling and descriptions stay in sync.
69
+ *
70
+ * @typedef {React.InputHTMLAttributes<HTMLInputElement> & {
71
+ * variant?: "primary"|"success"|"corrective"|"error"|"inactive",
72
+ * }} SwitchProps
73
+ */
74
+ declare const Switch: react.ForwardRefExoticComponent<react.RefAttributes<any>>;
75
+
61
76
  /**
62
77
  * Props for the Textarea component.
63
78
  *
@@ -141,7 +156,7 @@ type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement> & {
141
156
  * @returns {React.ReactElement}
142
157
  */
143
158
  declare function Feedback({ id, tone, children, className, ...rest }: FeedbackProps): React.ReactElement;
144
- declare function Field({ children }: FieldProps): React.ReactElement;
159
+ declare function Field({ children, orientation, className, }: FieldProps): React.ReactElement;
145
160
  declare namespace Field {
146
161
  export { Feedback };
147
162
  }
@@ -184,4 +199,28 @@ type FieldProps = {
184
199
  children: React.ReactNode;
185
200
  };
186
201
 
187
- export { Feedback, Field, Input, Label, Radio, Textarea };
202
+ declare const Option: react.ForwardRefExoticComponent<react.RefAttributes<any>>;
203
+
204
+ /**
205
+ * Accessible Combobox built with the existing Input + Popover primitives.
206
+ *
207
+ * @typedef {Object} ComboboxProps
208
+ * @property {React.ReactNode} children - Collection of `<Option>` items.
209
+ * @property {string|string[]} [value] - controlled selected value(s)
210
+ * @property {string|string[]|null} [defaultValue=null] - uncontrolled default selection
211
+ * @property {(value: string|string[], option: { value: string, label: string }) => void} [onValueChange] - selection handler
212
+ * @property {string} [placeholder] - input placeholder text
213
+ * @property {boolean} [disabled=false] - disables the combobox
214
+ * @property {boolean} [autoComplete=false] - enables type-ahead filtering when true
215
+ * @property {boolean} [selectOnly=false] - hides freeform typing so users must pick from the list
216
+ * @property {boolean} [multiSelect=false] - allows selecting multiple options
217
+ * @property {string} [emptyState] - text shown when no results match
218
+ * @property {(option: { value: string, label: string }, query: string) => boolean} [filterFunction] - custom filter logic (used when filtering is active)
219
+ * @property {string} [wrapperClassName] - optional class for the outer wrapper
220
+ * @property {string} [contentClassName] - optional class for the popover content
221
+ * @property {string} [listClassName] - optional class for the list element
222
+ * @property {string} [optionClassName] - optional class for each option
223
+ */
224
+ declare const ComboboxRoot: react.ForwardRefExoticComponent<react.RefAttributes<any>>;
225
+
226
+ export { Checkbox, ComboboxRoot as Combobox, Option as ComboboxOption, Feedback, Field, Input, Label, Option, Radio, Switch, Textarea };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as react from 'react';
2
3
 
3
4
  declare function Label({ children, hint, description, ...props }: {
4
5
  children: React.ReactNode;
@@ -58,6 +59,20 @@ type InputProps = React.InputHTMLAttributes<HTMLInputElement> & {
58
59
 
59
60
  declare function Radio(): react_jsx_runtime.JSX.Element;
60
61
 
62
+ declare const Checkbox: react.ForwardRefExoticComponent<react.RefAttributes<any>>;
63
+
64
+ /**
65
+ * Switch component.
66
+ *
67
+ * Renders an accessible checkbox styled as a switch/toggle that plugs into the
68
+ * `Field` context so labeling and descriptions stay in sync.
69
+ *
70
+ * @typedef {React.InputHTMLAttributes<HTMLInputElement> & {
71
+ * variant?: "primary"|"success"|"corrective"|"error"|"inactive",
72
+ * }} SwitchProps
73
+ */
74
+ declare const Switch: react.ForwardRefExoticComponent<react.RefAttributes<any>>;
75
+
61
76
  /**
62
77
  * Props for the Textarea component.
63
78
  *
@@ -141,7 +156,7 @@ type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement> & {
141
156
  * @returns {React.ReactElement}
142
157
  */
143
158
  declare function Feedback({ id, tone, children, className, ...rest }: FeedbackProps): React.ReactElement;
144
- declare function Field({ children }: FieldProps): React.ReactElement;
159
+ declare function Field({ children, orientation, className, }: FieldProps): React.ReactElement;
145
160
  declare namespace Field {
146
161
  export { Feedback };
147
162
  }
@@ -184,4 +199,28 @@ type FieldProps = {
184
199
  children: React.ReactNode;
185
200
  };
186
201
 
187
- export { Feedback, Field, Input, Label, Radio, Textarea };
202
+ declare const Option: react.ForwardRefExoticComponent<react.RefAttributes<any>>;
203
+
204
+ /**
205
+ * Accessible Combobox built with the existing Input + Popover primitives.
206
+ *
207
+ * @typedef {Object} ComboboxProps
208
+ * @property {React.ReactNode} children - Collection of `<Option>` items.
209
+ * @property {string|string[]} [value] - controlled selected value(s)
210
+ * @property {string|string[]|null} [defaultValue=null] - uncontrolled default selection
211
+ * @property {(value: string|string[], option: { value: string, label: string }) => void} [onValueChange] - selection handler
212
+ * @property {string} [placeholder] - input placeholder text
213
+ * @property {boolean} [disabled=false] - disables the combobox
214
+ * @property {boolean} [autoComplete=false] - enables type-ahead filtering when true
215
+ * @property {boolean} [selectOnly=false] - hides freeform typing so users must pick from the list
216
+ * @property {boolean} [multiSelect=false] - allows selecting multiple options
217
+ * @property {string} [emptyState] - text shown when no results match
218
+ * @property {(option: { value: string, label: string }, query: string) => boolean} [filterFunction] - custom filter logic (used when filtering is active)
219
+ * @property {string} [wrapperClassName] - optional class for the outer wrapper
220
+ * @property {string} [contentClassName] - optional class for the popover content
221
+ * @property {string} [listClassName] - optional class for the list element
222
+ * @property {string} [optionClassName] - optional class for each option
223
+ */
224
+ declare const ComboboxRoot: react.ForwardRefExoticComponent<react.RefAttributes<any>>;
225
+
226
+ export { Checkbox, ComboboxRoot as Combobox, Option as ComboboxOption, Feedback, Field, Input, Label, Option, Radio, Switch, Textarea };