@asnewyla/unstyled-checkbox 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 XD
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # @asnewyla/unstyled-checkbox
2
+
3
+ Unstyled, accessible checkbox input primitive. Handles checked/indeterminate
4
+ state, focus, and invalid-state tracking without imposing any visual
5
+ styling — the base layer under `@asnewyla/checkbox`.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @asnewyla/unstyled-checkbox
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```tsx
16
+ import { UnstyledCheckbox } from '@asnewyla/unstyled-checkbox';
17
+
18
+ // Uncontrolled
19
+ <UnstyledCheckbox aria-label="Accept terms" defaultChecked={false} />
20
+
21
+ // Controlled
22
+ <UnstyledCheckbox
23
+ aria-label="Accept terms"
24
+ checked={accepted}
25
+ onChange={(e) => setAccepted(e.target.checked)}
26
+ />
27
+
28
+ // Indeterminate ("select all" style)
29
+ <UnstyledCheckbox aria-label="Select all" indeterminate />
30
+
31
+ // Invalid state
32
+ <UnstyledCheckbox aria-label="Accept terms" invalid />
33
+ ```
34
+
35
+ ### Props
36
+
37
+ | Prop | Type | Default |
38
+ |---|---|---|
39
+ | `checked` / `defaultChecked` | `boolean` | — |
40
+ | `indeterminate` | `boolean` | `false` |
41
+ | `onChange` | `(event: React.ChangeEvent<HTMLInputElement>) => void` | — |
42
+ | `disabled` | `boolean` | `false` |
43
+ | `invalid` | `boolean` | `false` |
44
+
45
+ Uncontrolled by default, same as a native `<input type="checkbox">` — pass
46
+ `checked` + `onChange` to control it yourself. `indeterminate` is a
47
+ DOM-only property with no HTML attribute or plain-JSX-prop equivalent, so
48
+ it's applied imperatively via a ref internally; you don't need to do
49
+ anything differently as a consumer.
50
+
51
+ State is exposed via `data-checked`, `data-indeterminate`, `data-focused`,
52
+ `data-disabled`, and `data-invalid`/`aria-invalid` — style off these
53
+ attributes in `@asnewyla/checkbox` (or your own styled wrapper) rather than
54
+ `:checked`/`:indeterminate` pseudo-classes, for the same DevTools-visible,
55
+ test-stable reasons every other `@asnewyla/*` primitive uses `data-*` for
56
+ state.
57
+
58
+ ## License
59
+
60
+ MIT
@@ -0,0 +1,20 @@
1
+ import * as react from 'react';
2
+
3
+ type UnstyledCheckboxProps = {
4
+ checked?: boolean;
5
+ defaultChecked?: boolean;
6
+ indeterminate?: boolean;
7
+ onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
8
+ disabled?: boolean;
9
+ invalid?: boolean;
10
+ } & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'checked' | 'defaultChecked' | 'onChange' | 'disabled' | 'invalid'>;
11
+ declare const UnstyledCheckbox: react.ForwardRefExoticComponent<{
12
+ checked?: boolean;
13
+ defaultChecked?: boolean;
14
+ indeterminate?: boolean;
15
+ onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
16
+ disabled?: boolean;
17
+ invalid?: boolean;
18
+ } & Omit<react.InputHTMLAttributes<HTMLInputElement>, "type" | "checked" | "defaultChecked" | "onChange" | "disabled" | "invalid"> & react.RefAttributes<HTMLInputElement>>;
19
+
20
+ export { UnstyledCheckbox, type UnstyledCheckboxProps };
@@ -0,0 +1,20 @@
1
+ import * as react from 'react';
2
+
3
+ type UnstyledCheckboxProps = {
4
+ checked?: boolean;
5
+ defaultChecked?: boolean;
6
+ indeterminate?: boolean;
7
+ onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
8
+ disabled?: boolean;
9
+ invalid?: boolean;
10
+ } & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'checked' | 'defaultChecked' | 'onChange' | 'disabled' | 'invalid'>;
11
+ declare const UnstyledCheckbox: react.ForwardRefExoticComponent<{
12
+ checked?: boolean;
13
+ defaultChecked?: boolean;
14
+ indeterminate?: boolean;
15
+ onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
16
+ disabled?: boolean;
17
+ invalid?: boolean;
18
+ } & Omit<react.InputHTMLAttributes<HTMLInputElement>, "type" | "checked" | "defaultChecked" | "onChange" | "disabled" | "invalid"> & react.RefAttributes<HTMLInputElement>>;
19
+
20
+ export { UnstyledCheckbox, type UnstyledCheckboxProps };
package/dist/index.js ADDED
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ // src/components/UnstyledCheckbox.tsx
7
+ function booleanToString(value) {
8
+ return value ? "true" : void 0;
9
+ }
10
+ function UnstyledCheckboxInner({
11
+ checked,
12
+ defaultChecked,
13
+ indeterminate,
14
+ onChange,
15
+ disabled,
16
+ invalid,
17
+ onFocus,
18
+ onBlur,
19
+ ...rest
20
+ }, ref) {
21
+ const isControlled = checked !== void 0;
22
+ const [internalChecked, setInternalChecked] = react.useState(
23
+ defaultChecked ?? false
24
+ );
25
+ const isChecked = isControlled ? checked : internalChecked;
26
+ const [focused, setFocused] = react.useState(false);
27
+ const inputRef = react.useRef(null);
28
+ react.useEffect(() => {
29
+ if (inputRef.current) {
30
+ inputRef.current.indeterminate = indeterminate ?? false;
31
+ }
32
+ }, [indeterminate]);
33
+ const setRefs = (node) => {
34
+ inputRef.current = node;
35
+ if (typeof ref === "function") {
36
+ ref(node);
37
+ } else if (ref) {
38
+ ref.current = node;
39
+ }
40
+ };
41
+ const handleChange = (e) => {
42
+ if (!isControlled) {
43
+ setInternalChecked(e.target.checked);
44
+ }
45
+ onChange?.(e);
46
+ };
47
+ const handleFocus = (e) => {
48
+ setFocused(true);
49
+ onFocus?.(e);
50
+ };
51
+ const handleBlur = (e) => {
52
+ setFocused(false);
53
+ onBlur?.(e);
54
+ };
55
+ return /* @__PURE__ */ jsxRuntime.jsx(
56
+ "input",
57
+ {
58
+ ref: setRefs,
59
+ ...rest,
60
+ type: "checkbox",
61
+ checked: isChecked,
62
+ onChange: handleChange,
63
+ "data-checked": booleanToString(isChecked),
64
+ "data-indeterminate": booleanToString(indeterminate),
65
+ onFocus: handleFocus,
66
+ onBlur: handleBlur,
67
+ "data-focused": booleanToString(focused),
68
+ disabled,
69
+ "data-disabled": booleanToString(disabled),
70
+ "data-invalid": booleanToString(invalid),
71
+ "aria-invalid": invalid ? "true" : void 0
72
+ }
73
+ );
74
+ }
75
+ var UnstyledCheckbox = react.forwardRef(UnstyledCheckboxInner);
76
+
77
+ exports.UnstyledCheckbox = UnstyledCheckbox;
78
+ //# sourceMappingURL=index.js.map
79
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/UnstyledCheckbox.tsx"],"names":["useState","useRef","useEffect","jsx","forwardRef"],"mappings":";;;;;;AAoBA,SAAS,gBAAgB,KAAA,EAAgD;AACvE,EAAA,OAAO,QAAQ,MAAA,GAAS,MAAA;AAC1B;AAEA,SAAS,qBAAA,CACP;AAAA,EACE,OAAA;AAAA,EACA,cAAA;AAAA,EACA,aAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA,GAAG;AACL,CAAA,EACA,GAAA,EACA;AACA,EAAA,MAAM,eAAe,OAAA,KAAY,MAAA;AACjC,EAAA,MAAM,CAAC,eAAA,EAAiB,kBAAkB,CAAA,GAAIA,cAAA;AAAA,IAC5C,cAAA,IAAkB;AAAA,GACpB;AACA,EAAA,MAAM,SAAA,GAAY,eAAe,OAAA,GAAU,eAAA;AAE3C,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,eAAS,KAAK,CAAA;AAE5C,EAAA,MAAM,QAAA,GAAWC,aAAgC,IAAI,CAAA;AAErD,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,SAAS,OAAA,EAAS;AACpB,MAAA,QAAA,CAAS,OAAA,CAAQ,gBAAgB,aAAA,IAAiB,KAAA;AAAA,IACpD;AAAA,EACF,CAAA,EAAG,CAAC,aAAa,CAAC,CAAA;AAElB,EAAA,MAAM,OAAA,GAAU,CAAC,IAAA,KAAkC;AACjD,IAAA,QAAA,CAAS,OAAA,GAAU,IAAA;AACnB,IAAA,IAAI,OAAO,QAAQ,UAAA,EAAY;AAC7B,MAAA,GAAA,CAAI,IAAI,CAAA;AAAA,IACV,WAAW,GAAA,EAAK;AACd,MAAA,GAAA,CAAI,OAAA,GAAU,IAAA;AAAA,IAChB;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,YAAA,GAAe,CAAC,CAAA,KAA2C;AAC/D,IAAA,IAAI,CAAC,YAAA,EAAc;AACjB,MAAA,kBAAA,CAAmB,CAAA,CAAE,OAAO,OAAO,CAAA;AAAA,IACrC;AACA,IAAA,QAAA,GAAW,CAAC,CAAA;AAAA,EACd,CAAA;AAEA,EAAA,MAAM,WAAA,GAAc,CAAC,CAAA,KAA0C;AAC7D,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,OAAA,GAAU,CAAC,CAAA;AAAA,EACb,CAAA;AAEA,EAAA,MAAM,UAAA,GAAa,CAAC,CAAA,KAA0C;AAC5D,IAAA,UAAA,CAAW,KAAK,CAAA;AAChB,IAAA,MAAA,GAAS,CAAC,CAAA;AAAA,EACZ,CAAA;AAEA,EAAA,uBACEC,cAAA;AAAA,IAAC,OAAA;AAAA,IAAA;AAAA,MACC,GAAA,EAAK,OAAA;AAAA,MACJ,GAAG,IAAA;AAAA,MACJ,IAAA,EAAK,UAAA;AAAA,MACL,OAAA,EAAS,SAAA;AAAA,MACT,QAAA,EAAU,YAAA;AAAA,MACV,cAAA,EAAc,gBAAgB,SAAS,CAAA;AAAA,MACvC,oBAAA,EAAoB,gBAAgB,aAAa,CAAA;AAAA,MACjD,OAAA,EAAS,WAAA;AAAA,MACT,MAAA,EAAQ,UAAA;AAAA,MACR,cAAA,EAAc,gBAAgB,OAAO,CAAA;AAAA,MACrC,QAAA;AAAA,MACA,eAAA,EAAe,gBAAgB,QAAQ,CAAA;AAAA,MACvC,cAAA,EAAc,gBAAgB,OAAO,CAAA;AAAA,MACrC,cAAA,EAAc,UAAU,MAAA,GAAS;AAAA;AAAA,GACnC;AAEJ;AAEO,IAAM,gBAAA,GAAmBC,iBAG9B,qBAAqB","file":"index.js","sourcesContent":["import {\n type ForwardedRef,\n forwardRef,\n useEffect,\n useRef,\n useState,\n} from 'react';\n\nexport type UnstyledCheckboxProps = {\n checked?: boolean;\n defaultChecked?: boolean;\n indeterminate?: boolean;\n onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;\n disabled?: boolean;\n invalid?: boolean;\n} & Omit<\n React.InputHTMLAttributes<HTMLInputElement>,\n 'type' | 'checked' | 'defaultChecked' | 'onChange' | 'disabled' | 'invalid'\n>;\n\nfunction booleanToString(value: boolean | undefined): string | undefined {\n return value ? 'true' : undefined;\n}\n\nfunction UnstyledCheckboxInner(\n {\n checked,\n defaultChecked,\n indeterminate,\n onChange,\n disabled,\n invalid,\n onFocus,\n onBlur,\n ...rest\n }: UnstyledCheckboxProps,\n ref: ForwardedRef<HTMLInputElement>,\n) {\n const isControlled = checked !== undefined;\n const [internalChecked, setInternalChecked] = useState(\n defaultChecked ?? false,\n );\n const isChecked = isControlled ? checked : internalChecked;\n\n const [focused, setFocused] = useState(false);\n\n const inputRef = useRef<HTMLInputElement | null>(null);\n\n useEffect(() => {\n if (inputRef.current) {\n inputRef.current.indeterminate = indeterminate ?? false;\n }\n }, [indeterminate]);\n\n const setRefs = (node: HTMLInputElement | null) => {\n inputRef.current = node;\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n };\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n if (!isControlled) {\n setInternalChecked(e.target.checked);\n }\n onChange?.(e);\n };\n\n const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {\n setFocused(true);\n onFocus?.(e);\n };\n\n const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {\n setFocused(false);\n onBlur?.(e);\n };\n\n return (\n <input\n ref={setRefs}\n {...rest}\n type=\"checkbox\"\n checked={isChecked}\n onChange={handleChange}\n data-checked={booleanToString(isChecked)}\n data-indeterminate={booleanToString(indeterminate)}\n onFocus={handleFocus}\n onBlur={handleBlur}\n data-focused={booleanToString(focused)}\n disabled={disabled}\n data-disabled={booleanToString(disabled)}\n data-invalid={booleanToString(invalid)}\n aria-invalid={invalid ? 'true' : undefined}\n />\n );\n}\n\nexport const UnstyledCheckbox = forwardRef<\n HTMLInputElement,\n UnstyledCheckboxProps\n>(UnstyledCheckboxInner);\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,77 @@
1
+ import { forwardRef, useState, useRef, useEffect } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ // src/components/UnstyledCheckbox.tsx
5
+ function booleanToString(value) {
6
+ return value ? "true" : void 0;
7
+ }
8
+ function UnstyledCheckboxInner({
9
+ checked,
10
+ defaultChecked,
11
+ indeterminate,
12
+ onChange,
13
+ disabled,
14
+ invalid,
15
+ onFocus,
16
+ onBlur,
17
+ ...rest
18
+ }, ref) {
19
+ const isControlled = checked !== void 0;
20
+ const [internalChecked, setInternalChecked] = useState(
21
+ defaultChecked ?? false
22
+ );
23
+ const isChecked = isControlled ? checked : internalChecked;
24
+ const [focused, setFocused] = useState(false);
25
+ const inputRef = useRef(null);
26
+ useEffect(() => {
27
+ if (inputRef.current) {
28
+ inputRef.current.indeterminate = indeterminate ?? false;
29
+ }
30
+ }, [indeterminate]);
31
+ const setRefs = (node) => {
32
+ inputRef.current = node;
33
+ if (typeof ref === "function") {
34
+ ref(node);
35
+ } else if (ref) {
36
+ ref.current = node;
37
+ }
38
+ };
39
+ const handleChange = (e) => {
40
+ if (!isControlled) {
41
+ setInternalChecked(e.target.checked);
42
+ }
43
+ onChange?.(e);
44
+ };
45
+ const handleFocus = (e) => {
46
+ setFocused(true);
47
+ onFocus?.(e);
48
+ };
49
+ const handleBlur = (e) => {
50
+ setFocused(false);
51
+ onBlur?.(e);
52
+ };
53
+ return /* @__PURE__ */ jsx(
54
+ "input",
55
+ {
56
+ ref: setRefs,
57
+ ...rest,
58
+ type: "checkbox",
59
+ checked: isChecked,
60
+ onChange: handleChange,
61
+ "data-checked": booleanToString(isChecked),
62
+ "data-indeterminate": booleanToString(indeterminate),
63
+ onFocus: handleFocus,
64
+ onBlur: handleBlur,
65
+ "data-focused": booleanToString(focused),
66
+ disabled,
67
+ "data-disabled": booleanToString(disabled),
68
+ "data-invalid": booleanToString(invalid),
69
+ "aria-invalid": invalid ? "true" : void 0
70
+ }
71
+ );
72
+ }
73
+ var UnstyledCheckbox = forwardRef(UnstyledCheckboxInner);
74
+
75
+ export { UnstyledCheckbox };
76
+ //# sourceMappingURL=index.mjs.map
77
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/UnstyledCheckbox.tsx"],"names":[],"mappings":";;;;AAoBA,SAAS,gBAAgB,KAAA,EAAgD;AACvE,EAAA,OAAO,QAAQ,MAAA,GAAS,MAAA;AAC1B;AAEA,SAAS,qBAAA,CACP;AAAA,EACE,OAAA;AAAA,EACA,cAAA;AAAA,EACA,aAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA,GAAG;AACL,CAAA,EACA,GAAA,EACA;AACA,EAAA,MAAM,eAAe,OAAA,KAAY,MAAA;AACjC,EAAA,MAAM,CAAC,eAAA,EAAiB,kBAAkB,CAAA,GAAI,QAAA;AAAA,IAC5C,cAAA,IAAkB;AAAA,GACpB;AACA,EAAA,MAAM,SAAA,GAAY,eAAe,OAAA,GAAU,eAAA;AAE3C,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,KAAK,CAAA;AAE5C,EAAA,MAAM,QAAA,GAAW,OAAgC,IAAI,CAAA;AAErD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,SAAS,OAAA,EAAS;AACpB,MAAA,QAAA,CAAS,OAAA,CAAQ,gBAAgB,aAAA,IAAiB,KAAA;AAAA,IACpD;AAAA,EACF,CAAA,EAAG,CAAC,aAAa,CAAC,CAAA;AAElB,EAAA,MAAM,OAAA,GAAU,CAAC,IAAA,KAAkC;AACjD,IAAA,QAAA,CAAS,OAAA,GAAU,IAAA;AACnB,IAAA,IAAI,OAAO,QAAQ,UAAA,EAAY;AAC7B,MAAA,GAAA,CAAI,IAAI,CAAA;AAAA,IACV,WAAW,GAAA,EAAK;AACd,MAAA,GAAA,CAAI,OAAA,GAAU,IAAA;AAAA,IAChB;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,YAAA,GAAe,CAAC,CAAA,KAA2C;AAC/D,IAAA,IAAI,CAAC,YAAA,EAAc;AACjB,MAAA,kBAAA,CAAmB,CAAA,CAAE,OAAO,OAAO,CAAA;AAAA,IACrC;AACA,IAAA,QAAA,GAAW,CAAC,CAAA;AAAA,EACd,CAAA;AAEA,EAAA,MAAM,WAAA,GAAc,CAAC,CAAA,KAA0C;AAC7D,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,OAAA,GAAU,CAAC,CAAA;AAAA,EACb,CAAA;AAEA,EAAA,MAAM,UAAA,GAAa,CAAC,CAAA,KAA0C;AAC5D,IAAA,UAAA,CAAW,KAAK,CAAA;AAChB,IAAA,MAAA,GAAS,CAAC,CAAA;AAAA,EACZ,CAAA;AAEA,EAAA,uBACE,GAAA;AAAA,IAAC,OAAA;AAAA,IAAA;AAAA,MACC,GAAA,EAAK,OAAA;AAAA,MACJ,GAAG,IAAA;AAAA,MACJ,IAAA,EAAK,UAAA;AAAA,MACL,OAAA,EAAS,SAAA;AAAA,MACT,QAAA,EAAU,YAAA;AAAA,MACV,cAAA,EAAc,gBAAgB,SAAS,CAAA;AAAA,MACvC,oBAAA,EAAoB,gBAAgB,aAAa,CAAA;AAAA,MACjD,OAAA,EAAS,WAAA;AAAA,MACT,MAAA,EAAQ,UAAA;AAAA,MACR,cAAA,EAAc,gBAAgB,OAAO,CAAA;AAAA,MACrC,QAAA;AAAA,MACA,eAAA,EAAe,gBAAgB,QAAQ,CAAA;AAAA,MACvC,cAAA,EAAc,gBAAgB,OAAO,CAAA;AAAA,MACrC,cAAA,EAAc,UAAU,MAAA,GAAS;AAAA;AAAA,GACnC;AAEJ;AAEO,IAAM,gBAAA,GAAmB,WAG9B,qBAAqB","file":"index.mjs","sourcesContent":["import {\n type ForwardedRef,\n forwardRef,\n useEffect,\n useRef,\n useState,\n} from 'react';\n\nexport type UnstyledCheckboxProps = {\n checked?: boolean;\n defaultChecked?: boolean;\n indeterminate?: boolean;\n onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;\n disabled?: boolean;\n invalid?: boolean;\n} & Omit<\n React.InputHTMLAttributes<HTMLInputElement>,\n 'type' | 'checked' | 'defaultChecked' | 'onChange' | 'disabled' | 'invalid'\n>;\n\nfunction booleanToString(value: boolean | undefined): string | undefined {\n return value ? 'true' : undefined;\n}\n\nfunction UnstyledCheckboxInner(\n {\n checked,\n defaultChecked,\n indeterminate,\n onChange,\n disabled,\n invalid,\n onFocus,\n onBlur,\n ...rest\n }: UnstyledCheckboxProps,\n ref: ForwardedRef<HTMLInputElement>,\n) {\n const isControlled = checked !== undefined;\n const [internalChecked, setInternalChecked] = useState(\n defaultChecked ?? false,\n );\n const isChecked = isControlled ? checked : internalChecked;\n\n const [focused, setFocused] = useState(false);\n\n const inputRef = useRef<HTMLInputElement | null>(null);\n\n useEffect(() => {\n if (inputRef.current) {\n inputRef.current.indeterminate = indeterminate ?? false;\n }\n }, [indeterminate]);\n\n const setRefs = (node: HTMLInputElement | null) => {\n inputRef.current = node;\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n };\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n if (!isControlled) {\n setInternalChecked(e.target.checked);\n }\n onChange?.(e);\n };\n\n const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {\n setFocused(true);\n onFocus?.(e);\n };\n\n const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {\n setFocused(false);\n onBlur?.(e);\n };\n\n return (\n <input\n ref={setRefs}\n {...rest}\n type=\"checkbox\"\n checked={isChecked}\n onChange={handleChange}\n data-checked={booleanToString(isChecked)}\n data-indeterminate={booleanToString(indeterminate)}\n onFocus={handleFocus}\n onBlur={handleBlur}\n data-focused={booleanToString(focused)}\n disabled={disabled}\n data-disabled={booleanToString(disabled)}\n data-invalid={booleanToString(invalid)}\n aria-invalid={invalid ? 'true' : undefined}\n />\n );\n}\n\nexport const UnstyledCheckbox = forwardRef<\n HTMLInputElement,\n UnstyledCheckboxProps\n>(UnstyledCheckboxInner);\n"]}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@asnewyla/unstyled-checkbox",
3
+ "version": "0.2.0",
4
+ "description": "Unstyled checkbox input primitive with accessibility",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/xavierDelaFuente/xd-components.git",
9
+ "directory": "packages/unstyled-checkbox"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "main": "dist/index.js",
15
+ "module": "dist/index.mjs",
16
+ "types": "dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.mjs",
21
+ "require": "./dist/index.js"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "sideEffects": false,
28
+ "keywords": [
29
+ "react",
30
+ "checkbox",
31
+ "unstyled",
32
+ "accessible"
33
+ ],
34
+ "peerDependencies": {
35
+ "react": "^18.0.0 || ^19.0.0",
36
+ "react-dom": "^18.0.0 || ^19.0.0"
37
+ },
38
+ "devDependencies": {
39
+ "react": "^18.3.0",
40
+ "react-dom": "^18.3.0",
41
+ "tsup": "^8.1.0",
42
+ "vitest": "^2.0.0",
43
+ "@vitest/coverage-v8": "^2.0.0",
44
+ "@testing-library/react": "^16.0.0",
45
+ "@testing-library/user-event": "^14.5.0",
46
+ "@testing-library/jest-dom": "^6.4.0",
47
+ "jsdom": "^24.0.0",
48
+ "typescript": "^5.5.0"
49
+ },
50
+ "scripts": {
51
+ "build": "tsup",
52
+ "test": "vitest run",
53
+ "test:watch": "vitest",
54
+ "test:coverage": "vitest run --coverage",
55
+ "type-check": "tsc --noEmit"
56
+ }
57
+ }