@misoto22/design 0.3.1 → 0.5.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/article.css +393 -0
- package/dist/components/AppShell/AppShell.d.ts +15 -1
- package/dist/components/AppShell/AppShell.js +8 -4
- package/dist/components/AppShell/AppShell.js.map +1 -1
- package/dist/components/Article/Article.d.ts +51 -0
- package/dist/components/Article/Article.js +21 -0
- package/dist/components/Article/Article.js.map +1 -0
- package/dist/components/Button/Button.js +2 -2
- package/dist/components/Button/Button.js.map +1 -1
- package/dist/components/Calendar/Calendar.d.ts +1 -1
- package/dist/components/Calendar/Calendar.js +259 -24
- package/dist/components/Calendar/Calendar.js.map +1 -1
- package/dist/components/Checkbox/Checkbox.js +1 -1
- package/dist/components/Checkbox/Checkbox.js.map +1 -1
- package/dist/components/Collapsible/Collapsible.d.ts +15 -1
- package/dist/components/Collapsible/Collapsible.js +18 -0
- package/dist/components/Collapsible/Collapsible.js.map +1 -1
- package/dist/components/Combobox/Combobox.js +1 -1
- package/dist/components/Combobox/Combobox.js.map +1 -1
- package/dist/components/Command/Command.js +7 -7
- package/dist/components/Command/Command.js.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.js +2 -2
- package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
- package/dist/components/Diagram/Diagram.d.ts +81 -0
- package/dist/components/Diagram/Diagram.js +139 -0
- package/dist/components/Diagram/Diagram.js.map +1 -0
- package/dist/components/Dialog/Dialog.js +1 -1
- package/dist/components/Dialog/Dialog.js.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.js +2 -2
- package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
- package/dist/components/FigureBand/FigureBand.js +41 -30
- package/dist/components/FigureBand/FigureBand.js.map +1 -1
- package/dist/components/Popover/Popover.js +1 -1
- package/dist/components/Popover/Popover.js.map +1 -1
- package/dist/components/Select/Select.js +2 -2
- 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/Slider/Slider.js +1 -1
- package/dist/components/Slider/Slider.js.map +1 -1
- package/dist/components/Steps/Steps.d.ts +65 -0
- package/dist/components/Steps/Steps.js +55 -0
- package/dist/components/Steps/Steps.js.map +1 -0
- package/dist/components/Switch/Switch.js +1 -1
- package/dist/components/Switch/Switch.js.map +1 -1
- package/dist/components/Tooltip/Tooltip.js +1 -1
- package/dist/components/Tooltip/Tooltip.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/useSelectionIndicator.js +14 -10
- package/dist/lib/useSelectionIndicator.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/themes.css +53 -7
- package/dist/tokens.css +75 -10
- package/dist/tokens.d.ts +8 -0
- package/dist/tokens.js +53 -8
- package/dist/tokens.json +53 -8
- package/package.json +22 -19
|
@@ -13,7 +13,7 @@ function Command({
|
|
|
13
13
|
CommandPrimitive,
|
|
14
14
|
{
|
|
15
15
|
className: cn(
|
|
16
|
-
"flex w-full flex-col overflow-hidden rounded-(--radius) border border-(--
|
|
16
|
+
"flex w-full flex-col overflow-hidden rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) panel-blur",
|
|
17
17
|
className
|
|
18
18
|
),
|
|
19
19
|
...props
|
|
@@ -24,13 +24,13 @@ function CommandInput({
|
|
|
24
24
|
className,
|
|
25
25
|
...props
|
|
26
26
|
}) {
|
|
27
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-
|
|
28
|
-
/* @__PURE__ */ jsx(Search, { size:
|
|
27
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 border-b border-(--rule) px-4", children: [
|
|
28
|
+
/* @__PURE__ */ jsx(Search, { size: 18, strokeWidth: 1.5, "aria-hidden": true, className: "shrink-0 text-(--ink-3-aa)" }),
|
|
29
29
|
/* @__PURE__ */ jsx(
|
|
30
30
|
CommandPrimitive.Input,
|
|
31
31
|
{
|
|
32
32
|
className: cn(
|
|
33
|
-
"h-
|
|
33
|
+
"h-13 w-full bg-transparent text-[15px] text-(--ink) outline-none placeholder:text-(--ink-3-aa)",
|
|
34
34
|
className
|
|
35
35
|
),
|
|
36
36
|
...props
|
|
@@ -111,7 +111,7 @@ function CommandItem({ shortcut, icon, meta, className, children, ...props }) {
|
|
|
111
111
|
CommandPrimitive.Item,
|
|
112
112
|
{
|
|
113
113
|
className: cn(
|
|
114
|
-
"flex cursor-pointer items-center gap-
|
|
114
|
+
"flex cursor-pointer items-center gap-3 rounded-(--radius-row) px-3 py-2.5 text-[15px] text-(--ink-2) outline-none transition-colors duration-(--duration-fast)",
|
|
115
115
|
// The highlighted row is a chosen state, and law 7 says a chosen state
|
|
116
116
|
// reads --accent. The leading rule gives it an edge the eye catches
|
|
117
117
|
// while scrolling, which a fill alone does not.
|
|
@@ -137,12 +137,12 @@ function CommandDialog({ open, onOpenChange, label, children }) {
|
|
|
137
137
|
title: label,
|
|
138
138
|
hideTitle: true,
|
|
139
139
|
showClose: false,
|
|
140
|
-
className: "top-[
|
|
140
|
+
className: "top-[10vh] w-[min(94vw,44rem)] translate-y-0 overflow-hidden p-0 [scrollbar-gutter:auto]",
|
|
141
141
|
children: /* @__PURE__ */ jsx(
|
|
142
142
|
Command,
|
|
143
143
|
{
|
|
144
144
|
label,
|
|
145
|
-
className: "rounded-none border-0 [&_[cmdk-list]]:max-h-[
|
|
145
|
+
className: "rounded-none border-0 [&_[cmdk-list]]:max-h-[26rem] [&_[cmdk-list]]:scroll-hairline",
|
|
146
146
|
children
|
|
147
147
|
}
|
|
148
148
|
)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/Command/Command.tsx"],"sourcesContent":["'use client'\n\nimport { Command as CommandPrimitive } from 'cmdk'\nimport { Search } from 'lucide-react'\nimport type { ComponentProps, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { Dialog, DialogContent } from '../Dialog/Dialog'\nimport { Kbd } from '../Kbd/Kbd'\n\n/**\n * A filterable list of actions — the ⌘K surface.\n *\n * Built on cmdk rather than on a `Select` or a menu, because the interaction is\n * neither: the list is filtered as you type, the highlighted row moves with the\n * arrow keys while focus STAYS in the input, and Enter runs the highlighted\n * row. That is the ARIA combobox pattern, and it is the part nobody should\n * hand-roll — `aria-activedescendant` moving without focus moving is precisely\n * where a home-made palette stops working with a screen reader.\n *\n * @example\n * <Command label=\"Command palette\">\n * <CommandInput placeholder=\"Type a command…\" />\n * <CommandList>\n * <CommandEmpty>Nothing matches.</CommandEmpty>\n * <CommandGroup heading=\"Navigate\">\n * <CommandItem onSelect={go}>Components</CommandItem>\n * </CommandGroup>\n * </CommandList>\n * </Command>\n */\nexport function Command({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive>) {\n return (\n <CommandPrimitive\n className={cn(\n 'flex w-full flex-col overflow-hidden rounded-(--radius) border border-(--rule-2) bg-(--paper)',\n className,\n )}\n {...props}\n />\n )\n}\n\n/** The filter field. Carries the search icon and the combobox semantics. */\nexport function CommandInput({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.Input>) {\n return (\n <div className=\"flex items-center gap-2.5 border-b border-(--rule) px-3.5\">\n <Search size={16} strokeWidth={1.5} aria-hidden className=\"shrink-0 text-(--ink-3-aa)\" />\n <CommandPrimitive.Input\n className={cn(\n 'h-11 w-full bg-transparent text-sm text-(--ink) outline-none placeholder:text-(--ink-3-aa)',\n className,\n )}\n {...props}\n />\n </div>\n )\n}\n\nexport function CommandList({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.List>) {\n return (\n <CommandPrimitive.List\n className={cn('max-h-72 overflow-y-auto overflow-x-hidden p-2 scroll-slim', className)}\n {...props}\n />\n )\n}\n\n/** Shown when the filter matches nothing. Say what would match, not \"no results\". */\nexport function CommandEmpty(props: ComponentProps<typeof CommandPrimitive.Empty>) {\n return (\n <CommandPrimitive.Empty\n className=\"px-3 py-10 text-center text-sm text-(--ink-3-aa)\"\n {...props}\n />\n )\n}\n\n/**\n * The key-hint strip along the bottom.\n *\n * A palette is a keyboard surface whose keys are invisible: nothing on screen\n * says the arrows move the row or that Enter runs it, and a reader who reaches\n * for the mouse has been failed by the design rather than by themselves.\n *\n * @example\n * <CommandFooter>\n * <CommandHint keys={['↑', '↓']}>navigate</CommandHint>\n * <CommandHint keys={['↵']}>open</CommandHint>\n * </CommandFooter>\n */\nexport function CommandFooter({ className, ...props }: ComponentProps<'div'>) {\n return (\n <div\n className={cn(\n 'flex items-center gap-4 border-t border-(--rule) bg-(--paper-2) px-3.5 py-2',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport interface CommandHintProps extends Omit<ComponentProps<'span'>, 'children'> {\n /** The keys this hint describes, printed as `Kbd` chips. */\n keys: string[]\n /** What they do — a verb, lowercase, no sentence. */\n children: ReactNode\n}\n\n/** One key-and-verb pair inside a `CommandFooter`. */\nexport function CommandHint({ keys, children, className, ...props }: CommandHintProps) {\n return (\n <span className={cn('flex items-center gap-1.5 mono-meta text-(--ink-3-aa)', className)} {...props}>\n {keys.map((key) => (\n <Kbd key={key}>{key}</Kbd>\n ))}\n {children}\n </span>\n )\n}\n\nexport function CommandGroup({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.Group>) {\n return (\n <CommandPrimitive.Group\n className={cn(\n 'overflow-hidden [&_[cmdk-group-heading]]:px-2.5 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:eyebrow [&_[cmdk-group-heading]]:text-(--ink-3-aa)',\n className,\n )}\n {...props}\n />\n )\n}\n\n/**\n * A divider between groups.\n *\n * Marked presentational, because ARIA permits a `listbox` to contain only\n * `option` and `group` — and the library renders this as `role=\"separator\"`,\n * which puts a critical `aria-required-children` violation inside every palette\n * that uses one. The grouping is already announced by the groups themselves, so\n * removing the divider from the accessibility tree loses nothing.\n */\nexport function CommandSeparator({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.Separator>) {\n return (\n <CommandPrimitive.Separator\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn('my-1 h-px bg-(--rule)', className)}\n {...props}\n />\n )\n}\n\nexport interface CommandItemProps extends ComponentProps<typeof CommandPrimitive.Item> {\n /** A shortcut printed at the end of the row. */\n shortcut?: string\n /**\n * A leading glyph. Pass the icon element, sized 16.\n *\n * It is what makes a long list scannable — the eye sorts by shape before it\n * reads, and forty identical rows of text defeat that.\n */\n icon?: ReactNode\n /**\n * A quiet note at the end of the row — what kind of thing this is, or its\n * current state. Not a description: a palette that prints a sentence per row\n * stops being scannable at about six of them.\n */\n meta?: ReactNode\n}\n\nexport function CommandItem({ shortcut, icon, meta, className, children, ...props }: CommandItemProps) {\n return (\n <CommandPrimitive.Item\n className={cn(\n 'flex cursor-pointer items-center gap-2.5 rounded-(--radius-sm) px-2.5 py-2 text-sm text-(--ink-2) outline-none transition-colors duration-(--duration-fast)',\n // The highlighted row is a chosen state, and law 7 says a chosen state\n // reads --accent. The leading rule gives it an edge the eye catches\n // while scrolling, which a fill alone does not.\n 'relative data-[selected=true]:bg-(--accent-muted) data-[selected=true]:text-(--ink)',\n 'before:absolute before:inset-y-1 before:start-0 before:w-0.5 before:rounded-(--radius-pill) before:bg-transparent data-[selected=true]:before:bg-(--accent)',\n 'data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-(--disabled-opacity)',\n className,\n )}\n {...props}\n >\n {icon && (\n <span className=\"grid size-4 shrink-0 place-items-center text-(--ink-3-aa) [&_svg]:size-4\">\n {icon}\n </span>\n )}\n {children}\n {meta && <span className=\"ms-auto ps-3 mono-meta text-(--ink-3-aa)\">{meta}</span>}\n {shortcut && <Kbd className={cn(meta ? '' : 'ms-auto')}>{shortcut}</Kbd>}\n </CommandPrimitive.Item>\n )\n}\n\nexport interface CommandDialogProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n /** Names the palette for assistive tech. */\n label: string\n children: ReactNode\n}\n\n/**\n * The palette in a modal, which is how it is nearly always used.\n *\n * The dialog's own padding is removed: a palette is edge-to-edge, and its input\n * is the first thing focus lands on.\n */\nexport function CommandDialog({ open, onOpenChange, label, children }: CommandDialogProps) {\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent\n title={label}\n hideTitle\n showClose={false}\n // Above centre, not at it. A palette is read as a layer over the page\n // rather than as a message about it, and centring it puts the list\n // under the reader's own hands on a laptop.\n className=\"top-[12vh] w-[min(92vw,40rem)] translate-y-0 overflow-hidden p-0\"\n >\n {/* The modal has the whole screen, so its list may be taller than the\n default an inline menu should keep.\n\n `scroll-hairline` rather than `scroll-slim`: eleven pixels of grey\n down the side of a palette is the widest thing in it. */}\n <Command\n label={label}\n className=\"rounded-none border-0 [&_[cmdk-list]]:max-h-[22rem] [&_[cmdk-list]]:scroll-hairline\"\n >\n {children}\n </Command>\n </DialogContent>\n </Dialog>\n )\n}\n\nexport default Command\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Command/Command.tsx"],"sourcesContent":["'use client'\n\nimport { Command as CommandPrimitive } from 'cmdk'\nimport { Search } from 'lucide-react'\nimport type { ComponentProps, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport { Dialog, DialogContent } from '../Dialog/Dialog'\nimport { Kbd } from '../Kbd/Kbd'\n\n/**\n * A filterable list of actions — the ⌘K surface.\n *\n * Built on cmdk rather than on a `Select` or a menu, because the interaction is\n * neither: the list is filtered as you type, the highlighted row moves with the\n * arrow keys while focus STAYS in the input, and Enter runs the highlighted\n * row. That is the ARIA combobox pattern, and it is the part nobody should\n * hand-roll — `aria-activedescendant` moving without focus moving is precisely\n * where a home-made palette stops working with a screen reader.\n *\n * @example\n * <Command label=\"Command palette\">\n * <CommandInput placeholder=\"Type a command…\" />\n * <CommandList>\n * <CommandEmpty>Nothing matches.</CommandEmpty>\n * <CommandGroup heading=\"Navigate\">\n * <CommandItem onSelect={go}>Components</CommandItem>\n * </CommandGroup>\n * </CommandList>\n * </Command>\n */\nexport function Command({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive>) {\n return (\n <CommandPrimitive\n className={cn(\n 'flex w-full flex-col overflow-hidden rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) panel-blur',\n className,\n )}\n {...props}\n />\n )\n}\n\n/** The filter field. Carries the search icon and the combobox semantics. */\nexport function CommandInput({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.Input>) {\n return (\n <div className=\"flex items-center gap-3 border-b border-(--rule) px-4\">\n <Search size={18} strokeWidth={1.5} aria-hidden className=\"shrink-0 text-(--ink-3-aa)\" />\n <CommandPrimitive.Input\n className={cn(\n 'h-13 w-full bg-transparent text-[15px] text-(--ink) outline-none placeholder:text-(--ink-3-aa)',\n className,\n )}\n {...props}\n />\n </div>\n )\n}\n\nexport function CommandList({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.List>) {\n return (\n <CommandPrimitive.List\n className={cn('max-h-72 overflow-y-auto overflow-x-hidden p-2 scroll-slim', className)}\n {...props}\n />\n )\n}\n\n/** Shown when the filter matches nothing. Say what would match, not \"no results\". */\nexport function CommandEmpty(props: ComponentProps<typeof CommandPrimitive.Empty>) {\n return (\n <CommandPrimitive.Empty\n className=\"px-3 py-10 text-center text-sm text-(--ink-3-aa)\"\n {...props}\n />\n )\n}\n\n/**\n * The key-hint strip along the bottom.\n *\n * A palette is a keyboard surface whose keys are invisible: nothing on screen\n * says the arrows move the row or that Enter runs it, and a reader who reaches\n * for the mouse has been failed by the design rather than by themselves.\n *\n * @example\n * <CommandFooter>\n * <CommandHint keys={['↑', '↓']}>navigate</CommandHint>\n * <CommandHint keys={['↵']}>open</CommandHint>\n * </CommandFooter>\n */\nexport function CommandFooter({ className, ...props }: ComponentProps<'div'>) {\n return (\n <div\n className={cn(\n 'flex items-center gap-4 border-t border-(--rule) bg-(--paper-2) px-3.5 py-2',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport interface CommandHintProps extends Omit<ComponentProps<'span'>, 'children'> {\n /** The keys this hint describes, printed as `Kbd` chips. */\n keys: string[]\n /** What they do — a verb, lowercase, no sentence. */\n children: ReactNode\n}\n\n/** One key-and-verb pair inside a `CommandFooter`. */\nexport function CommandHint({ keys, children, className, ...props }: CommandHintProps) {\n return (\n <span className={cn('flex items-center gap-1.5 mono-meta text-(--ink-3-aa)', className)} {...props}>\n {keys.map((key) => (\n <Kbd key={key}>{key}</Kbd>\n ))}\n {children}\n </span>\n )\n}\n\nexport function CommandGroup({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.Group>) {\n return (\n <CommandPrimitive.Group\n className={cn(\n 'overflow-hidden [&_[cmdk-group-heading]]:px-2.5 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:eyebrow [&_[cmdk-group-heading]]:text-(--ink-3-aa)',\n className,\n )}\n {...props}\n />\n )\n}\n\n/**\n * A divider between groups.\n *\n * Marked presentational, because ARIA permits a `listbox` to contain only\n * `option` and `group` — and the library renders this as `role=\"separator\"`,\n * which puts a critical `aria-required-children` violation inside every palette\n * that uses one. The grouping is already announced by the groups themselves, so\n * removing the divider from the accessibility tree loses nothing.\n */\nexport function CommandSeparator({\n className,\n ...props\n}: ComponentProps<typeof CommandPrimitive.Separator>) {\n return (\n <CommandPrimitive.Separator\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn('my-1 h-px bg-(--rule)', className)}\n {...props}\n />\n )\n}\n\nexport interface CommandItemProps extends ComponentProps<typeof CommandPrimitive.Item> {\n /** A shortcut printed at the end of the row. */\n shortcut?: string\n /**\n * A leading glyph. Pass the icon element, sized 16.\n *\n * It is what makes a long list scannable — the eye sorts by shape before it\n * reads, and forty identical rows of text defeat that.\n */\n icon?: ReactNode\n /**\n * A quiet note at the end of the row — what kind of thing this is, or its\n * current state. Not a description: a palette that prints a sentence per row\n * stops being scannable at about six of them.\n */\n meta?: ReactNode\n}\n\nexport function CommandItem({ shortcut, icon, meta, className, children, ...props }: CommandItemProps) {\n return (\n <CommandPrimitive.Item\n className={cn(\n 'flex cursor-pointer items-center gap-3 rounded-(--radius-row) px-3 py-2.5 text-[15px] text-(--ink-2) outline-none transition-colors duration-(--duration-fast)',\n // The highlighted row is a chosen state, and law 7 says a chosen state\n // reads --accent. The leading rule gives it an edge the eye catches\n // while scrolling, which a fill alone does not.\n 'relative data-[selected=true]:bg-(--accent-muted) data-[selected=true]:text-(--ink)',\n 'before:absolute before:inset-y-1 before:start-0 before:w-0.5 before:rounded-(--radius-pill) before:bg-transparent data-[selected=true]:before:bg-(--accent)',\n 'data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-(--disabled-opacity)',\n className,\n )}\n {...props}\n >\n {icon && (\n <span className=\"grid size-4 shrink-0 place-items-center text-(--ink-3-aa) [&_svg]:size-4\">\n {icon}\n </span>\n )}\n {children}\n {meta && <span className=\"ms-auto ps-3 mono-meta text-(--ink-3-aa)\">{meta}</span>}\n {shortcut && <Kbd className={cn(meta ? '' : 'ms-auto')}>{shortcut}</Kbd>}\n </CommandPrimitive.Item>\n )\n}\n\nexport interface CommandDialogProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n /** Names the palette for assistive tech. */\n label: string\n children: ReactNode\n}\n\n/**\n * The palette in a modal, which is how it is nearly always used.\n *\n * The dialog's own padding is removed: a palette is edge-to-edge, and its input\n * is the first thing focus lands on.\n */\nexport function CommandDialog({ open, onOpenChange, label, children }: CommandDialogProps) {\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent\n title={label}\n hideTitle\n showClose={false}\n // Above centre, not at it. A palette is read as a layer over the page\n // rather than as a message about it, and centring it puts the list\n // under the reader's own hands on a laptop.\n // `scrollbar-gutter: auto` undoes the `stable` that `Dialog`'s own\n // `scroll-slim` sets. The dialog never scrolls — the list inside it\n // does — but the reserved gutter was still there, so the list stopped\n // thirteen pixels short of the panel and drew its own hairline inside\n // an empty channel. Two bars, one of them permanently blank.\n className=\"top-[10vh] w-[min(94vw,44rem)] translate-y-0 overflow-hidden p-0 [scrollbar-gutter:auto]\"\n >\n {/* The modal has the whole screen, so its list may be taller than the\n default an inline menu should keep.\n\n `scroll-hairline` rather than `scroll-slim`: eleven pixels of grey\n down the side of a palette is the widest thing in it. */}\n <Command\n label={label}\n className=\"rounded-none border-0 [&_[cmdk-list]]:max-h-[26rem] [&_[cmdk-list]]:scroll-hairline\"\n >\n {children}\n </Command>\n </DialogContent>\n </Dialog>\n )\n}\n\nexport default Command\n"],"mappings":"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","names":[]}
|
|
@@ -17,7 +17,7 @@ function ContextMenuContent({
|
|
|
17
17
|
collisionPadding: 8,
|
|
18
18
|
"data-m22-animated": true,
|
|
19
19
|
className: cn(
|
|
20
|
-
"z-(--z-dropdown) min-w-44 rounded-(--radius) border border-(--
|
|
20
|
+
"z-(--z-dropdown) min-w-44 rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-1.5 panel-blur",
|
|
21
21
|
"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)",
|
|
22
22
|
className
|
|
23
23
|
),
|
|
@@ -36,7 +36,7 @@ function ContextMenuItem({
|
|
|
36
36
|
ContextMenuPrimitive.Item,
|
|
37
37
|
{
|
|
38
38
|
className: cn(
|
|
39
|
-
"flex cursor-pointer items-center gap-2.5 rounded-(--radius-
|
|
39
|
+
"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)",
|
|
40
40
|
destructive ? "text-(--danger)" : "text-(--ink-2) data-[highlighted]:text-(--ink)",
|
|
41
41
|
className
|
|
42
42
|
),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/ContextMenu/ContextMenu.tsx"],"sourcesContent":["'use client'\n\nimport * as ContextMenuPrimitive from '@radix-ui/react-context-menu'\nimport type { LucideIcon } from 'lucide-react'\nimport type { ComponentProps } from 'react'\nimport { cn } from '../../lib/cn'\nimport { useOverlayContainer } from '../../lib/overlay-container'\n\n/** Radix ContextMenu root and trigger, as typed passthroughs. */\nexport const ContextMenu = ContextMenuPrimitive.Root\nexport const ContextMenuTrigger = ContextMenuPrimitive.Trigger\n\n/**\n * The menu a right-click opens.\n *\n * Never the only way to reach an action. A context menu is opened by a\n * secondary pointer button or a keyboard's own menu key, and a meaningful share\n * of readers have neither — a touch user, someone on a trackpad they have not\n * configured, anyone driving the page by keyboard alone. Whatever is in here\n * belongs somewhere reachable too: a row's overflow button, a toolbar.\n *\n * @example\n * <ContextMenu>\n * <ContextMenuTrigger asChild><Card>Right-click me</Card></ContextMenuTrigger>\n * <ContextMenuContent>\n * <ContextMenuItem icon={Copy}>Copy</ContextMenuItem>\n * </ContextMenuContent>\n * </ContextMenu>\n */\nexport function ContextMenuContent({\n className,\n ...rest\n}: ComponentProps<typeof ContextMenuPrimitive.Content>) {\n const container = useOverlayContainer()\n\n return (\n <ContextMenuPrimitive.Portal container={container ?? undefined}>\n <ContextMenuPrimitive.Content\n collisionBoundary={container ?? undefined}\n collisionPadding={8}\n data-m22-animated\n className={cn(\n 'z-(--z-dropdown) min-w-44 rounded-(--radius) border border-(--
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ContextMenu/ContextMenu.tsx"],"sourcesContent":["'use client'\n\nimport * as ContextMenuPrimitive from '@radix-ui/react-context-menu'\nimport type { LucideIcon } from 'lucide-react'\nimport type { ComponentProps } from 'react'\nimport { cn } from '../../lib/cn'\nimport { useOverlayContainer } from '../../lib/overlay-container'\n\n/** Radix ContextMenu root and trigger, as typed passthroughs. */\nexport const ContextMenu = ContextMenuPrimitive.Root\nexport const ContextMenuTrigger = ContextMenuPrimitive.Trigger\n\n/**\n * The menu a right-click opens.\n *\n * Never the only way to reach an action. A context menu is opened by a\n * secondary pointer button or a keyboard's own menu key, and a meaningful share\n * of readers have neither — a touch user, someone on a trackpad they have not\n * configured, anyone driving the page by keyboard alone. Whatever is in here\n * belongs somewhere reachable too: a row's overflow button, a toolbar.\n *\n * @example\n * <ContextMenu>\n * <ContextMenuTrigger asChild><Card>Right-click me</Card></ContextMenuTrigger>\n * <ContextMenuContent>\n * <ContextMenuItem icon={Copy}>Copy</ContextMenuItem>\n * </ContextMenuContent>\n * </ContextMenu>\n */\nexport function ContextMenuContent({\n className,\n ...rest\n}: ComponentProps<typeof ContextMenuPrimitive.Content>) {\n const container = useOverlayContainer()\n\n return (\n <ContextMenuPrimitive.Portal container={container ?? undefined}>\n <ContextMenuPrimitive.Content\n collisionBoundary={container ?? undefined}\n collisionPadding={8}\n data-m22-animated\n className={cn(\n 'z-(--z-dropdown) min-w-44 rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-1.5 panel-blur',\n 'data-[state=open]:animate-[m22-pop-in_var(--duration-fast)_var(--ease-out-expo)] data-[state=closed]:animate-[m22-pop-out_var(--duration-fast)_var(--ease)] origin-(--radix-popper-transform-origin)',\n className,\n )}\n {...rest}\n />\n </ContextMenuPrimitive.Portal>\n )\n}\n\nexport interface ContextMenuItemProps\n extends ComponentProps<typeof ContextMenuPrimitive.Item> {\n icon?: LucideIcon\n destructive?: boolean\n}\n\n/** A row. Highlight follows `data-highlighted`, which covers hover and keyboard. */\nexport function ContextMenuItem({\n icon: Icon,\n destructive = false,\n className,\n children,\n ...rest\n}: ContextMenuItemProps) {\n return (\n <ContextMenuPrimitive.Item\n className={cn(\n 'flex cursor-pointer items-center gap-2.5 rounded-(--radius-row) px-2.5 py-2 text-sm outline-none transition-colors duration-(--duration-fast) data-[highlighted]:bg-(--stone) data-[disabled]:pointer-events-none data-[disabled]:opacity-(--disabled-opacity)',\n destructive ? 'text-(--danger)' : 'text-(--ink-2) data-[highlighted]:text-(--ink)',\n className,\n )}\n {...rest}\n >\n {Icon && <Icon size={16} strokeWidth={1.5} className=\"shrink-0\" aria-hidden />}\n {children}\n </ContextMenuPrimitive.Item>\n )\n}\n\n/** Hairline divider between groups. */\nexport function ContextMenuSeparator({\n className,\n ...rest\n}: ComponentProps<typeof ContextMenuPrimitive.Separator>) {\n return (\n <ContextMenuPrimitive.Separator className={cn('my-1 h-px bg-(--rule)', className)} {...rest} />\n )\n}\n\n/** Mono eyebrow heading for a group. */\nexport function ContextMenuLabel({\n className,\n ...rest\n}: ComponentProps<typeof ContextMenuPrimitive.Label>) {\n return (\n <ContextMenuPrimitive.Label\n className={cn('px-2.5 py-1.5 eyebrow text-(--ink-3-aa)', className)}\n {...rest}\n />\n )\n}\n\nexport default ContextMenu\n"],"mappings":";AAqCM,cA8BF,YA9BE;AAnCN,YAAY,0BAA0B;AAGtC,SAAS,UAAU;AACnB,SAAS,2BAA2B;AAG7B,MAAM,cAAc,qBAAqB;AACzC,MAAM,qBAAqB,qBAAqB;AAmBhD,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA,GAAG;AACL,GAAwD;AACtD,QAAM,YAAY,oBAAoB;AAEtC,SACE,oBAAC,qBAAqB,QAArB,EAA4B,WAAW,aAAa,QACnD;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,mBAAmB,aAAa;AAAA,MAChC,kBAAkB;AAAA,MAClB,qBAAiB;AAAA,MACjB,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN,GACF;AAEJ;AASO,SAAS,gBAAgB;AAAA,EAC9B,MAAM;AAAA,EACN,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAyB;AACvB,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,cAAc,oBAAoB;AAAA,QAClC;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA,gBAAQ,oBAAC,QAAK,MAAM,IAAI,aAAa,KAAK,WAAU,YAAW,eAAW,MAAC;AAAA,QAC3E;AAAA;AAAA;AAAA,EACH;AAEJ;AAGO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA,GAAG;AACL,GAA0D;AACxD,SACE,oBAAC,qBAAqB,WAArB,EAA+B,WAAW,GAAG,yBAAyB,SAAS,GAAI,GAAG,MAAM;AAEjG;AAGO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA,GAAG;AACL,GAAsD;AACpD,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,WAAW,GAAG,2CAA2C,SAAS;AAAA,MACjE,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,IAAO,sBAAQ;","names":[]}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
interface DiagramNode {
|
|
4
|
+
/** Only needed when an edge names this node. */
|
|
5
|
+
id?: string;
|
|
6
|
+
/** The node's name, set in the interface face. */
|
|
7
|
+
label: string;
|
|
8
|
+
/** What it is — one short line, a step back. */
|
|
9
|
+
note?: string;
|
|
10
|
+
/** The one node the diagram is about. At most one or two per diagram. */
|
|
11
|
+
accent?: boolean;
|
|
12
|
+
/** How this node's children are laid out. Defaults to a row. */
|
|
13
|
+
direction?: 'row' | 'column';
|
|
14
|
+
children?: DiagramNode[];
|
|
15
|
+
/** A line printed under the frame, outside it — what the box means. */
|
|
16
|
+
footnote?: string;
|
|
17
|
+
}
|
|
18
|
+
interface DiagramEdge {
|
|
19
|
+
from: string;
|
|
20
|
+
to: string;
|
|
21
|
+
label?: string;
|
|
22
|
+
}
|
|
23
|
+
interface DiagramSpec {
|
|
24
|
+
/** How the top-level nodes are laid out. Defaults to a row. */
|
|
25
|
+
direction?: 'row' | 'column';
|
|
26
|
+
nodes: DiagramNode[];
|
|
27
|
+
/** Arrows between adjacent top-level nodes, by id. */
|
|
28
|
+
edges?: DiagramEdge[];
|
|
29
|
+
/** The figure's caption. */
|
|
30
|
+
caption?: string;
|
|
31
|
+
/** Names the figure for assistive tech when the caption does not. */
|
|
32
|
+
label?: string;
|
|
33
|
+
}
|
|
34
|
+
interface DiagramProps {
|
|
35
|
+
spec: DiagramSpec;
|
|
36
|
+
className?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A flow or architecture diagram, drawn out of the system's own parts.
|
|
40
|
+
*
|
|
41
|
+
* The alternative it replaces is a fenced block of box-drawing characters —
|
|
42
|
+
* `┌──────┬──────┐` — which is a picture rendered in a font chosen for code. It
|
|
43
|
+
* inherits the code block's frame and scrollbar, so a diagram reads as terminal
|
|
44
|
+
* output; it cannot wrap, so on a phone it either overflows or is scaled to
|
|
45
|
+
* nothing; the box edges are text, so a screen reader reads the rules out loud;
|
|
46
|
+
* and none of it responds to the theme.
|
|
47
|
+
*
|
|
48
|
+
* The other alternative is a diagramming library, which is several hundred
|
|
49
|
+
* kilobytes of layout engine, renders after hydration, and draws in its own
|
|
50
|
+
* palette. This is neither: hairline frames on the radius scale, mono labels,
|
|
51
|
+
* the muted step for anything supporting — so a diagram belongs to the page it
|
|
52
|
+
* sits on. It server-renders, because it is markup.
|
|
53
|
+
*
|
|
54
|
+
* NESTING IS CONTAINMENT, which is what most architecture diagrams actually
|
|
55
|
+
* describe: this is inside that, and these two sit beside each other. Edges are
|
|
56
|
+
* for the sequence between siblings, not for arbitrary wiring — a diagram that
|
|
57
|
+
* needs arbitrary wiring is a diagram that wants a drawing, and this will not
|
|
58
|
+
* pretend otherwise.
|
|
59
|
+
*
|
|
60
|
+
* It takes a spec rather than markup, so a fenced ```diagram block in an
|
|
61
|
+
* article and a hand-written figure on a page are one renderer and one look —
|
|
62
|
+
* and a wrong diagram is corrected by editing data.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* <Diagram
|
|
66
|
+
* spec={{
|
|
67
|
+
* caption: 'One request, end to end.',
|
|
68
|
+
* edges: [{ from: 'edge', to: 'app', label: 'HTTPS' }],
|
|
69
|
+
* nodes: [
|
|
70
|
+
* { id: 'edge', label: 'Edge', note: 'CDN' },
|
|
71
|
+
* { id: 'app', label: 'Application', accent: true, children: [
|
|
72
|
+
* { label: 'Router' },
|
|
73
|
+
* { label: 'Handlers' },
|
|
74
|
+
* ] },
|
|
75
|
+
* ],
|
|
76
|
+
* }}
|
|
77
|
+
* />
|
|
78
|
+
*/
|
|
79
|
+
declare function Diagram({ spec, className }: DiagramProps): react.JSX.Element;
|
|
80
|
+
|
|
81
|
+
export { Diagram, type DiagramEdge, type DiagramNode, type DiagramProps, type DiagramSpec, Diagram as default };
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../lib/cn.js";
|
|
3
|
+
function Diagram({ spec, className }) {
|
|
4
|
+
const { nodes, edges = [], direction = "row", caption, label } = spec;
|
|
5
|
+
return /* @__PURE__ */ jsxs("figure", { className: cn("m-0", className), role: "group", "aria-label": label ?? caption ?? void 0, children: [
|
|
6
|
+
/* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-(--radius-lg) border border-(--rule) bg-(--paper-2) p-[clamp(1.125rem,calc(2.5*var(--fluid)),1.875rem)] scroll-slim", children: /* @__PURE__ */ jsx(NodeList, { nodes, edges, direction, depth: 0 }) }),
|
|
7
|
+
caption && // A <div>, not a <figcaption>. A long-form stylesheet dropped around
|
|
8
|
+
// this will style `figcaption` and `p` unlayered, and an unlayered rule
|
|
9
|
+
// beats a utility whatever the specificity — inside an article a real
|
|
10
|
+
// figcaption came out uppercased and every label in the diagram
|
|
11
|
+
// inherited a paragraph margin. Nothing here uses a tag prose reaches
|
|
12
|
+
// for.
|
|
13
|
+
/* @__PURE__ */ jsx("div", { className: "mt-3 mono-meta leading-[1.6] text-(--ink-3-aa)", children: caption })
|
|
14
|
+
] });
|
|
15
|
+
}
|
|
16
|
+
function NodeList({
|
|
17
|
+
nodes,
|
|
18
|
+
edges,
|
|
19
|
+
direction,
|
|
20
|
+
depth
|
|
21
|
+
}) {
|
|
22
|
+
const row = direction === "row";
|
|
23
|
+
return /* @__PURE__ */ jsx("div", { className: cn("flex min-w-0 gap-3", row ? "flex-col sm:flex-row" : "flex-col"), children: nodes.map((node, index) => /* @__PURE__ */ jsx(
|
|
24
|
+
NodeFrame,
|
|
25
|
+
{
|
|
26
|
+
node,
|
|
27
|
+
edges,
|
|
28
|
+
depth,
|
|
29
|
+
lead: index > 0 ? findEdge(edges, nodes[index - 1], node) : void 0,
|
|
30
|
+
row
|
|
31
|
+
},
|
|
32
|
+
node.id ?? `${node.label}-${index}`
|
|
33
|
+
)) });
|
|
34
|
+
}
|
|
35
|
+
function findEdge(edges, from, to) {
|
|
36
|
+
if (!from.id || !to.id) return void 0;
|
|
37
|
+
return edges.find((edge) => edge.from === from.id && edge.to === to.id);
|
|
38
|
+
}
|
|
39
|
+
function NodeFrame({
|
|
40
|
+
node,
|
|
41
|
+
edges,
|
|
42
|
+
depth,
|
|
43
|
+
lead,
|
|
44
|
+
row
|
|
45
|
+
}) {
|
|
46
|
+
const children = node.children ?? [];
|
|
47
|
+
const isGroup = children.length > 0;
|
|
48
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
49
|
+
lead && /* @__PURE__ */ jsx(EdgeMark, { label: lead.label, row }),
|
|
50
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
51
|
+
isGroup ? /* @__PURE__ */ jsxs(
|
|
52
|
+
"div",
|
|
53
|
+
{
|
|
54
|
+
className: cn(
|
|
55
|
+
"min-w-0 flex-1 border-t pt-3",
|
|
56
|
+
depth === 0 ? "border-(--ink)" : "border-(--rule-2)"
|
|
57
|
+
),
|
|
58
|
+
children: [
|
|
59
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline gap-x-3 gap-y-0.5", children: [
|
|
60
|
+
/* @__PURE__ */ jsx("span", { className: "font-sans text-[13.5px] leading-[1.35] text-(--ink)", children: node.label }),
|
|
61
|
+
node.note && /* @__PURE__ */ jsx("span", { className: "mono-meta text-(--ink-3-aa)", children: node.note })
|
|
62
|
+
] }),
|
|
63
|
+
/* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
|
|
64
|
+
NodeList,
|
|
65
|
+
{
|
|
66
|
+
nodes: children,
|
|
67
|
+
edges,
|
|
68
|
+
direction: node.direction ?? "row",
|
|
69
|
+
depth: depth + 1
|
|
70
|
+
}
|
|
71
|
+
) })
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
) : /* @__PURE__ */ jsxs(
|
|
75
|
+
"div",
|
|
76
|
+
{
|
|
77
|
+
className: cn(
|
|
78
|
+
"flex min-w-0 flex-1 flex-col justify-center rounded-(--radius) px-3.5 py-3",
|
|
79
|
+
node.accent ? "bg-(--accent)" : "border border-(--rule-2) bg-(--paper)"
|
|
80
|
+
),
|
|
81
|
+
children: [
|
|
82
|
+
/* @__PURE__ */ jsx(
|
|
83
|
+
"span",
|
|
84
|
+
{
|
|
85
|
+
className: cn(
|
|
86
|
+
"block break-words font-sans text-[13.5px] leading-[1.35]",
|
|
87
|
+
node.accent ? "text-(--accent-foreground)" : "text-(--ink)"
|
|
88
|
+
),
|
|
89
|
+
children: node.label
|
|
90
|
+
}
|
|
91
|
+
),
|
|
92
|
+
node.note && /* @__PURE__ */ jsx(
|
|
93
|
+
"span",
|
|
94
|
+
{
|
|
95
|
+
className: cn(
|
|
96
|
+
"mt-1 block mono-meta",
|
|
97
|
+
node.accent ? "text-(--accent-foreground) opacity-70" : "text-(--ink-3-aa)"
|
|
98
|
+
),
|
|
99
|
+
children: node.note
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
),
|
|
105
|
+
node.footnote && /* @__PURE__ */ jsx("span", { className: "mt-2 block mono-meta text-(--ink-3-aa)", children: node.footnote })
|
|
106
|
+
] })
|
|
107
|
+
] });
|
|
108
|
+
}
|
|
109
|
+
function EdgeMark({ label, row }) {
|
|
110
|
+
return /* @__PURE__ */ jsxs(
|
|
111
|
+
"div",
|
|
112
|
+
{
|
|
113
|
+
"aria-hidden": "true",
|
|
114
|
+
className: cn(
|
|
115
|
+
"flex flex-none items-center justify-center gap-1.5 text-(--ink-3-aa)",
|
|
116
|
+
row ? "sm:flex-col" : "flex-col"
|
|
117
|
+
),
|
|
118
|
+
children: [
|
|
119
|
+
/* @__PURE__ */ jsx(
|
|
120
|
+
"span",
|
|
121
|
+
{
|
|
122
|
+
className: cn(
|
|
123
|
+
"font-mono text-[13px] leading-none",
|
|
124
|
+
row ? "rotate-90 sm:rotate-0 sm:rtl:-scale-x-100" : "rotate-90"
|
|
125
|
+
),
|
|
126
|
+
children: "\u2192"
|
|
127
|
+
}
|
|
128
|
+
),
|
|
129
|
+
label && /* @__PURE__ */ jsx("span", { className: "mono-meta whitespace-nowrap", children: label })
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
var Diagram_default = Diagram;
|
|
135
|
+
export {
|
|
136
|
+
Diagram,
|
|
137
|
+
Diagram_default as default
|
|
138
|
+
};
|
|
139
|
+
//# sourceMappingURL=Diagram.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Diagram/Diagram.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface DiagramNode {\n /** Only needed when an edge names this node. */\n id?: string\n /** The node's name, set in the interface face. */\n label: string\n /** What it is — one short line, a step back. */\n note?: string\n /** The one node the diagram is about. At most one or two per diagram. */\n accent?: boolean\n /** How this node's children are laid out. Defaults to a row. */\n direction?: 'row' | 'column'\n children?: DiagramNode[]\n /** A line printed under the frame, outside it — what the box means. */\n footnote?: string\n}\n\nexport interface DiagramEdge {\n from: string\n to: string\n label?: string\n}\n\nexport interface DiagramSpec {\n /** How the top-level nodes are laid out. Defaults to a row. */\n direction?: 'row' | 'column'\n nodes: DiagramNode[]\n /** Arrows between adjacent top-level nodes, by id. */\n edges?: DiagramEdge[]\n /** The figure's caption. */\n caption?: string\n /** Names the figure for assistive tech when the caption does not. */\n label?: string\n}\n\nexport interface DiagramProps {\n spec: DiagramSpec\n className?: string\n}\n\n/**\n * A flow or architecture diagram, drawn out of the system's own parts.\n *\n * The alternative it replaces is a fenced block of box-drawing characters —\n * `┌──────┬──────┐` — which is a picture rendered in a font chosen for code. It\n * inherits the code block's frame and scrollbar, so a diagram reads as terminal\n * output; it cannot wrap, so on a phone it either overflows or is scaled to\n * nothing; the box edges are text, so a screen reader reads the rules out loud;\n * and none of it responds to the theme.\n *\n * The other alternative is a diagramming library, which is several hundred\n * kilobytes of layout engine, renders after hydration, and draws in its own\n * palette. This is neither: hairline frames on the radius scale, mono labels,\n * the muted step for anything supporting — so a diagram belongs to the page it\n * sits on. It server-renders, because it is markup.\n *\n * NESTING IS CONTAINMENT, which is what most architecture diagrams actually\n * describe: this is inside that, and these two sit beside each other. Edges are\n * for the sequence between siblings, not for arbitrary wiring — a diagram that\n * needs arbitrary wiring is a diagram that wants a drawing, and this will not\n * pretend otherwise.\n *\n * It takes a spec rather than markup, so a fenced ```diagram block in an\n * article and a hand-written figure on a page are one renderer and one look —\n * and a wrong diagram is corrected by editing data.\n *\n * @example\n * <Diagram\n * spec={{\n * caption: 'One request, end to end.',\n * edges: [{ from: 'edge', to: 'app', label: 'HTTPS' }],\n * nodes: [\n * { id: 'edge', label: 'Edge', note: 'CDN' },\n * { id: 'app', label: 'Application', accent: true, children: [\n * { label: 'Router' },\n * { label: 'Handlers' },\n * ] },\n * ],\n * }}\n * />\n */\nexport function Diagram({ spec, className }: DiagramProps) {\n const { nodes, edges = [], direction = 'row', caption, label } = spec\n\n return (\n <figure className={cn('m-0', className)} role=\"group\" aria-label={label ?? caption ?? undefined}>\n <div className=\"overflow-x-auto rounded-(--radius-lg) border border-(--rule) bg-(--paper-2) p-[clamp(1.125rem,calc(2.5*var(--fluid)),1.875rem)] scroll-slim\">\n <NodeList nodes={nodes} edges={edges} direction={direction} depth={0} />\n </div>\n {caption && (\n // A <div>, not a <figcaption>. A long-form stylesheet dropped around\n // this will style `figcaption` and `p` unlayered, and an unlayered rule\n // beats a utility whatever the specificity — inside an article a real\n // figcaption came out uppercased and every label in the diagram\n // inherited a paragraph margin. Nothing here uses a tag prose reaches\n // for.\n <div className=\"mt-3 mono-meta leading-[1.6] text-(--ink-3-aa)\">{caption}</div>\n )}\n </figure>\n )\n}\n\n/** One rank of the diagram: the nodes, and any arrows between them. */\nfunction NodeList({\n nodes,\n edges,\n direction,\n depth,\n}: {\n nodes: DiagramNode[]\n edges: DiagramEdge[]\n direction: 'row' | 'column'\n depth: number\n}) {\n const row = direction === 'row'\n\n return (\n <div className={cn('flex min-w-0 gap-3', row ? 'flex-col sm:flex-row' : 'flex-col')}>\n {nodes.map((node, index) => (\n <NodeFrame\n key={node.id ?? `${node.label}-${index}`}\n node={node}\n edges={edges}\n depth={depth}\n lead={index > 0 ? findEdge(edges, nodes[index - 1]!, node) : undefined}\n row={row}\n />\n ))}\n </div>\n )\n}\n\n/** The arrow that sits between two adjacent nodes, when an edge names them. */\nfunction findEdge(edges: DiagramEdge[], from: DiagramNode, to: DiagramNode): DiagramEdge | undefined {\n if (!from.id || !to.id) return undefined\n return edges.find((edge) => edge.from === from.id && edge.to === to.id)\n}\n\n/**\n * A node is one of two things, and they are drawn differently on purpose.\n *\n * A LEAF is a plate: a bordered card on paper, or ink-filled when it is what\n * the diagram is about. A CONTAINER is a band: a labelled hairline with its\n * children underneath, and NO frame of its own.\n *\n * That distinction is the whole design. Drawing a container as another box puts\n * three borders around anything two levels deep — the figure's frame, the\n * group's, and the plate's — and the result reads as packaging rather than as\n * structure. A rule with a name on it says \"these belong together\" using the\n * device every section head already uses, and leaves the borders to the things\n * that are actually objects.\n */\nfunction NodeFrame({\n node,\n edges,\n depth,\n lead,\n row,\n}: {\n node: DiagramNode\n edges: DiagramEdge[]\n depth: number\n lead?: DiagramEdge\n row: boolean\n}) {\n const children = node.children ?? []\n const isGroup = children.length > 0\n\n return (\n <>\n {lead && <EdgeMark label={lead.label} row={row} />}\n <div className=\"flex min-w-0 flex-1 flex-col\">\n {isGroup ? (\n <div\n className={cn(\n 'min-w-0 flex-1 border-t pt-3',\n depth === 0 ? 'border-(--ink)' : 'border-(--rule-2)',\n )}\n >\n <div className=\"flex flex-wrap items-baseline gap-x-3 gap-y-0.5\">\n <span className=\"font-sans text-[13.5px] leading-[1.35] text-(--ink)\">\n {node.label}\n </span>\n {node.note && <span className=\"mono-meta text-(--ink-3-aa)\">{node.note}</span>}\n </div>\n <div className=\"mt-3\">\n <NodeList\n nodes={children}\n edges={edges}\n direction={node.direction ?? 'row'}\n depth={depth + 1}\n />\n </div>\n </div>\n ) : (\n <div\n className={cn(\n 'flex min-w-0 flex-1 flex-col justify-center rounded-(--radius) px-3.5 py-3',\n node.accent ? 'bg-(--accent)' : 'border border-(--rule-2) bg-(--paper)',\n )}\n >\n {/* An identifier can be one long word — `TenantMainMiddleware` —\n with no break opportunity, so it runs into the plate's edge\n unless it is allowed to break inside itself. */}\n <span\n className={cn(\n 'block break-words font-sans text-[13.5px] leading-[1.35]',\n node.accent ? 'text-(--accent-foreground)' : 'text-(--ink)',\n )}\n >\n {node.label}\n </span>\n {node.note && (\n <span\n className={cn(\n 'mt-1 block mono-meta',\n node.accent ? 'text-(--accent-foreground) opacity-70' : 'text-(--ink-3-aa)',\n )}\n >\n {node.note}\n </span>\n )}\n </div>\n )}\n\n {node.footnote && (\n <span className=\"mt-2 block mono-meta text-(--ink-3-aa)\">{node.footnote}</span>\n )}\n </div>\n </>\n )\n}\n\n/**\n * The connector. An arrow and, when the edge is named, the name under it —\n * hidden from assistive tech, which reads the nodes in document order anyway\n * and has no use for a glyph pointing at the next one.\n */\nfunction EdgeMark({ label, row }: { label?: string; row: boolean }): ReactNode {\n return (\n <div\n aria-hidden=\"true\"\n className={cn(\n 'flex flex-none items-center justify-center gap-1.5 text-(--ink-3-aa)',\n row ? 'sm:flex-col' : 'flex-col',\n )}\n >\n {/* One glyph, turned. A row stacks into a column below `sm`, so the arrow\n has to point the way the layout actually runs at that width. In RTL the\n inline axis reverses, so the flat arrow is mirrored with it. */}\n <span\n className={cn(\n 'font-mono text-[13px] leading-none',\n row ? 'rotate-90 sm:rotate-0 sm:rtl:-scale-x-100' : 'rotate-90',\n )}\n >\n →\n </span>\n {label && <span className=\"mono-meta whitespace-nowrap\">{label}</span>}\n </div>\n )\n}\n\nexport default Diagram\n"],"mappings":"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","names":[]}
|
|
@@ -39,7 +39,7 @@ function DialogContent({
|
|
|
39
39
|
{
|
|
40
40
|
"data-m22-animated": true,
|
|
41
41
|
className: cn(
|
|
42
|
-
"fixed left-1/2 top-1/2 z-(--z-modal) max-h-[85vh] w-[min(92vw,32rem)] -translate-x-1/2 -translate-y-1/2 overflow-auto rounded-(--radius-lg) border border-(--
|
|
42
|
+
"fixed left-1/2 top-1/2 z-(--z-modal) max-h-[85vh] w-[min(92vw,32rem)] -translate-x-1/2 -translate-y-1/2 overflow-auto rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-6 panel-blur scroll-slim",
|
|
43
43
|
"data-[state=open]:animate-[m22-panel-in_var(--duration-base)_var(--ease)]",
|
|
44
44
|
className
|
|
45
45
|
),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/Dialog/Dialog.tsx"],"sourcesContent":["'use client'\n\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport { X } from 'lucide-react'\nimport type { ComponentProps, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\n/** Radix Dialog root + trigger + close, re-exported as typed passthroughs. */\nexport const Dialog = DialogPrimitive.Root\nexport const DialogTrigger = DialogPrimitive.Trigger\nexport const DialogClose = DialogPrimitive.Close\n\n/** Title + description stack; renders nothing when both are omitted. */\nfunction DialogHeader({\n title,\n description,\n hidden,\n}: {\n title?: ReactNode\n description?: ReactNode\n hidden: boolean\n}) {\n if (title === undefined && description === undefined) return null\n return (\n <div className={cn('mb-4 flex flex-col gap-1 pe-8', hidden && 'sr-only')}>\n {title !== undefined && (\n <DialogPrimitive.Title className=\"m-0 font-heading text-[length:var(--fs-item)] font-normal text-(--ink)\">\n {title}\n </DialogPrimitive.Title>\n )}\n {description !== undefined && (\n <DialogPrimitive.Description className=\"m-0 text-sm leading-relaxed text-(--ink-3-aa)\">\n {description}\n </DialogPrimitive.Description>\n )}\n </div>\n )\n}\n\nexport interface DialogContentProps\n extends Omit<ComponentProps<typeof DialogPrimitive.Content>, 'title' | 'className'> {\n /** Heading text. When omitted, a visually-hidden title is rendered for a11y. */\n title?: ReactNode\n /** Sub-heading under the title. */\n description?: ReactNode\n className?: string\n /** Show the top-right close control (default true). */\n showClose?: boolean\n /**\n * Keeps the title for assistive tech and hides it visually.\n *\n * For a surface whose purpose is obvious to anyone who can see it — a command\n * palette, a media lightbox — where a printed heading would be furniture. The\n * title itself is never optional: Radix requires one, and a modal with no\n * accessible name drops a screen reader into an unnamed region.\n */\n hideTitle?: boolean\n}\n\n/**\n * A modal surface: portal → scrim → centred panel.\n *\n * Radix owns the focus trap, the escape key, the scroll lock and the\n * `aria-modal` wiring — all of which a hand-rolled dialog gets subtly wrong,\n * usually by leaving focus behind in the page underneath.\n *\n * Radix requires a `Dialog.Title` whether or not one is shown, so a dialog\n * without a visible heading still renders a hidden one rather than logging a\n * warning and shipping an unnamed modal.\n *\n * @example\n * <Dialog>\n * <DialogTrigger asChild><Button>Delete</Button></DialogTrigger>\n * <DialogContent title=\"Delete file\" description=\"This cannot be undone.\">\n * <DialogClose asChild><Button variant=\"secondary\">Cancel</Button></DialogClose>\n * </DialogContent>\n * </Dialog>\n */\nexport function DialogContent({\n title,\n description,\n children,\n className,\n showClose = true,\n hideTitle = false,\n ...rest\n}: DialogContentProps) {\n return (\n <DialogPrimitive.Portal>\n <DialogPrimitive.Overlay\n data-m22-animated\n className=\"fixed inset-0 z-(--z-overlay) bg-(--scrim) data-[state=open]:animate-[m22-fade-in_var(--duration-fast)_var(--ease)]\"\n />\n <DialogPrimitive.Content\n data-m22-animated\n className={cn(\n 'fixed left-1/2 top-1/2 z-(--z-modal) max-h-[85vh] w-[min(92vw,32rem)] -translate-x-1/2 -translate-y-1/2 overflow-auto rounded-(--radius-lg) border border-(--
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Dialog/Dialog.tsx"],"sourcesContent":["'use client'\n\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport { X } from 'lucide-react'\nimport type { ComponentProps, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\n/** Radix Dialog root + trigger + close, re-exported as typed passthroughs. */\nexport const Dialog = DialogPrimitive.Root\nexport const DialogTrigger = DialogPrimitive.Trigger\nexport const DialogClose = DialogPrimitive.Close\n\n/** Title + description stack; renders nothing when both are omitted. */\nfunction DialogHeader({\n title,\n description,\n hidden,\n}: {\n title?: ReactNode\n description?: ReactNode\n hidden: boolean\n}) {\n if (title === undefined && description === undefined) return null\n return (\n <div className={cn('mb-4 flex flex-col gap-1 pe-8', hidden && 'sr-only')}>\n {title !== undefined && (\n <DialogPrimitive.Title className=\"m-0 font-heading text-[length:var(--fs-item)] font-normal text-(--ink)\">\n {title}\n </DialogPrimitive.Title>\n )}\n {description !== undefined && (\n <DialogPrimitive.Description className=\"m-0 text-sm leading-relaxed text-(--ink-3-aa)\">\n {description}\n </DialogPrimitive.Description>\n )}\n </div>\n )\n}\n\nexport interface DialogContentProps\n extends Omit<ComponentProps<typeof DialogPrimitive.Content>, 'title' | 'className'> {\n /** Heading text. When omitted, a visually-hidden title is rendered for a11y. */\n title?: ReactNode\n /** Sub-heading under the title. */\n description?: ReactNode\n className?: string\n /** Show the top-right close control (default true). */\n showClose?: boolean\n /**\n * Keeps the title for assistive tech and hides it visually.\n *\n * For a surface whose purpose is obvious to anyone who can see it — a command\n * palette, a media lightbox — where a printed heading would be furniture. The\n * title itself is never optional: Radix requires one, and a modal with no\n * accessible name drops a screen reader into an unnamed region.\n */\n hideTitle?: boolean\n}\n\n/**\n * A modal surface: portal → scrim → centred panel.\n *\n * Radix owns the focus trap, the escape key, the scroll lock and the\n * `aria-modal` wiring — all of which a hand-rolled dialog gets subtly wrong,\n * usually by leaving focus behind in the page underneath.\n *\n * Radix requires a `Dialog.Title` whether or not one is shown, so a dialog\n * without a visible heading still renders a hidden one rather than logging a\n * warning and shipping an unnamed modal.\n *\n * @example\n * <Dialog>\n * <DialogTrigger asChild><Button>Delete</Button></DialogTrigger>\n * <DialogContent title=\"Delete file\" description=\"This cannot be undone.\">\n * <DialogClose asChild><Button variant=\"secondary\">Cancel</Button></DialogClose>\n * </DialogContent>\n * </Dialog>\n */\nexport function DialogContent({\n title,\n description,\n children,\n className,\n showClose = true,\n hideTitle = false,\n ...rest\n}: DialogContentProps) {\n return (\n <DialogPrimitive.Portal>\n <DialogPrimitive.Overlay\n data-m22-animated\n className=\"fixed inset-0 z-(--z-overlay) bg-(--scrim) data-[state=open]:animate-[m22-fade-in_var(--duration-fast)_var(--ease)]\"\n />\n <DialogPrimitive.Content\n data-m22-animated\n className={cn(\n 'fixed left-1/2 top-1/2 z-(--z-modal) max-h-[85vh] w-[min(92vw,32rem)] -translate-x-1/2 -translate-y-1/2 overflow-auto rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-6 panel-blur scroll-slim',\n 'data-[state=open]:animate-[m22-panel-in_var(--duration-base)_var(--ease)]',\n className,\n )}\n {...rest}\n >\n <DialogHeader title={title} description={description} hidden={hideTitle} />\n\n {title === undefined && (\n <DialogPrimitive.Title className=\"sr-only\">Dialog</DialogPrimitive.Title>\n )}\n\n {showClose && (\n <DialogPrimitive.Close\n aria-label=\"Close\"\n className=\"absolute end-3 top-3 grid size-9 place-items-center rounded-(--radius-pill) text-(--ink-3-aa) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)\"\n >\n <X size={16} strokeWidth={1.5} aria-hidden />\n </DialogPrimitive.Close>\n )}\n\n {children}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n )\n}\n\nexport default Dialog\n"],"mappings":";AAwBI,SAEI,KAFJ;AAtBJ,YAAY,qBAAqB;AACjC,SAAS,SAAS;AAElB,SAAS,UAAU;AAGZ,MAAM,SAAS,gBAAgB;AAC/B,MAAM,gBAAgB,gBAAgB;AACtC,MAAM,cAAc,gBAAgB;AAG3C,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,MAAI,UAAU,UAAa,gBAAgB,OAAW,QAAO;AAC7D,SACE,qBAAC,SAAI,WAAW,GAAG,iCAAiC,UAAU,SAAS,GACpE;AAAA,cAAU,UACT,oBAAC,gBAAgB,OAAhB,EAAsB,WAAU,0EAC9B,iBACH;AAAA,IAED,gBAAgB,UACf,oBAAC,gBAAgB,aAAhB,EAA4B,WAAU,iDACpC,uBACH;AAAA,KAEJ;AAEJ;AAyCO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,GAAG;AACL,GAAuB;AACrB,SACE,qBAAC,gBAAgB,QAAhB,EACC;AAAA;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC,qBAAiB;AAAA,QACjB,WAAU;AAAA;AAAA,IACZ;AAAA,IACA;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC,qBAAiB;AAAA,QACjB,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,QACC,GAAG;AAAA,QAEJ;AAAA,8BAAC,gBAAa,OAAc,aAA0B,QAAQ,WAAW;AAAA,UAExE,UAAU,UACT,oBAAC,gBAAgB,OAAhB,EAAsB,WAAU,WAAU,oBAAM;AAAA,UAGlD,aACC;AAAA,YAAC,gBAAgB;AAAA,YAAhB;AAAA,cACC,cAAW;AAAA,cACX,WAAU;AAAA,cAEV,8BAAC,KAAE,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC;AAAA;AAAA,UAC7C;AAAA,UAGD;AAAA;AAAA;AAAA,IACH;AAAA,KACF;AAEJ;AAEA,IAAO,iBAAQ;","names":[]}
|
|
@@ -20,7 +20,7 @@ function DropdownMenuContent({
|
|
|
20
20
|
sideOffset,
|
|
21
21
|
"data-m22-animated": true,
|
|
22
22
|
className: cn(
|
|
23
|
-
"z-(--z-dropdown) min-w-44 rounded-(--radius) border border-(--
|
|
23
|
+
"z-(--z-dropdown) min-w-44 rounded-(--radius-lg) border border-(--panel-border) bg-(--panel-bg) p-1.5 panel-blur",
|
|
24
24
|
"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)",
|
|
25
25
|
className
|
|
26
26
|
),
|
|
@@ -40,7 +40,7 @@ function DropdownMenuItem({
|
|
|
40
40
|
DropdownMenuPrimitive.Item,
|
|
41
41
|
{
|
|
42
42
|
className: cn(
|
|
43
|
-
"flex cursor-pointer items-center gap-2.5 rounded-(--radius-
|
|
43
|
+
"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)",
|
|
44
44
|
destructive ? "text-(--danger)" : "text-(--ink-2) data-[highlighted]:text-(--ink)",
|
|
45
45
|
className
|
|
46
46
|
),
|
|
@@ -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 type { ComponentProps } from 'react'\nimport { cn } from '../../lib/cn'\nimport { useOverlayContainer } from '../../lib/overlay-container'\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` — Radix has\n * menu variants for both, and a plain item pretending to be a choice loses the\n * checked state a screen reader needs.\n *\n * @example\n * <DropdownMenu>\n * <DropdownMenuTrigger asChild><Button variant=\"secondary\">Menu</Button></DropdownMenuTrigger>\n * <DropdownMenuContent>\n * <DropdownMenuLabel>Account</DropdownMenuLabel>\n * <DropdownMenuItem icon={Settings}>Settings</DropdownMenuItem>\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) border border-(--
|
|
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 type { ComponentProps } from 'react'\nimport { cn } from '../../lib/cn'\nimport { useOverlayContainer } from '../../lib/overlay-container'\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` — Radix has\n * menu variants for both, and a plain item pretending to be a choice loses the\n * checked state a screen reader needs.\n *\n * @example\n * <DropdownMenu>\n * <DropdownMenuTrigger asChild><Button variant=\"secondary\">Menu</Button></DropdownMenuTrigger>\n * <DropdownMenuContent>\n * <DropdownMenuLabel>Account</DropdownMenuLabel>\n * <DropdownMenuItem icon={Settings}>Settings</DropdownMenuItem>\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 /** Optional leading icon, rendered before the label. */\n icon?: LucideIcon\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: 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 {Icon && <Icon size={16} strokeWidth={1.5} className=\"shrink-0\" aria-hidden />}\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/** Mono eyebrow heading for a group of items. */\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 default DropdownMenu\n"],"mappings":";AA2CM,cAwCF,YAxCE;AAzCN,YAAY,2BAA2B;AAGvC,SAAS,UAAU;AACnB,SAAS,2BAA2B;AAG7B,MAAM,eAAe,sBAAsB;AAC3C,MAAM,sBAAsB,sBAAsB;AAuBlD,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;AAgBO,SAAS,iBAAiB;AAAA,EAC/B,MAAM;AAAA,EACN,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,gBAAQ,oBAAC,QAAK,MAAM,IAAI,aAAa,KAAK,WAAU,YAAW,eAAW,MAAC;AAAA,QAC3E;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;AAGO,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;AAEA,IAAO,uBAAQ;","names":[]}
|
|
@@ -12,36 +12,47 @@ function FigureBand({
|
|
|
12
12
|
...rest
|
|
13
13
|
}) {
|
|
14
14
|
if (figures.length === 0) return null;
|
|
15
|
-
return
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
figure
|
|
43
|
-
|
|
44
|
-
|
|
15
|
+
return (
|
|
16
|
+
// The container is a WRAPPER, and it has to be. A container query resolves
|
|
17
|
+
// against an ANCESTOR container, never against the element that declares
|
|
18
|
+
// one — so `@container … @5xl:grid-cols-4` written on the `<dl>` itself
|
|
19
|
+
// could never match, and the band stayed two columns at every width. Four
|
|
20
|
+
// figures on a 1288px page came out as a 2×2 block with a hole in it, which
|
|
21
|
+
// is the arrangement this replaces.
|
|
22
|
+
//
|
|
23
|
+
// `w-full` is load-bearing: `@container` applies `contain: inline-size`,
|
|
24
|
+
// which computes the element's width WITHOUT looking at its contents. As a
|
|
25
|
+
// shrink-to-fit flex item that resolved to zero, and the band rendered as
|
|
26
|
+
// two 0px columns of overlapping text.
|
|
27
|
+
/* @__PURE__ */ jsx("div", { className: "@container w-full", children: /* @__PURE__ */ jsx(
|
|
28
|
+
"dl",
|
|
29
|
+
{
|
|
30
|
+
"aria-label": label,
|
|
31
|
+
className: cn(
|
|
32
|
+
// A container, not a viewport reader: four figures across is a
|
|
33
|
+
// decision about how wide THIS band is, and it is wrong in a 390px
|
|
34
|
+
// frame inside a 1440px window.
|
|
35
|
+
"m-0 grid w-full grid-cols-2 border-y border-(--rule) @3xl:grid-cols-4",
|
|
36
|
+
// Each divider width names the cells that do NOT open a row rather
|
|
37
|
+
// than adding a rule and taking it back.
|
|
38
|
+
"[&>div]:border-(--rule) @max-3xl:[&>div:nth-child(even)]:border-s @max-3xl:[&>div:nth-child(n+3)]:border-t @3xl:[&>div:not(:first-child)]:border-s",
|
|
39
|
+
className
|
|
40
|
+
),
|
|
41
|
+
...rest,
|
|
42
|
+
children: figures.map((figure) => /* @__PURE__ */ jsxs(
|
|
43
|
+
"div",
|
|
44
|
+
{
|
|
45
|
+
className: "bg-transparent px-[clamp(1rem,calc(3*var(--fluid)),1.625rem)] py-[clamp(1.5rem,calc(4*var(--fluid)),2.25rem)]",
|
|
46
|
+
children: [
|
|
47
|
+
/* @__PURE__ */ jsx("dt", { className: "mb-3 eyebrow text-(--ink-3-aa)", children: figure.label }),
|
|
48
|
+
/* @__PURE__ */ jsx("dd", { className: cn("m-0 font-heading font-normal text-(--ink)", VALUE_SCALE[scale]), children: figure.value }),
|
|
49
|
+
figure.note !== void 0 && figure.note !== null && /* @__PURE__ */ jsx("dd", { className: "m-0 mt-2.5 font-sans text-[13px] font-light leading-[1.5] text-(--ink-3-aa)", children: figure.note })
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
figure.id
|
|
53
|
+
))
|
|
54
|
+
}
|
|
55
|
+
) })
|
|
45
56
|
);
|
|
46
57
|
}
|
|
47
58
|
var FigureBand_default = FigureBand;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/FigureBand/FigureBand.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface Figure {\n /** Stable key; also what a caller keys its own data by. */\n id: string\n /** The mono kicker over the value. */\n label: string\n /** The value itself, set in the serif at the band's scale. */\n value: ReactNode\n /** The quiet line under it — a trend, a qualifier, a second fact. */\n note?: ReactNode\n}\n\nexport interface FigureBandProps extends Omit<HTMLAttributes<HTMLDListElement>, 'children'> {\n figures: Figure[]\n /**\n * How large the value is set.\n *\n * `lead` is for a band that is the point of its page — a stats headline,\n * where the numbers ARE the content. `sub` is for a band that supports the\n * page around it, and keeps the ladder honest: a supporting figure must not\n * be set at the same size as the page's own subject.\n */\n scale?: 'lead' | 'sub'\n /** Names the band for assistive tech when it has no visible heading of its own. */\n label?: string\n}\n\nconst VALUE_SCALE = {\n lead: 'text-[length:var(--fs-lead)] leading-[1.1] tracking-[-0.02em]',\n sub: 'text-[length:var(--fs-sub)] leading-[1.2]',\n} as const\n\n/**\n * A row of counted facts, divided by hairlines and nothing else.\n *\n * A `<dl>`, because that is what this is: each cell is a term and its value,\n * and a grid of `<div>`s tells a screen reader nothing about which number goes\n * with which label.\n *\n * Each divider width names the cells that do NOT open a row rather than adding\n * a rule and taking it back — an `undo` at equal specificity resolves on\n * Tailwind's own sort order, which is not something a layout should depend on.\n * Drawn this way, no edge is ever painted past the last column.\n *\n * @example\n * <FigureBand\n * label=\"At a glance\"\n * figures={[\n * { id: 'posts', label: 'Posts', value: '48', note: '+6 this year' },\n * { id: 'photos', label: 'Frames', value: '1,204' },\n * ]}\n * />\n */\nexport function FigureBand({\n figures,\n scale = 'lead',\n label,\n className,\n ...rest\n}: FigureBandProps) {\n if (figures.length === 0) return null\n\n return (\n
|
|
1
|
+
{"version":3,"sources":["../../../src/components/FigureBand/FigureBand.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface Figure {\n /** Stable key; also what a caller keys its own data by. */\n id: string\n /** The mono kicker over the value. */\n label: string\n /** The value itself, set in the serif at the band's scale. */\n value: ReactNode\n /** The quiet line under it — a trend, a qualifier, a second fact. */\n note?: ReactNode\n}\n\nexport interface FigureBandProps extends Omit<HTMLAttributes<HTMLDListElement>, 'children'> {\n figures: Figure[]\n /**\n * How large the value is set.\n *\n * `lead` is for a band that is the point of its page — a stats headline,\n * where the numbers ARE the content. `sub` is for a band that supports the\n * page around it, and keeps the ladder honest: a supporting figure must not\n * be set at the same size as the page's own subject.\n */\n scale?: 'lead' | 'sub'\n /** Names the band for assistive tech when it has no visible heading of its own. */\n label?: string\n}\n\nconst VALUE_SCALE = {\n lead: 'text-[length:var(--fs-lead)] leading-[1.1] tracking-[-0.02em]',\n sub: 'text-[length:var(--fs-sub)] leading-[1.2]',\n} as const\n\n/**\n * A row of counted facts, divided by hairlines and nothing else.\n *\n * A `<dl>`, because that is what this is: each cell is a term and its value,\n * and a grid of `<div>`s tells a screen reader nothing about which number goes\n * with which label.\n *\n * Each divider width names the cells that do NOT open a row rather than adding\n * a rule and taking it back — an `undo` at equal specificity resolves on\n * Tailwind's own sort order, which is not something a layout should depend on.\n * Drawn this way, no edge is ever painted past the last column.\n *\n * @example\n * <FigureBand\n * label=\"At a glance\"\n * figures={[\n * { id: 'posts', label: 'Posts', value: '48', note: '+6 this year' },\n * { id: 'photos', label: 'Frames', value: '1,204' },\n * ]}\n * />\n */\nexport function FigureBand({\n figures,\n scale = 'lead',\n label,\n className,\n ...rest\n}: FigureBandProps) {\n if (figures.length === 0) return null\n\n return (\n // The container is a WRAPPER, and it has to be. A container query resolves\n // against an ANCESTOR container, never against the element that declares\n // one — so `@container … @5xl:grid-cols-4` written on the `<dl>` itself\n // could never match, and the band stayed two columns at every width. Four\n // figures on a 1288px page came out as a 2×2 block with a hole in it, which\n // is the arrangement this replaces.\n //\n // `w-full` is load-bearing: `@container` applies `contain: inline-size`,\n // which computes the element's width WITHOUT looking at its contents. As a\n // shrink-to-fit flex item that resolved to zero, and the band rendered as\n // two 0px columns of overlapping text.\n <div className=\"@container w-full\">\n <dl\n aria-label={label}\n className={cn(\n // A container, not a viewport reader: four figures across is a\n // decision about how wide THIS band is, and it is wrong in a 390px\n // frame inside a 1440px window.\n 'm-0 grid w-full grid-cols-2 border-y border-(--rule) @3xl:grid-cols-4',\n // Each divider width names the cells that do NOT open a row rather\n // than adding a rule and taking it back.\n '[&>div]:border-(--rule) @max-3xl:[&>div:nth-child(even)]:border-s @max-3xl:[&>div:nth-child(n+3)]:border-t @3xl:[&>div:not(:first-child)]:border-s',\n className,\n )}\n {...rest}\n >\n {figures.map((figure) => (\n <div\n key={figure.id}\n className=\"bg-transparent px-[clamp(1rem,calc(3*var(--fluid)),1.625rem)] py-[clamp(1.5rem,calc(4*var(--fluid)),2.25rem)]\"\n >\n <dt className=\"mb-3 eyebrow text-(--ink-3-aa)\">{figure.label}</dt>\n <dd className={cn('m-0 font-heading font-normal text-(--ink)', VALUE_SCALE[scale])}>\n {figure.value}\n </dd>\n {figure.note !== undefined && figure.note !== null && (\n <dd className=\"m-0 mt-2.5 font-sans text-[13px] font-light leading-[1.5] text-(--ink-3-aa)\">\n {figure.note}\n </dd>\n )}\n </div>\n ))}\n </dl>\n </div>\n )\n}\n\nexport default FigureBand\n"],"mappings":"AA4FU,SAIE,KAJF;AA3FV,SAAS,UAAU;AA4BnB,MAAM,cAAc;AAAA,EAClB,MAAM;AAAA,EACN,KAAK;AACP;AAuBO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAoB;AAClB,MAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAYE,oBAAC,SAAI,WAAU,qBACb;AAAA,MAAC;AAAA;AAAA,QACC,cAAY;AAAA,QACZ,WAAW;AAAA;AAAA;AAAA;AAAA,UAIT;AAAA;AAAA;AAAA,UAGA;AAAA,UACA;AAAA,QACF;AAAA,QACC,GAAG;AAAA,QAEH,kBAAQ,IAAI,CAAC,WACZ;AAAA,UAAC;AAAA;AAAA,YAEC,WAAU;AAAA,YAEV;AAAA,kCAAC,QAAG,WAAU,kCAAkC,iBAAO,OAAM;AAAA,cAC7D,oBAAC,QAAG,WAAW,GAAG,6CAA6C,YAAY,KAAK,CAAC,GAC9E,iBAAO,OACV;AAAA,cACC,OAAO,SAAS,UAAa,OAAO,SAAS,QAC5C,oBAAC,QAAG,WAAU,+EACX,iBAAO,MACV;AAAA;AAAA;AAAA,UAVG,OAAO;AAAA,QAYd,CACD;AAAA;AAAA,IACH,GACF;AAAA;AAEJ;AAEA,IAAO,qBAAQ;","names":[]}
|