@varialkit/checkbox 0.1.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/docs.md ADDED
@@ -0,0 +1,31 @@
1
+ # Checkbox
2
+
3
+ Checkboxes let users toggle a boolean option on or off. Use them for independent choices or multi-select lists.
4
+
5
+ ## Usage
6
+
7
+ ```tsx
8
+ import { Checkbox } from "@solara/checkbox";
9
+
10
+ export function Example() {
11
+ return (
12
+ <Checkbox
13
+ label="Email me updates"
14
+ helperText="We send product news twice a month."
15
+ />
16
+ );
17
+ }
18
+ ```
19
+
20
+ ## Props
21
+
22
+ | Prop | Type | Default | Description |
23
+ | --- | --- | --- | --- |
24
+ | `label` | `string` | — | Label text next to the checkbox. |
25
+ | `helperText` | `string` | — | Helper text below the checkbox. |
26
+ | `size` | `"small" | "medium" | "large"` | `"medium"` | Controls checkbox sizing. |
27
+ | `isInvalid` | `boolean` | `false` | Shows an error state. |
28
+ | `isDisabled` | `boolean` | `false` | Disables the checkbox. |
29
+ | `indeterminate` | `boolean` | `false` | Shows a mixed state. |
30
+
31
+ Checkbox also accepts standard input props like `checked`, `defaultChecked`, and `onChange`.
package/examples.tsx ADDED
@@ -0,0 +1,110 @@
1
+ import React from "react";
2
+ import { Checkbox } from "./src/Checkbox";
3
+ import type { CheckboxProps } from "./src/Checkbox.types";
4
+
5
+ export const stories = {
6
+ playground: {
7
+ title: "Playground",
8
+ description: "Tweak the props to explore the Checkbox API.",
9
+ render: (props: CheckboxProps & { showLabel?: boolean; showHelper?: boolean }) => {
10
+ const { showLabel = true, showHelper = true, ...checkboxProps } = props;
11
+ const [checked, setChecked] = React.useState<boolean>(
12
+ checkboxProps.checked ?? checkboxProps.defaultChecked ?? false
13
+ );
14
+
15
+ React.useEffect(() => {
16
+ if (checkboxProps.checked !== undefined) {
17
+ setChecked(Boolean(checkboxProps.checked));
18
+ }
19
+ }, [checkboxProps.checked]);
20
+
21
+ return (
22
+ <Checkbox
23
+ {...checkboxProps}
24
+ checked={checked}
25
+ onChange={(event) => setChecked(event.currentTarget.checked)}
26
+ label={showLabel ? checkboxProps.label : undefined}
27
+ helperText={showHelper ? checkboxProps.helperText : undefined}
28
+ />
29
+ );
30
+ },
31
+ controls: [
32
+ { name: "showLabel", type: "boolean", label: "Show Label" },
33
+ { name: "label", type: "text" },
34
+ { name: "showHelper", type: "boolean", label: "Show Helper" },
35
+ { name: "helperText", type: "text" },
36
+ {
37
+ name: "size",
38
+ type: "select",
39
+ options: ["small", "medium", "large"],
40
+ },
41
+ { name: "checked", type: "boolean" },
42
+ { name: "indeterminate", type: "boolean" },
43
+ { name: "isInvalid", type: "boolean" },
44
+ { name: "isDisabled", type: "boolean" },
45
+ ],
46
+ initialProps: {
47
+ showLabel: false,
48
+ label: "Notify me about updates",
49
+ showHelper: false,
50
+ helperText: "You can change this later.",
51
+ size: "medium",
52
+ checked: false,
53
+ indeterminate: false,
54
+ isInvalid: false,
55
+ isDisabled: false,
56
+ },
57
+ },
58
+ states: {
59
+ title: "States",
60
+ description: "Checked, indeterminate, and disabled checkboxes.",
61
+ showProps: false,
62
+ render: () => (
63
+ <div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
64
+ <Checkbox label="Unchecked" />
65
+ <Checkbox label="Checked" defaultChecked />
66
+ <Checkbox label="Indeterminate" indeterminate />
67
+ <Checkbox label="Disabled" isDisabled />
68
+ <Checkbox label="Invalid" isInvalid helperText="Required" />
69
+ </div>
70
+ ),
71
+ code: `import { Checkbox } from "@solara/checkbox";
72
+
73
+ export function Example() {
74
+ return (
75
+ <div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
76
+ <Checkbox label="Unchecked" />
77
+ <Checkbox label="Checked" defaultChecked />
78
+ <Checkbox label="Indeterminate" indeterminate />
79
+ <Checkbox label="Disabled" isDisabled />
80
+ <Checkbox label="Invalid" isInvalid helperText="Required" />
81
+ </div>
82
+ );
83
+ }
84
+ `,
85
+ },
86
+ sizes: {
87
+ title: "Sizes",
88
+ description: "Checkbox size options.",
89
+ showProps: false,
90
+ render: () => (
91
+ <div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
92
+ <Checkbox label="Small" size="small" />
93
+ <Checkbox label="Medium" size="medium" />
94
+ <Checkbox label="Large" size="large" />
95
+ </div>
96
+ ),
97
+ code: `import { Checkbox } from "@solara/checkbox";
98
+
99
+ export function Example() {
100
+ return (
101
+ <div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
102
+ <Checkbox label="Small" size="small" />
103
+ <Checkbox label="Medium" size="medium" />
104
+ <Checkbox label="Large" size="large" />
105
+ </div>
106
+ );
107
+ }
108
+ `,
109
+ },
110
+ };
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@varialkit/checkbox",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "src/index.ts",
6
+ "types": "src/index.ts",
7
+ "exports": {
8
+ ".": "./src/index.ts",
9
+ "./examples": "./examples/index.tsx"
10
+ },
11
+ "files": [
12
+ "src",
13
+ "docs.md",
14
+ "examples",
15
+ "examples.tsx"
16
+ ],
17
+ "peerDependencies": {
18
+ "react": "^19.0.0"
19
+ },
20
+ "devDependencies": {
21
+ "@types/react": "19.0.10",
22
+ "react": "19.0.0"
23
+ }
24
+ }
@@ -0,0 +1,222 @@
1
+ .solara-checkbox {
2
+ --checkbox-size: 18px;
3
+ --checkbox-font-size: var(--font-size-body-scaled);
4
+ --checkbox-helper-font-size: var(--font-size-footnote-scaled);
5
+ --checkbox-gap: var(--space-2);
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: calc(var(--space-1) * var(--spacing-multiplier));
9
+ }
10
+
11
+ .solara-checkbox--size-small {
12
+ --checkbox-size: 16px;
13
+ --checkbox-font-size: var(--font-size-caption-scaled);
14
+ --checkbox-helper-font-size: var(--font-size-footnote-scaled);
15
+ }
16
+
17
+ .solara-checkbox--size-large {
18
+ --checkbox-size: 20px;
19
+ --checkbox-font-size: var(--font-size-h5-scaled);
20
+ --checkbox-helper-font-size: var(--font-size-caption-scaled);
21
+ }
22
+
23
+ .solara-checkbox__control {
24
+ display: flex;
25
+ align-items: flex-start;
26
+ gap: calc(var(--checkbox-gap) * var(--spacing-multiplier));
27
+ }
28
+
29
+ .solara-checkbox__input {
30
+ position: absolute;
31
+ opacity: 0;
32
+ width: 0;
33
+ height: 0;
34
+ margin: 0;
35
+ padding: 0;
36
+ border: none;
37
+ appearance: none;
38
+ outline: none;
39
+ }
40
+
41
+ .solara-checkbox__label {
42
+ display: flex;
43
+ align-items: flex-start;
44
+ gap: calc(var(--checkbox-gap) * var(--spacing-multiplier));
45
+ cursor: pointer;
46
+ user-select: none;
47
+ font-size: var(--checkbox-font-size);
48
+ color: var(--color-text-primary);
49
+ }
50
+
51
+ .solara-checkbox__box {
52
+ --checkbox-border-color: var(--color-surface-400);
53
+ --checkbox-bg-color: var(--color-surface-0);
54
+ --checkbox-hover-border-color: var(--color-primary);
55
+ --checkbox-hover-bg-color: var(--color-surface-200);
56
+ --checkbox-focus-border-color: var(--color-primary);
57
+ --checkbox-focus-halo-color: var(--color-primary-focus);
58
+ position: relative;
59
+ display: flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+ width: var(--checkbox-size);
63
+ height: var(--checkbox-size);
64
+ border-radius: 6px;
65
+ border: 2px solid var(--checkbox-border-color);
66
+ background-color: var(--checkbox-bg-color);
67
+ transition: border-color 0.2s ease, background-color 0.2s ease,
68
+ box-shadow 0.2s ease, transform 0.2s ease;
69
+ flex-shrink: 0;
70
+ }
71
+
72
+
73
+
74
+ .solara-checkbox__box::before {
75
+ content: "";
76
+ position: absolute;
77
+ width: 60%;
78
+ height: 2px;
79
+ background-color: var(--color-on-primary);
80
+ opacity: 0;
81
+ transition: opacity 0.15s ease;
82
+ }
83
+
84
+ .solara-checkbox--custom-icon .solara-checkbox__box::before,
85
+ .solara-checkbox--custom-icon .solara-checkbox__box::after {
86
+ opacity: 0;
87
+ }
88
+
89
+ .solara-checkbox__icon {
90
+ position: absolute;
91
+ inset: 0;
92
+ display: inline-flex;
93
+ align-items: center;
94
+ justify-content: center;
95
+ opacity: 0;
96
+ transform: translateY(-5%);
97
+ transition: opacity 0.15s ease;
98
+ color: var(--color-on-primary);
99
+ }
100
+
101
+ .solara-checkbox__text {
102
+ line-height: 1.4;
103
+ margin-top: 1px;
104
+ color: var(--color-text-primary);
105
+ }
106
+
107
+ .solara-checkbox__helper {
108
+ font-size: var(--checkbox-helper-font-size);
109
+ color: var(--color-text-secondary);
110
+ margin-left: calc(
111
+ var(--checkbox-size) +
112
+ (var(--checkbox-gap) * var(--spacing-multiplier))
113
+ );
114
+ line-height: 1.4;
115
+ }
116
+
117
+ .solara-checkbox__helper--invalid {
118
+ color: var(--color-text-alert);
119
+ }
120
+
121
+ .solara-checkbox__input:checked + .solara-checkbox__label .solara-checkbox__box {
122
+ background-color: var(--color-primary);
123
+ border-color: var(--color-primary);
124
+ }
125
+
126
+
127
+
128
+ .solara-checkbox__input:checked
129
+ + .solara-checkbox__label
130
+ .solara-checkbox__icon--check {
131
+ opacity: 1;
132
+ }
133
+
134
+ /* Fallback for controlled state or environments where :checked styles are unreliable. */
135
+ .solara-checkbox--checked .solara-checkbox__box {
136
+ background-color: var(--color-primary);
137
+ border-color: var(--color-primary);
138
+ }
139
+
140
+
141
+
142
+
143
+
144
+ .solara-checkbox--checked .solara-checkbox__icon--check {
145
+ opacity: 1;
146
+ }
147
+
148
+ .solara-checkbox--indeterminate .solara-checkbox__box {
149
+ background-color: var(--color-primary);
150
+ border-color: var(--color-primary);
151
+ }
152
+
153
+ .solara-checkbox--indeterminate .solara-checkbox__box::before {
154
+ opacity: 1;
155
+ }
156
+
157
+
158
+
159
+ .solara-checkbox--indeterminate .solara-checkbox__icon--check {
160
+ opacity: 0;
161
+ }
162
+
163
+ .solara-checkbox--indeterminate .solara-checkbox__icon--indeterminate {
164
+ opacity: 1;
165
+ }
166
+
167
+ .solara-checkbox__input:focus-visible
168
+ + .solara-checkbox__label
169
+ .solara-checkbox__box {
170
+ border-color: var(--checkbox-focus-border-color);
171
+ box-shadow: 0 0 0 2px var(--color-surface-0), 0 0 0 4px var(--checkbox-focus-halo-color);
172
+ }
173
+
174
+ .solara-checkbox__input:checked:focus-visible
175
+ + .solara-checkbox__label
176
+ .solara-checkbox__box,
177
+ .solara-checkbox__input:indeterminate:focus-visible
178
+ + .solara-checkbox__label
179
+ .solara-checkbox__box {
180
+ border-color: var(--checkbox-focus-border-color);
181
+ box-shadow: 0 0 0 2px var(--color-surface-0), 0 0 0 4px var(--checkbox-focus-halo-color);
182
+ }
183
+
184
+ .solara-checkbox__input:not(:disabled)
185
+ + .solara-checkbox__label:hover
186
+ .solara-checkbox__box {
187
+ border-color: var(--checkbox-hover-border-color);
188
+ background-color: var(--checkbox-hover-bg-color);
189
+ }
190
+
191
+ .solara-checkbox__input:active:not(:disabled)
192
+ + .solara-checkbox__label
193
+ .solara-checkbox__box {
194
+ transform: scale(0.98);
195
+ }
196
+
197
+ .solara-checkbox--invalid .solara-checkbox__box {
198
+ border-color: var(--color-destructive);
199
+ --checkbox-focus-border-color: var(--color-destructive);
200
+ --checkbox-focus-halo-color: var(--color-destructive-focus);
201
+ }
202
+
203
+ .solara-checkbox--invalid .solara-checkbox__input:checked
204
+ + .solara-checkbox__label
205
+ .solara-checkbox__box {
206
+ background-color: var(--color-destructive);
207
+ border-color: var(--color-destructive);
208
+ }
209
+
210
+ .solara-checkbox--disabled .solara-checkbox__label {
211
+ cursor: not-allowed;
212
+ opacity: 0.6;
213
+ }
214
+
215
+ .solara-checkbox--disabled .solara-checkbox__box {
216
+ background-color: var(--color-surface-200);
217
+ border-color: var(--color-surface-300);
218
+ }
219
+
220
+ .solara-checkbox--disabled .solara-checkbox__text {
221
+ color: var(--color-text-secondary);
222
+ }
@@ -0,0 +1,138 @@
1
+ import { Icon } from "@solara/icons";
2
+ import React, { forwardRef, useEffect, useRef } from "react";
3
+ import type { CheckboxProps, CheckboxSize } from "./Checkbox.types";
4
+ import "./Checkbox.scss";
5
+
6
+ const sizeAliasMap: Record<CheckboxSize, "small" | "medium" | "large"> = {
7
+ sm: "small",
8
+ md: "medium",
9
+ lg: "large",
10
+ small: "small",
11
+ medium: "medium",
12
+ large: "large",
13
+ };
14
+
15
+ /**
16
+ * Checkbox component for boolean input.
17
+ */
18
+ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
19
+ (
20
+ {
21
+ className,
22
+ size = "medium",
23
+ label,
24
+ helperText,
25
+ error = false,
26
+ isInvalid,
27
+ isDisabled,
28
+ indeterminate = false,
29
+ checkIcon,
30
+ indeterminateIcon,
31
+ disabled,
32
+ id,
33
+ checked,
34
+ defaultChecked,
35
+ onChange,
36
+ ...restProps
37
+ },
38
+ ref
39
+ ) => {
40
+ const resolvedSize = sizeAliasMap[size];
41
+ const inputId = id ?? React.useId();
42
+ const internalRef = useRef<HTMLInputElement>(null);
43
+ const mergedRef = (node: HTMLInputElement | null) => {
44
+ internalRef.current = node;
45
+ if (typeof ref === "function") {
46
+ ref(node);
47
+ } else if (ref) {
48
+ ref.current = node;
49
+ }
50
+ };
51
+
52
+ const resolvedDisabled = isDisabled ?? disabled ?? false;
53
+ const resolvedInvalid = isInvalid ?? error ?? false;
54
+ const isControlled = checked !== undefined;
55
+ const [uncontrolledChecked, setUncontrolledChecked] = React.useState<boolean>(
56
+ Boolean(defaultChecked)
57
+ );
58
+ const resolvedChecked = isControlled ? checked : uncontrolledChecked;
59
+
60
+ useEffect(() => {
61
+ if (internalRef.current) {
62
+ internalRef.current.indeterminate = Boolean(indeterminate);
63
+ }
64
+ }, [indeterminate]);
65
+
66
+ const hasCustomIcons = Boolean(checkIcon || indeterminateIcon);
67
+
68
+ const rootClasses = [
69
+ "solara-checkbox",
70
+ `solara-checkbox--size-${resolvedSize}`,
71
+ resolvedChecked ? "solara-checkbox--checked" : null,
72
+ resolvedDisabled ? "solara-checkbox--disabled" : null,
73
+ resolvedInvalid ? "solara-checkbox--invalid" : null,
74
+ indeterminate ? "solara-checkbox--indeterminate" : null,
75
+ hasCustomIcons ? "solara-checkbox--custom-icon" : null,
76
+ className,
77
+ ]
78
+ .filter(Boolean)
79
+ .join(" ");
80
+
81
+ const helperClasses = [
82
+ "solara-checkbox__helper",
83
+ resolvedInvalid ? "solara-checkbox__helper--invalid" : null,
84
+ ]
85
+ .filter(Boolean)
86
+ .join(" ");
87
+
88
+ return (
89
+ <div className={rootClasses}>
90
+ <div className="solara-checkbox__control">
91
+ <input
92
+ {...restProps}
93
+ ref={mergedRef}
94
+ type="checkbox"
95
+ id={inputId}
96
+ className="solara-checkbox__input"
97
+ disabled={resolvedDisabled}
98
+ aria-invalid={resolvedInvalid || undefined}
99
+ aria-checked={indeterminate ? "mixed" : undefined}
100
+ readOnly={isControlled && !onChange}
101
+ checked={resolvedChecked}
102
+ onChange={(event) => {
103
+ if (!isControlled) {
104
+ setUncontrolledChecked(event.currentTarget.checked);
105
+ }
106
+ onChange?.(event);
107
+ }}
108
+ />
109
+ <label className="solara-checkbox__label" htmlFor={inputId}>
110
+ <span className="solara-checkbox__box">
111
+ {checkIcon ? (
112
+ <span className="solara-checkbox__icon solara-checkbox__icon--check">
113
+ {checkIcon}
114
+ </span>
115
+ ) : (
116
+ <Icon
117
+ className="solara-checkbox__icon solara-checkbox__icon--check"
118
+ name="checkmark_16"
119
+ style={{ color: "white" }}
120
+ />
121
+ )}
122
+ {indeterminateIcon ? (
123
+ <span className="solara-checkbox__icon solara-checkbox__icon--indeterminate">
124
+ {indeterminateIcon}
125
+ </span>
126
+ ) : null}
127
+ </span>
128
+ {label ? <span className="solara-checkbox__text">{label}</span> : null}
129
+ </label>
130
+ </div>
131
+
132
+ {helperText ? <p className={helperClasses}>{helperText}</p> : null}
133
+ </div>
134
+ );
135
+ }
136
+ );
137
+
138
+ Checkbox.displayName = "Checkbox";
@@ -0,0 +1,43 @@
1
+ import React from "react";
2
+
3
+ export type CheckboxSize = "small" | "medium" | "large" | "sm" | "md" | "lg";
4
+
5
+ export interface CheckboxProps
6
+ extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> {
7
+ /**
8
+ * Size of the checkbox.
9
+ */
10
+ size?: CheckboxSize;
11
+ /**
12
+ * Label for the checkbox.
13
+ */
14
+ label?: string;
15
+ /**
16
+ * Helper text to display below the checkbox.
17
+ */
18
+ helperText?: string;
19
+ /**
20
+ * Error state.
21
+ */
22
+ error?: boolean;
23
+ /**
24
+ * Invalid state (preferred over `error`).
25
+ */
26
+ isInvalid?: boolean;
27
+ /**
28
+ * Disabled state (preferred over `disabled`).
29
+ */
30
+ isDisabled?: boolean;
31
+ /**
32
+ * Indeterminate state (shows dash instead of check).
33
+ */
34
+ indeterminate?: boolean;
35
+ /**
36
+ * Custom check icon.
37
+ */
38
+ checkIcon?: React.ReactNode;
39
+ /**
40
+ * Custom indeterminate icon.
41
+ */
42
+ indeterminateIcon?: React.ReactNode;
43
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./Checkbox";
2
+ export type { CheckboxProps } from "./Checkbox.types";