@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.
- package/README.md +3 -2
- package/dist/ActionTiles/ActionTiles.d.ts +66 -8
- package/dist/ActionTiles/ActionTiles.d.ts.map +1 -1
- package/dist/ActionTiles/ActionTiles.js +88 -11
- package/dist/ActionTiles/ActionTiles.js.map +1 -1
- package/dist/PortraitTiles/PortraitAvatar.d.ts +21 -0
- package/dist/PortraitTiles/PortraitAvatar.d.ts.map +1 -0
- package/dist/PortraitTiles/PortraitAvatar.js +29 -0
- package/dist/PortraitTiles/PortraitAvatar.js.map +1 -0
- package/dist/PortraitTiles/PortraitTiles.d.ts +162 -0
- package/dist/PortraitTiles/PortraitTiles.d.ts.map +1 -0
- package/dist/PortraitTiles/PortraitTiles.js +263 -0
- package/dist/PortraitTiles/PortraitTiles.js.map +1 -0
- package/dist/PortraitTiles/portraitColour.d.ts +49 -0
- package/dist/PortraitTiles/portraitColour.d.ts.map +1 -0
- package/dist/PortraitTiles/portraitColour.js +55 -0
- package/dist/PortraitTiles/portraitColour.js.map +1 -0
- package/dist/RouterLink/UnstyledLink.d.ts +17 -0
- package/dist/RouterLink/UnstyledLink.d.ts.map +1 -0
- package/dist/RouterLink/UnstyledLink.js +24 -0
- package/dist/RouterLink/UnstyledLink.js.map +1 -0
- package/dist/categoricalStyles.d.ts +27 -0
- package/dist/categoricalStyles.d.ts.map +1 -1
- package/dist/categoricalStyles.js +49 -0
- package/dist/categoricalStyles.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/tileStyles.d.ts +15 -0
- package/dist/tileStyles.d.ts.map +1 -1
- package/dist/tileStyles.js +26 -1
- package/dist/tileStyles.js.map +1 -1
- package/package.json +2 -2
- package/src/ActionTiles/ActionTiles.mdx +82 -8
- package/src/ActionTiles/ActionTiles.stories.tsx +107 -2
- package/src/ActionTiles/ActionTiles.test.tsx +205 -5
- package/src/ActionTiles/ActionTiles.tsx +274 -110
- package/src/PortraitTiles/PortraitAvatar.tsx +49 -0
- package/src/PortraitTiles/PortraitTiles.mdx +153 -0
- package/src/PortraitTiles/PortraitTiles.stories.tsx +403 -0
- package/src/PortraitTiles/PortraitTiles.test.tsx +399 -0
- package/src/PortraitTiles/PortraitTiles.tsx +577 -0
- package/src/PortraitTiles/portraitColour.test.ts +39 -0
- package/src/PortraitTiles/portraitColour.ts +67 -0
- package/src/RadioGroup/RadioGroup.stories.tsx +39 -0
- package/src/RouterLink/RouterLink.mdx +25 -4
- package/src/RouterLink/UnstyledLink.stories.tsx +57 -0
- package/src/RouterLink/UnstyledLink.test.tsx +40 -0
- package/src/RouterLink/UnstyledLink.tsx +34 -0
- package/src/categoricalStyles.ts +57 -0
- package/src/index.ts +11 -0
- package/src/sourceRules.test.ts +17 -4
- 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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAOH,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA2B,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/tileStyles.d.ts
CHANGED
|
@@ -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
|
package/dist/tileStyles.d.ts.map
CHANGED
|
@@ -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"}
|
package/dist/tileStyles.js
CHANGED
|
@@ -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
|
|
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
|
package/dist/tileStyles.js.map
CHANGED
|
@@ -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,
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
99
|
-
tile — an announced icon is the label read twice.
|
|
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.
|
|
247
|
-
<StoryGrid columns={
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
|