@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 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,kEAQtB,aAAa,KAAG,SAyDlB,CAAA"}
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 cursor-pointer items-center gap-2 text-content-secondary select-none", TEXT_SIZE_CLASS[size], isDisabled &&
49
- "cursor-not-allowed text-content-disabled", className), children: [_jsxs("span", { className: "relative grid shrink-0 place-items-center", children: [_jsx("input", { className: toClassName("peer cursor-pointer appearance-none rounded-sm border border-border-strong bg-surface-sunken transition-colors duration-(--duration-fast) ease-standard", "checked:border-intent-accent-solid checked:bg-intent-accent-solid",
50
- // Disabled mutes the border and the cursor, and — unlike the
51
- // shared `DISABLED_CLASS` — does *not* touch the background.
52
- // The generic bundle repaints the box `surface-sunken`, which
53
- // erased both the checked accent fill (white tick on grey)
54
- // and, in a light scheme, the unchecked box entirely: a
55
- // `border-subtle` outline on `surface-sunken` on
56
- // `surface-base` is three near-identical creams. A disabled
57
- // box is still a box the user has to see.
58
- "disabled:cursor-not-allowed disabled:border-border-default", BOX_SIZE_CLASS[size], FOCUS_RING_CLASS), defaultChecked: isChecked, disabled: isDisabled, id: id, onChange: (event) => {
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
- }, type: "checkbox" }), _jsx("svg", { "aria-hidden": "true", className: toClassName("pointer-events-none invisible absolute text-intent-accent-on-solid peer-checked:visible", 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 })] }));
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;AA6B/C,MAAM,cAAc,GAAgC;IAClD,EAAE,EAAE,UAAU;IACd,EAAE,EAAE,QAAQ;IACZ,EAAE,EAAE,QAAQ;CACb,CAAA;AAED,MAAM,gBAAgB,GAAgC;IACpD,EAAE,EAAE,UAAU;IACd,EAAE,EAAE,QAAQ;IACZ,EAAE,EAAE,UAAU;CACf,CAAA;AAED,MAAM,eAAe,GAAgC;IACnD,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,SAAS;CACd,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,EACvB,SAAS,EACT,EAAE,EACF,SAAS,GAAG,KAAK,EACjB,UAAU,GAAG,KAAK,EAClB,KAAK,EACL,QAAQ,EACR,IAAI,GAAG,IAAI,GACG,EAAa,EAAE,CAAC,CAC9B,iBACE,SAAS,EAAE,WAAW,CACpB,kFAAkF,EAClF,eAAe,CAAC,IAAI,CAAC,EACrB,UAAU;QACR,0CAA0C,EAC5C,SAAS,CACV,aAED,gBAAM,SAAS,EAAC,2CAA2C,aACzD,gBACE,SAAS,EAAE,WAAW,CACpB,yJAAyJ,EACzJ,mEAAmE;oBACnE,6DAA6D;oBAC7D,6DAA6D;oBAC7D,8DAA8D;oBAC9D,2DAA2D;oBAC3D,wDAAwD;oBACxD,iDAAiD;oBACjD,4DAA4D;oBAC5D,0CAA0C;oBAC1C,4DAA4D,EAC5D,cAAc,CAAC,IAAI,CAAC,EACpB,gBAAgB,CACjB,EACD,cAAc,EAAE,SAAS,EACzB,QAAQ,EAAE,UAAU,EACpB,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,CACR,KAAoC,EACpC,EAAE;wBACF,QAAQ,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;oBAClC,CAAC,EACD,IAAI,EAAC,UAAU,GACf,EAEF,6BACc,MAAM,EAClB,SAAS,EAAE,WAAW,CACpB,yFAAyF,EACzF,gBAAgB,CAAC,IAAI,CAAC,CACvB,EACD,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EACtB,WAAW,EAAE,CAAC,EACd,OAAO,EAAC,WAAW,YAEnB,eAAM,CAAC,EAAC,iBAAiB,GAAG,GACxB,IACD,EAEP,yBAAO,KAAK,GAAQ,IACd,CACT,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,6DAOxB,eAAe,KAAG,SAqIpB,CAAA"}
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
- select(value);
58
- }, [select, setActiveValue]);
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
- if (activeValue !== null) {
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 === " " && activeValue !== null) {
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":"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"}
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;AAoBD;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,GAAI,gGAS9B,qBAAqB,KAAG,SAiE1B,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.5",
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 cursor-pointer items-center gap-2 rounded-sm text-start transition-colors duration-(--duration-fast) ease-standard", TEXT_SIZE_CLASS[size], isDisabled
39
- ? "cursor-not-allowed text-content-disabled"
40
- : "text-content-secondary hover:text-content-primary", FOCUS_RING_CLASS), disabled: isDisabled, onClick: () => {
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("grid shrink-0 place-items-center rounded-full border transition-colors duration-(--duration-fast) ease-standard", isDisabled
45
- ? "border-border-subtle"
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", isChecked ? "scale-100" : "scale-0", isDisabled
49
- ? "bg-content-disabled"
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;AAmB/C,MAAM,eAAe,GAAgC;IACnD,EAAE,EAAE,UAAU;IACd,EAAE,EAAE,QAAQ;IACZ,EAAE,EAAE,QAAQ;CACb,CAAA;AAED,MAAM,cAAc,GAAgC;IAClD,EAAE,EAAE,UAAU;IACd,EAAE,EAAE,QAAQ;IACZ,EAAE,EAAE,UAAU;CACf,CAAA;AAED,MAAM,eAAe,GAAgC;IACnD,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,SAAS;CACd,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,EAC/B,SAAS,EACT,IAAI,EACJ,QAAQ,EACR,aAAa,EACb,iBAAiB,EACjB,IAAI,EACJ,QAAQ,EACR,YAAY,GACU,EAAa,EAAE;IACrC,MAAM,EAAE,UAAU,GAAG,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,IAAI,CAAA;IAEjD,SAAS,CACP,GAAG,EAAE,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAC9B,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAC3B,CAAA;IAED,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,UAAU,EAAE,CAAC;YACf,OAAM;QACR,CAAC;QAED,OAAO,aAAa,CAAC,KAAK,CAAC,CAAA;IAC7B,CAAC,EAAE,CAAC,UAAU,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC,CAAA;IAEtC,OAAO,CAEL,kCACgB,SAAS,EACvB,SAAS,EAAE,WAAW,CACpB,gIAAgI,EAChI,eAAe,CAAC,IAAI,CAAC,EACrB,UAAU;YACR,CAAC,CAAC,0CAA0C;YAC5C,CAAC,CAAC,mDAAmD,EACvD,gBAAgB,CACjB,EACD,QAAQ,EAAE,UAAU,EACpB,OAAO,EAAE,GAAG,EAAE;YACZ,QAAQ,CAAC,KAAK,CAAC,CAAA;QACjB,CAAC,EACD,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE;YACf,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;QAC9B,CAAC,EACD,IAAI,EAAC,OAAO,EACZ,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAC,QAAQ,aAEb,eACE,SAAS,EAAE,WAAW,CACpB,iHAAiH,EACjH,UAAU;oBACR,CAAC,CAAC,sBAAsB;oBACxB,CAAC,CAAC,SAAS;wBACT,CAAC,CAAC,4BAA4B;wBAC9B,CAAC,CAAC,sBAAsB,EAC5B,eAAe,CAAC,IAAI,CAAC,CACtB,YAED,eACE,SAAS,EAAE,WAAW,CACpB,4EAA4E,EAC5E,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,EACnC,UAAU;wBACR,CAAC,CAAC,qBAAqB;wBACvB,CAAC,CAAC,wBAAwB,EAC5B,cAAc,CAAC,IAAI,CAAC,CACrB,GACD,GACG,EAEP,yBAAO,KAAK,GAAQ,IACb,CACV,CAAA;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"RadioGroupOption.js","sourceRoot":"","sources":["../../src/RadioGroup/RadioGroupOption.tsx"],"names":[],"mappings":"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"}
@@ -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,8DAOpB,WAAW,KAAG,SA+DhB,CAAA"}
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"}
@@ -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], isDisabled && "text-content-disabled", className), children: [_jsx("button", { "aria-checked": isOn, "aria-labelledby": labelId, className: toClassName(
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 cursor-pointer items-center rounded-full p-1 transition-colors duration-(--duration-fast) ease-standard",
74
- // One state class, chosen by ternary — two `bg-`/`ring-`
75
- // utilities of the same specificity do not resolve by
76
- // class-list order, so disabled has to win as the only one
77
- // written.
78
- isDisabled
79
- ? "cursor-not-allowed bg-surface-sunken ring-2 ring-border-subtle ring-inset"
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", TRACK_SIZE_CLASS[size], FOCUS_RING_CLASS), disabled: isDisabled, onClick: () => {
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;AAkB/C,oEAAoE;AACpE,iEAAiE;AACjE,uEAAuE;AACvE,sEAAsE;AACtE,kDAAkD;AAClD,MAAM,gBAAgB,GAAgC;IACpD,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,UAAU;IACd,EAAE,EAAE,UAAU;CACf,CAAA;AAED,MAAM,gBAAgB,GAAgC;IACpD,EAAE,EAAE,QAAQ;IACZ,EAAE,EAAE,QAAQ;IACZ,EAAE,EAAE,QAAQ;CACb,CAAA;AAED,MAAM,wBAAwB,GAG1B;IACF,EAAE,EAAE,eAAe;IACnB,EAAE,EAAE,eAAe;IACnB,EAAE,EAAE,eAAe;CACpB,CAAA;AAED,MAAM,eAAe,GAAgC;IACnD,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,SAAS;CACd,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EACrB,SAAS,EACT,SAAS,GAAG,KAAK,EACjB,UAAU,GAAG,KAAK,EAClB,KAAK,EACL,QAAQ,EACR,IAAI,GAAG,IAAI,GACC,EAAa,EAAE;IAC3B,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAA;IAE3C,MAAM,OAAO,GAAG,WAAW,EAAE,CAAA;IAE7B,OAAO,CACL,gBACE,SAAS,EAAE,WAAW,CACpB,mEAAmE,EACnE,eAAe,CAAC,IAAI,CAAC,EACrB,UAAU,IAAI,uBAAuB,EACrC,SAAS,CACV,aAED,iCACgB,IAAI,qBACD,OAAO,EACxB,SAAS,EAAE,WAAW;gBACpB,0DAA0D;gBAC1D,yDAAyD;gBACzD,gCAAgC;gBAChC,8HAA8H;gBAC9H,yDAAyD;gBACzD,sDAAsD;gBACtD,2DAA2D;gBAC3D,WAAW;gBACX,UAAU;oBACR,CAAC,CAAC,2EAA2E;oBAC7E,CAAC,CAAC,IAAI;wBACJ,CAAC,CAAC,wBAAwB;wBAC1B,CAAC,CAAC,wDAAwD,EAC9D,gBAAgB,CAAC,IAAI,CAAC,EACtB,gBAAgB,CACjB,EACD,QAAQ,EAAE,UAAU,EACpB,OAAO,EAAE,GAAG,EAAE;oBACZ,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAA;oBAEtB,OAAO,CAAC,QAAQ,CAAC,CAAA;oBAEjB,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAA;gBACtB,CAAC,EACD,IAAI,EAAC,QAAQ,EACb,IAAI,EAAC,QAAQ,YAEb,eACE,SAAS,EAAE,WAAW,CACpB,4EAA4E;oBAC5E,uDAAuD;oBACvD,UAAU;wBACR,CAAC,CAAC,qBAAqB;wBACvB,CAAC,CAAC,mBAAmB,EACvB,IAAI;wBACF,CAAC,CAAC,wBAAwB,CAAC,IAAI,CAAC;wBAChC,CAAC,CAAC,eAAe,EACnB,gBAAgB,CAAC,IAAI,CAAC,CACvB,GACD,GACK,EAET,eAAM,EAAE,EAAE,OAAO,YAAG,KAAK,GAAQ,IAC5B,CACR,CAAA;AACH,CAAC,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, and
2
- * nothing else.
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. Animation is the one thing utilities
7
- * cannot express against our tokens: Tailwind's own `animate-spin`
8
- * and `animate-pulse` hardcode 1s and 2s, ignore
9
- * `prefers-reduced-motion`, and are therefore beyond the theme's
10
- * reach — which is exactly why `MotionTokens` carries a separate
11
- * `loopFast`/`loopSlow` ramp.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@charcuterie/ui",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "description": "The Charcuterie components. Tokens for look, @charcuterie/logic for state, ARIA that agents can actually drive.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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