@charcuterie/ui 4.0.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +3 -2
  2. package/dist/ActionTiles/ActionTiles.d.ts +66 -8
  3. package/dist/ActionTiles/ActionTiles.d.ts.map +1 -1
  4. package/dist/ActionTiles/ActionTiles.js +88 -11
  5. package/dist/ActionTiles/ActionTiles.js.map +1 -1
  6. package/dist/PortraitTiles/PortraitAvatar.d.ts +21 -0
  7. package/dist/PortraitTiles/PortraitAvatar.d.ts.map +1 -0
  8. package/dist/PortraitTiles/PortraitAvatar.js +29 -0
  9. package/dist/PortraitTiles/PortraitAvatar.js.map +1 -0
  10. package/dist/PortraitTiles/PortraitTiles.d.ts +162 -0
  11. package/dist/PortraitTiles/PortraitTiles.d.ts.map +1 -0
  12. package/dist/PortraitTiles/PortraitTiles.js +263 -0
  13. package/dist/PortraitTiles/PortraitTiles.js.map +1 -0
  14. package/dist/PortraitTiles/portraitColour.d.ts +49 -0
  15. package/dist/PortraitTiles/portraitColour.d.ts.map +1 -0
  16. package/dist/PortraitTiles/portraitColour.js +55 -0
  17. package/dist/PortraitTiles/portraitColour.js.map +1 -0
  18. package/dist/RouterLink/UnstyledLink.d.ts +17 -0
  19. package/dist/RouterLink/UnstyledLink.d.ts.map +1 -0
  20. package/dist/RouterLink/UnstyledLink.js +24 -0
  21. package/dist/RouterLink/UnstyledLink.js.map +1 -0
  22. package/dist/categoricalStyles.d.ts +27 -0
  23. package/dist/categoricalStyles.d.ts.map +1 -1
  24. package/dist/categoricalStyles.js +49 -0
  25. package/dist/categoricalStyles.js.map +1 -1
  26. package/dist/index.d.ts +6 -2
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +3 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/tileStyles.d.ts +15 -0
  31. package/dist/tileStyles.d.ts.map +1 -1
  32. package/dist/tileStyles.js +26 -1
  33. package/dist/tileStyles.js.map +1 -1
  34. package/package.json +2 -2
  35. package/src/ActionTiles/ActionTiles.mdx +82 -8
  36. package/src/ActionTiles/ActionTiles.stories.tsx +107 -2
  37. package/src/ActionTiles/ActionTiles.test.tsx +205 -5
  38. package/src/ActionTiles/ActionTiles.tsx +274 -110
  39. package/src/PortraitTiles/PortraitAvatar.tsx +49 -0
  40. package/src/PortraitTiles/PortraitTiles.mdx +153 -0
  41. package/src/PortraitTiles/PortraitTiles.stories.tsx +403 -0
  42. package/src/PortraitTiles/PortraitTiles.test.tsx +399 -0
  43. package/src/PortraitTiles/PortraitTiles.tsx +577 -0
  44. package/src/PortraitTiles/portraitColour.test.ts +39 -0
  45. package/src/PortraitTiles/portraitColour.ts +67 -0
  46. package/src/RadioGroup/RadioGroup.stories.tsx +39 -0
  47. package/src/RouterLink/RouterLink.mdx +25 -4
  48. package/src/RouterLink/UnstyledLink.stories.tsx +57 -0
  49. package/src/RouterLink/UnstyledLink.test.tsx +40 -0
  50. package/src/RouterLink/UnstyledLink.tsx +34 -0
  51. package/src/categoricalStyles.ts +57 -0
  52. package/src/index.ts +11 -0
  53. package/src/sourceRules.test.ts +17 -4
  54. package/src/tileStyles.ts +30 -1
package/dist/index.js CHANGED
@@ -50,7 +50,7 @@ export { ColorSchemeToggle } from "./ColorSchemeToggle/ColorSchemeToggle.js";
50
50
  export { Combobox } from "./Combobox/Combobox.js";
51
51
  export { CopyButton } from "./CopyButton/CopyButton.js";
52
52
  export { copyText } from "./CopyButton/copyText.js";
53
- export { CATEGORICAL_APPEARANCE_CLASS, CATEGORICAL_CONTENT_CLASS, CATEGORICAL_HOVER_CLASS, CATEGORICAL_SOLID_FILL_CLASS, } from "./categoricalStyles.js";
53
+ export { CATEGORICAL_APPEARANCE_CLASS, CATEGORICAL_CONTENT_CLASS, CATEGORICAL_HOVER_BORDER_CLASS, CATEGORICAL_HOVER_CLASS, CATEGORICAL_RING_CLASS, CATEGORICAL_SOLID_FILL_CLASS, } from "./categoricalStyles.js";
54
54
  export { BADGE_SIZE_CLASS, CONTROL_BASE_CLASS, CONTROL_SIZE_CLASS, DOT_SIZE_CLASS, getControlClassName, ICON_CONTROL_SIZE_CLASS, MIN_TOUCH_TARGET_CLASS, SPINNER_SIZE_CLASS, } from "./controlStyles.js";
55
55
  export { DataTable } from "./DataTable/DataTable.js";
56
56
  export { DatePicker } from "./DatePicker/DatePicker.js";
@@ -88,6 +88,7 @@ export { useNavLayout } from "./Nav/useNavLayout.js";
88
88
  export { NavBar } from "./NavBar/NavBar.js";
89
89
  export { Picker } from "./Picker/Picker.js";
90
90
  export { Popover } from "./Popover/Popover.js";
91
+ export { PortraitTiles } from "./PortraitTiles/PortraitTiles.js";
91
92
  export { ProgressBar } from "./ProgressBar/ProgressBar.js";
92
93
  export { getProgressIntent, toProgressPercent, toProgressValue, } from "./ProgressBar/progressValue.js";
93
94
  export { QueryBuilder } from "./QueryBuilder/QueryBuilder.js";
@@ -98,6 +99,7 @@ export { ReorderList } from "./ReorderList/ReorderList.js";
98
99
  export { AnchorLink } from "./RouterLink/AnchorLink.js";
99
100
  export { RouterLinkProvider, useRouterLink, } from "./RouterLink/RouterLinkProvider.js";
100
101
  export { getIsRoutedHref } from "./RouterLink/routerLink.js";
102
+ export { UnstyledLink } from "./RouterLink/UnstyledLink.js";
101
103
  export { SearchInput } from "./SearchInput/SearchInput.js";
102
104
  export { SegmentedControl } from "./SegmentedControl/SegmentedControl.js";
103
105
  export { Select } from "./Select/Select.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -31,6 +31,21 @@ import type { ControlSize } from "@charcuterie/tokens";
31
31
  * anywhere. QueuePilot's queue-type chooser shipped exactly that.
32
32
  */
33
33
  export declare const TILE_PADDING_CLASS: Record<ControlSize, string>;
34
+ /**
35
+ * What a tile with an accent edge adds to its LEADING padding.
36
+ *
37
+ * The bar is drawn by `Card`'s accent-edge pseudo-element, which is
38
+ * an overlay rather than a border — so it takes no space in the box
39
+ * model and lands on top of the text unless the tile makes room for
40
+ * it. `Card` has the same arrangement and the same answer.
41
+ *
42
+ * A separate map rather than a second full padding scale, because
43
+ * only the leading side changes and the shared box is the promise
44
+ * the 2026-09-01 record made. It is applied ONLY when an edge is
45
+ * drawn: a radio tile has no bar, and giving it this padding would
46
+ * be dead space on the side its own control sits on.
47
+ */
48
+ export declare const TILE_ACCENT_EDGE_PADDING_CLASS: Record<ControlSize, string>;
34
49
  /**
35
50
  * The box, and only the box. Every state layered over it is a
36
51
  * **border and a surface**, never colour alone — the same reason
@@ -1 +1 @@
1
- {"version":3,"file":"tileStyles.d.ts","sourceRoot":"","sources":["../src/tileStyles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEtD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CACrC,WAAW,EACX,MAAM,CAKP,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,6DACiC,CAAA;AAE5D,eAAO,MAAM,oBAAoB,EAAE,MAAM,CACvC,WAAW,EACX,MAAM,CAKP,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAAM,CACvC,WAAW,EACX,MAAM,CAKP,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAItD,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,kBAAkB,gGACgE,CAAA;AAE/F,8DAA8D;AAC9D,eAAO,MAAM,6BAA6B,uCACJ,CAAA"}
1
+ {"version":3,"file":"tileStyles.d.ts","sourceRoot":"","sources":["../src/tileStyles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEtD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CACrC,WAAW,EACX,MAAM,CAWP,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,8BAA8B,EAAE,MAAM,CACjD,WAAW,EACX,MAAM,CAKP,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,6DACiC,CAAA;AAE5D,eAAO,MAAM,oBAAoB,EAAE,MAAM,CACvC,WAAW,EACX,MAAM,CAKP,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAAM,CACvC,WAAW,EACX,MAAM,CAKP,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAItD,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,kBAAkB,gGACgE,CAAA;AAE/F,8DAA8D;AAC9D,eAAO,MAAM,6BAA6B,uCACJ,CAAA"}
@@ -32,7 +32,32 @@
32
32
  export const TILE_PADDING_CLASS = {
33
33
  sm: "px-3 py-2.5",
34
34
  md: "px-3.5 py-3",
35
- lg: "px-4 py-3.5",
35
+ // `lg` is the landing-page tile — mux-magic's "Pick a tool",
36
+ // Gallery Downloader's two cards — where the tile is the only
37
+ // thing on the page and reads as a panel rather than as a row.
38
+ // It was `px-4 py-3.5`, which is a control's padding wearing a
39
+ // card's name, and the owner called the result "very boring"
40
+ // when it shipped in QueuePilot.
41
+ lg: "px-6 py-5",
42
+ };
43
+ /**
44
+ * What a tile with an accent edge adds to its LEADING padding.
45
+ *
46
+ * The bar is drawn by `Card`'s accent-edge pseudo-element, which is
47
+ * an overlay rather than a border — so it takes no space in the box
48
+ * model and lands on top of the text unless the tile makes room for
49
+ * it. `Card` has the same arrangement and the same answer.
50
+ *
51
+ * A separate map rather than a second full padding scale, because
52
+ * only the leading side changes and the shared box is the promise
53
+ * the 2026-09-01 record made. It is applied ONLY when an edge is
54
+ * drawn: a radio tile has no bar, and giving it this padding would
55
+ * be dead space on the side its own control sits on.
56
+ */
57
+ export const TILE_ACCENT_EDGE_PADDING_CLASS = {
58
+ sm: "ps-4",
59
+ md: "ps-4.5",
60
+ lg: "ps-7",
36
61
  };
37
62
  /**
38
63
  * The box, and only the box. Every state layered over it is a
@@ -1 +1 @@
1
- {"version":3,"file":"tileStyles.js","sourceRoot":"","sources":["../src/tileStyles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAG3B;IACF,EAAE,EAAE,aAAa;IACjB,EAAE,EAAE,aAAa;IACjB,EAAE,EAAE,aAAa;CAClB,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GACzB,0DAA0D,CAAA;AAE5D,MAAM,CAAC,MAAM,oBAAoB,GAG7B;IACF,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,SAAS;CACd,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAG7B;IACF,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,SAAS;CACd,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAgC;IACzD,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,OAAO;IACX,EAAE,EAAE,OAAO;CACZ,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAC7B,6FAA6F,CAAA;AAE/F,8DAA8D;AAC9D,MAAM,CAAC,MAAM,6BAA6B,GACxC,oCAAoC,CAAA"}
1
+ {"version":3,"file":"tileStyles.js","sourceRoot":"","sources":["../src/tileStyles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAG3B;IACF,EAAE,EAAE,aAAa;IACjB,EAAE,EAAE,aAAa;IACjB,6DAA6D;IAC7D,8DAA8D;IAC9D,+DAA+D;IAC/D,+DAA+D;IAC/D,6DAA6D;IAC7D,iCAAiC;IACjC,EAAE,EAAE,WAAW;CAChB,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAGvC;IACF,EAAE,EAAE,MAAM;IACV,EAAE,EAAE,QAAQ;IACZ,EAAE,EAAE,MAAM;CACX,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GACzB,0DAA0D,CAAA;AAE5D,MAAM,CAAC,MAAM,oBAAoB,GAG7B;IACF,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,SAAS;CACd,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAG7B;IACF,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,SAAS;CACd,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAgC;IACzD,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,OAAO;IACX,EAAE,EAAE,OAAO;CACZ,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAC7B,6FAA6F,CAAA;AAE/F,8DAA8D;AAC9D,MAAM,CAAC,MAAM,6BAA6B,GACxC,oCAAoC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@charcuterie/ui",
3
- "version": "4.0.0",
3
+ "version": "4.2.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",
@@ -54,7 +54,7 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@charcuterie/logic": "2.3.0",
57
- "@charcuterie/tokens": "1.6.0",
57
+ "@charcuterie/tokens": "1.7.0",
58
58
  "@floating-ui/react": "^0.27.20",
59
59
  "@tanstack/react-virtual": "^3.13.12",
60
60
  "tailwind-merge": "^3.6.0"
@@ -6,8 +6,8 @@ import * as ActionTilesStories from "./ActionTiles.stories";
6
6
 
7
7
  # ActionTiles
8
8
 
9
- A set of **actions** drawn as tiles — a bordered card carrying a name and a line of help, in
10
- a grid that gains columns with its container.
9
+ A set of **actions** drawn as tiles — a bordered card carrying a colour bar, an icon beside
10
+ its name, and a line of help, in a grid that gains columns with its container.
11
11
 
12
12
  This is [`RadioGroup`](/docs/components-controls-radiogroup--docs)'s `itemShape="tile"` with
13
13
  the radio taken away. The 2026-08-25 record that made the choice tile a `RadioGroup` shape
@@ -21,6 +21,49 @@ giving one `aria-checked` would be worse than the paint it replaced"*.
21
21
 
22
22
  <Controls />
23
23
 
24
+ ## The colour is the point, and it is not decoration
25
+
26
+ The first version of this component was neutral, and the owner's verdict on it was *"very
27
+ boring, not colorful"*. Three apps had already answered the question themselves — mux-magic,
28
+ Gallery Downloader and points-market all coloured their tiles by hand — so the paint is now
29
+ the library's and every one of them can stop.
30
+
31
+ A tile wears a **bar down its leading edge**, its icon takes the same hue, and the box hovers
32
+ in it. The hue comes from the ten-wide
33
+ [categorical palette](/docs/foundations-colour--docs) taken **in order**, which is why a set
34
+ of five needs no colour props at all.
35
+
36
+ <Canvas of={ActionTilesStories.AutoHues} />
37
+
38
+ The bar is `Card`'s accent-edge pseudo-element rather than a second copy of one, so a tile and
39
+ a [`Card`](/docs/components-layout-card--docs) on the same page draw the same bar. That also
40
+ buys the corner: a border is painted on the border box and cannot follow a curve, which is why
41
+ all three hand-rolled versions in the fleet ran past the radius and stopped square.
42
+
43
+ Because it is an **overlay**, it occupies nothing in the box model — so the tile widens its
44
+ leading padding to make room. Without that the bar draws straight through the first letter of
45
+ the name, and nothing reports it: a pseudo-element is not in the DOM, so no query can miss it
46
+ and axe has nothing to say about a glyph with a stripe on it.
47
+
48
+ `categorical` and not `intent`, for the reason `cardAccentEdge.ts` gives: an intent is a
49
+ *claim* — `danger` says what happens if you press the thing — and "Builder" beside "Jobs" is
50
+ making no claim about either. Ten numbered hues say only "these are different from each
51
+ other", which is what a tile set means.
52
+
53
+ ### Naming a hue, and turning them off
54
+
55
+ An item's `categorical` overrides the one its position would have given it, and the rest go on
56
+ walking the palette from their own positions — so naming one does not force naming all of
57
+ them. Worth doing when the colour belongs to the **thing** rather than to the list.
58
+
59
+ <Canvas of={ActionTilesStories.NamedHues} />
60
+
61
+ `accent="none"` is the paint this component shipped with, kept as an opt-out. Reach for it
62
+ when the tiles sit inside something already carrying a colour of its own, where a second
63
+ palette is noise rather than information.
64
+
65
+ <Canvas of={ActionTilesStories.Neutral} />
66
+
24
67
  ## Which of the two you want
25
68
 
26
69
  Ask what a press does.
@@ -66,6 +109,14 @@ between `Button` and `ButtonLink`.
66
109
  The test compares **computed** styles of an action tile and a resting radio tile, not class
67
110
  names. A class-name assertion passes while the paint is wrong.
68
111
 
112
+ There is exactly one difference, and it is asserted rather than waived: a coloured action tile
113
+ has more **leading** padding than a radio tile, by the room its bar takes. Every other side
114
+ matches, and `accent="none"` matches on all four — so "the boxes match" cannot quietly come to
115
+ mean "the boxes match apart from whatever changed last". A radio tile draws no bar at all: a
116
+ checked one says so by turning its whole border `intent-accent-solid`, and a coloured stripe
117
+ on the same box would be one card carrying two claims — *this one is selected* and *this one
118
+ is the blue one* — in the same place.
119
+
69
120
  ## `href` makes the tile a real anchor
70
121
 
71
122
  Middle-click, ctrl-click, "open in a new tab", "copy link address" and the status bar all
@@ -93,11 +144,18 @@ cost a keyboard user the ability to Tab to the second tile.
93
144
  The group takes a required `label`, which names the set — "Queue type", "Pick a tool" — the
94
145
  way a `radiogroup` is named. A set of unnamed tiles is a pile of buttons.
95
146
 
96
- ## A tile may lead with an icon
147
+ ## A tile may lead with an icon, beside the name
148
+
149
+ The icon sits **to the left of the title**, with the hint below both — which is how all three
150
+ apps that had grown this card by hand drew it, and how the owner asked for it back: *"I like
151
+ how Mux-Magic looks today with the icon to the left of the title."*
152
+
153
+ It takes the tile's hue, so an app passes a `stroke="currentColor"` path and states no colour
154
+ of its own. Size is the app's; around 22px at `md` and 30px at `lg` reads the way those
155
+ hand-rolled cards did. The library still ships no icons.
97
156
 
98
- `icon` is `aria-hidden` by construction, because the name it sits above is inside the same
99
- tile — an announced icon is the label read twice. The library still ships no icons; apps
100
- bring their own.
157
+ `icon` is `aria-hidden` by construction, because the name it sits beside is inside the same
158
+ tile — an announced icon is the label read twice.
101
159
 
102
160
  <Canvas of={ActionTilesStories.WithIcons} />
103
161
 
@@ -115,12 +173,28 @@ same constant `RadioGroup`'s tile shape uses, and each of its three parts is loa
115
173
  class strings, so `` `grid-cols-[…${n}px…]` `` generates nothing, paints nothing and
116
174
  reports nothing.
117
175
 
176
+ ### The floor sizes the CONTAINER's tracks, not the tiles
177
+
178
+ `minTileInlineSize` is the narrowest a **track** may be, and `auto-fill` lays as many of
179
+ them as fit whether or not there are tiles to put in them. So the number to pick comes from
180
+ the width of the box the set is in, divided by how many tiles you want across it — not from
181
+ how many tiles you have.
182
+
183
+ Leave it at the 200px default in a 1024px container and the grid cuts four tracks. Two
184
+ tiles take the first two, the other half of the row is empty background, and the set reads
185
+ as a layout bug.
186
+
187
+ ⚠️ **Nothing reports this.** It type-checks, it lints, it passes every unit and e2e test,
188
+ and the DOM is correct — the tiles really are in a grid, and the grid really has the columns
189
+ it was asked for. Three apps shipped a half-empty row on the day they adopted this
190
+ component, and each one was caught by a screenshot. If a set has few tiles, look at it.
191
+
192
+ <Canvas of={ActionTilesStories.Responsive} />
193
+
118
194
  This is **not** `useAdaptiveColumns`. That hook buys a column with **height**, for an
119
195
  unbounded gallery that is going to scroll; a tile set is a bounded group inside a form section
120
196
  or a first step, where the only question is how many fit across the box it was given.
121
197
 
122
- <Canvas of={ActionTilesStories.Responsive} />
123
-
124
198
  ## What this deliberately does not do
125
199
 
126
200
  - **Hold a selection.** That is `RadioGroup itemShape="tile"`, and giving these tiles
@@ -8,9 +8,11 @@ import {
8
8
  StorySection,
9
9
  } from "../board.storyHelpers.tsx"
10
10
  import {
11
+ ImageIcon,
11
12
  InboxIcon,
12
13
  PlayIcon,
13
14
  SearchIcon,
15
+ SettingsIcon,
14
16
  } from "../icons.storyHelpers.tsx"
15
17
  import { RadioGroup } from "../RadioGroup/RadioGroup.tsx"
16
18
  import type { ActionTileItem } from "./ActionTiles.tsx"
@@ -56,6 +58,43 @@ const START_ITEMS: ActionTileItem[] = [
56
58
  },
57
59
  ]
58
60
 
61
+ /**
62
+ * Enough tiles to see the palette walk. Ten hues, taken in order,
63
+ * and not one colour prop between them.
64
+ */
65
+ const LIBRARY_ITEMS: ActionTileItem[] = [
66
+ {
67
+ hint: "Feature films.",
68
+ icon: <PlayIcon />,
69
+ label: "Movies",
70
+ value: "movies",
71
+ },
72
+ {
73
+ hint: "Episodic television.",
74
+ icon: <InboxIcon />,
75
+ label: "Shows",
76
+ value: "shows",
77
+ },
78
+ {
79
+ hint: "Albums and tracks.",
80
+ icon: <SearchIcon />,
81
+ label: "Music",
82
+ value: "music",
83
+ },
84
+ {
85
+ hint: "Scanned pages by series.",
86
+ icon: <ImageIcon />,
87
+ label: "Comics",
88
+ value: "comics",
89
+ },
90
+ {
91
+ hint: "Cores and ROMs.",
92
+ icon: <SettingsIcon />,
93
+ label: "Games",
94
+ value: "games",
95
+ },
96
+ ]
97
+
59
98
  /**
60
99
  * A tile that carries an `href` is a real `<a href>`, so
61
100
  * middle-click, ctrl-click and "copy link address" all work. The
@@ -92,6 +131,7 @@ const meta = {
92
131
  // `args` from docgen, so an unstated default shows in the props
93
132
  // table with nothing selected in its control.
94
133
  args: {
134
+ accent: "auto",
95
135
  minTileInlineSize: 200,
96
136
  size: "md",
97
137
  },
@@ -243,8 +283,8 @@ export const AllStates: Story = {
243
283
  export const BesideTheRadioTile: Story = {
244
284
  args: { items: QUEUE_TYPE_ITEMS, label: "Queue type" },
245
285
  render: (controlProps) => (
246
- <StorySection title="One box, two controls. Only the resting radio tile matches — a checked one wears the accent edge, which is the state ActionTiles has none of.">
247
- <StoryGrid columns={2}>
286
+ <StorySection title="One box, two controls. The neutral action tile matches the resting radio tile on every side; the coloured one differs only on the leading edge, by the room its bar takes.">
287
+ <StoryGrid columns={3}>
248
288
  <StoryCell
249
289
  align="stretch"
250
290
  label="ActionTiles — presses and is done"
@@ -252,6 +292,17 @@ export const BesideTheRadioTile: Story = {
252
292
  <ActionTiles {...controlProps} />
253
293
  </StoryCell>
254
294
 
295
+ <StoryCell
296
+ align="stretch"
297
+ label="ActionTiles accent='none' — the same box exactly"
298
+ >
299
+ <ActionTiles
300
+ {...controlProps}
301
+ accent="none"
302
+ label="Queue type, uncoloured"
303
+ />
304
+ </StoryCell>
305
+
255
306
  <StoryCell
256
307
  align="stretch"
257
308
  label="RadioGroup itemShape='tile' — holds a value"
@@ -309,3 +360,57 @@ export const Responsive: Story = {
309
360
  export const Interactive: Story = {
310
361
  args: { items: QUEUE_TYPE_ITEMS, label: "Queue type" },
311
362
  }
363
+
364
+ /**
365
+ * The default: the set walks the ten-hue categorical palette in
366
+ * order, so ten tiles get ten colours and the call site passes no
367
+ * colour at all.
368
+ *
369
+ * The bar is `Card`'s accent-edge pseudo-element, which is why a
370
+ * tile and a card on one page are the same bar rather than two that
371
+ * nearly match, and why it follows the tile's corner instead of
372
+ * running past it and stopping square.
373
+ */
374
+ export const AutoHues: Story = {
375
+ args: { items: LIBRARY_ITEMS, label: "Pick a library" },
376
+ }
377
+
378
+ /**
379
+ * A tile that names its own `categorical` keeps that hue, and the
380
+ * rest go on walking the palette from their own positions — so
381
+ * naming one does not force naming all of them.
382
+ *
383
+ * Worth doing when the colour belongs to the THING rather than to
384
+ * the list: a destination that is hue 7 everywhere else in the app
385
+ * should not become hue 2 because a tile was inserted above it.
386
+ */
387
+ export const NamedHues: Story = {
388
+ args: {
389
+ items: [
390
+ { ...(LIBRARY_ITEMS[0] as ActionTileItem) },
391
+ {
392
+ ...(LIBRARY_ITEMS[1] as ActionTileItem),
393
+ categorical: 7,
394
+ },
395
+ { ...(LIBRARY_ITEMS[2] as ActionTileItem) },
396
+ ],
397
+ label: "Pick a library, with Shows pinned to hue 7",
398
+ },
399
+ }
400
+
401
+ /**
402
+ * `accent="none"` is the paint this component shipped with, kept as
403
+ * an opt-out rather than deleted.
404
+ *
405
+ * Reach for it when the tiles sit inside something already carrying
406
+ * a colour of its own — a coloured panel, a lane header — where a
407
+ * second palette is noise rather than information. Everything else
408
+ * about the tile is unchanged.
409
+ */
410
+ export const Neutral: Story = {
411
+ args: {
412
+ accent: "none",
413
+ items: START_ITEMS,
414
+ label: "Where to start",
415
+ },
416
+ }
@@ -9,14 +9,36 @@ import * as stories from "./ActionTiles.stories.tsx"
9
9
 
10
10
  const {
11
11
  AllStates,
12
+ AutoHues,
12
13
  BesideTheRadioTile,
13
14
  Default,
14
15
  Interactive,
15
16
  Links,
17
+ NamedHues,
18
+ Neutral,
16
19
  Responsive,
17
20
  WithIcons,
18
21
  } = composeStories(stories)
19
22
 
23
+ /**
24
+ * The accent-edge layer out of a computed `box-shadow`.
25
+ *
26
+ * Reading the whole string does not work: Tailwind's shadow chain
27
+ * always emits four EMPTY `rgba(0, 0, 0, 0) 0px 0px 0px 0px` layers
28
+ * for the ring and shadow slots nothing filled, so a naive
29
+ * "contains no transparent colour" assertion fails on a bar that is
30
+ * painted perfectly. The bar is the `inset` layer, and it is the
31
+ * only one.
32
+ *
33
+ * Returns `"none"` when there is no inset layer at all, which is
34
+ * what `accent="none"` must produce.
35
+ */
36
+ const getAccentEdgeLayer = (boxShadow: string) =>
37
+ boxShadow
38
+ .split(/,(?![^(]*\))/)
39
+ .map((layer) => layer.trim())
40
+ .find((layer) => layer.endsWith("inset")) ?? "none"
41
+
20
42
  test("it is a named group of pressable tiles", async () => {
21
43
  const { canvas, canvasElement } =
22
44
  await mountStory(Default)
@@ -187,14 +209,33 @@ test("a disabled link drops its href and stays announced as a link", async () =>
187
209
  * rather than trusted — the same discipline `ButtonLink.test.tsx`
188
210
  * applies to `Button`. Two copies of one string is a promise that
189
211
  * survives exactly one edit.
212
+ *
213
+ * The UNCOLOURED action tile is the one held to an exact match. A
214
+ * coloured one carries a bar its neighbour does not, and the room
215
+ * for that bar is a real difference on one side — asserted
216
+ * separately below, so "the boxes match" cannot quietly come to mean
217
+ * "the boxes match apart from whatever changed last".
190
218
  */
191
- test("an action tile is the same box as a resting radio tile", async () => {
219
+ test("an uncoloured action tile is the same box as a resting radio tile", async () => {
192
220
  const { canvas } = await mountStory(BesideTheRadioTile)
193
221
 
194
- const actionTile = expectAgentDrivable(canvas, {
195
- name: "Picks Choose titles yourself, then arrange them in priority and random lanes.",
196
- role: "button",
197
- })
222
+ // Scoped to the group rather than fetched by accessible name:
223
+ // the board draws the same two tiles three times, so a bare
224
+ // `getByRole("button", { name: "Picks …" })` is ambiguous.
225
+ const getFirstTile = (name: string) => {
226
+ const tile = expectAgentDrivable(canvas, {
227
+ name,
228
+ role: "group",
229
+ }).querySelector<HTMLElement>("button")
230
+
231
+ if (!tile) {
232
+ throw new Error(`${name} drew no tile`)
233
+ }
234
+
235
+ return tile
236
+ }
237
+
238
+ const actionTile = getFirstTile("Queue type, uncoloured")
198
239
 
199
240
  const radioGroup = expectAgentDrivable(canvas, {
200
241
  name: "Queue type, as radios",
@@ -227,6 +268,10 @@ test("an action tile is the same box as a resting radio tile", async () => {
227
268
  radio.paddingInlineStart,
228
269
  )
229
270
 
271
+ await expect(action.paddingInlineEnd).toBe(
272
+ radio.paddingInlineEnd,
273
+ )
274
+
230
275
  await expect(action.borderTopWidth).toBe(
231
276
  radio.borderTopWidth,
232
277
  )
@@ -253,6 +298,161 @@ test("an action tile is the same box as a resting radio tile", async () => {
253
298
  ).toBeGreaterThan(0)
254
299
  })
255
300
 
301
+ /**
302
+ * The bar is an OVERLAY — `Card`'s accent-edge pseudo-element, which
303
+ * is painted over the box and occupies nothing in it. A tile that
304
+ * did not widen its leading padding would draw the bar straight
305
+ * through the first letter of its own name, and nothing would report
306
+ * that: the pseudo-element is not in the DOM, so no query can miss
307
+ * it and axe has nothing to say about a glyph with a stripe on it.
308
+ *
309
+ * So the assertion is the difference itself, in computed pixels.
310
+ */
311
+ test("a coloured tile makes room for its bar, and only on the leading side", async () => {
312
+ const { canvas } = await mountStory(BesideTheRadioTile)
313
+
314
+ const getFirstTile = (name: string) => {
315
+ const tile = expectAgentDrivable(canvas, {
316
+ name,
317
+ role: "group",
318
+ }).querySelector<HTMLElement>("button")
319
+
320
+ if (!tile) {
321
+ throw new Error(`${name} drew no tile`)
322
+ }
323
+
324
+ return tile
325
+ }
326
+
327
+ const coloured = getComputedStyle(
328
+ getFirstTile("Queue type"),
329
+ )
330
+
331
+ const uncoloured = getComputedStyle(
332
+ getFirstTile("Queue type, uncoloured"),
333
+ )
334
+
335
+ await expect(
336
+ Number.parseFloat(coloured.paddingInlineStart),
337
+ ).toBeGreaterThan(
338
+ Number.parseFloat(uncoloured.paddingInlineStart),
339
+ )
340
+
341
+ // Every other side is untouched. The colour is a bar and a hue,
342
+ // not a second set of metrics.
343
+ await expect(coloured.paddingInlineEnd).toBe(
344
+ uncoloured.paddingInlineEnd,
345
+ )
346
+
347
+ await expect(coloured.paddingTop).toBe(
348
+ uncoloured.paddingTop,
349
+ )
350
+
351
+ await expect(coloured.paddingBottom).toBe(
352
+ uncoloured.paddingBottom,
353
+ )
354
+
355
+ await expect(coloured.borderTopLeftRadius).toBe(
356
+ uncoloured.borderTopLeftRadius,
357
+ )
358
+ })
359
+
360
+ /**
361
+ * Ten hues taken in order, which is what makes a five-tile set need
362
+ * no colour props at all.
363
+ *
364
+ * Asserted through the pseudo-element's own `box-shadow`, because
365
+ * that is where the bar actually lives — a class-name assertion
366
+ * passes while the paint is wrong, and a `--color-categorical-*`
367
+ * that had never existed would resolve to nothing, paint
368
+ * transparent, and satisfy every check that the element rendered.
369
+ */
370
+ test("a set walks the palette, and a named hue holds its place", async () => {
371
+ const { canvas } = await mountStory(AutoHues)
372
+
373
+ const group = expectAgentDrivable(canvas, {
374
+ name: "Pick a library",
375
+ role: "group",
376
+ })
377
+
378
+ const bars = Array.from(
379
+ group.querySelectorAll<HTMLElement>("button"),
380
+ ).map((tile) =>
381
+ getAccentEdgeLayer(
382
+ getComputedStyle(tile, "::before").boxShadow,
383
+ ),
384
+ )
385
+
386
+ await expect(bars).toHaveLength(5)
387
+
388
+ for (const bar of bars) {
389
+ // A token that resolved to nothing paints a transparent bar and
390
+ // passes every "did it render" check ever written.
391
+ await expect(bar).toMatch(/^rgb\(\d+, \d+, \d+\) /)
392
+ }
393
+
394
+ // Five tiles, five different colours.
395
+ await expect(new Set(bars).size).toBe(5)
396
+ })
397
+
398
+ test("a named hue survives a tile being inserted above it", async () => {
399
+ const { canvas } = await mountStory(NamedHues)
400
+
401
+ const pinned = expectAgentDrivable(canvas, {
402
+ name: "Shows Episodic television.",
403
+ role: "button",
404
+ })
405
+
406
+ const { canvas: autoCanvas } = await mountStory(AutoHues)
407
+
408
+ const seventh = Array.from(
409
+ expectAgentDrivable(autoCanvas, {
410
+ name: "Pick a library",
411
+ role: "group",
412
+ }).querySelectorAll<HTMLElement>("button"),
413
+ )[1]
414
+
415
+ if (!seventh) {
416
+ throw new Error("the auto-hue set drew no second tile")
417
+ }
418
+
419
+ // In the auto set this tile is position 1 and wears hue 2. Naming
420
+ // `categorical: 7` has to change it, or the prop does nothing and
421
+ // every test above still passes.
422
+ await expect(
423
+ getAccentEdgeLayer(
424
+ getComputedStyle(pinned, "::before").boxShadow,
425
+ ),
426
+ ).not.toBe(
427
+ getAccentEdgeLayer(
428
+ getComputedStyle(seventh, "::before").boxShadow,
429
+ ),
430
+ )
431
+ })
432
+
433
+ /**
434
+ * `accent="none"` is an opt-out, so it has to actually draw no bar
435
+ * — not a bar in a neutral colour, which would still be a stripe on
436
+ * a page that asked for none.
437
+ */
438
+ test("accent='none' draws no bar at all", async () => {
439
+ const { canvas, canvasElement } =
440
+ await mountStory(Neutral)
441
+
442
+ const tile = expectAgentDrivable(canvas, {
443
+ name: "New arrivals Everything that arrived since the last run.",
444
+ role: "button",
445
+ })
446
+
447
+ await expect(
448
+ getAccentEdgeLayer(
449
+ getComputedStyle(tile, "::before").boxShadow,
450
+ ),
451
+ ).toBe("none")
452
+
453
+ await expectNoAxeViolations(canvasElement)
454
+ })
455
+
256
456
  test("the column count comes from the container, not the window", async () => {
257
457
  const { canvas } = await mountStory(Responsive)
258
458