@fpkit/acss 3.7.0 → 3.9.0
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/libs/components/form/checkbox.css +1 -0
- package/libs/components/form/checkbox.css.map +1 -0
- package/libs/components/form/checkbox.min.css +3 -0
- package/libs/components/form/form.css +1 -1
- package/libs/components/form/form.css.map +1 -1
- package/libs/components/form/form.min.css +2 -2
- package/libs/index.cjs +26 -25
- package/libs/index.cjs.map +1 -1
- package/libs/index.css +1 -1
- package/libs/index.css.map +1 -1
- package/libs/index.d.cts +207 -2
- package/libs/index.d.ts +207 -2
- package/libs/index.js +5 -4
- package/libs/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/form/README.mdx +173 -146
- package/src/components/form/checkbox.scss +129 -0
- package/src/components/form/checkbox.tsx +302 -0
- package/src/components/form/form.scss +59 -20
- package/src/components/form/form.types.ts +6 -0
- package/src/components/form/input.stories.tsx +258 -1
- package/src/index.scss +1 -0
- package/src/index.ts +13 -1
- package/src/sass/_columns.scss +13 -9
- package/src/styles/checkbox/checkbox.css.map +1 -0
- package/src/styles/form/checkbox.css +97 -0
- package/src/styles/form/checkbox.css.map +1 -0
- package/src/styles/form/form.css +138 -22
- package/src/styles/form/form.css.map +1 -1
- package/src/styles/index.css +138 -22
- package/src/styles/index.css.map +1 -1
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@fpkit/acss",
|
|
3
3
|
"description": "A lightweight React UI library for building modern and accessible components that leverage CSS custom properties for reactive Styles.",
|
|
4
4
|
"private": false,
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.9.0",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=22.12.0",
|
|
8
8
|
"npm": ">=8.0.0"
|
|
@@ -126,5 +126,5 @@
|
|
|
126
126
|
"publishConfig": {
|
|
127
127
|
"access": "public"
|
|
128
128
|
},
|
|
129
|
-
"gitHead": "
|
|
129
|
+
"gitHead": "87b188279548219c8d93538b58504077aef71a71"
|
|
130
130
|
}
|
|
@@ -1,22 +1,27 @@
|
|
|
1
|
-
import { Meta } from "@storybook/addon-docs/blocks";
|
|
2
|
-
|
|
3
1
|
<Meta title="FP.REACT Forms/Form/Readme" />
|
|
4
2
|
|
|
5
3
|
# Form Components
|
|
6
4
|
|
|
7
|
-
A comprehensive set of accessible React form components built with TypeScript,
|
|
5
|
+
A comprehensive set of accessible React form components built with TypeScript,
|
|
6
|
+
designed for building robust, WCAG 2.1 AA compliant forms with proper
|
|
7
|
+
validation, error handling, and status management.
|
|
8
8
|
|
|
9
|
-
> 💡 **Interactive Examples**: See the
|
|
9
|
+
> 💡 **Interactive Examples**: See the
|
|
10
|
+
> [Form stories](./?path=/docs/fp-react-forms-form--docs) for live, interactive
|
|
11
|
+
> examples with automated accessibility testing.
|
|
10
12
|
|
|
11
13
|
## Features
|
|
12
14
|
|
|
13
|
-
- ✅ **WCAG 2.1 AA Compliant** - Full accessibility support with proper ARIA
|
|
14
|
-
|
|
15
|
+
- ✅ **WCAG 2.1 AA Compliant** - Full accessibility support with proper ARIA
|
|
16
|
+
attributes
|
|
17
|
+
- ✅ **Compound Component Pattern** - Intuitive API with Form.Field, Form.Input,
|
|
18
|
+
etc.
|
|
15
19
|
- ✅ **TypeScript First** - Full type safety with comprehensive interfaces
|
|
16
20
|
- ✅ **Status Management** - Built-in loading states and submission tracking
|
|
17
21
|
- ✅ **Validation Support** - Client-side and server-side validation patterns
|
|
18
22
|
- ✅ **Flexible** - Supports both controlled and uncontrolled form patterns
|
|
19
|
-
- ✅ **Keyboard Navigation** - Full keyboard accessibility including Enter key
|
|
23
|
+
- ✅ **Keyboard Navigation** - Full keyboard accessibility including Enter key
|
|
24
|
+
handlers
|
|
20
25
|
|
|
21
26
|
---
|
|
22
27
|
|
|
@@ -24,22 +29,22 @@ A comprehensive set of accessible React form components built with TypeScript, d
|
|
|
24
29
|
|
|
25
30
|
### Core Components
|
|
26
31
|
|
|
27
|
-
| Component
|
|
28
|
-
|
|
29
|
-
| **Form**
|
|
30
|
-
| **Form.Field**
|
|
31
|
-
| **Form.Input**
|
|
32
|
-
| **Form.Textarea** | Multi-line text input
|
|
33
|
-
| **Form.Select**
|
|
32
|
+
| Component | Purpose | Key Props |
|
|
33
|
+
| ----------------- | --------------------------------------- | ------------------------------------------- |
|
|
34
|
+
| **Form** | Form wrapper with submission handling | `onSubmit`, `status`, `noValidate` |
|
|
35
|
+
| **Form.Field** | Label + input wrapper for accessibility | `label`, `labelFor`, `required`, `optional` |
|
|
36
|
+
| **Form.Input** | Text input with validation | `type`, `validationState`, `onEnter` |
|
|
37
|
+
| **Form.Textarea** | Multi-line text input | `rows`, `cols`, `onEnter` |
|
|
38
|
+
| **Form.Select** | Dropdown select with options | `onSelectionChange`, `onEnter` |
|
|
34
39
|
|
|
35
40
|
---
|
|
36
41
|
|
|
37
42
|
## Installation & Import
|
|
38
43
|
|
|
39
44
|
```tsx
|
|
40
|
-
import Form from
|
|
45
|
+
import Form from "@fpkit/acss";
|
|
41
46
|
// Or import specific components
|
|
42
|
-
import { Form, Input, Field } from
|
|
47
|
+
import { Form, Input, Field } from "@fpkit/acss";
|
|
43
48
|
```
|
|
44
49
|
|
|
45
50
|
---
|
|
@@ -48,10 +53,11 @@ import { Form, Input, Field } from '@fpkit/acss';
|
|
|
48
53
|
|
|
49
54
|
### Simple Contact Form
|
|
50
55
|
|
|
51
|
-
A basic form with required fields, proper label associations, and submission
|
|
56
|
+
A basic form with required fields, proper label associations, and submission
|
|
57
|
+
handling.
|
|
52
58
|
|
|
53
59
|
```tsx
|
|
54
|
-
import Form from
|
|
60
|
+
import Form from "@fpkit/acss";
|
|
55
61
|
|
|
56
62
|
function ContactForm() {
|
|
57
63
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
|
@@ -85,33 +91,34 @@ function ContactForm() {
|
|
|
85
91
|
|
|
86
92
|
### Form with Status Management
|
|
87
93
|
|
|
88
|
-
Use the `status` prop to manage form submission states. This example shows how
|
|
94
|
+
Use the `status` prop to manage form submission states. This example shows how
|
|
95
|
+
the form automatically sets `aria-busy` and disables fields during submission.
|
|
89
96
|
|
|
90
97
|
```tsx
|
|
91
|
-
import { useState } from
|
|
92
|
-
import Form, { FormStatus } from
|
|
98
|
+
import { useState } from "react";
|
|
99
|
+
import Form, { FormStatus } from "@fpkit/acss";
|
|
93
100
|
|
|
94
101
|
function RegistrationForm() {
|
|
95
|
-
const [status, setStatus] = useState<FormStatus>(
|
|
102
|
+
const [status, setStatus] = useState<FormStatus>("idle");
|
|
96
103
|
|
|
97
104
|
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
|
98
105
|
e.preventDefault();
|
|
99
106
|
|
|
100
|
-
setStatus(
|
|
107
|
+
setStatus("submitting");
|
|
101
108
|
|
|
102
109
|
try {
|
|
103
110
|
const formData = new FormData(e.currentTarget);
|
|
104
|
-
await fetch(
|
|
105
|
-
method:
|
|
111
|
+
await fetch("/api/register", {
|
|
112
|
+
method: "POST",
|
|
106
113
|
body: formData,
|
|
107
114
|
});
|
|
108
|
-
setStatus(
|
|
115
|
+
setStatus("success");
|
|
109
116
|
} catch (error) {
|
|
110
|
-
setStatus(
|
|
117
|
+
setStatus("error");
|
|
111
118
|
}
|
|
112
119
|
};
|
|
113
120
|
|
|
114
|
-
const isSubmitting = status ===
|
|
121
|
+
const isSubmitting = status === "submitting";
|
|
115
122
|
|
|
116
123
|
return (
|
|
117
124
|
<Form
|
|
@@ -140,11 +147,11 @@ function RegistrationForm() {
|
|
|
140
147
|
</Form.Field>
|
|
141
148
|
|
|
142
149
|
<button type="submit" disabled={isSubmitting}>
|
|
143
|
-
{isSubmitting ?
|
|
150
|
+
{isSubmitting ? "Submitting..." : "Create Account"}
|
|
144
151
|
</button>
|
|
145
152
|
|
|
146
|
-
{status ===
|
|
147
|
-
{status ===
|
|
153
|
+
{status === "success" && <p>Account created successfully!</p>}
|
|
154
|
+
{status === "error" && <p>Error creating account. Please try again.</p>}
|
|
148
155
|
</Form>
|
|
149
156
|
);
|
|
150
157
|
}
|
|
@@ -152,23 +159,24 @@ function RegistrationForm() {
|
|
|
152
159
|
|
|
153
160
|
### Form with Validation
|
|
154
161
|
|
|
155
|
-
Implement client-side validation with error messages. Notice how the input uses
|
|
162
|
+
Implement client-side validation with error messages. Notice how the input uses
|
|
163
|
+
`validationState` to show visual feedback and `aria-invalid` for screen readers.
|
|
156
164
|
|
|
157
165
|
```tsx
|
|
158
|
-
import { useState } from
|
|
159
|
-
import Form from
|
|
166
|
+
import { useState } from "react";
|
|
167
|
+
import Form from "@fpkit/acss";
|
|
160
168
|
|
|
161
169
|
function ValidatedForm() {
|
|
162
|
-
const [email, setEmail] = useState(
|
|
163
|
-
const [emailError, setEmailError] = useState(
|
|
170
|
+
const [email, setEmail] = useState("");
|
|
171
|
+
const [emailError, setEmailError] = useState("");
|
|
164
172
|
|
|
165
173
|
const validateEmail = (value: string) => {
|
|
166
174
|
if (!value) {
|
|
167
|
-
setEmailError(
|
|
175
|
+
setEmailError("Email is required");
|
|
168
176
|
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
|
|
169
|
-
setEmailError(
|
|
177
|
+
setEmailError("Please enter a valid email address");
|
|
170
178
|
} else {
|
|
171
|
-
setEmailError(
|
|
179
|
+
setEmailError("");
|
|
172
180
|
}
|
|
173
181
|
};
|
|
174
182
|
|
|
@@ -194,7 +202,7 @@ function ValidatedForm() {
|
|
|
194
202
|
value={email}
|
|
195
203
|
onChange={(e) => setEmail(e.target.value)}
|
|
196
204
|
onBlur={(e) => validateEmail(e.target.value)}
|
|
197
|
-
validationState={emailError ?
|
|
205
|
+
validationState={emailError ? "invalid" : email ? "valid" : "none"}
|
|
198
206
|
required
|
|
199
207
|
/>
|
|
200
208
|
</Form.Field>
|
|
@@ -207,17 +215,18 @@ function ValidatedForm() {
|
|
|
207
215
|
|
|
208
216
|
### Keyboard-Driven Workflows
|
|
209
217
|
|
|
210
|
-
Use the `onEnter` prop for keyboard-friendly interactions. This example
|
|
218
|
+
Use the `onEnter` prop for keyboard-friendly interactions. This example
|
|
219
|
+
demonstrates `onEnter` on inputs, textareas, and selects.
|
|
211
220
|
|
|
212
221
|
```tsx
|
|
213
|
-
import { useState } from
|
|
214
|
-
import Form from
|
|
222
|
+
import { useState } from "react";
|
|
223
|
+
import Form from "@fpkit/acss";
|
|
215
224
|
|
|
216
225
|
function SearchForm() {
|
|
217
|
-
const [query, setQuery] = useState(
|
|
226
|
+
const [query, setQuery] = useState("");
|
|
218
227
|
|
|
219
228
|
const handleSearch = () => {
|
|
220
|
-
console.log(
|
|
229
|
+
console.log("Searching for:", query);
|
|
221
230
|
// Perform search
|
|
222
231
|
};
|
|
223
232
|
|
|
@@ -268,23 +277,29 @@ function ServerSideForm() {
|
|
|
268
277
|
|
|
269
278
|
## Disabled State
|
|
270
279
|
|
|
271
|
-
Form components support an accessible disabled state using the `aria-disabled`
|
|
280
|
+
Form components support an accessible disabled state using the `aria-disabled`
|
|
281
|
+
pattern, which provides better accessibility than the native HTML `disabled`
|
|
282
|
+
attribute.
|
|
272
283
|
|
|
273
284
|
### Why aria-disabled?
|
|
274
285
|
|
|
275
286
|
The `aria-disabled` pattern offers key advantages:
|
|
276
287
|
|
|
277
|
-
- **Keyboard Accessibility**: Disabled elements remain in the tab order,
|
|
278
|
-
|
|
279
|
-
- **
|
|
280
|
-
|
|
288
|
+
- **Keyboard Accessibility**: Disabled elements remain in the tab order,
|
|
289
|
+
allowing keyboard users to discover them
|
|
290
|
+
- **Screen Reader Discovery**: Screen readers can announce the disabled state
|
|
291
|
+
and read associated tooltips or help text
|
|
292
|
+
- **WCAG Compliance**: Meets WCAG 2.1.1 (Keyboard) and 4.1.2 (Name, Role, Value)
|
|
293
|
+
requirements
|
|
294
|
+
- **Interactive Help**: Enables tooltips or contextual help on disabled elements
|
|
295
|
+
to explain why they're disabled
|
|
281
296
|
|
|
282
297
|
### Basic Usage
|
|
283
298
|
|
|
284
299
|
Use the `disabled` prop to disable form inputs, textareas, and selects:
|
|
285
300
|
|
|
286
301
|
```tsx
|
|
287
|
-
import Form from
|
|
302
|
+
import Form from "@fpkit/acss";
|
|
288
303
|
|
|
289
304
|
function DisabledInputExample() {
|
|
290
305
|
return (
|
|
@@ -300,11 +315,7 @@ function DisabledInputExample() {
|
|
|
300
315
|
</Form.Field>
|
|
301
316
|
|
|
302
317
|
<Form.Field label="Comments" labelFor="comments">
|
|
303
|
-
<Form.Textarea
|
|
304
|
-
id="comments"
|
|
305
|
-
name="comments"
|
|
306
|
-
disabled={true}
|
|
307
|
-
/>
|
|
318
|
+
<Form.Textarea id="comments" name="comments" disabled={true} />
|
|
308
319
|
</Form.Field>
|
|
309
320
|
|
|
310
321
|
<Form.Field label="Country" labelFor="country">
|
|
@@ -320,48 +331,52 @@ function DisabledInputExample() {
|
|
|
320
331
|
|
|
321
332
|
### Migration from isDisabled
|
|
322
333
|
|
|
323
|
-
For backward compatibility, the deprecated `isDisabled` prop is still supported
|
|
334
|
+
For backward compatibility, the deprecated `isDisabled` prop is still supported
|
|
335
|
+
but will be removed in a future version.
|
|
324
336
|
|
|
325
337
|
**Before (deprecated):**
|
|
338
|
+
|
|
326
339
|
```tsx
|
|
327
340
|
<Form.Input isDisabled={true} />
|
|
328
341
|
```
|
|
329
342
|
|
|
330
343
|
**After (recommended):**
|
|
344
|
+
|
|
331
345
|
```tsx
|
|
332
346
|
<Form.Input disabled={true} />
|
|
333
347
|
```
|
|
334
348
|
|
|
335
|
-
Both props work identically, but `disabled` follows standard HTML conventions
|
|
349
|
+
Both props work identically, but `disabled` follows standard HTML conventions
|
|
350
|
+
and should be used for all new code.
|
|
336
351
|
|
|
337
352
|
### Disabled State During Form Submission
|
|
338
353
|
|
|
339
354
|
A common pattern is disabling all form fields while a form is submitting:
|
|
340
355
|
|
|
341
356
|
```tsx
|
|
342
|
-
import { useState } from
|
|
343
|
-
import Form, { FormStatus } from
|
|
357
|
+
import { useState } from "react";
|
|
358
|
+
import Form, { FormStatus } from "@fpkit/acss";
|
|
344
359
|
|
|
345
360
|
function SubmitDisabledForm() {
|
|
346
|
-
const [status, setStatus] = useState<FormStatus>(
|
|
361
|
+
const [status, setStatus] = useState<FormStatus>("idle");
|
|
347
362
|
|
|
348
363
|
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
|
349
364
|
e.preventDefault();
|
|
350
|
-
setStatus(
|
|
365
|
+
setStatus("submitting");
|
|
351
366
|
|
|
352
367
|
try {
|
|
353
368
|
const formData = new FormData(e.currentTarget);
|
|
354
|
-
await fetch(
|
|
355
|
-
method:
|
|
369
|
+
await fetch("/api/save", {
|
|
370
|
+
method: "POST",
|
|
356
371
|
body: formData,
|
|
357
372
|
});
|
|
358
|
-
setStatus(
|
|
373
|
+
setStatus("success");
|
|
359
374
|
} catch (error) {
|
|
360
|
-
setStatus(
|
|
375
|
+
setStatus("error");
|
|
361
376
|
}
|
|
362
377
|
};
|
|
363
378
|
|
|
364
|
-
const isSubmitting = status ===
|
|
379
|
+
const isSubmitting = status === "submitting";
|
|
365
380
|
|
|
366
381
|
return (
|
|
367
382
|
<Form status={status} onSubmit={handleSubmit}>
|
|
@@ -375,15 +390,11 @@ function SubmitDisabledForm() {
|
|
|
375
390
|
</Form.Field>
|
|
376
391
|
|
|
377
392
|
<Form.Field label="Bio" labelFor="bio">
|
|
378
|
-
<Form.Textarea
|
|
379
|
-
id="bio"
|
|
380
|
-
name="bio"
|
|
381
|
-
disabled={isSubmitting}
|
|
382
|
-
/>
|
|
393
|
+
<Form.Textarea id="bio" name="bio" disabled={isSubmitting} />
|
|
383
394
|
</Form.Field>
|
|
384
395
|
|
|
385
396
|
<button type="submit" disabled={isSubmitting}>
|
|
386
|
-
{isSubmitting ?
|
|
397
|
+
{isSubmitting ? "Saving..." : "Save Profile"}
|
|
387
398
|
</button>
|
|
388
399
|
</Form>
|
|
389
400
|
);
|
|
@@ -394,10 +405,14 @@ function SubmitDisabledForm() {
|
|
|
394
405
|
|
|
395
406
|
When a form control is disabled:
|
|
396
407
|
|
|
397
|
-
- **Keyboard Navigation**: Element remains focusable via Tab key (maintains tab
|
|
398
|
-
|
|
399
|
-
- **
|
|
400
|
-
|
|
408
|
+
- **Keyboard Navigation**: Element remains focusable via Tab key (maintains tab
|
|
409
|
+
order)
|
|
410
|
+
- **Interaction Prevention**: All interaction events are prevented (click,
|
|
411
|
+
change, keydown, etc.)
|
|
412
|
+
- **Focus Events**: Focus events still work, allowing screen readers to discover
|
|
413
|
+
and announce the element
|
|
414
|
+
- **Visual Styling**: `.is-disabled` class and `aria-disabled="true"` attribute
|
|
415
|
+
are applied
|
|
401
416
|
- **Screen Readers**: Announce "disabled" state when focused
|
|
402
417
|
|
|
403
418
|
### Styling
|
|
@@ -406,9 +421,9 @@ Disabled elements can be styled using CSS custom properties:
|
|
|
406
421
|
|
|
407
422
|
```css
|
|
408
423
|
:root {
|
|
409
|
-
--disabled-opacity: 0.6;
|
|
410
|
-
--disabled-cursor: not-allowed;
|
|
411
|
-
--disabled-color: hsl(0 0% 40%);
|
|
424
|
+
--disabled-opacity: 0.6; /* Visual opacity for disabled state */
|
|
425
|
+
--disabled-cursor: not-allowed; /* Cursor style */
|
|
426
|
+
--disabled-color: hsl(0 0% 40%); /* Text color (3:1 contrast minimum) */
|
|
412
427
|
}
|
|
413
428
|
```
|
|
414
429
|
|
|
@@ -418,13 +433,14 @@ Override these properties for custom styling:
|
|
|
418
433
|
<Form.Input
|
|
419
434
|
disabled={true}
|
|
420
435
|
styles={{
|
|
421
|
-
|
|
422
|
-
|
|
436
|
+
"--disabled-opacity": "0.5",
|
|
437
|
+
"--disabled-color": "#666666",
|
|
423
438
|
}}
|
|
424
439
|
/>
|
|
425
440
|
```
|
|
426
441
|
|
|
427
442
|
**Selectors Available:**
|
|
443
|
+
|
|
428
444
|
- `.is-disabled` - Class added to disabled elements
|
|
429
445
|
- `[aria-disabled="true"]` - Attribute selector
|
|
430
446
|
|
|
@@ -433,9 +449,11 @@ Override these properties for custom styling:
|
|
|
433
449
|
The `aria-disabled` pattern ensures compliance with:
|
|
434
450
|
|
|
435
451
|
- **WCAG 2.1.1 (Keyboard)**: Elements remain in keyboard tab order for discovery
|
|
436
|
-
- **WCAG 4.1.2 (Name, Role, Value)**: `aria-disabled` announces state to screen
|
|
452
|
+
- **WCAG 4.1.2 (Name, Role, Value)**: `aria-disabled` announces state to screen
|
|
453
|
+
readers
|
|
437
454
|
- **WCAG 1.4.3 (Contrast Minimum)**: Disabled text maintains 3:1 contrast ratio
|
|
438
|
-
- **WCAG 2.4.7 (Focus Visible)**: Focus indicators preserved on disabled
|
|
455
|
+
- **WCAG 2.4.7 (Focus Visible)**: Focus indicators preserved on disabled
|
|
456
|
+
elements
|
|
439
457
|
|
|
440
458
|
---
|
|
441
459
|
|
|
@@ -447,18 +465,18 @@ The main form wrapper component.
|
|
|
447
465
|
|
|
448
466
|
#### Props
|
|
449
467
|
|
|
450
|
-
| Prop
|
|
451
|
-
|
|
452
|
-
| `onSubmit`
|
|
453
|
-
| `status`
|
|
454
|
-
| `action`
|
|
455
|
-
| `formMethod` | `'get' \| 'post'`
|
|
456
|
-
| `noValidate` | `boolean`
|
|
457
|
-
| `target`
|
|
458
|
-
| `id`
|
|
459
|
-
| `name`
|
|
460
|
-
| `classes`
|
|
461
|
-
| `styles`
|
|
468
|
+
| Prop | Type | Default | Description |
|
|
469
|
+
| ------------ | ------------------------------------------------ | -------- | ------------------------------------------ |
|
|
470
|
+
| `onSubmit` | `(event: FormEvent) => void` | - | Form submission handler (prevents default) |
|
|
471
|
+
| `status` | `'idle' \| 'submitting' \| 'success' \| 'error'` | `'idle'` | Current form status |
|
|
472
|
+
| `action` | `string` | - | Form submission URL |
|
|
473
|
+
| `formMethod` | `'get' \| 'post'` | `'post'` | HTTP method |
|
|
474
|
+
| `noValidate` | `boolean` | `false` | Disable HTML5 validation |
|
|
475
|
+
| `target` | `string` | - | Form submission target |
|
|
476
|
+
| `id` | `string` | - | Unique form identifier |
|
|
477
|
+
| `name` | `string` | - | Form name attribute |
|
|
478
|
+
| `classes` | `string` | - | CSS class names |
|
|
479
|
+
| `styles` | `CSSProperties` | - | Inline styles |
|
|
462
480
|
|
|
463
481
|
#### Accessibility
|
|
464
482
|
|
|
@@ -472,14 +490,14 @@ Wrapper component that associates labels with inputs for accessibility.
|
|
|
472
490
|
|
|
473
491
|
#### Props
|
|
474
492
|
|
|
475
|
-
| Prop
|
|
476
|
-
|
|
477
|
-
| `label`
|
|
478
|
-
| `labelFor`
|
|
479
|
-
| `required`
|
|
480
|
-
| `optional`
|
|
481
|
-
| `errorMessage` | `string`
|
|
482
|
-
| `hintText`
|
|
493
|
+
| Prop | Type | Default | Description |
|
|
494
|
+
| -------------- | ----------- | ------- | -------------------------------------- |
|
|
495
|
+
| `label` | `ReactNode` | - | **Required.** Label text or element |
|
|
496
|
+
| `labelFor` | `string` | - | ID of associated input (for `htmlFor`) |
|
|
497
|
+
| `required` | `boolean` | `false` | Show required indicator (\*) |
|
|
498
|
+
| `optional` | `boolean` | `false` | Show optional indicator |
|
|
499
|
+
| `errorMessage` | `string` | - | Error message to display |
|
|
500
|
+
| `hintText` | `string` | - | Helper text below input |
|
|
483
501
|
|
|
484
502
|
### Form.Input
|
|
485
503
|
|
|
@@ -487,17 +505,17 @@ Text input component with validation support.
|
|
|
487
505
|
|
|
488
506
|
#### Props
|
|
489
507
|
|
|
490
|
-
| Prop
|
|
491
|
-
|
|
492
|
-
| `type`
|
|
493
|
-
| `validationState` | `'none' \| 'valid' \| 'invalid'` | `'none'` | Validation state
|
|
494
|
-
| `errorMessage`
|
|
495
|
-
| `hintText`
|
|
496
|
-
| `onEnter`
|
|
497
|
-
| `disabled`
|
|
498
|
-
| `isDisabled`
|
|
499
|
-
| `readOnly`
|
|
500
|
-
| `required`
|
|
508
|
+
| Prop | Type | Default | Description |
|
|
509
|
+
| ----------------- | -------------------------------- | -------- | --------------------------------------------------------------- |
|
|
510
|
+
| `type` | `string` | `'text'` | Input type (text, email, password, etc.) |
|
|
511
|
+
| `validationState` | `'none' \| 'valid' \| 'invalid'` | `'none'` | Validation state |
|
|
512
|
+
| `errorMessage` | `string` | - | Error message for `aria-describedby` |
|
|
513
|
+
| `hintText` | `string` | - | Hint text for `aria-describedby` |
|
|
514
|
+
| `onEnter` | `(event: KeyboardEvent) => void` | - | Handler for Enter key press |
|
|
515
|
+
| `disabled` | `boolean` | `false` | Disable input using `aria-disabled` pattern (remains focusable) |
|
|
516
|
+
| `isDisabled` | `boolean` | `false` | **Deprecated.** Use `disabled` instead |
|
|
517
|
+
| `readOnly` | `boolean` | `false` | Make input read-only |
|
|
518
|
+
| `required` | `boolean` | `false` | Mark input as required |
|
|
501
519
|
|
|
502
520
|
### Form.Textarea
|
|
503
521
|
|
|
@@ -505,13 +523,13 @@ Multi-line text input component.
|
|
|
505
523
|
|
|
506
524
|
#### Props
|
|
507
525
|
|
|
508
|
-
| Prop
|
|
509
|
-
|
|
510
|
-
| `rows`
|
|
511
|
-
| `cols`
|
|
512
|
-
| `onEnter`
|
|
513
|
-
| `disabled`
|
|
514
|
-
| `isDisabled` | `boolean`
|
|
526
|
+
| Prop | Type | Default | Description |
|
|
527
|
+
| ------------ | -------------------------------- | ------- | ------------------------------------------------------------------ |
|
|
528
|
+
| `rows` | `number` | `5` | Number of visible rows |
|
|
529
|
+
| `cols` | `number` | `25` | Number of visible columns |
|
|
530
|
+
| `onEnter` | `(event: KeyboardEvent) => void` | - | Handler for Enter (without Shift) |
|
|
531
|
+
| `disabled` | `boolean` | `false` | Disable textarea using `aria-disabled` pattern (remains focusable) |
|
|
532
|
+
| `isDisabled` | `boolean` | `false` | **Deprecated.** Use `disabled` instead |
|
|
515
533
|
|
|
516
534
|
**Note**: Shift+Enter adds a new line without triggering `onEnter`.
|
|
517
535
|
|
|
@@ -521,13 +539,13 @@ Dropdown select component with keyboard support.
|
|
|
521
539
|
|
|
522
540
|
#### Props
|
|
523
541
|
|
|
524
|
-
| Prop
|
|
525
|
-
|
|
526
|
-
| `onSelectionChange` | `(event: ChangeEvent) => void`
|
|
527
|
-
| `onEnter`
|
|
528
|
-
| `required`
|
|
529
|
-
| `disabled`
|
|
530
|
-
| `isDisabled`
|
|
542
|
+
| Prop | Type | Default | Description |
|
|
543
|
+
| ------------------- | -------------------------------- | ------- | ---------------------------------------------------------------- |
|
|
544
|
+
| `onSelectionChange` | `(event: ChangeEvent) => void` | - | Selection change handler |
|
|
545
|
+
| `onEnter` | `(event: KeyboardEvent) => void` | - | Handler for Enter key press |
|
|
546
|
+
| `required` | `boolean` | `false` | Mark select as required |
|
|
547
|
+
| `disabled` | `boolean` | `false` | Disable select using `aria-disabled` pattern (remains focusable) |
|
|
548
|
+
| `isDisabled` | `boolean` | `false` | **Deprecated.** Use `disabled` instead |
|
|
531
549
|
|
|
532
550
|
---
|
|
533
551
|
|
|
@@ -538,21 +556,26 @@ Dropdown select component with keyboard support.
|
|
|
538
556
|
All form components meet WCAG 2.1 Level AA standards:
|
|
539
557
|
|
|
540
558
|
1. **WCAG 3.3.1 Error Identification** ✅
|
|
559
|
+
|
|
541
560
|
- Error messages clearly associated with inputs via `aria-describedby`
|
|
542
561
|
- Validation states communicated via `aria-invalid`
|
|
543
562
|
|
|
544
563
|
2. **WCAG 3.3.2 Labels or Instructions** ✅
|
|
564
|
+
|
|
545
565
|
- All inputs have associated labels via `<label htmlFor>`
|
|
546
566
|
- Required fields marked with visual and programmatic indicators
|
|
547
567
|
|
|
548
568
|
3. **WCAG 4.1.2 Name, Role, Value** ✅
|
|
569
|
+
|
|
549
570
|
- All inputs have proper `role`, `name`, and `aria-` attributes
|
|
550
571
|
- Form status communicated via `aria-busy`
|
|
551
572
|
|
|
552
573
|
4. **WCAG 2.4.7 Focus Visible** ✅
|
|
574
|
+
|
|
553
575
|
- Focus indicators styled via `:focus-visible` in SCSS
|
|
554
576
|
|
|
555
577
|
5. **WCAG 2.1.1 Keyboard** ✅
|
|
578
|
+
|
|
556
579
|
- All interactive elements keyboard accessible
|
|
557
580
|
- `onEnter` prop for custom keyboard workflows
|
|
558
581
|
|
|
@@ -640,20 +663,20 @@ input[data-validation="valid"] {
|
|
|
640
663
|
All components are fully typed with comprehensive interfaces:
|
|
641
664
|
|
|
642
665
|
```tsx
|
|
643
|
-
import Form, {
|
|
644
|
-
FormProps,
|
|
645
|
-
FormStatus,
|
|
646
|
-
InputProps
|
|
647
|
-
} from '@fpkit/acss';
|
|
666
|
+
import Form, { FormProps, FormStatus, InputProps } from "@fpkit/acss";
|
|
648
667
|
|
|
649
668
|
const MyForm: React.FC = () => {
|
|
650
|
-
const [status, setStatus] = useState<FormStatus>(
|
|
669
|
+
const [status, setStatus] = useState<FormStatus>("idle");
|
|
651
670
|
|
|
652
|
-
const handleSubmit: FormProps[
|
|
671
|
+
const handleSubmit: FormProps["onSubmit"] = (e) => {
|
|
653
672
|
// Fully typed event
|
|
654
673
|
};
|
|
655
674
|
|
|
656
|
-
return
|
|
675
|
+
return (
|
|
676
|
+
<Form status={status} onSubmit={handleSubmit}>
|
|
677
|
+
...
|
|
678
|
+
</Form>
|
|
679
|
+
);
|
|
657
680
|
};
|
|
658
681
|
```
|
|
659
682
|
|
|
@@ -664,10 +687,10 @@ const MyForm: React.FC = () => {
|
|
|
664
687
|
Form components are fully tested with Vitest and React Testing Library:
|
|
665
688
|
|
|
666
689
|
```tsx
|
|
667
|
-
import { render, screen, userEvent } from
|
|
668
|
-
import Form from
|
|
690
|
+
import { render, screen, userEvent } from "@testing-library/react";
|
|
691
|
+
import Form from "@fpkit/acss";
|
|
669
692
|
|
|
670
|
-
test(
|
|
693
|
+
test("submits form data", async () => {
|
|
671
694
|
const handleSubmit = vi.fn();
|
|
672
695
|
const user = userEvent.setup();
|
|
673
696
|
|
|
@@ -678,8 +701,8 @@ test('submits form data', async () => {
|
|
|
678
701
|
</Form>
|
|
679
702
|
);
|
|
680
703
|
|
|
681
|
-
await user.type(screen.getByRole(
|
|
682
|
-
await user.click(screen.getByRole(
|
|
704
|
+
await user.type(screen.getByRole("textbox"), "test@example.com");
|
|
705
|
+
await user.click(screen.getByRole("button"));
|
|
683
706
|
|
|
684
707
|
expect(handleSubmit).toHaveBeenCalled();
|
|
685
708
|
});
|
|
@@ -711,11 +734,13 @@ test('submits form data', async () => {
|
|
|
711
734
|
|
|
712
735
|
## More Examples
|
|
713
736
|
|
|
714
|
-
See the [Form Interactive Guide](./?path=/docs/fp-react-forms-form--docs) for
|
|
737
|
+
See the [Form Interactive Guide](./?path=/docs/fp-react-forms-form--docs) for
|
|
738
|
+
complete examples including:
|
|
715
739
|
|
|
716
740
|
### Form with Hint Text
|
|
717
741
|
|
|
718
|
-
Guide users with helpful hint text below fields, properly associated using
|
|
742
|
+
Guide users with helpful hint text below fields, properly associated using
|
|
743
|
+
`aria-describedby`.
|
|
719
744
|
|
|
720
745
|
### Form with Select Dropdown
|
|
721
746
|
|
|
@@ -727,7 +752,8 @@ Clearly distinguish between required and optional fields for better UX.
|
|
|
727
752
|
|
|
728
753
|
### Complete Registration Form
|
|
729
754
|
|
|
730
|
-
A comprehensive example combining all features: validation, hints, different
|
|
755
|
+
A comprehensive example combining all features: validation, hints, different
|
|
756
|
+
field types, and proper accessibility.
|
|
731
757
|
|
|
732
758
|
---
|
|
733
759
|
|
|
@@ -743,7 +769,8 @@ A comprehensive example combining all features: validation, hints, different fie
|
|
|
743
769
|
|
|
744
770
|
## Contributing
|
|
745
771
|
|
|
746
|
-
Found an issue or have a suggestion? Please
|
|
772
|
+
Found an issue or have a suggestion? Please
|
|
773
|
+
[open an issue](https://github.com/your-repo/issues) or submit a pull request.
|
|
747
774
|
|
|
748
775
|
---
|
|
749
776
|
|