@charcuterie/ui 3.9.0 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +11 -0
  2. package/dist/Badge/Badge.d.ts +2 -37
  3. package/dist/Badge/Badge.d.ts.map +1 -1
  4. package/dist/Badge/Badge.js +15 -30
  5. package/dist/Badge/Badge.js.map +1 -1
  6. package/dist/Badge/useBadgeShape.d.ts +57 -0
  7. package/dist/Badge/useBadgeShape.d.ts.map +1 -0
  8. package/dist/Badge/useBadgeShape.js +35 -0
  9. package/dist/Badge/useBadgeShape.js.map +1 -0
  10. package/dist/BadgeButton/BadgeButton.d.ts +59 -0
  11. package/dist/BadgeButton/BadgeButton.d.ts.map +1 -0
  12. package/dist/BadgeButton/BadgeButton.js +64 -0
  13. package/dist/BadgeButton/BadgeButton.js.map +1 -0
  14. package/dist/Card/Card.d.ts +12 -1
  15. package/dist/Card/Card.d.ts.map +1 -1
  16. package/dist/Card/Card.js +14 -2
  17. package/dist/Card/Card.js.map +1 -1
  18. package/dist/Card/cardAccentEdge.d.ts +101 -0
  19. package/dist/Card/cardAccentEdge.d.ts.map +1 -0
  20. package/dist/Card/cardAccentEdge.js +92 -0
  21. package/dist/Card/cardAccentEdge.js.map +1 -0
  22. package/dist/index.d.ts +4 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +1 -0
  25. package/dist/index.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/Badge/Badge.tsx +30 -113
  28. package/src/Badge/useBadgeShape.tsx +142 -0
  29. package/src/BadgeButton/BadgeButton.mdx +67 -0
  30. package/src/BadgeButton/BadgeButton.stories.tsx +199 -0
  31. package/src/BadgeButton/BadgeButton.test.tsx +164 -0
  32. package/src/BadgeButton/BadgeButton.tsx +129 -0
  33. package/src/Card/Card.mdx +21 -0
  34. package/src/Card/Card.stories.tsx +91 -0
  35. package/src/Card/Card.test.tsx +92 -1
  36. package/src/Card/Card.tsx +34 -0
  37. package/src/Card/cardAccentEdge.test.ts +105 -0
  38. package/src/Card/cardAccentEdge.ts +135 -0
  39. package/src/index.ts +4 -0
  40. package/src/sourceRules.test.ts +8 -1
package/README.md CHANGED
@@ -48,6 +48,16 @@ Moving a card takes **no drag-and-drop dependency**: one handle per card, a `Men
48
48
  other lanes as the primary path, Pointer-Event dragging at 1.4 KB gzip beside it
49
49
  ([decision](../../docs/decisions/2026-08-19-the-board-owns-the-move-and-takes-no-drag-and-drop-dependency.md)).
50
50
 
51
+ **`BadgeButton` is a `Badge` you can press**, and it came from a consumer rather than from a
52
+ plan: QueuePilot has six pill-shaped controls — per-entry setting tags, an Edit chip, two
53
+ start-point chips, a group chip, a pool's Exclude chip — that were hand-rolled
54
+ `<button className="badge …">` because `Badge` is a `<span>`. A sibling component, exactly
55
+ as `ButtonLink` is to `Button`: one paint through `useBadgeShape`, compared as **computed**
56
+ styles in the test, and a real `<button>` for focus, Enter, Space, `:disabled` and a role a
57
+ screen reader announces. `onClick` is required, so a pill nobody can press is a `Badge` by
58
+ construction
59
+ ([decision](../../docs/decisions/2026-08-22-a-pressable-badge-is-a-sibling-component-not-a-prop.md)).
60
+
51
61
  **The unified app shell adds four**: `Shell`, `Header`, `Rail`, `Main` — the fleet's largest
52
62
  single duplication, where ten of twelve UI repos hand-roll the page chrome and three of them
53
63
  have a file named `AppShell.tsx` (two of those headers are a *byte-identical* class string,
@@ -136,6 +146,7 @@ an hour of confusion.
136
146
  | Type | `text-sm`/`text-md`/`text-lg`, which are **ours**: `theme.css` bridges `--text-*` onto the density-scaled `--font-size-*` |
137
147
  | Motion | `styles.css`, at `--duration-loop-*`, switched off under `prefers-reduced-motion` |
138
148
  | Scrollbar | `styles.css` — add `charcuterie-scrollbar` to any scrolling element for a token-tinted bar (rounded thumb on Chromium/Safari; thin standard match on Firefox) that flips with `[data-scheme]` |
149
+ | Accent edge | `Card/cardAccentEdge.ts` — `<Card accentEdge={{ categorical }} \| {{ color }}>` draws a bar down the leading edge from a pseudo-element that **inherits the card's radius**. A border cannot follow a corner, which is why three apps had a square bar on a round card |
139
150
  | State | `@charcuterie/logic` — `useUniqueId` for label wiring, `useStatus` for `MediaTile`'s three image states |
140
151
 
141
152
  **Class names are never interpolated.** `` `bg-intent-${intent}-solid` `` generates nothing
@@ -1,41 +1,6 @@
1
- import type { CategoricalIndex, IntentName } from "@charcuterie/tokens";
2
1
  import type { ComponentPropsWithRef, ReactNode } from "react";
3
- import type { BadgeSize } from "../controlStyles.ts";
4
- import type { IntentAppearance } from "../intentStyles.ts";
5
- export type BadgeProps = ComponentPropsWithRef<"span"> & {
6
- appearance?: Exclude<IntentAppearance, "ghost">;
7
- /**
8
- * A numbered, **non-semantic** colour: 1–10, no meaning attached.
9
- *
10
- * For a badge whose colour the user picked — a Docket label, a
11
- * project, a chart series — where `intent` would be a lie. A
12
- * "Homelab" label is not a `danger`, and the palette is curated
13
- * and contrast-gated in both schemes precisely so the user cannot
14
- * pick one that is unreadable. `getCategoricalIndex(key)` from
15
- * `@charcuterie/tokens` is the stable fallback for a row nobody
16
- * has chosen a colour for yet.
17
- *
18
- * Mutually exclusive with `intent` **in the type**: a badge is one
19
- * colour, and passing both is a question with no answer rather
20
- * than a precedence rule to remember.
21
- */
22
- categorical?: CategoricalIndex;
23
- children: ReactNode;
24
- icon?: ReactNode;
25
- intent?: IntentName;
26
- /**
27
- * What happens when the label is wider than the space it is given.
28
- *
29
- * - `truncate` — one line, capped at the container, ellipsis. The
30
- * default, because a status pill that changes its row's height
31
- * when a message gets longer breaks the table it sits in.
32
- * - `wrap` — the pill grows taller and shows everything. For the
33
- * kiosk and any touch context, where the hover readout that
34
- * `truncate` relies on does not exist.
35
- */
36
- overflow?: "truncate" | "wrap";
37
- size?: BadgeSize;
38
- } & ({
2
+ import type { BadgeShapeProps } from "./useBadgeShape.tsx";
3
+ export type BadgeProps = ComponentPropsWithRef<"span"> & BadgeShapeProps & {} & ({
39
4
  categorical?: never;
40
5
  } | {
41
6
  intent?: never;
@@ -1 +1 @@
1
- {"version":3,"file":"Badge.d.ts","sourceRoot":"","sources":["../../src/Badge/Badge.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EACX,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EACV,qBAAqB,EACrB,SAAS,EACV,MAAM,OAAO,CAAA;AAGd,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAEpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAK1D,MAAM,MAAM,UAAU,GAAG,qBAAqB,CAAC,MAAM,CAAC,GAAG;IACvD,UAAU,CAAC,EAAE,OAAO,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAA;IAC/C;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,EAAE,gBAAgB,CAAA;IAC9B,QAAQ,EAAE,SAAS,CAAA;IACnB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,UAAU,GAAG,MAAM,CAAA;IAC9B,IAAI,CAAC,EAAE,SAAS,CAAA;CAQjB,GAAG,CAAC;IAAE,WAAW,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC,CAAA;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,eAAO,MAAM,KAAK,GAAI,qGAWnB,UAAU,KAAG,SAiEf,CAAA"}
1
+ {"version":3,"file":"Badge.d.ts","sourceRoot":"","sources":["../../src/Badge/Badge.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,SAAS,EACV,MAAM,OAAO,CAAA;AAEd,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAG1D,MAAM,MAAM,UAAU,GAAG,qBAAqB,CAAC,MAAM,CAAC,GACpD,eAAe,GAAG,EAQjB,GAAG,CAAC;IAAE,WAAW,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC,CAAA;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,eAAO,MAAM,KAAK,GAAI,qGAWnB,UAAU,KAAG,SAsBf,CAAA"}
@@ -1,9 +1,5 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { CATEGORICAL_APPEARANCE_CLASS } from "../categoricalStyles.js";
3
- import { BADGE_SIZE_CLASS } from "../controlStyles.js";
4
- import { INTENT_APPEARANCE_CLASS } from "../intentStyles.js";
5
- import { toClassName } from "../toClassName.js";
6
- import { useIsTextClipped } from "./useIsTextClipped.js";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useBadgeShape } from "./useBadgeShape.js";
7
3
  /**
8
4
  * Four repos, and rip-deck declares the **identical** `TONE_CLASS`
9
5
  * map twice — `VerdictBadge.tsx` and `TowerAlerts.tsx`, same five
@@ -44,29 +40,18 @@ import { useIsTextClipped } from "./useIsTextClipped.js";
44
40
  * across the next cell. `max-inline-size: 100%` is what stops it, and
45
41
  * `overflow` is what happens next.
46
42
  */
47
- export const Badge = ({ appearance = "soft", categorical, children, className, icon, intent = "neutral", overflow = "truncate", size = "md", title, ...spanProps }) => {
48
- const [labelRef, clippedText] = useIsTextClipped();
49
- return (_jsxs("span", { ...spanProps, className: toClassName("inline-flex shrink-0 items-center rounded-full border font-medium",
50
- // The cap. Without it every other rule here is decoration.
51
- "max-w-full", overflow === "wrap"
52
- ? // A stadium end-cap on a three-line box reads as a
53
- // rendering fault rather than a badge, so the wrapping
54
- // pill relaxes to a rounded rectangle.
55
- "rounded-2xl"
56
- : "rounded-full", BADGE_SIZE_CLASS[size],
57
- // A ternary rather than a merged map: the two are indexed
58
- // by different keys, and `undefined` is the only honest
59
- // discriminant once the type has already refused the case
60
- // where both arrive.
61
- categorical === undefined
62
- ? INTENT_APPEARANCE_CLASS[intent][appearance]
63
- : CATEGORICAL_APPEARANCE_CLASS[categorical][appearance], className),
64
- // Only when something is actually hidden. A tooltip on every
65
- // short pill in a bay list is noise, and a `title` that
66
- // duplicates fully-visible text is a screen-reader duplicate
67
- // for no gain.
68
- title: title ?? clippedText, children: [icon ? (_jsx("span", { "aria-hidden": "true", className: "contents shrink-0", children: icon })) : null, _jsx("span", { className: overflow === "wrap"
69
- ? "min-w-0"
70
- : "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", ref: labelRef, children: children })] }));
43
+ export const Badge = ({ appearance, categorical, children, className, icon, intent, overflow, size, title, ...spanProps }) => {
44
+ const shape = useBadgeShape({
45
+ appearance,
46
+ categorical,
47
+ children,
48
+ className,
49
+ icon,
50
+ intent,
51
+ overflow,
52
+ size,
53
+ title,
54
+ });
55
+ return (_jsx("span", { ...spanProps, className: shape.className, title: shape.title, children: shape.content }));
71
56
  };
72
57
  //# sourceMappingURL=Badge.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Badge.js","sourceRoot":"","sources":["../../src/Badge/Badge.tsx"],"names":[],"mappings":";AASA,OAAO,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAA;AAEtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAEtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAA;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AA4CxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EACpB,UAAU,GAAG,MAAM,EACnB,WAAW,EACX,QAAQ,EACR,SAAS,EACT,IAAI,EACJ,MAAM,GAAG,SAAS,EAClB,QAAQ,GAAG,UAAU,EACrB,IAAI,GAAG,IAAI,EACX,KAAK,EACL,GAAG,SAAS,EACD,EAAa,EAAE;IAC1B,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,gBAAgB,EAAE,CAAA;IAElD,OAAO,CACL,mBACM,SAAS,EACb,SAAS,EAAE,WAAW,CACpB,mEAAmE;QACnE,2DAA2D;QAC3D,YAAY,EACZ,QAAQ,KAAK,MAAM;YACjB,CAAC,CAAC,mDAAmD;gBACnD,uDAAuD;gBACvD,uCAAuC;gBACvC,aAAa;YACf,CAAC,CAAC,cAAc,EAClB,gBAAgB,CAAC,IAAI,CAAC;QACtB,0DAA0D;QAC1D,wDAAwD;QACxD,0DAA0D;QAC1D,qBAAqB;QACrB,WAAW,KAAK,SAAS;YACvB,CAAC,CAAC,uBAAuB,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC;YAC7C,CAAC,CAAC,4BAA4B,CAAC,WAAW,CAAC,CACvC,UAAU,CACX,EACL,SAAS,CACV;QACD,6DAA6D;QAC7D,wDAAwD;QACxD,6DAA6D;QAC7D,eAAe;QACf,KAAK,EAAE,KAAK,IAAI,WAAW,aAE1B,IAAI,CAAC,CAAC,CAAC,CACN,8BACc,MAAM,EAClB,SAAS,EAAC,mBAAmB,YAE5B,IAAI,GACA,CACR,CAAC,CAAC,CAAC,IAAI,EAYR,eACE,SAAS,EACP,QAAQ,KAAK,MAAM;oBACjB,CAAC,CAAC,SAAS;oBACX,CAAC,CAAC,yDAAyD,EAE/D,GAAG,EAAE,QAAQ,YAEZ,QAAQ,GACJ,IACF,CACR,CAAA;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"Badge.js","sourceRoot":"","sources":["../../src/Badge/Badge.tsx"],"names":[],"mappings":";AAMA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAqB,CAAA;AAanD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EACpB,UAAU,EACV,WAAW,EACX,QAAQ,EACR,SAAS,EACT,IAAI,EACJ,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,GAAG,SAAS,EACD,EAAa,EAAE;IAC1B,MAAM,KAAK,GAAG,aAAa,CAAC;QAC1B,UAAU;QACV,WAAW;QACX,QAAQ;QACR,SAAS;QACT,IAAI;QACJ,MAAM;QACN,QAAQ;QACR,IAAI;QACJ,KAAK;KACN,CAAC,CAAA;IAEF,OAAO,CACL,kBACM,SAAS,EACb,SAAS,EAAE,KAAK,CAAC,SAAS,EAC1B,KAAK,EAAE,KAAK,CAAC,KAAK,YAEjB,KAAK,CAAC,OAAO,GACT,CACR,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,57 @@
1
+ import type { CategoricalIndex, IntentName } from "@charcuterie/tokens";
2
+ import type { ReactNode } from "react";
3
+ import type { BadgeSize } from "../controlStyles.ts";
4
+ import type { IntentAppearance } from "../intentStyles.ts";
5
+ /**
6
+ * The visual half of a badge, shared by the two elements that wear
7
+ * it.
8
+ *
9
+ * `Badge` is a `<span>` and `BadgeButton` is a `<button>`, and the
10
+ * pill is supposed to be indistinguishable between them — so the
11
+ * class list, the icon slot, the truncation and the
12
+ * only-when-clipped `title` are computed **here**, once, exactly as
13
+ * `Button` and `ButtonLink` both call `getControlClassName`. A
14
+ * second copy of this arithmetic is how the two drift: the wrapping
15
+ * pill's `rounded-2xl` and the label's `min-w-0` are each one line,
16
+ * and each is load-bearing.
17
+ */
18
+ export type BadgeShapeProps = {
19
+ appearance?: Exclude<IntentAppearance, "ghost">;
20
+ /**
21
+ * A numbered, **non-semantic** colour: 1–10, no meaning attached.
22
+ *
23
+ * For a badge whose colour the user picked — a Docket label, a
24
+ * project, a chart series — where `intent` would be a lie.
25
+ * `getCategoricalIndex(key)` from `@charcuterie/tokens` is the
26
+ * stable fallback for a row nobody has chosen a colour for yet.
27
+ *
28
+ * Mutually exclusive with `intent` **in the type**: a badge is one
29
+ * colour, and passing both is a question with no answer rather
30
+ * than a precedence rule to remember.
31
+ */
32
+ categorical?: CategoricalIndex;
33
+ children: ReactNode;
34
+ icon?: ReactNode;
35
+ intent?: IntentName;
36
+ /**
37
+ * What happens when the label is wider than the space it is given.
38
+ *
39
+ * - `truncate` — one line, capped at the container, ellipsis. The
40
+ * default, because a status pill that changes its row's height
41
+ * when a message gets longer breaks the table it sits in.
42
+ * - `wrap` — the pill grows taller and shows everything. For the
43
+ * kiosk and any touch context, where the hover readout that
44
+ * `truncate` relies on does not exist.
45
+ */
46
+ overflow?: "truncate" | "wrap";
47
+ size?: BadgeSize;
48
+ };
49
+ export declare const useBadgeShape: ({ appearance, categorical, children, className, icon, intent, overflow, size, title, }: BadgeShapeProps & {
50
+ className?: string;
51
+ title?: string;
52
+ }) => {
53
+ className: string;
54
+ content: ReactNode;
55
+ title?: string;
56
+ };
57
+ //# sourceMappingURL=useBadgeShape.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBadgeShape.d.ts","sourceRoot":"","sources":["../../src/Badge/useBadgeShape.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EACX,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAEpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAK1D;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAA;IAC/C;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,EAAE,gBAAgB,CAAA;IAC9B,QAAQ,EAAE,SAAS,CAAA;IACnB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,UAAU,GAAG,MAAM,CAAA;IAC9B,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB,CAAA;AAED,eAAO,MAAM,aAAa,GAAI,wFAU3B,eAAe,GAAG;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,KAAG;IACF,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,SAAS,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;CAkEf,CAAA"}
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CATEGORICAL_APPEARANCE_CLASS } from "../categoricalStyles.js";
3
+ import { BADGE_SIZE_CLASS } from "../controlStyles.js";
4
+ import { INTENT_APPEARANCE_CLASS } from "../intentStyles.js";
5
+ import { toClassName } from "../toClassName.js";
6
+ import { useIsTextClipped } from "./useIsTextClipped.js";
7
+ export const useBadgeShape = ({ appearance = "soft", categorical, children, className, icon, intent = "neutral", overflow = "truncate", size = "md", title, }) => {
8
+ const [labelRef, clippedText] = useIsTextClipped();
9
+ return {
10
+ className: toClassName("inline-flex shrink-0 items-center border font-medium",
11
+ // The cap. Without it every other rule here is decoration.
12
+ "max-w-full", overflow === "wrap"
13
+ ? // A stadium end-cap on a three-line box reads as a
14
+ // rendering fault rather than a badge, so the wrapping
15
+ // pill relaxes to a rounded rectangle.
16
+ "rounded-2xl"
17
+ : "rounded-full", BADGE_SIZE_CLASS[size],
18
+ // A ternary rather than a merged map: the two are indexed
19
+ // by different keys, and `undefined` is the only honest
20
+ // discriminant once the type has already refused the case
21
+ // where both arrive.
22
+ categorical === undefined
23
+ ? INTENT_APPEARANCE_CLASS[intent][appearance]
24
+ : CATEGORICAL_APPEARANCE_CLASS[categorical][appearance], className),
25
+ content: (_jsxs(_Fragment, { children: [icon ? (_jsx("span", { "aria-hidden": "true", className: "contents shrink-0", children: icon })) : null, _jsx("span", { className: overflow === "wrap"
26
+ ? "min-w-0"
27
+ : "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", ref: labelRef, children: children })] })),
28
+ // Only when something is actually hidden. A tooltip on every
29
+ // short pill in a bay list is noise, and a `title` that
30
+ // duplicates fully-visible text is a screen-reader duplicate
31
+ // for no gain.
32
+ title: title ?? clippedText,
33
+ };
34
+ };
35
+ //# sourceMappingURL=useBadgeShape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBadgeShape.js","sourceRoot":"","sources":["../../src/Badge/useBadgeShape.tsx"],"names":[],"mappings":";AAMA,OAAO,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAA;AAEtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAEtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAA;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AA+CxD,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAC5B,UAAU,GAAG,MAAM,EACnB,WAAW,EACX,QAAQ,EACR,SAAS,EACT,IAAI,EACJ,MAAM,GAAG,SAAS,EAClB,QAAQ,GAAG,UAAU,EACrB,IAAI,GAAG,IAAI,EACX,KAAK,GAIN,EAIC,EAAE;IACF,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,gBAAgB,EAAE,CAAA;IAElD,OAAO;QACL,SAAS,EAAE,WAAW,CACpB,sDAAsD;QACtD,2DAA2D;QAC3D,YAAY,EACZ,QAAQ,KAAK,MAAM;YACjB,CAAC,CAAC,mDAAmD;gBACnD,uDAAuD;gBACvD,uCAAuC;gBACvC,aAAa;YACf,CAAC,CAAC,cAAc,EAClB,gBAAgB,CAAC,IAAI,CAAC;QACtB,0DAA0D;QAC1D,wDAAwD;QACxD,0DAA0D;QAC1D,qBAAqB;QACrB,WAAW,KAAK,SAAS;YACvB,CAAC,CAAC,uBAAuB,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC;YAC7C,CAAC,CAAC,4BAA4B,CAAC,WAAW,CAAC,CACvC,UAAU,CACX,EACL,SAAS,CACV;QACD,OAAO,EAAE,CACP,8BACG,IAAI,CAAC,CAAC,CAAC,CACN,8BACc,MAAM,EAClB,SAAS,EAAC,mBAAmB,YAE5B,IAAI,GACA,CACR,CAAC,CAAC,CAAC,IAAI,EAYR,eACE,SAAS,EACP,QAAQ,KAAK,MAAM;wBACjB,CAAC,CAAC,SAAS;wBACX,CAAC,CAAC,yDAAyD,EAE/D,GAAG,EAAE,QAAQ,YAEZ,QAAQ,GACJ,IACN,CACJ;QACD,6DAA6D;QAC7D,wDAAwD;QACxD,6DAA6D;QAC7D,eAAe;QACf,KAAK,EAAE,KAAK,IAAI,WAAW;KAC5B,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,59 @@
1
+ import type { ComponentPropsWithRef, ReactNode } from "react";
2
+ import type { BadgeShapeProps } from "../Badge/useBadgeShape.tsx";
3
+ export type BadgeButtonProps = Omit<ComponentPropsWithRef<"button">, "disabled"> & BadgeShapeProps & {
4
+ /**
5
+ * Required, and that is the component's argument in one line: a
6
+ * pill nobody can press is a `Badge`, and a pill that looks
7
+ * pressable and is not is the defect this exists to stop.
8
+ */
9
+ onClick: NonNullable<ComponentPropsWithRef<"button">["onClick"]>;
10
+ /**
11
+ * `isDisabled`, not `disabled` — the same prop `Button` keeps
12
+ * instead of the native one, so a control's disabled state is
13
+ * spelled one way across the library.
14
+ */
15
+ isDisabled?: boolean;
16
+ } & ({
17
+ categorical?: never;
18
+ } | {
19
+ intent?: never;
20
+ });
21
+ /**
22
+ * A badge you can press.
23
+ *
24
+ * A sibling component rather than a prop, exactly as `ButtonLink` is
25
+ * to `Button`: the paint is shared (both call `useBadgeShape`, so
26
+ * there is one class list and `BadgeButton.test.tsx` compares the
27
+ * two elements' *computed* styles rather than trusting that), and
28
+ * what differs is the element and everything the platform hangs off
29
+ * it.
30
+ *
31
+ * **Why not `asChild`, and why not `Badge` growing an `onClick`.**
32
+ * This library has no polymorphism pattern and has not needed one;
33
+ * adding it here would mean every future component answering "can it
34
+ * be something else?" instead of "what is it?". And an element type
35
+ * that changes as a *side effect* of passing a handler is a footgun
36
+ * with no error: forget the handler and a control silently ships as
37
+ * a `<span>` — not focusable, not in the tab order, invisible to
38
+ * `getByRole("button")` and to every agent driving the app.
39
+ *
40
+ * **What the element buys, and none of it is paint.** Focus, the tab
41
+ * order, Enter and Space, `:disabled`, form participation, and a
42
+ * `button` role a screen reader announces as pressable. QueuePilot
43
+ * had six pills doing all of this with a hand-rolled
44
+ * `<button className="badge …">`, which is where this came from —
45
+ * six chips that open an editor, change a start point, or drop a
46
+ * title from a pool.
47
+ *
48
+ * **`type="button"` by default.** These chips sit inside forms —
49
+ * QueuePilot's entry sheet is one — and the platform's default of
50
+ * `submit` would make a "change the start point" chip save the whole
51
+ * dialog. Overridable, because a badge-shaped submit is a real thing
52
+ * and the default is a safe floor rather than a ban.
53
+ *
54
+ * **`ghost` is still excluded**, inherited from `Badge`: a pill that
55
+ * paints nothing until hovered has no pill left, and a control that
56
+ * only advertises itself on hover cannot be found by touch.
57
+ */
58
+ export declare const BadgeButton: ({ appearance, categorical, children, className, icon, intent, isDisabled, overflow, size, title, type, ...buttonProps }: BadgeButtonProps) => ReactNode;
59
+ //# sourceMappingURL=BadgeButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BadgeButton.d.ts","sourceRoot":"","sources":["../../src/BadgeButton/BadgeButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,SAAS,EACV,MAAM,OAAO,CAAA;AAEd,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAUjE,MAAM,MAAM,gBAAgB,GAAG,IAAI,CACjC,qBAAqB,CAAC,QAAQ,CAAC,EAC/B,UAAU,CACX,GACC,eAAe,GAAG;IAChB;;;;OAIG;IACH,OAAO,EAAE,WAAW,CAClB,qBAAqB,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,CAC3C,CAAA;IACD;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CAKrB,GAAG,CAAC;IAAE,WAAW,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC,CAAA;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,WAAW,GAAI,yHAazB,gBAAgB,KAAG,SAsCrB,CAAA"}
@@ -0,0 +1,64 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useBadgeShape } from "../Badge/useBadgeShape.js";
3
+ import { CATEGORICAL_HOVER_CLASS } from "../categoricalStyles.js";
4
+ import { DISABLED_CLASS, FOCUS_RING_CLASS, INTENT_HOVER_CLASS, } from "../intentStyles.js";
5
+ import { toClassName } from "../toClassName.js";
6
+ /**
7
+ * A badge you can press.
8
+ *
9
+ * A sibling component rather than a prop, exactly as `ButtonLink` is
10
+ * to `Button`: the paint is shared (both call `useBadgeShape`, so
11
+ * there is one class list and `BadgeButton.test.tsx` compares the
12
+ * two elements' *computed* styles rather than trusting that), and
13
+ * what differs is the element and everything the platform hangs off
14
+ * it.
15
+ *
16
+ * **Why not `asChild`, and why not `Badge` growing an `onClick`.**
17
+ * This library has no polymorphism pattern and has not needed one;
18
+ * adding it here would mean every future component answering "can it
19
+ * be something else?" instead of "what is it?". And an element type
20
+ * that changes as a *side effect* of passing a handler is a footgun
21
+ * with no error: forget the handler and a control silently ships as
22
+ * a `<span>` — not focusable, not in the tab order, invisible to
23
+ * `getByRole("button")` and to every agent driving the app.
24
+ *
25
+ * **What the element buys, and none of it is paint.** Focus, the tab
26
+ * order, Enter and Space, `:disabled`, form participation, and a
27
+ * `button` role a screen reader announces as pressable. QueuePilot
28
+ * had six pills doing all of this with a hand-rolled
29
+ * `<button className="badge …">`, which is where this came from —
30
+ * six chips that open an editor, change a start point, or drop a
31
+ * title from a pool.
32
+ *
33
+ * **`type="button"` by default.** These chips sit inside forms —
34
+ * QueuePilot's entry sheet is one — and the platform's default of
35
+ * `submit` would make a "change the start point" chip save the whole
36
+ * dialog. Overridable, because a badge-shaped submit is a real thing
37
+ * and the default is a safe floor rather than a ban.
38
+ *
39
+ * **`ghost` is still excluded**, inherited from `Badge`: a pill that
40
+ * paints nothing until hovered has no pill left, and a control that
41
+ * only advertises itself on hover cannot be found by touch.
42
+ */
43
+ export const BadgeButton = ({ appearance = "soft", categorical, children, className, icon, intent = "neutral", isDisabled = false, overflow, size, title, type = "button", ...buttonProps }) => {
44
+ const shape = useBadgeShape({
45
+ appearance,
46
+ categorical,
47
+ children,
48
+ icon,
49
+ intent,
50
+ overflow,
51
+ size,
52
+ title,
53
+ });
54
+ return (_jsx("button", { ...buttonProps, className: toClassName(shape.className, "cursor-pointer",
55
+ // The three states the `<span>` has no business carrying,
56
+ // keyed the same way the appearance above was.
57
+ categorical === undefined
58
+ ? INTENT_HOVER_CLASS[intent][appearance]
59
+ : CATEGORICAL_HOVER_CLASS[categorical][appearance], FOCUS_RING_CLASS, DISABLED_CLASS,
60
+ // Last, so a caller's layout beats the defaults above — the
61
+ // same order `getControlClassName` uses.
62
+ className), disabled: isDisabled, title: shape.title, type: type, children: shape.content }));
63
+ };
64
+ //# sourceMappingURL=BadgeButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BadgeButton.js","sourceRoot":"","sources":["../../src/BadgeButton/BadgeButton.tsx"],"names":[],"mappings":";AAMA,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA4B,CAAA;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AA2B/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,EAC1B,UAAU,GAAG,MAAM,EACnB,WAAW,EACX,QAAQ,EACR,SAAS,EACT,IAAI,EACJ,MAAM,GAAG,SAAS,EAClB,UAAU,GAAG,KAAK,EAClB,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,IAAI,GAAG,QAAQ,EACf,GAAG,WAAW,EACG,EAAa,EAAE;IAChC,MAAM,KAAK,GAAG,aAAa,CAAC;QAC1B,UAAU;QACV,WAAW;QACX,QAAQ;QACR,IAAI;QACJ,MAAM;QACN,QAAQ;QACR,IAAI;QACJ,KAAK;KACN,CAAC,CAAA;IAEF,OAAO,CACL,oBACM,WAAW,EACf,SAAS,EAAE,WAAW,CACpB,KAAK,CAAC,SAAS,EACf,gBAAgB;QAChB,0DAA0D;QAC1D,+CAA+C;QAC/C,WAAW,KAAK,SAAS;YACvB,CAAC,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC;YACxC,CAAC,CAAC,uBAAuB,CAAC,WAAW,CAAC,CAClC,UAAU,CACX,EACL,gBAAgB,EAChB,cAAc;QACd,4DAA4D;QAC5D,yCAAyC;QACzC,SAAS,CACV,EACD,QAAQ,EAAE,UAAU,EACpB,KAAK,EAAE,KAAK,CAAC,KAAK,EAClB,IAAI,EAAE,IAAI,YAET,KAAK,CAAC,OAAO,GACP,CACV,CAAA;AACH,CAAC,CAAA"}
@@ -1,8 +1,19 @@
1
1
  import type { ElevationStep, SurfaceRole } from "@charcuterie/tokens";
2
2
  import type { ComponentPropsWithRef, ReactNode } from "react";
3
+ import type { CardAccentEdge } from "./cardAccentEdge.ts";
3
4
  export type CardSurface = Extract<SurfaceRole, "base" | "raised" | "sunken">;
4
5
  export type CardPadding = "lg" | "md" | "none" | "sm";
5
6
  export type CardProps = ComponentPropsWithRef<"section"> & {
7
+ /**
8
+ * A coloured bar down the leading edge, following the card's own
9
+ * corners.
10
+ *
11
+ * `{ categorical: 3 }` for a colour a user picked; `{ color }`
12
+ * for one an app computed — a hue hashed from a repo's name, say,
13
+ * where ten indices are not enough. See `cardAccentEdge.ts` for
14
+ * why it is a pseudo-element and not a border.
15
+ */
16
+ accentEdge?: CardAccentEdge;
6
17
  /** Buttons or a badge, laid out beside the heading. */
7
18
  actions?: ReactNode;
8
19
  elevation?: ElevationStep;
@@ -40,5 +51,5 @@ export declare const ELEVATION_CLASS: Record<ElevationStep, string>;
40
51
  * because a card wrapping a poster grid genuinely has none, which is
41
52
  * a layout fact rather than a density one.
42
53
  */
43
- export declare const Card: ({ actions, children, className, elevation, footer, heading, headingLevel, padding, surface, ...sectionProps }: CardProps) => ReactNode;
54
+ export declare const Card: ({ accentEdge, actions, children, className, elevation, footer, heading, headingLevel, padding, style, surface, ...sectionProps }: CardProps) => ReactNode;
44
55
  //# sourceMappingURL=Card.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Card.d.ts","sourceRoot":"","sources":["../../src/Card/Card.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,aAAa,EACb,WAAW,EACZ,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EACV,qBAAqB,EACrB,SAAS,EACV,MAAM,OAAO,CAAA;AAId,MAAM,MAAM,WAAW,GAAG,OAAO,CAC/B,WAAW,EACX,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAC7B,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;AAErD,MAAM,MAAM,SAAS,GAAG,qBAAqB,CAAC,SAAS,CAAC,GAAG;IACzD,uDAAuD;IACvD,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,SAAS,CAAC,EAAE,aAAa,CAAA;IACzB,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACxB,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,OAAO,CAAC,EAAE,WAAW,CAAA;CACtB,CAAA;AAQD;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAClC,aAAa,EACb,MAAM,CAMP,CAAA;AASD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,IAAI,GAAI,+GAWlB,SAAS,KAAG,SAkDd,CAAA"}
1
+ {"version":3,"file":"Card.d.ts","sourceRoot":"","sources":["../../src/Card/Card.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,aAAa,EACb,WAAW,EACZ,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EACV,qBAAqB,EACrB,SAAS,EACV,MAAM,OAAO,CAAA;AAGd,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAMzD,MAAM,MAAM,WAAW,GAAG,OAAO,CAC/B,WAAW,EACX,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAC7B,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;AAErD,MAAM,MAAM,SAAS,GAAG,qBAAqB,CAAC,SAAS,CAAC,GAAG;IACzD;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,cAAc,CAAA;IAC3B,uDAAuD;IACvD,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,SAAS,CAAC,EAAE,aAAa,CAAA;IACzB,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACxB,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,OAAO,CAAC,EAAE,WAAW,CAAA;CACtB,CAAA;AAQD;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAClC,aAAa,EACb,MAAM,CAMP,CAAA;AASD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,IAAI,GAAI,kIAalB,SAAS,KAAG,SAmEd,CAAA"}
package/dist/Card/Card.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useUniqueId } from "@charcuterie/logic";
3
3
  import { toClassName } from "../toClassName.js";
4
+ import { getAccentEdgeClassName, getAccentEdgeStyle, } from "./cardAccentEdge.js";
4
5
  const SURFACE_CLASS = {
5
6
  base: "bg-surface-base",
6
7
  raised: "bg-surface-raised",
@@ -45,8 +46,17 @@ const PADDING_CLASS = {
45
46
  * because a card wrapping a poster grid genuinely has none, which is
46
47
  * a layout fact rather than a density one.
47
48
  */
48
- export const Card = ({ actions, children, className, elevation = "low", footer, heading, headingLevel = 2, padding = "md", surface = "raised", ...sectionProps }) => {
49
+ export const Card = ({ accentEdge, actions, children, className, elevation = "low", footer, heading, headingLevel = 2, padding = "md", style, surface = "raised", ...sectionProps }) => {
49
50
  const headingId = useUniqueId();
51
+ // The caller's own `style` wins. An accent colour is one custom
52
+ // property, and silently dropping whatever else the call site set
53
+ // would be a worse trade than letting it override this one.
54
+ const accentEdgeStyle = accentEdge == null
55
+ ? undefined
56
+ : getAccentEdgeStyle(accentEdge);
57
+ const mergedStyle = accentEdgeStyle == null
58
+ ? style
59
+ : { ...accentEdgeStyle, ...style };
50
60
  const Heading = `h${headingLevel}`;
51
61
  return (_jsxs("section", { ...sectionProps, "aria-labelledby": heading ? headingId : undefined, className: toClassName(
52
62
  // `@container` here, not on a wrapper, is what lets the
@@ -54,6 +64,8 @@ export const Card = ({ actions, children, className, elevation = "low", footer,
54
64
  // a card in a sidebar and the same card full-bleed are the
55
65
  // case a media query cannot tell apart, and the fleet's
56
66
  // poster grids get it wrong at intermediate widths today.
57
- "@container flex flex-col gap-3 rounded-lg border border-border-subtle text-content-primary", SURFACE_CLASS[surface], ELEVATION_CLASS[elevation], PADDING_CLASS[padding], className), children: [heading || actions ? (_jsxs("header", { className: "flex flex-col gap-2 cq-sm:flex-row cq-sm:items-start cq-sm:justify-between", children: [heading ? (_jsx(Heading, { className: "font-semibold text-md leading-tight", id: headingId, children: heading })) : null, actions ? (_jsx("div", { className: "flex shrink-0 items-center gap-1", children: actions })) : null] })) : null, children, footer ? (_jsx("footer", { className: "border-t border-border-subtle pt-3 text-content-secondary text-sm", children: footer })) : null] }));
67
+ "@container flex flex-col gap-3 rounded-lg border border-border-subtle text-content-primary", SURFACE_CLASS[surface], ELEVATION_CLASS[elevation], PADDING_CLASS[padding], accentEdge == null
68
+ ? undefined
69
+ : getAccentEdgeClassName(accentEdge), className), style: mergedStyle, children: [heading || actions ? (_jsxs("header", { className: "flex flex-col gap-2 cq-sm:flex-row cq-sm:items-start cq-sm:justify-between", children: [heading ? (_jsx(Heading, { className: "font-semibold text-md leading-tight", id: headingId, children: heading })) : null, actions ? (_jsx("div", { className: "flex shrink-0 items-center gap-1", children: actions })) : null] })) : null, children, footer ? (_jsx("footer", { className: "border-t border-border-subtle pt-3 text-content-secondary text-sm", children: footer })) : null] }));
58
70
  };
59
71
  //# sourceMappingURL=Card.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Card.js","sourceRoot":"","sources":["../../src/Card/Card.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAUhD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAoB/C,MAAM,aAAa,GAAgC;IACjD,IAAI,EAAE,iBAAiB;IACvB,MAAM,EAAE,mBAAmB;IAC3B,MAAM,EAAE,mBAAmB;CAC5B,CAAA;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAGxB;IACF,IAAI,EAAE,aAAa;IACnB,GAAG,EAAE,YAAY;IACjB,MAAM,EAAE,eAAe;IACvB,IAAI,EAAE,aAAa;CACpB,CAAA;AAED,MAAM,aAAa,GAAgC;IACjD,IAAI,EAAE,KAAK;IACX,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;CACV,CAAA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,EACnB,OAAO,EACP,QAAQ,EACR,SAAS,EACT,SAAS,GAAG,KAAK,EACjB,MAAM,EACN,OAAO,EACP,YAAY,GAAG,CAAC,EAChB,OAAO,GAAG,IAAI,EACd,OAAO,GAAG,QAAQ,EAClB,GAAG,YAAY,EACL,EAAa,EAAE;IACzB,MAAM,SAAS,GAAG,WAAW,EAAE,CAAA;IAE/B,MAAM,OAAO,GAAG,IAAI,YAAY,EAAW,CAAA;IAE3C,OAAO,CACL,sBACM,YAAY,qBACC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAChD,SAAS,EAAE,WAAW;QACpB,wDAAwD;QACxD,2DAA2D;QAC3D,2DAA2D;QAC3D,wDAAwD;QACxD,0DAA0D;QAC1D,4FAA4F,EAC5F,aAAa,CAAC,OAAO,CAAC,EACtB,eAAe,CAAC,SAAS,CAAC,EAC1B,aAAa,CAAC,OAAO,CAAC,EACtB,SAAS,CACV,aAEA,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,CACpB,kBAAQ,SAAS,EAAC,4EAA4E,aAC3F,OAAO,CAAC,CAAC,CAAC,CACT,KAAC,OAAO,IACN,SAAS,EAAC,qCAAqC,EAC/C,EAAE,EAAE,SAAS,YAEZ,OAAO,GACA,CACX,CAAC,CAAC,CAAC,IAAI,EAEP,OAAO,CAAC,CAAC,CAAC,CACT,cAAK,SAAS,EAAC,kCAAkC,YAC9C,OAAO,GACJ,CACP,CAAC,CAAC,CAAC,IAAI,IACD,CACV,CAAC,CAAC,CAAC,IAAI,EAEP,QAAQ,EAER,MAAM,CAAC,CAAC,CAAC,CACR,iBAAQ,SAAS,EAAC,mEAAmE,YAClF,MAAM,GACA,CACV,CAAC,CAAC,CAAC,IAAI,IACA,CACX,CAAA;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"Card.js","sourceRoot":"","sources":["../../src/Card/Card.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAUhD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C,OAAO,EACL,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,qBAAqB,CAAA;AA8B5B,MAAM,aAAa,GAAgC;IACjD,IAAI,EAAE,iBAAiB;IACvB,MAAM,EAAE,mBAAmB;IAC3B,MAAM,EAAE,mBAAmB;CAC5B,CAAA;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAGxB;IACF,IAAI,EAAE,aAAa;IACnB,GAAG,EAAE,YAAY;IACjB,MAAM,EAAE,eAAe;IACvB,IAAI,EAAE,aAAa;CACpB,CAAA;AAED,MAAM,aAAa,GAAgC;IACjD,IAAI,EAAE,KAAK;IACX,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;CACV,CAAA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,EACnB,UAAU,EACV,OAAO,EACP,QAAQ,EACR,SAAS,EACT,SAAS,GAAG,KAAK,EACjB,MAAM,EACN,OAAO,EACP,YAAY,GAAG,CAAC,EAChB,OAAO,GAAG,IAAI,EACd,KAAK,EACL,OAAO,GAAG,QAAQ,EAClB,GAAG,YAAY,EACL,EAAa,EAAE;IACzB,MAAM,SAAS,GAAG,WAAW,EAAE,CAAA;IAE/B,gEAAgE;IAChE,kEAAkE;IAClE,4DAA4D;IAC5D,MAAM,eAAe,GACnB,UAAU,IAAI,IAAI;QAChB,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAA;IAEpC,MAAM,WAAW,GACf,eAAe,IAAI,IAAI;QACrB,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,EAAE,GAAG,eAAe,EAAE,GAAG,KAAK,EAAE,CAAA;IAEtC,MAAM,OAAO,GAAG,IAAI,YAAY,EAAW,CAAA;IAE3C,OAAO,CACL,sBACM,YAAY,qBACC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAChD,SAAS,EAAE,WAAW;QACpB,wDAAwD;QACxD,2DAA2D;QAC3D,2DAA2D;QAC3D,wDAAwD;QACxD,0DAA0D;QAC1D,4FAA4F,EAC5F,aAAa,CAAC,OAAO,CAAC,EACtB,eAAe,CAAC,SAAS,CAAC,EAC1B,aAAa,CAAC,OAAO,CAAC,EACtB,UAAU,IAAI,IAAI;YAChB,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,sBAAsB,CAAC,UAAU,CAAC,EACtC,SAAS,CACV,EACD,KAAK,EAAE,WAAW,aAEjB,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,CACpB,kBAAQ,SAAS,EAAC,4EAA4E,aAC3F,OAAO,CAAC,CAAC,CAAC,CACT,KAAC,OAAO,IACN,SAAS,EAAC,qCAAqC,EAC/C,EAAE,EAAE,SAAS,YAEZ,OAAO,GACA,CACX,CAAC,CAAC,CAAC,IAAI,EAEP,OAAO,CAAC,CAAC,CAAC,CACT,cAAK,SAAS,EAAC,kCAAkC,YAC9C,OAAO,GACJ,CACP,CAAC,CAAC,CAAC,IAAI,IACD,CACV,CAAC,CAAC,CAAC,IAAI,EAEP,QAAQ,EAER,MAAM,CAAC,CAAC,CAAC,CACR,iBAAQ,SAAS,EAAC,mEAAmE,YAClF,MAAM,GACA,CACV,CAAC,CAAC,CAAC,IAAI,IACA,CACX,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * A coloured bar down a card's leading edge — and the reason it is
3
+ * a pseudo-element rather than a border.
4
+ *
5
+ * Three apps had grown this shape independently and all three drew
6
+ * it as a straight border beside a rounded box, so the bar ran past
7
+ * the curve and stopped square while the card it belonged to was
8
+ * round. Folio put `border-inline-start` on a WRAPPER around the
9
+ * card; mail-sifter puts `borderInlineStartWidth: 4` on a
10
+ * `rounded-xl` anchor; spoolbuddy writes `rounded-lg border-l-3`.
11
+ * A border is painted on the border box, so none of them can follow
12
+ * a corner — the notch is not a bug in any one app, it is what that
13
+ * technique does.
14
+ *
15
+ * A pseudo-element overlays the card and takes
16
+ * `border-radius: inherit`, so the bar follows whatever radius the
17
+ * card already has and nobody states a radius twice.
18
+ *
19
+ * ⚠️ **The shadow goes on the pseudo-element, never on the card.**
20
+ * `Card` carries `shadow-low` from the elevation scale, and an app
21
+ * stylesheet is unlayered while Tailwind's utilities sit in
22
+ * `@layer utilities` — so a `box-shadow` written at the call site
23
+ * outranks the utility and takes the card's elevation away with it.
24
+ * That is not hypothetical; it is the version of this fix that was
25
+ * written first.
26
+ *
27
+ * ⚠️ **Not a 3px-wide pseudo-element with a rounded leading edge.**
28
+ * A `border-radius` of `0.5rem` on a 3px-wide box is clamped by the
29
+ * browser to fit, so it caps the bar instead of following the
30
+ * card's curve.
31
+ *
32
+ * ⚠️ **`box-shadow` has no logical form**, so the offset is
33
+ * physical and the `rtl:` variant is what carries the bar to the
34
+ * other side. A single `inset 3px` would put it on the left of an
35
+ * Arabic page, which is the trailing edge there.
36
+ */
37
+ import type { CategoricalIndex } from "@charcuterie/tokens";
38
+ /**
39
+ * Which colour the bar takes.
40
+ *
41
+ * Two arms, because the fleet has two answers and neither is wrong.
42
+ * Docket picks a **categorical index** — a colour a user chose for
43
+ * a project, from the ten-wide contrast-audited family. Folio and
44
+ * mail-sifter derive a **hue from a name hash**, so a repo added
45
+ * tomorrow already has a colour and nobody maintains a palette;
46
+ * that is 360 answers, not ten, and it cannot be an index.
47
+ *
48
+ * `intent` is deliberately absent. An intent is a claim the design
49
+ * system makes — `danger` says what happens if you press the thing
50
+ * — and no card in this fleet wants its edge to make one. When one
51
+ * does, that is a third arm and a decision record, not a guess
52
+ * made here.
53
+ */
54
+ export type CardAccentEdge = {
55
+ categorical: CategoricalIndex;
56
+ color?: never;
57
+ } | {
58
+ categorical?: never;
59
+ color: string;
60
+ };
61
+ /**
62
+ * The custom property an explicit colour is threaded through.
63
+ *
64
+ * A property rather than an inline `box-shadow`, because the shadow
65
+ * string has to stay a COMPLETE literal in the source for Tailwind
66
+ * to generate it at all. One literal reading one variable covers
67
+ * every colour an app can compute; interpolating the colour into
68
+ * the class name would generate nothing, paint nothing, and report
69
+ * nothing.
70
+ */
71
+ export declare const ACCENT_EDGE_COLOR_PROPERTY = "--charcuterie-accent-edge";
72
+ /**
73
+ * The overlay itself. Colour arrives separately.
74
+ *
75
+ * `pointer-events: none` is load-bearing rather than tidy: this box
76
+ * covers the whole card, including every link and button in it.
77
+ */
78
+ export declare const ACCENT_EDGE_BASE_CLASS = "relative before:pointer-events-none before:absolute before:inset-0 before:rounded-[inherit] before:content-['']";
79
+ /**
80
+ * Ten indices, written out in full, for the reason
81
+ * `categoricalStyles.ts` opens with: Tailwind scans source text for
82
+ * COMPLETE class strings, so a template literal generates nothing
83
+ * at all and the bar renders invisible with no error and no failing
84
+ * test. `tailwindCandidates.test.ts` compiles every literal in this
85
+ * package through the real Tailwind, which is the something that
86
+ * checks.
87
+ */
88
+ export declare const ACCENT_EDGE_CATEGORICAL_CLASS: Record<CategoricalIndex, string>;
89
+ /** The same bar, reading the caller's own colour. */
90
+ export declare const ACCENT_EDGE_COLOR_CLASS = "before:shadow-[inset_3px_0_0_var(--charcuterie-accent-edge)] rtl:before:shadow-[inset_-3px_0_0_var(--charcuterie-accent-edge)]";
91
+ export declare const getAccentEdgeClassName: (accentEdge: CardAccentEdge) => string;
92
+ /**
93
+ * The inline style an explicit colour needs, and nothing when the
94
+ * colour came from an index.
95
+ *
96
+ * Returns `undefined` rather than `{}` so a card with a categorical
97
+ * edge and no `style` of its own renders no `style` attribute at
98
+ * all.
99
+ */
100
+ export declare const getAccentEdgeStyle: (accentEdge: CardAccentEdge) => Record<string, string> | undefined;
101
+ //# sourceMappingURL=cardAccentEdge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cardAccentEdge.d.ts","sourceRoot":"","sources":["../../src/Card/cardAccentEdge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAE3D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,WAAW,EAAE,gBAAgB,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GAChD;IAAE,WAAW,CAAC,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAE1C;;;;;;;;;GASG;AACH,eAAO,MAAM,0BAA0B,8BACV,CAAA;AAE7B;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,oHACgF,CAAA;AAEnH;;;;;;;;GAQG;AACH,eAAO,MAAM,6BAA6B,EAAE,MAAM,CAChD,gBAAgB,EAChB,MAAM,CAYP,CAAA;AAED,qDAAqD;AACrD,eAAO,MAAM,uBAAuB,mIAC8F,CAAA;AAElI,eAAO,MAAM,sBAAsB,GACjC,YAAY,cAAc,KACzB,MAOC,CAAA;AAEJ;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAC7B,YAAY,cAAc,KACzB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAG4B,CAAA"}