@motir/design-system 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/theme/StyleVignette.js +5 -2
- package/dist/components/theme/StyleVignette.js.map +1 -1
- package/dist/components/ui/Button.js +5 -2
- package/dist/components/ui/Button.js.map +1 -1
- package/dist/components/ui/Combobox.d.ts +32 -1
- package/dist/components/ui/Combobox.js +38 -11
- package/dist/components/ui/Combobox.js.map +1 -1
- package/dist/components/ui/ErrorState.js +5 -2
- package/dist/components/ui/ErrorState.js.map +1 -1
- package/dist/components/ui/Segmented.js +5 -2
- package/dist/components/ui/Segmented.js.map +1 -1
- package/dist/specimen/TokensSpecimen.d.ts +11 -3
- package/dist/specimen/TokensSpecimen.js +9 -290
- package/dist/specimen/TokensSpecimen.js.map +1 -1
- package/package.json +3 -1
- package/theme.css +29 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/cn.ts","../../../src/utils/fullscreen.ts","../../../src/components/ui/Combobox.tsx"],"names":["useSyncExternalStore","rect","gap","inset","spaceBelow","spaceAbove","placeAbove","avail","menu","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 { useSyncExternalStore } from 'react';\n\n/**\n * The element currently in NATIVE full screen (`document.fullscreenElement`), or\n * `null` — re-read on every `fullscreenchange`.\n *\n * While an element is in native full screen the browser paints ONLY that\n * element's subtree, in the top layer. Anything portalled to `document.body` —\n * a dialog, a popover, a picker's menu — is not shown at all, whatever its\n * z-index (MOTIR-7658: the roadmap canvas's quick view opened invisibly and\n * held focus and scroll lock until Esc). So every portalling primitive in this\n * package mounts into this element while there is one, and into\n * `document.body` otherwise — exactly as before.\n *\n * `null` on the server and in a document with no Fullscreen API.\n */\nexport function useFullscreenElement(): Element | null {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n\nfunction subscribe(onChange: () => void): () => void {\n document.addEventListener('fullscreenchange', onChange);\n return () => document.removeEventListener('fullscreenchange', onChange);\n}\n\nfunction getSnapshot(): Element | null {\n return document.fullscreenElement ?? null;\n}\n\nfunction getServerSnapshot(): null {\n return null;\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';\nimport { useFullscreenElement } from '../../utils/fullscreen';\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\n// The element a `position: fixed` descendant of `el` is laid out against, or\n// null when that is the viewport. A transform (the centered Modal panel's\n// `-translate-x/y-1/2`, which Tailwind v4 emits as the `translate` property), a\n// filter, a backdrop-filter (the glassmorphism material), `contain` or a\n// matching `will-change` all make an ancestor the containing block for fixed\n// descendants — so a fixed child of the Modal panel is positioned relative to\n// the PANEL, not the viewport.\nfunction fixedContainingBlock(el: HTMLElement): HTMLElement | null {\n let node = el.parentElement;\n while (node && node !== document.documentElement) {\n const s = getComputedStyle(node);\n const set = (v: string | undefined) => !!v && v !== 'none';\n if (\n set(s.transform) ||\n set(s.translate) ||\n set(s.scale) ||\n set(s.rotate) ||\n set(s.perspective) ||\n set(s.filter) ||\n set(s.backdropFilter) ||\n /\\b(paint|layout|strict|content)\\b/.test(s.contain || '') ||\n /\\b(transform|translate|scale|rotate|perspective|filter)\\b/.test(s.willChange || '')\n ) {\n return node;\n }\n node = node.parentElement;\n }\n return null;\n}\n\n// An element's padding box in viewport coordinates — the edge its overflow\n// clips at (and, for a containing block, the box fixed offsets are measured\n// from).\nfunction paddingBox(el: HTMLElement): { top: number; left: number; bottom: number } {\n const r = el.getBoundingClientRect();\n const top = r.top + el.clientTop;\n // happy-dom reports clientHeight 0; fall back to the border box there.\n const height = el.clientHeight || r.height;\n return { top, left: r.left + el.clientLeft, bottom: top + height };\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 // The portal target: the element in native full screen while there is one,\n // else <body> — the browser paints nothing outside a full-screen element\n // (MOTIR-7658).\n const fullscreenElement = useFullscreenElement();\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 // Inline (in-dialog) branch, SCROLLING clip only (a `Modal.Body`): the fixed\n // offsets that lift the menu out of the scroll box while keeping it in the\n // DOM — and so inside the dialog's focus scope (MOTIR-7655). Null otherwise.\n const [inlineFixed, setInlineFixed] = useState<CSSProperties | null>(null);\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. A NON-scrolling\n // `overflow: hidden` clip — the centered Modal panel's `overflow-hidden\n // max-h-[90vh]` box — traps the menu, so clamp + flip against THAT box\n // (bug-combobox-menu-clipped-inside-modal). A SCROLLING clip lifts the menu\n // out with fixed positioning instead (MOTIR-7655, below), bounded above by\n // the scroll box's visible top so it never lands under the panel's header\n // (the regression the first clamp-against-the-scroll-box cut caused).\n const clip = nearestClipBox(trigger);\n if (!clip) {\n setInlineFixed(null);\n setInlineAbove(false);\n setListMaxHeight(256); // == the max-h-64 fallback (original behaviour)\n return;\n }\n if (clip.scrolls) {\n // A SCROLLING clip (`Modal.Body`, a popover's scroll body) does trap an\n // absolute menu after all: the menu extends the box's scroll area, the\n // part past its visible edge is cut, and the box grows a second\n // scrollbar — on a short dialog body that leaves one option visible\n // (MOTIR-7655). Lift the menu out instead: `position: fixed`, still in\n // the DOM. A fixed box is clipped only by the ancestors on its\n // containing-block chain, so the scroll box stops clipping it while the\n // panel (the containing block, through its transform) still bounds it.\n const rect = trigger.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 cb = fixedContainingBlock(trigger);\n // The region the menu may paint in: never above the scroll box's visible\n // top (that is the dialog's header), and never past the viewport or any\n // clip at or above the containing block (the panel) — which lets the\n // menu overlay the dialog's footer, as a dropdown should.\n let top = Math.max(0, paddingBox(clip.box).top);\n let bottom = window.innerHeight;\n let node: HTMLElement | null = cb;\n while (node && node !== document.body && node !== document.documentElement) {\n const oy = getComputedStyle(node).overflowY;\n if (oy === 'hidden' || oy === 'clip' || oy === 'auto' || oy === 'scroll') {\n const b = paddingBox(node);\n top = Math.max(top, b.top);\n bottom = Math.min(bottom, b.bottom);\n }\n node = node.parentElement;\n }\n const spaceBelow = bottom - rect.bottom - gap - inset;\n const spaceAbove = rect.top - top - gap - inset;\n const placeAbove = spaceAbove > spaceBelow;\n const avail = Math.max(80, placeAbove ? spaceAbove : spaceBelow);\n // The menu's own chrome (search input, padding, footer note), measured\n // when it is laid out; the portaled branch's budget before that.\n const menu = menuRef.current;\n const list = listRef.current;\n const measured = menu && list ? menu.offsetHeight - list.offsetHeight : 0;\n const chrome = measured > 0 ? measured : searchable ? 52 : 12;\n setListMaxHeight(Math.max(80, Math.min(256, avail - chrome)));\n // Fixed offsets are measured from the containing block's padding box\n // (the viewport when there is none).\n const origin = cb ? paddingBox(cb) : { top: 0, left: 0, bottom: window.innerHeight };\n const style: CSSProperties = {\n position: 'fixed',\n left: Math.round(rect.left - origin.left),\n minWidth: Math.round(rect.width),\n };\n if (placeAbove) style.bottom = Math.round(origin.bottom - (rect.top - gap));\n else style.top = Math.round(rect.bottom + gap - origin.top);\n setInlineAbove(placeAbove);\n setInlineFixed(style);\n return;\n }\n setInlineFixed(null);\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 } else if (open && e.key === 'Escape') {\n // The open menu takes focus from a `setTimeout(0)` (the on-open effect),\n // so an Escape pressed before that fires lands HERE, not on the list.\n // Ignoring it left the menu open, and the next click on the trigger then\n // toggled it SHUT — the picker closed for good (MOTIR-7345). Focus is\n // already on the trigger, so close without moving it.\n e.preventDefault();\n closeMenu();\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 style={inlineFixed ?? undefined}\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 // Lifted out of a scrolling clip (MOTIR-7655): positioned by\n // `inlineFixed`. Otherwise an absolute child of the trigger that flips\n // above it when the dialog has more room there, so a tall list near the\n // modal's bottom edge isn't clipped (clamped + scrolled).\n inlineFixed\n ? null\n : cn('absolute left-0 min-w-full', 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\n ? inDialog\n ? menu\n : mounted\n ? createPortal(menu, fullscreenElement ?? document.body)\n : null\n : null}\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/cn.ts","../../../src/utils/fullscreen.ts","../../../src/components/ui/Combobox.tsx"],"names":["useSyncExternalStore","rect","gap","inset","spaceBelow","spaceAbove","placeAbove","avail","menu","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 { useSyncExternalStore } from 'react';\n\n/**\n * The element currently in NATIVE full screen (`document.fullscreenElement`), or\n * `null` — re-read on every `fullscreenchange`.\n *\n * While an element is in native full screen the browser paints ONLY that\n * element's subtree, in the top layer. Anything portalled to `document.body` —\n * a dialog, a popover, a picker's menu — is not shown at all, whatever its\n * z-index (MOTIR-7658: the roadmap canvas's quick view opened invisibly and\n * held focus and scroll lock until Esc). So every portalling primitive in this\n * package mounts into this element while there is one, and into\n * `document.body` otherwise — exactly as before.\n *\n * `null` on the server and in a document with no Fullscreen API.\n */\nexport function useFullscreenElement(): Element | null {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n\nfunction subscribe(onChange: () => void): () => void {\n document.addEventListener('fullscreenchange', onChange);\n return () => document.removeEventListener('fullscreenchange', onChange);\n}\n\nfunction getSnapshot(): Element | null {\n return document.fullscreenElement ?? null;\n}\n\nfunction getServerSnapshot(): null {\n return null;\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, LoaderCircle } from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { useFullscreenElement } from '../../utils/fullscreen';\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 * The language the LABEL is written in (a BCP 47 code), rendered as the `lang`\n * attribute on this option's row and, when it is selected, on the trigger's\n * label (MOTIR-7758 — a language picker names each language in its own script,\n * so a reader announces 日本語 in Japanese inside an English page). Omit it and\n * the label inherits the page's language, as before.\n */\n lang?: string;\n}\n\nexport interface ComboboxProps<T extends string> {\n options: ComboboxOption<T>[];\n value: T | null;\n onChange: (value: T) => void;\n /** Accessible name for the trigger + listbox. */\n label: string;\n /** Trigger text when nothing is selected. */\n placeholder?: string;\n searchable?: boolean;\n searchPlaceholder?: string;\n /** Empty-listbox content. A string, or rich nodes (e.g. a \"no matches\" line\n * plus a hint sub-line — use block `<span>`s, since this renders in a `<p>`). */\n emptyText?: ReactNode;\n loading?: boolean;\n loadingText?: string;\n disabled?: boolean;\n /** id for the trigger button. */\n id?: string;\n className?: string;\n /**\n * Mount already-open (Subtask 2.5.5) — for an inline-edit cell that renders the\n * picker on a single click, so the menu is open immediately (mirrors\n * `DatePicker`'s `autoOpen`). Focus lands on the search input / listbox.\n */\n autoOpen?: boolean;\n /**\n * Fired whenever the menu transitions open → closed (a pick, Escape,\n * click-outside, or toggling the trigger). The inline-edit cell uses it to\n * leave edit mode and return to the static pill/avatar (Subtask 2.5.5).\n */\n onClose?: () => void;\n /**\n * Server-driven search mode (Subtask 6.9.2 — the link picker). When\n * `onQueryChange` is provided the type-ahead query is CONTROLLED by the parent\n * (`query`) and client-side filtering is SKIPPED — `options` is taken as the\n * server's authoritative result for the current query (the parent debounces\n * its fetch off `onQueryChange`, the same contract as `MultiSelectPicker`).\n * Omit both for the default self-filtering behaviour. The parent owns the\n * query lifecycle, so opening the menu does NOT reset it.\n */\n query?: string;\n onQueryChange?: (query: string) => void;\n /**\n * 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 * A leading glyph drawn on the TRIGGER only, before the selected label (or the\n * placeholder), and never in the option rows — unlike an option's `icon`,\n * which is drawn in both (MOTIR-7758: the signed-out language control's\n * `Languages` glyph). Decorative: it is rendered `aria-hidden`, and the\n * trigger's accessible name stays `label`.\n */\n triggerIcon?: ReactNode;\n /**\n * The value the trigger shows is being applied (MOTIR-7758). The trigger is\n * marked `aria-busy` and a spinner takes the chevron's 16px slot, so the\n * trigger keeps its size. It does NOT disable the control — a caller that\n * wants that passes `disabled` as well. Pair it with a polite status that says\n * what is happening; this prop draws nothing a reader hears.\n */\n busy?: boolean;\n /**\n * Which edge of the trigger the menu lines up with (MOTIR-7758). `start`\n * (the default) aligns the menu's left edge with the trigger's, as before;\n * `end` aligns the RIGHT edges, so a menu wider than a trigger that sits in a\n * right-hand corner grows leftwards instead of running past the viewport.\n */\n align?: 'start' | 'end';\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\n// The element a `position: fixed` descendant of `el` is laid out against, or\n// null when that is the viewport. A transform (the centered Modal panel's\n// `-translate-x/y-1/2`, which Tailwind v4 emits as the `translate` property), a\n// filter, a backdrop-filter (the glassmorphism material), `contain` or a\n// matching `will-change` all make an ancestor the containing block for fixed\n// descendants — so a fixed child of the Modal panel is positioned relative to\n// the PANEL, not the viewport.\nfunction fixedContainingBlock(el: HTMLElement): HTMLElement | null {\n let node = el.parentElement;\n while (node && node !== document.documentElement) {\n const s = getComputedStyle(node);\n const set = (v: string | undefined) => !!v && v !== 'none';\n if (\n set(s.transform) ||\n set(s.translate) ||\n set(s.scale) ||\n set(s.rotate) ||\n set(s.perspective) ||\n set(s.filter) ||\n set(s.backdropFilter) ||\n /\\b(paint|layout|strict|content)\\b/.test(s.contain || '') ||\n /\\b(transform|translate|scale|rotate|perspective|filter)\\b/.test(s.willChange || '')\n ) {\n return node;\n }\n node = node.parentElement;\n }\n return null;\n}\n\n// An element's padding box in viewport coordinates — the edge its overflow\n// clips at (and, for a containing block, the box fixed offsets are measured\n// from).\nfunction paddingBox(el: HTMLElement): {\n top: number;\n left: number;\n bottom: number;\n right: number;\n} {\n const r = el.getBoundingClientRect();\n const top = r.top + el.clientTop;\n const left = r.left + el.clientLeft;\n // happy-dom reports clientHeight/Width 0; fall back to the border box there.\n const height = el.clientHeight || r.height;\n const width = el.clientWidth || r.width;\n return { top, left, bottom: top + height, right: left + width };\n}\n\n// The right edge of the viewport a `position: fixed` box is laid out against —\n// the layout viewport, which excludes a vertical scrollbar (`innerWidth` does not).\nfunction viewportRight(): number {\n return document.documentElement.clientWidth || window.innerWidth;\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 triggerIcon,\n busy = false,\n align = 'start',\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 // The portal target: the element in native full screen while there is one,\n // else <body> — the browser paints nothing outside a full-screen element\n // (MOTIR-7658).\n const fullscreenElement = useFullscreenElement();\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 // Inline (in-dialog) branch, SCROLLING clip only (a `Modal.Body`): the fixed\n // offsets that lift the menu out of the scroll box while keeping it in the\n // DOM — and so inside the dialog's focus scope (MOTIR-7655). Null otherwise.\n const [inlineFixed, setInlineFixed] = useState<CSSProperties | null>(null);\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 minWidth: Math.round(rect.width),\n };\n // `end` pins the menu's right edge to the trigger's, so it grows leftwards.\n if (align === 'end') style.right = Math.round(viewportRight() - rect.right);\n else style.left = Math.round(rect.left);\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, align]);\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. A NON-scrolling\n // `overflow: hidden` clip — the centered Modal panel's `overflow-hidden\n // max-h-[90vh]` box — traps the menu, so clamp + flip against THAT box\n // (bug-combobox-menu-clipped-inside-modal). A SCROLLING clip lifts the menu\n // out with fixed positioning instead (MOTIR-7655, below), bounded above by\n // the scroll box's visible top so it never lands under the panel's header\n // (the regression the first clamp-against-the-scroll-box cut caused).\n const clip = nearestClipBox(trigger);\n if (!clip) {\n setInlineFixed(null);\n setInlineAbove(false);\n setListMaxHeight(256); // == the max-h-64 fallback (original behaviour)\n return;\n }\n if (clip.scrolls) {\n // A SCROLLING clip (`Modal.Body`, a popover's scroll body) does trap an\n // absolute menu after all: the menu extends the box's scroll area, the\n // part past its visible edge is cut, and the box grows a second\n // scrollbar — on a short dialog body that leaves one option visible\n // (MOTIR-7655). Lift the menu out instead: `position: fixed`, still in\n // the DOM. A fixed box is clipped only by the ancestors on its\n // containing-block chain, so the scroll box stops clipping it while the\n // panel (the containing block, through its transform) still bounds it.\n const rect = trigger.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 cb = fixedContainingBlock(trigger);\n // The region the menu may paint in: never above the scroll box's visible\n // top (that is the dialog's header), and never past the viewport or any\n // clip at or above the containing block (the panel) — which lets the\n // menu overlay the dialog's footer, as a dropdown should.\n let top = Math.max(0, paddingBox(clip.box).top);\n let bottom = window.innerHeight;\n let node: HTMLElement | null = cb;\n while (node && node !== document.body && node !== document.documentElement) {\n const oy = getComputedStyle(node).overflowY;\n if (oy === 'hidden' || oy === 'clip' || oy === 'auto' || oy === 'scroll') {\n const b = paddingBox(node);\n top = Math.max(top, b.top);\n bottom = Math.min(bottom, b.bottom);\n }\n node = node.parentElement;\n }\n const spaceBelow = bottom - rect.bottom - gap - inset;\n const spaceAbove = rect.top - top - gap - inset;\n const placeAbove = spaceAbove > spaceBelow;\n const avail = Math.max(80, placeAbove ? spaceAbove : spaceBelow);\n // The menu's own chrome (search input, padding, footer note), measured\n // when it is laid out; the portaled branch's budget before that.\n const menu = menuRef.current;\n const list = listRef.current;\n const measured = menu && list ? menu.offsetHeight - list.offsetHeight : 0;\n const chrome = measured > 0 ? measured : searchable ? 52 : 12;\n setListMaxHeight(Math.max(80, Math.min(256, avail - chrome)));\n // Fixed offsets are measured from the containing block's padding box\n // (the viewport when there is none).\n const origin = cb\n ? paddingBox(cb)\n : { top: 0, left: 0, bottom: window.innerHeight, right: viewportRight() };\n const style: CSSProperties = {\n position: 'fixed',\n minWidth: Math.round(rect.width),\n };\n if (align === 'end') style.right = Math.round(origin.right - rect.right);\n else style.left = Math.round(rect.left - origin.left);\n if (placeAbove) style.bottom = Math.round(origin.bottom - (rect.top - gap));\n else style.top = Math.round(rect.bottom + gap - origin.top);\n setInlineAbove(placeAbove);\n setInlineFixed(style);\n return;\n }\n setInlineFixed(null);\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, align]);\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 } else if (open && e.key === 'Escape') {\n // The open menu takes focus from a `setTimeout(0)` (the on-open effect),\n // so an Escape pressed before that fires lands HERE, not on the list.\n // Ignoring it left the menu open, and the next click on the trigger then\n // toggled it SHUT — the picker closed for good (MOTIR-7345). Focus is\n // already on the trigger, so close without moving it.\n e.preventDefault();\n closeMenu();\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 lang={opt.lang}\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 style={inlineFixed ?? undefined}\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 // Lifted out of a scrolling clip (MOTIR-7655): positioned by\n // `inlineFixed`. Otherwise an absolute child of the trigger that flips\n // above it when the dialog has more room there, so a tall list near the\n // modal's bottom edge isn't clipped (clamped + scrolled).\n inlineFixed\n ? null\n : cn(\n 'absolute min-w-full',\n align === 'end' ? 'right-0' : 'left-0',\n inlineAbove ? 'bottom-full mb-1' : 'top-full mt-1',\n ),\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 aria-busy={busy || undefined}\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 {triggerIcon ? (\n <span aria-hidden className={ICON_SLOT}>\n {triggerIcon}\n </span>\n ) : null}\n {selected ? (\n <>\n {selected.icon ? (\n <span aria-hidden className={ICON_SLOT}>\n {selected.icon}\n </span>\n ) : null}\n <span lang={selected.lang} className=\"text-(--el-text) truncate\">\n {selected.label}\n </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 {busy ? (\n // The spinner takes the chevron's 16px slot, so the trigger keeps its size.\n <LoaderCircle\n data-combobox-busy=\"\"\n className=\"text-(--el-icon-muted) ml-auto h-4 w-4 shrink-0 animate-spin\"\n aria-hidden\n />\n ) : (\n <ChevronsUpDown className=\"text-(--el-icon-muted) ml-auto h-4 w-4 shrink-0\" aria-hidden />\n )}\n </button>\n\n {open\n ? inDialog\n ? menu\n : mounted\n ? createPortal(menu, fullscreenElement ?? document.body)\n : null\n : null}\n </div>\n );\n}\n"]}
|
|
@@ -38,8 +38,11 @@ var buttonVariants = cva(
|
|
|
38
38
|
cn(
|
|
39
39
|
// Base layout
|
|
40
40
|
"inline-flex items-center justify-center gap-2",
|
|
41
|
-
// Typography
|
|
42
|
-
|
|
41
|
+
// Typography. `whitespace-nowrap`: a label longer than its English draft
|
|
42
|
+
// (German and Polish run 30–40% longer) WIDENS the button instead of
|
|
43
|
+
// wrapping to a second line inside a box one line tall, where the text
|
|
44
|
+
// spills out of the control (MOTIR-7759).
|
|
45
|
+
"font-sans text-sm font-medium leading-none whitespace-nowrap",
|
|
43
46
|
// Shape — semantic tokens that flip with the active style
|
|
44
47
|
"rounded-(--radius-btn)",
|
|
45
48
|
// Interaction
|
|
@@ -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":"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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"]}
|
|
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. `whitespace-nowrap`: a label longer than its English draft\n // (German and Polish run 30–40% longer) WIDENS the button instead of\n // wrapping to a second line inside a box one line tall, where the text\n // spills out of the control (MOTIR-7759).\n 'font-sans text-sm font-medium leading-none whitespace-nowrap',\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"]}
|
|
@@ -33,7 +33,7 @@ function Segmented({
|
|
|
33
33
|
type: "button",
|
|
34
34
|
"aria-pressed": active,
|
|
35
35
|
disabled: disabled || opt.disabled,
|
|
36
|
-
title: opt.title,
|
|
36
|
+
title: opt.title ?? (fill ? opt.label : void 0),
|
|
37
37
|
onClick: () => {
|
|
38
38
|
if (!active) onChange(opt.value);
|
|
39
39
|
},
|
|
@@ -48,7 +48,10 @@ function Segmented({
|
|
|
48
48
|
// In FILL mode the segment's width comes from the track, so the
|
|
49
49
|
// per-style control padding no longer sizes it — keeping it would
|
|
50
50
|
// re-introduce the token dependency the variant exists to remove.
|
|
51
|
-
fill
|
|
51
|
+
// Outside fill mode a segment sizes to its label and never wraps,
|
|
52
|
+
// so a longer translation widens it rather than spilling out of
|
|
53
|
+
// the one-line control height (MOTIR-7759).
|
|
54
|
+
fill ? "min-w-0 flex-1 justify-center px-1" : "px-(--spacing-control-x) whitespace-nowrap",
|
|
52
55
|
"focus-visible:ring-2 focus-visible:ring-(--focus-ring-color) focus-visible:outline-none",
|
|
53
56
|
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
54
57
|
active ? "bg-(--el-page-bg) text-(--el-text-strong) shadow-(--shadow-subtle)" : "text-(--el-text-secondary) hover:text-(--el-text)"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/cn.ts","../../../src/components/ui/Segmented.tsx"],"names":[],"mappings":";;;;AAcO,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACqBO,SAAS,SAAA,CAA4B;AAAA,EAC1C,OAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,KAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,EAuBG;AACD,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,IAAA,EAAK,OAAA;AAAA,MACL,YAAA,EAAY,KAAA;AAAA,MACZ,SAAA,EAAW,EAAA;AAAA,QACT,sGAAA;AAAA,QACA,OAAO,aAAA,GAAgB,aAAA;AAAA,QACvB;AAAA,OACF;AAAA,MAEC,QAAA,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAC,GAAA,KAAQ;AACpB,QAAA,MAAM,MAAA,GAAS,IAAI,KAAA,KAAU,KAAA;AAC7B,QAAA,uBACE,IAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YAEC,IAAA,EAAK,QAAA;AAAA,YACL,cAAA,EAAc,MAAA;AAAA,YACd,QAAA,EAAU,YAAY,GAAA,CAAI,QAAA;AAAA,
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/cn.ts","../../../src/components/ui/Segmented.tsx"],"names":[],"mappings":";;;;AAcO,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACqBO,SAAS,SAAA,CAA4B;AAAA,EAC1C,OAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,KAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,EAuBG;AACD,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,IAAA,EAAK,OAAA;AAAA,MACL,YAAA,EAAY,KAAA;AAAA,MACZ,SAAA,EAAW,EAAA;AAAA,QACT,sGAAA;AAAA,QACA,OAAO,aAAA,GAAgB,aAAA;AAAA,QACvB;AAAA,OACF;AAAA,MAEC,QAAA,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAC,GAAA,KAAQ;AACpB,QAAA,MAAM,MAAA,GAAS,IAAI,KAAA,KAAU,KAAA;AAC7B,QAAA,uBACE,IAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YAEC,IAAA,EAAK,QAAA;AAAA,YACL,cAAA,EAAc,MAAA;AAAA,YACd,QAAA,EAAU,YAAY,GAAA,CAAI,QAAA;AAAA,YAG1B,KAAA,EAAO,GAAA,CAAI,KAAA,KAAU,IAAA,GAAO,IAAI,KAAA,GAAQ,MAAA,CAAA;AAAA,YACxC,SAAS,MAAM;AACb,cAAA,IAAI,CAAC,MAAA,EAAQ,QAAA,CAAS,GAAA,CAAI,KAAK,CAAA;AAAA,YACjC,CAAA;AAAA,YACA,SAAA,EAAW,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAOT,uIAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAOA,OACI,oCAAA,GACA,4CAAA;AAAA,cACJ,yFAAA;AAAA,cACA,iDAAA;AAAA,cACA,SACI,oEAAA,GACA;AAAA,aACN;AAAA,YAQC,QAAA,EAAA;AAAA,cAAA,GAAA,CAAI,IAAA,mBACH,GAAA;AAAA,gBAAC,MAAA;AAAA,gBAAA;AAAA,kBACC,aAAA,EAAW,IAAA;AAAA,kBACX,SAAA,EAAW,EAAA;AAAA,oBACT,qEAAA;AAAA,oBACA,SAAS,2BAAA,GAA8B;AAAA,mBACzC;AAAA,kBAEC,QAAA,EAAA,GAAA,CAAI;AAAA;AAAA,eACP,GACE,IAAA;AAAA,cAMH,IAAA,uBAAQ,MAAA,EAAA,EAAK,SAAA,EAAU,YAAY,QAAA,EAAA,GAAA,CAAI,KAAA,EAAM,IAAU,GAAA,CAAI,KAAA;AAAA,cAC3D,GAAA,CAAI,YAAY,IAAA,mBACf,GAAA;AAAA,gBAAC,MAAA;AAAA,gBAAA;AAAA,kBACC,SAAA,EAAW,EAAA;AAAA,oBACT,kDAAA;AAAA,oBACA,SAAS,2BAAA,GAA8B;AAAA,mBACzC;AAAA,kBAEC,QAAA,EAAA,GAAA,CAAI;AAAA;AAAA,eACP,GACE;AAAA;AAAA,WAAA;AAAA,UAlEC,GAAA,CAAI;AAAA,SAmEX;AAAA,MAEJ,CAAC;AAAA;AAAA,GACH;AAEJ","file":"Segmented.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merge Tailwind class names without conflicts.\n *\n * `clsx` handles conditional inclusion (booleans, undefineds, arrays);\n * `twMerge` resolves conflicting Tailwind classes (later wins, e.g.\n * `px-2 px-4` → `px-4`). Together they are the de facto standard for\n * composing Tailwind classes in React components.\n *\n * @example\n * cn('px-2 py-1', isActive && 'bg-primary', className)\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","'use client';\n\nimport type { ReactNode } from 'react';\nimport { cn } from '../../utils/cn';\n\n// Segmented — a single-select inline control: a row of mutually-exclusive\n// buttons where the active option is raised (`aria-pressed`). The accessible,\n// space-cheap alternative to a `<select>` when the option set is small and worth\n// showing at a glance (the board group-by, Subtask 3.3.5; reusable beyond it).\n//\n// Built FROM the design system per `design/boards/swimlanes-wip.mock.html` (the\n// `.seg` block): an `--el-surface` track (`--radius-btn`) with a 2px inset, each\n// option a `calc(--radius-btn - 2px)` button so it NESTS in the track at any style\n// (a fixed `--radius-control` floats wrong when a style pills `--radius-btn`); the\n// pressed option gets the `--el-page-bg` raised fill + `--shadow-subtle` and its\n// leading glyph takes the `--el-accent` hue.\n// Colour via `--el-*`, shape via element-semantic tokens (the colour + shape\n// swap rules). A11y: a labelled `role=\"group\"`; each option is a real `<button>`\n// carrying `aria-pressed`, so it is keyboard-operable and announced as a toggle.\n\nexport interface SegmentedOption<T extends string> {\n value: T;\n label: string;\n /** Optional leading glyph (decorative — the label carries the accessible name). */\n icon?: ReactNode;\n /** Optional trailing content after the label (e.g. the notification drawer's\n * unread count on the Direct tab, Subtask 5.7.5). Decorative — faint when the\n * option is inactive, the `--el-accent` hue when active (mirrors the leading\n * glyph's active treatment). */\n trailing?: ReactNode;\n /** Disable just this option — a forward-compatible seam (e.g. the Activity\n * card's History filter, Story 5.5's slot) drawn present-but-inert. */\n disabled?: boolean;\n /** Tooltip for the option (e.g. why a seam option is disabled). */\n title?: string;\n}\n\nexport function Segmented<T extends string>({\n options,\n value,\n onChange,\n label,\n className,\n disabled,\n fill,\n}: {\n options: SegmentedOption<T>[];\n value: T;\n onChange: (value: T) => void;\n /** Accessible group name (e.g. \"Swimlane group by\") — not rendered visually. */\n label: string;\n className?: string;\n disabled?: boolean;\n /**\n * FILL the container instead of sizing to the options (MOTIR-6200, for\n * MOTIR-6199): the track becomes `w-full` and the segments divide it evenly.\n *\n * WHY IT IS A VARIANT AND NOT THE DEFAULT. The track is `inline-flex` with no\n * wrap and no shrink, so its width is `max-content` — fine in a toolbar that\n * can grow, and unsurvivable in a FIXED rail. The Difficulty editor sits in an\n * 18rem item-page rail and a 300px quick-view rail, and when its scale gained a\n * fourth member the track overran both. Filling makes the width the\n * CONTAINER's, so it holds at any label set, at any `--spacing-control-x`\n * (10 / 12 / 14px across the shipped styles) and at a fifth member.\n *\n * Off by default: every other call site keeps byte-identical markup.\n */\n fill?: boolean;\n}) {\n return (\n <div\n role=\"group\"\n aria-label={label}\n className={cn(\n 'items-center gap-0.5 rounded-(--radius-btn) border border-(--el-border) bg-(--el-tabnav-track) p-0.5',\n fill ? 'flex w-full' : 'inline-flex',\n className,\n )}\n >\n {options.map((opt) => {\n const active = opt.value === value;\n return (\n <button\n key={opt.value}\n type=\"button\"\n aria-pressed={active}\n disabled={disabled || opt.disabled}\n // A FILL segment may truncate its label, so it defaults its tooltip\n // to the full label — a cut-off word stays readable (MOTIR-7759).\n title={opt.title ?? (fill ? opt.label : undefined)}\n onClick={() => {\n if (!active) onChange(opt.value);\n }}\n className={cn(\n // The segment radius nests inside the track: the track is\n // `rounded-(--radius-btn)` with a 2px (`p-0.5`) inset, so a segment\n // fits the shell only at `--radius-btn - 2px`. Using a fixed\n // `--radius-control` breaks when a style makes `--radius-btn` a full\n // pill (soft-playful / retrofuturism: pill track, but a small-radius\n // chip floating inside it). Mirrors AppearancePickers' option radius.\n 'inline-flex h-(--height-control) items-center gap-1.5 rounded-[calc(var(--radius-btn)-2px)] text-[13px] font-medium transition-colors',\n // In FILL mode the segment's width comes from the track, so the\n // per-style control padding no longer sizes it — keeping it would\n // re-introduce the token dependency the variant exists to remove.\n // Outside fill mode a segment sizes to its label and never wraps,\n // so a longer translation widens it rather than spilling out of\n // the one-line control height (MOTIR-7759).\n fill\n ? 'min-w-0 flex-1 justify-center px-1'\n : 'px-(--spacing-control-x) whitespace-nowrap',\n 'focus-visible:ring-2 focus-visible:ring-(--focus-ring-color) focus-visible:outline-none',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n active\n ? 'bg-(--el-page-bg) text-(--el-text-strong) shadow-(--shadow-subtle)'\n : 'text-(--el-text-secondary) hover:text-(--el-text)',\n )}\n >\n {/* The icon and the trailing count take their ink HERE rather than\n through the segment's `[&_.seg-*]:…` descendant variants\n (MOTIR-2475). Two things follow: the glyph says it is\n `aria-hidden`, so its faint ink is one of the token's legitimate\n jobs; and the trailing COUNT — text a reader reads — is no\n longer painted at 2.4:1 on an inactive segment. */}\n {opt.icon ? (\n <span\n aria-hidden\n className={cn(\n 'seg-ic inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center',\n active ? 'text-(--el-tabnav-active)' : 'text-(--el-text-faint)',\n )}\n >\n {opt.icon}\n </span>\n ) : null}\n {/* FILL mode gives each segment a share of the track rather than its\n own content width, so a long label has to yield rather than push.\n Truncating needs an element to truncate, hence the span — added\n ONLY in fill mode, so every other call site's markup is\n unchanged. */}\n {fill ? <span className=\"truncate\">{opt.label}</span> : opt.label}\n {opt.trailing != null ? (\n <span\n className={cn(\n 'seg-trail text-[11px] font-semibold tabular-nums',\n active ? 'text-(--el-tabnav-active)' : 'text-(--el-text-secondary)',\n )}\n >\n {opt.trailing}\n </span>\n ) : null}\n </button>\n );\n })}\n </div>\n );\n}\n"]}
|
|
@@ -4,9 +4,17 @@ interface TokensSpecimenProps {
|
|
|
4
4
|
className?: string;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
* The design-system isolation specimen. Wrap-free: it
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* The design-system isolation specimen. Wrap-free: it can be dropped into any
|
|
8
|
+
* route (or a headless render) to see the whole system at once.
|
|
9
|
+
*
|
|
10
|
+
* It deliberately does NOT mount a `ThemeProvider` (MOTIR-7725). That provider
|
|
11
|
+
* is not scoped: it stamps `data-theme` / `data-style` / `data-palette` /
|
|
12
|
+
* `data-type` onto `<html>` from storage or `THEME_DEFAULTS`, and re-stamps
|
|
13
|
+
* `data-theme` on every OS colour-scheme change, so wrapping the specimen in one
|
|
14
|
+
* re-themed whatever page it was dropped into. Nothing inside calls `useTheme()`
|
|
15
|
+
* — it reads only the registries and the `--el-*` tokens — so it renders under
|
|
16
|
+
* whatever appearance its host has applied, and the per-axis vignettes scope
|
|
17
|
+
* their own.
|
|
10
18
|
*/
|
|
11
19
|
declare function TokensSpecimen({ className }: TokensSpecimenProps): react_jsx_runtime.JSX.Element;
|
|
12
20
|
|