@charcuterie/ui 2.17.0 → 2.18.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.
@@ -34,6 +34,25 @@ export type CheckboxProps = {
34
34
  label: ReactNode;
35
35
  onChange?: (isChecked: boolean) => void;
36
36
  size?: ControlSize;
37
+ /**
38
+ * The `<input>`'s `value` attribute — which member of a group this
39
+ * box IS, as opposed to whether it is ticked.
40
+ *
41
+ * A lone boolean does not need this. A GROUP does: the browser
42
+ * submits `name=value` for each ticked box in a form, and, more
43
+ * relevantly here, a group is normally read back with one query —
44
+ * `[...root.querySelectorAll("input")].filter(i => i.checked)
45
+ * .map(i => i.value)`. Without a `value` every box in the group
46
+ * reports the string `"on"` and the read is useless, which is why
47
+ * queuepilot's library and ratings pickers were still hand-rolling
48
+ * a raw `<input type="checkbox">` rather than adopting this
49
+ * component.
50
+ *
51
+ * Passed straight through and otherwise inert. It is NOT the
52
+ * checked state and does not become one: `isChecked` still seeds
53
+ * the box, and `onChange` still reports a boolean.
54
+ */
55
+ value?: string;
37
56
  };
38
57
  /**
39
58
  * A single boolean, on or off, that owns nothing but itself.
@@ -64,5 +83,5 @@ export type CheckboxProps = {
64
83
  * `<input>`'s job. `isChecked` seeds the first paint and nothing
65
84
  * after.
66
85
  */
67
- export declare const Checkbox: ({ className, id, isChecked, isDisabled, isReadOnly, label, onChange, size, }: CheckboxProps) => ReactNode;
86
+ export declare const Checkbox: ({ className, id, isChecked, isDisabled, isReadOnly, label, onChange, size, value, }: CheckboxProps) => ReactNode;
68
87
  //# 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;;;;;;;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"}
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;IAClB;;;;;;;;;;;;;;;;;OAiBG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAoBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,QAAQ,GAAI,qFAUtB,aAAa,KAAG,SA2GlB,CAAA"}
@@ -45,7 +45,7 @@ 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, 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],
48
+ export const Checkbox = ({ className, id, isChecked = false, isDisabled = false, isReadOnly = false, label, onChange, size = "md", value, }) => (_jsxs("label", { className: toClassName("inline-flex items-center gap-2 text-content-secondary select-none", TEXT_SIZE_CLASS[size],
49
49
  // One cursor, chosen by state — two cursor utilities of the same
50
50
  // specificity do not resolve by class-list order.
51
51
  isDisabled
@@ -99,7 +99,7 @@ export const Checkbox = ({ className, id, isChecked = false, isDisabled = false,
99
99
  if (isReadOnly && keyEvent.key === " ") {
100
100
  keyEvent.preventDefault();
101
101
  }
102
- }, type: "checkbox" }), _jsx("svg", { "aria-hidden": "true", className: toClassName("pointer-events-none invisible absolute peer-checked:visible",
102
+ }, type: "checkbox", value: value }), _jsx("svg", { "aria-hidden": "true", className: toClassName("pointer-events-none invisible absolute peer-checked:visible",
103
103
  // Matches whichever fill the box wears, so the tick keeps its
104
104
  // guaranteed on-solid contrast in either intent.
105
105
  isReadOnly
@@ -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;AAsC/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,UAAU,GAAG,KAAK,EAClB,KAAK,EACL,QAAQ,EACR,IAAI,GAAG,IAAI,GACG,EAAa,EAAE,CAAC,CAC9B,iBACE,SAAS,EAAE,WAAW,CACpB,mEAAmE,EACnE,eAAe,CAAC,IAAI,CAAC;IACrB,iEAAiE;IACjE,kDAAkD;IAClD,UAAU;QACR,CAAC,CAAC,oBAAoB;QACtB,CAAC,CAAC,UAAU;YACV,CAAC,CAAC,gBAAgB;YAClB,CAAC,CAAC,gBAAgB;IACtB,kEAAkE;IAClE,kEAAkE;IAClE,+DAA+D;IAC/D,mEAAmE;IACnE,kEAAkE;IAClE,kEAAkE;IAClE,mEAAmE;IACnE,sDAAsD;IACtD,UAAU,IAAI,YAAY,EAC1B,SAAS,CACV,aAED,gBAAM,SAAS,EAAC,2CAA2C,aACzD,iCACiB,UAAU,IAAI,SAAS,EACtC,SAAS,EAAE,WAAW;oBACpB,0DAA0D;oBAC1D,8DAA8D;oBAC9D,8DAA8D;oBAC9D,mDAAmD;oBACnD,uHAAuH;oBACvH,+DAA+D;oBAC/D,8DAA8D;oBAC9D,+DAA+D;oBAC/D,0DAA0D;oBAC1D,iEAAiE;oBACjE,6DAA6D;oBAC7D,8DAA8D;oBAC9D,4BAA4B;oBAC5B,UAAU;wBACR,CAAC,CAAC,2FAA2F;wBAC7F,CAAC,CAAC,wFAAwF,EAC5F,UAAU;wBACR,CAAC,CAAC,oBAAoB;wBACtB,CAAC,CAAC,UAAU;4BACV,CAAC,CAAC,gBAAgB;4BAClB,CAAC,CAAC,gBAAgB,EACtB,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,IAAI,UAAU,EAAE,CAAC;4BACf,OAAM;wBACR,CAAC;wBAED,QAAQ,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;oBAClC,CAAC,EACD,OAAO,EAAE,CAAC,UAAU,EAAE,EAAE;wBACtB,yDAAyD;wBACzD,yDAAyD;wBACzD,qCAAqC;wBACrC,IAAI,UAAU,EAAE,CAAC;4BACf,UAAU,CAAC,cAAc,EAAE,CAAA;wBAC7B,CAAC;oBACH,CAAC,EACD,SAAS,EAAE,CAAC,QAAQ,EAAE,EAAE;wBACtB,yDAAyD;wBACzD,SAAS;wBACT,IAAI,UAAU,IAAI,QAAQ,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC;4BACvC,QAAQ,CAAC,cAAc,EAAE,CAAA;wBAC3B,CAAC;oBACH,CAAC,EACD,IAAI,EAAC,UAAU,GACf,EAEF,6BACc,MAAM,EAClB,SAAS,EAAE,WAAW,CACpB,6DAA6D;oBAC7D,8DAA8D;oBAC9D,iDAAiD;oBACjD,UAAU;wBACR,CAAC,CAAC,8BAA8B;wBAChC,CAAC,CAAC,6BAA6B,EACjC,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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@charcuterie/ui",
3
- "version": "2.17.0",
3
+ "version": "2.18.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",
@@ -62,6 +62,30 @@ is a control every consumer has to hold it for.
62
62
  here and change elsewhere, not an actionable accent. It announces `aria-readonly`, and
63
63
  the toggle is blocked on both pointer and `Space`.
64
64
 
65
+ ## A group of boxes needs `value`
66
+
67
+ `value` is the `<input>`'s `value` attribute: **which member of a group** this box is, as
68
+ opposed to whether it is ticked. A lone boolean should omit it.
69
+
70
+ A group needs it, because a group is read back with one query over its container rather
71
+ than by keeping a callback's worth of state per box:
72
+
73
+ ```ts
74
+ const chosen = [...group.querySelectorAll<HTMLInputElement>("input[type=checkbox]")]
75
+ .filter((input) => input.checked)
76
+ .map((input) => input.value)
77
+ ```
78
+
79
+ Without a `value` every box in the group answers the UA default `"on"`, so that read
80
+ returns N copies of one meaningless string. This is why queuepilot's library and ratings
81
+ pickers were still hand-rolling a raw `<input type="checkbox">` instead of using this
82
+ component — the component was correct and simply could not express the case.
83
+
84
+ It is passed straight through and is otherwise inert: it is **not** the checked state and
85
+ does not become one. `isChecked` still seeds the box, `onChange` still reports a boolean.
86
+
87
+ <Canvas of={CheckboxStories.WithValues} />
88
+
65
89
  ## Checkbox or `Switch`
66
90
 
67
91
  Same state kind, different meaning. A checkbox is a value you **submit** — "delete
@@ -120,3 +120,45 @@ export const AllStates: Story = {
120
120
  export const Interactive: Story = {
121
121
  args: { label: "Delete originals after import" },
122
122
  }
123
+
124
+ /**
125
+ * `value` names which member of a group a box IS — not whether it is
126
+ * ticked. It matters as soon as there is more than one box: a group is
127
+ * read back with a single query over its container, and without a
128
+ * `value` every box in it answers `"on"`.
129
+ *
130
+ * ```ts
131
+ * const chosen = [...group.querySelectorAll("input")]
132
+ * .filter((input) => input.checked)
133
+ * .map((input) => input.value)
134
+ * ```
135
+ *
136
+ * A lone boolean needs none of this and should omit the prop.
137
+ */
138
+ export const WithValues: Story = {
139
+ args: { label: "Anime" },
140
+ render: (controlProps) => (
141
+ <StorySection title="A library picker: each box carries the id the caller will read back, and the checked ones are the answer.">
142
+ <StoryGrid columns={2}>
143
+ {[
144
+ { id: "11", isChosen: true, title: "Anime" },
145
+ { id: "1", isChosen: false, title: "Movies" },
146
+ { id: "15", isChosen: true, title: "Shorts" },
147
+ { id: "2", isChosen: false, title: "Shows" },
148
+ ].map((library) => (
149
+ <StoryCell
150
+ key={library.id}
151
+ label={`value="${library.id}"`}
152
+ >
153
+ <Checkbox
154
+ {...controlProps}
155
+ isChecked={library.isChosen}
156
+ label={library.title}
157
+ value={library.id}
158
+ />
159
+ </StoryCell>
160
+ ))}
161
+ </StoryGrid>
162
+ </StorySection>
163
+ ),
164
+ }
@@ -7,7 +7,7 @@ import { mountStory } from "../mountStory.testHelpers.ts"
7
7
  import { expectAgentDrivable } from "../testing/index.ts"
8
8
  import * as stories from "./Checkbox.stories.tsx"
9
9
 
10
- const { AllStates, Default, Interactive } =
10
+ const { AllStates, Default, Interactive, WithValues } =
11
11
  composeStories(stories)
12
12
 
13
13
  test("it is a checkbox a screen reader can name", async () => {
@@ -107,3 +107,54 @@ test("a read-only box is announced and cannot be toggled", async () => {
107
107
 
108
108
  await expectNoAxeViolations(canvasElement)
109
109
  })
110
+
111
+ test("value names the member, and a group reads back the ticked ones", async () => {
112
+ const { canvas, canvasElement } =
113
+ await mountStory(WithValues)
114
+
115
+ // The prop is the `<input>`'s `value` and nothing else — it must not
116
+ // touch the checked state, which `isChecked` still owns alone.
117
+ await expect(
118
+ expectAgentDrivable(canvas, {
119
+ name: "Anime",
120
+ role: "checkbox",
121
+ }),
122
+ ).toHaveAttribute("value", "11")
123
+
124
+ await expect(
125
+ expectAgentDrivable(canvas, {
126
+ name: "Movies",
127
+ role: "checkbox",
128
+ }),
129
+ ).not.toBeChecked()
130
+
131
+ // The read this prop exists for. Without a `value` every box here
132
+ // answers the UA default `"on"` and the result is four copies of one
133
+ // meaningless string, which is why a group could not use this
134
+ // component at all before.
135
+ const chosen = [
136
+ ...canvasElement.querySelectorAll<HTMLInputElement>(
137
+ "input[type=checkbox]",
138
+ ),
139
+ ]
140
+ .filter((input) => input.checked)
141
+ .map((input) => input.value)
142
+
143
+ await expect(chosen).toEqual(["11", "15"])
144
+
145
+ await expectNoAxeViolations(canvasElement)
146
+ })
147
+
148
+ test("a box with no value stays out of the way", async () => {
149
+ const { canvas } = await mountStory(Default)
150
+
151
+ // `value` is optional and a lone boolean should omit it. React drops
152
+ // an `undefined` attribute entirely rather than writing an empty
153
+ // string, so the DOM looks exactly as it did before the prop existed.
154
+ await expect(
155
+ expectAgentDrivable(canvas, {
156
+ name: "Delete originals after import",
157
+ role: "checkbox",
158
+ }),
159
+ ).not.toHaveAttribute("value")
160
+ })
@@ -38,6 +38,25 @@ export type CheckboxProps = {
38
38
  label: ReactNode
39
39
  onChange?: (isChecked: boolean) => void
40
40
  size?: ControlSize
41
+ /**
42
+ * The `<input>`'s `value` attribute — which member of a group this
43
+ * box IS, as opposed to whether it is ticked.
44
+ *
45
+ * A lone boolean does not need this. A GROUP does: the browser
46
+ * submits `name=value` for each ticked box in a form, and, more
47
+ * relevantly here, a group is normally read back with one query —
48
+ * `[...root.querySelectorAll("input")].filter(i => i.checked)
49
+ * .map(i => i.value)`. Without a `value` every box in the group
50
+ * reports the string `"on"` and the read is useless, which is why
51
+ * queuepilot's library and ratings pickers were still hand-rolling
52
+ * a raw `<input type="checkbox">` rather than adopting this
53
+ * component.
54
+ *
55
+ * Passed straight through and otherwise inert. It is NOT the
56
+ * checked state and does not become one: `isChecked` still seeds
57
+ * the box, and `onChange` still reports a boolean.
58
+ */
59
+ value?: string
41
60
  }
42
61
 
43
62
  const BOX_SIZE_CLASS: Record<ControlSize, string> = {
@@ -96,6 +115,7 @@ export const Checkbox = ({
96
115
  label,
97
116
  onChange,
98
117
  size = "md",
118
+ value,
99
119
  }: CheckboxProps): ReactNode => (
100
120
  <label
101
121
  className={toClassName(
@@ -176,6 +196,7 @@ export const Checkbox = ({
176
196
  }
177
197
  }}
178
198
  type="checkbox"
199
+ value={value}
179
200
  />
180
201
 
181
202
  <svg