@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
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { StoryObj, Meta } from "@storybook/react-vite";
|
|
2
2
|
import { within, userEvent, expect } from "storybook/test";
|
|
3
|
+
import React from "react";
|
|
3
4
|
|
|
4
5
|
import Input from "./inputs";
|
|
6
|
+
import { Checkbox as CheckboxComponent } from "./checkbox";
|
|
5
7
|
import "./form.scss";
|
|
6
8
|
|
|
7
9
|
const meta: Meta<typeof Input> = {
|
|
@@ -222,6 +224,254 @@ export const UrlInput: Story = {
|
|
|
222
224
|
},
|
|
223
225
|
} as Story;
|
|
224
226
|
|
|
227
|
+
export const Checkbox: Story = {
|
|
228
|
+
args: {
|
|
229
|
+
type: "checkbox",
|
|
230
|
+
},
|
|
231
|
+
play: async ({ canvasElement }) => {
|
|
232
|
+
const canvas = within(canvasElement);
|
|
233
|
+
const input = canvas.getByRole("checkbox");
|
|
234
|
+
expect(input).toHaveAttribute("type", "checkbox");
|
|
235
|
+
|
|
236
|
+
await userEvent.click(input);
|
|
237
|
+
expect(input).toBeChecked();
|
|
238
|
+
|
|
239
|
+
await userEvent.click(input);
|
|
240
|
+
expect(input).not.toBeChecked();
|
|
241
|
+
},
|
|
242
|
+
} as Story;
|
|
243
|
+
|
|
244
|
+
// ============================================================================
|
|
245
|
+
// Checkbox Wrapper Component Stories
|
|
246
|
+
// ============================================================================
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* CheckboxWrapper - Basic checkbox with label
|
|
250
|
+
*
|
|
251
|
+
* Demonstrates the Checkbox wrapper component with simplified API.
|
|
252
|
+
* Features automatic label association, boolean onChange, and keyboard support.
|
|
253
|
+
*/
|
|
254
|
+
export const CheckboxWrapper: Story = {
|
|
255
|
+
render: () => (
|
|
256
|
+
<CheckboxComponent id="basic" label="I accept the terms and conditions" />
|
|
257
|
+
),
|
|
258
|
+
play: async ({ canvasElement, step }) => {
|
|
259
|
+
const canvas = within(canvasElement);
|
|
260
|
+
const checkbox = canvas.getByRole("checkbox");
|
|
261
|
+
|
|
262
|
+
await step("Checkbox renders unchecked", async () => {
|
|
263
|
+
expect(checkbox).toBeInTheDocument();
|
|
264
|
+
expect(checkbox).not.toBeChecked();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
await step("Checkbox can be checked by clicking", async () => {
|
|
268
|
+
await userEvent.click(checkbox);
|
|
269
|
+
expect(checkbox).toBeChecked();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
await step("Label can be clicked to toggle", async () => {
|
|
273
|
+
const label = canvas.getByText("I accept the terms and conditions");
|
|
274
|
+
await userEvent.click(label);
|
|
275
|
+
expect(checkbox).not.toBeChecked();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
await step("Space key toggles checkbox", async () => {
|
|
279
|
+
checkbox.focus();
|
|
280
|
+
await userEvent.keyboard(" ");
|
|
281
|
+
expect(checkbox).toBeChecked();
|
|
282
|
+
});
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* CheckboxControlled - Controlled checkbox with state management
|
|
288
|
+
*
|
|
289
|
+
* Demonstrates controlled mode with React state and boolean onChange API.
|
|
290
|
+
*/
|
|
291
|
+
const CheckboxControlledExample = () => {
|
|
292
|
+
const [checked, setChecked] = React.useState(false);
|
|
293
|
+
return (
|
|
294
|
+
<div>
|
|
295
|
+
<CheckboxComponent
|
|
296
|
+
id="controlled"
|
|
297
|
+
label="Subscribe to newsletter"
|
|
298
|
+
checked={checked}
|
|
299
|
+
onChange={setChecked}
|
|
300
|
+
/>
|
|
301
|
+
<p style={{ marginTop: "1rem", fontSize: "0.875rem", color: "#6b7280" }}>
|
|
302
|
+
Status: {checked ? "✓ Subscribed" : "Not subscribed"}
|
|
303
|
+
</p>
|
|
304
|
+
</div>
|
|
305
|
+
);
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
export const CheckboxControlled: Story = {
|
|
309
|
+
render: () => <CheckboxControlledExample />,
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* CheckboxRequired - Required checkbox with asterisk indicator
|
|
314
|
+
*
|
|
315
|
+
* Shows required field indicator and aria-required attribute.
|
|
316
|
+
*/
|
|
317
|
+
export const CheckboxRequired: Story = {
|
|
318
|
+
render: () => (
|
|
319
|
+
<CheckboxComponent
|
|
320
|
+
id="required"
|
|
321
|
+
label="I accept the terms"
|
|
322
|
+
required
|
|
323
|
+
/>
|
|
324
|
+
),
|
|
325
|
+
play: async ({ canvasElement }) => {
|
|
326
|
+
const canvas = within(canvasElement);
|
|
327
|
+
const checkbox = canvas.getByRole("checkbox");
|
|
328
|
+
expect(checkbox).toHaveAttribute("aria-required", "true");
|
|
329
|
+
expect(canvas.getByText("*")).toBeInTheDocument();
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* CheckboxDisabled - Disabled checkbox (WCAG compliant)
|
|
335
|
+
*
|
|
336
|
+
* Demonstrates aria-disabled pattern that remains focusable for screen readers.
|
|
337
|
+
*/
|
|
338
|
+
export const CheckboxDisabled: Story = {
|
|
339
|
+
render: () => (
|
|
340
|
+
<CheckboxComponent
|
|
341
|
+
id="disabled"
|
|
342
|
+
label="Disabled option"
|
|
343
|
+
disabled
|
|
344
|
+
defaultChecked
|
|
345
|
+
/>
|
|
346
|
+
),
|
|
347
|
+
play: async ({ canvasElement, step }) => {
|
|
348
|
+
const canvas = within(canvasElement);
|
|
349
|
+
const checkbox = canvas.getByRole("checkbox");
|
|
350
|
+
|
|
351
|
+
await step("Disabled checkbox has aria-disabled", async () => {
|
|
352
|
+
expect(checkbox).toHaveAttribute("aria-disabled", "true");
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
await step("Disabled checkbox remains focusable", async () => {
|
|
356
|
+
await userEvent.tab();
|
|
357
|
+
expect(checkbox).toHaveFocus();
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
await step("Disabled checkbox prevents interaction", async () => {
|
|
361
|
+
const wasChecked = checkbox.checked;
|
|
362
|
+
await userEvent.click(checkbox);
|
|
363
|
+
// Value should remain unchanged due to disabled state
|
|
364
|
+
expect(checkbox.checked).toBe(wasChecked);
|
|
365
|
+
});
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* CheckboxValidation - Checkbox with validation error
|
|
371
|
+
*
|
|
372
|
+
* Shows error state with aria-invalid and error message.
|
|
373
|
+
*/
|
|
374
|
+
export const CheckboxValidation: Story = {
|
|
375
|
+
render: () => (
|
|
376
|
+
<CheckboxComponent
|
|
377
|
+
id="validation"
|
|
378
|
+
label="I accept the terms"
|
|
379
|
+
validationState="invalid"
|
|
380
|
+
errorMessage="You must accept the terms to continue"
|
|
381
|
+
/>
|
|
382
|
+
),
|
|
383
|
+
play: async ({ canvasElement }) => {
|
|
384
|
+
const canvas = within(canvasElement);
|
|
385
|
+
const checkbox = canvas.getByRole("checkbox");
|
|
386
|
+
expect(checkbox).toHaveAttribute("aria-invalid", "true");
|
|
387
|
+
expect(checkbox).toHaveAttribute("aria-describedby");
|
|
388
|
+
expect(canvas.getByText("You must accept the terms to continue")).toBeInTheDocument();
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* CheckboxWithHint - Checkbox with hint text
|
|
394
|
+
*
|
|
395
|
+
* Demonstrates hint text for additional context.
|
|
396
|
+
*/
|
|
397
|
+
export const CheckboxWithHint: Story = {
|
|
398
|
+
render: () => (
|
|
399
|
+
<CheckboxComponent
|
|
400
|
+
id="hint"
|
|
401
|
+
label="Enable two-factor authentication"
|
|
402
|
+
hintText="Adds an extra layer of security to your account"
|
|
403
|
+
/>
|
|
404
|
+
),
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* CheckboxCustomSize - Custom sized checkboxes using CSS variables
|
|
409
|
+
*
|
|
410
|
+
* Demonstrates responsive sizing via --checkbox-size variable.
|
|
411
|
+
*/
|
|
412
|
+
export const CheckboxCustomSize: Story = {
|
|
413
|
+
render: () => (
|
|
414
|
+
<div style={{ display: "flex", gap: "1.5rem", flexDirection: "column" }}>
|
|
415
|
+
<CheckboxComponent
|
|
416
|
+
id="small"
|
|
417
|
+
label="Small (1rem)"
|
|
418
|
+
styles={{ "--checkbox-gap": "0.375rem" } as React.CSSProperties}
|
|
419
|
+
/>
|
|
420
|
+
<CheckboxComponent
|
|
421
|
+
id="medium"
|
|
422
|
+
label="Medium (1.25rem - default)"
|
|
423
|
+
/>
|
|
424
|
+
<CheckboxComponent
|
|
425
|
+
id="large"
|
|
426
|
+
label="Large (1.5rem)"
|
|
427
|
+
styles={{ "--checkbox-gap": "0.75rem" } as React.CSSProperties}
|
|
428
|
+
/>
|
|
429
|
+
<CheckboxComponent
|
|
430
|
+
id="xlarge"
|
|
431
|
+
label="Extra Large (2rem)"
|
|
432
|
+
styles={{ "--checkbox-gap": "1rem" } as React.CSSProperties}
|
|
433
|
+
/>
|
|
434
|
+
</div>
|
|
435
|
+
),
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* CheckboxGroup - Multiple checkboxes in a fieldset
|
|
440
|
+
*
|
|
441
|
+
* Demonstrates grouping related checkboxes with semantic HTML.
|
|
442
|
+
*/
|
|
443
|
+
export const CheckboxGroup: Story = {
|
|
444
|
+
render: () => (
|
|
445
|
+
<fieldset style={{ border: "1px solid #d1d5db", padding: "1.5rem", borderRadius: "0.5rem" }}>
|
|
446
|
+
<legend style={{ fontWeight: "600", fontSize: "1rem", padding: "0 0.5rem" }}>
|
|
447
|
+
Notification Preferences
|
|
448
|
+
</legend>
|
|
449
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "1rem", marginTop: "1rem" }}>
|
|
450
|
+
<CheckboxComponent
|
|
451
|
+
id="email"
|
|
452
|
+
name="notifications"
|
|
453
|
+
value="email"
|
|
454
|
+
label="Email notifications"
|
|
455
|
+
defaultChecked
|
|
456
|
+
/>
|
|
457
|
+
<CheckboxComponent
|
|
458
|
+
id="sms"
|
|
459
|
+
name="notifications"
|
|
460
|
+
value="sms"
|
|
461
|
+
label="SMS notifications"
|
|
462
|
+
/>
|
|
463
|
+
<CheckboxComponent
|
|
464
|
+
id="push"
|
|
465
|
+
name="notifications"
|
|
466
|
+
value="push"
|
|
467
|
+
label="Push notifications"
|
|
468
|
+
defaultChecked
|
|
469
|
+
/>
|
|
470
|
+
</div>
|
|
471
|
+
</fieldset>
|
|
472
|
+
),
|
|
473
|
+
};
|
|
474
|
+
|
|
225
475
|
/**
|
|
226
476
|
* CSS Variable Customization
|
|
227
477
|
*
|
|
@@ -237,7 +487,14 @@ export const UrlInput: Story = {
|
|
|
237
487
|
*/
|
|
238
488
|
export const Customization: Story = {
|
|
239
489
|
render: () => (
|
|
240
|
-
<div
|
|
490
|
+
<div
|
|
491
|
+
style={{
|
|
492
|
+
display: "flex",
|
|
493
|
+
flexDirection: "column",
|
|
494
|
+
gap: "2rem",
|
|
495
|
+
maxWidth: "600px",
|
|
496
|
+
}}
|
|
497
|
+
>
|
|
241
498
|
{/* Custom brand styling */}
|
|
242
499
|
<div>
|
|
243
500
|
<h4>Custom Brand Styling</h4>
|
package/src/index.scss
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
@use "./components/badge/badge.scss";
|
|
24
24
|
@use "./components/nav/nav.scss";
|
|
25
25
|
@use "./components/form/form.scss";
|
|
26
|
+
// @use "./components/checkbox/checkbox.scss"; // Deprecated - checkbox styles now in form/checkbox.scss (imported by form.scss)
|
|
26
27
|
@use "./components/breadcrumbs/breadcrumb.scss";
|
|
27
28
|
@use "./components/list/list.scss";
|
|
28
29
|
@use "./components/alert/alert.scss";
|
package/src/index.ts
CHANGED
|
@@ -63,6 +63,13 @@ export {
|
|
|
63
63
|
export { Alert, type AlertProps } from "./components/alert/alert";
|
|
64
64
|
export { Field, type FieldProps } from "./components/form/fields";
|
|
65
65
|
export { Input, type InputProps } from "./components/form/inputs";
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Checkbox wrapper component (uses Input type="checkbox")
|
|
69
|
+
* This is the recommended checkbox component.
|
|
70
|
+
*/
|
|
71
|
+
export { Checkbox, type CheckboxProps } from "./components/form/checkbox";
|
|
72
|
+
|
|
66
73
|
export { Icon, type IconProps } from "./components/icons/icon";
|
|
67
74
|
export { Img } from "./components/images/img";
|
|
68
75
|
export type { ImgProps } from "./components/images/img.types";
|
|
@@ -121,7 +128,12 @@ export * from "./components/layout/landmarks";
|
|
|
121
128
|
export { Box, type BoxProps } from "./components/box/box";
|
|
122
129
|
export { Stack, type StackProps } from "./components/stack/stack";
|
|
123
130
|
export { Cluster, type ClusterProps } from "./components/cluster/cluster";
|
|
124
|
-
export {
|
|
131
|
+
export {
|
|
132
|
+
default as Grid,
|
|
133
|
+
GridItem,
|
|
134
|
+
type GridProps,
|
|
135
|
+
type GridItemProps,
|
|
136
|
+
} from "./components/grid/grid";
|
|
125
137
|
export { Row, type RowProps } from "./components/row/row";
|
|
126
138
|
export { Col, type ColProps } from "./components/col/col";
|
|
127
139
|
export { default as Flex } from "./components/flexbox/flex";
|
package/src/sass/_columns.scss
CHANGED
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
* accessed via JavaScript. SCSS uses literal values for media queries.
|
|
37
37
|
*/
|
|
38
38
|
:root {
|
|
39
|
-
--col-breakpoint-xs: 0rem;
|
|
40
|
-
--col-breakpoint-sm: 30rem;
|
|
41
|
-
--col-breakpoint-md: 48rem;
|
|
42
|
-
--col-breakpoint-lg: 64rem;
|
|
39
|
+
--col-breakpoint-xs: 0rem; /* 0px - base mobile */
|
|
40
|
+
--col-breakpoint-sm: 30rem; /* 480px - large phones */
|
|
41
|
+
--col-breakpoint-md: 48rem; /* 768px - tablets */
|
|
42
|
+
--col-breakpoint-lg: 64rem; /* 1024px - desktops */
|
|
43
43
|
|
|
44
44
|
/* Legacy support - keep for backward compatibility */
|
|
45
45
|
--col-breakpoint: var(--col-breakpoint-md);
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
* @media queries require compile-time values, not runtime CSS variables.
|
|
52
52
|
*/
|
|
53
53
|
$col-breakpoints: (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
"sm": 30rem,
|
|
55
|
+
"md": 48rem,
|
|
56
|
+
"lg": 64rem,
|
|
57
57
|
);
|
|
58
58
|
|
|
59
59
|
/* ============================================================================
|
|
@@ -432,8 +432,12 @@ $col-breakpoints: (
|
|
|
432
432
|
*/
|
|
433
433
|
@each $breakpoint, $min-width in $col-breakpoints {
|
|
434
434
|
@media (width >= #{$min-width}) {
|
|
435
|
-
.col-#{$breakpoint}-order-first {
|
|
436
|
-
|
|
435
|
+
.col-#{$breakpoint}-order-first {
|
|
436
|
+
order: -1;
|
|
437
|
+
}
|
|
438
|
+
.col-#{$breakpoint}-order-last {
|
|
439
|
+
order: 13;
|
|
440
|
+
}
|
|
437
441
|
|
|
438
442
|
@for $i from 0 through 12 {
|
|
439
443
|
.col-#{$breakpoint}-order-#{$i} {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sourceRoot":"","sources":["../../components/checkbox/checkbox.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAeA;AACE;AAAA;AAAA;EAIA;EACA;EACA;AAEA;AAAA;AAAA;EAIA;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;AAAA;AAAA;EAIA;EACA;EACA;AAEA;AAAA;AAAA;EAIA;EACA;EACA;EACA;EACA;AAEA;AAAA;AAAA;EAIA;EACA;EACA;EACA;AAAA;AAAA;AAIA;AAAA;AAAA;EAIA;EACA;EACA;EACA;AAAA;AAAA;AAIA;AAAA;AAAA;EAIA;EACA;AAEA;AAAA;AAAA;AAIA;EACA;EACA;EACA;EACA;AAEA;EACA;EACA;EACA;EACA;AAEA;EACA;EACA;EACA;EACA;AAEA;EACA;EACA;EACA;EACA;AAEA;AAAA;AAAA;AAIA;EACA;EACA;AAEA;EACA;EACA;EACA;AAEA;EACA;EACA;EACA;EACA;AAEA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;EACA;EACA;EACA;AAEA;EACA;EACA;;;AAGF;AAAA;AAAA;AAIA;EACE;EACA;EACA;AAEA;AAgBA;;AAfA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;AAAA;AAAA;EAGA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;AAAA;AAAA;EAGA;EACA;;;AAIJ;AAAA;AAAA;AAIA;EACE;EACA;EACA;EACA;;;AAGF;AAAA;AAAA;AAIA;EACE;EACA;EACA;EACA;EACA;;;AAGF;AAAA;AAAA;AAIA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;AAOA;AAMA;AAeA;AAcA;AAaA;;AAtDA;EACE;EACA;EACA;;AAIF;EACE;EACA;;AAIF;EACE;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAKJ;EACE;EACA;;AAEA;EACE;;AAGF;EACE;;AAKJ;EAEE;;AAEA;EACE;EACA;EACA;EACA;;AAKJ;EACE;;AAGF;EACE;;;AAIJ;AAAA;AAAA;AAIA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;AAAA;AAAA;AAIA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;AAAA;AAAA;AAIA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;AAAA;AAAA;AAIA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;;;AAGF;AACA;EACE;EACA;;;AAGF;AAAA;AAAA;AAIA;EACE;EACA;EACA;EACA;EACA;;;AAGF;AAAA;AAAA;AAIA;EACE;EACA;EACA;EACA;EACA;;;AAGF;AAAA;AAAA;AAKE;EACE;EACA;;AAGF;EACE;;AAGF;AAAA;EAEE","file":"checkbox.css"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checkbox Wrapper Component Styles
|
|
3
|
+
*
|
|
4
|
+
* Modern CSS architecture using :has() selector with ARIA attributes.
|
|
5
|
+
* No JavaScript class management required - ARIA attributes drive both
|
|
6
|
+
* accessibility AND styling.
|
|
7
|
+
*
|
|
8
|
+
* CSS Custom Properties:
|
|
9
|
+
* - --checkbox-gap: Space between checkbox and label (default: 0.5rem)
|
|
10
|
+
* - --checkbox-disabled-opacity: Opacity for disabled state (default: 0.6)
|
|
11
|
+
* - --checkbox-disabled-color: Label color when disabled (default: #6b7280)
|
|
12
|
+
* - --checkbox-label-fs: Label font size (default: 1rem)
|
|
13
|
+
* - --checkbox-label-lh: Label line height (default: 1.5)
|
|
14
|
+
* - --color-required: Required indicator color (default: #dc2626)
|
|
15
|
+
* - --checkbox-focus-ring-color: Focus ring color (default: #2563eb)
|
|
16
|
+
* - --checkbox-focus-ring-width: Focus ring width (default: 0.125rem)
|
|
17
|
+
* - --checkbox-focus-ring-offset: Focus ring offset (default: 0.125rem)
|
|
18
|
+
* - --checkbox-hover-label-color: Label color on hover (default: inherit)
|
|
19
|
+
* - --checkbox-error-label-color: Label color when invalid (default: #dc2626)
|
|
20
|
+
* - --checkbox-valid-label-color: Label color when valid (default: #16a34a)
|
|
21
|
+
* - --checkbox-focus-radius: Focus outline border radius (default: 0.125rem)
|
|
22
|
+
*
|
|
23
|
+
* WCAG 2.1 AA Compliance:
|
|
24
|
+
* - 2.4.7 Focus Visible: Focus-visible indicators with sufficient contrast
|
|
25
|
+
* - 2.3.3 Animation from Interactions: Respects prefers-reduced-motion
|
|
26
|
+
* - 3.3.1 Error Identification: Visual error states with color + text
|
|
27
|
+
* - 4.1.2 Name, Role, Value: ARIA attributes for assistive technologies
|
|
28
|
+
* - 1.4.13 Content on Hover or Focus: Hover states for visual feedback
|
|
29
|
+
*/
|
|
30
|
+
div:has(> input[type=checkbox]) {
|
|
31
|
+
display: flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
gap: var(--checkbox-gap, 0.5rem);
|
|
34
|
+
position: relative;
|
|
35
|
+
}
|
|
36
|
+
div:has(> input[type=checkbox]) > input[type=checkbox] {
|
|
37
|
+
flex-shrink: 0;
|
|
38
|
+
order: -1;
|
|
39
|
+
}
|
|
40
|
+
div:has(> input[type=checkbox]):not(:has(> input[aria-disabled=true])):hover .checkbox-label {
|
|
41
|
+
color: var(--checkbox-hover-label-color, inherit);
|
|
42
|
+
}
|
|
43
|
+
div:has(> input[type=checkbox]):has(> input:focus-visible) .checkbox-label {
|
|
44
|
+
outline: var(--checkbox-focus-ring-width, 0.125rem) solid var(--checkbox-focus-ring-color, #2563eb);
|
|
45
|
+
outline-offset: var(--checkbox-focus-ring-offset, 0.125rem);
|
|
46
|
+
border-radius: var(--checkbox-focus-radius, 0.125rem);
|
|
47
|
+
}
|
|
48
|
+
div:has(> input[type=checkbox]):has(> input[aria-disabled=true]) {
|
|
49
|
+
opacity: var(--checkbox-disabled-opacity, 0.6);
|
|
50
|
+
cursor: not-allowed;
|
|
51
|
+
}
|
|
52
|
+
div:has(> input[type=checkbox]):has(> input[aria-disabled=true]) .checkbox-label {
|
|
53
|
+
color: var(--checkbox-disabled-color, #6b7280);
|
|
54
|
+
cursor: not-allowed;
|
|
55
|
+
}
|
|
56
|
+
div:has(> input[type=checkbox]):has(> input[aria-invalid=true]) .checkbox-label {
|
|
57
|
+
color: var(--checkbox-error-label-color, #dc2626);
|
|
58
|
+
}
|
|
59
|
+
div:has(> input[type=checkbox]):has(> input[aria-invalid=false]:checked) .checkbox-label {
|
|
60
|
+
color: var(--checkbox-valid-label-color, #16a34a);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.checkbox-label {
|
|
64
|
+
cursor: pointer;
|
|
65
|
+
font-size: var(--checkbox-label-fs, 1rem);
|
|
66
|
+
line-height: var(--checkbox-label-lh, 1.5);
|
|
67
|
+
user-select: none;
|
|
68
|
+
margin: 0;
|
|
69
|
+
flex: 1;
|
|
70
|
+
min-width: 0;
|
|
71
|
+
transition: color 0.2s ease-in-out;
|
|
72
|
+
}
|
|
73
|
+
@media (prefers-reduced-motion: reduce) {
|
|
74
|
+
.checkbox-label {
|
|
75
|
+
transition: none;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
.checkbox-label .checkbox-required {
|
|
79
|
+
color: var(--color-required, #dc2626);
|
|
80
|
+
font-weight: 600;
|
|
81
|
+
margin-inline-start: 0.125rem;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@media (forced-colors: active) {
|
|
85
|
+
.checkbox-input {
|
|
86
|
+
forced-color-adjust: auto;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@container (max-width: 400px) {
|
|
91
|
+
div:has(> input[type=checkbox]) {
|
|
92
|
+
flex-direction: column;
|
|
93
|
+
align-items: flex-start;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/*# sourceMappingURL=checkbox.css.map */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sourceRoot":"","sources":["../../components/form/checkbox.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+BA;EACE;EACA;EACA;EACA;;AAGA;EACE;EACA;;AAKA;EACE;;AAOF;EACE;EAEA;EACA;;AAMJ;EACE;EACA;;AAEA;EACE;EACA;;AAOF;EACE;;AAMF;EACE;;;AAMN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EAXF;IAYI;;;AAGF;EACE;EACA;EACA;;;AAQF;EAHF;IAII;;;;AAMJ;EACE;IACE;IACA","file":"checkbox.css"}
|
package/src/styles/form/form.css
CHANGED
|
@@ -1,3 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checkbox Wrapper Component Styles
|
|
3
|
+
*
|
|
4
|
+
* Modern CSS architecture using :has() selector with ARIA attributes.
|
|
5
|
+
* No JavaScript class management required - ARIA attributes drive both
|
|
6
|
+
* accessibility AND styling.
|
|
7
|
+
*
|
|
8
|
+
* CSS Custom Properties:
|
|
9
|
+
* - --checkbox-gap: Space between checkbox and label (default: 0.5rem)
|
|
10
|
+
* - --checkbox-disabled-opacity: Opacity for disabled state (default: 0.6)
|
|
11
|
+
* - --checkbox-disabled-color: Label color when disabled (default: #6b7280)
|
|
12
|
+
* - --checkbox-label-fs: Label font size (default: 1rem)
|
|
13
|
+
* - --checkbox-label-lh: Label line height (default: 1.5)
|
|
14
|
+
* - --color-required: Required indicator color (default: #dc2626)
|
|
15
|
+
* - --checkbox-focus-ring-color: Focus ring color (default: #2563eb)
|
|
16
|
+
* - --checkbox-focus-ring-width: Focus ring width (default: 0.125rem)
|
|
17
|
+
* - --checkbox-focus-ring-offset: Focus ring offset (default: 0.125rem)
|
|
18
|
+
* - --checkbox-hover-label-color: Label color on hover (default: inherit)
|
|
19
|
+
* - --checkbox-error-label-color: Label color when invalid (default: #dc2626)
|
|
20
|
+
* - --checkbox-valid-label-color: Label color when valid (default: #16a34a)
|
|
21
|
+
* - --checkbox-focus-radius: Focus outline border radius (default: 0.125rem)
|
|
22
|
+
*
|
|
23
|
+
* WCAG 2.1 AA Compliance:
|
|
24
|
+
* - 2.4.7 Focus Visible: Focus-visible indicators with sufficient contrast
|
|
25
|
+
* - 2.3.3 Animation from Interactions: Respects prefers-reduced-motion
|
|
26
|
+
* - 3.3.1 Error Identification: Visual error states with color + text
|
|
27
|
+
* - 4.1.2 Name, Role, Value: ARIA attributes for assistive technologies
|
|
28
|
+
* - 1.4.13 Content on Hover or Focus: Hover states for visual feedback
|
|
29
|
+
*/
|
|
30
|
+
div:has(> input[type=checkbox]) {
|
|
31
|
+
display: flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
gap: var(--checkbox-gap, 0.5rem);
|
|
34
|
+
position: relative;
|
|
35
|
+
}
|
|
36
|
+
div:has(> input[type=checkbox]) > input[type=checkbox] {
|
|
37
|
+
flex-shrink: 0;
|
|
38
|
+
order: -1;
|
|
39
|
+
}
|
|
40
|
+
div:has(> input[type=checkbox]):not(:has(> input[aria-disabled=true])):hover .checkbox-label {
|
|
41
|
+
color: var(--checkbox-hover-label-color, inherit);
|
|
42
|
+
}
|
|
43
|
+
div:has(> input[type=checkbox]):has(> input:focus-visible) .checkbox-label {
|
|
44
|
+
outline: var(--checkbox-focus-ring-width, 0.125rem) solid var(--checkbox-focus-ring-color, #2563eb);
|
|
45
|
+
outline-offset: var(--checkbox-focus-ring-offset, 0.125rem);
|
|
46
|
+
border-radius: var(--checkbox-focus-radius, 0.125rem);
|
|
47
|
+
}
|
|
48
|
+
div:has(> input[type=checkbox]):has(> input[aria-disabled=true]) {
|
|
49
|
+
opacity: var(--checkbox-disabled-opacity, 0.6);
|
|
50
|
+
cursor: not-allowed;
|
|
51
|
+
}
|
|
52
|
+
div:has(> input[type=checkbox]):has(> input[aria-disabled=true]) .checkbox-label {
|
|
53
|
+
color: var(--checkbox-disabled-color, #6b7280);
|
|
54
|
+
cursor: not-allowed;
|
|
55
|
+
}
|
|
56
|
+
div:has(> input[type=checkbox]):has(> input[aria-invalid=true]) .checkbox-label {
|
|
57
|
+
color: var(--checkbox-error-label-color, #dc2626);
|
|
58
|
+
}
|
|
59
|
+
div:has(> input[type=checkbox]):has(> input[aria-invalid=false]:checked) .checkbox-label {
|
|
60
|
+
color: var(--checkbox-valid-label-color, #16a34a);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.checkbox-label {
|
|
64
|
+
cursor: pointer;
|
|
65
|
+
font-size: var(--checkbox-label-fs, 1rem);
|
|
66
|
+
line-height: var(--checkbox-label-lh, 1.5);
|
|
67
|
+
user-select: none;
|
|
68
|
+
margin: 0;
|
|
69
|
+
flex: 1;
|
|
70
|
+
min-width: 0;
|
|
71
|
+
transition: color 0.2s ease-in-out;
|
|
72
|
+
}
|
|
73
|
+
@media (prefers-reduced-motion: reduce) {
|
|
74
|
+
.checkbox-label {
|
|
75
|
+
transition: none;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
.checkbox-label .checkbox-required {
|
|
79
|
+
color: var(--color-required, #dc2626);
|
|
80
|
+
font-weight: 600;
|
|
81
|
+
margin-inline-start: 0.125rem;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@media (forced-colors: active) {
|
|
85
|
+
.checkbox-input {
|
|
86
|
+
forced-color-adjust: auto;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@container (max-width: 400px) {
|
|
91
|
+
div:has(> input[type=checkbox]) {
|
|
92
|
+
flex-direction: column;
|
|
93
|
+
align-items: flex-start;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
1
96
|
:root {
|
|
2
97
|
--input-border-color: gray;
|
|
3
98
|
--input-appearance: none;
|
|
@@ -19,6 +114,22 @@
|
|
|
19
114
|
--placeholder-fs: smaller;
|
|
20
115
|
--form-direction: column;
|
|
21
116
|
--select-arrow: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20'><polyline points='6,9 10,13 14,9' stroke='%23000000' stroke-width='1.5' fill='none' /></svg>");
|
|
117
|
+
/* ==========================================================================
|
|
118
|
+
Size Tokens
|
|
119
|
+
========================================================================== */
|
|
120
|
+
--checkbox-size-sm: 1rem; /* 16px */
|
|
121
|
+
--checkbox-size-md: 1.25rem; /* 20px */
|
|
122
|
+
--checkbox-size-lg: 1.5rem; /* 24px */
|
|
123
|
+
/* ==========================================================================
|
|
124
|
+
Base Properties
|
|
125
|
+
========================================================================== */
|
|
126
|
+
--checkbox-size: var(--checkbox-size-md);
|
|
127
|
+
--checkbox-bg: #ffffff;
|
|
128
|
+
--checkbox-border: 0.125rem solid #6b7280; /* 2px border */
|
|
129
|
+
--checkbox-border-color: #6b7280; /* Gray 500 */
|
|
130
|
+
--checkbox-radius: 0.25rem; /* 4px */
|
|
131
|
+
--checkbox-cursor: pointer;
|
|
132
|
+
--checkbox-transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
22
133
|
}
|
|
23
134
|
|
|
24
135
|
form {
|
|
@@ -35,9 +146,7 @@ form label {
|
|
|
35
146
|
display: block;
|
|
36
147
|
}
|
|
37
148
|
|
|
38
|
-
input
|
|
39
|
-
textarea,
|
|
40
|
-
select {
|
|
149
|
+
input {
|
|
41
150
|
-webkit-appearance: var(--input-appearance);
|
|
42
151
|
-moz-appearance: var(--input-appearance);
|
|
43
152
|
appearance: var(--input-appearance);
|
|
@@ -49,6 +158,19 @@ select {
|
|
|
49
158
|
border-radius: var(--input-radius);
|
|
50
159
|
background-color: var(--input-bg, #fff);
|
|
51
160
|
}
|
|
161
|
+
input:focus-visible, input:focus {
|
|
162
|
+
outline: var(--input-focus-outline);
|
|
163
|
+
outline-offset: var(--input-focus-outline-offset);
|
|
164
|
+
}
|
|
165
|
+
input[aria-disabled=true], input:disabled {
|
|
166
|
+
--input-border-color: lightgray;
|
|
167
|
+
background-color: var(--input-disabled-bg);
|
|
168
|
+
opacity: var(--input-disabled-opacity);
|
|
169
|
+
cursor: var(--input-disabled-cursor);
|
|
170
|
+
text-transform: capitalize;
|
|
171
|
+
text-decoration: line-through;
|
|
172
|
+
}
|
|
173
|
+
|
|
52
174
|
input[type]:not([type=checkbox], [type=radio])::placeholder,
|
|
53
175
|
textarea::placeholder,
|
|
54
176
|
select::placeholder {
|
|
@@ -57,14 +179,6 @@ select::placeholder {
|
|
|
57
179
|
font-size: var(--placeholder-fs);
|
|
58
180
|
text-transform: capitalize;
|
|
59
181
|
}
|
|
60
|
-
input[type]:not([type=checkbox], [type=radio]):focus-visible, input[type]:not([type=checkbox], [type=radio]):focus,
|
|
61
|
-
textarea:focus-visible,
|
|
62
|
-
textarea:focus,
|
|
63
|
-
select:focus-visible,
|
|
64
|
-
select:focus {
|
|
65
|
-
outline: var(--input-focus-outline);
|
|
66
|
-
outline-offset: var(--input-focus-outline-offset);
|
|
67
|
-
}
|
|
68
182
|
input[type]:not([type=checkbox], [type=radio])[aria-required=true]::placeholder,
|
|
69
183
|
textarea[aria-required=true]::placeholder,
|
|
70
184
|
select[aria-required=true]::placeholder {
|
|
@@ -76,17 +190,19 @@ textarea[aria-required=true]::placeholder::after,
|
|
|
76
190
|
select[aria-required=true]::placeholder::after {
|
|
77
191
|
content: "* ";
|
|
78
192
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
193
|
+
|
|
194
|
+
input[type=checkbox] {
|
|
195
|
+
opacity: 1;
|
|
196
|
+
width: var(--checkbox-size);
|
|
197
|
+
height: var(--checkbox-size);
|
|
198
|
+
margin: 0;
|
|
199
|
+
cursor: var(--checkbox-cursor);
|
|
200
|
+
flex-shrink: 0;
|
|
201
|
+
}
|
|
202
|
+
input[type=checkbox]:checked {
|
|
203
|
+
background-color: var(--checkbox-bg, red);
|
|
204
|
+
outline: rebeccapurple solid 2px;
|
|
205
|
+
background: #dbeafe;
|
|
90
206
|
}
|
|
91
207
|
|
|
92
208
|
select {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sourceRoot":"","sources":["../../components/form/form.scss"],"names":[],"mappings":"AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EAGA;EACA;EACA;EAGA;EACA;EACA;EAEA;EACA;;;AAGF;EACE;EACA;EACA;;AACA;EACE;EACA;EACA;;AAGF;EACE;;;AAIJ;
|
|
1
|
+
{"version":3,"sourceRoot":"","sources":["../../components/form/checkbox.scss","../../components/form/form.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+BA;EACE;EACA;EACA;EACA;;AAGA;EACE;EACA;;AAKA;EACE;;AAOF;EACE;EAEA;EACA;;AAMJ;EACE;EACA;;AAEA;EACE;EACA;;AAOF;EACE;;AAMF;EACE;;;AAMN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EAXF;IAYI;;;AAGF;EACE;EACA;EACA;;;AAQF;EAHF;IAII;;;;AAMJ;EACE;IACE;IACA;;;AC3HJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EAGA;EACA;EACA;EAGA;EACA;EACA;EAEA;EACA;AAEA;AAAA;AAAA;EAIA;EACA;EACA;AAEA;AAAA;AAAA;EAIA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;AACA;EACE;EACA;EACA;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAEE;EACA;;AAGF;EAEE;EACA;EACA;EACA;EACA;EACA;;;AAOF;AAAA;AAAA;EACE;EACA;EACA;EACA;;AAGA;AAAA;AAAA;EACE;EACA;;AACA;AAAA;AAAA;EACE;;;AAMR;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA","file":"form.css"}
|