@zendir/ui 0.6.0 → 0.6.1

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.
Files changed (116) hide show
  1. package/CHANGELOG.md +419 -406
  2. package/dist/react/3d/CesiumCaptureSource.js.map +1 -1
  3. package/dist/react/3d/ZenSpace3D.js.map +1 -1
  4. package/dist/react/3d/ZenSpace3DCesium.js.map +1 -1
  5. package/dist/react/3d/ZenSpace3DShaders.js.map +1 -1
  6. package/dist/react/3d/ZenSpace3DUtils.js.map +1 -1
  7. package/dist/react/3d/capturePngAnalysis.js.map +1 -1
  8. package/dist/react/3d/threeLoader.js.map +1 -1
  9. package/dist/react/astro/ClassificationBanner.js.map +1 -1
  10. package/dist/react/astro/GlobalStatusBar.js.map +1 -1
  11. package/dist/react/astro/MissionClock.js.map +1 -1
  12. package/dist/react/astro/MonitoringIcon.js.map +1 -1
  13. package/dist/react/astro/Notification.js.map +1 -1
  14. package/dist/react/astro/Progress.js.map +1 -1
  15. package/dist/react/astro/SimulationControls.js.map +1 -1
  16. package/dist/react/astro/StatusIndicator.js.map +1 -1
  17. package/dist/react/astro/UnifiedTimeline.js.map +1 -1
  18. package/dist/react/cards/AccessCard.js.map +1 -1
  19. package/dist/react/cards/OrbitCard.js.map +1 -1
  20. package/dist/react/cards/SpacecraftCard.js.map +1 -1
  21. package/dist/react/cards/TelemetryCard.js.map +1 -1
  22. package/dist/react/cards/TelemetryStreamCard.js.map +1 -1
  23. package/dist/react/charts/GroundTrackMap.js.map +1 -1
  24. package/dist/react/charts/GroundTrackMapLeaflet.js.map +1 -1
  25. package/dist/react/charts/groundTrackMapLeafletTiles.js.map +1 -1
  26. package/dist/react/charts/groundTrackMapLeafletUtils.js.map +1 -1
  27. package/dist/react/charts/unified/AstroChart.js.map +1 -1
  28. package/dist/react/charts/unified/PowerOverviewChart.js.map +1 -1
  29. package/dist/react/charts/unified/domain.js.map +1 -1
  30. package/dist/react/charts/unified/generators.js.map +1 -1
  31. package/dist/react/charts/unified/presets.js.map +1 -1
  32. package/dist/react/charts/unified/sync.js.map +1 -1
  33. package/dist/react/charts/unified/theme.js.map +1 -1
  34. package/dist/react/charts/unified/useChartStream.js.map +1 -1
  35. package/dist/react/chatgpt/index.js.map +1 -1
  36. package/dist/react/context/CategoryContext.js.map +1 -1
  37. package/dist/react/context/DisplaySettingsContext.js.map +1 -1
  38. package/dist/react/context/SpatialSelectionContext.js.map +1 -1
  39. package/dist/react/core/data/DataTable.js.map +1 -1
  40. package/dist/react/core/data/DataValue.js.map +1 -1
  41. package/dist/react/core/data/propertyConfig.js.map +1 -1
  42. package/dist/react/core/display/AstroIcon.js.map +1 -1
  43. package/dist/react/core/display/Badge.js.map +1 -1
  44. package/dist/react/core/display/CardHeader.js.map +1 -1
  45. package/dist/react/core/display/Container.js.map +1 -1
  46. package/dist/react/core/display/CopyButton.js.map +1 -1
  47. package/dist/react/core/display/GlassCard.js.map +1 -1
  48. package/dist/react/core/display/HeaderIconWithStatus.js.map +1 -1
  49. package/dist/react/core/display/Icon.js.map +1 -1
  50. package/dist/react/core/display/Typography.js.map +1 -1
  51. package/dist/react/core/feedback/ConfirmDialog.js.map +1 -1
  52. package/dist/react/core/feedback/Dialog.js.map +1 -1
  53. package/dist/react/core/feedback/Toast.js.map +1 -1
  54. package/dist/react/core/inputs/Button.js.map +1 -1
  55. package/dist/react/core/inputs/Checkbox.js.map +1 -1
  56. package/dist/react/core/inputs/Input.js.map +1 -1
  57. package/dist/react/core/inputs/LimitsBar.js.map +1 -1
  58. package/dist/react/core/inputs/NumberInput.js.map +1 -1
  59. package/dist/react/core/inputs/PinInput.js.map +1 -1
  60. package/dist/react/core/inputs/Select.js.map +1 -1
  61. package/dist/react/core/inputs/Toggle.js.map +1 -1
  62. package/dist/react/core/layout/Box.js.map +1 -1
  63. package/dist/react/core/layout/Center.js.map +1 -1
  64. package/dist/react/core/layout/Divider.js.map +1 -1
  65. package/dist/react/core/layout/Flex.js.map +1 -1
  66. package/dist/react/core/layout/Grid.js.map +1 -1
  67. package/dist/react/core/layout/Spacer.js.map +1 -1
  68. package/dist/react/core/layout/Stack.js.map +1 -1
  69. package/dist/react/core/layout/responsive.js.map +1 -1
  70. package/dist/react/core/layout/useBreakpoint.js.map +1 -1
  71. package/dist/react/core/navigation/AppBar.js.map +1 -1
  72. package/dist/react/core/navigation/Pagination.js.map +1 -1
  73. package/dist/react/core/navigation/SideNav.js.map +1 -1
  74. package/dist/react/core/navigation/Tabs.js.map +1 -1
  75. package/dist/react/core/overlays/Popover.js.map +1 -1
  76. package/dist/react/core/overlays/SidePanel.js.map +1 -1
  77. package/dist/react/core/overlays/Tooltip.js.map +1 -1
  78. package/dist/react/core/widgets/ActivityPlanner.js.map +1 -1
  79. package/dist/react/core/widgets/Capture.js.map +1 -1
  80. package/dist/react/core/widgets/ChatPanel.js.map +1 -1
  81. package/dist/react/core/widgets/ColorPickerPanel.js.map +1 -1
  82. package/dist/react/core/widgets/CommandBuilder.js.map +1 -1
  83. package/dist/react/core/widgets/ConnectionForm.js.map +1 -1
  84. package/dist/react/core/widgets/FileExplorer.js.map +1 -1
  85. package/dist/react/core/widgets/HexViewer.js.map +1 -1
  86. package/dist/react/core/widgets/ImageFrame.js.map +1 -1
  87. package/dist/react/core/widgets/ImageGallery.js.map +1 -1
  88. package/dist/react/core/widgets/ImagePanel.js.map +1 -1
  89. package/dist/react/core/widgets/LogViewer.js.map +1 -1
  90. package/dist/react/core/widgets/MessageStream.js.map +1 -1
  91. package/dist/react/core/widgets/MissionCalendar.js.map +1 -1
  92. package/dist/react/core/widgets/PacketViewer.js.map +1 -1
  93. package/dist/react/hooks/useCompactMode.js.map +1 -1
  94. package/dist/react/hooks/useSimulationScene.js.map +1 -1
  95. package/dist/react/panels/LayerControlPanel.js.map +1 -1
  96. package/dist/react/panels/ObjectInventoryPanel.js.map +1 -1
  97. package/dist/react/shared/ErrorBoundary.js.map +1 -1
  98. package/dist/react/shared/Skeleton.js.map +1 -1
  99. package/dist/react/theme/ThemeProvider.js +2 -1
  100. package/dist/react/theme/ThemeProvider.js.map +1 -1
  101. package/dist/react/theme/cardAccent.js.map +1 -1
  102. package/dist/react/theme/config.js.map +1 -1
  103. package/dist/react/theme/types.js.map +1 -1
  104. package/dist/react/types.js.map +1 -1
  105. package/dist/react/utils/categoryPalette.js.map +1 -1
  106. package/dist/react/utils/index.js.map +1 -1
  107. package/dist/style.css +216 -216
  108. package/dist/tokens/core.js.map +1 -1
  109. package/dist/tokens/index.js.map +1 -1
  110. package/dist/tokens/palettes/astro.js.map +1 -1
  111. package/dist/tokens/palettes/index.js.map +1 -1
  112. package/dist/tokens/palettes/tailwind.js.map +1 -1
  113. package/dist/tokens/palettes/zendir.js.map +1 -1
  114. package/dist/tokens/themes.js.map +1 -1
  115. package/dist/tokens/tokens.css +533 -533
  116. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ObjectInventoryPanel.js","names":[],"sources":["../../../src/react/panels/ObjectInventoryPanel.tsx"],"sourcesContent":["/**\n * @zendir/ui - ObjectInventoryPanel\n *\n * Themed list of simulation objects, grouped by `kind` (spacecraft /\n * ground stations / celestial bodies). Pair with `useSimulationScene`:\n *\n * ```tsx\n * const scene = useSimulationScene({ client, containerId, simulationId });\n * <ObjectInventoryPanel\n * objects={scene.objects}\n * onSelect={(obj) => zenRef.current?.focusOn(obj.id, {\n * zoom: recommendedFlyDistance(obj.kind),\n * })}\n * />\n * ```\n *\n * The panel renders the toggle UI only — it has no opinion about WHAT\n * happens on select. The consumer wires `onSelect` to its 3D viewer's\n * imperative handle (or anywhere else — the panel is reusable in any\n * dashboard or debug context).\n */\n\nimport { memo, useCallback, useState } from \"react\";\nimport type { CSSProperties, ReactNode } from \"react\";\nimport { useThemeTokens } from \"../theme/ThemeProvider\";\nimport { useSpatialSelection } from \"../context/SpatialSelectionContext\";\nimport type {\n SimulationSceneObject,\n StructureDiag,\n PositionTickDiag,\n} from \"../hooks/useSimulationScene\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type ObjectInventoryKind = SimulationSceneObject[\"kind\"];\n\nexport interface ObjectInventoryPanelProps {\n /** Objects to list. Typically `useSimulationScene().objects`. */\n objects: SimulationSceneObject[];\n /** Click handler — fires with the clicked object. Pure forward; the\n * panel does not navigate, focus, or fly anywhere itself. */\n onSelect?: (object: SimulationSceneObject) => void;\n /** Currently selected object id (highlights the row). Pass `null` for none. */\n selectedId?: string | null;\n\n /** Where to anchor the panel on its parent. Default: `top-left`. */\n position?: \"top-left\" | \"top-right\" | \"bottom-left\" | \"bottom-right\";\n /** Pixel offsets from the anchor. Defaults to (16, 56) so a top bar fits above. */\n offset?: { x?: number; y?: number };\n /** Hide the panel without unmounting (preserves `selectedId` state). */\n hidden?: boolean;\n /** Width in pixels. Default 330. */\n width?: number;\n /** Maximum height (CSS value). Default `'50vh'`. */\n maxHeight?: string | number;\n\n /** Allow the user to collapse / expand the panel. Default true. */\n collapsible?: boolean;\n /** Initial collapsed state. Default false. */\n defaultCollapsed?: boolean;\n\n /** Optional title override. Default: `\"API Objects · N\"`. */\n title?: ReactNode;\n\n /**\n * Optional structure diagnostic block (raw keys + counts from\n * getSimulationStructure). Useful in dev / debug builds; omit in\n * production for a cleaner look.\n */\n structureDiag?: StructureDiag | null;\n /** Optional position-tick diagnostic block. */\n positionTickDiag?: PositionTickDiag | null;\n\n /** Custom labels per kind (defaults: \"Spacecraft\", \"Ground Stations\", \"Celestial Bodies\"). */\n kindLabels?: Partial<Record<ObjectInventoryKind, string>>;\n\n /** Inline style override on the outer container. */\n style?: CSSProperties;\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\n/** Stable short-form of a GUID for inline display. */\nfunction shortId(id: string): string {\n return id.length <= 8 ? id : id.slice(0, 8);\n}\n\nconst DEFAULT_KIND_LABELS: Record<ObjectInventoryKind, string> = {\n spacecraft: \"Spacecraft\",\n groundStation: \"Ground Stations\",\n celestialBody: \"Celestial Bodies\",\n};\n\nconst KIND_ORDER: ObjectInventoryKind[] = [\n \"spacecraft\",\n \"groundStation\",\n \"celestialBody\",\n];\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nexport const ObjectInventoryPanel = memo(function ObjectInventoryPanel({\n objects,\n onSelect,\n selectedId,\n position = \"top-left\",\n offset,\n hidden = false,\n width = 330,\n maxHeight = \"50vh\",\n collapsible = true,\n defaultCollapsed = false,\n title,\n structureDiag,\n positionTickDiag,\n kindLabels,\n style,\n}: ObjectInventoryPanelProps) {\n const tokens = useThemeTokens();\n const [collapsed, setCollapsed] = useState(defaultCollapsed);\n\n // Optional <SpatialSelectionProvider> integration. When present, the\n // panel uses the shared selection state so other components (3D\n // viewer, GroundTrackMap, etc.) stay synchronised. Explicit props\n // always win — consumers can still override per-instance.\n const ctx = useSpatialSelection();\n const effectiveSelectedId =\n selectedId !== undefined ? selectedId : (ctx?.selectedId ?? null);\n const effectiveOnSelect = useCallback(\n (obj: SimulationSceneObject) => {\n onSelect?.(obj);\n // Also push into the shared context if no explicit onSelect wins it.\n if (!onSelect) ctx?.setSelectedId(obj.id);\n },\n [onSelect, ctx],\n );\n\n const labels = { ...DEFAULT_KIND_LABELS, ...kindLabels };\n const entries = Array.isArray(objects) ? objects : [];\n\n // Position anchor — translate the prop into top/left/right/bottom CSS.\n const offX = offset?.x ?? 16;\n const offY = offset?.y ?? 56;\n const anchor: CSSProperties = (() => {\n switch (position) {\n case \"top-right\":\n return { top: offY, right: offX };\n case \"bottom-left\":\n return { bottom: offY, left: offX };\n case \"bottom-right\":\n return { bottom: offY, right: offX };\n case \"top-left\":\n default:\n return { top: offY, left: offX };\n }\n })();\n\n if (hidden) return null;\n\n const dim: CSSProperties = { opacity: 0.55, fontSize: \"0.9em\" };\n\n return (\n <div\n data-testid=\"zendir-object-inventory\"\n style={{\n position: \"absolute\",\n zIndex: 2,\n width,\n maxHeight,\n overflow: \"auto\",\n padding: tokens.spacing.sm,\n borderRadius: tokens.borderRadius?.md ?? 6,\n border: \"1px solid rgba(255,255,255,0.12)\",\n background: \"rgba(0,0,0,0.72)\",\n color: tokens.colors.text.primary,\n fontSize: tokens.typography.fontSize.xs,\n lineHeight: 1.35,\n ...anchor,\n ...style,\n }}\n >\n <PanelHeader\n title={title ?? `API Objects · ${entries.length}`}\n collapsible={collapsible}\n collapsed={collapsed}\n onToggle={() => setCollapsed((c) => !c)}\n />\n\n {!collapsed && (\n <>\n {KIND_ORDER.map((kind) => {\n const items = entries.filter((o) => o.kind === kind);\n return (\n <ObjectInventoryGroup\n key={kind}\n title={`${labels[kind]} (${items.length})`}\n items={items}\n selectedId={effectiveSelectedId}\n onSelect={\n onSelect != null\n ? onSelect\n : ctx\n ? effectiveOnSelect\n : undefined\n }\n />\n );\n })}\n\n {structureDiag !== undefined && (\n <StructureDiagBlock diag={structureDiag} dim={dim} />\n )}\n\n {positionTickDiag !== undefined && (\n <PositionTickDiagBlock diag={positionTickDiag} dim={dim} />\n )}\n </>\n )}\n </div>\n );\n});\n\n// ─── Sub-components ───────────────────────────────────────────────────────────\n\nconst PanelHeader = ({\n title,\n collapsible,\n collapsed,\n onToggle,\n}: {\n title: ReactNode;\n collapsible: boolean;\n collapsed: boolean;\n onToggle: () => void;\n}) => {\n if (!collapsible) {\n return <div style={{ marginBottom: 6, fontWeight: 600 }}>{title}</div>;\n }\n return (\n <button\n type=\"button\"\n onClick={onToggle}\n aria-expanded={!collapsed}\n style={{\n display: \"flex\",\n width: \"100%\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n marginBottom: 6,\n padding: 0,\n background: \"none\",\n border: \"none\",\n color: \"inherit\",\n font: \"inherit\",\n fontWeight: 600,\n cursor: \"pointer\",\n textAlign: \"left\",\n }}\n >\n <span>{title}</span>\n <span aria-hidden style={{ opacity: 0.7, fontSize: \"0.85em\" }}>\n {collapsed ? \"▸\" : \"▾\"}\n </span>\n </button>\n );\n};\n\nconst ObjectInventoryGroup = ({\n title,\n items,\n selectedId,\n onSelect,\n}: {\n title: string;\n items: SimulationSceneObject[];\n selectedId: string | null;\n onSelect?: (obj: SimulationSceneObject) => void;\n}) => (\n <div style={{ marginBottom: 6 }}>\n <div style={{ opacity: 0.85 }}>{title}</div>\n {items.length === 0 ? (\n <div style={{ opacity: 0.6 }}>—</div>\n ) : (\n items.map((item) => (\n <ObjectRow\n key={item.id}\n object={item}\n selected={item.id === selectedId}\n onSelect={onSelect}\n />\n ))\n )}\n </div>\n);\n\nconst ObjectRow = ({\n object,\n selected,\n onSelect,\n}: {\n object: SimulationSceneObject;\n selected: boolean;\n onSelect?: (obj: SimulationSceneObject) => void;\n}) => {\n const handleClick = useCallback(() => onSelect?.(object), [onSelect, object]);\n const interactive = onSelect != null;\n return (\n <div\n role={interactive ? \"button\" : undefined}\n tabIndex={interactive ? 0 : undefined}\n onClick={interactive ? handleClick : undefined}\n onKeyDown={\n interactive\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n handleClick();\n }\n }\n : undefined\n }\n style={{\n opacity: 0.95,\n padding: \"1px 4px\",\n borderRadius: 3,\n cursor: interactive ? \"pointer\" : \"default\",\n background: selected ? \"rgba(255,255,255,0.12)\" : \"transparent\",\n }}\n >\n {object.name} <span style={{ opacity: 0.6 }}>({shortId(object.id)})</span>\n </div>\n );\n};\n\nconst StructureDiagBlock = ({\n diag,\n dim,\n}: {\n diag: StructureDiag | null | undefined;\n dim: CSSProperties;\n}) => (\n <div\n style={{\n marginTop: 8,\n borderTop: \"1px solid rgba(255,255,255,0.1)\",\n paddingTop: 6,\n }}\n >\n {diag ? (\n <>\n <div style={{ ...dim, marginBottom: 2 }}>Raw structure keys:</div>\n <div style={dim}>{diag.topLevelKeys.join(\", \") || \"—\"}</div>\n <div style={{ ...dim, marginTop: 2 }}>\n Objects[]: {diag.rawObjectCount < 0 ? \"absent\" : diag.rawObjectCount}\n {\" · \"}\n Systems[]: {diag.rawSystemCount < 0 ? \"absent\" : diag.rawSystemCount}\n </div>\n </>\n ) : (\n <div style={{ ...dim, marginTop: 4 }}>Waiting for structure…</div>\n )}\n </div>\n);\n\nconst PositionTickDiagBlock = ({\n diag,\n dim,\n}: {\n diag: PositionTickDiag | null | undefined;\n dim: CSSProperties;\n}) => {\n if (!diag) return null;\n return (\n <div\n style={{\n marginTop: 8,\n borderTop: \"1px solid rgba(255,255,255,0.1)\",\n paddingTop: 6,\n }}\n >\n <div style={{ ...dim, marginBottom: 2 }}>Last position tick:</div>\n <div style={dim}>\n ok {diag.parsedOk}/{diag.requested}\n {diag.parseFailed > 0 && ` · parseFail ${diag.parseFailed}`}\n {diag.requestFailed > 0 && ` · reqFail ${diag.requestFailed}`}\n </div>\n <div style={dim}>\n asked for:{\" \"}\n {diag.requestedProps\n ? diag.requestedProps.join(\", \")\n : \"getAllProperties (fallback)\"}\n </div>\n {diag.firstResponseKeys.length > 0 && (\n <div style={{ ...dim, wordBreak: \"break-all\" }}>\n got keys: {diag.firstResponseKeys.slice(0, 12).join(\", \")}\n {diag.firstResponseKeys.length > 12 && \" …\"}\n </div>\n )}\n {diag.firstResponseSample && (\n <div style={{ ...dim, wordBreak: \"break-all\" }}>\n {diag.firstResponseSample}\n </div>\n )}\n </div>\n );\n};\n\n// ─── Companion helper ─────────────────────────────────────────────────────────\n\n/**\n * Sensible default fly-to distance per object kind, for use with\n * `<ZenSpace3D />`'s imperative `focusOn(id, { zoom })` handle.\n *\n * spacecraft: 2 000 000 m (≈ 2 000 km — close orbital view)\n * groundStation: 1 500 000 m (≈ 1 500 km — high-altitude angled view)\n * celestialBody: 50 000 000 m (≈ 50 000 km — full-planet view)\n *\n * Override per call if the consumer wants tighter/looser framing.\n */\nexport function recommendedFlyDistance(kind: ObjectInventoryKind): number {\n switch (kind) {\n case \"spacecraft\":\n return 2_000_000;\n case \"groundStation\":\n return 1_500_000;\n case \"celestialBody\":\n return 50_000_000;\n }\n}\n\nexport default ObjectInventoryPanel;\n"],"mappings":"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"}
1
+ {"version":3,"file":"ObjectInventoryPanel.js","names":[],"sources":["../../../src/react/panels/ObjectInventoryPanel.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - ObjectInventoryPanel\r\n *\r\n * Themed list of simulation objects, grouped by `kind` (spacecraft /\r\n * ground stations / celestial bodies). Pair with `useSimulationScene`:\r\n *\r\n * ```tsx\r\n * const scene = useSimulationScene({ client, containerId, simulationId });\r\n * <ObjectInventoryPanel\r\n * objects={scene.objects}\r\n * onSelect={(obj) => zenRef.current?.focusOn(obj.id, {\r\n * zoom: recommendedFlyDistance(obj.kind),\r\n * })}\r\n * />\r\n * ```\r\n *\r\n * The panel renders the toggle UI only — it has no opinion about WHAT\r\n * happens on select. The consumer wires `onSelect` to its 3D viewer's\r\n * imperative handle (or anywhere else — the panel is reusable in any\r\n * dashboard or debug context).\r\n */\r\n\r\nimport { memo, useCallback, useState } from \"react\";\r\nimport type { CSSProperties, ReactNode } from \"react\";\r\nimport { useThemeTokens } from \"../theme/ThemeProvider\";\r\nimport { useSpatialSelection } from \"../context/SpatialSelectionContext\";\r\nimport type {\r\n SimulationSceneObject,\r\n StructureDiag,\r\n PositionTickDiag,\r\n} from \"../hooks/useSimulationScene\";\r\n\r\n// ─── Types ────────────────────────────────────────────────────────────────────\r\n\r\nexport type ObjectInventoryKind = SimulationSceneObject[\"kind\"];\r\n\r\nexport interface ObjectInventoryPanelProps {\r\n /** Objects to list. Typically `useSimulationScene().objects`. */\r\n objects: SimulationSceneObject[];\r\n /** Click handler — fires with the clicked object. Pure forward; the\r\n * panel does not navigate, focus, or fly anywhere itself. */\r\n onSelect?: (object: SimulationSceneObject) => void;\r\n /** Currently selected object id (highlights the row). Pass `null` for none. */\r\n selectedId?: string | null;\r\n\r\n /** Where to anchor the panel on its parent. Default: `top-left`. */\r\n position?: \"top-left\" | \"top-right\" | \"bottom-left\" | \"bottom-right\";\r\n /** Pixel offsets from the anchor. Defaults to (16, 56) so a top bar fits above. */\r\n offset?: { x?: number; y?: number };\r\n /** Hide the panel without unmounting (preserves `selectedId` state). */\r\n hidden?: boolean;\r\n /** Width in pixels. Default 330. */\r\n width?: number;\r\n /** Maximum height (CSS value). Default `'50vh'`. */\r\n maxHeight?: string | number;\r\n\r\n /** Allow the user to collapse / expand the panel. Default true. */\r\n collapsible?: boolean;\r\n /** Initial collapsed state. Default false. */\r\n defaultCollapsed?: boolean;\r\n\r\n /** Optional title override. Default: `\"API Objects · N\"`. */\r\n title?: ReactNode;\r\n\r\n /**\r\n * Optional structure diagnostic block (raw keys + counts from\r\n * getSimulationStructure). Useful in dev / debug builds; omit in\r\n * production for a cleaner look.\r\n */\r\n structureDiag?: StructureDiag | null;\r\n /** Optional position-tick diagnostic block. */\r\n positionTickDiag?: PositionTickDiag | null;\r\n\r\n /** Custom labels per kind (defaults: \"Spacecraft\", \"Ground Stations\", \"Celestial Bodies\"). */\r\n kindLabels?: Partial<Record<ObjectInventoryKind, string>>;\r\n\r\n /** Inline style override on the outer container. */\r\n style?: CSSProperties;\r\n}\r\n\r\n// ─── Helpers ──────────────────────────────────────────────────────────────────\r\n\r\n/** Stable short-form of a GUID for inline display. */\r\nfunction shortId(id: string): string {\r\n return id.length <= 8 ? id : id.slice(0, 8);\r\n}\r\n\r\nconst DEFAULT_KIND_LABELS: Record<ObjectInventoryKind, string> = {\r\n spacecraft: \"Spacecraft\",\r\n groundStation: \"Ground Stations\",\r\n celestialBody: \"Celestial Bodies\",\r\n};\r\n\r\nconst KIND_ORDER: ObjectInventoryKind[] = [\r\n \"spacecraft\",\r\n \"groundStation\",\r\n \"celestialBody\",\r\n];\r\n\r\n// ─── Component ────────────────────────────────────────────────────────────────\r\n\r\nexport const ObjectInventoryPanel = memo(function ObjectInventoryPanel({\r\n objects,\r\n onSelect,\r\n selectedId,\r\n position = \"top-left\",\r\n offset,\r\n hidden = false,\r\n width = 330,\r\n maxHeight = \"50vh\",\r\n collapsible = true,\r\n defaultCollapsed = false,\r\n title,\r\n structureDiag,\r\n positionTickDiag,\r\n kindLabels,\r\n style,\r\n}: ObjectInventoryPanelProps) {\r\n const tokens = useThemeTokens();\r\n const [collapsed, setCollapsed] = useState(defaultCollapsed);\r\n\r\n // Optional <SpatialSelectionProvider> integration. When present, the\r\n // panel uses the shared selection state so other components (3D\r\n // viewer, GroundTrackMap, etc.) stay synchronised. Explicit props\r\n // always win — consumers can still override per-instance.\r\n const ctx = useSpatialSelection();\r\n const effectiveSelectedId =\r\n selectedId !== undefined ? selectedId : (ctx?.selectedId ?? null);\r\n const effectiveOnSelect = useCallback(\r\n (obj: SimulationSceneObject) => {\r\n onSelect?.(obj);\r\n // Also push into the shared context if no explicit onSelect wins it.\r\n if (!onSelect) ctx?.setSelectedId(obj.id);\r\n },\r\n [onSelect, ctx],\r\n );\r\n\r\n const labels = { ...DEFAULT_KIND_LABELS, ...kindLabels };\r\n const entries = Array.isArray(objects) ? objects : [];\r\n\r\n // Position anchor — translate the prop into top/left/right/bottom CSS.\r\n const offX = offset?.x ?? 16;\r\n const offY = offset?.y ?? 56;\r\n const anchor: CSSProperties = (() => {\r\n switch (position) {\r\n case \"top-right\":\r\n return { top: offY, right: offX };\r\n case \"bottom-left\":\r\n return { bottom: offY, left: offX };\r\n case \"bottom-right\":\r\n return { bottom: offY, right: offX };\r\n case \"top-left\":\r\n default:\r\n return { top: offY, left: offX };\r\n }\r\n })();\r\n\r\n if (hidden) return null;\r\n\r\n const dim: CSSProperties = { opacity: 0.55, fontSize: \"0.9em\" };\r\n\r\n return (\r\n <div\r\n data-testid=\"zendir-object-inventory\"\r\n style={{\r\n position: \"absolute\",\r\n zIndex: 2,\r\n width,\r\n maxHeight,\r\n overflow: \"auto\",\r\n padding: tokens.spacing.sm,\r\n borderRadius: tokens.borderRadius?.md ?? 6,\r\n border: \"1px solid rgba(255,255,255,0.12)\",\r\n background: \"rgba(0,0,0,0.72)\",\r\n color: tokens.colors.text.primary,\r\n fontSize: tokens.typography.fontSize.xs,\r\n lineHeight: 1.35,\r\n ...anchor,\r\n ...style,\r\n }}\r\n >\r\n <PanelHeader\r\n title={title ?? `API Objects · ${entries.length}`}\r\n collapsible={collapsible}\r\n collapsed={collapsed}\r\n onToggle={() => setCollapsed((c) => !c)}\r\n />\r\n\r\n {!collapsed && (\r\n <>\r\n {KIND_ORDER.map((kind) => {\r\n const items = entries.filter((o) => o.kind === kind);\r\n return (\r\n <ObjectInventoryGroup\r\n key={kind}\r\n title={`${labels[kind]} (${items.length})`}\r\n items={items}\r\n selectedId={effectiveSelectedId}\r\n onSelect={\r\n onSelect != null\r\n ? onSelect\r\n : ctx\r\n ? effectiveOnSelect\r\n : undefined\r\n }\r\n />\r\n );\r\n })}\r\n\r\n {structureDiag !== undefined && (\r\n <StructureDiagBlock diag={structureDiag} dim={dim} />\r\n )}\r\n\r\n {positionTickDiag !== undefined && (\r\n <PositionTickDiagBlock diag={positionTickDiag} dim={dim} />\r\n )}\r\n </>\r\n )}\r\n </div>\r\n );\r\n});\r\n\r\n// ─── Sub-components ───────────────────────────────────────────────────────────\r\n\r\nconst PanelHeader = ({\r\n title,\r\n collapsible,\r\n collapsed,\r\n onToggle,\r\n}: {\r\n title: ReactNode;\r\n collapsible: boolean;\r\n collapsed: boolean;\r\n onToggle: () => void;\r\n}) => {\r\n if (!collapsible) {\r\n return <div style={{ marginBottom: 6, fontWeight: 600 }}>{title}</div>;\r\n }\r\n return (\r\n <button\r\n type=\"button\"\r\n onClick={onToggle}\r\n aria-expanded={!collapsed}\r\n style={{\r\n display: \"flex\",\r\n width: \"100%\",\r\n alignItems: \"center\",\r\n justifyContent: \"space-between\",\r\n marginBottom: 6,\r\n padding: 0,\r\n background: \"none\",\r\n border: \"none\",\r\n color: \"inherit\",\r\n font: \"inherit\",\r\n fontWeight: 600,\r\n cursor: \"pointer\",\r\n textAlign: \"left\",\r\n }}\r\n >\r\n <span>{title}</span>\r\n <span aria-hidden style={{ opacity: 0.7, fontSize: \"0.85em\" }}>\r\n {collapsed ? \"▸\" : \"▾\"}\r\n </span>\r\n </button>\r\n );\r\n};\r\n\r\nconst ObjectInventoryGroup = ({\r\n title,\r\n items,\r\n selectedId,\r\n onSelect,\r\n}: {\r\n title: string;\r\n items: SimulationSceneObject[];\r\n selectedId: string | null;\r\n onSelect?: (obj: SimulationSceneObject) => void;\r\n}) => (\r\n <div style={{ marginBottom: 6 }}>\r\n <div style={{ opacity: 0.85 }}>{title}</div>\r\n {items.length === 0 ? (\r\n <div style={{ opacity: 0.6 }}>—</div>\r\n ) : (\r\n items.map((item) => (\r\n <ObjectRow\r\n key={item.id}\r\n object={item}\r\n selected={item.id === selectedId}\r\n onSelect={onSelect}\r\n />\r\n ))\r\n )}\r\n </div>\r\n);\r\n\r\nconst ObjectRow = ({\r\n object,\r\n selected,\r\n onSelect,\r\n}: {\r\n object: SimulationSceneObject;\r\n selected: boolean;\r\n onSelect?: (obj: SimulationSceneObject) => void;\r\n}) => {\r\n const handleClick = useCallback(() => onSelect?.(object), [onSelect, object]);\r\n const interactive = onSelect != null;\r\n return (\r\n <div\r\n role={interactive ? \"button\" : undefined}\r\n tabIndex={interactive ? 0 : undefined}\r\n onClick={interactive ? handleClick : undefined}\r\n onKeyDown={\r\n interactive\r\n ? (e) => {\r\n if (e.key === \"Enter\" || e.key === \" \") {\r\n e.preventDefault();\r\n handleClick();\r\n }\r\n }\r\n : undefined\r\n }\r\n style={{\r\n opacity: 0.95,\r\n padding: \"1px 4px\",\r\n borderRadius: 3,\r\n cursor: interactive ? \"pointer\" : \"default\",\r\n background: selected ? \"rgba(255,255,255,0.12)\" : \"transparent\",\r\n }}\r\n >\r\n {object.name} <span style={{ opacity: 0.6 }}>({shortId(object.id)})</span>\r\n </div>\r\n );\r\n};\r\n\r\nconst StructureDiagBlock = ({\r\n diag,\r\n dim,\r\n}: {\r\n diag: StructureDiag | null | undefined;\r\n dim: CSSProperties;\r\n}) => (\r\n <div\r\n style={{\r\n marginTop: 8,\r\n borderTop: \"1px solid rgba(255,255,255,0.1)\",\r\n paddingTop: 6,\r\n }}\r\n >\r\n {diag ? (\r\n <>\r\n <div style={{ ...dim, marginBottom: 2 }}>Raw structure keys:</div>\r\n <div style={dim}>{diag.topLevelKeys.join(\", \") || \"—\"}</div>\r\n <div style={{ ...dim, marginTop: 2 }}>\r\n Objects[]: {diag.rawObjectCount < 0 ? \"absent\" : diag.rawObjectCount}\r\n {\" · \"}\r\n Systems[]: {diag.rawSystemCount < 0 ? \"absent\" : diag.rawSystemCount}\r\n </div>\r\n </>\r\n ) : (\r\n <div style={{ ...dim, marginTop: 4 }}>Waiting for structure…</div>\r\n )}\r\n </div>\r\n);\r\n\r\nconst PositionTickDiagBlock = ({\r\n diag,\r\n dim,\r\n}: {\r\n diag: PositionTickDiag | null | undefined;\r\n dim: CSSProperties;\r\n}) => {\r\n if (!diag) return null;\r\n return (\r\n <div\r\n style={{\r\n marginTop: 8,\r\n borderTop: \"1px solid rgba(255,255,255,0.1)\",\r\n paddingTop: 6,\r\n }}\r\n >\r\n <div style={{ ...dim, marginBottom: 2 }}>Last position tick:</div>\r\n <div style={dim}>\r\n ok {diag.parsedOk}/{diag.requested}\r\n {diag.parseFailed > 0 && ` · parseFail ${diag.parseFailed}`}\r\n {diag.requestFailed > 0 && ` · reqFail ${diag.requestFailed}`}\r\n </div>\r\n <div style={dim}>\r\n asked for:{\" \"}\r\n {diag.requestedProps\r\n ? diag.requestedProps.join(\", \")\r\n : \"getAllProperties (fallback)\"}\r\n </div>\r\n {diag.firstResponseKeys.length > 0 && (\r\n <div style={{ ...dim, wordBreak: \"break-all\" }}>\r\n got keys: {diag.firstResponseKeys.slice(0, 12).join(\", \")}\r\n {diag.firstResponseKeys.length > 12 && \" …\"}\r\n </div>\r\n )}\r\n {diag.firstResponseSample && (\r\n <div style={{ ...dim, wordBreak: \"break-all\" }}>\r\n {diag.firstResponseSample}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n};\r\n\r\n// ─── Companion helper ─────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Sensible default fly-to distance per object kind, for use with\r\n * `<ZenSpace3D />`'s imperative `focusOn(id, { zoom })` handle.\r\n *\r\n * spacecraft: 2 000 000 m (≈ 2 000 km — close orbital view)\r\n * groundStation: 1 500 000 m (≈ 1 500 km — high-altitude angled view)\r\n * celestialBody: 50 000 000 m (≈ 50 000 km — full-planet view)\r\n *\r\n * Override per call if the consumer wants tighter/looser framing.\r\n */\r\nexport function recommendedFlyDistance(kind: ObjectInventoryKind): number {\r\n switch (kind) {\r\n case \"spacecraft\":\r\n return 2_000_000;\r\n case \"groundStation\":\r\n return 1_500_000;\r\n case \"celestialBody\":\r\n return 50_000_000;\r\n }\r\n}\r\n\r\nexport default ObjectInventoryPanel;\r\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ErrorBoundary.js","names":[],"sources":["../../../src/react/shared/ErrorBoundary.tsx"],"sourcesContent":["/**\n * @zendir/ui - ErrorBoundary Component\n *\n * React error boundary that catches JavaScript errors in child components.\n * Prevents white screen of death and displays graceful fallback UI.\n *\n * Features:\n * - Customizable fallback UI\n * - Error callback for logging/reporting\n * - Retry functionality\n * - Accessible error display\n */\n\nimport React, { Component, ErrorInfo, ReactNode } from \"react\";\nimport { getStatusColor } from \"../utils\";\n\nexport interface ErrorBoundaryProps {\n /** Custom fallback UI or render function */\n fallback?: ReactNode | ((error: Error, retry: () => void) => ReactNode);\n /** Callback when error is caught */\n onError?: (error: Error, errorInfo: ErrorInfo) => void;\n /** Children to render */\n children: ReactNode;\n /** Component name for error context */\n componentName?: string;\n}\n\ninterface ErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n}\n\n/**\n * ErrorBoundary - Catches and handles component errors gracefully\n *\n * @example\n * ```tsx\n * <ErrorBoundary\n * fallback={<ErrorCard message=\"Failed to load\" />}\n * onError={(error) => logError(error)}\n * >\n * <SpacecraftCard {...props} />\n * </ErrorBoundary>\n * ```\n *\n * @example\n * ```tsx\n * <ErrorBoundary\n * fallback={(error, retry) => (\n * <div>\n * <p>Error: {error.message}</p>\n * <button onClick={retry}>Retry</button>\n * </div>\n * )}\n * >\n * <TelemetryCard {...props} />\n * </ErrorBoundary>\n * ```\n */\nexport class ErrorBoundary extends Component<\n ErrorBoundaryProps,\n ErrorBoundaryState\n> {\n constructor(props: ErrorBoundaryProps) {\n super(props);\n this.state = { hasError: false, error: null };\n }\n\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: ErrorInfo): void {\n this.props.onError?.(error, errorInfo);\n\n // Log to console in development\n if (process.env.NODE_ENV === \"development\") {\n console.error(\"ErrorBoundary caught an error:\", error, errorInfo);\n }\n }\n\n handleRetry = (): void => {\n this.setState({ hasError: false, error: null });\n };\n\n render(): ReactNode {\n if (this.state.hasError) {\n const { fallback, componentName } = this.props;\n const { error } = this.state;\n\n // Custom fallback render function\n if (typeof fallback === \"function\" && error) {\n return fallback(error, this.handleRetry);\n }\n\n // Custom fallback element (exclude function - already handled above)\n if (fallback && typeof fallback !== \"function\") {\n return fallback;\n }\n\n // Default fallback UI\n return (\n <DefaultErrorFallback\n error={error}\n componentName={componentName}\n onRetry={this.handleRetry}\n />\n );\n }\n\n return this.props.children;\n }\n}\n\n// ============================================================================\n// Default Error Fallback\n// ============================================================================\n\ninterface DefaultErrorFallbackProps {\n error: Error | null;\n componentName?: string;\n onRetry: () => void;\n}\n\nfunction DefaultErrorFallback({\n error,\n componentName,\n onRetry,\n}: DefaultErrorFallbackProps): React.ReactElement {\n const criticalColor = getStatusColor(\"critical\");\n\n return (\n <div\n role=\"alert\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n padding: 24,\n minHeight: 120,\n backgroundColor: \"var(--zendir-bg-surface, #1b2d3e)\",\n borderRadius: 8,\n border: `1px solid ${criticalColor}40`,\n textAlign: \"center\",\n }}\n >\n {/* Error icon */}\n <svg\n viewBox=\"0 0 24 24\"\n fill={criticalColor}\n width=\"32\"\n height=\"32\"\n style={{ marginBottom: 12, opacity: 0.8 }}\n >\n <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\" />\n </svg>\n\n {/* Error message */}\n <h3\n style={{\n margin: 0,\n marginBottom: 8,\n fontSize: \"0.875rem\", // 14px in rem\n fontWeight: 500, // AstroUXDS medium\n color: \"var(--zendir-text-primary, #ffffff)\",\n }}\n >\n {componentName ? `${componentName} Error` : \"Something went wrong\"}\n </h3>\n\n <p\n style={{\n margin: 0,\n marginBottom: 16,\n fontSize: 12,\n color: \"var(--zendir-text-secondary, #a4abb6)\",\n maxWidth: 280,\n }}\n >\n {error?.message ?? \"An unexpected error occurred\"}\n </p>\n\n {/* Retry button */}\n <button\n onClick={onRetry}\n style={{\n padding: \"8px 16px\",\n fontSize: 12,\n fontWeight: 500,\n color: \"#ffffff\",\n backgroundColor: \"var(--zendir-accent-primary, #4dacff)\",\n border: \"none\",\n borderRadius: 4,\n cursor: \"pointer\",\n transition: \"background-color 150ms ease\",\n }}\n onMouseEnter={(e) =>\n (e.currentTarget.style.backgroundColor =\n \"var(--zendir-accent-secondary, #92cbff)\")\n }\n onMouseLeave={(e) =>\n (e.currentTarget.style.backgroundColor =\n \"var(--zendir-accent-primary, #4dacff)\")\n }\n >\n Try Again\n </button>\n </div>\n );\n}\n\n// ============================================================================\n// Higher-Order Component for Error Boundaries\n// ============================================================================\n\n/**\n * Wraps a component with an ErrorBoundary\n *\n * @example\n * ```tsx\n * const SafeSpacecraftCard = withErrorBoundary(SpacecraftCard, {\n * componentName: 'SpacecraftCard',\n * onError: (error) => logError(error),\n * });\n * ```\n */\nexport function withErrorBoundary<P extends object>(\n Component: React.ComponentType<P>,\n options: Omit<ErrorBoundaryProps, \"children\"> = {},\n): React.FC<P> {\n const WrappedComponent: React.FC<P> = (props) => (\n <ErrorBoundary {...options}>\n <Component {...props} />\n </ErrorBoundary>\n );\n\n WrappedComponent.displayName = `WithErrorBoundary(${Component.displayName ?? Component.name ?? \"Component\"})`;\n\n return WrappedComponent;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2DA,IAAa,gBAAb,cAAmC,UAGjC;CACA,YAAY,OAA2B;EACrC,MAAM,KAAK;EACX,KAAK,QAAQ;GAAE,UAAU;GAAO,OAAO;EAAK;CAC9C;CAEA,OAAO,yBAAyB,OAAkC;EAChE,OAAO;GAAE,UAAU;GAAM;EAAM;CACjC;CAEA,kBAAkB,OAAc,WAA4B;EAC1D,KAAK,MAAM,UAAU,OAAO,SAAS;EAGrC,IAAA,QAAA,IAAA,aAA6B,eAC3B,QAAQ,MAAM,kCAAkC,OAAO,SAAS;CAEpE;CAEA,oBAA0B;EACxB,KAAK,SAAS;GAAE,UAAU;GAAO,OAAO;EAAK,CAAC;CAChD;CAEA,SAAoB;EAClB,IAAI,KAAK,MAAM,UAAU;GACvB,MAAM,EAAE,UAAU,kBAAkB,KAAK;GACzC,MAAM,EAAE,UAAU,KAAK;GAGvB,IAAI,OAAO,aAAa,cAAc,OACpC,OAAO,SAAS,OAAO,KAAK,WAAW;GAIzC,IAAI,YAAY,OAAO,aAAa,YAClC,OAAO;GAIT,OACE,oBAAC,sBAAD;IACS;IACQ;IACf,SAAS,KAAK;GACf,CAAA;EAEL;EAEA,OAAO,KAAK,MAAM;CACpB;AACF;AAYA,SAAS,qBAAqB,EAC5B,OACA,eACA,WACgD;CAChD,MAAM,gBAAgB,eAAe,UAAU;CAE/C,OACE,qBAAC,OAAD;EACE,MAAK;EACL,OAAO;GACL,SAAS;GACT,eAAe;GACf,YAAY;GACZ,gBAAgB;GAChB,SAAS;GACT,WAAW;GACX,iBAAiB;GACjB,cAAc;GACd,QAAQ,aAAa,cAAc;GACnC,WAAW;EACb;YAbF;GAgBE,oBAAC,OAAD;IACE,SAAQ;IACR,MAAM;IACN,OAAM;IACN,QAAO;IACP,OAAO;KAAE,cAAc;KAAI,SAAS;IAAI;cAExC,oBAAC,QAAD,EAAM,GAAE,mGAAoG,CAAA;GACzG,CAAA;GAGL,oBAAC,MAAD;IACE,OAAO;KACL,QAAQ;KACR,cAAc;KACd,UAAU;KACV,YAAY;KACZ,OAAO;IACT;cAEC,gBAAgB,GAAG,cAAc,UAAU;GAC1C,CAAA;GAEJ,oBAAC,KAAD;IACE,OAAO;KACL,QAAQ;KACR,cAAc;KACd,UAAU;KACV,OAAO;KACP,UAAU;IACZ;cAEC,OAAO,WAAW;GAClB,CAAA;GAGH,oBAAC,UAAD;IACE,SAAS;IACT,OAAO;KACL,SAAS;KACT,UAAU;KACV,YAAY;KACZ,OAAO;KACP,iBAAiB;KACjB,QAAQ;KACR,cAAc;KACd,QAAQ;KACR,YAAY;IACd;IACA,eAAe,MACZ,EAAE,cAAc,MAAM,kBACrB;IAEJ,eAAe,MACZ,EAAE,cAAc,MAAM,kBACrB;cAEL;GAEO,CAAA;EACL;;AAET;;;;;;;;;;;;AAiBA,SAAgB,kBACd,WACA,UAAgD,CAAC,GACpC;CACb,MAAM,oBAAiC,UACrC,oBAAC,eAAD;EAAe,GAAI;YACjB,oBAAC,WAAD,EAAW,GAAI,MAAQ,CAAA;CACV,CAAA;CAGjB,iBAAiB,cAAc,qBAAqB,UAAU,eAAe,UAAU,QAAQ,YAAY;CAE3G,OAAO;AACT"}
1
+ {"version":3,"file":"ErrorBoundary.js","names":[],"sources":["../../../src/react/shared/ErrorBoundary.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - ErrorBoundary Component\r\n *\r\n * React error boundary that catches JavaScript errors in child components.\r\n * Prevents white screen of death and displays graceful fallback UI.\r\n *\r\n * Features:\r\n * - Customizable fallback UI\r\n * - Error callback for logging/reporting\r\n * - Retry functionality\r\n * - Accessible error display\r\n */\r\n\r\nimport React, { Component, ErrorInfo, ReactNode } from \"react\";\r\nimport { getStatusColor } from \"../utils\";\r\n\r\nexport interface ErrorBoundaryProps {\r\n /** Custom fallback UI or render function */\r\n fallback?: ReactNode | ((error: Error, retry: () => void) => ReactNode);\r\n /** Callback when error is caught */\r\n onError?: (error: Error, errorInfo: ErrorInfo) => void;\r\n /** Children to render */\r\n children: ReactNode;\r\n /** Component name for error context */\r\n componentName?: string;\r\n}\r\n\r\ninterface ErrorBoundaryState {\r\n hasError: boolean;\r\n error: Error | null;\r\n}\r\n\r\n/**\r\n * ErrorBoundary - Catches and handles component errors gracefully\r\n *\r\n * @example\r\n * ```tsx\r\n * <ErrorBoundary\r\n * fallback={<ErrorCard message=\"Failed to load\" />}\r\n * onError={(error) => logError(error)}\r\n * >\r\n * <SpacecraftCard {...props} />\r\n * </ErrorBoundary>\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * <ErrorBoundary\r\n * fallback={(error, retry) => (\r\n * <div>\r\n * <p>Error: {error.message}</p>\r\n * <button onClick={retry}>Retry</button>\r\n * </div>\r\n * )}\r\n * >\r\n * <TelemetryCard {...props} />\r\n * </ErrorBoundary>\r\n * ```\r\n */\r\nexport class ErrorBoundary extends Component<\r\n ErrorBoundaryProps,\r\n ErrorBoundaryState\r\n> {\r\n constructor(props: ErrorBoundaryProps) {\r\n super(props);\r\n this.state = { hasError: false, error: null };\r\n }\r\n\r\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\r\n return { hasError: true, error };\r\n }\r\n\r\n componentDidCatch(error: Error, errorInfo: ErrorInfo): void {\r\n this.props.onError?.(error, errorInfo);\r\n\r\n // Log to console in development\r\n if (process.env.NODE_ENV === \"development\") {\r\n console.error(\"ErrorBoundary caught an error:\", error, errorInfo);\r\n }\r\n }\r\n\r\n handleRetry = (): void => {\r\n this.setState({ hasError: false, error: null });\r\n };\r\n\r\n render(): ReactNode {\r\n if (this.state.hasError) {\r\n const { fallback, componentName } = this.props;\r\n const { error } = this.state;\r\n\r\n // Custom fallback render function\r\n if (typeof fallback === \"function\" && error) {\r\n return fallback(error, this.handleRetry);\r\n }\r\n\r\n // Custom fallback element (exclude function - already handled above)\r\n if (fallback && typeof fallback !== \"function\") {\r\n return fallback;\r\n }\r\n\r\n // Default fallback UI\r\n return (\r\n <DefaultErrorFallback\r\n error={error}\r\n componentName={componentName}\r\n onRetry={this.handleRetry}\r\n />\r\n );\r\n }\r\n\r\n return this.props.children;\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Default Error Fallback\r\n// ============================================================================\r\n\r\ninterface DefaultErrorFallbackProps {\r\n error: Error | null;\r\n componentName?: string;\r\n onRetry: () => void;\r\n}\r\n\r\nfunction DefaultErrorFallback({\r\n error,\r\n componentName,\r\n onRetry,\r\n}: DefaultErrorFallbackProps): React.ReactElement {\r\n const criticalColor = getStatusColor(\"critical\");\r\n\r\n return (\r\n <div\r\n role=\"alert\"\r\n style={{\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n padding: 24,\r\n minHeight: 120,\r\n backgroundColor: \"var(--zendir-bg-surface, #1b2d3e)\",\r\n borderRadius: 8,\r\n border: `1px solid ${criticalColor}40`,\r\n textAlign: \"center\",\r\n }}\r\n >\r\n {/* Error icon */}\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n fill={criticalColor}\r\n width=\"32\"\r\n height=\"32\"\r\n style={{ marginBottom: 12, opacity: 0.8 }}\r\n >\r\n <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\" />\r\n </svg>\r\n\r\n {/* Error message */}\r\n <h3\r\n style={{\r\n margin: 0,\r\n marginBottom: 8,\r\n fontSize: \"0.875rem\", // 14px in rem\r\n fontWeight: 500, // AstroUXDS medium\r\n color: \"var(--zendir-text-primary, #ffffff)\",\r\n }}\r\n >\r\n {componentName ? `${componentName} Error` : \"Something went wrong\"}\r\n </h3>\r\n\r\n <p\r\n style={{\r\n margin: 0,\r\n marginBottom: 16,\r\n fontSize: 12,\r\n color: \"var(--zendir-text-secondary, #a4abb6)\",\r\n maxWidth: 280,\r\n }}\r\n >\r\n {error?.message ?? \"An unexpected error occurred\"}\r\n </p>\r\n\r\n {/* Retry button */}\r\n <button\r\n onClick={onRetry}\r\n style={{\r\n padding: \"8px 16px\",\r\n fontSize: 12,\r\n fontWeight: 500,\r\n color: \"#ffffff\",\r\n backgroundColor: \"var(--zendir-accent-primary, #4dacff)\",\r\n border: \"none\",\r\n borderRadius: 4,\r\n cursor: \"pointer\",\r\n transition: \"background-color 150ms ease\",\r\n }}\r\n onMouseEnter={(e) =>\r\n (e.currentTarget.style.backgroundColor =\r\n \"var(--zendir-accent-secondary, #92cbff)\")\r\n }\r\n onMouseLeave={(e) =>\r\n (e.currentTarget.style.backgroundColor =\r\n \"var(--zendir-accent-primary, #4dacff)\")\r\n }\r\n >\r\n Try Again\r\n </button>\r\n </div>\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// Higher-Order Component for Error Boundaries\r\n// ============================================================================\r\n\r\n/**\r\n * Wraps a component with an ErrorBoundary\r\n *\r\n * @example\r\n * ```tsx\r\n * const SafeSpacecraftCard = withErrorBoundary(SpacecraftCard, {\r\n * componentName: 'SpacecraftCard',\r\n * onError: (error) => logError(error),\r\n * });\r\n * ```\r\n */\r\nexport function withErrorBoundary<P extends object>(\r\n Component: React.ComponentType<P>,\r\n options: Omit<ErrorBoundaryProps, \"children\"> = {},\r\n): React.FC<P> {\r\n const WrappedComponent: React.FC<P> = (props) => (\r\n <ErrorBoundary {...options}>\r\n <Component {...props} />\r\n </ErrorBoundary>\r\n );\r\n\r\n WrappedComponent.displayName = `WithErrorBoundary(${Component.displayName ?? Component.name ?? \"Component\"})`;\r\n\r\n return WrappedComponent;\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2DA,IAAa,gBAAb,cAAmC,UAGjC;CACA,YAAY,OAA2B;EACrC,MAAM,KAAK;EACX,KAAK,QAAQ;GAAE,UAAU;GAAO,OAAO;EAAK;CAC9C;CAEA,OAAO,yBAAyB,OAAkC;EAChE,OAAO;GAAE,UAAU;GAAM;EAAM;CACjC;CAEA,kBAAkB,OAAc,WAA4B;EAC1D,KAAK,MAAM,UAAU,OAAO,SAAS;EAGrC,IAAA,QAAA,IAAA,aAA6B,eAC3B,QAAQ,MAAM,kCAAkC,OAAO,SAAS;CAEpE;CAEA,oBAA0B;EACxB,KAAK,SAAS;GAAE,UAAU;GAAO,OAAO;EAAK,CAAC;CAChD;CAEA,SAAoB;EAClB,IAAI,KAAK,MAAM,UAAU;GACvB,MAAM,EAAE,UAAU,kBAAkB,KAAK;GACzC,MAAM,EAAE,UAAU,KAAK;GAGvB,IAAI,OAAO,aAAa,cAAc,OACpC,OAAO,SAAS,OAAO,KAAK,WAAW;GAIzC,IAAI,YAAY,OAAO,aAAa,YAClC,OAAO;GAIT,OACE,oBAAC,sBAAD;IACS;IACQ;IACf,SAAS,KAAK;GACf,CAAA;EAEL;EAEA,OAAO,KAAK,MAAM;CACpB;AACF;AAYA,SAAS,qBAAqB,EAC5B,OACA,eACA,WACgD;CAChD,MAAM,gBAAgB,eAAe,UAAU;CAE/C,OACE,qBAAC,OAAD;EACE,MAAK;EACL,OAAO;GACL,SAAS;GACT,eAAe;GACf,YAAY;GACZ,gBAAgB;GAChB,SAAS;GACT,WAAW;GACX,iBAAiB;GACjB,cAAc;GACd,QAAQ,aAAa,cAAc;GACnC,WAAW;EACb;YAbF;GAgBE,oBAAC,OAAD;IACE,SAAQ;IACR,MAAM;IACN,OAAM;IACN,QAAO;IACP,OAAO;KAAE,cAAc;KAAI,SAAS;IAAI;cAExC,oBAAC,QAAD,EAAM,GAAE,mGAAoG,CAAA;GACzG,CAAA;GAGL,oBAAC,MAAD;IACE,OAAO;KACL,QAAQ;KACR,cAAc;KACd,UAAU;KACV,YAAY;KACZ,OAAO;IACT;cAEC,gBAAgB,GAAG,cAAc,UAAU;GAC1C,CAAA;GAEJ,oBAAC,KAAD;IACE,OAAO;KACL,QAAQ;KACR,cAAc;KACd,UAAU;KACV,OAAO;KACP,UAAU;IACZ;cAEC,OAAO,WAAW;GAClB,CAAA;GAGH,oBAAC,UAAD;IACE,SAAS;IACT,OAAO;KACL,SAAS;KACT,UAAU;KACV,YAAY;KACZ,OAAO;KACP,iBAAiB;KACjB,QAAQ;KACR,cAAc;KACd,QAAQ;KACR,YAAY;IACd;IACA,eAAe,MACZ,EAAE,cAAc,MAAM,kBACrB;IAEJ,eAAe,MACZ,EAAE,cAAc,MAAM,kBACrB;cAEL;GAEO,CAAA;EACL;;AAET;;;;;;;;;;;;AAiBA,SAAgB,kBACd,WACA,UAAgD,CAAC,GACpC;CACb,MAAM,oBAAiC,UACrC,oBAAC,eAAD;EAAe,GAAI;YACjB,oBAAC,WAAD,EAAW,GAAI,MAAQ,CAAA;CACV,CAAA;CAGjB,iBAAiB,cAAc,qBAAqB,UAAU,eAAe,UAAU,QAAQ,YAAY;CAE3G,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"Skeleton.js","names":[],"sources":["../../../src/react/shared/Skeleton.tsx"],"sourcesContent":["/**\n * @zendir/ui - Skeleton Components\n *\n * Loading skeleton components with Apple-style shimmer animation.\n * Used for loading states before data arrives.\n *\n * Features:\n * - Smooth shimmer animation\n * - Multiple variants (text, card, chart, circle)\n * - Respects reduced-motion preferences\n * - Matches component sizes for seamless transition\n */\n\nimport React, { memo } from \"react\";\nimport { classNames } from \"../utils\";\n\n// ============================================================================\n// Shared Styles\n// ============================================================================\n\nconst SHIMMER_KEYFRAMES = `\n@keyframes skeletonShimmer {\n 0% {\n background-position: -200% 0;\n }\n 100% {\n background-position: 200% 0;\n }\n}\n`;\n\nconst getShimmerStyle = (\n width?: string | number,\n height?: string | number,\n): React.CSSProperties => ({\n width: width ?? \"100%\",\n height: height ?? 16,\n backgroundColor:\n \"var(--color-background-interactive-default, rgba(255,255,255,0.1))\",\n backgroundImage:\n \"linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.08) 50%, transparent 100%)\",\n backgroundSize: \"200% 100%\",\n backgroundRepeat: \"no-repeat\",\n animation: \"skeletonShimmer 1.5s ease-in-out infinite\",\n borderRadius: 4,\n});\n\n// ============================================================================\n// SkeletonText\n// ============================================================================\n\nexport interface SkeletonTextProps {\n /** Number of lines to render */\n lines?: number;\n /** Width of the last line (as percentage or px) */\n lastLineWidth?: string | number;\n /** Height of each line */\n lineHeight?: number;\n /** Gap between lines */\n gap?: number;\n /** Custom className */\n className?: string;\n}\n\n/**\n * SkeletonText - Placeholder for text content\n *\n * @example\n * ```tsx\n * <SkeletonText lines={3} lastLineWidth=\"60%\" />\n * ```\n */\nexport const SkeletonText = memo(function SkeletonText({\n lines = 1,\n lastLineWidth = \"80%\",\n lineHeight = 14,\n gap = 8,\n className = \"\",\n}: SkeletonTextProps): React.ReactElement {\n return (\n <div\n className={classNames(\"zendir-skeleton-text\", className)}\n role=\"presentation\"\n aria-hidden=\"true\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap,\n }}\n >\n {Array.from({ length: lines }).map((_, i) => (\n <div\n key={i}\n style={{\n ...getShimmerStyle(\n i === lines - 1 ? lastLineWidth : \"100%\",\n lineHeight,\n ),\n }}\n />\n ))}\n <style>{SHIMMER_KEYFRAMES}</style>\n </div>\n );\n});\n\n// ============================================================================\n// SkeletonBox\n// ============================================================================\n\nexport interface SkeletonBoxProps {\n /** Width of the box */\n width?: string | number;\n /** Height of the box */\n height?: string | number;\n /** Border radius */\n borderRadius?: number;\n /** Custom className */\n className?: string;\n}\n\n/**\n * SkeletonBox - Generic rectangular placeholder\n *\n * @example\n * ```tsx\n * <SkeletonBox width={200} height={100} />\n * ```\n */\nexport const SkeletonBox = memo(function SkeletonBox({\n width = \"100%\",\n height = 100,\n borderRadius = 4,\n className = \"\",\n}: SkeletonBoxProps): React.ReactElement {\n return (\n <div\n className={classNames(\"zendir-skeleton-box\", className)}\n role=\"presentation\"\n aria-hidden=\"true\"\n style={{\n ...getShimmerStyle(width, height),\n borderRadius,\n }}\n >\n <style>{SHIMMER_KEYFRAMES}</style>\n </div>\n );\n});\n\n// ============================================================================\n// SkeletonCircle\n// ============================================================================\n\nexport interface SkeletonCircleProps {\n /** Diameter of the circle */\n size?: number;\n /** Custom className */\n className?: string;\n}\n\n/**\n * SkeletonCircle - Circular placeholder (avatars, icons)\n *\n * @example\n * ```tsx\n * <SkeletonCircle size={48} />\n * ```\n */\nexport const SkeletonCircle = memo(function SkeletonCircle({\n size = 40,\n className = \"\",\n}: SkeletonCircleProps): React.ReactElement {\n return (\n <div\n className={classNames(\"zendir-skeleton-circle\", className)}\n role=\"presentation\"\n aria-hidden=\"true\"\n style={{\n ...getShimmerStyle(size, size),\n borderRadius: \"50%\",\n flexShrink: 0,\n }}\n >\n <style>{SHIMMER_KEYFRAMES}</style>\n </div>\n );\n});\n\n// ============================================================================\n// SkeletonCard\n// ============================================================================\n\nexport interface SkeletonCardProps {\n /** Card height */\n height?: number;\n /** Show header placeholder */\n showHeader?: boolean;\n /** Show content lines */\n showContent?: boolean;\n /** Number of content lines */\n contentLines?: number;\n /** Custom className */\n className?: string;\n}\n\n/**\n * SkeletonCard - Card-shaped placeholder matching component cards\n *\n * @example\n * ```tsx\n * <SkeletonCard showHeader showContent contentLines={4} />\n * ```\n */\nexport const SkeletonCard = memo(function SkeletonCard({\n height,\n showHeader = true,\n showContent = true,\n contentLines = 3,\n className = \"\",\n}: SkeletonCardProps): React.ReactElement {\n return (\n <div\n className={classNames(\"zendir-skeleton-card\", className)}\n role=\"presentation\"\n aria-label=\"Loading...\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: 16,\n padding: 16,\n minHeight: height,\n backgroundColor: \"var(--zendir-bg-surface, #1b2d3e)\",\n borderRadius: 8,\n border: \"1px solid var(--zendir-border-default, rgba(255,255,255,0.1))\",\n }}\n >\n {/* Header */}\n {showHeader && (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 12 }}>\n <SkeletonCircle size={32} />\n <div style={{ flex: 1 }}>\n <SkeletonText lines={1} lineHeight={16} />\n <div style={{ height: 6 }} />\n <SkeletonText lines={1} lineHeight={12} lastLineWidth=\"50%\" />\n </div>\n </div>\n )}\n\n {/* Content */}\n {showContent && (\n <div style={{ flex: 1 }}>\n <SkeletonText lines={contentLines} gap={10} lineHeight={12} />\n </div>\n )}\n\n <style>{SHIMMER_KEYFRAMES}</style>\n </div>\n );\n});\n\n// ============================================================================\n// SkeletonChart\n// ============================================================================\n\nexport interface SkeletonChartProps {\n /** Chart height */\n height?: number;\n /** Chart type visualization */\n type?: \"line\" | \"bar\" | \"area\";\n /** Custom className */\n className?: string;\n}\n\n/**\n * SkeletonChart - Chart placeholder with visualization hint\n *\n * @example\n * ```tsx\n * <SkeletonChart height={200} type=\"line\" />\n * ```\n */\nexport const SkeletonChart = memo(function SkeletonChart({\n height = 200,\n type = \"line\",\n className = \"\",\n}: SkeletonChartProps): React.ReactElement {\n return (\n <div\n className={classNames(\"zendir-skeleton-chart\", className)}\n role=\"presentation\"\n aria-label=\"Loading chart...\"\n style={{\n position: \"relative\",\n height,\n backgroundColor: \"var(--zendir-bg-surface, #1b2d3e)\",\n borderRadius: 8,\n border: \"1px solid var(--zendir-border-default, rgba(255,255,255,0.1))\",\n overflow: \"hidden\",\n }}\n >\n {/* Chart area */}\n <div\n style={{\n position: \"absolute\",\n inset: 16,\n display: \"flex\",\n alignItems: \"flex-end\",\n justifyContent: \"space-between\",\n gap: type === \"bar\" ? 8 : 0,\n }}\n >\n {type === \"bar\" ? (\n // Bar chart skeleton\n Array.from({ length: 8 }).map((_, i) => (\n <div\n key={i}\n style={{\n ...getShimmerStyle(undefined, `${30 + Math.random() * 50}%`),\n flex: 1,\n borderRadius: \"4px 4px 0 0\",\n }}\n />\n ))\n ) : (\n // Line/Area chart skeleton\n <svg\n viewBox=\"0 0 100 50\"\n preserveAspectRatio=\"none\"\n style={{\n width: \"100%\",\n height: \"100%\",\n }}\n >\n <defs>\n <linearGradient\n id=\"skeletonGradient\"\n x1=\"0%\"\n y1=\"0%\"\n x2=\"100%\"\n y2=\"0%\"\n >\n <stop offset=\"0%\" stopColor=\"rgba(255,255,255,0.05)\">\n <animate\n attributeName=\"offset\"\n values=\"-0.5;1.5\"\n dur=\"1.5s\"\n repeatCount=\"indefinite\"\n />\n </stop>\n <stop offset=\"50%\" stopColor=\"rgba(255,255,255,0.1)\">\n <animate\n attributeName=\"offset\"\n values=\"0;2\"\n dur=\"1.5s\"\n repeatCount=\"indefinite\"\n />\n </stop>\n <stop offset=\"100%\" stopColor=\"rgba(255,255,255,0.05)\">\n <animate\n attributeName=\"offset\"\n values=\"0.5;2.5\"\n dur=\"1.5s\"\n repeatCount=\"indefinite\"\n />\n </stop>\n </linearGradient>\n </defs>\n <path\n d=\"M0,40 Q15,35 25,30 T50,25 T75,20 T100,15 L100,50 L0,50 Z\"\n fill={type === \"area\" ? \"url(#skeletonGradient)\" : \"none\"}\n stroke=\"url(#skeletonGradient)\"\n strokeWidth=\"2\"\n />\n </svg>\n )}\n </div>\n\n {/* Axis labels */}\n <div\n style={{\n position: \"absolute\",\n left: 4,\n top: 16,\n bottom: 16,\n width: 24,\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"space-between\",\n }}\n >\n {Array.from({ length: 4 }).map((_, i) => (\n <SkeletonBox key={i} width={24} height={8} />\n ))}\n </div>\n\n <style>{SHIMMER_KEYFRAMES}</style>\n </div>\n );\n});\n\n// ============================================================================\n// Skeleton3D\n// ============================================================================\n\nexport interface Skeleton3DProps {\n /** Container height */\n height?: number;\n /** Custom className */\n className?: string;\n}\n\n/**\n * Skeleton3D - 3D viewer placeholder with sphere hint\n *\n * @example\n * ```tsx\n * <Skeleton3D height={400} />\n * ```\n */\nexport const Skeleton3D = memo(function Skeleton3D({\n height = 400,\n className = \"\",\n}: Skeleton3DProps): React.ReactElement {\n return (\n <div\n className={classNames(\"zendir-skeleton-3d\", className)}\n role=\"presentation\"\n aria-label=\"Loading 3D view...\"\n style={{\n position: \"relative\",\n height,\n backgroundColor: \"var(--zendir-bg-base, #101923)\",\n borderRadius: 8,\n overflow: \"hidden\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n {/* Spinning sphere hint */}\n <div\n style={{\n width: Math.min(height * 0.5, 200),\n height: Math.min(height * 0.5, 200),\n borderRadius: \"50%\",\n border: \"2px solid rgba(255,255,255,0.1)\",\n backgroundImage:\n \"linear-gradient(135deg, transparent 40%, rgba(77,172,255,0.1) 50%, transparent 60%)\",\n backgroundSize: \"200% 200%\",\n animation:\n \"skeleton3DSpin 3s linear infinite, skeletonShimmer 1.5s ease-in-out infinite\",\n }}\n />\n\n {/* Loading text */}\n <div\n style={{\n position: \"absolute\",\n bottom: 16,\n left: 0,\n right: 0,\n textAlign: \"center\",\n fontSize: 12,\n color: \"rgba(255,255,255,0.4)\",\n }}\n >\n Loading 3D Scene...\n </div>\n\n <style>{`\n ${SHIMMER_KEYFRAMES}\n @keyframes skeleton3DSpin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n `}</style>\n </div>\n );\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"Skeleton.js","names":[],"sources":["../../../src/react/shared/Skeleton.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - Skeleton Components\r\n *\r\n * Loading skeleton components with Apple-style shimmer animation.\r\n * Used for loading states before data arrives.\r\n *\r\n * Features:\r\n * - Smooth shimmer animation\r\n * - Multiple variants (text, card, chart, circle)\r\n * - Respects reduced-motion preferences\r\n * - Matches component sizes for seamless transition\r\n */\r\n\r\nimport React, { memo } from \"react\";\r\nimport { classNames } from \"../utils\";\r\n\r\n// ============================================================================\r\n// Shared Styles\r\n// ============================================================================\r\n\r\nconst SHIMMER_KEYFRAMES = `\r\n@keyframes skeletonShimmer {\r\n 0% {\r\n background-position: -200% 0;\r\n }\r\n 100% {\r\n background-position: 200% 0;\r\n }\r\n}\r\n`;\r\n\r\nconst getShimmerStyle = (\r\n width?: string | number,\r\n height?: string | number,\r\n): React.CSSProperties => ({\r\n width: width ?? \"100%\",\r\n height: height ?? 16,\r\n backgroundColor:\r\n \"var(--color-background-interactive-default, rgba(255,255,255,0.1))\",\r\n backgroundImage:\r\n \"linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.08) 50%, transparent 100%)\",\r\n backgroundSize: \"200% 100%\",\r\n backgroundRepeat: \"no-repeat\",\r\n animation: \"skeletonShimmer 1.5s ease-in-out infinite\",\r\n borderRadius: 4,\r\n});\r\n\r\n// ============================================================================\r\n// SkeletonText\r\n// ============================================================================\r\n\r\nexport interface SkeletonTextProps {\r\n /** Number of lines to render */\r\n lines?: number;\r\n /** Width of the last line (as percentage or px) */\r\n lastLineWidth?: string | number;\r\n /** Height of each line */\r\n lineHeight?: number;\r\n /** Gap between lines */\r\n gap?: number;\r\n /** Custom className */\r\n className?: string;\r\n}\r\n\r\n/**\r\n * SkeletonText - Placeholder for text content\r\n *\r\n * @example\r\n * ```tsx\r\n * <SkeletonText lines={3} lastLineWidth=\"60%\" />\r\n * ```\r\n */\r\nexport const SkeletonText = memo(function SkeletonText({\r\n lines = 1,\r\n lastLineWidth = \"80%\",\r\n lineHeight = 14,\r\n gap = 8,\r\n className = \"\",\r\n}: SkeletonTextProps): React.ReactElement {\r\n return (\r\n <div\r\n className={classNames(\"zendir-skeleton-text\", className)}\r\n role=\"presentation\"\r\n aria-hidden=\"true\"\r\n style={{\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n gap,\r\n }}\r\n >\r\n {Array.from({ length: lines }).map((_, i) => (\r\n <div\r\n key={i}\r\n style={{\r\n ...getShimmerStyle(\r\n i === lines - 1 ? lastLineWidth : \"100%\",\r\n lineHeight,\r\n ),\r\n }}\r\n />\r\n ))}\r\n <style>{SHIMMER_KEYFRAMES}</style>\r\n </div>\r\n );\r\n});\r\n\r\n// ============================================================================\r\n// SkeletonBox\r\n// ============================================================================\r\n\r\nexport interface SkeletonBoxProps {\r\n /** Width of the box */\r\n width?: string | number;\r\n /** Height of the box */\r\n height?: string | number;\r\n /** Border radius */\r\n borderRadius?: number;\r\n /** Custom className */\r\n className?: string;\r\n}\r\n\r\n/**\r\n * SkeletonBox - Generic rectangular placeholder\r\n *\r\n * @example\r\n * ```tsx\r\n * <SkeletonBox width={200} height={100} />\r\n * ```\r\n */\r\nexport const SkeletonBox = memo(function SkeletonBox({\r\n width = \"100%\",\r\n height = 100,\r\n borderRadius = 4,\r\n className = \"\",\r\n}: SkeletonBoxProps): React.ReactElement {\r\n return (\r\n <div\r\n className={classNames(\"zendir-skeleton-box\", className)}\r\n role=\"presentation\"\r\n aria-hidden=\"true\"\r\n style={{\r\n ...getShimmerStyle(width, height),\r\n borderRadius,\r\n }}\r\n >\r\n <style>{SHIMMER_KEYFRAMES}</style>\r\n </div>\r\n );\r\n});\r\n\r\n// ============================================================================\r\n// SkeletonCircle\r\n// ============================================================================\r\n\r\nexport interface SkeletonCircleProps {\r\n /** Diameter of the circle */\r\n size?: number;\r\n /** Custom className */\r\n className?: string;\r\n}\r\n\r\n/**\r\n * SkeletonCircle - Circular placeholder (avatars, icons)\r\n *\r\n * @example\r\n * ```tsx\r\n * <SkeletonCircle size={48} />\r\n * ```\r\n */\r\nexport const SkeletonCircle = memo(function SkeletonCircle({\r\n size = 40,\r\n className = \"\",\r\n}: SkeletonCircleProps): React.ReactElement {\r\n return (\r\n <div\r\n className={classNames(\"zendir-skeleton-circle\", className)}\r\n role=\"presentation\"\r\n aria-hidden=\"true\"\r\n style={{\r\n ...getShimmerStyle(size, size),\r\n borderRadius: \"50%\",\r\n flexShrink: 0,\r\n }}\r\n >\r\n <style>{SHIMMER_KEYFRAMES}</style>\r\n </div>\r\n );\r\n});\r\n\r\n// ============================================================================\r\n// SkeletonCard\r\n// ============================================================================\r\n\r\nexport interface SkeletonCardProps {\r\n /** Card height */\r\n height?: number;\r\n /** Show header placeholder */\r\n showHeader?: boolean;\r\n /** Show content lines */\r\n showContent?: boolean;\r\n /** Number of content lines */\r\n contentLines?: number;\r\n /** Custom className */\r\n className?: string;\r\n}\r\n\r\n/**\r\n * SkeletonCard - Card-shaped placeholder matching component cards\r\n *\r\n * @example\r\n * ```tsx\r\n * <SkeletonCard showHeader showContent contentLines={4} />\r\n * ```\r\n */\r\nexport const SkeletonCard = memo(function SkeletonCard({\r\n height,\r\n showHeader = true,\r\n showContent = true,\r\n contentLines = 3,\r\n className = \"\",\r\n}: SkeletonCardProps): React.ReactElement {\r\n return (\r\n <div\r\n className={classNames(\"zendir-skeleton-card\", className)}\r\n role=\"presentation\"\r\n aria-label=\"Loading...\"\r\n style={{\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n gap: 16,\r\n padding: 16,\r\n minHeight: height,\r\n backgroundColor: \"var(--zendir-bg-surface, #1b2d3e)\",\r\n borderRadius: 8,\r\n border: \"1px solid var(--zendir-border-default, rgba(255,255,255,0.1))\",\r\n }}\r\n >\r\n {/* Header */}\r\n {showHeader && (\r\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 12 }}>\r\n <SkeletonCircle size={32} />\r\n <div style={{ flex: 1 }}>\r\n <SkeletonText lines={1} lineHeight={16} />\r\n <div style={{ height: 6 }} />\r\n <SkeletonText lines={1} lineHeight={12} lastLineWidth=\"50%\" />\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Content */}\r\n {showContent && (\r\n <div style={{ flex: 1 }}>\r\n <SkeletonText lines={contentLines} gap={10} lineHeight={12} />\r\n </div>\r\n )}\r\n\r\n <style>{SHIMMER_KEYFRAMES}</style>\r\n </div>\r\n );\r\n});\r\n\r\n// ============================================================================\r\n// SkeletonChart\r\n// ============================================================================\r\n\r\nexport interface SkeletonChartProps {\r\n /** Chart height */\r\n height?: number;\r\n /** Chart type visualization */\r\n type?: \"line\" | \"bar\" | \"area\";\r\n /** Custom className */\r\n className?: string;\r\n}\r\n\r\n/**\r\n * SkeletonChart - Chart placeholder with visualization hint\r\n *\r\n * @example\r\n * ```tsx\r\n * <SkeletonChart height={200} type=\"line\" />\r\n * ```\r\n */\r\nexport const SkeletonChart = memo(function SkeletonChart({\r\n height = 200,\r\n type = \"line\",\r\n className = \"\",\r\n}: SkeletonChartProps): React.ReactElement {\r\n return (\r\n <div\r\n className={classNames(\"zendir-skeleton-chart\", className)}\r\n role=\"presentation\"\r\n aria-label=\"Loading chart...\"\r\n style={{\r\n position: \"relative\",\r\n height,\r\n backgroundColor: \"var(--zendir-bg-surface, #1b2d3e)\",\r\n borderRadius: 8,\r\n border: \"1px solid var(--zendir-border-default, rgba(255,255,255,0.1))\",\r\n overflow: \"hidden\",\r\n }}\r\n >\r\n {/* Chart area */}\r\n <div\r\n style={{\r\n position: \"absolute\",\r\n inset: 16,\r\n display: \"flex\",\r\n alignItems: \"flex-end\",\r\n justifyContent: \"space-between\",\r\n gap: type === \"bar\" ? 8 : 0,\r\n }}\r\n >\r\n {type === \"bar\" ? (\r\n // Bar chart skeleton\r\n Array.from({ length: 8 }).map((_, i) => (\r\n <div\r\n key={i}\r\n style={{\r\n ...getShimmerStyle(undefined, `${30 + Math.random() * 50}%`),\r\n flex: 1,\r\n borderRadius: \"4px 4px 0 0\",\r\n }}\r\n />\r\n ))\r\n ) : (\r\n // Line/Area chart skeleton\r\n <svg\r\n viewBox=\"0 0 100 50\"\r\n preserveAspectRatio=\"none\"\r\n style={{\r\n width: \"100%\",\r\n height: \"100%\",\r\n }}\r\n >\r\n <defs>\r\n <linearGradient\r\n id=\"skeletonGradient\"\r\n x1=\"0%\"\r\n y1=\"0%\"\r\n x2=\"100%\"\r\n y2=\"0%\"\r\n >\r\n <stop offset=\"0%\" stopColor=\"rgba(255,255,255,0.05)\">\r\n <animate\r\n attributeName=\"offset\"\r\n values=\"-0.5;1.5\"\r\n dur=\"1.5s\"\r\n repeatCount=\"indefinite\"\r\n />\r\n </stop>\r\n <stop offset=\"50%\" stopColor=\"rgba(255,255,255,0.1)\">\r\n <animate\r\n attributeName=\"offset\"\r\n values=\"0;2\"\r\n dur=\"1.5s\"\r\n repeatCount=\"indefinite\"\r\n />\r\n </stop>\r\n <stop offset=\"100%\" stopColor=\"rgba(255,255,255,0.05)\">\r\n <animate\r\n attributeName=\"offset\"\r\n values=\"0.5;2.5\"\r\n dur=\"1.5s\"\r\n repeatCount=\"indefinite\"\r\n />\r\n </stop>\r\n </linearGradient>\r\n </defs>\r\n <path\r\n d=\"M0,40 Q15,35 25,30 T50,25 T75,20 T100,15 L100,50 L0,50 Z\"\r\n fill={type === \"area\" ? \"url(#skeletonGradient)\" : \"none\"}\r\n stroke=\"url(#skeletonGradient)\"\r\n strokeWidth=\"2\"\r\n />\r\n </svg>\r\n )}\r\n </div>\r\n\r\n {/* Axis labels */}\r\n <div\r\n style={{\r\n position: \"absolute\",\r\n left: 4,\r\n top: 16,\r\n bottom: 16,\r\n width: 24,\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n justifyContent: \"space-between\",\r\n }}\r\n >\r\n {Array.from({ length: 4 }).map((_, i) => (\r\n <SkeletonBox key={i} width={24} height={8} />\r\n ))}\r\n </div>\r\n\r\n <style>{SHIMMER_KEYFRAMES}</style>\r\n </div>\r\n );\r\n});\r\n\r\n// ============================================================================\r\n// Skeleton3D\r\n// ============================================================================\r\n\r\nexport interface Skeleton3DProps {\r\n /** Container height */\r\n height?: number;\r\n /** Custom className */\r\n className?: string;\r\n}\r\n\r\n/**\r\n * Skeleton3D - 3D viewer placeholder with sphere hint\r\n *\r\n * @example\r\n * ```tsx\r\n * <Skeleton3D height={400} />\r\n * ```\r\n */\r\nexport const Skeleton3D = memo(function Skeleton3D({\r\n height = 400,\r\n className = \"\",\r\n}: Skeleton3DProps): React.ReactElement {\r\n return (\r\n <div\r\n className={classNames(\"zendir-skeleton-3d\", className)}\r\n role=\"presentation\"\r\n aria-label=\"Loading 3D view...\"\r\n style={{\r\n position: \"relative\",\r\n height,\r\n backgroundColor: \"var(--zendir-bg-base, #101923)\",\r\n borderRadius: 8,\r\n overflow: \"hidden\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n }}\r\n >\r\n {/* Spinning sphere hint */}\r\n <div\r\n style={{\r\n width: Math.min(height * 0.5, 200),\r\n height: Math.min(height * 0.5, 200),\r\n borderRadius: \"50%\",\r\n border: \"2px solid rgba(255,255,255,0.1)\",\r\n backgroundImage:\r\n \"linear-gradient(135deg, transparent 40%, rgba(77,172,255,0.1) 50%, transparent 60%)\",\r\n backgroundSize: \"200% 200%\",\r\n animation:\r\n \"skeleton3DSpin 3s linear infinite, skeletonShimmer 1.5s ease-in-out infinite\",\r\n }}\r\n />\r\n\r\n {/* Loading text */}\r\n <div\r\n style={{\r\n position: \"absolute\",\r\n bottom: 16,\r\n left: 0,\r\n right: 0,\r\n textAlign: \"center\",\r\n fontSize: 12,\r\n color: \"rgba(255,255,255,0.4)\",\r\n }}\r\n >\r\n Loading 3D Scene...\r\n </div>\r\n\r\n <style>{`\r\n ${SHIMMER_KEYFRAMES}\r\n @keyframes skeleton3DSpin {\r\n from { transform: rotate(0deg); }\r\n to { transform: rotate(360deg); }\r\n }\r\n `}</style>\r\n </div>\r\n );\r\n});\r\n"],"mappings":";;;;;;;;;;;;;;;;AAoBA,IAAM,oBAAoB;;;;;;;;;;AAW1B,IAAM,mBACJ,OACA,YACyB;CACzB,OAAO,SAAS;CAChB,QAAQ,UAAU;CAClB,iBACE;CACF,iBACE;CACF,gBAAgB;CAChB,kBAAkB;CAClB,WAAW;CACX,cAAc;AAChB;;;;;;;;;AA2BA,IAAa,eAAe,KAAK,SAAS,aAAa,EACrD,QAAQ,GACR,gBAAgB,OAChB,aAAa,IACb,MAAM,GACN,YAAY,MAC4B;CACxC,OACE,qBAAC,OAAD;EACE,WAAW,WAAW,wBAAwB,SAAS;EACvD,MAAK;EACL,eAAY;EACZ,OAAO;GACL,SAAS;GACT,eAAe;GACf;EACF;YARF,CAUG,MAAM,KAAK,EAAE,QAAQ,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,MACrC,oBAAC,OAAD,EAEE,OAAO,EACL,GAAG,gBACD,MAAM,QAAQ,IAAI,gBAAgB,QAClC,UACF,EACF,EACD,GAPM,CAON,CACF,GACD,oBAAC,SAAD,EAAA,UAAQ,kBAAyB,CAAA,CAC9B;;AAET,CAAC;;;;;;;;;AAyBD,IAAa,cAAc,KAAK,SAAS,YAAY,EACnD,QAAQ,QACR,SAAS,KACT,eAAe,GACf,YAAY,MAC2B;CACvC,OACE,oBAAC,OAAD;EACE,WAAW,WAAW,uBAAuB,SAAS;EACtD,MAAK;EACL,eAAY;EACZ,OAAO;GACL,GAAG,gBAAgB,OAAO,MAAM;GAChC;EACF;YAEA,oBAAC,SAAD,EAAA,UAAQ,kBAAyB,CAAA;CAC9B,CAAA;AAET,CAAC;;;;;;;;;AAqBD,IAAa,iBAAiB,KAAK,SAAS,eAAe,EACzD,OAAO,IACP,YAAY,MAC8B;CAC1C,OACE,oBAAC,OAAD;EACE,WAAW,WAAW,0BAA0B,SAAS;EACzD,MAAK;EACL,eAAY;EACZ,OAAO;GACL,GAAG,gBAAgB,MAAM,IAAI;GAC7B,cAAc;GACd,YAAY;EACd;YAEA,oBAAC,SAAD,EAAA,UAAQ,kBAAyB,CAAA;CAC9B,CAAA;AAET,CAAC;;;;;;;;;AA2BD,IAAa,eAAe,KAAK,SAAS,aAAa,EACrD,QACA,aAAa,MACb,cAAc,MACd,eAAe,GACf,YAAY,MAC4B;CACxC,OACE,qBAAC,OAAD;EACE,WAAW,WAAW,wBAAwB,SAAS;EACvD,MAAK;EACL,cAAW;EACX,OAAO;GACL,SAAS;GACT,eAAe;GACf,KAAK;GACL,SAAS;GACT,WAAW;GACX,iBAAiB;GACjB,cAAc;GACd,QAAQ;EACV;YAbF;GAgBG,cACC,qBAAC,OAAD;IAAK,OAAO;KAAE,SAAS;KAAQ,YAAY;KAAU,KAAK;IAAG;cAA7D,CACE,oBAAC,gBAAD,EAAgB,MAAM,GAAK,CAAA,GAC3B,qBAAC,OAAD;KAAK,OAAO,EAAE,MAAM,EAAE;eAAtB;MACE,oBAAC,cAAD;OAAc,OAAO;OAAG,YAAY;MAAK,CAAA;MACzC,oBAAC,OAAD,EAAK,OAAO,EAAE,QAAQ,EAAE,EAAI,CAAA;MAC5B,oBAAC,cAAD;OAAc,OAAO;OAAG,YAAY;OAAI,eAAc;MAAO,CAAA;KAC1D;MACF;;GAIN,eACC,oBAAC,OAAD;IAAK,OAAO,EAAE,MAAM,EAAE;cACpB,oBAAC,cAAD;KAAc,OAAO;KAAc,KAAK;KAAI,YAAY;IAAK,CAAA;GAC1D,CAAA;GAGP,oBAAC,SAAD,EAAA,UAAQ,kBAAyB,CAAA;EAC9B;;AAET,CAAC;;;;;;;;;AAuBD,IAAa,gBAAgB,KAAK,SAAS,cAAc,EACvD,SAAS,KACT,OAAO,QACP,YAAY,MAC6B;CACzC,OACE,qBAAC,OAAD;EACE,WAAW,WAAW,yBAAyB,SAAS;EACxD,MAAK;EACL,cAAW;EACX,OAAO;GACL,UAAU;GACV;GACA,iBAAiB;GACjB,cAAc;GACd,QAAQ;GACR,UAAU;EACZ;YAXF;GAcE,oBAAC,OAAD;IACE,OAAO;KACL,UAAU;KACV,OAAO;KACP,SAAS;KACT,YAAY;KACZ,gBAAgB;KAChB,KAAK,SAAS,QAAQ,IAAI;IAC5B;cAEC,SAAS,QAER,MAAM,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,MAChC,oBAAC,OAAD,EAEE,OAAO;KACL,GAAG,gBAAgB,KAAA,GAAW,GAAG,KAAK,KAAK,OAAO,IAAI,GAAG,EAAE;KAC3D,MAAM;KACN,cAAc;IAChB,EACD,GANM,CAMN,CACF,IAGD,qBAAC,OAAD;KACE,SAAQ;KACR,qBAAoB;KACpB,OAAO;MACL,OAAO;MACP,QAAQ;KACV;eANF,CAQE,oBAAC,QAAD,EAAA,UACE,qBAAC,kBAAD;MACE,IAAG;MACH,IAAG;MACH,IAAG;MACH,IAAG;MACH,IAAG;gBALL;OAOE,oBAAC,QAAD;QAAM,QAAO;QAAK,WAAU;kBAC1B,oBAAC,WAAD;SACE,eAAc;SACd,QAAO;SACP,KAAI;SACJ,aAAY;QACb,CAAA;OACG,CAAA;OACN,oBAAC,QAAD;QAAM,QAAO;QAAM,WAAU;kBAC3B,oBAAC,WAAD;SACE,eAAc;SACd,QAAO;SACP,KAAI;SACJ,aAAY;QACb,CAAA;OACG,CAAA;OACN,oBAAC,QAAD;QAAM,QAAO;QAAO,WAAU;kBAC5B,oBAAC,WAAD;SACE,eAAc;SACd,QAAO;SACP,KAAI;SACJ,aAAY;QACb,CAAA;OACG,CAAA;MACQ;QACZ,CAAA,GACN,oBAAC,QAAD;MACE,GAAE;MACF,MAAM,SAAS,SAAS,2BAA2B;MACnD,QAAO;MACP,aAAY;KACb,CAAA,CACE;;GAEJ,CAAA;GAGL,oBAAC,OAAD;IACE,OAAO;KACL,UAAU;KACV,MAAM;KACN,KAAK;KACL,QAAQ;KACR,OAAO;KACP,SAAS;KACT,eAAe;KACf,gBAAgB;IAClB;cAEC,MAAM,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,MACjC,oBAAC,aAAD;KAAqB,OAAO;KAAI,QAAQ;IAAI,GAA1B,CAA0B,CAC7C;GACE,CAAA;GAEL,oBAAC,SAAD,EAAA,UAAQ,kBAAyB,CAAA;EAC9B;;AAET,CAAC;;;;;;;;;AAqBD,IAAa,aAAa,KAAK,SAAS,WAAW,EACjD,SAAS,KACT,YAAY,MAC0B;CACtC,OACE,qBAAC,OAAD;EACE,WAAW,WAAW,sBAAsB,SAAS;EACrD,MAAK;EACL,cAAW;EACX,OAAO;GACL,UAAU;GACV;GACA,iBAAiB;GACjB,cAAc;GACd,UAAU;GACV,SAAS;GACT,YAAY;GACZ,gBAAgB;EAClB;YAbF;GAgBE,oBAAC,OAAD,EACE,OAAO;IACL,OAAO,KAAK,IAAI,SAAS,IAAK,GAAG;IACjC,QAAQ,KAAK,IAAI,SAAS,IAAK,GAAG;IAClC,cAAc;IACd,QAAQ;IACR,iBACE;IACF,gBAAgB;IAChB,WACE;GACJ,EACD,CAAA;GAGD,oBAAC,OAAD;IACE,OAAO;KACL,UAAU;KACV,QAAQ;KACR,MAAM;KACN,OAAO;KACP,WAAW;KACX,UAAU;KACV,OAAO;IACT;cACD;GAEI,CAAA;GAEL,oBAAC,SAAD,EAAA,UAAQ;UACJ,kBAAkB;;;;;QAKb,CAAA;EACN;;AAET,CAAC"}
@@ -316,7 +316,8 @@ function ThemeProvider({ children, theme: themeProp, defaultVariant, mode: modeP
316
316
  if (typeof document === "undefined") return;
317
317
  const root = document.documentElement;
318
318
  const { colors, typography, spacing, borderRadius, animation, focus, shadows } = tokens;
319
- root.setAttribute("data-theme", mode);
319
+ root.setAttribute("data-theme", theme);
320
+ root.setAttribute("data-mode", mode);
320
321
  root.setAttribute("data-variant", theme);
321
322
  root.style.setProperty("--color-background-base", colors.background.base);
322
323
  root.style.setProperty("--color-background-surface", colors.background.surface);
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeProvider.js","names":[],"sources":["../../../src/react/theme/ThemeProvider.tsx"],"sourcesContent":["/**\n * @zendir/ui - Theme Provider\n *\n * Enterprise-grade theming system supporting Astro UX Design System and Zendir themes.\n * Full Tier 3 compliance with official Astro tokens (dark + light themes).\n *\n * Features:\n * - Multiple theme variants (Zen Hybrid default, astro, purple-hue, transparent)\n * - Light and dark modes\n * - Animation tokens with reduced-motion support\n * - Focus ring styles for accessibility\n * - CSS variable injection\n * - Local storage persistence\n */\n\nimport React, {\n createContext,\n useContext,\n useState,\n useEffect,\n useMemo,\n ReactNode,\n} from \"react\";\nimport {\n astroThemeDark,\n astroThemeLight,\n hybridTheme,\n purpleHueTheme,\n nebulaTheme,\n transparentTheme,\n transparentMinimalTheme,\n} from \"../../tokens/themes\";\nimport {\n computeBorderTokens,\n type ThemeVariant,\n type ThemeMode,\n type ThemeColors,\n type ThemeTokens,\n type ThemeAnimation,\n type ThemeFocus,\n type LayoutTokens,\n type BorderTokens,\n} from \"./types\";\n\n// Theme type shapes and derived-token helpers live in ./types so the token\n// build (which imports the theme definitions) never pulls in this provider.\n// Re-exported here to preserve the long-standing `@zendir/ui` import surface.\nexport type {\n ThemeVariant,\n ThemeMode,\n ThemeColors,\n ThemeTokens,\n ThemeAnimation,\n ThemeFocus,\n LayoutTokens,\n BorderTokens,\n};\n\nfunction adjustHexBrightness(hex: string, factor: number): string {\n const normalized = hex.replace(\"#\", \"\").trim();\n const expanded =\n normalized.length === 3\n ? normalized\n .split(\"\")\n .map((ch) => ch + ch)\n .join(\"\")\n : normalized;\n\n if (!/^[0-9a-fA-F]{6}$/.test(expanded)) {\n return hex;\n }\n\n const clamp = (value: number) =>\n Math.max(0, Math.min(255, Math.round(value)));\n const r = clamp(parseInt(expanded.slice(0, 2), 16) * factor);\n const g = clamp(parseInt(expanded.slice(2, 4), 16) * factor);\n const b = clamp(parseInt(expanded.slice(4, 6), 16) * factor);\n\n const toHex = (value: number) => value.toString(16).padStart(2, \"0\");\n return `#${toHex(r)}${toHex(g)}${toHex(b)}`;\n}\n\n// ============================================================================\n// Theme Context\n// ============================================================================\n\nexport interface ThemeContextValue {\n theme: ThemeVariant;\n mode: ThemeMode;\n tokens: ThemeTokens;\n prefersReducedMotion: boolean;\n /** Custom accent color override (null = use theme default) */\n accentColor: string | null;\n setTheme: (theme: ThemeVariant) => void;\n setMode: (mode: ThemeMode) => void;\n toggleMode: () => void;\n /** Override the accent color for the current theme */\n setAccentColor: (color: string | null) => void;\n}\n\nconst ThemeContext = createContext<ThemeContextValue | undefined>(undefined);\n\nexport interface ThemeProviderProps {\n children: ReactNode;\n /** Default theme variant */\n theme?: ThemeVariant;\n /** Alias for `theme` */\n defaultVariant?: ThemeVariant;\n /** Default color mode */\n mode?: ThemeMode;\n /** Alias for `mode` */\n defaultMode?: ThemeMode;\n /** Persist preferences to localStorage */\n persist?: boolean;\n}\n\n// ============================================================================\n// Scrollbar Styling System\n// ============================================================================\n// Generates global CSS for thin, elegant, theme-aware scrollbars.\n//\n// Features:\n// - Ultra-thin (6px) scrollbar tracks with soft border radius\n// - Accent-colored thumb with hover/active brightness shifts\n// - Semi-transparent track that blends into the surface\n// - Auto-hide behaviour on modern browsers (overlay mode)\n// - Firefox support via scrollbar-width / scrollbar-color\n// - WCAG 2.1 AA compliant: sufficient contrast, respects reduced-motion\n// - Smooth transitions (disabled when prefers-reduced-motion)\n// - Scoped .zendir-scroll utility class for individual containers\n// ============================================================================\n\n/**\n * Converts a hex color to an rgba string helper.\n * Accepts 3/6/8-char hex codes (#rgb, #rrggbb, #rrggbbaa).\n */\nfunction hexToRgba(hex: string, alpha: number): string {\n const clean = hex.replace(\"#\", \"\");\n let r: number, g: number, b: number;\n if (clean.length === 3) {\n r = parseInt(clean[0] + clean[0], 16);\n g = parseInt(clean[1] + clean[1], 16);\n b = parseInt(clean[2] + clean[2], 16);\n } else {\n r = parseInt(clean.slice(0, 2), 16);\n g = parseInt(clean.slice(2, 4), 16);\n b = parseInt(clean.slice(4, 6), 16);\n }\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\nfunction buildScrollbarCSS(\n tokens: ThemeTokens,\n mode: ThemeMode,\n reducedMotion: boolean,\n): string {\n const accent = tokens.colors.accent.primary;\n const _surface = tokens.colors.background.surface; // eslint-disable-line @typescript-eslint/no-unused-vars -- reserved for future scrollbar track customisation\n const _base = tokens.colors.background.base; // eslint-disable-line @typescript-eslint/no-unused-vars -- reserved for future scrollbar corner styling\n const isLight = mode === \"light\";\n\n // Thumb colors: accent-tinted, low opacity at rest, stronger on hover/active\n const thumbRest = isLight ? hexToRgba(accent, 0.28) : hexToRgba(accent, 0.35);\n const thumbHover = isLight\n ? hexToRgba(accent, 0.48)\n : hexToRgba(accent, 0.55);\n const thumbActive = isLight\n ? hexToRgba(accent, 0.64)\n : hexToRgba(accent, 0.72);\n\n // Track: nearly invisible surface blend\n const trackColor = isLight\n ? hexToRgba(accent, 0.04)\n : hexToRgba(accent, 0.06);\n const trackHover = isLight ? hexToRgba(accent, 0.08) : hexToRgba(accent, 0.1);\n\n const transition = reducedMotion\n ? \"none\"\n : \"background-color 200ms ease, width 200ms ease, opacity 200ms ease\";\n const thumbRadius = \"9999px\"; // pill shape\n\n // Firefox colors (two-value syntax: thumb track)\n const ffThumb = thumbRest;\n const ffTrack = trackColor;\n\n return `\n/* ═══════════════════════════════════════════════════════════════════════════\n Zendir Scrollbar System — Thin · Elegant · Theme-Connected\n ═══════════════════════════════════════════════════════════════════════════ */\n\n/* ── Firefox ────────────────────────────────────────────────────────────── */\n*,\n.zendir-scroll {\n scrollbar-width: thin;\n scrollbar-color: ${ffThumb} ${ffTrack};\n}\n\n/* ── Webkit / Blink (Chrome, Edge, Safari, Opera) ───────────────────────── */\n\n/* Global thin scrollbar */\n::-webkit-scrollbar {\n width: 6px;\n height: 6px;\n background: transparent;\n}\n\n/* Track — nearly invisible, subtle on hover */\n::-webkit-scrollbar-track {\n background: ${trackColor};\n border-radius: ${thumbRadius};\n margin: 2px;\n}\n::-webkit-scrollbar-track:hover {\n background: ${trackHover};\n}\n\n/* Thumb — accent-tinted pill */\n::-webkit-scrollbar-thumb {\n background: ${thumbRest};\n border-radius: ${thumbRadius};\n border: 1px solid transparent;\n background-clip: padding-box;\n transition: ${transition};\n min-height: 32px;\n}\n::-webkit-scrollbar-thumb:hover {\n background: ${thumbHover};\n border-color: transparent;\n background-clip: padding-box;\n}\n::-webkit-scrollbar-thumb:active {\n background: ${thumbActive};\n border-color: transparent;\n background-clip: padding-box;\n}\n\n/* Corner — where horizontal & vertical scrollbars meet */\n::-webkit-scrollbar-corner {\n background: transparent;\n}\n\n/* ── Utility class: zendir-scroll ────────────────────────────────────────\n Apply to individual containers for scoped thin scrollbar.\n Also enables overlay behaviour where supported.\n e.g. <div className=\"zendir-scroll\" style={{overflow:'auto'}}>…</div>\n ─────────────────────────────────────────────────────────────────────── */\n.zendir-scroll {\n overflow: auto;\n overflow: overlay; /* Chrome / Edge: auto-hide scrollbar */\n}\n.zendir-scroll::-webkit-scrollbar {\n width: 5px;\n height: 5px;\n}\n.zendir-scroll::-webkit-scrollbar-track {\n background: transparent;\n border-radius: ${thumbRadius};\n}\n.zendir-scroll::-webkit-scrollbar-thumb {\n background: ${thumbRest};\n border-radius: ${thumbRadius};\n transition: ${transition};\n}\n.zendir-scroll::-webkit-scrollbar-thumb:hover {\n background: ${thumbHover};\n}\n.zendir-scroll::-webkit-scrollbar-thumb:active {\n background: ${thumbActive};\n}\n\n/* ── Utility: zendir-scroll--hidden ─────────────────────────────────────\n Hides scrollbar visually but keeps scroll behaviour (accessible).\n Content remains scrollable via mouse wheel / touch / keyboard.\n ─────────────────────────────────────────────────────────────────────── */\n.zendir-scroll--hidden {\n overflow: auto;\n -ms-overflow-style: none;\n scrollbar-width: none;\n}\n.zendir-scroll--hidden::-webkit-scrollbar {\n display: none;\n}\n\n/* ── Focus-visible: keyboard users can still scroll with focus ─────────── */\n.zendir-scroll:focus-visible {\n outline: 2px solid ${accent};\n outline-offset: -2px;\n border-radius: ${tokens.borderRadius.md};\n}\n\n/* ── Reduced Motion: disable scrollbar transitions ─────────────────────── */\n@media (prefers-reduced-motion: reduce) {\n ::-webkit-scrollbar-thumb,\n .zendir-scroll::-webkit-scrollbar-thumb {\n transition: none !important;\n }\n}\n\n/* ── High Contrast Mode: ensure scrollbar is visible ───────────────────── */\n@media (forced-colors: active) {\n ::-webkit-scrollbar-thumb {\n background: ButtonText !important;\n }\n ::-webkit-scrollbar-track {\n background: Canvas !important;\n }\n}\n`.trim();\n}\n\n/**\n * useScrollbarStyles — returns inline React.CSSProperties for a\n * scrollable container that matches the current Zendir theme.\n *\n * Use when you need programmatic control rather than the\n * `zendir-scroll` CSS class.\n *\n * @example\n * ```tsx\n * const scrollStyle = useScrollbarStyles();\n * <div style={{ maxHeight: 400, ...scrollStyle }}>…</div>\n * ```\n */\nexport function useScrollbarStyles(): React.CSSProperties {\n const { tokens, mode } = useTheme();\n return useMemo((): React.CSSProperties => {\n const accent = tokens.colors.accent.primary;\n const isLight = mode === \"light\";\n const thumbColor = isLight\n ? hexToRgba(accent, 0.28)\n : hexToRgba(accent, 0.35);\n const trackColor = isLight\n ? hexToRgba(accent, 0.04)\n : hexToRgba(accent, 0.06);\n return {\n // Firefox\n scrollbarWidth: \"thin\" as const,\n scrollbarColor: `${thumbColor} ${trackColor}`,\n overflow: \"auto\",\n };\n }, [tokens, mode]);\n}\n\n/**\n * ThemeProvider - Enterprise theme context provider\n *\n * Provides theme tokens, mode switching, and accessibility features\n * to all child components.\n *\n * @example\n * ```tsx\n * <ThemeProvider theme=\"hybrid\" mode=\"dark\"> // Zen (Hybrid) is the default\n * <App />\n * </ThemeProvider>\n * ```\n */\nexport function ThemeProvider({\n children,\n theme: themeProp,\n defaultVariant,\n mode: modeProp,\n defaultMode: defaultModeProp,\n persist = true,\n}: ThemeProviderProps): React.ReactElement {\n const defaultTheme = themeProp ?? defaultVariant ?? \"hybrid\";\n const defaultMode = modeProp ?? defaultModeProp ?? \"dark\";\n // Initialize theme from localStorage or defaults\n const [theme, setThemeState] = useState<ThemeVariant>(() => {\n if (persist && typeof window !== \"undefined\") {\n const saved = localStorage.getItem(\"zendir-ui-theme\");\n if (\n saved === \"astro\" ||\n saved === \"zendir\" ||\n saved === \"hybrid\" ||\n saved === \"nebula\" ||\n saved === \"purple-hue\" ||\n saved === \"transparent\" ||\n saved === \"transparent-bold\" ||\n saved === \"transparent-minimal\"\n ) {\n return saved as ThemeVariant;\n }\n }\n return defaultTheme;\n });\n\n const [mode, setModeState] = useState<ThemeMode>(() => {\n if (persist && typeof window !== \"undefined\") {\n const saved = localStorage.getItem(\"zendir-ui-mode\");\n if (saved === \"light\" || saved === \"dark\") {\n return saved;\n }\n }\n return defaultMode;\n });\n\n // Detect reduced motion preference\n const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n const mediaQuery = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n setPrefersReducedMotion(mediaQuery.matches);\n\n const handler = (e: MediaQueryListEvent) =>\n setPrefersReducedMotion(e.matches);\n mediaQuery.addEventListener(\"change\", handler);\n return () => mediaQuery.removeEventListener(\"change\", handler);\n }, []);\n\n // Theme setters\n const setTheme = (newTheme: ThemeVariant) => {\n setThemeState(newTheme);\n if (persist && typeof window !== \"undefined\") {\n localStorage.setItem(\"zendir-ui-theme\", newTheme);\n }\n };\n\n const setMode = (newMode: ThemeMode) => {\n setModeState(newMode);\n if (persist && typeof window !== \"undefined\") {\n localStorage.setItem(\"zendir-ui-mode\", newMode);\n }\n };\n\n const toggleMode = () => {\n setMode(mode === \"dark\" ? \"light\" : \"dark\");\n };\n\n // Accent color override\n const [accentColor, setAccentColorState] = useState<string | null>(() => {\n if (persist && typeof window !== \"undefined\") {\n return localStorage.getItem(\"zendir-ui-accent\") || null;\n }\n return null;\n });\n\n const setAccentColor = (color: string | null) => {\n setAccentColorState(color);\n if (persist && typeof window !== \"undefined\") {\n if (color) {\n localStorage.setItem(\"zendir-ui-accent\", color);\n } else {\n localStorage.removeItem(\"zendir-ui-accent\");\n }\n }\n };\n\n // Get theme tokens (card border styles computed so all cards use theme-driven borders)\n const tokens = useMemo((): ThemeTokens => {\n // If light mode is selected, always use Astro Light theme (unified light mode)\n // regardless of the selected theme variant\n const base =\n mode === \"light\"\n ? astroThemeLight\n : theme === \"astro\"\n ? astroThemeDark\n : theme === \"hybrid\"\n ? hybridTheme\n : theme === \"nebula\"\n ? nebulaTheme\n : theme === \"transparent\" || theme === \"transparent-bold\"\n ? transparentTheme\n : theme === \"transparent-minimal\"\n ? transparentMinimalTheme\n : purpleHueTheme;\n const isTransparent =\n theme === \"transparent\" ||\n theme === \"transparent-bold\" ||\n theme === \"transparent-minimal\";\n const isMinimal = theme === \"transparent-minimal\";\n\n // For transparent-minimal: use visible accent-colored borders.\n // For other transparent themes: use subtle fadedBoxShadow.\n // Accent overrides are intentionally scoped to transparent variants only,\n // so switching to Astro/Hybrid/Purple-Hue restores each theme's native accent.\n const resolvedAccent =\n isTransparent && accentColor\n ? {\n primary: accentColor,\n secondary: adjustHexBrightness(accentColor, 1.2),\n tertiary: adjustHexBrightness(accentColor, 0.85),\n }\n : base.colors.accent;\n\n const accentPrimary = resolvedAccent.primary;\n const accentBorderMuted = `${accentPrimary}40`; // 25% opacity\n const accentBorderDefault = `${accentPrimary}66`; // 40% opacity\n const accentFadedBoxShadow = `inset 0 1px 0 ${hexToRgba(accentPrimary, 0.12)}, inset 0 -1px 0 rgba(0, 0, 0, 0.05), inset 1px 0 0 ${hexToRgba(accentPrimary, 0.06)}, inset -1px 0 0 ${hexToRgba(accentPrimary, 0.06)}`;\n\n const cardStyle = isMinimal\n ? { border: `1px solid ${accentBorderMuted}` }\n : isTransparent\n ? { border: \"none\" as const, boxShadow: accentFadedBoxShadow }\n : { border: `1px solid ${base.colors.border.muted}` };\n const cardStyleDashed = isMinimal\n ? { border: `1px dashed ${accentBorderMuted}` }\n : isTransparent\n ? { border: \"none\" as const, boxShadow: accentFadedBoxShadow }\n : { border: `1px dashed ${base.colors.border.muted}` };\n const cardStyleDefault = isMinimal\n ? { border: `1px solid ${accentBorderDefault}` }\n : isTransparent\n ? { border: \"none\" as const, boxShadow: accentFadedBoxShadow }\n : { border: `1px solid ${base.colors.border.default}` };\n // Compute border tokens from the resolved theme colors\n const resolvedColors: ThemeColors = {\n ...base.colors,\n accent: resolvedAccent,\n border: {\n ...base.colors.border,\n cardStyle,\n cardStyleDashed,\n cardStyleDefault,\n },\n };\n\n return {\n ...base,\n colors: resolvedColors,\n borders: computeBorderTokens(resolvedColors),\n } as ThemeTokens;\n }, [theme, mode, accentColor]);\n\n // Apply CSS variables to document\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n\n const root = document.documentElement;\n const {\n colors,\n typography,\n spacing,\n borderRadius,\n animation,\n focus,\n shadows,\n } = tokens;\n\n // Theme mode attribute\n root.setAttribute(\"data-theme\", mode);\n root.setAttribute(\"data-variant\", theme);\n\n // Background colors\n root.style.setProperty(\"--color-background-base\", colors.background.base);\n root.style.setProperty(\n \"--color-background-surface\",\n colors.background.surface,\n );\n root.style.setProperty(\n \"--color-background-elevated\",\n colors.background.elevated,\n );\n root.style.setProperty(\n \"--color-background-overlay\",\n colors.background.overlay,\n );\n\n // Border colors\n root.style.setProperty(\"--color-border\", colors.border.default);\n root.style.setProperty(\"--color-border-muted\", colors.border.muted);\n root.style.setProperty(\"--color-border-focus\", colors.border.focus);\n\n // Text colors\n root.style.setProperty(\"--color-text-primary\", colors.text.primary);\n root.style.setProperty(\"--color-text-secondary\", colors.text.secondary);\n root.style.setProperty(\"--color-text-tertiary\", colors.text.tertiary);\n root.style.setProperty(\"--color-text-inverse\", colors.text.inverse);\n\n // Status colors\n root.style.setProperty(\"--color-status-normal\", colors.status.normal);\n root.style.setProperty(\"--color-status-standby\", colors.status.standby);\n root.style.setProperty(\"--color-status-caution\", colors.status.caution);\n root.style.setProperty(\"--color-status-serious\", colors.status.serious);\n root.style.setProperty(\"--color-status-critical\", colors.status.critical);\n root.style.setProperty(\"--color-status-off\", colors.status.off);\n\n // Interactive colors\n root.style.setProperty(\n \"--color-interactive-default\",\n colors.interactive.default,\n );\n root.style.setProperty(\n \"--color-interactive-hover\",\n colors.interactive.hover,\n );\n root.style.setProperty(\n \"--color-interactive-active\",\n colors.interactive.active,\n );\n root.style.setProperty(\n \"--color-interactive-disabled\",\n colors.interactive.disabled,\n );\n\n // Accent colors\n root.style.setProperty(\"--color-accent-primary\", colors.accent.primary);\n root.style.setProperty(\"--color-accent-secondary\", colors.accent.secondary);\n root.style.setProperty(\"--color-accent-tertiary\", colors.accent.tertiary);\n\n // Typography\n root.style.setProperty(\n \"--font-family-heading\",\n typography.fontFamily.heading || typography.fontFamily.primary,\n );\n root.style.setProperty(\n \"--font-family-primary\",\n typography.fontFamily.primary,\n );\n root.style.setProperty(\"--font-family-mono\", typography.fontFamily.mono);\n\n // Spacing (full scale so CSS and layout can pull from SDK)\n root.style.setProperty(\"--spacing-xxs\", spacing.xxs);\n root.style.setProperty(\"--spacing-xs\", spacing.xs);\n root.style.setProperty(\"--spacing-sm\", spacing.sm);\n root.style.setProperty(\"--spacing-smd\", spacing.smd);\n root.style.setProperty(\"--spacing-md\", spacing.md);\n root.style.setProperty(\"--spacing-mdl\", spacing.mdl);\n root.style.setProperty(\"--spacing-lg\", spacing.lg);\n root.style.setProperty(\"--spacing-xl\", spacing.xl);\n root.style.setProperty(\"--spacing-xxl\", spacing.xxl);\n\n // Border radius\n root.style.setProperty(\"--radius-sm\", borderRadius.sm);\n root.style.setProperty(\"--radius-md\", borderRadius.md);\n root.style.setProperty(\"--radius-lg\", borderRadius.lg);\n root.style.setProperty(\"--radius-xl\", borderRadius.xl);\n\n // Animation (respect reduced motion)\n const motionMultiplier = prefersReducedMotion ? 0 : 1;\n root.style.setProperty(\n \"--animation-fast\",\n prefersReducedMotion ? \"none\" : animation.fast,\n );\n root.style.setProperty(\n \"--animation-normal\",\n prefersReducedMotion ? \"none\" : animation.normal,\n );\n root.style.setProperty(\n \"--animation-slow\",\n prefersReducedMotion ? \"none\" : animation.slow,\n );\n root.style.setProperty(\n \"--duration-fast\",\n `${animation.duration.fast * motionMultiplier}ms`,\n );\n root.style.setProperty(\n \"--duration-normal\",\n `${animation.duration.normal * motionMultiplier}ms`,\n );\n root.style.setProperty(\n \"--duration-slow\",\n `${animation.duration.slow * motionMultiplier}ms`,\n );\n\n // Focus\n root.style.setProperty(\"--focus-ring-color\", focus.color);\n root.style.setProperty(\"--focus-ring-width\", focus.width);\n root.style.setProperty(\"--focus-ring-offset\", focus.offset);\n\n // Shadows\n root.style.setProperty(\"--shadow-sm\", shadows.sm);\n root.style.setProperty(\"--shadow-md\", shadows.md);\n root.style.setProperty(\"--shadow-lg\", shadows.lg);\n root.style.setProperty(\"--shadow-xl\", shadows.xl);\n\n // Apply base styles to body\n document.body.style.backgroundColor = colors.background.base;\n document.body.style.color = colors.text.primary;\n document.body.style.fontFamily = typography.fontFamily.primary;\n }, [tokens, theme, mode, prefersReducedMotion]);\n\n // ──────────────────────────────────────────────────────────────────────────\n // Scrollbar Styles — thin, elegant, theme-connected, fully accessible\n // ──────────────────────────────────────────────────────────────────────────\n const scrollbarCSS = useMemo(\n () => buildScrollbarCSS(tokens, mode, prefersReducedMotion),\n [tokens, mode, prefersReducedMotion],\n );\n\n const value: ThemeContextValue = useMemo(\n () => ({\n theme,\n mode,\n tokens,\n prefersReducedMotion,\n accentColor,\n setTheme,\n setMode,\n toggleMode,\n setAccentColor,\n }),\n [theme, mode, tokens, prefersReducedMotion, accentColor],\n );\n\n // Global reduced-motion rule — ensures ALL animations and transitions across\n // every Zendir component respect the OS preference (WCAG 2.3.3).\n // Uses 0.01ms (not 0) so animation-end events still fire correctly.\n const reducedMotionCSS = `@media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n scroll-behavior: auto !important;\n }\n}`;\n\n return (\n <ThemeContext.Provider value={value}>\n <style\n data-zendir-scrollbar=\"\"\n dangerouslySetInnerHTML={{ __html: scrollbarCSS }}\n />\n <style\n data-zendir-reduced-motion=\"\"\n dangerouslySetInnerHTML={{ __html: reducedMotionCSS }}\n />\n {children}\n </ThemeContext.Provider>\n );\n}\n\n/**\n * useTheme - Access theme context\n *\n * @throws Error if used outside ThemeProvider\n *\n * @example\n * ```tsx\n * const { tokens, mode, toggleMode } = useTheme();\n * ```\n */\nexport function useTheme(): ThemeContextValue {\n const context = useContext(ThemeContext);\n if (!context) {\n throw new Error(\"useTheme must be used within a ThemeProvider\");\n }\n return context;\n}\n\n/**\n * useThemeTokens - Access only theme tokens (for memoization)\n */\nexport function useThemeTokens(): ThemeTokens {\n return useTheme().tokens;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ThemeProvider.js","names":[],"sources":["../../../src/react/theme/ThemeProvider.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - Theme Provider\r\n *\r\n * Enterprise-grade theming system supporting Astro UX Design System and Zendir themes.\r\n * Full Tier 3 compliance with official Astro tokens (dark + light themes).\r\n *\r\n * Features:\r\n * - Multiple theme variants (Zen Hybrid default, astro, purple-hue, transparent)\r\n * - Light and dark modes\r\n * - Animation tokens with reduced-motion support\r\n * - Focus ring styles for accessibility\r\n * - CSS variable injection\r\n * - Local storage persistence\r\n */\r\n\r\nimport React, {\r\n createContext,\r\n useContext,\r\n useState,\r\n useEffect,\r\n useMemo,\r\n ReactNode,\r\n} from \"react\";\r\nimport {\r\n astroThemeDark,\r\n astroThemeLight,\r\n hybridTheme,\r\n purpleHueTheme,\r\n nebulaTheme,\r\n transparentTheme,\r\n transparentMinimalTheme,\r\n} from \"../../tokens/themes\";\r\nimport {\r\n computeBorderTokens,\r\n type ThemeVariant,\r\n type ThemeMode,\r\n type ThemeColors,\r\n type ThemeTokens,\r\n type ThemeAnimation,\r\n type ThemeFocus,\r\n type LayoutTokens,\r\n type BorderTokens,\r\n} from \"./types\";\r\n\r\n// Theme type shapes and derived-token helpers live in ./types so the token\r\n// build (which imports the theme definitions) never pulls in this provider.\r\n// Re-exported here to preserve the long-standing `@zendir/ui` import surface.\r\nexport type {\r\n ThemeVariant,\r\n ThemeMode,\r\n ThemeColors,\r\n ThemeTokens,\r\n ThemeAnimation,\r\n ThemeFocus,\r\n LayoutTokens,\r\n BorderTokens,\r\n};\r\n\r\nfunction adjustHexBrightness(hex: string, factor: number): string {\r\n const normalized = hex.replace(\"#\", \"\").trim();\r\n const expanded =\r\n normalized.length === 3\r\n ? normalized\r\n .split(\"\")\r\n .map((ch) => ch + ch)\r\n .join(\"\")\r\n : normalized;\r\n\r\n if (!/^[0-9a-fA-F]{6}$/.test(expanded)) {\r\n return hex;\r\n }\r\n\r\n const clamp = (value: number) =>\r\n Math.max(0, Math.min(255, Math.round(value)));\r\n const r = clamp(parseInt(expanded.slice(0, 2), 16) * factor);\r\n const g = clamp(parseInt(expanded.slice(2, 4), 16) * factor);\r\n const b = clamp(parseInt(expanded.slice(4, 6), 16) * factor);\r\n\r\n const toHex = (value: number) => value.toString(16).padStart(2, \"0\");\r\n return `#${toHex(r)}${toHex(g)}${toHex(b)}`;\r\n}\r\n\r\n// ============================================================================\r\n// Theme Context\r\n// ============================================================================\r\n\r\nexport interface ThemeContextValue {\r\n theme: ThemeVariant;\r\n mode: ThemeMode;\r\n tokens: ThemeTokens;\r\n prefersReducedMotion: boolean;\r\n /** Custom accent color override (null = use theme default) */\r\n accentColor: string | null;\r\n setTheme: (theme: ThemeVariant) => void;\r\n setMode: (mode: ThemeMode) => void;\r\n toggleMode: () => void;\r\n /** Override the accent color for the current theme */\r\n setAccentColor: (color: string | null) => void;\r\n}\r\n\r\nconst ThemeContext = createContext<ThemeContextValue | undefined>(undefined);\r\n\r\nexport interface ThemeProviderProps {\r\n children: ReactNode;\r\n /** Default theme variant */\r\n theme?: ThemeVariant;\r\n /** Alias for `theme` */\r\n defaultVariant?: ThemeVariant;\r\n /** Default color mode */\r\n mode?: ThemeMode;\r\n /** Alias for `mode` */\r\n defaultMode?: ThemeMode;\r\n /** Persist preferences to localStorage */\r\n persist?: boolean;\r\n}\r\n\r\n// ============================================================================\r\n// Scrollbar Styling System\r\n// ============================================================================\r\n// Generates global CSS for thin, elegant, theme-aware scrollbars.\r\n//\r\n// Features:\r\n// - Ultra-thin (6px) scrollbar tracks with soft border radius\r\n// - Accent-colored thumb with hover/active brightness shifts\r\n// - Semi-transparent track that blends into the surface\r\n// - Auto-hide behaviour on modern browsers (overlay mode)\r\n// - Firefox support via scrollbar-width / scrollbar-color\r\n// - WCAG 2.1 AA compliant: sufficient contrast, respects reduced-motion\r\n// - Smooth transitions (disabled when prefers-reduced-motion)\r\n// - Scoped .zendir-scroll utility class for individual containers\r\n// ============================================================================\r\n\r\n/**\r\n * Converts a hex color to an rgba string helper.\r\n * Accepts 3/6/8-char hex codes (#rgb, #rrggbb, #rrggbbaa).\r\n */\r\nfunction hexToRgba(hex: string, alpha: number): string {\r\n const clean = hex.replace(\"#\", \"\");\r\n let r: number, g: number, b: number;\r\n if (clean.length === 3) {\r\n r = parseInt(clean[0] + clean[0], 16);\r\n g = parseInt(clean[1] + clean[1], 16);\r\n b = parseInt(clean[2] + clean[2], 16);\r\n } else {\r\n r = parseInt(clean.slice(0, 2), 16);\r\n g = parseInt(clean.slice(2, 4), 16);\r\n b = parseInt(clean.slice(4, 6), 16);\r\n }\r\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\r\n}\r\n\r\nfunction buildScrollbarCSS(\r\n tokens: ThemeTokens,\r\n mode: ThemeMode,\r\n reducedMotion: boolean,\r\n): string {\r\n const accent = tokens.colors.accent.primary;\r\n const _surface = tokens.colors.background.surface; // eslint-disable-line @typescript-eslint/no-unused-vars -- reserved for future scrollbar track customisation\r\n const _base = tokens.colors.background.base; // eslint-disable-line @typescript-eslint/no-unused-vars -- reserved for future scrollbar corner styling\r\n const isLight = mode === \"light\";\r\n\r\n // Thumb colors: accent-tinted, low opacity at rest, stronger on hover/active\r\n const thumbRest = isLight ? hexToRgba(accent, 0.28) : hexToRgba(accent, 0.35);\r\n const thumbHover = isLight\r\n ? hexToRgba(accent, 0.48)\r\n : hexToRgba(accent, 0.55);\r\n const thumbActive = isLight\r\n ? hexToRgba(accent, 0.64)\r\n : hexToRgba(accent, 0.72);\r\n\r\n // Track: nearly invisible surface blend\r\n const trackColor = isLight\r\n ? hexToRgba(accent, 0.04)\r\n : hexToRgba(accent, 0.06);\r\n const trackHover = isLight ? hexToRgba(accent, 0.08) : hexToRgba(accent, 0.1);\r\n\r\n const transition = reducedMotion\r\n ? \"none\"\r\n : \"background-color 200ms ease, width 200ms ease, opacity 200ms ease\";\r\n const thumbRadius = \"9999px\"; // pill shape\r\n\r\n // Firefox colors (two-value syntax: thumb track)\r\n const ffThumb = thumbRest;\r\n const ffTrack = trackColor;\r\n\r\n return `\r\n/* ═══════════════════════════════════════════════════════════════════════════\r\n Zendir Scrollbar System — Thin · Elegant · Theme-Connected\r\n ═══════════════════════════════════════════════════════════════════════════ */\r\n\r\n/* ── Firefox ────────────────────────────────────────────────────────────── */\r\n*,\r\n.zendir-scroll {\r\n scrollbar-width: thin;\r\n scrollbar-color: ${ffThumb} ${ffTrack};\r\n}\r\n\r\n/* ── Webkit / Blink (Chrome, Edge, Safari, Opera) ───────────────────────── */\r\n\r\n/* Global thin scrollbar */\r\n::-webkit-scrollbar {\r\n width: 6px;\r\n height: 6px;\r\n background: transparent;\r\n}\r\n\r\n/* Track — nearly invisible, subtle on hover */\r\n::-webkit-scrollbar-track {\r\n background: ${trackColor};\r\n border-radius: ${thumbRadius};\r\n margin: 2px;\r\n}\r\n::-webkit-scrollbar-track:hover {\r\n background: ${trackHover};\r\n}\r\n\r\n/* Thumb — accent-tinted pill */\r\n::-webkit-scrollbar-thumb {\r\n background: ${thumbRest};\r\n border-radius: ${thumbRadius};\r\n border: 1px solid transparent;\r\n background-clip: padding-box;\r\n transition: ${transition};\r\n min-height: 32px;\r\n}\r\n::-webkit-scrollbar-thumb:hover {\r\n background: ${thumbHover};\r\n border-color: transparent;\r\n background-clip: padding-box;\r\n}\r\n::-webkit-scrollbar-thumb:active {\r\n background: ${thumbActive};\r\n border-color: transparent;\r\n background-clip: padding-box;\r\n}\r\n\r\n/* Corner — where horizontal & vertical scrollbars meet */\r\n::-webkit-scrollbar-corner {\r\n background: transparent;\r\n}\r\n\r\n/* ── Utility class: zendir-scroll ────────────────────────────────────────\r\n Apply to individual containers for scoped thin scrollbar.\r\n Also enables overlay behaviour where supported.\r\n e.g. <div className=\"zendir-scroll\" style={{overflow:'auto'}}>…</div>\r\n ─────────────────────────────────────────────────────────────────────── */\r\n.zendir-scroll {\r\n overflow: auto;\r\n overflow: overlay; /* Chrome / Edge: auto-hide scrollbar */\r\n}\r\n.zendir-scroll::-webkit-scrollbar {\r\n width: 5px;\r\n height: 5px;\r\n}\r\n.zendir-scroll::-webkit-scrollbar-track {\r\n background: transparent;\r\n border-radius: ${thumbRadius};\r\n}\r\n.zendir-scroll::-webkit-scrollbar-thumb {\r\n background: ${thumbRest};\r\n border-radius: ${thumbRadius};\r\n transition: ${transition};\r\n}\r\n.zendir-scroll::-webkit-scrollbar-thumb:hover {\r\n background: ${thumbHover};\r\n}\r\n.zendir-scroll::-webkit-scrollbar-thumb:active {\r\n background: ${thumbActive};\r\n}\r\n\r\n/* ── Utility: zendir-scroll--hidden ─────────────────────────────────────\r\n Hides scrollbar visually but keeps scroll behaviour (accessible).\r\n Content remains scrollable via mouse wheel / touch / keyboard.\r\n ─────────────────────────────────────────────────────────────────────── */\r\n.zendir-scroll--hidden {\r\n overflow: auto;\r\n -ms-overflow-style: none;\r\n scrollbar-width: none;\r\n}\r\n.zendir-scroll--hidden::-webkit-scrollbar {\r\n display: none;\r\n}\r\n\r\n/* ── Focus-visible: keyboard users can still scroll with focus ─────────── */\r\n.zendir-scroll:focus-visible {\r\n outline: 2px solid ${accent};\r\n outline-offset: -2px;\r\n border-radius: ${tokens.borderRadius.md};\r\n}\r\n\r\n/* ── Reduced Motion: disable scrollbar transitions ─────────────────────── */\r\n@media (prefers-reduced-motion: reduce) {\r\n ::-webkit-scrollbar-thumb,\r\n .zendir-scroll::-webkit-scrollbar-thumb {\r\n transition: none !important;\r\n }\r\n}\r\n\r\n/* ── High Contrast Mode: ensure scrollbar is visible ───────────────────── */\r\n@media (forced-colors: active) {\r\n ::-webkit-scrollbar-thumb {\r\n background: ButtonText !important;\r\n }\r\n ::-webkit-scrollbar-track {\r\n background: Canvas !important;\r\n }\r\n}\r\n`.trim();\r\n}\r\n\r\n/**\r\n * useScrollbarStyles — returns inline React.CSSProperties for a\r\n * scrollable container that matches the current Zendir theme.\r\n *\r\n * Use when you need programmatic control rather than the\r\n * `zendir-scroll` CSS class.\r\n *\r\n * @example\r\n * ```tsx\r\n * const scrollStyle = useScrollbarStyles();\r\n * <div style={{ maxHeight: 400, ...scrollStyle }}>…</div>\r\n * ```\r\n */\r\nexport function useScrollbarStyles(): React.CSSProperties {\r\n const { tokens, mode } = useTheme();\r\n return useMemo((): React.CSSProperties => {\r\n const accent = tokens.colors.accent.primary;\r\n const isLight = mode === \"light\";\r\n const thumbColor = isLight\r\n ? hexToRgba(accent, 0.28)\r\n : hexToRgba(accent, 0.35);\r\n const trackColor = isLight\r\n ? hexToRgba(accent, 0.04)\r\n : hexToRgba(accent, 0.06);\r\n return {\r\n // Firefox\r\n scrollbarWidth: \"thin\" as const,\r\n scrollbarColor: `${thumbColor} ${trackColor}`,\r\n overflow: \"auto\",\r\n };\r\n }, [tokens, mode]);\r\n}\r\n\r\n/**\r\n * ThemeProvider - Enterprise theme context provider\r\n *\r\n * Provides theme tokens, mode switching, and accessibility features\r\n * to all child components.\r\n *\r\n * @example\r\n * ```tsx\r\n * <ThemeProvider theme=\"hybrid\" mode=\"dark\"> // Zen (Hybrid) is the default\r\n * <App />\r\n * </ThemeProvider>\r\n * ```\r\n */\r\nexport function ThemeProvider({\r\n children,\r\n theme: themeProp,\r\n defaultVariant,\r\n mode: modeProp,\r\n defaultMode: defaultModeProp,\r\n persist = true,\r\n}: ThemeProviderProps): React.ReactElement {\r\n const defaultTheme = themeProp ?? defaultVariant ?? \"hybrid\";\r\n const defaultMode = modeProp ?? defaultModeProp ?? \"dark\";\r\n // Initialize theme from localStorage or defaults\r\n const [theme, setThemeState] = useState<ThemeVariant>(() => {\r\n if (persist && typeof window !== \"undefined\") {\r\n const saved = localStorage.getItem(\"zendir-ui-theme\");\r\n if (\r\n saved === \"astro\" ||\r\n saved === \"zendir\" ||\r\n saved === \"hybrid\" ||\r\n saved === \"nebula\" ||\r\n saved === \"purple-hue\" ||\r\n saved === \"transparent\" ||\r\n saved === \"transparent-bold\" ||\r\n saved === \"transparent-minimal\"\r\n ) {\r\n return saved as ThemeVariant;\r\n }\r\n }\r\n return defaultTheme;\r\n });\r\n\r\n const [mode, setModeState] = useState<ThemeMode>(() => {\r\n if (persist && typeof window !== \"undefined\") {\r\n const saved = localStorage.getItem(\"zendir-ui-mode\");\r\n if (saved === \"light\" || saved === \"dark\") {\r\n return saved;\r\n }\r\n }\r\n return defaultMode;\r\n });\r\n\r\n // Detect reduced motion preference\r\n const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);\r\n\r\n useEffect(() => {\r\n if (typeof window === \"undefined\") return;\r\n\r\n const mediaQuery = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\r\n setPrefersReducedMotion(mediaQuery.matches);\r\n\r\n const handler = (e: MediaQueryListEvent) =>\r\n setPrefersReducedMotion(e.matches);\r\n mediaQuery.addEventListener(\"change\", handler);\r\n return () => mediaQuery.removeEventListener(\"change\", handler);\r\n }, []);\r\n\r\n // Theme setters\r\n const setTheme = (newTheme: ThemeVariant) => {\r\n setThemeState(newTheme);\r\n if (persist && typeof window !== \"undefined\") {\r\n localStorage.setItem(\"zendir-ui-theme\", newTheme);\r\n }\r\n };\r\n\r\n const setMode = (newMode: ThemeMode) => {\r\n setModeState(newMode);\r\n if (persist && typeof window !== \"undefined\") {\r\n localStorage.setItem(\"zendir-ui-mode\", newMode);\r\n }\r\n };\r\n\r\n const toggleMode = () => {\r\n setMode(mode === \"dark\" ? \"light\" : \"dark\");\r\n };\r\n\r\n // Accent color override\r\n const [accentColor, setAccentColorState] = useState<string | null>(() => {\r\n if (persist && typeof window !== \"undefined\") {\r\n return localStorage.getItem(\"zendir-ui-accent\") || null;\r\n }\r\n return null;\r\n });\r\n\r\n const setAccentColor = (color: string | null) => {\r\n setAccentColorState(color);\r\n if (persist && typeof window !== \"undefined\") {\r\n if (color) {\r\n localStorage.setItem(\"zendir-ui-accent\", color);\r\n } else {\r\n localStorage.removeItem(\"zendir-ui-accent\");\r\n }\r\n }\r\n };\r\n\r\n // Get theme tokens (card border styles computed so all cards use theme-driven borders)\r\n const tokens = useMemo((): ThemeTokens => {\r\n // If light mode is selected, always use Astro Light theme (unified light mode)\r\n // regardless of the selected theme variant\r\n const base =\r\n mode === \"light\"\r\n ? astroThemeLight\r\n : theme === \"astro\"\r\n ? astroThemeDark\r\n : theme === \"hybrid\"\r\n ? hybridTheme\r\n : theme === \"nebula\"\r\n ? nebulaTheme\r\n : theme === \"transparent\" || theme === \"transparent-bold\"\r\n ? transparentTheme\r\n : theme === \"transparent-minimal\"\r\n ? transparentMinimalTheme\r\n : purpleHueTheme;\r\n const isTransparent =\r\n theme === \"transparent\" ||\r\n theme === \"transparent-bold\" ||\r\n theme === \"transparent-minimal\";\r\n const isMinimal = theme === \"transparent-minimal\";\r\n\r\n // For transparent-minimal: use visible accent-colored borders.\r\n // For other transparent themes: use subtle fadedBoxShadow.\r\n // Accent overrides are intentionally scoped to transparent variants only,\r\n // so switching to Astro/Hybrid/Purple-Hue restores each theme's native accent.\r\n const resolvedAccent =\r\n isTransparent && accentColor\r\n ? {\r\n primary: accentColor,\r\n secondary: adjustHexBrightness(accentColor, 1.2),\r\n tertiary: adjustHexBrightness(accentColor, 0.85),\r\n }\r\n : base.colors.accent;\r\n\r\n const accentPrimary = resolvedAccent.primary;\r\n const accentBorderMuted = `${accentPrimary}40`; // 25% opacity\r\n const accentBorderDefault = `${accentPrimary}66`; // 40% opacity\r\n const accentFadedBoxShadow = `inset 0 1px 0 ${hexToRgba(accentPrimary, 0.12)}, inset 0 -1px 0 rgba(0, 0, 0, 0.05), inset 1px 0 0 ${hexToRgba(accentPrimary, 0.06)}, inset -1px 0 0 ${hexToRgba(accentPrimary, 0.06)}`;\r\n\r\n const cardStyle = isMinimal\r\n ? { border: `1px solid ${accentBorderMuted}` }\r\n : isTransparent\r\n ? { border: \"none\" as const, boxShadow: accentFadedBoxShadow }\r\n : { border: `1px solid ${base.colors.border.muted}` };\r\n const cardStyleDashed = isMinimal\r\n ? { border: `1px dashed ${accentBorderMuted}` }\r\n : isTransparent\r\n ? { border: \"none\" as const, boxShadow: accentFadedBoxShadow }\r\n : { border: `1px dashed ${base.colors.border.muted}` };\r\n const cardStyleDefault = isMinimal\r\n ? { border: `1px solid ${accentBorderDefault}` }\r\n : isTransparent\r\n ? { border: \"none\" as const, boxShadow: accentFadedBoxShadow }\r\n : { border: `1px solid ${base.colors.border.default}` };\r\n // Compute border tokens from the resolved theme colors\r\n const resolvedColors: ThemeColors = {\r\n ...base.colors,\r\n accent: resolvedAccent,\r\n border: {\r\n ...base.colors.border,\r\n cardStyle,\r\n cardStyleDashed,\r\n cardStyleDefault,\r\n },\r\n };\r\n\r\n return {\r\n ...base,\r\n colors: resolvedColors,\r\n borders: computeBorderTokens(resolvedColors),\r\n } as ThemeTokens;\r\n }, [theme, mode, accentColor]);\r\n\r\n // Apply CSS variables to document\r\n useEffect(() => {\r\n if (typeof document === \"undefined\") return;\r\n\r\n const root = document.documentElement;\r\n const {\r\n colors,\r\n typography,\r\n spacing,\r\n borderRadius,\r\n animation,\r\n focus,\r\n shadows,\r\n } = tokens;\r\n\r\n // Theme attributes. tokens.css keys its variable blocks on the VARIANT\r\n // via [data-theme=\"<variant>\"] and the mode via [data-mode=\"light\"], so\r\n // data-theme must carry the variant and data-mode the light/dark mode —\r\n // otherwise the static --zendir-* CSS variables never leave the default\r\n // (hybrid) block. data-variant is retained for backward compatibility.\r\n root.setAttribute(\"data-theme\", theme);\r\n root.setAttribute(\"data-mode\", mode);\r\n root.setAttribute(\"data-variant\", theme);\r\n\r\n // Background colors\r\n root.style.setProperty(\"--color-background-base\", colors.background.base);\r\n root.style.setProperty(\r\n \"--color-background-surface\",\r\n colors.background.surface,\r\n );\r\n root.style.setProperty(\r\n \"--color-background-elevated\",\r\n colors.background.elevated,\r\n );\r\n root.style.setProperty(\r\n \"--color-background-overlay\",\r\n colors.background.overlay,\r\n );\r\n\r\n // Border colors\r\n root.style.setProperty(\"--color-border\", colors.border.default);\r\n root.style.setProperty(\"--color-border-muted\", colors.border.muted);\r\n root.style.setProperty(\"--color-border-focus\", colors.border.focus);\r\n\r\n // Text colors\r\n root.style.setProperty(\"--color-text-primary\", colors.text.primary);\r\n root.style.setProperty(\"--color-text-secondary\", colors.text.secondary);\r\n root.style.setProperty(\"--color-text-tertiary\", colors.text.tertiary);\r\n root.style.setProperty(\"--color-text-inverse\", colors.text.inverse);\r\n\r\n // Status colors\r\n root.style.setProperty(\"--color-status-normal\", colors.status.normal);\r\n root.style.setProperty(\"--color-status-standby\", colors.status.standby);\r\n root.style.setProperty(\"--color-status-caution\", colors.status.caution);\r\n root.style.setProperty(\"--color-status-serious\", colors.status.serious);\r\n root.style.setProperty(\"--color-status-critical\", colors.status.critical);\r\n root.style.setProperty(\"--color-status-off\", colors.status.off);\r\n\r\n // Interactive colors\r\n root.style.setProperty(\r\n \"--color-interactive-default\",\r\n colors.interactive.default,\r\n );\r\n root.style.setProperty(\r\n \"--color-interactive-hover\",\r\n colors.interactive.hover,\r\n );\r\n root.style.setProperty(\r\n \"--color-interactive-active\",\r\n colors.interactive.active,\r\n );\r\n root.style.setProperty(\r\n \"--color-interactive-disabled\",\r\n colors.interactive.disabled,\r\n );\r\n\r\n // Accent colors\r\n root.style.setProperty(\"--color-accent-primary\", colors.accent.primary);\r\n root.style.setProperty(\"--color-accent-secondary\", colors.accent.secondary);\r\n root.style.setProperty(\"--color-accent-tertiary\", colors.accent.tertiary);\r\n\r\n // Typography\r\n root.style.setProperty(\r\n \"--font-family-heading\",\r\n typography.fontFamily.heading || typography.fontFamily.primary,\r\n );\r\n root.style.setProperty(\r\n \"--font-family-primary\",\r\n typography.fontFamily.primary,\r\n );\r\n root.style.setProperty(\"--font-family-mono\", typography.fontFamily.mono);\r\n\r\n // Spacing (full scale so CSS and layout can pull from SDK)\r\n root.style.setProperty(\"--spacing-xxs\", spacing.xxs);\r\n root.style.setProperty(\"--spacing-xs\", spacing.xs);\r\n root.style.setProperty(\"--spacing-sm\", spacing.sm);\r\n root.style.setProperty(\"--spacing-smd\", spacing.smd);\r\n root.style.setProperty(\"--spacing-md\", spacing.md);\r\n root.style.setProperty(\"--spacing-mdl\", spacing.mdl);\r\n root.style.setProperty(\"--spacing-lg\", spacing.lg);\r\n root.style.setProperty(\"--spacing-xl\", spacing.xl);\r\n root.style.setProperty(\"--spacing-xxl\", spacing.xxl);\r\n\r\n // Border radius\r\n root.style.setProperty(\"--radius-sm\", borderRadius.sm);\r\n root.style.setProperty(\"--radius-md\", borderRadius.md);\r\n root.style.setProperty(\"--radius-lg\", borderRadius.lg);\r\n root.style.setProperty(\"--radius-xl\", borderRadius.xl);\r\n\r\n // Animation (respect reduced motion)\r\n const motionMultiplier = prefersReducedMotion ? 0 : 1;\r\n root.style.setProperty(\r\n \"--animation-fast\",\r\n prefersReducedMotion ? \"none\" : animation.fast,\r\n );\r\n root.style.setProperty(\r\n \"--animation-normal\",\r\n prefersReducedMotion ? \"none\" : animation.normal,\r\n );\r\n root.style.setProperty(\r\n \"--animation-slow\",\r\n prefersReducedMotion ? \"none\" : animation.slow,\r\n );\r\n root.style.setProperty(\r\n \"--duration-fast\",\r\n `${animation.duration.fast * motionMultiplier}ms`,\r\n );\r\n root.style.setProperty(\r\n \"--duration-normal\",\r\n `${animation.duration.normal * motionMultiplier}ms`,\r\n );\r\n root.style.setProperty(\r\n \"--duration-slow\",\r\n `${animation.duration.slow * motionMultiplier}ms`,\r\n );\r\n\r\n // Focus\r\n root.style.setProperty(\"--focus-ring-color\", focus.color);\r\n root.style.setProperty(\"--focus-ring-width\", focus.width);\r\n root.style.setProperty(\"--focus-ring-offset\", focus.offset);\r\n\r\n // Shadows\r\n root.style.setProperty(\"--shadow-sm\", shadows.sm);\r\n root.style.setProperty(\"--shadow-md\", shadows.md);\r\n root.style.setProperty(\"--shadow-lg\", shadows.lg);\r\n root.style.setProperty(\"--shadow-xl\", shadows.xl);\r\n\r\n // Apply base styles to body\r\n document.body.style.backgroundColor = colors.background.base;\r\n document.body.style.color = colors.text.primary;\r\n document.body.style.fontFamily = typography.fontFamily.primary;\r\n }, [tokens, theme, mode, prefersReducedMotion]);\r\n\r\n // ──────────────────────────────────────────────────────────────────────────\r\n // Scrollbar Styles — thin, elegant, theme-connected, fully accessible\r\n // ──────────────────────────────────────────────────────────────────────────\r\n const scrollbarCSS = useMemo(\r\n () => buildScrollbarCSS(tokens, mode, prefersReducedMotion),\r\n [tokens, mode, prefersReducedMotion],\r\n );\r\n\r\n const value: ThemeContextValue = useMemo(\r\n () => ({\r\n theme,\r\n mode,\r\n tokens,\r\n prefersReducedMotion,\r\n accentColor,\r\n setTheme,\r\n setMode,\r\n toggleMode,\r\n setAccentColor,\r\n }),\r\n [theme, mode, tokens, prefersReducedMotion, accentColor],\r\n );\r\n\r\n // Global reduced-motion rule — ensures ALL animations and transitions across\r\n // every Zendir component respect the OS preference (WCAG 2.3.3).\r\n // Uses 0.01ms (not 0) so animation-end events still fire correctly.\r\n const reducedMotionCSS = `@media (prefers-reduced-motion: reduce) {\r\n *, *::before, *::after {\r\n animation-duration: 0.01ms !important;\r\n animation-iteration-count: 1 !important;\r\n transition-duration: 0.01ms !important;\r\n scroll-behavior: auto !important;\r\n }\r\n}`;\r\n\r\n return (\r\n <ThemeContext.Provider value={value}>\r\n <style\r\n data-zendir-scrollbar=\"\"\r\n dangerouslySetInnerHTML={{ __html: scrollbarCSS }}\r\n />\r\n <style\r\n data-zendir-reduced-motion=\"\"\r\n dangerouslySetInnerHTML={{ __html: reducedMotionCSS }}\r\n />\r\n {children}\r\n </ThemeContext.Provider>\r\n );\r\n}\r\n\r\n/**\r\n * useTheme - Access theme context\r\n *\r\n * @throws Error if used outside ThemeProvider\r\n *\r\n * @example\r\n * ```tsx\r\n * const { tokens, mode, toggleMode } = useTheme();\r\n * ```\r\n */\r\nexport function useTheme(): ThemeContextValue {\r\n const context = useContext(ThemeContext);\r\n if (!context) {\r\n throw new Error(\"useTheme must be used within a ThemeProvider\");\r\n }\r\n return context;\r\n}\r\n\r\n/**\r\n * useThemeTokens - Access only theme tokens (for memoization)\r\n */\r\nexport function useThemeTokens(): ThemeTokens {\r\n return useTheme().tokens;\r\n}\r\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"cardAccent.js","names":[],"sources":["../../../src/react/theme/cardAccent.tsx"],"sourcesContent":["/**\n * @zendir/ui - Card Accent System\n *\n * Provides accent color configuration for cards and containers in space operations UIs.\n * Maps space system domains to appropriate accent colors for visual organization.\n *\n * Features:\n * - Predefined accent colors for common use cases\n * - Space system keyword → color mapping for auto-coloring\n * - Mix mode for automatic per-domain coloring\n * - React context and hook for easy integration\n *\n * @example\n * ```tsx\n * import {\n * CardAccentProvider,\n * useCardAccent,\n * getSystemAccentColor\n * } from '@zendir/ui';\n *\n * // Wrap your app with the provider\n * <CardAccentProvider accentMode=\"mix\">\n * <MyApp />\n * </CardAccentProvider>\n *\n * // Use in components\n * const { getAccentColor } = useCardAccent();\n * <Container accentColor={getAccentColor('Power Systems')} />\n * ```\n */\n\nimport React, {\n createContext,\n useContext,\n useMemo,\n type ReactNode,\n} from \"react\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport type CardAccentKey =\n | \"cyan\"\n | \"electric\"\n | \"purple\"\n | \"teal\"\n | \"prussianBlue\"\n | \"green\"\n | \"amber\"\n | \"mix\";\n\nexport interface CardAccentColor {\n /** Display label */\n label: string;\n /** Hex color value (or 'mix' for automatic) */\n value: string;\n}\n\n// =============================================================================\n// Accent Color Definitions\n// =============================================================================\n\n/**\n * Predefined card accent colors\n * These are optimized for dark themes and space operations UIs\n */\nexport const CARD_ACCENT_COLORS: Record<CardAccentKey, CardAccentColor> = {\n cyan: { label: \"Cyan\", value: \"#00c8ff\" },\n electric: { label: \"Electric\", value: \"#4d7cff\" },\n purple: { label: \"Purple\", value: \"#a855f7\" },\n teal: { label: \"Teal\", value: \"#14b8a6\" },\n prussianBlue: { label: \"Prussian Blue\", value: \"#1e3a5f\" },\n green: { label: \"Green\", value: \"#22c55e\" },\n amber: { label: \"Amber\", value: \"#f59e0b\" },\n mix: { label: \"Mix (Per System)\", value: \"mix\" },\n} as const;\n\n/**\n * Space system keyword → accent color mapping\n * Used in 'mix' mode to automatically assign colors based on content\n */\nexport const SPACE_SYSTEM_COLORS: Record<string, string> = {\n // === Telemetry & Data ===\n telemetry: \"#00c8ff\",\n data: \"#00c8ff\",\n preview: \"#00c8ff\",\n stream: \"#00c8ff\",\n\n // === Power Systems ===\n power: \"#22c55e\",\n battery: \"#22c55e\",\n solar: \"#22c55e\",\n voltage: \"#22c55e\",\n energy: \"#22c55e\",\n\n // === Thermal Systems ===\n thermal: \"#f59e0b\",\n temperature: \"#f59e0b\",\n heatmap: \"#f59e0b\",\n heater: \"#f59e0b\",\n\n // === Attitude & Control ===\n attitude: \"#a855f7\",\n control: \"#a855f7\",\n orientation: \"#a855f7\",\n pointing: \"#a855f7\",\n gyro: \"#a855f7\",\n\n // === Communications ===\n spectrum: \"#4d7cff\",\n link: \"#4d7cff\",\n rf: \"#4d7cff\",\n downlink: \"#4d7cff\",\n uplink: \"#4d7cff\",\n packet: \"#4d7cff\",\n antenna: \"#4d7cff\",\n comm: \"#4d7cff\",\n\n // === Navigation & Orbit ===\n orbit: \"#14b8a6\",\n eclipse: \"#14b8a6\",\n polar: \"#14b8a6\",\n heliocentric: \"#14b8a6\",\n maneuver: \"#14b8a6\",\n contact: \"#14b8a6\",\n ground: \"#14b8a6\",\n pass: \"#14b8a6\",\n\n // === Sensors & Instruments ===\n sensor: \"#00c8ff\",\n star: \"#00c8ff\",\n tracker: \"#00c8ff\",\n scatter: \"#00c8ff\",\n payload: \"#00c8ff\",\n\n // === Health & Status ===\n health: \"#22c55e\",\n gauge: \"#22c55e\",\n subsystem: \"#22c55e\",\n status: \"#22c55e\",\n\n // === Analytics & Visualization ===\n sankey: \"#a855f7\",\n treemap: \"#4d7cff\",\n sunburst: \"#14b8a6\",\n parallel: \"#f59e0b\",\n radar: \"#00c8ff\",\n graph: \"#a855f7\",\n candlestick: \"#f59e0b\",\n histogram: \"#00c8ff\",\n pie: \"#a855f7\",\n donut: \"#a855f7\",\n bubble: \"#14b8a6\",\n rose: \"#f59e0b\",\n\n // === 3D Visualizations ===\n \"3d\": \"#4d7cff\",\n scatter3d: \"#00c8ff\",\n bar3d: \"#22c55e\",\n surface3d: \"#f59e0b\",\n lines3d: \"#a855f7\",\n debris: \"#f59e0b\",\n constellation: \"#4d7cff\",\n collision: \"#f59e0b\",\n satellite: \"#4d7cff\",\n\n // === Default ===\n default: \"#00c8ff\",\n} as const;\n\n// =============================================================================\n// Utility Functions\n// =============================================================================\n\n/**\n * Get accent color based on title/content and accent mode\n *\n * @param title - Content title or description to analyze\n * @param accentMode - Current accent mode (specific color or 'mix')\n * @returns Hex color string or undefined\n *\n * @example\n * ```tsx\n * getSystemAccentColor('Power Systems Overview', 'mix') // '#22c55e'\n * getSystemAccentColor('Power Systems', 'purple') // '#a855f7'\n * ```\n */\nexport function getSystemAccentColor(\n title: string,\n accentMode: CardAccentKey,\n): string | undefined {\n if (accentMode !== \"mix\") {\n return CARD_ACCENT_COLORS[accentMode].value;\n }\n\n const lowerTitle = title.toLowerCase();\n for (const [keyword, color] of Object.entries(SPACE_SYSTEM_COLORS)) {\n if (lowerTitle.includes(keyword)) {\n return color;\n }\n }\n return SPACE_SYSTEM_COLORS.default;\n}\n\n/**\n * Get all accent color options for building UI selectors\n */\nexport function getAccentColorOptions(): Array<{\n key: CardAccentKey;\n label: string;\n value: string;\n}> {\n return Object.entries(CARD_ACCENT_COLORS).map(([key, config]) => ({\n key: key as CardAccentKey,\n label: config.label,\n value: config.value,\n }));\n}\n\n// =============================================================================\n// React Context & Provider\n// =============================================================================\n\nexport interface CardAccentContextValue {\n /** Current accent mode */\n accentMode: CardAccentKey;\n /** Get accent color for a given title/content */\n getAccentColor: (title: string) => string | undefined;\n /** All available accent options */\n accentOptions: Array<{ key: CardAccentKey; label: string; value: string }>;\n}\n\nconst CardAccentContext = createContext<CardAccentContextValue>({\n accentMode: \"mix\",\n getAccentColor: () => undefined,\n accentOptions: getAccentColorOptions(),\n});\n\nexport interface CardAccentProviderProps {\n /** Accent mode to use */\n accentMode?: CardAccentKey;\n /** Children */\n children: ReactNode;\n}\n\n/**\n * Provider for card accent context\n * Wrap your app or section to provide consistent accent coloring\n */\nexport function CardAccentProvider({\n accentMode = \"mix\",\n children,\n}: CardAccentProviderProps): React.ReactElement {\n const value = useMemo(\n (): CardAccentContextValue => ({\n accentMode,\n getAccentColor: (title: string) =>\n getSystemAccentColor(title, accentMode),\n accentOptions: getAccentColorOptions(),\n }),\n [accentMode],\n );\n\n return (\n <CardAccentContext.Provider value={value}>\n {children}\n </CardAccentContext.Provider>\n );\n}\n\n/**\n * Hook to access card accent context\n *\n * @example\n * ```tsx\n * const { getAccentColor, accentMode } = useCardAccent();\n * <Container accentColor={getAccentColor('Power Chart')} />\n * ```\n */\nexport function useCardAccent(): CardAccentContextValue {\n return useContext(CardAccentContext);\n}\n\nexport default CardAccentProvider;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmEA,IAAa,qBAA6D;CACxE,MAAM;EAAE,OAAO;EAAQ,OAAO;CAAU;CACxC,UAAU;EAAE,OAAO;EAAY,OAAO;CAAU;CAChD,QAAQ;EAAE,OAAO;EAAU,OAAO;CAAU;CAC5C,MAAM;EAAE,OAAO;EAAQ,OAAO;CAAU;CACxC,cAAc;EAAE,OAAO;EAAiB,OAAO;CAAU;CACzD,OAAO;EAAE,OAAO;EAAS,OAAO;CAAU;CAC1C,OAAO;EAAE,OAAO;EAAS,OAAO;CAAU;CAC1C,KAAK;EAAE,OAAO;EAAoB,OAAO;CAAM;AACjD;;;;;AAMA,IAAa,sBAA8C;CAEzD,WAAW;CACX,MAAM;CACN,SAAS;CACT,QAAQ;CAGR,OAAO;CACP,SAAS;CACT,OAAO;CACP,SAAS;CACT,QAAQ;CAGR,SAAS;CACT,aAAa;CACb,SAAS;CACT,QAAQ;CAGR,UAAU;CACV,SAAS;CACT,aAAa;CACb,UAAU;CACV,MAAM;CAGN,UAAU;CACV,MAAM;CACN,IAAI;CACJ,UAAU;CACV,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,MAAM;CAGN,OAAO;CACP,SAAS;CACT,OAAO;CACP,cAAc;CACd,UAAU;CACV,SAAS;CACT,QAAQ;CACR,MAAM;CAGN,QAAQ;CACR,MAAM;CACN,SAAS;CACT,SAAS;CACT,SAAS;CAGT,QAAQ;CACR,OAAO;CACP,WAAW;CACX,QAAQ;CAGR,QAAQ;CACR,SAAS;CACT,UAAU;CACV,UAAU;CACV,OAAO;CACP,OAAO;CACP,aAAa;CACb,WAAW;CACX,KAAK;CACL,OAAO;CACP,QAAQ;CACR,MAAM;CAGN,MAAM;CACN,WAAW;CACX,OAAO;CACP,WAAW;CACX,SAAS;CACT,QAAQ;CACR,eAAe;CACf,WAAW;CACX,WAAW;CAGX,SAAS;AACX;;;;;;;;;;;;;;AAmBA,SAAgB,qBACd,OACA,YACoB;CACpB,IAAI,eAAe,OACjB,OAAO,mBAAmB,WAAW,CAAC;CAGxC,MAAM,aAAa,MAAM,YAAY;CACrC,KAAK,MAAM,CAAC,SAAS,UAAU,OAAO,QAAQ,mBAAmB,GAC/D,IAAI,WAAW,SAAS,OAAO,GAC7B,OAAO;CAGX,OAAO,oBAAoB;AAC7B;;;;AAKA,SAAgB,wBAIb;CACD,OAAO,OAAO,QAAQ,kBAAkB,CAAC,CAAC,KAAK,CAAC,KAAK,aAAa;EAC3D;EACL,OAAO,OAAO;EACd,OAAO,OAAO;CAChB,EAAE;AACJ;AAeA,IAAM,oBAAoB,cAAsC;CAC9D,YAAY;CACZ,sBAAsB,KAAA;CACtB,eAAe,sBAAsB;AACvC,CAAC;;;;;AAaD,SAAgB,mBAAmB,EACjC,aAAa,OACb,YAC8C;CAC9C,MAAM,QAAQ,eACmB;EAC7B;EACA,iBAAiB,UACf,qBAAqB,OAAO,UAAU;EACxC,eAAe,sBAAsB;CACvC,IACA,CAAC,UAAU,CACb;CAEA,OACE,oBAAC,kBAAkB,UAAnB;EAAmC;EAChC;CACyB,CAAA;AAEhC;;;;;;;;;;AAWA,SAAgB,gBAAwC;CACtD,OAAO,WAAW,iBAAiB;AACrC"}
1
+ {"version":3,"file":"cardAccent.js","names":[],"sources":["../../../src/react/theme/cardAccent.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - Card Accent System\r\n *\r\n * Provides accent color configuration for cards and containers in space operations UIs.\r\n * Maps space system domains to appropriate accent colors for visual organization.\r\n *\r\n * Features:\r\n * - Predefined accent colors for common use cases\r\n * - Space system keyword → color mapping for auto-coloring\r\n * - Mix mode for automatic per-domain coloring\r\n * - React context and hook for easy integration\r\n *\r\n * @example\r\n * ```tsx\r\n * import {\r\n * CardAccentProvider,\r\n * useCardAccent,\r\n * getSystemAccentColor\r\n * } from '@zendir/ui';\r\n *\r\n * // Wrap your app with the provider\r\n * <CardAccentProvider accentMode=\"mix\">\r\n * <MyApp />\r\n * </CardAccentProvider>\r\n *\r\n * // Use in components\r\n * const { getAccentColor } = useCardAccent();\r\n * <Container accentColor={getAccentColor('Power Systems')} />\r\n * ```\r\n */\r\n\r\nimport React, {\r\n createContext,\r\n useContext,\r\n useMemo,\r\n type ReactNode,\r\n} from \"react\";\r\n\r\n// =============================================================================\r\n// Types\r\n// =============================================================================\r\n\r\nexport type CardAccentKey =\r\n | \"cyan\"\r\n | \"electric\"\r\n | \"purple\"\r\n | \"teal\"\r\n | \"prussianBlue\"\r\n | \"green\"\r\n | \"amber\"\r\n | \"mix\";\r\n\r\nexport interface CardAccentColor {\r\n /** Display label */\r\n label: string;\r\n /** Hex color value (or 'mix' for automatic) */\r\n value: string;\r\n}\r\n\r\n// =============================================================================\r\n// Accent Color Definitions\r\n// =============================================================================\r\n\r\n/**\r\n * Predefined card accent colors\r\n * These are optimized for dark themes and space operations UIs\r\n */\r\nexport const CARD_ACCENT_COLORS: Record<CardAccentKey, CardAccentColor> = {\r\n cyan: { label: \"Cyan\", value: \"#00c8ff\" },\r\n electric: { label: \"Electric\", value: \"#4d7cff\" },\r\n purple: { label: \"Purple\", value: \"#a855f7\" },\r\n teal: { label: \"Teal\", value: \"#14b8a6\" },\r\n prussianBlue: { label: \"Prussian Blue\", value: \"#1e3a5f\" },\r\n green: { label: \"Green\", value: \"#22c55e\" },\r\n amber: { label: \"Amber\", value: \"#f59e0b\" },\r\n mix: { label: \"Mix (Per System)\", value: \"mix\" },\r\n} as const;\r\n\r\n/**\r\n * Space system keyword → accent color mapping\r\n * Used in 'mix' mode to automatically assign colors based on content\r\n */\r\nexport const SPACE_SYSTEM_COLORS: Record<string, string> = {\r\n // === Telemetry & Data ===\r\n telemetry: \"#00c8ff\",\r\n data: \"#00c8ff\",\r\n preview: \"#00c8ff\",\r\n stream: \"#00c8ff\",\r\n\r\n // === Power Systems ===\r\n power: \"#22c55e\",\r\n battery: \"#22c55e\",\r\n solar: \"#22c55e\",\r\n voltage: \"#22c55e\",\r\n energy: \"#22c55e\",\r\n\r\n // === Thermal Systems ===\r\n thermal: \"#f59e0b\",\r\n temperature: \"#f59e0b\",\r\n heatmap: \"#f59e0b\",\r\n heater: \"#f59e0b\",\r\n\r\n // === Attitude & Control ===\r\n attitude: \"#a855f7\",\r\n control: \"#a855f7\",\r\n orientation: \"#a855f7\",\r\n pointing: \"#a855f7\",\r\n gyro: \"#a855f7\",\r\n\r\n // === Communications ===\r\n spectrum: \"#4d7cff\",\r\n link: \"#4d7cff\",\r\n rf: \"#4d7cff\",\r\n downlink: \"#4d7cff\",\r\n uplink: \"#4d7cff\",\r\n packet: \"#4d7cff\",\r\n antenna: \"#4d7cff\",\r\n comm: \"#4d7cff\",\r\n\r\n // === Navigation & Orbit ===\r\n orbit: \"#14b8a6\",\r\n eclipse: \"#14b8a6\",\r\n polar: \"#14b8a6\",\r\n heliocentric: \"#14b8a6\",\r\n maneuver: \"#14b8a6\",\r\n contact: \"#14b8a6\",\r\n ground: \"#14b8a6\",\r\n pass: \"#14b8a6\",\r\n\r\n // === Sensors & Instruments ===\r\n sensor: \"#00c8ff\",\r\n star: \"#00c8ff\",\r\n tracker: \"#00c8ff\",\r\n scatter: \"#00c8ff\",\r\n payload: \"#00c8ff\",\r\n\r\n // === Health & Status ===\r\n health: \"#22c55e\",\r\n gauge: \"#22c55e\",\r\n subsystem: \"#22c55e\",\r\n status: \"#22c55e\",\r\n\r\n // === Analytics & Visualization ===\r\n sankey: \"#a855f7\",\r\n treemap: \"#4d7cff\",\r\n sunburst: \"#14b8a6\",\r\n parallel: \"#f59e0b\",\r\n radar: \"#00c8ff\",\r\n graph: \"#a855f7\",\r\n candlestick: \"#f59e0b\",\r\n histogram: \"#00c8ff\",\r\n pie: \"#a855f7\",\r\n donut: \"#a855f7\",\r\n bubble: \"#14b8a6\",\r\n rose: \"#f59e0b\",\r\n\r\n // === 3D Visualizations ===\r\n \"3d\": \"#4d7cff\",\r\n scatter3d: \"#00c8ff\",\r\n bar3d: \"#22c55e\",\r\n surface3d: \"#f59e0b\",\r\n lines3d: \"#a855f7\",\r\n debris: \"#f59e0b\",\r\n constellation: \"#4d7cff\",\r\n collision: \"#f59e0b\",\r\n satellite: \"#4d7cff\",\r\n\r\n // === Default ===\r\n default: \"#00c8ff\",\r\n} as const;\r\n\r\n// =============================================================================\r\n// Utility Functions\r\n// =============================================================================\r\n\r\n/**\r\n * Get accent color based on title/content and accent mode\r\n *\r\n * @param title - Content title or description to analyze\r\n * @param accentMode - Current accent mode (specific color or 'mix')\r\n * @returns Hex color string or undefined\r\n *\r\n * @example\r\n * ```tsx\r\n * getSystemAccentColor('Power Systems Overview', 'mix') // '#22c55e'\r\n * getSystemAccentColor('Power Systems', 'purple') // '#a855f7'\r\n * ```\r\n */\r\nexport function getSystemAccentColor(\r\n title: string,\r\n accentMode: CardAccentKey,\r\n): string | undefined {\r\n if (accentMode !== \"mix\") {\r\n return CARD_ACCENT_COLORS[accentMode].value;\r\n }\r\n\r\n const lowerTitle = title.toLowerCase();\r\n for (const [keyword, color] of Object.entries(SPACE_SYSTEM_COLORS)) {\r\n if (lowerTitle.includes(keyword)) {\r\n return color;\r\n }\r\n }\r\n return SPACE_SYSTEM_COLORS.default;\r\n}\r\n\r\n/**\r\n * Get all accent color options for building UI selectors\r\n */\r\nexport function getAccentColorOptions(): Array<{\r\n key: CardAccentKey;\r\n label: string;\r\n value: string;\r\n}> {\r\n return Object.entries(CARD_ACCENT_COLORS).map(([key, config]) => ({\r\n key: key as CardAccentKey,\r\n label: config.label,\r\n value: config.value,\r\n }));\r\n}\r\n\r\n// =============================================================================\r\n// React Context & Provider\r\n// =============================================================================\r\n\r\nexport interface CardAccentContextValue {\r\n /** Current accent mode */\r\n accentMode: CardAccentKey;\r\n /** Get accent color for a given title/content */\r\n getAccentColor: (title: string) => string | undefined;\r\n /** All available accent options */\r\n accentOptions: Array<{ key: CardAccentKey; label: string; value: string }>;\r\n}\r\n\r\nconst CardAccentContext = createContext<CardAccentContextValue>({\r\n accentMode: \"mix\",\r\n getAccentColor: () => undefined,\r\n accentOptions: getAccentColorOptions(),\r\n});\r\n\r\nexport interface CardAccentProviderProps {\r\n /** Accent mode to use */\r\n accentMode?: CardAccentKey;\r\n /** Children */\r\n children: ReactNode;\r\n}\r\n\r\n/**\r\n * Provider for card accent context\r\n * Wrap your app or section to provide consistent accent coloring\r\n */\r\nexport function CardAccentProvider({\r\n accentMode = \"mix\",\r\n children,\r\n}: CardAccentProviderProps): React.ReactElement {\r\n const value = useMemo(\r\n (): CardAccentContextValue => ({\r\n accentMode,\r\n getAccentColor: (title: string) =>\r\n getSystemAccentColor(title, accentMode),\r\n accentOptions: getAccentColorOptions(),\r\n }),\r\n [accentMode],\r\n );\r\n\r\n return (\r\n <CardAccentContext.Provider value={value}>\r\n {children}\r\n </CardAccentContext.Provider>\r\n );\r\n}\r\n\r\n/**\r\n * Hook to access card accent context\r\n *\r\n * @example\r\n * ```tsx\r\n * const { getAccentColor, accentMode } = useCardAccent();\r\n * <Container accentColor={getAccentColor('Power Chart')} />\r\n * ```\r\n */\r\nexport function useCardAccent(): CardAccentContextValue {\r\n return useContext(CardAccentContext);\r\n}\r\n\r\nexport default CardAccentProvider;\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmEA,IAAa,qBAA6D;CACxE,MAAM;EAAE,OAAO;EAAQ,OAAO;CAAU;CACxC,UAAU;EAAE,OAAO;EAAY,OAAO;CAAU;CAChD,QAAQ;EAAE,OAAO;EAAU,OAAO;CAAU;CAC5C,MAAM;EAAE,OAAO;EAAQ,OAAO;CAAU;CACxC,cAAc;EAAE,OAAO;EAAiB,OAAO;CAAU;CACzD,OAAO;EAAE,OAAO;EAAS,OAAO;CAAU;CAC1C,OAAO;EAAE,OAAO;EAAS,OAAO;CAAU;CAC1C,KAAK;EAAE,OAAO;EAAoB,OAAO;CAAM;AACjD;;;;;AAMA,IAAa,sBAA8C;CAEzD,WAAW;CACX,MAAM;CACN,SAAS;CACT,QAAQ;CAGR,OAAO;CACP,SAAS;CACT,OAAO;CACP,SAAS;CACT,QAAQ;CAGR,SAAS;CACT,aAAa;CACb,SAAS;CACT,QAAQ;CAGR,UAAU;CACV,SAAS;CACT,aAAa;CACb,UAAU;CACV,MAAM;CAGN,UAAU;CACV,MAAM;CACN,IAAI;CACJ,UAAU;CACV,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,MAAM;CAGN,OAAO;CACP,SAAS;CACT,OAAO;CACP,cAAc;CACd,UAAU;CACV,SAAS;CACT,QAAQ;CACR,MAAM;CAGN,QAAQ;CACR,MAAM;CACN,SAAS;CACT,SAAS;CACT,SAAS;CAGT,QAAQ;CACR,OAAO;CACP,WAAW;CACX,QAAQ;CAGR,QAAQ;CACR,SAAS;CACT,UAAU;CACV,UAAU;CACV,OAAO;CACP,OAAO;CACP,aAAa;CACb,WAAW;CACX,KAAK;CACL,OAAO;CACP,QAAQ;CACR,MAAM;CAGN,MAAM;CACN,WAAW;CACX,OAAO;CACP,WAAW;CACX,SAAS;CACT,QAAQ;CACR,eAAe;CACf,WAAW;CACX,WAAW;CAGX,SAAS;AACX;;;;;;;;;;;;;;AAmBA,SAAgB,qBACd,OACA,YACoB;CACpB,IAAI,eAAe,OACjB,OAAO,mBAAmB,WAAW,CAAC;CAGxC,MAAM,aAAa,MAAM,YAAY;CACrC,KAAK,MAAM,CAAC,SAAS,UAAU,OAAO,QAAQ,mBAAmB,GAC/D,IAAI,WAAW,SAAS,OAAO,GAC7B,OAAO;CAGX,OAAO,oBAAoB;AAC7B;;;;AAKA,SAAgB,wBAIb;CACD,OAAO,OAAO,QAAQ,kBAAkB,CAAC,CAAC,KAAK,CAAC,KAAK,aAAa;EAC3D;EACL,OAAO,OAAO;EACd,OAAO,OAAO;CAChB,EAAE;AACJ;AAeA,IAAM,oBAAoB,cAAsC;CAC9D,YAAY;CACZ,sBAAsB,KAAA;CACtB,eAAe,sBAAsB;AACvC,CAAC;;;;;AAaD,SAAgB,mBAAmB,EACjC,aAAa,OACb,YAC8C;CAC9C,MAAM,QAAQ,eACmB;EAC7B;EACA,iBAAiB,UACf,qBAAqB,OAAO,UAAU;EACxC,eAAe,sBAAsB;CACvC,IACA,CAAC,UAAU,CACb;CAEA,OACE,oBAAC,kBAAkB,UAAnB;EAAmC;EAChC;CACyB,CAAA;AAEhC;;;;;;;;;;AAWA,SAAgB,gBAAwC;CACtD,OAAO,WAAW,iBAAiB;AACrC"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","names":[],"sources":["../../../src/react/theme/config.ts"],"sourcesContent":["/**\n * @zendir/ui - Theme Configuration\n *\n * Easy configuration for default theme settings.\n * Import and use in your app root to set defaults.\n */\n\nimport type { ThemeVariant, ThemeMode } from \"./ThemeProvider\";\n\nexport interface ThemeConfig {\n /** Default theme variant */\n defaultTheme?: ThemeVariant;\n /** Default theme mode */\n defaultMode?: ThemeMode;\n /** Persist theme selection to localStorage */\n persist?: boolean;\n}\n\n/**\n * Default theme configuration\n *\n * Available themes:\n * - 'hybrid' - Zen (Hybrid): default; Astro status colors with purple accents\n * - 'astro' - Astro UX (U.S. Space Force) compliant theme\n * - 'purple-hue' - Zen (Purple Hue) theme with modern aesthetics\n * - 'transparent' / 'transparent-bold' / 'transparent-minimal' - Glassmorphic themes\n */\nexport const DEFAULT_THEME_CONFIG: ThemeConfig = {\n defaultTheme: \"hybrid\", // Zen (Hybrid) is the default\n defaultMode: \"dark\",\n persist: true,\n};\n\n/**\n * Get theme configuration from environment or use defaults\n */\nexport function getThemeConfig(): ThemeConfig {\n // Allow override via environment variable or build-time config\n if (typeof window !== \"undefined\") {\n const envTheme = (window as any).__ZENDIR_UI_THEME__;\n if (\n envTheme === \"astro\" ||\n envTheme === \"purple-hue\" ||\n envTheme === \"hybrid\" ||\n envTheme === \"nebula\"\n ) {\n return {\n ...DEFAULT_THEME_CONFIG,\n defaultTheme: envTheme,\n };\n }\n }\n\n return DEFAULT_THEME_CONFIG;\n}\n\n/**\n * Set default theme at build/runtime\n *\n * @example\n * ```tsx\n * // In your app root\n * import { setDefaultTheme } from '@zendir/ui/react/theme/config';\n *\n * // Zen (Hybrid) is the default; override if needed:\n * setDefaultTheme('hybrid');\n * setDefaultTheme('purple-hue');\n * setDefaultTheme('astro');\n * ```\n */\nexport function setDefaultTheme(theme: ThemeVariant): void {\n if (typeof window !== \"undefined\") {\n (window as any).__ZENDIR_UI_THEME__ = theme;\n // Also update localStorage if persist is enabled\n localStorage.setItem(\"zendir-ui-theme\", theme);\n }\n}\n"],"mappings":";;;;;;;;;;AA2BA,IAAa,uBAAoC;CAC/C,cAAc;CACd,aAAa;CACb,SAAS;AACX;;;;AAKA,SAAgB,iBAA8B;CAE5C,IAAI,OAAO,WAAW,aAAa;EACjC,MAAM,WAAY,OAAe;EACjC,IACE,aAAa,WACb,aAAa,gBACb,aAAa,YACb,aAAa,UAEb,OAAO;GACL,GAAG;GACH,cAAc;EAChB;CAEJ;CAEA,OAAO;AACT;;;;;;;;;;;;;;;AAgBA,SAAgB,gBAAgB,OAA2B;CACzD,IAAI,OAAO,WAAW,aAAa;EACjC,OAAgB,sBAAsB;EAEtC,aAAa,QAAQ,mBAAmB,KAAK;CAC/C;AACF"}
1
+ {"version":3,"file":"config.js","names":[],"sources":["../../../src/react/theme/config.ts"],"sourcesContent":["/**\r\n * @zendir/ui - Theme Configuration\r\n *\r\n * Easy configuration for default theme settings.\r\n * Import and use in your app root to set defaults.\r\n */\r\n\r\nimport type { ThemeVariant, ThemeMode } from \"./ThemeProvider\";\r\n\r\nexport interface ThemeConfig {\r\n /** Default theme variant */\r\n defaultTheme?: ThemeVariant;\r\n /** Default theme mode */\r\n defaultMode?: ThemeMode;\r\n /** Persist theme selection to localStorage */\r\n persist?: boolean;\r\n}\r\n\r\n/**\r\n * Default theme configuration\r\n *\r\n * Available themes:\r\n * - 'hybrid' - Zen (Hybrid): default; Astro status colors with purple accents\r\n * - 'astro' - Astro UX (U.S. Space Force) compliant theme\r\n * - 'purple-hue' - Zen (Purple Hue) theme with modern aesthetics\r\n * - 'transparent' / 'transparent-bold' / 'transparent-minimal' - Glassmorphic themes\r\n */\r\nexport const DEFAULT_THEME_CONFIG: ThemeConfig = {\r\n defaultTheme: \"hybrid\", // Zen (Hybrid) is the default\r\n defaultMode: \"dark\",\r\n persist: true,\r\n};\r\n\r\n/**\r\n * Get theme configuration from environment or use defaults\r\n */\r\nexport function getThemeConfig(): ThemeConfig {\r\n // Allow override via environment variable or build-time config\r\n if (typeof window !== \"undefined\") {\r\n const envTheme = (window as any).__ZENDIR_UI_THEME__;\r\n if (\r\n envTheme === \"astro\" ||\r\n envTheme === \"purple-hue\" ||\r\n envTheme === \"hybrid\" ||\r\n envTheme === \"nebula\"\r\n ) {\r\n return {\r\n ...DEFAULT_THEME_CONFIG,\r\n defaultTheme: envTheme,\r\n };\r\n }\r\n }\r\n\r\n return DEFAULT_THEME_CONFIG;\r\n}\r\n\r\n/**\r\n * Set default theme at build/runtime\r\n *\r\n * @example\r\n * ```tsx\r\n * // In your app root\r\n * import { setDefaultTheme } from '@zendir/ui/react/theme/config';\r\n *\r\n * // Zen (Hybrid) is the default; override if needed:\r\n * setDefaultTheme('hybrid');\r\n * setDefaultTheme('purple-hue');\r\n * setDefaultTheme('astro');\r\n * ```\r\n */\r\nexport function setDefaultTheme(theme: ThemeVariant): void {\r\n if (typeof window !== \"undefined\") {\r\n (window as any).__ZENDIR_UI_THEME__ = theme;\r\n // Also update localStorage if persist is enabled\r\n localStorage.setItem(\"zendir-ui-theme\", theme);\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;AA2BA,IAAa,uBAAoC;CAC/C,cAAc;CACd,aAAa;CACb,SAAS;AACX;;;;AAKA,SAAgB,iBAA8B;CAE5C,IAAI,OAAO,WAAW,aAAa;EACjC,MAAM,WAAY,OAAe;EACjC,IACE,aAAa,WACb,aAAa,gBACb,aAAa,YACb,aAAa,UAEb,OAAO;GACL,GAAG;GACH,cAAc;EAChB;CAEJ;CAEA,OAAO;AACT;;;;;;;;;;;;;;;AAgBA,SAAgB,gBAAgB,OAA2B;CACzD,IAAI,OAAO,WAAW,aAAa;EACjC,OAAgB,sBAAsB;EAEtC,aAAa,QAAQ,mBAAmB,KAAK;CAC/C;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":[],"sources":["../../../src/react/theme/types.ts"],"sourcesContent":["/**\n * @zendir/ui — Theme type definitions and derived-token helpers.\n *\n * Shared home for the theme token shapes so both the runtime provider\n * (ThemeProvider) and the static theme definitions (src/tokens/themes.ts)\n * reference one set of types. Isolating these here keeps the token-build\n * script (which imports the theme definitions) free of the full React\n * provider module.\n */\n\nimport React from \"react\";\n\nexport type ThemeVariant =\n | \"astro\"\n | \"purple-hue\"\n | \"hybrid\"\n | \"nebula\"\n | \"transparent\"\n | \"transparent-bold\"\n | \"transparent-minimal\";\nexport type ThemeMode = \"light\" | \"dark\";\n\nexport interface ThemeColors {\n background: {\n base: string;\n surface: string;\n elevated: string;\n overlay: string;\n };\n border: {\n default: string;\n muted: string;\n focus: string;\n /** Transparent theme: thin faded top-to-bottom border (box-shadow, use with border: 'none') */\n fadedBoxShadow?: string;\n /** Card border style (computed from theme – solid or transparent faded). Use ...tokens.colors.border.cardStyle */\n cardStyle?: React.CSSProperties;\n /** Card border for dashed/empty state. Use ...tokens.colors.border.cardStyleDashed */\n cardStyleDashed?: React.CSSProperties;\n /** Card border using default color (e.g. NavBall, EclipseTimer). Use ...tokens.colors.border.cardStyleDefault */\n cardStyleDefault?: React.CSSProperties;\n };\n text: {\n primary: string;\n secondary: string;\n tertiary: string;\n /** Muted/placeholder text (alias for tertiary when not set) */\n muted?: string;\n inverse: string;\n };\n status: {\n normal: string;\n standby: string;\n caution: string;\n serious: string;\n critical: string;\n off: string;\n };\n semantic: {\n success: string;\n warning: string;\n error: string;\n info: string;\n };\n accent: {\n primary: string;\n secondary: string;\n tertiary: string;\n };\n interactive: {\n default: string;\n hover: string;\n active: string;\n disabled: string;\n /** Transparent theme: button/control bg (low opacity purple) when not hovered */\n transparentDefault?: string;\n /** Transparent theme: button/control bg on hover (higher opacity purple) */\n transparentHover?: string;\n /** Transparent theme: input field bg (semi-transparent purple tint) */\n transparentInputBg?: string;\n };\n}\n\nexport interface ThemeAnimation {\n /** Fast transitions (hover states) - 150ms */\n fast: string;\n /** Normal transitions (most interactions) - 250ms */\n normal: string;\n /** Slow transitions (page/mode changes) - 400ms */\n slow: string;\n /** Spring animation for playful interactions */\n spring: string;\n /** Easing function */\n easing: {\n default: string;\n in: string;\n out: string;\n inOut: string;\n };\n /** Duration values in milliseconds */\n duration: {\n instant: number;\n fast: number;\n normal: number;\n slow: number;\n };\n}\n\nexport interface ThemeFocus {\n /** Focus ring color */\n color: string;\n /** Focus ring width */\n width: string;\n /** Focus ring offset */\n offset: string;\n /** Full focus ring CSS */\n ring: string;\n /** Focus ring style object */\n style: React.CSSProperties;\n}\n\n/**\n * Astro UX Typography Style Token\n * Based on https://www.astrouxds.com/foundations/typography/\n */\nexport interface TypographyStyle {\n fontSize: string;\n fontWeight: number;\n letterSpacing: string;\n lineHeight: string;\n}\n\nexport interface ThemeTokens {\n colors: ThemeColors;\n spacing: {\n /** 2px — micro adjustments, decorative offsets */\n xxs: string;\n /** 4px — tight gaps, icon padding */\n xs: string;\n /** 8px — compact spacing, small gaps */\n sm: string;\n /** 12px — form fields, compact cards (between sm and md) */\n smd: string;\n /** 16px — default card padding, section gaps */\n md: string;\n /** 20px — slightly generous spacing */\n mdl: string;\n /** 24px — generous spacing, content area padding */\n lg: string;\n /** 32px — large section gaps */\n xl: string;\n /** 48px — extra large spacing */\n xxl: string;\n };\n borderRadius: {\n none: string;\n /** 1px — very subtle rounding */\n xs: string;\n /** 2px — small elements, badges, tooltips */\n sm: string;\n /** 4px — inputs, selects, standard controls */\n md: string;\n /** 8px — cards, containers */\n lg: string;\n /** 12px — prominent surfaces, modals */\n xl: string;\n /** 9999px — pills, fully rounded */\n full: string;\n };\n /** Standardised element heights for interactive controls */\n elementSize: {\n /** 28px — compact controls */\n sm: string;\n /** 36px — default controls */\n md: string;\n /** 44px — prominent controls, touch targets */\n lg: string;\n };\n typography: {\n fontFamily: {\n primary: string;\n /** Heading stack (Sora → fallbacks); set by every theme. */\n heading: string;\n mono: string;\n };\n // Astro UX Typography Tokens (rem-based)\n // https://www.astrouxds.com/foundations/typography/\n display: {\n 1: TypographyStyle;\n 2: TypographyStyle;\n };\n heading: {\n 1: TypographyStyle;\n \"1Bold\": TypographyStyle;\n 2: TypographyStyle;\n 3: TypographyStyle;\n 4: TypographyStyle;\n 5: TypographyStyle;\n 6: TypographyStyle;\n };\n body: {\n 1: TypographyStyle;\n \"1Bold\": TypographyStyle;\n 2: TypographyStyle;\n \"2Bold\": TypographyStyle;\n 3: TypographyStyle;\n \"3Bold\": TypographyStyle;\n };\n control: {\n 1: TypographyStyle;\n \"1Bold\": TypographyStyle;\n };\n // Legacy/convenience tokens (keeping for backward compatibility)\n fontSize: {\n micro: string; // 9px - very tight spaces\n xxs: string; // 10px - compact labels\n xs: string;\n sm: string;\n base: string;\n md: string;\n lg: string;\n xl: string;\n xxl: string;\n xxxl: string;\n };\n fontWeight: {\n light: number;\n normal: number;\n medium: number;\n semibold: number;\n bold: number;\n };\n lineHeight: {\n tight: string;\n normal: string;\n relaxed: string;\n };\n letterSpacing: {\n tight: string;\n normal: string;\n wide: string;\n };\n };\n shadows: {\n none: string;\n sm: string;\n md: string;\n lg: string;\n xl: string;\n glow: (color: string) => string;\n };\n animation: ThemeAnimation;\n focus: ThemeFocus;\n /** Semantic layout tokens for consistent spacing across cards, forms, and sections */\n layout: LayoutTokens;\n /** Semantic border tokens for consistent element styling (derived from theme colors) */\n borders: BorderTokens;\n}\n\n// ============================================================================\n// Layout Tokens Interface\n// Semantic spacing for cards, sections, forms, and nested surfaces.\n// These decouple layout intent from raw pixel values, enabling consistent\n// spacing across all consuming applications.\n// ============================================================================\n\nexport interface LayoutTokens {\n /** Card / panel container spacing */\n card: {\n /** Internal padding of card containers */\n padding: string;\n /** Gap between cards in grid layouts (desktop) */\n gap: string;\n /** Gap between cards in grid layouts (mobile / compact) */\n gapCompact: string;\n /** Card heading: icon size, title typography, gap — single system for all cards/charts */\n heading: {\n iconSize: number;\n iconSizeCompact: number;\n titleFontSize: string;\n /** Smaller title for chart headers (e.g. Power Trends) so they match card visual weight */\n titleFontSizeChart: string;\n titleFontWeight: number;\n gap: number;\n };\n };\n /** Section heading spacing (heading + divider + content) */\n section: {\n /** Padding below heading text, above the divider border */\n headerPaddingBottom: string;\n /** Margin below the divider border, before content */\n headerMarginBottom: string;\n /** Padding above the first content element after heading */\n contentPaddingTop: string;\n };\n /** Form and input field spacing */\n form: {\n /** Vertical gap between stacked form fields */\n fieldGap: string;\n /** Horizontal gap between side-by-side inline fields */\n inlineGap: string;\n /** Padding inside grouped / nested field containers */\n groupPadding: string;\n /** Gap between items within a field group */\n groupGap: string;\n /** Gap between label text and helper icons (e.g. tooltip) */\n labelGap: string;\n };\n /** Nested surface / panel spacing (e.g. grouped fields within a card) */\n surface: {\n /** Internal padding of nested surfaces */\n padding: string;\n /** Border radius of nested surfaces */\n borderRadius: string;\n /** Gap between items in compact nested surfaces */\n gap: string;\n };\n}\n\n// ============================================================================\n// Border Tokens Interface\n// Standardised border widths, element borders, focus rings, and dividers.\n// These are DERIVED from theme colors at runtime via computeBorderTokens().\n// ============================================================================\n\nexport interface BorderTokens {\n /** Border widths — standardised across all interactive elements */\n width: {\n /** Thin (1px) — inputs, cards, dividers, dropdowns */\n thin: string;\n /** Medium (1.5px) — secondary buttons, emphasised inputs */\n medium: string;\n /** Thick (2px) — checkboxes, accent borders, active tabs */\n thick: string;\n };\n /** Pre-computed input / control borders (width + style + color) */\n input: {\n /** Default resting state */\n default: string;\n /** Hover state (accent at 50% opacity) */\n hover: string;\n /** Focused state (full accent) */\n focus: string;\n /** Error / critical state */\n error: string;\n };\n /** Focus ring box-shadow values for interactive elements */\n focusRing: {\n /** Standard ring for inputs and selects */\n default: string;\n /** Ring for buttons (double-ring pattern) */\n button: string;\n /** Subtle ring for checkboxes, tabs, toggles */\n subtle: string;\n };\n /** Section divider border (headings, dialog headers/footers, tab bars) */\n divider: string;\n /** Dropdown / popover container border */\n dropdown: string;\n /** Separator line inside elements (unit separators, table rows) */\n separator: string;\n}\n\n/**\n * Derives border tokens from a theme's resolved colors.\n * Called once per theme change inside the ThemeProvider useMemo.\n */\nexport function computeBorderTokens(colors: ThemeColors): BorderTokens {\n const thin = \"1px\";\n const medium = \"1.5px\";\n const thick = \"2px\";\n const accent = colors.accent.primary;\n const muted = colors.border.muted;\n const critical = colors.status.critical;\n const bgBase = colors.background.base;\n\n return {\n width: { thin, medium, thick },\n input: {\n default: `${thin} solid ${muted}`,\n hover: `${thin} solid ${accent}80`,\n focus: `${thin} solid ${accent}`,\n error: `${thin} solid ${critical}`,\n },\n focusRing: {\n // WCAG 2.4.13: focus indicators ≥ 3:1 contrast — raised from 20%/30% to 50%/60%\n default: `0 0 0 3px ${accent}50, 0 0 20px ${accent}15`,\n button: `0 0 0 2px ${bgBase}, 0 0 0 4px ${accent}`,\n subtle: `0 0 0 2px ${accent}60`,\n },\n divider: `${thin} solid ${muted}`,\n dropdown: `${thin} solid ${accent}30`,\n separator: `${thin} solid ${muted}`,\n };\n}\n\n/**\n * Base theme type used for static theme definitions.\n * `borders` is omitted because it is derived from colors at runtime\n * inside the ThemeProvider useMemo (see computeBorderTokens).\n */\nexport type ThemeTokensBase = Omit<ThemeTokens, \"borders\">;\n"],"mappings":";;;;;AA+WA,SAAgB,oBAAoB,QAAmC;CACrE,MAAM,OAAO;CACb,MAAM,SAAS;CACf,MAAM,QAAQ;CACd,MAAM,SAAS,OAAO,OAAO;CAC7B,MAAM,QAAQ,OAAO,OAAO;CAC5B,MAAM,WAAW,OAAO,OAAO;CAC/B,MAAM,SAAS,OAAO,WAAW;CAEjC,OAAO;EACL,OAAO;GAAE;GAAM;GAAQ;EAAM;EAC7B,OAAO;GACL,SAAS,GAAG,KAAK,SAAS;GAC1B,OAAO,GAAG,KAAK,SAAS,OAAO;GAC/B,OAAO,GAAG,KAAK,SAAS;GACxB,OAAO,GAAG,KAAK,SAAS;EAC1B;EACA,WAAW;GAET,SAAS,aAAa,OAAO,eAAe,OAAO;GACnD,QAAQ,aAAa,OAAO,cAAc;GAC1C,QAAQ,aAAa,OAAO;EAC9B;EACA,SAAS,GAAG,KAAK,SAAS;EAC1B,UAAU,GAAG,KAAK,SAAS,OAAO;EAClC,WAAW,GAAG,KAAK,SAAS;CAC9B;AACF"}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../../src/react/theme/types.ts"],"sourcesContent":["/**\r\n * @zendir/ui — Theme type definitions and derived-token helpers.\r\n *\r\n * Shared home for the theme token shapes so both the runtime provider\r\n * (ThemeProvider) and the static theme definitions (src/tokens/themes.ts)\r\n * reference one set of types. Isolating these here keeps the token-build\r\n * script (which imports the theme definitions) free of the full React\r\n * provider module.\r\n */\r\n\r\nimport React from \"react\";\r\n\r\nexport type ThemeVariant =\r\n | \"astro\"\r\n | \"purple-hue\"\r\n | \"hybrid\"\r\n | \"nebula\"\r\n | \"transparent\"\r\n | \"transparent-bold\"\r\n | \"transparent-minimal\";\r\nexport type ThemeMode = \"light\" | \"dark\";\r\n\r\nexport interface ThemeColors {\r\n background: {\r\n base: string;\r\n surface: string;\r\n elevated: string;\r\n overlay: string;\r\n };\r\n border: {\r\n default: string;\r\n muted: string;\r\n focus: string;\r\n /** Transparent theme: thin faded top-to-bottom border (box-shadow, use with border: 'none') */\r\n fadedBoxShadow?: string;\r\n /** Card border style (computed from theme – solid or transparent faded). Use ...tokens.colors.border.cardStyle */\r\n cardStyle?: React.CSSProperties;\r\n /** Card border for dashed/empty state. Use ...tokens.colors.border.cardStyleDashed */\r\n cardStyleDashed?: React.CSSProperties;\r\n /** Card border using default color (e.g. NavBall, EclipseTimer). Use ...tokens.colors.border.cardStyleDefault */\r\n cardStyleDefault?: React.CSSProperties;\r\n };\r\n text: {\r\n primary: string;\r\n secondary: string;\r\n tertiary: string;\r\n /** Muted/placeholder text (alias for tertiary when not set) */\r\n muted?: string;\r\n inverse: string;\r\n };\r\n status: {\r\n normal: string;\r\n standby: string;\r\n caution: string;\r\n serious: string;\r\n critical: string;\r\n off: string;\r\n };\r\n semantic: {\r\n success: string;\r\n warning: string;\r\n error: string;\r\n info: string;\r\n };\r\n accent: {\r\n primary: string;\r\n secondary: string;\r\n tertiary: string;\r\n };\r\n interactive: {\r\n default: string;\r\n hover: string;\r\n active: string;\r\n disabled: string;\r\n /** Transparent theme: button/control bg (low opacity purple) when not hovered */\r\n transparentDefault?: string;\r\n /** Transparent theme: button/control bg on hover (higher opacity purple) */\r\n transparentHover?: string;\r\n /** Transparent theme: input field bg (semi-transparent purple tint) */\r\n transparentInputBg?: string;\r\n };\r\n}\r\n\r\nexport interface ThemeAnimation {\r\n /** Fast transitions (hover states) - 150ms */\r\n fast: string;\r\n /** Normal transitions (most interactions) - 250ms */\r\n normal: string;\r\n /** Slow transitions (page/mode changes) - 400ms */\r\n slow: string;\r\n /** Spring animation for playful interactions */\r\n spring: string;\r\n /** Easing function */\r\n easing: {\r\n default: string;\r\n in: string;\r\n out: string;\r\n inOut: string;\r\n };\r\n /** Duration values in milliseconds */\r\n duration: {\r\n instant: number;\r\n fast: number;\r\n normal: number;\r\n slow: number;\r\n };\r\n}\r\n\r\nexport interface ThemeFocus {\r\n /** Focus ring color */\r\n color: string;\r\n /** Focus ring width */\r\n width: string;\r\n /** Focus ring offset */\r\n offset: string;\r\n /** Full focus ring CSS */\r\n ring: string;\r\n /** Focus ring style object */\r\n style: React.CSSProperties;\r\n}\r\n\r\n/**\r\n * Astro UX Typography Style Token\r\n * Based on https://www.astrouxds.com/foundations/typography/\r\n */\r\nexport interface TypographyStyle {\r\n fontSize: string;\r\n fontWeight: number;\r\n letterSpacing: string;\r\n lineHeight: string;\r\n}\r\n\r\nexport interface ThemeTokens {\r\n colors: ThemeColors;\r\n spacing: {\r\n /** 2px — micro adjustments, decorative offsets */\r\n xxs: string;\r\n /** 4px — tight gaps, icon padding */\r\n xs: string;\r\n /** 8px — compact spacing, small gaps */\r\n sm: string;\r\n /** 12px — form fields, compact cards (between sm and md) */\r\n smd: string;\r\n /** 16px — default card padding, section gaps */\r\n md: string;\r\n /** 20px — slightly generous spacing */\r\n mdl: string;\r\n /** 24px — generous spacing, content area padding */\r\n lg: string;\r\n /** 32px — large section gaps */\r\n xl: string;\r\n /** 48px — extra large spacing */\r\n xxl: string;\r\n };\r\n borderRadius: {\r\n none: string;\r\n /** 1px — very subtle rounding */\r\n xs: string;\r\n /** 2px — small elements, badges, tooltips */\r\n sm: string;\r\n /** 4px — inputs, selects, standard controls */\r\n md: string;\r\n /** 8px — cards, containers */\r\n lg: string;\r\n /** 12px — prominent surfaces, modals */\r\n xl: string;\r\n /** 9999px — pills, fully rounded */\r\n full: string;\r\n };\r\n /** Standardised element heights for interactive controls */\r\n elementSize: {\r\n /** 28px — compact controls */\r\n sm: string;\r\n /** 36px — default controls */\r\n md: string;\r\n /** 44px — prominent controls, touch targets */\r\n lg: string;\r\n };\r\n typography: {\r\n fontFamily: {\r\n primary: string;\r\n /** Heading stack (Sora → fallbacks); set by every theme. */\r\n heading: string;\r\n mono: string;\r\n };\r\n // Astro UX Typography Tokens (rem-based)\r\n // https://www.astrouxds.com/foundations/typography/\r\n display: {\r\n 1: TypographyStyle;\r\n 2: TypographyStyle;\r\n };\r\n heading: {\r\n 1: TypographyStyle;\r\n \"1Bold\": TypographyStyle;\r\n 2: TypographyStyle;\r\n 3: TypographyStyle;\r\n 4: TypographyStyle;\r\n 5: TypographyStyle;\r\n 6: TypographyStyle;\r\n };\r\n body: {\r\n 1: TypographyStyle;\r\n \"1Bold\": TypographyStyle;\r\n 2: TypographyStyle;\r\n \"2Bold\": TypographyStyle;\r\n 3: TypographyStyle;\r\n \"3Bold\": TypographyStyle;\r\n };\r\n control: {\r\n 1: TypographyStyle;\r\n \"1Bold\": TypographyStyle;\r\n };\r\n // Legacy/convenience tokens (keeping for backward compatibility)\r\n fontSize: {\r\n micro: string; // 9px - very tight spaces\r\n xxs: string; // 10px - compact labels\r\n xs: string;\r\n sm: string;\r\n base: string;\r\n md: string;\r\n lg: string;\r\n xl: string;\r\n xxl: string;\r\n xxxl: string;\r\n };\r\n fontWeight: {\r\n light: number;\r\n normal: number;\r\n medium: number;\r\n semibold: number;\r\n bold: number;\r\n };\r\n lineHeight: {\r\n tight: string;\r\n normal: string;\r\n relaxed: string;\r\n };\r\n letterSpacing: {\r\n tight: string;\r\n normal: string;\r\n wide: string;\r\n };\r\n };\r\n shadows: {\r\n none: string;\r\n sm: string;\r\n md: string;\r\n lg: string;\r\n xl: string;\r\n glow: (color: string) => string;\r\n };\r\n animation: ThemeAnimation;\r\n focus: ThemeFocus;\r\n /** Semantic layout tokens for consistent spacing across cards, forms, and sections */\r\n layout: LayoutTokens;\r\n /** Semantic border tokens for consistent element styling (derived from theme colors) */\r\n borders: BorderTokens;\r\n}\r\n\r\n// ============================================================================\r\n// Layout Tokens Interface\r\n// Semantic spacing for cards, sections, forms, and nested surfaces.\r\n// These decouple layout intent from raw pixel values, enabling consistent\r\n// spacing across all consuming applications.\r\n// ============================================================================\r\n\r\nexport interface LayoutTokens {\r\n /** Card / panel container spacing */\r\n card: {\r\n /** Internal padding of card containers */\r\n padding: string;\r\n /** Gap between cards in grid layouts (desktop) */\r\n gap: string;\r\n /** Gap between cards in grid layouts (mobile / compact) */\r\n gapCompact: string;\r\n /** Card heading: icon size, title typography, gap — single system for all cards/charts */\r\n heading: {\r\n iconSize: number;\r\n iconSizeCompact: number;\r\n titleFontSize: string;\r\n /** Smaller title for chart headers (e.g. Power Trends) so they match card visual weight */\r\n titleFontSizeChart: string;\r\n titleFontWeight: number;\r\n gap: number;\r\n };\r\n };\r\n /** Section heading spacing (heading + divider + content) */\r\n section: {\r\n /** Padding below heading text, above the divider border */\r\n headerPaddingBottom: string;\r\n /** Margin below the divider border, before content */\r\n headerMarginBottom: string;\r\n /** Padding above the first content element after heading */\r\n contentPaddingTop: string;\r\n };\r\n /** Form and input field spacing */\r\n form: {\r\n /** Vertical gap between stacked form fields */\r\n fieldGap: string;\r\n /** Horizontal gap between side-by-side inline fields */\r\n inlineGap: string;\r\n /** Padding inside grouped / nested field containers */\r\n groupPadding: string;\r\n /** Gap between items within a field group */\r\n groupGap: string;\r\n /** Gap between label text and helper icons (e.g. tooltip) */\r\n labelGap: string;\r\n };\r\n /** Nested surface / panel spacing (e.g. grouped fields within a card) */\r\n surface: {\r\n /** Internal padding of nested surfaces */\r\n padding: string;\r\n /** Border radius of nested surfaces */\r\n borderRadius: string;\r\n /** Gap between items in compact nested surfaces */\r\n gap: string;\r\n };\r\n}\r\n\r\n// ============================================================================\r\n// Border Tokens Interface\r\n// Standardised border widths, element borders, focus rings, and dividers.\r\n// These are DERIVED from theme colors at runtime via computeBorderTokens().\r\n// ============================================================================\r\n\r\nexport interface BorderTokens {\r\n /** Border widths — standardised across all interactive elements */\r\n width: {\r\n /** Thin (1px) — inputs, cards, dividers, dropdowns */\r\n thin: string;\r\n /** Medium (1.5px) — secondary buttons, emphasised inputs */\r\n medium: string;\r\n /** Thick (2px) — checkboxes, accent borders, active tabs */\r\n thick: string;\r\n };\r\n /** Pre-computed input / control borders (width + style + color) */\r\n input: {\r\n /** Default resting state */\r\n default: string;\r\n /** Hover state (accent at 50% opacity) */\r\n hover: string;\r\n /** Focused state (full accent) */\r\n focus: string;\r\n /** Error / critical state */\r\n error: string;\r\n };\r\n /** Focus ring box-shadow values for interactive elements */\r\n focusRing: {\r\n /** Standard ring for inputs and selects */\r\n default: string;\r\n /** Ring for buttons (double-ring pattern) */\r\n button: string;\r\n /** Subtle ring for checkboxes, tabs, toggles */\r\n subtle: string;\r\n };\r\n /** Section divider border (headings, dialog headers/footers, tab bars) */\r\n divider: string;\r\n /** Dropdown / popover container border */\r\n dropdown: string;\r\n /** Separator line inside elements (unit separators, table rows) */\r\n separator: string;\r\n}\r\n\r\n/**\r\n * Derives border tokens from a theme's resolved colors.\r\n * Called once per theme change inside the ThemeProvider useMemo.\r\n */\r\nexport function computeBorderTokens(colors: ThemeColors): BorderTokens {\r\n const thin = \"1px\";\r\n const medium = \"1.5px\";\r\n const thick = \"2px\";\r\n const accent = colors.accent.primary;\r\n const muted = colors.border.muted;\r\n const critical = colors.status.critical;\r\n const bgBase = colors.background.base;\r\n\r\n return {\r\n width: { thin, medium, thick },\r\n input: {\r\n default: `${thin} solid ${muted}`,\r\n hover: `${thin} solid ${accent}80`,\r\n focus: `${thin} solid ${accent}`,\r\n error: `${thin} solid ${critical}`,\r\n },\r\n focusRing: {\r\n // WCAG 2.4.13: focus indicators ≥ 3:1 contrast — raised from 20%/30% to 50%/60%\r\n default: `0 0 0 3px ${accent}50, 0 0 20px ${accent}15`,\r\n button: `0 0 0 2px ${bgBase}, 0 0 0 4px ${accent}`,\r\n subtle: `0 0 0 2px ${accent}60`,\r\n },\r\n divider: `${thin} solid ${muted}`,\r\n dropdown: `${thin} solid ${accent}30`,\r\n separator: `${thin} solid ${muted}`,\r\n };\r\n}\r\n\r\n/**\r\n * Base theme type used for static theme definitions.\r\n * `borders` is omitted because it is derived from colors at runtime\r\n * inside the ThemeProvider useMemo (see computeBorderTokens).\r\n */\r\nexport type ThemeTokensBase = Omit<ThemeTokens, \"borders\">;\r\n"],"mappings":";;;;;AA+WA,SAAgB,oBAAoB,QAAmC;CACrE,MAAM,OAAO;CACb,MAAM,SAAS;CACf,MAAM,QAAQ;CACd,MAAM,SAAS,OAAO,OAAO;CAC7B,MAAM,QAAQ,OAAO,OAAO;CAC5B,MAAM,WAAW,OAAO,OAAO;CAC/B,MAAM,SAAS,OAAO,WAAW;CAEjC,OAAO;EACL,OAAO;GAAE;GAAM;GAAQ;EAAM;EAC7B,OAAO;GACL,SAAS,GAAG,KAAK,SAAS;GAC1B,OAAO,GAAG,KAAK,SAAS,OAAO;GAC/B,OAAO,GAAG,KAAK,SAAS;GACxB,OAAO,GAAG,KAAK,SAAS;EAC1B;EACA,WAAW;GAET,SAAS,aAAa,OAAO,eAAe,OAAO;GACnD,QAAQ,aAAa,OAAO,cAAc;GAC1C,QAAQ,aAAa,OAAO;EAC9B;EACA,SAAS,GAAG,KAAK,SAAS;EAC1B,UAAU,GAAG,KAAK,SAAS,OAAO;EAClC,WAAW,GAAG,KAAK,SAAS;CAC9B;AACF"}