@misoto22/design 0.8.0 → 0.9.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.
Files changed (157) hide show
  1. package/dist/agent/Accordion.md +1 -1
  2. package/dist/agent/Alert.md +1 -1
  3. package/dist/agent/AppShell.md +1 -1
  4. package/dist/agent/ArchitectureFigure.md +1 -1
  5. package/dist/agent/AreaChart.md +1 -1
  6. package/dist/agent/Article.md +1 -1
  7. package/dist/agent/AspectRatio.md +1 -1
  8. package/dist/agent/Avatar.md +1 -1
  9. package/dist/agent/Badge.md +1 -1
  10. package/dist/agent/BarChart.md +1 -1
  11. package/dist/agent/BarList.md +1 -1
  12. package/dist/agent/BigNumber.md +1 -1
  13. package/dist/agent/BoxPlot.md +1 -1
  14. package/dist/agent/Breadcrumb.md +1 -1
  15. package/dist/agent/BulletChart.md +1 -1
  16. package/dist/agent/Button.md +1 -1
  17. package/dist/agent/Calendar.md +1 -1
  18. package/dist/agent/Card.md +1 -1
  19. package/dist/agent/Checkbox.md +1 -1
  20. package/dist/agent/Code.md +1 -1
  21. package/dist/agent/CodeBlock.md +1 -1
  22. package/dist/agent/Collapsible.md +1 -1
  23. package/dist/agent/ColorPicker.md +75 -0
  24. package/dist/agent/Combobox.md +1 -1
  25. package/dist/agent/Command.md +1 -1
  26. package/dist/agent/ComposedChart.md +1 -1
  27. package/dist/agent/ContextMenu.md +1 -1
  28. package/dist/agent/DataflowFigure.md +1 -1
  29. package/dist/agent/DatePicker.md +1 -1
  30. package/dist/agent/DescriptionList.md +1 -1
  31. package/dist/agent/Diagram.md +1 -1
  32. package/dist/agent/DiagramCanvas.md +1 -1
  33. package/dist/agent/DiagramExportMenu.md +1 -1
  34. package/dist/agent/DiagramInspector.md +1 -1
  35. package/dist/agent/DiagramLegend.md +1 -1
  36. package/dist/agent/DiagramMinimap.md +1 -1
  37. package/dist/agent/DiagramToolbar.md +1 -1
  38. package/dist/agent/Dialog.md +1 -1
  39. package/dist/agent/DropdownMenu.md +1 -1
  40. package/dist/agent/EmptyState.md +1 -1
  41. package/dist/agent/ErrorState.md +1 -1
  42. package/dist/agent/Facet.md +1 -1
  43. package/dist/agent/Field.md +1 -1
  44. package/dist/agent/FigureBand.md +1 -1
  45. package/dist/agent/FloatingIconButton.md +1 -1
  46. package/dist/agent/FunnelChart.md +1 -1
  47. package/dist/agent/Heading.md +1 -1
  48. package/dist/agent/Heatmap.md +1 -1
  49. package/dist/agent/Histogram.md +1 -1
  50. package/dist/agent/Input.md +1 -1
  51. package/dist/agent/Kbd.md +1 -1
  52. package/dist/agent/LifecycleFigure.md +1 -1
  53. package/dist/agent/LineChart.md +1 -1
  54. package/dist/agent/LinkArrow.md +1 -1
  55. package/dist/agent/Markdown.md +1 -1
  56. package/dist/agent/NativeSelect.md +1 -1
  57. package/dist/agent/NavItem.md +1 -1
  58. package/dist/agent/NumberField.md +72 -0
  59. package/dist/agent/Pagination.md +1 -1
  60. package/dist/agent/PieChart.md +1 -1
  61. package/dist/agent/Popover.md +1 -1
  62. package/dist/agent/Progress.md +1 -1
  63. package/dist/agent/RadarChart.md +1 -1
  64. package/dist/agent/RadialChart.md +1 -1
  65. package/dist/agent/RadioGroup.md +1 -1
  66. package/dist/agent/SankeyChart.md +1 -1
  67. package/dist/agent/ScatterChart.md +1 -1
  68. package/dist/agent/ScrollArea.md +1 -1
  69. package/dist/agent/SearchableMenu.md +1 -1
  70. package/dist/agent/Select.md +1 -1
  71. package/dist/agent/Separator.md +1 -1
  72. package/dist/agent/SequenceFigure.md +1 -1
  73. package/dist/agent/Sheet.md +1 -1
  74. package/dist/agent/Sidebar.md +193 -0
  75. package/dist/agent/Skeleton.md +1 -1
  76. package/dist/agent/Slider.md +7 -3
  77. package/dist/agent/Sparkline.md +1 -1
  78. package/dist/agent/Spinner.md +1 -1
  79. package/dist/agent/StatusDot.md +1 -1
  80. package/dist/agent/StatusPill.md +1 -1
  81. package/dist/agent/Steps.md +1 -1
  82. package/dist/agent/Switch.md +1 -1
  83. package/dist/agent/Table.md +3 -1
  84. package/dist/agent/Tabs.md +1 -1
  85. package/dist/agent/Tag.md +1 -1
  86. package/dist/agent/Text.md +1 -1
  87. package/dist/agent/Textarea.md +1 -1
  88. package/dist/agent/Timestamp.md +1 -1
  89. package/dist/agent/Toast.md +1 -1
  90. package/dist/agent/ToggleGroup.md +1 -1
  91. package/dist/agent/Toolbar.md +1 -1
  92. package/dist/agent/Tooltip.md +1 -1
  93. package/dist/agent/TreemapChart.md +1 -1
  94. package/dist/agent/WaterfallChart.md +1 -1
  95. package/dist/agent/WorkflowFigure.md +1 -1
  96. package/dist/agent/catalog.json +361 -2
  97. package/dist/agent/index.md +8 -5
  98. package/dist/charts/BulletChart/BulletChart.js +16 -2
  99. package/dist/charts/BulletChart/BulletChart.js.map +1 -1
  100. package/dist/charts/RadarChart/RadarChart.js +4 -2
  101. package/dist/charts/RadarChart/RadarChart.js.map +1 -1
  102. package/dist/components/ColorPicker/ColorPicker.d.ts +67 -0
  103. package/dist/components/ColorPicker/ColorPicker.js +334 -0
  104. package/dist/components/ColorPicker/ColorPicker.js.map +1 -0
  105. package/dist/components/Command/Command.js +1 -1
  106. package/dist/components/Command/Command.js.map +1 -1
  107. package/dist/components/ContextMenu/ContextMenu.js +1 -1
  108. package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
  109. package/dist/components/Dialog/Dialog.js +1 -1
  110. package/dist/components/Dialog/Dialog.js.map +1 -1
  111. package/dist/components/DropdownMenu/DropdownMenu.js +1 -1
  112. package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
  113. package/dist/components/ErrorState/ErrorState.js +1 -1
  114. package/dist/components/ErrorState/ErrorState.js.map +1 -1
  115. package/dist/components/NavItem/NavItem.js +1 -1
  116. package/dist/components/NavItem/NavItem.js.map +1 -1
  117. package/dist/components/NumberField/NumberField.d.ts +69 -0
  118. package/dist/components/NumberField/NumberField.js +162 -0
  119. package/dist/components/NumberField/NumberField.js.map +1 -0
  120. package/dist/components/Popover/Popover.js +1 -1
  121. package/dist/components/Popover/Popover.js.map +1 -1
  122. package/dist/components/Select/Select.js +1 -1
  123. package/dist/components/Select/Select.js.map +1 -1
  124. package/dist/components/Sheet/Sheet.js +1 -1
  125. package/dist/components/Sheet/Sheet.js.map +1 -1
  126. package/dist/components/Sidebar/Sidebar.d.ts +349 -0
  127. package/dist/components/Sidebar/Sidebar.js +455 -0
  128. package/dist/components/Sidebar/Sidebar.js.map +1 -0
  129. package/dist/components/Slider/Slider.d.ts +22 -1
  130. package/dist/components/Slider/Slider.js +72 -8
  131. package/dist/components/Slider/Slider.js.map +1 -1
  132. package/dist/components/Table/Table.d.ts +14 -0
  133. package/dist/components/Table/Table.js.map +1 -1
  134. package/dist/components/Tabs/Tabs.js +7 -1
  135. package/dist/components/Tabs/Tabs.js.map +1 -1
  136. package/dist/diagrams/lib/route.d.ts +22 -1
  137. package/dist/diagrams/lib/route.js +24 -1
  138. package/dist/diagrams/lib/route.js.map +1 -1
  139. package/dist/diagrams/lib/wires.js +9 -5
  140. package/dist/diagrams/lib/wires.js.map +1 -1
  141. package/dist/index.d.ts +3 -0
  142. package/dist/index.js +3 -0
  143. package/dist/index.js.map +1 -1
  144. package/dist/lib/color.d.ts +80 -0
  145. package/dist/lib/color.js +189 -0
  146. package/dist/lib/color.js.map +1 -0
  147. package/dist/lib/numeric.d.ts +35 -0
  148. package/dist/lib/numeric.js +29 -0
  149. package/dist/lib/numeric.js.map +1 -0
  150. package/dist/styles.css +1 -1
  151. package/dist/themes.css +8 -0
  152. package/dist/tokens.css +66 -3
  153. package/dist/tokens.d.ts +3 -0
  154. package/dist/tokens.js +17 -0
  155. package/dist/tokens.json +17 -0
  156. package/package.json +1 -1
  157. package/skills/misoto22-design/rules/forms.md +3 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/ColorPicker/ColorPicker.tsx"],"sourcesContent":["'use client'\n\nimport { useEffect, useId, useRef, useState, type PointerEvent } from 'react'\nimport { cn } from '../../lib/cn'\nimport {\n clamp,\n colorFormat,\n colorToRgb,\n fitGamut,\n formatColor,\n maxChroma,\n parseColor,\n type Color,\n type ColorFormat,\n type ColorSpace,\n} from '../../lib/color'\nimport { CONTROL_BASE, CONTROL_BORDER, isInvalid } from '../../lib/control'\nimport { warnBlankName } from '../../lib/warn'\nimport { useFieldControl } from '../Field/field-control'\nimport { Input } from '../Input/Input'\nimport { Popover, PopoverContent, PopoverTrigger } from '../Popover/Popover'\nimport { ToggleGroup, ToggleGroupItem } from '../ToggleGroup/ToggleGroup'\n\n/** The plane's pixel grid. Wider than it is tall, like the panel holding it. */\nconst PLANE_W = 256\nconst PLANE_H = 160\n\n/** Hue stops in the strip behind the hue slider. Every 5°, plus the wrap. */\nconst HUE_STOPS = 73\n\nconst BLACK: Color = { l: 0, c: 0, h: 0, a: 1 }\n\nconst FORMAT_LABELS: Record<ColorFormat, string> = {\n hex: 'Hex',\n oklch: 'OKLCH',\n // Not \"Display P3\": the three segments share the strip evenly, and the long\n // name wraps to two lines inside its third and makes the whole row twice as\n // tall as the two beside it.\n p3: 'P3',\n}\n\n/**\n * A checkerboard, so alpha reads as alpha rather than as a lighter colour.\n *\n * Sized per surface: 8px checks read as a texture behind a full-width track and\n * as two grey squares inside a 16px swatch.\n */\nconst checkerboard = (square: string) =>\n `repeating-conic-gradient(var(--stone) 0% 25%, var(--paper) 0% 50%) 0 0 / ${square} ${square}`\n\nexport interface ColorPickerProps {\n /**\n * Names the control. Required — the trigger shows a colour, and a colour is\n * not a name.\n *\n * Announced together with the value, the way `Select`'s is: a reader hears\n * \"Brand colour, #a78bfa\". Inside a `Field` with a label, that label is used\n * and this one is not repeated.\n */\n label: string\n /** The colour, as any absolute CSS colour string. */\n value?: string\n defaultValue?: string\n /**\n * Fires with a CSS colour string in whichever notation the panel is set to —\n * so the notation a caller passes in is the notation they get back until\n * somebody changes it in the panel.\n */\n onValueChange?: (value: string) => void\n disabled?: boolean\n /** Paints the resting border with `--danger` and reflects `aria-invalid`. */\n invalid?: boolean\n className?: string\n /** The TRIGGER's id — the element a label points at. A `Field` sets it. */\n id?: string\n /** Ids of the copy describing the control. A `Field` sets it from hint, error and description. */\n 'aria-describedby'?: string\n /** The spelling a form library sets; read together with `invalid`. */\n 'aria-invalid'?: boolean | 'true' | 'false'\n /** Announced on the trigger. A `Field` sets it from `required`. */\n 'aria-required'?: boolean\n}\n\n/**\n * A colour, chosen or typed.\n *\n * The panel works in OKLCH, and that is the reason to reach for this rather\n * than `<input type=\"color\">`. In HSV — which is what the native picker and\n * most libraries use — dragging along a row of constant \"lightness\" walks\n * through colours the eye reads as getting darker, so a reader tuning a palette\n * is fighting the instrument. OKLCH's lightness is the one a person sees, so\n * two colours at the same height on the plane genuinely match, and the hue\n * strip stays at the lightness already chosen instead of showing a rainbow that\n * belongs to some other colour.\n *\n * The plane is normalised to the gamut ROW BY ROW: its right edge is the most\n * chroma that exists at that lightness and hue, so the whole surface is\n * reachable rather than a lens of colour inside a field of clipped duplicates.\n *\n * **What it accepts.** Hex, `rgb()`, `hsl()`, `oklch()` and\n * `color(display-p3 …)`, in both syntaxes. Named colours are not accepted:\n * resolving them needs a table of every CSS name or a live DOM, and a box that\n * takes \"rebeccapurple\" but not \"papayawhip\" is worse than one that takes\n * neither.\n *\n * **Keyboard.** The plane is a group of two real sliders — chroma across,\n * lightness up — so the arrows move it and a screen reader announces where it\n * is. That is the part a 2D canvas usually leaves out, and leaving it out makes\n * the control unusable rather than merely awkward.\n *\n * @example\n * <Field label=\"Brand colour\"><ColorPicker label=\"Brand colour\" defaultValue=\"#a78bfa\" /></Field>\n */\nexport function ColorPicker({\n label,\n value: controlled,\n defaultValue = '#000000',\n onValueChange,\n disabled,\n invalid,\n className,\n id,\n 'aria-describedby': describedBy,\n 'aria-invalid': ariaInvalid,\n 'aria-required': ariaRequired,\n}: ColorPickerProps) {\n warnBlankName('ColorPicker', 'label', label, 'the trigger is announced with no name')\n const bad = isInvalid(invalid, ariaInvalid)\n\n const field = useFieldControl()\n const generated = useId()\n const triggerId = id ?? generated\n const valueId = `${triggerId}-value`\n const nameId = field?.labelId ?? `${triggerId}-name`\n\n const [uncontrolled, setUncontrolled] = useState(defaultValue)\n const value = controlled ?? uncontrolled\n\n const [color, setColor] = useState<Color>(() => parseColor(defaultValue) ?? BLACK)\n const [format, setFormat] = useState<ColorFormat>(() => colorFormat(defaultValue))\n // What this component last wrote out, so its own emission does not read back\n // as an outside change and re-derive the hue it just preserved.\n const emitted = useRef<string | null>(null)\n\n // The plane's horizontal position, kept across the two rows where it cannot\n // be derived: at pure black and pure white every chroma is zero, so `c / max`\n // is 0/0 and the handle would jump to the left edge on the way past.\n const saturationRef = useRef(0)\n\n useEffect(() => {\n if (value === emitted.current) return\n const parsed = parseColor(value)\n if (!parsed) return\n // A grey has no hue — only the angle of its rounding error. Keeping the\n // last real one is what stops the strip spinning when a reader drags the\n // handle through the neutral column.\n setColor((prev) => ({ ...parsed, h: parsed.c < 1e-7 ? prev.h : parsed.h }))\n setFormat(colorFormat(value))\n }, [value])\n\n // Hex is bounded to sRGB; both of the others can address the wider gamut, so\n // the plane shows what the notation can actually hold.\n const space: ColorSpace = format === 'hex' ? 'srgb' : 'p3'\n const ceiling = maxChroma(color.l, color.h, space)\n const saturation = ceiling > 0 ? clamp(color.c / ceiling) : saturationRef.current\n\n useEffect(() => {\n if (ceiling > 0) saturationRef.current = saturation\n }, [ceiling, saturation])\n\n const commit = (next: Color, nextFormat: ColorFormat = format) => {\n const fitted = nextFormat === 'oklch' ? next : fitGamut(next, nextFormat === 'p3' ? 'p3' : 'srgb')\n setColor(fitted)\n setFormat(nextFormat)\n const text = formatColor(fitted, nextFormat)\n emitted.current = text\n if (controlled === undefined) setUncontrolled(text)\n onValueChange?.(text)\n }\n\n /** Moves the plane handle, in the 0–1 coordinates the plane is drawn in. */\n const setPlane = (nextSaturation: number, lightness: number) => {\n saturationRef.current = nextSaturation\n commit({ ...color, l: lightness, c: nextSaturation * maxChroma(lightness, color.h, space) })\n }\n\n const planeRef = useRef<HTMLDivElement>(null)\n const canvasRef = useRef<HTMLCanvasElement>(null)\n const painted = useRef<string>('')\n\n const trackPointer = (event: PointerEvent<HTMLDivElement>) => {\n const rect = planeRef.current?.getBoundingClientRect()\n if (!rect || rect.width === 0 || rect.height === 0) return\n setPlane(\n clamp((event.clientX - rect.left) / rect.width),\n clamp(1 - (event.clientY - rect.top) / rect.height),\n )\n }\n\n /**\n * Fills the plane, at most once per hue.\n *\n * Called from the canvas's own ref callback rather than only from an effect,\n * because the canvas does not exist until the panel is open AND Radix's\n * popper has measured it — which is a commit later than the one that opened\n * it. An effect keyed to the open state runs on the commit BEFORE the node\n * arrives, finds a ref holding null, and never runs again: a plane that\n * stayed blank until the reader happened to move the hue.\n */\n const paint = (canvas: HTMLCanvasElement | null) => {\n if (!canvas) return\n const key = `${color.h.toFixed(3)}:${space}`\n if (painted.current === key) return\n const context = canvas.getContext('2d')\n // A headless environment can hand back a 2D context with most of the\n // drawing surface missing. Nothing here is load-bearing — the plane is the\n // pointer's aim, and the two sliders under it work either way — so a\n // partial context means no plane rather than a thrown render.\n if (!context || typeof context.createImageData !== 'function') return\n painted.current = key\n\n const pixels = context.createImageData(PLANE_W, PLANE_H)\n for (let y = 0; y < PLANE_H; y++) {\n const l = 1 - y / (PLANE_H - 1)\n // Each row is normalised to its OWN reachable chroma. Painted against one\n // global maximum instead, the top and bottom thirds of the plane are\n // bands of clipped duplicates a reader can never pick apart.\n const rowMax = maxChroma(l, color.h, space)\n for (let x = 0; x < PLANE_W; x++) {\n const rgb = colorToRgb({ l, c: (x / (PLANE_W - 1)) * rowMax, h: color.h, a: 1 }, 'srgb')\n const at = (y * PLANE_W + x) * 4\n for (let channel = 0; channel < 3; channel++) {\n pixels.data[at + channel] = Math.round(clamp(rgb[channel] ?? 0) * 255)\n }\n pixels.data[at + 3] = 255\n }\n }\n context.putImageData(pixels, 0, 0)\n }\n\n /** Repaints a canvas that is already on screen when the hue moves under it. */\n useEffect(() => {\n paint(canvasRef.current)\n })\n\n const opaque = formatColor({ ...color, a: 1 }, 'oklch')\n const hueTrack = Array.from({ length: HUE_STOPS }, (_, i) => {\n const h = i * (360 / (HUE_STOPS - 1))\n return formatColor({ l: color.l, c: saturation * maxChroma(color.l, h, space), h, a: 1 }, 'oklch')\n }).join(', ')\n\n const typed = parseColor(value)\n\n return (\n <Popover>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n id={triggerId}\n disabled={disabled}\n // Named by the label AND by its own value, in that order — the same\n // arrangement Select uses, and for the same reason: `aria-label`\n // outranks the content, so naming it \"Brand colour\" would tell a\n // reader the noun and never the answer.\n aria-labelledby={`${nameId} ${valueId}`}\n aria-describedby={describedBy}\n aria-required={ariaRequired}\n aria-invalid={bad || undefined}\n className={cn(\n CONTROL_BASE,\n bad ? CONTROL_BORDER.invalid : CONTROL_BORDER.resting,\n 'flex cursor-pointer items-center gap-2.5 text-start',\n className,\n )}\n >\n {/* Standing outside a Field there is no label to point at, so the\n trigger carries its own — the name half of the two the\n `aria-labelledby` above names, with the value as the other. */}\n {field?.labelId == null && (\n <span id={nameId} className=\"sr-only\">\n {label}\n </span>\n )}\n <span\n aria-hidden=\"true\"\n className=\"size-4 shrink-0 rounded-(--radius-xs) border border-(--rule-2)\"\n // The colour as a flat GRADIENT, not as a colour. In the\n // `background` shorthand a plain colour is only legal in the last\n // layer, so `background: #a78bfa, <checks>` is invalid and the\n // whole declaration is dropped — which drew an empty swatch.\n style={{ background: `linear-gradient(${value}, ${value}), ${checkerboard('4px')}` }}\n />\n <span id={valueId} className=\"mono-meta truncate\">\n {value}\n </span>\n </button>\n </PopoverTrigger>\n\n <PopoverContent label={`${label} picker`} className=\"flex w-72 flex-col gap-3\">\n <ToggleGroup\n type=\"single\"\n value={format}\n // `type=\"single\"` commits an empty string when the pressed segment is\n // the selected one, which here would leave the panel with no notation\n // to write in at all.\n onValueChange={(next) => next && commit(color, next as ColorFormat)}\n aria-label=\"Colour notation\"\n className=\"w-full\"\n >\n {(Object.keys(FORMAT_LABELS) as ColorFormat[]).map((option) => (\n <ToggleGroupItem key={option} value={option} className=\"flex-1\">\n {FORMAT_LABELS[option]}\n </ToggleGroupItem>\n ))}\n </ToggleGroup>\n\n {/* A group of two real sliders rather than a canvas with key handlers:\n the arrows, Home and End are then the platform's, and a reader is\n told which axis they are on and where they are along it. The canvas\n underneath is what a pointer aims at. */}\n <div\n ref={planeRef}\n role=\"group\"\n aria-label={`${label} field`}\n onPointerDown={(event) => {\n if (event.button !== 0) return\n event.preventDefault()\n event.currentTarget.setPointerCapture(event.pointerId)\n trackPointer(event)\n }}\n onPointerMove={(event) => {\n if (event.currentTarget.hasPointerCapture(event.pointerId)) trackPointer(event)\n }}\n onPointerUp={(event) => {\n if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n event.currentTarget.releasePointerCapture(event.pointerId)\n }\n }}\n // The focus ring has to be drawn HERE: the two sliders that take the\n // focus are `sr-only`, so the one the browser draws on them is\n // clipped away with the rest of the box.\n className=\"relative h-40 w-full cursor-crosshair touch-none select-none rounded-(--radius) focus-within:[outline:var(--focus-w)_solid_var(--focus)] focus-within:[outline-offset:var(--focus-off)]\"\n >\n <canvas\n ref={(node) => {\n canvasRef.current = node\n // Closing destroys the canvas and its bitmap with it, so the next\n // open has to repaint even at the same hue.\n if (!node) painted.current = ''\n paint(node)\n }}\n width={PLANE_W}\n height={PLANE_H}\n aria-hidden=\"true\"\n className=\"block size-full rounded-(--radius)\"\n />\n {/* A hairline over the plane, so the light corner still has an edge. */}\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-0 rounded-(--radius) shadow-[inset_0_0_0_1px_var(--rule)]\"\n />\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute -m-1.5 size-3 rounded-(--radius-pill) border-2 border-(--paper) shadow-[0_0_0_1px_var(--rule-hard)]\"\n style={{ left: `${saturation * 100}%`, top: `${(1 - color.l) * 100}%`, background: opaque }}\n />\n <input\n type=\"range\"\n min={0}\n max={100}\n step={1}\n value={Math.round(saturation * 100)}\n aria-label=\"Chroma\"\n aria-valuetext={`${Math.round(saturation * 100)} percent chroma`}\n onChange={(event) => setPlane(Number(event.target.value) / 100, color.l)}\n className=\"sr-only\"\n />\n <input\n type=\"range\"\n min={0}\n max={100}\n step={1}\n value={Math.round(color.l * 100)}\n aria-label=\"Lightness\"\n aria-valuetext={`${Math.round(color.l * 100)} percent lightness`}\n onChange={(event) => setPlane(saturation, Number(event.target.value) / 100)}\n className=\"sr-only\"\n />\n </div>\n\n <ColorTrack\n name=\"Hue\"\n max={360}\n step={1}\n value={color.h}\n announce={`${Math.round(color.h)} degrees`}\n thumb={opaque}\n track={`linear-gradient(to right in oklab, ${hueTrack})`}\n onChange={(h) => commit({ ...color, h, c: saturation * maxChroma(color.l, h, space) })}\n />\n <ColorTrack\n name=\"Opacity\"\n max={100}\n step={1}\n value={color.a * 100}\n announce={`${Math.round(color.a * 100)} percent`}\n thumb={formatColor(color, 'oklch')}\n track={`linear-gradient(to right, transparent, ${opaque}), ${checkerboard('8px')}`}\n onChange={(percent) => commit({ ...color, a: percent / 100 })}\n />\n\n <Input\n value={value}\n spellCheck={false}\n autoComplete=\"off\"\n aria-label={`${label} as CSS`}\n invalid={typed === null}\n onChange={(event) => {\n const next = event.target.value\n emitted.current = next\n if (controlled === undefined) setUncontrolled(next)\n const parsed = parseColor(next)\n if (parsed) {\n setColor(parsed)\n setFormat(colorFormat(next))\n }\n onValueChange?.(next)\n }}\n className=\"mono-meta\"\n />\n </PopoverContent>\n </Popover>\n )\n}\n\ninterface ColorTrackProps {\n name: string\n max: number\n step: number\n value: number\n /** What the number MEANS, since a bare \"212\" on a hue slider is not a reading. */\n announce: string\n /** CSS for the strip behind the thumb. */\n track: string\n /** The colour the thumb is filled with. */\n thumb: string\n onChange: (value: number) => void\n}\n\n/**\n * One channel of the picker, as a native range input.\n *\n * Native rather than this system's `Slider`, and deliberately: the whole point\n * of these two is the strip BEHIND the thumb — a hue ramp taken at the current\n * lightness, an opacity ramp over a checkerboard — and Radix's track is not a\n * surface a caller can paint. A native range gives the same keyboard contract\n * for nothing, which is what made the trade worth taking.\n */\nfunction ColorTrack({ name, max, step, value, announce, track, thumb, onChange }: ColorTrackProps) {\n return (\n <label className=\"flex items-center gap-3 text-sm text-(--ink-2)\">\n <span className=\"w-14 shrink-0\">{name}</span>\n <input\n type=\"range\"\n min={0}\n max={max}\n step={step}\n value={value}\n aria-valuetext={announce}\n onChange={(event) => onChange(Number(event.target.value))}\n style={{ background: track }}\n className={cn(\n 'h-4 w-full cursor-pointer appearance-none rounded-(--radius-xs) shadow-[inset_0_0_0_1px_var(--rule)]',\n '[&::-webkit-slider-thumb]:h-6 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-(--radius-xs) [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-(--paper) [&::-webkit-slider-thumb]:bg-(--thumb) [&::-webkit-slider-thumb]:bg-clip-padding [&::-webkit-slider-thumb]:shadow-[0_0_0_1px_var(--rule-hard)]',\n '[&::-moz-range-thumb]:h-6 [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:rounded-(--radius-xs) [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-(--paper) [&::-moz-range-thumb]:bg-(--thumb) [&::-moz-range-thumb]:bg-clip-padding [&::-moz-range-thumb]:shadow-[0_0_0_1px_var(--rule-hard)]',\n )}\n // Read by both thumb rules above; a pseudo-element cannot take a style\n // attribute of its own, so the colour has to arrive as a variable.\n ref={(node) => {\n node?.style.setProperty('--thumb', thumb)\n }}\n />\n </label>\n )\n}\n\nexport default ColorPicker\n"],"mappings":"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","names":[]}
@@ -25,7 +25,7 @@ function Command({
25
25
  CommandPrimitive,
26
26
  {
27
27
  className: cn(
28
- "flex w-full flex-col overflow-hidden rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) panel-blur",
28
+ "flex w-full flex-col overflow-hidden rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) shadow-(--panel-lift) panel-blur",
29
29
  className
30
30
  ),
31
31
  ...props
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Command/Command.tsx"],"sourcesContent":["'use client'\n\nimport { Command as CommandPrimitive } from 'cmdk'\nimport { Search, type LucideIcon } from 'lucide-react'\nimport { isValidElement, type ComponentProps, type ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\n/**\n * True for a component type — `icon={Settings}` — rather than a rendered element.\n *\n * Everything a `ReactNode` can be is an element, a primitive, an array, another\n * iterable or a promise; a Lucide icon is none of those, it is a `forwardRef`\n * object. So both spellings are tellable apart at runtime, which is what lets\n * one prop take either — and it has to, because this prop meant the opposite\n * thing one import away and the wrong spelling failed at render, not at the\n * type.\n */\nfunction isIconComponent(icon: LucideIcon | ReactNode): icon is LucideIcon {\n if (isValidElement(icon)) return false\n if (typeof icon === 'function') return true\n if (typeof icon !== 'object' || icon === null) return false\n return !Array.isArray(icon) && !(Symbol.iterator in icon) && !('then' in icon)\n}\n\n/** The leading glyph, sized by this component when it was handed a component. */\nfunction iconNode(icon: LucideIcon | ReactNode): ReactNode {\n if (!isIconComponent(icon)) return icon\n const Icon = icon\n return <Icon size={16} strokeWidth={1.5} aria-hidden />\n}\nimport { Dialog, DialogContent } from '../Dialog/Dialog'\nimport { Kbd } from '../Kbd/Kbd'\n\n/**\n * A filterable list of actions — the ⌘K surface.\n *\n * Built on cmdk rather than on a `Select` or a menu, because the interaction is\n * neither: the list is filtered as you type, the highlighted row moves with the\n * arrow keys while focus STAYS in the input, and Enter runs the highlighted\n * row. That is the ARIA combobox pattern, and it is the part nobody should\n * hand-roll — `aria-activedescendant` moving without focus moving is precisely\n * where a home-made palette stops working with a screen reader.\n *\n * @example\n * <Command label=\"Command palette\">\n * <CommandInput placeholder=\"Type a command…\" />\n * <CommandList>\n * <CommandEmpty>Nothing matches.</CommandEmpty>\n * <CommandGroup heading=\"Navigate\">\n * <CommandItem onSelect={go}>Components</CommandItem>\n * </CommandGroup>\n * </CommandList>\n * </Command>\n */\nexport function Command({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive>) {\n return (\n <CommandPrimitive\n className={cn(\n 'flex w-full flex-col overflow-hidden rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) panel-blur',\n className,\n )}\n {...props}\n />\n )\n}\n\n/** The filter field. Carries the search icon and the combobox semantics. */\nexport function CommandInput({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.Input>) {\n return (\n <div className=\"flex items-center gap-3 border-b border-(--rule) px-4\">\n <Search size={18} strokeWidth={1.5} aria-hidden className=\"shrink-0 text-(--ink-3-aa)\" />\n <CommandPrimitive.Input\n className={cn(\n 'h-13 w-full bg-transparent text-[15px] text-(--ink) outline-none placeholder:text-(--ink-3-aa)',\n className,\n )}\n {...props}\n />\n </div>\n )\n}\n\nexport function CommandList({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.List>) {\n return (\n <CommandPrimitive.List\n className={cn('max-h-72 overflow-y-auto overflow-x-hidden p-2 scroll-slim', className)}\n {...props}\n />\n )\n}\n\n/** Shown when the filter matches nothing. Say what would match, not \"no results\". */\nexport function CommandEmpty(props: ComponentProps<typeof CommandPrimitive.Empty>) {\n return (\n <CommandPrimitive.Empty\n className=\"px-3 py-10 text-center text-sm text-(--ink-3-aa)\"\n {...props}\n />\n )\n}\n\n/**\n * The key-hint strip along the bottom.\n *\n * A palette is a keyboard surface whose keys are invisible: nothing on screen\n * says the arrows move the row or that Enter runs it, and a reader who reaches\n * for the mouse has been failed by the design rather than by themselves.\n *\n * @example\n * <CommandFooter>\n * <CommandHint keys={['↑', '↓']}>navigate</CommandHint>\n * <CommandHint keys={['↵']}>open</CommandHint>\n * </CommandFooter>\n */\nexport function CommandFooter({ className, ...props }: ComponentProps<'div'>) {\n return (\n <div\n className={cn(\n 'flex items-center gap-4 border-t border-(--rule) bg-(--paper-2) px-3.5 py-2',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport interface CommandHintProps extends Omit<ComponentProps<'span'>, 'children'> {\n /** The keys this hint describes, printed as `Kbd` chips. */\n keys: string[]\n /** What they do — a verb, lowercase, no sentence. */\n children: ReactNode\n}\n\n/** One key-and-verb pair inside a `CommandFooter`. */\nexport function CommandHint({ keys, children, className, ...props }: CommandHintProps) {\n return (\n <span className={cn('flex items-center gap-1.5 mono-meta text-(--ink-3-aa)', className)} {...props}>\n {keys.map((key) => (\n <Kbd key={key}>{key}</Kbd>\n ))}\n {children}\n </span>\n )\n}\n\nexport function CommandGroup({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.Group>) {\n return (\n <CommandPrimitive.Group\n className={cn(\n 'overflow-hidden [&_[cmdk-group-heading]]:px-2.5 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:eyebrow [&_[cmdk-group-heading]]:text-(--ink-3-aa)',\n className,\n )}\n {...props}\n />\n )\n}\n\n/**\n * A divider between groups.\n *\n * Marked presentational, because ARIA permits a `listbox` to contain only\n * `option` and `group` — and the library renders this as `role=\"separator\"`,\n * which puts a critical `aria-required-children` violation inside every palette\n * that uses one. The grouping is already announced by the groups themselves, so\n * removing the divider from the accessibility tree loses nothing.\n */\nexport function CommandSeparator({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.Separator>) {\n return (\n <CommandPrimitive.Separator\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn('my-1 h-px bg-(--rule)', className)}\n {...props}\n />\n )\n}\n\nexport interface CommandItemProps extends ComponentProps<typeof CommandPrimitive.Item> {\n /** A shortcut printed at the end of the row. */\n shortcut?: string\n /**\n * A leading glyph. Either spelling — `icon={<Settings size={16} />}` passes\n * the element, `icon={Settings}` passes the component and this sizes it.\n *\n * It is what makes a long list scannable — the eye sorts by shape before it\n * reads, and forty identical rows of text defeat that.\n */\n icon?: LucideIcon | ReactNode\n /**\n * A quiet note at the end of the row — what kind of thing this is, or its\n * current state. Not a description: a palette that prints a sentence per row\n * stops being scannable at about six of them.\n */\n meta?: ReactNode\n}\n\nexport function CommandItem({ shortcut, icon, meta, className, children, ...props }: CommandItemProps) {\n return (\n <CommandPrimitive.Item\n className={cn(\n 'flex cursor-pointer items-center gap-3 rounded-(--radius-row) px-3 py-2.5 text-[15px] text-(--ink-2) outline-none transition-colors duration-(--duration-fast)',\n // The highlighted row is a chosen state, and law 7 says a chosen state\n // reads --accent. The leading rule gives it an edge the eye catches\n // while scrolling, which a fill alone does not.\n 'relative data-[selected=true]:bg-(--accent-muted) data-[selected=true]:text-(--ink)',\n 'before:absolute before:inset-y-1 before:start-0 before:w-0.5 before:rounded-(--radius-pill) before:bg-transparent data-[selected=true]:before:bg-(--accent)',\n 'data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-(--disabled-opacity)',\n className,\n )}\n {...props}\n >\n {icon && (\n <span className=\"grid size-4 shrink-0 place-items-center text-(--ink-3-aa) [&_svg]:size-4\">\n {iconNode(icon)}\n </span>\n )}\n {children}\n {meta && <span className=\"ms-auto ps-3 mono-meta text-(--ink-3-aa)\">{meta}</span>}\n {shortcut && <Kbd className={cn(meta ? '' : 'ms-auto')}>{shortcut}</Kbd>}\n </CommandPrimitive.Item>\n )\n}\n\nexport interface CommandDialogProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n /** Names the palette for assistive tech. */\n label: string\n children: ReactNode\n}\n\n/**\n * The palette in a modal, which is how it is nearly always used.\n *\n * The dialog's own padding is removed: a palette is edge-to-edge, and its input\n * is the first thing focus lands on.\n */\nexport function CommandDialog({ open, onOpenChange, label, children }: CommandDialogProps) {\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent\n title={label}\n hideTitle\n showClose={false}\n // Above centre, not at it. A palette is read as a layer over the page\n // rather than as a message about it, and centring it puts the list\n // under the reader's own hands on a laptop.\n // `scrollbar-gutter: auto` undoes the `stable` that `Dialog`'s own\n // `scroll-slim` sets. The dialog never scrolls — the list inside it\n // does — but the reserved gutter was still there, so the list stopped\n // thirteen pixels short of the panel and drew its own hairline inside\n // an empty channel. Two bars, one of them permanently blank.\n className=\"top-[10vh] w-[min(94vw,44rem)] translate-y-0 overflow-hidden p-0 [scrollbar-gutter:auto]\"\n >\n {/* The modal has the whole screen, so its list may be taller than the\n default an inline menu should keep.\n\n `scroll-hairline` rather than `scroll-slim`: eleven pixels of grey\n down the side of a palette is the widest thing in it. */}\n <Command\n label={label}\n className=\"rounded-none border-0 [&_[cmdk-list]]:max-h-[26rem] [&_[cmdk-list]]:scroll-hairline\"\n >\n {children}\n </Command>\n </DialogContent>\n </Dialog>\n )\n}\n\nexport default Command\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/components/Command/Command.tsx"],"sourcesContent":["'use client'\n\nimport { Command as CommandPrimitive } from 'cmdk'\nimport { Search, type LucideIcon } from 'lucide-react'\nimport { isValidElement, type ComponentProps, type ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\n/**\n * True for a component type — `icon={Settings}` — rather than a rendered element.\n *\n * Everything a `ReactNode` can be is an element, a primitive, an array, another\n * iterable or a promise; a Lucide icon is none of those, it is a `forwardRef`\n * object. So both spellings are tellable apart at runtime, which is what lets\n * one prop take either — and it has to, because this prop meant the opposite\n * thing one import away and the wrong spelling failed at render, not at the\n * type.\n */\nfunction isIconComponent(icon: LucideIcon | ReactNode): icon is LucideIcon {\n if (isValidElement(icon)) return false\n if (typeof icon === 'function') return true\n if (typeof icon !== 'object' || icon === null) return false\n return !Array.isArray(icon) && !(Symbol.iterator in icon) && !('then' in icon)\n}\n\n/** The leading glyph, sized by this component when it was handed a component. */\nfunction iconNode(icon: LucideIcon | ReactNode): ReactNode {\n if (!isIconComponent(icon)) return icon\n const Icon = icon\n return <Icon size={16} strokeWidth={1.5} aria-hidden />\n}\nimport { Dialog, DialogContent } from '../Dialog/Dialog'\nimport { Kbd } from '../Kbd/Kbd'\n\n/**\n * A filterable list of actions — the ⌘K surface.\n *\n * Built on cmdk rather than on a `Select` or a menu, because the interaction is\n * neither: the list is filtered as you type, the highlighted row moves with the\n * arrow keys while focus STAYS in the input, and Enter runs the highlighted\n * row. That is the ARIA combobox pattern, and it is the part nobody should\n * hand-roll — `aria-activedescendant` moving without focus moving is precisely\n * where a home-made palette stops working with a screen reader.\n *\n * @example\n * <Command label=\"Command palette\">\n * <CommandInput placeholder=\"Type a command…\" />\n * <CommandList>\n * <CommandEmpty>Nothing matches.</CommandEmpty>\n * <CommandGroup heading=\"Navigate\">\n * <CommandItem onSelect={go}>Components</CommandItem>\n * </CommandGroup>\n * </CommandList>\n * </Command>\n */\nexport function Command({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive>) {\n return (\n <CommandPrimitive\n className={cn(\n 'flex w-full flex-col overflow-hidden rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) shadow-(--panel-lift) panel-blur',\n className,\n )}\n {...props}\n />\n )\n}\n\n/** The filter field. Carries the search icon and the combobox semantics. */\nexport function CommandInput({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.Input>) {\n return (\n <div className=\"flex items-center gap-3 border-b border-(--rule) px-4\">\n <Search size={18} strokeWidth={1.5} aria-hidden className=\"shrink-0 text-(--ink-3-aa)\" />\n <CommandPrimitive.Input\n className={cn(\n 'h-13 w-full bg-transparent text-[15px] text-(--ink) outline-none placeholder:text-(--ink-3-aa)',\n className,\n )}\n {...props}\n />\n </div>\n )\n}\n\nexport function CommandList({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.List>) {\n return (\n <CommandPrimitive.List\n className={cn('max-h-72 overflow-y-auto overflow-x-hidden p-2 scroll-slim', className)}\n {...props}\n />\n )\n}\n\n/** Shown when the filter matches nothing. Say what would match, not \"no results\". */\nexport function CommandEmpty(props: ComponentProps<typeof CommandPrimitive.Empty>) {\n return (\n <CommandPrimitive.Empty\n className=\"px-3 py-10 text-center text-sm text-(--ink-3-aa)\"\n {...props}\n />\n )\n}\n\n/**\n * The key-hint strip along the bottom.\n *\n * A palette is a keyboard surface whose keys are invisible: nothing on screen\n * says the arrows move the row or that Enter runs it, and a reader who reaches\n * for the mouse has been failed by the design rather than by themselves.\n *\n * @example\n * <CommandFooter>\n * <CommandHint keys={['↑', '↓']}>navigate</CommandHint>\n * <CommandHint keys={['↵']}>open</CommandHint>\n * </CommandFooter>\n */\nexport function CommandFooter({ className, ...props }: ComponentProps<'div'>) {\n return (\n <div\n className={cn(\n 'flex items-center gap-4 border-t border-(--rule) bg-(--paper-2) px-3.5 py-2',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport interface CommandHintProps extends Omit<ComponentProps<'span'>, 'children'> {\n /** The keys this hint describes, printed as `Kbd` chips. */\n keys: string[]\n /** What they do — a verb, lowercase, no sentence. */\n children: ReactNode\n}\n\n/** One key-and-verb pair inside a `CommandFooter`. */\nexport function CommandHint({ keys, children, className, ...props }: CommandHintProps) {\n return (\n <span className={cn('flex items-center gap-1.5 mono-meta text-(--ink-3-aa)', className)} {...props}>\n {keys.map((key) => (\n <Kbd key={key}>{key}</Kbd>\n ))}\n {children}\n </span>\n )\n}\n\nexport function CommandGroup({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.Group>) {\n return (\n <CommandPrimitive.Group\n className={cn(\n 'overflow-hidden [&_[cmdk-group-heading]]:px-2.5 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:eyebrow [&_[cmdk-group-heading]]:text-(--ink-3-aa)',\n className,\n )}\n {...props}\n />\n )\n}\n\n/**\n * A divider between groups.\n *\n * Marked presentational, because ARIA permits a `listbox` to contain only\n * `option` and `group` — and the library renders this as `role=\"separator\"`,\n * which puts a critical `aria-required-children` violation inside every palette\n * that uses one. The grouping is already announced by the groups themselves, so\n * removing the divider from the accessibility tree loses nothing.\n */\nexport function CommandSeparator({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.Separator>) {\n return (\n <CommandPrimitive.Separator\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn('my-1 h-px bg-(--rule)', className)}\n {...props}\n />\n )\n}\n\nexport interface CommandItemProps extends ComponentProps<typeof CommandPrimitive.Item> {\n /** A shortcut printed at the end of the row. */\n shortcut?: string\n /**\n * A leading glyph. Either spelling — `icon={<Settings size={16} />}` passes\n * the element, `icon={Settings}` passes the component and this sizes it.\n *\n * It is what makes a long list scannable — the eye sorts by shape before it\n * reads, and forty identical rows of text defeat that.\n */\n icon?: LucideIcon | ReactNode\n /**\n * A quiet note at the end of the row — what kind of thing this is, or its\n * current state. Not a description: a palette that prints a sentence per row\n * stops being scannable at about six of them.\n */\n meta?: ReactNode\n}\n\nexport function CommandItem({ shortcut, icon, meta, className, children, ...props }: CommandItemProps) {\n return (\n <CommandPrimitive.Item\n className={cn(\n 'flex cursor-pointer items-center gap-3 rounded-(--radius-row) px-3 py-2.5 text-[15px] text-(--ink-2) outline-none transition-colors duration-(--duration-fast)',\n // The highlighted row is a chosen state, and law 7 says a chosen state\n // reads --accent. The leading rule gives it an edge the eye catches\n // while scrolling, which a fill alone does not.\n 'relative data-[selected=true]:bg-(--accent-muted) data-[selected=true]:text-(--ink)',\n 'before:absolute before:inset-y-1 before:start-0 before:w-0.5 before:rounded-(--radius-pill) before:bg-transparent data-[selected=true]:before:bg-(--accent)',\n 'data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-(--disabled-opacity)',\n className,\n )}\n {...props}\n >\n {icon && (\n <span className=\"grid size-4 shrink-0 place-items-center text-(--ink-3-aa) [&_svg]:size-4\">\n {iconNode(icon)}\n </span>\n )}\n {children}\n {meta && <span className=\"ms-auto ps-3 mono-meta text-(--ink-3-aa)\">{meta}</span>}\n {shortcut && <Kbd className={cn(meta ? '' : 'ms-auto')}>{shortcut}</Kbd>}\n </CommandPrimitive.Item>\n )\n}\n\nexport interface CommandDialogProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n /** Names the palette for assistive tech. */\n label: string\n children: ReactNode\n}\n\n/**\n * The palette in a modal, which is how it is nearly always used.\n *\n * The dialog's own padding is removed: a palette is edge-to-edge, and its input\n * is the first thing focus lands on.\n */\nexport function CommandDialog({ open, onOpenChange, label, children }: CommandDialogProps) {\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent\n title={label}\n hideTitle\n showClose={false}\n // Above centre, not at it. A palette is read as a layer over the page\n // rather than as a message about it, and centring it puts the list\n // under the reader's own hands on a laptop.\n // `scrollbar-gutter: auto` undoes the `stable` that `Dialog`'s own\n // `scroll-slim` sets. The dialog never scrolls — the list inside it\n // does — but the reserved gutter was still there, so the list stopped\n // thirteen pixels short of the panel and drew its own hairline inside\n // an empty channel. Two bars, one of them permanently blank.\n className=\"top-[10vh] w-[min(94vw,44rem)] translate-y-0 overflow-hidden p-0 [scrollbar-gutter:auto]\"\n >\n {/* The modal has the whole screen, so its list may be taller than the\n default an inline menu should keep.\n\n `scroll-hairline` rather than `scroll-slim`: eleven pixels of grey\n down the side of a palette is the widest thing in it. */}\n <Command\n label={label}\n className=\"rounded-none border-0 [&_[cmdk-list]]:max-h-[26rem] [&_[cmdk-list]]:scroll-hairline\"\n >\n {children}\n </Command>\n </DialogContent>\n </Dialog>\n )\n}\n\nexport default Command\n"],"mappings":"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","names":[]}
@@ -32,7 +32,7 @@ function ContextMenuContent({
32
32
  collisionPadding: 8,
33
33
  "data-m22-animated": true,
34
34
  className: cn(
35
- "z-(--z-dropdown) min-w-44 rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-1.5 panel-blur",
35
+ "z-(--z-dropdown) min-w-44 rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-1.5 shadow-(--panel-lift) panel-blur",
36
36
  "data-[state=open]:animate-[m22-pop-in_var(--duration-fast)_var(--ease-out-expo)] data-[state=closed]:animate-[m22-pop-out_var(--duration-fast)_var(--ease)] origin-(--radix-popper-transform-origin)",
37
37
  className
38
38
  ),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ContextMenu/ContextMenu.tsx"],"sourcesContent":["'use client'\n\nimport * as ContextMenuPrimitive from '@radix-ui/react-context-menu'\nimport type { LucideIcon } from 'lucide-react'\nimport { isValidElement, useId, type ComponentProps, type ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { useOverlayContainer } from '../../lib/overlay-container'\n\n/**\n * True for a component type — `icon={Copy}` — rather than a rendered element.\n *\n * Everything a `ReactNode` can be is an element, a primitive, an array, another\n * iterable or a promise; a Lucide icon is none of those, it is a `forwardRef`\n * object. So both spellings are tellable apart at runtime, which is what lets\n * one prop take either — and it has to, because this prop meant the opposite\n * thing one import away and the wrong spelling failed at render, not at the\n * type.\n */\nfunction isIconComponent(icon: LucideIcon | ReactNode): icon is LucideIcon {\n if (isValidElement(icon)) return false\n if (typeof icon === 'function') return true\n if (typeof icon !== 'object' || icon === null) return false\n return !Array.isArray(icon) && !(Symbol.iterator in icon) && !('then' in icon)\n}\n\n/** The leading glyph, sized by this component when it was handed a component. */\nfunction iconNode(icon: LucideIcon | ReactNode): ReactNode {\n if (icon === undefined || icon === null || icon === false) return null\n if (!isIconComponent(icon)) {\n return (\n <span aria-hidden className=\"grid size-4 shrink-0 place-items-center [&_svg]:size-4\">\n {icon}\n </span>\n )\n }\n const Icon = icon\n return <Icon size={16} strokeWidth={1.5} className=\"shrink-0\" aria-hidden />\n}\n\n/** Radix ContextMenu root and trigger, as typed passthroughs. */\nexport const ContextMenu = ContextMenuPrimitive.Root\nexport const ContextMenuTrigger = ContextMenuPrimitive.Trigger\n\n/**\n * The menu a right-click opens.\n *\n * Never the only way to reach an action. A context menu is opened by a\n * secondary pointer button or a keyboard's own menu key, and a meaningful share\n * of readers have neither — a touch user, someone on a trackpad they have not\n * configured, anyone driving the page by keyboard alone. Whatever is in here\n * belongs somewhere reachable too: a row's overflow button, a toolbar.\n *\n * @example\n * <ContextMenu>\n * <ContextMenuTrigger asChild><Card>Right-click me</Card></ContextMenuTrigger>\n * <ContextMenuContent>\n * <ContextMenuItem icon={Copy}>Copy</ContextMenuItem>\n * </ContextMenuContent>\n * </ContextMenu>\n */\nexport function ContextMenuContent({\n className,\n ...rest\n}: ComponentProps<typeof ContextMenuPrimitive.Content>) {\n const container = useOverlayContainer()\n\n return (\n <ContextMenuPrimitive.Portal container={container ?? undefined}>\n <ContextMenuPrimitive.Content\n collisionBoundary={container ?? undefined}\n collisionPadding={8}\n data-m22-animated\n className={cn(\n 'z-(--z-dropdown) min-w-44 rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-1.5 panel-blur',\n 'data-[state=open]:animate-[m22-pop-in_var(--duration-fast)_var(--ease-out-expo)] data-[state=closed]:animate-[m22-pop-out_var(--duration-fast)_var(--ease)] origin-(--radix-popper-transform-origin)',\n className,\n )}\n {...rest}\n />\n </ContextMenuPrimitive.Portal>\n )\n}\n\nexport interface ContextMenuItemProps\n extends ComponentProps<typeof ContextMenuPrimitive.Item> {\n /**\n * Optional leading icon. Either spelling — `icon={Copy}` passes the component\n * and this sizes it, `icon={<Copy size={16} />}` passes the element and this\n * places it.\n */\n icon?: LucideIcon | ReactNode\n /** Paints the row as destructive. Use for delete, revoke, disconnect. */\n destructive?: boolean\n}\n\n/** A row. Highlight follows `data-highlighted`, which covers hover and keyboard. */\nexport function ContextMenuItem({\n icon,\n destructive = false,\n className,\n children,\n ...rest\n}: ContextMenuItemProps) {\n return (\n <ContextMenuPrimitive.Item\n className={cn(\n 'flex cursor-pointer items-center gap-2.5 rounded-(--radius-row) px-2.5 py-2 text-sm outline-none transition-colors duration-(--duration-fast) data-[highlighted]:bg-(--stone) data-[disabled]:pointer-events-none data-[disabled]:opacity-(--disabled-opacity)',\n destructive ? 'text-(--danger)' : 'text-(--ink-2) data-[highlighted]:text-(--ink)',\n className,\n )}\n {...rest}\n >\n {iconNode(icon)}\n {children}\n </ContextMenuPrimitive.Item>\n )\n}\n\n/** Hairline divider between groups. */\nexport function ContextMenuSeparator({\n className,\n ...rest\n}: ComponentProps<typeof ContextMenuPrimitive.Separator>) {\n return (\n <ContextMenuPrimitive.Separator className={cn('my-1 h-px bg-(--rule)', className)} {...rest} />\n )\n}\n\n/**\n * Mono eyebrow heading, on its own.\n *\n * Visual only: Radix renders it as a bare `<div>` with no role. Reach for\n * `ContextMenuGroup` when the eyebrow is a HEADING over rows; this is right for\n * a line that heads nothing.\n */\nexport function ContextMenuLabel({\n className,\n ...rest\n}: ComponentProps<typeof ContextMenuPrimitive.Label>) {\n return (\n <ContextMenuPrimitive.Label\n className={cn('px-2.5 py-1.5 eyebrow text-(--ink-3-aa)', className)}\n {...rest}\n />\n )\n}\n\nexport interface ContextMenuGroupProps\n extends ComponentProps<typeof ContextMenuPrimitive.Group> {\n /** The eyebrow over the rows, and the group's accessible name. */\n label?: ReactNode\n}\n\n/**\n * A named section of a menu.\n *\n * The eyebrow alone was a picture of a heading: Radix's `MenuLabel` carries no\n * role and no `aria-labelledby` wiring, so a sighted reader saw sections and a\n * screen-reader user got one undifferentiated list. This renders the group,\n * renders the label inside it, and points the one at the other.\n */\nexport function ContextMenuGroup({ label, children, ...rest }: ContextMenuGroupProps) {\n const id = useId()\n\n return (\n <ContextMenuPrimitive.Group\n aria-labelledby={label === undefined ? undefined : id}\n {...rest}\n >\n {label !== undefined && <ContextMenuLabel id={id}>{label}</ContextMenuLabel>}\n {children}\n </ContextMenuPrimitive.Group>\n )\n}\n\nexport default ContextMenu\n"],"mappings":";AA8BM,cA0EF,YA1EE;AA5BN,YAAY,0BAA0B;AAEtC,SAAS,gBAAgB,aAAkD;AAC3E,SAAS,UAAU;AACnB,SAAS,2BAA2B;AAYpC,SAAS,gBAAgB,MAAkD;AACzE,MAAI,eAAe,IAAI,EAAG,QAAO;AACjC,MAAI,OAAO,SAAS,WAAY,QAAO;AACvC,MAAI,OAAO,SAAS,YAAY,SAAS,KAAM,QAAO;AACtD,SAAO,CAAC,MAAM,QAAQ,IAAI,KAAK,EAAE,OAAO,YAAY,SAAS,EAAE,UAAU;AAC3E;AAGA,SAAS,SAAS,MAAyC;AACzD,MAAI,SAAS,UAAa,SAAS,QAAQ,SAAS,MAAO,QAAO;AAClE,MAAI,CAAC,gBAAgB,IAAI,GAAG;AAC1B,WACE,oBAAC,UAAK,eAAW,MAAC,WAAU,0DACzB,gBACH;AAAA,EAEJ;AACA,QAAM,OAAO;AACb,SAAO,oBAAC,QAAK,MAAM,IAAI,aAAa,KAAK,WAAU,YAAW,eAAW,MAAC;AAC5E;AAGO,MAAM,cAAc,qBAAqB;AACzC,MAAM,qBAAqB,qBAAqB;AAmBhD,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA,GAAG;AACL,GAAwD;AACtD,QAAM,YAAY,oBAAoB;AAEtC,SACE,oBAAC,qBAAqB,QAArB,EAA4B,WAAW,aAAa,QACnD;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,mBAAmB,aAAa;AAAA,MAChC,kBAAkB;AAAA,MAClB,qBAAiB;AAAA,MACjB,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN,GACF;AAEJ;AAeO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAyB;AACvB,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,cAAc,oBAAoB;AAAA,QAClC;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA,iBAAS,IAAI;AAAA,QACb;AAAA;AAAA;AAAA,EACH;AAEJ;AAGO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA,GAAG;AACL,GAA0D;AACxD,SACE,oBAAC,qBAAqB,WAArB,EAA+B,WAAW,GAAG,yBAAyB,SAAS,GAAI,GAAG,MAAM;AAEjG;AASO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA,GAAG;AACL,GAAsD;AACpD,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,WAAW,GAAG,2CAA2C,SAAS;AAAA,MACjE,GAAG;AAAA;AAAA,EACN;AAEJ;AAgBO,SAAS,iBAAiB,EAAE,OAAO,UAAU,GAAG,KAAK,GAA0B;AACpF,QAAM,KAAK,MAAM;AAEjB,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,mBAAiB,UAAU,SAAY,SAAY;AAAA,MAClD,GAAG;AAAA,MAEH;AAAA,kBAAU,UAAa,oBAAC,oBAAiB,IAAS,iBAAM;AAAA,QACxD;AAAA;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,sBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/ContextMenu/ContextMenu.tsx"],"sourcesContent":["'use client'\n\nimport * as ContextMenuPrimitive from '@radix-ui/react-context-menu'\nimport type { LucideIcon } from 'lucide-react'\nimport { isValidElement, useId, type ComponentProps, type ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { useOverlayContainer } from '../../lib/overlay-container'\n\n/**\n * True for a component type — `icon={Copy}` — rather than a rendered element.\n *\n * Everything a `ReactNode` can be is an element, a primitive, an array, another\n * iterable or a promise; a Lucide icon is none of those, it is a `forwardRef`\n * object. So both spellings are tellable apart at runtime, which is what lets\n * one prop take either — and it has to, because this prop meant the opposite\n * thing one import away and the wrong spelling failed at render, not at the\n * type.\n */\nfunction isIconComponent(icon: LucideIcon | ReactNode): icon is LucideIcon {\n if (isValidElement(icon)) return false\n if (typeof icon === 'function') return true\n if (typeof icon !== 'object' || icon === null) return false\n return !Array.isArray(icon) && !(Symbol.iterator in icon) && !('then' in icon)\n}\n\n/** The leading glyph, sized by this component when it was handed a component. */\nfunction iconNode(icon: LucideIcon | ReactNode): ReactNode {\n if (icon === undefined || icon === null || icon === false) return null\n if (!isIconComponent(icon)) {\n return (\n <span aria-hidden className=\"grid size-4 shrink-0 place-items-center [&_svg]:size-4\">\n {icon}\n </span>\n )\n }\n const Icon = icon\n return <Icon size={16} strokeWidth={1.5} className=\"shrink-0\" aria-hidden />\n}\n\n/** Radix ContextMenu root and trigger, as typed passthroughs. */\nexport const ContextMenu = ContextMenuPrimitive.Root\nexport const ContextMenuTrigger = ContextMenuPrimitive.Trigger\n\n/**\n * The menu a right-click opens.\n *\n * Never the only way to reach an action. A context menu is opened by a\n * secondary pointer button or a keyboard's own menu key, and a meaningful share\n * of readers have neither — a touch user, someone on a trackpad they have not\n * configured, anyone driving the page by keyboard alone. Whatever is in here\n * belongs somewhere reachable too: a row's overflow button, a toolbar.\n *\n * @example\n * <ContextMenu>\n * <ContextMenuTrigger asChild><Card>Right-click me</Card></ContextMenuTrigger>\n * <ContextMenuContent>\n * <ContextMenuItem icon={Copy}>Copy</ContextMenuItem>\n * </ContextMenuContent>\n * </ContextMenu>\n */\nexport function ContextMenuContent({\n className,\n ...rest\n}: ComponentProps<typeof ContextMenuPrimitive.Content>) {\n const container = useOverlayContainer()\n\n return (\n <ContextMenuPrimitive.Portal container={container ?? undefined}>\n <ContextMenuPrimitive.Content\n collisionBoundary={container ?? undefined}\n collisionPadding={8}\n data-m22-animated\n className={cn(\n 'z-(--z-dropdown) min-w-44 rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-1.5 shadow-(--panel-lift) panel-blur',\n 'data-[state=open]:animate-[m22-pop-in_var(--duration-fast)_var(--ease-out-expo)] data-[state=closed]:animate-[m22-pop-out_var(--duration-fast)_var(--ease)] origin-(--radix-popper-transform-origin)',\n className,\n )}\n {...rest}\n />\n </ContextMenuPrimitive.Portal>\n )\n}\n\nexport interface ContextMenuItemProps\n extends ComponentProps<typeof ContextMenuPrimitive.Item> {\n /**\n * Optional leading icon. Either spelling — `icon={Copy}` passes the component\n * and this sizes it, `icon={<Copy size={16} />}` passes the element and this\n * places it.\n */\n icon?: LucideIcon | ReactNode\n /** Paints the row as destructive. Use for delete, revoke, disconnect. */\n destructive?: boolean\n}\n\n/** A row. Highlight follows `data-highlighted`, which covers hover and keyboard. */\nexport function ContextMenuItem({\n icon,\n destructive = false,\n className,\n children,\n ...rest\n}: ContextMenuItemProps) {\n return (\n <ContextMenuPrimitive.Item\n className={cn(\n 'flex cursor-pointer items-center gap-2.5 rounded-(--radius-row) px-2.5 py-2 text-sm outline-none transition-colors duration-(--duration-fast) data-[highlighted]:bg-(--stone) data-[disabled]:pointer-events-none data-[disabled]:opacity-(--disabled-opacity)',\n destructive ? 'text-(--danger)' : 'text-(--ink-2) data-[highlighted]:text-(--ink)',\n className,\n )}\n {...rest}\n >\n {iconNode(icon)}\n {children}\n </ContextMenuPrimitive.Item>\n )\n}\n\n/** Hairline divider between groups. */\nexport function ContextMenuSeparator({\n className,\n ...rest\n}: ComponentProps<typeof ContextMenuPrimitive.Separator>) {\n return (\n <ContextMenuPrimitive.Separator className={cn('my-1 h-px bg-(--rule)', className)} {...rest} />\n )\n}\n\n/**\n * Mono eyebrow heading, on its own.\n *\n * Visual only: Radix renders it as a bare `<div>` with no role. Reach for\n * `ContextMenuGroup` when the eyebrow is a HEADING over rows; this is right for\n * a line that heads nothing.\n */\nexport function ContextMenuLabel({\n className,\n ...rest\n}: ComponentProps<typeof ContextMenuPrimitive.Label>) {\n return (\n <ContextMenuPrimitive.Label\n className={cn('px-2.5 py-1.5 eyebrow text-(--ink-3-aa)', className)}\n {...rest}\n />\n )\n}\n\nexport interface ContextMenuGroupProps\n extends ComponentProps<typeof ContextMenuPrimitive.Group> {\n /** The eyebrow over the rows, and the group's accessible name. */\n label?: ReactNode\n}\n\n/**\n * A named section of a menu.\n *\n * The eyebrow alone was a picture of a heading: Radix's `MenuLabel` carries no\n * role and no `aria-labelledby` wiring, so a sighted reader saw sections and a\n * screen-reader user got one undifferentiated list. This renders the group,\n * renders the label inside it, and points the one at the other.\n */\nexport function ContextMenuGroup({ label, children, ...rest }: ContextMenuGroupProps) {\n const id = useId()\n\n return (\n <ContextMenuPrimitive.Group\n aria-labelledby={label === undefined ? undefined : id}\n {...rest}\n >\n {label !== undefined && <ContextMenuLabel id={id}>{label}</ContextMenuLabel>}\n {children}\n </ContextMenuPrimitive.Group>\n )\n}\n\nexport default ContextMenu\n"],"mappings":";AA8BM,cA0EF,YA1EE;AA5BN,YAAY,0BAA0B;AAEtC,SAAS,gBAAgB,aAAkD;AAC3E,SAAS,UAAU;AACnB,SAAS,2BAA2B;AAYpC,SAAS,gBAAgB,MAAkD;AACzE,MAAI,eAAe,IAAI,EAAG,QAAO;AACjC,MAAI,OAAO,SAAS,WAAY,QAAO;AACvC,MAAI,OAAO,SAAS,YAAY,SAAS,KAAM,QAAO;AACtD,SAAO,CAAC,MAAM,QAAQ,IAAI,KAAK,EAAE,OAAO,YAAY,SAAS,EAAE,UAAU;AAC3E;AAGA,SAAS,SAAS,MAAyC;AACzD,MAAI,SAAS,UAAa,SAAS,QAAQ,SAAS,MAAO,QAAO;AAClE,MAAI,CAAC,gBAAgB,IAAI,GAAG;AAC1B,WACE,oBAAC,UAAK,eAAW,MAAC,WAAU,0DACzB,gBACH;AAAA,EAEJ;AACA,QAAM,OAAO;AACb,SAAO,oBAAC,QAAK,MAAM,IAAI,aAAa,KAAK,WAAU,YAAW,eAAW,MAAC;AAC5E;AAGO,MAAM,cAAc,qBAAqB;AACzC,MAAM,qBAAqB,qBAAqB;AAmBhD,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA,GAAG;AACL,GAAwD;AACtD,QAAM,YAAY,oBAAoB;AAEtC,SACE,oBAAC,qBAAqB,QAArB,EAA4B,WAAW,aAAa,QACnD;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,mBAAmB,aAAa;AAAA,MAChC,kBAAkB;AAAA,MAClB,qBAAiB;AAAA,MACjB,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN,GACF;AAEJ;AAeO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAyB;AACvB,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,cAAc,oBAAoB;AAAA,QAClC;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA,iBAAS,IAAI;AAAA,QACb;AAAA;AAAA;AAAA,EACH;AAEJ;AAGO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA,GAAG;AACL,GAA0D;AACxD,SACE,oBAAC,qBAAqB,WAArB,EAA+B,WAAW,GAAG,yBAAyB,SAAS,GAAI,GAAG,MAAM;AAEjG;AASO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA,GAAG;AACL,GAAsD;AACpD,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,WAAW,GAAG,2CAA2C,SAAS;AAAA,MACjE,GAAG;AAAA;AAAA,EACN;AAEJ;AAgBO,SAAS,iBAAiB,EAAE,OAAO,UAAU,GAAG,KAAK,GAA0B;AACpF,QAAM,KAAK,MAAM;AAEjB,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,mBAAiB,UAAU,SAAY,SAAY;AAAA,MAClD,GAAG;AAAA,MAEH;AAAA,kBAAU,UAAa,oBAAC,oBAAiB,IAAS,iBAAM;AAAA,QACxD;AAAA;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,sBAAQ;","names":[]}
@@ -54,7 +54,7 @@ function DialogContent({
54
54
  {
55
55
  "data-m22-animated": true,
56
56
  className: cn(
57
- "fixed left-1/2 top-1/2 z-(--z-modal) max-h-[85vh] w-[min(92vw,32rem)] -translate-x-1/2 -translate-y-1/2 overflow-auto rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-6 panel-blur scroll-slim",
57
+ "fixed left-1/2 top-1/2 z-(--z-modal) max-h-[85vh] w-[min(92vw,32rem)] -translate-x-1/2 -translate-y-1/2 overflow-auto rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-6 shadow-(--panel-lift) panel-blur scroll-slim",
58
58
  "data-[state=open]:animate-[m22-panel-in_var(--duration-base)_var(--ease)]",
59
59
  // Against the container's box rather than the viewport's, and capped
60
60
  // by it: 92vw inside a 400px frame is not a cap at all.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Dialog/Dialog.tsx"],"sourcesContent":["'use client'\n\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport { X } from 'lucide-react'\nimport type { ComponentProps, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { useOverlayContainer } from '../../lib/overlay-container'\nimport { DEV, warn } from '../../lib/warn'\n\n/** Radix Dialog root + trigger + close, re-exported as typed passthroughs. */\nexport const Dialog = DialogPrimitive.Root\nexport const DialogTrigger = DialogPrimitive.Trigger\nexport const DialogClose = DialogPrimitive.Close\n\n/** Title + description stack; renders nothing when both are omitted. */\nfunction DialogHeader({\n title,\n description,\n hidden,\n}: {\n title?: ReactNode\n description?: ReactNode\n hidden: boolean\n}) {\n if (title === undefined && description === undefined) return null\n return (\n <div className={cn('mb-4 flex flex-col gap-1 pe-8', hidden && 'sr-only')}>\n {title !== undefined && (\n <DialogPrimitive.Title className=\"m-0 font-heading text-[length:var(--fs-item)] font-normal text-(--ink)\">\n {title}\n </DialogPrimitive.Title>\n )}\n {description !== undefined && (\n <DialogPrimitive.Description className=\"m-0 text-sm leading-relaxed text-(--ink-3-aa)\">\n {description}\n </DialogPrimitive.Description>\n )}\n </div>\n )\n}\n\nexport interface DialogContentProps\n extends Omit<ComponentProps<typeof DialogPrimitive.Content>, 'title' | 'className'> {\n /**\n * Heading text.\n *\n * Pass one even when `hideTitle` is set. Radix requires a title, so omitting\n * it renders a hidden fallback reading the literal word \"Dialog\" — which\n * satisfies an automated accessibility check and announces every unnamed\n * modal in the application as the same thing. Development says so out loud.\n */\n title?: ReactNode\n /** Sub-heading under the title. */\n description?: ReactNode\n className?: string\n /** Show the top-right close control (default true). */\n showClose?: boolean\n /**\n * Keeps the title for assistive tech and hides it visually.\n *\n * For a surface whose purpose is obvious to anyone who can see it — a command\n * palette, a media lightbox — where a printed heading would be furniture. The\n * title itself is never optional: Radix requires one, and a modal with no\n * accessible name drops a screen reader into an unnamed region.\n */\n hideTitle?: boolean\n}\n\n/**\n * A modal surface: portal → scrim → centred panel.\n *\n * Radix owns the focus trap, the escape key, the scroll lock and the\n * `aria-modal` wiring — all of which a hand-rolled dialog gets subtly wrong,\n * usually by leaving focus behind in the page underneath.\n *\n * Radix requires a `Dialog.Title` whether or not one is shown, so a dialog\n * without a visible heading still renders a hidden one rather than shipping an\n * unnamed modal — and warns in development, because the fallback it renders is\n * the literal word \"Dialog\" and a placeholder that passes an accessibility\n * check is how the problem survives a review.\n *\n * Portals into the element an enclosing `OverlayContainer` names, and switches\n * from viewport positioning to container positioning when there is one. A\n * `fixed` panel covers the page whatever it is portalled into, so honouring\n * the container without that swap would have moved the markup and left the\n * picture unchanged.\n *\n * @example\n * <Dialog>\n * <DialogTrigger asChild><Button>Delete</Button></DialogTrigger>\n * <DialogContent title=\"Delete file\" description=\"This cannot be undone.\">\n * <DialogClose asChild><Button variant=\"secondary\">Cancel</Button></DialogClose>\n * </DialogContent>\n * </Dialog>\n */\nexport function DialogContent({\n title,\n description,\n children,\n className,\n showClose = true,\n hideTitle = false,\n ...rest\n}: DialogContentProps) {\n const container = useOverlayContainer()\n\n if (DEV && title === undefined) {\n warn({\n code: 'DIALOG_TITLE_MISSING',\n problem:\n 'DialogContent has no title, so its accessible name is the fallback string \"Dialog\". Every unnamed modal in the app announces identically, and the check that would have caught it passes.',\n field: 'title',\n fix: 'Give it a title that says what the dialog asks. Add hideTitle when the heading would be furniture on screen.',\n component: 'Dialog',\n })\n }\n\n return (\n <DialogPrimitive.Portal container={container ?? undefined}>\n <DialogPrimitive.Overlay\n data-m22-animated\n className={cn(\n 'inset-0 z-(--z-overlay) bg-(--scrim) data-[state=open]:animate-[m22-fade-in_var(--duration-fast)_var(--ease)]',\n container ? 'absolute' : 'fixed',\n )}\n />\n <DialogPrimitive.Content\n data-m22-animated\n className={cn(\n 'fixed left-1/2 top-1/2 z-(--z-modal) max-h-[85vh] w-[min(92vw,32rem)] -translate-x-1/2 -translate-y-1/2 overflow-auto rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-6 panel-blur scroll-slim',\n 'data-[state=open]:animate-[m22-panel-in_var(--duration-base)_var(--ease)]',\n // Against the container's box rather than the viewport's, and capped\n // by it: 92vw inside a 400px frame is not a cap at all.\n container && 'absolute max-h-[calc(100%-2rem)] max-w-[calc(100%-2rem)]',\n className,\n )}\n {...rest}\n >\n <DialogHeader title={title} description={description} hidden={hideTitle} />\n\n {title === undefined && (\n <DialogPrimitive.Title className=\"sr-only\">Dialog</DialogPrimitive.Title>\n )}\n\n {showClose && (\n <DialogPrimitive.Close\n aria-label=\"Close\"\n className=\"absolute end-3 top-3 grid size-9 place-items-center rounded-(--radius-pill) text-(--ink-3-aa) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)\"\n >\n <X size={16} strokeWidth={1.5} aria-hidden />\n </DialogPrimitive.Close>\n )}\n\n {children}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n )\n}\n\nexport default Dialog\n"],"mappings":";AA0BI,SAEI,KAFJ;AAxBJ,YAAY,qBAAqB;AACjC,SAAS,SAAS;AAElB,SAAS,UAAU;AACnB,SAAS,2BAA2B;AACpC,SAAS,KAAK,YAAY;AAGnB,MAAM,SAAS,gBAAgB;AAC/B,MAAM,gBAAgB,gBAAgB;AACtC,MAAM,cAAc,gBAAgB;AAG3C,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,MAAI,UAAU,UAAa,gBAAgB,OAAW,QAAO;AAC7D,SACE,qBAAC,SAAI,WAAW,GAAG,iCAAiC,UAAU,SAAS,GACpE;AAAA,cAAU,UACT,oBAAC,gBAAgB,OAAhB,EAAsB,WAAU,0EAC9B,iBACH;AAAA,IAED,gBAAgB,UACf,oBAAC,gBAAgB,aAAhB,EAA4B,WAAU,iDACpC,uBACH;AAAA,KAEJ;AAEJ;AAwDO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,GAAG;AACL,GAAuB;AACrB,QAAM,YAAY,oBAAoB;AAEtC,MAAI,OAAO,UAAU,QAAW;AAC9B,SAAK;AAAA,MACH,MAAM;AAAA,MACN,SACE;AAAA,MACF,OAAO;AAAA,MACP,KAAK;AAAA,MACL,WAAW;AAAA,IACb,CAAC;AAAA,EACH;AAEA,SACE,qBAAC,gBAAgB,QAAhB,EAAuB,WAAW,aAAa,QAC9C;AAAA;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC,qBAAiB;AAAA,QACjB,WAAW;AAAA,UACT;AAAA,UACA,YAAY,aAAa;AAAA,QAC3B;AAAA;AAAA,IACF;AAAA,IACA;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC,qBAAiB;AAAA,QACjB,WAAW;AAAA,UACT;AAAA,UACA;AAAA;AAAA;AAAA,UAGA,aAAa;AAAA,UACb;AAAA,QACF;AAAA,QACC,GAAG;AAAA,QAEJ;AAAA,8BAAC,gBAAa,OAAc,aAA0B,QAAQ,WAAW;AAAA,UAExE,UAAU,UACT,oBAAC,gBAAgB,OAAhB,EAAsB,WAAU,WAAU,oBAAM;AAAA,UAGlD,aACC;AAAA,YAAC,gBAAgB;AAAA,YAAhB;AAAA,cACC,cAAW;AAAA,cACX,WAAU;AAAA,cAEV,8BAAC,KAAE,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC;AAAA;AAAA,UAC7C;AAAA,UAGD;AAAA;AAAA;AAAA,IACH;AAAA,KACF;AAEJ;AAEA,IAAO,iBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/Dialog/Dialog.tsx"],"sourcesContent":["'use client'\n\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport { X } from 'lucide-react'\nimport type { ComponentProps, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { useOverlayContainer } from '../../lib/overlay-container'\nimport { DEV, warn } from '../../lib/warn'\n\n/** Radix Dialog root + trigger + close, re-exported as typed passthroughs. */\nexport const Dialog = DialogPrimitive.Root\nexport const DialogTrigger = DialogPrimitive.Trigger\nexport const DialogClose = DialogPrimitive.Close\n\n/** Title + description stack; renders nothing when both are omitted. */\nfunction DialogHeader({\n title,\n description,\n hidden,\n}: {\n title?: ReactNode\n description?: ReactNode\n hidden: boolean\n}) {\n if (title === undefined && description === undefined) return null\n return (\n <div className={cn('mb-4 flex flex-col gap-1 pe-8', hidden && 'sr-only')}>\n {title !== undefined && (\n <DialogPrimitive.Title className=\"m-0 font-heading text-[length:var(--fs-item)] font-normal text-(--ink)\">\n {title}\n </DialogPrimitive.Title>\n )}\n {description !== undefined && (\n <DialogPrimitive.Description className=\"m-0 text-sm leading-relaxed text-(--ink-3-aa)\">\n {description}\n </DialogPrimitive.Description>\n )}\n </div>\n )\n}\n\nexport interface DialogContentProps\n extends Omit<ComponentProps<typeof DialogPrimitive.Content>, 'title' | 'className'> {\n /**\n * Heading text.\n *\n * Pass one even when `hideTitle` is set. Radix requires a title, so omitting\n * it renders a hidden fallback reading the literal word \"Dialog\" — which\n * satisfies an automated accessibility check and announces every unnamed\n * modal in the application as the same thing. Development says so out loud.\n */\n title?: ReactNode\n /** Sub-heading under the title. */\n description?: ReactNode\n className?: string\n /** Show the top-right close control (default true). */\n showClose?: boolean\n /**\n * Keeps the title for assistive tech and hides it visually.\n *\n * For a surface whose purpose is obvious to anyone who can see it — a command\n * palette, a media lightbox — where a printed heading would be furniture. The\n * title itself is never optional: Radix requires one, and a modal with no\n * accessible name drops a screen reader into an unnamed region.\n */\n hideTitle?: boolean\n}\n\n/**\n * A modal surface: portal → scrim → centred panel.\n *\n * Radix owns the focus trap, the escape key, the scroll lock and the\n * `aria-modal` wiring — all of which a hand-rolled dialog gets subtly wrong,\n * usually by leaving focus behind in the page underneath.\n *\n * Radix requires a `Dialog.Title` whether or not one is shown, so a dialog\n * without a visible heading still renders a hidden one rather than shipping an\n * unnamed modal — and warns in development, because the fallback it renders is\n * the literal word \"Dialog\" and a placeholder that passes an accessibility\n * check is how the problem survives a review.\n *\n * Portals into the element an enclosing `OverlayContainer` names, and switches\n * from viewport positioning to container positioning when there is one. A\n * `fixed` panel covers the page whatever it is portalled into, so honouring\n * the container without that swap would have moved the markup and left the\n * picture unchanged.\n *\n * @example\n * <Dialog>\n * <DialogTrigger asChild><Button>Delete</Button></DialogTrigger>\n * <DialogContent title=\"Delete file\" description=\"This cannot be undone.\">\n * <DialogClose asChild><Button variant=\"secondary\">Cancel</Button></DialogClose>\n * </DialogContent>\n * </Dialog>\n */\nexport function DialogContent({\n title,\n description,\n children,\n className,\n showClose = true,\n hideTitle = false,\n ...rest\n}: DialogContentProps) {\n const container = useOverlayContainer()\n\n if (DEV && title === undefined) {\n warn({\n code: 'DIALOG_TITLE_MISSING',\n problem:\n 'DialogContent has no title, so its accessible name is the fallback string \"Dialog\". Every unnamed modal in the app announces identically, and the check that would have caught it passes.',\n field: 'title',\n fix: 'Give it a title that says what the dialog asks. Add hideTitle when the heading would be furniture on screen.',\n component: 'Dialog',\n })\n }\n\n return (\n <DialogPrimitive.Portal container={container ?? undefined}>\n <DialogPrimitive.Overlay\n data-m22-animated\n className={cn(\n 'inset-0 z-(--z-overlay) bg-(--scrim) data-[state=open]:animate-[m22-fade-in_var(--duration-fast)_var(--ease)]',\n container ? 'absolute' : 'fixed',\n )}\n />\n <DialogPrimitive.Content\n data-m22-animated\n className={cn(\n 'fixed left-1/2 top-1/2 z-(--z-modal) max-h-[85vh] w-[min(92vw,32rem)] -translate-x-1/2 -translate-y-1/2 overflow-auto rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-6 shadow-(--panel-lift) panel-blur scroll-slim',\n 'data-[state=open]:animate-[m22-panel-in_var(--duration-base)_var(--ease)]',\n // Against the container's box rather than the viewport's, and capped\n // by it: 92vw inside a 400px frame is not a cap at all.\n container && 'absolute max-h-[calc(100%-2rem)] max-w-[calc(100%-2rem)]',\n className,\n )}\n {...rest}\n >\n <DialogHeader title={title} description={description} hidden={hideTitle} />\n\n {title === undefined && (\n <DialogPrimitive.Title className=\"sr-only\">Dialog</DialogPrimitive.Title>\n )}\n\n {showClose && (\n <DialogPrimitive.Close\n aria-label=\"Close\"\n className=\"absolute end-3 top-3 grid size-9 place-items-center rounded-(--radius-pill) text-(--ink-3-aa) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)\"\n >\n <X size={16} strokeWidth={1.5} aria-hidden />\n </DialogPrimitive.Close>\n )}\n\n {children}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n )\n}\n\nexport default Dialog\n"],"mappings":";AA0BI,SAEI,KAFJ;AAxBJ,YAAY,qBAAqB;AACjC,SAAS,SAAS;AAElB,SAAS,UAAU;AACnB,SAAS,2BAA2B;AACpC,SAAS,KAAK,YAAY;AAGnB,MAAM,SAAS,gBAAgB;AAC/B,MAAM,gBAAgB,gBAAgB;AACtC,MAAM,cAAc,gBAAgB;AAG3C,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,MAAI,UAAU,UAAa,gBAAgB,OAAW,QAAO;AAC7D,SACE,qBAAC,SAAI,WAAW,GAAG,iCAAiC,UAAU,SAAS,GACpE;AAAA,cAAU,UACT,oBAAC,gBAAgB,OAAhB,EAAsB,WAAU,0EAC9B,iBACH;AAAA,IAED,gBAAgB,UACf,oBAAC,gBAAgB,aAAhB,EAA4B,WAAU,iDACpC,uBACH;AAAA,KAEJ;AAEJ;AAwDO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,GAAG;AACL,GAAuB;AACrB,QAAM,YAAY,oBAAoB;AAEtC,MAAI,OAAO,UAAU,QAAW;AAC9B,SAAK;AAAA,MACH,MAAM;AAAA,MACN,SACE;AAAA,MACF,OAAO;AAAA,MACP,KAAK;AAAA,MACL,WAAW;AAAA,IACb,CAAC;AAAA,EACH;AAEA,SACE,qBAAC,gBAAgB,QAAhB,EAAuB,WAAW,aAAa,QAC9C;AAAA;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC,qBAAiB;AAAA,QACjB,WAAW;AAAA,UACT;AAAA,UACA,YAAY,aAAa;AAAA,QAC3B;AAAA;AAAA,IACF;AAAA,IACA;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC,qBAAiB;AAAA,QACjB,WAAW;AAAA,UACT;AAAA,UACA;AAAA;AAAA;AAAA,UAGA,aAAa;AAAA,UACb;AAAA,QACF;AAAA,QACC,GAAG;AAAA,QAEJ;AAAA,8BAAC,gBAAa,OAAc,aAA0B,QAAQ,WAAW;AAAA,UAExE,UAAU,UACT,oBAAC,gBAAgB,OAAhB,EAAsB,WAAU,WAAU,oBAAM;AAAA,UAGlD,aACC;AAAA,YAAC,gBAAgB;AAAA,YAAhB;AAAA,cACC,cAAW;AAAA,cACX,WAAU;AAAA,cAEV,8BAAC,KAAE,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC;AAAA;AAAA,UAC7C;AAAA,UAGD;AAAA;AAAA;AAAA,IACH;AAAA,KACF;AAEJ;AAEA,IAAO,iBAAQ;","names":[]}
@@ -35,7 +35,7 @@ function DropdownMenuContent({
35
35
  sideOffset,
36
36
  "data-m22-animated": true,
37
37
  className: cn(
38
- "z-(--z-dropdown) min-w-44 rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-1.5 panel-blur",
38
+ "z-(--z-dropdown) min-w-44 rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-1.5 shadow-(--panel-lift) panel-blur",
39
39
  "data-[state=open]:animate-[m22-pop-in_var(--duration-fast)_var(--ease-out-expo)] data-[state=closed]:animate-[m22-pop-out_var(--duration-fast)_var(--ease)] origin-(--radix-popper-transform-origin)",
40
40
  className
41
41
  ),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/DropdownMenu/DropdownMenu.tsx"],"sourcesContent":["'use client'\n\nimport * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'\nimport type { LucideIcon } from 'lucide-react'\nimport { isValidElement, useId, type ComponentProps, type ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { useOverlayContainer } from '../../lib/overlay-container'\n\n/**\n * True for a component type — `icon={Settings}` — rather than a rendered element.\n *\n * Everything a `ReactNode` can be is an element, a primitive, an array, another\n * iterable or a promise; a Lucide icon is none of those, it is a `forwardRef`\n * object. So both spellings are tellable apart at runtime, which is what lets\n * one prop take either — and it has to, because this prop meant the opposite\n * thing one import away and the wrong spelling failed at render, not at the\n * type.\n */\nfunction isIconComponent(icon: LucideIcon | ReactNode): icon is LucideIcon {\n if (isValidElement(icon)) return false\n if (typeof icon === 'function') return true\n if (typeof icon !== 'object' || icon === null) return false\n return !Array.isArray(icon) && !(Symbol.iterator in icon) && !('then' in icon)\n}\n\n/** The leading glyph, sized by this component when it was handed a component. */\nfunction iconNode(icon: LucideIcon | ReactNode): ReactNode {\n if (icon === undefined || icon === null || icon === false) return null\n if (!isIconComponent(icon)) {\n return (\n <span aria-hidden className=\"grid size-4 shrink-0 place-items-center [&_svg]:size-4\">\n {icon}\n </span>\n )\n }\n const Icon = icon\n return <Icon size={16} strokeWidth={1.5} className=\"shrink-0\" aria-hidden />\n}\n\n/** Radix DropdownMenu root + trigger, re-exported as typed passthroughs. */\nexport const DropdownMenu = DropdownMenuPrimitive.Root\nexport const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger\n\nexport type DropdownMenuContentProps = ComponentProps<typeof DropdownMenuPrimitive.Content>\n\n/**\n * A menu surface.\n *\n * A menu is a list of ACTIONS. If the items navigate somewhere, they belong in\n * a nav; if they set a value, that is a `Select` or a `RadioGroup` — this\n * package ships no checkbox or radio menu item, and a plain item pretending to\n * be a choice loses the checked state a screen reader needs.\n *\n * @example\n * <DropdownMenu>\n * <DropdownMenuTrigger asChild><Button variant=\"secondary\">Menu</Button></DropdownMenuTrigger>\n * <DropdownMenuContent>\n * <DropdownMenuGroup label=\"Account\">\n * <DropdownMenuItem icon={Settings}>Settings</DropdownMenuItem>\n * </DropdownMenuGroup>\n * <DropdownMenuSeparator />\n * <DropdownMenuItem icon={LogOut}>Sign out</DropdownMenuItem>\n * </DropdownMenuContent>\n * </DropdownMenu>\n */\nexport function DropdownMenuContent({\n className,\n sideOffset = 6,\n children,\n ...rest\n}: DropdownMenuContentProps) {\n const container = useOverlayContainer()\n\n return (\n <DropdownMenuPrimitive.Portal container={container ?? undefined}>\n <DropdownMenuPrimitive.Content\n collisionBoundary={container ?? undefined}\n collisionPadding={8}\n sideOffset={sideOffset}\n data-m22-animated\n className={cn(\n 'z-(--z-dropdown) min-w-44 rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-1.5 panel-blur',\n 'data-[state=open]:animate-[m22-pop-in_var(--duration-fast)_var(--ease-out-expo)] data-[state=closed]:animate-[m22-pop-out_var(--duration-fast)_var(--ease)] origin-(--radix-popper-transform-origin)',\n className,\n )}\n {...rest}\n >\n {children}\n </DropdownMenuPrimitive.Content>\n </DropdownMenuPrimitive.Portal>\n )\n}\n\nexport interface DropdownMenuItemProps extends ComponentProps<typeof DropdownMenuPrimitive.Item> {\n /**\n * Optional leading icon, rendered before the label.\n *\n * Either spelling: `icon={Settings}` passes the component and this sizes it,\n * `icon={<Settings size={16} />}` passes the element and this places it. The\n * two used to mean opposite things one import apart — `CommandItem.icon` took\n * the element while this took the component — and the wrong one did not fail\n * a type check into anything actionable, it failed at render.\n */\n icon?: LucideIcon | ReactNode\n /** Paints the row as destructive. Use for delete, revoke, disconnect. */\n destructive?: boolean\n}\n\n/**\n * A menu row.\n *\n * Highlight is driven by Radix's `data-highlighted`, which covers both pointer\n * hover and keyboard focus — styling `:hover` alone leaves the keyboard user\n * unable to see where they are.\n */\nexport function DropdownMenuItem({\n icon,\n destructive = false,\n className,\n children,\n ...rest\n}: DropdownMenuItemProps) {\n return (\n <DropdownMenuPrimitive.Item\n className={cn(\n 'flex cursor-pointer items-center gap-2.5 rounded-(--radius-row) px-2.5 py-2 text-sm outline-none transition-colors duration-(--duration-fast) data-[highlighted]:bg-(--stone) data-[disabled]:pointer-events-none data-[disabled]:opacity-(--disabled-opacity)',\n destructive ? 'text-(--danger)' : 'text-(--ink-2) data-[highlighted]:text-(--ink)',\n className,\n )}\n {...rest}\n >\n {iconNode(icon)}\n {children}\n </DropdownMenuPrimitive.Item>\n )\n}\n\n/** Hairline divider between menu groups. */\nexport function DropdownMenuSeparator({\n className,\n ...rest\n}: ComponentProps<typeof DropdownMenuPrimitive.Separator>) {\n return (\n <DropdownMenuPrimitive.Separator\n className={cn('my-1 h-px bg-(--rule)', className)}\n {...rest}\n />\n )\n}\n\n/**\n * Mono eyebrow heading, on its own.\n *\n * Visual only: Radix renders it as a bare `<div>` with no role, so it segments\n * the menu for a reader who can see it and for nobody else. Reach for\n * `DropdownMenuGroup` when the eyebrow is a HEADING over rows; this is right\n * for a line that heads nothing — the signed-in address at the top of an\n * account menu.\n */\nexport function DropdownMenuLabel({\n className,\n ...rest\n}: ComponentProps<typeof DropdownMenuPrimitive.Label>) {\n return (\n <DropdownMenuPrimitive.Label\n className={cn('px-2.5 py-1.5 eyebrow text-(--ink-3-aa)', className)}\n {...rest}\n />\n )\n}\n\nexport interface DropdownMenuGroupProps\n extends ComponentProps<typeof DropdownMenuPrimitive.Group> {\n /** The eyebrow over the rows, and the group's accessible name. */\n label?: ReactNode\n}\n\n/**\n * A named section of a menu.\n *\n * The eyebrow alone was a picture of a heading. Radix's `MenuLabel` carries no\n * role and no `aria-labelledby` wiring, and `MenuGroup` — which does carry\n * `role=\"group\"` — was not re-exported by this package at all, so a sighted\n * reader saw three labelled sections and a screen-reader user got one\n * undifferentiated list. This renders the group, renders the label inside it,\n * and points the one at the other, which is the whole of the fix and not\n * something a caller should have to remember.\n *\n * @example\n * <DropdownMenuGroup label=\"Account\">\n * <DropdownMenuItem icon={Settings}>Settings</DropdownMenuItem>\n * </DropdownMenuGroup>\n */\nexport function DropdownMenuGroup({ label, children, ...rest }: DropdownMenuGroupProps) {\n const id = useId()\n\n return (\n <DropdownMenuPrimitive.Group\n aria-labelledby={label === undefined ? undefined : id}\n {...rest}\n >\n {label !== undefined && <DropdownMenuLabel id={id}>{label}</DropdownMenuLabel>}\n {children}\n </DropdownMenuPrimitive.Group>\n )\n}\n\nexport default DropdownMenu\n"],"mappings":";AA8BM,cA6FF,YA7FE;AA5BN,YAAY,2BAA2B;AAEvC,SAAS,gBAAgB,aAAkD;AAC3E,SAAS,UAAU;AACnB,SAAS,2BAA2B;AAYpC,SAAS,gBAAgB,MAAkD;AACzE,MAAI,eAAe,IAAI,EAAG,QAAO;AACjC,MAAI,OAAO,SAAS,WAAY,QAAO;AACvC,MAAI,OAAO,SAAS,YAAY,SAAS,KAAM,QAAO;AACtD,SAAO,CAAC,MAAM,QAAQ,IAAI,KAAK,EAAE,OAAO,YAAY,SAAS,EAAE,UAAU;AAC3E;AAGA,SAAS,SAAS,MAAyC;AACzD,MAAI,SAAS,UAAa,SAAS,QAAQ,SAAS,MAAO,QAAO;AAClE,MAAI,CAAC,gBAAgB,IAAI,GAAG;AAC1B,WACE,oBAAC,UAAK,eAAW,MAAC,WAAU,0DACzB,gBACH;AAAA,EAEJ;AACA,QAAM,OAAO;AACb,SAAO,oBAAC,QAAK,MAAM,IAAI,aAAa,KAAK,WAAU,YAAW,eAAW,MAAC;AAC5E;AAGO,MAAM,eAAe,sBAAsB;AAC3C,MAAM,sBAAsB,sBAAsB;AAwBlD,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EACA,GAAG;AACL,GAA6B;AAC3B,QAAM,YAAY,oBAAoB;AAEtC,SACE,oBAAC,sBAAsB,QAAtB,EAA6B,WAAW,aAAa,QACpD;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC,mBAAmB,aAAa;AAAA,MAChC,kBAAkB;AAAA,MAClB;AAAA,MACA,qBAAiB;AAAA,MACjB,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,EACH,GACF;AAEJ;AAwBO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAA0B;AACxB,SACE;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,cAAc,oBAAoB;AAAA,QAClC;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA,iBAAS,IAAI;AAAA,QACb;AAAA;AAAA;AAAA,EACH;AAEJ;AAGO,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA,GAAG;AACL,GAA2D;AACzD,SACE;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC,WAAW,GAAG,yBAAyB,SAAS;AAAA,MAC/C,GAAG;AAAA;AAAA,EACN;AAEJ;AAWO,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA,GAAG;AACL,GAAuD;AACrD,SACE;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC,WAAW,GAAG,2CAA2C,SAAS;AAAA,MACjE,GAAG;AAAA;AAAA,EACN;AAEJ;AAwBO,SAAS,kBAAkB,EAAE,OAAO,UAAU,GAAG,KAAK,GAA2B;AACtF,QAAM,KAAK,MAAM;AAEjB,SACE;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC,mBAAiB,UAAU,SAAY,SAAY;AAAA,MAClD,GAAG;AAAA,MAEH;AAAA,kBAAU,UAAa,oBAAC,qBAAkB,IAAS,iBAAM;AAAA,QACzD;AAAA;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,uBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/DropdownMenu/DropdownMenu.tsx"],"sourcesContent":["'use client'\n\nimport * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'\nimport type { LucideIcon } from 'lucide-react'\nimport { isValidElement, useId, type ComponentProps, type ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { useOverlayContainer } from '../../lib/overlay-container'\n\n/**\n * True for a component type — `icon={Settings}` — rather than a rendered element.\n *\n * Everything a `ReactNode` can be is an element, a primitive, an array, another\n * iterable or a promise; a Lucide icon is none of those, it is a `forwardRef`\n * object. So both spellings are tellable apart at runtime, which is what lets\n * one prop take either — and it has to, because this prop meant the opposite\n * thing one import away and the wrong spelling failed at render, not at the\n * type.\n */\nfunction isIconComponent(icon: LucideIcon | ReactNode): icon is LucideIcon {\n if (isValidElement(icon)) return false\n if (typeof icon === 'function') return true\n if (typeof icon !== 'object' || icon === null) return false\n return !Array.isArray(icon) && !(Symbol.iterator in icon) && !('then' in icon)\n}\n\n/** The leading glyph, sized by this component when it was handed a component. */\nfunction iconNode(icon: LucideIcon | ReactNode): ReactNode {\n if (icon === undefined || icon === null || icon === false) return null\n if (!isIconComponent(icon)) {\n return (\n <span aria-hidden className=\"grid size-4 shrink-0 place-items-center [&_svg]:size-4\">\n {icon}\n </span>\n )\n }\n const Icon = icon\n return <Icon size={16} strokeWidth={1.5} className=\"shrink-0\" aria-hidden />\n}\n\n/** Radix DropdownMenu root + trigger, re-exported as typed passthroughs. */\nexport const DropdownMenu = DropdownMenuPrimitive.Root\nexport const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger\n\nexport type DropdownMenuContentProps = ComponentProps<typeof DropdownMenuPrimitive.Content>\n\n/**\n * A menu surface.\n *\n * A menu is a list of ACTIONS. If the items navigate somewhere, they belong in\n * a nav; if they set a value, that is a `Select` or a `RadioGroup` — this\n * package ships no checkbox or radio menu item, and a plain item pretending to\n * be a choice loses the checked state a screen reader needs.\n *\n * @example\n * <DropdownMenu>\n * <DropdownMenuTrigger asChild><Button variant=\"secondary\">Menu</Button></DropdownMenuTrigger>\n * <DropdownMenuContent>\n * <DropdownMenuGroup label=\"Account\">\n * <DropdownMenuItem icon={Settings}>Settings</DropdownMenuItem>\n * </DropdownMenuGroup>\n * <DropdownMenuSeparator />\n * <DropdownMenuItem icon={LogOut}>Sign out</DropdownMenuItem>\n * </DropdownMenuContent>\n * </DropdownMenu>\n */\nexport function DropdownMenuContent({\n className,\n sideOffset = 6,\n children,\n ...rest\n}: DropdownMenuContentProps) {\n const container = useOverlayContainer()\n\n return (\n <DropdownMenuPrimitive.Portal container={container ?? undefined}>\n <DropdownMenuPrimitive.Content\n collisionBoundary={container ?? undefined}\n collisionPadding={8}\n sideOffset={sideOffset}\n data-m22-animated\n className={cn(\n 'z-(--z-dropdown) min-w-44 rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-1.5 shadow-(--panel-lift) panel-blur',\n 'data-[state=open]:animate-[m22-pop-in_var(--duration-fast)_var(--ease-out-expo)] data-[state=closed]:animate-[m22-pop-out_var(--duration-fast)_var(--ease)] origin-(--radix-popper-transform-origin)',\n className,\n )}\n {...rest}\n >\n {children}\n </DropdownMenuPrimitive.Content>\n </DropdownMenuPrimitive.Portal>\n )\n}\n\nexport interface DropdownMenuItemProps extends ComponentProps<typeof DropdownMenuPrimitive.Item> {\n /**\n * Optional leading icon, rendered before the label.\n *\n * Either spelling: `icon={Settings}` passes the component and this sizes it,\n * `icon={<Settings size={16} />}` passes the element and this places it. The\n * two used to mean opposite things one import apart — `CommandItem.icon` took\n * the element while this took the component — and the wrong one did not fail\n * a type check into anything actionable, it failed at render.\n */\n icon?: LucideIcon | ReactNode\n /** Paints the row as destructive. Use for delete, revoke, disconnect. */\n destructive?: boolean\n}\n\n/**\n * A menu row.\n *\n * Highlight is driven by Radix's `data-highlighted`, which covers both pointer\n * hover and keyboard focus — styling `:hover` alone leaves the keyboard user\n * unable to see where they are.\n */\nexport function DropdownMenuItem({\n icon,\n destructive = false,\n className,\n children,\n ...rest\n}: DropdownMenuItemProps) {\n return (\n <DropdownMenuPrimitive.Item\n className={cn(\n 'flex cursor-pointer items-center gap-2.5 rounded-(--radius-row) px-2.5 py-2 text-sm outline-none transition-colors duration-(--duration-fast) data-[highlighted]:bg-(--stone) data-[disabled]:pointer-events-none data-[disabled]:opacity-(--disabled-opacity)',\n destructive ? 'text-(--danger)' : 'text-(--ink-2) data-[highlighted]:text-(--ink)',\n className,\n )}\n {...rest}\n >\n {iconNode(icon)}\n {children}\n </DropdownMenuPrimitive.Item>\n )\n}\n\n/** Hairline divider between menu groups. */\nexport function DropdownMenuSeparator({\n className,\n ...rest\n}: ComponentProps<typeof DropdownMenuPrimitive.Separator>) {\n return (\n <DropdownMenuPrimitive.Separator\n className={cn('my-1 h-px bg-(--rule)', className)}\n {...rest}\n />\n )\n}\n\n/**\n * Mono eyebrow heading, on its own.\n *\n * Visual only: Radix renders it as a bare `<div>` with no role, so it segments\n * the menu for a reader who can see it and for nobody else. Reach for\n * `DropdownMenuGroup` when the eyebrow is a HEADING over rows; this is right\n * for a line that heads nothing — the signed-in address at the top of an\n * account menu.\n */\nexport function DropdownMenuLabel({\n className,\n ...rest\n}: ComponentProps<typeof DropdownMenuPrimitive.Label>) {\n return (\n <DropdownMenuPrimitive.Label\n className={cn('px-2.5 py-1.5 eyebrow text-(--ink-3-aa)', className)}\n {...rest}\n />\n )\n}\n\nexport interface DropdownMenuGroupProps\n extends ComponentProps<typeof DropdownMenuPrimitive.Group> {\n /** The eyebrow over the rows, and the group's accessible name. */\n label?: ReactNode\n}\n\n/**\n * A named section of a menu.\n *\n * The eyebrow alone was a picture of a heading. Radix's `MenuLabel` carries no\n * role and no `aria-labelledby` wiring, and `MenuGroup` — which does carry\n * `role=\"group\"` — was not re-exported by this package at all, so a sighted\n * reader saw three labelled sections and a screen-reader user got one\n * undifferentiated list. This renders the group, renders the label inside it,\n * and points the one at the other, which is the whole of the fix and not\n * something a caller should have to remember.\n *\n * @example\n * <DropdownMenuGroup label=\"Account\">\n * <DropdownMenuItem icon={Settings}>Settings</DropdownMenuItem>\n * </DropdownMenuGroup>\n */\nexport function DropdownMenuGroup({ label, children, ...rest }: DropdownMenuGroupProps) {\n const id = useId()\n\n return (\n <DropdownMenuPrimitive.Group\n aria-labelledby={label === undefined ? undefined : id}\n {...rest}\n >\n {label !== undefined && <DropdownMenuLabel id={id}>{label}</DropdownMenuLabel>}\n {children}\n </DropdownMenuPrimitive.Group>\n )\n}\n\nexport default DropdownMenu\n"],"mappings":";AA8BM,cA6FF,YA7FE;AA5BN,YAAY,2BAA2B;AAEvC,SAAS,gBAAgB,aAAkD;AAC3E,SAAS,UAAU;AACnB,SAAS,2BAA2B;AAYpC,SAAS,gBAAgB,MAAkD;AACzE,MAAI,eAAe,IAAI,EAAG,QAAO;AACjC,MAAI,OAAO,SAAS,WAAY,QAAO;AACvC,MAAI,OAAO,SAAS,YAAY,SAAS,KAAM,QAAO;AACtD,SAAO,CAAC,MAAM,QAAQ,IAAI,KAAK,EAAE,OAAO,YAAY,SAAS,EAAE,UAAU;AAC3E;AAGA,SAAS,SAAS,MAAyC;AACzD,MAAI,SAAS,UAAa,SAAS,QAAQ,SAAS,MAAO,QAAO;AAClE,MAAI,CAAC,gBAAgB,IAAI,GAAG;AAC1B,WACE,oBAAC,UAAK,eAAW,MAAC,WAAU,0DACzB,gBACH;AAAA,EAEJ;AACA,QAAM,OAAO;AACb,SAAO,oBAAC,QAAK,MAAM,IAAI,aAAa,KAAK,WAAU,YAAW,eAAW,MAAC;AAC5E;AAGO,MAAM,eAAe,sBAAsB;AAC3C,MAAM,sBAAsB,sBAAsB;AAwBlD,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EACA,GAAG;AACL,GAA6B;AAC3B,QAAM,YAAY,oBAAoB;AAEtC,SACE,oBAAC,sBAAsB,QAAtB,EAA6B,WAAW,aAAa,QACpD;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC,mBAAmB,aAAa;AAAA,MAChC,kBAAkB;AAAA,MAClB;AAAA,MACA,qBAAiB;AAAA,MACjB,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,EACH,GACF;AAEJ;AAwBO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAA0B;AACxB,SACE;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,cAAc,oBAAoB;AAAA,QAClC;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA,iBAAS,IAAI;AAAA,QACb;AAAA;AAAA;AAAA,EACH;AAEJ;AAGO,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA,GAAG;AACL,GAA2D;AACzD,SACE;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC,WAAW,GAAG,yBAAyB,SAAS;AAAA,MAC/C,GAAG;AAAA;AAAA,EACN;AAEJ;AAWO,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA,GAAG;AACL,GAAuD;AACrD,SACE;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC,WAAW,GAAG,2CAA2C,SAAS;AAAA,MACjE,GAAG;AAAA;AAAA,EACN;AAEJ;AAwBO,SAAS,kBAAkB,EAAE,OAAO,UAAU,GAAG,KAAK,GAA2B;AACtF,QAAM,KAAK,MAAM;AAEjB,SACE;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC,mBAAiB,UAAU,SAAY,SAAY;AAAA,MAClD,GAAG;AAAA,MAEH;AAAA,kBAAU,UAAa,oBAAC,qBAAkB,IAAS,iBAAM;AAAA,QACzD;AAAA;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,uBAAQ;","names":[]}
@@ -21,7 +21,7 @@ function ErrorState({
21
21
  "p",
22
22
  {
23
23
  "aria-hidden": "true",
24
- className: "m-0 font-heading text-[length:var(--fs-title)] leading-none text-(--ink)",
24
+ className: "m-0 font-heading text-[length:var(--fs-title)] leading-tight text-(--ink)",
25
25
  children: code
26
26
  }
27
27
  ),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ErrorState/ErrorState.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react'\nimport { Heading, type HeadingLevel } from '../Heading/Heading'\nimport { cn } from '../../lib/cn'\n\nexport interface ErrorStateProps extends HTMLAttributes<HTMLElement> {\n /** The status, set large — \"404\", \"500\". Decorative; the heading carries the meaning. */\n code: ReactNode\n heading: ReactNode\n message: ReactNode\n /** The way back. Render it with {@link ERROR_ACTION_CLASS}. */\n action: ReactNode\n /**\n * The heading level `heading` opens, in the document the error state lands in.\n *\n * Defaults to `1`, and that is not a compromise: this component replaces the\n * page rather than sitting inside one — its own ground, its own viewport, its\n * own top clearance — so the page's single `h1` is the one it renders. Kept\n * as the default, existing call sites render exactly the markup they did.\n *\n * The prop exists because the level was previously fixed, and the one piece\n * of advice this component's documentation gives about headings — do not put\n * it inside a shell that already has an `h1` — was advice a caller had no way\n * to take. Now they do: inside a shell that owns the page heading, pass `2`.\n *\n * The size does not follow the level. `heading` renders at `--fs-heading` at\n * every level; a failure demoted in the outline is the same failure.\n */\n level?: HeadingLevel\n}\n\n/**\n * The recovery affordance's look, exported so every error page offers the same\n * one rather than each inventing a link style.\n */\nexport const ERROR_ACTION_CLASS =\n 'text-sm text-(--ink) underline decoration-(--rule-2) underline-offset-4 transition-colors duration-(--duration-fast) hover:decoration-(--ink)'\n\n/**\n * A page that could not be shown.\n *\n * The code is set at the top of the type ladder and marked `aria-hidden` — read\n * aloud, \"404\" before the sentence explaining it is noise, and the heading\n * immediately after says the same thing in words.\n *\n * @example\n * <ErrorState\n * code=\"404\"\n * heading=\"Page not found\"\n * message=\"The page you're looking for has moved, or never existed.\"\n * action={<a href=\"/\" className={ERROR_ACTION_CLASS}>Back home</a>}\n * />\n * @example\n * // Inside a shell that already owns the page's h1.\n * <ErrorState level={2} code=\"503\" heading=\"Invoices could not be loaded\" … />\n */\nexport function ErrorState({\n code,\n heading,\n message,\n action,\n level = 1,\n className,\n ...rest\n}: ErrorStateProps) {\n return (\n <section\n className={cn('flex min-h-svh flex-col justify-center bg-(--paper) pt-24', className)}\n {...rest}\n >\n <div className=\"mx-auto w-full max-w-(--w-page) px-(--page-pad)\">\n <p\n aria-hidden=\"true\"\n className=\"m-0 font-heading text-[length:var(--fs-title)] leading-none text-(--ink)\"\n >\n {code}\n </p>\n <Heading level={level} size=\"heading\" className=\"mb-4 mt-6\">\n {heading}\n </Heading>\n <p className=\"mb-10 max-w-(--measure-record) text-base leading-relaxed text-(--ink-3-aa)\">\n {message}\n </p>\n {action}\n </div>\n </section>\n )\n}\n\nexport default ErrorState\n"],"mappings":"AAqEM,SACE,KADF;AApEN,SAAS,eAAkC;AAC3C,SAAS,UAAU;AAgCZ,MAAM,qBACX;AAoBK,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,GAAG;AACL,GAAoB;AAClB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,6DAA6D,SAAS;AAAA,MACnF,GAAG;AAAA,MAEJ,+BAAC,SAAI,WAAU,mDACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,eAAY;AAAA,YACZ,WAAU;AAAA,YAET;AAAA;AAAA,QACH;AAAA,QACA,oBAAC,WAAQ,OAAc,MAAK,WAAU,WAAU,aAC7C,mBACH;AAAA,QACA,oBAAC,OAAE,WAAU,8EACV,mBACH;AAAA,QACC;AAAA,SACH;AAAA;AAAA,EACF;AAEJ;AAEA,IAAO,qBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/ErrorState/ErrorState.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react'\nimport { Heading, type HeadingLevel } from '../Heading/Heading'\nimport { cn } from '../../lib/cn'\n\nexport interface ErrorStateProps extends HTMLAttributes<HTMLElement> {\n /** The status, set large — \"404\", \"500\". Decorative; the heading carries the meaning. */\n code: ReactNode\n heading: ReactNode\n message: ReactNode\n /** The way back. Render it with {@link ERROR_ACTION_CLASS}. */\n action: ReactNode\n /**\n * The heading level `heading` opens, in the document the error state lands in.\n *\n * Defaults to `1`, and that is not a compromise: this component replaces the\n * page rather than sitting inside one — its own ground, its own viewport, its\n * own top clearance — so the page's single `h1` is the one it renders. Kept\n * as the default, existing call sites render exactly the markup they did.\n *\n * The prop exists because the level was previously fixed, and the one piece\n * of advice this component's documentation gives about headings — do not put\n * it inside a shell that already has an `h1` — was advice a caller had no way\n * to take. Now they do: inside a shell that owns the page heading, pass `2`.\n *\n * The size does not follow the level. `heading` renders at `--fs-heading` at\n * every level; a failure demoted in the outline is the same failure.\n */\n level?: HeadingLevel\n}\n\n/**\n * The recovery affordance's look, exported so every error page offers the same\n * one rather than each inventing a link style.\n */\nexport const ERROR_ACTION_CLASS =\n 'text-sm text-(--ink) underline decoration-(--rule-2) underline-offset-4 transition-colors duration-(--duration-fast) hover:decoration-(--ink)'\n\n/**\n * A page that could not be shown.\n *\n * The code is set at the top of the type ladder and marked `aria-hidden` — read\n * aloud, \"404\" before the sentence explaining it is noise, and the heading\n * immediately after says the same thing in words.\n *\n * @example\n * <ErrorState\n * code=\"404\"\n * heading=\"Page not found\"\n * message=\"The page you're looking for has moved, or never existed.\"\n * action={<a href=\"/\" className={ERROR_ACTION_CLASS}>Back home</a>}\n * />\n * @example\n * // Inside a shell that already owns the page's h1.\n * <ErrorState level={2} code=\"503\" heading=\"Invoices could not be loaded\" … />\n */\nexport function ErrorState({\n code,\n heading,\n message,\n action,\n level = 1,\n className,\n ...rest\n}: ErrorStateProps) {\n return (\n <section\n className={cn('flex min-h-svh flex-col justify-center bg-(--paper) pt-24', className)}\n {...rest}\n >\n <div className=\"mx-auto w-full max-w-(--w-page) px-(--page-pad)\">\n {/* `leading-tight`, not `leading-none`. At the title step this face\n draws about 62px of ink, and a line box of exactly the font size is\n 47 — so the figures overflowed their own box by seven pixels at the\n top and seven at the bottom, pressing up against the eyebrow above\n and eating half the gap to the heading below. The box that says\n nothing is spaced from is the box the ink is actually in. */}\n <p\n aria-hidden=\"true\"\n className=\"m-0 font-heading text-[length:var(--fs-title)] leading-tight text-(--ink)\"\n >\n {code}\n </p>\n <Heading level={level} size=\"heading\" className=\"mb-4 mt-6\">\n {heading}\n </Heading>\n <p className=\"mb-10 max-w-(--measure-record) text-base leading-relaxed text-(--ink-3-aa)\">\n {message}\n </p>\n {action}\n </div>\n </section>\n )\n}\n\nexport default ErrorState\n"],"mappings":"AAqEM,SAOE,KAPF;AApEN,SAAS,eAAkC;AAC3C,SAAS,UAAU;AAgCZ,MAAM,qBACX;AAoBK,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,GAAG;AACL,GAAoB;AAClB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,6DAA6D,SAAS;AAAA,MACnF,GAAG;AAAA,MAEJ,+BAAC,SAAI,WAAU,mDAOb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,eAAY;AAAA,YACZ,WAAU;AAAA,YAET;AAAA;AAAA,QACH;AAAA,QACA,oBAAC,WAAQ,OAAc,MAAK,WAAU,WAAU,aAC7C,mBACH;AAAA,QACA,oBAAC,OAAE,WAAU,8EACV,mBACH;AAAA,QACC;AAAA,SACH;AAAA;AAAA,EACF;AAEJ;AAEA,IAAO,qBAAQ;","names":[]}
@@ -11,7 +11,7 @@ function NavItem({
11
11
  ...rest
12
12
  }) {
13
13
  const cls = cn(
14
- "flex min-h-(--control-h-sm) items-center gap-3 rounded-(--radius) px-3 py-2 text-sm transition-colors duration-(--duration-fast)",
14
+ "flex min-h-(--control-h-sm) items-center gap-2.5 rounded-(--radius-sm) px-2.5 py-1.5 text-sm transition-colors duration-(--duration-fast)",
15
15
  active ? "bg-(--stone) font-medium text-(--ink)" : "text-(--ink-3-aa) hover:bg-(--stone) hover:text-(--ink)",
16
16
  className
17
17
  );
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/NavItem/NavItem.tsx"],"sourcesContent":["import { Slot } from '@radix-ui/react-slot'\nimport type { LucideIcon } from 'lucide-react'\nimport type { AnchorHTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface NavItemProps\n extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'className' | 'children'> {\n href: string\n /** Optional leading icon (a lucide-react component, rendered at 18px). */\n icon?: LucideIcon\n /** Marks the current route. Also sets `aria-current=\"page\"`. */\n active?: boolean\n /**\n * Hand the styling to a router's own `Link` instead of a native `<a>`.\n *\n * The slotted child receives the classes and `aria-current`, and becomes the\n * row — so `icon` is not rendered in this mode; put it inside the child.\n */\n asChild?: boolean\n children: ReactNode\n className?: string\n}\n\n/**\n * A row in a sidebar.\n *\n * `aria-current=\"page\"` and not only a colour: the active row has to be\n * identifiable to a reader who cannot see that it is darker. In this system it\n * is also carried by weight and by a filled ground, so it survives monochrome\n * printing and low contrast.\n *\n * Framework-agnostic. `asChild` is how a Next or React Router app gets\n * client-side navigation without this package importing either.\n *\n * @example\n * <NavItem href=\"/components/button\" icon={Square} active>Button</NavItem>\n * @example\n * <NavItem asChild href=\"/work\"><Link href=\"/work\">Work</Link></NavItem>\n */\nexport function NavItem({\n href,\n icon: Icon,\n active = false,\n asChild = false,\n children,\n className,\n ...rest\n}: NavItemProps) {\n const cls = cn(\n 'flex min-h-(--control-h-sm) items-center gap-3 rounded-(--radius) px-3 py-2 text-sm transition-colors duration-(--duration-fast)',\n active\n ? 'bg-(--stone) font-medium text-(--ink)'\n : 'text-(--ink-3-aa) hover:bg-(--stone) hover:text-(--ink)',\n className,\n )\n\n if (asChild) {\n // Slot demands exactly ONE child, and `{Icon && …}` is a second one the\n // moment `icon` is omitted — which is how a whole sidebar of slotted rows\n // failed to render at all. So a slotted row takes its icon inside the child\n // it was handed, and this branch passes `children` through untouched.\n return (\n <Slot aria-current={active ? 'page' : undefined} className={cls} {...rest}>\n {children}\n </Slot>\n )\n }\n\n return (\n <a href={href} aria-current={active ? 'page' : undefined} className={cls} {...rest}>\n {Icon && <Icon size={18} strokeWidth={1.5} aria-hidden className=\"shrink-0\" />}\n {children}\n </a>\n )\n}\n\nexport default NavItem\n"],"mappings":"AA8DM,cAOF,YAPE;AA9DN,SAAS,YAAY;AAGrB,SAAS,UAAU;AAoCZ,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA,MAAM;AAAA,EACN,SAAS;AAAA,EACT,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAiB;AACf,QAAM,MAAM;AAAA,IACV;AAAA,IACA,SACI,0CACA;AAAA,IACJ;AAAA,EACF;AAEA,MAAI,SAAS;AAKX,WACE,oBAAC,QAAK,gBAAc,SAAS,SAAS,QAAW,WAAW,KAAM,GAAG,MAClE,UACH;AAAA,EAEJ;AAEA,SACE,qBAAC,OAAE,MAAY,gBAAc,SAAS,SAAS,QAAW,WAAW,KAAM,GAAG,MAC3E;AAAA,YAAQ,oBAAC,QAAK,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC,WAAU,YAAW;AAAA,IAC3E;AAAA,KACH;AAEJ;AAEA,IAAO,kBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/NavItem/NavItem.tsx"],"sourcesContent":["import { Slot } from '@radix-ui/react-slot'\nimport type { LucideIcon } from 'lucide-react'\nimport type { AnchorHTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface NavItemProps\n extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'className' | 'children'> {\n href: string\n /** Optional leading icon (a lucide-react component, rendered at 18px). */\n icon?: LucideIcon\n /** Marks the current route. Also sets `aria-current=\"page\"`. */\n active?: boolean\n /**\n * Hand the styling to a router's own `Link` instead of a native `<a>`.\n *\n * The slotted child receives the classes and `aria-current`, and becomes the\n * row — so `icon` is not rendered in this mode; put it inside the child.\n */\n asChild?: boolean\n children: ReactNode\n className?: string\n}\n\n/**\n * A row in a sidebar.\n *\n * `aria-current=\"page\"` and not only a colour: the active row has to be\n * identifiable to a reader who cannot see that it is darker. In this system it\n * is also carried by weight and by a filled ground, so it survives monochrome\n * printing and low contrast.\n *\n * Framework-agnostic. `asChild` is how a Next or React Router app gets\n * client-side navigation without this package importing either.\n *\n * @example\n * <NavItem href=\"/components/button\" icon={Square} active>Button</NavItem>\n * @example\n * <NavItem asChild href=\"/work\"><Link href=\"/work\">Work</Link></NavItem>\n */\nexport function NavItem({\n href,\n icon: Icon,\n active = false,\n asChild = false,\n children,\n className,\n ...rest\n}: NavItemProps) {\n const cls = cn(\n 'flex min-h-(--control-h-sm) items-center gap-2.5 rounded-(--radius-sm) px-2.5 py-1.5 text-sm transition-colors duration-(--duration-fast)',\n active\n ? 'bg-(--stone) font-medium text-(--ink)'\n : 'text-(--ink-3-aa) hover:bg-(--stone) hover:text-(--ink)',\n className,\n )\n\n if (asChild) {\n // Slot demands exactly ONE child, and `{Icon && …}` is a second one the\n // moment `icon` is omitted — which is how a whole sidebar of slotted rows\n // failed to render at all. So a slotted row takes its icon inside the child\n // it was handed, and this branch passes `children` through untouched.\n return (\n <Slot aria-current={active ? 'page' : undefined} className={cls} {...rest}>\n {children}\n </Slot>\n )\n }\n\n return (\n <a href={href} aria-current={active ? 'page' : undefined} className={cls} {...rest}>\n {Icon && <Icon size={18} strokeWidth={1.5} aria-hidden className=\"shrink-0\" />}\n {children}\n </a>\n )\n}\n\nexport default NavItem\n"],"mappings":"AA8DM,cAOF,YAPE;AA9DN,SAAS,YAAY;AAGrB,SAAS,UAAU;AAoCZ,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA,MAAM;AAAA,EACN,SAAS;AAAA,EACT,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAiB;AACf,QAAM,MAAM;AAAA,IACV;AAAA,IACA,SACI,0CACA;AAAA,IACJ;AAAA,EACF;AAEA,MAAI,SAAS;AAKX,WACE,oBAAC,QAAK,gBAAc,SAAS,SAAS,QAAW,WAAW,KAAM,GAAG,MAClE,UACH;AAAA,EAEJ;AAEA,SACE,qBAAC,OAAE,MAAY,gBAAc,SAAS,SAAS,QAAW,WAAW,KAAM,GAAG,MAC3E;AAAA,YAAQ,oBAAC,QAAK,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC,WAAU,YAAW;AAAA,IAC3E;AAAA,KACH;AAEJ;AAEA,IAAO,kBAAQ;","names":[]}
@@ -0,0 +1,69 @@
1
+ import * as react from 'react';
2
+ import { InputHTMLAttributes, Ref } from 'react';
3
+
4
+ interface NumberFieldProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'value' | 'defaultValue' | 'min' | 'max' | 'step' | 'type'> {
5
+ value?: number;
6
+ defaultValue?: number;
7
+ /** Fires with the parsed number. Raw `onChange` still fires with the event. */
8
+ onValueChange?: (value: number) => void;
9
+ min?: number;
10
+ max?: number;
11
+ /** The increment for the arrows and for one notch of a scrub. */
12
+ step?: number;
13
+ /**
14
+ * A unit drawn inside the end of the field — `%`, `px`, `ms`.
15
+ *
16
+ * The field reserves a fixed slot for it, so keep it to about four
17
+ * characters; a longer one runs under a long number. It is announced through
18
+ * `aria-describedby` as well as drawn, because a unit nobody hears turns
19
+ * "300" into a number with no dimension.
20
+ */
21
+ unit?: string;
22
+ /**
23
+ * A grip at the start of the field that changes the value as it is dragged.
24
+ *
25
+ * On by default, and the reason to reach for this over `<Input type="number">`:
26
+ * a value that is being TUNED — a duration, a weight, an offset — is found by
27
+ * sweeping through neighbouring values, not by typing candidates one at a
28
+ * time. Shift multiplies the travel by ten.
29
+ *
30
+ * Pointer only, and `aria-hidden` for that reason: the same journey on a
31
+ * keyboard is the arrow keys on the field itself, which the platform already
32
+ * provides. Turn it off where the number is a quantity rather than a setting
33
+ * — a line item's quantity is chosen, not swept to.
34
+ */
35
+ scrub?: boolean;
36
+ /** Paints the resting border with `--danger` and reflects `aria-invalid`. */
37
+ invalid?: boolean;
38
+ ref?: Ref<HTMLInputElement>;
39
+ }
40
+ /**
41
+ * A number, typed or swept to.
42
+ *
43
+ * `Input` with `type="number"` is a box that happens to reject letters. This is
44
+ * the control for a number that has a RANGE and a sensible increment: the
45
+ * arrows step it, the grip sweeps it, and `min`, `max` and `step` are honoured
46
+ * on the way out rather than merely announced.
47
+ *
48
+ * Reach for `Slider` instead when the position along the range is the
49
+ * information — a volume, a confidence, anything a reader judges by where the
50
+ * thumb sits. Reach for this when the digits are. A slider that also has to be
51
+ * exact is a `Slider` with `editable`, not one of these beside it.
52
+ *
53
+ * **Clamping happens when the field is left, not while it is being typed.** A
54
+ * minimum of 10 would otherwise make 50 unreachable: the `5` is clamped up to
55
+ * 10 before the `0` arrives. So `onValueChange` can report a number outside the
56
+ * range mid-keystroke, and the value that settles is always inside it.
57
+ *
58
+ * The native spinner buttons are hidden — they are three different controls in
59
+ * three browsers, and none of them is this system's. The grip replaces them for
60
+ * a pointer; the arrow keys were always the keyboard's answer.
61
+ *
62
+ * @example
63
+ * <Field label="Line height"><NumberField defaultValue={1.5} min={1} max={3} step={0.1} /></Field>
64
+ * @example
65
+ * <Field label="Timeout"><NumberField defaultValue={30} min={0} max={600} unit="s" /></Field>
66
+ */
67
+ declare function NumberField({ value, defaultValue, onValueChange, min, max, step, unit, scrub, invalid, disabled, className, ref, onChange, onFocus, onBlur, onKeyDown, 'aria-describedby': describedBy, 'aria-invalid': ariaInvalid, ...rest }: NumberFieldProps): react.JSX.Element;
68
+
69
+ export { NumberField, type NumberFieldProps, NumberField as default };
@@ -0,0 +1,162 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { MoveHorizontal } from "lucide-react";
4
+ import { useId, useRef, useState } from "react";
5
+ import { cn } from "../../lib/cn.js";
6
+ import { CONTROL_BASE, CONTROL_BORDER, isInvalid } from "../../lib/control.js";
7
+ import { clampToRange, parseNumber, snapToStep } from "../../lib/numeric.js";
8
+ const PX_PER_STEP = 4;
9
+ const COARSE = 10;
10
+ function NumberField({
11
+ value,
12
+ defaultValue,
13
+ onValueChange,
14
+ min = Number.NEGATIVE_INFINITY,
15
+ max = Number.POSITIVE_INFINITY,
16
+ step = 1,
17
+ unit,
18
+ scrub = true,
19
+ invalid,
20
+ disabled,
21
+ className,
22
+ ref,
23
+ onChange,
24
+ onFocus,
25
+ onBlur,
26
+ onKeyDown,
27
+ "aria-describedby": describedBy,
28
+ "aria-invalid": ariaInvalid,
29
+ ...rest
30
+ }) {
31
+ const bad = isInvalid(invalid, ariaInvalid);
32
+ const unitId = useId();
33
+ const [uncontrolled, setUncontrolled] = useState(
34
+ defaultValue ?? (Number.isFinite(min) ? min : 0)
35
+ );
36
+ const current = value ?? uncontrolled;
37
+ const [draft, setDraft] = useState(null);
38
+ const draftRef = useRef(null);
39
+ const setDraftText = (text) => {
40
+ draftRef.current = text;
41
+ setDraft(text);
42
+ };
43
+ const emit = (next) => {
44
+ if (value === void 0) setUncontrolled(next);
45
+ onValueChange?.(next);
46
+ };
47
+ const settle = (next) => emit(clampToRange(snapToStep(next, step, Number.isFinite(min) ? min : 0), min, max));
48
+ const entry = useRef(current);
49
+ const drag = useRef(null);
50
+ const startScrub = (event) => {
51
+ if (event.button !== 0 || disabled) return;
52
+ event.preventDefault();
53
+ event.currentTarget.setPointerCapture(event.pointerId);
54
+ drag.current = {
55
+ pointerId: event.pointerId,
56
+ startX: event.clientX,
57
+ startValue: current,
58
+ // A scrub is spatial, so it follows the reading direction the way the
59
+ // slider's own track does: in an RTL page, more is to the left.
60
+ sign: getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1
61
+ };
62
+ };
63
+ const moveScrub = (event) => {
64
+ const state = drag.current;
65
+ if (!state || state.pointerId !== event.pointerId) return;
66
+ const notches = Math.round((event.clientX - state.startX) / PX_PER_STEP) * state.sign;
67
+ settle(state.startValue + notches * step * (event.shiftKey ? COARSE : 1));
68
+ };
69
+ const endScrub = (event) => {
70
+ if (drag.current?.pointerId !== event.pointerId) return;
71
+ event.currentTarget.releasePointerCapture(event.pointerId);
72
+ drag.current = null;
73
+ };
74
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative w-full", disabled && "opacity-(--disabled-opacity)", className), children: [
75
+ /* @__PURE__ */ jsx(
76
+ "input",
77
+ {
78
+ ref,
79
+ type: "number",
80
+ inputMode: "decimal",
81
+ disabled,
82
+ value: draft ?? String(current),
83
+ min: Number.isFinite(min) ? min : void 0,
84
+ max: Number.isFinite(max) ? max : void 0,
85
+ step,
86
+ "aria-describedby": [describedBy, unit ? unitId : null].filter(Boolean).join(" ") || void 0,
87
+ "aria-invalid": ariaInvalid ?? (invalid ? true : void 0),
88
+ onFocus: (event) => {
89
+ entry.current = current;
90
+ onFocus?.(event);
91
+ },
92
+ onChange: (event) => {
93
+ setDraftText(event.target.value);
94
+ const parsed = parseNumber(event.target.value);
95
+ if (parsed !== null) emit(parsed);
96
+ onChange?.(event);
97
+ },
98
+ onBlur: (event) => {
99
+ const text = draftRef.current;
100
+ const parsed = text === null ? null : parseNumber(text);
101
+ if (parsed !== null) settle(parsed);
102
+ setDraftText(null);
103
+ onBlur?.(event);
104
+ },
105
+ onKeyDown: (event) => {
106
+ if (event.key === "Enter") {
107
+ const text = draftRef.current;
108
+ const parsed = text === null ? null : parseNumber(text);
109
+ if (parsed !== null) settle(parsed);
110
+ setDraftText(null);
111
+ }
112
+ if (event.key === "Escape") {
113
+ emit(entry.current);
114
+ setDraftText(null);
115
+ event.currentTarget.blur();
116
+ }
117
+ onKeyDown?.(event);
118
+ },
119
+ className: cn(
120
+ CONTROL_BASE,
121
+ bad ? CONTROL_BORDER.invalid : CONTROL_BORDER.resting,
122
+ "tabular-nums",
123
+ // Full literal strings: Tailwind only emits what it can read here.
124
+ scrub && "ps-9",
125
+ unit && "pe-12",
126
+ // Three browsers, three spinners, none of them this system's.
127
+ "[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
128
+ ),
129
+ ...rest
130
+ }
131
+ ),
132
+ scrub && /* @__PURE__ */ jsx(
133
+ "span",
134
+ {
135
+ "aria-hidden": "true",
136
+ onPointerDown: startScrub,
137
+ onPointerMove: moveScrub,
138
+ onPointerUp: endScrub,
139
+ onPointerCancel: endScrub,
140
+ className: cn(
141
+ "absolute inset-y-0 start-0 flex touch-none select-none items-center ps-2.5 text-(--ink-3-aa) transition-colors duration-(--duration-fast)",
142
+ disabled ? "pointer-events-none" : "cursor-ew-resize hover:text-(--ink)"
143
+ ),
144
+ children: /* @__PURE__ */ jsx(MoveHorizontal, { className: "size-4" })
145
+ }
146
+ ),
147
+ unit && /* @__PURE__ */ jsx(
148
+ "span",
149
+ {
150
+ id: unitId,
151
+ className: "pointer-events-none absolute inset-y-0 end-0 flex items-center pe-3 text-sm text-(--ink-3-aa)",
152
+ children: unit
153
+ }
154
+ )
155
+ ] });
156
+ }
157
+ var NumberField_default = NumberField;
158
+ export {
159
+ NumberField,
160
+ NumberField_default as default
161
+ };
162
+ //# sourceMappingURL=NumberField.js.map