@fpkit/acss 3.7.0 → 3.8.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/checkbox/checkbox.css +1 -0
- package/libs/components/checkbox/checkbox.css.map +1 -0
- package/libs/components/checkbox/checkbox.min.css +3 -0
- package/libs/index.cjs +27 -26
- 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 +356 -1
- package/libs/index.d.ts +356 -1
- package/libs/index.js +5 -5
- package/libs/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/checkbox/README.mdx +263 -0
- package/src/components/checkbox/STYLES.mdx +434 -0
- package/src/components/checkbox/checkbox.scss +432 -0
- package/src/components/checkbox/checkbox.stories.tsx +607 -0
- package/src/components/checkbox/checkbox.test.tsx +535 -0
- package/src/components/checkbox/checkbox.tsx +575 -0
- package/src/components/form/README.mdx +173 -146
- package/src/index.scss +1 -0
- package/src/index.ts +7 -0
- package/src/sass/_columns.scss +13 -9
- package/src/styles/checkbox/checkbox.css +372 -0
- package/src/styles/checkbox/checkbox.css.map +1 -0
- package/src/styles/index.css +371 -0
- package/src/styles/index.css.map +1 -1
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
import type { StoryObj, Meta } from "@storybook/react-vite";
|
|
2
|
+
import { within, userEvent, expect, fn } from "storybook/test";
|
|
3
|
+
import React from "react";
|
|
4
|
+
|
|
5
|
+
import { Checkbox } from "./checkbox";
|
|
6
|
+
import "./checkbox.scss";
|
|
7
|
+
|
|
8
|
+
const checkboxChanged = fn();
|
|
9
|
+
|
|
10
|
+
const meta = {
|
|
11
|
+
title: "FP.React Forms/Inputs/Checkbox",
|
|
12
|
+
component: Checkbox,
|
|
13
|
+
tags: ["beta"],
|
|
14
|
+
args: {
|
|
15
|
+
onChange: checkboxChanged,
|
|
16
|
+
},
|
|
17
|
+
parameters: {
|
|
18
|
+
actions: { argTypesRegex: "^on.*" },
|
|
19
|
+
},
|
|
20
|
+
} as Meta<typeof Checkbox>;
|
|
21
|
+
|
|
22
|
+
export default meta;
|
|
23
|
+
type Story = StoryObj<typeof Checkbox>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Default checkbox with interactive play function testing.
|
|
27
|
+
*
|
|
28
|
+
* Tests keyboard navigation (Tab, Space) and mouse interaction.
|
|
29
|
+
*/
|
|
30
|
+
export const CheckboxComponent: Story = {
|
|
31
|
+
args: {
|
|
32
|
+
id: "default-checkbox",
|
|
33
|
+
label: "Accept terms and conditions",
|
|
34
|
+
onChange: checkboxChanged,
|
|
35
|
+
},
|
|
36
|
+
play: async ({ canvasElement, step }) => {
|
|
37
|
+
const canvas = within(canvasElement);
|
|
38
|
+
const checkbox = canvas.getByRole("checkbox");
|
|
39
|
+
|
|
40
|
+
await step("Checkbox is rendered", async () => {
|
|
41
|
+
expect(checkbox).toBeInTheDocument();
|
|
42
|
+
expect(checkbox).not.toBeChecked();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
await step("Checkbox gets focus on tab", async () => {
|
|
46
|
+
await userEvent.tab();
|
|
47
|
+
expect(checkbox).toHaveFocus();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
await step("Checkbox toggles on space key", async () => {
|
|
51
|
+
await userEvent.keyboard("{space}");
|
|
52
|
+
expect(checkbox).toBeChecked();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await step("Checkbox toggles on click", async () => {
|
|
56
|
+
await userEvent.click(checkbox);
|
|
57
|
+
expect(checkbox).not.toBeChecked();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
await step("onChange handler is called", async () => {
|
|
61
|
+
await userEvent.click(checkbox);
|
|
62
|
+
expect(checkboxChanged).toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Size variants: small (sm), medium (md), large (lg)
|
|
69
|
+
*
|
|
70
|
+
* All sizes maintain proper touch target accessibility.
|
|
71
|
+
*/
|
|
72
|
+
export const Sizes: Story = {
|
|
73
|
+
render: () => (
|
|
74
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
|
|
75
|
+
<Checkbox id="size-sm" label="Small checkbox" size="sm" />
|
|
76
|
+
<Checkbox id="size-md" label="Medium checkbox (default)" size="md" />
|
|
77
|
+
<Checkbox id="size-lg" label="Large checkbox" size="lg" />
|
|
78
|
+
</div>
|
|
79
|
+
),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Color variants: primary, secondary, error, success
|
|
84
|
+
*
|
|
85
|
+
* All colors meet WCAG 2.1 AA contrast requirements (4.5:1 minimum).
|
|
86
|
+
*/
|
|
87
|
+
export const Colors: Story = {
|
|
88
|
+
render: () => (
|
|
89
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
|
|
90
|
+
<Checkbox
|
|
91
|
+
id="color-primary"
|
|
92
|
+
label="Primary (Blue - 4.68:1 contrast)"
|
|
93
|
+
color="primary"
|
|
94
|
+
defaultChecked
|
|
95
|
+
/>
|
|
96
|
+
<Checkbox
|
|
97
|
+
id="color-secondary"
|
|
98
|
+
label="Secondary (Gray - 7.56:1 contrast)"
|
|
99
|
+
color="secondary"
|
|
100
|
+
defaultChecked
|
|
101
|
+
/>
|
|
102
|
+
<Checkbox
|
|
103
|
+
id="color-error"
|
|
104
|
+
label="Error (Red - 5.14:1 contrast)"
|
|
105
|
+
color="error"
|
|
106
|
+
defaultChecked
|
|
107
|
+
/>
|
|
108
|
+
<Checkbox
|
|
109
|
+
id="color-success"
|
|
110
|
+
label="Success (Green - 4.54:1 contrast)"
|
|
111
|
+
color="success"
|
|
112
|
+
defaultChecked
|
|
113
|
+
/>
|
|
114
|
+
</div>
|
|
115
|
+
),
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Checkbox states: unchecked, checked, indeterminate, disabled
|
|
120
|
+
*
|
|
121
|
+
* Indeterminate state is useful for "select all" patterns.
|
|
122
|
+
*/
|
|
123
|
+
export const States: Story = {
|
|
124
|
+
render: () => (
|
|
125
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
|
|
126
|
+
<Checkbox id="state-unchecked" label="Unchecked" />
|
|
127
|
+
<Checkbox id="state-checked" label="Checked" defaultChecked />
|
|
128
|
+
<Checkbox id="state-indeterminate" label="Indeterminate" indeterminate />
|
|
129
|
+
<Checkbox
|
|
130
|
+
id="state-disabled-unchecked"
|
|
131
|
+
label="Disabled (unchecked)"
|
|
132
|
+
disabled
|
|
133
|
+
/>
|
|
134
|
+
<Checkbox
|
|
135
|
+
id="state-disabled-checked"
|
|
136
|
+
label="Disabled (checked)"
|
|
137
|
+
disabled
|
|
138
|
+
defaultChecked
|
|
139
|
+
/>
|
|
140
|
+
<Checkbox
|
|
141
|
+
id="state-disabled-indeterminate"
|
|
142
|
+
label="Disabled (indeterminate)"
|
|
143
|
+
disabled
|
|
144
|
+
indeterminate
|
|
145
|
+
/>
|
|
146
|
+
</div>
|
|
147
|
+
),
|
|
148
|
+
parameters: {
|
|
149
|
+
docs: {
|
|
150
|
+
description: {
|
|
151
|
+
story: `
|
|
152
|
+
Checkbox supports multiple states:
|
|
153
|
+
- **Unchecked**: Default state
|
|
154
|
+
- **Checked**: Selected state
|
|
155
|
+
- **Indeterminate**: Partial selection (e.g., some but not all items selected)
|
|
156
|
+
- **Disabled**: Non-interactive state (remains focusable for accessibility)
|
|
157
|
+
|
|
158
|
+
Disabled checkboxes use aria-disabled to maintain keyboard focusability per WCAG 2.1 AA.
|
|
159
|
+
`,
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Checkbox with description helper text
|
|
167
|
+
*
|
|
168
|
+
* Description is linked via aria-describedby for screen readers.
|
|
169
|
+
*/
|
|
170
|
+
export const WithDescription: Story = {
|
|
171
|
+
args: {
|
|
172
|
+
id: "description-checkbox",
|
|
173
|
+
label: "Enable email notifications",
|
|
174
|
+
description:
|
|
175
|
+
"Receive email updates about important changes to your account",
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Checkbox with validation error
|
|
181
|
+
*
|
|
182
|
+
* Error message is linked via aria-errormessage when validationState="invalid".
|
|
183
|
+
*/
|
|
184
|
+
export const WithError: Story = {
|
|
185
|
+
args: {
|
|
186
|
+
id: "error-checkbox",
|
|
187
|
+
label: "I accept the terms and conditions",
|
|
188
|
+
required: true,
|
|
189
|
+
validationState: "invalid",
|
|
190
|
+
errorMessage: "You must accept the terms to continue",
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Label positioning: left vs right
|
|
196
|
+
*
|
|
197
|
+
* Label can appear before or after the checkbox.
|
|
198
|
+
*/
|
|
199
|
+
export const LabelPositions: Story = {
|
|
200
|
+
render: () => (
|
|
201
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
|
|
202
|
+
<Checkbox
|
|
203
|
+
id="label-right"
|
|
204
|
+
label="Label on right (default)"
|
|
205
|
+
labelPosition="right"
|
|
206
|
+
/>
|
|
207
|
+
<Checkbox id="label-left" label="Label on left" labelPosition="left" />
|
|
208
|
+
</div>
|
|
209
|
+
),
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Controlled mode with React state
|
|
214
|
+
*
|
|
215
|
+
* Checkbox state is managed by parent component.
|
|
216
|
+
*/
|
|
217
|
+
export const Controlled: Story = {
|
|
218
|
+
render: function ControlledCheckbox() {
|
|
219
|
+
const [checked, setChecked] = React.useState(false);
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
|
|
223
|
+
<Checkbox
|
|
224
|
+
id="controlled-checkbox"
|
|
225
|
+
label="Subscribe to newsletter"
|
|
226
|
+
checked={checked}
|
|
227
|
+
onChange={(e) => setChecked(e.target.checked)}
|
|
228
|
+
/>
|
|
229
|
+
<div style={{ fontSize: "0.875rem", color: "#6b7280" }}>
|
|
230
|
+
State: <strong>{checked ? "Checked" : "Unchecked"}</strong>
|
|
231
|
+
</div>
|
|
232
|
+
<button
|
|
233
|
+
type="button"
|
|
234
|
+
onClick={() => setChecked(!checked)}
|
|
235
|
+
style={{
|
|
236
|
+
padding: "0.5rem 1rem",
|
|
237
|
+
border: "1px solid #d1d5db",
|
|
238
|
+
borderRadius: "0.375rem",
|
|
239
|
+
background: "white",
|
|
240
|
+
cursor: "pointer",
|
|
241
|
+
}}
|
|
242
|
+
>
|
|
243
|
+
Toggle via button
|
|
244
|
+
</button>
|
|
245
|
+
</div>
|
|
246
|
+
);
|
|
247
|
+
},
|
|
248
|
+
parameters: {
|
|
249
|
+
docs: {
|
|
250
|
+
description: {
|
|
251
|
+
story: `
|
|
252
|
+
In controlled mode, the parent component manages the checkbox state via the \`checked\` prop and \`onChange\` handler.
|
|
253
|
+
|
|
254
|
+
Use controlled mode when you need to:
|
|
255
|
+
- Sync checkbox state with other UI elements
|
|
256
|
+
- Validate or transform input before updating state
|
|
257
|
+
- Integrate with form libraries
|
|
258
|
+
`,
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Uncontrolled mode with defaultChecked
|
|
266
|
+
*
|
|
267
|
+
* Browser manages checkbox state internally.
|
|
268
|
+
*/
|
|
269
|
+
export const Uncontrolled: Story = {
|
|
270
|
+
render: () => (
|
|
271
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
|
|
272
|
+
<Checkbox
|
|
273
|
+
id="uncontrolled-unchecked"
|
|
274
|
+
label="Starts unchecked"
|
|
275
|
+
defaultChecked={false}
|
|
276
|
+
/>
|
|
277
|
+
<Checkbox
|
|
278
|
+
id="uncontrolled-checked"
|
|
279
|
+
label="Starts checked"
|
|
280
|
+
defaultChecked={true}
|
|
281
|
+
/>
|
|
282
|
+
</div>
|
|
283
|
+
),
|
|
284
|
+
parameters: {
|
|
285
|
+
docs: {
|
|
286
|
+
description: {
|
|
287
|
+
story: `
|
|
288
|
+
In uncontrolled mode, use \`defaultChecked\` to set the initial state. The browser manages the state internally.
|
|
289
|
+
|
|
290
|
+
Use uncontrolled mode when:
|
|
291
|
+
- Building simple forms without complex validation
|
|
292
|
+
- You don't need to read the value until form submission
|
|
293
|
+
- State management overhead isn't needed
|
|
294
|
+
`,
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Required field indicator
|
|
302
|
+
*
|
|
303
|
+
* Shows asterisk and sets aria-required for screen readers.
|
|
304
|
+
*/
|
|
305
|
+
export const Required: Story = {
|
|
306
|
+
args: {
|
|
307
|
+
id: "required-checkbox",
|
|
308
|
+
label: "I agree to the privacy policy",
|
|
309
|
+
required: true,
|
|
310
|
+
},
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Complex form with validation
|
|
315
|
+
*
|
|
316
|
+
* Demonstrates multiple checkboxes with description, validation, and states.
|
|
317
|
+
*/
|
|
318
|
+
export const ComplexForm: Story = {
|
|
319
|
+
render: function ComplexFormExample() {
|
|
320
|
+
const [accepted, setAccepted] = React.useState(false);
|
|
321
|
+
const [newsletter, setNewsletter] = React.useState(false);
|
|
322
|
+
const [submitted, setSubmitted] = React.useState(false);
|
|
323
|
+
|
|
324
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
325
|
+
e.preventDefault();
|
|
326
|
+
setSubmitted(true);
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
return (
|
|
330
|
+
<form
|
|
331
|
+
onSubmit={handleSubmit}
|
|
332
|
+
style={{
|
|
333
|
+
display: "flex",
|
|
334
|
+
flexDirection: "column",
|
|
335
|
+
gap: "1.5rem",
|
|
336
|
+
maxWidth: "32rem",
|
|
337
|
+
}}
|
|
338
|
+
>
|
|
339
|
+
<h3 style={{ margin: 0, fontSize: "1.25rem", fontWeight: 600 }}>
|
|
340
|
+
Account Preferences
|
|
341
|
+
</h3>
|
|
342
|
+
|
|
343
|
+
<Checkbox
|
|
344
|
+
id="terms-checkbox"
|
|
345
|
+
label="I accept the terms and conditions"
|
|
346
|
+
required
|
|
347
|
+
checked={accepted}
|
|
348
|
+
onChange={(e) => setAccepted(e.target.checked)}
|
|
349
|
+
validationState={submitted && !accepted ? "invalid" : "none"}
|
|
350
|
+
errorMessage="You must accept the terms to continue"
|
|
351
|
+
description="Read our terms and conditions before accepting"
|
|
352
|
+
/>
|
|
353
|
+
|
|
354
|
+
<Checkbox
|
|
355
|
+
id="newsletter-checkbox"
|
|
356
|
+
label="Subscribe to newsletter"
|
|
357
|
+
checked={newsletter}
|
|
358
|
+
onChange={(e) => setNewsletter(e.target.checked)}
|
|
359
|
+
description="Receive monthly updates about new features"
|
|
360
|
+
/>
|
|
361
|
+
|
|
362
|
+
<button
|
|
363
|
+
type="submit"
|
|
364
|
+
style={{
|
|
365
|
+
padding: "0.75rem 1.5rem",
|
|
366
|
+
border: "none",
|
|
367
|
+
borderRadius: "0.375rem",
|
|
368
|
+
background: "#2563eb",
|
|
369
|
+
color: "white",
|
|
370
|
+
fontSize: "1rem",
|
|
371
|
+
fontWeight: 500,
|
|
372
|
+
cursor: "pointer",
|
|
373
|
+
}}
|
|
374
|
+
>
|
|
375
|
+
Submit
|
|
376
|
+
</button>
|
|
377
|
+
|
|
378
|
+
{submitted && accepted && (
|
|
379
|
+
<div
|
|
380
|
+
style={{
|
|
381
|
+
padding: "1rem",
|
|
382
|
+
background: "#d4edda",
|
|
383
|
+
border: "1px solid #c3e6cb",
|
|
384
|
+
borderRadius: "0.375rem",
|
|
385
|
+
color: "#155724",
|
|
386
|
+
}}
|
|
387
|
+
>
|
|
388
|
+
✓ Form submitted successfully!
|
|
389
|
+
{newsletter && " You're subscribed to the newsletter."}
|
|
390
|
+
</div>
|
|
391
|
+
)}
|
|
392
|
+
</form>
|
|
393
|
+
);
|
|
394
|
+
},
|
|
395
|
+
parameters: {
|
|
396
|
+
docs: {
|
|
397
|
+
description: {
|
|
398
|
+
story: `
|
|
399
|
+
Complete form example demonstrating:
|
|
400
|
+
- Required checkboxes with validation
|
|
401
|
+
- Optional checkboxes
|
|
402
|
+
- Description text
|
|
403
|
+
- Error messages
|
|
404
|
+
- Form submission handling
|
|
405
|
+
`,
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Select All pattern with indeterminate state
|
|
413
|
+
*
|
|
414
|
+
* Common pattern for batch selection with "select all" checkbox.
|
|
415
|
+
*/
|
|
416
|
+
export const SelectAll: Story = {
|
|
417
|
+
render: function SelectAllExample() {
|
|
418
|
+
const items = [
|
|
419
|
+
{ id: "item-1", label: "Item 1" },
|
|
420
|
+
{ id: "item-2", label: "Item 2" },
|
|
421
|
+
{ id: "item-3", label: "Item 3" },
|
|
422
|
+
{ id: "item-4", label: "Item 4" },
|
|
423
|
+
];
|
|
424
|
+
|
|
425
|
+
const [selectedItems, setSelectedItems] = React.useState<string[]>([]);
|
|
426
|
+
|
|
427
|
+
const allSelected = selectedItems.length === items.length;
|
|
428
|
+
const someSelected = selectedItems.length > 0 && !allSelected;
|
|
429
|
+
|
|
430
|
+
const handleSelectAll = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
431
|
+
if (e.target.checked) {
|
|
432
|
+
setSelectedItems(items.map((item) => item.id));
|
|
433
|
+
} else {
|
|
434
|
+
setSelectedItems([]);
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
const handleItemToggle = (itemId: string) => {
|
|
439
|
+
setSelectedItems((prev) =>
|
|
440
|
+
prev.includes(itemId)
|
|
441
|
+
? prev.filter((id) => id !== itemId)
|
|
442
|
+
: [...prev, itemId]
|
|
443
|
+
);
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
return (
|
|
447
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
|
|
448
|
+
<Checkbox
|
|
449
|
+
id="select-all"
|
|
450
|
+
label="Select all"
|
|
451
|
+
checked={allSelected}
|
|
452
|
+
indeterminate={someSelected}
|
|
453
|
+
onChange={handleSelectAll}
|
|
454
|
+
styles={{
|
|
455
|
+
fontWeight: 600,
|
|
456
|
+
borderBottom: "1px solid #e5e7eb",
|
|
457
|
+
paddingBottom: "0.75rem",
|
|
458
|
+
}}
|
|
459
|
+
/>
|
|
460
|
+
|
|
461
|
+
<div
|
|
462
|
+
style={{
|
|
463
|
+
display: "flex",
|
|
464
|
+
flexDirection: "column",
|
|
465
|
+
gap: "0.75rem",
|
|
466
|
+
paddingLeft: "1.5rem",
|
|
467
|
+
}}
|
|
468
|
+
>
|
|
469
|
+
{items.map((item) => (
|
|
470
|
+
<Checkbox
|
|
471
|
+
key={item.id}
|
|
472
|
+
id={item.id}
|
|
473
|
+
label={item.label}
|
|
474
|
+
checked={selectedItems.includes(item.id)}
|
|
475
|
+
onChange={() => handleItemToggle(item.id)}
|
|
476
|
+
/>
|
|
477
|
+
))}
|
|
478
|
+
</div>
|
|
479
|
+
|
|
480
|
+
<div
|
|
481
|
+
style={{
|
|
482
|
+
marginTop: "1rem",
|
|
483
|
+
padding: "0.75rem",
|
|
484
|
+
background: "#f9fafb",
|
|
485
|
+
border: "1px solid #e5e7eb",
|
|
486
|
+
borderRadius: "0.375rem",
|
|
487
|
+
fontSize: "0.875rem",
|
|
488
|
+
color: "#4b5563",
|
|
489
|
+
}}
|
|
490
|
+
>
|
|
491
|
+
Selected: <strong>{selectedItems.length}</strong> of{" "}
|
|
492
|
+
<strong>{items.length}</strong> items
|
|
493
|
+
</div>
|
|
494
|
+
</div>
|
|
495
|
+
);
|
|
496
|
+
},
|
|
497
|
+
parameters: {
|
|
498
|
+
docs: {
|
|
499
|
+
description: {
|
|
500
|
+
story: `
|
|
501
|
+
The "select all" pattern uses the indeterminate state to show partial selection:
|
|
502
|
+
|
|
503
|
+
- **Unchecked**: No items selected
|
|
504
|
+
- **Indeterminate**: Some items selected (shows dash)
|
|
505
|
+
- **Checked**: All items selected
|
|
506
|
+
|
|
507
|
+
This provides clear visual feedback about selection state at a glance.
|
|
508
|
+
`,
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Custom styling with CSS variables
|
|
516
|
+
*
|
|
517
|
+
* Demonstrates customization using CSS custom properties.
|
|
518
|
+
*/
|
|
519
|
+
export const CustomStyling: Story = {
|
|
520
|
+
render: () => (
|
|
521
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "2rem" }}>
|
|
522
|
+
<div>
|
|
523
|
+
<h4 style={{ marginTop: 0, marginBottom: "1rem" }}>Custom Size</h4>
|
|
524
|
+
<Checkbox
|
|
525
|
+
id="custom-size"
|
|
526
|
+
label="Extra large checkbox"
|
|
527
|
+
defaultChecked
|
|
528
|
+
styles={
|
|
529
|
+
{
|
|
530
|
+
"--checkbox-size": "2rem",
|
|
531
|
+
"--checkbox-label-fs": "1.25rem",
|
|
532
|
+
} as React.CSSProperties
|
|
533
|
+
}
|
|
534
|
+
/>
|
|
535
|
+
</div>
|
|
536
|
+
|
|
537
|
+
<div>
|
|
538
|
+
<h4 style={{ marginTop: 0, marginBottom: "1rem" }}>
|
|
539
|
+
Custom Colors (Brand Purple)
|
|
540
|
+
</h4>
|
|
541
|
+
<Checkbox
|
|
542
|
+
id="custom-color"
|
|
543
|
+
label="Custom brand color"
|
|
544
|
+
defaultChecked
|
|
545
|
+
styles={
|
|
546
|
+
{
|
|
547
|
+
"--checkbox-checked-bg": "#7c3aed",
|
|
548
|
+
"--checkbox-checked-border": "#7c3aed",
|
|
549
|
+
"--checkbox-focus-outline-color": "#c4b5fd",
|
|
550
|
+
} as React.CSSProperties
|
|
551
|
+
}
|
|
552
|
+
/>
|
|
553
|
+
</div>
|
|
554
|
+
|
|
555
|
+
<div>
|
|
556
|
+
<h4 style={{ marginTop: 0, marginBottom: "1rem" }}>Rounded Style</h4>
|
|
557
|
+
<Checkbox
|
|
558
|
+
id="custom-rounded"
|
|
559
|
+
label="Fully rounded checkbox"
|
|
560
|
+
defaultChecked
|
|
561
|
+
styles={
|
|
562
|
+
{
|
|
563
|
+
"--checkbox-radius": "100rem",
|
|
564
|
+
} as React.CSSProperties
|
|
565
|
+
}
|
|
566
|
+
/>
|
|
567
|
+
</div>
|
|
568
|
+
|
|
569
|
+
<div>
|
|
570
|
+
<h4 style={{ marginTop: 0, marginBottom: "1rem" }}>
|
|
571
|
+
No Border, Shadow Style
|
|
572
|
+
</h4>
|
|
573
|
+
<Checkbox
|
|
574
|
+
id="custom-shadow"
|
|
575
|
+
label="Shadow instead of border"
|
|
576
|
+
defaultChecked
|
|
577
|
+
styles={
|
|
578
|
+
{
|
|
579
|
+
"--checkbox-border": "none",
|
|
580
|
+
"--checkbox-bg": "#f3f4f6",
|
|
581
|
+
"--checkbox-checked-bg": "#10b981",
|
|
582
|
+
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.1)",
|
|
583
|
+
} as React.CSSProperties
|
|
584
|
+
}
|
|
585
|
+
/>
|
|
586
|
+
</div>
|
|
587
|
+
</div>
|
|
588
|
+
),
|
|
589
|
+
parameters: {
|
|
590
|
+
docs: {
|
|
591
|
+
description: {
|
|
592
|
+
story: `
|
|
593
|
+
Customize checkbox appearance using CSS custom properties:
|
|
594
|
+
|
|
595
|
+
**Available Variables:**
|
|
596
|
+
- \`--checkbox-size\`: Checkbox dimensions
|
|
597
|
+
- \`--checkbox-checked-bg\`: Background when checked
|
|
598
|
+
- \`--checkbox-checked-border\`: Border when checked
|
|
599
|
+
- \`--checkbox-radius\`: Border radius
|
|
600
|
+
- \`--checkbox-focus-outline-color\`: Focus indicator color
|
|
601
|
+
- \`--checkbox-label-fs\`: Label font size
|
|
602
|
+
- And many more! See STYLES.mdx for complete reference.
|
|
603
|
+
`,
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
};
|