@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.
- package/dist/components/theme/StyleVignette.d.ts +4 -7
- package/dist/components/theme/StyleVignette.js +22 -1
- package/dist/components/theme/StyleVignette.js.map +1 -1
- package/dist/components/ui/Combobox.d.ts +8 -0
- package/dist/components/ui/Combobox.js +6 -3
- package/dist/components/ui/Combobox.js.map +1 -1
- package/dist/components/ui/Pill.d.ts +2 -2
- package/dist/components/ui/Pill.js +22 -1
- package/dist/components/ui/Pill.js.map +1 -1
- package/dist/components/ui/Popover.js +8 -1
- package/dist/components/ui/Popover.js.map +1 -1
- package/dist/components/ui/Toast.js +0 -2
- package/dist/components/ui/Toast.js.map +1 -1
- package/dist/components/ui/Tooltip.js +0 -1
- package/dist/components/ui/Tooltip.js.map +1 -1
- package/dist/specimen/TokensSpecimen.js +22 -1
- package/dist/specimen/TokensSpecimen.js.map +1 -1
- package/package.json +1 -1
- package/theme.css +769 -25
|
@@ -49,8 +49,9 @@ import { TypeId } from '../../theme/typography.js';
|
|
|
49
49
|
* base entry too.
|
|
50
50
|
* • TYPE (`type`) — ISOLATED, same mechanism plus a base `[data-type='motir']`
|
|
51
51
|
* block. That axis ships no descendant rules, so the tokens are all of it.
|
|
52
|
-
* • SHAPE (`styleId`, the token block) — ISOLATED.
|
|
53
|
-
*
|
|
52
|
+
* • SHAPE (`styleId`, the token block) — ISOLATED. Every style, including the
|
|
53
|
+
* Tier-0 `warm-editorial` base, has a `[data-style]` block that sets plain
|
|
54
|
+
* custom properties on the wrapper; they inherit down only that subtree.
|
|
54
55
|
* • MATERIAL (`styleId`, the descendant layer) — ISOLATED by the SCOPING
|
|
55
56
|
* LIMIT, since MOTIR-3997. The layer used to ship as descendant rules
|
|
56
57
|
* (`[data-style='X'] [data-surface=…]`, `[data-style='X'] body`, …), and a
|
|
@@ -64,11 +65,7 @@ import { TypeId } from '../../theme/typography.js';
|
|
|
64
65
|
* matrix measures 121 / 121. See
|
|
65
66
|
* `docs/decisions/scoped-preview-isolation.md` for the measurement, the two
|
|
66
67
|
* rejected candidates and what the mechanism costs.
|
|
67
|
-
*
|
|
68
|
-
* component today: do NOT add a `[data-style='warm-editorial']` base block
|
|
69
|
-
* as a remedy — the base ships no material layer, and such a block only
|
|
70
|
-
* makes a resolved-TOKEN assertion pass on a tile that still paints as the
|
|
71
|
-
* ancestor. And the mechanism is structural, not automatic: a material rule
|
|
68
|
+
* The mechanism is structural, not automatic: a material rule
|
|
72
69
|
* authored as a plain descendant crosses the boundary exactly as these did,
|
|
73
70
|
* which is what `tests/theme/styleRegistry.test.ts`'s 69-rule ratchet
|
|
74
71
|
* exists to catch.
|
|
@@ -169,6 +169,16 @@ var pillVariants = cva(
|
|
|
169
169
|
// carries a GLYPH, because a 14% tint is a subtle mark by construction
|
|
170
170
|
// and finding #35 forbids resting a state on colour alone.
|
|
171
171
|
implemented: "bg-[color-mix(in_srgb,var(--el-status-implemented)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent",
|
|
172
|
+
// A person's YES, and not yet shipped (MOTIR-5141). The SECOND
|
|
173
|
+
// per-status tone, on the same documented recipe `implemented` uses
|
|
174
|
+
// rather than a second one — `--el-status-approved` is the `done` green
|
|
175
|
+
// one ink step deeper, measured at ΔE2000 >= 10 against every other
|
|
176
|
+
// status hue in all ten palettes. Like `implemented` it carries a GLYPH
|
|
177
|
+
// (`CircleCheck`, in `StatusPill`), because a 14% tint is a subtle mark
|
|
178
|
+
// and finding #35 forbids resting a state on colour alone — and here it
|
|
179
|
+
// does more work than usual: at 14% the whole ramp compresses, and this
|
|
180
|
+
// chip's nearest neighbour is `done`'s.
|
|
181
|
+
approved: "bg-[color-mix(in_srgb,var(--el-status-approved)_14%,var(--el-surface))] text-(--el-text-strong) border-transparent",
|
|
172
182
|
done: "bg-(--el-tint-mint) text-(--el-text-strong) border-transparent"
|
|
173
183
|
},
|
|
174
184
|
severity: {
|
|
@@ -230,7 +240,18 @@ var pillVariants = cva(
|
|
|
230
240
|
// severity. Dedicated `--el-archived-pill-*` tokens so a palette can tune
|
|
231
241
|
// the inactive badge apart from the neutral chip; AA-safe (slate on muted
|
|
232
242
|
// clears AA both themes).
|
|
233
|
-
archived: "bg-(--el-archived-pill-bg) text-(--el-archived-pill-text) border-transparent"
|
|
243
|
+
archived: "bg-(--el-archived-pill-bg) text-(--el-archived-pill-text) border-transparent",
|
|
244
|
+
// A decision is WAITING on somebody (Story MOTIR-4778 · MOTIR-4792,
|
|
245
|
+
// design `design/work-items/approval-control.mock.html` — the approval
|
|
246
|
+
// frame's `pill-await`). Yellow, and the tint is chosen for DISTINCTNESS
|
|
247
|
+
// rather than for mood: the frame renders four chips a reader must tell
|
|
248
|
+
// apart at a glance — awaiting, recording (`severity="info"`, sky),
|
|
249
|
+
// approved (`severity="success"`, mint) and changes-requested
|
|
250
|
+
// (`severity="warning"`, peach) — and `--el-tint-yellow` was the only
|
|
251
|
+
// slot none of the other three had spent. `tone` rather than `severity`
|
|
252
|
+
// because waiting is not a severity; it sits beside `private` and
|
|
253
|
+
// `archived`, which are states rather than judgements.
|
|
254
|
+
awaiting: "bg-(--el-tint-yellow) text-(--el-text-strong) border-transparent"
|
|
234
255
|
}
|
|
235
256
|
},
|
|
236
257
|
defaultVariants: {}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/cn.ts","../../../src/components/ui/Spinner.tsx","../../../src/components/ui/Button.tsx","../../../src/components/ui/Card.tsx","../../../src/components/ui/Pill.tsx","../../../src/components/theme/StyleVignette.tsx"],"names":["Spinner","cva","forwardRef","Button","jsx","Card","jsxs","Pill"],"mappings":"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","file":"StyleVignette.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 * Spinner — pure CSS rotation indicator.\n *\n * Three sizes; works standalone or inside a Button's loading state.\n *\n * @example\n * <Spinner size=\"md\" />\n * <Spinner size=\"sm\" aria-label=\"Loading\" />\n */\nconst spinnerVariants = cva(\n 'inline-block animate-spin rounded-full border-current border-t-transparent',\n {\n variants: {\n size: {\n sm: 'h-4 w-4 border-[1.5px]',\n md: 'h-5 w-5 border-2',\n lg: 'h-8 w-8 border-[3px]',\n },\n },\n defaultVariants: { size: 'md' },\n },\n);\n\nexport interface SpinnerProps\n extends Omit<HTMLAttributes<HTMLSpanElement>, 'role'>, VariantProps<typeof spinnerVariants> {}\n\nexport const Spinner = forwardRef<HTMLSpanElement, SpinnerProps>(function Spinner(\n { className, size, ...rest },\n ref,\n) {\n return (\n <span\n ref={ref}\n role=\"status\"\n aria-label={rest['aria-label'] ?? 'Loading'}\n className={cn(spinnerVariants({ size }), className)}\n {...rest}\n />\n );\n});\n","import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { Spinner } from './Spinner';\nimport { cn } from '../../utils/cn';\n\n/**\n * Button — primary interactive primitive.\n *\n * Variants × sizes × states. The `loading` state shows an inline Spinner\n * and disables the button. Use semantic Tailwind token classes; never\n * hardcode colors. Shape responds to `data-style` via\n * `--radius-btn` (rectangles in default, pills in `soft`).\n *\n * @example\n * <Button variant=\"primary\" size=\"md\">Save</Button>\n * <Button variant=\"ghost\" leftIcon={<Plus />} loading>Saving…</Button>\n */\n// Exported so a Next `<Link>` can render AS a styled button (an anchor styled\n// with the button variants — keeps real-link semantics for navigation) without\n// nesting a <button> inside an <a>. Used by the /ready empty state's \"View all\n// issues\" link (Subtask 7.0.6).\nexport const buttonVariants = cva(\n cn(\n // Base layout\n 'inline-flex items-center justify-center gap-2',\n // Typography\n 'font-sans text-sm font-medium leading-none',\n // Shape — semantic tokens that flip with the active style\n 'rounded-(--radius-btn)',\n // Interaction\n 'transition-[transform,background-color,border-color,color] duration-(--transition-duration)',\n 'active:scale-(--active-scale)',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--focus-ring-color) focus-visible:ring-offset-2 focus-visible:ring-offset-background',\n 'disabled:pointer-events-none disabled:opacity-50',\n ),\n {\n variants: {\n variant: {\n primary: 'bg-(--el-accent) text-(--el-accent-text) hover:opacity-90',\n secondary:\n 'bg-transparent text-(--el-text) border border-(--el-button-border) hover:bg-(--el-surface)',\n ghost: 'bg-transparent text-(--el-text) hover:bg-(--el-surface)',\n danger: 'bg-(--el-danger) text-(--el-danger-text) hover:opacity-90',\n },\n size: {\n sm: 'h-(--height-btn-sm) px-3 text-xs',\n md: 'h-(--height-btn-md) px-(--spacing-btn-x)',\n lg: 'h-(--height-btn-lg) px-6 text-base',\n },\n },\n defaultVariants: {\n variant: 'primary',\n size: 'md',\n },\n },\n);\n\nexport interface ButtonProps\n extends ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {\n /** Shows inline Spinner + disables interaction. */\n loading?: boolean;\n /** Icon rendered before the label. */\n leftIcon?: ReactNode;\n /** Icon rendered after the label. */\n rightIcon?: ReactNode;\n}\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n { variant, size, loading, leftIcon, rightIcon, disabled, className, children, ...rest },\n ref,\n) {\n return (\n <button\n ref={ref}\n type=\"button\"\n // `data-variant` is the hook the 3D / Immersive style uses to give FILLED\n // buttons physical thickness (a solid palette-derived base edge) + a\n // press-DOWN (7.3.39). Inert under every other style.\n data-variant={variant ?? 'primary'}\n className={cn(buttonVariants({ variant, size }), className)}\n disabled={disabled || loading}\n aria-busy={loading || undefined}\n {...rest}\n >\n {loading ? (\n <Spinner size={size === 'lg' ? 'md' : 'sm'} aria-hidden />\n ) : leftIcon ? (\n <span aria-hidden className=\"inline-flex\">\n {leftIcon}\n </span>\n ) : null}\n <span>{children}</span>\n {!loading && rightIcon ? (\n <span aria-hidden className=\"inline-flex\">\n {rightIcon}\n </span>\n ) : null}\n </button>\n );\n});\n","import { forwardRef, type HTMLAttributes, type ReactNode } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../../utils/cn';\n\n/**\n * Card — content container with optional header/footer slots and tint variants.\n *\n * Compose with `header` / `footer` props for the common pattern, or just\n * pass children for a bare card. `clickable` makes the whole card a button\n * (use `onClick` + Enter/Space; pass `href` to render as `<a>` instead).\n *\n * Tint variants use the pastel feature tints from the palette\n * (peach/rose/mint/lavender/sky/yellow) — apply sparingly, never to\n * page-level surfaces.\n *\n * @example\n * <Card header={<h3>Title</h3>}>Body content</Card>\n * <Card tint=\"lavender\" clickable onClick={open}>Pastel card</Card>\n * <Card tint=\"mint\"><a href=\"/x\">Link wrapper</a></Card>\n */\nconst cardVariants = cva(\n cn(\n 'rounded-(--radius-card) border border-(--el-border)',\n 'p-(--spacing-card-padding)',\n 'transition-shadow duration-(--transition-duration)',\n ),\n {\n variants: {\n tint: {\n none: 'bg-(--el-card)',\n peach: 'bg-(--el-tint-peach) border-transparent',\n rose: 'bg-(--el-tint-rose) border-transparent',\n mint: 'bg-(--el-tint-mint) border-transparent',\n lavender: 'bg-(--el-tint-lavender) border-transparent',\n sky: 'bg-(--el-tint-sky) border-transparent',\n yellow: 'bg-(--el-tint-yellow) border-transparent',\n },\n clickable: {\n true: 'cursor-pointer hover:shadow-(--shadow-card) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--focus-ring-color) focus-visible:ring-offset-2 focus-visible:ring-offset-background',\n false: '',\n },\n },\n defaultVariants: { tint: 'none', clickable: false },\n },\n);\n\nexport interface CardProps\n extends Omit<HTMLAttributes<HTMLDivElement>, 'children'>, VariantProps<typeof cardVariants> {\n header?: ReactNode;\n footer?: ReactNode;\n children?: ReactNode;\n}\n\nexport const Card = forwardRef<HTMLDivElement, CardProps>(function Card(\n { tint, clickable, header, footer, className, children, ...rest },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cn(cardVariants({ tint, clickable }), className)}\n // `data-tilt` is the hook the 3D / Immersive style's pointer-parallax\n // engine (ImmersiveTilt, 7.3.39) uses to tip this tile toward the cursor.\n // Inert for every other style + under reduced motion; harmless otherwise.\n data-tilt=\"\"\n // `data-surface` is the hook a surface-MATERIAL style (e.g. glassmorphism)\n // uses to frost this panel — see globals.css's material layer. Only the\n // default (untinted) card opts in; a pastel-tint feature card keeps its\n // opaque tint, never a frosted overlay that would wash the hue out.\n {...(tint && tint !== 'none' ? {} : { 'data-surface': 'card' })}\n {...(clickable ? { tabIndex: 0, role: 'button' } : {})}\n {...rest}\n >\n {/* `data-tilt-layer` lifts the slot onto a translateZ plane under the 3D /\n Immersive style (7.3.39) so it PARALLAXES above the card face as the\n card tilts — `front` (the header/title pops most), `back` (the footer\n sits nearer the face). Inert under every other style + reduced motion.\n The body stays on the face so arbitrary content never renders on a\n skewed Z-plane. */}\n {header ? (\n <div data-tilt-layer=\"front\" className=\"mb-(--spacing-md)\">\n {header}\n </div>\n ) : null}\n <div>{children}</div>\n {footer ? (\n <div\n data-tilt-layer=\"back\"\n className=\"border-(--el-border) mt-(--spacing-md) border-t pt-(--spacing-md)\"\n >\n {footer}\n </div>\n ) : null}\n </div>\n );\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","import type { ReactNode } from 'react';\nimport { BookOpen, Search, SlidersHorizontal } from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { buttonVariants } from '../ui/Button';\nimport { Card } from '../ui/Card';\nimport { Pill } from '../ui/Pill';\nimport type { StyleId } from '../../theme/styles';\nimport type { PaletteId } from '../../theme/palettes';\nimport type { TypeId } from '../../theme/typography';\n\n/**\n * StyleVignette — the LIVE preview specimen that makes a design axis's FEEL\n * legible (Subtask 7.3.37 / MOTIR-1050).\n *\n * The fix for \"I only see typography change\": instead of a token-swatch table,\n * this is a composed, realistic MINI-SURFACE — a nav rail, a work-item card, a\n * search input, a button row, and a floating modal — rendered LIVE under a\n * style's shape/feel tokens + component overrides (and, independently, a palette\n * and a type pairing). Switching any axis re-shapes / re-skins / re-types the\n * whole vignette, so the user sees the product's feel, not a colour chip.\n *\n * ── Two modes ────────────────────────────────────────────────────────────\n * • LIVE (no axis props) — the wrapper sets NO `data-*` axis attribute, so it\n * INHERITS the active theme from `<html>` (driven by `theme-context`). Use\n * this for the appearance-settings preview and the design-step showcase: the\n * vignette re-renders automatically as the global selection changes.\n * • SCOPED (any of `styleId` / `palette` / `type`) — the wrapper carries that\n * axis attribute, so the globals.css axis block recomputes the tokens for\n * this subtree ONLY. Use this for the onboarding Style gallery (one vignette\n * per style) and the Palette step (one style re-rendered under each palette).\n *\n * Built on the `app/tokens/page.tsx` specimen pattern: real `--el-*` colour\n * tokens + element-semantic shape tokens + the `data-surface` material hooks, so\n * surface-material styles (glassmorphism's frosted glass, cybercore's glow grid)\n * apply exactly as they do in the shipped app. NEVER a Tier-0 `--color-*` or a\n * raw `rounded-*`/`p-*`/`h-*` (the colour + shape token rules in CLAUDE.md).\n *\n * ⚠️ WHAT A SCOPED VIGNETTE IS AND IS NOT ISOLATED FROM (rewritten, MOTIR-3933).\n * This paragraph used to describe the nested-base leak and then discharge it\n * with \"in the real consumers this never bites\". That was a claim about the\n * consumer set at the time of writing, recorded as though it were a property of\n * the component — and it is what let a public page ship ten identical palette\n * previews. The consumer set grew. Measured, not assumed:\n *\n * • COLOUR (`palette`) — ISOLATED. The wrapper carries `data-appearance-scope`\n * alongside the axis attribute, which re-emits the Tier-3 `--el-*` layer\n * onto this element so it reads the `--color-*` this subtree overrides\n * rather than the ones `:root` declared. theme.css states that requirement\n * at the declaration site; this component did not honour it, and without it\n * a scoped palette changed nothing the vignette paints with. The base\n * `motir` entry now has its own block, and every per-palette dark block has\n * a descendant arm, so a scoped tile is correct in BOTH themes and for the\n * base entry too.\n * • TYPE (`type`) — ISOLATED, same mechanism plus a base `[data-type='motir']`\n * block. That axis ships no descendant rules, so the tokens are all of it.\n * • SHAPE (`styleId`, the token block) — ISOLATED. A `[data-style]` block sets\n * plain custom properties on the wrapper and they inherit down the subtree.\n * • MATERIAL (`styleId`, the descendant layer) — ISOLATED by the SCOPING\n * LIMIT, since MOTIR-3997. The layer used to ship as descendant rules\n * (`[data-style='X'] [data-surface=…]`, `[data-style='X'] body`, …), and a\n * descendant combinator does not stop at a nested `data-style`: under an\n * active `glassmorphism` only 1 of 11 scoped tiles wore its own material,\n * under `aurora` 2 of 11, 64 of 121 over the full matrix. All 69 rules now\n * ship as `@scope ([data-style='X']) to ([data-style]) { … }`, where\n * `to (…)` is a scoping LIMIT — THIS component's `data-style` wrapper is\n * what an ancestor style's scope stops at, so \"the nearest `[data-style]`\n * ancestor wins\" is structural rather than a per-rule opt-out. The full\n * matrix measures 121 / 121. See\n * `docs/decisions/scoped-preview-isolation.md` for the measurement, the two\n * rejected candidates and what the mechanism costs.\n * Two things that follow, and neither is a claim about who consumes this\n * component today: do NOT add a `[data-style='warm-editorial']` base block\n * as a remedy — the base ships no material layer, and such a block only\n * makes a resolved-TOKEN assertion pass on a tile that still paints as the\n * ancestor. And the mechanism is structural, not automatic: a material rule\n * authored as a plain descendant crosses the boundary exactly as these did,\n * which is what `tests/theme/styleRegistry.test.ts`'s 69-rule ratchet\n * exists to catch.\n */\n\nexport interface StyleVignetteProps {\n /** Scope the vignette to a specific style; omit to inherit the active style. */\n styleId?: StyleId;\n /** Scope the vignette to a specific palette; omit to inherit the active palette. */\n palette?: PaletteId;\n /** Scope the vignette to a specific type pairing; omit to inherit the active type. */\n type?: TypeId;\n /**\n * Accessible name for the preview region. The vignette is decorative chrome,\n * so it is announced as a single labelled image rather than a pile of\n * read-out controls.\n */\n label?: string;\n className?: string;\n}\n\n/** A muted progress bar; `accent` tints it with the active accent. */\nfunction Bar({ accent, widthPct }: { accent?: boolean; widthPct: number }) {\n return (\n <div className=\"h-[6px] w-full overflow-hidden rounded-(--radius-badge) bg-(--el-muted)\">\n <div\n className={cn(\n 'h-full rounded-(--radius-badge)',\n accent ? 'bg-(--el-accent)' : 'bg-(--el-border-strong)',\n )}\n style={{ width: `${widthPct}%` }}\n />\n </div>\n );\n}\n\n/** One row of the work-item card's mini list — a status dot + label + chip. */\nfunction ListRow({ dotVar, label, chip }: { dotVar: string; label: string; chip: string }) {\n return (\n <div className=\"flex items-center gap-2 text-[11px] text-(--el-text-secondary)\">\n <span\n className=\"size-2 shrink-0 rounded-full\"\n style={{ background: `var(${dotVar})` }}\n aria-hidden\n />\n <span className=\"truncate\">{label}</span>\n <span className=\"ml-auto shrink-0 rounded-(--radius-badge) bg-(--el-surface) px-(--spacing-chip-x) py-(--spacing-chip-y) text-[9px] font-medium text-(--el-text-secondary)\">\n {chip}\n </span>\n </div>\n );\n}\n\nexport function StyleVignette({ styleId, palette, type, label, className }: StyleVignetteProps) {\n // Only emit an axis attribute when the caller pins it; an omitted axis stays\n // inherited (LIVE mode), which is how the appearance preview follows the\n // global selection without re-implementing the theme context.\n const axisAttrs: Record<string, string> = {};\n if (styleId) axisAttrs['data-style'] = styleId;\n if (palette) axisAttrs['data-palette'] = palette;\n if (type) axisAttrs['data-type'] = type;\n // SCOPED mode only. `data-appearance-scope` re-emits the Tier-3 `--el-*`\n // layer onto this element, which is what makes the axis attributes above\n // actually change what the subtree paints with: `--el-*` reference\n // `--color-*` and `var()` resolves at the DECLARING element, so without it a\n // nested `data-palette` overrides Tier-0 that nothing here reads.\n //\n // ⚠️ NOT in LIVE mode. With no axis prop the vignette is meant to FOLLOW the\n // global selection, and re-emitting the layer locally would pin the subtree\n // to whatever `--color-*` resolved at mount — the exact opposite of what the\n // appearance preview mounts it for.\n if (Object.keys(axisAttrs).length > 0) axisAttrs['data-appearance-scope'] = '';\n\n return (\n <div\n {...axisAttrs}\n role=\"img\"\n aria-label={label ?? 'Design style preview'}\n className={cn(\n 'style-vignette relative isolate overflow-hidden',\n 'rounded-(--radius-card) border border-(--el-border) bg-(--el-surface)',\n 'shadow-(--shadow-subtle)',\n className,\n )}\n >\n {/* Material canvas: transparent for flat styles; the glassmorphism gradient\n / cybercore grid is painted here via the descendant rules in\n globals.css (a preview can't lean on the body-scoped canvas). */}\n <span className=\"sv-canvas pointer-events-none absolute inset-0 -z-10\" aria-hidden />\n\n <div className=\"flex flex-col gap-3 p-3\">\n {/* NAV — a compact rail; data-surface lets a material style frost it. */}\n <div\n data-surface=\"sidebar\"\n className=\"flex items-center gap-2 rounded-(--radius-control) bg-(--el-sidebar-bg) px-3 py-2\"\n >\n <span className=\"size-2.5 rounded-full bg-(--el-accent)\" aria-hidden />\n <span className=\"font-serif text-[12px] font-semibold text-(--el-text)\">Acme</span>\n <span className=\"ml-2 rounded-(--radius-control) bg-(--el-accent) px-2 py-[3px] text-[10px] font-medium text-(--el-accent-text)\">\n Board\n </span>\n <span className=\"text-[10px] text-(--el-text-secondary)\">Backlog</span>\n <span className=\"text-[10px] text-(--el-text-secondary)\">Reports</span>\n <span className=\"ml-auto size-5 rounded-full bg-(--el-muted)\" aria-hidden />\n </div>\n\n <div className=\"grid grid-cols-[1.4fr_1fr] gap-3\">\n {/* WORK-ITEM CARD — the centrepiece: type hue + serif title + status. */}\n <Card className=\"flex flex-col gap-2 !p-3\">\n <div className=\"flex items-center gap-2\">\n {/* A decorative story-type icon in its `--el-type-story` hue. The\n package can't depend on motir-core's domain `IssueTypeIcon`\n (which maps work-item kinds → glyphs), so the vignette inlines\n the same glyph + hue directly — pixel-identical, boundary-clean. */}\n <BookOpen className=\"size-4 text-(--el-type-story)\" aria-hidden />\n <span className=\"truncate font-serif text-[13px] font-semibold text-(--el-text)\">\n Ship the billing flow\n </span>\n <Pill status=\"in-progress\" className=\"ml-auto !text-[9px]\">\n In progress\n </Pill>\n </div>\n <div className=\"flex flex-col gap-1.5\">\n <ListRow dotVar=\"--el-success\" label=\"Invoice schema\" chip=\"Done\" />\n <ListRow dotVar=\"--el-warning\" label=\"Dunning emails\" chip=\"Review\" />\n <ListRow dotVar=\"--el-info\" label=\"Stripe webhook\" chip=\"To do\" />\n </div>\n <div className=\"mt-0.5 flex flex-col gap-1.5\">\n <Bar accent widthPct={66} />\n <Bar widthPct={38} />\n </div>\n </Card>\n\n {/* MODAL — a floating dialog panel; modal radius + elevation + scrim. */}\n <div className=\"relative\">\n <span\n className=\"pointer-events-none absolute -inset-1 rounded-(--radius-card) bg-black/10\"\n aria-hidden\n />\n <div\n data-surface=\"modal\"\n className=\"relative flex flex-col gap-2 rounded-(--radius-modal) border border-(--el-border) bg-(--el-page-bg) p-3 shadow-(--shadow-modal)\"\n >\n <span className=\"font-serif text-[12px] font-semibold text-(--el-text)\">\n New invoice\n </span>\n <Bar widthPct={100} />\n <Bar widthPct={72} />\n <div className=\"mt-1 flex gap-2\">\n <span\n className={cn(\n buttonVariants({ variant: 'primary', size: 'sm' }),\n '!h-7 !px-3 !text-[10px]',\n )}\n >\n Create\n </span>\n <span\n className={cn(\n buttonVariants({ variant: 'ghost', size: 'sm' }),\n '!h-7 !px-3 !text-[10px]',\n )}\n >\n Cancel\n </span>\n </div>\n </div>\n </div>\n </div>\n\n {/* INPUT — a search field; data-surface lets a material style fill it. */}\n <div\n data-surface=\"input\"\n className=\"flex h-(--height-input) items-center gap-2 rounded-(--radius-input) border border-(--el-border-strong) bg-(--el-page-bg) px-(--spacing-input-x) text-[11px] text-(--el-text-secondary)\"\n >\n <Search className=\"size-3.5\" aria-hidden />\n <span>Search work items…</span>\n </div>\n\n {/* BUTTON ROW — primary + ghost, exact Button silhouettes (non-interactive). */}\n <div className=\"flex gap-2\">\n <span className={cn(buttonVariants({ variant: 'primary', size: 'sm' }), '!text-[11px]')}>\n New work item\n </span>\n <span className={cn(buttonVariants({ variant: 'ghost', size: 'sm' }), '!text-[11px]')}>\n <SlidersHorizontal className=\"size-3.5\" aria-hidden />\n Filter\n </span>\n </div>\n </div>\n </div>\n );\n}\n\nexport default StyleVignette as (props: StyleVignetteProps) => ReactNode;\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/cn.ts","../../../src/components/ui/Spinner.tsx","../../../src/components/ui/Button.tsx","../../../src/components/ui/Card.tsx","../../../src/components/ui/Pill.tsx","../../../src/components/theme/StyleVignette.tsx"],"names":["Spinner","cva","forwardRef","Button","jsx","Card","jsxs","Pill"],"mappings":"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","file":"StyleVignette.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 * Spinner — pure CSS rotation indicator.\n *\n * Three sizes; works standalone or inside a Button's loading state.\n *\n * @example\n * <Spinner size=\"md\" />\n * <Spinner size=\"sm\" aria-label=\"Loading\" />\n */\nconst spinnerVariants = cva(\n 'inline-block animate-spin rounded-full border-current border-t-transparent',\n {\n variants: {\n size: {\n sm: 'h-4 w-4 border-[1.5px]',\n md: 'h-5 w-5 border-2',\n lg: 'h-8 w-8 border-[3px]',\n },\n },\n defaultVariants: { size: 'md' },\n },\n);\n\nexport interface SpinnerProps\n extends Omit<HTMLAttributes<HTMLSpanElement>, 'role'>, VariantProps<typeof spinnerVariants> {}\n\nexport const Spinner = forwardRef<HTMLSpanElement, SpinnerProps>(function Spinner(\n { className, size, ...rest },\n ref,\n) {\n return (\n <span\n ref={ref}\n role=\"status\"\n aria-label={rest['aria-label'] ?? 'Loading'}\n className={cn(spinnerVariants({ size }), className)}\n {...rest}\n />\n );\n});\n","import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { Spinner } from './Spinner';\nimport { cn } from '../../utils/cn';\n\n/**\n * Button — primary interactive primitive.\n *\n * Variants × sizes × states. The `loading` state shows an inline Spinner\n * and disables the button. Use semantic Tailwind token classes; never\n * hardcode colors. Shape responds to `data-style` via\n * `--radius-btn` (rectangles in default, pills in `soft`).\n *\n * @example\n * <Button variant=\"primary\" size=\"md\">Save</Button>\n * <Button variant=\"ghost\" leftIcon={<Plus />} loading>Saving…</Button>\n */\n// Exported so a Next `<Link>` can render AS a styled button (an anchor styled\n// with the button variants — keeps real-link semantics for navigation) without\n// nesting a <button> inside an <a>. Used by the /ready empty state's \"View all\n// issues\" link (Subtask 7.0.6).\nexport const buttonVariants = cva(\n cn(\n // Base layout\n 'inline-flex items-center justify-center gap-2',\n // Typography\n 'font-sans text-sm font-medium leading-none',\n // Shape — semantic tokens that flip with the active style\n 'rounded-(--radius-btn)',\n // Interaction\n 'transition-[transform,background-color,border-color,color] duration-(--transition-duration)',\n 'active:scale-(--active-scale)',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--focus-ring-color) focus-visible:ring-offset-2 focus-visible:ring-offset-background',\n 'disabled:pointer-events-none disabled:opacity-50',\n ),\n {\n variants: {\n variant: {\n primary: 'bg-(--el-accent) text-(--el-accent-text) hover:opacity-90',\n secondary:\n 'bg-transparent text-(--el-text) border border-(--el-button-border) hover:bg-(--el-surface)',\n ghost: 'bg-transparent text-(--el-text) hover:bg-(--el-surface)',\n danger: 'bg-(--el-danger) text-(--el-danger-text) hover:opacity-90',\n },\n size: {\n sm: 'h-(--height-btn-sm) px-3 text-xs',\n md: 'h-(--height-btn-md) px-(--spacing-btn-x)',\n lg: 'h-(--height-btn-lg) px-6 text-base',\n },\n },\n defaultVariants: {\n variant: 'primary',\n size: 'md',\n },\n },\n);\n\nexport interface ButtonProps\n extends ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {\n /** Shows inline Spinner + disables interaction. */\n loading?: boolean;\n /** Icon rendered before the label. */\n leftIcon?: ReactNode;\n /** Icon rendered after the label. */\n rightIcon?: ReactNode;\n}\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n { variant, size, loading, leftIcon, rightIcon, disabled, className, children, ...rest },\n ref,\n) {\n return (\n <button\n ref={ref}\n type=\"button\"\n // `data-variant` is the hook the 3D / Immersive style uses to give FILLED\n // buttons physical thickness (a solid palette-derived base edge) + a\n // press-DOWN (7.3.39). Inert under every other style.\n data-variant={variant ?? 'primary'}\n className={cn(buttonVariants({ variant, size }), className)}\n disabled={disabled || loading}\n aria-busy={loading || undefined}\n {...rest}\n >\n {loading ? (\n <Spinner size={size === 'lg' ? 'md' : 'sm'} aria-hidden />\n ) : leftIcon ? (\n <span aria-hidden className=\"inline-flex\">\n {leftIcon}\n </span>\n ) : null}\n <span>{children}</span>\n {!loading && rightIcon ? (\n <span aria-hidden className=\"inline-flex\">\n {rightIcon}\n </span>\n ) : null}\n </button>\n );\n});\n","import { forwardRef, type HTMLAttributes, type ReactNode } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../../utils/cn';\n\n/**\n * Card — content container with optional header/footer slots and tint variants.\n *\n * Compose with `header` / `footer` props for the common pattern, or just\n * pass children for a bare card. `clickable` makes the whole card a button\n * (use `onClick` + Enter/Space; pass `href` to render as `<a>` instead).\n *\n * Tint variants use the pastel feature tints from the palette\n * (peach/rose/mint/lavender/sky/yellow) — apply sparingly, never to\n * page-level surfaces.\n *\n * @example\n * <Card header={<h3>Title</h3>}>Body content</Card>\n * <Card tint=\"lavender\" clickable onClick={open}>Pastel card</Card>\n * <Card tint=\"mint\"><a href=\"/x\">Link wrapper</a></Card>\n */\nconst cardVariants = cva(\n cn(\n 'rounded-(--radius-card) border border-(--el-border)',\n 'p-(--spacing-card-padding)',\n 'transition-shadow duration-(--transition-duration)',\n ),\n {\n variants: {\n tint: {\n none: 'bg-(--el-card)',\n peach: 'bg-(--el-tint-peach) border-transparent',\n rose: 'bg-(--el-tint-rose) border-transparent',\n mint: 'bg-(--el-tint-mint) border-transparent',\n lavender: 'bg-(--el-tint-lavender) border-transparent',\n sky: 'bg-(--el-tint-sky) border-transparent',\n yellow: 'bg-(--el-tint-yellow) border-transparent',\n },\n clickable: {\n true: 'cursor-pointer hover:shadow-(--shadow-card) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--focus-ring-color) focus-visible:ring-offset-2 focus-visible:ring-offset-background',\n false: '',\n },\n },\n defaultVariants: { tint: 'none', clickable: false },\n },\n);\n\nexport interface CardProps\n extends Omit<HTMLAttributes<HTMLDivElement>, 'children'>, VariantProps<typeof cardVariants> {\n header?: ReactNode;\n footer?: ReactNode;\n children?: ReactNode;\n}\n\nexport const Card = forwardRef<HTMLDivElement, CardProps>(function Card(\n { tint, clickable, header, footer, className, children, ...rest },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cn(cardVariants({ tint, clickable }), className)}\n // `data-tilt` is the hook the 3D / Immersive style's pointer-parallax\n // engine (ImmersiveTilt, 7.3.39) uses to tip this tile toward the cursor.\n // Inert for every other style + under reduced motion; harmless otherwise.\n data-tilt=\"\"\n // `data-surface` is the hook a surface-MATERIAL style (e.g. glassmorphism)\n // uses to frost this panel — see globals.css's material layer. Only the\n // default (untinted) card opts in; a pastel-tint feature card keeps its\n // opaque tint, never a frosted overlay that would wash the hue out.\n {...(tint && tint !== 'none' ? {} : { 'data-surface': 'card' })}\n {...(clickable ? { tabIndex: 0, role: 'button' } : {})}\n {...rest}\n >\n {/* `data-tilt-layer` lifts the slot onto a translateZ plane under the 3D /\n Immersive style (7.3.39) so it PARALLAXES above the card face as the\n card tilts — `front` (the header/title pops most), `back` (the footer\n sits nearer the face). Inert under every other style + reduced motion.\n The body stays on the face so arbitrary content never renders on a\n skewed Z-plane. */}\n {header ? (\n <div data-tilt-layer=\"front\" className=\"mb-(--spacing-md)\">\n {header}\n </div>\n ) : null}\n <div>{children}</div>\n {footer ? (\n <div\n data-tilt-layer=\"back\"\n className=\"border-(--el-border) mt-(--spacing-md) border-t pt-(--spacing-md)\"\n >\n {footer}\n </div>\n ) : null}\n </div>\n );\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","import type { ReactNode } from 'react';\nimport { BookOpen, Search, SlidersHorizontal } from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { buttonVariants } from '../ui/Button';\nimport { Card } from '../ui/Card';\nimport { Pill } from '../ui/Pill';\nimport type { StyleId } from '../../theme/styles';\nimport type { PaletteId } from '../../theme/palettes';\nimport type { TypeId } from '../../theme/typography';\n\n/**\n * StyleVignette — the LIVE preview specimen that makes a design axis's FEEL\n * legible (Subtask 7.3.37 / MOTIR-1050).\n *\n * The fix for \"I only see typography change\": instead of a token-swatch table,\n * this is a composed, realistic MINI-SURFACE — a nav rail, a work-item card, a\n * search input, a button row, and a floating modal — rendered LIVE under a\n * style's shape/feel tokens + component overrides (and, independently, a palette\n * and a type pairing). Switching any axis re-shapes / re-skins / re-types the\n * whole vignette, so the user sees the product's feel, not a colour chip.\n *\n * ── Two modes ────────────────────────────────────────────────────────────\n * • LIVE (no axis props) — the wrapper sets NO `data-*` axis attribute, so it\n * INHERITS the active theme from `<html>` (driven by `theme-context`). Use\n * this for the appearance-settings preview and the design-step showcase: the\n * vignette re-renders automatically as the global selection changes.\n * • SCOPED (any of `styleId` / `palette` / `type`) — the wrapper carries that\n * axis attribute, so the globals.css axis block recomputes the tokens for\n * this subtree ONLY. Use this for the onboarding Style gallery (one vignette\n * per style) and the Palette step (one style re-rendered under each palette).\n *\n * Built on the `app/tokens/page.tsx` specimen pattern: real `--el-*` colour\n * tokens + element-semantic shape tokens + the `data-surface` material hooks, so\n * surface-material styles (glassmorphism's frosted glass, cybercore's glow grid)\n * apply exactly as they do in the shipped app. NEVER a Tier-0 `--color-*` or a\n * raw `rounded-*`/`p-*`/`h-*` (the colour + shape token rules in CLAUDE.md).\n *\n * ⚠️ WHAT A SCOPED VIGNETTE IS AND IS NOT ISOLATED FROM (rewritten, MOTIR-3933).\n * This paragraph used to describe the nested-base leak and then discharge it\n * with \"in the real consumers this never bites\". That was a claim about the\n * consumer set at the time of writing, recorded as though it were a property of\n * the component — and it is what let a public page ship ten identical palette\n * previews. The consumer set grew. Measured, not assumed:\n *\n * • COLOUR (`palette`) — ISOLATED. The wrapper carries `data-appearance-scope`\n * alongside the axis attribute, which re-emits the Tier-3 `--el-*` layer\n * onto this element so it reads the `--color-*` this subtree overrides\n * rather than the ones `:root` declared. theme.css states that requirement\n * at the declaration site; this component did not honour it, and without it\n * a scoped palette changed nothing the vignette paints with. The base\n * `motir` entry now has its own block, and every per-palette dark block has\n * a descendant arm, so a scoped tile is correct in BOTH themes and for the\n * base entry too.\n * • TYPE (`type`) — ISOLATED, same mechanism plus a base `[data-type='motir']`\n * block. That axis ships no descendant rules, so the tokens are all of it.\n * • SHAPE (`styleId`, the token block) — ISOLATED. Every style, including the\n * Tier-0 `warm-editorial` base, has a `[data-style]` block that sets plain\n * custom properties on the wrapper; they inherit down only that subtree.\n * • MATERIAL (`styleId`, the descendant layer) — ISOLATED by the SCOPING\n * LIMIT, since MOTIR-3997. The layer used to ship as descendant rules\n * (`[data-style='X'] [data-surface=…]`, `[data-style='X'] body`, …), and a\n * descendant combinator does not stop at a nested `data-style`: under an\n * active `glassmorphism` only 1 of 11 scoped tiles wore its own material,\n * under `aurora` 2 of 11, 64 of 121 over the full matrix. All 69 rules now\n * ship as `@scope ([data-style='X']) to ([data-style]) { … }`, where\n * `to (…)` is a scoping LIMIT — THIS component's `data-style` wrapper is\n * what an ancestor style's scope stops at, so \"the nearest `[data-style]`\n * ancestor wins\" is structural rather than a per-rule opt-out. The full\n * matrix measures 121 / 121. See\n * `docs/decisions/scoped-preview-isolation.md` for the measurement, the two\n * rejected candidates and what the mechanism costs.\n * The mechanism is structural, not automatic: a material rule\n * authored as a plain descendant crosses the boundary exactly as these did,\n * which is what `tests/theme/styleRegistry.test.ts`'s 69-rule ratchet\n * exists to catch.\n */\n\nexport interface StyleVignetteProps {\n /** Scope the vignette to a specific style; omit to inherit the active style. */\n styleId?: StyleId;\n /** Scope the vignette to a specific palette; omit to inherit the active palette. */\n palette?: PaletteId;\n /** Scope the vignette to a specific type pairing; omit to inherit the active type. */\n type?: TypeId;\n /**\n * Accessible name for the preview region. The vignette is decorative chrome,\n * so it is announced as a single labelled image rather than a pile of\n * read-out controls.\n */\n label?: string;\n className?: string;\n}\n\n/** A muted progress bar; `accent` tints it with the active accent. */\nfunction Bar({ accent, widthPct }: { accent?: boolean; widthPct: number }) {\n return (\n <div className=\"h-[6px] w-full overflow-hidden rounded-(--radius-badge) bg-(--el-muted)\">\n <div\n className={cn(\n 'h-full rounded-(--radius-badge)',\n accent ? 'bg-(--el-accent)' : 'bg-(--el-border-strong)',\n )}\n style={{ width: `${widthPct}%` }}\n />\n </div>\n );\n}\n\n/** One row of the work-item card's mini list — a status dot + label + chip. */\nfunction ListRow({ dotVar, label, chip }: { dotVar: string; label: string; chip: string }) {\n return (\n <div className=\"flex items-center gap-2 text-[11px] text-(--el-text-secondary)\">\n <span\n className=\"size-2 shrink-0 rounded-full\"\n style={{ background: `var(${dotVar})` }}\n aria-hidden\n />\n <span className=\"truncate\">{label}</span>\n <span className=\"ml-auto shrink-0 rounded-(--radius-badge) bg-(--el-surface) px-(--spacing-chip-x) py-(--spacing-chip-y) text-[9px] font-medium text-(--el-text-secondary)\">\n {chip}\n </span>\n </div>\n );\n}\n\nexport function StyleVignette({ styleId, palette, type, label, className }: StyleVignetteProps) {\n // Only emit an axis attribute when the caller pins it; an omitted axis stays\n // inherited (LIVE mode), which is how the appearance preview follows the\n // global selection without re-implementing the theme context.\n const axisAttrs: Record<string, string> = {};\n if (styleId) axisAttrs['data-style'] = styleId;\n if (palette) axisAttrs['data-palette'] = palette;\n if (type) axisAttrs['data-type'] = type;\n // SCOPED mode only. `data-appearance-scope` re-emits the Tier-3 `--el-*`\n // layer onto this element, which is what makes the axis attributes above\n // actually change what the subtree paints with: `--el-*` reference\n // `--color-*` and `var()` resolves at the DECLARING element, so without it a\n // nested `data-palette` overrides Tier-0 that nothing here reads.\n //\n // ⚠️ NOT in LIVE mode. With no axis prop the vignette is meant to FOLLOW the\n // global selection, and re-emitting the layer locally would pin the subtree\n // to whatever `--color-*` resolved at mount — the exact opposite of what the\n // appearance preview mounts it for.\n if (Object.keys(axisAttrs).length > 0) axisAttrs['data-appearance-scope'] = '';\n\n return (\n <div\n {...axisAttrs}\n role=\"img\"\n aria-label={label ?? 'Design style preview'}\n className={cn(\n 'style-vignette relative isolate overflow-hidden',\n 'rounded-(--radius-card) border border-(--el-border) bg-(--el-surface)',\n 'shadow-(--shadow-subtle)',\n className,\n )}\n >\n {/* Material canvas: transparent for flat styles; the glassmorphism gradient\n / cybercore grid is painted here via the descendant rules in\n globals.css (a preview can't lean on the body-scoped canvas). */}\n <span className=\"sv-canvas pointer-events-none absolute inset-0 -z-10\" aria-hidden />\n\n <div className=\"flex flex-col gap-3 p-3\">\n {/* NAV — a compact rail; data-surface lets a material style frost it. */}\n <div\n data-surface=\"sidebar\"\n className=\"flex items-center gap-2 rounded-(--radius-control) bg-(--el-sidebar-bg) px-3 py-2\"\n >\n <span className=\"size-2.5 rounded-full bg-(--el-accent)\" aria-hidden />\n <span className=\"font-serif text-[12px] font-semibold text-(--el-text)\">Acme</span>\n <span className=\"ml-2 rounded-(--radius-control) bg-(--el-accent) px-2 py-[3px] text-[10px] font-medium text-(--el-accent-text)\">\n Board\n </span>\n <span className=\"text-[10px] text-(--el-text-secondary)\">Backlog</span>\n <span className=\"text-[10px] text-(--el-text-secondary)\">Reports</span>\n <span className=\"ml-auto size-5 rounded-full bg-(--el-muted)\" aria-hidden />\n </div>\n\n <div className=\"grid grid-cols-[1.4fr_1fr] gap-3\">\n {/* WORK-ITEM CARD — the centrepiece: type hue + serif title + status. */}\n <Card className=\"flex flex-col gap-2 !p-3\">\n <div className=\"flex items-center gap-2\">\n {/* A decorative story-type icon in its `--el-type-story` hue. The\n package can't depend on motir-core's domain `IssueTypeIcon`\n (which maps work-item kinds → glyphs), so the vignette inlines\n the same glyph + hue directly — pixel-identical, boundary-clean. */}\n <BookOpen className=\"size-4 text-(--el-type-story)\" aria-hidden />\n <span className=\"truncate font-serif text-[13px] font-semibold text-(--el-text)\">\n Ship the billing flow\n </span>\n <Pill status=\"in-progress\" className=\"ml-auto !text-[9px]\">\n In progress\n </Pill>\n </div>\n <div className=\"flex flex-col gap-1.5\">\n <ListRow dotVar=\"--el-success\" label=\"Invoice schema\" chip=\"Done\" />\n <ListRow dotVar=\"--el-warning\" label=\"Dunning emails\" chip=\"Review\" />\n <ListRow dotVar=\"--el-info\" label=\"Stripe webhook\" chip=\"To do\" />\n </div>\n <div className=\"mt-0.5 flex flex-col gap-1.5\">\n <Bar accent widthPct={66} />\n <Bar widthPct={38} />\n </div>\n </Card>\n\n {/* MODAL — a floating dialog panel; modal radius + elevation + scrim. */}\n <div className=\"relative\">\n <span\n className=\"pointer-events-none absolute -inset-1 rounded-(--radius-card) bg-black/10\"\n aria-hidden\n />\n <div\n data-surface=\"modal\"\n className=\"relative flex flex-col gap-2 rounded-(--radius-modal) border border-(--el-border) bg-(--el-page-bg) p-3 shadow-(--shadow-modal)\"\n >\n <span className=\"font-serif text-[12px] font-semibold text-(--el-text)\">\n New invoice\n </span>\n <Bar widthPct={100} />\n <Bar widthPct={72} />\n <div className=\"mt-1 flex gap-2\">\n <span\n className={cn(\n buttonVariants({ variant: 'primary', size: 'sm' }),\n '!h-7 !px-3 !text-[10px]',\n )}\n >\n Create\n </span>\n <span\n className={cn(\n buttonVariants({ variant: 'ghost', size: 'sm' }),\n '!h-7 !px-3 !text-[10px]',\n )}\n >\n Cancel\n </span>\n </div>\n </div>\n </div>\n </div>\n\n {/* INPUT — a search field; data-surface lets a material style fill it. */}\n <div\n data-surface=\"input\"\n className=\"flex h-(--height-input) items-center gap-2 rounded-(--radius-input) border border-(--el-border-strong) bg-(--el-page-bg) px-(--spacing-input-x) text-[11px] text-(--el-text-secondary)\"\n >\n <Search className=\"size-3.5\" aria-hidden />\n <span>Search work items…</span>\n </div>\n\n {/* BUTTON ROW — primary + ghost, exact Button silhouettes (non-interactive). */}\n <div className=\"flex gap-2\">\n <span className={cn(buttonVariants({ variant: 'primary', size: 'sm' }), '!text-[11px]')}>\n New work item\n </span>\n <span className={cn(buttonVariants({ variant: 'ghost', size: 'sm' }), '!text-[11px]')}>\n <SlidersHorizontal className=\"size-3.5\" aria-hidden />\n Filter\n </span>\n </div>\n </div>\n </div>\n );\n}\n\nexport default StyleVignette as (props: StyleVignetteProps) => ReactNode;\n"]}
|
|
@@ -41,6 +41,14 @@ interface ComboboxOption<T extends string> {
|
|
|
41
41
|
* the listbox's option indices, so keyboard nav is unaffected.
|
|
42
42
|
*/
|
|
43
43
|
group?: string;
|
|
44
|
+
/**
|
|
45
|
+
* The option is shown but cannot be picked (MOTIR-5528 — a status move an
|
|
46
|
+
* approval holds). It stays FOCUSABLE and is announced as unavailable
|
|
47
|
+
* (`aria-disabled`), rather than skipped, so a keyboard reader learns the move
|
|
48
|
+
* exists and why it is held; a click or Enter on it does nothing. Its label
|
|
49
|
+
* reads in `--el-text-secondary`, never an opacity dim, which keeps AA.
|
|
50
|
+
*/
|
|
51
|
+
disabled?: boolean;
|
|
44
52
|
}
|
|
45
53
|
interface ComboboxProps<T extends string> {
|
|
46
54
|
options: ComboboxOption<T>[];
|
|
@@ -177,7 +177,7 @@ function Combobox({
|
|
|
177
177
|
}
|
|
178
178
|
function commit(i) {
|
|
179
179
|
const opt = filtered[i];
|
|
180
|
-
if (!opt) return;
|
|
180
|
+
if (!opt || opt.disabled) return;
|
|
181
181
|
onChange(opt.value);
|
|
182
182
|
closeAndRefocus();
|
|
183
183
|
}
|
|
@@ -267,11 +267,14 @@ function Combobox({
|
|
|
267
267
|
id: optionId(i),
|
|
268
268
|
role: "option",
|
|
269
269
|
"aria-selected": isSelected,
|
|
270
|
+
"aria-disabled": opt.disabled ? true : void 0,
|
|
270
271
|
onMouseEnter: () => setActiveIndex(i),
|
|
271
272
|
onClick: () => commit(i),
|
|
272
273
|
className: cn(
|
|
273
|
-
"flex
|
|
274
|
-
|
|
274
|
+
"flex items-center gap-2 rounded-(--radius-control) px-(--spacing-control-x) py-(--spacing-control-y) text-sm",
|
|
275
|
+
opt.disabled ? "cursor-default" : "cursor-pointer",
|
|
276
|
+
isActive ? "bg-(--el-option-active-bg)" : "",
|
|
277
|
+
opt.disabled ? "text-(--el-text-secondary)" : "text-(--el-text)"
|
|
275
278
|
),
|
|
276
279
|
children: [
|
|
277
280
|
opt.icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: ICON_SLOT, children: opt.icon }) : null,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/cn.ts","../../../src/components/ui/Combobox.tsx"],"names":["Fragment"],"mappings":"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","file":"Combobox.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 {\n Fragment,\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type CSSProperties,\n type ReactNode,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { Check, ChevronsUpDown } from 'lucide-react';\nimport { cn } from '../../utils/cn';\n\n// Run layout effects on the client, fall back to useEffect during SSR (the menu\n// only mounts client-side anyway — see the `mounted` gate below).\nconst useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;\n\n// `false` during SSR / first hydration render, `true` on the client thereafter —\n// gates the createPortal call so document.body is guaranteed present. Hydration-\n// safe (matches the server's `false`) and avoids setState-in-effect.\nconst subscribeNoop = () => () => {};\n\n/**\n * The LEADING-VISUAL slot, on the trigger and on every option row (MOTIR-3080).\n *\n * ⚠️ `inline-flex` is load-bearing, not decoration. The wrapper is a flex item of\n * its row, so IT is blockified — but whatever a caller passes INSIDE it is not,\n * and a plain `<span>` therefore stays `display: inline`, where width and height\n * do not apply at all. `StatusPicker`'s 10px status dot rendered as a 2 × 18\n * sliver for exactly that reason: three cards of palette work (MOTIR-1273 /\n * -2073 / -2075) measured that dot's HUE while its box was two pixels wide.\n *\n * It survived every check because the markup was right — the classes ask for\n * 10 × 10, `getComputedStyle` reports 10px, and only the USED value disagreed.\n * A used value exists only in a browser, so `tests/e2e/issue-detail-flow` MEASURES\n * it rather than asserting the element exists.\n *\n * Making the slot a flex CONTAINER blockifies its child, which is what the slot's\n * contract (\"a leading visual\") always implied. A lucide `<svg>` is replaced and\n * was never affected, so no existing caller's icon changes size.\n */\nconst ICON_SLOT = 'inline-flex shrink-0 items-center';\n\nfunction useMounted() {\n return useSyncExternalStore(\n subscribeNoop,\n () => true,\n () => false,\n );\n}\n\n/**\n * Combobox — an accessible select/combobox primitive (Subtask 2.3.4). A trigger\n * button (`role=\"combobox\"`, `aria-haspopup=\"listbox\"`) opens an anchored panel\n * holding an optional type-ahead filter and a `role=\"listbox\"` of\n * `role=\"option\"` rows. The active option is tracked with `aria-activedescendant`\n * on whichever control holds focus (the filter input when `searchable`, else the\n * listbox) — the CommandPalette pattern that already clears the STRICT axe sweep.\n *\n * Deliberately NOT built on the Radix Popover primitive: that injects\n * `aria-haspopup=\"dialog\"` + dialog focus semantics onto its trigger, which\n * conflicts with the listbox combobox pattern. This is a self-contained anchored\n * dropdown (click-outside + Escape + focus return handled here) so the ARIA is\n * exactly the WAI-ARIA combobox shape.\n *\n * Composed by `components/issues/TypePicker` (searchable=false, 5 options) and\n * `ParentPicker` (searchable, async candidate list).\n */\nexport interface ComboboxOption<T extends string> {\n value: T;\n /** Primary text — also the accessible name of the option. */\n label: string;\n /** Extra text matched by the filter (e.g. a PROD-N identifier). */\n keywords?: string;\n /** Leading visual (a kind icon); decorative — label carries the name. */\n icon?: ReactNode;\n /** Trailing muted text (e.g. the identifier). */\n secondary?: string;\n /** Trailing rich content pinned to the option row's far end (e.g. a state\n * Pill or a \"Linked to …\" chip in the PR-link picker, MOTIR-1596).\n * Decorative — the accessible name is `label`; renders after `secondary`. */\n trailing?: ReactNode;\n /**\n * Optional section label. When consecutive options carry different `group`\n * values, a non-interactive header row is rendered at each transition (the\n * advanced filter's \"Fields\" / \"Custom fields\" / \"Other\" field menu). Pass\n * options pre-sorted by group; headers follow list order, so a group whose\n * every option is filtered out disappears with it. Decorative — not part of\n * the listbox's option indices, so keyboard nav is unaffected.\n */\n group?: string;\n}\n\nexport interface ComboboxProps<T extends string> {\n options: ComboboxOption<T>[];\n value: T | null;\n onChange: (value: T) => void;\n /** Accessible name for the trigger + listbox. */\n label: string;\n /** Trigger text when nothing is selected. */\n placeholder?: string;\n searchable?: boolean;\n searchPlaceholder?: string;\n /** Empty-listbox content. A string, or rich nodes (e.g. a \"no matches\" line\n * plus a hint sub-line — use block `<span>`s, since this renders in a `<p>`). */\n emptyText?: ReactNode;\n loading?: boolean;\n loadingText?: string;\n disabled?: boolean;\n /** id for the trigger button. */\n id?: string;\n className?: string;\n /**\n * Mount already-open (Subtask 2.5.5) — for an inline-edit cell that renders the\n * picker on a single click, so the menu is open immediately (mirrors\n * `DatePicker`'s `autoOpen`). Focus lands on the search input / listbox.\n */\n autoOpen?: boolean;\n /**\n * Fired whenever the menu transitions open → closed (a pick, Escape,\n * click-outside, or toggling the trigger). The inline-edit cell uses it to\n * leave edit mode and return to the static pill/avatar (Subtask 2.5.5).\n */\n onClose?: () => void;\n /**\n * Server-driven search mode (Subtask 6.9.2 — the link picker). When\n * `onQueryChange` is provided the type-ahead query is CONTROLLED by the parent\n * (`query`) and client-side filtering is SKIPPED — `options` is taken as the\n * server's authoritative result for the current query (the parent debounces\n * its fetch off `onQueryChange`, the same contract as `MultiSelectPicker`).\n * Omit both for the default self-filtering behaviour. The parent owns the\n * query lifecycle, so opening the menu does NOT reset it.\n */\n query?: string;\n onQueryChange?: (query: string) => void;\n}\n\n// Walk up from a node to the nearest ancestor that clips overflow, classifying\n// it as scrolling (overflow auto/scroll — the menu can be scrolled into view)\n// or a hard `hidden`/`clip` box (no scroll — the menu is trapped). Used by the\n// inline (in-dialog) branch to decide whether to clamp + flip the menu (only a\n// hard non-scrolling clip needs it). Stops at <body>; returns null if nothing\n// up the chain clips.\nfunction nearestClipBox(el: HTMLElement): { box: HTMLElement; scrolls: boolean } | null {\n let node = el.parentElement;\n while (node && node !== document.body) {\n const oy = getComputedStyle(node).overflowY;\n if (oy === 'auto' || oy === 'scroll') return { box: node, scrolls: true };\n if (oy === 'hidden' || oy === 'clip') return { box: node, scrolls: false };\n node = node.parentElement;\n }\n return null;\n}\n\nexport function Combobox<T extends string>({\n options,\n value,\n onChange,\n label,\n placeholder = 'Select…',\n searchable = false,\n searchPlaceholder = 'Search…',\n emptyText = 'No matches',\n loading = false,\n loadingText = 'Loading…',\n disabled = false,\n id,\n className,\n autoOpen = false,\n onClose,\n query: controlledQuery,\n onQueryChange,\n}: ComboboxProps<T>) {\n const [open, setOpen] = useState(autoOpen);\n // Server-driven mode (6.9.2): when `onQueryChange` is provided the query is\n // controlled by the parent and client filtering is bypassed (the server's\n // `options` are authoritative). Otherwise the query is internal state.\n const serverFiltered = onQueryChange !== undefined;\n const [internalQuery, setInternalQuery] = useState('');\n const query = serverFiltered ? (controlledQuery ?? '') : internalQuery;\n const setQuery = (q: string) => (serverFiltered ? onQueryChange(q) : setInternalQuery(q));\n const [activeIndex, setActiveIndex] = useState(0);\n // The menu is portaled to <body> by default, so it escapes any clipping\n // ancestor — a short table's overflow:hidden\n // (bug-inline-edit-clipped-when-table-short) OR the Advanced-filter popover's\n // overflow-hidden + overflow-y-auto body (MOTIR-1346). We render INLINE only\n // inside a focus-trapping MODAL (the shared Modal primitive — `data-surface=\n // \"modal\"`; `aria-modal` covers any other), where a portaled menu would land\n // outside the dialog's focus scope (focus-trap war → a click on it hangs /\n // dismisses) AND the modal centers with a CSS transform that breaks a fixed-\n // positioned child's coords. A NON-modal Radix Popover (the filter builder,\n // promote-sprint, mark-duplicate — `data-surface=\"popover\"`) carries\n // `role=\"dialog\"` for a11y but does NOT trap focus, so it portals safely —\n // keyed on the modal surface, NOT `role`, so those popovers escape the clip\n // too. Only render the portal once mounted, since createPortal needs document.body.\n const mounted = useMounted();\n // Viewport-anchored position for the portaled menu + the listbox's available\n // height, recomputed from the trigger rect on open / scroll / resize.\n const [menuStyle, setMenuStyle] = useState<CSSProperties | null>(null);\n const [listMaxHeight, setListMaxHeight] = useState(256);\n // Inline (in-dialog) branch only: whether the menu is flipped ABOVE the\n // trigger because there's more room there than below within the dialog's\n // overflow-hidden clip box (bug-combobox-menu-clipped-inside-modal).\n const [inlineAbove, setInlineAbove] = useState(false);\n const baseId = useId();\n const listId = `${baseId}-listbox`;\n const optionId = (i: number) => `${baseId}-opt-${i}`;\n\n const containerRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const listRef = useRef<HTMLDivElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n\n const filtered = useMemo(() => {\n // Server-driven mode: `options` is already the query's result — never filter\n // it again client-side (a substring re-filter would hide trigram / token\n // matches the server legitimately returned).\n if (serverFiltered || !searchable || query.trim() === '') return options;\n const needle = query.trim().toLowerCase();\n return options.filter(\n (o) =>\n o.label.toLowerCase().includes(needle) ||\n (o.secondary ? o.secondary.toLowerCase().includes(needle) : false) ||\n (o.keywords ? o.keywords.toLowerCase().includes(needle) : false),\n );\n }, [options, query, searchable, serverFiltered]);\n\n const selected = value != null ? (options.find((o) => o.value === value) ?? null) : null;\n\n // The active row, clamped to the (possibly just-filtered) list — DERIVED, not\n // an effect, so a shrinking filter never needs a setState-in-effect.\n const active = filtered.length > 0 ? Math.min(activeIndex, filtered.length - 1) : 0;\n\n // Anchor the portaled menu to the trigger in VIEWPORT coordinates (position:\n // fixed), flipping above the trigger when there's more room there, and capping\n // the listbox height to the available space so it never runs off-screen.\n const updatePosition = useCallback(() => {\n const trigger = triggerRef.current;\n if (!trigger) return;\n const rect = trigger.getBoundingClientRect();\n const gap = 4; // matches the old mt-1\n const viewportH = window.innerHeight;\n const spaceBelow = viewportH - rect.bottom - gap;\n const spaceAbove = rect.top - gap;\n const placeBelow = spaceBelow >= spaceAbove;\n const avail = Math.max(120, placeBelow ? spaceBelow : spaceAbove);\n // Reserve room for the optional search input + container padding.\n setListMaxHeight(Math.max(80, Math.min(256, avail - (searchable ? 52 : 12))));\n const style: CSSProperties = {\n position: 'fixed',\n left: Math.round(rect.left),\n minWidth: Math.round(rect.width),\n };\n if (placeBelow) style.top = Math.round(rect.bottom + gap);\n else style.bottom = Math.round(viewportH - rect.top + gap);\n setMenuStyle(style);\n }, [searchable]);\n\n // The in-dialog branch renders INLINE (an absolute child of the trigger),\n // so — unlike the body-portaled branch — it cannot escape the modal's\n // `overflow-hidden` panel: an absolute child contributes nothing to the\n // dialog body's scroll height, so a menu taller than the room below the\n // trigger is simply CLIPPED, not scrolled-to (bug-combobox-menu-clipped-\n // inside-modal). Mirror the portaled branch's logic against the DIALOG's\n // clip box instead of the viewport: measure the space above/below the\n // trigger inside the dialog, flip to whichever side is taller, and clamp the\n // listbox height to it so the menu scrolls INTERNALLY and always fits.\n const updateInlinePosition = useCallback(() => {\n const trigger = triggerRef.current;\n if (!trigger) return;\n // Find the nearest ancestor that CLIPS the inline menu. If that ancestor\n // SCROLLS (overflow auto/scroll — e.g. the Advanced-filter popover's\n // `overflow-y-auto` body, or a `Modal.Body`), the menu can be scrolled into\n // view, so leave it exactly as before (top-full, max-h-64) — flipping/\n // clamping against a scroll box would wrongly shove the menu up under the\n // panel's header (the regression the first cut caused). Only a NON-scrolling\n // `overflow: hidden` clip — the centered Modal panel's `overflow-hidden\n // max-h-[90vh]` box — actually traps the menu (bug-combobox-menu-clipped-\n // inside-modal); clamp + flip against THAT box.\n const clip = nearestClipBox(trigger);\n if (!clip || clip.scrolls) {\n setInlineAbove(false);\n setListMaxHeight(256); // == the max-h-64 fallback (original behaviour)\n return;\n }\n const rect = trigger.getBoundingClientRect();\n const clipRect = clip.box.getBoundingClientRect();\n const gap = 4; // matches mt-1 / mb-1\n const inset = 8; // stay a hair inside the panel's rounded clip edge\n const spaceBelow = clipRect.bottom - rect.bottom - gap - inset;\n const spaceAbove = rect.top - clipRect.top - gap - inset;\n const placeAbove = spaceAbove > spaceBelow;\n setInlineAbove(placeAbove);\n const avail = Math.max(80, placeAbove ? spaceAbove : spaceBelow);\n // Reserve room for the optional search input + container padding (same\n // budget as the portaled branch).\n setListMaxHeight(Math.max(80, Math.min(256, avail - (searchable ? 52 : 12))));\n }, [searchable]);\n\n // On open: focus the right control (the only side effect — query/active reset\n // happens in openMenu so this effect never calls setState).\n useEffect(() => {\n if (!(open && mounted)) return;\n const t = setTimeout(() => {\n if (searchable) inputRef.current?.focus();\n else listRef.current?.focus();\n }, 0);\n return () => clearTimeout(t);\n }, [open, mounted, searchable]);\n\n // Position the menu before paint, and keep it glued to the trigger while open\n // (ancestor scroll uses capture so a scrolling table re-anchors the menu).\n // Skip entirely for the inline (in-dialog) branch — it positions via CSS.\n useIsomorphicLayoutEffect(() => {\n if (!(open && mounted)) return;\n if (triggerRef.current?.closest('[data-surface=\"modal\"],[aria-modal=\"true\"]')) return;\n updatePosition();\n const onReflow = () => updatePosition();\n window.addEventListener('scroll', onReflow, true);\n window.addEventListener('resize', onReflow);\n return () => {\n window.removeEventListener('scroll', onReflow, true);\n window.removeEventListener('resize', onReflow);\n };\n }, [open, mounted, updatePosition, filtered.length]);\n\n // Inline (in-dialog) counterpart: measure the menu against the dialog's clip\n // box before paint and keep it clamped while open (re-measure on resize, and\n // on capture-phase scroll so a scrolling dialog body re-anchors it).\n useIsomorphicLayoutEffect(() => {\n if (!(open && mounted)) return;\n if (!triggerRef.current?.closest('[data-surface=\"modal\"],[aria-modal=\"true\"]')) return;\n updateInlinePosition();\n const onReflow = () => updateInlinePosition();\n window.addEventListener('scroll', onReflow, true);\n window.addEventListener('resize', onReflow);\n return () => {\n window.removeEventListener('scroll', onReflow, true);\n window.removeEventListener('resize', onReflow);\n };\n }, [open, mounted, updateInlinePosition, filtered.length]);\n\n // Click-outside closes, restoring focus to the trigger. The menu is portaled\n // out of containerRef, so a click on it must also count as \"inside\".\n useEffect(() => {\n if (!open) return;\n function onDocMouseDown(e: MouseEvent) {\n const target = e.target as Node;\n if (containerRef.current?.contains(target) || menuRef.current?.contains(target)) return;\n closeMenu();\n }\n document.addEventListener('mousedown', onDocMouseDown);\n return () => document.removeEventListener('mousedown', onDocMouseDown);\n }, [open]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Keep the active row in view.\n useEffect(() => {\n if (open) document.getElementById(optionId(active))?.scrollIntoView({ block: 'nearest' });\n }, [active, open]); // eslint-disable-line react-hooks/exhaustive-deps\n\n function openMenu() {\n // Uncontrolled mode resets its own type-ahead on open; server-driven mode\n // leaves the query to the parent (it owns the lifecycle — resets on form\n // open / relationship change), so reopening keeps the current results.\n if (!serverFiltered) setInternalQuery('');\n const sel = value != null ? options.findIndex((o) => o.value === value) : -1;\n setActiveIndex(sel >= 0 ? sel : 0);\n setOpen(true);\n }\n\n // Close the menu, notifying the consumer (Subtask 2.5.5's inline-edit cell\n // leaves edit mode on close). Every close path funnels through here.\n function closeMenu() {\n setOpen(false);\n onClose?.();\n }\n\n function closeAndRefocus() {\n closeMenu();\n triggerRef.current?.focus();\n }\n\n function commit(i: number) {\n const opt = filtered[i];\n if (!opt) return;\n onChange(opt.value);\n closeAndRefocus();\n }\n\n function onListKeyDown(e: React.KeyboardEvent) {\n if (e.key === 'ArrowDown') {\n e.preventDefault();\n setActiveIndex(Math.min(active + 1, filtered.length - 1));\n } else if (e.key === 'ArrowUp') {\n e.preventDefault();\n setActiveIndex(Math.max(active - 1, 0));\n } else if (e.key === 'Home') {\n e.preventDefault();\n setActiveIndex(0);\n } else if (e.key === 'End') {\n e.preventDefault();\n setActiveIndex(filtered.length - 1);\n } else if (e.key === 'Enter') {\n e.preventDefault();\n commit(active);\n } else if (e.key === 'Escape') {\n e.preventDefault();\n closeAndRefocus();\n }\n }\n\n function onTriggerKeyDown(e: React.KeyboardEvent) {\n if (!open && (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n openMenu();\n }\n }\n\n const activeId = filtered.length > 0 ? optionId(active) : undefined;\n // A `role=\"listbox\"` MUST own `role=\"option\"` children, so the listbox role\n // only applies when options are actually rendered. While loading or with no\n // matches the panel holds a status message instead — present it as a\n // `role=\"status\"` live region (announced to AT, and not an empty/childless\n // listbox, which is a critical `aria-required-children` axe violation). The\n // empty state is the link picker's DEFAULT on-open state since the 6.9.2\n // query-driven retrofit, so this is now reachable on every open.\n const hasOptions = !loading && filtered.length > 0;\n\n // Portal out to escape a short table's overflow:hidden — UNLESS we're inside a\n // focus-trapping dialog, where an inline menu is required (see the `mounted`\n // comment above).\n //\n // ⚠️ This is SETTLED IN A LAYOUT EFFECT, not read during render. It used to be\n // read during render on the stated invariant that \"the only ref-null case is\n // autoOpen, used solely by inline-edit cells (always in a table, never a\n // dialog)\". That invariant no longer holds: the quick-view peek's editable\n // rail (MOTIR-2563) is an `autoOpen` caller INSIDE the modal. The render-time\n // read fails permanently for it — `useMounted` returns `true` on the very\n // first client render, `triggerRef.current` is still null at that point, and\n // `subscribeNoop` means nothing ever re-renders to correct it. The menu then\n // portals to <body>, lands outside the dialog's focus scope, and every one of\n // its options is `aria-hidden` to assistive tech and to the accessibility\n // tree. A layout effect runs after the ref attaches and before paint, so the\n // branch is right for click-opened AND mount-opened menus alike.\n const [inDialog, setInDialog] = useState(false);\n useIsomorphicLayoutEffect(() => {\n setInDialog(!!triggerRef.current?.closest('[data-surface=\"modal\"],[aria-modal=\"true\"]'));\n }, [open]);\n\n const menuInner = (\n <>\n {searchable ? (\n <input\n ref={inputRef}\n type=\"text\"\n role=\"combobox\"\n aria-expanded\n aria-controls={listId}\n aria-activedescendant={activeId}\n aria-label={searchPlaceholder}\n value={query}\n onChange={(e) => {\n setQuery(e.target.value);\n setActiveIndex(0);\n }}\n onKeyDown={onListKeyDown}\n placeholder={searchPlaceholder}\n className=\"border-(--el-border) bg-(--el-page-bg) mb-1 w-full rounded-(--radius-input) border px-(--spacing-control-x) py-(--spacing-control-y) text-sm focus-visible:outline-none\"\n />\n ) : null}\n <div\n ref={listRef}\n id={listId}\n role={hasOptions ? 'listbox' : 'status'}\n aria-label={label}\n tabIndex={hasOptions && !searchable ? 0 : -1}\n aria-activedescendant={hasOptions && !searchable ? activeId : undefined}\n onKeyDown={hasOptions && !searchable ? onListKeyDown : undefined}\n // Both branches cap height to their measured available space (inline\n // style wins over the max-h-64 fallback): the portaled menu against the\n // viewport, the inline (in-dialog) menu against the dialog's clip box —\n // so an over-tall list scrolls internally instead of being clipped.\n style={{ maxHeight: listMaxHeight }}\n className=\"max-h-64 overflow-y-auto focus:outline-none\"\n >\n {loading ? (\n <p className=\"text-(--el-text-muted) px-2.5 py-2 text-sm\">{loadingText}</p>\n ) : filtered.length === 0 ? (\n <p className=\"text-(--el-text-muted) px-2.5 py-2 text-sm\">{emptyText}</p>\n ) : (\n filtered.map((opt, i) => {\n const isSelected = opt.value === value;\n const isActive = i === active;\n // A non-interactive group header at each group transition (in list\n // order, so a fully-filtered group's header drops with it). Not an\n // option row — keyboard nav over `filtered` is unaffected.\n const header =\n opt.group !== undefined && opt.group !== filtered[i - 1]?.group ? (\n <div\n key={`group-${opt.group}`}\n role=\"presentation\"\n className=\"px-(--spacing-control-x) pt-2 pb-1 font-mono text-[11px] font-semibold tracking-wider text-(--el-text-eyebrow) uppercase\"\n >\n {opt.group}\n </div>\n ) : null;\n return (\n <Fragment key={opt.value}>\n {header}\n <div\n id={optionId(i)}\n role=\"option\"\n aria-selected={isSelected}\n onMouseEnter={() => setActiveIndex(i)}\n onClick={() => commit(i)}\n className={cn(\n 'flex cursor-pointer items-center gap-2 rounded-(--radius-control) px-(--spacing-control-x) py-(--spacing-control-y) text-sm',\n isActive ? 'bg-(--el-option-active-bg) text-(--el-text)' : 'text-(--el-text)',\n )}\n >\n {opt.icon ? (\n <span aria-hidden className={ICON_SLOT}>\n {opt.icon}\n </span>\n ) : null}\n <span className=\"truncate\">{opt.label}</span>\n {opt.secondary ? (\n // --el-text-identifier (= --color-slate, the -secondary\n // weight), NOT -muted: muted (#787671) fails AA (4.16:1) on\n // the panel surface (#f6f5f4) at this 12px size (the\n // sidebar-caption contrast lesson) — identifier/slate clears it.\n <span className=\"text-(--el-text-identifier) ml-auto truncate text-xs\">\n {opt.secondary}\n </span>\n ) : null}\n {opt.trailing ? (\n // Rich trailing content (a Pill / chip). Pinned right; takes\n // the `ml-auto` itself when there is no `secondary` before it.\n <span\n className={cn(\n 'flex shrink-0 items-center gap-1.5',\n opt.secondary ? 'ml-1.5' : 'ml-auto',\n )}\n >\n {opt.trailing}\n </span>\n ) : null}\n {isSelected ? (\n // The selected-row check uses the accent (matching\n // MultiSelectPicker) — unifies the check-icon colour across\n // the two pickers (was bare ink here, accent there).\n <Check\n className=\"ml-1 h-4 w-4 shrink-0 text-(--el-accent-on-surface)\"\n aria-hidden\n />\n ) : null}\n </div>\n </Fragment>\n );\n })\n )}\n </div>\n </>\n );\n\n // The shared menu panel. In a dialog: an inline absolute panel (the original,\n // proven layout). Otherwise: a body-portaled panel with viewport-anchored\n // fixed positioning that escapes every overflow ancestor.\n const menu = inDialog ? (\n <div\n ref={menuRef}\n data-menu-surface=\"\"\n className={cn(\n 'absolute left-0 z-50 w-max min-w-full max-w-[18rem] rounded-(--radius-card) bg-(--el-page-bg) p-1',\n 'shadow-(--shadow-elevated) border border-(--el-border)',\n // Flip above the trigger when the dialog has more room there, so a tall\n // list near the modal's bottom edge isn't clipped (clamped + scrolled).\n inlineAbove ? 'bottom-full mb-1' : 'top-full mt-1',\n )}\n >\n {menuInner}\n </div>\n ) : (\n <div\n ref={menuRef}\n data-menu-surface=\"\"\n // Width sizes to the widest option but never narrower than the trigger\n // (minWidth set in updatePosition) and is capped so a long label can't run\n // off-screen. Hidden until positioned to avoid a first-paint flash at 0,0.\n style={menuStyle ?? { position: 'fixed', visibility: 'hidden' }}\n className={cn(\n 'z-50 w-max max-w-[18rem] rounded-(--radius-card) bg-(--el-page-bg) p-1',\n 'shadow-(--shadow-elevated) border border-(--el-border)',\n )}\n >\n {menuInner}\n </div>\n );\n\n return (\n // `data-inner-dismiss` while the menu is open — see MultiSelectPicker:\n // an enclosing Radix layer's onEscapeKeyDown checks it so Esc closes the\n // MENU (our own handler) instead of dismissing the whole layer.\n <div ref={containerRef} data-inner-dismiss={open ? true : undefined} className=\"relative\">\n <button\n ref={triggerRef}\n type=\"button\"\n id={id}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-controls={open ? listId : undefined}\n aria-label={label}\n disabled={disabled}\n onClick={() => (open ? closeMenu() : openMenu())}\n onKeyDown={onTriggerKeyDown}\n className={cn(\n 'border-(--el-border) bg-(--el-page-bg) flex h-(--height-control) w-full items-center gap-2 rounded-(--radius-input) border px-(--spacing-control-x) text-sm',\n 'focus-visible:ring-(--focus-ring-color) focus-visible:outline-none focus-visible:ring-2',\n 'disabled:opacity-50',\n className,\n )}\n >\n {selected ? (\n <>\n {selected.icon ? (\n <span aria-hidden className={ICON_SLOT}>\n {selected.icon}\n </span>\n ) : null}\n <span className=\"text-(--el-text) truncate\">{selected.label}</span>\n {selected.secondary ? (\n // identifier (= slate, the -secondary weight): AA on the trigger surface at 12px (as above).\n <span className=\"text-(--el-text-identifier) ml-auto truncate text-xs\">\n {selected.secondary}\n </span>\n ) : null}\n </>\n ) : (\n <span className=\"text-(--el-text-muted) truncate\">{placeholder}</span>\n )}\n <ChevronsUpDown className=\"text-(--el-icon-muted) ml-auto h-4 w-4 shrink-0\" aria-hidden />\n </button>\n\n {open ? (inDialog ? menu : mounted ? createPortal(menu, document.body) : null) : null}\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/cn.ts","../../../src/components/ui/Combobox.tsx"],"names":["Fragment"],"mappings":"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","file":"Combobox.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 {\n Fragment,\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type CSSProperties,\n type ReactNode,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { Check, ChevronsUpDown } from 'lucide-react';\nimport { cn } from '../../utils/cn';\n\n// Run layout effects on the client, fall back to useEffect during SSR (the menu\n// only mounts client-side anyway — see the `mounted` gate below).\nconst useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;\n\n// `false` during SSR / first hydration render, `true` on the client thereafter —\n// gates the createPortal call so document.body is guaranteed present. Hydration-\n// safe (matches the server's `false`) and avoids setState-in-effect.\nconst subscribeNoop = () => () => {};\n\n/**\n * The LEADING-VISUAL slot, on the trigger and on every option row (MOTIR-3080).\n *\n * ⚠️ `inline-flex` is load-bearing, not decoration. The wrapper is a flex item of\n * its row, so IT is blockified — but whatever a caller passes INSIDE it is not,\n * and a plain `<span>` therefore stays `display: inline`, where width and height\n * do not apply at all. `StatusPicker`'s 10px status dot rendered as a 2 × 18\n * sliver for exactly that reason: three cards of palette work (MOTIR-1273 /\n * -2073 / -2075) measured that dot's HUE while its box was two pixels wide.\n *\n * It survived every check because the markup was right — the classes ask for\n * 10 × 10, `getComputedStyle` reports 10px, and only the USED value disagreed.\n * A used value exists only in a browser, so `tests/e2e/issue-detail-flow` MEASURES\n * it rather than asserting the element exists.\n *\n * Making the slot a flex CONTAINER blockifies its child, which is what the slot's\n * contract (\"a leading visual\") always implied. A lucide `<svg>` is replaced and\n * was never affected, so no existing caller's icon changes size.\n */\nconst ICON_SLOT = 'inline-flex shrink-0 items-center';\n\nfunction useMounted() {\n return useSyncExternalStore(\n subscribeNoop,\n () => true,\n () => false,\n );\n}\n\n/**\n * Combobox — an accessible select/combobox primitive (Subtask 2.3.4). A trigger\n * button (`role=\"combobox\"`, `aria-haspopup=\"listbox\"`) opens an anchored panel\n * holding an optional type-ahead filter and a `role=\"listbox\"` of\n * `role=\"option\"` rows. The active option is tracked with `aria-activedescendant`\n * on whichever control holds focus (the filter input when `searchable`, else the\n * listbox) — the CommandPalette pattern that already clears the STRICT axe sweep.\n *\n * Deliberately NOT built on the Radix Popover primitive: that injects\n * `aria-haspopup=\"dialog\"` + dialog focus semantics onto its trigger, which\n * conflicts with the listbox combobox pattern. This is a self-contained anchored\n * dropdown (click-outside + Escape + focus return handled here) so the ARIA is\n * exactly the WAI-ARIA combobox shape.\n *\n * Composed by `components/issues/TypePicker` (searchable=false, 5 options) and\n * `ParentPicker` (searchable, async candidate list).\n */\nexport interface ComboboxOption<T extends string> {\n value: T;\n /** Primary text — also the accessible name of the option. */\n label: string;\n /** Extra text matched by the filter (e.g. a PROD-N identifier). */\n keywords?: string;\n /** Leading visual (a kind icon); decorative — label carries the name. */\n icon?: ReactNode;\n /** Trailing muted text (e.g. the identifier). */\n secondary?: string;\n /** Trailing rich content pinned to the option row's far end (e.g. a state\n * Pill or a \"Linked to …\" chip in the PR-link picker, MOTIR-1596).\n * Decorative — the accessible name is `label`; renders after `secondary`. */\n trailing?: ReactNode;\n /**\n * Optional section label. When consecutive options carry different `group`\n * values, a non-interactive header row is rendered at each transition (the\n * advanced filter's \"Fields\" / \"Custom fields\" / \"Other\" field menu). Pass\n * options pre-sorted by group; headers follow list order, so a group whose\n * every option is filtered out disappears with it. Decorative — not part of\n * the listbox's option indices, so keyboard nav is unaffected.\n */\n group?: string;\n /**\n * The option is shown but cannot be picked (MOTIR-5528 — a status move an\n * approval holds). It stays FOCUSABLE and is announced as unavailable\n * (`aria-disabled`), rather than skipped, so a keyboard reader learns the move\n * exists and why it is held; a click or Enter on it does nothing. Its label\n * reads in `--el-text-secondary`, never an opacity dim, which keeps AA.\n */\n disabled?: boolean;\n}\n\nexport interface ComboboxProps<T extends string> {\n options: ComboboxOption<T>[];\n value: T | null;\n onChange: (value: T) => void;\n /** Accessible name for the trigger + listbox. */\n label: string;\n /** Trigger text when nothing is selected. */\n placeholder?: string;\n searchable?: boolean;\n searchPlaceholder?: string;\n /** Empty-listbox content. A string, or rich nodes (e.g. a \"no matches\" line\n * plus a hint sub-line — use block `<span>`s, since this renders in a `<p>`). */\n emptyText?: ReactNode;\n loading?: boolean;\n loadingText?: string;\n disabled?: boolean;\n /** id for the trigger button. */\n id?: string;\n className?: string;\n /**\n * Mount already-open (Subtask 2.5.5) — for an inline-edit cell that renders the\n * picker on a single click, so the menu is open immediately (mirrors\n * `DatePicker`'s `autoOpen`). Focus lands on the search input / listbox.\n */\n autoOpen?: boolean;\n /**\n * Fired whenever the menu transitions open → closed (a pick, Escape,\n * click-outside, or toggling the trigger). The inline-edit cell uses it to\n * leave edit mode and return to the static pill/avatar (Subtask 2.5.5).\n */\n onClose?: () => void;\n /**\n * Server-driven search mode (Subtask 6.9.2 — the link picker). When\n * `onQueryChange` is provided the type-ahead query is CONTROLLED by the parent\n * (`query`) and client-side filtering is SKIPPED — `options` is taken as the\n * server's authoritative result for the current query (the parent debounces\n * its fetch off `onQueryChange`, the same contract as `MultiSelectPicker`).\n * Omit both for the default self-filtering behaviour. The parent owns the\n * query lifecycle, so opening the menu does NOT reset it.\n */\n query?: string;\n onQueryChange?: (query: string) => void;\n}\n\n// Walk up from a node to the nearest ancestor that clips overflow, classifying\n// it as scrolling (overflow auto/scroll — the menu can be scrolled into view)\n// or a hard `hidden`/`clip` box (no scroll — the menu is trapped). Used by the\n// inline (in-dialog) branch to decide whether to clamp + flip the menu (only a\n// hard non-scrolling clip needs it). Stops at <body>; returns null if nothing\n// up the chain clips.\nfunction nearestClipBox(el: HTMLElement): { box: HTMLElement; scrolls: boolean } | null {\n let node = el.parentElement;\n while (node && node !== document.body) {\n const oy = getComputedStyle(node).overflowY;\n if (oy === 'auto' || oy === 'scroll') return { box: node, scrolls: true };\n if (oy === 'hidden' || oy === 'clip') return { box: node, scrolls: false };\n node = node.parentElement;\n }\n return null;\n}\n\nexport function Combobox<T extends string>({\n options,\n value,\n onChange,\n label,\n placeholder = 'Select…',\n searchable = false,\n searchPlaceholder = 'Search…',\n emptyText = 'No matches',\n loading = false,\n loadingText = 'Loading…',\n disabled = false,\n id,\n className,\n autoOpen = false,\n onClose,\n query: controlledQuery,\n onQueryChange,\n}: ComboboxProps<T>) {\n const [open, setOpen] = useState(autoOpen);\n // Server-driven mode (6.9.2): when `onQueryChange` is provided the query is\n // controlled by the parent and client filtering is bypassed (the server's\n // `options` are authoritative). Otherwise the query is internal state.\n const serverFiltered = onQueryChange !== undefined;\n const [internalQuery, setInternalQuery] = useState('');\n const query = serverFiltered ? (controlledQuery ?? '') : internalQuery;\n const setQuery = (q: string) => (serverFiltered ? onQueryChange(q) : setInternalQuery(q));\n const [activeIndex, setActiveIndex] = useState(0);\n // The menu is portaled to <body> by default, so it escapes any clipping\n // ancestor — a short table's overflow:hidden\n // (bug-inline-edit-clipped-when-table-short) OR the Advanced-filter popover's\n // overflow-hidden + overflow-y-auto body (MOTIR-1346). We render INLINE only\n // inside a focus-trapping MODAL (the shared Modal primitive — `data-surface=\n // \"modal\"`; `aria-modal` covers any other), where a portaled menu would land\n // outside the dialog's focus scope (focus-trap war → a click on it hangs /\n // dismisses) AND the modal centers with a CSS transform that breaks a fixed-\n // positioned child's coords. A NON-modal Radix Popover (the filter builder,\n // promote-sprint, mark-duplicate — `data-surface=\"popover\"`) carries\n // `role=\"dialog\"` for a11y but does NOT trap focus, so it portals safely —\n // keyed on the modal surface, NOT `role`, so those popovers escape the clip\n // too. Only render the portal once mounted, since createPortal needs document.body.\n const mounted = useMounted();\n // Viewport-anchored position for the portaled menu + the listbox's available\n // height, recomputed from the trigger rect on open / scroll / resize.\n const [menuStyle, setMenuStyle] = useState<CSSProperties | null>(null);\n const [listMaxHeight, setListMaxHeight] = useState(256);\n // Inline (in-dialog) branch only: whether the menu is flipped ABOVE the\n // trigger because there's more room there than below within the dialog's\n // overflow-hidden clip box (bug-combobox-menu-clipped-inside-modal).\n const [inlineAbove, setInlineAbove] = useState(false);\n const baseId = useId();\n const listId = `${baseId}-listbox`;\n const optionId = (i: number) => `${baseId}-opt-${i}`;\n\n const containerRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const listRef = useRef<HTMLDivElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n\n const filtered = useMemo(() => {\n // Server-driven mode: `options` is already the query's result — never filter\n // it again client-side (a substring re-filter would hide trigram / token\n // matches the server legitimately returned).\n if (serverFiltered || !searchable || query.trim() === '') return options;\n const needle = query.trim().toLowerCase();\n return options.filter(\n (o) =>\n o.label.toLowerCase().includes(needle) ||\n (o.secondary ? o.secondary.toLowerCase().includes(needle) : false) ||\n (o.keywords ? o.keywords.toLowerCase().includes(needle) : false),\n );\n }, [options, query, searchable, serverFiltered]);\n\n const selected = value != null ? (options.find((o) => o.value === value) ?? null) : null;\n\n // The active row, clamped to the (possibly just-filtered) list — DERIVED, not\n // an effect, so a shrinking filter never needs a setState-in-effect.\n const active = filtered.length > 0 ? Math.min(activeIndex, filtered.length - 1) : 0;\n\n // Anchor the portaled menu to the trigger in VIEWPORT coordinates (position:\n // fixed), flipping above the trigger when there's more room there, and capping\n // the listbox height to the available space so it never runs off-screen.\n const updatePosition = useCallback(() => {\n const trigger = triggerRef.current;\n if (!trigger) return;\n const rect = trigger.getBoundingClientRect();\n const gap = 4; // matches the old mt-1\n const viewportH = window.innerHeight;\n const spaceBelow = viewportH - rect.bottom - gap;\n const spaceAbove = rect.top - gap;\n const placeBelow = spaceBelow >= spaceAbove;\n const avail = Math.max(120, placeBelow ? spaceBelow : spaceAbove);\n // Reserve room for the optional search input + container padding.\n setListMaxHeight(Math.max(80, Math.min(256, avail - (searchable ? 52 : 12))));\n const style: CSSProperties = {\n position: 'fixed',\n left: Math.round(rect.left),\n minWidth: Math.round(rect.width),\n };\n if (placeBelow) style.top = Math.round(rect.bottom + gap);\n else style.bottom = Math.round(viewportH - rect.top + gap);\n setMenuStyle(style);\n }, [searchable]);\n\n // The in-dialog branch renders INLINE (an absolute child of the trigger),\n // so — unlike the body-portaled branch — it cannot escape the modal's\n // `overflow-hidden` panel: an absolute child contributes nothing to the\n // dialog body's scroll height, so a menu taller than the room below the\n // trigger is simply CLIPPED, not scrolled-to (bug-combobox-menu-clipped-\n // inside-modal). Mirror the portaled branch's logic against the DIALOG's\n // clip box instead of the viewport: measure the space above/below the\n // trigger inside the dialog, flip to whichever side is taller, and clamp the\n // listbox height to it so the menu scrolls INTERNALLY and always fits.\n const updateInlinePosition = useCallback(() => {\n const trigger = triggerRef.current;\n if (!trigger) return;\n // Find the nearest ancestor that CLIPS the inline menu. If that ancestor\n // SCROLLS (overflow auto/scroll — e.g. the Advanced-filter popover's\n // `overflow-y-auto` body, or a `Modal.Body`), the menu can be scrolled into\n // view, so leave it exactly as before (top-full, max-h-64) — flipping/\n // clamping against a scroll box would wrongly shove the menu up under the\n // panel's header (the regression the first cut caused). Only a NON-scrolling\n // `overflow: hidden` clip — the centered Modal panel's `overflow-hidden\n // max-h-[90vh]` box — actually traps the menu (bug-combobox-menu-clipped-\n // inside-modal); clamp + flip against THAT box.\n const clip = nearestClipBox(trigger);\n if (!clip || clip.scrolls) {\n setInlineAbove(false);\n setListMaxHeight(256); // == the max-h-64 fallback (original behaviour)\n return;\n }\n const rect = trigger.getBoundingClientRect();\n const clipRect = clip.box.getBoundingClientRect();\n const gap = 4; // matches mt-1 / mb-1\n const inset = 8; // stay a hair inside the panel's rounded clip edge\n const spaceBelow = clipRect.bottom - rect.bottom - gap - inset;\n const spaceAbove = rect.top - clipRect.top - gap - inset;\n const placeAbove = spaceAbove > spaceBelow;\n setInlineAbove(placeAbove);\n const avail = Math.max(80, placeAbove ? spaceAbove : spaceBelow);\n // Reserve room for the optional search input + container padding (same\n // budget as the portaled branch).\n setListMaxHeight(Math.max(80, Math.min(256, avail - (searchable ? 52 : 12))));\n }, [searchable]);\n\n // On open: focus the right control (the only side effect — query/active reset\n // happens in openMenu so this effect never calls setState).\n useEffect(() => {\n if (!(open && mounted)) return;\n const t = setTimeout(() => {\n if (searchable) inputRef.current?.focus();\n else listRef.current?.focus();\n }, 0);\n return () => clearTimeout(t);\n }, [open, mounted, searchable]);\n\n // Position the menu before paint, and keep it glued to the trigger while open\n // (ancestor scroll uses capture so a scrolling table re-anchors the menu).\n // Skip entirely for the inline (in-dialog) branch — it positions via CSS.\n useIsomorphicLayoutEffect(() => {\n if (!(open && mounted)) return;\n if (triggerRef.current?.closest('[data-surface=\"modal\"],[aria-modal=\"true\"]')) return;\n updatePosition();\n const onReflow = () => updatePosition();\n window.addEventListener('scroll', onReflow, true);\n window.addEventListener('resize', onReflow);\n return () => {\n window.removeEventListener('scroll', onReflow, true);\n window.removeEventListener('resize', onReflow);\n };\n }, [open, mounted, updatePosition, filtered.length]);\n\n // Inline (in-dialog) counterpart: measure the menu against the dialog's clip\n // box before paint and keep it clamped while open (re-measure on resize, and\n // on capture-phase scroll so a scrolling dialog body re-anchors it).\n useIsomorphicLayoutEffect(() => {\n if (!(open && mounted)) return;\n if (!triggerRef.current?.closest('[data-surface=\"modal\"],[aria-modal=\"true\"]')) return;\n updateInlinePosition();\n const onReflow = () => updateInlinePosition();\n window.addEventListener('scroll', onReflow, true);\n window.addEventListener('resize', onReflow);\n return () => {\n window.removeEventListener('scroll', onReflow, true);\n window.removeEventListener('resize', onReflow);\n };\n }, [open, mounted, updateInlinePosition, filtered.length]);\n\n // Click-outside closes, restoring focus to the trigger. The menu is portaled\n // out of containerRef, so a click on it must also count as \"inside\".\n useEffect(() => {\n if (!open) return;\n function onDocMouseDown(e: MouseEvent) {\n const target = e.target as Node;\n if (containerRef.current?.contains(target) || menuRef.current?.contains(target)) return;\n closeMenu();\n }\n document.addEventListener('mousedown', onDocMouseDown);\n return () => document.removeEventListener('mousedown', onDocMouseDown);\n }, [open]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Keep the active row in view.\n useEffect(() => {\n if (open) document.getElementById(optionId(active))?.scrollIntoView({ block: 'nearest' });\n }, [active, open]); // eslint-disable-line react-hooks/exhaustive-deps\n\n function openMenu() {\n // Uncontrolled mode resets its own type-ahead on open; server-driven mode\n // leaves the query to the parent (it owns the lifecycle — resets on form\n // open / relationship change), so reopening keeps the current results.\n if (!serverFiltered) setInternalQuery('');\n const sel = value != null ? options.findIndex((o) => o.value === value) : -1;\n setActiveIndex(sel >= 0 ? sel : 0);\n setOpen(true);\n }\n\n // Close the menu, notifying the consumer (Subtask 2.5.5's inline-edit cell\n // leaves edit mode on close). Every close path funnels through here.\n function closeMenu() {\n setOpen(false);\n onClose?.();\n }\n\n function closeAndRefocus() {\n closeMenu();\n triggerRef.current?.focus();\n }\n\n function commit(i: number) {\n const opt = filtered[i];\n if (!opt || opt.disabled) return;\n onChange(opt.value);\n closeAndRefocus();\n }\n\n function onListKeyDown(e: React.KeyboardEvent) {\n if (e.key === 'ArrowDown') {\n e.preventDefault();\n setActiveIndex(Math.min(active + 1, filtered.length - 1));\n } else if (e.key === 'ArrowUp') {\n e.preventDefault();\n setActiveIndex(Math.max(active - 1, 0));\n } else if (e.key === 'Home') {\n e.preventDefault();\n setActiveIndex(0);\n } else if (e.key === 'End') {\n e.preventDefault();\n setActiveIndex(filtered.length - 1);\n } else if (e.key === 'Enter') {\n e.preventDefault();\n commit(active);\n } else if (e.key === 'Escape') {\n e.preventDefault();\n closeAndRefocus();\n }\n }\n\n function onTriggerKeyDown(e: React.KeyboardEvent) {\n if (!open && (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n openMenu();\n }\n }\n\n const activeId = filtered.length > 0 ? optionId(active) : undefined;\n // A `role=\"listbox\"` MUST own `role=\"option\"` children, so the listbox role\n // only applies when options are actually rendered. While loading or with no\n // matches the panel holds a status message instead — present it as a\n // `role=\"status\"` live region (announced to AT, and not an empty/childless\n // listbox, which is a critical `aria-required-children` axe violation). The\n // empty state is the link picker's DEFAULT on-open state since the 6.9.2\n // query-driven retrofit, so this is now reachable on every open.\n const hasOptions = !loading && filtered.length > 0;\n\n // Portal out to escape a short table's overflow:hidden — UNLESS we're inside a\n // focus-trapping dialog, where an inline menu is required (see the `mounted`\n // comment above).\n //\n // ⚠️ This is SETTLED IN A LAYOUT EFFECT, not read during render. It used to be\n // read during render on the stated invariant that \"the only ref-null case is\n // autoOpen, used solely by inline-edit cells (always in a table, never a\n // dialog)\". That invariant no longer holds: the quick-view peek's editable\n // rail (MOTIR-2563) is an `autoOpen` caller INSIDE the modal. The render-time\n // read fails permanently for it — `useMounted` returns `true` on the very\n // first client render, `triggerRef.current` is still null at that point, and\n // `subscribeNoop` means nothing ever re-renders to correct it. The menu then\n // portals to <body>, lands outside the dialog's focus scope, and every one of\n // its options is `aria-hidden` to assistive tech and to the accessibility\n // tree. A layout effect runs after the ref attaches and before paint, so the\n // branch is right for click-opened AND mount-opened menus alike.\n const [inDialog, setInDialog] = useState(false);\n useIsomorphicLayoutEffect(() => {\n setInDialog(!!triggerRef.current?.closest('[data-surface=\"modal\"],[aria-modal=\"true\"]'));\n }, [open]);\n\n const menuInner = (\n <>\n {searchable ? (\n <input\n ref={inputRef}\n type=\"text\"\n role=\"combobox\"\n aria-expanded\n aria-controls={listId}\n aria-activedescendant={activeId}\n aria-label={searchPlaceholder}\n value={query}\n onChange={(e) => {\n setQuery(e.target.value);\n setActiveIndex(0);\n }}\n onKeyDown={onListKeyDown}\n placeholder={searchPlaceholder}\n className=\"border-(--el-border) bg-(--el-page-bg) mb-1 w-full rounded-(--radius-input) border px-(--spacing-control-x) py-(--spacing-control-y) text-sm focus-visible:outline-none\"\n />\n ) : null}\n <div\n ref={listRef}\n id={listId}\n role={hasOptions ? 'listbox' : 'status'}\n aria-label={label}\n tabIndex={hasOptions && !searchable ? 0 : -1}\n aria-activedescendant={hasOptions && !searchable ? activeId : undefined}\n onKeyDown={hasOptions && !searchable ? onListKeyDown : undefined}\n // Both branches cap height to their measured available space (inline\n // style wins over the max-h-64 fallback): the portaled menu against the\n // viewport, the inline (in-dialog) menu against the dialog's clip box —\n // so an over-tall list scrolls internally instead of being clipped.\n style={{ maxHeight: listMaxHeight }}\n className=\"max-h-64 overflow-y-auto focus:outline-none\"\n >\n {loading ? (\n <p className=\"text-(--el-text-muted) px-2.5 py-2 text-sm\">{loadingText}</p>\n ) : filtered.length === 0 ? (\n <p className=\"text-(--el-text-muted) px-2.5 py-2 text-sm\">{emptyText}</p>\n ) : (\n filtered.map((opt, i) => {\n const isSelected = opt.value === value;\n const isActive = i === active;\n // A non-interactive group header at each group transition (in list\n // order, so a fully-filtered group's header drops with it). Not an\n // option row — keyboard nav over `filtered` is unaffected.\n const header =\n opt.group !== undefined && opt.group !== filtered[i - 1]?.group ? (\n <div\n key={`group-${opt.group}`}\n role=\"presentation\"\n className=\"px-(--spacing-control-x) pt-2 pb-1 font-mono text-[11px] font-semibold tracking-wider text-(--el-text-eyebrow) uppercase\"\n >\n {opt.group}\n </div>\n ) : null;\n return (\n <Fragment key={opt.value}>\n {header}\n <div\n id={optionId(i)}\n role=\"option\"\n aria-selected={isSelected}\n aria-disabled={opt.disabled ? true : undefined}\n onMouseEnter={() => setActiveIndex(i)}\n onClick={() => commit(i)}\n className={cn(\n 'flex items-center gap-2 rounded-(--radius-control) px-(--spacing-control-x) py-(--spacing-control-y) text-sm',\n opt.disabled ? 'cursor-default' : 'cursor-pointer',\n isActive ? 'bg-(--el-option-active-bg)' : '',\n opt.disabled ? 'text-(--el-text-secondary)' : 'text-(--el-text)',\n )}\n >\n {opt.icon ? (\n <span aria-hidden className={ICON_SLOT}>\n {opt.icon}\n </span>\n ) : null}\n <span className=\"truncate\">{opt.label}</span>\n {opt.secondary ? (\n // --el-text-identifier (= --color-slate, the -secondary\n // weight), NOT -muted: muted (#787671) fails AA (4.16:1) on\n // the panel surface (#f6f5f4) at this 12px size (the\n // sidebar-caption contrast lesson) — identifier/slate clears it.\n <span className=\"text-(--el-text-identifier) ml-auto truncate text-xs\">\n {opt.secondary}\n </span>\n ) : null}\n {opt.trailing ? (\n // Rich trailing content (a Pill / chip). Pinned right; takes\n // the `ml-auto` itself when there is no `secondary` before it.\n <span\n className={cn(\n 'flex shrink-0 items-center gap-1.5',\n opt.secondary ? 'ml-1.5' : 'ml-auto',\n )}\n >\n {opt.trailing}\n </span>\n ) : null}\n {isSelected ? (\n // The selected-row check uses the accent (matching\n // MultiSelectPicker) — unifies the check-icon colour across\n // the two pickers (was bare ink here, accent there).\n <Check\n className=\"ml-1 h-4 w-4 shrink-0 text-(--el-accent-on-surface)\"\n aria-hidden\n />\n ) : null}\n </div>\n </Fragment>\n );\n })\n )}\n </div>\n </>\n );\n\n // The shared menu panel. In a dialog: an inline absolute panel (the original,\n // proven layout). Otherwise: a body-portaled panel with viewport-anchored\n // fixed positioning that escapes every overflow ancestor.\n const menu = inDialog ? (\n <div\n ref={menuRef}\n data-menu-surface=\"\"\n className={cn(\n 'absolute left-0 z-50 w-max min-w-full max-w-[18rem] rounded-(--radius-card) bg-(--el-page-bg) p-1',\n 'shadow-(--shadow-elevated) border border-(--el-border)',\n // Flip above the trigger when the dialog has more room there, so a tall\n // list near the modal's bottom edge isn't clipped (clamped + scrolled).\n inlineAbove ? 'bottom-full mb-1' : 'top-full mt-1',\n )}\n >\n {menuInner}\n </div>\n ) : (\n <div\n ref={menuRef}\n data-menu-surface=\"\"\n // Width sizes to the widest option but never narrower than the trigger\n // (minWidth set in updatePosition) and is capped so a long label can't run\n // off-screen. Hidden until positioned to avoid a first-paint flash at 0,0.\n style={menuStyle ?? { position: 'fixed', visibility: 'hidden' }}\n className={cn(\n 'z-50 w-max max-w-[18rem] rounded-(--radius-card) bg-(--el-page-bg) p-1',\n 'shadow-(--shadow-elevated) border border-(--el-border)',\n )}\n >\n {menuInner}\n </div>\n );\n\n return (\n // `data-inner-dismiss` while the menu is open — see MultiSelectPicker:\n // an enclosing Radix layer's onEscapeKeyDown checks it so Esc closes the\n // MENU (our own handler) instead of dismissing the whole layer.\n <div ref={containerRef} data-inner-dismiss={open ? true : undefined} className=\"relative\">\n <button\n ref={triggerRef}\n type=\"button\"\n id={id}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-controls={open ? listId : undefined}\n aria-label={label}\n disabled={disabled}\n onClick={() => (open ? closeMenu() : openMenu())}\n onKeyDown={onTriggerKeyDown}\n className={cn(\n 'border-(--el-border) bg-(--el-page-bg) flex h-(--height-control) w-full items-center gap-2 rounded-(--radius-input) border px-(--spacing-control-x) text-sm',\n 'focus-visible:ring-(--focus-ring-color) focus-visible:outline-none focus-visible:ring-2',\n 'disabled:opacity-50',\n className,\n )}\n >\n {selected ? (\n <>\n {selected.icon ? (\n <span aria-hidden className={ICON_SLOT}>\n {selected.icon}\n </span>\n ) : null}\n <span className=\"text-(--el-text) truncate\">{selected.label}</span>\n {selected.secondary ? (\n // identifier (= slate, the -secondary weight): AA on the trigger surface at 12px (as above).\n <span className=\"text-(--el-text-identifier) ml-auto truncate text-xs\">\n {selected.secondary}\n </span>\n ) : null}\n </>\n ) : (\n <span className=\"text-(--el-text-muted) truncate\">{placeholder}</span>\n )}\n <ChevronsUpDown className=\"text-(--el-icon-muted) ml-auto h-4 w-4 shrink-0\" aria-hidden />\n </button>\n\n {open ? (inDialog ? menu : mounted ? createPortal(menu, document.body) : null) : null}\n </div>\n );\n}\n"]}
|
|
@@ -38,12 +38,12 @@ import { VariantProps } from 'class-variance-authority';
|
|
|
38
38
|
* <Pill tone="neutral">3 members</Pill>
|
|
39
39
|
*/
|
|
40
40
|
declare const pillVariants: (props?: ({
|
|
41
|
-
status?: "done" | "planned" | "in-progress" | "implemented" | null | undefined;
|
|
41
|
+
status?: "done" | "planned" | "in-progress" | "implemented" | "approved" | null | undefined;
|
|
42
42
|
severity?: "danger" | "info" | "success" | "warning" | null | undefined;
|
|
43
43
|
priority?: "highest" | "high" | "medium" | "low" | "lowest" | null | undefined;
|
|
44
44
|
memberRole?: "admin" | "member" | "viewer" | "custom" | null | undefined;
|
|
45
45
|
orgRole?: "admin" | "member" | "owner" | null | undefined;
|
|
46
|
-
tone?: "neutral" | "private" | "archived" | null | undefined;
|
|
46
|
+
tone?: "neutral" | "private" | "archived" | "awaiting" | null | undefined;
|
|
47
47
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
48
48
|
interface PillProps extends HTMLAttributes<HTMLSpanElement>, VariantProps<typeof pillVariants> {
|
|
49
49
|
}
|