@misoto22/design 0.8.0 → 0.10.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 +2 -2
- 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 +362 -2
- package/dist/agent/index.md +9 -6
- 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/charts/lib/chart.js +16 -1
- package/dist/charts/lib/chart.js.map +1 -1
- package/dist/components/AppShell/AppShell.js +2 -2
- package/dist/components/AppShell/AppShell.js.map +1 -1
- package/dist/components/Button/Button.js +3 -3
- package/dist/components/Button/Button.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/Kbd/Kbd.d.ts +7 -0
- package/dist/components/Kbd/Kbd.js +1 -1
- package/dist/components/Kbd/Kbd.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 +357 -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/semantic.css +4 -2
- package/dist/styles.css +1 -1
- package/dist/themes.css +99 -0
- package/dist/tokens.css +133 -4
- package/dist/tokens.d.ts +5 -0
- package/dist/tokens.js +31 -3
- package/dist/tokens.json +31 -3
- package/package.json +1 -1
- package/skills/misoto22-design/rules/forms.md +3 -1
|
@@ -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-
|
|
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-
|
|
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":[]}
|
|
@@ -12,6 +12,13 @@ interface KbdProps extends HTMLAttributes<HTMLElement> {
|
|
|
12
12
|
* whatever type it sits beside rather than fixing itself at one px value, which
|
|
13
13
|
* is how the same shortcut ended up three different sizes across a page.
|
|
14
14
|
*
|
|
15
|
+
* Every dimension here tracked its context except the leading, which was a
|
|
16
|
+
* literal `1.6` — the body's READING leading, on a chip that is never a
|
|
17
|
+
* paragraph. Inside a control it therefore stood proud of the label beside it
|
|
18
|
+
* and pushed the whole box past its own height token: the masthead's search
|
|
19
|
+
* field measured 39px against a 36px `sm`, and it was this keycap doing it.
|
|
20
|
+
* `--control-lh` is the leading a one-line box gets, and this is one.
|
|
21
|
+
*
|
|
15
22
|
* @example
|
|
16
23
|
* Press <Kbd>⌘</Kbd> <Kbd>K</Kbd> to open the palette.
|
|
17
24
|
*/
|
|
@@ -5,7 +5,7 @@ function Kbd({ children, className, ...rest }) {
|
|
|
5
5
|
"kbd",
|
|
6
6
|
{
|
|
7
7
|
className: cn(
|
|
8
|
-
"inline-flex min-w-[1.6em] items-center justify-center rounded-(--radius-sm) border border-(--rule-2) px-[0.4em] py-[0.1em] font-mono text-[0.8em] leading-
|
|
8
|
+
"inline-flex min-w-[1.6em] items-center justify-center rounded-(--radius-sm) border border-(--rule-2) px-[0.4em] py-[0.1em] font-mono text-[0.8em] leading-(--control-lh) text-(--ink-2)",
|
|
9
9
|
className
|
|
10
10
|
),
|
|
11
11
|
...rest,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/Kbd/Kbd.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface KbdProps extends HTMLAttributes<HTMLElement> {\n children: ReactNode\n}\n\n/**\n * A key on a keyboard, set as one.\n *\n * Renders a `<kbd>`, which is the element that means this — a `<span>` styled\n * to look like a key tells a screen reader nothing. Sized in `em` so it tracks\n * whatever type it sits beside rather than fixing itself at one px value, which\n * is how the same shortcut ended up three different sizes across a page.\n *\n * @example\n * Press <Kbd>⌘</Kbd> <Kbd>K</Kbd> to open the palette.\n */\nexport function Kbd({ children, className, ...rest }: KbdProps) {\n return (\n <kbd\n className={cn(\n 'inline-flex min-w-[1.6em] items-center justify-center rounded-(--radius-sm) border border-(--rule-2) px-[0.4em] py-[0.1em] font-mono text-[0.8em] leading-
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Kbd/Kbd.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface KbdProps extends HTMLAttributes<HTMLElement> {\n children: ReactNode\n}\n\n/**\n * A key on a keyboard, set as one.\n *\n * Renders a `<kbd>`, which is the element that means this — a `<span>` styled\n * to look like a key tells a screen reader nothing. Sized in `em` so it tracks\n * whatever type it sits beside rather than fixing itself at one px value, which\n * is how the same shortcut ended up three different sizes across a page.\n *\n * Every dimension here tracked its context except the leading, which was a\n * literal `1.6` — the body's READING leading, on a chip that is never a\n * paragraph. Inside a control it therefore stood proud of the label beside it\n * and pushed the whole box past its own height token: the masthead's search\n * field measured 39px against a 36px `sm`, and it was this keycap doing it.\n * `--control-lh` is the leading a one-line box gets, and this is one.\n *\n * @example\n * Press <Kbd>⌘</Kbd> <Kbd>K</Kbd> to open the palette.\n */\nexport function Kbd({ children, className, ...rest }: KbdProps) {\n return (\n <kbd\n className={cn(\n 'inline-flex min-w-[1.6em] items-center justify-center rounded-(--radius-sm) border border-(--rule-2) px-[0.4em] py-[0.1em] font-mono text-[0.8em] leading-(--control-lh) text-(--ink-2)',\n className,\n )}\n {...rest}\n >\n {children}\n </kbd>\n )\n}\n\nexport default Kbd\n"],"mappings":"AA2BI;AA1BJ,SAAS,UAAU;AAwBZ,SAAS,IAAI,EAAE,UAAU,WAAW,GAAG,KAAK,GAAa;AAC9D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,cAAQ;","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-
|
|
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-
|
|
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
|
|
@@ -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":"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","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":[]}
|