@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
|
@@ -0,0 +1,129 @@
|
|
|
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
|
+
|
|
31
|
+
// Checkbox wrapper styling using modern :has() selector
|
|
32
|
+
div:has(> input[type="checkbox"]) {
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
gap: var(--checkbox-gap, 0.5rem);
|
|
36
|
+
position: relative;
|
|
37
|
+
|
|
38
|
+
// Ensure checkbox doesn't overlap label
|
|
39
|
+
> input[type="checkbox"] {
|
|
40
|
+
flex-shrink: 0; // Prevent checkbox from shrinking
|
|
41
|
+
order: -1; // Ensure checkbox appears first
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Hover state (only when not disabled) - WCAG 1.4.13 Content on Hover
|
|
45
|
+
&:not(:has(> input[aria-disabled="true"])):hover {
|
|
46
|
+
.checkbox-label {
|
|
47
|
+
color: var(--checkbox-hover-label-color, inherit);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Focus-visible state for keyboard navigation - WCAG 2.4.7 Focus Visible
|
|
52
|
+
// Using :focus-visible to only show focus ring for keyboard users
|
|
53
|
+
&:has(> input:focus-visible) {
|
|
54
|
+
.checkbox-label {
|
|
55
|
+
outline: var(--checkbox-focus-ring-width, 0.125rem) solid
|
|
56
|
+
var(--checkbox-focus-ring-color, #2563eb);
|
|
57
|
+
outline-offset: var(--checkbox-focus-ring-offset, 0.125rem);
|
|
58
|
+
border-radius: var(--checkbox-focus-radius, 0.125rem);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Disabled state styling using aria-disabled attribute selector
|
|
63
|
+
// WCAG 4.1.2: aria-disabled remains focusable for screen readers
|
|
64
|
+
&:has(> input[aria-disabled="true"]) {
|
|
65
|
+
opacity: var(--checkbox-disabled-opacity, 0.6);
|
|
66
|
+
cursor: not-allowed;
|
|
67
|
+
|
|
68
|
+
.checkbox-label {
|
|
69
|
+
color: var(--checkbox-disabled-color, #6b7280);
|
|
70
|
+
cursor: not-allowed;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Invalid state styling - WCAG 3.3.1 Error Identification
|
|
75
|
+
// Color alone is not sufficient - error message text must also be provided
|
|
76
|
+
&:has(> input[aria-invalid="true"]) {
|
|
77
|
+
.checkbox-label {
|
|
78
|
+
color: var(--checkbox-error-label-color, #dc2626);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Valid state styling (when checked and explicitly marked valid)
|
|
83
|
+
&:has(> input[aria-invalid="false"]:checked) {
|
|
84
|
+
.checkbox-label {
|
|
85
|
+
color: var(--checkbox-valid-label-color, #16a34a);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Checkbox label styling
|
|
91
|
+
.checkbox-label {
|
|
92
|
+
cursor: pointer;
|
|
93
|
+
font-size: var(--checkbox-label-fs, 1rem);
|
|
94
|
+
line-height: var(--checkbox-label-lh, 1.5);
|
|
95
|
+
user-select: none;
|
|
96
|
+
margin: 0;
|
|
97
|
+
flex: 1; // Allow label to take remaining space
|
|
98
|
+
min-width: 0; // Prevent flex item from overflowing
|
|
99
|
+
transition: color 0.2s ease-in-out;
|
|
100
|
+
|
|
101
|
+
// Respect user's motion preferences - WCAG 2.3.3 Animation from Interactions
|
|
102
|
+
@media (prefers-reduced-motion: reduce) {
|
|
103
|
+
transition: none;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.checkbox-required {
|
|
107
|
+
color: var(--color-required, #dc2626);
|
|
108
|
+
font-weight: 600;
|
|
109
|
+
margin-inline-start: 0.125rem;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Checkbox input element
|
|
114
|
+
.checkbox-input {
|
|
115
|
+
// High contrast mode support (Windows High Contrast Mode)
|
|
116
|
+
// forced-color-adjust: auto respects user's color scheme
|
|
117
|
+
@media (forced-colors: active) {
|
|
118
|
+
forced-color-adjust: auto;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Optional: Container query support for responsive layouts
|
|
123
|
+
// Stacks checkbox and label vertically on small containers
|
|
124
|
+
@container (max-width: 400px) {
|
|
125
|
+
div:has(> input[type="checkbox"]) {
|
|
126
|
+
flex-direction: column;
|
|
127
|
+
align-items: flex-start;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Input, type InputProps } from "./inputs";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Props for the Checkbox component
|
|
6
|
+
*
|
|
7
|
+
* A simplified, checkbox-specific interface that wraps the Input component.
|
|
8
|
+
* Provides a boolean onChange API and automatic label association.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* // Controlled mode
|
|
13
|
+
* <Checkbox
|
|
14
|
+
* id="terms"
|
|
15
|
+
* label="I accept the terms"
|
|
16
|
+
* checked={accepted}
|
|
17
|
+
* onChange={setAccepted}
|
|
18
|
+
* required
|
|
19
|
+
* />
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* // Uncontrolled mode with default
|
|
25
|
+
* <Checkbox
|
|
26
|
+
* id="newsletter"
|
|
27
|
+
* label="Subscribe to newsletter"
|
|
28
|
+
* defaultChecked={true}
|
|
29
|
+
* />
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export interface CheckboxProps extends Omit<
|
|
33
|
+
InputProps,
|
|
34
|
+
'type' | 'value' | 'onChange' | 'defaultValue' | 'placeholder'
|
|
35
|
+
> {
|
|
36
|
+
/**
|
|
37
|
+
* Unique identifier for the checkbox input.
|
|
38
|
+
* Required for proper label association via htmlFor attribute.
|
|
39
|
+
*
|
|
40
|
+
* @required
|
|
41
|
+
* @see {@link https://www.w3.org/WAI/WCAG21/Understanding/name-role-value.html|WCAG 4.1.2 Name, Role, Value}
|
|
42
|
+
*/
|
|
43
|
+
id: string;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Label text or React node displayed next to the checkbox.
|
|
47
|
+
* Automatically associated with the checkbox via htmlFor.
|
|
48
|
+
*
|
|
49
|
+
* @required
|
|
50
|
+
* @see {@link https://www.w3.org/WAI/WCAG21/Understanding/labels-or-instructions.html|WCAG 3.3.2 Labels or Instructions}
|
|
51
|
+
*/
|
|
52
|
+
label: React.ReactNode;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Controlled mode: Current checked state.
|
|
56
|
+
* When provided, component becomes controlled and requires onChange handler.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```tsx
|
|
60
|
+
* const [checked, setChecked] = useState(false);
|
|
61
|
+
* <Checkbox id="opt" label="Option" checked={checked} onChange={setChecked} />
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
checked?: boolean;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Uncontrolled mode: Initial checked state.
|
|
68
|
+
* Use this for forms where React doesn't need to track the checkbox state.
|
|
69
|
+
*
|
|
70
|
+
* @default false
|
|
71
|
+
* @example
|
|
72
|
+
* ```tsx
|
|
73
|
+
* <Checkbox id="opt" label="Option" defaultChecked={true} />
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
defaultChecked?: boolean;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Form submission value when checkbox is checked.
|
|
80
|
+
* This is the value submitted with the form when the checkbox is checked.
|
|
81
|
+
*
|
|
82
|
+
* @default "on"
|
|
83
|
+
*/
|
|
84
|
+
value?: string;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Change handler with simplified boolean API.
|
|
88
|
+
* Receives true when checked, false when unchecked.
|
|
89
|
+
*
|
|
90
|
+
* @param checked - The new checked state
|
|
91
|
+
* @example
|
|
92
|
+
* ```tsx
|
|
93
|
+
* <Checkbox
|
|
94
|
+
* id="opt"
|
|
95
|
+
* label="Option"
|
|
96
|
+
* onChange={(checked) => console.log('Checked:', checked)}
|
|
97
|
+
* />
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
onChange?: (checked: boolean) => void;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Optional custom CSS classes for the wrapper div.
|
|
104
|
+
* Applied alongside automatic checkbox wrapper styling.
|
|
105
|
+
*/
|
|
106
|
+
classes?: string;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Optional custom CSS classes for the input element.
|
|
110
|
+
*
|
|
111
|
+
* @default "checkbox-input"
|
|
112
|
+
*/
|
|
113
|
+
inputClasses?: string;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* CSS custom properties for theming.
|
|
117
|
+
*
|
|
118
|
+
* Available variables:
|
|
119
|
+
* - --checkbox-gap: Space between checkbox and label (default: 0.5rem)
|
|
120
|
+
* - --checkbox-disabled-opacity: Opacity for disabled state (default: 0.6)
|
|
121
|
+
* - --checkbox-disabled-color: Label color when disabled (default: #6b7280)
|
|
122
|
+
* - --checkbox-label-fs: Label font size (default: 1rem)
|
|
123
|
+
* - --checkbox-label-lh: Label line height (default: 1.5)
|
|
124
|
+
* - --color-required: Required indicator color (default: #dc2626)
|
|
125
|
+
* - --checkbox-focus-ring-color: Focus ring color (default: #2563eb)
|
|
126
|
+
* - --checkbox-focus-ring-width: Focus ring width (default: 0.125rem)
|
|
127
|
+
* - --checkbox-focus-ring-offset: Focus ring offset (default: 0.125rem)
|
|
128
|
+
* - --checkbox-hover-label-color: Label color on hover
|
|
129
|
+
* - --checkbox-error-label-color: Label color when invalid (default: #dc2626)
|
|
130
|
+
* - --checkbox-valid-label-color: Label color when valid (default: #16a34a)
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```tsx
|
|
134
|
+
* <Checkbox
|
|
135
|
+
* id="custom"
|
|
136
|
+
* label="Custom styled"
|
|
137
|
+
* styles={{
|
|
138
|
+
* '--checkbox-gap': '1rem',
|
|
139
|
+
* '--checkbox-focus-ring-color': '#ff0000'
|
|
140
|
+
* }}
|
|
141
|
+
* />
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
styles?: React.CSSProperties;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Checkbox - Accessible checkbox input with automatic label association
|
|
149
|
+
*
|
|
150
|
+
* A thin wrapper around the Input component that provides a checkbox-specific API
|
|
151
|
+
* with simplified boolean onChange and automatic label rendering. Leverages all
|
|
152
|
+
* validation, disabled state, and ARIA logic from the base Input component.
|
|
153
|
+
*
|
|
154
|
+
* **Key Features:**
|
|
155
|
+
* - ✅ Boolean onChange API (`onChange={(checked) => ...}`)
|
|
156
|
+
* - ✅ Automatic label association via htmlFor
|
|
157
|
+
* - ✅ WCAG 2.1 AA compliant (uses aria-disabled pattern)
|
|
158
|
+
* - ✅ Supports both controlled and uncontrolled modes
|
|
159
|
+
* - ✅ Required indicator with asterisk
|
|
160
|
+
* - ✅ Validation states (invalid, valid, none)
|
|
161
|
+
* - ✅ Error messages and hint text via Input component
|
|
162
|
+
* - ✅ Customizable via CSS custom properties
|
|
163
|
+
* - ✅ Keyboard accessible (Space to toggle)
|
|
164
|
+
* - ✅ Focus-visible indicators
|
|
165
|
+
* - ✅ High contrast mode support
|
|
166
|
+
*
|
|
167
|
+
* @component
|
|
168
|
+
* @example
|
|
169
|
+
* ```tsx
|
|
170
|
+
* // Basic checkbox
|
|
171
|
+
* <Checkbox id="terms" label="I accept the terms and conditions" />
|
|
172
|
+
* ```
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```tsx
|
|
176
|
+
* // Controlled checkbox with validation
|
|
177
|
+
* const [agreed, setAgreed] = useState(false);
|
|
178
|
+
* <Checkbox
|
|
179
|
+
* id="terms"
|
|
180
|
+
* label="I accept the terms"
|
|
181
|
+
* checked={agreed}
|
|
182
|
+
* onChange={setAgreed}
|
|
183
|
+
* required
|
|
184
|
+
* validationState={agreed ? "valid" : "invalid"}
|
|
185
|
+
* errorMessage={!agreed ? "You must accept the terms" : undefined}
|
|
186
|
+
* />
|
|
187
|
+
* ```
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```tsx
|
|
191
|
+
* // Disabled checkbox
|
|
192
|
+
* <Checkbox
|
|
193
|
+
* id="disabled"
|
|
194
|
+
* label="Disabled option"
|
|
195
|
+
* disabled
|
|
196
|
+
* defaultChecked
|
|
197
|
+
* />
|
|
198
|
+
* ```
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```tsx
|
|
202
|
+
* // Custom styling
|
|
203
|
+
* <Checkbox
|
|
204
|
+
* id="custom"
|
|
205
|
+
* label="Large checkbox"
|
|
206
|
+
* styles={{ '--checkbox-gap': '1rem' }}
|
|
207
|
+
* />
|
|
208
|
+
* ```
|
|
209
|
+
*
|
|
210
|
+
* @param {CheckboxProps} props - Component props
|
|
211
|
+
* @param {React.Ref<HTMLInputElement>} ref - Forwarded ref to the input element
|
|
212
|
+
* @returns {JSX.Element} Checkbox wrapper with input and label
|
|
213
|
+
*
|
|
214
|
+
* @see {@link https://www.w3.org/WAI/WCAG21/Understanding/name-role-value.html|WCAG 4.1.2 Name, Role, Value}
|
|
215
|
+
* @see {@link https://www.w3.org/WAI/WCAG21/Understanding/focus-visible.html|WCAG 2.4.7 Focus Visible}
|
|
216
|
+
* @see {@link https://www.w3.org/WAI/WCAG21/Understanding/error-identification.html|WCAG 3.3.1 Error Identification}
|
|
217
|
+
*/
|
|
218
|
+
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
|
219
|
+
({
|
|
220
|
+
id, label, checked, defaultChecked, value = "on",
|
|
221
|
+
onChange, classes, inputClasses, styles,
|
|
222
|
+
name, disabled, required, validationState,
|
|
223
|
+
errorMessage, hintText, onBlur, onFocus, autoFocus,
|
|
224
|
+
...props
|
|
225
|
+
}, ref) => {
|
|
226
|
+
|
|
227
|
+
// Convert boolean onChange to native event handler
|
|
228
|
+
// Memoized to prevent unnecessary re-renders of child components
|
|
229
|
+
const handleChange = React.useCallback(
|
|
230
|
+
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
231
|
+
onChange?.(e.target.checked);
|
|
232
|
+
},
|
|
233
|
+
[onChange]
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
// Controlled vs uncontrolled mode
|
|
237
|
+
const isControlled = checked !== undefined;
|
|
238
|
+
const checkedProp = isControlled ? { checked } : {};
|
|
239
|
+
const defaultCheckedProp = !isControlled && defaultChecked !== undefined
|
|
240
|
+
? { defaultChecked }
|
|
241
|
+
: {};
|
|
242
|
+
|
|
243
|
+
// Dev-only validation: Warn if switching between controlled/uncontrolled
|
|
244
|
+
// This helps catch common React bugs where state management changes mid-lifecycle
|
|
245
|
+
const wasControlledRef = React.useRef(isControlled);
|
|
246
|
+
|
|
247
|
+
React.useEffect(() => {
|
|
248
|
+
if (process.env.NODE_ENV === 'development') {
|
|
249
|
+
if (wasControlledRef.current !== isControlled) {
|
|
250
|
+
// eslint-disable-next-line no-console
|
|
251
|
+
console.warn(
|
|
252
|
+
`Checkbox with id="${id}" is changing from ${
|
|
253
|
+
wasControlledRef.current ? 'controlled' : 'uncontrolled'
|
|
254
|
+
} to ${
|
|
255
|
+
isControlled ? 'controlled' : 'uncontrolled'
|
|
256
|
+
}. This is likely a bug. ` +
|
|
257
|
+
`Decide between using "checked" (controlled) or "defaultChecked" (uncontrolled) and stick with it.`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
wasControlledRef.current = isControlled;
|
|
261
|
+
}
|
|
262
|
+
}, [isControlled, id]);
|
|
263
|
+
|
|
264
|
+
// Note: No need to manage disabled class - CSS uses :has() selector with aria-disabled
|
|
265
|
+
// The Input component handles aria-disabled automatically via useDisabledState hook
|
|
266
|
+
return (
|
|
267
|
+
<div className={classes} style={styles}>
|
|
268
|
+
<Input
|
|
269
|
+
ref={ref}
|
|
270
|
+
type="checkbox"
|
|
271
|
+
id={id}
|
|
272
|
+
name={name}
|
|
273
|
+
value={value}
|
|
274
|
+
{...checkedProp}
|
|
275
|
+
{...defaultCheckedProp}
|
|
276
|
+
classes={inputClasses || "checkbox-input"}
|
|
277
|
+
disabled={disabled}
|
|
278
|
+
required={required}
|
|
279
|
+
validationState={validationState}
|
|
280
|
+
errorMessage={errorMessage}
|
|
281
|
+
hintText={hintText}
|
|
282
|
+
onChange={handleChange}
|
|
283
|
+
onBlur={onBlur}
|
|
284
|
+
onFocus={onFocus}
|
|
285
|
+
autoFocus={autoFocus}
|
|
286
|
+
{...props}
|
|
287
|
+
/>
|
|
288
|
+
<label htmlFor={id} className="checkbox-label">
|
|
289
|
+
{label}
|
|
290
|
+
{required && (
|
|
291
|
+
<span className="checkbox-required" aria-label="required">
|
|
292
|
+
{" *"}
|
|
293
|
+
</span>
|
|
294
|
+
)}
|
|
295
|
+
</label>
|
|
296
|
+
</div>
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
Checkbox.displayName = "Checkbox";
|
|
302
|
+
export default Checkbox;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Import checkbox wrapper component styles
|
|
2
|
+
@use "./checkbox";
|
|
3
|
+
|
|
1
4
|
:root {
|
|
2
5
|
--input-border-color: gray;
|
|
3
6
|
--input-appearance: none;
|
|
@@ -26,6 +29,26 @@
|
|
|
26
29
|
|
|
27
30
|
--form-direction: column;
|
|
28
31
|
--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>");
|
|
32
|
+
|
|
33
|
+
/* ==========================================================================
|
|
34
|
+
Size Tokens
|
|
35
|
+
========================================================================== */
|
|
36
|
+
|
|
37
|
+
--checkbox-size-sm: 1rem; /* 16px */
|
|
38
|
+
--checkbox-size-md: 1.25rem; /* 20px */
|
|
39
|
+
--checkbox-size-lg: 1.5rem; /* 24px */
|
|
40
|
+
|
|
41
|
+
/* ==========================================================================
|
|
42
|
+
Base Properties
|
|
43
|
+
========================================================================== */
|
|
44
|
+
|
|
45
|
+
--checkbox-size: var(--checkbox-size-md);
|
|
46
|
+
--checkbox-bg: #ffffff;
|
|
47
|
+
--checkbox-border: 0.125rem solid #6b7280; /* 2px border */
|
|
48
|
+
--checkbox-border-color: #6b7280; /* Gray 500 */
|
|
49
|
+
--checkbox-radius: 0.25rem; /* 4px */
|
|
50
|
+
--checkbox-cursor: pointer;
|
|
51
|
+
--checkbox-transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
29
52
|
}
|
|
30
53
|
|
|
31
54
|
form {
|
|
@@ -43,9 +66,7 @@ form {
|
|
|
43
66
|
}
|
|
44
67
|
}
|
|
45
68
|
|
|
46
|
-
input
|
|
47
|
-
textarea,
|
|
48
|
-
select {
|
|
69
|
+
input {
|
|
49
70
|
-webkit-appearance: var(--input-appearance);
|
|
50
71
|
-moz-appearance: var(--input-appearance);
|
|
51
72
|
appearance: var(--input-appearance);
|
|
@@ -57,37 +78,55 @@ select {
|
|
|
57
78
|
border-radius: var(--input-radius);
|
|
58
79
|
background-color: var(--input-bg, #fff);
|
|
59
80
|
|
|
81
|
+
&:focus-visible,
|
|
82
|
+
&:focus {
|
|
83
|
+
outline: var(--input-focus-outline);
|
|
84
|
+
outline-offset: var(--input-focus-outline-offset);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
&[aria-disabled="true"],
|
|
88
|
+
&:disabled {
|
|
89
|
+
--input-border-color: lightgray;
|
|
90
|
+
background-color: var(--input-disabled-bg);
|
|
91
|
+
opacity: var(--input-disabled-opacity);
|
|
92
|
+
cursor: var(--input-disabled-cursor);
|
|
93
|
+
text-transform: capitalize;
|
|
94
|
+
text-decoration: line-through;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
input[type]:not([type="checkbox"], [type="radio"]),
|
|
99
|
+
textarea,
|
|
100
|
+
select {
|
|
60
101
|
&::placeholder {
|
|
61
102
|
color: var(--placeholder-color);
|
|
62
103
|
font-style: var(--placeholder-style);
|
|
63
104
|
font-size: var(--placeholder-fs);
|
|
64
105
|
text-transform: capitalize;
|
|
65
106
|
}
|
|
66
|
-
|
|
67
|
-
&:focus-visible, &:focus {
|
|
68
|
-
outline: var(--input-focus-outline);
|
|
69
|
-
outline-offset: var(--input-focus-outline-offset);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
&[aria-required='true'] {
|
|
107
|
+
&[aria-required="true"] {
|
|
74
108
|
&::placeholder {
|
|
75
109
|
color: var(--color-required, var(--placeholder-color));
|
|
76
110
|
font-weight: 600;
|
|
77
111
|
&::after {
|
|
78
|
-
content:
|
|
112
|
+
content: "* ";
|
|
79
113
|
}
|
|
80
114
|
}
|
|
81
115
|
}
|
|
116
|
+
}
|
|
82
117
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
118
|
+
input[type="checkbox"] {
|
|
119
|
+
opacity: 1;
|
|
120
|
+
width: var(--checkbox-size);
|
|
121
|
+
height: var(--checkbox-size);
|
|
122
|
+
margin: 0;
|
|
123
|
+
cursor: var(--checkbox-cursor);
|
|
124
|
+
flex-shrink: 0; // Prevent checkbox from shrinking in flex layout
|
|
125
|
+
|
|
126
|
+
&:checked {
|
|
127
|
+
background-color: var(--checkbox-bg, red);
|
|
128
|
+
outline: rebeccapurple solid 2px;
|
|
129
|
+
background: #dbeafe;
|
|
91
130
|
}
|
|
92
131
|
}
|
|
93
132
|
|
|
@@ -376,3 +376,9 @@ export interface SelectProps extends Omit<React.ComponentPropsWithoutRef<'select
|
|
|
376
376
|
*/
|
|
377
377
|
children?: React.ReactNode
|
|
378
378
|
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Checkbox component props
|
|
382
|
+
* Re-exported from checkbox.tsx for convenience
|
|
383
|
+
*/
|
|
384
|
+
export type { CheckboxProps } from './checkbox'
|