@charcuterie/ui 2.0.1 → 2.1.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/dist/Swatch/Swatch.d.ts +83 -0
- package/dist/Swatch/Swatch.d.ts.map +1 -0
- package/dist/Swatch/Swatch.js +57 -0
- package/dist/Swatch/Swatch.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/Swatch/Swatch.mdx +50 -0
- package/src/Swatch/Swatch.stories.tsx +177 -0
- package/src/Swatch/Swatch.test.tsx +57 -0
- package/src/Swatch/Swatch.tsx +139 -0
- package/src/index.ts +6 -0
- package/src/sourceRules.test.ts +4 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef, ReactNode } from "react";
|
|
2
|
+
export type SwatchAppearance = "outline" | "solid";
|
|
3
|
+
export type SwatchSize = keyof typeof SWATCH_SIZE_CLASS;
|
|
4
|
+
export type SwatchProps = Omit<ComponentPropsWithRef<"span">, "children" | "color"> & {
|
|
5
|
+
/**
|
|
6
|
+
* `outline` keeps the hue and drops the fill — for a swatch whose
|
|
7
|
+
* subject is present but inactive. It is the one visual state a
|
|
8
|
+
* status colour cannot borrow from `intent`, because the colour is
|
|
9
|
+
* not an intent.
|
|
10
|
+
*/
|
|
11
|
+
appearance?: SwatchAppearance;
|
|
12
|
+
/**
|
|
13
|
+
* Any CSS colour, and this is the whole reason the component
|
|
14
|
+
* exists: it is a **value from data** — a physical sticker, an
|
|
15
|
+
* album's extracted accent, a user-chosen tag — not a token. A
|
|
16
|
+
* design system owns `intent.danger`; it does not own the colour
|
|
17
|
+
* of the dot somebody stuck on a game controller, and re-theming
|
|
18
|
+
* that would be re-theming the hardware. So it is a prop and lands
|
|
19
|
+
* in an inline `style`, the one sanctioned escape hatch for a
|
|
20
|
+
* runtime value.
|
|
21
|
+
*/
|
|
22
|
+
color: string;
|
|
23
|
+
isLabelVisible?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Required, and it is the point. A colour is not a name: a screen
|
|
26
|
+
* reader gets nothing at all from a `background-color`, and
|
|
27
|
+
* `getByRole("img", { name })` — the query an agent writes — has
|
|
28
|
+
* nothing to match. So the type asks for the name and there is no
|
|
29
|
+
* way to render the component without one.
|
|
30
|
+
*/
|
|
31
|
+
label: string;
|
|
32
|
+
size?: SwatchSize;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The scale is the component's own, and deliberately larger than
|
|
36
|
+
* `DOT_SIZE_CLASS`.
|
|
37
|
+
*
|
|
38
|
+
* That map tops out at 10px, which is right for a status dot beside
|
|
39
|
+
* a line of text and far too small for a swatch that is the primary
|
|
40
|
+
* way a user tells one row from another across a room. A swatch is
|
|
41
|
+
* content, not punctuation.
|
|
42
|
+
*/
|
|
43
|
+
declare const SWATCH_SIZE_CLASS: {
|
|
44
|
+
readonly sm: "size-4";
|
|
45
|
+
readonly md: "size-6";
|
|
46
|
+
readonly lg: "size-9";
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* A colour presented **as content**, with a name attached.
|
|
50
|
+
*
|
|
51
|
+
* It is not `LiveStatusIndicator`'s dot and not a `Badge`. Both of
|
|
52
|
+
* those take an `intent`, because both show a *state* whose colour
|
|
53
|
+
* the design system owns and can name in English. This shows a
|
|
54
|
+
* colour the system does **not** own and **cannot** name — the red
|
|
55
|
+
* sticker on a controller, red because someone put a red sticker on
|
|
56
|
+
* it; the accent castkit pulls off an album; a user's tag colour.
|
|
57
|
+
* `DOT_SIZE_CLASS` is exported precisely so an app can hand-roll one
|
|
58
|
+
* of these, and three of them across the fleet did, each without a
|
|
59
|
+
* name a screen reader could read.
|
|
60
|
+
*
|
|
61
|
+
* ### `role="img"`, and the name is required
|
|
62
|
+
*
|
|
63
|
+
* A swatch is a graphic that carries meaning, so it is an image with
|
|
64
|
+
* an accessible name — which makes it findable by
|
|
65
|
+
* `getByRole("img", { name })` in **both** the dot-only and
|
|
66
|
+
* labelled forms, one stable handle regardless of whether the text
|
|
67
|
+
* is on screen. The dot and any visible label are inside it and go
|
|
68
|
+
* `aria-hidden` by virtue of the name on the wrapper, exactly as an
|
|
69
|
+
* inline SVG with a caption would.
|
|
70
|
+
*
|
|
71
|
+
* ### The ring is not a token
|
|
72
|
+
*
|
|
73
|
+
* `solid` sits a translucent white ring inside the fill so a white
|
|
74
|
+
* or near-white swatch stays visible against `surface-raised` in
|
|
75
|
+
* both schemes; a hairline border alone disappears on one of the
|
|
76
|
+
* two. `outline` drops the fill to a faint tint of the same hue and
|
|
77
|
+
* draws the ring in the colour itself — via an inline `box-shadow`,
|
|
78
|
+
* because the ring colour has to come from the data and
|
|
79
|
+
* `--tw-ring-color` is not something `CSSProperties` will type.
|
|
80
|
+
*/
|
|
81
|
+
export declare const Swatch: ({ appearance, className, color, isLabelVisible, label, size, ...spanProps }: SwatchProps) => ReactNode;
|
|
82
|
+
export {};
|
|
83
|
+
//# sourceMappingURL=Swatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Swatch.d.ts","sourceRoot":"","sources":["../../src/Swatch/Swatch.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,SAAS,EACV,MAAM,OAAO,CAAA;AAId,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,OAAO,CAAA;AAElD,MAAM,MAAM,UAAU,GAAG,MAAM,OAAO,iBAAiB,CAAA;AAEvD,MAAM,MAAM,WAAW,GAAG,IAAI,CAC5B,qBAAqB,CAAC,MAAM,CAAC,EAC7B,UAAU,GAAG,OAAO,CACrB,GAAG;IACF;;;;;OAKG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAA;IAC7B;;;;;;;;;OASG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,UAAU,CAAA;CAClB,CAAA;AAED;;;;;;;;GAQG;AACH,QAAA,MAAM,iBAAiB;;;;CAIb,CAAA;AAEV;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,MAAM,GAAI,6EAQpB,WAAW,KAAG,SAqChB,CAAA"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { toClassName } from "../toClassName.js";
|
|
3
|
+
/**
|
|
4
|
+
* The scale is the component's own, and deliberately larger than
|
|
5
|
+
* `DOT_SIZE_CLASS`.
|
|
6
|
+
*
|
|
7
|
+
* That map tops out at 10px, which is right for a status dot beside
|
|
8
|
+
* a line of text and far too small for a swatch that is the primary
|
|
9
|
+
* way a user tells one row from another across a room. A swatch is
|
|
10
|
+
* content, not punctuation.
|
|
11
|
+
*/
|
|
12
|
+
const SWATCH_SIZE_CLASS = {
|
|
13
|
+
sm: "size-4",
|
|
14
|
+
md: "size-6",
|
|
15
|
+
lg: "size-9",
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* A colour presented **as content**, with a name attached.
|
|
19
|
+
*
|
|
20
|
+
* It is not `LiveStatusIndicator`'s dot and not a `Badge`. Both of
|
|
21
|
+
* those take an `intent`, because both show a *state* whose colour
|
|
22
|
+
* the design system owns and can name in English. This shows a
|
|
23
|
+
* colour the system does **not** own and **cannot** name — the red
|
|
24
|
+
* sticker on a controller, red because someone put a red sticker on
|
|
25
|
+
* it; the accent castkit pulls off an album; a user's tag colour.
|
|
26
|
+
* `DOT_SIZE_CLASS` is exported precisely so an app can hand-roll one
|
|
27
|
+
* of these, and three of them across the fleet did, each without a
|
|
28
|
+
* name a screen reader could read.
|
|
29
|
+
*
|
|
30
|
+
* ### `role="img"`, and the name is required
|
|
31
|
+
*
|
|
32
|
+
* A swatch is a graphic that carries meaning, so it is an image with
|
|
33
|
+
* an accessible name — which makes it findable by
|
|
34
|
+
* `getByRole("img", { name })` in **both** the dot-only and
|
|
35
|
+
* labelled forms, one stable handle regardless of whether the text
|
|
36
|
+
* is on screen. The dot and any visible label are inside it and go
|
|
37
|
+
* `aria-hidden` by virtue of the name on the wrapper, exactly as an
|
|
38
|
+
* inline SVG with a caption would.
|
|
39
|
+
*
|
|
40
|
+
* ### The ring is not a token
|
|
41
|
+
*
|
|
42
|
+
* `solid` sits a translucent white ring inside the fill so a white
|
|
43
|
+
* or near-white swatch stays visible against `surface-raised` in
|
|
44
|
+
* both schemes; a hairline border alone disappears on one of the
|
|
45
|
+
* two. `outline` drops the fill to a faint tint of the same hue and
|
|
46
|
+
* draws the ring in the colour itself — via an inline `box-shadow`,
|
|
47
|
+
* because the ring colour has to come from the data and
|
|
48
|
+
* `--tw-ring-color` is not something `CSSProperties` will type.
|
|
49
|
+
*/
|
|
50
|
+
export const Swatch = ({ appearance = "solid", className, color, isLabelVisible = false, label, size = "md", ...spanProps }) => (_jsxs("span", { ...spanProps, "aria-label": label, className: toClassName("inline-flex items-center gap-2 align-middle", className), role: "img", children: [_jsx("span", { "aria-hidden": "true", className: toClassName("inline-block shrink-0 rounded-full", SWATCH_SIZE_CLASS[size], appearance === "solid" &&
|
|
51
|
+
"shadow-low ring-2 ring-white/25 ring-inset"), style: appearance === "solid"
|
|
52
|
+
? { backgroundColor: color }
|
|
53
|
+
: {
|
|
54
|
+
backgroundColor: `color-mix(in srgb, ${color} 12%, transparent)`,
|
|
55
|
+
boxShadow: `inset 0 0 0 3px ${color}`,
|
|
56
|
+
} }), isLabelVisible ? (_jsx("span", { "aria-hidden": "true", className: "text-content-primary text-sm", children: label })) : null] }));
|
|
57
|
+
//# sourceMappingURL=Swatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Swatch.js","sourceRoot":"","sources":["../../src/Swatch/Swatch.tsx"],"names":[],"mappings":";AAKA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAwC/C;;;;;;;;GAQG;AACH,MAAM,iBAAiB,GAAG;IACxB,EAAE,EAAE,QAAQ;IACZ,EAAE,EAAE,QAAQ;IACZ,EAAE,EAAE,QAAQ;CACJ,CAAA;AAEV;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EACrB,UAAU,GAAG,OAAO,EACpB,SAAS,EACT,KAAK,EACL,cAAc,GAAG,KAAK,EACtB,KAAK,EACL,IAAI,GAAG,IAAI,EACX,GAAG,SAAS,EACA,EAAa,EAAE,CAAC,CAC5B,mBACM,SAAS,gBACD,KAAK,EACjB,SAAS,EAAE,WAAW,CACpB,6CAA6C,EAC7C,SAAS,CACV,EACD,IAAI,EAAC,KAAK,aAEV,8BACc,MAAM,EAClB,SAAS,EAAE,WAAW,CACpB,oCAAoC,EACpC,iBAAiB,CAAC,IAAI,CAAC,EACvB,UAAU,KAAK,OAAO;gBACpB,4CAA4C,CAC/C,EACD,KAAK,EACH,UAAU,KAAK,OAAO;gBACpB,CAAC,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE;gBAC5B,CAAC,CAAC;oBACE,eAAe,EAAE,sBAAsB,KAAK,oBAAoB;oBAChE,SAAS,EAAE,mBAAmB,KAAK,EAAE;iBACtC,GAEP,EAED,cAAc,CAAC,CAAC,CAAC,CAChB,8BACc,MAAM,EAClB,SAAS,EAAC,8BAA8B,YAEvC,KAAK,GACD,CACR,CAAC,CAAC,CAAC,IAAI,IACH,CACR,CAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -94,6 +94,8 @@ export type { SortableTableHeaderProps, SortDirection, } from "./SortableTableHe
|
|
|
94
94
|
export { SortableTableHeader } from "./SortableTableHeader/SortableTableHeader.tsx";
|
|
95
95
|
export type { SpinnerProps } from "./Spinner/Spinner.tsx";
|
|
96
96
|
export { Spinner } from "./Spinner/Spinner.tsx";
|
|
97
|
+
export type { SwatchAppearance, SwatchProps, SwatchSize, } from "./Swatch/Swatch.tsx";
|
|
98
|
+
export { Swatch } from "./Swatch/Swatch.tsx";
|
|
97
99
|
export type { SlotProps } from "./slotProps.ts";
|
|
98
100
|
export { mergeSlotProps } from "./slotProps.ts";
|
|
99
101
|
export { getAsyncIntent, getAsyncLabel, getConnectionIntent, getConnectionLabel, getIsConnectionBusy, } from "./statusIntent.ts";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -62,6 +62,7 @@ export { Select } from "./Select/Select.js";
|
|
|
62
62
|
export { Skeleton } from "./Skeleton/Skeleton.js";
|
|
63
63
|
export { SortableTableHeader } from "./SortableTableHeader/SortableTableHeader.js";
|
|
64
64
|
export { Spinner } from "./Spinner/Spinner.js";
|
|
65
|
+
export { Swatch } from "./Swatch/Swatch.js";
|
|
65
66
|
export { mergeSlotProps } from "./slotProps.js";
|
|
66
67
|
export { getAsyncIntent, getAsyncLabel, getConnectionIntent, getConnectionLabel, getIsConnectionBusy, } from "./statusIntent.js";
|
|
67
68
|
export { Tabs } from "./Tabs/Tabs.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;AAKrD,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAmB,CAAA;AAEzC,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAmB,CAAA;AAEzC,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAqB,CAAA;AAM5C,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAiB,CAAA;AAEtC,OAAO,EAAE,mBAAmB,EAAE,MAAM,8CAA+C,CAAA;AAKnF,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA2C,CAAA;AAE7E,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAyB,CAAA;AAElD,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,uBAAuB,EACvB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,oBAAoB,CAAA;AAK3B,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAqB,CAAA;AAK5C,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA6B,CAAA;AAExD,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAmB,CAAA;AAEzC,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAwB,CAAA;AAEnD,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAiC,CAAA;AAE9D,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA6B,CAAA;AAExD,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAyB,CAAA;AAKlD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAuB,CAAA;AAK/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,8CAA+C,CAAA;AAKnF,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA2B,CAAA;AAKrD,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA2B,CAAA;AAErD,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAK7D,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAiB,CAAA;AAEtC,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAmB,CAAA;AAEzC,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAuB,CAAA;AAK/C,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA+B,CAAA;AAE3D,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,GAChB,MAAM,gCAAgC,CAAA;AAKvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,wCAAyC,CAAA;AAO1E,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAqB,CAAA;AAK5C,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAyB,CAAA;AAKlD,OAAO,EAAE,mBAAmB,EAAE,MAAM,8CAA+C,CAAA;AAEnF,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@charcuterie/ui",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.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",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@charcuterie/logic": "1.1.0",
|
|
47
|
-
"@charcuterie/tokens": "1.1.
|
|
47
|
+
"@charcuterie/tokens": "1.1.1",
|
|
48
48
|
"@floating-ui/react": "^0.27.20",
|
|
49
49
|
"@tanstack/react-virtual": "^3.13.12"
|
|
50
50
|
},
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Canvas, Controls, Meta } from "@storybook/addon-docs/blocks";
|
|
2
|
+
|
|
3
|
+
import * as SwatchStories from "./Swatch.stories";
|
|
4
|
+
|
|
5
|
+
<Meta of={SwatchStories} />
|
|
6
|
+
|
|
7
|
+
# Swatch
|
|
8
|
+
|
|
9
|
+
A colour presented **as content**, with a name attached. It is the one colour the design
|
|
10
|
+
system does not own: the red sticker on a game controller, the accent castkit pulls off an
|
|
11
|
+
album, a user's tag colour. Not a token — a value from data.
|
|
12
|
+
|
|
13
|
+
<Canvas of={SwatchStories.Default} />
|
|
14
|
+
|
|
15
|
+
## Props
|
|
16
|
+
|
|
17
|
+
<Controls />
|
|
18
|
+
|
|
19
|
+
## Why it is not a `Badge` or a status dot
|
|
20
|
+
|
|
21
|
+
`Badge` and `LiveStatusIndicator` both take an `intent`, because both show a *state* whose
|
|
22
|
+
colour the system owns and can name — `danger` is red and is called "danger". A swatch
|
|
23
|
+
shows a colour the system cannot name, so the colour is a `color` prop that lands in an
|
|
24
|
+
inline `style`, and the meaning travels in a **required** `label`. A `background-color` is
|
|
25
|
+
nothing to a screen reader and nothing to `getByRole`, so the type asks for the name and
|
|
26
|
+
there is no way to render one without it.
|
|
27
|
+
|
|
28
|
+
`DOT_SIZE_CLASS` is exported so an app *can* hand-roll one of these — and three across the
|
|
29
|
+
fleet did, each without a name. This is that dot, named, and sized as content rather than
|
|
30
|
+
punctuation.
|
|
31
|
+
|
|
32
|
+
## `outline` — present but inactive
|
|
33
|
+
|
|
34
|
+
<Canvas of={SwatchStories.AllVariants} />
|
|
35
|
+
|
|
36
|
+
`outline` keeps the hue and drops the fill. It is the one visual state a status colour
|
|
37
|
+
cannot borrow from `intent`: a subject that is there but not active — a powered-off
|
|
38
|
+
controller beside its live siblings. The ring is drawn in the colour itself, so it survives
|
|
39
|
+
where a token could not follow.
|
|
40
|
+
|
|
41
|
+
## Findable, and white stays visible
|
|
42
|
+
|
|
43
|
+
<Canvas of={SwatchStories.AllStates} />
|
|
44
|
+
|
|
45
|
+
It is a `role="img"` with an accessible name, so `getByRole("img", { name })` resolves in
|
|
46
|
+
both the dot-only and labelled forms — one stable handle whether or not the text is on
|
|
47
|
+
screen. The translucent inset ring on `solid` is what keeps a white or near-white swatch
|
|
48
|
+
from vanishing against `surface-raised` in one of the two schemes.
|
|
49
|
+
|
|
50
|
+
<Canvas of={SwatchStories.InAList} />
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
StoryCell,
|
|
5
|
+
StoryGrid,
|
|
6
|
+
StoryRow,
|
|
7
|
+
StorySection,
|
|
8
|
+
} from "../board.storyHelpers.tsx"
|
|
9
|
+
import { Card } from "../Card/Card.tsx"
|
|
10
|
+
import { Swatch } from "./Swatch.tsx"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Colours that are **data**, not tokens — the whole reason the
|
|
14
|
+
* component exists. These stand in for stickers on a controller, and
|
|
15
|
+
* a design system re-theming them would be re-theming the hardware.
|
|
16
|
+
*/
|
|
17
|
+
const STICKERS = [
|
|
18
|
+
{ color: "#E5484D", label: "Red" },
|
|
19
|
+
{ color: "#3B82F6", label: "Blue" },
|
|
20
|
+
{ color: "#35D07F", label: "Green" },
|
|
21
|
+
{ color: "#F5C518", label: "Yellow" },
|
|
22
|
+
] as const
|
|
23
|
+
|
|
24
|
+
const meta = {
|
|
25
|
+
title: "Components/Swatch",
|
|
26
|
+
component: Swatch,
|
|
27
|
+
parameters: { layout: "padded" },
|
|
28
|
+
argTypes: { color: { control: "color" } },
|
|
29
|
+
args: {
|
|
30
|
+
appearance: "solid",
|
|
31
|
+
color: "#E5484D",
|
|
32
|
+
isLabelVisible: true,
|
|
33
|
+
label: "Red",
|
|
34
|
+
size: "md",
|
|
35
|
+
},
|
|
36
|
+
} satisfies Meta<typeof Swatch>
|
|
37
|
+
|
|
38
|
+
export default meta
|
|
39
|
+
|
|
40
|
+
type Story = StoryObj<typeof meta>
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A named graphic — `getByRole("img", { name: "Red" })` resolves,
|
|
44
|
+
* because a colour a screen reader cannot see needs a name it can
|
|
45
|
+
* read.
|
|
46
|
+
*/
|
|
47
|
+
export const Default: Story = {}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* `solid` fills; `outline` keeps the hue and drops the fill. The
|
|
51
|
+
* second is the one state a status colour cannot borrow from
|
|
52
|
+
* `intent` — "this thing is present but inactive" — which is what a
|
|
53
|
+
* powered-off controller looks like beside its live siblings.
|
|
54
|
+
*/
|
|
55
|
+
export const AllVariants: Story = {
|
|
56
|
+
render: (swatchProps) => (
|
|
57
|
+
<StorySection title="`outline` is the same colour with the fill taken away — for a subject that is there but not active.">
|
|
58
|
+
<StoryGrid columns={2}>
|
|
59
|
+
<StoryCell label="solid">
|
|
60
|
+
<StoryRow>
|
|
61
|
+
{STICKERS.map((sticker) => (
|
|
62
|
+
<Swatch
|
|
63
|
+
{...swatchProps}
|
|
64
|
+
appearance="solid"
|
|
65
|
+
color={sticker.color}
|
|
66
|
+
isLabelVisible={false}
|
|
67
|
+
key={sticker.label}
|
|
68
|
+
label={sticker.label}
|
|
69
|
+
/>
|
|
70
|
+
))}
|
|
71
|
+
</StoryRow>
|
|
72
|
+
</StoryCell>
|
|
73
|
+
|
|
74
|
+
<StoryCell label="outline">
|
|
75
|
+
<StoryRow>
|
|
76
|
+
{STICKERS.map((sticker) => (
|
|
77
|
+
<Swatch
|
|
78
|
+
{...swatchProps}
|
|
79
|
+
appearance="outline"
|
|
80
|
+
color={sticker.color}
|
|
81
|
+
isLabelVisible={false}
|
|
82
|
+
key={sticker.label}
|
|
83
|
+
label={sticker.label}
|
|
84
|
+
/>
|
|
85
|
+
))}
|
|
86
|
+
</StoryRow>
|
|
87
|
+
</StoryCell>
|
|
88
|
+
</StoryGrid>
|
|
89
|
+
</StorySection>
|
|
90
|
+
),
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const AllStates: Story = {
|
|
94
|
+
render: (swatchProps) => (
|
|
95
|
+
<StoryGrid columns={2}>
|
|
96
|
+
<StoryCell label="dot only — name still read">
|
|
97
|
+
<Swatch {...swatchProps} isLabelVisible={false} />
|
|
98
|
+
</StoryCell>
|
|
99
|
+
|
|
100
|
+
<StoryCell label="label visible">
|
|
101
|
+
<Swatch {...swatchProps} isLabelVisible />
|
|
102
|
+
</StoryCell>
|
|
103
|
+
|
|
104
|
+
<StoryCell label="sizes">
|
|
105
|
+
<StoryRow>
|
|
106
|
+
<Swatch
|
|
107
|
+
{...swatchProps}
|
|
108
|
+
isLabelVisible={false}
|
|
109
|
+
size="sm"
|
|
110
|
+
/>
|
|
111
|
+
|
|
112
|
+
<Swatch
|
|
113
|
+
{...swatchProps}
|
|
114
|
+
isLabelVisible={false}
|
|
115
|
+
size="md"
|
|
116
|
+
/>
|
|
117
|
+
|
|
118
|
+
<Swatch
|
|
119
|
+
{...swatchProps}
|
|
120
|
+
isLabelVisible={false}
|
|
121
|
+
size="lg"
|
|
122
|
+
/>
|
|
123
|
+
</StoryRow>
|
|
124
|
+
</StoryCell>
|
|
125
|
+
|
|
126
|
+
<StoryCell label="white stays visible in both schemes">
|
|
127
|
+
<StoryRow>
|
|
128
|
+
<Swatch
|
|
129
|
+
{...swatchProps}
|
|
130
|
+
color="#FFFFFF"
|
|
131
|
+
isLabelVisible={false}
|
|
132
|
+
label="White"
|
|
133
|
+
/>
|
|
134
|
+
|
|
135
|
+
<Swatch
|
|
136
|
+
{...swatchProps}
|
|
137
|
+
appearance="outline"
|
|
138
|
+
color="#FFFFFF"
|
|
139
|
+
isLabelVisible={false}
|
|
140
|
+
label="White"
|
|
141
|
+
/>
|
|
142
|
+
</StoryRow>
|
|
143
|
+
</StoryCell>
|
|
144
|
+
</StoryGrid>
|
|
145
|
+
),
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The real placement: a row per item, the swatch answering "which
|
|
150
|
+
* one" before the words do. This is castkit's album accent and
|
|
151
|
+
* plex-channels' channel colours as much as it is a controller
|
|
152
|
+
* sticker.
|
|
153
|
+
*/
|
|
154
|
+
export const InAList: Story = {
|
|
155
|
+
render: () => (
|
|
156
|
+
<Card heading="Controllers">
|
|
157
|
+
<ul className="flex list-none flex-col gap-2 p-0">
|
|
158
|
+
{STICKERS.map((sticker) => (
|
|
159
|
+
<li
|
|
160
|
+
className="flex items-center gap-3"
|
|
161
|
+
key={sticker.label}
|
|
162
|
+
>
|
|
163
|
+
<Swatch
|
|
164
|
+
color={sticker.color}
|
|
165
|
+
label={`${sticker.label} sticker`}
|
|
166
|
+
size="sm"
|
|
167
|
+
/>
|
|
168
|
+
|
|
169
|
+
<span className="text-content-secondary text-sm">
|
|
170
|
+
{sticker.label} controller
|
|
171
|
+
</span>
|
|
172
|
+
</li>
|
|
173
|
+
))}
|
|
174
|
+
</ul>
|
|
175
|
+
</Card>
|
|
176
|
+
),
|
|
177
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { composeStories } from "@storybook/react"
|
|
2
|
+
import { expect } from "storybook/test"
|
|
3
|
+
import { test } from "vitest"
|
|
4
|
+
|
|
5
|
+
import { mountStory } from "../mountStory.testHelpers.ts"
|
|
6
|
+
import { expectAgentDrivable } from "../testing/index.ts"
|
|
7
|
+
import * as stories from "./Swatch.stories.tsx"
|
|
8
|
+
|
|
9
|
+
const { Default, InAList } = composeStories(stories)
|
|
10
|
+
|
|
11
|
+
test("a colour is findable by the name a screen reader can read", async () => {
|
|
12
|
+
const { canvas } = await mountStory(Default)
|
|
13
|
+
|
|
14
|
+
// The whole contract: a `background-color` is nothing to an agent
|
|
15
|
+
// or a screen reader, so the swatch is an `img` with a name.
|
|
16
|
+
const swatch = expectAgentDrivable(canvas, {
|
|
17
|
+
name: "Red",
|
|
18
|
+
role: "img",
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
// The name comes from `aria-label`, not the dot, which carries no
|
|
22
|
+
// information of its own.
|
|
23
|
+
await expect(swatch.firstElementChild).toHaveAttribute(
|
|
24
|
+
"aria-hidden",
|
|
25
|
+
"true",
|
|
26
|
+
)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test("the name survives with no visible label", async () => {
|
|
30
|
+
// `InAList` renders the dot-only form — `isLabelVisible` defaults
|
|
31
|
+
// false — with a distinct name per row, which is the placement
|
|
32
|
+
// that has to stay drivable: the swatch answers "which one" before
|
|
33
|
+
// any text does.
|
|
34
|
+
const { canvas } = await mountStory(InAList)
|
|
35
|
+
|
|
36
|
+
for (const name of [
|
|
37
|
+
"Red sticker",
|
|
38
|
+
"Blue sticker",
|
|
39
|
+
"Green sticker",
|
|
40
|
+
"Yellow sticker",
|
|
41
|
+
]) {
|
|
42
|
+
expectAgentDrivable(canvas, { name, role: "img" })
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test("solid rings its fill; the ring is what keeps white visible", async () => {
|
|
47
|
+
const { canvas } = await mountStory(Default)
|
|
48
|
+
|
|
49
|
+
const dot = canvas.getByRole("img", {
|
|
50
|
+
name: "Red",
|
|
51
|
+
}).firstElementChild
|
|
52
|
+
|
|
53
|
+
// Not decoration: a hairline border on a white swatch disappears
|
|
54
|
+
// against `surface-raised` in one of the two schemes, and the
|
|
55
|
+
// inset ring is what stops that.
|
|
56
|
+
await expect(dot).toHaveClass("ring-inset")
|
|
57
|
+
})
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ComponentPropsWithRef,
|
|
3
|
+
ReactNode,
|
|
4
|
+
} from "react"
|
|
5
|
+
|
|
6
|
+
import { toClassName } from "../toClassName.ts"
|
|
7
|
+
|
|
8
|
+
export type SwatchAppearance = "outline" | "solid"
|
|
9
|
+
|
|
10
|
+
export type SwatchSize = keyof typeof SWATCH_SIZE_CLASS
|
|
11
|
+
|
|
12
|
+
export type SwatchProps = Omit<
|
|
13
|
+
ComponentPropsWithRef<"span">,
|
|
14
|
+
"children" | "color"
|
|
15
|
+
> & {
|
|
16
|
+
/**
|
|
17
|
+
* `outline` keeps the hue and drops the fill — for a swatch whose
|
|
18
|
+
* subject is present but inactive. It is the one visual state a
|
|
19
|
+
* status colour cannot borrow from `intent`, because the colour is
|
|
20
|
+
* not an intent.
|
|
21
|
+
*/
|
|
22
|
+
appearance?: SwatchAppearance
|
|
23
|
+
/**
|
|
24
|
+
* Any CSS colour, and this is the whole reason the component
|
|
25
|
+
* exists: it is a **value from data** — a physical sticker, an
|
|
26
|
+
* album's extracted accent, a user-chosen tag — not a token. A
|
|
27
|
+
* design system owns `intent.danger`; it does not own the colour
|
|
28
|
+
* of the dot somebody stuck on a game controller, and re-theming
|
|
29
|
+
* that would be re-theming the hardware. So it is a prop and lands
|
|
30
|
+
* in an inline `style`, the one sanctioned escape hatch for a
|
|
31
|
+
* runtime value.
|
|
32
|
+
*/
|
|
33
|
+
color: string
|
|
34
|
+
isLabelVisible?: boolean
|
|
35
|
+
/**
|
|
36
|
+
* Required, and it is the point. A colour is not a name: a screen
|
|
37
|
+
* reader gets nothing at all from a `background-color`, and
|
|
38
|
+
* `getByRole("img", { name })` — the query an agent writes — has
|
|
39
|
+
* nothing to match. So the type asks for the name and there is no
|
|
40
|
+
* way to render the component without one.
|
|
41
|
+
*/
|
|
42
|
+
label: string
|
|
43
|
+
size?: SwatchSize
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The scale is the component's own, and deliberately larger than
|
|
48
|
+
* `DOT_SIZE_CLASS`.
|
|
49
|
+
*
|
|
50
|
+
* That map tops out at 10px, which is right for a status dot beside
|
|
51
|
+
* a line of text and far too small for a swatch that is the primary
|
|
52
|
+
* way a user tells one row from another across a room. A swatch is
|
|
53
|
+
* content, not punctuation.
|
|
54
|
+
*/
|
|
55
|
+
const SWATCH_SIZE_CLASS = {
|
|
56
|
+
sm: "size-4",
|
|
57
|
+
md: "size-6",
|
|
58
|
+
lg: "size-9",
|
|
59
|
+
} as const
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* A colour presented **as content**, with a name attached.
|
|
63
|
+
*
|
|
64
|
+
* It is not `LiveStatusIndicator`'s dot and not a `Badge`. Both of
|
|
65
|
+
* those take an `intent`, because both show a *state* whose colour
|
|
66
|
+
* the design system owns and can name in English. This shows a
|
|
67
|
+
* colour the system does **not** own and **cannot** name — the red
|
|
68
|
+
* sticker on a controller, red because someone put a red sticker on
|
|
69
|
+
* it; the accent castkit pulls off an album; a user's tag colour.
|
|
70
|
+
* `DOT_SIZE_CLASS` is exported precisely so an app can hand-roll one
|
|
71
|
+
* of these, and three of them across the fleet did, each without a
|
|
72
|
+
* name a screen reader could read.
|
|
73
|
+
*
|
|
74
|
+
* ### `role="img"`, and the name is required
|
|
75
|
+
*
|
|
76
|
+
* A swatch is a graphic that carries meaning, so it is an image with
|
|
77
|
+
* an accessible name — which makes it findable by
|
|
78
|
+
* `getByRole("img", { name })` in **both** the dot-only and
|
|
79
|
+
* labelled forms, one stable handle regardless of whether the text
|
|
80
|
+
* is on screen. The dot and any visible label are inside it and go
|
|
81
|
+
* `aria-hidden` by virtue of the name on the wrapper, exactly as an
|
|
82
|
+
* inline SVG with a caption would.
|
|
83
|
+
*
|
|
84
|
+
* ### The ring is not a token
|
|
85
|
+
*
|
|
86
|
+
* `solid` sits a translucent white ring inside the fill so a white
|
|
87
|
+
* or near-white swatch stays visible against `surface-raised` in
|
|
88
|
+
* both schemes; a hairline border alone disappears on one of the
|
|
89
|
+
* two. `outline` drops the fill to a faint tint of the same hue and
|
|
90
|
+
* draws the ring in the colour itself — via an inline `box-shadow`,
|
|
91
|
+
* because the ring colour has to come from the data and
|
|
92
|
+
* `--tw-ring-color` is not something `CSSProperties` will type.
|
|
93
|
+
*/
|
|
94
|
+
export const Swatch = ({
|
|
95
|
+
appearance = "solid",
|
|
96
|
+
className,
|
|
97
|
+
color,
|
|
98
|
+
isLabelVisible = false,
|
|
99
|
+
label,
|
|
100
|
+
size = "md",
|
|
101
|
+
...spanProps
|
|
102
|
+
}: SwatchProps): ReactNode => (
|
|
103
|
+
<span
|
|
104
|
+
{...spanProps}
|
|
105
|
+
aria-label={label}
|
|
106
|
+
className={toClassName(
|
|
107
|
+
"inline-flex items-center gap-2 align-middle",
|
|
108
|
+
className,
|
|
109
|
+
)}
|
|
110
|
+
role="img"
|
|
111
|
+
>
|
|
112
|
+
<span
|
|
113
|
+
aria-hidden="true"
|
|
114
|
+
className={toClassName(
|
|
115
|
+
"inline-block shrink-0 rounded-full",
|
|
116
|
+
SWATCH_SIZE_CLASS[size],
|
|
117
|
+
appearance === "solid" &&
|
|
118
|
+
"shadow-low ring-2 ring-white/25 ring-inset",
|
|
119
|
+
)}
|
|
120
|
+
style={
|
|
121
|
+
appearance === "solid"
|
|
122
|
+
? { backgroundColor: color }
|
|
123
|
+
: {
|
|
124
|
+
backgroundColor: `color-mix(in srgb, ${color} 12%, transparent)`,
|
|
125
|
+
boxShadow: `inset 0 0 0 3px ${color}`,
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/>
|
|
129
|
+
|
|
130
|
+
{isLabelVisible ? (
|
|
131
|
+
<span
|
|
132
|
+
aria-hidden="true"
|
|
133
|
+
className="text-content-primary text-sm"
|
|
134
|
+
>
|
|
135
|
+
{label}
|
|
136
|
+
</span>
|
|
137
|
+
) : null}
|
|
138
|
+
</span>
|
|
139
|
+
)
|
package/src/index.ts
CHANGED
|
@@ -165,6 +165,12 @@ export type {
|
|
|
165
165
|
export { SortableTableHeader } from "./SortableTableHeader/SortableTableHeader.tsx"
|
|
166
166
|
export type { SpinnerProps } from "./Spinner/Spinner.tsx"
|
|
167
167
|
export { Spinner } from "./Spinner/Spinner.tsx"
|
|
168
|
+
export type {
|
|
169
|
+
SwatchAppearance,
|
|
170
|
+
SwatchProps,
|
|
171
|
+
SwatchSize,
|
|
172
|
+
} from "./Swatch/Swatch.tsx"
|
|
173
|
+
export { Swatch } from "./Swatch/Swatch.tsx"
|
|
168
174
|
export type { SlotProps } from "./slotProps.ts"
|
|
169
175
|
export { mergeSlotProps } from "./slotProps.ts"
|
|
170
176
|
export {
|
package/src/sourceRules.test.ts
CHANGED
|
@@ -300,7 +300,10 @@ test("the barrel is the only place components are re-exported", async () => {
|
|
|
300
300
|
// `Dialog` and reintroduces `Modal` as the base layer, so both are
|
|
301
301
|
// components: +1 → 29. The picker family adds `Listbox` (single-select)
|
|
302
302
|
// and `Combobox` (searchable/virtualized): +2 → 31.
|
|
303
|
-
|
|
303
|
+
// `Swatch` — a colour as content with a required name, the one
|
|
304
|
+
// colour the system does not own — comes out of `portly-controllers`,
|
|
305
|
+
// the fleet's newest consumer: +1 -> 32.
|
|
306
|
+
expect(componentNames.length).toBe(32)
|
|
304
307
|
|
|
305
308
|
for (const name of componentNames) {
|
|
306
309
|
expect(barrel).toContain(`export { ${name} }`)
|