@charcuterie/ui 3.20.0 → 3.21.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.
@@ -1,23 +1,8 @@
1
1
  import type { IntentName } from "@charcuterie/tokens";
2
2
  import type { ReactNode } from "react";
3
+ import type { CardAccentEdge } from "../Card/cardAccentEdge.ts";
3
4
  type BoardItemFields = {
4
- /**
5
- * The leading colour bar. Priority, in the consumer this was built
6
- * for — *"Priority is styled as a leading colour bar, and **never**
7
- * a coloured word before the title."*
8
- *
9
- * Pair it with `accentLabel`. A bar is colour and nothing else, so
10
- * on its own it fails WCAG 1.4.1 outright and is invisible to
11
- * every screen reader; the label rides along in a
12
- * `VisuallyHidden` so the same fact reaches both channels. Docket's
13
- * first board painted these `--color-danger-9` — a Radix-style
14
- * scale this token set has never had — so every bar was
15
- * transparent while every "is it rendered" assertion passed. The
16
- * families here are `--color-intent-<intent>-<role>`; there is no
17
- * numeric scale to reach for.
18
- */
19
- accentIntent?: IntentName;
20
- /** What the bar means, in words. "Priority 0". */
5
+ /** What the bar means, in words. "Priority 0", "Movies and Shows". */
21
6
  accentLabel?: string;
22
7
  /**
23
8
  * Under the title and metadata, spanning the card: a live run
@@ -55,6 +40,45 @@ type BoardItemFields = {
55
40
  /** The trailing cluster: an assignee avatar, an elapsed-time chip. */
56
41
  trailing?: ReactNode;
57
42
  };
43
+ /**
44
+ * The leading colour bar, in one of two shapes — and the second one
45
+ * exists because the first cannot follow a corner.
46
+ *
47
+ * `accentIntent` is an inset pill: a `w-1` span inside the card,
48
+ * beside the content. It was built for priority — *"Priority is
49
+ * styled as a leading colour bar, and **never** a coloured word
50
+ * before the title."* An intent is a claim the design system makes
51
+ * (`danger` says what happens if you press the thing), so this arm
52
+ * is for a bar that means a STATE.
53
+ *
54
+ * `accentEdge` is the card's own edge — `Card`'s treatment, the same
55
+ * pseudo-element, so it takes `border-radius: inherit` and wraps the
56
+ * corner at `cq-lg` where this card grows one. It carries a
57
+ * CATEGORICAL colour or a computed one, which is what an identity
58
+ * needs: a project, a repo, a source. The owner asked for exactly
59
+ * this on Docket's board — *"The Board has no colors. That could
60
+ * use project colors on the sides at least."* — after reporting the
61
+ * same treatment missing from the task rows.
62
+ *
63
+ * The two are mutually exclusive by type, not by convention. Both
64
+ * paint a leading bar in the same place, and a card wearing two of
65
+ * them is a card whose leading edge means two things.
66
+ *
67
+ * ⚠️ Either arm pairs with `accentLabel`. A bar is colour and
68
+ * nothing else, so on its own it fails WCAG 1.4.1 outright and is
69
+ * invisible to every screen reader; the label rides along in a
70
+ * `VisuallyHidden` so the same fact reaches both channels. Docket's
71
+ * first board painted its bars `--color-danger-9` — a Radix-style
72
+ * scale this token set has never had — so every bar was transparent
73
+ * while every "is it rendered" assertion passed.
74
+ */
75
+ type BoardItemAccent = {
76
+ accentEdge?: never;
77
+ accentIntent?: IntentName;
78
+ } | {
79
+ accentEdge: CardAccentEdge;
80
+ accentIntent?: never;
81
+ };
58
82
  /**
59
83
  * Who owns the card title's link — the board, or the caller.
60
84
  *
@@ -98,7 +122,7 @@ type BoardItemTitleLink = {
98
122
  */
99
123
  titleContent: ReactNode;
100
124
  };
101
- export type BoardItem = BoardItemFields & BoardItemTitleLink;
125
+ export type BoardItem = BoardItemAccent & BoardItemFields & BoardItemTitleLink;
102
126
  export type BoardCardProps = {
103
127
  item: BoardItem;
104
128
  laneLabel: string;
@@ -1 +1 @@
1
- {"version":3,"file":"BoardCard.d.ts","sourceRoot":"","sources":["../../src/Board/BoardCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAatC,KAAK,eAAe,GAAG;IACrB;;;;;;;;;;;;;;OAcG;IACH,YAAY,CAAC,EAAE,UAAU,CAAA;IACzB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,GAAG,EAAE,MAAM,CAAA;IACX;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;;;OAOG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,sEAAsE;IACtE,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,KAAK,kBAAkB,GACnB;IACE,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,YAAY,CAAC,EAAE,KAAK,CAAA;CACrB,GACD;IACE,IAAI,CAAC,EAAE,KAAK,CAAA;IACZ,QAAQ,CAAC,EAAE,KAAK,CAAA;IAChB;;;;;;;;OAQG;IACH,YAAY,EAAE,SAAS,CAAA;CACxB,CAAA;AAEL,MAAM,MAAM,SAAS,GAAG,eAAe,GAAG,kBAAkB,CAAA;AAE5D,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,SAAS,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,0DAA0D;IAC1D,WAAW,EAAE,SAAS;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACtD,8DAA8D;IAC9D,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,WAAW,CAAC,EAAE,CAAC,YAAY,EAAE;QAC3B,OAAO,EAAE,MAAM,CAAA;QACf,OAAO,EAAE,MAAM,CAAA;QACf,aAAa,EAAE,WAAW,GAAG,OAAO,CAAA;QACpC,SAAS,EAAE,MAAM,CAAA;KAClB,KAAK,IAAI,CAAA;CACX,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,eAAO,MAAM,SAAS,GAAI,wEAOvB,cAAc,KAAG,SA4JnB,CAAA"}
1
+ {"version":3,"file":"BoardCard.d.ts","sourceRoot":"","sources":["../../src/Board/BoardCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAc/D,KAAK,eAAe,GAAG;IACrB,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,GAAG,EAAE,MAAM,CAAA;IACX;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;;;OAOG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,sEAAsE;IACtE,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,KAAK,eAAe,GAChB;IAAE,UAAU,CAAC,EAAE,KAAK,CAAC;IAAC,YAAY,CAAC,EAAE,UAAU,CAAA;CAAE,GACjD;IAAE,UAAU,EAAE,cAAc,CAAC;IAAC,YAAY,CAAC,EAAE,KAAK,CAAA;CAAE,CAAA;AAExD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,KAAK,kBAAkB,GACnB;IACE,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,YAAY,CAAC,EAAE,KAAK,CAAA;CACrB,GACD;IACE,IAAI,CAAC,EAAE,KAAK,CAAA;IACZ,QAAQ,CAAC,EAAE,KAAK,CAAA;IAChB;;;;;;;;OAQG;IACH,YAAY,EAAE,SAAS,CAAA;CACxB,CAAA;AAEL,MAAM,MAAM,SAAS,GAAG,eAAe,GACrC,eAAe,GACf,kBAAkB,CAAA;AAEpB,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,SAAS,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,0DAA0D;IAC1D,WAAW,EAAE,SAAS;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACtD,8DAA8D;IAC9D,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,WAAW,CAAC,EAAE,CAAC,YAAY,EAAE;QAC3B,OAAO,EAAE,MAAM,CAAA;QACf,OAAO,EAAE,MAAM,CAAA;QACf,aAAa,EAAE,WAAW,GAAG,OAAO,CAAA;QACpC,SAAS,EAAE,MAAM,CAAA;KAClB,KAAK,IAAI,CAAA;CACX,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,eAAO,MAAM,SAAS,GAAI,wEAOvB,cAAc,KAAG,SAsLnB,CAAA"}
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
3
  import { Button } from "../Button/Button.js";
4
+ import { getAccentEdgeClassName, getAccentEdgeStyle, } from "../Card/cardAccentEdge.js";
4
5
  import { FOCUS_RING_CLASS, INTENT_SOLID_FILL_CLASS, } from "../intentStyles.js";
5
6
  import { Menu } from "../Menu/Menu.js";
6
7
  import { TextLink } from "../TextLink/TextLink.js";
@@ -52,7 +53,22 @@ export const BoardCard = ({ item, laneLabel, moveIcon, moveTargets, onMoveToLane
52
53
  // divider goes and each card grows its own box. The *list*
53
54
  // cannot do this to itself — it is the container, and a
54
55
  // container query never matches its own container.
55
- "cq-lg:mb-2 cq-lg:rounded-lg cq-lg:border cq-lg:bg-surface-raised cq-lg:p-3 cq-lg:shadow-low", item.isDimmed && "opacity-60"), "data-board-card": item.key, children: [item.accentIntent ? (_jsx("span", { className: toClassName("w-1 shrink-0 self-stretch rounded-full", INTENT_SOLID_FILL_CLASS[item.accentIntent]), children: item.accentLabel ? (_jsx(VisuallyHidden, { children: item.accentLabel })) : null })) : null, _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [_jsxs("div", { className: "flex min-w-0 items-start gap-2", children: [_jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1 cq-sm:flex-row cq-sm:items-center cq-sm:gap-2", children: [_jsxs("div", { className: "flex min-w-0 flex-1 items-start gap-1.5 text-sm leading-snug", children: [item.marks ? (_jsx("span", { className: "flex shrink-0 items-center gap-1 pt-1", children: item.marks })) : null, item.href ? (_jsx(TextLink, { appearance: "standalone", className: "min-w-0 max-w-full font-normal", href: item.href, intent: "neutral", children: titleBlock })) : item.onSelect ? (_jsx("button", { className: toClassName("min-w-0 max-w-full cursor-pointer rounded-xs text-start text-intent-neutral-content hover:underline", FOCUS_RING_CLASS), onClick: item.onSelect, type: "button", children: titleBlock })) : (titleBlock)] }), item.meta ? (_jsx("div", { className: "flex min-w-0 flex-wrap items-center gap-1 cq-sm:shrink-0 cq-sm:flex-nowrap cq-sm:justify-end", children: item.meta })) : null] }), item.trailing || onMoveToLane ? (_jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [item.trailing, onMoveToLane && moveTargets.length > 0 ? (_jsx(Menu, { isVisible: isMenuVisible, items: moveTargets.map((target) => ({
56
+ "cq-lg:mb-2 cq-lg:rounded-lg cq-lg:border cq-lg:bg-surface-raised cq-lg:p-3 cq-lg:shadow-low",
57
+ // The edge is drawn on THIS box, which is why it needs no
58
+ // shape of its own: the box is a row below `cq-lg` and a
59
+ // rounded card above it, and `border-radius: inherit` gives
60
+ // the right answer at both — a straight stripe down a row
61
+ // that has no corner, and a bar that wraps the card's
62
+ // corner once it grows one.
63
+ //
64
+ // `ps-2` is what keeps the content off it. The bar is 3px
65
+ // and overlays rather than displaces, so without this the
66
+ // first mark sits on top of it; at `cq-lg` the card's own
67
+ // `p-3` is already wider than the bar.
68
+ item.accentEdge != null &&
69
+ toClassName("ps-2", getAccentEdgeClassName(item.accentEdge)), item.isDimmed && "opacity-60"), "data-board-card": item.key, ...(item.accentEdge == null
70
+ ? {}
71
+ : { style: getAccentEdgeStyle(item.accentEdge) }), children: [item.accentEdge != null && item.accentLabel ? (_jsx(VisuallyHidden, { children: item.accentLabel })) : null, item.accentIntent ? (_jsx("span", { className: toClassName("w-1 shrink-0 self-stretch rounded-full", INTENT_SOLID_FILL_CLASS[item.accentIntent]), children: item.accentLabel ? (_jsx(VisuallyHidden, { children: item.accentLabel })) : null })) : null, _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [_jsxs("div", { className: "flex min-w-0 items-start gap-2", children: [_jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1 cq-sm:flex-row cq-sm:items-center cq-sm:gap-2", children: [_jsxs("div", { className: "flex min-w-0 flex-1 items-start gap-1.5 text-sm leading-snug", children: [item.marks ? (_jsx("span", { className: "flex shrink-0 items-center gap-1 pt-1", children: item.marks })) : null, item.href ? (_jsx(TextLink, { appearance: "standalone", className: "min-w-0 max-w-full font-normal", href: item.href, intent: "neutral", children: titleBlock })) : item.onSelect ? (_jsx("button", { className: toClassName("min-w-0 max-w-full cursor-pointer rounded-xs text-start text-intent-neutral-content hover:underline", FOCUS_RING_CLASS), onClick: item.onSelect, type: "button", children: titleBlock })) : (titleBlock)] }), item.meta ? (_jsx("div", { className: "flex min-w-0 flex-wrap items-center gap-1 cq-sm:shrink-0 cq-sm:flex-nowrap cq-sm:justify-end", children: item.meta })) : null] }), item.trailing || onMoveToLane ? (_jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [item.trailing, onMoveToLane && moveTargets.length > 0 ? (_jsx(Menu, { isVisible: isMenuVisible, items: moveTargets.map((target) => ({
56
72
  key: target.key,
57
73
  label: target.label,
58
74
  onSelect: () => {
@@ -1 +1 @@
1
- {"version":3,"file":"BoardCard.js","sourceRoot":"","sources":["../../src/Board/BoardCard.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEhC,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAsB,CAAA;AAC7C,OAAO,EACL,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAkB,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAA0B,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAsC,CAAA;AAmIrE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,EACxB,IAAI,EACJ,SAAS,EACT,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,WAAW,GACI,EAAa,EAAE;IAC9B,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAEzD,MAAM,UAAU,GAAG,CAOjB,eAAM,SAAS,EAAC,+DAA+D,YAC5E,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,KAAK,GAC3B,CACR,CAAA;IAED,OAAO,CACL,cACE,SAAS,EAAE,WAAW,CACpB,mFAAmF;QACnF,sDAAsD;QACtD,2DAA2D;QAC3D,wDAAwD;QACxD,mDAAmD;QACnD,6FAA6F,EAC7F,IAAI,CAAC,QAAQ,IAAI,YAAY,CAC9B,qBACgB,IAAI,CAAC,GAAG,aAExB,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CACnB,eACE,SAAS,EAAE,WAAW,CACpB,wCAAwC,EACxC,uBAAuB,CAAC,IAAI,CAAC,YAAY,CAAC,CAC3C,YAEA,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAClB,KAAC,cAAc,cACZ,IAAI,CAAC,WAAW,GACF,CAClB,CAAC,CAAC,CAAC,IAAI,GACH,CACR,CAAC,CAAC,CAAC,IAAI,EAER,eAAK,SAAS,EAAC,oCAAoC,aACjD,eAAK,SAAS,EAAC,gCAAgC,aAC7C,eAAK,SAAS,EAAC,kFAAkF,aAC/F,eAAK,SAAS,EAAC,8DAA8D,aAC1E,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CACZ,eAAM,SAAS,EAAC,uCAAuC,YACpD,IAAI,CAAC,KAAK,GACN,CACR,CAAC,CAAC,CAAC,IAAI,EAEP,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CACX,KAAC,QAAQ,IACP,UAAU,EAAC,YAAY,EACvB,SAAS,EAAC,gCAAgC,EAC1C,IAAI,EAAE,IAAI,CAAC,IAAI,EACf,MAAM,EAAC,SAAS,YAEf,UAAU,GACF,CACZ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAClB,iBACE,SAAS,EAAE,WAAW,CACpB,qGAAqG,EACrG,gBAAgB,CACjB,EACD,OAAO,EAAE,IAAI,CAAC,QAAQ,EACtB,IAAI,EAAC,QAAQ,YAEZ,UAAU,GACJ,CACV,CAAC,CAAC,CAAC,CACF,UAAU,CACX,IACG,EAEL,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CACX,cAAK,SAAS,EAAC,8FAA8F,YAC1G,IAAI,CAAC,IAAI,GACN,CACP,CAAC,CAAC,CAAC,IAAI,IACJ,EAEL,IAAI,CAAC,QAAQ,IAAI,YAAY,CAAC,CAAC,CAAC,CAC/B,eAAK,SAAS,EAAC,kCAAkC,aAC9C,IAAI,CAAC,QAAQ,EAEb,YAAY,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CACxC,KAAC,IAAI,IACH,SAAS,EAAE,aAAa,EACxB,KAAK,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;4CAClC,GAAG,EAAE,MAAM,CAAC,GAAG;4CACf,KAAK,EAAE,MAAM,CAAC,KAAK;4CACnB,QAAQ,EAAE,GAAG,EAAE;gDACb,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;4CAC1B,CAAC;yCACF,CAAC,CAAC,EACH,SAAS,EAAE,GAAG,EAAE;4CACd,gBAAgB,CAAC,KAAK,CAAC,CAAA;wCACzB,CAAC,EACD,SAAS,EAAC,YAAY,EACtB,OAAO,EACL,MAAC,MAAM,IACL,UAAU,EAAC,OAAO,EAClB,MAAM,EAAC,SAAS;4CAChB,yCAAyC;4CACzC,6CAA6C;4CAC7C,+CAA+C;4CAC/C,sCAAsC;4CACtC,iBAAiB;4CACjB,SAAS,EAAC,+CAA+C,EACzD,OAAO,EAAE,GAAG,EAAE;gDACZ,gBAAgB,CACd,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,SAAS,CAC1B,CAAA;4CACH,CAAC,EACD,aAAa,EAAE,CAAC,YAAY,EAAE,EAAE;gDAC9B,WAAW,EAAE,CAAC,YAAY,CAAC,CAAA;4CAC7B,CAAC,EACD,IAAI,EAAC,IAAI,EACT,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,aAEpC,QAAQ,IAAI,MAAM,EAWnB,KAAC,cAAc,cACZ,QAAQ;wDACP,CAAC,CAAC,QAAQ,IAAI,CAAC,KAAK,kBAAkB,SAAS,EAAE;wDACjD,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,kBAAkB,SAAS,EAAE,GAChC,IACV,GAEX,CACH,CAAC,CAAC,CAAC,IAAI,IACJ,CACP,CAAC,CAAC,CAAC,IAAI,IACJ,EAEL,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CACb,cAAK,SAAS,EAAC,wCAAwC,YACpD,IAAI,CAAC,MAAM,GACR,CACP,CAAC,CAAC,CAAC,IAAI,IACJ,IACH,CACN,CAAA;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"BoardCard.js","sourceRoot":"","sources":["../../src/Board/BoardCard.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@charcuterie/ui",
3
- "version": "3.20.0",
3
+ "version": "3.21.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",
@@ -188,6 +188,45 @@ const ADA: [string, string] = ["AD", "Ada"]
188
188
 
189
189
  const WREN: [string, string] = ["WR", "Wren"]
190
190
 
191
+ /**
192
+ * A categorical index per project, and the same four projects the
193
+ * rest of this file uses. The index is what a consumer's user
194
+ * PICKED — it is not derived here, and it is never a hex.
195
+ */
196
+ const PROJECT_EDGE_ITEMS: BoardItem[] = (
197
+ [
198
+ [
199
+ PROJECTS.signal,
200
+ 2,
201
+ "Retire the second scheduler and fold its jobs into the broker",
202
+ ],
203
+ [
204
+ PROJECTS.loom,
205
+ 5,
206
+ "One composed Storybook for every app in the fleet",
207
+ ],
208
+ [
209
+ PROJECTS.atlas,
210
+ 8,
211
+ "Fingerprint duplicate uploads before they reach the queue",
212
+ ],
213
+ [
214
+ PROJECTS.ferry,
215
+ 10,
216
+ "Rewrite the ingest runbook against the new broker",
217
+ ],
218
+ ] as const
219
+ ).map(([project, categorical, title]) => ({
220
+ accentEdge: { categorical },
221
+ // The words the bar cannot say. A colour alone is a WCAG
222
+ // 1.4.1 failure and is silent to a screen reader.
223
+ accentLabel: project,
224
+ href: `#/task/${encodeURIComponent(title)}`,
225
+ key: title,
226
+ meta: <ProjectChip name={project} />,
227
+ title,
228
+ }))
229
+
191
230
  const TODO_ITEMS: BoardItem[] = [
192
231
  toItem({
193
232
  assignee: WREN,
@@ -565,6 +604,67 @@ export const Responsive: Story = {
565
604
  ),
566
605
  }
567
606
 
607
+ /**
608
+ * **The project on the leading edge**, in both of the shapes a card
609
+ * takes — and the reason `accentEdge` exists beside `accentIntent`.
610
+ *
611
+ * The pill arm is a `w-1` span INSIDE the card, so it is a straight
612
+ * rectangle next to a rounded box. The edge arm is `Card`'s own
613
+ * treatment: a pseudo-element taking `border-radius: inherit`, so it
614
+ * paints a straight stripe down a row that has no corner and wraps
615
+ * the corner once the lane passes `cq-lg` and the row becomes a
616
+ * card. One prop, correct in both shapes, and never a notch.
617
+ *
618
+ * The colours are CATEGORICAL — an identity, not a state. A project,
619
+ * a repo, a source. Priority stays on the pill arm, where an intent
620
+ * belongs.
621
+ */
622
+ export const AccentEdge: Story = {
623
+ render: () => (
624
+ <div className="flex flex-col gap-8">
625
+ <div className="flex flex-col gap-2">
626
+ <span className="font-mono text-content-muted text-xs">
627
+ a narrow lane — rows, so the edge is a straight
628
+ stripe
629
+ </span>
630
+
631
+ <Frame inlineSize="22rem">
632
+ <Board
633
+ label="Today, by project"
634
+ lanes={[
635
+ {
636
+ items: PROJECT_EDGE_ITEMS,
637
+ key: "todo",
638
+ label: "Todo",
639
+ },
640
+ ]}
641
+ />
642
+ </Frame>
643
+ </div>
644
+
645
+ <div className="flex flex-col gap-2">
646
+ <span className="font-mono text-content-muted text-xs">
647
+ the same lane past --cq-lg (48rem) — cards, so the
648
+ edge wraps the corner
649
+ </span>
650
+
651
+ <Frame inlineSize="56rem">
652
+ <Board
653
+ label="Today, by project, wide"
654
+ lanes={[
655
+ {
656
+ items: PROJECT_EDGE_ITEMS,
657
+ key: "todo",
658
+ label: "Todo",
659
+ },
660
+ ]}
661
+ />
662
+ </Frame>
663
+ </div>
664
+ </div>
665
+ ),
666
+ }
667
+
568
668
  /**
569
669
  * The complete keyboard path, and it is the **primary** way to move
570
670
  * a card rather than an accessible alternative to the real one.
@@ -13,6 +13,7 @@ import { expectAgentDrivable } from "../testing/index.ts"
13
13
  import * as stories from "./Board.stories.tsx"
14
14
 
15
15
  const {
16
+ AccentEdge,
16
17
  AllStates,
17
18
  Default,
18
19
  InBoardScreen,
@@ -405,3 +406,64 @@ test("the board adds no link of its own around a rich title", async () => {
405
406
  null,
406
407
  )
407
408
  })
409
+
410
+ /**
411
+ * The edge is drawn on the card's OWN box, which is the whole
412
+ * difference between it and the `accentIntent` pill.
413
+ *
414
+ * A pill is a `w-1` span inside the card: a straight rectangle
415
+ * beside a rounded box, which is the notch three apps grew
416
+ * independently before `Card` took the shape over. The edge is a
417
+ * pseudo-element taking `border-radius: inherit`, so it is correct
418
+ * in both of a board card's shapes without being told which one it
419
+ * is in — a straight stripe down a row that has no corner, and a
420
+ * wrapped bar once the lane passes `cq-lg`.
421
+ *
422
+ * The class is what is asserted rather than a painted pixel: the
423
+ * bar is a `box-shadow` on a `::before`, and the radius it inherits
424
+ * comes from a container query the runner's own width does not
425
+ * decide. `cardAccentEdge.test.ts` owns what the class contains.
426
+ */
427
+ test("draws a project's colour on the card's own edge", async () => {
428
+ const { canvas } = await mountStory(AccentEdge)
429
+
430
+ const cards = canvas
431
+ .getAllByRole("listitem")
432
+ .filter(
433
+ (item: HTMLElement) => item.dataset.boardCard != null,
434
+ )
435
+
436
+ expect(cards.length).toBeGreaterThan(0)
437
+
438
+ for (const card of cards) {
439
+ // The overlay, and the radius it takes from the card.
440
+ expect(card.className).toContain("before:absolute")
441
+ expect(card.className).toContain(
442
+ "before:rounded-[inherit]",
443
+ )
444
+
445
+ // A categorical colour, never an intent — an edge is an
446
+ // identity and an intent is a claim about what happens.
447
+ expect(card.className).toMatch(
448
+ /before:shadow-\[inset_3px_0_0_var\(--color-categorical-\d+-solid\)\]/,
449
+ )
450
+
451
+ // And it never eats a press on the card it covers.
452
+ expect(card.className).toContain(
453
+ "before:pointer-events-none",
454
+ )
455
+ }
456
+
457
+ // The colour reaches a screen reader as words. Each card names
458
+ // its project through `accentLabel`, so the bar is not the only
459
+ // channel carrying the fact.
460
+ expect(
461
+ canvas.getAllByText("Ferry Docs").length,
462
+ ).toBeGreaterThan(1)
463
+
464
+ await expectNoAxeViolations(
465
+ canvas.getByRole("region", {
466
+ name: "Today, by project",
467
+ }),
468
+ )
469
+ })
@@ -3,6 +3,11 @@ import type { ReactNode } from "react"
3
3
  import { useState } from "react"
4
4
 
5
5
  import { Button } from "../Button/Button.tsx"
6
+ import type { CardAccentEdge } from "../Card/cardAccentEdge.ts"
7
+ import {
8
+ getAccentEdgeClassName,
9
+ getAccentEdgeStyle,
10
+ } from "../Card/cardAccentEdge.ts"
6
11
  import {
7
12
  FOCUS_RING_CLASS,
8
13
  INTENT_SOLID_FILL_CLASS,
@@ -13,23 +18,7 @@ import { toClassName } from "../toClassName.ts"
13
18
  import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden.tsx"
14
19
 
15
20
  type BoardItemFields = {
16
- /**
17
- * The leading colour bar. Priority, in the consumer this was built
18
- * for — *"Priority is styled as a leading colour bar, and **never**
19
- * a coloured word before the title."*
20
- *
21
- * Pair it with `accentLabel`. A bar is colour and nothing else, so
22
- * on its own it fails WCAG 1.4.1 outright and is invisible to
23
- * every screen reader; the label rides along in a
24
- * `VisuallyHidden` so the same fact reaches both channels. Docket's
25
- * first board painted these `--color-danger-9` — a Radix-style
26
- * scale this token set has never had — so every bar was
27
- * transparent while every "is it rendered" assertion passed. The
28
- * families here are `--color-intent-<intent>-<role>`; there is no
29
- * numeric scale to reach for.
30
- */
31
- accentIntent?: IntentName
32
- /** What the bar means, in words. "Priority 0". */
21
+ /** What the bar means, in words. "Priority 0", "Movies and Shows". */
33
22
  accentLabel?: string
34
23
  /**
35
24
  * Under the title and metadata, spanning the card: a live run
@@ -68,6 +57,42 @@ type BoardItemFields = {
68
57
  trailing?: ReactNode
69
58
  }
70
59
 
60
+ /**
61
+ * The leading colour bar, in one of two shapes — and the second one
62
+ * exists because the first cannot follow a corner.
63
+ *
64
+ * `accentIntent` is an inset pill: a `w-1` span inside the card,
65
+ * beside the content. It was built for priority — *"Priority is
66
+ * styled as a leading colour bar, and **never** a coloured word
67
+ * before the title."* An intent is a claim the design system makes
68
+ * (`danger` says what happens if you press the thing), so this arm
69
+ * is for a bar that means a STATE.
70
+ *
71
+ * `accentEdge` is the card's own edge — `Card`'s treatment, the same
72
+ * pseudo-element, so it takes `border-radius: inherit` and wraps the
73
+ * corner at `cq-lg` where this card grows one. It carries a
74
+ * CATEGORICAL colour or a computed one, which is what an identity
75
+ * needs: a project, a repo, a source. The owner asked for exactly
76
+ * this on Docket's board — *"The Board has no colors. That could
77
+ * use project colors on the sides at least."* — after reporting the
78
+ * same treatment missing from the task rows.
79
+ *
80
+ * The two are mutually exclusive by type, not by convention. Both
81
+ * paint a leading bar in the same place, and a card wearing two of
82
+ * them is a card whose leading edge means two things.
83
+ *
84
+ * ⚠️ Either arm pairs with `accentLabel`. A bar is colour and
85
+ * nothing else, so on its own it fails WCAG 1.4.1 outright and is
86
+ * invisible to every screen reader; the label rides along in a
87
+ * `VisuallyHidden` so the same fact reaches both channels. Docket's
88
+ * first board painted its bars `--color-danger-9` — a Radix-style
89
+ * scale this token set has never had — so every bar was transparent
90
+ * while every "is it rendered" assertion passed.
91
+ */
92
+ type BoardItemAccent =
93
+ | { accentEdge?: never; accentIntent?: IntentName }
94
+ | { accentEdge: CardAccentEdge; accentIntent?: never }
95
+
71
96
  /**
72
97
  * Who owns the card title's link — the board, or the caller.
73
98
  *
@@ -114,7 +139,9 @@ type BoardItemTitleLink =
114
139
  titleContent: ReactNode
115
140
  }
116
141
 
117
- export type BoardItem = BoardItemFields & BoardItemTitleLink
142
+ export type BoardItem = BoardItemAccent &
143
+ BoardItemFields &
144
+ BoardItemTitleLink
118
145
 
119
146
  export type BoardCardProps = {
120
147
  item: BoardItem
@@ -210,10 +237,36 @@ export const BoardCard = ({
210
237
  // cannot do this to itself — it is the container, and a
211
238
  // container query never matches its own container.
212
239
  "cq-lg:mb-2 cq-lg:rounded-lg cq-lg:border cq-lg:bg-surface-raised cq-lg:p-3 cq-lg:shadow-low",
240
+ // The edge is drawn on THIS box, which is why it needs no
241
+ // shape of its own: the box is a row below `cq-lg` and a
242
+ // rounded card above it, and `border-radius: inherit` gives
243
+ // the right answer at both — a straight stripe down a row
244
+ // that has no corner, and a bar that wraps the card's
245
+ // corner once it grows one.
246
+ //
247
+ // `ps-2` is what keeps the content off it. The bar is 3px
248
+ // and overlays rather than displaces, so without this the
249
+ // first mark sits on top of it; at `cq-lg` the card's own
250
+ // `p-3` is already wider than the bar.
251
+ item.accentEdge != null &&
252
+ toClassName(
253
+ "ps-2",
254
+ getAccentEdgeClassName(item.accentEdge),
255
+ ),
213
256
  item.isDimmed && "opacity-60",
214
257
  )}
215
258
  data-board-card={item.key}
259
+ {...(item.accentEdge == null
260
+ ? {}
261
+ : { style: getAccentEdgeStyle(item.accentEdge) })}
216
262
  >
263
+ {/* The label the bar cannot say. An edge has no content of
264
+ its own — it is a pseudo-element — so unlike the pill
265
+ arm below there is nowhere inside it to put the words. */}
266
+ {item.accentEdge != null && item.accentLabel ? (
267
+ <VisuallyHidden>{item.accentLabel}</VisuallyHidden>
268
+ ) : null}
269
+
217
270
  {item.accentIntent ? (
218
271
  <span
219
272
  className={toClassName(