@ladder-ui/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/dist/checkbox.css +88 -0
- package/dist/checkbox.d.ts +38 -0
- package/dist/checkbox.vars.css +17 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +51 -0
- package/dist/index.mjs +46 -0
- package/package.json +67 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.lui-checkbox {
|
|
3
|
+
position: relative;
|
|
4
|
+
display: inline-flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
justify-content: center;
|
|
7
|
+
flex-shrink: 0;
|
|
8
|
+
vertical-align: middle;
|
|
9
|
+
width: var(--lui-checkbox-size-md);
|
|
10
|
+
height: var(--lui-checkbox-size-md);
|
|
11
|
+
}
|
|
12
|
+
.lui-checkbox__input {
|
|
13
|
+
position: absolute;
|
|
14
|
+
inset: 0;
|
|
15
|
+
width: 100%;
|
|
16
|
+
height: 100%;
|
|
17
|
+
margin: 0;
|
|
18
|
+
padding: 0;
|
|
19
|
+
opacity: 0;
|
|
20
|
+
cursor: pointer;
|
|
21
|
+
z-index: 1;
|
|
22
|
+
}
|
|
23
|
+
.lui-checkbox__input:disabled {
|
|
24
|
+
cursor: not-allowed;
|
|
25
|
+
}
|
|
26
|
+
.lui-checkbox__control {
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
justify-content: center;
|
|
30
|
+
width: 100%;
|
|
31
|
+
height: 100%;
|
|
32
|
+
background-color: var(--lui-checkbox-bg);
|
|
33
|
+
border: 1px solid var(--lui-checkbox-border);
|
|
34
|
+
border-radius: var(--lui-checkbox-radius);
|
|
35
|
+
color: var(--lui-checkbox-checked-text);
|
|
36
|
+
transition: var(--lui-transition);
|
|
37
|
+
pointer-events: none;
|
|
38
|
+
flex-shrink: 0;
|
|
39
|
+
}
|
|
40
|
+
.lui-checkbox__icon {
|
|
41
|
+
display: none;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: center;
|
|
44
|
+
width: 65%;
|
|
45
|
+
height: 65%;
|
|
46
|
+
}
|
|
47
|
+
.lui-checkbox__icon svg {
|
|
48
|
+
width: 100%;
|
|
49
|
+
height: 100%;
|
|
50
|
+
}
|
|
51
|
+
.lui-checkbox:has(.lui-checkbox__input:focus-visible) .lui-checkbox__control {
|
|
52
|
+
outline: none;
|
|
53
|
+
border-color: var(--lui-checkbox-focus-border);
|
|
54
|
+
box-shadow: 0 0 0 2px var(--lui-surface), 0 0 0 4px var(--lui-checkbox-focus-ring-color);
|
|
55
|
+
}
|
|
56
|
+
.lui-checkbox:has(.lui-checkbox__input:checked:not(:indeterminate)) .lui-checkbox__control {
|
|
57
|
+
background-color: var(--lui-checkbox-checked-bg);
|
|
58
|
+
border-color: var(--lui-checkbox-checked-bg);
|
|
59
|
+
}
|
|
60
|
+
.lui-checkbox:has(.lui-checkbox__input:checked:not(:indeterminate)) .lui-checkbox__icon--check {
|
|
61
|
+
display: flex;
|
|
62
|
+
}
|
|
63
|
+
.lui-checkbox:has(.lui-checkbox__input:indeterminate) .lui-checkbox__control {
|
|
64
|
+
background-color: var(--lui-checkbox-checked-bg);
|
|
65
|
+
border-color: var(--lui-checkbox-checked-bg);
|
|
66
|
+
}
|
|
67
|
+
.lui-checkbox:has(.lui-checkbox__input:indeterminate) .lui-checkbox__icon--indeterminate {
|
|
68
|
+
display: flex;
|
|
69
|
+
}
|
|
70
|
+
.lui-checkbox:has(.lui-checkbox__input:disabled) {
|
|
71
|
+
opacity: var(--lui-disabled-opacity);
|
|
72
|
+
}
|
|
73
|
+
.lui-checkbox--error .lui-checkbox__control {
|
|
74
|
+
border-color: var(--lui-error);
|
|
75
|
+
}
|
|
76
|
+
.lui-checkbox--error:has(.lui-checkbox__input:focus-visible) .lui-checkbox__control {
|
|
77
|
+
border-color: var(--lui-error);
|
|
78
|
+
box-shadow: 0 0 0 2px var(--lui-surface), 0 0 0 4px color-mix(in srgb, var(--lui-error) 40%, transparent);
|
|
79
|
+
}
|
|
80
|
+
.lui-checkbox--sm {
|
|
81
|
+
width: var(--lui-checkbox-size-sm);
|
|
82
|
+
height: var(--lui-checkbox-size-sm);
|
|
83
|
+
}
|
|
84
|
+
.lui-checkbox--lg {
|
|
85
|
+
width: var(--lui-checkbox-size-lg);
|
|
86
|
+
height: var(--lui-checkbox-size-lg);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Ref } from "react";
|
|
2
|
+
export interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "type" | "size"> {
|
|
3
|
+
/** React 19 ref — points directly to the native <input type="checkbox"> element */
|
|
4
|
+
ref?: Ref<HTMLInputElement>;
|
|
5
|
+
/**
|
|
6
|
+
* Size variant.
|
|
7
|
+
* @default "md"
|
|
8
|
+
*/
|
|
9
|
+
size?: "sm" | "md" | "lg";
|
|
10
|
+
/**
|
|
11
|
+
* Shows a dash instead of a checkmark. Useful for "select all" controls where
|
|
12
|
+
* only some items in the group are checked. Set imperatively — there is no HTML
|
|
13
|
+
* attribute for this state.
|
|
14
|
+
* @default false
|
|
15
|
+
*/
|
|
16
|
+
indeterminate?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Visual validation state. `"error"` also sets `aria-invalid="true"` on the
|
|
19
|
+
* native input automatically.
|
|
20
|
+
* @default "default"
|
|
21
|
+
*/
|
|
22
|
+
status?: "default" | "error";
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Checkbox — a fully accessible, themeable checkbox.
|
|
26
|
+
*
|
|
27
|
+
* - Renders a native `<input type="checkbox">` for full browser and AT support.
|
|
28
|
+
* - `ref` points directly to the native input element.
|
|
29
|
+
* - Supports `indeterminate` state (shows a dash — useful for "select all" patterns).
|
|
30
|
+
* - `status="error"` sets `aria-invalid="true"` automatically.
|
|
31
|
+
* - No third-party dependencies — checkmark and dash are inline SVGs.
|
|
32
|
+
* - All visual state (checked, indeterminate, focus, disabled, error) is driven by
|
|
33
|
+
* CSS `:has()` selectors — zero JS involved in rendering.
|
|
34
|
+
*/
|
|
35
|
+
export declare function Checkbox({ ref, className, size, status, indeterminate, disabled, ...props }: CheckboxProps): import("react/jsx-runtime").JSX.Element;
|
|
36
|
+
export declare namespace Checkbox {
|
|
37
|
+
var displayName: string;
|
|
38
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
/* Sizes */
|
|
3
|
+
--lui-checkbox-size-sm: 0.875rem; /* 14px */
|
|
4
|
+
--lui-checkbox-size-md: 1rem; /* 16px */
|
|
5
|
+
--lui-checkbox-size-lg: 1.25rem; /* 20px */
|
|
6
|
+
/* Background & border (unchecked) */
|
|
7
|
+
--lui-checkbox-bg: var(--lui-surface);
|
|
8
|
+
--lui-checkbox-border: color-mix(in srgb, var(--lui-surface-border) 93%, var(--lui-surface-text));
|
|
9
|
+
/* Checked & indeterminate */
|
|
10
|
+
--lui-checkbox-checked-bg: var(--lui-primary);
|
|
11
|
+
--lui-checkbox-checked-text: var(--lui-primary-text);
|
|
12
|
+
/* Focus ring */
|
|
13
|
+
--lui-checkbox-focus-border: var(--lui-primary);
|
|
14
|
+
--lui-checkbox-focus-ring-color: color-mix(in srgb, var(--lui-primary) 40%, transparent);
|
|
15
|
+
/* Shape */
|
|
16
|
+
--lui-checkbox-radius: var(--lui-radius-sm);
|
|
17
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
var concatClassNames = require('@ladder-ui/core/concatClassNames');
|
|
7
|
+
var react = require('react');
|
|
8
|
+
|
|
9
|
+
// ── Internal SVG icons (no external dependency) ───────────────────────────────
|
|
10
|
+
function CheckIcon() {
|
|
11
|
+
return (jsxRuntime.jsx("svg", { viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: jsxRuntime.jsx("path", { d: "M2 6 L5 9 L10 3" }) }));
|
|
12
|
+
}
|
|
13
|
+
function IndeterminateIcon() {
|
|
14
|
+
return (jsxRuntime.jsx("svg", { viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": "true", children: jsxRuntime.jsx("line", { x1: "2.5", y1: "6", x2: "9.5", y2: "6" }) }));
|
|
15
|
+
}
|
|
16
|
+
// ── Component ─────────────────────────────────────────────────────────────────
|
|
17
|
+
/**
|
|
18
|
+
* Checkbox — a fully accessible, themeable checkbox.
|
|
19
|
+
*
|
|
20
|
+
* - Renders a native `<input type="checkbox">` for full browser and AT support.
|
|
21
|
+
* - `ref` points directly to the native input element.
|
|
22
|
+
* - Supports `indeterminate` state (shows a dash — useful for "select all" patterns).
|
|
23
|
+
* - `status="error"` sets `aria-invalid="true"` automatically.
|
|
24
|
+
* - No third-party dependencies — checkmark and dash are inline SVGs.
|
|
25
|
+
* - All visual state (checked, indeterminate, focus, disabled, error) is driven by
|
|
26
|
+
* CSS `:has()` selectors — zero JS involved in rendering.
|
|
27
|
+
*/
|
|
28
|
+
function Checkbox({ ref, className, size = "md", status = "default", indeterminate = false, disabled, ...props }) {
|
|
29
|
+
// Internal ref required to set .indeterminate imperatively (no HTML attribute).
|
|
30
|
+
const internalRef = react.useRef(null);
|
|
31
|
+
react.useEffect(() => {
|
|
32
|
+
if (internalRef.current) {
|
|
33
|
+
internalRef.current.indeterminate = indeterminate;
|
|
34
|
+
}
|
|
35
|
+
}, [indeterminate]);
|
|
36
|
+
// Merge external ref (from the consumer) with our internal ref.
|
|
37
|
+
function setRef(node) {
|
|
38
|
+
internalRef.current = node;
|
|
39
|
+
if (typeof ref === "function") {
|
|
40
|
+
ref(node);
|
|
41
|
+
}
|
|
42
|
+
else if (ref) {
|
|
43
|
+
ref.current = node;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return (jsxRuntime.jsxs("span", { "data-slot": "checkbox", "data-disabled": disabled ? "true" : undefined, "data-status": status !== "default" ? status : undefined, className: concatClassNames("lui-checkbox", `lui-checkbox--${size}`, status === "error" && "lui-checkbox--error", disabled && "lui-checkbox--disabled", className), children: [jsxRuntime.jsx("input", { ref: setRef, type: "checkbox", className: "lui-checkbox__input", disabled: disabled, "aria-invalid": props["aria-invalid"] ?? (status === "error" ? true : undefined), ...props }), jsxRuntime.jsxs("span", { className: "lui-checkbox__control", "aria-hidden": "true", children: [jsxRuntime.jsx("span", { className: "lui-checkbox__icon lui-checkbox__icon--check", children: jsxRuntime.jsx(CheckIcon, {}) }), jsxRuntime.jsx("span", { className: "lui-checkbox__icon lui-checkbox__icon--indeterminate", children: jsxRuntime.jsx(IndeterminateIcon, {}) })] })] }));
|
|
47
|
+
}
|
|
48
|
+
Checkbox.displayName = "Checkbox";
|
|
49
|
+
|
|
50
|
+
exports.Checkbox = Checkbox;
|
|
51
|
+
exports.default = Checkbox;
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import concatClassNames from '@ladder-ui/core/concatClassNames';
|
|
3
|
+
import { useRef, useEffect } from 'react';
|
|
4
|
+
|
|
5
|
+
// ── Internal SVG icons (no external dependency) ───────────────────────────────
|
|
6
|
+
function CheckIcon() {
|
|
7
|
+
return (jsx("svg", { viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: jsx("path", { d: "M2 6 L5 9 L10 3" }) }));
|
|
8
|
+
}
|
|
9
|
+
function IndeterminateIcon() {
|
|
10
|
+
return (jsx("svg", { viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": "true", children: jsx("line", { x1: "2.5", y1: "6", x2: "9.5", y2: "6" }) }));
|
|
11
|
+
}
|
|
12
|
+
// ── Component ─────────────────────────────────────────────────────────────────
|
|
13
|
+
/**
|
|
14
|
+
* Checkbox — a fully accessible, themeable checkbox.
|
|
15
|
+
*
|
|
16
|
+
* - Renders a native `<input type="checkbox">` for full browser and AT support.
|
|
17
|
+
* - `ref` points directly to the native input element.
|
|
18
|
+
* - Supports `indeterminate` state (shows a dash — useful for "select all" patterns).
|
|
19
|
+
* - `status="error"` sets `aria-invalid="true"` automatically.
|
|
20
|
+
* - No third-party dependencies — checkmark and dash are inline SVGs.
|
|
21
|
+
* - All visual state (checked, indeterminate, focus, disabled, error) is driven by
|
|
22
|
+
* CSS `:has()` selectors — zero JS involved in rendering.
|
|
23
|
+
*/
|
|
24
|
+
function Checkbox({ ref, className, size = "md", status = "default", indeterminate = false, disabled, ...props }) {
|
|
25
|
+
// Internal ref required to set .indeterminate imperatively (no HTML attribute).
|
|
26
|
+
const internalRef = useRef(null);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (internalRef.current) {
|
|
29
|
+
internalRef.current.indeterminate = indeterminate;
|
|
30
|
+
}
|
|
31
|
+
}, [indeterminate]);
|
|
32
|
+
// Merge external ref (from the consumer) with our internal ref.
|
|
33
|
+
function setRef(node) {
|
|
34
|
+
internalRef.current = node;
|
|
35
|
+
if (typeof ref === "function") {
|
|
36
|
+
ref(node);
|
|
37
|
+
}
|
|
38
|
+
else if (ref) {
|
|
39
|
+
ref.current = node;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return (jsxs("span", { "data-slot": "checkbox", "data-disabled": disabled ? "true" : undefined, "data-status": status !== "default" ? status : undefined, className: concatClassNames("lui-checkbox", `lui-checkbox--${size}`, status === "error" && "lui-checkbox--error", disabled && "lui-checkbox--disabled", className), children: [jsx("input", { ref: setRef, type: "checkbox", className: "lui-checkbox__input", disabled: disabled, "aria-invalid": props["aria-invalid"] ?? (status === "error" ? true : undefined), ...props }), jsxs("span", { className: "lui-checkbox__control", "aria-hidden": "true", children: [jsx("span", { className: "lui-checkbox__icon lui-checkbox__icon--check", children: jsx(CheckIcon, {}) }), jsx("span", { className: "lui-checkbox__icon lui-checkbox__icon--indeterminate", children: jsx(IndeterminateIcon, {}) })] })] }));
|
|
43
|
+
}
|
|
44
|
+
Checkbox.displayName = "Checkbox";
|
|
45
|
+
|
|
46
|
+
export { Checkbox, Checkbox as default };
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ladder-ui/checkbox",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"require": "./dist/index.js",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./*.css": "./dist/*.css",
|
|
15
|
+
"./styles/*.css": "./dist/*.css"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"nodejs",
|
|
22
|
+
"react",
|
|
23
|
+
"ui",
|
|
24
|
+
"components",
|
|
25
|
+
"library",
|
|
26
|
+
"checkbox",
|
|
27
|
+
"form"
|
|
28
|
+
],
|
|
29
|
+
"author": "Ivan Avila <ivelaval@gmail.com> - https://www.vennet.dev",
|
|
30
|
+
"license": "ISC",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+ssh://git@github.com/ivelaval/ladder-ui.git"
|
|
34
|
+
},
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/ivelaval/ladder-ui/issues"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/ivelaval/ladder-ui#readme",
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@rollup/plugin-typescript": "^11.1.6",
|
|
44
|
+
"@types/react": "^19.0.0",
|
|
45
|
+
"rollup": "^4.59.0",
|
|
46
|
+
"rollup-plugin-postcss": "^4.0.2",
|
|
47
|
+
"sass": "^1.90.0",
|
|
48
|
+
"tslib": "^2.6.2",
|
|
49
|
+
"typescript": "^5.3.3",
|
|
50
|
+
"@ladder-ui/core": "0.2.0"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"@ladder-ui/core": ">=0.0.0",
|
|
54
|
+
"react": ">=18.0.0"
|
|
55
|
+
},
|
|
56
|
+
"sideEffects": [
|
|
57
|
+
"**/*.css"
|
|
58
|
+
],
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "pnpm clean && rollup -c",
|
|
61
|
+
"dev": "rollup -c -w",
|
|
62
|
+
"test": "vitest run",
|
|
63
|
+
"test:watch": "vitest",
|
|
64
|
+
"type-check": "tsc --noEmit",
|
|
65
|
+
"clean": "rm -rf dist"
|
|
66
|
+
}
|
|
67
|
+
}
|