@motir/design-system 0.1.3 → 0.3.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.
@@ -40,6 +40,16 @@ var pillVariants = cva(
40
40
  // carries a GLYPH, because a 14% tint is a subtle mark by construction
41
41
  // and finding #35 forbids resting a state on colour alone.
42
42
  implemented: "bg-[color-mix(in_srgb,var(--el-status-implemented)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent",
43
+ // A person's YES, and not yet shipped (MOTIR-5141). The SECOND
44
+ // per-status tone, on the same documented recipe `implemented` uses
45
+ // rather than a second one — `--el-status-approved` is the `done` green
46
+ // one ink step deeper, measured at ΔE2000 >= 10 against every other
47
+ // status hue in all ten palettes. Like `implemented` it carries a GLYPH
48
+ // (`CircleCheck`, in `StatusPill`), because a 14% tint is a subtle mark
49
+ // and finding #35 forbids resting a state on colour alone — and here it
50
+ // does more work than usual: at 14% the whole ramp compresses, and this
51
+ // chip's nearest neighbour is `done`'s.
52
+ approved: "bg-[color-mix(in_srgb,var(--el-status-approved)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent",
43
53
  done: "bg-(--el-tint-mint) text-(--el-text-strong) border-transparent"
44
54
  },
45
55
  severity: {
@@ -101,7 +111,18 @@ var pillVariants = cva(
101
111
  // severity. Dedicated `--el-archived-pill-*` tokens so a palette can tune
102
112
  // the inactive badge apart from the neutral chip; AA-safe (slate on muted
103
113
  // clears AA both themes).
104
- archived: "bg-(--el-archived-pill-bg) text-(--el-archived-pill-text) border-transparent"
114
+ archived: "bg-(--el-archived-pill-bg) text-(--el-archived-pill-text) border-transparent",
115
+ // A decision is WAITING on somebody (Story MOTIR-4778 · MOTIR-4792,
116
+ // design `design/work-items/approval-control.mock.html` — the approval
117
+ // frame's `pill-await`). Yellow, and the tint is chosen for DISTINCTNESS
118
+ // rather than for mood: the frame renders four chips a reader must tell
119
+ // apart at a glance — awaiting, recording (`severity="info"`, sky),
120
+ // approved (`severity="success"`, mint) and changes-requested
121
+ // (`severity="warning"`, peach) — and `--el-tint-yellow` was the only
122
+ // slot none of the other three had spent. `tone` rather than `severity`
123
+ // because waiting is not a severity; it sits beside `private` and
124
+ // `archived`, which are states rather than judgements.
125
+ awaiting: "bg-(--el-tint-yellow) text-(--el-text-strong) border-transparent"
105
126
  }
106
127
  },
107
128
  defaultVariants: {}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/utils/cn.ts","../../../src/components/ui/Pill.tsx"],"names":["Pill"],"mappings":";;;;;;;AAcO,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACsBA,IAAM,YAAA,GAAe,GAAA;AAAA,EACnB,EAAA;AAAA,IACE,gCAAA;AAAA,IACA,0BAAA;AAAA,IACA,6CAAA;AAAA,IACA,+BAAA;AAAA,IACA;AAAA,GACF;AAAA,EACA;AAAA,IACE,QAAA,EAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMR,MAAA,EAAQ;AAAA,QACN,OAAA,EAAS,oEAAA;AAAA,QACT,aAAA,EAAe,+DAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAcf,WAAA,EACE,uHAAA;AAAA,QACF,IAAA,EAAM;AAAA,OACR;AAAA,MACA,QAAA,EAAU;AAAA,QACR,IAAA,EAAM,+DAAA;AAAA,QACN,OAAA,EAAS,gEAAA;AAAA,QACT,OAAA,EAAS,iEAAA;AAAA,QACT,MAAA,EAAQ;AAAA,OACV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQA,QAAA,EAAU;AAAA,QACR,OAAA,EACE,qHAAA;AAAA,QACF,IAAA,EAAM,kHAAA;AAAA,QACN,MAAA,EACE,oHAAA;AAAA,QACF,GAAA,EAAK,iHAAA;AAAA,QACL,MAAA,EACE;AAAA,OACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAYA,UAAA,EAAY;AAAA,QACV,KAAA,EAAO,iEAAA;AAAA,QACP,MAAA,EAAQ,kEAAA;AAAA,QACR,MAAA,EAAQ,kEAAA;AAAA,QACR,MAAA,EAAQ;AAAA,OACV;AAAA;AAAA;AAAA;AAAA;AAAA,MAKA,OAAA,EAAS;AAAA,QACP,KAAA,EAAO,qEAAA;AAAA,QACP,KAAA,EAAO,qEAAA;AAAA,QACP,MAAA,EAAQ;AAAA,OACV;AAAA,MACA,IAAA,EAAM;AAAA,QACJ,OAAA,EAAS,wEAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMT,OAAA,EAAS,sEAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMT,QAAA,EAAU;AAAA;AACZ,KACF;AAAA,IACA,iBAAiB;AAAC;AAEtB,CAAA;AAKO,IAAM,OAAO,UAAA,CAAuC,SAASA,KAAAA,CAClE,EAAE,QAAQ,QAAA,EAAU,QAAA,EAAU,UAAA,EAAY,OAAA,EAAS,MAAM,SAAA,EAAW,QAAA,EAAU,GAAG,IAAA,IACjF,GAAA,EACA;AACA,EAAA,uBACE,GAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,SAAA,EAAW,EAAA;AAAA,QACT,YAAA,CAAa,EAAE,MAAA,EAAQ,QAAA,EAAU,UAAU,UAAA,EAAY,OAAA,EAAS,MAAM,CAAA;AAAA,QACtE;AAAA,OACF;AAAA,MACC,GAAG,IAAA;AAAA,MAEH;AAAA;AAAA,GACH;AAEJ,CAAC","file":"Pill.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merge Tailwind class names without conflicts.\n *\n * `clsx` handles conditional inclusion (booleans, undefineds, arrays);\n * `twMerge` resolves conflicting Tailwind classes (later wins, e.g.\n * `px-2 px-4` → `px-4`). Together they are the de facto standard for\n * composing Tailwind classes in React components.\n *\n * @example\n * cn('px-2 py-1', isActive && 'bg-primary', className)\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","import { forwardRef, type HTMLAttributes } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../../utils/cn';\n\n/**\n * Pill — compact status or severity label. Pure span, no interaction.\n *\n * Variant axes (use exactly one):\n * - `status`: planned | in-progress | implemented | done — Motir's Subtask\n * lifecycle states. The first three are per-CATEGORY tones; `implemented` is\n * the one PER-STATUS tone (MOTIR-3103), because it shares a category with\n * In Progress / Planning / In Review and must not share their chip.\n * - `severity`: info | success | warning | danger — semantic UI states.\n * - `priority`: highest | high | medium | low | lowest — work-item priority, a\n * 5-step diverging ramp (`--el-priority-*` hue tinted over the surface, plus a\n * redundant direction icon). The single source of truth is `PRIORITY_META`.\n * - `memberRole`: admin | member | viewer | custom — a project membership role\n * (Story 6.4), one hued tint per role. (Named `memberRole`, not `role`, so\n * it can't collide with the DOM `role` ARIA attribute on the span.)\n * - `orgRole`: owner | admin | member — an ORGANIZATION role (Story 6.10),\n * sitting above the workspace `memberRole`. owner → lavender (the highest,\n * brand-purple tier), admin → sky, member → mint — per the org-admin design.\n * - `tone`: neutral — a non-semantic chip (counts, metadata). Dark text on a\n * neutral surface. `private` — the epic-level-privacy \"Not public\" badge\n * (Story 6.14 · design epic-privacy panels 1/2/6b): lavender tint + charcoal\n * text, the same AA-safe recipe as `status=\"planned\"` / `memberRole=\"admin\"`.\n *\n * All tones use adaptive dark/light text (`--color-charcoal`) on a hued tint,\n * so every variant clears WCAG AA contrast in both light and dark modes\n * (the hue lives in the background, not the text — PRODECT_FINDINGS #35).\n *\n * Always uses `--radius-badge` for fully rounded ends regardless of display style.\n *\n * @example\n * <Pill status=\"in-progress\">In progress</Pill>\n * <Pill severity=\"danger\">Validation failed</Pill>\n * <Pill tone=\"neutral\">3 members</Pill>\n */\nconst pillVariants = cva(\n cn(\n 'inline-flex items-center gap-1',\n 'rounded-(--radius-badge)',\n 'px-(--spacing-chip-x) py-(--spacing-chip-y)',\n 'font-sans text-xs font-medium',\n 'border',\n ),\n {\n variants: {\n // All colored tones carry the semantic hue in the TINT BACKGROUND and use\n // adaptive dark/light TEXT (`--color-charcoal`: #37352f light, #e5e5e5\n // dark) — the same pattern `planned` always used. The previous saturated\n // hue-on-tint TEXT failed WCAG AA (≈2.5–3.8:1 light, 3.2–4.0:1 dark);\n // charcoal-on-tint clears ~10:1 in both modes (PRODECT_FINDINGS #35).\n status: {\n planned: 'bg-(--el-tint-lavender) text-(--el-text-strong) border-transparent',\n 'in-progress': 'bg-(--el-tint-sky) text-(--el-text-strong) border-transparent',\n // BUILT, waiting on checks (MOTIR-3103). The first PER-STATUS tone on\n // this axis — the other three are per-CATEGORY, and `implemented` sits in\n // the same `in_progress` category as In Progress, Planning and In Review,\n // so without its own tone four statuses render one chip.\n //\n // It uses the recipe theme.css's own `--el-status-*` header prescribes —\n // \"a status CHIP bg = color-mix(var(--el-status-X) 14%, var(--el-surface))\n // with --el-text-strong\" — which is the same dilution the `priority`\n // variant below already renders. The hue is not spent by any other\n // status in any palette (measured at ΔE2000 >= 10 across all ten by\n // tests/theme/statusHueSeparation.test.ts), and the chip additionally\n // carries a GLYPH, because a 14% tint is a subtle mark by construction\n // and finding #35 forbids resting a state on colour alone.\n implemented:\n 'bg-[color-mix(in_srgb,var(--el-status-implemented)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n done: 'bg-(--el-tint-mint) text-(--el-text-strong) border-transparent',\n },\n severity: {\n info: 'bg-(--el-tint-sky) text-(--el-text-strong) border-transparent',\n success: 'bg-(--el-tint-mint) text-(--el-text-strong) border-transparent',\n warning: 'bg-(--el-tint-peach) text-(--el-text-strong) border-transparent',\n danger: 'bg-(--el-tint-rose) text-(--el-text-strong) border-transparent',\n },\n // Priority chips (MOTIR-1273 · 1266.2) — a 5-step diverging ramp keyed off\n // the `--el-priority-*` hues, un-collapsing `medium` and `lowest` (both\n // were `tone=\"neutral\"` grey). Each hue is diluted to a 14% tint over\n // `--el-surface` so charcoal `--el-text-strong` clears AA in both themes\n // (the hue lives in the background — finding #35); the redundant direction\n // icon (PRIORITY_META) is the non-colour cue. `medium` (slate) and\n // `lowest` (stone) resolve to two distinct greys in every palette.\n priority: {\n highest:\n 'bg-[color-mix(in_srgb,var(--el-priority-highest)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n high: 'bg-[color-mix(in_srgb,var(--el-priority-high)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n medium:\n 'bg-[color-mix(in_srgb,var(--el-priority-medium)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n low: 'bg-[color-mix(in_srgb,var(--el-priority-low)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n lowest:\n 'bg-[color-mix(in_srgb,var(--el-priority-lowest)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n },\n // Project membership roles (Story 6.4 · design/projects access-members):\n // admin → lavender, member → sky, viewer → mint — the hue in the tint,\n // charcoal text (AA-safe, finding #35), same recipe as `status`. Routed\n // through the DEDICATED `--el-role-*` tokens (MOTIR-1274 · 1266.3) so a\n // palette can tune roles apart from the other --el-tint-* meanings; each\n // defaults to its prior tint (zero visual change).\n // `custom` (MOTIR-2257 · MOTIR-2485) is a project's OWN role, which has no\n // enum value — it takes the next free tint slot (peach) because this\n // surface had already spent lavender / sky / mint on the built-ins, so a\n // custom chip in any of those would read as one of them. The KIND is\n // carried in WORDS by the chip's accessible name; nothing rests on the hue.\n memberRole: {\n admin: 'bg-(--el-role-admin) text-(--el-text-strong) border-transparent',\n member: 'bg-(--el-role-member) text-(--el-text-strong) border-transparent',\n viewer: 'bg-(--el-role-viewer) text-(--el-text-strong) border-transparent',\n custom: 'bg-(--el-role-custom) text-(--el-text-strong) border-transparent',\n },\n // Organization roles (Story 6.10 · design/org-admin): owner → lavender\n // (the highest, brand-purple tier), admin → sky, member → mint — the hue\n // in the tint, charcoal text (AA-safe, finding #35), same recipe as above.\n // Dedicated `--el-org-role-*` tokens (MOTIR-1274 · 1266.3); zero change.\n orgRole: {\n owner: 'bg-(--el-org-role-owner) text-(--el-text-strong) border-transparent',\n admin: 'bg-(--el-org-role-admin) text-(--el-text-strong) border-transparent',\n member: 'bg-(--el-org-role-member) text-(--el-text-strong) border-transparent',\n },\n tone: {\n neutral: 'bg-(--el-chip-bg) text-(--el-text-secondary) border-(--el-chip-border)',\n // Epic-level privacy \"Not public\" badge (Story 6.14 · design\n // epic-privacy panels 1/2/6b) — lavender tint, charcoal text, the same\n // AA-safe recipe as `status=\"planned\"` / `memberRole=\"admin\"` (≈9.7:1).\n // Dedicated `--el-privacy-private` token (MOTIR-1274 · 1266.3); the\n // matching `--el-privacy-public` is defined ahead of a \"public\" badge.\n private: 'bg-(--el-privacy-private) text-(--el-text-strong) border-transparent',\n // Inactive \"Archived\" badge (MOTIR-1276 · 1266.5) — a QUIET muted fill\n // with slate ink, not a hued tint: archived is an inactive state, not a\n // severity. Dedicated `--el-archived-pill-*` tokens so a palette can tune\n // the inactive badge apart from the neutral chip; AA-safe (slate on muted\n // clears AA both themes).\n archived: 'bg-(--el-archived-pill-bg) text-(--el-archived-pill-text) border-transparent',\n },\n },\n defaultVariants: {},\n },\n);\n\nexport interface PillProps\n extends HTMLAttributes<HTMLSpanElement>, VariantProps<typeof pillVariants> {}\n\nexport const Pill = forwardRef<HTMLSpanElement, PillProps>(function Pill(\n { status, severity, priority, memberRole, orgRole, tone, className, children, ...rest },\n ref,\n) {\n return (\n <span\n ref={ref}\n className={cn(\n pillVariants({ status, severity, priority, memberRole, orgRole, tone }),\n className,\n )}\n {...rest}\n >\n {children}\n </span>\n );\n});\n"]}
1
+ {"version":3,"sources":["../../../src/utils/cn.ts","../../../src/components/ui/Pill.tsx"],"names":["Pill"],"mappings":";;;;;;;AAcO,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACsBA,IAAM,YAAA,GAAe,GAAA;AAAA,EACnB,EAAA;AAAA,IACE,gCAAA;AAAA,IACA,0BAAA;AAAA,IACA,6CAAA;AAAA,IACA,+BAAA;AAAA,IACA;AAAA,GACF;AAAA,EACA;AAAA,IACE,QAAA,EAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMR,MAAA,EAAQ;AAAA,QACN,OAAA,EAAS,oEAAA;AAAA,QACT,aAAA,EAAe,+DAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAcf,WAAA,EACE,uHAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAUF,QAAA,EACE,oHAAA;AAAA,QACF,IAAA,EAAM;AAAA,OACR;AAAA,MACA,QAAA,EAAU;AAAA,QACR,IAAA,EAAM,+DAAA;AAAA,QACN,OAAA,EAAS,gEAAA;AAAA,QACT,OAAA,EAAS,iEAAA;AAAA,QACT,MAAA,EAAQ;AAAA,OACV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQA,QAAA,EAAU;AAAA,QACR,OAAA,EACE,qHAAA;AAAA,QACF,IAAA,EAAM,kHAAA;AAAA,QACN,MAAA,EACE,oHAAA;AAAA,QACF,GAAA,EAAK,iHAAA;AAAA,QACL,MAAA,EACE;AAAA,OACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAYA,UAAA,EAAY;AAAA,QACV,KAAA,EAAO,iEAAA;AAAA,QACP,MAAA,EAAQ,kEAAA;AAAA,QACR,MAAA,EAAQ,kEAAA;AAAA,QACR,MAAA,EAAQ;AAAA,OACV;AAAA;AAAA;AAAA;AAAA;AAAA,MAKA,OAAA,EAAS;AAAA,QACP,KAAA,EAAO,qEAAA;AAAA,QACP,KAAA,EAAO,qEAAA;AAAA,QACP,MAAA,EAAQ;AAAA,OACV;AAAA,MACA,IAAA,EAAM;AAAA,QACJ,OAAA,EAAS,wEAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMT,OAAA,EAAS,sEAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMT,QAAA,EAAU,8EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAWV,QAAA,EAAU;AAAA;AACZ,KACF;AAAA,IACA,iBAAiB;AAAC;AAEtB,CAAA;AAKO,IAAM,OAAO,UAAA,CAAuC,SAASA,KAAAA,CAClE,EAAE,QAAQ,QAAA,EAAU,QAAA,EAAU,UAAA,EAAY,OAAA,EAAS,MAAM,SAAA,EAAW,QAAA,EAAU,GAAG,IAAA,IACjF,GAAA,EACA;AACA,EAAA,uBACE,GAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,SAAA,EAAW,EAAA;AAAA,QACT,YAAA,CAAa,EAAE,MAAA,EAAQ,QAAA,EAAU,UAAU,UAAA,EAAY,OAAA,EAAS,MAAM,CAAA;AAAA,QACtE;AAAA,OACF;AAAA,MACC,GAAG,IAAA;AAAA,MAEH;AAAA;AAAA,GACH;AAEJ,CAAC","file":"Pill.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merge Tailwind class names without conflicts.\n *\n * `clsx` handles conditional inclusion (booleans, undefineds, arrays);\n * `twMerge` resolves conflicting Tailwind classes (later wins, e.g.\n * `px-2 px-4` → `px-4`). Together they are the de facto standard for\n * composing Tailwind classes in React components.\n *\n * @example\n * cn('px-2 py-1', isActive && 'bg-primary', className)\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","import { forwardRef, type HTMLAttributes } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../../utils/cn';\n\n/**\n * Pill — compact status or severity label. Pure span, no interaction.\n *\n * Variant axes (use exactly one):\n * - `status`: planned | in-progress | implemented | done — Motir's Subtask\n * lifecycle states. The first three are per-CATEGORY tones; `implemented` is\n * the one PER-STATUS tone (MOTIR-3103), because it shares a category with\n * In Progress / Planning / In Review and must not share their chip.\n * - `severity`: info | success | warning | danger — semantic UI states.\n * - `priority`: highest | high | medium | low | lowest — work-item priority, a\n * 5-step diverging ramp (`--el-priority-*` hue tinted over the surface, plus a\n * redundant direction icon). The single source of truth is `PRIORITY_META`.\n * - `memberRole`: admin | member | viewer | custom — a project membership role\n * (Story 6.4), one hued tint per role. (Named `memberRole`, not `role`, so\n * it can't collide with the DOM `role` ARIA attribute on the span.)\n * - `orgRole`: owner | admin | member — an ORGANIZATION role (Story 6.10),\n * sitting above the workspace `memberRole`. owner → lavender (the highest,\n * brand-purple tier), admin → sky, member → mint — per the org-admin design.\n * - `tone`: neutral — a non-semantic chip (counts, metadata). Dark text on a\n * neutral surface. `private` — the epic-level-privacy \"Not public\" badge\n * (Story 6.14 · design epic-privacy panels 1/2/6b): lavender tint + charcoal\n * text, the same AA-safe recipe as `status=\"planned\"` / `memberRole=\"admin\"`.\n *\n * All tones use adaptive dark/light text (`--color-charcoal`) on a hued tint,\n * so every variant clears WCAG AA contrast in both light and dark modes\n * (the hue lives in the background, not the text — PRODECT_FINDINGS #35).\n *\n * Always uses `--radius-badge` for fully rounded ends regardless of display style.\n *\n * @example\n * <Pill status=\"in-progress\">In progress</Pill>\n * <Pill severity=\"danger\">Validation failed</Pill>\n * <Pill tone=\"neutral\">3 members</Pill>\n */\nconst pillVariants = cva(\n cn(\n 'inline-flex items-center gap-1',\n 'rounded-(--radius-badge)',\n 'px-(--spacing-chip-x) py-(--spacing-chip-y)',\n 'font-sans text-xs font-medium',\n 'border',\n ),\n {\n variants: {\n // All colored tones carry the semantic hue in the TINT BACKGROUND and use\n // adaptive dark/light TEXT (`--color-charcoal`: #37352f light, #e5e5e5\n // dark) — the same pattern `planned` always used. The previous saturated\n // hue-on-tint TEXT failed WCAG AA (≈2.5–3.8:1 light, 3.2–4.0:1 dark);\n // charcoal-on-tint clears ~10:1 in both modes (PRODECT_FINDINGS #35).\n status: {\n planned: 'bg-(--el-tint-lavender) text-(--el-text-strong) border-transparent',\n 'in-progress': 'bg-(--el-tint-sky) text-(--el-text-strong) border-transparent',\n // BUILT, waiting on checks (MOTIR-3103). The first PER-STATUS tone on\n // this axis — the other three are per-CATEGORY, and `implemented` sits in\n // the same `in_progress` category as In Progress, Planning and In Review,\n // so without its own tone four statuses render one chip.\n //\n // It uses the recipe theme.css's own `--el-status-*` header prescribes —\n // \"a status CHIP bg = color-mix(var(--el-status-X) 14%, var(--el-surface))\n // with --el-text-strong\" — which is the same dilution the `priority`\n // variant below already renders. The hue is not spent by any other\n // status in any palette (measured at ΔE2000 >= 10 across all ten by\n // tests/theme/statusHueSeparation.test.ts), and the chip additionally\n // carries a GLYPH, because a 14% tint is a subtle mark by construction\n // and finding #35 forbids resting a state on colour alone.\n implemented:\n 'bg-[color-mix(in_srgb,var(--el-status-implemented)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n // A person's YES, and not yet shipped (MOTIR-5141). The SECOND\n // per-status tone, on the same documented recipe `implemented` uses\n // rather than a second one — `--el-status-approved` is the `done` green\n // one ink step deeper, measured at ΔE2000 >= 10 against every other\n // status hue in all ten palettes. Like `implemented` it carries a GLYPH\n // (`CircleCheck`, in `StatusPill`), because a 14% tint is a subtle mark\n // and finding #35 forbids resting a state on colour alone — and here it\n // does more work than usual: at 14% the whole ramp compresses, and this\n // chip's nearest neighbour is `done`'s.\n approved:\n 'bg-[color-mix(in_srgb,var(--el-status-approved)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n done: 'bg-(--el-tint-mint) text-(--el-text-strong) border-transparent',\n },\n severity: {\n info: 'bg-(--el-tint-sky) text-(--el-text-strong) border-transparent',\n success: 'bg-(--el-tint-mint) text-(--el-text-strong) border-transparent',\n warning: 'bg-(--el-tint-peach) text-(--el-text-strong) border-transparent',\n danger: 'bg-(--el-tint-rose) text-(--el-text-strong) border-transparent',\n },\n // Priority chips (MOTIR-1273 · 1266.2) — a 5-step diverging ramp keyed off\n // the `--el-priority-*` hues, un-collapsing `medium` and `lowest` (both\n // were `tone=\"neutral\"` grey). Each hue is diluted to a 14% tint over\n // `--el-surface` so charcoal `--el-text-strong` clears AA in both themes\n // (the hue lives in the background — finding #35); the redundant direction\n // icon (PRIORITY_META) is the non-colour cue. `medium` (slate) and\n // `lowest` (stone) resolve to two distinct greys in every palette.\n priority: {\n highest:\n 'bg-[color-mix(in_srgb,var(--el-priority-highest)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n high: 'bg-[color-mix(in_srgb,var(--el-priority-high)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n medium:\n 'bg-[color-mix(in_srgb,var(--el-priority-medium)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n low: 'bg-[color-mix(in_srgb,var(--el-priority-low)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n lowest:\n 'bg-[color-mix(in_srgb,var(--el-priority-lowest)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent',\n },\n // Project membership roles (Story 6.4 · design/projects access-members):\n // admin → lavender, member → sky, viewer → mint — the hue in the tint,\n // charcoal text (AA-safe, finding #35), same recipe as `status`. Routed\n // through the DEDICATED `--el-role-*` tokens (MOTIR-1274 · 1266.3) so a\n // palette can tune roles apart from the other --el-tint-* meanings; each\n // defaults to its prior tint (zero visual change).\n // `custom` (MOTIR-2257 · MOTIR-2485) is a project's OWN role, which has no\n // enum value — it takes the next free tint slot (peach) because this\n // surface had already spent lavender / sky / mint on the built-ins, so a\n // custom chip in any of those would read as one of them. The KIND is\n // carried in WORDS by the chip's accessible name; nothing rests on the hue.\n memberRole: {\n admin: 'bg-(--el-role-admin) text-(--el-text-strong) border-transparent',\n member: 'bg-(--el-role-member) text-(--el-text-strong) border-transparent',\n viewer: 'bg-(--el-role-viewer) text-(--el-text-strong) border-transparent',\n custom: 'bg-(--el-role-custom) text-(--el-text-strong) border-transparent',\n },\n // Organization roles (Story 6.10 · design/org-admin): owner → lavender\n // (the highest, brand-purple tier), admin → sky, member → mint — the hue\n // in the tint, charcoal text (AA-safe, finding #35), same recipe as above.\n // Dedicated `--el-org-role-*` tokens (MOTIR-1274 · 1266.3); zero change.\n orgRole: {\n owner: 'bg-(--el-org-role-owner) text-(--el-text-strong) border-transparent',\n admin: 'bg-(--el-org-role-admin) text-(--el-text-strong) border-transparent',\n member: 'bg-(--el-org-role-member) text-(--el-text-strong) border-transparent',\n },\n tone: {\n neutral: 'bg-(--el-chip-bg) text-(--el-text-secondary) border-(--el-chip-border)',\n // Epic-level privacy \"Not public\" badge (Story 6.14 · design\n // epic-privacy panels 1/2/6b) — lavender tint, charcoal text, the same\n // AA-safe recipe as `status=\"planned\"` / `memberRole=\"admin\"` (≈9.7:1).\n // Dedicated `--el-privacy-private` token (MOTIR-1274 · 1266.3); the\n // matching `--el-privacy-public` is defined ahead of a \"public\" badge.\n private: 'bg-(--el-privacy-private) text-(--el-text-strong) border-transparent',\n // Inactive \"Archived\" badge (MOTIR-1276 · 1266.5) — a QUIET muted fill\n // with slate ink, not a hued tint: archived is an inactive state, not a\n // severity. Dedicated `--el-archived-pill-*` tokens so a palette can tune\n // the inactive badge apart from the neutral chip; AA-safe (slate on muted\n // clears AA both themes).\n archived: 'bg-(--el-archived-pill-bg) text-(--el-archived-pill-text) border-transparent',\n // A decision is WAITING on somebody (Story MOTIR-4778 · MOTIR-4792,\n // design `design/work-items/approval-control.mock.html` — the approval\n // frame's `pill-await`). Yellow, and the tint is chosen for DISTINCTNESS\n // rather than for mood: the frame renders four chips a reader must tell\n // apart at a glance — awaiting, recording (`severity=\"info\"`, sky),\n // approved (`severity=\"success\"`, mint) and changes-requested\n // (`severity=\"warning\"`, peach) — and `--el-tint-yellow` was the only\n // slot none of the other three had spent. `tone` rather than `severity`\n // because waiting is not a severity; it sits beside `private` and\n // `archived`, which are states rather than judgements.\n awaiting: 'bg-(--el-tint-yellow) text-(--el-text-strong) border-transparent',\n },\n },\n defaultVariants: {},\n },\n);\n\nexport interface PillProps\n extends HTMLAttributes<HTMLSpanElement>, VariantProps<typeof pillVariants> {}\n\nexport const Pill = forwardRef<HTMLSpanElement, PillProps>(function Pill(\n { status, severity, priority, memberRole, orgRole, tone, className, children, ...rest },\n ref,\n) {\n return (\n <span\n ref={ref}\n className={cn(\n pillVariants({ status, severity, priority, memberRole, orgRole, tone }),\n className,\n )}\n {...rest}\n >\n {children}\n </span>\n );\n});\n"]}
@@ -33,7 +33,14 @@ var PopoverContent = forwardRef(function PopoverContent2({
33
33
  overflowVisible ? "overflow-visible" : "overflow-hidden",
34
34
  "shadow-(--shadow-elevated) border border-(--el-border)",
35
35
  "focus:outline-none",
36
- "data-[state=open]:animate-in data-[state=closed]:animate-out fade-in-0 fade-out-0",
36
+ // NO entry/exit animation utilities here. Overlay motion is the
37
+ // THEME's, declared per style in `theme.css` against the
38
+ // `data-surface="popover"` hook above and gated behind
39
+ // `prefers-reduced-motion` — see the `immersive-menu-open` block.
40
+ // `animate-in` / `fade-in-0` are a Tailwind PLUGIN's vocabulary this
41
+ // repository has never installed, so they generated nothing
42
+ // (MOTIR-4889); `tests/theme/no-inert-animation-utilities.test.ts`
43
+ // keeps them out.
37
44
  className
38
45
  ),
39
46
  style: { width: typeof width === "number" ? `${width}px` : width, ...style },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/utils/cn.ts","../../../src/components/ui/Popover.tsx"],"names":["PopoverContent"],"mappings":";;;;;;AAcO,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACmBA,SAAS,YAAY,EAAE,IAAA,EAAM,YAAA,EAAc,KAAA,EAAO,UAAS,EAAiB;AAC1E,EAAA,2BACgB,YAAA,CAAA,IAAA,EAAb,EAAkB,IAAA,EAAY,YAAA,EAA4B,OACxD,QAAA,EACH,CAAA;AAEJ;AA0BA,IAAM,cAAA,GAAiB,UAAA,CAGrB,SAASA,eAAAA,CACT;AAAA,EACE,SAAA;AAAA,EACA,KAAA,GAAQ,OAAA;AAAA,EACR,UAAA,GAAa,CAAA;AAAA,EACb,KAAA,GAAQ,GAAA;AAAA,EACR,KAAA;AAAA,EACA,QAAA;AAAA,EACA,eAAA,GAAkB,KAAA;AAAA,EAClB,GAAG;AACL,CAAA,EACA,GAAA,EACA;AACA,EAAA,uBACE,GAAA,CAAc,qBAAb,EACC,QAAA,kBAAA,GAAA;AAAA,IAAc,YAAA,CAAA,OAAA;AAAA,IAAb;AAAA,MACC,GAAA;AAAA,MACA,KAAA;AAAA,MACA,UAAA;AAAA,MAGA,cAAA,EAAa,SAAA;AAAA,MACb,SAAA,EAAW,EAAA;AAAA,QACT,gDAAA;AAAA,QACA,kBAAkB,kBAAA,GAAqB,iBAAA;AAAA,QACvC,wDAAA;AAAA,QACA,oBAAA;AAAA,QACA,mFAAA;AAAA,QACA;AAAA,OACF;AAAA,MACA,KAAA,EAAO,EAAE,KAAA,EAAO,OAAO,KAAA,KAAU,QAAA,GAAW,CAAA,EAAG,KAAK,CAAA,EAAA,CAAA,GAAO,KAAA,EAAO,GAAG,KAAA,EAAM;AAAA,MAC1E,GAAG,IAAA;AAAA,MAEH;AAAA;AAAA,GACH,EACF,CAAA;AAEJ,CAAC,CAAA;AAED,IAAM,cAAA,GAA8B,YAAA,CAAA,OAAA;AACpC,IAAM,YAAA,GAA4B,YAAA,CAAA,KAAA;AAClC,IAAM,aAAA,GAA6B,YAAA,CAAA,MAAA;AAE5B,IAAM,OAAA,GAAU,MAAA,CAAO,MAAA,CAAO,WAAA,EAAa;AAAA,EAChD,OAAA,EAAS,cAAA;AAAA,EACT,OAAA,EAAS,cAAA;AAAA,EACT,KAAA,EAAO,YAAA;AAAA,EACP,MAAA,EAAQ;AACV,CAAC","file":"Popover.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merge Tailwind class names without conflicts.\n *\n * `clsx` handles conditional inclusion (booleans, undefineds, arrays);\n * `twMerge` resolves conflicting Tailwind classes (later wins, e.g.\n * `px-2 px-4` → `px-4`). Together they are the de facto standard for\n * composing Tailwind classes in React components.\n *\n * @example\n * cn('px-2 py-1', isActive && 'bg-primary', className)\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","'use client';\n\nimport { forwardRef, type ComponentPropsWithoutRef, type ReactNode } from 'react';\nimport * as RadixPopover from '@radix-ui/react-popover';\nimport { cn } from '../../utils/cn';\n\n/**\n * Popover — anchored, click-outside-dismissable, focus-managed floating\n * panel wrapping @radix-ui/react-popover.\n *\n * Same shape as Modal.tsx (Radix-wrapped, controlled open state). Unlike\n * Modal, the content is anchored to a trigger rather than centered, and\n * there is no overlay — clicking outside dismisses. Use it for menus and\n * dropdowns where the panel holds free-form content (the workspace\n * switcher's section header + membership rows, the user menu).\n *\n * Portal + border + shadow match Modal so the two primitives feel\n * consistent. No new tokens — reuses --radius-card, --shadow-elevated,\n * --color-hairline.\n *\n * @example\n * <Popover open={open} onOpenChange={setOpen}>\n * <Popover.Trigger asChild>\n * <Button variant=\"ghost\" rightIcon={<ChevronDown />}>Menu</Button>\n * </Popover.Trigger>\n * <Popover.Content align=\"start\">{items}</Popover.Content>\n * </Popover>\n */\nexport interface PopoverProps {\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n modal?: boolean;\n children: ReactNode;\n}\n\nfunction PopoverRoot({ open, onOpenChange, modal, children }: PopoverProps) {\n return (\n <RadixPopover.Root open={open} onOpenChange={onOpenChange} modal={modal}>\n {children}\n </RadixPopover.Root>\n );\n}\n\nexport interface PopoverContentProps extends ComponentPropsWithoutRef<typeof RadixPopover.Content> {\n /** Panel width; defaults to the 320px the switcher mockup pins. */\n width?: number | string;\n /**\n * Drop the panel's `overflow-hidden` so a child anchored dropdown — e.g. a\n * `Combobox` listbox — can render past the panel's edge instead of being\n * clipped by it. Set this whenever the popover hosts a `Combobox` (or any\n * other absolutely-positioned floating child) whose menu can grow taller\n * than the popover's content-sized height — a real-product Jira-style\n * dropdown-inside-popover (mark-duplicate target, promote sprint picker)\n * always escapes its anchor (`bug-combobox-menu-clipped-inside-popover`).\n *\n * Safe because the popover's own padding (`p-2`, `p-3`) keeps children off\n * the rounded corners, so the corners stay clean without overflow clipping;\n * and the Combobox menu stays in the popover's DOM subtree so non-modal\n * Radix Popover does not treat its clicks as outside-dismissals.\n *\n * Leave unset for popovers that hold only static rows (the workspace\n * switcher, the user menu) — the default `overflow-hidden` is fine and\n * cheaper to reason about.\n */\n overflowVisible?: boolean;\n}\n\nconst PopoverContent = forwardRef<\n React.ElementRef<typeof RadixPopover.Content>,\n PopoverContentProps\n>(function PopoverContent(\n {\n className,\n align = 'start',\n sideOffset = 8,\n width = 320,\n style,\n children,\n overflowVisible = false,\n ...rest\n },\n ref,\n) {\n return (\n <RadixPopover.Portal>\n <RadixPopover.Content\n ref={ref}\n align={align}\n sideOffset={sideOffset}\n // `data-surface` lets a surface-material style (glassmorphism) frost\n // the floating panel — see globals.css's material layer.\n data-surface=\"popover\"\n className={cn(\n 'z-50 rounded-(--radius-card) bg-(--el-page-bg)',\n overflowVisible ? 'overflow-visible' : 'overflow-hidden',\n 'shadow-(--shadow-elevated) border border-(--el-border)',\n 'focus:outline-none',\n 'data-[state=open]:animate-in data-[state=closed]:animate-out fade-in-0 fade-out-0',\n className,\n )}\n style={{ width: typeof width === 'number' ? `${width}px` : width, ...style }}\n {...rest}\n >\n {children}\n </RadixPopover.Content>\n </RadixPopover.Portal>\n );\n});\n\nconst PopoverTrigger = RadixPopover.Trigger;\nconst PopoverClose = RadixPopover.Close;\nconst PopoverAnchor = RadixPopover.Anchor;\n\nexport const Popover = Object.assign(PopoverRoot, {\n Trigger: PopoverTrigger,\n Content: PopoverContent,\n Close: PopoverClose,\n Anchor: PopoverAnchor,\n});\n"]}
1
+ {"version":3,"sources":["../../../src/utils/cn.ts","../../../src/components/ui/Popover.tsx"],"names":["PopoverContent"],"mappings":";;;;;;AAcO,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACmBA,SAAS,YAAY,EAAE,IAAA,EAAM,YAAA,EAAc,KAAA,EAAO,UAAS,EAAiB;AAC1E,EAAA,2BACgB,YAAA,CAAA,IAAA,EAAb,EAAkB,IAAA,EAAY,YAAA,EAA4B,OACxD,QAAA,EACH,CAAA;AAEJ;AA0BA,IAAM,cAAA,GAAiB,UAAA,CAGrB,SAASA,eAAAA,CACT;AAAA,EACE,SAAA;AAAA,EACA,KAAA,GAAQ,OAAA;AAAA,EACR,UAAA,GAAa,CAAA;AAAA,EACb,KAAA,GAAQ,GAAA;AAAA,EACR,KAAA;AAAA,EACA,QAAA;AAAA,EACA,eAAA,GAAkB,KAAA;AAAA,EAClB,GAAG;AACL,CAAA,EACA,GAAA,EACA;AACA,EAAA,uBACE,GAAA,CAAc,qBAAb,EACC,QAAA,kBAAA,GAAA;AAAA,IAAc,YAAA,CAAA,OAAA;AAAA,IAAb;AAAA,MACC,GAAA;AAAA,MACA,KAAA;AAAA,MACA,UAAA;AAAA,MAGA,cAAA,EAAa,SAAA;AAAA,MACb,SAAA,EAAW,EAAA;AAAA,QACT,gDAAA;AAAA,QACA,kBAAkB,kBAAA,GAAqB,iBAAA;AAAA,QACvC,wDAAA;AAAA,QACA,oBAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QASA;AAAA,OACF;AAAA,MACA,KAAA,EAAO,EAAE,KAAA,EAAO,OAAO,KAAA,KAAU,QAAA,GAAW,CAAA,EAAG,KAAK,CAAA,EAAA,CAAA,GAAO,KAAA,EAAO,GAAG,KAAA,EAAM;AAAA,MAC1E,GAAG,IAAA;AAAA,MAEH;AAAA;AAAA,GACH,EACF,CAAA;AAEJ,CAAC,CAAA;AAED,IAAM,cAAA,GAA8B,YAAA,CAAA,OAAA;AACpC,IAAM,YAAA,GAA4B,YAAA,CAAA,KAAA;AAClC,IAAM,aAAA,GAA6B,YAAA,CAAA,MAAA;AAE5B,IAAM,OAAA,GAAU,MAAA,CAAO,MAAA,CAAO,WAAA,EAAa;AAAA,EAChD,OAAA,EAAS,cAAA;AAAA,EACT,OAAA,EAAS,cAAA;AAAA,EACT,KAAA,EAAO,YAAA;AAAA,EACP,MAAA,EAAQ;AACV,CAAC","file":"Popover.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merge Tailwind class names without conflicts.\n *\n * `clsx` handles conditional inclusion (booleans, undefineds, arrays);\n * `twMerge` resolves conflicting Tailwind classes (later wins, e.g.\n * `px-2 px-4` → `px-4`). Together they are the de facto standard for\n * composing Tailwind classes in React components.\n *\n * @example\n * cn('px-2 py-1', isActive && 'bg-primary', className)\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","'use client';\n\nimport { forwardRef, type ComponentPropsWithoutRef, type ReactNode } from 'react';\nimport * as RadixPopover from '@radix-ui/react-popover';\nimport { cn } from '../../utils/cn';\n\n/**\n * Popover — anchored, click-outside-dismissable, focus-managed floating\n * panel wrapping @radix-ui/react-popover.\n *\n * Same shape as Modal.tsx (Radix-wrapped, controlled open state). Unlike\n * Modal, the content is anchored to a trigger rather than centered, and\n * there is no overlay — clicking outside dismisses. Use it for menus and\n * dropdowns where the panel holds free-form content (the workspace\n * switcher's section header + membership rows, the user menu).\n *\n * Portal + border + shadow match Modal so the two primitives feel\n * consistent. No new tokens — reuses --radius-card, --shadow-elevated,\n * --color-hairline.\n *\n * @example\n * <Popover open={open} onOpenChange={setOpen}>\n * <Popover.Trigger asChild>\n * <Button variant=\"ghost\" rightIcon={<ChevronDown />}>Menu</Button>\n * </Popover.Trigger>\n * <Popover.Content align=\"start\">{items}</Popover.Content>\n * </Popover>\n */\nexport interface PopoverProps {\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n modal?: boolean;\n children: ReactNode;\n}\n\nfunction PopoverRoot({ open, onOpenChange, modal, children }: PopoverProps) {\n return (\n <RadixPopover.Root open={open} onOpenChange={onOpenChange} modal={modal}>\n {children}\n </RadixPopover.Root>\n );\n}\n\nexport interface PopoverContentProps extends ComponentPropsWithoutRef<typeof RadixPopover.Content> {\n /** Panel width; defaults to the 320px the switcher mockup pins. */\n width?: number | string;\n /**\n * Drop the panel's `overflow-hidden` so a child anchored dropdown — e.g. a\n * `Combobox` listbox — can render past the panel's edge instead of being\n * clipped by it. Set this whenever the popover hosts a `Combobox` (or any\n * other absolutely-positioned floating child) whose menu can grow taller\n * than the popover's content-sized height — a real-product Jira-style\n * dropdown-inside-popover (mark-duplicate target, promote sprint picker)\n * always escapes its anchor (`bug-combobox-menu-clipped-inside-popover`).\n *\n * Safe because the popover's own padding (`p-2`, `p-3`) keeps children off\n * the rounded corners, so the corners stay clean without overflow clipping;\n * and the Combobox menu stays in the popover's DOM subtree so non-modal\n * Radix Popover does not treat its clicks as outside-dismissals.\n *\n * Leave unset for popovers that hold only static rows (the workspace\n * switcher, the user menu) — the default `overflow-hidden` is fine and\n * cheaper to reason about.\n */\n overflowVisible?: boolean;\n}\n\nconst PopoverContent = forwardRef<\n React.ElementRef<typeof RadixPopover.Content>,\n PopoverContentProps\n>(function PopoverContent(\n {\n className,\n align = 'start',\n sideOffset = 8,\n width = 320,\n style,\n children,\n overflowVisible = false,\n ...rest\n },\n ref,\n) {\n return (\n <RadixPopover.Portal>\n <RadixPopover.Content\n ref={ref}\n align={align}\n sideOffset={sideOffset}\n // `data-surface` lets a surface-material style (glassmorphism) frost\n // the floating panel — see globals.css's material layer.\n data-surface=\"popover\"\n className={cn(\n 'z-50 rounded-(--radius-card) bg-(--el-page-bg)',\n overflowVisible ? 'overflow-visible' : 'overflow-hidden',\n 'shadow-(--shadow-elevated) border border-(--el-border)',\n 'focus:outline-none',\n // NO entry/exit animation utilities here. Overlay motion is the\n // THEME's, declared per style in `theme.css` against the\n // `data-surface=\"popover\"` hook above and gated behind\n // `prefers-reduced-motion` — see the `immersive-menu-open` block.\n // `animate-in` / `fade-in-0` are a Tailwind PLUGIN's vocabulary this\n // repository has never installed, so they generated nothing\n // (MOTIR-4889); `tests/theme/no-inert-animation-utilities.test.ts`\n // keeps them out.\n className,\n )}\n style={{ width: typeof width === 'number' ? `${width}px` : width, ...style }}\n {...rest}\n >\n {children}\n </RadixPopover.Content>\n </RadixPopover.Portal>\n );\n});\n\nconst PopoverTrigger = RadixPopover.Trigger;\nconst PopoverClose = RadixPopover.Close;\nconst PopoverAnchor = RadixPopover.Anchor;\n\nexport const Popover = Object.assign(PopoverRoot, {\n Trigger: PopoverTrigger,\n Content: PopoverContent,\n Close: PopoverClose,\n Anchor: PopoverAnchor,\n});\n"]}
@@ -16,8 +16,6 @@ var toastVariants = cva(
16
16
  "rounded-(--radius-card) border bg-(--el-page-bg)",
17
17
  "px-(--spacing-md) py-(--spacing-sm)",
18
18
  "shadow-(--shadow-elevated)",
19
- "data-[state=open]:animate-in data-[state=closed]:animate-out",
20
- "data-[state=open]:fade-in-0 data-[state=closed]:fade-out-80",
21
19
  "data-[swipe=move]:translate-x-(--radix-toast-swipe-move-x)",
22
20
  "data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-(--radix-toast-swipe-end-x)",
23
21
  "data-[swipe=cancel]:transition-transform data-[swipe=end]:transition-transform"
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/utils/cn.ts","../../../src/components/ui/Toast.tsx"],"names":[],"mappings":"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","file":"Toast.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merge Tailwind class names without conflicts.\n *\n * `clsx` handles conditional inclusion (booleans, undefineds, arrays);\n * `twMerge` resolves conflicting Tailwind classes (later wins, e.g.\n * `px-2 px-4` → `px-4`). Together they are the de facto standard for\n * composing Tailwind classes in React components.\n *\n * @example\n * cn('px-2 py-1', isActive && 'bg-primary', className)\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","'use client';\n\nimport { createContext, useCallback, useContext, useMemo, useState, type ReactNode } from 'react';\nimport * as RadixToast from '@radix-ui/react-toast';\nimport { AlertCircle, CheckCircle2, Info, X, XCircle } from 'lucide-react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../../utils/cn';\n\n/**\n * Toast — transient notification with stacking, auto-dismiss, hover-to-pause.\n *\n * Wrap the app in `<ToastProvider>` once. Use `useToast()` from any client\n * component to dispatch:\n *\n * @example\n * const { toast } = useToast();\n * toast({ variant: 'success', title: 'Saved', description: 'Changes synced.' });\n */\nconst toastVariants = cva(\n cn(\n 'pointer-events-auto flex items-start gap-3',\n 'rounded-(--radius-card) border bg-(--el-page-bg)',\n 'px-(--spacing-md) py-(--spacing-sm)',\n 'shadow-(--shadow-elevated)',\n 'data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=open]:fade-in-0 data-[state=closed]:fade-out-80',\n 'data-[swipe=move]:translate-x-(--radix-toast-swipe-move-x)',\n 'data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-(--radix-toast-swipe-end-x)',\n 'data-[swipe=cancel]:transition-transform data-[swipe=end]:transition-transform',\n ),\n {\n variants: {\n variant: {\n info: 'border-(--el-info)',\n success: 'border-(--el-success)',\n warning: 'border-(--el-warning)',\n error: 'border-(--el-danger)',\n },\n },\n defaultVariants: { variant: 'info' },\n },\n);\n\ntype ToastVariant = NonNullable<VariantProps<typeof toastVariants>['variant']>;\n\n/** An optional inline action button (e.g. \"Undo\" after an archive). */\ninterface ToastAction {\n label: string;\n onClick: () => void;\n}\n\ninterface ToastItem {\n id: string;\n variant: ToastVariant;\n title: string;\n description?: string;\n action?: ToastAction;\n}\n\ninterface ToastContextValue {\n toast: (opts: {\n variant?: ToastVariant;\n title: string;\n description?: string;\n action?: ToastAction;\n }) => void;\n}\n\nconst ToastContext = createContext<ToastContextValue | null>(null);\n\nconst ICONS: Record<ToastVariant, typeof Info> = {\n info: Info,\n success: CheckCircle2,\n warning: AlertCircle,\n error: XCircle,\n};\n\nconst ICON_COLOR_VAR: Record<ToastVariant, string> = {\n info: 'var(--el-info)',\n success: 'var(--el-success)',\n warning: 'var(--el-warning)',\n error: 'var(--el-danger)',\n};\n\nexport function ToastProvider({\n children,\n closeLabel = 'Close',\n}: {\n children: ReactNode;\n /**\n * Accessible label for each toast's close (×) button. Defaults to `'Close'`.\n * i18n is the consuming app's concern (the package carries no `next-intl`\n * dependency), so a localized app passes the translated string — motir-core\n * wires it from its `common.close`.\n */\n closeLabel?: string;\n}) {\n const [toasts, setToasts] = useState<ToastItem[]>([]);\n\n const toast = useCallback<ToastContextValue['toast']>(\n ({ variant = 'info', title, description, action }) => {\n const id = crypto.randomUUID();\n setToasts((prev) => [...prev, { id, variant, title, description, action }]);\n },\n [],\n );\n\n const remove = useCallback((id: string) => {\n setToasts((prev) => prev.filter((t) => t.id !== id));\n }, []);\n\n const value = useMemo(() => ({ toast }), [toast]);\n\n return (\n <ToastContext.Provider value={value}>\n <RadixToast.Provider swipeDirection=\"right\" duration={5000}>\n {children}\n {toasts.map((t) => {\n const Icon = ICONS[t.variant];\n return (\n <RadixToast.Root\n key={t.id}\n className={toastVariants({ variant: t.variant })}\n onOpenChange={(open) => {\n if (!open) remove(t.id);\n }}\n >\n <span aria-hidden className=\"mt-0.5 inline-flex\">\n <Icon className=\"h-4 w-4\" style={{ color: ICON_COLOR_VAR[t.variant] }} />\n </span>\n <div className=\"flex-1\">\n <RadixToast.Title className=\"font-sans text-sm font-medium text-(--el-text)\">\n {t.title}\n </RadixToast.Title>\n {t.description ? (\n <RadixToast.Description className=\"text-(--el-text-muted) mt-0.5 font-sans text-xs\">\n {t.description}\n </RadixToast.Description>\n ) : null}\n </div>\n {t.action ? (\n <RadixToast.Action\n altText={t.action.label}\n onClick={t.action.onClick}\n className=\"shrink-0 self-center rounded-(--radius-control) px-(--spacing-control-x) py-(--spacing-control-y) font-sans text-xs font-semibold text-(--el-link) hover:bg-(--el-surface) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--focus-ring-color)\"\n >\n {t.action.label}\n </RadixToast.Action>\n ) : null}\n <RadixToast.Close\n aria-label={closeLabel}\n className=\"text-(--el-text-muted) hover:text-(--el-text) rounded-(--radius-control) p-(--spacing-icon-btn) transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--focus-ring-color)\"\n >\n <X className=\"h-3.5 w-3.5\" />\n </RadixToast.Close>\n </RadixToast.Root>\n );\n })}\n <RadixToast.Viewport className=\"fixed bottom-0 right-0 z-50 m-4 flex w-96 max-w-[100vw] flex-col gap-2 outline-none\" />\n </RadixToast.Provider>\n </ToastContext.Provider>\n );\n}\n\n// A no-op fallback for components rendered outside a ToastProvider — a unit test\n// or a surface mounted without the shell. The production root layout always\n// mounts the provider, so this only ever spares a deeply-embedded control (the\n// 2.8.4 ⋯ menu inside a board card / list row) from a hard provider dependency;\n// a stray toast outside the shell silently no-ops rather than crashing render.\nconst NO_OP_TOAST: ToastContextValue = { toast: () => {} };\n\nexport function useToast(): ToastContextValue {\n return useContext(ToastContext) ?? NO_OP_TOAST;\n}\n"]}
1
+ {"version":3,"sources":["../../../src/utils/cn.ts","../../../src/components/ui/Toast.tsx"],"names":[],"mappings":"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","file":"Toast.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merge Tailwind class names without conflicts.\n *\n * `clsx` handles conditional inclusion (booleans, undefineds, arrays);\n * `twMerge` resolves conflicting Tailwind classes (later wins, e.g.\n * `px-2 px-4` → `px-4`). Together they are the de facto standard for\n * composing Tailwind classes in React components.\n *\n * @example\n * cn('px-2 py-1', isActive && 'bg-primary', className)\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","'use client';\n\nimport { createContext, useCallback, useContext, useMemo, useState, type ReactNode } from 'react';\nimport * as RadixToast from '@radix-ui/react-toast';\nimport { AlertCircle, CheckCircle2, Info, X, XCircle } from 'lucide-react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../../utils/cn';\n\n/**\n * Toast — transient notification with stacking, auto-dismiss, hover-to-pause.\n *\n * Wrap the app in `<ToastProvider>` once. Use `useToast()` from any client\n * component to dispatch:\n *\n * @example\n * const { toast } = useToast();\n * toast({ variant: 'success', title: 'Saved', description: 'Changes synced.' });\n */\nconst toastVariants = cva(\n cn(\n 'pointer-events-auto flex items-start gap-3',\n 'rounded-(--radius-card) border bg-(--el-page-bg)',\n 'px-(--spacing-md) py-(--spacing-sm)',\n 'shadow-(--shadow-elevated)',\n 'data-[swipe=move]:translate-x-(--radix-toast-swipe-move-x)',\n 'data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-(--radix-toast-swipe-end-x)',\n 'data-[swipe=cancel]:transition-transform data-[swipe=end]:transition-transform',\n ),\n {\n variants: {\n variant: {\n info: 'border-(--el-info)',\n success: 'border-(--el-success)',\n warning: 'border-(--el-warning)',\n error: 'border-(--el-danger)',\n },\n },\n defaultVariants: { variant: 'info' },\n },\n);\n\ntype ToastVariant = NonNullable<VariantProps<typeof toastVariants>['variant']>;\n\n/** An optional inline action button (e.g. \"Undo\" after an archive). */\ninterface ToastAction {\n label: string;\n onClick: () => void;\n}\n\ninterface ToastItem {\n id: string;\n variant: ToastVariant;\n title: string;\n description?: string;\n action?: ToastAction;\n}\n\ninterface ToastContextValue {\n toast: (opts: {\n variant?: ToastVariant;\n title: string;\n description?: string;\n action?: ToastAction;\n }) => void;\n}\n\nconst ToastContext = createContext<ToastContextValue | null>(null);\n\nconst ICONS: Record<ToastVariant, typeof Info> = {\n info: Info,\n success: CheckCircle2,\n warning: AlertCircle,\n error: XCircle,\n};\n\nconst ICON_COLOR_VAR: Record<ToastVariant, string> = {\n info: 'var(--el-info)',\n success: 'var(--el-success)',\n warning: 'var(--el-warning)',\n error: 'var(--el-danger)',\n};\n\nexport function ToastProvider({\n children,\n closeLabel = 'Close',\n}: {\n children: ReactNode;\n /**\n * Accessible label for each toast's close (×) button. Defaults to `'Close'`.\n * i18n is the consuming app's concern (the package carries no `next-intl`\n * dependency), so a localized app passes the translated string — motir-core\n * wires it from its `common.close`.\n */\n closeLabel?: string;\n}) {\n const [toasts, setToasts] = useState<ToastItem[]>([]);\n\n const toast = useCallback<ToastContextValue['toast']>(\n ({ variant = 'info', title, description, action }) => {\n const id = crypto.randomUUID();\n setToasts((prev) => [...prev, { id, variant, title, description, action }]);\n },\n [],\n );\n\n const remove = useCallback((id: string) => {\n setToasts((prev) => prev.filter((t) => t.id !== id));\n }, []);\n\n const value = useMemo(() => ({ toast }), [toast]);\n\n return (\n <ToastContext.Provider value={value}>\n <RadixToast.Provider swipeDirection=\"right\" duration={5000}>\n {children}\n {toasts.map((t) => {\n const Icon = ICONS[t.variant];\n return (\n <RadixToast.Root\n key={t.id}\n className={toastVariants({ variant: t.variant })}\n onOpenChange={(open) => {\n if (!open) remove(t.id);\n }}\n >\n <span aria-hidden className=\"mt-0.5 inline-flex\">\n <Icon className=\"h-4 w-4\" style={{ color: ICON_COLOR_VAR[t.variant] }} />\n </span>\n <div className=\"flex-1\">\n <RadixToast.Title className=\"font-sans text-sm font-medium text-(--el-text)\">\n {t.title}\n </RadixToast.Title>\n {t.description ? (\n <RadixToast.Description className=\"text-(--el-text-muted) mt-0.5 font-sans text-xs\">\n {t.description}\n </RadixToast.Description>\n ) : null}\n </div>\n {t.action ? (\n <RadixToast.Action\n altText={t.action.label}\n onClick={t.action.onClick}\n className=\"shrink-0 self-center rounded-(--radius-control) px-(--spacing-control-x) py-(--spacing-control-y) font-sans text-xs font-semibold text-(--el-link) hover:bg-(--el-surface) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--focus-ring-color)\"\n >\n {t.action.label}\n </RadixToast.Action>\n ) : null}\n <RadixToast.Close\n aria-label={closeLabel}\n className=\"text-(--el-text-muted) hover:text-(--el-text) rounded-(--radius-control) p-(--spacing-icon-btn) transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--focus-ring-color)\"\n >\n <X className=\"h-3.5 w-3.5\" />\n </RadixToast.Close>\n </RadixToast.Root>\n );\n })}\n <RadixToast.Viewport className=\"fixed bottom-0 right-0 z-50 m-4 flex w-96 max-w-[100vw] flex-col gap-2 outline-none\" />\n </RadixToast.Provider>\n </ToastContext.Provider>\n );\n}\n\n// A no-op fallback for components rendered outside a ToastProvider — a unit test\n// or a surface mounted without the shell. The production root layout always\n// mounts the provider, so this only ever spares a deeply-embedded control (the\n// 2.8.4 ⋯ menu inside a board card / list row) from a hard provider dependency;\n// a stray toast outside the shell silently no-ops rather than crashing render.\nconst NO_OP_TOAST: ToastContextValue = { toast: () => {} };\n\nexport function useToast(): ToastContextValue {\n return useContext(ToastContext) ?? NO_OP_TOAST;\n}\n"]}
@@ -26,7 +26,6 @@ function Tooltip({
26
26
  "bg-(--el-tooltip-bg) text-(--el-tooltip-text)",
27
27
  "px-(--spacing-tooltip-x) py-(--spacing-tooltip-y) font-sans text-xs",
28
28
  "shadow-(--shadow-elevated)",
29
- "data-[state=delayed-open]:animate-in data-[state=closed]:animate-out fade-in-0 fade-out-0",
30
29
  className
31
30
  ),
32
31
  children: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/utils/cn.ts","../../../src/components/ui/Tooltip.tsx"],"names":[],"mappings":";;;;;AAcO,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACUO,SAAS,OAAA,CAAQ;AAAA,EACtB,OAAA;AAAA,EACA,QAAA;AAAA,EACA,IAAA,GAAO,KAAA;AAAA,EACP,OAAA,GAAU,GAAA;AAAA,EACV;AACF,CAAA,EAAiB;AACf,EAAA,2BACgB,YAAA,CAAA,QAAA,EAAb,EAAsB,eAAe,OAAA,EACpC,QAAA,kBAAA,IAAA,CAAc,mBAAb,EACC,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAc,YAAA,CAAA,OAAA,EAAb,EAAqB,OAAA,EAAO,IAAA,EAAE,QAAA,EAAS,CAAA;AAAA,oBACxC,GAAA,CAAc,qBAAb,EACC,QAAA,kBAAA,IAAA;AAAA,MAAc,YAAA,CAAA,OAAA;AAAA,MAAb;AAAA,QACC,IAAA;AAAA,QACA,UAAA,EAAY,CAAA;AAAA,QACZ,SAAA,EAAW,EAAA;AAAA,UACT,6CAAA;AAAA,UACA,+CAAA;AAAA,UACA,qEAAA;AAAA,UACA,4BAAA;AAAA,UACA,2FAAA;AAAA,UACA;AAAA,SACF;AAAA,QAEC,QAAA,EAAA;AAAA,UAAA,OAAA;AAAA,0BACD,GAAA,CAAc,YAAA,CAAA,KAAA,EAAb,EAAmB,SAAA,EAAU,wBAAA,EAAyB;AAAA;AAAA;AAAA,KACzD,EACF;AAAA,GAAA,EACF,CAAA,EACF,CAAA;AAEJ","file":"Tooltip.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merge Tailwind class names without conflicts.\n *\n * `clsx` handles conditional inclusion (booleans, undefineds, arrays);\n * `twMerge` resolves conflicting Tailwind classes (later wins, e.g.\n * `px-2 px-4` → `px-4`). Together they are the de facto standard for\n * composing Tailwind classes in React components.\n *\n * @example\n * cn('px-2 py-1', isActive && 'bg-primary', className)\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","'use client';\n\nimport { type ReactNode } from 'react';\nimport * as RadixTooltip from '@radix-ui/react-tooltip';\nimport { cn } from '../../utils/cn';\n\n/**\n * Tooltip — Radix-wrapped popover that appears on hover + focus.\n *\n * Requires a single TooltipProvider near the app root for delay coordination.\n * For simple use, the default delay (700ms) is fine.\n *\n * @example\n * <Tooltip content=\"Send message\">\n * <Button variant=\"ghost\"><Send /></Button>\n * </Tooltip>\n */\nexport interface TooltipProps {\n content: ReactNode;\n children: ReactNode;\n side?: 'top' | 'right' | 'bottom' | 'left';\n /** Delay before opening, in ms. Default 700 (Radix default). */\n delayMs?: number;\n className?: string;\n}\n\nexport function Tooltip({\n content,\n children,\n side = 'top',\n delayMs = 700,\n className,\n}: TooltipProps) {\n return (\n <RadixTooltip.Provider delayDuration={delayMs}>\n <RadixTooltip.Root>\n <RadixTooltip.Trigger asChild>{children}</RadixTooltip.Trigger>\n <RadixTooltip.Portal>\n <RadixTooltip.Content\n side={side}\n sideOffset={6}\n className={cn(\n 'z-50 select-none rounded-(--radius-control)',\n 'bg-(--el-tooltip-bg) text-(--el-tooltip-text)',\n 'px-(--spacing-tooltip-x) py-(--spacing-tooltip-y) font-sans text-xs',\n 'shadow-(--shadow-elevated)',\n 'data-[state=delayed-open]:animate-in data-[state=closed]:animate-out fade-in-0 fade-out-0',\n className,\n )}\n >\n {content}\n <RadixTooltip.Arrow className=\"fill-(--el-tooltip-bg)\" />\n </RadixTooltip.Content>\n </RadixTooltip.Portal>\n </RadixTooltip.Root>\n </RadixTooltip.Provider>\n );\n}\n"]}
1
+ {"version":3,"sources":["../../../src/utils/cn.ts","../../../src/components/ui/Tooltip.tsx"],"names":[],"mappings":";;;;;AAcO,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACUO,SAAS,OAAA,CAAQ;AAAA,EACtB,OAAA;AAAA,EACA,QAAA;AAAA,EACA,IAAA,GAAO,KAAA;AAAA,EACP,OAAA,GAAU,GAAA;AAAA,EACV;AACF,CAAA,EAAiB;AACf,EAAA,2BACgB,YAAA,CAAA,QAAA,EAAb,EAAsB,eAAe,OAAA,EACpC,QAAA,kBAAA,IAAA,CAAc,mBAAb,EACC,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAc,YAAA,CAAA,OAAA,EAAb,EAAqB,OAAA,EAAO,IAAA,EAAE,QAAA,EAAS,CAAA;AAAA,oBACxC,GAAA,CAAc,qBAAb,EACC,QAAA,kBAAA,IAAA;AAAA,MAAc,YAAA,CAAA,OAAA;AAAA,MAAb;AAAA,QACC,IAAA;AAAA,QACA,UAAA,EAAY,CAAA;AAAA,QACZ,SAAA,EAAW,EAAA;AAAA,UACT,6CAAA;AAAA,UACA,+CAAA;AAAA,UACA,qEAAA;AAAA,UACA,4BAAA;AAAA,UACA;AAAA,SACF;AAAA,QAEC,QAAA,EAAA;AAAA,UAAA,OAAA;AAAA,0BACD,GAAA,CAAc,YAAA,CAAA,KAAA,EAAb,EAAmB,SAAA,EAAU,wBAAA,EAAyB;AAAA;AAAA;AAAA,KACzD,EACF;AAAA,GAAA,EACF,CAAA,EACF,CAAA;AAEJ","file":"Tooltip.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merge Tailwind class names without conflicts.\n *\n * `clsx` handles conditional inclusion (booleans, undefineds, arrays);\n * `twMerge` resolves conflicting Tailwind classes (later wins, e.g.\n * `px-2 px-4` → `px-4`). Together they are the de facto standard for\n * composing Tailwind classes in React components.\n *\n * @example\n * cn('px-2 py-1', isActive && 'bg-primary', className)\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","'use client';\n\nimport { type ReactNode } from 'react';\nimport * as RadixTooltip from '@radix-ui/react-tooltip';\nimport { cn } from '../../utils/cn';\n\n/**\n * Tooltip — Radix-wrapped popover that appears on hover + focus.\n *\n * Requires a single TooltipProvider near the app root for delay coordination.\n * For simple use, the default delay (700ms) is fine.\n *\n * @example\n * <Tooltip content=\"Send message\">\n * <Button variant=\"ghost\"><Send /></Button>\n * </Tooltip>\n */\nexport interface TooltipProps {\n content: ReactNode;\n children: ReactNode;\n side?: 'top' | 'right' | 'bottom' | 'left';\n /** Delay before opening, in ms. Default 700 (Radix default). */\n delayMs?: number;\n className?: string;\n}\n\nexport function Tooltip({\n content,\n children,\n side = 'top',\n delayMs = 700,\n className,\n}: TooltipProps) {\n return (\n <RadixTooltip.Provider delayDuration={delayMs}>\n <RadixTooltip.Root>\n <RadixTooltip.Trigger asChild>{children}</RadixTooltip.Trigger>\n <RadixTooltip.Portal>\n <RadixTooltip.Content\n side={side}\n sideOffset={6}\n className={cn(\n 'z-50 select-none rounded-(--radius-control)',\n 'bg-(--el-tooltip-bg) text-(--el-tooltip-text)',\n 'px-(--spacing-tooltip-x) py-(--spacing-tooltip-y) font-sans text-xs',\n 'shadow-(--shadow-elevated)',\n className,\n )}\n >\n {content}\n <RadixTooltip.Arrow className=\"fill-(--el-tooltip-bg)\" />\n </RadixTooltip.Content>\n </RadixTooltip.Portal>\n </RadixTooltip.Root>\n </RadixTooltip.Provider>\n );\n}\n"]}
@@ -816,6 +816,16 @@ var pillVariants = cva(
816
816
  // carries a GLYPH, because a 14% tint is a subtle mark by construction
817
817
  // and finding #35 forbids resting a state on colour alone.
818
818
  implemented: "bg-[color-mix(in_srgb,var(--el-status-implemented)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent",
819
+ // A person's YES, and not yet shipped (MOTIR-5141). The SECOND
820
+ // per-status tone, on the same documented recipe `implemented` uses
821
+ // rather than a second one — `--el-status-approved` is the `done` green
822
+ // one ink step deeper, measured at ΔE2000 >= 10 against every other
823
+ // status hue in all ten palettes. Like `implemented` it carries a GLYPH
824
+ // (`CircleCheck`, in `StatusPill`), because a 14% tint is a subtle mark
825
+ // and finding #35 forbids resting a state on colour alone — and here it
826
+ // does more work than usual: at 14% the whole ramp compresses, and this
827
+ // chip's nearest neighbour is `done`'s.
828
+ approved: "bg-[color-mix(in_srgb,var(--el-status-approved)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent",
819
829
  done: "bg-(--el-tint-mint) text-(--el-text-strong) border-transparent"
820
830
  },
821
831
  severity: {
@@ -877,7 +887,18 @@ var pillVariants = cva(
877
887
  // severity. Dedicated `--el-archived-pill-*` tokens so a palette can tune
878
888
  // the inactive badge apart from the neutral chip; AA-safe (slate on muted
879
889
  // clears AA both themes).
880
- archived: "bg-(--el-archived-pill-bg) text-(--el-archived-pill-text) border-transparent"
890
+ archived: "bg-(--el-archived-pill-bg) text-(--el-archived-pill-text) border-transparent",
891
+ // A decision is WAITING on somebody (Story MOTIR-4778 · MOTIR-4792,
892
+ // design `design/work-items/approval-control.mock.html` — the approval
893
+ // frame's `pill-await`). Yellow, and the tint is chosen for DISTINCTNESS
894
+ // rather than for mood: the frame renders four chips a reader must tell
895
+ // apart at a glance — awaiting, recording (`severity="info"`, sky),
896
+ // approved (`severity="success"`, mint) and changes-requested
897
+ // (`severity="warning"`, peach) — and `--el-tint-yellow` was the only
898
+ // slot none of the other three had spent. `tone` rather than `severity`
899
+ // because waiting is not a severity; it sits beside `private` and
900
+ // `archived`, which are states rather than judgements.
901
+ awaiting: "bg-(--el-tint-yellow) text-(--el-text-strong) border-transparent"
881
902
  }
882
903
  },
883
904
  defaultVariants: {}