@loworbitstudio/visor 1.16.1 → 1.17.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "0.4.0",
3
- "generated_at": "2026-07-07T17:14:24.752Z",
3
+ "generated_at": "2026-07-08T04:58:15.600Z",
4
4
  "components": {
5
5
  "accessibility-specimen": {
6
6
  "changeType": "current",
package/dist/index.js CHANGED
@@ -35,7 +35,7 @@ function loadManifest() {
35
35
  function findItem(registry, name) {
36
36
  return registry.items.find((item) => item.name === name);
37
37
  }
38
- function resolveTransitiveDeps(registry, names, onWarning) {
38
+ function resolveTransitiveDeps(registry, names, onWarning, includeSuggested = false) {
39
39
  const resolved = /* @__PURE__ */ new Map();
40
40
  const queue = names.map((n) => ({
41
41
  name: n,
@@ -49,10 +49,11 @@ function resolveTransitiveDeps(registry, names, onWarning) {
49
49
  throw new Error(`Registry item "${name}" not found.`);
50
50
  }
51
51
  resolved.set(name, item);
52
- if (item.registryDependencies) {
52
+ const walkDeps = includeSuggested ? [...item.registryDependencies ?? [], ...item.suggestedDependencies ?? []] : item.registryDependencies;
53
+ if (walkDeps) {
53
54
  const childAncestors = new Set(ancestors);
54
55
  childAncestors.add(name);
55
- for (const dep of item.registryDependencies) {
56
+ for (const dep of walkDeps) {
56
57
  if (childAncestors.has(dep)) {
57
58
  onWarning?.(`Circular registry dependency: ${name} \u2192 ${dep}`);
58
59
  } else if (!resolved.has(dep)) {
@@ -63,6 +64,17 @@ function resolveTransitiveDeps(registry, names, onWarning) {
63
64
  }
64
65
  return Array.from(resolved.values());
65
66
  }
67
+ function collectSuggestedDeps(registry, rootNames, resolvedNames) {
68
+ const suggested = /* @__PURE__ */ new Set();
69
+ for (const name of rootNames) {
70
+ const item = findItem(registry, name);
71
+ if (!item?.suggestedDependencies) continue;
72
+ for (const dep of item.suggestedDependencies) {
73
+ if (!resolvedNames.has(dep)) suggested.add(dep);
74
+ }
75
+ }
76
+ return Array.from(suggested).sort();
77
+ }
66
78
  function collectDependencies(items) {
67
79
  const deps = /* @__PURE__ */ new Set();
68
80
  const devDeps = /* @__PURE__ */ new Set();
@@ -2169,6 +2181,7 @@ function runFlutterPubGet(cwd, bin) {
2169
2181
  function addCommand(components, cwd, options = {}) {
2170
2182
  const json = options.json ?? false;
2171
2183
  const dryRun = options.dryRun ?? false;
2184
+ const withSuggested = options.withSuggested ?? false;
2172
2185
  const target = options.target ?? "react";
2173
2186
  const prefix = dryRun ? "[dry-run] " : "";
2174
2187
  let autoInitialized = false;
@@ -2318,9 +2331,14 @@ function addCommand(components, cwd, options = {}) {
2318
2331
  const circularWarnings = [];
2319
2332
  let items;
2320
2333
  try {
2321
- items = resolveTransitiveDeps(targetRegistry, canonicalNames, (msg) => {
2322
- circularWarnings.push(msg);
2323
- });
2334
+ items = resolveTransitiveDeps(
2335
+ targetRegistry,
2336
+ canonicalNames,
2337
+ (msg) => {
2338
+ circularWarnings.push(msg);
2339
+ },
2340
+ withSuggested
2341
+ );
2324
2342
  } catch (error) {
2325
2343
  if (json) {
2326
2344
  const message = error instanceof Error ? error.message : String(error);
@@ -2334,6 +2352,8 @@ function addCommand(components, cwd, options = {}) {
2334
2352
  logger.warn(warning);
2335
2353
  }
2336
2354
  }
2355
+ const resolvedNames = new Set(items.map((i) => i.name));
2356
+ const suggestedAvailable = withSuggested ? [] : collectSuggestedDeps(targetRegistry, canonicalNames, resolvedNames);
2337
2357
  if (!json) {
2338
2358
  logger.info(
2339
2359
  `Resolving ${itemNames.length} item(s) \u2192 ${items.length} total (with dependencies)`
@@ -2501,6 +2521,7 @@ function addCommand(components, cwd, options = {}) {
2501
2521
  autoInitialized,
2502
2522
  requested: itemNames,
2503
2523
  resolved: items.map((i) => i.name),
2524
+ ...suggestedAvailable.length > 0 ? { suggested: suggestedAvailable } : {},
2504
2525
  files: { written: writtenFiles, skipped: skippedFiles },
2505
2526
  dependencies: { installed: installedDeps, failed: failedDeps },
2506
2527
  warnings
@@ -2511,6 +2532,17 @@ function addCommand(components, cwd, options = {}) {
2511
2532
  );
2512
2533
  process.exit(failedDeps.length > 0 ? 1 : 0);
2513
2534
  }
2535
+ if (suggestedAvailable.length > 0) {
2536
+ logger.blank();
2537
+ logger.info(
2538
+ `Suggested slot-fill components (not installed): ${suggestedAvailable.join(", ")}`
2539
+ );
2540
+ logger.info(
2541
+ ` These fill the block's slots but aren't required to render it. Add with:`
2542
+ );
2543
+ logger.info(` npx visor add ${canonicalNames.join(" ")} --block --with-suggested`);
2544
+ logger.info(` or: npx visor add ${suggestedAvailable.join(" ")}`);
2545
+ }
2514
2546
  if (failedDeps.length > 0) {
2515
2547
  process.exit(1);
2516
2548
  }
@@ -8154,9 +8186,9 @@ program.command("init").description("Initialize Visor \u2014 with --template nex
8154
8186
  program.command("list").description("List all available registry items").option("--json", "output structured JSON (for AI agents)").option("--category <name>", "filter items by category").action((options) => {
8155
8187
  listCommand(process.cwd(), options);
8156
8188
  });
8157
- program.command("add").description("Add components, hooks, blocks, or utilities to your project").argument("[items...]", "names of registry items to add").option("--overwrite", "overwrite existing files", false).option("--category <name>", "install all items from a category").option("--block", "install blocks instead of components").option("--target <platform>", "target platform: react (default) or flutter", "react").option("--dry-run", "preview what would be added without writing files").option("--json", "output structured JSON (for AI agents)").action((items, options) => {
8189
+ program.command("add").description("Add components, hooks, blocks, or utilities to your project").argument("[items...]", "names of registry items to add").option("--overwrite", "overwrite existing files", false).option("--category <name>", "install all items from a category").option("--block", "install blocks instead of components").option("--with-suggested", "also install a block's suggested slot-fill components (e.g. breadcrumb, dropdown-menu, sidebar for admin-shell)").option("--target <platform>", "target platform: react (default) or flutter", "react").option("--dry-run", "preview what would be added without writing files").option("--json", "output structured JSON (for AI agents)").action((items, options) => {
8158
8190
  const target = options.target === "flutter" ? "flutter" : "react";
8159
- addCommand(items, process.cwd(), { overwrite: options.overwrite, category: options.category, block: options.block, target, dryRun: options.dryRun, json: options.json });
8191
+ addCommand(items, process.cwd(), { overwrite: options.overwrite, category: options.category, block: options.block, withSuggested: options.withSuggested, target, dryRun: options.dryRun, json: options.json });
8160
8192
  });
8161
8193
  program.command("diff").description(
8162
8194
  "Show differences between local files and the registry"
@@ -2936,7 +2936,7 @@
2936
2936
  {
2937
2937
  "path": "components/ui/status-badge/status-badge.tsx",
2938
2938
  "type": "registry:ui",
2939
- "content": "import * as React from \"react\"\nimport { cn } from \"../../../lib/utils\"\nimport { Badge } from \"../badge/badge\"\nimport type { BadgeProps } from \"../badge/badge\"\nimport styles from \"./status-badge.module.css\"\n\nexport type StatusBadgeStatus =\n | \"healthy\"\n | \"degraded\"\n | \"down\"\n | \"failed\"\n | \"running\"\n | \"pending\"\n | \"queued\"\n | \"idle\"\n | \"complete\"\n | \"live\"\n | \"warn\"\n | \"scheduled\"\n | \"sold\"\n | \"draft\"\n | \"prospect\"\n | \"pitched\"\n | \"contracted\"\n | \"active\"\n | \"paused\"\n | \"completed\"\n | \"archived\"\n\nexport type StatusBadgeTone = \"subtle\" | \"filled\"\n\nexport interface StatusBadgeProps\n extends Omit<React.HTMLAttributes<HTMLSpanElement>, \"children\"> {\n /** Semantic admin status. Drives both color and default label. */\n status: StatusBadgeStatus\n /** Visible text. Defaults to the capitalized status key. */\n label?: React.ReactNode\n /** Which Badge variant family to use. Defaults to \"subtle\". */\n tone?: StatusBadgeTone\n /** Render the leading indicator dot. Defaults to true. */\n indicator?: boolean\n /** Animate the indicator dot with a soft pulse. Defaults to false. */\n pulse?: boolean\n}\n\n/**\n * Default human-readable labels for each status. Exported so consumers can\n * localize or override without reimplementing the map.\n */\nexport const statusBadgeLabels: Record<StatusBadgeStatus, string> = {\n healthy: \"Healthy\",\n degraded: \"Degraded\",\n down: \"Down\",\n failed: \"Failed\",\n running: \"Running\",\n pending: \"Pending\",\n queued: \"Queued\",\n idle: \"Idle\",\n complete: \"Complete\",\n live: \"Live\",\n warn: \"Warn\",\n scheduled: \"Scheduled\",\n sold: \"Sold\",\n draft: \"Draft\",\n prospect: \"Prospect\",\n pitched: \"Pitched\",\n contracted: \"Contracted\",\n active: \"Active\",\n paused: \"Paused\",\n completed: \"Completed\",\n archived: \"Archived\",\n}\n\ntype BadgeVariant = NonNullable<BadgeProps[\"variant\"]>\n\n/**\n * Color group keyed on the semantic status. Used to pick the indicator dot\n * class and the underlying Badge variant.\n */\ntype StatusColorGroup =\n | \"success\"\n | \"warning\"\n | \"destructive\"\n | \"info\"\n | \"neutral\"\n\nconst STATUS_COLOR_GROUP: Record<StatusBadgeStatus, StatusColorGroup> = {\n healthy: \"success\",\n complete: \"success\",\n degraded: \"warning\",\n pending: \"warning\",\n down: \"destructive\",\n failed: \"destructive\",\n running: \"info\",\n queued: \"neutral\",\n idle: \"neutral\",\n // Admin-ui event tones — map to existing semantic groups.\n // live: active/in-progress positive event → success accent\n // warn: needs attention but not failing → warning\n // scheduled: upcoming/planned, visually grouped with draft → neutral\n // sold: positive completed outcome → success\n // draft: unpublished/muted → neutral\n live: \"success\",\n warn: \"warning\",\n scheduled: \"neutral\",\n sold: \"success\",\n draft: \"neutral\",\n // CRM / pipeline stages (VI-492) — bind to existing semantic groups.\n // No new tokens: stages that share a meaning share a color.\n // prospect: new informational lead → info\n // pitched: awaiting response, needs attention → warning\n // contracted / active / completed: positive, in-good-standing → success\n // paused: temporarily on hold, needs attention → warning\n // archived: closed/muted, grouped with draft → neutral\n prospect: \"info\",\n pitched: \"warning\",\n contracted: \"success\",\n active: \"success\",\n paused: \"warning\",\n completed: \"success\",\n archived: \"neutral\",\n}\n\nconst SUBTLE_VARIANT: Record<StatusColorGroup, BadgeVariant> = {\n success: \"success\",\n warning: \"warning\",\n destructive: \"destructive\",\n info: \"info\",\n neutral: \"neutral\",\n}\n\nconst FILLED_VARIANT: Record<StatusColorGroup, BadgeVariant> = {\n success: \"filled-success\",\n warning: \"filled-warning\",\n destructive: \"filled-destructive\",\n info: \"filled-info\",\n neutral: \"filled-neutral\",\n}\n\nconst INDICATOR_CLASS: Record<StatusColorGroup, string> = {\n success: styles.indicatorSuccess,\n warning: styles.indicatorWarning,\n destructive: styles.indicatorDestructive,\n info: styles.indicatorInfo,\n neutral: styles.indicatorNeutral,\n}\n\nconst StatusBadge = React.forwardRef<HTMLSpanElement, StatusBadgeProps>(\n (\n {\n className,\n status,\n label,\n tone = \"subtle\",\n indicator = true,\n pulse = false,\n ...props\n },\n ref\n ) => {\n const group = STATUS_COLOR_GROUP[status]\n const variant: BadgeVariant =\n tone === \"filled\" ? FILLED_VARIANT[group] : SUBTLE_VARIANT[group]\n const visibleLabel = label ?? statusBadgeLabels[status]\n\n return (\n <Badge\n ref={ref}\n variant={variant}\n data-slot=\"status-badge\"\n data-status={status}\n data-tone={tone}\n className={cn(className)}\n {...props}\n >\n {indicator ? (\n <span\n data-slot=\"status-badge-indicator\"\n aria-hidden=\"true\"\n className={cn(\n styles.indicator,\n INDICATOR_CLASS[group],\n pulse && styles.pulse\n )}\n />\n ) : null}\n <span className={styles.srOnly}>Status: </span>\n <span data-slot=\"status-badge-label\">{visibleLabel}</span>\n </Badge>\n )\n }\n)\nStatusBadge.displayName = \"StatusBadge\"\n\nexport { StatusBadge }\n"
2939
+ "content": "import * as React from \"react\"\nimport { cn } from \"../../../lib/utils\"\nimport { Badge } from \"../badge/badge\"\nimport type { BadgeProps } from \"../badge/badge\"\nimport styles from \"./status-badge.module.css\"\n\nexport type StatusBadgeStatus =\n | \"healthy\"\n | \"degraded\"\n | \"down\"\n | \"failed\"\n | \"running\"\n | \"pending\"\n | \"queued\"\n | \"idle\"\n | \"complete\"\n | \"live\"\n | \"warn\"\n | \"scheduled\"\n | \"sold\"\n | \"draft\"\n | \"prospect\"\n | \"pitched\"\n | \"contracted\"\n | \"active\"\n | \"paused\"\n | \"completed\"\n | \"archived\"\n\nexport type StatusBadgeTone = \"subtle\" | \"filled\"\n\nexport interface StatusBadgeProps\n extends Omit<React.HTMLAttributes<HTMLSpanElement>, \"children\"> {\n /** Semantic admin status. Drives both color and default label. */\n status: StatusBadgeStatus\n /** Visible text. Defaults to the capitalized status key. */\n label?: React.ReactNode\n /** Which Badge variant family to use. Defaults to \"subtle\". */\n tone?: StatusBadgeTone\n /** Render the leading indicator dot. Defaults to true. */\n indicator?: boolean\n /** Animate the indicator dot with a soft pulse. Defaults to false. */\n pulse?: boolean\n}\n\n/**\n * Default human-readable labels for each status. Exported so consumers can\n * localize or override without reimplementing the map.\n */\nexport const statusBadgeLabels: Record<StatusBadgeStatus, string> = {\n healthy: \"Healthy\",\n degraded: \"Degraded\",\n down: \"Down\",\n failed: \"Failed\",\n running: \"Running\",\n pending: \"Pending\",\n queued: \"Queued\",\n idle: \"Idle\",\n complete: \"Complete\",\n live: \"Live\",\n warn: \"Warn\",\n scheduled: \"Scheduled\",\n sold: \"Sold\",\n draft: \"Draft\",\n prospect: \"Prospect\",\n pitched: \"Pitched\",\n contracted: \"Contracted\",\n active: \"Active\",\n paused: \"Paused\",\n completed: \"Completed\",\n archived: \"Archived\",\n}\n\ntype BadgeVariant = NonNullable<BadgeProps[\"variant\"]>\n\n/**\n * Color group keyed on the semantic status. Used to pick the indicator dot\n * class and the underlying Badge variant.\n */\ntype StatusColorGroup =\n | \"success\"\n | \"warning\"\n | \"destructive\"\n | \"info\"\n | \"neutral\"\n\nconst STATUS_COLOR_GROUP: Record<StatusBadgeStatus, StatusColorGroup> = {\n healthy: \"success\",\n complete: \"success\",\n degraded: \"warning\",\n pending: \"warning\",\n down: \"destructive\",\n failed: \"destructive\",\n running: \"info\",\n queued: \"neutral\",\n idle: \"neutral\",\n // Admin-ui event tones — map to existing semantic groups.\n // live: active/in-progress positive event → success accent\n // warn: needs attention but not failing → warning\n // scheduled: upcoming/committed, distinct from draft's muted grey info\n // sold: positive completed outcome → success\n // draft: unpublished/muted → neutral\n live: \"success\",\n warn: \"warning\",\n scheduled: \"info\",\n sold: \"success\",\n draft: \"neutral\",\n // CRM / pipeline stages (VI-492) — bind to existing semantic groups.\n // No new tokens: stages that share a meaning share a color.\n // prospect: new informational lead → info\n // pitched: awaiting response, needs attention → warning\n // contracted / active / completed: positive, in-good-standing → success\n // paused: temporarily on hold, needs attention → warning\n // archived: closed/muted, grouped with draft → neutral\n prospect: \"info\",\n pitched: \"warning\",\n contracted: \"success\",\n active: \"success\",\n paused: \"warning\",\n completed: \"success\",\n archived: \"neutral\",\n}\n\nconst SUBTLE_VARIANT: Record<StatusColorGroup, BadgeVariant> = {\n success: \"success\",\n warning: \"warning\",\n destructive: \"destructive\",\n info: \"info\",\n neutral: \"neutral\",\n}\n\nconst FILLED_VARIANT: Record<StatusColorGroup, BadgeVariant> = {\n success: \"filled-success\",\n warning: \"filled-warning\",\n destructive: \"filled-destructive\",\n info: \"filled-info\",\n neutral: \"filled-neutral\",\n}\n\nconst INDICATOR_CLASS: Record<StatusColorGroup, string> = {\n success: styles.indicatorSuccess,\n warning: styles.indicatorWarning,\n destructive: styles.indicatorDestructive,\n info: styles.indicatorInfo,\n neutral: styles.indicatorNeutral,\n}\n\nconst StatusBadge = React.forwardRef<HTMLSpanElement, StatusBadgeProps>(\n (\n {\n className,\n status,\n label,\n tone = \"subtle\",\n indicator = true,\n pulse = false,\n ...props\n },\n ref\n ) => {\n const group = STATUS_COLOR_GROUP[status]\n const variant: BadgeVariant =\n tone === \"filled\" ? FILLED_VARIANT[group] : SUBTLE_VARIANT[group]\n const visibleLabel = label ?? statusBadgeLabels[status]\n\n return (\n <Badge\n ref={ref}\n variant={variant}\n data-slot=\"status-badge\"\n data-status={status}\n data-tone={tone}\n className={cn(className)}\n {...props}\n >\n {indicator ? (\n <span\n data-slot=\"status-badge-indicator\"\n aria-hidden=\"true\"\n className={cn(\n styles.indicator,\n INDICATOR_CLASS[group],\n pulse && styles.pulse\n )}\n />\n ) : null}\n <span className={styles.srOnly}>Status: </span>\n <span data-slot=\"status-badge-label\">{visibleLabel}</span>\n </Badge>\n )\n }\n)\nStatusBadge.displayName = \"StatusBadge\"\n\nexport { StatusBadge }\n"
2940
2940
  },
2941
2941
  {
2942
2942
  "path": "components/ui/status-badge/status-badge.module.css",
@@ -3858,7 +3858,9 @@
3858
3858
  "@loworbitstudio/visor-core"
3859
3859
  ],
3860
3860
  "registryDependencies": [
3861
- "utils",
3861
+ "utils"
3862
+ ],
3863
+ "suggestedDependencies": [
3862
3864
  "breadcrumb",
3863
3865
  "dropdown-menu",
3864
3866
  "sidebar"
@@ -3934,6 +3936,34 @@
3934
3936
  }
3935
3937
  ]
3936
3938
  },
3939
+ {
3940
+ "name": "admin-detail",
3941
+ "type": "registry:block",
3942
+ "description": "Full-page, read-oriented detail RECORD for the admin-shell main column. Composes an identity header (media + title + StatusBadge + actions), N key-value sections built on KeyValueList, an optional sensitive/reveal panel gated behind a Switch, and optional sub-list slots for ledgers or history. The full-page sibling to admin-detail-drawer.",
3943
+ "category": "admin",
3944
+ "dependencies": [
3945
+ "@loworbitstudio/visor-core"
3946
+ ],
3947
+ "registryDependencies": [
3948
+ "utils",
3949
+ "key-value-list",
3950
+ "status-badge",
3951
+ "switch",
3952
+ "separator"
3953
+ ],
3954
+ "files": [
3955
+ {
3956
+ "path": "blocks/admin-detail/admin-detail.tsx",
3957
+ "type": "registry:block",
3958
+ "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"../../lib/utils\"\nimport {\n KeyValueList,\n type KeyValueItem,\n} from \"../../components/ui/key-value-list/key-value-list\"\nimport {\n StatusBadge,\n statusBadgeLabels,\n type StatusBadgeStatus,\n} from \"../../components/ui/status-badge/status-badge\"\nimport { Switch } from \"../../components/ui/switch/switch\"\nimport { Separator } from \"../../components/ui/separator/separator\"\nimport styles from \"./admin-detail.module.css\"\n\n// ─── Shared KeyValueList passthrough ─────────────────────────────────────────\n\n/** KeyValueList configuration shared by record sections and the sensitive panel. */\ninterface KeyValueConfig {\n /** Label/value pairs rendered via the composed `KeyValueList`. */\n items?: KeyValueItem[]\n /** Grid column count forwarded to `KeyValueList`. Defaults to 2. */\n columns?: 1 | 2 | 3 | 4\n /** Label placement forwarded to `KeyValueList`. Defaults to `stacked`. */\n orientation?: \"horizontal\" | \"stacked\"\n /** Row density forwarded to `KeyValueList`. Defaults to `editorial`. */\n density?: \"compact\" | \"default\" | \"editorial\"\n}\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface AdminDetailSection extends KeyValueConfig {\n /** Stable identifier — becomes the section's DOM `id` anchor. */\n id?: string\n /** Small uppercase label rendered above the section title. */\n eyebrow?: React.ReactNode\n /** Section heading. */\n title?: React.ReactNode\n /** Supporting copy rendered below the section title. */\n description?: React.ReactNode\n /** Right-aligned action slot for the section header (edit link, menu, etc.). */\n actions?: React.ReactNode\n /**\n * Arbitrary sub-list content rendered below the key-value pairs — invoice\n * ledger rows, booking history, or any bespoke table. Renders after `items`.\n */\n content?: React.ReactNode\n}\n\nexport interface AdminDetailSensitivePanel extends KeyValueConfig {\n /** Stable identifier — becomes the panel's DOM `id` anchor. */\n id?: string\n /** Small uppercase label rendered above the panel title. */\n eyebrow?: React.ReactNode\n /** Panel heading — e.g. \"Tax & Banking\". */\n title?: React.ReactNode\n /** Supporting copy rendered below the panel title. */\n description?: React.ReactNode\n /** Extra content revealed alongside `items` when the panel is unlocked. */\n content?: React.ReactNode\n /** Label paired with the reveal switch. Defaults to \"Reveal\". */\n revealLabel?: React.ReactNode\n /** Note shown while the panel is hidden. Defaults to \"Hidden for privacy.\" */\n hiddenNote?: React.ReactNode\n /** Controlled reveal state. Omit for uncontrolled behavior. */\n revealed?: boolean\n /** Reveal-state change handler (fires in both controlled and uncontrolled modes). */\n onRevealedChange?: (revealed: boolean) => void\n /** Initial reveal state when uncontrolled. Defaults to false. */\n defaultRevealed?: boolean\n}\n\nexport interface AdminDetailProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\"> {\n // ── Identity header ───────────────────────────────────────────────────────\n /** Small uppercase label rendered above the record title. */\n eyebrow?: React.ReactNode\n /** Record title — the primary identity of the page. */\n title: React.ReactNode\n /** Supporting line beneath the title (email, handle, category, etc.). */\n subtitle?: React.ReactNode\n /** Leading media slot — Avatar, logo plate, or icon. */\n media?: React.ReactNode\n /**\n * Record status. A `StatusBadgeStatus` string renders a composed\n * `StatusBadge`; any other node renders as-is.\n */\n status?: StatusBadgeStatus | React.ReactNode\n /** Breadcrumb node rendered above the identity row. */\n breadcrumb?: React.ReactNode\n /** Right-aligned header action slot (edit, archive, overflow menu). */\n actions?: React.ReactNode\n /** Replace the default identity header entirely with custom chrome. */\n header?: React.ReactNode\n /** Suppress the hairline divider beneath the identity header. */\n hideHeaderDivider?: boolean\n\n // ── Body ──────────────────────────────────────────────────────────────────\n /** Key-value record sections, each composing a `KeyValueList`. */\n sections?: AdminDetailSection[]\n /** Optional sensitive/reveal panel gated behind a reveal switch. */\n sensitive?: AdminDetailSensitivePanel\n /** Arbitrary trailing content appended after the sections. */\n children?: React.ReactNode\n\n // ── Layout ────────────────────────────────────────────────────────────────\n /** Max-width of the record column. Number is treated as pixels. */\n maxWidth?: number | string\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction isStatusBadgeStatus(\n value: React.ReactNode\n): value is StatusBadgeStatus {\n return typeof value === \"string\" && value in statusBadgeLabels\n}\n\nfunction resolveSize(\n value: number | string | undefined,\n fallback: string\n): string {\n if (value == null) return fallback\n return typeof value === \"number\" ? `${value}px` : value\n}\n\n// ─── Section renderer ────────────────────────────────────────────────────────\n\nfunction SectionHeader({\n eyebrow,\n title,\n description,\n actions,\n}: {\n eyebrow?: React.ReactNode\n title?: React.ReactNode\n description?: React.ReactNode\n actions?: React.ReactNode\n}) {\n if (!eyebrow && !title && !description && !actions) return null\n return (\n <div className={styles.sectionHeader} data-slot=\"admin-detail-section-header\">\n <div className={styles.sectionHeaderText}>\n {eyebrow ? (\n <p className={styles.sectionEyebrow}>{eyebrow}</p>\n ) : null}\n {title ? <h2 className={styles.sectionTitle}>{title}</h2> : null}\n {description ? (\n <p className={styles.sectionDescription}>{description}</p>\n ) : null}\n </div>\n {actions ? (\n <div className={styles.sectionActions}>{actions}</div>\n ) : null}\n </div>\n )\n}\n\nfunction RecordSection({ section }: { section: AdminDetailSection }) {\n const hasItems = section.items != null && section.items.length > 0\n return (\n <section\n id={section.id}\n className={styles.section}\n data-slot=\"admin-detail-section\"\n >\n <SectionHeader\n eyebrow={section.eyebrow}\n title={section.title}\n description={section.description}\n actions={section.actions}\n />\n {hasItems ? (\n <KeyValueList\n items={section.items!}\n columns={section.columns ?? 2}\n orientation={section.orientation ?? \"stacked\"}\n density={section.density ?? \"editorial\"}\n />\n ) : null}\n {section.content}\n </section>\n )\n}\n\n// ─── Sensitive panel ─────────────────────────────────────────────────────────\n\nfunction SensitivePanel({ panel }: { panel: AdminDetailSensitivePanel }) {\n const isControlled = panel.revealed !== undefined\n const [internalRevealed, setInternalRevealed] = React.useState(\n panel.defaultRevealed ?? false\n )\n const revealed = isControlled\n ? (panel.revealed as boolean)\n : internalRevealed\n const switchId = React.useId()\n const contentId = React.useId()\n\n const handleChange = React.useCallback(\n (next: boolean) => {\n if (!isControlled) setInternalRevealed(next)\n panel.onRevealedChange?.(next)\n },\n [isControlled, panel]\n )\n\n const hasItems = panel.items != null && panel.items.length > 0\n const revealLabel = panel.revealLabel ?? \"Reveal\"\n const hiddenNote = panel.hiddenNote ?? \"Hidden for privacy.\"\n\n return (\n <section\n id={panel.id}\n className={styles.sensitive}\n data-slot=\"admin-detail-sensitive\"\n data-revealed={revealed ? \"\" : undefined}\n >\n <div className={styles.sensitiveHeader}>\n <div className={styles.sectionHeaderText}>\n {panel.eyebrow ? (\n <p className={styles.sectionEyebrow}>{panel.eyebrow}</p>\n ) : null}\n {panel.title ? (\n <h2 className={styles.sectionTitle}>{panel.title}</h2>\n ) : null}\n {panel.description ? (\n <p className={styles.sectionDescription}>{panel.description}</p>\n ) : null}\n </div>\n <div\n className={styles.revealControl}\n data-slot=\"admin-detail-reveal\"\n >\n <label htmlFor={switchId} className={styles.revealLabel}>\n {revealLabel}\n </label>\n <Switch\n id={switchId}\n checked={revealed}\n onCheckedChange={handleChange}\n aria-controls={contentId}\n />\n </div>\n </div>\n\n <div\n id={contentId}\n className={styles.sensitiveBody}\n data-slot=\"admin-detail-sensitive-body\"\n >\n {revealed ? (\n <>\n {hasItems ? (\n <KeyValueList\n items={panel.items!}\n columns={panel.columns ?? 2}\n orientation={panel.orientation ?? \"stacked\"}\n density={panel.density ?? \"editorial\"}\n />\n ) : null}\n {panel.content}\n </>\n ) : (\n <p className={styles.sensitiveNote}>{hiddenNote}</p>\n )}\n </div>\n </section>\n )\n}\n\n// ─── AdminDetail ─────────────────────────────────────────────────────────────\n\nconst AdminDetail = React.forwardRef<HTMLDivElement, AdminDetailProps>(\n function AdminDetail(\n {\n eyebrow,\n title,\n subtitle,\n media,\n status,\n breadcrumb,\n actions,\n header,\n hideHeaderDivider = false,\n sections,\n sensitive,\n children,\n maxWidth,\n className,\n style,\n ...rest\n },\n ref\n ) {\n const resolvedStatus = isStatusBadgeStatus(status) ? (\n <StatusBadge status={status} />\n ) : (\n status\n )\n\n const rootStyle = {\n ...style,\n [\"--admin-detail-max-width\" as string]: resolveSize(maxWidth, \"none\"),\n } as React.CSSProperties\n\n // Collect body regions so dividers interleave cleanly.\n const regions: React.ReactNode[] = []\n sections?.forEach((section, i) => {\n regions.push(\n <RecordSection key={section.id ?? `section-${i}`} section={section} />\n )\n })\n if (sensitive) {\n regions.push(\n <SensitivePanel key={sensitive.id ?? \"sensitive\"} panel={sensitive} />\n )\n }\n if (children != null) {\n regions.push(\n <div\n key=\"extra\"\n className={styles.section}\n data-slot=\"admin-detail-extra\"\n >\n {children}\n </div>\n )\n }\n\n return (\n <div\n ref={ref}\n className={cn(styles.root, className)}\n style={rootStyle}\n data-slot=\"admin-detail\"\n {...rest}\n >\n {header ?? (\n <header\n className={cn(\n styles.identity,\n !hideHeaderDivider && styles.identityDivided\n )}\n data-slot=\"admin-detail-header\"\n >\n {breadcrumb ? (\n <div\n className={styles.breadcrumb}\n data-slot=\"admin-detail-breadcrumb\"\n >\n {breadcrumb}\n </div>\n ) : null}\n <div className={styles.identityRow}>\n {media ? (\n <div\n className={styles.media}\n data-slot=\"admin-detail-media\"\n >\n {media}\n </div>\n ) : null}\n <div className={styles.identityText}>\n {eyebrow ? (\n <p className={styles.eyebrow}>{eyebrow}</p>\n ) : null}\n <div className={styles.titleRow}>\n <h1\n className={styles.title}\n data-slot=\"admin-detail-title\"\n >\n {title}\n </h1>\n {resolvedStatus ? (\n <span\n className={styles.status}\n data-slot=\"admin-detail-status\"\n >\n {resolvedStatus}\n </span>\n ) : null}\n </div>\n {subtitle ? (\n <p className={styles.subtitle}>{subtitle}</p>\n ) : null}\n </div>\n {actions ? (\n <div\n className={styles.actions}\n data-slot=\"admin-detail-actions\"\n >\n {actions}\n </div>\n ) : null}\n </div>\n </header>\n )}\n\n {regions.length > 0 ? (\n <div className={styles.body} data-slot=\"admin-detail-body\">\n {regions.map((node, i) => (\n <React.Fragment key={i}>\n {i > 0 ? (\n <Separator className={styles.divider} decorative />\n ) : null}\n {node}\n </React.Fragment>\n ))}\n </div>\n ) : null}\n </div>\n )\n }\n)\n\nAdminDetail.displayName = \"AdminDetail\"\n\nexport { AdminDetail }\n"
3959
+ },
3960
+ {
3961
+ "path": "blocks/admin-detail/admin-detail.module.css",
3962
+ "type": "registry:block",
3963
+ "content": "/* Admin Detail\n * Full-page, read-oriented detail RECORD for the admin-shell main column.\n * Vertical stack: identity header → N key-value sections (composed\n * KeyValueList) → optional sensitive/reveal panel → optional sub-list slots,\n * separated by hairline dividers. The natural sibling to admin-list-page and\n * admin-detail-drawer — a page, never a drawer.\n */\n\n.root {\n container-type: inline-size;\n display: flex;\n flex-direction: column;\n width: 100%;\n max-width: var(--admin-detail-max-width, none);\n gap: var(--spacing-8, 2rem);\n color: var(--text-primary, #111827);\n}\n\n/* ── Identity header ──────────────────────────────────────────────── */\n.identity {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-4, 1rem);\n}\n\n.identityDivided {\n padding-bottom: var(--spacing-8, 2rem);\n border-bottom: var(--stroke-width-thin, 1px) solid\n var(--border-muted, #e5e7eb);\n}\n\n.breadcrumb {\n min-width: 0;\n}\n\n.identityRow {\n display: flex;\n flex-direction: row;\n align-items: flex-start;\n gap: var(--spacing-4, 1rem);\n}\n\n.media {\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n}\n\n.identityText {\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: var(--spacing-2, 0.5rem);\n}\n\n.eyebrow {\n margin: 0;\n font-size: var(--font-size-xs, 0.6875rem);\n font-weight: var(--font-weight-medium, 500);\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-tertiary, #6b7280);\n line-height: 1.4;\n}\n\n.titleRow {\n display: flex;\n flex-direction: row;\n align-items: center;\n flex-wrap: wrap;\n gap: var(--spacing-3, 0.75rem);\n min-width: 0;\n}\n\n.title {\n margin: 0;\n font-size: var(--font-size-2xl, 1.5rem);\n font-weight: var(--font-weight-semibold, 600);\n line-height: 1.2;\n color: var(--text-primary, #111827);\n}\n\n.status {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n}\n\n.subtitle {\n margin: 0;\n font-size: var(--font-size-sm, 0.875rem);\n color: var(--text-secondary, #6b7280);\n line-height: 1.5;\n}\n\n.actions {\n flex: 0 0 auto;\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n}\n\n/* ── Body / sections ──────────────────────────────────────────────── */\n.body {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-8, 2rem);\n min-width: 0;\n}\n\n.section {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-5, 1.25rem);\n min-width: 0;\n}\n\n.sectionHeader {\n display: flex;\n flex-direction: row;\n align-items: flex-start;\n justify-content: space-between;\n gap: var(--spacing-4, 1rem);\n min-width: 0;\n}\n\n.sectionHeaderText {\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: var(--spacing-1, 0.25rem);\n}\n\n.sectionEyebrow {\n margin: 0;\n font-size: var(--font-size-xs, 0.6875rem);\n font-weight: var(--font-weight-medium, 500);\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-tertiary, #6b7280);\n line-height: 1.4;\n}\n\n.sectionTitle {\n margin: 0;\n font-size: var(--font-size-lg, 1.125rem);\n font-weight: var(--font-weight-semibold, 600);\n line-height: 1.3;\n color: var(--text-primary, #111827);\n}\n\n.sectionDescription {\n margin: 0;\n font-size: var(--font-size-sm, 0.875rem);\n color: var(--text-secondary, #6b7280);\n line-height: 1.5;\n}\n\n.sectionActions {\n flex: 0 0 auto;\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n}\n\n/* Divider between body regions. */\n.divider {\n margin: 0;\n}\n\n/* ── Sensitive / reveal panel ─────────────────────────────────────── */\n.sensitive {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-4, 1rem);\n padding: var(--spacing-5, 1.25rem);\n border: var(--stroke-width-thin, 1px) solid var(--border-muted, #e5e7eb);\n border-radius: var(--radius-lg, 0.75rem);\n background-color: var(--surface-subtle, #f9fafb);\n}\n\n.sensitiveHeader {\n display: flex;\n flex-direction: row;\n align-items: flex-start;\n justify-content: space-between;\n gap: var(--spacing-4, 1rem);\n min-width: 0;\n}\n\n.revealControl {\n flex: 0 0 auto;\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n}\n\n.revealLabel {\n font-size: var(--font-size-sm, 0.875rem);\n font-weight: var(--font-weight-medium, 500);\n color: var(--text-secondary, #6b7280);\n cursor: pointer;\n user-select: none;\n}\n\n.sensitiveBody {\n min-width: 0;\n}\n\n.sensitiveNote {\n margin: 0;\n font-size: var(--font-size-sm, 0.875rem);\n font-style: italic;\n color: var(--text-tertiary, #6b7280);\n}\n\n/* ── Responsive ───────────────────────────────────────────────────── */\n@container (max-width: 640px) {\n .identityRow {\n flex-direction: column;\n }\n\n .actions {\n width: 100%;\n }\n\n .sectionHeader,\n .sensitiveHeader {\n flex-direction: column;\n }\n}\n"
3964
+ }
3965
+ ]
3966
+ },
3937
3967
  {
3938
3968
  "name": "admin-detail-drawer",
3939
3969
  "type": "registry:block",
@@ -4103,6 +4133,31 @@
4103
4133
  }
4104
4134
  ]
4105
4135
  },
4136
+ {
4137
+ "name": "month-calendar",
4138
+ "type": "registry:block",
4139
+ "description": "Theme-portable month event-grid (scheduler) block: a 6×7 day-cell grid under a localized weekday header, with prev/next month navigation, a view-mode segmented control (Month / Week / Day), and per-cell event chips. Each chip carries a status-dot slot (success / warning / danger / info) and an optional series tint (1–5) keyed to the theme's chart ramp. Days outside the displayed month are dimmed; optional today and selected-day highlighting. Fully token-driven and theme-agnostic.",
4140
+ "category": "data-display",
4141
+ "dependencies": [
4142
+ "@loworbitstudio/visor-core",
4143
+ "@phosphor-icons/react"
4144
+ ],
4145
+ "registryDependencies": [
4146
+ "utils"
4147
+ ],
4148
+ "files": [
4149
+ {
4150
+ "path": "blocks/month-calendar/month-calendar.tsx",
4151
+ "type": "registry:block",
4152
+ "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { CaretLeft, CaretRight } from \"@phosphor-icons/react\"\n\nimport { cn } from \"../../lib/utils\"\nimport styles from \"./month-calendar.module.css\"\n\n/**\n * Status tone driving the leading dot on an event chip. Each tone binds to a\n * Visor semantic status token so the dot adopts the active theme's palette.\n */\nexport type MonthCalendarStatus =\n | \"default\"\n | \"success\"\n | \"warning\"\n | \"danger\"\n | \"info\"\n\n/** Series tint index — keyed to the theme's five-stop chart ramp. */\nexport type MonthCalendarSeries = 1 | 2 | 3 | 4 | 5\n\nexport interface MonthCalendarEvent {\n /** Stable key for the event. */\n id: string\n /**\n * The day this event lands in. Only the calendar date (year/month/day) is\n * used for placement — any time component is ignored.\n */\n date: Date\n /** Chip label. */\n title: string\n /**\n * Status tone for the leading dot. Omit for a neutral dot. Binds to the\n * `--surface-{success,warning,error,info}-default` semantic tokens.\n */\n status?: MonthCalendarStatus\n /**\n * Series tint (1–5). Events sharing an index render with the same background\n * tint and accent bar, keyed to the theme's chart color ramp — the standard\n * way to color-code a recurring series or a resource lane.\n */\n series?: MonthCalendarSeries\n}\n\nexport interface MonthCalendarViewOption {\n /** Machine value emitted via `onViewChange`. */\n value: string\n /** Human label rendered in the segment. */\n label: React.ReactNode\n}\n\nexport interface MonthCalendarProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n /** Any date within the month to display. Controlled. */\n month: Date\n /**\n * Fired with the first day of the previous / next month when the month-nav\n * arrows are used.\n */\n onMonthChange?: (month: Date) => void\n /** Events to place into day cells. */\n events?: MonthCalendarEvent[]\n /**\n * The day the grid marks as \"today\". Omit to mark none — the block never\n * reads the system clock itself, keeping server and client render identical\n * (no hydration mismatch). Pass `new Date()` from a client boundary to opt in.\n */\n today?: Date\n /** Selected day, highlighted distinctly from today. */\n selectedDate?: Date\n /** Fired when a day cell is activated (only when provided — cells are inert otherwise). */\n onSelectDate?: (date: Date) => void\n /** Fired when an event chip is activated (only when provided — chips are inert otherwise). */\n onEventSelect?: (event: MonthCalendarEvent) => void\n /** First column of the week: 0 = Sunday (default), 1 = Monday. */\n weekStartsOn?: 0 | 1\n /** Max chips shown per day before collapsing to a \"+N more\" row. Default 3. */\n maxChipsPerDay?: number\n /** BCP-47 locale for the month title and weekday headers. Default `\"en-US\"`. */\n locale?: string\n /** View-mode options for the segmented control. Default Month / Week / Day. */\n viewOptions?: MonthCalendarViewOption[]\n /** Active view value (controlled). Falls back to internal state when omitted. */\n view?: string\n /** Fired when a view-mode segment is chosen. */\n onViewChange?: (view: string) => void\n}\n\nconst DEFAULT_VIEW_OPTIONS: MonthCalendarViewOption[] = [\n { value: \"month\", label: \"Month\" },\n { value: \"week\", label: \"Week\" },\n { value: \"day\", label: \"Day\" },\n]\n\nconst DAYS_IN_GRID = 42\n// 2023-01-01 was a Sunday — a fixed anchor for deriving localized weekday names.\nconst WEEKDAY_ANCHOR_YEAR = 2023\n\nfunction firstOfMonth(date: Date): Date {\n return new Date(date.getFullYear(), date.getMonth(), 1)\n}\n\nfunction addMonths(date: Date, delta: number): Date {\n return new Date(date.getFullYear(), date.getMonth() + delta, 1)\n}\n\nfunction isSameDay(a: Date | undefined, b: Date): boolean {\n return (\n a !== undefined &&\n a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n )\n}\n\nfunction dayKey(date: Date): string {\n return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`\n}\n\nconst MonthCalendar = React.forwardRef<HTMLDivElement, MonthCalendarProps>(\n function MonthCalendar(\n {\n month,\n onMonthChange,\n events = [],\n today,\n selectedDate,\n onSelectDate,\n onEventSelect,\n weekStartsOn = 0,\n maxChipsPerDay = 3,\n locale = \"en-US\",\n viewOptions = DEFAULT_VIEW_OPTIONS,\n view,\n onViewChange,\n className,\n ...rest\n },\n ref\n ) {\n const [internalView, setInternalView] = React.useState<string>(\n () => view ?? viewOptions[0]?.value ?? \"month\"\n )\n const activeView = view ?? internalView\n\n const handleViewSelect = React.useCallback(\n (next: string) => {\n if (view === undefined) setInternalView(next)\n onViewChange?.(next)\n },\n [view, onViewChange]\n )\n\n const monthLabel = React.useMemo(\n () =>\n new Intl.DateTimeFormat(locale, {\n month: \"long\",\n year: \"numeric\",\n }).format(month),\n [locale, month]\n )\n\n const weekdayLabels = React.useMemo(() => {\n const formatter = new Intl.DateTimeFormat(locale, { weekday: \"short\" })\n return Array.from({ length: 7 }, (_, i) =>\n formatter.format(\n new Date(WEEKDAY_ANCHOR_YEAR, 0, 1 + ((weekStartsOn + i) % 7))\n )\n )\n }, [locale, weekStartsOn])\n\n const cells = React.useMemo(() => {\n const first = firstOfMonth(month)\n const offset = (first.getDay() - weekStartsOn + 7) % 7\n return Array.from({ length: DAYS_IN_GRID }, (_, i) =>\n new Date(first.getFullYear(), first.getMonth(), 1 - offset + i)\n )\n }, [month, weekStartsOn])\n\n const eventsByDay = React.useMemo(() => {\n const map = new Map<string, MonthCalendarEvent[]>()\n for (const event of events) {\n const key = dayKey(event.date)\n const bucket = map.get(key)\n if (bucket) bucket.push(event)\n else map.set(key, [event])\n }\n return map\n }, [events])\n\n const dayLabelFormatter = React.useMemo(\n () =>\n new Intl.DateTimeFormat(locale, {\n month: \"long\",\n day: \"numeric\",\n year: \"numeric\",\n }),\n [locale]\n )\n\n const displayedMonth = month.getMonth()\n\n return (\n <div\n ref={ref}\n className={cn(styles.root, className)}\n data-slot=\"month-calendar\"\n {...rest}\n >\n <div className={styles.header} data-slot=\"month-calendar-header\">\n <div className={styles.nav} data-slot=\"month-calendar-nav\">\n <button\n type=\"button\"\n className={styles.navButton}\n onClick={() => onMonthChange?.(addMonths(month, -1))}\n disabled={!onMonthChange}\n aria-label=\"Previous month\"\n data-slot=\"month-calendar-prev\"\n >\n <CaretLeft weight=\"bold\" aria-hidden />\n </button>\n <h2 className={styles.monthLabel} data-slot=\"month-calendar-label\">\n {monthLabel}\n </h2>\n <button\n type=\"button\"\n className={styles.navButton}\n onClick={() => onMonthChange?.(addMonths(month, 1))}\n disabled={!onMonthChange}\n aria-label=\"Next month\"\n data-slot=\"month-calendar-next\"\n >\n <CaretRight weight=\"bold\" aria-hidden />\n </button>\n </div>\n\n {viewOptions.length > 0 ? (\n <div\n className={styles.segmented}\n role=\"group\"\n aria-label=\"Calendar view\"\n data-slot=\"month-calendar-view\"\n >\n {viewOptions.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n className={styles.segment}\n data-active={option.value === activeView ? \"true\" : undefined}\n aria-pressed={option.value === activeView}\n onClick={() => handleViewSelect(option.value)}\n >\n {option.label}\n </button>\n ))}\n </div>\n ) : null}\n </div>\n\n <div className={styles.weekdays} data-slot=\"month-calendar-weekdays\">\n {weekdayLabels.map((label, i) => (\n <div key={i} className={styles.weekday} aria-hidden=\"true\">\n {label}\n </div>\n ))}\n </div>\n\n <div className={styles.grid} data-slot=\"month-calendar-grid\">\n {cells.map((cell) => {\n const dayEvents = eventsByDay.get(dayKey(cell)) ?? []\n const visible = dayEvents.slice(0, maxChipsPerDay)\n const overflow = dayEvents.length - visible.length\n const outside = cell.getMonth() !== displayedMonth\n const isToday = isSameDay(today, cell)\n const isSelected = isSameDay(selectedDate, cell)\n const dayLabel = dayLabelFormatter.format(cell)\n const cellLabel =\n dayEvents.length > 0\n ? `${dayLabel}, ${dayEvents.length} event${dayEvents.length === 1 ? \"\" : \"s\"}`\n : dayLabel\n\n const DayTag: React.ElementType = onSelectDate ? \"button\" : \"div\"\n\n return (\n <div\n key={dayKey(cell)}\n className={styles.cell}\n data-slot=\"month-calendar-day\"\n data-outside={outside ? \"true\" : undefined}\n data-today={isToday ? \"true\" : undefined}\n data-selected={isSelected ? \"true\" : undefined}\n >\n <DayTag\n className={styles.dayHead}\n {...(onSelectDate\n ? {\n type: \"button\" as const,\n onClick: () => onSelectDate(cell),\n \"aria-label\": cellLabel,\n \"aria-pressed\": isSelected,\n }\n : {})}\n data-slot=\"month-calendar-day-head\"\n >\n <span className={styles.dayNumber}>{cell.getDate()}</span>\n </DayTag>\n\n {dayEvents.length > 0 ? (\n <div\n className={styles.events}\n data-slot=\"month-calendar-day-events\"\n >\n {visible.map((event) => {\n const ChipTag: React.ElementType = onEventSelect\n ? \"button\"\n : \"div\"\n return (\n <ChipTag\n key={event.id}\n className={styles.chip}\n data-status={event.status ?? \"default\"}\n data-series={event.series}\n data-slot=\"month-calendar-event\"\n {...(onEventSelect\n ? {\n type: \"button\" as const,\n onClick: () => onEventSelect(event),\n }\n : {})}\n >\n <span\n className={styles.dot}\n data-status={event.status ?? \"default\"}\n aria-hidden=\"true\"\n />\n <span className={styles.chipLabel}>{event.title}</span>\n </ChipTag>\n )\n })}\n {overflow > 0 ? (\n <div className={styles.overflow}>+{overflow} more</div>\n ) : null}\n </div>\n ) : null}\n </div>\n )\n })}\n </div>\n </div>\n )\n }\n)\n\nMonthCalendar.displayName = \"MonthCalendar\"\n\nexport { MonthCalendar }\n"
4153
+ },
4154
+ {
4155
+ "path": "blocks/month-calendar/month-calendar.module.css",
4156
+ "type": "registry:block",
4157
+ "content": "/* Month Calendar\n * A theme-portable month event-grid (scheduler) block: a 6×7 day-cell grid\n * under a weekday header, with month navigation, a view-mode segmented control,\n * and per-cell event chips carrying a status dot and an optional series tint.\n *\n * Every color, size, spacing, stroke, radius, and motion value binds to a Visor\n * token so the block adopts the active theme without modification. Selectors are\n * anchored on a local class to satisfy the docs bundler's pure-selector rule.\n */\n\n.root {\n display: flex;\n flex-direction: column;\n background: var(--surface-card);\n border: var(--stroke-width-thin) solid var(--border-muted);\n border-radius: var(--radius-lg);\n overflow: hidden;\n color: var(--text-primary);\n}\n\n/* ── Header: month nav + view segmented control ── */\n.header {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: var(--spacing-3, 0.75rem);\n padding: var(--spacing-4, 1rem);\n}\n\n.nav {\n display: flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n}\n\n.navButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n /* 32px — icon-button sizing; sizing tokens deferred (Rule 5 exception). */\n width: 2rem;\n height: 2rem;\n padding: 0;\n color: var(--text-secondary);\n background: transparent;\n border: var(--stroke-width-thin) solid transparent;\n border-radius: var(--radius-md);\n cursor: pointer;\n}\n\n.navButton:hover:not(:disabled) {\n background: var(--surface-interactive-hover);\n color: var(--text-primary);\n}\n\n.navButton:disabled {\n opacity: var(--opacity-40);\n cursor: default;\n}\n\n.navButton:focus-visible {\n outline: var(--focus-ring-width) solid var(--border-focus);\n outline-offset: var(--focus-ring-offset);\n}\n\n.monthLabel {\n margin: 0;\n min-width: 10ch;\n text-align: center;\n font-size: var(--font-size-lg, 1.125rem);\n font-weight: var(--font-weight-semibold, 600);\n color: var(--text-primary);\n}\n\n/* ── View-mode segmented control ── */\n.segmented {\n display: inline-flex;\n align-items: center;\n gap: var(--stroke-width-thin);\n padding: var(--spacing-1, 0.25rem);\n background: var(--surface-subtle);\n border: var(--stroke-width-thin) solid var(--border-muted);\n border-radius: var(--radius-md);\n}\n\n.segment {\n appearance: none;\n padding: var(--spacing-1, 0.25rem) var(--spacing-3, 0.75rem);\n font-size: var(--font-size-sm, 0.875rem);\n font-weight: var(--font-weight-medium, 500);\n color: var(--text-secondary);\n background: transparent;\n border: none;\n border-radius: var(--radius-sm);\n cursor: pointer;\n transition: color var(--motion-duration-fast, 100ms)\n var(--motion-easing-default, ease-in-out),\n background-color var(--motion-duration-fast, 100ms)\n var(--motion-easing-default, ease-in-out);\n}\n\n.segment:hover:not([data-active]) {\n color: var(--text-primary);\n}\n\n.segment[data-active] {\n color: var(--text-primary);\n background: var(--surface-card);\n box-shadow: var(--shadow-xs);\n}\n\n.segment:focus-visible {\n outline: var(--focus-ring-width) solid var(--border-focus);\n outline-offset: var(--focus-ring-offset);\n}\n\n/* ── Weekday header row ── */\n.weekdays {\n display: grid;\n grid-template-columns: repeat(7, minmax(0, 1fr));\n border-top: var(--stroke-width-thin) solid var(--border-muted);\n background: var(--surface-subtle);\n}\n\n.weekday {\n padding: var(--spacing-2, 0.5rem);\n text-align: center;\n font-size: var(--font-size-xs, 0.75rem);\n font-weight: var(--font-weight-medium, 500);\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--text-tertiary);\n}\n\n/* ── 6×7 day grid ── */\n/* The hairline background shows through the 1px gaps to draw the grid lines. */\n.grid {\n display: grid;\n grid-template-columns: repeat(7, minmax(0, 1fr));\n grid-auto-rows: minmax(0, 1fr);\n gap: var(--stroke-width-thin);\n background: var(--border-muted);\n border-top: var(--stroke-width-thin) solid var(--border-muted);\n}\n\n.cell {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-1, 0.25rem);\n /* 104px — day-cell min height; sizing tokens deferred (Rule 5 exception). */\n min-height: 6.5rem;\n padding: var(--spacing-1, 0.25rem);\n background: var(--surface-card);\n}\n\n.cell[data-outside] {\n background: var(--surface-subtle);\n}\n\n.cell[data-outside] .dayNumber {\n color: var(--text-disabled);\n}\n\n.cell[data-selected] {\n background: var(--interactive-primary-soft);\n}\n\n/* ── Day number head (button when the day is selectable) ── */\n.dayHead {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n align-self: flex-start;\n /* 24px — day-number badge sizing; sizing tokens deferred (Rule 5 exception). */\n min-width: 1.5rem;\n height: 1.5rem;\n padding: 0 var(--spacing-1, 0.25rem);\n font: inherit;\n color: inherit;\n background: transparent;\n border: none;\n border-radius: var(--radius-full);\n cursor: default;\n}\n\n.dayHead:is(button) {\n cursor: pointer;\n}\n\n.dayHead:is(button):hover {\n background: var(--surface-interactive-hover);\n}\n\n.dayHead:focus-visible {\n outline: var(--focus-ring-width) solid var(--border-focus);\n outline-offset: var(--focus-ring-offset);\n}\n\n.dayNumber {\n font-size: var(--font-size-sm, 0.875rem);\n font-weight: var(--font-weight-medium, 500);\n color: var(--text-secondary);\n font-variant-numeric: tabular-nums;\n}\n\n.cell[data-today] .dayHead {\n background: var(--interactive-primary-bg);\n}\n\n.cell[data-today] .dayNumber {\n color: var(--interactive-primary-text);\n font-weight: var(--font-weight-semibold, 600);\n}\n\n/* ── Event chips ── */\n.events {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-1, 0.25rem);\n min-width: 0;\n}\n\n.chip {\n display: flex;\n align-items: center;\n gap: var(--spacing-1, 0.25rem);\n width: 100%;\n padding: var(--spacing-1, 0.25rem) var(--spacing-2, 0.5rem);\n font-size: var(--font-size-xs, 0.75rem);\n font-weight: var(--font-weight-medium, 500);\n text-align: start;\n color: var(--text-primary);\n background: var(--surface-subtle);\n border: none;\n border-radius: var(--radius-sm);\n cursor: default;\n}\n\n.chip:is(button) {\n cursor: pointer;\n}\n\n.chip:is(button):hover {\n background: var(--surface-interactive-hover);\n}\n\n.chip:focus-visible {\n outline: var(--focus-ring-width) solid var(--border-focus);\n outline-offset: var(--focus-ring-offset);\n}\n\n/* Series tint — background wash + leading accent bar keyed to the chart ramp. */\n.chip[data-series=\"1\"] {\n --mc-series: var(--chart-1);\n}\n.chip[data-series=\"2\"] {\n --mc-series: var(--chart-2);\n}\n.chip[data-series=\"3\"] {\n --mc-series: var(--chart-3);\n}\n.chip[data-series=\"4\"] {\n --mc-series: var(--chart-4);\n}\n.chip[data-series=\"5\"] {\n --mc-series: var(--chart-5);\n}\n\n.chip[data-series] {\n background: color-mix(in srgb, var(--mc-series) 16%, transparent);\n box-shadow: inset var(--stroke-width-medium) 0 0 0 var(--mc-series);\n}\n\n.chip[data-series]:is(button):hover {\n background: color-mix(in srgb, var(--mc-series) 28%, transparent);\n}\n\n.chipLabel {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n/* ── Status dot ── */\n.dot {\n flex-shrink: 0;\n /* 8px dot — bound to the spacing scale. */\n width: var(--spacing-2, 0.5rem);\n height: var(--spacing-2, 0.5rem);\n border-radius: var(--radius-full);\n background: var(--text-tertiary);\n}\n\n.dot[data-status=\"success\"] {\n background: var(--surface-success-default);\n}\n.dot[data-status=\"warning\"] {\n background: var(--surface-warning-default);\n}\n.dot[data-status=\"danger\"] {\n background: var(--surface-error-default);\n}\n.dot[data-status=\"info\"] {\n background: var(--surface-info-default);\n}\n\n/* ── Overflow row ── */\n.overflow {\n padding: 0 var(--spacing-2, 0.5rem);\n font-size: var(--font-size-xs, 0.75rem);\n font-weight: var(--font-weight-medium, 500);\n color: var(--text-tertiary);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .segment {\n transition: none;\n }\n}\n"
4158
+ }
4159
+ ]
4160
+ },
4106
4161
  {
4107
4162
  "name": "workspace-switcher",
4108
4163
  "type": "registry:block",
@@ -4982,7 +5037,7 @@
4982
5037
  {
4983
5038
  "path": "components/devtools/source-inspector/visor-component-names.generated.ts",
4984
5039
  "type": "registry:devtool",
4985
- "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 \"ActivityFeed\",\n \"ActivityFeedContext\",\n \"ActivityFeedItem\",\n \"ActivityFeedRoot\",\n \"AdminDashboard\",\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 \"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 \"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 \"DialogClose\",\n \"DialogContent\",\n \"DialogDescription\",\n \"DialogFooter\",\n \"DialogHeader\",\n \"DialogOverlay\",\n \"DialogPortal\",\n \"DialogTitle\",\n \"DialogTrigger\",\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 \"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 \"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 \"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 \"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 \"RadiusScale\",\n \"RadiusSection\",\n \"RadiusSlide\",\n \"RightRailList\",\n \"ScoreIndicator\",\n \"ScrollArea\",\n \"ScrollBar\",\n \"SearchInput\",\n \"SectionHeader\",\n \"SectionIntro\",\n \"SectionNav\",\n \"SectionNavItem\",\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 \"Separator\",\n \"SessionTimeout\",\n \"ShadowSection\",\n \"Sheet\",\n \"SheetClose\",\n \"SheetContent\",\n \"SheetDescription\",\n \"SheetFooter\",\n \"SheetHeader\",\n \"SheetOverlay\",\n \"SheetPortal\",\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 \"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 \"Timeline\",\n \"TimelineContent\",\n \"TimelineDescription\",\n \"TimelineIcon\",\n \"TimelineItem\",\n \"TimelineTimestamp\",\n \"TimelineTitle\",\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"
5040
+ "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 \"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 \"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 \"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 \"DialogClose\",\n \"DialogContent\",\n \"DialogDescription\",\n \"DialogFooter\",\n \"DialogHeader\",\n \"DialogOverlay\",\n \"DialogPortal\",\n \"DialogTitle\",\n \"DialogTrigger\",\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 \"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 \"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 \"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 \"RadiusScale\",\n \"RadiusSection\",\n \"RadiusSlide\",\n \"RecordSection\",\n \"RightRailList\",\n \"ScoreIndicator\",\n \"ScrollArea\",\n \"ScrollBar\",\n \"SearchInput\",\n \"SectionHeader\",\n \"SectionIntro\",\n \"SectionNav\",\n \"SectionNavItem\",\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 \"SheetDescription\",\n \"SheetFooter\",\n \"SheetHeader\",\n \"SheetOverlay\",\n \"SheetPortal\",\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 \"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 \"Timeline\",\n \"TimelineContent\",\n \"TimelineDescription\",\n \"TimelineIcon\",\n \"TimelineItem\",\n \"TimelineTimestamp\",\n \"TimelineTitle\",\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"
4986
5041
  }
4987
5042
  ]
4988
5043
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "0.4.0",
3
- "generated_at": "2026-07-07T17:14:24.746Z",
3
+ "generated_at": "2026-07-08T04:58:15.594Z",
4
4
  "components": {
5
5
  "accessibility-specimen": {
6
6
  "category": "specimen",
@@ -10816,6 +10816,28 @@
10816
10816
  "Settings forms (use admin-settings-page)"
10817
10817
  ]
10818
10818
  },
10819
+ "admin-detail": {
10820
+ "category": "admin",
10821
+ "description": "Full-page, read-oriented detail RECORD for the admin-shell main column. Composes an identity header (media + title + StatusBadge + actions), N key-value sections built on KeyValueList, an optional sensitive/reveal panel gated behind a Switch, and optional sub-list slots for ledgers or history — separated by hairline dividers. The full-page sibling to admin-detail-drawer (a drawer) and admin-list-page.",
10822
+ "components_used": [
10823
+ "key-value-list",
10824
+ "status-badge",
10825
+ "switch",
10826
+ "separator"
10827
+ ],
10828
+ "when_to_use": [
10829
+ "Read-oriented single-record detail pages rendered in the admin-shell main column",
10830
+ "Records with an identity header plus grouped key-value facts (profile, account, metadata)",
10831
+ "Detail pages that expose sensitive data (tax IDs, banking, W-9) behind a reveal control",
10832
+ "Records with sub-ledgers or history tables beneath the key-value sections"
10833
+ ],
10834
+ "when_not_to_use": [
10835
+ "Inline editing of a record from a list without leaving the page (use admin-detail-drawer)",
10836
+ "Full-page multi-section editors with save/cancel (use admin-settings-page)",
10837
+ "Multi-step create/edit flows (use admin-wizard)",
10838
+ "Tabular lists of many records (use admin-list-page)"
10839
+ ]
10840
+ },
10819
10841
  "admin-detail-drawer": {
10820
10842
  "category": "admin",
10821
10843
  "description": "Right-side slide-out panel for viewing or editing a single record. Composes Sheet with a sticky save/cancel footer, async save handler with pending state, an unsaved-changes guard powered by ConfirmDialog, sm/md/lg/xl width variants, and optional header customization via hideHeader and customHeader props.",
@@ -11171,6 +11193,21 @@
11171
11193
  "Registration forms (extend with additional fields)"
11172
11194
  ]
11173
11195
  },
11196
+ "month-calendar": {
11197
+ "category": "data-display",
11198
+ "description": "A theme-portable month event-grid (scheduler) block. Renders a 6×7 day-cell grid under a localized weekday header, with prev/next month navigation, a view-mode segmented control (Month / Week / Day), and per-cell event chips. Each chip carries a status-dot slot (success / warning / danger / info) and an optional series tint (1–5) keyed to the theme's chart ramp for color-coding a recurring series or resource lane. Days outside the displayed month are dimmed; optional today and selected-day highlighting. Fully token-driven — every color, spacing, stroke, radius, shadow, opacity, and motion value binds to a Visor token, so the grid adopts the active theme without modification.",
11199
+ "components_used": [],
11200
+ "when_to_use": [
11201
+ "Admin or booking surfaces that show events-in-a-month (the most common scheduling view)",
11202
+ "Color-coding recurring series or resource lanes via the per-chip series tint",
11203
+ "Status-at-a-glance calendars where each event needs a success / warning / danger / info dot"
11204
+ ],
11205
+ "when_not_to_use": [
11206
+ "Picking a single date or date range (use calendar, date-picker, or date-range-picker)",
11207
+ "Time-slot / day-timeline scheduling with hour rows (use a dedicated day/week scheduler)",
11208
+ "Dense agenda lists with no month context (use a table or activity-feed)"
11209
+ ]
11210
+ },
11174
11211
  "pricing-section": {
11175
11212
  "category": "marketing",
11176
11213
  "description": "A responsive pricing tier grid with feature lists, highlighted plan, and per-tier CTAs.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loworbitstudio/visor",
3
- "version": "1.16.1",
3
+ "version": "1.17.0",
4
4
  "description": "CLI for the Visor design system — add components, hooks, and utilities to your project.",
5
5
  "type": "module",
6
6
  "bin": {