@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/README.md
CHANGED
|
@@ -27,12 +27,13 @@ an accordion panel is [a `group` rather than a landmark](../../docs/decisions/20
|
|
|
27
27
|
and [not every boolean is a state kind](../../docs/decisions/2026-07-31-not-every-boolean-is-a-state-kind.md).
|
|
28
28
|
Full write-up: [M6a handoff](../../docs/2026-07-31-m6a-the-p1-components.md).
|
|
29
29
|
|
|
30
|
-
**The link family** — `TextLink` and `
|
|
30
|
+
**The link family** — `TextLink`, `ButtonLink`, and `UnstyledLink` — closes the gap that had seven repos
|
|
31
31
|
hand-rolling a `←` back-link, plex-channels navigating with
|
|
32
32
|
`<button onClick={() => navigate(…)}>`, and mux-magic reloading the page through 14 raw
|
|
33
33
|
`<a href>`. Buttons are for on-page actions; links are for navigation, and both of these
|
|
34
34
|
render a real `<a href>` so middle-click, ctrl-click, open-in-new-tab and copy-link-address
|
|
35
|
-
work.
|
|
35
|
+
work. The first two differ in **paint, not semantics**. `UnstyledLink` owns no paint and
|
|
36
|
+
preserves an established app-owned treatment while still using the same router seam
|
|
36
37
|
([decision](../../docs/decisions/2026-08-10-buttons-are-actions-links-are-navigation.md)).
|
|
37
38
|
The router is **injected** rather than depended on — `RouterLinkProvider` at the app root,
|
|
38
39
|
with `@charcuterie/ui/react-router` as an optional subpath adapter and a plain `<a>` when
|
|
@@ -1,6 +1,20 @@
|
|
|
1
|
-
import type { ControlSize } from "@charcuterie/tokens";
|
|
1
|
+
import type { CategoricalIndex, ControlSize } from "@charcuterie/tokens";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
3
|
export type ActionTileItem = {
|
|
4
|
+
/**
|
|
5
|
+
* Which of the ten hues this tile wears, overriding the one its
|
|
6
|
+
* position would have given it.
|
|
7
|
+
*
|
|
8
|
+
* Reach for it when a tile's colour means something outside the
|
|
9
|
+
* set — a destination that is red everywhere else in the app, a
|
|
10
|
+
* pair that must not drift apart when a third tile is inserted
|
|
11
|
+
* between them. An overridden tile keeps its hue and the rest go
|
|
12
|
+
* on walking the palette in order, so naming one does not force
|
|
13
|
+
* naming all of them.
|
|
14
|
+
*
|
|
15
|
+
* Ignored when the set is `accent="none"`.
|
|
16
|
+
*/
|
|
17
|
+
categorical?: CategoricalIndex;
|
|
4
18
|
/**
|
|
5
19
|
* A line of help under the name. Drawn one step down the type ramp
|
|
6
20
|
* in `content-muted`, **inside** the tile — so `getByRole` computes
|
|
@@ -20,10 +34,15 @@ export type ActionTileItem = {
|
|
|
20
34
|
*/
|
|
21
35
|
href?: string;
|
|
22
36
|
/**
|
|
23
|
-
* Leading content,
|
|
24
|
-
* count.
|
|
37
|
+
* Leading content, drawn **beside** the name rather than above it.
|
|
38
|
+
* A glyph, a `Badge`, a count.
|
|
25
39
|
*
|
|
26
|
-
*
|
|
40
|
+
* Takes the tile's hue, so an app passes a `stroke="currentColor"`
|
|
41
|
+
* path and states no colour of its own. Size is the app's: around
|
|
42
|
+
* 22px at `md` and 30px at `lg` reads the way the fleet's
|
|
43
|
+
* hand-rolled tool cards did.
|
|
44
|
+
*
|
|
45
|
+
* `aria-hidden` by construction: the name it sits beside is inside
|
|
27
46
|
* the same tile, so an announced icon is the label read twice.
|
|
28
47
|
*/
|
|
29
48
|
icon?: ReactNode;
|
|
@@ -40,7 +59,22 @@ export type ActionTileItem = {
|
|
|
40
59
|
*/
|
|
41
60
|
value: string;
|
|
42
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* Where a tile's colour comes from.
|
|
64
|
+
*
|
|
65
|
+
* - `auto` — the set walks the ten-hue categorical palette in
|
|
66
|
+
* order, so eight tiles get eight colours and no call site does
|
|
67
|
+
* any work. An item may still name its own with `categorical`.
|
|
68
|
+
* - `none` — no bar, no hue, and the neutral `border-strong`
|
|
69
|
+
* hover every other box uses.
|
|
70
|
+
*/
|
|
71
|
+
export type ActionTilesAccent = "auto" | "none";
|
|
43
72
|
export type ActionTilesProps = {
|
|
73
|
+
/**
|
|
74
|
+
* Whether the set colours itself. `auto` by default — see
|
|
75
|
+
* {@link ActionTilesAccent}.
|
|
76
|
+
*/
|
|
77
|
+
accent?: ActionTilesAccent;
|
|
44
78
|
className?: string;
|
|
45
79
|
items: readonly ActionTileItem[];
|
|
46
80
|
/** The set's accessible name. Required. */
|
|
@@ -59,9 +93,9 @@ export type ActionTilesProps = {
|
|
|
59
93
|
size?: ControlSize;
|
|
60
94
|
};
|
|
61
95
|
/**
|
|
62
|
-
* A set of ACTIONS drawn as tiles — a bordered card carrying a
|
|
63
|
-
* and a line of help, in a
|
|
64
|
-
* container.
|
|
96
|
+
* A set of ACTIONS drawn as tiles — a bordered card carrying a
|
|
97
|
+
* colour bar, an icon beside its name, and a line of help, in a
|
|
98
|
+
* grid that gains columns with its container.
|
|
65
99
|
*
|
|
66
100
|
* This is `RadioGroup`'s `itemShape="tile"` with the radio taken
|
|
67
101
|
* away. The
|
|
@@ -90,6 +124,30 @@ export type ActionTilesProps = {
|
|
|
90
124
|
* why QueuePilot's "Queue type" chooser is here and its Tonight
|
|
91
125
|
* activity filter is not.
|
|
92
126
|
*
|
|
127
|
+
* ### The colour is the point, and it is not decoration
|
|
128
|
+
*
|
|
129
|
+
* The first version of this component was neutral, and the owner's
|
|
130
|
+
* verdict on it was *"very boring, not colorful"*. Three apps had
|
|
131
|
+
* already answered the question themselves — mux-magic, Gallery
|
|
132
|
+
* Downloader and points-market all colour their tiles by hand — so
|
|
133
|
+
* the paint is now the library's and every one of them can stop.
|
|
134
|
+
*
|
|
135
|
+
* A tile wears a **bar down its leading edge**, drawn by `Card`'s
|
|
136
|
+
* accent-edge pseudo-element so a tile and a card on the same page
|
|
137
|
+
* are the same bar rather than two that nearly match. Its icon takes
|
|
138
|
+
* the same hue, and the box hovers in it too. The hue comes from the
|
|
139
|
+
* ten-wide categorical palette, taken **in order**, which is why a
|
|
140
|
+
* set of eight needs no colour props at all. See
|
|
141
|
+
* [the tile paint record](../../../../docs/decisions/2026-09-02-an-action-tile-is-coloured-and-the-icon-sits-beside-the-name.md)
|
|
142
|
+
* for the four other paints that were drawn and rejected.
|
|
143
|
+
*
|
|
144
|
+
* `categorical` and not `intent`, for the reason
|
|
145
|
+
* [`cardAccentEdge.ts`](../Card/cardAccentEdge.ts) gives: an intent
|
|
146
|
+
* is a *claim* — `danger` says what happens if you press the thing —
|
|
147
|
+
* and "Builder" beside "Jobs" is making no claim about either. Ten
|
|
148
|
+
* numbered hues say only "these are different from each other",
|
|
149
|
+
* which is exactly what a tile set means.
|
|
150
|
+
*
|
|
93
151
|
* ### Why this is not a `Button` with a `className`
|
|
94
152
|
*
|
|
95
153
|
* A `Button` is sized by `h-(--control-height-md)` and carries **no
|
|
@@ -111,5 +169,5 @@ export type ActionTilesProps = {
|
|
|
111
169
|
* true, so imposing that model would only cost a keyboard user the
|
|
112
170
|
* ability to Tab to the second tile.
|
|
113
171
|
*/
|
|
114
|
-
export declare const ActionTiles: ({ className, items, label, minTileInlineSize, newTabLabel, onChoose, size, }: ActionTilesProps) => ReactNode;
|
|
172
|
+
export declare const ActionTiles: ({ accent, className, items, label, minTileInlineSize, newTabLabel, onChoose, size, }: ActionTilesProps) => ReactNode;
|
|
115
173
|
//# sourceMappingURL=ActionTiles.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActionTiles.d.ts","sourceRoot":"","sources":["../../src/ActionTiles/ActionTiles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"ActionTiles.d.ts","sourceRoot":"","sources":["../../src/ActionTiles/ActionTiles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,WAAW,EACZ,MAAM,qBAAqB,CAAA;AAK5B,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAwBrD,MAAM,MAAM,cAAc,GAAG;IAC3B;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,EAAE,gBAAgB,CAAA;IAC9B;;;;;OAKG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,uEAAuE;IACvE,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,KAAK,EAAE,SAAS,CAAA;IAChB;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,MAAM,CAAA;AAE/C,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;OAGG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAA;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,SAAS,cAAc,EAAE,CAAA;IAChC,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAA;IACb;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB,CAAA;AA4CD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4EG;AACH,eAAO,MAAM,WAAW,GAAI,sFASzB,gBAAgB,KAAG,SAwMrB,CAAA"}
|
|
@@ -1,14 +1,50 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { CATEGORICAL_INDEX_COUNT, CATEGORICAL_INDEXES, } from "@charcuterie/tokens";
|
|
3
|
+
import { getAccentEdgeClassName } from "../Card/cardAccentEdge.js";
|
|
4
|
+
import { CATEGORICAL_CONTENT_CLASS, CATEGORICAL_HOVER_BORDER_CLASS, CATEGORICAL_HOVER_CLASS, } from "../categoricalStyles.js";
|
|
2
5
|
import { FOCUS_RING_CLASS } from "../intentStyles.js";
|
|
3
6
|
import { useRouterLink } from "../RouterLink/RouterLinkProvider.js";
|
|
4
7
|
import { getIsRoutedHref } from "../RouterLink/routerLink.js";
|
|
5
|
-
import { TILE_BOX_CLASS, TILE_COLUMNS_CLASS, TILE_GAP_CLASS, TILE_HINT_TEXT_CLASS, TILE_MIN_INLINE_SIZE_PROPERTY, TILE_PADDING_CLASS, TILE_TEXT_SIZE_CLASS, } from "../tileStyles.js";
|
|
8
|
+
import { TILE_ACCENT_EDGE_PADDING_CLASS, TILE_BOX_CLASS, TILE_COLUMNS_CLASS, TILE_GAP_CLASS, TILE_HINT_TEXT_CLASS, TILE_MIN_INLINE_SIZE_PROPERTY, TILE_PADDING_CLASS, TILE_TEXT_SIZE_CLASS, } from "../tileStyles.js";
|
|
6
9
|
import { toClassName } from "../toClassName.js";
|
|
7
10
|
import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden.js";
|
|
8
11
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
+
* The gap between the icon and the name it sits beside.
|
|
13
|
+
*
|
|
14
|
+
* Local to this component rather than in `tileStyles.ts`, because a
|
|
15
|
+
* radio tile's inner layout is a control beside a label and has no
|
|
16
|
+
* head row to space. The shared file owns the BOX; this is what goes
|
|
17
|
+
* in it.
|
|
18
|
+
*/
|
|
19
|
+
const HEAD_GAP_CLASS = {
|
|
20
|
+
sm: "gap-2",
|
|
21
|
+
md: "gap-2.5",
|
|
22
|
+
lg: "gap-3",
|
|
23
|
+
};
|
|
24
|
+
/** The gap between the head row and the hint under it. */
|
|
25
|
+
const STACK_GAP_CLASS = {
|
|
26
|
+
sm: "gap-1",
|
|
27
|
+
md: "gap-1",
|
|
28
|
+
lg: "gap-2",
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* The hue a tile wears when it did not name one: its position in
|
|
32
|
+
* the set, wrapping at ten.
|
|
33
|
+
*
|
|
34
|
+
* Position rather than `getCategoricalIndex(value)`. That helper
|
|
35
|
+
* hashes a key so a row keeps its colour forever across a list
|
|
36
|
+
* nobody controls the order of — right for Docket's labels, wrong
|
|
37
|
+
* here, because a hash of two tile values can collide and hand a
|
|
38
|
+
* two-tile set the same colour twice. A tile set is short, ordered,
|
|
39
|
+
* and written out in the source; the eye reads it as a row, so the
|
|
40
|
+
* palette is walked as one.
|
|
41
|
+
*/
|
|
42
|
+
const getTileCategorical = (item, position) => item.categorical ??
|
|
43
|
+
CATEGORICAL_INDEXES[position % CATEGORICAL_INDEX_COUNT];
|
|
44
|
+
/**
|
|
45
|
+
* A set of ACTIONS drawn as tiles — a bordered card carrying a
|
|
46
|
+
* colour bar, an icon beside its name, and a line of help, in a
|
|
47
|
+
* grid that gains columns with its container.
|
|
12
48
|
*
|
|
13
49
|
* This is `RadioGroup`'s `itemShape="tile"` with the radio taken
|
|
14
50
|
* away. The
|
|
@@ -37,6 +73,30 @@ import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden.js";
|
|
|
37
73
|
* why QueuePilot's "Queue type" chooser is here and its Tonight
|
|
38
74
|
* activity filter is not.
|
|
39
75
|
*
|
|
76
|
+
* ### The colour is the point, and it is not decoration
|
|
77
|
+
*
|
|
78
|
+
* The first version of this component was neutral, and the owner's
|
|
79
|
+
* verdict on it was *"very boring, not colorful"*. Three apps had
|
|
80
|
+
* already answered the question themselves — mux-magic, Gallery
|
|
81
|
+
* Downloader and points-market all colour their tiles by hand — so
|
|
82
|
+
* the paint is now the library's and every one of them can stop.
|
|
83
|
+
*
|
|
84
|
+
* A tile wears a **bar down its leading edge**, drawn by `Card`'s
|
|
85
|
+
* accent-edge pseudo-element so a tile and a card on the same page
|
|
86
|
+
* are the same bar rather than two that nearly match. Its icon takes
|
|
87
|
+
* the same hue, and the box hovers in it too. The hue comes from the
|
|
88
|
+
* ten-wide categorical palette, taken **in order**, which is why a
|
|
89
|
+
* set of eight needs no colour props at all. See
|
|
90
|
+
* [the tile paint record](../../../../docs/decisions/2026-09-02-an-action-tile-is-coloured-and-the-icon-sits-beside-the-name.md)
|
|
91
|
+
* for the four other paints that were drawn and rejected.
|
|
92
|
+
*
|
|
93
|
+
* `categorical` and not `intent`, for the reason
|
|
94
|
+
* [`cardAccentEdge.ts`](../Card/cardAccentEdge.ts) gives: an intent
|
|
95
|
+
* is a *claim* — `danger` says what happens if you press the thing —
|
|
96
|
+
* and "Builder" beside "Jobs" is making no claim about either. Ten
|
|
97
|
+
* numbered hues say only "these are different from each other",
|
|
98
|
+
* which is exactly what a tile set means.
|
|
99
|
+
*
|
|
40
100
|
* ### Why this is not a `Button` with a `className`
|
|
41
101
|
*
|
|
42
102
|
* A `Button` is sized by `h-(--control-height-md)` and carries **no
|
|
@@ -58,11 +118,15 @@ import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden.js";
|
|
|
58
118
|
* true, so imposing that model would only cost a keyboard user the
|
|
59
119
|
* ability to Tab to the second tile.
|
|
60
120
|
*/
|
|
61
|
-
export const ActionTiles = ({ className, items, label, minTileInlineSize = 200, newTabLabel = "(opens in a new tab)", onChoose, size = "md", }) => {
|
|
121
|
+
export const ActionTiles = ({ accent = "auto", className, items, label, minTileInlineSize = 200, newTabLabel = "(opens in a new tab)", onChoose, size = "md", }) => {
|
|
62
122
|
const RouterLink = useRouterLink();
|
|
63
123
|
return (_jsx("div", { "aria-label": label, className: toClassName("grid", TILE_COLUMNS_CLASS, TILE_GAP_CLASS[size], className), role: "group", style: {
|
|
64
124
|
[TILE_MIN_INLINE_SIZE_PROPERTY]: `${minTileInlineSize}px`,
|
|
65
|
-
}, children: items.map((
|
|
125
|
+
}, children: items.map((item, position) => {
|
|
126
|
+
const { hint, href, icon, isDisabled = false, isExternal = false, label: tileLabel, value, } = item;
|
|
127
|
+
const categorical = accent === "none"
|
|
128
|
+
? null
|
|
129
|
+
: getTileCategorical(item, position);
|
|
66
130
|
const tileClassName = toClassName(
|
|
67
131
|
// `flex`, not `inline-flex`: a grid item stretches to its
|
|
68
132
|
// track, and an inline box would sit at max-content
|
|
@@ -71,15 +135,28 @@ export const ActionTiles = ({ className, items, label, minTileInlineSize = 200,
|
|
|
71
135
|
// min-content width — one unbroken token would otherwise
|
|
72
136
|
// set the track's floor and shove the grid out of its
|
|
73
137
|
// container.
|
|
74
|
-
"flex min-w-0 flex-col
|
|
138
|
+
"flex min-w-0 flex-col text-start wrap-anywhere transition-colors duration-(--duration-fast) ease-standard", STACK_GAP_CLASS[size], TILE_BOX_CLASS, TILE_PADDING_CLASS[size], TILE_TEXT_SIZE_CLASS[size],
|
|
139
|
+
// The bar is an overlay and takes no space in the box
|
|
140
|
+
// model, so the tile makes room for it or the text lands
|
|
141
|
+
// on top of it.
|
|
142
|
+
categorical === null
|
|
143
|
+
? null
|
|
144
|
+
: toClassName(getAccentEdgeClassName({ categorical }), TILE_ACCENT_EDGE_PADDING_CLASS[size]),
|
|
75
145
|
// Disabled turns the whole tile down with `opacity-60`,
|
|
76
146
|
// the same family treatment as `Checkbox`, `Switch` and
|
|
77
147
|
// the radio tile, rather than fading the border to a
|
|
78
|
-
// token that disappeared on a pale theme.
|
|
148
|
+
// token that disappeared on a pale theme. It also drops
|
|
149
|
+
// every hover class: a tile that cannot be pressed must
|
|
150
|
+
// not light up under the pointer.
|
|
79
151
|
isDisabled
|
|
80
152
|
? "cursor-not-allowed opacity-60"
|
|
81
|
-
: "cursor-pointer
|
|
82
|
-
|
|
153
|
+
: toClassName("cursor-pointer", categorical === null
|
|
154
|
+
? "hover:border-border-strong"
|
|
155
|
+
: toClassName(CATEGORICAL_HOVER_BORDER_CLASS[categorical], CATEGORICAL_HOVER_CLASS[categorical]
|
|
156
|
+
.ghost)), FOCUS_RING_CLASS);
|
|
157
|
+
const content = (_jsxs(_Fragment, { children: [_jsxs("span", { className: toClassName("flex min-w-0 items-center", HEAD_GAP_CLASS[size]), children: [icon === undefined ? null : (_jsx("span", { "aria-hidden": true, className: toClassName("flex shrink-0", categorical === null
|
|
158
|
+
? null
|
|
159
|
+
: CATEGORICAL_CONTENT_CLASS[categorical]), children: icon })), _jsx("span", { className: "min-w-0 font-semibold text-content-primary", children: tileLabel })] }), hint === undefined ? null : (_jsx("span", { className: toClassName("font-normal text-content-muted", TILE_HINT_TEXT_CLASS[size]), children: hint })), isExternal && href !== undefined ? (_jsx(VisuallyHidden, { children: newTabLabel })) : null] }));
|
|
83
160
|
if (href !== undefined && !isDisabled) {
|
|
84
161
|
// An external destination or a fragment is never the
|
|
85
162
|
// router's — pushing `https://…` or `#credits` onto the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActionTiles.js","sourceRoot":"","sources":["../../src/ActionTiles/ActionTiles.tsx"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"ActionTiles.js","sourceRoot":"","sources":["../../src/ActionTiles/ActionTiles.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* The face, and the one piece of this component that holds state.
|
|
4
|
+
*
|
|
5
|
+
* A portrait grid is the shape that cannot absorb a broken image:
|
|
6
|
+
* a missing `<img>` is a torn hole where a person's face was, next
|
|
7
|
+
* to nine that rendered. `onError` is the only signal a browser
|
|
8
|
+
* gives for a 404, a wrong content type, or a file the user's
|
|
9
|
+
* network stripped — so the fallback the caller already supplied for
|
|
10
|
+
* "no picture" covers "no picture *today*" as well.
|
|
11
|
+
*
|
|
12
|
+
* Its own component because the state is per portrait. Hoisting it
|
|
13
|
+
* to a `Record<string, boolean>` in the set would re-render every
|
|
14
|
+
* tile whenever any one image failed.
|
|
15
|
+
*/
|
|
16
|
+
export declare const PortraitAvatar: ({ className, imageSrc, initials, }: {
|
|
17
|
+
className: string;
|
|
18
|
+
imageSrc: string | undefined;
|
|
19
|
+
initials: ReactNode;
|
|
20
|
+
}) => ReactNode;
|
|
21
|
+
//# sourceMappingURL=PortraitAvatar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PortraitAvatar.d.ts","sourceRoot":"","sources":["../../src/PortraitTiles/PortraitAvatar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc,GAAI,oCAI5B;IACD,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5B,QAAQ,EAAE,SAAS,CAAA;CACpB,KAAG,SAuBH,CAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
/**
|
|
4
|
+
* The face, and the one piece of this component that holds state.
|
|
5
|
+
*
|
|
6
|
+
* A portrait grid is the shape that cannot absorb a broken image:
|
|
7
|
+
* a missing `<img>` is a torn hole where a person's face was, next
|
|
8
|
+
* to nine that rendered. `onError` is the only signal a browser
|
|
9
|
+
* gives for a 404, a wrong content type, or a file the user's
|
|
10
|
+
* network stripped — so the fallback the caller already supplied for
|
|
11
|
+
* "no picture" covers "no picture *today*" as well.
|
|
12
|
+
*
|
|
13
|
+
* Its own component because the state is per portrait. Hoisting it
|
|
14
|
+
* to a `Record<string, boolean>` in the set would re-render every
|
|
15
|
+
* tile whenever any one image failed.
|
|
16
|
+
*/
|
|
17
|
+
export const PortraitAvatar = ({ className, imageSrc, initials, }) => {
|
|
18
|
+
const [isImageBroken, setIsImageBroken] = useState(false);
|
|
19
|
+
if (imageSrc === undefined || isImageBroken) {
|
|
20
|
+
return (_jsx("span", { "aria-hidden": true, className: className, children: initials }));
|
|
21
|
+
}
|
|
22
|
+
return (_jsx("img", {
|
|
23
|
+
// The name is beside it, inside the same tile, so an alt here
|
|
24
|
+
// is the name announced twice.
|
|
25
|
+
alt: "", className: className, onError: () => {
|
|
26
|
+
setIsImageBroken(true);
|
|
27
|
+
}, src: imageSrc }));
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=PortraitAvatar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PortraitAvatar.js","sourceRoot":"","sources":["../../src/PortraitTiles/PortraitAvatar.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEhC;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,EAC7B,SAAS,EACT,QAAQ,EACR,QAAQ,GAKT,EAAa,EAAE;IACd,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAEzD,IAAI,QAAQ,KAAK,SAAS,IAAI,aAAa,EAAE,CAAC;QAC5C,OAAO,CACL,oCAAkB,SAAS,EAAE,SAAS,YACnC,QAAQ,GACJ,CACR,CAAA;IACH,CAAC;IAED,OAAO,CACL;QACE,8DAA8D;QAC9D,+BAA+B;QAC/B,GAAG,EAAC,EAAE,EACN,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,GAAG,EAAE;YACZ,gBAAgB,CAAC,IAAI,CAAC,CAAA;QACxB,CAAC,EACD,GAAG,EAAE,QAAQ,GACb,CACH,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { CategoricalIndex, ControlSize } from "@charcuterie/tokens";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
/**
|
|
4
|
+
* Where one portrait's colour comes from. Two arms, and the fleet
|
|
5
|
+
* has both — the same split
|
|
6
|
+
* [`cardAccentEdge.ts`](../Card/cardAccentEdge.ts) already draws,
|
|
7
|
+
* for the same reason.
|
|
8
|
+
*
|
|
9
|
+
* - **`categorical`** — one of the ten audited hues. A colour the
|
|
10
|
+
* library owns, re-themed by `data-variant`, gated in both
|
|
11
|
+
* schemes. Right whenever the colour is a *choice from what we
|
|
12
|
+
* offered*.
|
|
13
|
+
* - **`color`** — any CSS colour, from data. Right when the colour
|
|
14
|
+
* **arrived from the world** and re-theming it would be lying
|
|
15
|
+
* about a physical object. points-market is exactly this: each
|
|
16
|
+
* kid's colour matches the NFC card they tap, by the workspace's
|
|
17
|
+
* 2026-07-20 `kid-identity-colors-match-nfc-cards` record. Ten
|
|
18
|
+
* palette hues cannot represent that, and swapping them in would
|
|
19
|
+
* have made the app disagree with the cards on the fridge.
|
|
20
|
+
*
|
|
21
|
+
* Neither is a default. A portrait that names no colour takes the
|
|
22
|
+
* next `categorical` hue by position.
|
|
23
|
+
*/
|
|
24
|
+
type PortraitTileColour = {
|
|
25
|
+
categorical?: CategoricalIndex;
|
|
26
|
+
color?: never;
|
|
27
|
+
} | {
|
|
28
|
+
categorical?: never;
|
|
29
|
+
color: string;
|
|
30
|
+
};
|
|
31
|
+
export type PortraitTileItem = PortraitTileColour & {
|
|
32
|
+
/**
|
|
33
|
+
* The quiet line under the number, naming its unit — "points",
|
|
34
|
+
* "episodes left", "due today".
|
|
35
|
+
*
|
|
36
|
+
* Inside the tile, so it is part of the accessible name. "1,240"
|
|
37
|
+
* on its own tells a screen-reader user nothing.
|
|
38
|
+
*/
|
|
39
|
+
hint?: ReactNode;
|
|
40
|
+
/** Makes the portrait a real `<a href>`. Same rules as `ActionTiles`. */
|
|
41
|
+
href?: string;
|
|
42
|
+
/**
|
|
43
|
+
* A picture of the subject. Falls back to {@link initials} when
|
|
44
|
+
* absent, and **also** when the file 404s — a broken portrait grid
|
|
45
|
+
* is the one failure this shape cannot absorb.
|
|
46
|
+
*/
|
|
47
|
+
imageSrc?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Drawn in the hue's solid fill when there is no picture. One or
|
|
50
|
+
* two characters; longer strings are the caller's problem to
|
|
51
|
+
* shorten, because "which letters" is a naming question this
|
|
52
|
+
* component cannot answer (a mononym, a family that shares an
|
|
53
|
+
* initial).
|
|
54
|
+
*/
|
|
55
|
+
initials?: string;
|
|
56
|
+
isDisabled?: boolean;
|
|
57
|
+
/** Opens in a new tab, and says so to a screen reader. `href` only. */
|
|
58
|
+
isExternal?: boolean;
|
|
59
|
+
/** The name. */
|
|
60
|
+
label: ReactNode;
|
|
61
|
+
/**
|
|
62
|
+
* The one number this portrait is chosen by — a balance, a count,
|
|
63
|
+
* a streak. Optional: a picker that is only ever "who are you"
|
|
64
|
+
* passes none and the tile closes up around the name.
|
|
65
|
+
*/
|
|
66
|
+
stat?: ReactNode;
|
|
67
|
+
/** Identity. The React key, and what `onChoose` is handed. */
|
|
68
|
+
value: string;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* How a portrait arranges itself.
|
|
72
|
+
*
|
|
73
|
+
* - `auto` — a **row** while the set is narrow, a **column** once
|
|
74
|
+
* it has room. The switch is a container query on the set, not a
|
|
75
|
+
* media query: a picker in a 320px sidebar is a list of rows on a
|
|
76
|
+
* 2560px monitor, and the same picker filling a phone is a column
|
|
77
|
+
* per screenful.
|
|
78
|
+
* - `row` / `column` — fixed, for a layout that has already decided.
|
|
79
|
+
*/
|
|
80
|
+
export type PortraitTilesLayout = "auto" | "column" | "row";
|
|
81
|
+
export type PortraitTilesProps = {
|
|
82
|
+
className?: string;
|
|
83
|
+
items: readonly PortraitTileItem[];
|
|
84
|
+
/** The set's accessible name. Required. */
|
|
85
|
+
label: string;
|
|
86
|
+
layout?: PortraitTilesLayout;
|
|
87
|
+
/** The narrowest a portrait track may be, in CSS px. */
|
|
88
|
+
minTileInlineSize?: number;
|
|
89
|
+
/** What `isExternal` announces. Not shown. */
|
|
90
|
+
newTabLabel?: string;
|
|
91
|
+
/** Fired by a portrait that carries no `href`. */
|
|
92
|
+
onChoose?: (value: string) => void;
|
|
93
|
+
size?: ControlSize;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* A set of PEOPLE — or of anything else picked by its face — drawn
|
|
97
|
+
* as round pictures with a name and one big number.
|
|
98
|
+
*
|
|
99
|
+
* points-market's "Who's shopping?" is the shape this is taken from,
|
|
100
|
+
* and it is deliberately **not** an `ActionTiles` variant. The two
|
|
101
|
+
* answer different questions:
|
|
102
|
+
*
|
|
103
|
+
* - `ActionTiles` asks *what do you want to do* — a name, a line of
|
|
104
|
+
* help, an icon that illustrates the verb.
|
|
105
|
+
* - This asks *who is this* — a face, and one number that decides
|
|
106
|
+
* it. There is no description, because a person is not explained.
|
|
107
|
+
*
|
|
108
|
+
* Collapsing them would mean one component whose icon is sometimes a
|
|
109
|
+
* 20px glyph beside the title and sometimes a 144px circle above it,
|
|
110
|
+
* with `hint` meaning "what this does" in one mode and "points" in
|
|
111
|
+
* the other. That is two components wearing one name, which is the
|
|
112
|
+
* mistake the
|
|
113
|
+
* [2026-08-25 record](../../../../docs/decisions/2026-08-25-a-choice-tile-is-a-radiogroup-shape-not-a-third-component.md)
|
|
114
|
+
* warns about from the other direction.
|
|
115
|
+
*
|
|
116
|
+
* What they DO share is the box — `TILE_BOX_CLASS` and the same
|
|
117
|
+
* `auto-fill` grid — so a page carrying both does not show two
|
|
118
|
+
* different cards.
|
|
119
|
+
*
|
|
120
|
+
* ### It scales, and the scaling is the point
|
|
121
|
+
*
|
|
122
|
+
* The owner's one requirement when this moved into the library:
|
|
123
|
+
* *"that one is meant to scale. I don't wanna lose that
|
|
124
|
+
* functionality."* Two axes carry it.
|
|
125
|
+
*
|
|
126
|
+
* 1. **The container.** `layout="auto"` is a row while the set is
|
|
127
|
+
* narrow and a column once it has room, off a `cq-sm` query on
|
|
128
|
+
* the set itself. Nothing reads the viewport, so the same picker
|
|
129
|
+
* works in a sidebar and on a wall.
|
|
130
|
+
* 2. **The density.** Every length is a token, so `data-density="kiosk"`
|
|
131
|
+
* grows the type ramp and the control heights under it. points-market
|
|
132
|
+
* runs `kiosk` today; that is where its big numbers come from,
|
|
133
|
+
* and it keeps them.
|
|
134
|
+
*
|
|
135
|
+
* ### The colour is the person's, not the position's
|
|
136
|
+
*
|
|
137
|
+
* A positional hue is the default because it costs a call site
|
|
138
|
+
* nothing. It is also the wrong default for a household: add a
|
|
139
|
+
* fifth member and the first four change colour, which is exactly
|
|
140
|
+
* the identity the picker was using to be fast. Any set whose
|
|
141
|
+
* subjects persist should name its colour per subject.
|
|
142
|
+
*
|
|
143
|
+
* There are two ways to name one, and the difference is where the
|
|
144
|
+
* colour came from:
|
|
145
|
+
*
|
|
146
|
+
* - **`categorical`** — an index into the ten audited hues. A
|
|
147
|
+
* choice from what the library offered, so the library keeps
|
|
148
|
+
* owning it: re-themed by `data-variant`, contrast-gated in both
|
|
149
|
+
* schemes. The same call Docket makes for a project.
|
|
150
|
+
* - **`color`** — any CSS colour, straight from data. For a colour
|
|
151
|
+
* the system does **not** own and cannot re-theme without lying:
|
|
152
|
+
* points-market's kids are coloured to match the NFC cards they
|
|
153
|
+
* tap, so the picker and the cards on the fridge have to agree,
|
|
154
|
+
* and ten palette hues cannot promise that.
|
|
155
|
+
*
|
|
156
|
+
* The second arm is the narrow one. Reach for it only when the
|
|
157
|
+
* colour is a fact about the subject rather than a decision about
|
|
158
|
+
* the design — the same line `Swatch` draws, for the same reason.
|
|
159
|
+
*/
|
|
160
|
+
export declare const PortraitTiles: ({ className, items, label, layout, minTileInlineSize, newTabLabel, onChoose, size, }: PortraitTilesProps) => ReactNode;
|
|
161
|
+
export {};
|
|
162
|
+
//# sourceMappingURL=PortraitTiles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PortraitTiles.d.ts","sourceRoot":"","sources":["../../src/PortraitTiles/PortraitTiles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,WAAW,EACZ,MAAM,qBAAqB,CAAA;AAK5B,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAqBrD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,KAAK,kBAAkB,GACnB;IAAE,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GACjD;IAAE,WAAW,CAAC,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAE1C,MAAM,MAAM,gBAAgB,GAAG,kBAAkB,GAAG;IAClD;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,uEAAuE;IACvE,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,gBAAgB;IAChB,KAAK,EAAE,SAAS,CAAA;IAChB;;;;OAIG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAA;AAE3D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAA;IAClC,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,mBAAmB,CAAA;IAC5B,wDAAwD;IACxD,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB,CAAA;AAiLD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AACH,eAAO,MAAM,aAAa,GAAI,sFAS3B,kBAAkB,KAAG,SA8MvB,CAAA"}
|