@motir/design-system 0.5.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +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 * 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 * A non-interactive note pinned BELOW the listbox, inside the open menu\n * (MOTIR-5582 — the Monitoring room's minimum-level control says, at the\n * action that causes it, that lowering also re-checks earlier issues). It is\n * not an option: keyboard navigation and the listbox's option indices are\n * unaffected, and it is announced as ordinary text. Omit it for no note.\n */\n footer?: ReactNode;\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 footer,\n}: ComboboxProps<T>) {\n // A DISABLED combobox never opens — not even when it mounts with `autoOpen`\n // (MOTIR-6173). An inline editor that mounts open for an actor who may not\n // write used to hand them a live menu whose pick the server then refused\n // (MOTIR-4822); the trigger was disabled, the menu was not.\n const [open, setOpen] = useState(autoOpen && !disabled);\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 if (disabled) return;\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 if (disabled) return;\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 {footer ? (\n <p className=\"border-(--el-border-soft) mt-1 max-w-[18rem] border-t px-(--spacing-control-x) pt-2 pb-1 text-xs leading-relaxed text-(--el-text-secondary)\">\n {footer}\n </p>\n ) : null}\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":";;;;;;;AAcO,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACKA,IAAM,yBAAA,GAA4B,OAAO,MAAA,KAAW,WAAA,GAAc,eAAA,GAAkB,SAAA;AAKpF,IAAM,aAAA,GAAgB,MAAM,MAAM;AAAC,CAAA;AAqBnC,IAAM,SAAA,GAAY,mCAAA;AAElB,SAAS,UAAA,GAAa;AACpB,EAAA,OAAO,oBAAA;AAAA,IACL,aAAA;AAAA,IACA,MAAM,IAAA;AAAA,IACN,MAAM;AAAA,GACR;AACF;AAqHA,SAAS,eAAe,EAAA,EAAgE;AACtF,EAAA,IAAI,OAAO,EAAA,CAAG,aAAA;AACd,EAAA,OAAO,IAAA,IAAQ,IAAA,KAAS,QAAA,CAAS,IAAA,EAAM;AACrC,IAAA,MAAM,EAAA,GAAK,gBAAA,CAAiB,IAAI,CAAA,CAAE,SAAA;AAClC,IAAA,IAAI,EAAA,KAAO,UAAU,EAAA,KAAO,QAAA,SAAiB,EAAE,GAAA,EAAK,IAAA,EAAM,OAAA,EAAS,IAAA,EAAK;AACxE,IAAA,IAAI,EAAA,KAAO,YAAY,EAAA,KAAO,MAAA,SAAe,EAAE,GAAA,EAAK,IAAA,EAAM,OAAA,EAAS,KAAA,EAAM;AACzE,IAAA,IAAA,GAAO,IAAA,CAAK,aAAA;AAAA,EACd;AACA,EAAA,OAAO,IAAA;AACT;AAEO,SAAS,QAAA,CAA2B;AAAA,EACzC,OAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,KAAA;AAAA,EACA,WAAA,GAAc,cAAA;AAAA,EACd,UAAA,GAAa,KAAA;AAAA,EACb,iBAAA,GAAoB,cAAA;AAAA,EACpB,SAAA,GAAY,YAAA;AAAA,EACZ,OAAA,GAAU,KAAA;AAAA,EACV,WAAA,GAAc,eAAA;AAAA,EACd,QAAA,GAAW,KAAA;AAAA,EACX,EAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA,GAAW,KAAA;AAAA,EACX,OAAA;AAAA,EACA,KAAA,EAAO,eAAA;AAAA,EACP,aAAA;AAAA,EACA;AACF,CAAA,EAAqB;AAKnB,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,IAAI,QAAA,CAAS,QAAA,IAAY,CAAC,QAAQ,CAAA;AAItD,EAAA,MAAM,iBAAiB,aAAA,KAAkB,MAAA;AACzC,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAI,SAAS,EAAE,CAAA;AACrD,EAAA,MAAM,KAAA,GAAQ,cAAA,GAAkB,eAAA,IAAmB,EAAA,GAAM,aAAA;AACzD,EAAA,MAAM,QAAA,GAAW,CAAC,CAAA,KAAe,cAAA,GAAiB,cAAc,CAAC,CAAA,GAAI,iBAAiB,CAAC,CAAA;AACvF,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,SAAS,CAAC,CAAA;AAchD,EAAA,MAAM,UAAU,UAAA,EAAW;AAG3B,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAA+B,IAAI,CAAA;AACrE,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAI,SAAS,GAAG,CAAA;AAItD,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,SAAS,KAAK,CAAA;AACpD,EAAA,MAAM,SAAS,KAAA,EAAM;AACrB,EAAA,MAAM,MAAA,GAAS,GAAG,MAAM,CAAA,QAAA,CAAA;AACxB,EAAA,MAAM,WAAW,CAAC,CAAA,KAAc,CAAA,EAAG,MAAM,QAAQ,CAAC,CAAA,CAAA;AAElD,EAAA,MAAM,YAAA,GAAe,OAAuB,IAAI,CAAA;AAChD,EAAA,MAAM,UAAA,GAAa,OAA0B,IAAI,CAAA;AACjD,EAAA,MAAM,QAAA,GAAW,OAAyB,IAAI,CAAA;AAC9C,EAAA,MAAM,OAAA,GAAU,OAAuB,IAAI,CAAA;AAC3C,EAAA,MAAM,OAAA,GAAU,OAAuB,IAAI,CAAA;AAE3C,EAAA,MAAM,QAAA,GAAW,QAAQ,MAAM;AAI7B,IAAA,IAAI,kBAAkB,CAAC,UAAA,IAAc,MAAM,IAAA,EAAK,KAAM,IAAI,OAAO,OAAA;AACjE,IAAA,MAAM,MAAA,GAAS,KAAA,CAAM,IAAA,EAAK,CAAE,WAAA,EAAY;AACxC,IAAA,OAAO,OAAA,CAAQ,MAAA;AAAA,MACb,CAAC,CAAA,KACC,CAAA,CAAE,KAAA,CAAM,WAAA,EAAY,CAAE,QAAA,CAAS,MAAM,CAAA,KACpC,CAAA,CAAE,SAAA,GAAY,CAAA,CAAE,SAAA,CAAU,WAAA,EAAY,CAAE,QAAA,CAAS,MAAM,CAAA,GAAI,KAAA,CAAA,KAC3D,CAAA,CAAE,QAAA,GAAW,CAAA,CAAE,QAAA,CAAS,WAAA,EAAY,CAAE,QAAA,CAAS,MAAM,CAAA,GAAI,KAAA;AAAA,KAC9D;AAAA,EACF,GAAG,CAAC,OAAA,EAAS,KAAA,EAAO,UAAA,EAAY,cAAc,CAAC,CAAA;AAE/C,EAAA,MAAM,QAAA,GAAW,KAAA,IAAS,IAAA,GAAQ,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,KAAA,KAAU,KAAK,CAAA,IAAK,IAAA,GAAQ,IAAA;AAIpF,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,MAAA,GAAS,CAAA,GAAI,IAAA,CAAK,IAAI,WAAA,EAAa,QAAA,CAAS,MAAA,GAAS,CAAC,CAAA,GAAI,CAAA;AAKlF,EAAA,MAAM,cAAA,GAAiB,YAAY,MAAM;AACvC,IAAA,MAAM,UAAU,UAAA,CAAW,OAAA;AAC3B,IAAA,IAAI,CAAC,OAAA,EAAS;AACd,IAAA,MAAM,IAAA,GAAO,QAAQ,qBAAA,EAAsB;AAC3C,IAAA,MAAM,GAAA,GAAM,CAAA;AACZ,IAAA,MAAM,YAAY,MAAA,CAAO,WAAA;AACzB,IAAA,MAAM,UAAA,GAAa,SAAA,GAAY,IAAA,CAAK,MAAA,GAAS,GAAA;AAC7C,IAAA,MAAM,UAAA,GAAa,KAAK,GAAA,GAAM,GAAA;AAC9B,IAAA,MAAM,aAAa,UAAA,IAAc,UAAA;AACjC,IAAA,MAAM,QAAQ,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,UAAA,GAAa,aAAa,UAAU,CAAA;AAEhE,IAAA,gBAAA,CAAiB,IAAA,CAAK,GAAA,CAAI,EAAA,EAAI,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,KAAA,IAAS,UAAA,GAAa,EAAA,GAAK,EAAA,CAAG,CAAC,CAAC,CAAA;AAC5E,IAAA,MAAM,KAAA,GAAuB;AAAA,MAC3B,QAAA,EAAU,OAAA;AAAA,MACV,IAAA,EAAM,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,IAAI,CAAA;AAAA,MAC1B,QAAA,EAAU,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,KAAK;AAAA,KACjC;AACA,IAAA,IAAI,YAAY,KAAA,CAAM,GAAA,GAAM,KAAK,KAAA,CAAM,IAAA,CAAK,SAAS,GAAG,CAAA;AAAA,eAC7C,MAAA,GAAS,IAAA,CAAK,MAAM,SAAA,GAAY,IAAA,CAAK,MAAM,GAAG,CAAA;AACzD,IAAA,YAAA,CAAa,KAAK,CAAA;AAAA,EACpB,CAAA,EAAG,CAAC,UAAU,CAAC,CAAA;AAWf,EAAA,MAAM,oBAAA,GAAuB,YAAY,MAAM;AAC7C,IAAA,MAAM,UAAU,UAAA,CAAW,OAAA;AAC3B,IAAA,IAAI,CAAC,OAAA,EAAS;AAUd,IAAA,MAAM,IAAA,GAAO,eAAe,OAAO,CAAA;AACnC,IAAA,IAAI,CAAC,IAAA,IAAQ,IAAA,CAAK,OAAA,EAAS;AACzB,MAAA,cAAA,CAAe,KAAK,CAAA;AACpB,MAAA,gBAAA,CAAiB,GAAG,CAAA;AACpB,MAAA;AAAA,IACF;AACA,IAAA,MAAM,IAAA,GAAO,QAAQ,qBAAA,EAAsB;AAC3C,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,CAAI,qBAAA,EAAsB;AAChD,IAAA,MAAM,GAAA,GAAM,CAAA;AACZ,IAAA,MAAM,KAAA,GAAQ,CAAA;AACd,IAAA,MAAM,UAAA,GAAa,QAAA,CAAS,MAAA,GAAS,IAAA,CAAK,SAAS,GAAA,GAAM,KAAA;AACzD,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,GAAA,GAAM,QAAA,CAAS,MAAM,GAAA,GAAM,KAAA;AACnD,IAAA,MAAM,aAAa,UAAA,GAAa,UAAA;AAChC,IAAA,cAAA,CAAe,UAAU,CAAA;AACzB,IAAA,MAAM,QAAQ,IAAA,CAAK,GAAA,CAAI,EAAA,EAAI,UAAA,GAAa,aAAa,UAAU,CAAA;AAG/D,IAAA,gBAAA,CAAiB,IAAA,CAAK,GAAA,CAAI,EAAA,EAAI,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,KAAA,IAAS,UAAA,GAAa,EAAA,GAAK,EAAA,CAAG,CAAC,CAAC,CAAA;AAAA,EAC9E,CAAA,EAAG,CAAC,UAAU,CAAC,CAAA;AAIf,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,EAAE,QAAQ,OAAA,CAAA,EAAU;AACxB,IAAA,MAAM,CAAA,GAAI,WAAW,MAAM;AACzB,MAAA,IAAI,UAAA,EAAY,QAAA,CAAS,OAAA,EAAS,KAAA,EAAM;AAAA,WACnC,OAAA,CAAQ,SAAS,KAAA,EAAM;AAAA,IAC9B,GAAG,CAAC,CAAA;AACJ,IAAA,OAAO,MAAM,aAAa,CAAC,CAAA;AAAA,EAC7B,CAAA,EAAG,CAAC,IAAA,EAAM,OAAA,EAAS,UAAU,CAAC,CAAA;AAK9B,EAAA,yBAAA,CAA0B,MAAM;AAC9B,IAAA,IAAI,EAAE,QAAQ,OAAA,CAAA,EAAU;AACxB,IAAA,IAAI,UAAA,CAAW,OAAA,EAAS,OAAA,CAAQ,4CAA4C,CAAA,EAAG;AAC/E,IAAA,cAAA,EAAe;AACf,IAAA,MAAM,QAAA,GAAW,MAAM,cAAA,EAAe;AACtC,IAAA,MAAA,CAAO,gBAAA,CAAiB,QAAA,EAAU,QAAA,EAAU,IAAI,CAAA;AAChD,IAAA,MAAA,CAAO,gBAAA,CAAiB,UAAU,QAAQ,CAAA;AAC1C,IAAA,OAAO,MAAM;AACX,MAAA,MAAA,CAAO,mBAAA,CAAoB,QAAA,EAAU,QAAA,EAAU,IAAI,CAAA;AACnD,MAAA,MAAA,CAAO,mBAAA,CAAoB,UAAU,QAAQ,CAAA;AAAA,IAC/C,CAAA;AAAA,EACF,GAAG,CAAC,IAAA,EAAM,SAAS,cAAA,EAAgB,QAAA,CAAS,MAAM,CAAC,CAAA;AAKnD,EAAA,yBAAA,CAA0B,MAAM;AAC9B,IAAA,IAAI,EAAE,QAAQ,OAAA,CAAA,EAAU;AACxB,IAAA,IAAI,CAAC,UAAA,CAAW,OAAA,EAAS,OAAA,CAAQ,4CAA4C,CAAA,EAAG;AAChF,IAAA,oBAAA,EAAqB;AACrB,IAAA,MAAM,QAAA,GAAW,MAAM,oBAAA,EAAqB;AAC5C,IAAA,MAAA,CAAO,gBAAA,CAAiB,QAAA,EAAU,QAAA,EAAU,IAAI,CAAA;AAChD,IAAA,MAAA,CAAO,gBAAA,CAAiB,UAAU,QAAQ,CAAA;AAC1C,IAAA,OAAO,MAAM;AACX,MAAA,MAAA,CAAO,mBAAA,CAAoB,QAAA,EAAU,QAAA,EAAU,IAAI,CAAA;AACnD,MAAA,MAAA,CAAO,mBAAA,CAAoB,UAAU,QAAQ,CAAA;AAAA,IAC/C,CAAA;AAAA,EACF,GAAG,CAAC,IAAA,EAAM,SAAS,oBAAA,EAAsB,QAAA,CAAS,MAAM,CAAC,CAAA;AAIzD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,IAAA,EAAM;AACX,IAAA,SAAS,eAAe,CAAA,EAAe;AACrC,MAAA,MAAM,SAAS,CAAA,CAAE,MAAA;AACjB,MAAA,IAAI,YAAA,CAAa,SAAS,QAAA,CAAS,MAAM,KAAK,OAAA,CAAQ,OAAA,EAAS,QAAA,CAAS,MAAM,CAAA,EAAG;AACjF,MAAA,SAAA,EAAU;AAAA,IACZ;AACA,IAAA,QAAA,CAAS,gBAAA,CAAiB,aAAa,cAAc,CAAA;AACrD,IAAA,OAAO,MAAM,QAAA,CAAS,mBAAA,CAAoB,WAAA,EAAa,cAAc,CAAA;AAAA,EACvE,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AAGT,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,IAAA,EAAM,QAAA,CAAS,cAAA,CAAe,QAAA,CAAS,MAAM,CAAC,CAAA,EAAG,cAAA,CAAe,EAAE,KAAA,EAAO,SAAA,EAAW,CAAA;AAAA,EAC1F,CAAA,EAAG,CAAC,MAAA,EAAQ,IAAI,CAAC,CAAA;AAEjB,EAAA,SAAS,QAAA,GAAW;AAClB,IAAA,IAAI,QAAA,EAAU;AAId,IAAA,IAAI,CAAC,cAAA,EAAgB,gBAAA,CAAiB,EAAE,CAAA;AACxC,IAAA,MAAM,GAAA,GAAM,KAAA,IAAS,IAAA,GAAO,OAAA,CAAQ,SAAA,CAAU,CAAC,CAAA,KAAM,CAAA,CAAE,KAAA,KAAU,KAAK,CAAA,GAAI,EAAA;AAC1E,IAAA,cAAA,CAAe,GAAA,IAAO,CAAA,GAAI,GAAA,GAAM,CAAC,CAAA;AACjC,IAAA,OAAA,CAAQ,IAAI,CAAA;AAAA,EACd;AAIA,EAAA,SAAS,SAAA,GAAY;AACnB,IAAA,OAAA,CAAQ,KAAK,CAAA;AACb,IAAA,OAAA,IAAU;AAAA,EACZ;AAEA,EAAA,SAAS,eAAA,GAAkB;AACzB,IAAA,SAAA,EAAU;AACV,IAAA,UAAA,CAAW,SAAS,KAAA,EAAM;AAAA,EAC5B;AAEA,EAAA,SAAS,OAAO,CAAA,EAAW;AACzB,IAAA,IAAI,QAAA,EAAU;AACd,IAAA,MAAM,GAAA,GAAM,SAAS,CAAC,CAAA;AACtB,IAAA,IAAI,CAAC,GAAA,IAAO,GAAA,CAAI,QAAA,EAAU;AAC1B,IAAA,QAAA,CAAS,IAAI,KAAK,CAAA;AAClB,IAAA,eAAA,EAAgB;AAAA,EAClB;AAEA,EAAA,SAAS,cAAc,CAAA,EAAwB;AAC7C,IAAA,IAAI,CAAA,CAAE,QAAQ,WAAA,EAAa;AACzB,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,cAAA,CAAe,KAAK,GAAA,CAAI,MAAA,GAAS,GAAG,QAAA,CAAS,MAAA,GAAS,CAAC,CAAC,CAAA;AAAA,IAC1D,CAAA,MAAA,IAAW,CAAA,CAAE,GAAA,KAAQ,SAAA,EAAW;AAC9B,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,cAAA,CAAe,IAAA,CAAK,GAAA,CAAI,MAAA,GAAS,CAAA,EAAG,CAAC,CAAC,CAAA;AAAA,IACxC,CAAA,MAAA,IAAW,CAAA,CAAE,GAAA,KAAQ,MAAA,EAAQ;AAC3B,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,cAAA,CAAe,CAAC,CAAA;AAAA,IAClB,CAAA,MAAA,IAAW,CAAA,CAAE,GAAA,KAAQ,KAAA,EAAO;AAC1B,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,cAAA,CAAe,QAAA,CAAS,SAAS,CAAC,CAAA;AAAA,IACpC,CAAA,MAAA,IAAW,CAAA,CAAE,GAAA,KAAQ,OAAA,EAAS;AAC5B,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,MAAA,CAAO,MAAM,CAAA;AAAA,IACf,CAAA,MAAA,IAAW,CAAA,CAAE,GAAA,KAAQ,QAAA,EAAU;AAC7B,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,eAAA,EAAgB;AAAA,IAClB;AAAA,EACF;AAEA,EAAA,SAAS,iBAAiB,CAAA,EAAwB;AAChD,IAAA,IAAI,CAAC,IAAA,KAAS,CAAA,CAAE,GAAA,KAAQ,WAAA,IAAe,EAAE,GAAA,KAAQ,OAAA,IAAW,CAAA,CAAE,GAAA,KAAQ,GAAA,CAAA,EAAM;AAC1E,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,QAAA,EAAS;AAAA,IACX;AAAA,EACF;AAEA,EAAA,MAAM,WAAW,QAAA,CAAS,MAAA,GAAS,CAAA,GAAI,QAAA,CAAS,MAAM,CAAA,GAAI,MAAA;AAQ1D,EAAA,MAAM,UAAA,GAAa,CAAC,OAAA,IAAW,QAAA,CAAS,MAAA,GAAS,CAAA;AAkBjD,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAS,KAAK,CAAA;AAC9C,EAAA,yBAAA,CAA0B,MAAM;AAC9B,IAAA,WAAA,CAAY,CAAC,CAAC,UAAA,CAAW,OAAA,EAAS,OAAA,CAAQ,4CAA4C,CAAC,CAAA;AAAA,EACzF,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AAET,EAAA,MAAM,SAAA,mBACJ,IAAA,CAAAA,QAAAA,EAAA,EACG,QAAA,EAAA;AAAA,IAAA,UAAA,mBACC,GAAA;AAAA,MAAC,OAAA;AAAA,MAAA;AAAA,QACC,GAAA,EAAK,QAAA;AAAA,QACL,IAAA,EAAK,MAAA;AAAA,QACL,IAAA,EAAK,UAAA;AAAA,QACL,eAAA,EAAa,IAAA;AAAA,QACb,eAAA,EAAe,MAAA;AAAA,QACf,uBAAA,EAAuB,QAAA;AAAA,QACvB,YAAA,EAAY,iBAAA;AAAA,QACZ,KAAA,EAAO,KAAA;AAAA,QACP,QAAA,EAAU,CAAC,CAAA,KAAM;AACf,UAAA,QAAA,CAAS,CAAA,CAAE,OAAO,KAAK,CAAA;AACvB,UAAA,cAAA,CAAe,CAAC,CAAA;AAAA,QAClB,CAAA;AAAA,QACA,SAAA,EAAW,aAAA;AAAA,QACX,WAAA,EAAa,iBAAA;AAAA,QACb,SAAA,EAAU;AAAA;AAAA,KACZ,GACE,IAAA;AAAA,oBACJ,GAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAA,EAAK,OAAA;AAAA,QACL,EAAA,EAAI,MAAA;AAAA,QACJ,IAAA,EAAM,aAAa,SAAA,GAAY,QAAA;AAAA,QAC/B,YAAA,EAAY,KAAA;AAAA,QACZ,QAAA,EAAU,UAAA,IAAc,CAAC,UAAA,GAAa,CAAA,GAAI,EAAA;AAAA,QAC1C,uBAAA,EAAuB,UAAA,IAAc,CAAC,UAAA,GAAa,QAAA,GAAW,MAAA;AAAA,QAC9D,SAAA,EAAW,UAAA,IAAc,CAAC,UAAA,GAAa,aAAA,GAAgB,MAAA;AAAA,QAKvD,KAAA,EAAO,EAAE,SAAA,EAAW,aAAA,EAAc;AAAA,QAClC,SAAA,EAAU,6CAAA;AAAA,QAET,QAAA,EAAA,OAAA,uBACE,GAAA,EAAA,EAAE,SAAA,EAAU,8CAA8C,QAAA,EAAA,WAAA,EAAY,CAAA,GACrE,SAAS,MAAA,KAAW,CAAA,uBACrB,GAAA,EAAA,EAAE,SAAA,EAAU,8CAA8C,QAAA,EAAA,SAAA,EAAU,CAAA,GAErE,SAAS,GAAA,CAAI,CAAC,KAAK,CAAA,KAAM;AACvB,UAAA,MAAM,UAAA,GAAa,IAAI,KAAA,KAAU,KAAA;AACjC,UAAA,MAAM,WAAW,CAAA,KAAM,MAAA;AAIvB,UAAA,MAAM,MAAA,GACJ,GAAA,CAAI,KAAA,KAAU,MAAA,IAAa,GAAA,CAAI,UAAU,QAAA,CAAS,CAAA,GAAI,CAAC,CAAA,EAAG,KAAA,mBACxD,GAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cAEC,IAAA,EAAK,cAAA;AAAA,cACL,SAAA,EAAU,0HAAA;AAAA,cAET,QAAA,EAAA,GAAA,CAAI;AAAA,aAAA;AAAA,YAJA,CAAA,MAAA,EAAS,IAAI,KAAK,CAAA;AAAA,WAKzB,GACE,IAAA;AACN,UAAA,4BACGA,UAAA,EAAA,EACE,QAAA,EAAA;AAAA,YAAA,MAAA;AAAA,4BACD,IAAA;AAAA,cAAC,KAAA;AAAA,cAAA;AAAA,gBACC,EAAA,EAAI,SAAS,CAAC,CAAA;AAAA,gBACd,IAAA,EAAK,QAAA;AAAA,gBACL,eAAA,EAAe,UAAA;AAAA,gBACf,eAAA,EAAe,GAAA,CAAI,QAAA,GAAW,IAAA,GAAO,MAAA;AAAA,gBACrC,YAAA,EAAc,MAAM,cAAA,CAAe,CAAC,CAAA;AAAA,gBACpC,OAAA,EAAS,MAAM,MAAA,CAAO,CAAC,CAAA;AAAA,gBACvB,SAAA,EAAW,EAAA;AAAA,kBACT,8GAAA;AAAA,kBACA,GAAA,CAAI,WAAW,gBAAA,GAAmB,gBAAA;AAAA,kBAClC,WAAW,4BAAA,GAA+B,EAAA;AAAA,kBAC1C,GAAA,CAAI,WAAW,4BAAA,GAA+B;AAAA,iBAChD;AAAA,gBAEC,QAAA,EAAA;AAAA,kBAAA,GAAA,CAAI,IAAA,uBACF,MAAA,EAAA,EAAK,aAAA,EAAW,MAAC,SAAA,EAAW,SAAA,EAC1B,QAAA,EAAA,GAAA,CAAI,IAAA,EACP,CAAA,GACE,IAAA;AAAA,kBACH,GAAA,CAAI,WAAA,mBACH,IAAA,CAAC,MAAA,EAAA,EAAK,WAAU,uBAAA,EACd,QAAA,EAAA;AAAA,oCAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,UAAA,EAAY,QAAA,EAAA,GAAA,CAAI,KAAA,EAAM,CAAA;AAAA,oCACtC,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,iFAAA,EACb,cAAI,WAAA,EACP;AAAA,mBAAA,EACF,oBAEA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,UAAA,EAAY,cAAI,KAAA,EAAM,CAAA;AAAA,kBAEvC,GAAA,CAAI,SAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oCAKH,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,sDAAA,EACb,cAAI,SAAA,EACP;AAAA,sBACE,IAAA;AAAA,kBACH,GAAA,CAAI,QAAA;AAAA;AAAA;AAAA,oCAGH,GAAA;AAAA,sBAAC,MAAA;AAAA,sBAAA;AAAA,wBACC,SAAA,EAAW,EAAA;AAAA,0BACT,oCAAA;AAAA,0BACA,GAAA,CAAI,YAAY,QAAA,GAAW;AAAA,yBAC7B;AAAA,wBAEC,QAAA,EAAA,GAAA,CAAI;AAAA;AAAA;AACP,sBACE,IAAA;AAAA,kBACH,UAAA;AAAA;AAAA;AAAA;AAAA,oCAIC,GAAA;AAAA,sBAAC,KAAA;AAAA,sBAAA;AAAA,wBACC,SAAA,EAAU,qDAAA;AAAA,wBACV,aAAA,EAAW;AAAA;AAAA;AACb,sBACE;AAAA;AAAA;AAAA;AACN,WAAA,EAAA,EA7Da,IAAI,KA8DnB,CAAA;AAAA,QAEJ,CAAC;AAAA;AAAA,KAEL;AAAA,IACC,yBACC,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,6IAAA,EACV,kBACH,CAAA,GACE;AAAA,GAAA,EACN,CAAA;AAMF,EAAA,MAAM,OAAO,QAAA,mBACX,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA,EAAK,OAAA;AAAA,MACL,mBAAA,EAAkB,EAAA;AAAA,MAClB,SAAA,EAAW,EAAA;AAAA,QACT,mGAAA;AAAA,QACA,wDAAA;AAAA;AAAA;AAAA,QAGA,cAAc,kBAAA,GAAqB;AAAA,OACrC;AAAA,MAEC,QAAA,EAAA;AAAA;AAAA,GACH,mBAEA,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA,EAAK,OAAA;AAAA,MACL,mBAAA,EAAkB,EAAA;AAAA,MAIlB,OAAO,SAAA,IAAa,EAAE,QAAA,EAAU,OAAA,EAAS,YAAY,QAAA,EAAS;AAAA,MAC9D,SAAA,EAAW,EAAA;AAAA,QACT,wEAAA;AAAA,QACA;AAAA,OACF;AAAA,MAEC,QAAA,EAAA;AAAA;AAAA,GACH;AAGF,EAAA;AAAA;AAAA;AAAA;AAAA,oBAIE,IAAA,CAAC,SAAI,GAAA,EAAK,YAAA,EAAc,sBAAoB,IAAA,GAAO,IAAA,GAAO,MAAA,EAAW,SAAA,EAAU,UAAA,EAC7E,QAAA,EAAA;AAAA,sBAAA,IAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACC,GAAA,EAAK,UAAA;AAAA,UACL,IAAA,EAAK,QAAA;AAAA,UACL,EAAA;AAAA,UACA,IAAA,EAAK,UAAA;AAAA,UACL,eAAA,EAAc,SAAA;AAAA,UACd,eAAA,EAAe,IAAA;AAAA,UACf,eAAA,EAAe,OAAO,MAAA,GAAS,MAAA;AAAA,UAC/B,YAAA,EAAY,KAAA;AAAA,UACZ,QAAA;AAAA,UACA,OAAA,EAAS,MAAO,IAAA,GAAO,SAAA,KAAc,QAAA,EAAS;AAAA,UAC9C,SAAA,EAAW,gBAAA;AAAA,UACX,SAAA,EAAW,EAAA;AAAA,YACT,6JAAA;AAAA,YACA,yFAAA;AAAA,YACA,qBAAA;AAAA,YACA;AAAA,WACF;AAAA,UAEC,QAAA,EAAA;AAAA,YAAA,QAAA,mBACC,IAAA,CAAAA,UAAA,EACG,QAAA,EAAA;AAAA,cAAA,QAAA,CAAS,IAAA,uBACP,MAAA,EAAA,EAAK,aAAA,EAAW,MAAC,SAAA,EAAW,SAAA,EAC1B,QAAA,EAAA,QAAA,CAAS,IAAA,EACZ,CAAA,GACE,IAAA;AAAA,8BACJ,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,2BAAA,EAA6B,mBAAS,KAAA,EAAM,CAAA;AAAA,cAC3D,QAAA,CAAS,SAAA;AAAA;AAAA,gCAER,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,sDAAA,EACb,mBAAS,SAAA,EACZ;AAAA,kBACE;AAAA,aAAA,EACN,CAAA,mBAEA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,mCAAmC,QAAA,EAAA,WAAA,EAAY,CAAA;AAAA,4BAEjE,GAAA,CAAC,cAAA,EAAA,EAAe,SAAA,EAAU,iDAAA,EAAkD,eAAW,IAAA,EAAC;AAAA;AAAA;AAAA,OAC1F;AAAA,MAEC,IAAA,GAAQ,WAAW,IAAA,GAAO,OAAA,GAAU,aAAa,IAAA,EAAM,QAAA,CAAS,IAAI,CAAA,GAAI,IAAA,GAAQ;AAAA,KAAA,EACnF;AAAA;AAEJ","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 /**\n * A one-line description drawn UNDER the label in the menu row only — never in\n * the trigger, which stays the label alone (the workspace role picker's\n * \"what this role does\" line, MOTIR-6465). Decorative for the accessible name,\n * which stays `label`.\n */\n description?: 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 * A non-interactive note pinned BELOW the listbox, inside the open menu\n * (MOTIR-5582 — the Monitoring room's minimum-level control says, at the\n * action that causes it, that lowering also re-checks earlier issues). It is\n * not an option: keyboard navigation and the listbox's option indices are\n * unaffected, and it is announced as ordinary text. Omit it for no note.\n */\n footer?: ReactNode;\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 footer,\n}: ComboboxProps<T>) {\n // A DISABLED combobox never opens — not even when it mounts with `autoOpen`\n // (MOTIR-6173). An inline editor that mounts open for an actor who may not\n // write used to hand them a live menu whose pick the server then refused\n // (MOTIR-4822); the trigger was disabled, the menu was not.\n const [open, setOpen] = useState(autoOpen && !disabled);\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 if (disabled) return;\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 if (disabled) return;\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 {opt.description ? (\n <span className=\"flex min-w-0 flex-col\">\n <span className=\"truncate\">{opt.label}</span>\n <span className=\"text-(--el-text-secondary) max-w-[18rem] text-xs leading-snug whitespace-normal\">\n {opt.description}\n </span>\n </span>\n ) : (\n <span className=\"truncate\">{opt.label}</span>\n )}\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 {footer ? (\n <p className=\"border-(--el-border-soft) mt-1 max-w-[18rem] border-t px-(--spacing-control-x) pt-2 pb-1 text-xs leading-relaxed text-(--el-text-secondary)\">\n {footer}\n </p>\n ) : null}\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"]}
@@ -37,6 +37,15 @@ interface ErrorStateProps extends HTMLAttributes<HTMLDivElement> {
37
37
  * (motir-core wires it from its `common.retry`).
38
38
  */
39
39
  retryLabel?: string;
40
+ /**
41
+ * The retry is IN FLIGHT: the button takes `Button`'s `loading` state
42
+ * (Spinner, `disabled`, `aria-busy`) and shows `retryPendingLabel`. For a
43
+ * retry that re-requests something slow — a route segment's server render
44
+ * (motir-core's error boundaries, MOTIR-6855 · design MOTIR-6854 panel 1a).
45
+ */
46
+ retryPending?: boolean;
47
+ /** Label while `retryPending`. Defaults to `retryLabel` — i18n, as above, is the app's. */
48
+ retryPendingLabel?: string;
40
49
  }
41
50
  declare const ErrorState: react.ForwardRefExoticComponent<ErrorStateProps & react.RefAttributes<HTMLDivElement>>;
42
51
 
@@ -137,7 +137,17 @@ var Card = forwardRef(function Card2({ tint, clickable, header, footer, classNam
137
137
  }
138
138
  );
139
139
  });
140
- var ErrorState = forwardRef(function ErrorState2({ title, description, error, retry, className, retryLabel = "Try again", ...rest }, ref) {
140
+ var ErrorState = forwardRef(function ErrorState2({
141
+ title,
142
+ description,
143
+ error,
144
+ retry,
145
+ className,
146
+ retryLabel = "Try again",
147
+ retryPending = false,
148
+ retryPendingLabel,
149
+ ...rest
150
+ }, ref) {
141
151
  const showErrorDetail = error && process.env.NODE_ENV !== "production";
142
152
  return /* @__PURE__ */ jsxs(
143
153
  Card,
@@ -158,7 +168,7 @@ var ErrorState = forwardRef(function ErrorState2({ title, description, error, re
158
168
  /* @__PURE__ */ jsx("h2", { className: "font-serif text-xl text-(--el-text)", children: title }),
159
169
  description ? /* @__PURE__ */ jsx("p", { className: "text-(--el-text-muted) mt-(--spacing-sm) max-w-prose font-sans text-sm", children: description }) : null,
160
170
  showErrorDetail ? /* @__PURE__ */ jsx("pre", { className: "bg-(--el-surface) text-(--el-text-secondary) mt-(--spacing-md) max-w-full overflow-x-auto rounded-(--radius-control) px-(--spacing-tooltip-x) py-(--spacing-tooltip-y) text-left font-mono text-xs", children: error.message }) : null,
161
- retry ? /* @__PURE__ */ jsx("div", { className: "mt-(--spacing-md)", children: /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: retry, children: retryLabel }) }) : null
171
+ retry ? /* @__PURE__ */ jsx("div", { className: "mt-(--spacing-md)", children: /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: retry, loading: retryPending, children: retryPending ? retryPendingLabel ?? retryLabel : retryLabel }) }) : null
162
172
  ]
163
173
  }
164
174
  );
@@ -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/ErrorState.tsx"],"names":["Spinner","cva","forwardRef","Button","jsx","Card","jsxs","ErrorState"],"mappings":";;;;;;;;AAcO,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACHA,IAAM,eAAA,GAAkB,GAAA;AAAA,EACtB,4EAAA;AAAA,EACA;AAAA,IACE,QAAA,EAAU;AAAA,MACR,IAAA,EAAM;AAAA,QACJ,EAAA,EAAI,wBAAA;AAAA,QACJ,EAAA,EAAI,kBAAA;AAAA,QACJ,EAAA,EAAI;AAAA;AACN,KACF;AAAA,IACA,eAAA,EAAiB,EAAE,IAAA,EAAM,IAAA;AAAK;AAElC,CAAA;AAKO,IAAM,OAAA,GAAU,UAAA,CAA0C,SAASA,QAAAA,CACxE,EAAE,WAAW,IAAA,EAAM,GAAG,IAAA,EAAK,EAC3B,GAAA,EACA;AACA,EAAA,uBACE,GAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,IAAA,EAAK,QAAA;AAAA,MACL,YAAA,EAAY,IAAA,CAAK,YAAY,CAAA,IAAK,SAAA;AAAA,MAClC,WAAW,EAAA,CAAG,eAAA,CAAgB,EAAE,IAAA,EAAM,GAAG,SAAS,CAAA;AAAA,MACjD,GAAG;AAAA;AAAA,GACN;AAEJ,CAAC,CAAA;ACtBM,IAAM,cAAA,GAAiBC,GAAAA;AAAA,EAC5B,EAAA;AAAA;AAAA,IAEE,+CAAA;AAAA;AAAA,IAEA,4CAAA;AAAA;AAAA,IAEA,wBAAA;AAAA;AAAA,IAEA,6FAAA;AAAA,IACA,+BAAA;AAAA,IACA,0JAAA;AAAA,IACA;AAAA,GACF;AAAA,EACA;AAAA,IACE,QAAA,EAAU;AAAA,MACR,OAAA,EAAS;AAAA,QACP,OAAA,EAAS,2DAAA;AAAA,QACT,SAAA,EACE,4FAAA;AAAA,QACF,KAAA,EAAO,yDAAA;AAAA,QACP,MAAA,EAAQ;AAAA,OACV;AAAA,MACA,IAAA,EAAM;AAAA,QACJ,EAAA,EAAI,kCAAA;AAAA,QACJ,EAAA,EAAI,0CAAA;AAAA,QACJ,EAAA,EAAI;AAAA;AACN,KACF;AAAA,IACA,eAAA,EAAiB;AAAA,MACf,OAAA,EAAS,SAAA;AAAA,MACT,IAAA,EAAM;AAAA;AACR;AAEJ,CAAA;AAYO,IAAM,SAASC,UAAAA,CAA2C,SAASC,OAAAA,CACxE,EAAE,SAAS,IAAA,EAAM,OAAA,EAAS,QAAA,EAAU,SAAA,EAAW,UAAU,SAAA,EAAW,QAAA,EAAU,GAAG,IAAA,IACjF,GAAA,EACA;AACA,EAAA,uBACE,IAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,IAAA,EAAK,QAAA;AAAA,MAIL,gBAAc,OAAA,IAAW,SAAA;AAAA,MACzB,SAAA,EAAW,GAAG,cAAA,CAAe,EAAE,SAAS,IAAA,EAAM,GAAG,SAAS,CAAA;AAAA,MAC1D,UAAU,QAAA,IAAY,OAAA;AAAA,MACtB,aAAW,OAAA,IAAW,MAAA;AAAA,MACrB,GAAG,IAAA;AAAA,MAEH,QAAA,EAAA;AAAA,QAAA,OAAA,mBACCC,IAAC,OAAA,EAAA,EAAQ,IAAA,EAAM,SAAS,IAAA,GAAO,IAAA,GAAO,MAAM,aAAA,EAAW,IAAA,EAAC,IACtD,QAAA,mBACFA,IAAC,MAAA,EAAA,EAAK,aAAA,EAAW,MAAC,SAAA,EAAU,aAAA,EACzB,oBACH,CAAA,GACE,IAAA;AAAA,wBACJA,GAAAA,CAAC,MAAA,EAAA,EAAM,QAAA,EAAS,CAAA;AAAA,QACf,CAAC,OAAA,IAAW,SAAA,mBACXA,GAAAA,CAAC,MAAA,EAAA,EAAK,aAAA,EAAW,IAAA,EAAC,SAAA,EAAU,aAAA,EACzB,QAAA,EAAA,SAAA,EACH,CAAA,GACE;AAAA;AAAA;AAAA,GACN;AAEJ,CAAC,CAAA;AC/ED,IAAM,YAAA,GAAeH,GAAAA;AAAA,EACnB,EAAA;AAAA,IACE,qDAAA;AAAA,IACA,4BAAA;AAAA,IACA;AAAA,GACF;AAAA,EACA;AAAA,IACE,QAAA,EAAU;AAAA,MACR,IAAA,EAAM;AAAA,QACJ,IAAA,EAAM,gBAAA;AAAA,QACN,KAAA,EAAO,yCAAA;AAAA,QACP,IAAA,EAAM,wCAAA;AAAA,QACN,IAAA,EAAM,wCAAA;AAAA,QACN,QAAA,EAAU,4CAAA;AAAA,QACV,GAAA,EAAK,uCAAA;AAAA,QACL,MAAA,EAAQ;AAAA,OACV;AAAA,MACA,SAAA,EAAW;AAAA,QACT,IAAA,EAAM,sMAAA;AAAA,QACN,KAAA,EAAO;AAAA;AACT,KACF;AAAA,IACA,eAAA,EAAiB,EAAE,IAAA,EAAM,MAAA,EAAQ,WAAW,KAAA;AAAM;AAEtD,CAAA;AASO,IAAM,IAAA,GAAOC,UAAAA,CAAsC,SAASG,KAAAA,CACjE,EAAE,IAAA,EAAM,SAAA,EAAW,MAAA,EAAQ,MAAA,EAAQ,SAAA,EAAW,QAAA,EAAU,GAAG,IAAA,IAC3D,GAAA,EACA;AACA,EAAA,uBACEC,IAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,SAAA,EAAW,GAAG,YAAA,CAAa,EAAE,MAAM,SAAA,EAAW,GAAG,SAAS,CAAA;AAAA,MAI1D,WAAA,EAAU,EAAA;AAAA,MAKT,GAAI,QAAQ,IAAA,KAAS,MAAA,GAAS,EAAC,GAAI,EAAE,gBAAgB,MAAA,EAAO;AAAA,MAC5D,GAAI,YAAY,EAAE,QAAA,EAAU,GAAG,IAAA,EAAM,QAAA,KAAa,EAAC;AAAA,MACnD,GAAG,IAAA;AAAA,MAQH,QAAA,EAAA;AAAA,QAAA,MAAA,mBACCF,IAAC,KAAA,EAAA,EAAI,iBAAA,EAAgB,SAAQ,SAAA,EAAU,mBAAA,EACpC,kBACH,CAAA,GACE,IAAA;AAAA,wBACJA,GAAAA,CAAC,KAAA,EAAA,EAAK,QAAA,EAAS,CAAA;AAAA,QACd,yBACCA,GAAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,iBAAA,EAAgB,MAAA;AAAA,YAChB,SAAA,EAAU,mEAAA;AAAA,YAET,QAAA,EAAA;AAAA;AAAA,SACH,GACE;AAAA;AAAA;AAAA,GACN;AAEJ,CAAC,CAAA;ACnDM,IAAM,UAAA,GAAaF,UAAAA,CAA4C,SAASK,WAAAA,CAC7E,EAAE,KAAA,EAAO,WAAA,EAAa,KAAA,EAAO,KAAA,EAAO,WAAW,UAAA,GAAa,WAAA,EAAa,GAAG,IAAA,IAC5E,GAAA,EACA;AACA,EAAA,MAAM,eAAA,GAAkB,KAAA,IAAS,OAAA,CAAQ,GAAA,CAAI,QAAA,KAAa,YAAA;AAC1D,EAAA,uBACED,IAAAA;AAAA,IAAC,IAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,IAAA,EAAK,OAAA;AAAA,MACL,SAAA,EAAW,EAAA,CAAG,wCAAA,EAA0C,SAAS,CAAA;AAAA,MAChE,GAAG,IAAA;AAAA,MAEJ,QAAA,EAAA;AAAA,wBAAAF,GAAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,SAAA,EAAU,qEAAA;AAAA,YACV,KAAA,EAAO,EAAE,KAAA,EAAO,kBAAA,EAAmB;AAAA,YAEnC,0BAAAA,GAAAA,CAAC,aAAA,EAAA,EAAc,SAAA,EAAU,WAAA,EAAY,eAAW,IAAA,EAAC;AAAA;AAAA,SACnD;AAAA,wBACAA,GAAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,uCAAuC,QAAA,EAAA,KAAA,EAAM,CAAA;AAAA,QAC1D,8BACCA,GAAAA,CAAC,OAAE,SAAA,EAAU,wEAAA,EACV,uBACH,CAAA,GACE,IAAA;AAAA,QACH,eAAA,mBACCA,GAAAA,CAAC,KAAA,EAAA,EAAI,WAAU,oMAAA,EACZ,QAAA,EAAA,KAAA,CAAM,SACT,CAAA,GACE,IAAA;AAAA,QACH,KAAA,mBACCA,GAAAA,CAAC,KAAA,EAAA,EAAI,WAAU,mBAAA,EACb,QAAA,kBAAAA,GAAAA,CAAC,MAAA,EAAA,EAAO,SAAQ,WAAA,EAAY,OAAA,EAAS,KAAA,EAClC,QAAA,EAAA,UAAA,EACH,GACF,CAAA,GACE;AAAA;AAAA;AAAA,GACN;AAEJ,CAAC","file":"ErrorState.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 { AlertTriangle } from 'lucide-react';\nimport { Button } from './Button';\nimport { Card } from './Card';\nimport { cn } from '../../utils/cn';\n\n/**\n * ErrorState — composed pattern for \"something failed\" screens.\n *\n * Use this whenever a request, mutation, or background job has failed in a\n * way the user needs to acknowledge or recover from. Prefer this over a\n * silent log line whenever the failure prevents the user from progressing.\n *\n * Has `role=\"alert\"` so screen readers announce the failure when it's\n * inserted into the DOM. The `error.message` debug detail renders only in\n * non-production builds (Next.js statically replaces `process.env.NODE_ENV`\n * at build time, so the block is dead-code-eliminated in production).\n *\n * @example\n * <ErrorState\n * title=\"Couldn't load workspace\"\n * description=\"We couldn't reach the server. Check your connection and try again.\"\n * error={err}\n * retry={() => refetch()}\n * />\n */\nexport interface ErrorStateProps extends HTMLAttributes<HTMLDivElement> {\n /** Short, plain-language summary of what failed. Required. */\n title: string;\n /** Recovery hint or context. Avoid restating the title. */\n description?: string;\n /** Real Error object — rendered as a mono code block in dev only. */\n error?: Error;\n /** Callback for the \"Try again\" button; omit to hide the button. */\n retry?: () => void;\n /**\n * Label for the retry button. Defaults to `'Try again'`. i18n is the consuming\n * app's concern — the design-system package is framework-agnostic (no\n * `next-intl` dependency), so a localized app passes the translated string\n * (motir-core wires it from its `common.retry`).\n */\n retryLabel?: string;\n}\n\nexport const ErrorState = forwardRef<HTMLDivElement, ErrorStateProps>(function ErrorState(\n { title, description, error, retry, className, retryLabel = 'Try again', ...rest },\n ref,\n) {\n const showErrorDetail = error && process.env.NODE_ENV !== 'production';\n return (\n <Card\n ref={ref}\n role=\"alert\"\n className={cn('flex flex-col items-center text-center', className)}\n {...rest}\n >\n <div\n className=\"mb-(--spacing-md) inline-flex h-12 w-12 items-center justify-center\"\n style={{ color: 'var(--el-danger)' }}\n >\n <AlertTriangle className=\"h-12 w-12\" aria-hidden />\n </div>\n <h2 className=\"font-serif text-xl text-(--el-text)\">{title}</h2>\n {description ? (\n <p className=\"text-(--el-text-muted) mt-(--spacing-sm) max-w-prose font-sans text-sm\">\n {description}\n </p>\n ) : null}\n {showErrorDetail ? (\n <pre className=\"bg-(--el-surface) text-(--el-text-secondary) mt-(--spacing-md) max-w-full overflow-x-auto rounded-(--radius-control) px-(--spacing-tooltip-x) py-(--spacing-tooltip-y) text-left font-mono text-xs\">\n {error.message}\n </pre>\n ) : null}\n {retry ? (\n <div className=\"mt-(--spacing-md)\">\n <Button variant=\"secondary\" onClick={retry}>\n {retryLabel}\n </Button>\n </div>\n ) : null}\n </Card>\n );\n});\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/ErrorState.tsx"],"names":["Spinner","cva","forwardRef","Button","jsx","Card","jsxs","ErrorState"],"mappings":"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","file":"ErrorState.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 { AlertTriangle } from 'lucide-react';\nimport { Button } from './Button';\nimport { Card } from './Card';\nimport { cn } from '../../utils/cn';\n\n/**\n * ErrorState — composed pattern for \"something failed\" screens.\n *\n * Use this whenever a request, mutation, or background job has failed in a\n * way the user needs to acknowledge or recover from. Prefer this over a\n * silent log line whenever the failure prevents the user from progressing.\n *\n * Has `role=\"alert\"` so screen readers announce the failure when it's\n * inserted into the DOM. The `error.message` debug detail renders only in\n * non-production builds (Next.js statically replaces `process.env.NODE_ENV`\n * at build time, so the block is dead-code-eliminated in production).\n *\n * @example\n * <ErrorState\n * title=\"Couldn't load workspace\"\n * description=\"We couldn't reach the server. Check your connection and try again.\"\n * error={err}\n * retry={() => refetch()}\n * />\n */\nexport interface ErrorStateProps extends HTMLAttributes<HTMLDivElement> {\n /** Short, plain-language summary of what failed. Required. */\n title: string;\n /** Recovery hint or context. Avoid restating the title. */\n description?: string;\n /** Real Error object — rendered as a mono code block in dev only. */\n error?: Error;\n /** Callback for the \"Try again\" button; omit to hide the button. */\n retry?: () => void;\n /**\n * Label for the retry button. Defaults to `'Try again'`. i18n is the consuming\n * app's concern — the design-system package is framework-agnostic (no\n * `next-intl` dependency), so a localized app passes the translated string\n * (motir-core wires it from its `common.retry`).\n */\n retryLabel?: string;\n /**\n * The retry is IN FLIGHT: the button takes `Button`'s `loading` state\n * (Spinner, `disabled`, `aria-busy`) and shows `retryPendingLabel`. For a\n * retry that re-requests something slow — a route segment's server render\n * (motir-core's error boundaries, MOTIR-6855 · design MOTIR-6854 panel 1a).\n */\n retryPending?: boolean;\n /** Label while `retryPending`. Defaults to `retryLabel` — i18n, as above, is the app's. */\n retryPendingLabel?: string;\n}\n\nexport const ErrorState = forwardRef<HTMLDivElement, ErrorStateProps>(function ErrorState(\n {\n title,\n description,\n error,\n retry,\n className,\n retryLabel = 'Try again',\n retryPending = false,\n retryPendingLabel,\n ...rest\n },\n ref,\n) {\n const showErrorDetail = error && process.env.NODE_ENV !== 'production';\n return (\n <Card\n ref={ref}\n role=\"alert\"\n className={cn('flex flex-col items-center text-center', className)}\n {...rest}\n >\n <div\n className=\"mb-(--spacing-md) inline-flex h-12 w-12 items-center justify-center\"\n style={{ color: 'var(--el-danger)' }}\n >\n <AlertTriangle className=\"h-12 w-12\" aria-hidden />\n </div>\n <h2 className=\"font-serif text-xl text-(--el-text)\">{title}</h2>\n {description ? (\n <p className=\"text-(--el-text-muted) mt-(--spacing-sm) max-w-prose font-sans text-sm\">\n {description}\n </p>\n ) : null}\n {showErrorDetail ? (\n <pre className=\"bg-(--el-surface) text-(--el-text-secondary) mt-(--spacing-md) max-w-full overflow-x-auto rounded-(--radius-control) px-(--spacing-tooltip-x) py-(--spacing-tooltip-y) text-left font-mono text-xs\">\n {error.message}\n </pre>\n ) : null}\n {retry ? (\n <div className=\"mt-(--spacing-md)\">\n <Button variant=\"secondary\" onClick={retry} loading={retryPending}>\n {retryPending ? (retryPendingLabel ?? retryLabel) : retryLabel}\n </Button>\n </div>\n ) : null}\n </Card>\n );\n});\n"]}
@@ -269,10 +269,17 @@ var PALETTE_REGISTRY = {
269
269
  motir: {
270
270
  id: "motir",
271
271
  name: "Motir",
272
- tagline: "Warm and editorial \u2014 cream surfaces, charcoal ink, a purple primary, pastel tints.",
273
- inspiration: "Notion's warm marketing palette \u2014 the product's house colours.",
272
+ tagline: "Stark and editorial \u2014 cool greyscale surfaces + ink, an ink CTA, a single restrained cool-blue accent.",
273
+ inspiration: "Vercel's black-and-white precision + Linear's ultra-minimal, on Radix Slate/Blue scales.",
274
274
  designDoc: "docs/palettes/motir.md"
275
275
  },
276
+ amethyst: {
277
+ id: "amethyst",
278
+ name: "Amethyst",
279
+ tagline: "Warm and editorial \u2014 cream surfaces, charcoal ink, a purple primary, pastel tints.",
280
+ inspiration: "Notion's warm marketing palette \u2014 Motir's house colours until the monochrome rename.",
281
+ designDoc: "docs/palettes/amethyst.md"
282
+ },
276
283
  cobalt: {
277
284
  id: "cobalt",
278
285
  name: "Cobalt",
@@ -280,13 +287,6 @@ var PALETTE_REGISTRY = {
280
287
  inspiration: "Coinbase's clean institutional blue + IBM's structured blue, on Radix Blue/Indigo/Slate scales.",
281
288
  designDoc: "docs/palettes/cobalt.md"
282
289
  },
283
- graphite: {
284
- id: "graphite",
285
- name: "Graphite",
286
- tagline: "Stark and editorial \u2014 cool greyscale surfaces + ink, an ink CTA, a single restrained cool-blue accent.",
287
- inspiration: "Vercel's black-and-white precision + Linear's ultra-minimal, on Radix Slate/Blue scales.",
288
- designDoc: "docs/palettes/graphite.md"
289
- },
290
290
  evergreen: {
291
291
  id: "evergreen",
292
292
  name: "Evergreen",
@@ -347,12 +347,28 @@ var THEME_STORAGE_KEYS = {
347
347
  pattern: "motir.theme.pattern",
348
348
  style: "motir.theme.style",
349
349
  palette: "motir.theme.palette",
350
- type: "motir.theme.type"
350
+ type: "motir.theme.type",
351
+ /**
352
+ * The version marker of the STORED palette id's meaning. Absent (or anything
353
+ * but {@link PALETTE_IDS_VERSION}) means the stored `palette` was written
354
+ * before MOTIR-6471 swapped the palette identities, so it is read through
355
+ * {@link PALETTE_ID_MIGRATION} once and the marker is then written.
356
+ */
357
+ paletteIds: "motir.theme.paletteIds"
351
358
  };
359
+ var PALETTE_IDS_VERSION = "2";
360
+ var PALETTE_ID_MIGRATION = {
361
+ motir: "amethyst",
362
+ graphite: "motir"
363
+ };
364
+ function migrateStoredPaletteId(value, marker) {
365
+ if (value === null || marker === PALETTE_IDS_VERSION) return value;
366
+ if (!Object.prototype.hasOwnProperty.call(PALETTE_ID_MIGRATION, value)) return value;
367
+ return PALETTE_ID_MIGRATION[value] ?? value;
368
+ }
352
369
  var THEME_DEFAULTS = {
353
370
  pattern: "system",
354
- style: DEFAULT_STYLE_ID,
355
- palette: DEFAULT_PALETTE_ID};
371
+ style: DEFAULT_STYLE_ID};
356
372
  var SYNC_DEBOUNCE_MS = 250;
357
373
  var ThemeContext = createContext(null);
358
374
  function readStorage(key, fallback) {
@@ -396,7 +412,10 @@ function ThemeProvider({
396
412
  if (initialPreference) {
397
413
  return isPaletteId(initialPreference.paletteId) ? initialPreference.paletteId : DEFAULT_PALETTE_ID;
398
414
  }
399
- const stored = readStorage(THEME_STORAGE_KEYS.palette, THEME_DEFAULTS.palette);
415
+ const stored = migrateStoredPaletteId(
416
+ readStorage(THEME_STORAGE_KEYS.palette, "") || null,
417
+ readStorage(THEME_STORAGE_KEYS.paletteIds, "") || null
418
+ );
400
419
  return isPaletteId(stored) ? stored : DEFAULT_PALETTE_ID;
401
420
  });
402
421
  const [typeChoice, setTypeChoiceState] = useState(() => {
@@ -500,6 +519,7 @@ function ThemeProvider({
500
519
  setPaletteState(next);
501
520
  try {
502
521
  window.localStorage.setItem(THEME_STORAGE_KEYS.palette, next);
522
+ window.localStorage.setItem(THEME_STORAGE_KEYS.paletteIds, PALETTE_IDS_VERSION);
503
523
  } catch {
504
524
  }
505
525
  queueSync({ paletteId: next });