@loworbitstudio/visor 1.34.0 → 1.35.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/CHANGELOG.json +13 -1
- package/dist/index.js +93 -6
- package/dist/registry.json +55 -5
- package/dist/visor-manifest.json +199 -12
- package/package.json +1 -1
package/dist/CHANGELOG.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "0.4.0",
|
|
3
|
-
"generated_at": "2026-10-
|
|
3
|
+
"generated_at": "2026-10-04T07:08:34.776Z",
|
|
4
4
|
"components": {
|
|
5
5
|
"accessibility-specimen": {
|
|
6
6
|
"changeType": "current",
|
|
@@ -56,6 +56,12 @@
|
|
|
56
56
|
"breakingChange": false,
|
|
57
57
|
"migrationNote": null
|
|
58
58
|
},
|
|
59
|
+
"bottom-nav": {
|
|
60
|
+
"changeType": "current",
|
|
61
|
+
"files": [],
|
|
62
|
+
"breakingChange": false,
|
|
63
|
+
"migrationNote": null
|
|
64
|
+
},
|
|
59
65
|
"box": {
|
|
60
66
|
"changeType": "current",
|
|
61
67
|
"files": [],
|
|
@@ -494,6 +500,12 @@
|
|
|
494
500
|
"breakingChange": false,
|
|
495
501
|
"migrationNote": null
|
|
496
502
|
},
|
|
503
|
+
"position-picker": {
|
|
504
|
+
"changeType": "current",
|
|
505
|
+
"files": [],
|
|
506
|
+
"breakingChange": false,
|
|
507
|
+
"migrationNote": null
|
|
508
|
+
},
|
|
497
509
|
"progress": {
|
|
498
510
|
"changeType": "current",
|
|
499
511
|
"files": [],
|
package/dist/index.js
CHANGED
|
@@ -8760,6 +8760,9 @@ function spawnCommand(cwd, options) {
|
|
|
8760
8760
|
import { existsSync as existsSync31, mkdirSync as mkdirSync14, readFileSync as readFileSync33 } from "fs";
|
|
8761
8761
|
import { dirname as dirname15, isAbsolute as isAbsolute7, resolve as resolve21 } from "path";
|
|
8762
8762
|
var ICON_X = `React.createElement("svg", { viewBox: "0 0 256 256", width: "1em", height: "1em", fill: "currentColor", "aria-hidden": true }, React.createElement("path", { d: "M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z" }))`;
|
|
8763
|
+
var navIcon = (shape) => `function (p) { return React.createElement("svg", { viewBox: "0 0 256 256", width: p.size || 24, height: p.size || 24, className: p.className, fill: p.weight === "fill" ? "currentColor" : "none", stroke: "currentColor", strokeWidth: 16, strokeLinejoin: "round", "aria-hidden": true }, React.createElement("path", { d: ${JSON.stringify(shape)} })); }`;
|
|
8764
|
+
var NAV_SHAPES = ["M40 56h176v144H40z", "M128 32l96 176H32z", "M128 32a96 96 0 1 0 0 192a96 96 0 0 0 0-192z", "M48 64h160v128H48zM48 112h160", "M40 200V120M104 200V64M168 200V96M216 200V152", "M64 216V96l64-64l64 64v120z", "M128 40a40 40 0 1 0 0 80a40 40 0 0 0 0-80zM48 216c0-44 36-72 80-72s80 28 80 72z"];
|
|
8765
|
+
var NAV_ICONS = NAV_SHAPES.map(navIcon);
|
|
8763
8766
|
var FIXTURES = {
|
|
8764
8767
|
// VI-680: hairline consumers. `__mod` is the component module, so a fixture can
|
|
8765
8768
|
// reach the compound parts a single export does not expose.
|
|
@@ -9145,6 +9148,27 @@ var FIXTURES = {
|
|
|
9145
9148
|
invalid: { export: "OTPInput", interactiveTarget: "input", props: `{ value: "428519", invalid: true, errorMessage: "That code didn't work. Check it and try again." }` },
|
|
9146
9149
|
trailing: { export: "OTPInput", interactiveTarget: "input", props: `{ value: "428519", trailing: React.createElement("button", { type: "button" }, "Resend") }` }
|
|
9147
9150
|
},
|
|
9151
|
+
// VI-672: `default` is the grid on its own ground; `on-image` draws it over a busy
|
|
9152
|
+
// synthetic photograph (turbulence, one light half and one dark half) so each
|
|
9153
|
+
// target's scrim ring is checked against both. The picker is its own frame here:
|
|
9154
|
+
// `position: relative` and a size stand in for the positioned photo frame.
|
|
9155
|
+
"position-picker": {
|
|
9156
|
+
default: { export: "PositionPicker", interactiveTarget: '[role="radio"]', props: `{ "aria-label": "Focal point", defaultValue: { y: "center", x: "center" } }` },
|
|
9157
|
+
"on-image": {
|
|
9158
|
+
export: "PositionPicker",
|
|
9159
|
+
interactiveTarget: '[role="radio"]',
|
|
9160
|
+
props: `(function () {
|
|
9161
|
+
var svg = "<svg xmlns='http://www.w3.org/2000/svg' width='480' height='300'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.06' numOctaves='4' seed='7'/><feColorMatrix values='1.4 0 0 0 0.05 0 1.2 0 0 0.05 0 0 1.1 0 0.05 0 0 0 1 0'/></filter><rect width='480' height='300' fill='#d9c8a6'/><rect width='480' height='300' filter='url(#n)'/><rect x='0' y='0' width='240' height='300' fill='#f4efe4' opacity='0.55'/><rect x='240' y='0' width='240' height='300' fill='#0b0b14' opacity='0.6'/></svg>";
|
|
9162
|
+
return {
|
|
9163
|
+
variant: "on-image",
|
|
9164
|
+
"aria-label": "Focal point",
|
|
9165
|
+
defaultValue: { y: "top", x: "right" },
|
|
9166
|
+
style: { position: "relative", inset: "auto", width: 480, height: 300, backgroundImage: "url('data:image/svg+xml;utf8," + encodeURIComponent(svg).replace(/'/g, "%27") + "')", backgroundSize: "cover" },
|
|
9167
|
+
};
|
|
9168
|
+
})()`
|
|
9169
|
+
},
|
|
9170
|
+
thumbnail: { export: "PositionPicker", interactiveTarget: '[role="radio"]', props: `{ variant: "on-image", "aria-label": "Focal point", defaultValue: { y: "bottom", x: "left" }, style: { position: "relative", inset: "auto", width: 40, height: 28, background: "#667" } }` }
|
|
9171
|
+
},
|
|
9148
9172
|
// VI-624: `default` is the closed field; the popover fixtures render open so the
|
|
9149
9173
|
// two-column (24-hour) and three-column (12-hour) anatomy can be inspected.
|
|
9150
9174
|
"time-picker": {
|
|
@@ -9172,6 +9196,54 @@ var FIXTURES = {
|
|
|
9172
9196
|
export: "Select",
|
|
9173
9197
|
interactiveTarget: "button",
|
|
9174
9198
|
props: `{ children: React.createElement(__mod.SelectTrigger, { variant: "borderless" }, React.createElement(__mod.SelectValue, { placeholder: "Pick a plan" })) }`
|
|
9199
|
+
},
|
|
9200
|
+
// VI-692: forced open so the portaled listbox is in the shot.
|
|
9201
|
+
open: {
|
|
9202
|
+
export: "Select",
|
|
9203
|
+
props: `{ open: true, defaultValue: "pro", children: [React.createElement(__mod.SelectTrigger, { key: "t" }, React.createElement(__mod.SelectValue, { placeholder: "Pick a plan" })), React.createElement(__mod.SelectContent, { key: "c" }, [React.createElement(__mod.SelectItem, { key: "a", value: "free" }, "Free"), React.createElement(__mod.SelectItem, { key: "b", value: "pro" }, "Pro"), React.createElement(__mod.SelectItem, { key: "d", value: "team" }, "Team")])] }`
|
|
9204
|
+
}
|
|
9205
|
+
},
|
|
9206
|
+
// VI-692: forced open so the portaled tip is in the shot.
|
|
9207
|
+
tooltip: {
|
|
9208
|
+
default: { export: "TooltipProvider", props: `{ children: React.createElement(__mod.Tooltip, { open: true }, [React.createElement(__mod.TooltipTrigger, { key: "t", asChild: true }, React.createElement("button", { type: "button" }, "Hover me")), React.createElement(__mod.TooltipContent, { key: "c" }, "Tooltip on the theme")]) }` }
|
|
9209
|
+
},
|
|
9210
|
+
// VI-683: an icon rail under an app bar (position="contained"), collapsed to
|
|
9211
|
+
// icons. Render with `--width 1024 --state focus` to open the first item's
|
|
9212
|
+
// tooltip (the sidebar is mobile at or below 768px, and mobile never shows
|
|
9213
|
+
// it); the second item wears the unread dot.
|
|
9214
|
+
sidebar: {
|
|
9215
|
+
default: {
|
|
9216
|
+
export: "SidebarProvider",
|
|
9217
|
+
interactiveTarget: '[data-sidebar="menu-button"]',
|
|
9218
|
+
props: `(function () {
|
|
9219
|
+
var h = React.createElement;
|
|
9220
|
+
var glyph = function (d) {
|
|
9221
|
+
return h("svg", { viewBox: "0 0 256 256", width: 20, height: 20, fill: "currentColor", "aria-hidden": true }, h("path", { d: d }));
|
|
9222
|
+
};
|
|
9223
|
+
var items = [
|
|
9224
|
+
["Edit EPK", "M227.31,73.37,182.63,28.68a16,16,0,0,0-22.63,0L36.69,152A15.86,15.86,0,0,0,32,163.31V208a16,16,0,0,0,16,16H92.69A15.86,15.86,0,0,0,104,219.31L227.31,96a16,16,0,0,0,0-22.63Z"],
|
|
9225
|
+
["Inbox, 3 unread", "M32,136v56a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V136l-32-72a16,16,0,0,0-14.6-9.5H78.6A16,16,0,0,0,64,64Z", true],
|
|
9226
|
+
["Calendar", "M208,32H184V24a8,8,0,0,0-16,0v8H88V24a8,8,0,0,0-16,0v8H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32Z"],
|
|
9227
|
+
];
|
|
9228
|
+
return {
|
|
9229
|
+
defaultOpen: false,
|
|
9230
|
+
style: { flexDirection: "column", minHeight: 360, width: 420 },
|
|
9231
|
+
children: [
|
|
9232
|
+
h("div", { key: "bar", style: { height: 48, display: "flex", alignItems: "center", padding: "0 16px", background: "var(--surface-card)", color: "var(--text-primary)", fontSize: 14 } }, "App bar"),
|
|
9233
|
+
h("div", { key: "body", style: { display: "flex", flex: 1 } }, [
|
|
9234
|
+
h(__mod.Sidebar, { key: "rail", collapsible: "icon", position: "contained" },
|
|
9235
|
+
h(__mod.SidebarMenu, null, items.map(function (it) {
|
|
9236
|
+
return h(__mod.SidebarMenuItem, { key: it[0] }, [
|
|
9237
|
+
h(__mod.SidebarMenuButton, { key: "b", asChild: true, tooltip: it[0] }, h("a", { href: "#" }, glyph(it[1]))),
|
|
9238
|
+
it[2] ? h(__mod.SidebarMenuBadge, { key: "d", "aria-hidden": true, style: { top: 4, right: 4, minWidth: 8, height: 8, padding: 0, borderRadius: "50%", background: "var(--text-primary)" } }) : null,
|
|
9239
|
+
]);
|
|
9240
|
+
}))
|
|
9241
|
+
),
|
|
9242
|
+
h(__mod.SidebarInset, { key: "main" }, h("div", { style: { padding: 16, fontSize: 14 } }, "Page content")),
|
|
9243
|
+
]),
|
|
9244
|
+
],
|
|
9245
|
+
};
|
|
9246
|
+
})()`
|
|
9175
9247
|
}
|
|
9176
9248
|
},
|
|
9177
9249
|
checkbox: {
|
|
@@ -9298,6 +9370,18 @@ var FIXTURES = {
|
|
|
9298
9370
|
dot: { export: "SaveStatus", props: `{ status: "saved", dot: true }` },
|
|
9299
9371
|
"dot-unsaved": { export: "SaveStatus", props: `{ status: "unsaved", dot: true }` }
|
|
9300
9372
|
},
|
|
9373
|
+
// VI-673: the phone foot bar. `fixed: false` lays it in the render frame instead of pinning it.
|
|
9374
|
+
"bottom-nav": {
|
|
9375
|
+
default: {
|
|
9376
|
+
export: "BottomNav",
|
|
9377
|
+
interactiveTarget: "a",
|
|
9378
|
+
props: `{ "aria-label": "Workspace", fixed: false, items: [{ label: "Edit", icon: ${NAV_ICONS[0]}, href: "#edit", active: true }, { label: "Inbox", icon: ${NAV_ICONS[1]}, href: "#inbox", mark: 3 }, { label: "Calendar", icon: ${NAV_ICONS[2]}, href: "#calendar" }, { label: "Invoices", icon: ${NAV_ICONS[3]}, href: "#invoices" }, { label: "Analytics", icon: ${NAV_ICONS[4]}, href: "#analytics" }, { label: "Team", icon: ${NAV_ICONS[6]}, href: "#team", group: "admin" }, { label: "Settings", icon: ${NAV_ICONS[5]}, href: "#settings", group: "admin" }] }`
|
|
9379
|
+
},
|
|
9380
|
+
labelled: {
|
|
9381
|
+
export: "BottomNav",
|
|
9382
|
+
props: `{ "aria-label": "Workspace", fixed: false, showLabels: true, items: [{ label: "Home", icon: ${NAV_ICONS[5]}, href: "#home", active: true }, { label: "Inbox", icon: ${NAV_ICONS[1]}, href: "#inbox", mark: true }, { label: "Settings", icon: ${NAV_ICONS[5]}, href: "#settings" }] }`
|
|
9383
|
+
}
|
|
9384
|
+
},
|
|
9301
9385
|
// VI-615: the single-select pill group. `fullwidth` fills the render frame.
|
|
9302
9386
|
"segmented-control": {
|
|
9303
9387
|
default: {
|
|
@@ -9525,6 +9609,11 @@ ${opts.componentCss}
|
|
|
9525
9609
|
</style>
|
|
9526
9610
|
<style data-visor="harness">
|
|
9527
9611
|
html, body { margin: 0; padding: 0; }
|
|
9612
|
+
body {
|
|
9613
|
+
color: var(--text-primary, #111827);
|
|
9614
|
+
/* Same token the nextjs adapter's origination binds to body (VI-616). */
|
|
9615
|
+
font-family: var(--font-body, var(--font-sans, system-ui, -apple-system, sans-serif));
|
|
9616
|
+
}
|
|
9528
9617
|
#theme-scope {
|
|
9529
9618
|
box-sizing: border-box;
|
|
9530
9619
|
min-height: 100vh;
|
|
@@ -9533,15 +9622,12 @@ html, body { margin: 0; padding: 0; }
|
|
|
9533
9622
|
align-items: center;
|
|
9534
9623
|
justify-content: center;
|
|
9535
9624
|
background: var(--surface-page, #ffffff);
|
|
9536
|
-
color: var(--text-primary, #111827);
|
|
9537
|
-
/* Same token the nextjs adapter's origination binds to body (VI-616). */
|
|
9538
|
-
font-family: var(--font-body, var(--font-sans, system-ui, -apple-system, sans-serif));
|
|
9539
9625
|
}
|
|
9540
9626
|
#root { width: 100%; max-width: 420px; }
|
|
9541
9627
|
</style>
|
|
9542
9628
|
</head>
|
|
9543
|
-
<body>
|
|
9544
|
-
<div id="theme-scope"
|
|
9629
|
+
<body class="${opts.themeClass}">
|
|
9630
|
+
<div id="theme-scope">
|
|
9545
9631
|
<div id="root"></div>
|
|
9546
9632
|
</div>
|
|
9547
9633
|
<script>
|
|
@@ -9708,11 +9794,12 @@ async function renderCommand(component, cwd, options) {
|
|
|
9708
9794
|
probe = await page.evaluate(
|
|
9709
9795
|
`(function () {
|
|
9710
9796
|
var scope = document.getElementById("theme-scope");
|
|
9797
|
+
var body = document.body;
|
|
9711
9798
|
var html = document.documentElement;
|
|
9712
9799
|
var read = function (el, prop) {
|
|
9713
9800
|
return getComputedStyle(el).getPropertyValue(prop).trim();
|
|
9714
9801
|
};
|
|
9715
|
-
var themed = read(
|
|
9802
|
+
var themed = read(body, "--surface-card");
|
|
9716
9803
|
var base = read(html, "--surface-card");
|
|
9717
9804
|
return {
|
|
9718
9805
|
themedSurfaceCard: themed,
|
package/dist/registry.json
CHANGED
|
@@ -534,6 +534,30 @@
|
|
|
534
534
|
}
|
|
535
535
|
]
|
|
536
536
|
},
|
|
537
|
+
{
|
|
538
|
+
"name": "bottom-nav",
|
|
539
|
+
"type": "registry:ui",
|
|
540
|
+
"description": "A bottom navigation bar of icon destinations for phone widths: filled active destination, an optional unread mark, a hairline group break, safe-area padding, and its height published as --bottom-nav-height.",
|
|
541
|
+
"category": "navigation",
|
|
542
|
+
"dependencies": [
|
|
543
|
+
"@loworbitstudio/visor-core"
|
|
544
|
+
],
|
|
545
|
+
"registryDependencies": [
|
|
546
|
+
"utils"
|
|
547
|
+
],
|
|
548
|
+
"files": [
|
|
549
|
+
{
|
|
550
|
+
"path": "components/ui/bottom-nav/bottom-nav.tsx",
|
|
551
|
+
"type": "registry:ui",
|
|
552
|
+
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"../../../lib/utils\"\nimport styles from \"./bottom-nav.module.css\"\n\n/* ─── Types ─────────────────────────────────────────────────────────── */\n\n/** An icon component that takes a Phosphor-style `weight`. A Phosphor icon fits as is. */\nexport type BottomNavIcon = React.ComponentType<{\n weight?: \"regular\" | \"fill\"\n size?: number | string\n className?: string\n \"aria-hidden\"?: boolean | \"true\" | \"false\"\n}>\n\nexport interface BottomNavItem {\n /** Stable key. Falls back to `href`, then `label`. */\n key?: string\n /** The destination's name. Visually hidden unless the bar has `showLabels`. */\n label: string\n /** Icon component. The active destination renders it with `weight=\"fill\"`. */\n icon: BottomNavIcon\n /** Renders an `<a>`. Without it the destination is a `<button>`. */\n href?: string\n /** Fires on click, for an `<a>` as well as a `<button>`. */\n onSelect?: (event: React.MouseEvent<HTMLElement>) => void\n /** The destination that is open. Sets `aria-current=\"page\"` and fills the icon. */\n active?: boolean\n /**\n * A mark on the destination: `true` for a dot alone, a number for a count.\n * The mark is a solid dot and never draws the number; the count lives in the\n * accessible name (\"Inbox, 3 unread\"). `0` and `false` draw nothing.\n */\n mark?: boolean | number\n /** The word that follows the count in the accessible name. Default \"unread\". */\n markLabel?: string\n /** A hairline is drawn before the first destination of each new `group`. Not announced. */\n group?: string\n}\n\nexport interface BottomNavProps\n extends Omit<React.ComponentPropsWithoutRef<\"nav\">, \"children\"> {\n items: BottomNavItem[]\n /** Show each label under its icon. Default false: the label is visually hidden. */\n showLabels?: boolean\n /**\n * Pin the bar to the foot of the viewport (default) and publish its height as\n * `--bottom-nav-height` on the document root. `false` lays it out in flow, for\n * a frame or a preview, and publishes nothing.\n */\n fixed?: boolean\n}\n\n/* ─── Helpers ───────────────────────────────────────────────────────── */\n\n/** The destination's accessible name: the label, plus the mark's meaning. */\nexport function bottomNavItemName(item: BottomNavItem): string {\n const word = item.markLabel ?? \"unread\"\n if (typeof item.mark === \"number\" && item.mark > 0) {\n return `${item.label}, ${item.mark} ${word}`\n }\n if (item.mark === true) return `${item.label}, ${word}`\n return item.label\n}\n\nconst HEIGHT_TOKEN = \"--bottom-nav-height\"\n\n/**\n * Publishes the bar's rendered height (safe-area pad included) as\n * `--bottom-nav-height` on the document root, so a page can set\n * `scroll-padding-bottom: var(--bottom-nav-height)` and a focused field scrolls\n * clear of the bar (WCAG 2.4.11). Removed on unmount.\n */\nfunction usePublishHeight(\n ref: React.RefObject<HTMLElement | null>,\n enabled: boolean,\n) {\n React.useLayoutEffect(() => {\n const node = ref.current\n if (!enabled || !node) return\n const root = document.documentElement\n const publish = () => {\n root.style.setProperty(HEIGHT_TOKEN, `${node.getBoundingClientRect().height}px`)\n }\n publish()\n if (typeof ResizeObserver === \"undefined\") return () => root.style.removeProperty(HEIGHT_TOKEN)\n const observer = new ResizeObserver(publish)\n observer.observe(node)\n return () => {\n observer.disconnect()\n root.style.removeProperty(HEIGHT_TOKEN)\n }\n }, [ref, enabled])\n}\n\n/* ─── BottomNav ─────────────────────────────────────────────────────── */\n\nconst BottomNav = React.forwardRef<HTMLElement, BottomNavProps>(\n ({ items, showLabels = false, fixed = true, className, ...props }, ref) => {\n const navRef = React.useRef<HTMLElement | null>(null)\n const mergedRef = React.useCallback(\n (node: HTMLElement | null) => {\n navRef.current = node\n if (typeof ref === \"function\") ref(node)\n else if (ref) (ref as React.MutableRefObject<HTMLElement | null>).current = node\n },\n [ref],\n )\n usePublishHeight(navRef, fixed)\n\n const children: React.ReactNode[] = []\n items.forEach((item, index) => {\n const itemKey = item.key ?? item.href ?? item.label\n const previous = items[index - 1]\n if (previous && (previous.group ?? \"\") !== (item.group ?? \"\")) {\n children.push(\n <li\n key={`${itemKey}-group`}\n className={styles.group}\n role=\"presentation\"\n aria-hidden=\"true\"\n data-slot=\"bottom-nav-group\"\n />,\n )\n }\n\n const Icon = item.icon\n const hasMark =\n item.mark === true || (typeof item.mark === \"number\" && item.mark > 0)\n const content = (\n <>\n <span className={styles.iconBox}>\n <Icon\n className={styles.icon}\n size={24}\n weight={item.active ? \"fill\" : \"regular\"}\n aria-hidden=\"true\"\n />\n {hasMark && (\n <span className={styles.mark} aria-hidden=\"true\" data-slot=\"bottom-nav-mark\" />\n )}\n </span>\n <span className={showLabels ? styles.label : styles.hiddenLabel}>{item.label}</span>\n </>\n )\n const shared = {\n className: styles.item,\n \"aria-label\": bottomNavItemName(item),\n \"aria-current\": item.active ? (\"page\" as const) : undefined,\n \"data-active\": item.active ? \"true\" : undefined,\n \"data-labelled\": showLabels ? \"true\" : undefined,\n \"data-slot\": \"bottom-nav-item\",\n onClick: item.onSelect,\n }\n children.push(\n <li key={itemKey} className={styles.cell}>\n {item.href !== undefined ? (\n <a href={item.href} {...shared}>\n {content}\n </a>\n ) : (\n <button type=\"button\" {...shared}>\n {content}\n </button>\n )}\n </li>,\n )\n })\n\n return (\n <nav\n {...props}\n ref={mergedRef}\n data-slot=\"bottom-nav\"\n data-fixed={fixed ? \"true\" : undefined}\n className={cn(styles.root, className)}\n >\n <ul className={styles.list}>{children}</ul>\n </nav>\n )\n },\n)\nBottomNav.displayName = \"BottomNav\"\n\nexport { BottomNav }\n"
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
"path": "components/ui/bottom-nav/bottom-nav.module.css",
|
|
556
|
+
"type": "registry:ui",
|
|
557
|
+
"content": "/* BottomNav: a row of icon destinations at the foot of a phone screen. The\n resting top edge is a hairline: it reads --hairline-width (an inset shadow,\n never a border), so --hairline-width: 0 turns it off with no layout\n shift. Focus draws at the focus ring width, which that switch does not touch. */\n\n.root {\n box-sizing: border-box;\n width: 100%;\n background-color: var(--surface-card, #ffffff);\n color: var(--text-secondary, #6b7280);\n /* The home indicator never sits on a destination. */\n padding-bottom: env(safe-area-inset-bottom, 0px);\n box-shadow: inset 0 var(--hairline-width, var(--stroke-width-thin, 1px)) 0 var(--hairline, var(--border-default, #e5e7eb));\n}\n\n.root[data-fixed=\"true\"] {\n position: fixed;\n inset-inline: 0;\n bottom: 0;\n z-index: 20;\n}\n\n.list {\n display: flex;\n align-items: center;\n justify-content: space-around;\n gap: var(--spacing-1, 0.25rem);\n margin: 0;\n padding: var(--spacing-2, 0.5rem) var(--spacing-1, 0.25rem);\n list-style: none;\n}\n\n.cell {\n display: flex;\n flex: 1 1 0;\n justify-content: center;\n min-width: 0;\n}\n\n.item {\n position: relative;\n display: inline-flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--spacing-1, 0.25rem);\n box-sizing: border-box;\n /* 48px: the whole item is the target (WCAG 2.5.8 asks 24px). */\n min-width: var(--spacing-12, 3rem);\n min-height: var(--spacing-12, 3rem);\n margin: 0;\n padding: var(--spacing-1, 0.25rem) var(--spacing-2, 0.5rem);\n border: 0;\n border-radius: var(--radius-md, 0.375rem);\n background-color: transparent;\n color: inherit;\n text-decoration: none;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition:\n background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out),\n color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n.item:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, #111827);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n@media (hover: hover) {\n .item:hover {\n background-color: var(--surface-interactive-hover, #f3f4f6);\n color: var(--text-primary, #111827);\n }\n}\n\n/* Active: a lifted fill and the filled icon (the component swaps the glyph's\n weight), so colour is never the only cue (WCAG 1.4.1). */\n.item[aria-current=\"page\"] {\n background-color: var(--surface-interactive-active, #e5e7eb);\n color: var(--text-primary, #111827);\n}\n\n.iconBox {\n position: relative;\n display: inline-flex;\n}\n\n.icon {\n display: block;\n flex-shrink: 0;\n}\n\n.label {\n font-size: var(--font-size-xs, 0.75rem);\n font-weight: var(--font-weight-medium, 500);\n line-height: 1;\n white-space: nowrap;\n}\n\n.hiddenLabel {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n/* The unread mark: a solid 8px circle in the accent, no number, at the icon's\n top-right corner. Its meaning is in the destination's accessible name. */\n.mark {\n position: absolute;\n top: calc(-1 * var(--spacing-1, 0.25rem));\n right: calc(-1 * var(--spacing-1, 0.25rem));\n width: var(--spacing-2, 0.5rem);\n height: var(--spacing-2, 0.5rem);\n border-radius: 50%;\n background-color: var(--accent, #0369a1);\n}\n\n/* The group break: a vertical hairline between destinations, not announced. It\n reads Separator's colour token first, so it stays drawn on a borderless theme\n whose --hairline is transparent (blacklight-app binds the separator colour for this). */\n.group {\n flex: 0 0 auto;\n align-self: stretch;\n width: var(--hairline-width, var(--stroke-width-thin, 1px));\n margin-block: var(--spacing-2, 0.5rem);\n background-color: var(--separator-color, var(--hairline, var(--border-default, #e5e7eb)));\n}\n\n@media (prefers-reduced-motion: reduce) {\n .item {\n transition: none;\n }\n}\n"
|
|
558
|
+
}
|
|
559
|
+
]
|
|
560
|
+
},
|
|
537
561
|
{
|
|
538
562
|
"name": "separator",
|
|
539
563
|
"type": "registry:ui",
|
|
@@ -659,7 +683,7 @@
|
|
|
659
683
|
{
|
|
660
684
|
"path": "components/ui/tooltip/tooltip.module.css",
|
|
661
685
|
"type": "registry:ui",
|
|
662
|
-
"content": "/* Tooltip content */\n.content {\n z-index: 50;\n display: inline-flex;\n width: fit-content;\n max-width: 20rem;\n align-items: center;\n gap: calc(var(--spacing-1, 0.25rem) * 1.5);\n border-radius: var(--radius-lg, 0.5rem);\n background-color: var(--tooltip-bg, var(--surface-overlay, #111827));\n padding: calc(var(--spacing-1, 0.25rem) * 1.5) var(--spacing-3, 0.75rem);\n font-size: var(--font-size-xs, 0.75rem);\n color: var(--tooltip-text, var(--text-inverse, #ffffff));\n box-shadow: var(--shadow-md);\n animation-duration: var(--motion-duration-150, 150ms);\n animation-timing-function: var(--motion-easing-enter, ease-out);\n}\n\n.content[data-state=\"delayed-open\"] {\n animation-name: fadeIn;\n}\n\n.content[data-state=\"closed\"] {\n animation-name: fadeOut;\n}\n\n@keyframes fadeIn {\n from {\n opacity: 0;\n transform: scale(0.95);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n}\n\n@keyframes fadeOut {\n from {\n opacity: 1;\n transform: scale(1);\n }\n to {\n opacity: 0;\n transform: scale(0.95);\n }\n}\n"
|
|
686
|
+
"content": "/* Tooltip content */\n.content {\n z-index: 50;\n display: inline-flex;\n width: fit-content;\n max-width: 20rem;\n align-items: center;\n gap: calc(var(--spacing-1, 0.25rem) * 1.5);\n border-radius: var(--radius-lg, 0.5rem);\n background-color: var(--tooltip-bg, var(--surface-overlay, #111827));\n padding: calc(var(--spacing-1, 0.25rem) * 1.5) var(--spacing-3, 0.75rem);\n font-size: var(--font-size-xs, 0.75rem);\n color: var(--tooltip-text, var(--text-inverse, #ffffff));\n box-shadow: var(--shadow-md);\n animation-duration: var(--motion-duration-150, 150ms);\n animation-timing-function: var(--motion-easing-enter, ease-out);\n}\n\n/* Dark mode default (VI-683). Both of the inverse pair resolve dark there, so\n the label vanishes. Flip to the primary ink as ground under the inverse ink\n (the dark ink Calendar, Badge and Chip also read). A theme binding of\n --tooltip-bg / --tooltip-text still wins; light mode is untouched.\n :where() keeps this at the specificity of .content. */\n:global(:where(.dark)) .content,\n:global(:where([data-theme=\"dark\"])) .content {\n background-color: var(--tooltip-bg, var(--text-primary, #f9fafb));\n color: var(--tooltip-text, var(--text-inverse, #111827));\n}\n\n.content[data-state=\"delayed-open\"] {\n animation-name: fadeIn;\n}\n\n.content[data-state=\"closed\"] {\n animation-name: fadeOut;\n}\n\n@keyframes fadeIn {\n from {\n opacity: 0;\n transform: scale(0.95);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n}\n\n@keyframes fadeOut {\n from {\n opacity: 1;\n transform: scale(1);\n }\n to {\n opacity: 0;\n transform: scale(0.95);\n }\n}\n"
|
|
663
687
|
}
|
|
664
688
|
]
|
|
665
689
|
},
|
|
@@ -975,23 +999,25 @@
|
|
|
975
999
|
"description": "A feature-rich collapsible sidebar component with provider, menu, groups, and sub-menus.",
|
|
976
1000
|
"category": "layout",
|
|
977
1001
|
"dependencies": [
|
|
1002
|
+
"@radix-ui/react-slot",
|
|
978
1003
|
"class-variance-authority",
|
|
979
1004
|
"@phosphor-icons/react",
|
|
980
1005
|
"@loworbitstudio/visor-core"
|
|
981
1006
|
],
|
|
982
1007
|
"registryDependencies": [
|
|
983
|
-
"utils"
|
|
1008
|
+
"utils",
|
|
1009
|
+
"tooltip"
|
|
984
1010
|
],
|
|
985
1011
|
"files": [
|
|
986
1012
|
{
|
|
987
1013
|
"path": "components/ui/sidebar/sidebar.tsx",
|
|
988
1014
|
"type": "registry:ui",
|
|
989
|
-
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { SidebarIcon } from \"@phosphor-icons/react\"\nimport { cn } from \"../../../lib/utils\"\nimport styles from \"./sidebar.module.css\"\n\nconst SIDEBAR_COOKIE_NAME = \"sidebar_state\"\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7\nconst SIDEBAR_WIDTH = \"16rem\"\nconst SIDEBAR_WIDTH_MOBILE = \"18rem\"\nconst SIDEBAR_WIDTH_ICON = \"3rem\"\nconst SIDEBAR_KEYBOARD_SHORTCUT = \"b\"\nconst SIDEBAR_MOBILE_MEDIA_QUERY = \"(max-width: 768px)\"\n\n// `isMobile` is derived from a `matchMedia` subscription via\n// `useSyncExternalStore` (below) instead of a `useState` + `useEffect` pair.\n// This keeps SSR safe — the server snapshot is always `false` — while avoiding\n// a synchronous `setState` inside an effect body, which the React 19 compiler\n// lint (`react-hooks/set-state-in-effect`) flags as a cascading-render hazard.\nfunction subscribeIsMobile(callback: () => void) {\n const mql = window.matchMedia(SIDEBAR_MOBILE_MEDIA_QUERY)\n mql.addEventListener(\"change\", callback)\n return () => mql.removeEventListener(\"change\", callback)\n}\n\nfunction getIsMobileSnapshot() {\n return window.matchMedia(SIDEBAR_MOBILE_MEDIA_QUERY).matches\n}\n\nfunction getIsMobileServerSnapshot() {\n return false\n}\n\ntype SidebarContextProps = {\n state: \"expanded\" | \"collapsed\"\n open: boolean\n setOpen: (open: boolean) => void\n openMobile: boolean\n setOpenMobile: (open: boolean) => void\n isMobile: boolean\n toggleSidebar: () => void\n}\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null)\n\nfunction useSidebar() {\n const context = React.useContext(SidebarContext)\n if (!context) {\n throw new Error(\"useSidebar must be used within a SidebarProvider.\")\n }\n return context\n}\n\nexport interface SidebarProviderProps extends React.ComponentProps<\"div\"> {\n defaultOpen?: boolean\n open?: boolean\n onOpenChange?: (open: boolean) => void\n}\n\nconst SidebarProvider = React.forwardRef<HTMLDivElement, SidebarProviderProps>(\n (\n {\n defaultOpen = true,\n open: openProp,\n onOpenChange: setOpenProp,\n className,\n style,\n children,\n ...props\n },\n ref\n ) => {\n const isMobile = React.useSyncExternalStore(\n subscribeIsMobile,\n getIsMobileSnapshot,\n getIsMobileServerSnapshot\n )\n const [openMobile, setOpenMobile] = React.useState(false)\n const [_open, _setOpen] = React.useState(defaultOpen)\n const open = openProp ?? _open\n\n const setOpen = React.useCallback(\n (value: boolean | ((value: boolean) => boolean)) => {\n const openState = typeof value === \"function\" ? value(open) : value\n if (setOpenProp) {\n setOpenProp(openState)\n } else {\n _setOpen(openState)\n }\n document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`\n },\n [setOpenProp, open]\n )\n\n const toggleSidebar = React.useCallback(() => {\n return isMobile\n ? setOpenMobile((prev) => !prev)\n : setOpen((prev) => !prev)\n }, [isMobile, setOpen, setOpenMobile])\n\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n toggleSidebar()\n }\n }\n window.addEventListener(\"keydown\", handleKeyDown)\n return () => window.removeEventListener(\"keydown\", handleKeyDown)\n }, [toggleSidebar])\n\n const state = open ? \"expanded\" : \"collapsed\"\n\n const contextValue = React.useMemo<SidebarContextProps>(\n () => ({\n state,\n open,\n setOpen,\n isMobile,\n openMobile,\n setOpenMobile,\n toggleSidebar,\n }),\n [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]\n )\n\n return (\n <SidebarContext.Provider value={contextValue}>\n <div\n ref={ref}\n data-slot=\"sidebar-wrapper\"\n style={\n {\n \"--sidebar-width\": SIDEBAR_WIDTH,\n \"--sidebar-width-mobile\": SIDEBAR_WIDTH_MOBILE,\n \"--sidebar-width-icon\": SIDEBAR_WIDTH_ICON,\n ...style,\n } as React.CSSProperties\n }\n className={cn(styles.wrapper, className)}\n {...props}\n >\n {children}\n </div>\n </SidebarContext.Provider>\n )\n }\n)\nSidebarProvider.displayName = \"SidebarProvider\"\n\nexport interface SidebarProps extends React.ComponentProps<\"div\"> {\n side?: \"left\" | \"right\"\n variant?: \"sidebar\" | \"floating\" | \"inset\"\n collapsible?: \"offcanvas\" | \"icon\" | \"none\"\n}\n\nconst Sidebar = React.forwardRef<HTMLDivElement, SidebarProps>(\n (\n {\n side = \"left\",\n variant = \"sidebar\",\n collapsible = \"offcanvas\",\n className,\n children,\n ...props\n },\n ref\n ) => {\n const { state } = useSidebar()\n\n if (collapsible === \"none\") {\n return (\n <div\n ref={ref}\n data-slot=\"sidebar\"\n className={cn(styles.sidebarStatic, className)}\n {...props}\n >\n {children}\n </div>\n )\n }\n\n return (\n <div\n ref={ref}\n className={cn(styles.sidebarContainer, className)}\n data-state={state}\n data-collapsible={state === \"collapsed\" ? collapsible : \"\"}\n data-variant={variant}\n data-side={side}\n data-slot=\"sidebar\"\n {...props}\n >\n <div\n data-slot=\"sidebar-gap\"\n className={styles.sidebarGap}\n />\n <div\n data-slot=\"sidebar-inner\"\n data-side={side}\n className={styles.sidebarInner}\n >\n <div\n data-sidebar=\"sidebar\"\n className={styles.sidebarContent}\n >\n {children}\n </div>\n </div>\n </div>\n )\n }\n)\nSidebar.displayName = \"Sidebar\"\n\nconst SidebarTrigger = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<\"button\">\n>(({ className, onClick, children, ...props }, ref) => {\n const { toggleSidebar } = useSidebar()\n\n return (\n <button\n ref={ref}\n data-sidebar=\"trigger\"\n data-slot=\"sidebar-trigger\"\n className={cn(styles.trigger, className)}\n onClick={(event) => {\n onClick?.(event)\n toggleSidebar()\n }}\n {...props}\n >\n {children ?? (\n <>\n <SidebarIcon />\n <span className={styles.srOnly}>Toggle Sidebar</span>\n </>\n )}\n </button>\n )\n})\nSidebarTrigger.displayName = \"SidebarTrigger\"\n\nconst SidebarRail = React.forwardRef<HTMLButtonElement, React.ComponentProps<\"button\">>(\n ({ className, ...props }, ref) => {\n const { toggleSidebar } = useSidebar()\n\n return (\n <button\n ref={ref}\n data-sidebar=\"rail\"\n data-slot=\"sidebar-rail\"\n aria-label=\"Toggle Sidebar\"\n tabIndex={-1}\n onClick={toggleSidebar}\n title=\"Toggle Sidebar\"\n className={cn(styles.rail, className)}\n {...props}\n />\n )\n }\n)\nSidebarRail.displayName = \"SidebarRail\"\n\nconst SidebarInset = React.forwardRef<HTMLElement, React.ComponentProps<\"main\">>(\n ({ className, ...props }, ref) => (\n <main\n ref={ref}\n data-slot=\"sidebar-inset\"\n className={cn(styles.inset, className)}\n {...props}\n />\n )\n)\nSidebarInset.displayName = \"SidebarInset\"\n\nconst SidebarHeader = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"sidebar-header\"\n data-sidebar=\"header\"\n className={cn(styles.header, className)}\n {...props}\n />\n )\n)\nSidebarHeader.displayName = \"SidebarHeader\"\n\nconst SidebarFooter = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"sidebar-footer\"\n data-sidebar=\"footer\"\n className={cn(styles.footer, className)}\n {...props}\n />\n )\n)\nSidebarFooter.displayName = \"SidebarFooter\"\n\nconst SidebarSeparator = React.forwardRef<HTMLHRElement, React.ComponentProps<\"hr\">>(\n ({ className, ...props }, ref) => (\n <hr\n ref={ref}\n data-slot=\"sidebar-separator\"\n data-sidebar=\"separator\"\n className={cn(styles.separator, className)}\n {...props}\n />\n )\n)\nSidebarSeparator.displayName = \"SidebarSeparator\"\n\nconst SidebarContent = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"sidebar-content\"\n data-sidebar=\"content\"\n className={cn(styles.contentArea, className)}\n {...props}\n />\n )\n)\nSidebarContent.displayName = \"SidebarContent\"\n\nconst SidebarGroup = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"sidebar-group\"\n data-sidebar=\"group\"\n className={cn(styles.group, className)}\n {...props}\n />\n )\n)\nSidebarGroup.displayName = \"SidebarGroup\"\n\nconst SidebarGroupLabel = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"sidebar-group-label\"\n data-sidebar=\"group-label\"\n className={cn(styles.groupLabel, className)}\n {...props}\n />\n )\n)\nSidebarGroupLabel.displayName = \"SidebarGroupLabel\"\n\nconst SidebarGroupAction = React.forwardRef<HTMLButtonElement, React.ComponentProps<\"button\">>(\n ({ className, ...props }, ref) => (\n <button\n ref={ref}\n data-slot=\"sidebar-group-action\"\n data-sidebar=\"group-action\"\n className={cn(styles.groupAction, className)}\n {...props}\n />\n )\n)\nSidebarGroupAction.displayName = \"SidebarGroupAction\"\n\nconst SidebarGroupContent = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"sidebar-group-content\"\n data-sidebar=\"group-content\"\n className={cn(styles.groupContent, className)}\n {...props}\n />\n )\n)\nSidebarGroupContent.displayName = \"SidebarGroupContent\"\n\nconst SidebarMenu = React.forwardRef<HTMLUListElement, React.ComponentProps<\"ul\">>(\n ({ className, ...props }, ref) => (\n <ul\n ref={ref}\n data-slot=\"sidebar-menu\"\n data-sidebar=\"menu\"\n className={cn(styles.menu, className)}\n {...props}\n />\n )\n)\nSidebarMenu.displayName = \"SidebarMenu\"\n\nconst SidebarMenuItem = React.forwardRef<HTMLLIElement, React.ComponentProps<\"li\">>(\n ({ className, ...props }, ref) => (\n <li\n ref={ref}\n data-slot=\"sidebar-menu-item\"\n data-sidebar=\"menu-item\"\n className={cn(styles.menuItem, className)}\n {...props}\n />\n )\n)\nSidebarMenuItem.displayName = \"SidebarMenuItem\"\n\nconst sidebarMenuButtonVariants = cva(styles.menuButton, {\n variants: {\n variant: {\n default: styles.menuButtonDefault,\n outline: styles.menuButtonOutline,\n },\n size: {\n default: styles.menuButtonSizeDefault,\n sm: styles.menuButtonSizeSm,\n lg: styles.menuButtonSizeLg,\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n})\n\nexport interface SidebarMenuButtonProps\n extends React.ComponentProps<\"button\">,\n VariantProps<typeof sidebarMenuButtonVariants> {\n asChild?: boolean\n isActive?: boolean\n tooltip?: string\n}\n\nconst SidebarMenuButton = React.forwardRef<HTMLButtonElement, SidebarMenuButtonProps>(\n ({ className, variant = \"default\", size = \"default\", isActive, children, ...props }, ref) => (\n <button\n ref={ref}\n data-slot=\"sidebar-menu-button\"\n data-sidebar=\"menu-button\"\n data-size={size}\n data-active={isActive || undefined}\n className={cn(sidebarMenuButtonVariants({ variant, size }), className)}\n {...props}\n >\n {children}\n </button>\n )\n)\nSidebarMenuButton.displayName = \"SidebarMenuButton\"\n\nconst SidebarMenuAction = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<\"button\"> & { showOnHover?: boolean }\n>(({ className, showOnHover, ...props }, ref) => (\n <button\n ref={ref}\n data-slot=\"sidebar-menu-action\"\n data-sidebar=\"menu-action\"\n className={cn(\n styles.menuAction,\n showOnHover && styles.menuActionHover,\n className\n )}\n {...props}\n />\n))\nSidebarMenuAction.displayName = \"SidebarMenuAction\"\n\nconst SidebarMenuBadge = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"sidebar-menu-badge\"\n data-sidebar=\"menu-badge\"\n className={cn(styles.menuBadge, className)}\n {...props}\n />\n )\n)\nSidebarMenuBadge.displayName = \"SidebarMenuBadge\"\n\nconst SidebarMenuSub = React.forwardRef<HTMLUListElement, React.ComponentProps<\"ul\">>(\n ({ className, ...props }, ref) => (\n <ul\n ref={ref}\n data-slot=\"sidebar-menu-sub\"\n data-sidebar=\"menu-sub\"\n className={cn(styles.menuSub, className)}\n {...props}\n />\n )\n)\nSidebarMenuSub.displayName = \"SidebarMenuSub\"\n\nconst SidebarMenuSubItem = React.forwardRef<HTMLLIElement, React.ComponentProps<\"li\">>(\n ({ className, ...props }, ref) => (\n <li\n ref={ref}\n data-slot=\"sidebar-menu-sub-item\"\n data-sidebar=\"menu-sub-item\"\n className={cn(styles.menuSubItem, className)}\n {...props}\n />\n )\n)\nSidebarMenuSubItem.displayName = \"SidebarMenuSubItem\"\n\nexport interface SidebarMenuSubButtonProps extends React.ComponentProps<\"a\"> {\n asChild?: boolean\n size?: \"sm\" | \"md\"\n isActive?: boolean\n}\n\nconst SidebarMenuSubButton = React.forwardRef<HTMLAnchorElement, SidebarMenuSubButtonProps>(\n ({ className, size = \"md\", isActive, ...props }, ref) => (\n <a\n ref={ref}\n data-slot=\"sidebar-menu-sub-button\"\n data-sidebar=\"menu-sub-button\"\n data-size={size}\n data-active={isActive || undefined}\n className={cn(\n styles.menuSubButton,\n size === \"sm\" && styles.menuSubButtonSm,\n className\n )}\n {...props}\n />\n )\n)\nSidebarMenuSubButton.displayName = \"SidebarMenuSubButton\"\n\nexport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupAction,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarInset,\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuBadge,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarProvider,\n SidebarRail,\n SidebarSeparator,\n SidebarTrigger,\n useSidebar,\n sidebarMenuButtonVariants,\n}\n"
|
|
1015
|
+
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { SidebarIcon } from \"@phosphor-icons/react\"\nimport { cn } from \"../../../lib/utils\"\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n type TooltipContentProps,\n} from \"../tooltip/tooltip\"\nimport styles from \"./sidebar.module.css\"\n\nconst SIDEBAR_COOKIE_NAME = \"sidebar_state\"\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7\nconst SIDEBAR_WIDTH = \"16rem\"\nconst SIDEBAR_WIDTH_MOBILE = \"18rem\"\nconst SIDEBAR_WIDTH_ICON = \"3rem\"\nconst SIDEBAR_KEYBOARD_SHORTCUT = \"b\"\nconst SIDEBAR_MOBILE_MEDIA_QUERY = \"(max-width: 768px)\"\n\n// `isMobile` is derived from a `matchMedia` subscription via\n// `useSyncExternalStore` (below) instead of a `useState` + `useEffect` pair.\n// This keeps SSR safe — the server snapshot is always `false` — while avoiding\n// a synchronous `setState` inside an effect body, which the React 19 compiler\n// lint (`react-hooks/set-state-in-effect`) flags as a cascading-render hazard.\nfunction subscribeIsMobile(callback: () => void) {\n const mql = window.matchMedia(SIDEBAR_MOBILE_MEDIA_QUERY)\n mql.addEventListener(\"change\", callback)\n return () => mql.removeEventListener(\"change\", callback)\n}\n\nfunction getIsMobileSnapshot() {\n return window.matchMedia(SIDEBAR_MOBILE_MEDIA_QUERY).matches\n}\n\nfunction getIsMobileServerSnapshot() {\n return false\n}\n\ntype SidebarContextProps = {\n state: \"expanded\" | \"collapsed\"\n open: boolean\n setOpen: (open: boolean) => void\n openMobile: boolean\n setOpenMobile: (open: boolean) => void\n isMobile: boolean\n toggleSidebar: () => void\n /** Used by Sidebar to tell the provider what kind of sidebars are mounted. */\n registerSidebar: (collapsible: SidebarCollapsible) => () => void\n}\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null)\n\n/**\n * What the nearest Sidebar tells its menu buttons: whether a button's\n * `tooltip` applies, and where the tooltip portals (the Sidebar's own element,\n * so a class-scoped theme on an ancestor of the sidebar reaches it).\n */\ntype SidebarScopeProps = {\n collapsible: SidebarCollapsible\n container: HTMLElement | null\n}\n\nconst SidebarScopeContext = React.createContext<SidebarScopeProps | null>(null)\n\nfunction useSidebar() {\n const context = React.useContext(SidebarContext)\n if (!context) {\n throw new Error(\"useSidebar must be used within a SidebarProvider.\")\n }\n return context\n}\n\nexport interface SidebarProviderProps extends React.ComponentProps<\"div\"> {\n defaultOpen?: boolean\n open?: boolean\n onOpenChange?: (open: boolean) => void\n /**\n * Binds Cmd/Ctrl+B to toggle the sidebar. Defaults to `true`. The binding is\n * skipped, and `preventDefault` is not called, while the only sidebars\n * mounted are `collapsible=\"none\"` (nothing would toggle). Pass `false` to\n * never bind it.\n */\n keyboardShortcut?: boolean\n}\n\nconst SidebarProvider = React.forwardRef<HTMLDivElement, SidebarProviderProps>(\n (\n {\n defaultOpen = true,\n open: openProp,\n onOpenChange: setOpenProp,\n keyboardShortcut = true,\n className,\n style,\n children,\n ...props\n },\n ref\n ) => {\n const isMobile = React.useSyncExternalStore(\n subscribeIsMobile,\n getIsMobileSnapshot,\n getIsMobileServerSnapshot\n )\n const [openMobile, setOpenMobile] = React.useState(false)\n const [_open, _setOpen] = React.useState(defaultOpen)\n const open = openProp ?? _open\n\n const setOpen = React.useCallback(\n (value: boolean | ((value: boolean) => boolean)) => {\n const openState = typeof value === \"function\" ? value(open) : value\n if (setOpenProp) {\n setOpenProp(openState)\n } else {\n _setOpen(openState)\n }\n document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`\n },\n [setOpenProp, open]\n )\n\n const toggleSidebar = React.useCallback(() => {\n return isMobile\n ? setOpenMobile((prev) => !prev)\n : setOpen((prev) => !prev)\n }, [isMobile, setOpen, setOpenMobile])\n\n // How many collapsible and static (`collapsible=\"none\"`) sidebars are\n // mounted under this provider. A static-only provider has nothing to toggle.\n const mountedRef = React.useRef({ collapsible: 0, none: 0 })\n const registerSidebar = React.useCallback((collapsible: SidebarCollapsible) => {\n const key = collapsible === \"none\" ? \"none\" : \"collapsible\"\n mountedRef.current[key] += 1\n return () => {\n mountedRef.current[key] -= 1\n }\n }, [])\n\n React.useEffect(() => {\n if (!keyboardShortcut) return\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {\n const { collapsible, none } = mountedRef.current\n if (none > 0 && collapsible === 0) return\n event.preventDefault()\n toggleSidebar()\n }\n }\n window.addEventListener(\"keydown\", handleKeyDown)\n return () => window.removeEventListener(\"keydown\", handleKeyDown)\n }, [keyboardShortcut, toggleSidebar])\n\n const state = open ? \"expanded\" : \"collapsed\"\n\n const contextValue = React.useMemo<SidebarContextProps>(\n () => ({\n state,\n open,\n setOpen,\n isMobile,\n openMobile,\n setOpenMobile,\n toggleSidebar,\n registerSidebar,\n }),\n [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar, registerSidebar]\n )\n\n return (\n <SidebarContext.Provider value={contextValue}>\n <TooltipProvider delayDuration={200}>\n <div\n ref={ref}\n data-slot=\"sidebar-wrapper\"\n style={\n {\n \"--sidebar-width\": SIDEBAR_WIDTH,\n \"--sidebar-width-mobile\": SIDEBAR_WIDTH_MOBILE,\n \"--sidebar-width-icon\": SIDEBAR_WIDTH_ICON,\n ...style,\n } as React.CSSProperties\n }\n className={cn(styles.wrapper, className)}\n {...props}\n >\n {children}\n </div>\n </TooltipProvider>\n </SidebarContext.Provider>\n )\n }\n)\nSidebarProvider.displayName = \"SidebarProvider\"\n\ntype SidebarCollapsible = \"offcanvas\" | \"icon\" | \"none\"\n\nexport interface SidebarProps extends React.ComponentProps<\"div\"> {\n side?: \"left\" | \"right\"\n variant?: \"sidebar\" | \"floating\" | \"inset\"\n collapsible?: SidebarCollapsible\n /**\n * `fixed` (default) pins the sidebar to the viewport at full height, the\n * page-sidebar shape. `contained` lays it out inside its parent instead, so\n * an icon rail can sit under an app bar without covering it. A contained\n * sidebar is as tall as the SidebarProvider that holds it.\n */\n position?: \"fixed\" | \"contained\"\n}\n\nconst Sidebar = React.forwardRef<HTMLDivElement, SidebarProps>(\n (\n {\n side = \"left\",\n variant = \"sidebar\",\n collapsible = \"offcanvas\",\n position = \"fixed\",\n className,\n children,\n ...props\n },\n ref\n ) => {\n const { state, registerSidebar } = useSidebar()\n const [element, setElement] = React.useState<HTMLDivElement | null>(null)\n const setRefs = React.useCallback(\n (node: HTMLDivElement | null) => {\n setElement(node)\n if (typeof ref === \"function\") ref(node)\n else if (ref) ref.current = node\n },\n [ref]\n )\n\n React.useEffect(() => registerSidebar(collapsible), [registerSidebar, collapsible])\n\n const scope = React.useMemo<SidebarScopeProps>(\n () => ({ collapsible, container: element }),\n [collapsible, element]\n )\n\n if (collapsible === \"none\") {\n return (\n <SidebarScopeContext.Provider value={scope}>\n <div\n ref={setRefs}\n data-slot=\"sidebar\"\n className={cn(styles.sidebarStatic, className)}\n {...props}\n >\n {children}\n </div>\n </SidebarScopeContext.Provider>\n )\n }\n\n return (\n <SidebarScopeContext.Provider value={scope}>\n <div\n ref={setRefs}\n className={cn(styles.sidebarContainer, className)}\n data-state={state}\n data-collapsible={state === \"collapsed\" ? collapsible : \"\"}\n data-variant={variant}\n data-position={position}\n data-side={side}\n data-slot=\"sidebar\"\n {...props}\n >\n <div\n data-slot=\"sidebar-gap\"\n className={styles.sidebarGap}\n />\n <div\n data-slot=\"sidebar-inner\"\n data-side={side}\n className={styles.sidebarInner}\n >\n <div\n data-sidebar=\"sidebar\"\n className={styles.sidebarContent}\n >\n {children}\n </div>\n </div>\n </div>\n </SidebarScopeContext.Provider>\n )\n }\n)\nSidebar.displayName = \"Sidebar\"\n\nconst SidebarTrigger = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<\"button\">\n>(({ className, onClick, children, ...props }, ref) => {\n const { toggleSidebar } = useSidebar()\n\n return (\n <button\n ref={ref}\n data-sidebar=\"trigger\"\n data-slot=\"sidebar-trigger\"\n className={cn(styles.trigger, className)}\n onClick={(event) => {\n onClick?.(event)\n toggleSidebar()\n }}\n {...props}\n >\n {children ?? (\n <>\n <SidebarIcon />\n <span className={styles.srOnly}>Toggle Sidebar</span>\n </>\n )}\n </button>\n )\n})\nSidebarTrigger.displayName = \"SidebarTrigger\"\n\nconst SidebarRail = React.forwardRef<HTMLButtonElement, React.ComponentProps<\"button\">>(\n ({ className, ...props }, ref) => {\n const { toggleSidebar } = useSidebar()\n\n return (\n <button\n ref={ref}\n data-sidebar=\"rail\"\n data-slot=\"sidebar-rail\"\n aria-label=\"Toggle Sidebar\"\n tabIndex={-1}\n onClick={toggleSidebar}\n title=\"Toggle Sidebar\"\n className={cn(styles.rail, className)}\n {...props}\n />\n )\n }\n)\nSidebarRail.displayName = \"SidebarRail\"\n\nconst SidebarInset = React.forwardRef<HTMLElement, React.ComponentProps<\"main\">>(\n ({ className, ...props }, ref) => (\n <main\n ref={ref}\n data-slot=\"sidebar-inset\"\n className={cn(styles.inset, className)}\n {...props}\n />\n )\n)\nSidebarInset.displayName = \"SidebarInset\"\n\nconst SidebarHeader = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"sidebar-header\"\n data-sidebar=\"header\"\n className={cn(styles.header, className)}\n {...props}\n />\n )\n)\nSidebarHeader.displayName = \"SidebarHeader\"\n\nconst SidebarFooter = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"sidebar-footer\"\n data-sidebar=\"footer\"\n className={cn(styles.footer, className)}\n {...props}\n />\n )\n)\nSidebarFooter.displayName = \"SidebarFooter\"\n\nconst SidebarSeparator = React.forwardRef<HTMLHRElement, React.ComponentProps<\"hr\">>(\n ({ className, ...props }, ref) => (\n <hr\n ref={ref}\n data-slot=\"sidebar-separator\"\n data-sidebar=\"separator\"\n className={cn(styles.separator, className)}\n {...props}\n />\n )\n)\nSidebarSeparator.displayName = \"SidebarSeparator\"\n\nconst SidebarContent = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"sidebar-content\"\n data-sidebar=\"content\"\n className={cn(styles.contentArea, className)}\n {...props}\n />\n )\n)\nSidebarContent.displayName = \"SidebarContent\"\n\nconst SidebarGroup = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"sidebar-group\"\n data-sidebar=\"group\"\n className={cn(styles.group, className)}\n {...props}\n />\n )\n)\nSidebarGroup.displayName = \"SidebarGroup\"\n\nconst SidebarGroupLabel = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"sidebar-group-label\"\n data-sidebar=\"group-label\"\n className={cn(styles.groupLabel, className)}\n {...props}\n />\n )\n)\nSidebarGroupLabel.displayName = \"SidebarGroupLabel\"\n\nconst SidebarGroupAction = React.forwardRef<HTMLButtonElement, React.ComponentProps<\"button\">>(\n ({ className, ...props }, ref) => (\n <button\n ref={ref}\n data-slot=\"sidebar-group-action\"\n data-sidebar=\"group-action\"\n className={cn(styles.groupAction, className)}\n {...props}\n />\n )\n)\nSidebarGroupAction.displayName = \"SidebarGroupAction\"\n\nconst SidebarGroupContent = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"sidebar-group-content\"\n data-sidebar=\"group-content\"\n className={cn(styles.groupContent, className)}\n {...props}\n />\n )\n)\nSidebarGroupContent.displayName = \"SidebarGroupContent\"\n\nconst SidebarMenu = React.forwardRef<HTMLUListElement, React.ComponentProps<\"ul\">>(\n ({ className, ...props }, ref) => (\n <ul\n ref={ref}\n data-slot=\"sidebar-menu\"\n data-sidebar=\"menu\"\n className={cn(styles.menu, className)}\n {...props}\n />\n )\n)\nSidebarMenu.displayName = \"SidebarMenu\"\n\nconst SidebarMenuItem = React.forwardRef<HTMLLIElement, React.ComponentProps<\"li\">>(\n ({ className, ...props }, ref) => (\n <li\n ref={ref}\n data-slot=\"sidebar-menu-item\"\n data-sidebar=\"menu-item\"\n className={cn(styles.menuItem, className)}\n {...props}\n />\n )\n)\nSidebarMenuItem.displayName = \"SidebarMenuItem\"\n\nconst sidebarMenuButtonVariants = cva(styles.menuButton, {\n variants: {\n variant: {\n default: styles.menuButtonDefault,\n outline: styles.menuButtonOutline,\n },\n size: {\n default: styles.menuButtonSizeDefault,\n sm: styles.menuButtonSizeSm,\n lg: styles.menuButtonSizeLg,\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n})\n\nexport type SidebarMenuButtonTooltip =\n | string\n | (Omit<TooltipContentProps, \"children\"> & { children: React.ReactNode })\n\nexport interface SidebarMenuButtonProps\n extends React.ComponentProps<\"button\">,\n VariantProps<typeof sidebarMenuButtonVariants> {\n /** Render the child (a Next `<Link>`, an `<a>`) as the button, via Radix Slot. */\n asChild?: boolean\n isActive?: boolean\n /**\n * Shown in a Tooltip when the sidebar is an icon rail: collapsed to icons\n * (`collapsible=\"icon\"`), or a `collapsible=\"none\"` sidebar (a static rail has\n * no collapsed state, so passing `tooltip` is the opt-in). Never shown on\n * mobile or in an expanded sidebar, where the visible label names the item.\n * A string (or a string `children`) also names the button for assistive tech\n * while the label is hidden, unless the button sets its own `aria-label` or\n * `aria-labelledby`. Pass an object to set `side`, `container` and the rest of\n * TooltipContent's props; the tooltip portals into the Sidebar by default.\n */\n tooltip?: SidebarMenuButtonTooltip\n}\n\nconst SidebarMenuButton = React.forwardRef<HTMLButtonElement, SidebarMenuButtonProps>(\n (\n {\n asChild = false,\n className,\n variant = \"default\",\n size = \"default\",\n isActive,\n tooltip,\n children,\n ...props\n },\n ref\n ) => {\n const { state, isMobile } = useSidebar()\n const scope = React.useContext(SidebarScopeContext)\n const [tooltipOpen, setTooltipOpen] = React.useState(false)\n\n const tooltipApplies =\n tooltip != null &&\n !isMobile &&\n scope != null &&\n (scope.collapsible === \"none\" ||\n (scope.collapsible === \"icon\" && state === \"collapsed\"))\n\n const tooltipProps = typeof tooltip === \"object\" ? tooltip : undefined\n const tooltipLabel = typeof tooltip === \"string\" ? tooltip : tooltipProps?.children\n const needsName =\n tooltipApplies &&\n typeof tooltipLabel === \"string\" &&\n props[\"aria-label\"] === undefined &&\n props[\"aria-labelledby\"] === undefined\n\n const Comp = asChild ? Slot : \"button\"\n const button = (\n <Comp\n ref={ref}\n data-slot=\"sidebar-menu-button\"\n data-sidebar=\"menu-button\"\n data-size={size}\n data-active={isActive || undefined}\n className={cn(sidebarMenuButtonVariants({ variant, size }), className)}\n {...(needsName ? { \"aria-label\": tooltipLabel as string } : null)}\n {...props}\n >\n {children}\n </Comp>\n )\n\n if (tooltip == null) return button\n\n const { children: tooltipChildren, ...contentProps } =\n typeof tooltip === \"object\" ? tooltip : { children: tooltip }\n\n return (\n <Tooltip open={tooltipApplies && tooltipOpen} onOpenChange={setTooltipOpen}>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent\n side=\"right\"\n container={scope?.container}\n {...contentProps}\n >\n {tooltipChildren}\n </TooltipContent>\n </Tooltip>\n )\n }\n)\nSidebarMenuButton.displayName = \"SidebarMenuButton\"\n\nconst SidebarMenuAction = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<\"button\"> & { showOnHover?: boolean }\n>(({ className, showOnHover, ...props }, ref) => (\n <button\n ref={ref}\n data-slot=\"sidebar-menu-action\"\n data-sidebar=\"menu-action\"\n className={cn(\n styles.menuAction,\n showOnHover && styles.menuActionHover,\n className\n )}\n {...props}\n />\n))\nSidebarMenuAction.displayName = \"SidebarMenuAction\"\n\nconst SidebarMenuBadge = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"sidebar-menu-badge\"\n data-sidebar=\"menu-badge\"\n className={cn(styles.menuBadge, className)}\n {...props}\n />\n )\n)\nSidebarMenuBadge.displayName = \"SidebarMenuBadge\"\n\nconst SidebarMenuSub = React.forwardRef<HTMLUListElement, React.ComponentProps<\"ul\">>(\n ({ className, ...props }, ref) => (\n <ul\n ref={ref}\n data-slot=\"sidebar-menu-sub\"\n data-sidebar=\"menu-sub\"\n className={cn(styles.menuSub, className)}\n {...props}\n />\n )\n)\nSidebarMenuSub.displayName = \"SidebarMenuSub\"\n\nconst SidebarMenuSubItem = React.forwardRef<HTMLLIElement, React.ComponentProps<\"li\">>(\n ({ className, ...props }, ref) => (\n <li\n ref={ref}\n data-slot=\"sidebar-menu-sub-item\"\n data-sidebar=\"menu-sub-item\"\n className={cn(styles.menuSubItem, className)}\n {...props}\n />\n )\n)\nSidebarMenuSubItem.displayName = \"SidebarMenuSubItem\"\n\nexport interface SidebarMenuSubButtonProps extends React.ComponentProps<\"a\"> {\n asChild?: boolean\n size?: \"sm\" | \"md\"\n isActive?: boolean\n}\n\nconst SidebarMenuSubButton = React.forwardRef<HTMLAnchorElement, SidebarMenuSubButtonProps>(\n ({ className, size = \"md\", isActive, ...props }, ref) => (\n <a\n ref={ref}\n data-slot=\"sidebar-menu-sub-button\"\n data-sidebar=\"menu-sub-button\"\n data-size={size}\n data-active={isActive || undefined}\n className={cn(\n styles.menuSubButton,\n size === \"sm\" && styles.menuSubButtonSm,\n className\n )}\n {...props}\n />\n )\n)\nSidebarMenuSubButton.displayName = \"SidebarMenuSubButton\"\n\nexport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupAction,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarInset,\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuBadge,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarProvider,\n SidebarRail,\n SidebarSeparator,\n SidebarTrigger,\n useSidebar,\n sidebarMenuButtonVariants,\n}\n"
|
|
990
1016
|
},
|
|
991
1017
|
{
|
|
992
1018
|
"path": "components/ui/sidebar/sidebar.module.css",
|
|
993
1019
|
"type": "registry:ui",
|
|
994
|
-
"content": "/* Sidebar wrapper */\n.wrapper {\n display: flex;\n min-height: 100svh;\n width: 100%;\n background-color: var(--sidebar-bg);\n}\n\n/* Sidebar static (collapsible=none) */\n.sidebarStatic {\n display: flex;\n height: 100%;\n width: var(--sidebar-width, 16rem);\n flex-direction: column;\n background-color: var(--sidebar-bg);\n color: var(--sidebar-text);\n}\n\n/* Sidebar container (collapsible versions) */\n.sidebarContainer {\n position: relative;\n color: var(--sidebar-text);\n}\n\n/* Sidebar gap (reserves layout space) */\n.sidebarGap {\n width: var(--sidebar-width, 16rem);\n background: transparent;\n transition: width var(--motion-duration-normal, 200ms) var(--motion-easing-default, ease-in-out);\n flex-shrink: 0;\n}\n\n.sidebarContainer[data-state=\"collapsed\"][data-collapsible=\"offcanvas\"] .sidebarGap {\n width: 0;\n}\n\n.sidebarContainer[data-state=\"collapsed\"][data-collapsible=\"icon\"] .sidebarGap {\n width: var(--sidebar-width-icon, 3rem);\n}\n\n/* Sidebar inner */\n.sidebarInner {\n position: fixed;\n inset-block: 0;\n z-index: 10;\n display: flex;\n height: 100svh;\n width: var(--sidebar-width, 16rem);\n transition: left var(--motion-duration-normal, 200ms) var(--motion-easing-default, ease-in-out), right var(--motion-duration-normal, 200ms) var(--motion-easing-default, ease-in-out), width var(--motion-duration-normal, 200ms) var(--motion-easing-default, ease-in-out);\n}\n\n.sidebarInner[data-side=\"left\"] {\n left: 0;\n}\n\n.sidebarContainer[data-state=\"collapsed\"][data-collapsible=\"offcanvas\"] .sidebarInner[data-side=\"left\"] {\n left: calc(var(--sidebar-width, 16rem) * -1);\n}\n\n.sidebarInner[data-side=\"right\"] {\n right: 0;\n}\n\n.sidebarContainer[data-state=\"collapsed\"][data-collapsible=\"offcanvas\"] .sidebarInner[data-side=\"right\"] {\n right: calc(var(--sidebar-width, 16rem) * -1);\n}\n\n.sidebarContainer[data-state=\"collapsed\"][data-collapsible=\"icon\"] .sidebarInner {\n width: var(--sidebar-width-icon, 3rem);\n}\n\n/* Sidebar content container */\n.sidebarContent {\n display: flex;\n width: 100%;\n height: 100%;\n flex-direction: column;\n background-color: var(--sidebar-bg);\n overflow: hidden;\n}\n\n.sidebarContainer[data-variant=\"floating\"] .sidebarContent,\n.sidebarContainer[data-variant=\"inset\"] .sidebarContent {\n border-radius: var(--radius-lg, 0.5rem);\n box-shadow: var(--shadow-sm);\n}\n\n/* Trigger button */\n.trigger {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n background: transparent;\n cursor: pointer;\n border-radius: var(--radius-sm, 0.25rem);\n padding: calc(var(--spacing-1, 0.25rem) * 1.5);\n color: var(--text-primary, #111827);\n transition: background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n.trigger:active {\n transform: translateY(1px);\n transition: none;\n}\n\n@media (hover: hover) {\n .trigger:hover {\n background-color: var(--surface-muted, #f3f4f6);\n }\n}\n\n.trigger:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n/* Rail */\n.rail {\n position: absolute;\n inset-block: 0;\n z-index: 20;\n width: 1rem;\n border: none;\n background: transparent;\n cursor: col-resize;\n transition: background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n@media (hover: hover) {\n .rail:hover {\n background-color: var(--sidebar-border);\n }\n}\n\n/* Inset main content area */\n.inset {\n position: relative;\n display: flex;\n width: 100%;\n flex: 1;\n flex-direction: column;\n background-color: var(--surface-page, #ffffff);\n}\n\n/* Header */\n.header {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-2, 0.5rem);\n padding: var(--sidebar-header-padding, var(--spacing-2, 0.5rem));\n}\n\n/* Footer */\n.footer {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-2, 0.5rem);\n padding: var(--sidebar-footer-padding, var(--spacing-2, 0.5rem));\n}\n\n/* Separator */\n.separator {\n margin-inline: var(--spacing-2, 0.5rem);\n border: none;\n height: 1px;\n background-color: var(--sidebar-border);\n}\n\n/* Content area */\n.contentArea {\n display: flex;\n min-height: 0;\n flex: 1;\n flex-direction: column;\n gap: var(--spacing-2, 0.5rem);\n overflow: auto;\n padding: var(--sidebar-content-padding, var(--spacing-1, 0.25rem));\n}\n\n/* Group */\n.group {\n position: relative;\n display: flex;\n width: 100%;\n min-width: 0;\n flex-direction: column;\n padding: var(--spacing-2, 0.5rem);\n}\n\n/* Group label */\n.groupLabel {\n display: flex;\n height: var(--sidebar-group-label-height, 2rem);\n flex-shrink: 0;\n align-items: center;\n border-radius: var(--radius-sm, 0.25rem);\n padding-inline: var(--spacing-2, 0.5rem);\n font-size: var(--sidebar-group-label-font-size, var(--font-size-xs, 0.75rem));\n font-weight: var(--sidebar-group-label-font-weight, var(--font-weight-medium, 500));\n letter-spacing: var(--sidebar-group-label-letter-spacing, inherit);\n text-transform: var(--sidebar-group-label-text-transform, inherit);\n color: var(--sidebar-text-muted);\n transition: margin var(--motion-duration-normal, 200ms) var(--motion-easing-default, ease-in-out), opacity var(--motion-duration-normal, 200ms) var(--motion-easing-default, ease-in-out);\n}\n\n/* Group action */\n.groupAction {\n position: absolute;\n top: 0.875rem;\n right: 0.75rem;\n display: flex;\n aspect-ratio: 1;\n width: 1.25rem;\n align-items: center;\n justify-content: center;\n border-radius: var(--radius-sm, 0.25rem);\n border: none;\n background: transparent;\n cursor: pointer;\n padding: 0;\n color: var(--sidebar-text);\n transition: background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n.groupAction:active {\n transform: translateY(1px);\n transition: none;\n}\n\n@media (hover: hover) {\n .groupAction:hover {\n background-color: var(--sidebar-accent-bg);\n color: var(--sidebar-accent-text);\n }\n}\n\n/* Group content */\n.groupContent {\n width: 100%;\n font-size: var(--font-size-sm, 0.875rem);\n}\n\n/* Menu */\n.menu {\n display: flex;\n width: 100%;\n min-width: 0;\n flex-direction: column;\n gap: var(--sidebar-menu-gap, var(--spacing-1, 0.25rem));\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n/* Menu item */\n.menuItem {\n position: relative;\n}\n\n/* Menu button base — the nav-item treatment. VI-625 hooks let a theme pin the\n item shape (radius, height, inset, type) once instead of every admin shell\n re-deriving it. */\n.menuButton {\n display: flex;\n width: 100%;\n align-items: center;\n gap: var(--sidebar-item-gap, var(--spacing-2, 0.5rem));\n overflow: hidden;\n border-radius: var(--sidebar-item-radius, var(--radius-md, 0.375rem));\n border: none;\n text-align: left;\n font-size: var(--sidebar-item-font-size, var(--font-size-sm, 0.875rem));\n font-weight: var(--sidebar-item-font-weight, inherit);\n letter-spacing: var(--sidebar-item-letter-spacing, inherit);\n text-transform: var(--sidebar-item-text-transform, inherit);\n cursor: pointer;\n background: transparent;\n color: var(--sidebar-text);\n transition: background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n outline: none;\n}\n\n.menuButton:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n.menuButton:disabled {\n pointer-events: none;\n opacity: 0.5;\n}\n\n.menuButton[data-active] {\n background-color: var(--sidebar-accent-bg);\n color: var(--sidebar-accent-text);\n font-weight: var(--sidebar-item-active-font-weight, var(--font-weight-medium, 500));\n}\n\n/* Menu button variants */\n.menuButton:active:not(:disabled) {\n transform: translateY(1px);\n transition: none;\n}\n\n@media (hover: hover) {\n .menuButtonDefault:hover {\n background-color: var(--sidebar-accent-bg);\n color: var(--sidebar-accent-text);\n }\n}\n\n.menuButtonOutline {\n background-color: var(--surface-page, #ffffff);\n box-shadow: inset 0 0 0 1px var(--sidebar-border);\n}\n\n@media (hover: hover) {\n .menuButtonOutline:hover {\n background-color: var(--sidebar-accent-bg);\n color: var(--sidebar-accent-text);\n }\n}\n\n/* Menu button sizes */\n.menuButtonSizeDefault {\n height: var(--sidebar-item-height, 2.25rem);\n padding: var(--sidebar-item-padding, 0 var(--spacing-3, 0.75rem));\n font-size: var(--sidebar-item-font-size, var(--font-size-sm, 0.875rem));\n}\n\n.menuButtonSizeSm {\n height: 2rem;\n padding: 0 calc(var(--spacing-2, 0.5rem) + var(--spacing-1, 0.25rem) / 2);\n font-size: var(--font-size-xs, 0.75rem);\n}\n\n.menuButtonSizeLg {\n height: 3.5rem;\n padding: 0 var(--spacing-3, 0.75rem);\n font-size: var(--font-size-sm, 0.875rem);\n}\n\n/* Menu action */\n.menuAction {\n position: absolute;\n top: 0.375rem;\n right: 0.25rem;\n display: flex;\n aspect-ratio: 1;\n width: 1.25rem;\n align-items: center;\n justify-content: center;\n border-radius: var(--radius-sm, 0.25rem);\n border: none;\n background: transparent;\n cursor: pointer;\n padding: 0;\n color: var(--sidebar-text);\n transition: background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n.menuAction:active {\n transform: translateY(1px);\n transition: none;\n}\n\n@media (hover: hover) {\n .menuAction:hover {\n background-color: var(--sidebar-accent-bg);\n color: var(--sidebar-accent-text);\n }\n}\n\n.menuActionHover {\n opacity: 0;\n}\n\n@media (hover: hover) {\n .menuItem:hover .menuActionHover {\n opacity: 1;\n }\n}\n\n.menuItem:focus-within .menuActionHover {\n opacity: 1;\n}\n\n/* Menu badge */\n.menuBadge {\n pointer-events: none;\n position: absolute;\n right: 0.25rem;\n display: flex;\n height: 1.25rem;\n min-width: 1.25rem;\n align-items: center;\n justify-content: center;\n border-radius: var(--radius-sm, 0.25rem);\n padding-inline: var(--spacing-1, 0.25rem);\n font-size: var(--font-size-xs, 0.75rem);\n font-weight: var(--font-weight-medium, 500);\n color: var(--sidebar-text);\n font-variant-numeric: tabular-nums;\n user-select: none;\n}\n\n/* Menu sub */\n.menuSub {\n margin-inline: calc(var(--spacing-3, 0.75rem) + var(--spacing-1, 0.25rem) / 2);\n display: flex;\n min-width: 0;\n flex-direction: column;\n gap: var(--spacing-1, 0.25rem);\n border-left: 1px solid var(--sidebar-border);\n padding-inline: calc(var(--spacing-2, 0.5rem) + var(--spacing-1, 0.25rem) / 2);\n padding-block: calc(var(--spacing-1, 0.25rem) / 2);\n list-style: none;\n margin: 0;\n margin-inline: calc(var(--spacing-3, 0.75rem) + var(--spacing-1, 0.25rem) / 2);\n}\n\n/* Menu sub item */\n.menuSubItem {\n position: relative;\n}\n\n/* Menu sub button */\n.menuSubButton {\n display: flex;\n height: 1.75rem;\n min-width: 0;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n overflow: hidden;\n border-radius: var(--radius-sm, 0.25rem);\n padding-inline: var(--spacing-2, 0.5rem);\n font-size: var(--font-size-sm, 0.875rem);\n color: var(--sidebar-text);\n text-decoration: none;\n transition: background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n outline: none;\n}\n\n.menuSubButton:active {\n transform: translateY(1px);\n transition: none;\n}\n\n@media (hover: hover) {\n .menuSubButton:hover {\n background-color: var(--sidebar-accent-bg);\n color: var(--sidebar-accent-text);\n }\n}\n\n.menuSubButton:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n.menuSubButton[data-active] {\n background-color: var(--sidebar-accent-bg);\n color: var(--sidebar-accent-text);\n}\n\n.menuSubButtonSm {\n font-size: var(--font-size-xs, 0.75rem);\n}\n\n/* Screen reader only */\n.srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border-width: 0;\n}\n"
|
|
1020
|
+
"content": "/* Sidebar wrapper */\n.wrapper {\n display: flex;\n min-height: 100svh;\n width: 100%;\n background-color: var(--sidebar-bg);\n}\n\n/* Sidebar static (collapsible=none) */\n.sidebarStatic {\n display: flex;\n height: 100%;\n width: var(--sidebar-width, 16rem);\n flex-direction: column;\n background-color: var(--sidebar-bg);\n color: var(--sidebar-text);\n}\n\n/* Sidebar container (collapsible versions) */\n.sidebarContainer {\n position: relative;\n color: var(--sidebar-text);\n}\n\n/* Sidebar gap (reserves layout space) */\n.sidebarGap {\n width: var(--sidebar-width, 16rem);\n background: transparent;\n transition: width var(--motion-duration-normal, 200ms) var(--motion-easing-default, ease-in-out);\n flex-shrink: 0;\n}\n\n.sidebarContainer[data-state=\"collapsed\"][data-collapsible=\"offcanvas\"] .sidebarGap {\n width: 0;\n}\n\n.sidebarContainer[data-state=\"collapsed\"][data-collapsible=\"icon\"] .sidebarGap {\n width: var(--sidebar-width-icon, 3rem);\n}\n\n/* Sidebar inner */\n.sidebarInner {\n position: fixed;\n inset-block: 0;\n z-index: 10;\n display: flex;\n height: 100svh;\n width: var(--sidebar-width, 16rem);\n transition: left var(--motion-duration-normal, 200ms) var(--motion-easing-default, ease-in-out), right var(--motion-duration-normal, 200ms) var(--motion-easing-default, ease-in-out), width var(--motion-duration-normal, 200ms) var(--motion-easing-default, ease-in-out);\n}\n\n.sidebarInner[data-side=\"left\"] {\n left: 0;\n}\n\n.sidebarContainer[data-state=\"collapsed\"][data-collapsible=\"offcanvas\"] .sidebarInner[data-side=\"left\"] {\n left: calc(var(--sidebar-width, 16rem) * -1);\n}\n\n.sidebarInner[data-side=\"right\"] {\n right: 0;\n}\n\n.sidebarContainer[data-state=\"collapsed\"][data-collapsible=\"offcanvas\"] .sidebarInner[data-side=\"right\"] {\n right: calc(var(--sidebar-width, 16rem) * -1);\n}\n\n.sidebarContainer[data-state=\"collapsed\"][data-collapsible=\"icon\"] .sidebarInner {\n width: var(--sidebar-width-icon, 3rem);\n}\n\n/* Contained (position=\"contained\"): the sidebar lays out inside its parent\n instead of pinning to the viewport, so a rail can sit under an app bar. */\n.sidebarContainer[data-position=\"contained\"] .sidebarInner {\n position: absolute;\n height: auto;\n}\n\n/* Sidebar content container */\n.sidebarContent {\n display: flex;\n width: 100%;\n height: 100%;\n flex-direction: column;\n background-color: var(--sidebar-bg);\n overflow: hidden;\n}\n\n.sidebarContainer[data-variant=\"floating\"] .sidebarContent,\n.sidebarContainer[data-variant=\"inset\"] .sidebarContent {\n border-radius: var(--radius-lg, 0.5rem);\n box-shadow: var(--shadow-sm);\n}\n\n/* Trigger button */\n.trigger {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n background: transparent;\n cursor: pointer;\n border-radius: var(--radius-sm, 0.25rem);\n padding: calc(var(--spacing-1, 0.25rem) * 1.5);\n color: var(--text-primary, #111827);\n transition: background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n.trigger:active {\n transform: translateY(1px);\n transition: none;\n}\n\n@media (hover: hover) {\n .trigger:hover {\n background-color: var(--surface-muted, #f3f4f6);\n }\n}\n\n.trigger:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n/* Rail */\n.rail {\n position: absolute;\n inset-block: 0;\n z-index: 20;\n width: 1rem;\n border: none;\n background: transparent;\n cursor: col-resize;\n transition: background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n@media (hover: hover) {\n .rail:hover {\n background-color: var(--sidebar-border);\n }\n}\n\n/* Inset main content area */\n.inset {\n position: relative;\n display: flex;\n width: 100%;\n flex: 1;\n flex-direction: column;\n background-color: var(--surface-page, #ffffff);\n}\n\n/* Header */\n.header {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-2, 0.5rem);\n padding: var(--sidebar-header-padding, var(--spacing-2, 0.5rem));\n}\n\n/* Footer */\n.footer {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-2, 0.5rem);\n padding: var(--sidebar-footer-padding, var(--spacing-2, 0.5rem));\n}\n\n/* Separator */\n.separator {\n margin-inline: var(--spacing-2, 0.5rem);\n border: none;\n height: 1px;\n background-color: var(--sidebar-border);\n}\n\n/* Content area */\n.contentArea {\n display: flex;\n min-height: 0;\n flex: 1;\n flex-direction: column;\n gap: var(--spacing-2, 0.5rem);\n overflow: auto;\n padding: var(--sidebar-content-padding, var(--spacing-1, 0.25rem));\n}\n\n/* Group */\n.group {\n position: relative;\n display: flex;\n width: 100%;\n min-width: 0;\n flex-direction: column;\n padding: var(--spacing-2, 0.5rem);\n}\n\n/* Group label */\n.groupLabel {\n display: flex;\n height: var(--sidebar-group-label-height, 2rem);\n flex-shrink: 0;\n align-items: center;\n border-radius: var(--radius-sm, 0.25rem);\n padding-inline: var(--spacing-2, 0.5rem);\n font-size: var(--sidebar-group-label-font-size, var(--font-size-xs, 0.75rem));\n font-weight: var(--sidebar-group-label-font-weight, var(--font-weight-medium, 500));\n letter-spacing: var(--sidebar-group-label-letter-spacing, inherit);\n text-transform: var(--sidebar-group-label-text-transform, inherit);\n color: var(--sidebar-text-muted);\n transition: margin var(--motion-duration-normal, 200ms) var(--motion-easing-default, ease-in-out), opacity var(--motion-duration-normal, 200ms) var(--motion-easing-default, ease-in-out);\n}\n\n/* Group action */\n.groupAction {\n position: absolute;\n top: 0.875rem;\n right: 0.75rem;\n display: flex;\n aspect-ratio: 1;\n width: 1.25rem;\n align-items: center;\n justify-content: center;\n border-radius: var(--radius-sm, 0.25rem);\n border: none;\n background: transparent;\n cursor: pointer;\n padding: 0;\n color: var(--sidebar-text);\n transition: background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n.groupAction:active {\n transform: translateY(1px);\n transition: none;\n}\n\n@media (hover: hover) {\n .groupAction:hover {\n background-color: var(--sidebar-accent-bg);\n color: var(--sidebar-accent-text);\n }\n}\n\n/* Group content */\n.groupContent {\n width: 100%;\n font-size: var(--font-size-sm, 0.875rem);\n}\n\n/* Menu */\n.menu {\n display: flex;\n width: 100%;\n min-width: 0;\n flex-direction: column;\n gap: var(--sidebar-menu-gap, var(--spacing-1, 0.25rem));\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n/* Menu item */\n.menuItem {\n position: relative;\n}\n\n/* Menu button base — the nav-item treatment. VI-625 hooks let a theme pin the\n item shape (radius, height, inset, type) once instead of every admin shell\n re-deriving it. */\n.menuButton {\n display: flex;\n width: 100%;\n align-items: center;\n gap: var(--sidebar-item-gap, var(--spacing-2, 0.5rem));\n overflow: hidden;\n border-radius: var(--sidebar-item-radius, var(--radius-md, 0.375rem));\n border: none;\n text-align: left;\n font-size: var(--sidebar-item-font-size, var(--font-size-sm, 0.875rem));\n font-weight: var(--sidebar-item-font-weight, inherit);\n letter-spacing: var(--sidebar-item-letter-spacing, inherit);\n text-transform: var(--sidebar-item-text-transform, inherit);\n cursor: pointer;\n background: transparent;\n color: var(--sidebar-text);\n transition: background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n outline: none;\n}\n\n/* Inset ring: .sidebarContent clips overflow, so an outset ring is cut to a\n bottom line on the icon rail. Drawing it inside the button (width + offset token) keeps all four\n sides visible (WCAG 2.4.7) without consumer CSS. */\n.menuButton:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: calc(-1 * (var(--focus-ring-width, 2px) + var(--focus-ring-offset, 0px)));\n}\n\n.menuButton:disabled {\n pointer-events: none;\n opacity: 0.5;\n}\n\n.menuButton[data-active] {\n background-color: var(--sidebar-accent-bg);\n color: var(--sidebar-accent-text);\n font-weight: var(--sidebar-item-active-font-weight, var(--font-weight-medium, 500));\n}\n\n/* Menu button variants */\n.menuButton:active:not(:disabled) {\n transform: translateY(1px);\n transition: none;\n}\n\n@media (hover: hover) {\n .menuButtonDefault:hover {\n background-color: var(--sidebar-accent-bg);\n color: var(--sidebar-accent-text);\n }\n}\n\n.menuButtonOutline {\n background-color: var(--surface-page, #ffffff);\n box-shadow: inset 0 0 0 1px var(--sidebar-border);\n}\n\n@media (hover: hover) {\n .menuButtonOutline:hover {\n background-color: var(--sidebar-accent-bg);\n color: var(--sidebar-accent-text);\n }\n}\n\n/* Menu button sizes */\n.menuButtonSizeDefault {\n height: var(--sidebar-item-height, 2.25rem);\n padding: var(--sidebar-item-padding, 0 var(--spacing-3, 0.75rem));\n font-size: var(--sidebar-item-font-size, var(--font-size-sm, 0.875rem));\n}\n\n.menuButtonSizeSm {\n height: 2rem;\n padding: 0 calc(var(--spacing-2, 0.5rem) + var(--spacing-1, 0.25rem) / 2);\n font-size: var(--font-size-xs, 0.75rem);\n}\n\n.menuButtonSizeLg {\n height: 3.5rem;\n padding: 0 var(--spacing-3, 0.75rem);\n font-size: var(--font-size-sm, 0.875rem);\n}\n\n/* Menu action */\n.menuAction {\n position: absolute;\n top: 0.375rem;\n right: 0.25rem;\n display: flex;\n aspect-ratio: 1;\n width: 1.25rem;\n align-items: center;\n justify-content: center;\n border-radius: var(--radius-sm, 0.25rem);\n border: none;\n background: transparent;\n cursor: pointer;\n padding: 0;\n color: var(--sidebar-text);\n transition: background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n.menuAction:active {\n transform: translateY(1px);\n transition: none;\n}\n\n@media (hover: hover) {\n .menuAction:hover {\n background-color: var(--sidebar-accent-bg);\n color: var(--sidebar-accent-text);\n }\n}\n\n.menuActionHover {\n opacity: 0;\n}\n\n@media (hover: hover) {\n .menuItem:hover .menuActionHover {\n opacity: 1;\n }\n}\n\n.menuItem:focus-within .menuActionHover {\n opacity: 1;\n}\n\n/* Menu badge */\n.menuBadge {\n pointer-events: none;\n position: absolute;\n right: 0.25rem;\n display: flex;\n height: 1.25rem;\n min-width: 1.25rem;\n align-items: center;\n justify-content: center;\n border-radius: var(--radius-sm, 0.25rem);\n padding-inline: var(--spacing-1, 0.25rem);\n font-size: var(--font-size-xs, 0.75rem);\n font-weight: var(--font-weight-medium, 500);\n color: var(--sidebar-text);\n font-variant-numeric: tabular-nums;\n user-select: none;\n}\n\n/* Menu sub */\n.menuSub {\n margin-inline: calc(var(--spacing-3, 0.75rem) + var(--spacing-1, 0.25rem) / 2);\n display: flex;\n min-width: 0;\n flex-direction: column;\n gap: var(--spacing-1, 0.25rem);\n border-left: 1px solid var(--sidebar-border);\n padding-inline: calc(var(--spacing-2, 0.5rem) + var(--spacing-1, 0.25rem) / 2);\n padding-block: calc(var(--spacing-1, 0.25rem) / 2);\n list-style: none;\n margin: 0;\n margin-inline: calc(var(--spacing-3, 0.75rem) + var(--spacing-1, 0.25rem) / 2);\n}\n\n/* Menu sub item */\n.menuSubItem {\n position: relative;\n}\n\n/* Menu sub button */\n.menuSubButton {\n display: flex;\n height: 1.75rem;\n min-width: 0;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n overflow: hidden;\n border-radius: var(--radius-sm, 0.25rem);\n padding-inline: var(--spacing-2, 0.5rem);\n font-size: var(--font-size-sm, 0.875rem);\n color: var(--sidebar-text);\n text-decoration: none;\n transition: background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n outline: none;\n}\n\n.menuSubButton:active {\n transform: translateY(1px);\n transition: none;\n}\n\n@media (hover: hover) {\n .menuSubButton:hover {\n background-color: var(--sidebar-accent-bg);\n color: var(--sidebar-accent-text);\n }\n}\n\n.menuSubButton:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n.menuSubButton[data-active] {\n background-color: var(--sidebar-accent-bg);\n color: var(--sidebar-accent-text);\n}\n\n.menuSubButtonSm {\n font-size: var(--font-size-xs, 0.75rem);\n}\n\n/* Screen reader only */\n.srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border-width: 0;\n}\n"
|
|
995
1021
|
}
|
|
996
1022
|
]
|
|
997
1023
|
},
|
|
@@ -2116,6 +2142,30 @@
|
|
|
2116
2142
|
}
|
|
2117
2143
|
]
|
|
2118
2144
|
},
|
|
2145
|
+
{
|
|
2146
|
+
"name": "position-picker",
|
|
2147
|
+
"type": "registry:ui",
|
|
2148
|
+
"description": "A 3x3 anchor grid: nine targets, one chosen, the value a { y, x } pair. Draws on its own ground or over a photograph.",
|
|
2149
|
+
"category": "form",
|
|
2150
|
+
"dependencies": [
|
|
2151
|
+
"@loworbitstudio/visor-core"
|
|
2152
|
+
],
|
|
2153
|
+
"registryDependencies": [
|
|
2154
|
+
"utils"
|
|
2155
|
+
],
|
|
2156
|
+
"files": [
|
|
2157
|
+
{
|
|
2158
|
+
"path": "components/ui/position-picker/position-picker.tsx",
|
|
2159
|
+
"type": "registry:ui",
|
|
2160
|
+
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"../../../lib/utils\"\nimport styles from \"./position-picker.module.css\"\n\nexport type PositionY = \"top\" | \"center\" | \"bottom\"\nexport type PositionX = \"left\" | \"center\" | \"right\"\n\n/** The chosen anchor: a vertical and a horizontal position. */\nexport interface PositionValue {\n y: PositionY\n x: PositionX\n}\n\nexport type PositionPickerVariant = \"default\" | \"on-image\"\n\nconst ROWS: readonly PositionY[] = [\"top\", \"center\", \"bottom\"]\nconst COLS: readonly PositionX[] = [\"left\", \"center\", \"right\"]\n\n/** The accessible name of one target: \"Top left\", \"Center\", \"Bottom right\". */\nfunction targetName({ y, x }: PositionValue): string {\n if (y === \"center\" && x === \"center\") return \"Center\"\n const name = y === \"center\" ? `Center ${x}` : x === \"center\" ? `${y} center` : `${y} ${x}`\n return name.charAt(0).toUpperCase() + name.slice(1)\n}\n\nexport interface PositionPickerTargetState {\n selected: boolean\n}\n\nexport interface PositionPickerProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"defaultValue\" | \"onChange\" | \"children\"> {\n /** Controlled value. `null` is controlled with nothing chosen. */\n value?: PositionValue | null\n /** Initial value when uncontrolled. */\n defaultValue?: PositionValue | null\n /** Called with the new `{ y, x }` pair when a target is chosen. */\n onValueChange?: (value: PositionValue) => void\n /**\n * `default` draws the grid on its own ground. `on-image` fills a positioned\n * parent (the photograph's frame) and gives every target its own scrim ring,\n * so each keeps 3:1 against any photograph without touching the photo.\n */\n variant?: PositionPickerVariant\n disabled?: boolean\n /**\n * Replaces the drawn dot of one target, for example an alignment icon in the\n * occupied row. Decorative: the target keeps its own accessible name.\n */\n renderTarget?: (position: PositionValue, state: PositionPickerTargetState) => React.ReactNode\n}\n\nconst PositionPicker = React.forwardRef<HTMLDivElement, PositionPickerProps>(\n (\n {\n value: valueProp,\n defaultValue = null,\n onValueChange,\n variant = \"default\",\n disabled = false,\n renderTarget,\n className,\n ...props\n },\n ref,\n ) => {\n const isControlled = valueProp !== undefined\n const [inner, setInner] = React.useState<PositionValue | null>(defaultValue)\n const value = isControlled ? valueProp : inner\n const buttons = React.useRef<Array<HTMLButtonElement | null>>([])\n\n const select = (index: number) => {\n const next: PositionValue = { y: ROWS[Math.floor(index / 3)], x: COLS[index % 3] }\n if (!isControlled) setInner(next)\n onValueChange?.(next)\n }\n\n const selectedIndex = value ? ROWS.indexOf(value.y) * 3 + COLS.indexOf(value.x) : -1\n // Roving tab stop: the chosen target, or the first when nothing is chosen.\n const tabStop = selectedIndex === -1 ? 0 : selectedIndex\n\n const move = (from: number, key: string): number | null => {\n const row = Math.floor(from / 3)\n const col = from % 3\n switch (key) {\n case \"ArrowLeft\": return row * 3 + ((col + 2) % 3)\n case \"ArrowRight\": return row * 3 + ((col + 1) % 3)\n case \"ArrowUp\": return ((row + 2) % 3) * 3 + col\n case \"ArrowDown\": return ((row + 1) % 3) * 3 + col\n case \"Home\": return 0\n case \"End\": return 8\n default: return null\n }\n }\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>, index: number) => {\n const to = move(index, event.key)\n if (to === null) return\n event.preventDefault()\n buttons.current[to]?.focus()\n select(to)\n }\n\n return (\n <div\n ref={ref}\n role=\"radiogroup\"\n aria-disabled={disabled || undefined}\n data-slot=\"position-picker\"\n data-variant={variant}\n className={cn(styles.root, variant === \"on-image\" && styles.onImage, className)}\n {...props}\n >\n {ROWS.flatMap((y, r) =>\n COLS.map((x, c) => {\n const index = r * 3 + c\n const position: PositionValue = { y, x }\n const selected = index === selectedIndex\n return (\n <button\n key={index}\n ref={(node) => {\n buttons.current[index] = node\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={selected}\n aria-label={targetName(position)}\n tabIndex={index === tabStop ? 0 : -1}\n disabled={disabled}\n data-slot=\"position-picker-target\"\n data-state={selected ? \"checked\" : \"unchecked\"}\n data-y={y}\n data-x={x}\n className={styles.target}\n onClick={() => select(index)}\n onKeyDown={(event) => handleKeyDown(event, index)}\n >\n <span className={styles.mark} aria-hidden=\"true\">\n {renderTarget?.(position, { selected })}\n </span>\n </button>\n )\n }),\n )}\n </div>\n )\n },\n)\nPositionPicker.displayName = \"PositionPicker\"\n\nexport { PositionPicker }\n"
|
|
2161
|
+
},
|
|
2162
|
+
{
|
|
2163
|
+
"path": "components/ui/position-picker/position-picker.module.css",
|
|
2164
|
+
"type": "registry:ui",
|
|
2165
|
+
"content": "/* PositionPicker: nine targets in a 3x3 grid, one chosen.\n\n Target size (WCAG 2.5.8): every target's hit area is at least\n --position-picker-target-size (24px) square, even though the drawn dot is\n smaller. The grid grows to fit three targets a side rather than shrinking\n them. --position-picker-target-size is the per-component override. */\n.root {\n --_target: var(--position-picker-target-size, var(--spacing-6, 1.5rem));\n position: relative;\n display: inline-grid;\n grid-template-columns: repeat(3, minmax(var(--_target), 1fr));\n grid-template-rows: repeat(3, minmax(var(--_target), 1fr));\n min-width: calc(3 * var(--_target));\n min-height: calc(3 * var(--_target));\n /* The standalone grid sits on its own ground. The resting edge is an inset\n outline on the shared --control-edge-* tokens (VI-655), never a border, so\n --control-edge-width: 0 turns it off. */\n padding: var(--spacing-1, 0.25rem);\n border-radius: var(--radius-md, 0.375rem);\n background-color: var(--position-picker-bg, var(--surface-subtle, #f9fafb));\n outline: var(--control-edge-width, 1px) var(--control-edge-style, solid) var(--position-picker-border, var(--control-edge-color, var(--border-default, #e5e7eb)));\n outline-offset: calc(-1 * var(--control-edge-width, 1px));\n}\n\n/* Standalone targets are a comfortable size, not the bare minimum. */\n.root:not(.onImage) {\n --_target: var(--position-picker-target-size, var(--spacing-8, 2rem));\n}\n\n/* On an image the grid fills the photograph's frame (a positioned parent). It\n has no ground of its own: the photo is the ground, and each target carries\n its own scrim ring below. On a small thumbnail min-width/min-height win over\n the frame, so the grid grows to the target size instead of shrinking. */\n.onImage {\n --_scrim: var(--position-picker-scrim, color-mix(in srgb, var(--color-black, #000000) 80%, transparent));\n position: absolute;\n inset: 0;\n display: grid;\n padding: 0;\n border-radius: 0;\n background-color: transparent;\n outline: none;\n}\n\n.target {\n display: grid;\n place-items: center;\n min-width: var(--_target);\n min-height: var(--_target);\n margin: 0;\n padding: 0;\n border: 0;\n border-radius: var(--radius-sm, 0.25rem);\n background: transparent;\n color: var(--text-primary, #111827);\n cursor: pointer;\n}\n\n.target:disabled {\n cursor: not-allowed;\n opacity: var(--opacity-50, 0.5);\n}\n\n/* The drawn dot. Smaller than the hit area on purpose. */\n.mark {\n display: grid;\n place-items: center;\n width: var(--spacing-3, 0.75rem);\n height: var(--spacing-3, 0.75rem);\n border-radius: var(--radius-full, 9999px);\n background-color: var(--position-picker-mark, var(--text-tertiary, #9ca3af));\n transition: width var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out),\n height var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out),\n background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out),\n box-shadow var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n@media (hover: hover) {\n .target:hover:not(:disabled) .mark {\n background-color: var(--text-secondary, #6b7280);\n }\n}\n\n/* Chosen: filled with the accent, larger, and ringed. Size and ring carry the\n state as well as colour (1.4.1). */\n.target[data-state=\"checked\"] .mark {\n width: var(--spacing-4, 1rem);\n height: var(--spacing-4, 1rem);\n background-color: var(--position-picker-mark-selected, var(--interactive-primary-bg, var(--primary, #111827)));\n color: var(--interactive-primary-text, #f9fafb);\n box-shadow: 0 0 0 var(--stroke-width-medium, 2px) var(--position-picker-ring, var(--surface-subtle, #f9fafb)),\n 0 0 0 calc(var(--stroke-width-medium, 2px) * 2) var(--position-picker-mark-selected, var(--interactive-primary-bg, var(--primary, #111827)));\n}\n\n/* A custom glyph (an alignment icon) fills the mark and sizes to it. */\n.mark > svg {\n width: 100%;\n height: 100%;\n}\n\n/* Focus is the one ring the edge switch never removes. */\n.target:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, #111827);\n outline-offset: calc(-1 * var(--focus-ring-offset, 2px));\n}\n\n/* ---------------------------------------------------------------------------\n On image. The dot is a fixed light mark inside a dark scrim ring, whatever\n the theme mode: the photograph is the background, and any photograph can be\n light or dark. Light-on-dark-ring keeps 3:1 over light and dark regions\n alike. The scrim is a fixed black, not a theme surface: a photograph does not\n follow the theme. Nothing is applied to the photo itself (no filter).\n --------------------------------------------------------------------------- */\n.onImage .mark {\n background-color: var(--position-picker-image-mark, var(--color-white, #ffffff));\n box-shadow: 0 0 0 var(--stroke-width-medium, 2px) var(--_scrim);\n}\n\n@media (hover: hover) {\n .onImage .target:hover:not(:disabled) .mark {\n background-color: var(--position-picker-image-mark, var(--color-white, #ffffff));\n width: var(--spacing-4, 1rem);\n height: var(--spacing-4, 1rem);\n }\n}\n\n.onImage .target[data-state=\"checked\"] .mark {\n background-color: var(--position-picker-mark-selected, var(--interactive-primary-bg, var(--primary, #111827)));\n box-shadow: 0 0 0 var(--stroke-width-medium, 2px) var(--position-picker-image-mark, var(--color-white, #ffffff)),\n 0 0 0 calc(var(--stroke-width-medium, 2px) * 2) var(--_scrim);\n}\n\n/* The photo can put any colour under the focus ring, so it gets a scrim halo. */\n.onImage .target:focus-visible {\n outline-color: var(--position-picker-image-mark, var(--color-white, #ffffff));\n box-shadow: 0 0 0 var(--focus-ring-width, 2px) var(--_scrim);\n}\n"
|
|
2166
|
+
}
|
|
2167
|
+
]
|
|
2168
|
+
},
|
|
2119
2169
|
{
|
|
2120
2170
|
"name": "tin-input",
|
|
2121
2171
|
"type": "registry:ui",
|
|
@@ -5343,7 +5393,7 @@
|
|
|
5343
5393
|
{
|
|
5344
5394
|
"path": "components/devtools/source-inspector/visor-component-names.generated.ts",
|
|
5345
5395
|
"type": "registry:devtool",
|
|
5346
|
-
"content": "// THIS FILE IS GENERATED BY scripts/generate-visor-component-names.ts.\n// Do not edit by hand. Re-run `npm run generate:component-names` after\n// adding, removing, or renaming a Visor component.\n//\n// Source of truth: registry/registry-{ui,blocks,deck,visual,devtools}.ts\n// Used by: components/devtools/source-inspector/* (VI-311)\n\nexport const VISOR_COMPONENT_NAMES: ReadonlySet<string> = new Set([\n \"AccessibilitySection\",\n \"AccessibilitySlide\",\n \"AccessibilitySpecimen\",\n \"Accordion\",\n \"AccordionContent\",\n \"AccordionItem\",\n \"AccordionTrigger\",\n \"ActionRow\",\n \"ActionRowList\",\n \"ActivityFeed\",\n \"ActivityFeedContext\",\n \"ActivityFeedItem\",\n \"ActivityFeedRoot\",\n \"AdminDashboard\",\n \"AdminDetail\",\n \"AdminDetailDrawer\",\n \"AdminListPage\",\n \"AdminListPageInner\",\n \"AdminSettingsPage\",\n \"AdminShell\",\n \"AdminTabbedEditor\",\n \"AdminWizard\",\n \"Alert\",\n \"AlertActions\",\n \"AlertDescription\",\n \"AlertTitle\",\n \"AmbientGlow\",\n \"Avatar\",\n \"AvatarFallback\",\n \"AvatarImage\",\n \"AvatarStack\",\n \"Badge\",\n \"Banner\",\n \"BannerAction\",\n \"BannerDescription\",\n \"BannerTitle\",\n \"BentoGrid\",\n \"BentoTile\",\n \"BentoTileBody\",\n \"BentoTileDescription\",\n \"BentoTileFigure\",\n \"BentoTileHeadline\",\n \"BentoTileMedia\",\n \"BentoTileMeta\",\n \"BentoTileTitle\",\n \"Box\",\n \"Breadcrumb\",\n \"BreadcrumbEllipsis\",\n \"BreadcrumbItem\",\n \"BreadcrumbLink\",\n \"BreadcrumbList\",\n \"BreadcrumbPage\",\n \"BreadcrumbSeparator\",\n \"BrowserFrame\",\n \"BulkActionBar\",\n \"Button\",\n \"ButtonSpecimenSection\",\n \"ButtonSpecimenSlide\",\n \"Calendar\",\n \"Capture\",\n \"Card\",\n \"CardContent\",\n \"CardDescription\",\n \"CardFooter\",\n \"CardGrid\",\n \"CardHeader\",\n \"CardLift\",\n \"CardTitle\",\n \"Carousel\",\n \"CarouselContent\",\n \"CarouselContext\",\n \"CarouselGallery\",\n \"CarouselItem\",\n \"CarouselNext\",\n \"CarouselPrevious\",\n \"ChallengeCard\",\n \"ChallengeCardAction\",\n \"ChallengeCardActions\",\n \"ChallengeCardBody\",\n \"ChallengeCardGate\",\n \"ChallengeCardHeader\",\n \"ChartContainer\",\n \"ChartContext\",\n \"ChartLegend\",\n \"ChartLegendContent\",\n \"ChartStyle\",\n \"ChartTooltip\",\n \"ChartTooltipContent\",\n \"Checkbox\",\n \"CheckGroup\",\n \"CheckRow\",\n \"Chip\",\n \"ChipGroup\",\n \"ChipGroupContext\",\n \"ChipGroupItem\",\n \"ChoiceChip\",\n \"ChromeButton\",\n \"ClosingSlide\",\n \"CodeBlock\",\n \"Collapsible\",\n \"CollapsibleContent\",\n \"CollapsibleTrigger\",\n \"ColorBar\",\n \"ColorPaletteSection\",\n \"ColorPicker\",\n \"ColorPickerSurface\",\n \"ColorSwatch\",\n \"ColorSwatchGrid\",\n \"Column\",\n \"Combobox\",\n \"ComboboxContent\",\n \"ComboboxContext\",\n \"ComboboxEmpty\",\n \"ComboboxGroup\",\n \"ComboboxInput\",\n \"ComboboxItem\",\n \"ComboboxSeparator\",\n \"Command\",\n \"CommandDialog\",\n \"CommandEmpty\",\n \"CommandGroup\",\n \"CommandInput\",\n \"CommandItem\",\n \"CommandList\",\n \"CommandLoading\",\n \"CommandSeparator\",\n \"CommandShortcut\",\n \"ComponentShowcaseContent\",\n \"ComponentShowcaseSection\",\n \"ComponentShowcaseSlide\",\n \"Composer\",\n \"ComposerContext\",\n \"ComposerField\",\n \"ComposerSend\",\n \"ComposerSpacer\",\n \"ComposerToolbar\",\n \"ComposerToolButton\",\n \"ConceptSlide\",\n \"ConfigurationPanel\",\n \"ConfirmDialog\",\n \"ConflictBanner\",\n \"Container\",\n \"ContextMenu\",\n \"ContextMenuCheckboxItem\",\n \"ContextMenuContent\",\n \"ContextMenuGroup\",\n \"ContextMenuItem\",\n \"ContextMenuLabel\",\n \"ContextMenuPortal\",\n \"ContextMenuRadioGroup\",\n \"ContextMenuRadioItem\",\n \"ContextMenuSeparator\",\n \"ContextMenuShortcut\",\n \"ContextMenuSub\",\n \"ContextMenuSubContent\",\n \"ContextMenuSubTrigger\",\n \"ContextMenuTrigger\",\n \"Controls\",\n \"CtaSection\",\n \"DataTable\",\n \"DataTableInner\",\n \"DatePicker\",\n \"DateRangePicker\",\n \"DeckContext\",\n \"DeckFooter\",\n \"DeckLayout\",\n \"DeckProvider\",\n \"DeckRenderer\",\n \"DesignSystemDeck\",\n \"DesignSystemSpecimen\",\n \"Dialog\",\n \"DialogBack\",\n \"DialogClose\",\n \"DialogContent\",\n \"DialogContext\",\n \"DialogDescription\",\n \"DialogField\",\n \"DialogFieldControl\",\n \"DialogFieldLabel\",\n \"DialogFooter\",\n \"DialogForm\",\n \"DialogFormBody\",\n \"DialogFormClose\",\n \"DialogFormContent\",\n \"DialogFormDescription\",\n \"DialogFormFooter\",\n \"DialogFormHeader\",\n \"DialogFormTitle\",\n \"DialogFormTrigger\",\n \"DialogHeader\",\n \"DialogOverlay\",\n \"DialogPortal\",\n \"DialogScrim\",\n \"DialogTitle\",\n \"DialogTrigger\",\n \"DocFrame\",\n \"DocFrameBrand\",\n \"DocNav\",\n \"DocNavGroup\",\n \"DocNavPill\",\n \"DotNav\",\n \"DropdownMenu\",\n \"DropdownMenuCheckboxItem\",\n \"DropdownMenuContent\",\n \"DropdownMenuGroup\",\n \"DropdownMenuItem\",\n \"DropdownMenuLabel\",\n \"DropdownMenuPortal\",\n \"DropdownMenuRadioGroup\",\n \"DropdownMenuRadioItem\",\n \"DropdownMenuSeparator\",\n \"DropdownMenuShortcut\",\n \"DropdownMenuSub\",\n \"DropdownMenuSubContent\",\n \"DropdownMenuSubTrigger\",\n \"DropdownMenuTrigger\",\n \"EditableBlock\",\n \"ElevationCard\",\n \"ElevationSlide\",\n \"EmptyState\",\n \"ErrorPlacard\",\n \"ExportMenu\",\n \"FeaturesGrid\",\n \"FidelityMirror\",\n \"Field\",\n \"FieldDescription\",\n \"FieldError\",\n \"FieldLabel\",\n \"Fieldset\",\n \"FieldsetLegend\",\n \"FileUpload\",\n \"FilterBar\",\n \"FilterChip\",\n \"FontShowcase\",\n \"FontShowcaseGrid\",\n \"Footer\",\n \"FooterSection\",\n \"Form\",\n \"FormError\",\n \"FormErrorDescription\",\n \"FormErrorTitle\",\n \"FormField\",\n \"FormSpecimenSection\",\n \"FormSpecimenSlide\",\n \"FullscreenOverlay\",\n \"FullscreenOverlayContent\",\n \"FullscreenOverlayTrigger\",\n \"GrainOverlay\",\n \"Grid\",\n \"Header\",\n \"Heading\",\n \"HeroGlow\",\n \"HeroSection\",\n \"HeroSlide\",\n \"HoverCard\",\n \"HoverCardContent\",\n \"HoverCardTrigger\",\n \"IconGrid\",\n \"IconGridSection\",\n \"IconSizeRow\",\n \"IconsSlide\",\n \"Image\",\n \"InfographicBar\",\n \"Inline\",\n \"InlineEdit\",\n \"Input\",\n \"Kbd\",\n \"KeyValueList\",\n \"Label\",\n \"Landing\",\n \"Lightbox\",\n \"LightboxContent\",\n \"LightboxContext\",\n \"LightboxTrigger\",\n \"LoginForm\",\n \"Marquee\",\n \"MarqueeBandRenderer\",\n \"MatrixCell\",\n \"MatrixTable\",\n \"MatrixTableInner\",\n \"Menubar\",\n \"MenubarCheckboxItem\",\n \"MenubarContent\",\n \"MenubarGroup\",\n \"MenubarItem\",\n \"MenubarLabel\",\n \"MenubarMenu\",\n \"MenubarRadioGroup\",\n \"MenubarRadioItem\",\n \"MenubarSeparator\",\n \"MenubarShortcut\",\n \"MenubarSub\",\n \"MenubarSubContent\",\n \"MenubarSubTrigger\",\n \"MenubarTrigger\",\n \"MonthCalendar\",\n \"MotionDuration\",\n \"MotionDurationSection\",\n \"MotionEasing\",\n \"MotionEasingSection\",\n \"MotionSlide\",\n \"NameRoster\",\n \"NameRosterItem\",\n \"Navbar\",\n \"NavbarBrand\",\n \"NavbarContent\",\n \"NavbarItem\",\n \"NavbarLink\",\n \"NavbarToggle\",\n \"NumberInput\",\n \"OfflineBanner\",\n \"OpacityBar\",\n \"OpacitySlide\",\n \"OTPInput\",\n \"PageHeader\",\n \"Pagination\",\n \"PaginationContent\",\n \"PaginationEllipsis\",\n \"PaginationItem\",\n \"PaginationLink\",\n \"PaginationNext\",\n \"PaginationPrevious\",\n \"PasswordInput\",\n \"PhoneInput\",\n \"PlatformChip\",\n \"Popover\",\n \"PopoverAnchor\",\n \"PopoverContent\",\n \"PopoverFooter\",\n \"PopoverSelectionItem\",\n \"PopoverSelectionLabel\",\n \"PopoverSelectionList\",\n \"PopoverTrigger\",\n \"PricingSection\",\n \"ProfileMenu\",\n \"Progress\",\n \"PrototypeReview\",\n \"QuickActions\",\n \"RadioGroup\",\n \"RadioGroupItem\",\n \"RadioGroupVariantContext\",\n \"RadiusScale\",\n \"RadiusSection\",\n \"RadiusSlide\",\n \"RecordSection\",\n \"RightRailList\",\n \"SaveStatus\",\n \"ScoreIndicator\",\n \"ScrollArea\",\n \"ScrollBar\",\n \"SearchInput\",\n \"SectionHeader\",\n \"SectionIntro\",\n \"SectionNav\",\n \"SectionNavItem\",\n \"SegmentedControl\",\n \"SegmentedProgress\",\n \"Select\",\n \"SelectContent\",\n \"SelectGroup\",\n \"SelectionListContext\",\n \"SelectItem\",\n \"SelectLabel\",\n \"SelectScrollDownButton\",\n \"SelectScrollUpButton\",\n \"SelectSeparator\",\n \"SelectTrigger\",\n \"SelectValue\",\n \"SemanticColorGrid\",\n \"SemanticColorItem\",\n \"SemanticTokensSlide\",\n \"SensitivePanel\",\n \"Separator\",\n \"SessionTimeout\",\n \"ShadowSection\",\n \"Sheet\",\n \"SheetClose\",\n \"SheetContent\",\n \"SheetContext\",\n \"SheetDescription\",\n \"SheetFooter\",\n \"SheetHeader\",\n \"SheetOverlay\",\n \"SheetPortal\",\n \"SheetScrim\",\n \"SheetTitle\",\n \"SheetTrigger\",\n \"Sidebar\",\n \"SidebarContent\",\n \"SidebarContext\",\n \"SidebarFooter\",\n \"SidebarGroup\",\n \"SidebarGroupAction\",\n \"SidebarGroupContent\",\n \"SidebarGroupLabel\",\n \"SidebarHeader\",\n \"SidebarInset\",\n \"SidebarMenu\",\n \"SidebarMenuAction\",\n \"SidebarMenuBadge\",\n \"SidebarMenuButton\",\n \"SidebarMenuItem\",\n \"SidebarMenuSub\",\n \"SidebarMenuSubButton\",\n \"SidebarMenuSubItem\",\n \"SidebarProvider\",\n \"SidebarRail\",\n \"SidebarSeparator\",\n \"SidebarTrigger\",\n \"Skeleton\",\n \"SkeletonDetail\",\n \"SkeletonList\",\n \"SkeletonTable\",\n \"Slide\",\n \"SlideHeader\",\n \"Slider\",\n \"SliderControl\",\n \"SlideThemeContext\",\n \"SlideThemeProvider\",\n \"SlowNetworkBar\",\n \"SourceInspector\",\n \"SourceInspectorContext\",\n \"SourceInspectorDevImpl\",\n \"SourceInspectorProvider\",\n \"SourceInspectorRunner\",\n \"SourceInspectorToggle\",\n \"SpacingScale\",\n \"SpacingSection\",\n \"SpacingSlide\",\n \"Sparkline\",\n \"SpecimenCard\",\n \"SpecimenCardFooter\",\n \"Sphere\",\n \"SpherePlayground\",\n \"Spinner\",\n \"SpinnerOrb\",\n \"Stack\",\n \"Stage\",\n \"StatCard\",\n \"StatHero\",\n \"StationSpectrum\",\n \"StatusBadge\",\n \"StatusColorsSlide\",\n \"StatusDot\",\n \"Stepper\",\n \"StepperContext\",\n \"StepperDescription\",\n \"StepperItem\",\n \"StepperSeparator\",\n \"StepperTitle\",\n \"StepperTrigger\",\n \"StepsSection\",\n \"StructuredPrompt\",\n \"StructuredPromptBody\",\n \"StructuredPromptHeader\",\n \"StructuredPromptHint\",\n \"StructuredPromptSlot\",\n \"SuccessLiveRegion\",\n \"SurfaceRow\",\n \"SurfaceScaleStack\",\n \"SurfaceSection\",\n \"Switch\",\n \"Table\",\n \"TableBody\",\n \"TableCaption\",\n \"TableCell\",\n \"TableFooter\",\n \"TableHead\",\n \"TableHeader\",\n \"TableRow\",\n \"Tabs\",\n \"TabsContent\",\n \"TabsList\",\n \"TabsTrigger\",\n \"TagInput\",\n \"TestimonialAttribution\",\n \"TestimonialSection\",\n \"Text\",\n \"Textarea\",\n \"ThemeArchitectureSlide\",\n \"ThemeColorsSlide\",\n \"ThemeSwitcher\",\n \"ThinkingOrb\",\n \"Timeline\",\n \"TimelineContent\",\n \"TimelineDescription\",\n \"TimelineIcon\",\n \"TimelineItem\",\n \"TimelineTimestamp\",\n \"TimelineTitle\",\n \"TimePicker\",\n \"TinInput\",\n \"TitleSlide\",\n \"ToastCard\",\n \"ToastCardStack\",\n \"Toaster\",\n \"TOCSlide\",\n \"ToggleButton\",\n \"ToggleDevImpl\",\n \"ToggleGroup\",\n \"ToggleGroupContext\",\n \"ToggleGroupItem\",\n \"Tooltip\",\n \"TooltipContent\",\n \"TooltipProvider\",\n \"TooltipTrigger\",\n \"TypeBodySlide\",\n \"TypeDisplaySlide\",\n \"TypeScaleStack\",\n \"TypeSpecimen\",\n \"TypographySection\",\n \"Vignette\",\n \"WorkspaceSwitcher\",\n])\n"
|
|
5396
|
+
"content": "// THIS FILE IS GENERATED BY scripts/generate-visor-component-names.ts.\n// Do not edit by hand. Re-run `npm run generate:component-names` after\n// adding, removing, or renaming a Visor component.\n//\n// Source of truth: registry/registry-{ui,blocks,deck,visual,devtools}.ts\n// Used by: components/devtools/source-inspector/* (VI-311)\n\nexport const VISOR_COMPONENT_NAMES: ReadonlySet<string> = new Set([\n \"AccessibilitySection\",\n \"AccessibilitySlide\",\n \"AccessibilitySpecimen\",\n \"Accordion\",\n \"AccordionContent\",\n \"AccordionItem\",\n \"AccordionTrigger\",\n \"ActionRow\",\n \"ActionRowList\",\n \"ActivityFeed\",\n \"ActivityFeedContext\",\n \"ActivityFeedItem\",\n \"ActivityFeedRoot\",\n \"AdminDashboard\",\n \"AdminDetail\",\n \"AdminDetailDrawer\",\n \"AdminListPage\",\n \"AdminListPageInner\",\n \"AdminSettingsPage\",\n \"AdminShell\",\n \"AdminTabbedEditor\",\n \"AdminWizard\",\n \"Alert\",\n \"AlertActions\",\n \"AlertDescription\",\n \"AlertTitle\",\n \"AmbientGlow\",\n \"Avatar\",\n \"AvatarFallback\",\n \"AvatarImage\",\n \"AvatarStack\",\n \"Badge\",\n \"Banner\",\n \"BannerAction\",\n \"BannerDescription\",\n \"BannerTitle\",\n \"BentoGrid\",\n \"BentoTile\",\n \"BentoTileBody\",\n \"BentoTileDescription\",\n \"BentoTileFigure\",\n \"BentoTileHeadline\",\n \"BentoTileMedia\",\n \"BentoTileMeta\",\n \"BentoTileTitle\",\n \"BottomNav\",\n \"Box\",\n \"Breadcrumb\",\n \"BreadcrumbEllipsis\",\n \"BreadcrumbItem\",\n \"BreadcrumbLink\",\n \"BreadcrumbList\",\n \"BreadcrumbPage\",\n \"BreadcrumbSeparator\",\n \"BrowserFrame\",\n \"BulkActionBar\",\n \"Button\",\n \"ButtonSpecimenSection\",\n \"ButtonSpecimenSlide\",\n \"Calendar\",\n \"Capture\",\n \"Card\",\n \"CardContent\",\n \"CardDescription\",\n \"CardFooter\",\n \"CardGrid\",\n \"CardHeader\",\n \"CardLift\",\n \"CardTitle\",\n \"Carousel\",\n \"CarouselContent\",\n \"CarouselContext\",\n \"CarouselGallery\",\n \"CarouselItem\",\n \"CarouselNext\",\n \"CarouselPrevious\",\n \"ChallengeCard\",\n \"ChallengeCardAction\",\n \"ChallengeCardActions\",\n \"ChallengeCardBody\",\n \"ChallengeCardGate\",\n \"ChallengeCardHeader\",\n \"ChartContainer\",\n \"ChartContext\",\n \"ChartLegend\",\n \"ChartLegendContent\",\n \"ChartStyle\",\n \"ChartTooltip\",\n \"ChartTooltipContent\",\n \"Checkbox\",\n \"CheckGroup\",\n \"CheckRow\",\n \"Chip\",\n \"ChipGroup\",\n \"ChipGroupContext\",\n \"ChipGroupItem\",\n \"ChoiceChip\",\n \"ChromeButton\",\n \"ClosingSlide\",\n \"CodeBlock\",\n \"Collapsible\",\n \"CollapsibleContent\",\n \"CollapsibleTrigger\",\n \"ColorBar\",\n \"ColorPaletteSection\",\n \"ColorPicker\",\n \"ColorPickerSurface\",\n \"ColorSwatch\",\n \"ColorSwatchGrid\",\n \"Column\",\n \"Combobox\",\n \"ComboboxContent\",\n \"ComboboxContext\",\n \"ComboboxEmpty\",\n \"ComboboxGroup\",\n \"ComboboxInput\",\n \"ComboboxItem\",\n \"ComboboxSeparator\",\n \"Command\",\n \"CommandDialog\",\n \"CommandEmpty\",\n \"CommandGroup\",\n \"CommandInput\",\n \"CommandItem\",\n \"CommandList\",\n \"CommandLoading\",\n \"CommandSeparator\",\n \"CommandShortcut\",\n \"ComponentShowcaseContent\",\n \"ComponentShowcaseSection\",\n \"ComponentShowcaseSlide\",\n \"Composer\",\n \"ComposerContext\",\n \"ComposerField\",\n \"ComposerSend\",\n \"ComposerSpacer\",\n \"ComposerToolbar\",\n \"ComposerToolButton\",\n \"ConceptSlide\",\n \"ConfigurationPanel\",\n \"ConfirmDialog\",\n \"ConflictBanner\",\n \"Container\",\n \"ContextMenu\",\n \"ContextMenuCheckboxItem\",\n \"ContextMenuContent\",\n \"ContextMenuGroup\",\n \"ContextMenuItem\",\n \"ContextMenuLabel\",\n \"ContextMenuPortal\",\n \"ContextMenuRadioGroup\",\n \"ContextMenuRadioItem\",\n \"ContextMenuSeparator\",\n \"ContextMenuShortcut\",\n \"ContextMenuSub\",\n \"ContextMenuSubContent\",\n \"ContextMenuSubTrigger\",\n \"ContextMenuTrigger\",\n \"Controls\",\n \"CtaSection\",\n \"DataTable\",\n \"DataTableInner\",\n \"DatePicker\",\n \"DateRangePicker\",\n \"DeckContext\",\n \"DeckFooter\",\n \"DeckLayout\",\n \"DeckProvider\",\n \"DeckRenderer\",\n \"DesignSystemDeck\",\n \"DesignSystemSpecimen\",\n \"Dialog\",\n \"DialogBack\",\n \"DialogClose\",\n \"DialogContent\",\n \"DialogContext\",\n \"DialogDescription\",\n \"DialogField\",\n \"DialogFieldControl\",\n \"DialogFieldLabel\",\n \"DialogFooter\",\n \"DialogForm\",\n \"DialogFormBody\",\n \"DialogFormClose\",\n \"DialogFormContent\",\n \"DialogFormDescription\",\n \"DialogFormFooter\",\n \"DialogFormHeader\",\n \"DialogFormTitle\",\n \"DialogFormTrigger\",\n \"DialogHeader\",\n \"DialogOverlay\",\n \"DialogPortal\",\n \"DialogScrim\",\n \"DialogTitle\",\n \"DialogTrigger\",\n \"DocFrame\",\n \"DocFrameBrand\",\n \"DocNav\",\n \"DocNavGroup\",\n \"DocNavPill\",\n \"DotNav\",\n \"DropdownMenu\",\n \"DropdownMenuCheckboxItem\",\n \"DropdownMenuContent\",\n \"DropdownMenuGroup\",\n \"DropdownMenuItem\",\n \"DropdownMenuLabel\",\n \"DropdownMenuPortal\",\n \"DropdownMenuRadioGroup\",\n \"DropdownMenuRadioItem\",\n \"DropdownMenuSeparator\",\n \"DropdownMenuShortcut\",\n \"DropdownMenuSub\",\n \"DropdownMenuSubContent\",\n \"DropdownMenuSubTrigger\",\n \"DropdownMenuTrigger\",\n \"EditableBlock\",\n \"ElevationCard\",\n \"ElevationSlide\",\n \"EmptyState\",\n \"ErrorPlacard\",\n \"ExportMenu\",\n \"FeaturesGrid\",\n \"FidelityMirror\",\n \"Field\",\n \"FieldDescription\",\n \"FieldError\",\n \"FieldLabel\",\n \"Fieldset\",\n \"FieldsetLegend\",\n \"FileUpload\",\n \"FilterBar\",\n \"FilterChip\",\n \"FontShowcase\",\n \"FontShowcaseGrid\",\n \"Footer\",\n \"FooterSection\",\n \"Form\",\n \"FormError\",\n \"FormErrorDescription\",\n \"FormErrorTitle\",\n \"FormField\",\n \"FormSpecimenSection\",\n \"FormSpecimenSlide\",\n \"FullscreenOverlay\",\n \"FullscreenOverlayContent\",\n \"FullscreenOverlayTrigger\",\n \"GrainOverlay\",\n \"Grid\",\n \"Header\",\n \"Heading\",\n \"HeroGlow\",\n \"HeroSection\",\n \"HeroSlide\",\n \"HoverCard\",\n \"HoverCardContent\",\n \"HoverCardTrigger\",\n \"IconGrid\",\n \"IconGridSection\",\n \"IconSizeRow\",\n \"IconsSlide\",\n \"Image\",\n \"InfographicBar\",\n \"Inline\",\n \"InlineEdit\",\n \"Input\",\n \"Kbd\",\n \"KeyValueList\",\n \"Label\",\n \"Landing\",\n \"Lightbox\",\n \"LightboxContent\",\n \"LightboxContext\",\n \"LightboxTrigger\",\n \"LoginForm\",\n \"Marquee\",\n \"MarqueeBandRenderer\",\n \"MatrixCell\",\n \"MatrixTable\",\n \"MatrixTableInner\",\n \"Menubar\",\n \"MenubarCheckboxItem\",\n \"MenubarContent\",\n \"MenubarGroup\",\n \"MenubarItem\",\n \"MenubarLabel\",\n \"MenubarMenu\",\n \"MenubarRadioGroup\",\n \"MenubarRadioItem\",\n \"MenubarSeparator\",\n \"MenubarShortcut\",\n \"MenubarSub\",\n \"MenubarSubContent\",\n \"MenubarSubTrigger\",\n \"MenubarTrigger\",\n \"MonthCalendar\",\n \"MotionDuration\",\n \"MotionDurationSection\",\n \"MotionEasing\",\n \"MotionEasingSection\",\n \"MotionSlide\",\n \"NameRoster\",\n \"NameRosterItem\",\n \"Navbar\",\n \"NavbarBrand\",\n \"NavbarContent\",\n \"NavbarItem\",\n \"NavbarLink\",\n \"NavbarToggle\",\n \"NumberInput\",\n \"OfflineBanner\",\n \"OpacityBar\",\n \"OpacitySlide\",\n \"OTPInput\",\n \"PageHeader\",\n \"Pagination\",\n \"PaginationContent\",\n \"PaginationEllipsis\",\n \"PaginationItem\",\n \"PaginationLink\",\n \"PaginationNext\",\n \"PaginationPrevious\",\n \"PasswordInput\",\n \"PhoneInput\",\n \"PlatformChip\",\n \"Popover\",\n \"PopoverAnchor\",\n \"PopoverContent\",\n \"PopoverFooter\",\n \"PopoverSelectionItem\",\n \"PopoverSelectionLabel\",\n \"PopoverSelectionList\",\n \"PopoverTrigger\",\n \"PositionPicker\",\n \"PricingSection\",\n \"ProfileMenu\",\n \"Progress\",\n \"PrototypeReview\",\n \"QuickActions\",\n \"RadioGroup\",\n \"RadioGroupItem\",\n \"RadioGroupVariantContext\",\n \"RadiusScale\",\n \"RadiusSection\",\n \"RadiusSlide\",\n \"RecordSection\",\n \"RightRailList\",\n \"SaveStatus\",\n \"ScoreIndicator\",\n \"ScrollArea\",\n \"ScrollBar\",\n \"SearchInput\",\n \"SectionHeader\",\n \"SectionIntro\",\n \"SectionNav\",\n \"SectionNavItem\",\n \"SegmentedControl\",\n \"SegmentedProgress\",\n \"Select\",\n \"SelectContent\",\n \"SelectGroup\",\n \"SelectionListContext\",\n \"SelectItem\",\n \"SelectLabel\",\n \"SelectScrollDownButton\",\n \"SelectScrollUpButton\",\n \"SelectSeparator\",\n \"SelectTrigger\",\n \"SelectValue\",\n \"SemanticColorGrid\",\n \"SemanticColorItem\",\n \"SemanticTokensSlide\",\n \"SensitivePanel\",\n \"Separator\",\n \"SessionTimeout\",\n \"ShadowSection\",\n \"Sheet\",\n \"SheetClose\",\n \"SheetContent\",\n \"SheetContext\",\n \"SheetDescription\",\n \"SheetFooter\",\n \"SheetHeader\",\n \"SheetOverlay\",\n \"SheetPortal\",\n \"SheetScrim\",\n \"SheetTitle\",\n \"SheetTrigger\",\n \"Sidebar\",\n \"SidebarContent\",\n \"SidebarContext\",\n \"SidebarFooter\",\n \"SidebarGroup\",\n \"SidebarGroupAction\",\n \"SidebarGroupContent\",\n \"SidebarGroupLabel\",\n \"SidebarHeader\",\n \"SidebarInset\",\n \"SidebarMenu\",\n \"SidebarMenuAction\",\n \"SidebarMenuBadge\",\n \"SidebarMenuButton\",\n \"SidebarMenuItem\",\n \"SidebarMenuSub\",\n \"SidebarMenuSubButton\",\n \"SidebarMenuSubItem\",\n \"SidebarProvider\",\n \"SidebarRail\",\n \"SidebarScopeContext\",\n \"SidebarSeparator\",\n \"SidebarTrigger\",\n \"Skeleton\",\n \"SkeletonDetail\",\n \"SkeletonList\",\n \"SkeletonTable\",\n \"Slide\",\n \"SlideHeader\",\n \"Slider\",\n \"SliderControl\",\n \"SlideThemeContext\",\n \"SlideThemeProvider\",\n \"SlowNetworkBar\",\n \"SourceInspector\",\n \"SourceInspectorContext\",\n \"SourceInspectorDevImpl\",\n \"SourceInspectorProvider\",\n \"SourceInspectorRunner\",\n \"SourceInspectorToggle\",\n \"SpacingScale\",\n \"SpacingSection\",\n \"SpacingSlide\",\n \"Sparkline\",\n \"SpecimenCard\",\n \"SpecimenCardFooter\",\n \"Sphere\",\n \"SpherePlayground\",\n \"Spinner\",\n \"SpinnerOrb\",\n \"Stack\",\n \"Stage\",\n \"StatCard\",\n \"StatHero\",\n \"StationSpectrum\",\n \"StatusBadge\",\n \"StatusColorsSlide\",\n \"StatusDot\",\n \"Stepper\",\n \"StepperContext\",\n \"StepperDescription\",\n \"StepperItem\",\n \"StepperSeparator\",\n \"StepperTitle\",\n \"StepperTrigger\",\n \"StepsSection\",\n \"StructuredPrompt\",\n \"StructuredPromptBody\",\n \"StructuredPromptHeader\",\n \"StructuredPromptHint\",\n \"StructuredPromptSlot\",\n \"SuccessLiveRegion\",\n \"SurfaceRow\",\n \"SurfaceScaleStack\",\n \"SurfaceSection\",\n \"Switch\",\n \"Table\",\n \"TableBody\",\n \"TableCaption\",\n \"TableCell\",\n \"TableFooter\",\n \"TableHead\",\n \"TableHeader\",\n \"TableRow\",\n \"Tabs\",\n \"TabsContent\",\n \"TabsList\",\n \"TabsTrigger\",\n \"TagInput\",\n \"TestimonialAttribution\",\n \"TestimonialSection\",\n \"Text\",\n \"Textarea\",\n \"ThemeArchitectureSlide\",\n \"ThemeColorsSlide\",\n \"ThemeSwitcher\",\n \"ThinkingOrb\",\n \"Timeline\",\n \"TimelineContent\",\n \"TimelineDescription\",\n \"TimelineIcon\",\n \"TimelineItem\",\n \"TimelineTimestamp\",\n \"TimelineTitle\",\n \"TimePicker\",\n \"TinInput\",\n \"TitleSlide\",\n \"ToastCard\",\n \"ToastCardStack\",\n \"Toaster\",\n \"TOCSlide\",\n \"ToggleButton\",\n \"ToggleDevImpl\",\n \"ToggleGroup\",\n \"ToggleGroupContext\",\n \"ToggleGroupItem\",\n \"Tooltip\",\n \"TooltipContent\",\n \"TooltipProvider\",\n \"TooltipTrigger\",\n \"TypeBodySlide\",\n \"TypeDisplaySlide\",\n \"TypeScaleStack\",\n \"TypeSpecimen\",\n \"TypographySection\",\n \"Vignette\",\n \"WorkspaceSwitcher\",\n])\n"
|
|
5347
5397
|
}
|
|
5348
5398
|
]
|
|
5349
5399
|
},
|
package/dist/visor-manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "0.4.0",
|
|
3
|
-
"generated_at": "2026-10-
|
|
3
|
+
"generated_at": "2026-10-04T07:08:34.766Z",
|
|
4
4
|
"components": {
|
|
5
5
|
"accessibility-specimen": {
|
|
6
6
|
"category": "specimen",
|
|
@@ -880,6 +880,76 @@
|
|
|
880
880
|
],
|
|
881
881
|
"example": "<BentoGrid cols={{ base: 1, md: 2 }} gap=\"4\">\n <BentoTile span=\"full\" aspect=\"21/9\">\n <BentoTileMedia src=\"/projects/knowmentum.jpg\" alt=\"Knowmentum project hero\" />\n <BentoTileBody>\n <h3>Knowmentum</h3>\n <p>Knowledge platform for high-performers.</p>\n </BentoTileBody>\n </BentoTile>\n\n <BentoTile span=\"half\" aspect=\"2/1\" href=\"https://animal.nyc/\" target=\"_blank\">\n <BentoTileMedia src=\"/projects/animal-nyc.jpg\" alt=\"Animal NYC\" />\n <BentoTileBody>\n <h3>Animal NYC</h3>\n </BentoTileBody>\n </BentoTile>\n\n <BentoTile span=\"half\" aspect=\"2/1\" href=\"https://animal.la/\" target=\"_blank\">\n <BentoTileMedia src=\"/projects/animal-la.jpg\" alt=\"Animal LA\" />\n <BentoTileBody>\n <h3>Animal LA</h3>\n </BentoTileBody>\n </BentoTile>\n\n <BentoTile span=\"half\" aspect=\"4/3\" fit=\"contain\">\n <BentoTileMedia src=\"/logos/client-logo.png\" alt=\"Client logo\" />\n <BentoTileBody>\n <p>Natural-fit logo tile</p>\n </BentoTileBody>\n </BentoTile>\n</BentoGrid>\n"
|
|
882
882
|
},
|
|
883
|
+
"bottom-nav": {
|
|
884
|
+
"category": "navigation",
|
|
885
|
+
"description": "A bottom navigation bar of icon destinations for phone widths. Filled active destination, an optional unread mark, a hairline group break, safe-area padding, and its height published as --bottom-nav-height.",
|
|
886
|
+
"when_to_use": [
|
|
887
|
+
"The primary destinations of an app at phone width, as a row fixed to the foot of the screen (the desktop rail becomes the foot bar)",
|
|
888
|
+
"3 to 7 top-level destinations that each deserve one tap",
|
|
889
|
+
"A destination that sometimes has something waiting on it (an unread dot on Inbox)"
|
|
890
|
+
],
|
|
891
|
+
"when_not_to_use": [
|
|
892
|
+
"A top bar of site links (use Navbar)",
|
|
893
|
+
"A collapsible side rail on wide screens (use Sidebar)",
|
|
894
|
+
"Switching a view in place without changing the page (use SegmentedControl or Tabs)",
|
|
895
|
+
"More than 7 destinations (group the rest behind one More destination)"
|
|
896
|
+
],
|
|
897
|
+
"variants": {
|
|
898
|
+
"showLabels": [
|
|
899
|
+
true,
|
|
900
|
+
false
|
|
901
|
+
],
|
|
902
|
+
"fixed": [
|
|
903
|
+
true,
|
|
904
|
+
false
|
|
905
|
+
]
|
|
906
|
+
},
|
|
907
|
+
"props": [
|
|
908
|
+
{
|
|
909
|
+
"name": "items",
|
|
910
|
+
"type": "{ label: string; icon: BottomNavIcon; href?: string; onSelect?: (e) => void; active?: boolean; mark?: boolean | number; markLabel?: string; group?: string; key?: string }[]",
|
|
911
|
+
"description": "The destinations, in order. A hairline is drawn before the first destination of each new group."
|
|
912
|
+
},
|
|
913
|
+
{
|
|
914
|
+
"name": "showLabels",
|
|
915
|
+
"type": "boolean",
|
|
916
|
+
"default": false,
|
|
917
|
+
"description": "Show each label under its icon. Otherwise the label is visually hidden."
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
"name": "fixed",
|
|
921
|
+
"type": "boolean",
|
|
922
|
+
"default": true,
|
|
923
|
+
"description": "Pin to the foot of the viewport and publish --bottom-nav-height on the document root."
|
|
924
|
+
}
|
|
925
|
+
],
|
|
926
|
+
"dependencies": [],
|
|
927
|
+
"tokens_used": [
|
|
928
|
+
"--accent",
|
|
929
|
+
"--border-default",
|
|
930
|
+
"--border-focus",
|
|
931
|
+
"--focus-ring-offset",
|
|
932
|
+
"--focus-ring-width",
|
|
933
|
+
"--font-size-xs",
|
|
934
|
+
"--font-weight-medium",
|
|
935
|
+
"--hairline",
|
|
936
|
+
"--hairline-width",
|
|
937
|
+
"--motion-duration-150",
|
|
938
|
+
"--motion-easing-default",
|
|
939
|
+
"--radius-md",
|
|
940
|
+
"--separator-color",
|
|
941
|
+
"--spacing-1",
|
|
942
|
+
"--spacing-12",
|
|
943
|
+
"--spacing-2",
|
|
944
|
+
"--stroke-width-thin",
|
|
945
|
+
"--surface-card",
|
|
946
|
+
"--surface-interactive-active",
|
|
947
|
+
"--surface-interactive-hover",
|
|
948
|
+
"--text-primary",
|
|
949
|
+
"--text-secondary"
|
|
950
|
+
],
|
|
951
|
+
"example": "<BottomNav\n aria-label=\"Workspace\"\n items={[\n { label: \"Edit\", icon: PencilSimple, href: \"/edit\", active: true },\n { label: \"Inbox\", icon: Tray, href: \"/inbox\", mark: 3 },\n { label: \"Team\", icon: UsersThree, href: \"/team\", group: \"admin\" },\n { label: \"Settings\", icon: Gear, href: \"/settings\", group: \"admin\" },\n ]}\n/>\n"
|
|
952
|
+
},
|
|
883
953
|
"box": {
|
|
884
954
|
"category": "layout",
|
|
885
955
|
"description": "Universal layout wrapper for padding, margin, background, border, and border-radius via Visor design tokens.",
|
|
@@ -7269,6 +7339,96 @@
|
|
|
7269
7339
|
],
|
|
7270
7340
|
"example": "<Popover>\n <PopoverTrigger asChild>\n <Button variant=\"outline\">Filter</Button>\n </PopoverTrigger>\n <PopoverContent>\n <p>Filter options here</p>\n <PopoverFooter>\n <Button variant=\"ghost\">Cancel</Button>\n <Button>Apply</Button>\n </PopoverFooter>\n </PopoverContent>\n</Popover>\n"
|
|
7271
7341
|
},
|
|
7342
|
+
"position-picker": {
|
|
7343
|
+
"category": "form",
|
|
7344
|
+
"description": "A 3x3 anchor grid. Nine targets, one chosen, the value a { y, x } pair. Draws on its own ground or over a photograph.",
|
|
7345
|
+
"when_to_use": [
|
|
7346
|
+
"Choosing a focal point over a photograph (nine dots on the image)",
|
|
7347
|
+
"Placing a logo, title or block at one of nine anchors",
|
|
7348
|
+
"Any choice of one of nine positions, in two dimensions"
|
|
7349
|
+
],
|
|
7350
|
+
"when_not_to_use": [
|
|
7351
|
+
"A choice along one axis only (use RadioGroup or SegmentedControl)",
|
|
7352
|
+
"A free-form coordinate or percentage (use Slider or two NumberInputs)",
|
|
7353
|
+
"Dragging a point to an exact spot (this picks from nine, it does not drag)"
|
|
7354
|
+
],
|
|
7355
|
+
"props": [
|
|
7356
|
+
{
|
|
7357
|
+
"name": "value",
|
|
7358
|
+
"type": "PositionValue | null",
|
|
7359
|
+
"description": "Controlled value, a { y: 'top' | 'center' | 'bottom', x: 'left' | 'center' | 'right' } pair. null is controlled with nothing chosen."
|
|
7360
|
+
},
|
|
7361
|
+
{
|
|
7362
|
+
"name": "defaultValue",
|
|
7363
|
+
"type": "PositionValue | null",
|
|
7364
|
+
"description": "Initial value when uncontrolled."
|
|
7365
|
+
},
|
|
7366
|
+
{
|
|
7367
|
+
"name": "onValueChange",
|
|
7368
|
+
"type": "(value: PositionValue) => void",
|
|
7369
|
+
"description": "Called with the chosen { y, x } pair."
|
|
7370
|
+
},
|
|
7371
|
+
{
|
|
7372
|
+
"name": "variant",
|
|
7373
|
+
"type": "'default' | 'on-image'",
|
|
7374
|
+
"default": "'default'",
|
|
7375
|
+
"description": "default draws the grid on its own ground. on-image fills a positioned parent (the photograph's frame) and gives every target its own scrim ring."
|
|
7376
|
+
},
|
|
7377
|
+
{
|
|
7378
|
+
"name": "disabled",
|
|
7379
|
+
"type": "boolean",
|
|
7380
|
+
"default": "false",
|
|
7381
|
+
"description": "Disables all nine targets."
|
|
7382
|
+
},
|
|
7383
|
+
{
|
|
7384
|
+
"name": "renderTarget",
|
|
7385
|
+
"type": "(position: PositionValue, state: { selected: boolean }) => ReactNode",
|
|
7386
|
+
"description": "Replaces the drawn dot of one target, for example an alignment icon in the occupied row. Decorative; the target keeps its accessible name."
|
|
7387
|
+
}
|
|
7388
|
+
],
|
|
7389
|
+
"dependencies": [],
|
|
7390
|
+
"tokens_used": [
|
|
7391
|
+
"--_scrim",
|
|
7392
|
+
"--_target",
|
|
7393
|
+
"--border-default",
|
|
7394
|
+
"--border-focus",
|
|
7395
|
+
"--color-black",
|
|
7396
|
+
"--color-white",
|
|
7397
|
+
"--control-edge-color",
|
|
7398
|
+
"--control-edge-style",
|
|
7399
|
+
"--control-edge-width",
|
|
7400
|
+
"--focus-ring-offset",
|
|
7401
|
+
"--focus-ring-width",
|
|
7402
|
+
"--interactive-primary-bg",
|
|
7403
|
+
"--interactive-primary-text",
|
|
7404
|
+
"--motion-duration-150",
|
|
7405
|
+
"--motion-easing-default",
|
|
7406
|
+
"--opacity-50",
|
|
7407
|
+
"--position-picker-bg",
|
|
7408
|
+
"--position-picker-border",
|
|
7409
|
+
"--position-picker-image-mark",
|
|
7410
|
+
"--position-picker-mark",
|
|
7411
|
+
"--position-picker-mark-selected",
|
|
7412
|
+
"--position-picker-ring",
|
|
7413
|
+
"--position-picker-scrim",
|
|
7414
|
+
"--position-picker-target-size",
|
|
7415
|
+
"--primary",
|
|
7416
|
+
"--radius-full",
|
|
7417
|
+
"--radius-md",
|
|
7418
|
+
"--radius-sm",
|
|
7419
|
+
"--spacing-1",
|
|
7420
|
+
"--spacing-3",
|
|
7421
|
+
"--spacing-4",
|
|
7422
|
+
"--spacing-6",
|
|
7423
|
+
"--spacing-8",
|
|
7424
|
+
"--stroke-width-medium",
|
|
7425
|
+
"--surface-subtle",
|
|
7426
|
+
"--text-primary",
|
|
7427
|
+
"--text-secondary",
|
|
7428
|
+
"--text-tertiary"
|
|
7429
|
+
],
|
|
7430
|
+
"example": "<PositionPicker\n aria-label=\"Focal point\"\n defaultValue={{ y: \"center\", x: \"center\" }}\n onValueChange={(position) => console.log(position)}\n/>\n"
|
|
7431
|
+
},
|
|
7272
7432
|
"progress": {
|
|
7273
7433
|
"category": "feedback",
|
|
7274
7434
|
"description": "Horizontal bar indicating completion percentage of a task.",
|
|
@@ -8604,11 +8764,34 @@
|
|
|
8604
8764
|
"type": "'offcanvas' | 'icon' | 'none'",
|
|
8605
8765
|
"default": "offcanvas"
|
|
8606
8766
|
},
|
|
8767
|
+
{
|
|
8768
|
+
"name": "position",
|
|
8769
|
+
"type": "'fixed' | 'contained'",
|
|
8770
|
+
"default": "fixed",
|
|
8771
|
+
"description": "Sidebar prop. `fixed` pins the sidebar to the viewport at full height. `contained` lays it out inside its parent, so an icon rail can sit under an app bar without covering it.\n"
|
|
8772
|
+
},
|
|
8607
8773
|
{
|
|
8608
8774
|
"name": "defaultOpen",
|
|
8609
8775
|
"type": "boolean",
|
|
8610
8776
|
"default": true,
|
|
8611
8777
|
"description": "Initial open state (SidebarProvider prop)"
|
|
8778
|
+
},
|
|
8779
|
+
{
|
|
8780
|
+
"name": "keyboardShortcut",
|
|
8781
|
+
"type": "boolean",
|
|
8782
|
+
"default": true,
|
|
8783
|
+
"description": "SidebarProvider prop. Binds Cmd/Ctrl+B to toggle the sidebar. Skipped (no preventDefault) when only collapsible=\"none\" sidebars are mounted; false never binds it.\n"
|
|
8784
|
+
},
|
|
8785
|
+
{
|
|
8786
|
+
"name": "asChild",
|
|
8787
|
+
"type": "boolean",
|
|
8788
|
+
"default": false,
|
|
8789
|
+
"description": "SidebarMenuButton prop. Renders the child (a Next Link or anchor) as the item via Radix Slot."
|
|
8790
|
+
},
|
|
8791
|
+
{
|
|
8792
|
+
"name": "tooltip",
|
|
8793
|
+
"type": "string | (TooltipContentProps & { children: ReactNode })",
|
|
8794
|
+
"description": "SidebarMenuButton prop. Shown in a Tooltip when the sidebar is an icon rail (collapsible=\"icon\" while collapsed, or collapsible=\"none\"); never on mobile or when expanded. A string also names the button (aria-label) unless the button sets its own. Portals into the Sidebar so a scoped theme reaches it; pass `container` to override.\n"
|
|
8612
8795
|
}
|
|
8613
8796
|
],
|
|
8614
8797
|
"sub_components": [
|
|
@@ -8698,6 +8881,7 @@
|
|
|
8698
8881
|
}
|
|
8699
8882
|
],
|
|
8700
8883
|
"dependencies": [
|
|
8884
|
+
"@radix-ui/react-slot",
|
|
8701
8885
|
"class-variance-authority",
|
|
8702
8886
|
"@phosphor-icons/react"
|
|
8703
8887
|
],
|
|
@@ -11305,6 +11489,7 @@
|
|
|
11305
11489
|
"--spacing-3",
|
|
11306
11490
|
"--surface-overlay",
|
|
11307
11491
|
"--text-inverse",
|
|
11492
|
+
"--text-primary",
|
|
11308
11493
|
"--tooltip-bg",
|
|
11309
11494
|
"--tooltip-text"
|
|
11310
11495
|
],
|
|
@@ -13755,18 +13940,8 @@
|
|
|
13755
13940
|
"toast-card",
|
|
13756
13941
|
"tooltip"
|
|
13757
13942
|
],
|
|
13758
|
-
"layout": [
|
|
13759
|
-
"box",
|
|
13760
|
-
"collapsible",
|
|
13761
|
-
"container",
|
|
13762
|
-
"grid",
|
|
13763
|
-
"inline",
|
|
13764
|
-
"scroll-area",
|
|
13765
|
-
"separator",
|
|
13766
|
-
"sidebar",
|
|
13767
|
-
"stack"
|
|
13768
|
-
],
|
|
13769
13943
|
"navigation": [
|
|
13944
|
+
"bottom-nav",
|
|
13770
13945
|
"breadcrumb",
|
|
13771
13946
|
"command",
|
|
13772
13947
|
"doc-frame",
|
|
@@ -13780,6 +13955,17 @@
|
|
|
13780
13955
|
"stepper",
|
|
13781
13956
|
"tabs"
|
|
13782
13957
|
],
|
|
13958
|
+
"layout": [
|
|
13959
|
+
"box",
|
|
13960
|
+
"collapsible",
|
|
13961
|
+
"container",
|
|
13962
|
+
"grid",
|
|
13963
|
+
"inline",
|
|
13964
|
+
"scroll-area",
|
|
13965
|
+
"separator",
|
|
13966
|
+
"sidebar",
|
|
13967
|
+
"stack"
|
|
13968
|
+
],
|
|
13783
13969
|
"form": [
|
|
13784
13970
|
"button",
|
|
13785
13971
|
"calendar",
|
|
@@ -13802,6 +13988,7 @@
|
|
|
13802
13988
|
"otp-input",
|
|
13803
13989
|
"password-input",
|
|
13804
13990
|
"phone-input",
|
|
13991
|
+
"position-picker",
|
|
13805
13992
|
"radio-group",
|
|
13806
13993
|
"search-input",
|
|
13807
13994
|
"segmented-control",
|