@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.
- package/dist/agent/Accordion.md +1 -1
- package/dist/agent/Alert.md +1 -1
- package/dist/agent/AppShell.md +1 -1
- package/dist/agent/ArchitectureFigure.md +1 -1
- package/dist/agent/AreaChart.md +1 -1
- package/dist/agent/Article.md +1 -1
- package/dist/agent/AspectRatio.md +1 -1
- package/dist/agent/Avatar.md +1 -1
- package/dist/agent/Badge.md +1 -1
- package/dist/agent/BarChart.md +1 -1
- package/dist/agent/BarList.md +1 -1
- package/dist/agent/BigNumber.md +1 -1
- package/dist/agent/BoxPlot.md +1 -1
- package/dist/agent/Breadcrumb.md +1 -1
- package/dist/agent/BulletChart.md +1 -1
- package/dist/agent/Button.md +1 -1
- package/dist/agent/Calendar.md +1 -1
- package/dist/agent/Card.md +1 -1
- package/dist/agent/Checkbox.md +1 -1
- package/dist/agent/Code.md +1 -1
- package/dist/agent/CodeBlock.md +1 -1
- package/dist/agent/Collapsible.md +1 -1
- package/dist/agent/ColorPicker.md +75 -0
- package/dist/agent/Combobox.md +1 -1
- package/dist/agent/Command.md +1 -1
- package/dist/agent/ComposedChart.md +1 -1
- package/dist/agent/ContextMenu.md +1 -1
- package/dist/agent/DataflowFigure.md +1 -1
- package/dist/agent/DatePicker.md +1 -1
- package/dist/agent/DescriptionList.md +1 -1
- package/dist/agent/Diagram.md +1 -1
- package/dist/agent/DiagramCanvas.md +1 -1
- package/dist/agent/DiagramExportMenu.md +1 -1
- package/dist/agent/DiagramInspector.md +1 -1
- package/dist/agent/DiagramLegend.md +1 -1
- package/dist/agent/DiagramMinimap.md +1 -1
- package/dist/agent/DiagramToolbar.md +1 -1
- package/dist/agent/Dialog.md +1 -1
- package/dist/agent/DropdownMenu.md +1 -1
- package/dist/agent/EmptyState.md +1 -1
- package/dist/agent/ErrorState.md +1 -1
- package/dist/agent/Facet.md +1 -1
- package/dist/agent/Field.md +1 -1
- package/dist/agent/FigureBand.md +1 -1
- package/dist/agent/FloatingIconButton.md +1 -1
- package/dist/agent/FunnelChart.md +1 -1
- package/dist/agent/Heading.md +1 -1
- package/dist/agent/Heatmap.md +1 -1
- package/dist/agent/Histogram.md +1 -1
- package/dist/agent/Input.md +1 -1
- package/dist/agent/Kbd.md +1 -1
- package/dist/agent/LifecycleFigure.md +1 -1
- package/dist/agent/LineChart.md +1 -1
- package/dist/agent/LinkArrow.md +1 -1
- package/dist/agent/Markdown.md +1 -1
- package/dist/agent/NativeSelect.md +1 -1
- package/dist/agent/NavItem.md +1 -1
- package/dist/agent/NumberField.md +72 -0
- package/dist/agent/Pagination.md +1 -1
- package/dist/agent/PieChart.md +1 -1
- package/dist/agent/Popover.md +1 -1
- package/dist/agent/Progress.md +1 -1
- package/dist/agent/RadarChart.md +1 -1
- package/dist/agent/RadialChart.md +1 -1
- package/dist/agent/RadioGroup.md +1 -1
- package/dist/agent/SankeyChart.md +1 -1
- package/dist/agent/ScatterChart.md +1 -1
- package/dist/agent/ScrollArea.md +1 -1
- package/dist/agent/SearchableMenu.md +1 -1
- package/dist/agent/Select.md +1 -1
- package/dist/agent/Separator.md +1 -1
- package/dist/agent/SequenceFigure.md +1 -1
- package/dist/agent/Sheet.md +1 -1
- package/dist/agent/Sidebar.md +193 -0
- package/dist/agent/Skeleton.md +1 -1
- package/dist/agent/Slider.md +7 -3
- package/dist/agent/Sparkline.md +1 -1
- package/dist/agent/Spinner.md +1 -1
- package/dist/agent/StatusDot.md +1 -1
- package/dist/agent/StatusPill.md +1 -1
- package/dist/agent/Steps.md +1 -1
- package/dist/agent/Switch.md +1 -1
- package/dist/agent/Table.md +3 -1
- package/dist/agent/Tabs.md +1 -1
- package/dist/agent/Tag.md +1 -1
- package/dist/agent/Text.md +1 -1
- package/dist/agent/Textarea.md +1 -1
- package/dist/agent/Timestamp.md +1 -1
- package/dist/agent/Toast.md +1 -1
- package/dist/agent/ToggleGroup.md +1 -1
- package/dist/agent/Toolbar.md +1 -1
- package/dist/agent/Tooltip.md +1 -1
- package/dist/agent/TreemapChart.md +1 -1
- package/dist/agent/WaterfallChart.md +1 -1
- package/dist/agent/WorkflowFigure.md +1 -1
- package/dist/agent/catalog.json +361 -2
- package/dist/agent/index.md +8 -5
- package/dist/charts/BulletChart/BulletChart.js +16 -2
- package/dist/charts/BulletChart/BulletChart.js.map +1 -1
- package/dist/charts/RadarChart/RadarChart.js +4 -2
- package/dist/charts/RadarChart/RadarChart.js.map +1 -1
- package/dist/components/ColorPicker/ColorPicker.d.ts +67 -0
- package/dist/components/ColorPicker/ColorPicker.js +334 -0
- package/dist/components/ColorPicker/ColorPicker.js.map +1 -0
- package/dist/components/Command/Command.js +1 -1
- package/dist/components/Command/Command.js.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.js +1 -1
- package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
- package/dist/components/Dialog/Dialog.js +1 -1
- package/dist/components/Dialog/Dialog.js.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.js +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
- package/dist/components/ErrorState/ErrorState.js +1 -1
- package/dist/components/ErrorState/ErrorState.js.map +1 -1
- package/dist/components/NavItem/NavItem.js +1 -1
- package/dist/components/NavItem/NavItem.js.map +1 -1
- package/dist/components/NumberField/NumberField.d.ts +69 -0
- package/dist/components/NumberField/NumberField.js +162 -0
- package/dist/components/NumberField/NumberField.js.map +1 -0
- package/dist/components/Popover/Popover.js +1 -1
- package/dist/components/Popover/Popover.js.map +1 -1
- package/dist/components/Select/Select.js +1 -1
- package/dist/components/Select/Select.js.map +1 -1
- package/dist/components/Sheet/Sheet.js +1 -1
- package/dist/components/Sheet/Sheet.js.map +1 -1
- package/dist/components/Sidebar/Sidebar.d.ts +349 -0
- package/dist/components/Sidebar/Sidebar.js +455 -0
- package/dist/components/Sidebar/Sidebar.js.map +1 -0
- package/dist/components/Slider/Slider.d.ts +22 -1
- package/dist/components/Slider/Slider.js +72 -8
- package/dist/components/Slider/Slider.js.map +1 -1
- package/dist/components/Table/Table.d.ts +14 -0
- package/dist/components/Table/Table.js.map +1 -1
- package/dist/components/Tabs/Tabs.js +7 -1
- package/dist/components/Tabs/Tabs.js.map +1 -1
- package/dist/diagrams/lib/route.d.ts +22 -1
- package/dist/diagrams/lib/route.js +24 -1
- package/dist/diagrams/lib/route.js.map +1 -1
- package/dist/diagrams/lib/wires.js +9 -5
- package/dist/diagrams/lib/wires.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/color.d.ts +80 -0
- package/dist/lib/color.js +189 -0
- package/dist/lib/color.js.map +1 -0
- package/dist/lib/numeric.d.ts +35 -0
- package/dist/lib/numeric.js +29 -0
- package/dist/lib/numeric.js.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/themes.css +8 -0
- package/dist/tokens.css +66 -3
- package/dist/tokens.d.ts +3 -0
- package/dist/tokens.js +17 -0
- package/dist/tokens.json +17 -0
- package/package.json +1 -1
- package/skills/misoto22-design/rules/forms.md +3 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/NumberField/NumberField.tsx"],"sourcesContent":["'use client'\n\nimport { MoveHorizontal } from 'lucide-react'\nimport { useId, useRef, useState, type InputHTMLAttributes, type PointerEvent, type Ref } from 'react'\nimport { cn } from '../../lib/cn'\nimport { CONTROL_BASE, CONTROL_BORDER, isInvalid } from '../../lib/control'\nimport { clampToRange, parseNumber, snapToStep } from '../../lib/numeric'\n\n/**\n * Pixels of travel per step while scrubbing.\n *\n * Four is the number a pointer reads as \"one notch\". At one or two the value\n * runs away from the hand; past about eight the gesture stops feeling like it\n * is driving anything and a reader goes back to typing.\n */\nconst PX_PER_STEP = 4\n\n/** How much Shift multiplies a scrub by. Ten, as in every drawing tool. */\nconst COARSE = 10\n\nexport interface NumberFieldProps\n extends Omit<InputHTMLAttributes<HTMLInputElement>, 'value' | 'defaultValue' | 'min' | 'max' | 'step' | 'type'> {\n value?: number\n defaultValue?: number\n /** Fires with the parsed number. Raw `onChange` still fires with the event. */\n onValueChange?: (value: number) => void\n min?: number\n max?: number\n /** The increment for the arrows and for one notch of a scrub. */\n step?: number\n /**\n * A unit drawn inside the end of the field — `%`, `px`, `ms`.\n *\n * The field reserves a fixed slot for it, so keep it to about four\n * characters; a longer one runs under a long number. It is announced through\n * `aria-describedby` as well as drawn, because a unit nobody hears turns\n * \"300\" into a number with no dimension.\n */\n unit?: string\n /**\n * A grip at the start of the field that changes the value as it is dragged.\n *\n * On by default, and the reason to reach for this over `<Input type=\"number\">`:\n * a value that is being TUNED — a duration, a weight, an offset — is found by\n * sweeping through neighbouring values, not by typing candidates one at a\n * time. Shift multiplies the travel by ten.\n *\n * Pointer only, and `aria-hidden` for that reason: the same journey on a\n * keyboard is the arrow keys on the field itself, which the platform already\n * provides. Turn it off where the number is a quantity rather than a setting\n * — a line item's quantity is chosen, not swept to.\n */\n scrub?: boolean\n /** Paints the resting border with `--danger` and reflects `aria-invalid`. */\n invalid?: boolean\n ref?: Ref<HTMLInputElement>\n}\n\n/**\n * A number, typed or swept to.\n *\n * `Input` with `type=\"number\"` is a box that happens to reject letters. This is\n * the control for a number that has a RANGE and a sensible increment: the\n * arrows step it, the grip sweeps it, and `min`, `max` and `step` are honoured\n * on the way out rather than merely announced.\n *\n * Reach for `Slider` instead when the position along the range is the\n * information — a volume, a confidence, anything a reader judges by where the\n * thumb sits. Reach for this when the digits are. A slider that also has to be\n * exact is a `Slider` with `editable`, not one of these beside it.\n *\n * **Clamping happens when the field is left, not while it is being typed.** A\n * minimum of 10 would otherwise make 50 unreachable: the `5` is clamped up to\n * 10 before the `0` arrives. So `onValueChange` can report a number outside the\n * range mid-keystroke, and the value that settles is always inside it.\n *\n * The native spinner buttons are hidden — they are three different controls in\n * three browsers, and none of them is this system's. The grip replaces them for\n * a pointer; the arrow keys were always the keyboard's answer.\n *\n * @example\n * <Field label=\"Line height\"><NumberField defaultValue={1.5} min={1} max={3} step={0.1} /></Field>\n * @example\n * <Field label=\"Timeout\"><NumberField defaultValue={30} min={0} max={600} unit=\"s\" /></Field>\n */\nexport function NumberField({\n value,\n defaultValue,\n onValueChange,\n min = Number.NEGATIVE_INFINITY,\n max = Number.POSITIVE_INFINITY,\n step = 1,\n unit,\n scrub = true,\n invalid,\n disabled,\n className,\n ref,\n onChange,\n onFocus,\n onBlur,\n onKeyDown,\n 'aria-describedby': describedBy,\n 'aria-invalid': ariaInvalid,\n ...rest\n}: NumberFieldProps) {\n const bad = isInvalid(invalid, ariaInvalid)\n const unitId = useId()\n\n const [uncontrolled, setUncontrolled] = useState<number>(\n defaultValue ?? (Number.isFinite(min) ? min : 0),\n )\n const current = value ?? uncontrolled\n\n // Non-null only while the box is being typed into. The field shows the draft\n // verbatim then, so a half-typed \"1.\" or \"-\" survives to the next keystroke\n // instead of being rewritten into a number the moment it parses.\n //\n // Mirrored into a ref because Escape has to clear it and then blur the field\n // in the SAME event: `setDraft(null)` has not been applied by the time the\n // blur handler runs, so a handler reading the state variable would still see\n // the abandoned text and commit it.\n const [draft, setDraft] = useState<string | null>(null)\n const draftRef = useRef<string | null>(null)\n const setDraftText = (text: string | null) => {\n draftRef.current = text\n setDraft(text)\n }\n\n const emit = (next: number) => {\n if (value === undefined) setUncontrolled(next)\n onValueChange?.(next)\n }\n\n /** Reconciles a number with the range and the step, then commits it. */\n const settle = (next: number) => emit(clampToRange(snapToStep(next, step, Number.isFinite(min) ? min : 0), min, max))\n\n // What the value was when the box gained focus, so Escape has something to\n // go back to: the raw emits above have already moved it by then.\n const entry = useRef(current)\n\n const drag = useRef<{ pointerId: number; startX: number; startValue: number; sign: 1 | -1 } | null>(null)\n\n const startScrub = (event: PointerEvent<HTMLSpanElement>) => {\n if (event.button !== 0 || disabled) return\n // Otherwise the gesture selects the text of the field it is dragging over.\n event.preventDefault()\n event.currentTarget.setPointerCapture(event.pointerId)\n drag.current = {\n pointerId: event.pointerId,\n startX: event.clientX,\n startValue: current,\n // A scrub is spatial, so it follows the reading direction the way the\n // slider's own track does: in an RTL page, more is to the left.\n sign: getComputedStyle(event.currentTarget).direction === 'rtl' ? -1 : 1,\n }\n }\n\n const moveScrub = (event: PointerEvent<HTMLSpanElement>) => {\n const state = drag.current\n if (!state || state.pointerId !== event.pointerId) return\n const notches = Math.round((event.clientX - state.startX) / PX_PER_STEP) * state.sign\n settle(state.startValue + notches * step * (event.shiftKey ? COARSE : 1))\n }\n\n const endScrub = (event: PointerEvent<HTMLSpanElement>) => {\n if (drag.current?.pointerId !== event.pointerId) return\n event.currentTarget.releasePointerCapture(event.pointerId)\n drag.current = null\n }\n\n return (\n <div className={cn('relative w-full', disabled && 'opacity-(--disabled-opacity)', className)}>\n <input\n ref={ref}\n type=\"number\"\n inputMode=\"decimal\"\n disabled={disabled}\n value={draft ?? String(current)}\n min={Number.isFinite(min) ? min : undefined}\n max={Number.isFinite(max) ? max : undefined}\n step={step}\n aria-describedby={[describedBy, unit ? unitId : null].filter(Boolean).join(' ') || undefined}\n aria-invalid={ariaInvalid ?? (invalid ? true : undefined)}\n onFocus={(event) => {\n entry.current = current\n onFocus?.(event)\n }}\n onChange={(event) => {\n setDraftText(event.target.value)\n // The raw number, unclamped — see the note on the component.\n const parsed = parseNumber(event.target.value)\n if (parsed !== null) emit(parsed)\n onChange?.(event)\n }}\n onBlur={(event) => {\n const text = draftRef.current\n const parsed = text === null ? null : parseNumber(text)\n if (parsed !== null) settle(parsed)\n setDraftText(null)\n onBlur?.(event)\n }}\n onKeyDown={(event) => {\n if (event.key === 'Enter') {\n const text = draftRef.current\n const parsed = text === null ? null : parseNumber(text)\n if (parsed !== null) settle(parsed)\n setDraftText(null)\n }\n if (event.key === 'Escape') {\n emit(entry.current)\n setDraftText(null)\n event.currentTarget.blur()\n }\n onKeyDown?.(event)\n }}\n className={cn(\n CONTROL_BASE,\n bad ? CONTROL_BORDER.invalid : CONTROL_BORDER.resting,\n 'tabular-nums',\n // Full literal strings: Tailwind only emits what it can read here.\n scrub && 'ps-9',\n unit && 'pe-12',\n // Three browsers, three spinners, none of them this system's.\n '[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none',\n )}\n {...rest}\n />\n {scrub && (\n <span\n // Not a button: it commits nothing, has no keyboard journey of its\n // own, and announcing it would give a reader a control that does\n // nothing when they press it. The arrow keys on the field are the\n // keyboard's path to the same values.\n aria-hidden=\"true\"\n onPointerDown={startScrub}\n onPointerMove={moveScrub}\n onPointerUp={endScrub}\n onPointerCancel={endScrub}\n className={cn(\n '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)',\n disabled ? 'pointer-events-none' : 'cursor-ew-resize hover:text-(--ink)',\n )}\n >\n <MoveHorizontal className=\"size-4\" />\n </span>\n )}\n {unit && (\n <span\n id={unitId}\n className=\"pointer-events-none absolute inset-y-0 end-0 flex items-center pe-3 text-sm text-(--ink-3-aa)\"\n >\n {unit}\n </span>\n )}\n </div>\n )\n}\n\nexport default NumberField\n"],"mappings":"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","names":[]}
|
|
@@ -26,7 +26,7 @@ function PopoverContent({
|
|
|
26
26
|
sideOffset,
|
|
27
27
|
"data-m22-animated": true,
|
|
28
28
|
className: cn(
|
|
29
|
-
"z-(--z-dropdown) w-72 rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-4 panel-blur",
|
|
29
|
+
"z-(--z-dropdown) w-72 rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-4 shadow-(--panel-lift) panel-blur",
|
|
30
30
|
"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)",
|
|
31
31
|
className
|
|
32
32
|
),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/Popover/Popover.tsx"],"sourcesContent":["'use client'\n\nimport * as PopoverPrimitive from '@radix-ui/react-popover'\nimport { X } from 'lucide-react'\nimport type { ComponentProps } from 'react'\nimport { cn } from '../../lib/cn'\nimport { useOverlayContainer } from '../../lib/overlay-container'\n\n/** Radix Popover root, trigger, anchor and close, as typed passthroughs. */\nexport const Popover = PopoverPrimitive.Root\nexport const PopoverTrigger = PopoverPrimitive.Trigger\nexport const PopoverAnchor = PopoverPrimitive.Anchor\nexport const PopoverClose = PopoverPrimitive.Close\n\nexport interface PopoverContentProps\n extends ComponentProps<typeof PopoverPrimitive.Content> {\n /** Names the panel for assistive tech. Required — a popover is a dialog. */\n label: string\n /** Show the top-end close control. */\n showClose?: boolean\n}\n\n/**\n * A panel anchored to a control, holding content the reader can interact with.\n *\n * The line against `Tooltip` is not visual, it is behavioural: a tooltip\n * describes and cannot be entered; a popover holds things you tab to. Anything\n * with a link, a field or a button in it is a popover, and putting that inside\n * a tooltip makes it unreachable — the tooltip closes as soon as focus tries to\n * move into it.\n *\n * Against `DropdownMenu`: a menu is a list of actions with menu semantics and\n * arrow-key navigation. A popover is free-form, and its contents Tab like the\n * rest of the page.\n *\n * @example\n * <Popover>\n * <PopoverTrigger asChild><Button variant=\"secondary\">Filters</Button></PopoverTrigger>\n * <PopoverContent label=\"Filters\">\n * <Field label=\"Status\"><Select>…</Select></Field>\n * </PopoverContent>\n * </Popover>\n */\nexport function PopoverContent({\n label,\n showClose = false,\n className,\n sideOffset = 8,\n children,\n ...rest\n}: PopoverContentProps) {\n const container = useOverlayContainer()\n\n return (\n <PopoverPrimitive.Portal container={container ?? undefined}>\n <PopoverPrimitive.Content\n collisionBoundary={container ?? undefined}\n collisionPadding={8}\n aria-label={label}\n sideOffset={sideOffset}\n data-m22-animated\n className={cn(\n 'z-(--z-dropdown) w-72 rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-4 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 {showClose && (\n <PopoverPrimitive.Close\n aria-label=\"Close\"\n className=\"absolute end-2 top-2 grid size-8 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={14} strokeWidth={1.5} aria-hidden />\n </PopoverPrimitive.Close>\n )}\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n )\n}\n\nexport default Popover\n"],"mappings":";AAuDM,SAmBM,KAnBN;AArDN,YAAY,sBAAsB;AAClC,SAAS,SAAS;AAElB,SAAS,UAAU;AACnB,SAAS,2BAA2B;AAG7B,MAAM,UAAU,iBAAiB;AACjC,MAAM,iBAAiB,iBAAiB;AACxC,MAAM,gBAAgB,iBAAiB;AACvC,MAAM,eAAe,iBAAiB;AA+BtC,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EACA,GAAG;AACL,GAAwB;AACtB,QAAM,YAAY,oBAAoB;AAEtC,SACE,oBAAC,iBAAiB,QAAjB,EAAwB,WAAW,aAAa,QAC/C;AAAA,IAAC,iBAAiB;AAAA,IAAjB;AAAA,MACC,mBAAmB,aAAa;AAAA,MAChC,kBAAkB;AAAA,MAClB,cAAY;AAAA,MACZ;AAAA,MACA,qBAAiB;AAAA,MACjB,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,QACA,aACC;AAAA,UAAC,iBAAiB;AAAA,UAAjB;AAAA,YACC,cAAW;AAAA,YACX,WAAU;AAAA,YAEV,8BAAC,KAAE,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC;AAAA;AAAA,QAC7C;AAAA;AAAA;AAAA,EAEJ,GACF;AAEJ;AAEA,IAAO,kBAAQ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Popover/Popover.tsx"],"sourcesContent":["'use client'\n\nimport * as PopoverPrimitive from '@radix-ui/react-popover'\nimport { X } from 'lucide-react'\nimport type { ComponentProps } from 'react'\nimport { cn } from '../../lib/cn'\nimport { useOverlayContainer } from '../../lib/overlay-container'\n\n/** Radix Popover root, trigger, anchor and close, as typed passthroughs. */\nexport const Popover = PopoverPrimitive.Root\nexport const PopoverTrigger = PopoverPrimitive.Trigger\nexport const PopoverAnchor = PopoverPrimitive.Anchor\nexport const PopoverClose = PopoverPrimitive.Close\n\nexport interface PopoverContentProps\n extends ComponentProps<typeof PopoverPrimitive.Content> {\n /** Names the panel for assistive tech. Required — a popover is a dialog. */\n label: string\n /** Show the top-end close control. */\n showClose?: boolean\n}\n\n/**\n * A panel anchored to a control, holding content the reader can interact with.\n *\n * The line against `Tooltip` is not visual, it is behavioural: a tooltip\n * describes and cannot be entered; a popover holds things you tab to. Anything\n * with a link, a field or a button in it is a popover, and putting that inside\n * a tooltip makes it unreachable — the tooltip closes as soon as focus tries to\n * move into it.\n *\n * Against `DropdownMenu`: a menu is a list of actions with menu semantics and\n * arrow-key navigation. A popover is free-form, and its contents Tab like the\n * rest of the page.\n *\n * @example\n * <Popover>\n * <PopoverTrigger asChild><Button variant=\"secondary\">Filters</Button></PopoverTrigger>\n * <PopoverContent label=\"Filters\">\n * <Field label=\"Status\"><Select>…</Select></Field>\n * </PopoverContent>\n * </Popover>\n */\nexport function PopoverContent({\n label,\n showClose = false,\n className,\n sideOffset = 8,\n children,\n ...rest\n}: PopoverContentProps) {\n const container = useOverlayContainer()\n\n return (\n <PopoverPrimitive.Portal container={container ?? undefined}>\n <PopoverPrimitive.Content\n collisionBoundary={container ?? undefined}\n collisionPadding={8}\n aria-label={label}\n sideOffset={sideOffset}\n data-m22-animated\n className={cn(\n 'z-(--z-dropdown) w-72 rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-4 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 {showClose && (\n <PopoverPrimitive.Close\n aria-label=\"Close\"\n className=\"absolute end-2 top-2 grid size-8 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={14} strokeWidth={1.5} aria-hidden />\n </PopoverPrimitive.Close>\n )}\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n )\n}\n\nexport default Popover\n"],"mappings":";AAuDM,SAmBM,KAnBN;AArDN,YAAY,sBAAsB;AAClC,SAAS,SAAS;AAElB,SAAS,UAAU;AACnB,SAAS,2BAA2B;AAG7B,MAAM,UAAU,iBAAiB;AACjC,MAAM,iBAAiB,iBAAiB;AACxC,MAAM,gBAAgB,iBAAiB;AACvC,MAAM,eAAe,iBAAiB;AA+BtC,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EACA,GAAG;AACL,GAAwB;AACtB,QAAM,YAAY,oBAAoB;AAEtC,SACE,oBAAC,iBAAiB,QAAjB,EAAwB,WAAW,aAAa,QAC/C;AAAA,IAAC,iBAAiB;AAAA,IAAjB;AAAA,MACC,mBAAmB,aAAa;AAAA,MAChC,kBAAkB;AAAA,MAClB,cAAY;AAAA,MACZ;AAAA,MACA,qBAAiB;AAAA,MACjB,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,QACA,aACC;AAAA,UAAC,iBAAiB;AAAA,UAAjB;AAAA,YACC,cAAW;AAAA,YACX,WAAU;AAAA,YAEV,8BAAC,KAAE,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC;AAAA;AAAA,QAC7C;AAAA;AAAA;AAAA,EAEJ,GACF;AAEJ;AAEA,IAAO,kBAAQ;","names":[]}
|
|
@@ -71,7 +71,7 @@ function Select({
|
|
|
71
71
|
sideOffset: 6,
|
|
72
72
|
"data-m22-animated": true,
|
|
73
73
|
className: cn(
|
|
74
|
-
"z-(--z-dropdown) max-h-72 min-w-(--radix-select-trigger-width) overflow-hidden rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) panel-blur",
|
|
74
|
+
"z-(--z-dropdown) max-h-72 min-w-(--radix-select-trigger-width) overflow-hidden rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) shadow-(--panel-lift) panel-blur",
|
|
75
75
|
contentClassName,
|
|
76
76
|
"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)"
|
|
77
77
|
),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/Select/Select.tsx"],"sourcesContent":["'use client'\n\nimport * as SelectPrimitive from '@radix-ui/react-select'\nimport { Check, ChevronDown, ChevronUp } from 'lucide-react'\nimport { useId, type ComponentProps, type ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { warnBlankName } from '../../lib/warn'\nimport { useOverlayContainer } from '../../lib/overlay-container'\nimport { useFieldControl } from '../Field/field-control'\nimport { CONTROL_BASE, CONTROL_BORDER, isInvalid } from '../../lib/control'\n\n/** Radix Select root, group and label, as typed passthroughs. */\nexport const SelectRoot = SelectPrimitive.Root\nexport const SelectGroup = SelectPrimitive.Group\n\nexport interface SelectProps extends ComponentProps<typeof SelectPrimitive.Root> {\n /**\n * Classes for the option PANEL, not the trigger.\n *\n * `className` styles the trigger, which is the common case. This exists for\n * the uncommon one: a select inside a bounded surface, where the default\n * 18rem of list would cover the thing the reader is choosing for — a year\n * picker over its own calendar, say.\n */\n contentClassName?: string\n /**\n * Names the control. Required — the trigger shows a value, and a value is not\n * a name.\n *\n * It is announced together with the value, not instead of it: the trigger is\n * named by the label and by its own text, so a reader hears \"Region,\n * Australia\". Inside a `Field` with a label, that label is used and this one\n * is not repeated.\n */\n label: string\n placeholder?: string\n /** Paints the resting border with `--danger` and reflects `aria-invalid`. */\n invalid?: boolean\n disabled?: 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 /** `SelectItem`s, optionally wrapped in `SelectGroup` with a `SelectLabel`. */\n children: ReactNode\n}\n\n/**\n * A choice from a list, styled the whole way down.\n *\n * The option list is ours — drawn from the same tokens as everything else, so\n * it does not change typeface, spacing and selection colour the moment it\n * opens. That is the whole reason this replaced the native control as the\n * default: a design system whose most common form control stops being part of\n * the system on click is not a design system, it is a stylesheet for closed\n * states.\n *\n * The keyboard contract is Radix's, which means it is the platform's: typeahead\n * works, the arrows move, Home and End reach the ends, and Escape closes\n * without choosing. That was the one genuine argument for staying native, and\n * it is answered.\n *\n * Past roughly a dozen options, reach for `Combobox` — a list nobody can filter\n * is worse than one they can type into. Where the platform picker is genuinely\n * better — a phone, or a form that must survive without JavaScript — reach for\n * `NativeSelect`.\n *\n * @example\n * <Field label=\"Region\">\n * <Select label=\"Region\" defaultValue=\"au\">\n * <SelectItem value=\"au\">Australia</SelectItem>\n * <SelectItem value=\"nz\">New Zealand</SelectItem>\n * </Select>\n * </Field>\n */\nexport function Select({\n label,\n placeholder = 'Select…',\n invalid,\n disabled,\n className,\n contentClassName,\n children,\n id,\n 'aria-describedby': describedBy,\n 'aria-invalid': ariaInvalid,\n 'aria-required': ariaRequired,\n ...props\n}: SelectProps) {\n warnBlankName('Select', '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 // The id belongs on the TRIGGER: Radix's root renders no DOM node at all, so\n // an id handed to it lands nowhere and the label above points at nothing.\n const triggerId = id ?? generated\n const valueId = `${triggerId}-value`\n const nameId = field?.labelId ?? `${triggerId}-name`\n\n const container = useOverlayContainer()\n\n return (\n <SelectPrimitive.Root disabled={disabled} {...props}>\n <SelectPrimitive.Trigger\n id={triggerId}\n // Named by the label AND by its own value, in that order. `aria-label`\n // outranks name-from-content, so naming it \"Region\" told a reader the\n // noun and never the answer — and the answer is the only thing on the\n // trigger.\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 'group flex cursor-pointer items-center justify-between gap-2 text-start data-[placeholder]:text-(--ink-3-aa)',\n bad ? CONTROL_BORDER.invalid : CONTROL_BORDER.resting,\n className,\n )}\n >\n {field?.labelId == null && (\n <span id={nameId} className=\"sr-only\">\n {label}\n </span>\n )}\n {/* Wrapped rather than styled directly: Radix's Value drops className.\n The wrapper is also what `aria-labelledby` points at, so the name\n picks up the value and not the chevron. */}\n <span id={valueId} className=\"truncate\">\n <SelectPrimitive.Value placeholder={placeholder} />\n </span>\n <SelectPrimitive.Icon asChild>\n <ChevronDown\n size={16}\n strokeWidth={1.5}\n aria-hidden\n className=\"shrink-0 text-(--ink-3-aa) transition-transform duration-(--duration-fast) group-data-[state=open]:rotate-180\"\n />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n\n <SelectPrimitive.Portal container={container ?? undefined}>\n <SelectPrimitive.Content\n collisionBoundary={container ?? undefined}\n collisionPadding={8}\n position=\"popper\"\n sideOffset={6}\n data-m22-animated\n className={cn(\n 'z-(--z-dropdown) max-h-72 min-w-(--radix-select-trigger-width) overflow-hidden rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) panel-blur',\n contentClassName,\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 )}\n >\n <SelectPrimitive.ScrollUpButton className=\"flex h-6 items-center justify-center text-(--ink-3-aa)\">\n <ChevronUp size={14} strokeWidth={1.5} aria-hidden />\n </SelectPrimitive.ScrollUpButton>\n <SelectPrimitive.Viewport className=\"p-1.5\">{children}</SelectPrimitive.Viewport>\n <SelectPrimitive.ScrollDownButton className=\"flex h-6 items-center justify-center text-(--ink-3-aa)\">\n <ChevronDown size={14} strokeWidth={1.5} aria-hidden />\n </SelectPrimitive.ScrollDownButton>\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n </SelectPrimitive.Root>\n )\n}\n\nexport type SelectItemProps = ComponentProps<typeof SelectPrimitive.Item>\n\n/** One option. The tick marks the chosen one; the fill marks the highlighted one. */\nexport function SelectItem({ className, children, ...props }: SelectItemProps) {\n return (\n <SelectPrimitive.Item\n className={cn(\n 'flex cursor-pointer select-none items-center gap-2.5 rounded-(--radius-row) px-2.5 py-2 text-sm text-(--ink-2) outline-none transition-colors duration-(--duration-fast) data-[highlighted]:bg-(--stone) data-[highlighted]:text-(--ink) data-[disabled]:pointer-events-none data-[disabled]:opacity-(--disabled-opacity)',\n className,\n )}\n {...props}\n >\n {/* The box is always there and only the tick inside it appears, so\n choosing an option does not shunt every label sideways. */}\n <span className=\"grid size-3.5 shrink-0 place-items-center\">\n <SelectPrimitive.ItemIndicator asChild>\n <Check size={14} strokeWidth={2} aria-hidden />\n </SelectPrimitive.ItemIndicator>\n </span>\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n )\n}\n\n/**\n * Mono eyebrow heading for a group of options.\n *\n * Must sit inside a `SelectGroup` — Radix throws otherwise, because a heading\n * with no group is a heading for nothing, and assistive tech would announce it\n * as an option.\n */\nexport function SelectLabel({\n className,\n ...props\n}: ComponentProps<typeof SelectPrimitive.Label>) {\n return (\n <SelectPrimitive.Label\n className={cn('px-2.5 py-1.5 eyebrow text-(--ink-3-aa)', className)}\n {...props}\n />\n )\n}\n\n/** Hairline divider between groups. */\nexport function SelectSeparator({\n className,\n ...props\n}: ComponentProps<typeof SelectPrimitive.Separator>) {\n return (\n <SelectPrimitive.Separator className={cn('my-1 h-px bg-(--rule)', className)} {...props} />\n )\n}\n\nexport default Select\n"],"mappings":";AA6GM,SAkBI,KAlBJ;AA3GN,YAAY,qBAAqB;AACjC,SAAS,OAAO,aAAa,iBAAiB;AAC9C,SAAS,aAAkD;AAC3D,SAAS,UAAU;AACnB,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AACpC,SAAS,uBAAuB;AAChC,SAAS,cAAc,gBAAgB,iBAAiB;AAGjD,MAAM,aAAa,gBAAgB;AACnC,MAAM,cAAc,gBAAgB;AAmEpC,SAAS,OAAO;AAAA,EACrB;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,oBAAoB;AAAA,EACpB,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,GAAG;AACL,GAAgB;AACd,gBAAc,UAAU,SAAS,OAAO,uCAAuC;AAC/E,QAAM,MAAM,UAAU,SAAS,WAAW;AAE1C,QAAM,QAAQ,gBAAgB;AAC9B,QAAM,YAAY,MAAM;AAGxB,QAAM,YAAY,MAAM;AACxB,QAAM,UAAU,GAAG,SAAS;AAC5B,QAAM,SAAS,OAAO,WAAW,GAAG,SAAS;AAE7C,QAAM,YAAY,oBAAoB;AAEtC,SACE,qBAAC,gBAAgB,MAAhB,EAAqB,UAAqB,GAAG,OAC5C;AAAA;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC,IAAI;AAAA,QAKJ,mBAAiB,GAAG,MAAM,IAAI,OAAO;AAAA,QACrC,oBAAkB;AAAA,QAClB,iBAAe;AAAA,QACf,gBAAc,OAAO;AAAA,QACrB,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA,MAAM,eAAe,UAAU,eAAe;AAAA,UAC9C;AAAA,QACF;AAAA,QAEC;AAAA,iBAAO,WAAW,QACjB,oBAAC,UAAK,IAAI,QAAQ,WAAU,WACzB,iBACH;AAAA,UAKF,oBAAC,UAAK,IAAI,SAAS,WAAU,YAC3B,8BAAC,gBAAgB,OAAhB,EAAsB,aAA0B,GACnD;AAAA,UACA,oBAAC,gBAAgB,MAAhB,EAAqB,SAAO,MAC3B;AAAA,YAAC;AAAA;AAAA,cACC,MAAM;AAAA,cACN,aAAa;AAAA,cACb,eAAW;AAAA,cACX,WAAU;AAAA;AAAA,UACZ,GACF;AAAA;AAAA;AAAA,IACF;AAAA,IAEA,oBAAC,gBAAgB,QAAhB,EAAuB,WAAW,aAAa,QAC9C;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACD,mBAAmB,aAAa;AAAA,QAChC,kBAAkB;AAAA,QAChB,UAAS;AAAA,QACT,YAAY;AAAA,QACZ,qBAAiB;AAAA,QACjB,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,QAEA;AAAA,8BAAC,gBAAgB,gBAAhB,EAA+B,WAAU,0DACxC,8BAAC,aAAU,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC,GACrD;AAAA,UACA,oBAAC,gBAAgB,UAAhB,EAAyB,WAAU,SAAS,UAAS;AAAA,UACtD,oBAAC,gBAAgB,kBAAhB,EAAiC,WAAU,0DAC1C,8BAAC,eAAY,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC,GACvD;AAAA;AAAA;AAAA,IACF,GACF;AAAA,KACF;AAEJ;AAKO,SAAS,WAAW,EAAE,WAAW,UAAU,GAAG,MAAM,GAAoB;AAC7E,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAIJ;AAAA,4BAAC,UAAK,WAAU,6CACd,8BAAC,gBAAgB,eAAhB,EAA8B,SAAO,MACpC,8BAAC,SAAM,MAAM,IAAI,aAAa,GAAG,eAAW,MAAC,GAC/C,GACF;AAAA,QACA,oBAAC,gBAAgB,UAAhB,EAA0B,UAAS;AAAA;AAAA;AAAA,EACtC;AAEJ;AASO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA,GAAG;AACL,GAAiD;AAC/C,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,WAAW,GAAG,2CAA2C,SAAS;AAAA,MACjE,GAAG;AAAA;AAAA,EACN;AAEJ;AAGO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,GAAG;AACL,GAAqD;AACnD,SACE,oBAAC,gBAAgB,WAAhB,EAA0B,WAAW,GAAG,yBAAyB,SAAS,GAAI,GAAG,OAAO;AAE7F;AAEA,IAAO,iBAAQ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Select/Select.tsx"],"sourcesContent":["'use client'\n\nimport * as SelectPrimitive from '@radix-ui/react-select'\nimport { Check, ChevronDown, ChevronUp } from 'lucide-react'\nimport { useId, type ComponentProps, type ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { warnBlankName } from '../../lib/warn'\nimport { useOverlayContainer } from '../../lib/overlay-container'\nimport { useFieldControl } from '../Field/field-control'\nimport { CONTROL_BASE, CONTROL_BORDER, isInvalid } from '../../lib/control'\n\n/** Radix Select root, group and label, as typed passthroughs. */\nexport const SelectRoot = SelectPrimitive.Root\nexport const SelectGroup = SelectPrimitive.Group\n\nexport interface SelectProps extends ComponentProps<typeof SelectPrimitive.Root> {\n /**\n * Classes for the option PANEL, not the trigger.\n *\n * `className` styles the trigger, which is the common case. This exists for\n * the uncommon one: a select inside a bounded surface, where the default\n * 18rem of list would cover the thing the reader is choosing for — a year\n * picker over its own calendar, say.\n */\n contentClassName?: string\n /**\n * Names the control. Required — the trigger shows a value, and a value is not\n * a name.\n *\n * It is announced together with the value, not instead of it: the trigger is\n * named by the label and by its own text, so a reader hears \"Region,\n * Australia\". Inside a `Field` with a label, that label is used and this one\n * is not repeated.\n */\n label: string\n placeholder?: string\n /** Paints the resting border with `--danger` and reflects `aria-invalid`. */\n invalid?: boolean\n disabled?: 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 /** `SelectItem`s, optionally wrapped in `SelectGroup` with a `SelectLabel`. */\n children: ReactNode\n}\n\n/**\n * A choice from a list, styled the whole way down.\n *\n * The option list is ours — drawn from the same tokens as everything else, so\n * it does not change typeface, spacing and selection colour the moment it\n * opens. That is the whole reason this replaced the native control as the\n * default: a design system whose most common form control stops being part of\n * the system on click is not a design system, it is a stylesheet for closed\n * states.\n *\n * The keyboard contract is Radix's, which means it is the platform's: typeahead\n * works, the arrows move, Home and End reach the ends, and Escape closes\n * without choosing. That was the one genuine argument for staying native, and\n * it is answered.\n *\n * Past roughly a dozen options, reach for `Combobox` — a list nobody can filter\n * is worse than one they can type into. Where the platform picker is genuinely\n * better — a phone, or a form that must survive without JavaScript — reach for\n * `NativeSelect`.\n *\n * @example\n * <Field label=\"Region\">\n * <Select label=\"Region\" defaultValue=\"au\">\n * <SelectItem value=\"au\">Australia</SelectItem>\n * <SelectItem value=\"nz\">New Zealand</SelectItem>\n * </Select>\n * </Field>\n */\nexport function Select({\n label,\n placeholder = 'Select…',\n invalid,\n disabled,\n className,\n contentClassName,\n children,\n id,\n 'aria-describedby': describedBy,\n 'aria-invalid': ariaInvalid,\n 'aria-required': ariaRequired,\n ...props\n}: SelectProps) {\n warnBlankName('Select', '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 // The id belongs on the TRIGGER: Radix's root renders no DOM node at all, so\n // an id handed to it lands nowhere and the label above points at nothing.\n const triggerId = id ?? generated\n const valueId = `${triggerId}-value`\n const nameId = field?.labelId ?? `${triggerId}-name`\n\n const container = useOverlayContainer()\n\n return (\n <SelectPrimitive.Root disabled={disabled} {...props}>\n <SelectPrimitive.Trigger\n id={triggerId}\n // Named by the label AND by its own value, in that order. `aria-label`\n // outranks name-from-content, so naming it \"Region\" told a reader the\n // noun and never the answer — and the answer is the only thing on the\n // trigger.\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 'group flex cursor-pointer items-center justify-between gap-2 text-start data-[placeholder]:text-(--ink-3-aa)',\n bad ? CONTROL_BORDER.invalid : CONTROL_BORDER.resting,\n className,\n )}\n >\n {field?.labelId == null && (\n <span id={nameId} className=\"sr-only\">\n {label}\n </span>\n )}\n {/* Wrapped rather than styled directly: Radix's Value drops className.\n The wrapper is also what `aria-labelledby` points at, so the name\n picks up the value and not the chevron. */}\n <span id={valueId} className=\"truncate\">\n <SelectPrimitive.Value placeholder={placeholder} />\n </span>\n <SelectPrimitive.Icon asChild>\n <ChevronDown\n size={16}\n strokeWidth={1.5}\n aria-hidden\n className=\"shrink-0 text-(--ink-3-aa) transition-transform duration-(--duration-fast) group-data-[state=open]:rotate-180\"\n />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n\n <SelectPrimitive.Portal container={container ?? undefined}>\n <SelectPrimitive.Content\n collisionBoundary={container ?? undefined}\n collisionPadding={8}\n position=\"popper\"\n sideOffset={6}\n data-m22-animated\n className={cn(\n 'z-(--z-dropdown) max-h-72 min-w-(--radix-select-trigger-width) overflow-hidden rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) shadow-(--panel-lift) panel-blur',\n contentClassName,\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 )}\n >\n <SelectPrimitive.ScrollUpButton className=\"flex h-6 items-center justify-center text-(--ink-3-aa)\">\n <ChevronUp size={14} strokeWidth={1.5} aria-hidden />\n </SelectPrimitive.ScrollUpButton>\n <SelectPrimitive.Viewport className=\"p-1.5\">{children}</SelectPrimitive.Viewport>\n <SelectPrimitive.ScrollDownButton className=\"flex h-6 items-center justify-center text-(--ink-3-aa)\">\n <ChevronDown size={14} strokeWidth={1.5} aria-hidden />\n </SelectPrimitive.ScrollDownButton>\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n </SelectPrimitive.Root>\n )\n}\n\nexport type SelectItemProps = ComponentProps<typeof SelectPrimitive.Item>\n\n/** One option. The tick marks the chosen one; the fill marks the highlighted one. */\nexport function SelectItem({ className, children, ...props }: SelectItemProps) {\n return (\n <SelectPrimitive.Item\n className={cn(\n 'flex cursor-pointer select-none items-center gap-2.5 rounded-(--radius-row) px-2.5 py-2 text-sm text-(--ink-2) outline-none transition-colors duration-(--duration-fast) data-[highlighted]:bg-(--stone) data-[highlighted]:text-(--ink) data-[disabled]:pointer-events-none data-[disabled]:opacity-(--disabled-opacity)',\n className,\n )}\n {...props}\n >\n {/* The box is always there and only the tick inside it appears, so\n choosing an option does not shunt every label sideways. */}\n <span className=\"grid size-3.5 shrink-0 place-items-center\">\n <SelectPrimitive.ItemIndicator asChild>\n <Check size={14} strokeWidth={2} aria-hidden />\n </SelectPrimitive.ItemIndicator>\n </span>\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n )\n}\n\n/**\n * Mono eyebrow heading for a group of options.\n *\n * Must sit inside a `SelectGroup` — Radix throws otherwise, because a heading\n * with no group is a heading for nothing, and assistive tech would announce it\n * as an option.\n */\nexport function SelectLabel({\n className,\n ...props\n}: ComponentProps<typeof SelectPrimitive.Label>) {\n return (\n <SelectPrimitive.Label\n className={cn('px-2.5 py-1.5 eyebrow text-(--ink-3-aa)', className)}\n {...props}\n />\n )\n}\n\n/** Hairline divider between groups. */\nexport function SelectSeparator({\n className,\n ...props\n}: ComponentProps<typeof SelectPrimitive.Separator>) {\n return (\n <SelectPrimitive.Separator className={cn('my-1 h-px bg-(--rule)', className)} {...props} />\n )\n}\n\nexport default Select\n"],"mappings":"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","names":[]}
|
|
@@ -39,7 +39,7 @@ function SheetContent({
|
|
|
39
39
|
{
|
|
40
40
|
"data-m22-animated": true,
|
|
41
41
|
className: cn(
|
|
42
|
-
"fixed z-(--z-modal) flex flex-col overflow-y-auto border-(--panel-border) bg-(--panel-bg) p-6 panel-blur transition-transform duration-(--duration-slow) ease-(--ease-out-expo) scroll-slim",
|
|
42
|
+
"fixed z-(--z-modal) flex flex-col overflow-y-auto border-(--panel-border) bg-(--panel-bg) p-6 shadow-(--panel-lift) panel-blur transition-transform duration-(--duration-slow) ease-(--ease-out-expo) scroll-slim",
|
|
43
43
|
SIDE[side],
|
|
44
44
|
// Docked to the container's edge, and capped by its box — the widths
|
|
45
45
|
// above are read against the viewport and would overflow a frame.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/Sheet/Sheet.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'\n\n/** Radix Dialog root, trigger and close — a sheet IS a dialog, docked. */\nexport const Sheet = DialogPrimitive.Root\nexport const SheetTrigger = DialogPrimitive.Trigger\nexport const SheetClose = DialogPrimitive.Close\n\nexport type SheetSide = 'start' | 'end' | 'top' | 'bottom'\n\n/**\n * Full literal class strings per edge, because Tailwind only generates what it\n * can see verbatim. The inline sides are named in READING order, so a sheet\n * docked to `end` comes from the right in English and the left in Arabic.\n */\nconst SIDE: Record<SheetSide, string> = {\n start:\n 'inset-y-0 start-0 h-full w-[min(24rem,92vw)] border-e data-[state=closed]:-translate-x-full rtl:data-[state=closed]:translate-x-full',\n end: 'inset-y-0 end-0 h-full w-[min(24rem,92vw)] border-s data-[state=closed]:translate-x-full rtl:data-[state=closed]:-translate-x-full',\n top: 'inset-x-0 top-0 w-full max-h-[85vh] border-b data-[state=closed]:-translate-y-full',\n bottom:\n 'inset-x-0 bottom-0 w-full max-h-[85vh] border-t data-[state=closed]:translate-y-full',\n}\n\nexport interface SheetContentProps\n extends Omit<ComponentProps<typeof DialogPrimitive.Content>, 'title'> {\n /** Which edge it is docked to. `end` by default. */\n side?: SheetSide\n title: ReactNode\n description?: ReactNode\n /** Hide the title visually while keeping it for assistive tech. */\n hideTitle?: boolean\n}\n\n/**\n * A panel docked to an edge of the viewport.\n *\n * It is a modal dialog — Radix's, so the focus trap, the escape key, the scroll\n * lock and the `aria-modal` wiring are the same ones `Dialog` gets. The only\n * differences are where it sits and which way it arrives, which is why this\n * shares that implementation rather than reproducing it: a second focus trap is\n * a second focus trap to get wrong.\n *\n * The title is required, visible or not. A modal with no accessible name drops\n * a screen reader into an unnamed region with no way back out.\n *\n * Portals into the element an enclosing `OverlayContainer` names, docking to\n * that element's edge rather than the viewport's when there is one.\n *\n * @example\n * <Sheet>\n * <SheetTrigger asChild><Button variant=\"secondary\">Filters</Button></SheetTrigger>\n * <SheetContent title=\"Filters\" description=\"Narrow the list.\">…</SheetContent>\n * </Sheet>\n */\nexport function SheetContent({\n side = 'end',\n title,\n description,\n hideTitle = false,\n className,\n children,\n ...rest\n}: SheetContentProps) {\n const container = useOverlayContainer()\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 // The panel's own assertion that its travel is decorative — the scrim\n // beside it always made one and this half never did, so under reduced\n // motion the fade was cancelled and the panel still slid the whole\n // width of itself, which reads worse than leaving both alone. See the\n // reduced-motion block in keyframes.css for what the marker now means:\n // documentation with teeth, over a universal floor that no longer\n // depends on anyone remembering it.\n data-m22-animated\n className={cn(\n 'fixed z-(--z-modal) flex flex-col overflow-y-auto border-(--panel-border) bg-(--panel-bg) p-6 panel-blur transition-transform duration-(--duration-slow) ease-(--ease-out-expo) scroll-slim',\n SIDE[side],\n // Docked to the container's edge, and capped by its box — the widths\n // above are read against the viewport and would overflow a frame.\n container && 'absolute max-h-full max-w-full',\n className,\n )}\n {...rest}\n >\n <div className={cn('mb-4 flex flex-col gap-1 pe-8', hideTitle && 'sr-only')}>\n <DialogPrimitive.Title className=\"m-0 font-heading text-[length:var(--fs-item)] font-normal text-(--ink)\">\n {title}\n </DialogPrimitive.Title>\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 <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 {children}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n )\n}\n\nexport default Sheet\n"],"mappings":";AAyEM,cA0BE,YA1BF;AAvEN,YAAY,qBAAqB;AACjC,SAAS,SAAS;AAElB,SAAS,UAAU;AACnB,SAAS,2BAA2B;AAG7B,MAAM,QAAQ,gBAAgB;AAC9B,MAAM,eAAe,gBAAgB;AACrC,MAAM,aAAa,gBAAgB;AAS1C,MAAM,OAAkC;AAAA,EACtC,OACE;AAAA,EACF,KAAK;AAAA,EACL,KAAK;AAAA,EACL,QACE;AACJ;AAiCO,SAAS,aAAa;AAAA,EAC3B,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAsB;AACpB,QAAM,YAAY,oBAAoB;AAEtC,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,QAQC,qBAAiB;AAAA,QACjB,WAAW;AAAA,UACT;AAAA,UACA,KAAK,IAAI;AAAA;AAAA;AAAA,UAGT,aAAa;AAAA,UACb;AAAA,QACF;AAAA,QACC,GAAG;AAAA,QAEJ;AAAA,+BAAC,SAAI,WAAW,GAAG,iCAAiC,aAAa,SAAS,GACxE;AAAA,gCAAC,gBAAgB,OAAhB,EAAsB,WAAU,0EAC9B,iBACH;AAAA,YACC,gBAAgB,UACf,oBAAC,gBAAgB,aAAhB,EAA4B,WAAU,iDACpC,uBACH;AAAA,aAEJ;AAAA,UAEA;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,UAEC;AAAA;AAAA;AAAA,IACH;AAAA,KACF;AAEJ;AAEA,IAAO,gBAAQ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Sheet/Sheet.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'\n\n/** Radix Dialog root, trigger and close — a sheet IS a dialog, docked. */\nexport const Sheet = DialogPrimitive.Root\nexport const SheetTrigger = DialogPrimitive.Trigger\nexport const SheetClose = DialogPrimitive.Close\n\nexport type SheetSide = 'start' | 'end' | 'top' | 'bottom'\n\n/**\n * Full literal class strings per edge, because Tailwind only generates what it\n * can see verbatim. The inline sides are named in READING order, so a sheet\n * docked to `end` comes from the right in English and the left in Arabic.\n */\nconst SIDE: Record<SheetSide, string> = {\n start:\n 'inset-y-0 start-0 h-full w-[min(24rem,92vw)] border-e data-[state=closed]:-translate-x-full rtl:data-[state=closed]:translate-x-full',\n end: 'inset-y-0 end-0 h-full w-[min(24rem,92vw)] border-s data-[state=closed]:translate-x-full rtl:data-[state=closed]:-translate-x-full',\n top: 'inset-x-0 top-0 w-full max-h-[85vh] border-b data-[state=closed]:-translate-y-full',\n bottom:\n 'inset-x-0 bottom-0 w-full max-h-[85vh] border-t data-[state=closed]:translate-y-full',\n}\n\nexport interface SheetContentProps\n extends Omit<ComponentProps<typeof DialogPrimitive.Content>, 'title'> {\n /** Which edge it is docked to. `end` by default. */\n side?: SheetSide\n title: ReactNode\n description?: ReactNode\n /** Hide the title visually while keeping it for assistive tech. */\n hideTitle?: boolean\n}\n\n/**\n * A panel docked to an edge of the viewport.\n *\n * It is a modal dialog — Radix's, so the focus trap, the escape key, the scroll\n * lock and the `aria-modal` wiring are the same ones `Dialog` gets. The only\n * differences are where it sits and which way it arrives, which is why this\n * shares that implementation rather than reproducing it: a second focus trap is\n * a second focus trap to get wrong.\n *\n * The title is required, visible or not. A modal with no accessible name drops\n * a screen reader into an unnamed region with no way back out.\n *\n * Portals into the element an enclosing `OverlayContainer` names, docking to\n * that element's edge rather than the viewport's when there is one.\n *\n * @example\n * <Sheet>\n * <SheetTrigger asChild><Button variant=\"secondary\">Filters</Button></SheetTrigger>\n * <SheetContent title=\"Filters\" description=\"Narrow the list.\">…</SheetContent>\n * </Sheet>\n */\nexport function SheetContent({\n side = 'end',\n title,\n description,\n hideTitle = false,\n className,\n children,\n ...rest\n}: SheetContentProps) {\n const container = useOverlayContainer()\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 // The panel's own assertion that its travel is decorative — the scrim\n // beside it always made one and this half never did, so under reduced\n // motion the fade was cancelled and the panel still slid the whole\n // width of itself, which reads worse than leaving both alone. See the\n // reduced-motion block in keyframes.css for what the marker now means:\n // documentation with teeth, over a universal floor that no longer\n // depends on anyone remembering it.\n data-m22-animated\n className={cn(\n 'fixed z-(--z-modal) flex flex-col overflow-y-auto border-(--panel-border) bg-(--panel-bg) p-6 shadow-(--panel-lift) panel-blur transition-transform duration-(--duration-slow) ease-(--ease-out-expo) scroll-slim',\n SIDE[side],\n // Docked to the container's edge, and capped by its box — the widths\n // above are read against the viewport and would overflow a frame.\n container && 'absolute max-h-full max-w-full',\n className,\n )}\n {...rest}\n >\n <div className={cn('mb-4 flex flex-col gap-1 pe-8', hideTitle && 'sr-only')}>\n <DialogPrimitive.Title className=\"m-0 font-heading text-[length:var(--fs-item)] font-normal text-(--ink)\">\n {title}\n </DialogPrimitive.Title>\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 <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 {children}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n )\n}\n\nexport default Sheet\n"],"mappings":";AAyEM,cA0BE,YA1BF;AAvEN,YAAY,qBAAqB;AACjC,SAAS,SAAS;AAElB,SAAS,UAAU;AACnB,SAAS,2BAA2B;AAG7B,MAAM,QAAQ,gBAAgB;AAC9B,MAAM,eAAe,gBAAgB;AACrC,MAAM,aAAa,gBAAgB;AAS1C,MAAM,OAAkC;AAAA,EACtC,OACE;AAAA,EACF,KAAK;AAAA,EACL,KAAK;AAAA,EACL,QACE;AACJ;AAiCO,SAAS,aAAa;AAAA,EAC3B,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAsB;AACpB,QAAM,YAAY,oBAAoB;AAEtC,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,QAQC,qBAAiB;AAAA,QACjB,WAAW;AAAA,UACT;AAAA,UACA,KAAK,IAAI;AAAA;AAAA;AAAA,UAGT,aAAa;AAAA,UACb;AAAA,QACF;AAAA,QACC,GAAG;AAAA,QAEJ;AAAA,+BAAC,SAAI,WAAW,GAAG,iCAAiC,aAAa,SAAS,GACxE;AAAA,gCAAC,gBAAgB,OAAhB,EAAsB,WAAU,0EAC9B,iBACH;AAAA,YACC,gBAAgB,UACf,oBAAC,gBAAgB,aAAhB,EAA4B,WAAU,iDACpC,uBACH;AAAA,aAEJ;AAAA,UAEA;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,UAEC;AAAA;AAAA;AAAA,IACH;AAAA,KACF;AAEJ;AAEA,IAAO,gBAAQ;","names":[]}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentProps, ReactNode } from 'react';
|
|
3
|
+
import { LucideIcon } from 'lucide-react';
|
|
4
|
+
import { NavItemProps } from '../NavItem/NavItem.js';
|
|
5
|
+
|
|
6
|
+
/** What happens to the rail when it is closed. See {@link SidebarProviderProps.collapsible}. */
|
|
7
|
+
type SidebarCollapsible = 'icon' | 'offcanvas' | 'none';
|
|
8
|
+
/** Which edge of the layout the rail is on. Logical, so it follows `dir`. */
|
|
9
|
+
type SidebarSide = 'start' | 'end';
|
|
10
|
+
/** How the rail meets the page. See {@link SidebarProviderProps.variant}. */
|
|
11
|
+
type SidebarVariant = 'flush' | 'inset' | 'floating';
|
|
12
|
+
/** Where the rail stops being a column. See {@link SidebarProviderProps.breakpoint}. */
|
|
13
|
+
type SidebarBreakpoint = 'sm' | 'md' | 'lg' | 'xl';
|
|
14
|
+
interface SidebarState {
|
|
15
|
+
/** Whether the rail is showing — the docked column, or the open drawer. */
|
|
16
|
+
open: boolean;
|
|
17
|
+
setOpen: (open: boolean) => void;
|
|
18
|
+
toggle: () => void;
|
|
19
|
+
collapsible: SidebarCollapsible;
|
|
20
|
+
/** True only in the state where labels are hidden and rows are icons. */
|
|
21
|
+
iconOnly: boolean;
|
|
22
|
+
side: SidebarSide;
|
|
23
|
+
variant: SidebarVariant;
|
|
24
|
+
/**
|
|
25
|
+
* True while the viewport is under the provider's breakpoint, where the rail
|
|
26
|
+
* is a drawer over the page rather than a column beside it.
|
|
27
|
+
*/
|
|
28
|
+
mobile: boolean;
|
|
29
|
+
/** The drawer covers its positioned ancestor rather than the window. */
|
|
30
|
+
contained: boolean;
|
|
31
|
+
/** The step below which the rail is a drawer, or `null` for never. */
|
|
32
|
+
breakpoint: SidebarBreakpoint | null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The rail's own state, for anything that has to answer to it.
|
|
36
|
+
*
|
|
37
|
+
* A page beside the rail needs it to reserve the right width; a control inside
|
|
38
|
+
* needs to know whether its label is being drawn.
|
|
39
|
+
*
|
|
40
|
+
* This one THROWS outside a provider, and the parts above do not, and the
|
|
41
|
+
* difference is who made the mistake. A part rendered on its own is somebody
|
|
42
|
+
* writing `<Sidebar>` to see what it looks like; a call to this hook is code
|
|
43
|
+
* asking for state that nothing is keeping, and returning a plausible default
|
|
44
|
+
* there is a layout that is wrong in one state and right in the other with
|
|
45
|
+
* nothing to say which.
|
|
46
|
+
*/
|
|
47
|
+
declare function useSidebar(): SidebarState;
|
|
48
|
+
interface SidebarProviderProps {
|
|
49
|
+
/** Controlled open state. Leave off to let the provider own it. */
|
|
50
|
+
open?: boolean;
|
|
51
|
+
defaultOpen?: boolean;
|
|
52
|
+
onOpenChange?: (open: boolean) => void;
|
|
53
|
+
/** What closing does to the rail. See {@link SidebarProps.collapsible}. */
|
|
54
|
+
collapsible?: SidebarCollapsible;
|
|
55
|
+
/**
|
|
56
|
+
* The keyboard shortcut that toggles the rail, as a single letter.
|
|
57
|
+
*
|
|
58
|
+
* `b`, with the platform's own modifier, because that is what every editor
|
|
59
|
+
* and every application shell already uses for the same thing. Pass `null`
|
|
60
|
+
* to bind nothing — an app that already owns that chord should not have it
|
|
61
|
+
* taken twice, and a rail that cannot be closed does not need a shortcut for
|
|
62
|
+
* closing it.
|
|
63
|
+
*/
|
|
64
|
+
shortcut?: string | null;
|
|
65
|
+
/** Which edge the rail is on. Logical: `end` is the right in LTR, the left in RTL. */
|
|
66
|
+
side?: SidebarSide;
|
|
67
|
+
/**
|
|
68
|
+
* How the rail meets the page.
|
|
69
|
+
*
|
|
70
|
+
* `flush` is a column with a hairline down its inner edge — the rail and the
|
|
71
|
+
* page are one surface divided by a line. `floating` lifts the rail off that
|
|
72
|
+
* surface as its own bordered panel with the page ground showing around it.
|
|
73
|
+
* `inset` is the same gesture the other way up: the rail sits on the page
|
|
74
|
+
* ground and `SidebarInset` draws the CONTENT as the panel.
|
|
75
|
+
*
|
|
76
|
+
* `floating` and `inset` both need a ground to sit on — put `bg-(--stone)` on
|
|
77
|
+
* the element holding the provider, or they are a panel on the same colour as
|
|
78
|
+
* the thing behind it.
|
|
79
|
+
*/
|
|
80
|
+
variant?: SidebarVariant;
|
|
81
|
+
/**
|
|
82
|
+
* The step below which the rail becomes a drawer over the page.
|
|
83
|
+
*
|
|
84
|
+
* A rail is a column when there is a column's worth of room and an overlay
|
|
85
|
+
* when there is not; below this the rail is `fixed`, covers the page from its
|
|
86
|
+
* own edge, and closes onto a scrim. `null` pins it as a column at every
|
|
87
|
+
* width, for a layout that is never narrow — an embedded console, a preview
|
|
88
|
+
* frame.
|
|
89
|
+
*
|
|
90
|
+
* Named, not a number: the switch is a media query in the stylesheet, and a
|
|
91
|
+
* class built from a runtime number is a class the compiler never emitted. It
|
|
92
|
+
* also puts the rail on the same four steps as the page around it.
|
|
93
|
+
*/
|
|
94
|
+
breakpoint?: SidebarBreakpoint | null;
|
|
95
|
+
/**
|
|
96
|
+
* A `localStorage` key under which the DOCKED state is remembered.
|
|
97
|
+
*
|
|
98
|
+
* A reader who put the rail away did not mean "until the next page". Only the
|
|
99
|
+
* docked state is kept: an open drawer is a thing a reader did to this screen,
|
|
100
|
+
* and restoring it on the next visit is a page that opens with its navigation
|
|
101
|
+
* over the top of itself.
|
|
102
|
+
*
|
|
103
|
+
* Read after mount, never during render — a value from storage in the first
|
|
104
|
+
* pass is a hydration mismatch, and the markup the server sent is the one the
|
|
105
|
+
* client has to agree with.
|
|
106
|
+
*/
|
|
107
|
+
persist?: string | null;
|
|
108
|
+
/**
|
|
109
|
+
* The drawer covers its nearest positioned ancestor rather than the window.
|
|
110
|
+
*
|
|
111
|
+
* For a rail inside a bounded frame — a device preview, an embedded console,
|
|
112
|
+
* an example on a documentation page. `fixed` resolves against the viewport
|
|
113
|
+
* wherever the markup sits, so without this the drawer inside a 400px preview
|
|
114
|
+
* opens across the whole page it is previewed on. Give the frame `relative`.
|
|
115
|
+
*/
|
|
116
|
+
contained?: boolean;
|
|
117
|
+
children: ReactNode;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Holds whether the rail is open, and binds the shortcut that changes it.
|
|
121
|
+
*
|
|
122
|
+
* Separate from `Sidebar` itself because the answer is needed on BOTH sides of
|
|
123
|
+
* the layout: the rail draws itself from it, and the content beside the rail
|
|
124
|
+
* reserves width from it. A state that lived inside the rail could only ever
|
|
125
|
+
* be read downwards.
|
|
126
|
+
*/
|
|
127
|
+
declare function SidebarProvider({ open: controlled, defaultOpen, onOpenChange, collapsible, shortcut, side, variant, breakpoint, persist, contained, children, }: SidebarProviderProps): react.JSX.Element;
|
|
128
|
+
interface SidebarProps extends Omit<ComponentProps<'nav'>, 'children'> {
|
|
129
|
+
/**
|
|
130
|
+
* Names the landmark. Required: a page with two navigations in it announces
|
|
131
|
+
* two things called "navigation" unless each of them says which it is.
|
|
132
|
+
*/
|
|
133
|
+
label: string;
|
|
134
|
+
/**
|
|
135
|
+
* What the scrim behind the open drawer announces.
|
|
136
|
+
*
|
|
137
|
+
* It is a button — tapping beside a drawer closes it, and that has to be a
|
|
138
|
+
* control a keyboard and a screen reader can reach, not a decorated `<div>`.
|
|
139
|
+
*/
|
|
140
|
+
scrimLabel?: string;
|
|
141
|
+
children: ReactNode;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* A navigation rail down the side of an application.
|
|
145
|
+
*
|
|
146
|
+
* A `<nav>`, not an `<aside>`. The element decides the landmark, and a rail of
|
|
147
|
+
* links announced as "complementary" is not the one a screen reader user jumps
|
|
148
|
+
* to when they go looking for the navigation.
|
|
149
|
+
*
|
|
150
|
+
* Composed rather than configured: a rail is a header, a scrolling middle and a
|
|
151
|
+
* footer, and every product wants different things in all three. What this owns
|
|
152
|
+
* is the part that is the same everywhere — the width, the edge, the scroll
|
|
153
|
+
* behaviour, and what happens when it closes.
|
|
154
|
+
*
|
|
155
|
+
* **Closing has three shapes**, and `collapsible` on the provider picks one.
|
|
156
|
+
* `icon` keeps the rail and drops the labels, which is right when the rows are
|
|
157
|
+
* a fixed set a reader learns the shape of. `offcanvas` takes the whole rail
|
|
158
|
+
* away, which is right when the rows are a long index nobody memorises.
|
|
159
|
+
* `none` is a rail that does not close.
|
|
160
|
+
*
|
|
161
|
+
* The trigger belongs INSIDE the rail — `SidebarHeader` places it — rather than
|
|
162
|
+
* out in an application's masthead. A control that hides a thing should live on
|
|
163
|
+
* the thing: in the masthead it is one more anonymous icon in a row of them,
|
|
164
|
+
* and nothing connects it to the column it operates.
|
|
165
|
+
*
|
|
166
|
+
* **Under the provider's `breakpoint` it is a drawer**, not a column: `fixed`
|
|
167
|
+
* against its own edge, over a scrim, and `inert` while closed. Merely
|
|
168
|
+
* translating it off-screen is not closed — it keeps focus and it is still read
|
|
169
|
+
* aloud, so a shut drawer puts its whole index between the reader and the page
|
|
170
|
+
* they were on. The scrim is a real button, because tapping beside a drawer is
|
|
171
|
+
* how a drawer is closed and that gesture has to exist for a keyboard too.
|
|
172
|
+
*
|
|
173
|
+
* `fixed` resolves against the viewport unless an ancestor has a `transform`,
|
|
174
|
+
* `filter` or `perspective`, which makes that ancestor the containing block
|
|
175
|
+
* instead. Do not put one on an element wrapping the provider, or the drawer
|
|
176
|
+
* opens inside it.
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* <SidebarProvider>
|
|
180
|
+
* <Sidebar label="Documentation">
|
|
181
|
+
* <SidebarHeader>Acme</SidebarHeader>
|
|
182
|
+
* <SidebarContent>
|
|
183
|
+
* <SidebarGroup label="Guide" count={2}>
|
|
184
|
+
* <SidebarItem href="/start" icon={Home}>Getting started</SidebarItem>
|
|
185
|
+
* </SidebarGroup>
|
|
186
|
+
* </SidebarContent>
|
|
187
|
+
* </Sidebar>
|
|
188
|
+
* </SidebarProvider>
|
|
189
|
+
*/
|
|
190
|
+
declare function Sidebar({ label, scrimLabel, className, children, ...rest }: SidebarProps): react.JSX.Element;
|
|
191
|
+
/**
|
|
192
|
+
* The page beside the rail.
|
|
193
|
+
*
|
|
194
|
+
* Every layout built on this component was writing the same
|
|
195
|
+
* `flex min-w-0 flex-1 flex-col` by hand, and `min-w-0` is the half everybody
|
|
196
|
+
* forgets: a flex child's floor is its content, so one wide table inside pushes
|
|
197
|
+
* the whole page past the viewport and takes the rail's width with it.
|
|
198
|
+
*
|
|
199
|
+
* It is also the other half of `variant="inset"`. There the rail is the ground
|
|
200
|
+
* and this is the panel — a bordered `--paper` surface with the ground showing
|
|
201
|
+
* around it — which is why the variant lives on the provider rather than on
|
|
202
|
+
* either piece: one setting, two components, and no way to set half of it.
|
|
203
|
+
*
|
|
204
|
+
* A `<div>`, not a `<main>`. What goes in here is usually a masthead AND the
|
|
205
|
+
* page under it, and only one of those is the main landmark.
|
|
206
|
+
*/
|
|
207
|
+
declare function SidebarInset({ className, children, ...rest }: ComponentProps<'div'>): react.JSX.Element;
|
|
208
|
+
/**
|
|
209
|
+
* The block at the top of the rail: a brand, a workspace, a switcher.
|
|
210
|
+
*
|
|
211
|
+
* It is also where `SidebarTrigger` belongs, and the layout assumes one:
|
|
212
|
+
* anything passed as `children` takes the space and the trigger sits at the
|
|
213
|
+
* inline end of the row.
|
|
214
|
+
*/
|
|
215
|
+
declare function SidebarHeader({ className, children, ...rest }: ComponentProps<'div'>): react.JSX.Element;
|
|
216
|
+
/** The scrolling middle. Everything that is a list of places goes here. */
|
|
217
|
+
declare function SidebarContent({ className, children, ...rest }: ComponentProps<'div'>): react.JSX.Element;
|
|
218
|
+
/**
|
|
219
|
+
* The block at the bottom: the utilities, the account, the thing a rail ends on.
|
|
220
|
+
*
|
|
221
|
+
* A separate landmark from the content above it because it is a separate kind
|
|
222
|
+
* of thing — a reader scanning the index does not want Trash and Help in it.
|
|
223
|
+
*/
|
|
224
|
+
declare function SidebarFooter({ className, children, ...rest }: ComponentProps<'div'>): react.JSX.Element;
|
|
225
|
+
/** A rule between blocks of rows, inset to the rail's own padding. */
|
|
226
|
+
declare function SidebarSeparator({ className, ...rest }: ComponentProps<'hr'>): react.JSX.Element;
|
|
227
|
+
interface SidebarTriggerProps extends ComponentProps<'button'> {
|
|
228
|
+
/** What the button announces. Both states, because it says both things. */
|
|
229
|
+
labels?: {
|
|
230
|
+
open: string;
|
|
231
|
+
close: string;
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The control that opens and closes the rail.
|
|
236
|
+
*
|
|
237
|
+
* Its accessible name changes with what it will DO, and `aria-expanded` reports
|
|
238
|
+
* what is true now — a button permanently called "Toggle sidebar" tells a
|
|
239
|
+
* screen reader user nothing about which way it will go.
|
|
240
|
+
*/
|
|
241
|
+
declare function SidebarTrigger({ labels, className, onClick, ...rest }: SidebarTriggerProps): react.JSX.Element;
|
|
242
|
+
interface SidebarGroupProps {
|
|
243
|
+
/** The heading over the rows. */
|
|
244
|
+
label: string;
|
|
245
|
+
/** How many rows are inside, printed on the far side of the heading. */
|
|
246
|
+
count?: number;
|
|
247
|
+
/**
|
|
248
|
+
* A mark that belongs to the GROUP — "Beta", "3 new".
|
|
249
|
+
*
|
|
250
|
+
* Beside the label, not out at the end with the count: it qualifies the
|
|
251
|
+
* words, and a qualifier that has drifted to the other side of the row reads
|
|
252
|
+
* as a second, unrelated fact.
|
|
253
|
+
*/
|
|
254
|
+
badge?: ReactNode;
|
|
255
|
+
/**
|
|
256
|
+
* A control on the heading row — a menu, an "add" button.
|
|
257
|
+
*
|
|
258
|
+
* Sits between the label and the count, and is NOT rendered inside the
|
|
259
|
+
* heading's own button: a control nested in a control is one the keyboard
|
|
260
|
+
* reaches by pressing the thing it is inside.
|
|
261
|
+
*/
|
|
262
|
+
action?: ReactNode;
|
|
263
|
+
/** Whether the group folds at all. A group of two rows usually should not. */
|
|
264
|
+
collapsible?: boolean;
|
|
265
|
+
defaultOpen?: boolean;
|
|
266
|
+
children: ReactNode;
|
|
267
|
+
className?: string;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* A labelled block of rows, optionally foldable.
|
|
271
|
+
*
|
|
272
|
+
* The heading is the same SIZE as the rows beneath it and outranks them by
|
|
273
|
+
* weight and by ink. Both halves of that are corrections. Smaller, it inverted
|
|
274
|
+
* the hierarchy it exists to express — a group read as a footnote over a list
|
|
275
|
+
* rather than as a title over its own contents. In the MONO face, which is
|
|
276
|
+
* where it went next, ten of them stacked in a column read as a terminal
|
|
277
|
+
* listing: mono is this system's voice for code, metadata and figures, and a
|
|
278
|
+
* navigation heading is none of those. Rank belongs to weight and to a step up
|
|
279
|
+
* the ink ladder, which are the two signals that can outrank a row without
|
|
280
|
+
* changing what kind of thing it is.
|
|
281
|
+
*
|
|
282
|
+
* An open group draws a hairline down its rows. Fifty rows under seven headings
|
|
283
|
+
* have nothing in them saying which heading any given row belongs to — only the
|
|
284
|
+
* distance to the last one, which is gone the moment the list is scrolled.
|
|
285
|
+
*
|
|
286
|
+
* The whole block hides when the rail is collapsed to icons: a heading with no
|
|
287
|
+
* room for its own word is two or three letters and a number, and the rows are
|
|
288
|
+
* still there underneath as icons.
|
|
289
|
+
*/
|
|
290
|
+
declare function SidebarGroup({ label, count, badge, action, collapsible, defaultOpen, children, className, }: SidebarGroupProps): react.JSX.Element;
|
|
291
|
+
interface SidebarBranchProps {
|
|
292
|
+
/** The row's own words, and the name of the group it opens. */
|
|
293
|
+
label: string;
|
|
294
|
+
/** Drawn at the start of the row, and the whole of the row when collapsed. */
|
|
295
|
+
icon?: LucideIcon;
|
|
296
|
+
/** A count or a state at the end of the row. */
|
|
297
|
+
trailing?: ReactNode;
|
|
298
|
+
defaultOpen?: boolean;
|
|
299
|
+
open?: boolean;
|
|
300
|
+
onOpenChange?: (open: boolean) => void;
|
|
301
|
+
children: ReactNode;
|
|
302
|
+
className?: string;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* A row that opens onto more rows.
|
|
306
|
+
*
|
|
307
|
+
* The thing a rail is for and the thing a flat list of groups cannot do: a
|
|
308
|
+
* workspace with projects in it, a folder with documents in it, a service with
|
|
309
|
+
* its environments. `SidebarGroup` is a HEADING over a set — it is not itself a
|
|
310
|
+
* place, and it has no icon and no state. This is a place that contains places,
|
|
311
|
+
* so it is a row like any other and it carries the same icon, trailing slot and
|
|
312
|
+
* hover as its children.
|
|
313
|
+
*
|
|
314
|
+
* The children sit behind the same hairline a group draws, one indent further
|
|
315
|
+
* in, so nesting reads as depth rather than as two unrelated lists. Two levels
|
|
316
|
+
* is what the indent has room for at this width; a third is a tree, and a tree
|
|
317
|
+
* in a 16rem column is a horizontal scrollbar with an outline in it.
|
|
318
|
+
*
|
|
319
|
+
* Collapsed to icons the row becomes its icon and the children are not drawn —
|
|
320
|
+
* there is nowhere for an indent to go, and a nested icon under an unnested one
|
|
321
|
+
* is two glyphs with no visible relationship.
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* <SidebarBranch label="Acme HQ" icon={Building} defaultOpen>
|
|
325
|
+
* <SidebarItem href="/hq" icon={Home}>Home</SidebarItem>
|
|
326
|
+
* <SidebarItem href="/hq/tasks" icon={CheckSquare}>My tasks</SidebarItem>
|
|
327
|
+
* </SidebarBranch>
|
|
328
|
+
*/
|
|
329
|
+
declare function SidebarBranch({ label, icon: Icon, trailing, defaultOpen, open: controlled, onOpenChange, children, className, }: SidebarBranchProps): react.JSX.Element;
|
|
330
|
+
interface SidebarItemProps extends NavItemProps {
|
|
331
|
+
/** A count or a state on the far side of the row. Hidden with the label. */
|
|
332
|
+
trailing?: ReactNode;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* One row.
|
|
336
|
+
*
|
|
337
|
+
* `NavItem` with the two things a RAIL adds: a trailing slot, and an answer for
|
|
338
|
+
* the state where there is no room for words. Collapsed to icons the label is
|
|
339
|
+
* removed from the layout rather than hidden with CSS — a `sr-only` label still
|
|
340
|
+
* occupies the flex row's gap — and moves into a tooltip, because an icon on
|
|
341
|
+
* its own is a guess for everyone and unusable for a screen reader.
|
|
342
|
+
*
|
|
343
|
+
* A row with no `icon` keeps its label collapsed, since hiding it would leave a
|
|
344
|
+
* blank row: the icon is what makes the collapsed state legible, and a rail
|
|
345
|
+
* that collapses needs one on every row.
|
|
346
|
+
*/
|
|
347
|
+
declare function SidebarItem({ trailing, children, className, onClick, ...rest }: SidebarItemProps): react.JSX.Element;
|
|
348
|
+
|
|
349
|
+
export { Sidebar, SidebarBranch, type SidebarBranchProps, type SidebarBreakpoint, type SidebarCollapsible, SidebarContent, SidebarFooter, SidebarGroup, type SidebarGroupProps, SidebarHeader, SidebarInset, SidebarItem, type SidebarItemProps, type SidebarProps, SidebarProvider, type SidebarProviderProps, SidebarSeparator, type SidebarSide, SidebarTrigger, type SidebarTriggerProps, type SidebarVariant, useSidebar };
|