@charcuterie/ui 2.5.0 → 2.7.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/README.md +2 -1
- package/dist/Checkbox/Checkbox.d.ts +10 -1
- package/dist/Checkbox/Checkbox.d.ts.map +1 -1
- package/dist/Checkbox/Checkbox.js +59 -12
- package/dist/Checkbox/Checkbox.js.map +1 -1
- package/dist/RadioGroup/RadioGroup.d.ts +8 -1
- package/dist/RadioGroup/RadioGroup.d.ts.map +1 -1
- package/dist/RadioGroup/RadioGroup.js +17 -10
- package/dist/RadioGroup/RadioGroup.js.map +1 -1
- package/dist/RadioGroup/RadioGroupOption.d.ts +2 -1
- package/dist/RadioGroup/RadioGroupOption.d.ts.map +1 -1
- package/dist/RadioGroup/RadioGroupOption.js +33 -10
- package/dist/RadioGroup/RadioGroupOption.js.map +1 -1
- package/dist/Switch/Switch.d.ts +7 -1
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +27 -15
- package/dist/Switch/Switch.js.map +1 -1
- package/dist/styles.css +88 -8
- package/package.json +1 -1
- package/src/Checkbox/Checkbox.mdx +13 -0
- package/src/Checkbox/Checkbox.stories.tsx +23 -1
- package/src/Checkbox/Checkbox.test.tsx +29 -0
- package/src/Checkbox/Checkbox.tsx +74 -15
- package/src/RadioGroup/RadioGroup.mdx +9 -0
- package/src/RadioGroup/RadioGroup.stories.tsx +11 -1
- package/src/RadioGroup/RadioGroup.test.tsx +41 -0
- package/src/RadioGroup/RadioGroup.tsx +26 -7
- package/src/RadioGroup/RadioGroupOption.tsx +32 -10
- package/src/Switch/Switch.mdx +8 -6
- package/src/Switch/Switch.stories.tsx +23 -1
- package/src/Switch/Switch.test.tsx +29 -0
- package/src/Switch/Switch.tsx +34 -13
- package/src/styles.css +88 -8
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ import { Button, Card } from "@charcuterie/ui"
|
|
|
37
37
|
@import "tailwindcss";
|
|
38
38
|
@import "@charcuterie/tokens/theme.css"; /* colours, type ramp, radii, motion */
|
|
39
39
|
@import "@charcuterie/tokens/fonts.css"; /* Baloo 2, Outfit, Victor Mono */
|
|
40
|
-
@import "@charcuterie/ui/styles.css"; /* the four looping affordances */
|
|
40
|
+
@import "@charcuterie/ui/styles.css"; /* the four looping affordances + `.charcuterie-scrollbar` */
|
|
41
41
|
|
|
42
42
|
@source "../node_modules/@charcuterie/ui/dist";
|
|
43
43
|
```
|
|
@@ -105,6 +105,7 @@ an hour of confusion.
|
|
|
105
105
|
| Size | `controlStyles.ts` — `h-(--control-height-md)`, so `[data-density]` decides |
|
|
106
106
|
| Type | `text-sm`/`text-md`/`text-lg`, which are **ours**: `theme.css` bridges `--text-*` onto the density-scaled `--font-size-*` |
|
|
107
107
|
| Motion | `styles.css`, at `--duration-loop-*`, switched off under `prefers-reduced-motion` |
|
|
108
|
+
| Scrollbar | `styles.css` — add `charcuterie-scrollbar` to any scrolling element for a token-tinted bar (rounded thumb on Chromium/Safari; thin standard match on Firefox) that flips with `[data-scheme]` |
|
|
108
109
|
| State | `@charcuterie/logic` — `useUniqueId` for label wiring, `useStatus` for `MediaTile`'s three image states |
|
|
109
110
|
|
|
110
111
|
**Class names are never interpolated.** `` `bg-intent-${intent}-solid` `` generates nothing
|
|
@@ -16,6 +16,15 @@ export type CheckboxProps = {
|
|
|
16
16
|
*/
|
|
17
17
|
isChecked?: boolean;
|
|
18
18
|
isDisabled?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Shows the value at full contrast but refuses to change it — a
|
|
21
|
+
* setting you may read here and edit elsewhere. Distinct from
|
|
22
|
+
* `isDisabled`, which dims: a read-only box is not turned down,
|
|
23
|
+
* because the whole point is that you can still read it. Announced
|
|
24
|
+
* as `aria-readonly`, and the toggle is blocked on both pointer and
|
|
25
|
+
* `Space`.
|
|
26
|
+
*/
|
|
27
|
+
isReadOnly?: boolean;
|
|
19
28
|
/**
|
|
20
29
|
* The visible text beside the box, and the checkbox's accessible
|
|
21
30
|
* name — the `<label>` wraps the control, so there is no `for` to
|
|
@@ -55,5 +64,5 @@ export type CheckboxProps = {
|
|
|
55
64
|
* `<input>`'s job. `isChecked` seeds the first paint and nothing
|
|
56
65
|
* after.
|
|
57
66
|
*/
|
|
58
|
-
export declare const Checkbox: ({ className, id, isChecked, isDisabled, label, onChange, size, }: CheckboxProps) => ReactNode;
|
|
67
|
+
export declare const Checkbox: ({ className, id, isChecked, isDisabled, isReadOnly, label, onChange, size, }: CheckboxProps) => ReactNode;
|
|
59
68
|
//# sourceMappingURL=Checkbox.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Checkbox.d.ts","sourceRoot":"","sources":["../../src/Checkbox/Checkbox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAe,SAAS,EAAE,MAAM,OAAO,CAAA;AAKnD,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,EAAE,CAAC,EAAE,MAAM,CAAA;IACX;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;OAKG;IACH,KAAK,EAAE,SAAS,CAAA;IAChB,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAA;IACvC,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB,CAAA;AAoBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,QAAQ,GAAI,
|
|
1
|
+
{"version":3,"file":"Checkbox.d.ts","sourceRoot":"","sources":["../../src/Checkbox/Checkbox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAe,SAAS,EAAE,MAAM,OAAO,CAAA;AAKnD,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,EAAE,CAAC,EAAE,MAAM,CAAA;IACX;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;OAKG;IACH,KAAK,EAAE,SAAS,CAAA;IAChB,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAA;IACvC,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB,CAAA;AAoBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,QAAQ,GAAI,8EAStB,aAAa,KAAG,SA0GlB,CAAA"}
|
|
@@ -45,17 +45,64 @@ const TEXT_SIZE_CLASS = {
|
|
|
45
45
|
* `<input>`'s job. `isChecked` seeds the first paint and nothing
|
|
46
46
|
* after.
|
|
47
47
|
*/
|
|
48
|
-
export const Checkbox = ({ className, id, isChecked = false, isDisabled = false, label, onChange, size = "md", }) => (_jsxs("label", { className: toClassName("inline-flex
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
48
|
+
export const Checkbox = ({ className, id, isChecked = false, isDisabled = false, isReadOnly = false, label, onChange, size = "md", }) => (_jsxs("label", { className: toClassName("inline-flex items-center gap-2 text-content-secondary select-none", TEXT_SIZE_CLASS[size],
|
|
49
|
+
// One cursor, chosen by state — two cursor utilities of the same
|
|
50
|
+
// specificity do not resolve by class-list order.
|
|
51
|
+
isDisabled
|
|
52
|
+
? "cursor-not-allowed"
|
|
53
|
+
: isReadOnly
|
|
54
|
+
? "cursor-default"
|
|
55
|
+
: "cursor-pointer",
|
|
56
|
+
// Disabled dims the whole control with opacity rather than fading
|
|
57
|
+
// the box to a quieter token. The token scale has no step between
|
|
58
|
+
// `border-default` and `border-strong`, so a muted outline was
|
|
59
|
+
// either invisible or indistinguishable from enabled; `opacity-60`
|
|
60
|
+
// keeps the full shape and colour and just turns them down, which
|
|
61
|
+
// is the "present but inactive" the box actually is. Read-only is
|
|
62
|
+
// *not* dimmed — its whole point is that the value stays readable.
|
|
63
|
+
// The same treatment is on `Switch` and `RadioGroup`.
|
|
64
|
+
isDisabled && "opacity-60", className), children: [_jsxs("span", { className: "relative grid shrink-0 place-items-center", children: [_jsx("input", { "aria-readonly": isReadOnly || undefined, className: toClassName(
|
|
65
|
+
// `border-2` is the family's shared weight — the `Switch`
|
|
66
|
+
// track's `ring-2` and the `RadioGroup` ring match it, so the
|
|
67
|
+
// three read as one set — and a 2px edge is also what keeps a
|
|
68
|
+
// disabled box visible where a 1px one washed out.
|
|
69
|
+
"peer appearance-none rounded-sm border-2 bg-surface-sunken transition-colors duration-(--duration-fast) ease-standard",
|
|
70
|
+
// Read-only wears the **neutral** intent, not the accent: full
|
|
71
|
+
// contrast (so the value reads) but plainly not an actionable
|
|
72
|
+
// accent control, with a softer `border-default` resting edge.
|
|
73
|
+
// Three distinct states result — enabled (accent, crisp),
|
|
74
|
+
// disabled (accent, dimmed by the wrapper), read-only (neutral).
|
|
75
|
+
// No `disabled:` colour override; the wrapper's `opacity-60`
|
|
76
|
+
// does the dimming, and doubling it on the box is what made a
|
|
77
|
+
// disabled checkbox vanish.
|
|
78
|
+
isReadOnly
|
|
79
|
+
? "border-border-default checked:border-intent-neutral-solid checked:bg-intent-neutral-solid"
|
|
80
|
+
: "border-border-strong checked:border-intent-accent-solid checked:bg-intent-accent-solid", isDisabled
|
|
81
|
+
? "cursor-not-allowed"
|
|
82
|
+
: isReadOnly
|
|
83
|
+
? "cursor-default"
|
|
84
|
+
: "cursor-pointer", BOX_SIZE_CLASS[size], FOCUS_RING_CLASS), defaultChecked: isChecked, disabled: isDisabled, id: id, onChange: (event) => {
|
|
85
|
+
if (isReadOnly) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
59
88
|
onChange?.(event.target.checked);
|
|
60
|
-
},
|
|
89
|
+
}, onClick: (clickEvent) => {
|
|
90
|
+
// Native checkboxes have no `readonly`, so the toggle is
|
|
91
|
+
// blocked here — `preventDefault` on the click stops the
|
|
92
|
+
// checked flip a pointer would make.
|
|
93
|
+
if (isReadOnly) {
|
|
94
|
+
clickEvent.preventDefault();
|
|
95
|
+
}
|
|
96
|
+
}, onKeyDown: (keyEvent) => {
|
|
97
|
+
// …and Space, the keyboard toggle a click block does not
|
|
98
|
+
// cover.
|
|
99
|
+
if (isReadOnly && keyEvent.key === " ") {
|
|
100
|
+
keyEvent.preventDefault();
|
|
101
|
+
}
|
|
102
|
+
}, type: "checkbox" }), _jsx("svg", { "aria-hidden": "true", className: toClassName("pointer-events-none invisible absolute peer-checked:visible",
|
|
103
|
+
// Matches whichever fill the box wears, so the tick keeps its
|
|
104
|
+
// guaranteed on-solid contrast in either intent.
|
|
105
|
+
isReadOnly
|
|
106
|
+
? "text-intent-neutral-on-solid"
|
|
107
|
+
: "text-intent-accent-on-solid", CHECK_SIZE_CLASS[size]), fill: "none", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 3, viewBox: "0 0 24 24", children: _jsx("path", { d: "M20 6 9 17l-5-5" }) })] }), _jsx("span", { children: label })] }));
|
|
61
108
|
//# sourceMappingURL=Checkbox.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Checkbox.js","sourceRoot":"","sources":["../../src/Checkbox/Checkbox.tsx"],"names":[],"mappings":";AAGA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"Checkbox.js","sourceRoot":"","sources":["../../src/Checkbox/Checkbox.tsx"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,13 @@ export type RadioItem = {
|
|
|
14
14
|
export type RadioGroupProps = {
|
|
15
15
|
className?: string;
|
|
16
16
|
items: readonly RadioItem[];
|
|
17
|
+
/**
|
|
18
|
+
* Shows which option is chosen at full contrast but refuses to move
|
|
19
|
+
* the choice — the arrow keys and clicks no longer select, and the
|
|
20
|
+
* group announces `aria-readonly`. Focus can still travel the
|
|
21
|
+
* options so a keyboard user can read them.
|
|
22
|
+
*/
|
|
23
|
+
isReadOnly?: boolean;
|
|
17
24
|
/** The group's accessible name. Required. */
|
|
18
25
|
label: string;
|
|
19
26
|
onChange?: (selectedValue: string | null) => void;
|
|
@@ -43,5 +50,5 @@ export type RadioGroupProps = {
|
|
|
43
50
|
* move focus and check — APG's one activation mode, the same reason
|
|
44
51
|
* `SegmentedControl` has no `manual` prop.
|
|
45
52
|
*/
|
|
46
|
-
export declare const RadioGroup: ({ className, items, label, onChange, selectedValue, size, }: RadioGroupProps) => ReactNode;
|
|
53
|
+
export declare const RadioGroup: ({ className, items, isReadOnly, label, onChange, selectedValue, size, }: RadioGroupProps) => ReactNode;
|
|
47
54
|
//# sourceMappingURL=RadioGroup.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RadioGroup.d.ts","sourceRoot":"","sources":["../../src/RadioGroup/RadioGroup.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtC,MAAM,MAAM,SAAS,GAAG;IACtB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,KAAK,EAAE,SAAS,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,SAAS,SAAS,EAAE,CAAA;IAC3B,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACjD,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB,CAAA;AAQD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,UAAU,GAAI,
|
|
1
|
+
{"version":3,"file":"RadioGroup.d.ts","sourceRoot":"","sources":["../../src/RadioGroup/RadioGroup.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtC,MAAM,MAAM,SAAS,GAAG;IACtB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,KAAK,EAAE,SAAS,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,SAAS,SAAS,EAAE,CAAA;IAC3B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACjD,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB,CAAA;AAQD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,UAAU,GAAI,yEAQxB,eAAe,KAAG,SAgJpB,CAAA"}
|
|
@@ -30,7 +30,7 @@ const GAP_CLASS = {
|
|
|
30
30
|
* move focus and check — APG's one activation mode, the same reason
|
|
31
31
|
* `SegmentedControl` has no `manual` prop.
|
|
32
32
|
*/
|
|
33
|
-
export const RadioGroup = ({ className, items, label, onChange, selectedValue, size = "md", }) => {
|
|
33
|
+
export const RadioGroup = ({ className, items, isReadOnly = false, label, onChange, selectedValue, size = "md", }) => {
|
|
34
34
|
const groupRef = useRef(null);
|
|
35
35
|
const optionElements = useRef(new Map());
|
|
36
36
|
const initialValue = selectedValue ??
|
|
@@ -53,17 +53,22 @@ export const RadioGroup = ({ className, items, label, onChange, selectedValue, s
|
|
|
53
53
|
*/
|
|
54
54
|
const checkedValue = selection.selectedValue ?? selection.pendingValue;
|
|
55
55
|
const chooseOption = useCallback((value) => {
|
|
56
|
+
// Focus may still land on the option so it can be read; only the
|
|
57
|
+
// choice is frozen when read-only.
|
|
56
58
|
setActiveValue(value);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
if (!isReadOnly) {
|
|
60
|
+
select(value);
|
|
61
|
+
}
|
|
62
|
+
}, [isReadOnly, select, setActiveValue]);
|
|
59
63
|
useEffect(() => {
|
|
60
64
|
// Selection follows focus — the radio-group rule, and the one
|
|
61
65
|
// line a `manual` mode this component deliberately lacks would
|
|
62
|
-
// remove.
|
|
63
|
-
|
|
66
|
+
// remove. Read-only severs exactly this link: focus still travels
|
|
67
|
+
// for reading, but moving it no longer moves the choice.
|
|
68
|
+
if (activeValue !== null && !isReadOnly) {
|
|
64
69
|
select(activeValue);
|
|
65
70
|
}
|
|
66
|
-
}, [activeValue, select]);
|
|
71
|
+
}, [activeValue, isReadOnly, select]);
|
|
67
72
|
useEffect(() => {
|
|
68
73
|
const group = groupRef.current;
|
|
69
74
|
if (!group ||
|
|
@@ -73,7 +78,7 @@ export const RadioGroup = ({ className, items, label, onChange, selectedValue, s
|
|
|
73
78
|
}
|
|
74
79
|
optionElements.current.get(activeValue)?.focus();
|
|
75
80
|
}, [activeValue]);
|
|
76
|
-
return (_jsx("div", { "aria-label": label, className: toClassName("flex flex-col", GAP_CLASS[size], className), onKeyDown: (keyEvent) => {
|
|
81
|
+
return (_jsx("div", { "aria-label": label, "aria-readonly": isReadOnly || undefined, className: toClassName("flex flex-col", GAP_CLASS[size], className), onKeyDown: (keyEvent) => {
|
|
77
82
|
const commands = {
|
|
78
83
|
ArrowDown: focus.next,
|
|
79
84
|
ArrowLeft: focus.previous,
|
|
@@ -93,12 +98,14 @@ export const RadioGroup = ({ className, items, label, onChange, selectedValue, s
|
|
|
93
98
|
}
|
|
94
99
|
// Space checks the focused option. Selection already follows
|
|
95
100
|
// focus, so this is almost always a no-op — one idempotent
|
|
96
|
-
// command, not a second code path.
|
|
97
|
-
if (keyEvent.key === " " &&
|
|
101
|
+
// command, not a second code path — and read-only skips it.
|
|
102
|
+
if (keyEvent.key === " " &&
|
|
103
|
+
activeValue !== null &&
|
|
104
|
+
!isReadOnly) {
|
|
98
105
|
keyEvent.preventDefault();
|
|
99
106
|
select(activeValue);
|
|
100
107
|
}
|
|
101
|
-
}, ref: groupRef, role: "radiogroup", children: items.map((item) => (_jsx(RadioGroupOption, { isChecked: checkedValue === item.value, item: item, onChoose: chooseOption, registerFocus: focus.register, registerSelection: selection.register, size: size,
|
|
108
|
+
}, ref: groupRef, role: "radiogroup", children: items.map((item) => (_jsx(RadioGroupOption, { isChecked: checkedValue === item.value, isReadOnly: isReadOnly, item: item, onChoose: chooseOption, registerFocus: focus.register, registerSelection: selection.register, size: size,
|
|
102
109
|
// The roving-tabindex rule read from the core rather than
|
|
103
110
|
// restated here: exactly one option is in the tab order, so
|
|
104
111
|
// Tab enters and leaves the group once.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RadioGroup.js","sourceRoot":"","sources":["../../src/RadioGroup/RadioGroup.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,cAAc,EACd,cAAc,EACd,eAAe,GAChB,MAAM,oBAAoB,CAAA;AAG3B,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAEtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"RadioGroup.js","sourceRoot":"","sources":["../../src/RadioGroup/RadioGroup.tsx"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import type { ReactNode } from "react";
|
|
|
3
3
|
import type { RadioItem } from "./RadioGroup.tsx";
|
|
4
4
|
export type RadioGroupOptionProps = {
|
|
5
5
|
isChecked: boolean;
|
|
6
|
+
isReadOnly: boolean;
|
|
6
7
|
item: RadioItem;
|
|
7
8
|
onChoose: (value: string) => void;
|
|
8
9
|
/** `RovingFocus.register` — membership of the arrow-key group. */
|
|
@@ -22,5 +23,5 @@ export type RadioGroupOptionProps = {
|
|
|
22
23
|
* and sized independently of the ring — a filled `bg-intent-accent`
|
|
23
24
|
* ring with no inner dot reads as a checkbox, not a radio.
|
|
24
25
|
*/
|
|
25
|
-
export declare const RadioGroupOption: ({ isChecked, item, onChoose, registerFocus, registerSelection, size, tabIndex, trackElement, }: RadioGroupOptionProps) => ReactNode;
|
|
26
|
+
export declare const RadioGroupOption: ({ isChecked, isReadOnly, item, onChoose, registerFocus, registerSelection, size, tabIndex, trackElement, }: RadioGroupOptionProps) => ReactNode;
|
|
26
27
|
//# sourceMappingURL=RadioGroupOption.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RadioGroupOption.d.ts","sourceRoot":"","sources":["../../src/RadioGroup/RadioGroupOption.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAEjD,MAAM,MAAM,qBAAqB,GAAG;IAClC,SAAS,EAAE,OAAO,CAAA;IAClB,IAAI,EAAE,SAAS,CAAA;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,kEAAkE;IAClE,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,IAAI,CAAA;IAC5C,0DAA0D;IAC1D,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,IAAI,CAAA;IAChD,IAAI,EAAE,WAAW,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,CACZ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,iBAAiB,GAAG,IAAI,KAC9B,IAAI,CAAA;CACV,CAAA;
|
|
1
|
+
{"version":3,"file":"RadioGroupOption.d.ts","sourceRoot":"","sources":["../../src/RadioGroup/RadioGroupOption.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAEjD,MAAM,MAAM,qBAAqB,GAAG;IAClC,SAAS,EAAE,OAAO,CAAA;IAClB,UAAU,EAAE,OAAO,CAAA;IACnB,IAAI,EAAE,SAAS,CAAA;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,kEAAkE;IAClE,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,IAAI,CAAA;IAC5C,0DAA0D;IAC1D,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,IAAI,CAAA;IAChD,IAAI,EAAE,WAAW,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,CACZ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,iBAAiB,GAAG,IAAI,KAC9B,IAAI,CAAA;CACV,CAAA;AAuBD;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,GAAI,4GAU9B,qBAAqB,KAAG,SAkF1B,CAAA"}
|
|
@@ -2,8 +2,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useEffect } from "react";
|
|
3
3
|
import { FOCUS_RING_CLASS } from "../intentStyles.js";
|
|
4
4
|
import { toClassName } from "../toClassName.js";
|
|
5
|
+
// Deliberately the `Switch`'s `THUMB_SIZE_CLASS`, value for value — a
|
|
6
|
+
// radio's ring and a switch's knob are then the same circle at every
|
|
7
|
+
// density, which is what makes the family read as one set.
|
|
5
8
|
const RING_SIZE_CLASS = {
|
|
6
|
-
sm: "size-3
|
|
9
|
+
sm: "size-3",
|
|
7
10
|
md: "size-4",
|
|
8
11
|
lg: "size-5",
|
|
9
12
|
};
|
|
@@ -26,7 +29,7 @@ const TEXT_SIZE_CLASS = {
|
|
|
26
29
|
* and sized independently of the ring — a filled `bg-intent-accent`
|
|
27
30
|
* ring with no inner dot reads as a checkbox, not a radio.
|
|
28
31
|
*/
|
|
29
|
-
export const RadioGroupOption = ({ isChecked, item, onChoose, registerFocus, registerSelection, size, tabIndex, trackElement, }) => {
|
|
32
|
+
export const RadioGroupOption = ({ isChecked, isReadOnly, item, onChoose, registerFocus, registerSelection, size, tabIndex, trackElement, }) => {
|
|
30
33
|
const { isDisabled = false, label, value } = item;
|
|
31
34
|
useEffect(() => registerSelection(value), [registerSelection, value]);
|
|
32
35
|
useEffect(() => {
|
|
@@ -35,18 +38,38 @@ export const RadioGroupOption = ({ isChecked, item, onChoose, registerFocus, reg
|
|
|
35
38
|
}
|
|
36
39
|
return registerFocus(value);
|
|
37
40
|
}, [isDisabled, registerFocus, value]);
|
|
38
|
-
return (_jsxs("button", { "aria-checked": isChecked, className: toClassName("inline-flex
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
return (_jsxs("button", { "aria-checked": isChecked, className: toClassName("inline-flex items-center gap-2 rounded-sm text-content-secondary text-start transition-colors duration-(--duration-fast) ease-standard", TEXT_SIZE_CLASS[size],
|
|
42
|
+
// Disabled turns the whole option down with `opacity-60`, the
|
|
43
|
+
// same family treatment as `Checkbox` and `Switch`, rather than
|
|
44
|
+
// fading the ring to a token that disappeared on a pale theme.
|
|
45
|
+
// Read-only keeps full contrast — only the cursor and the hover
|
|
46
|
+
// affordance say it will not move.
|
|
47
|
+
isDisabled
|
|
48
|
+
? "cursor-not-allowed opacity-60"
|
|
49
|
+
: isReadOnly
|
|
50
|
+
? "cursor-default"
|
|
51
|
+
: "cursor-pointer hover:text-content-primary", FOCUS_RING_CLASS), disabled: isDisabled, onClick: () => {
|
|
41
52
|
onChoose(value);
|
|
42
53
|
}, ref: (element) => {
|
|
43
54
|
trackElement(value, element);
|
|
44
|
-
}, role: "radio", tabIndex: tabIndex, type: "button", children: [_jsx("span", { className: toClassName(
|
|
45
|
-
|
|
55
|
+
}, role: "radio", tabIndex: tabIndex, type: "button", children: [_jsx("span", { className: toClassName(
|
|
56
|
+
// `border-2` matches the `Switch` thumb's ring weight and the
|
|
57
|
+
// `Checkbox` box, and `RING_SIZE_CLASS` matches the thumb's
|
|
58
|
+
// diameter — so a radio dot and a switch knob are the same
|
|
59
|
+
// circle. A disabled ring holds `border-default` rather than
|
|
60
|
+
// fading to the `border-subtle` that vanished on a pale theme.
|
|
61
|
+
"grid shrink-0 place-items-center rounded-full border-2 transition-colors duration-(--duration-fast) ease-standard",
|
|
62
|
+
// Read-only wears the neutral intent and a softer resting edge
|
|
63
|
+
// — the same "readable but not an actionable accent" language
|
|
64
|
+
// as `Checkbox` and `Switch`.
|
|
65
|
+
isReadOnly
|
|
66
|
+
? isChecked
|
|
67
|
+
? "border-intent-neutral-solid"
|
|
68
|
+
: "border-border-default"
|
|
46
69
|
: isChecked
|
|
47
70
|
? "border-intent-accent-solid"
|
|
48
|
-
: "border-border-strong", RING_SIZE_CLASS[size]), children: _jsx("span", { className: toClassName("rounded-full transition-transform duration-(--duration-fast) ease-standard",
|
|
49
|
-
? "bg-
|
|
50
|
-
: "bg-intent-accent-solid", DOT_SIZE_CLASS[size]) }) }), _jsx("span", { children: label })] }));
|
|
71
|
+
: "border-border-strong", RING_SIZE_CLASS[size]), children: _jsx("span", { className: toClassName("rounded-full transition-transform duration-(--duration-fast) ease-standard", isReadOnly
|
|
72
|
+
? "bg-intent-neutral-solid"
|
|
73
|
+
: "bg-intent-accent-solid", isChecked ? "scale-100" : "scale-0", DOT_SIZE_CLASS[size]) }) }), _jsx("span", { children: label })] }));
|
|
51
74
|
};
|
|
52
75
|
//# sourceMappingURL=RadioGroupOption.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RadioGroupOption.js","sourceRoot":"","sources":["../../src/RadioGroup/RadioGroupOption.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"RadioGroupOption.js","sourceRoot":"","sources":["../../src/RadioGroup/RadioGroupOption.tsx"],"names":[],"mappings":"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"}
|
package/dist/Switch/Switch.d.ts
CHANGED
|
@@ -10,6 +10,12 @@ export type SwitchProps = {
|
|
|
10
10
|
*/
|
|
11
11
|
isChecked?: boolean;
|
|
12
12
|
isDisabled?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Shows the state at full contrast but refuses to flip it — a
|
|
15
|
+
* setting you may read here and change elsewhere. Unlike
|
|
16
|
+
* `isDisabled` it does not dim, and it announces `aria-readonly`.
|
|
17
|
+
*/
|
|
18
|
+
isReadOnly?: boolean;
|
|
13
19
|
/** The visible text, and the switch's accessible name. */
|
|
14
20
|
label: ReactNode;
|
|
15
21
|
onChange?: (isChecked: boolean) => void;
|
|
@@ -50,5 +56,5 @@ export type SwitchProps = {
|
|
|
50
56
|
* gives the off track the defined edge a bare well would miss on a pale
|
|
51
57
|
* theme.
|
|
52
58
|
*/
|
|
53
|
-
export declare const Switch: ({ className, isChecked, isDisabled, label, onChange, size, }: SwitchProps) => ReactNode;
|
|
59
|
+
export declare const Switch: ({ className, isChecked, isDisabled, isReadOnly, label, onChange, size, }: SwitchProps) => ReactNode;
|
|
54
60
|
//# sourceMappingURL=Switch.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Switch.d.ts","sourceRoot":"","sources":["../../src/Switch/Switch.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtC,MAAM,MAAM,WAAW,GAAG;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,0DAA0D;IAC1D,KAAK,EAAE,SAAS,CAAA;IAChB,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAA;IACvC,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB,CAAA;AAkCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,MAAM,GAAI,
|
|
1
|
+
{"version":3,"file":"Switch.d.ts","sourceRoot":"","sources":["../../src/Switch/Switch.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtC,MAAM,MAAM,WAAW,GAAG;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,0DAA0D;IAC1D,KAAK,EAAE,SAAS,CAAA;IAChB,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAA;IACvC,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB,CAAA;AAkCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,MAAM,GAAI,0EAQpB,WAAW,KAAG,SA6EhB,CAAA"}
|
package/dist/Switch/Switch.js
CHANGED
|
@@ -63,31 +63,43 @@ const TEXT_SIZE_CLASS = {
|
|
|
63
63
|
* gives the off track the defined edge a bare well would miss on a pale
|
|
64
64
|
* theme.
|
|
65
65
|
*/
|
|
66
|
-
export const Switch = ({ className, isChecked = false, isDisabled = false, label, onChange, size = "md", }) => {
|
|
66
|
+
export const Switch = ({ className, isChecked = false, isDisabled = false, isReadOnly = false, label, onChange, size = "md", }) => {
|
|
67
67
|
const [isOn, setIsOn] = useState(isChecked);
|
|
68
68
|
const labelId = useUniqueId();
|
|
69
|
-
return (_jsxs("span", { className: toClassName("inline-flex items-center gap-2 text-content-secondary select-none", TEXT_SIZE_CLASS[size],
|
|
69
|
+
return (_jsxs("span", { className: toClassName("inline-flex items-center gap-2 text-content-secondary select-none", TEXT_SIZE_CLASS[size],
|
|
70
|
+
// Disabled turns the whole control down with `opacity-60`, the
|
|
71
|
+
// same family treatment as `Checkbox` and `RadioGroup` — the
|
|
72
|
+
// track keeps its full-contrast state colour underneath.
|
|
73
|
+
isDisabled && "opacity-60", className), children: [_jsx("button", { "aria-checked": isOn, "aria-labelledby": labelId, "aria-readonly": isReadOnly || undefined, className: toClassName(
|
|
70
74
|
// `p-1` is the breathing room; `ring-inset` draws the off
|
|
71
75
|
// outline without changing the box size, so the centring
|
|
72
76
|
// arithmetic above stays exact.
|
|
73
|
-
"inline-flex shrink-0
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
"inline-flex shrink-0 items-center rounded-full p-1 transition-colors duration-(--duration-fast) ease-standard",
|
|
78
|
+
// The track carries the state at full contrast whether or not
|
|
79
|
+
// it is disabled; `opacity-60` on the wrapper does the dimming.
|
|
80
|
+
// Read-only swaps the accent for the neutral intent and softens
|
|
81
|
+
// the off outline — readable, plainly not an actionable accent,
|
|
82
|
+
// the same language as `Checkbox` and `RadioGroup`.
|
|
83
|
+
isReadOnly
|
|
84
|
+
? isOn
|
|
85
|
+
? "bg-intent-neutral-solid"
|
|
86
|
+
: "bg-surface-sunken ring-2 ring-border-default ring-inset"
|
|
80
87
|
: isOn
|
|
81
88
|
? "bg-intent-accent-solid"
|
|
82
|
-
: "bg-surface-sunken ring-2 ring-border-strong ring-inset",
|
|
89
|
+
: "bg-surface-sunken ring-2 ring-border-strong ring-inset", isDisabled
|
|
90
|
+
? "cursor-not-allowed"
|
|
91
|
+
: isReadOnly
|
|
92
|
+
? "cursor-default"
|
|
93
|
+
: "cursor-pointer", TRACK_SIZE_CLASS[size], FOCUS_RING_CLASS), disabled: isDisabled, onClick: () => {
|
|
94
|
+
// Read-only shows the state but will not flip it — the switch
|
|
95
|
+
// owns its value, so the guard is just an early return.
|
|
96
|
+
if (isReadOnly) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
83
99
|
const isNextOn = !isOn;
|
|
84
100
|
setIsOn(isNextOn);
|
|
85
101
|
onChange?.(isNextOn);
|
|
86
|
-
}, role: "switch", type: "button", children: _jsx("span", { className: toClassName("rounded-full transition-transform duration-(--duration-fast) ease-standard",
|
|
87
|
-
// One knob, same colour off and on; disabled mutes it.
|
|
88
|
-
isDisabled
|
|
89
|
-
? "bg-content-disabled"
|
|
90
|
-
: "bg-surface-raised", isOn
|
|
102
|
+
}, role: "switch", type: "button", children: _jsx("span", { className: toClassName("rounded-full bg-surface-raised transition-transform duration-(--duration-fast) ease-standard", isOn
|
|
91
103
|
? THUMB_ON_TRANSLATE_CLASS[size]
|
|
92
104
|
: "translate-x-0", THUMB_SIZE_CLASS[size]) }) }), _jsx("span", { id: labelId, children: label })] }));
|
|
93
105
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Switch.js","sourceRoot":"","sources":["../../src/Switch/Switch.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAGhD,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEhC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"Switch.js","sourceRoot":"","sources":["../../src/Switch/Switch.tsx"],"names":[],"mappings":"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"}
|
package/dist/styles.css
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
/* `@charcuterie/ui/styles.css` — the four looping affordances
|
|
2
|
-
*
|
|
1
|
+
/* `@charcuterie/ui/styles.css` — the four looping affordances and
|
|
2
|
+
* the token-tinted scrollbar: the visuals a Tailwind utility cannot
|
|
3
|
+
* express against our tokens, and nothing else.
|
|
3
4
|
*
|
|
4
5
|
* Every other visual in this package is a Tailwind utility reading
|
|
5
6
|
* a token, which is what makes a scheme/density/variant flip a
|
|
6
|
-
* repaint with no re-render.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* `
|
|
7
|
+
* repaint with no re-render. Two things sit outside that reach.
|
|
8
|
+
* Animation is one: Tailwind's own `animate-spin` and `animate-pulse`
|
|
9
|
+
* hardcode 1s and 2s, ignore `prefers-reduced-motion`, and are
|
|
10
|
+
* therefore beyond the theme's reach — which is exactly why
|
|
11
|
+
* `MotionTokens` carries a separate `loopFast`/`loopSlow` ramp. The
|
|
12
|
+
* scrollbar is the other: the `::-webkit-scrollbar` pseudo-elements
|
|
13
|
+
* (and Firefox's `scrollbar-width`/`scrollbar-color`) have no
|
|
14
|
+
* `@theme`-generated utility, so `.charcuterie-scrollbar` at the foot
|
|
15
|
+
* of this file reads the same `--color-*` roles by hand and flips with
|
|
16
|
+
* `data-scheme` on the same repaint as everything else.
|
|
12
17
|
*
|
|
13
18
|
* Consumers import this once, after `@charcuterie/tokens/theme.css`.
|
|
14
19
|
*
|
|
@@ -128,3 +133,78 @@
|
|
|
128
133
|
);
|
|
129
134
|
}
|
|
130
135
|
}
|
|
136
|
+
|
|
137
|
+
/* `.charcuterie-scrollbar` — a token-tinted scrollbar for any element
|
|
138
|
+
* that scrolls. The fleet grew this twice by hand (gallery-downloader's
|
|
139
|
+
* `scrollbar-thin-token`, image-viewer's global `::-webkit-scrollbar`
|
|
140
|
+
* block); this is the one copy, promoted here so a scheme flip
|
|
141
|
+
* repaints it for free.
|
|
142
|
+
*
|
|
143
|
+
* The *designed* look is the `::-webkit-scrollbar` path: a 12px bar, a
|
|
144
|
+
* rounded thumb, a track-coloured inset, no step arrows. That is what
|
|
145
|
+
* Chromium, Edge, and Safari paint. Chromium 121+ will *prefer*
|
|
146
|
+
* `scrollbar-width` / `scrollbar-color` over these pseudo-elements
|
|
147
|
+
* whenever both are set, which collapses the bar to the thin OS chrome
|
|
148
|
+
* (arrows and all on Windows) — so the standard properties are scoped
|
|
149
|
+
* to Firefox only, where `::-webkit-scrollbar` never runs.
|
|
150
|
+
*
|
|
151
|
+
* Firefox gets the closest standard-property match (thin, token-tinted
|
|
152
|
+
* thumb, transparent track). It cannot round a corner or draw a track
|
|
153
|
+
* inset — a platform limit, not an omission here.
|
|
154
|
+
*
|
|
155
|
+
* `::-webkit-scrollbar-button` is suppressed deliberately: the
|
|
156
|
+
* affordance is the thumb on a track, not step arrows.
|
|
157
|
+
*
|
|
158
|
+
* Both engines read the same three roles — `border-strong` for the
|
|
159
|
+
* thumb, `surface-sunken` for the track, `content-muted` for the thumb
|
|
160
|
+
* on hover — so the bar is themed by `[data-scheme]` on `<html>` with
|
|
161
|
+
* nothing in React observing it. `width`/`height` rather than a
|
|
162
|
+
* logical property is not a lapse: the `::-webkit-scrollbar`
|
|
163
|
+
* pseudo-elements size only by the physical pair, and a vertical bar's
|
|
164
|
+
* thickness is its `width` regardless of writing direction. */
|
|
165
|
+
|
|
166
|
+
/* The designed bar — Chromium, Edge, Safari. */
|
|
167
|
+
.charcuterie-scrollbar::-webkit-scrollbar {
|
|
168
|
+
width: 12px;
|
|
169
|
+
height: 12px;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.charcuterie-scrollbar::-webkit-scrollbar-button {
|
|
173
|
+
display: none;
|
|
174
|
+
width: 0;
|
|
175
|
+
height: 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.charcuterie-scrollbar::-webkit-scrollbar-track {
|
|
179
|
+
background-color: var(--color-surface-sunken);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.charcuterie-scrollbar::-webkit-scrollbar-thumb {
|
|
183
|
+
background-color: var(--color-border-strong);
|
|
184
|
+
border: 2px solid var(--color-surface-sunken);
|
|
185
|
+
border-radius: 6px;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.charcuterie-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
189
|
+
background-color: var(--color-content-muted);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.charcuterie-scrollbar::-webkit-scrollbar-corner {
|
|
193
|
+
background-color: var(--color-surface-sunken);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* Firefox only: it never paints `::-webkit-scrollbar`. `-moz-appearance`
|
|
197
|
+
* is the supports probe that Chromium and Safari reject, so this block
|
|
198
|
+
* does not reintroduce the standard properties on engines that would
|
|
199
|
+
* then drop the designed bar. */
|
|
200
|
+
@supports (-moz-appearance: none) {
|
|
201
|
+
.charcuterie-scrollbar {
|
|
202
|
+
scrollbar-width: thin;
|
|
203
|
+
scrollbar-color: color-mix(
|
|
204
|
+
in oklab,
|
|
205
|
+
var(--color-border-strong) 70%,
|
|
206
|
+
transparent
|
|
207
|
+
)
|
|
208
|
+
transparent;
|
|
209
|
+
}
|
|
210
|
+
}
|
package/package.json
CHANGED
|
@@ -49,6 +49,19 @@ is a control every consumer has to hold it for.
|
|
|
49
49
|
|
|
50
50
|
<Canvas of={CheckboxStories.AllStates} />
|
|
51
51
|
|
|
52
|
+
## Three ways a box can be inactive
|
|
53
|
+
|
|
54
|
+
`isDisabled` and `isReadOnly` are different states, and the box shows all three:
|
|
55
|
+
|
|
56
|
+
- **Enabled** — the accent intent, a crisp `border-strong` edge.
|
|
57
|
+
- **Disabled** — the same box turned down with `opacity-60`. It dims rather than fading a
|
|
58
|
+
token because the scale has no step between `border-default` and `border-strong`, so a
|
|
59
|
+
muted outline was either invisible or indistinguishable from enabled.
|
|
60
|
+
- **Read-only** — full contrast, because the whole point is that the value stays legible,
|
|
61
|
+
but in the **neutral** intent with a softer edge: an informational value you may read
|
|
62
|
+
here and change elsewhere, not an actionable accent. It announces `aria-readonly`, and
|
|
63
|
+
the toggle is blocked on both pointer and `Space`.
|
|
64
|
+
|
|
52
65
|
## Checkbox or `Switch`
|
|
53
66
|
|
|
54
67
|
Same state kind, different meaning. A checkbox is a value you **submit** — "delete
|