@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":"SimulationControls.js","names":[],"sources":["../../../src/react/astro/SimulationControls.tsx"],"sourcesContent":["/**\n * @zendir/ui - SimulationControls Component\n *\n * Space operations simulation playback controls following Astro UX patterns.\n * Provides play/pause/stop/step controls for simulation time management.\n *\n * Features:\n * - Play/Pause toggle with visual state feedback\n * - Stop (reset) functionality\n * - Step forward/backward controls\n * - Time scale (playback rate) adjustment\n * - Simulation time display integration\n * - AOS/LOS timer integration\n * - Keyboard accessibility\n * - AstroUXDS compliant styling\n *\n * Space Operations Terminology:\n * - SIM: Simulation mode indicator\n * - MET: Mission Elapsed Time\n * - Epoch: Simulation start time (t=0)\n * - Time Scale: Playback speed multiplier\n *\n * @example\n * ```tsx\n * import { SimulationControls } from '@zendir/ui/react';\n *\n * <SimulationControls\n * isPlaying={isPlaying}\n * onPlay={handlePlay}\n * onPause={handlePause}\n * onStop={handleStop}\n * timeScale={1}\n * onTimeScaleChange={setTimeScale}\n * currentTime={simulationTime}\n * epoch={epoch}\n * />\n * ```\n */\n\nimport React, { memo, useState, useCallback, useMemo } from \"react\";\nimport { useTheme } from \"../theme\";\nimport { classNames, tabularNumsStyle, safeAccentText } from \"../utils\";\nimport { AstroIcon } from \"../core/display/AstroIcon\";\nimport { Tooltip } from \"../core/overlays/Tooltip\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type SimulationState = \"stopped\" | \"playing\" | \"paused\";\n\nexport interface SimulationControlsProps {\n /** Current simulation state */\n state?: SimulationState;\n /** Whether simulation is playing (alternative to state) */\n isPlaying?: boolean;\n /** Whether simulation is paused (used with isPlaying for 3-state) */\n isPaused?: boolean;\n\n // === Playback Callbacks ===\n /** Called when play is triggered */\n onPlay?: () => void;\n /** Called when pause is triggered */\n onPause?: () => void;\n /** Called when stop (reset) is triggered */\n onStop?: () => void;\n /** Called when step forward is triggered */\n onStepForward?: () => void;\n /** Called when step backward is triggered */\n onStepBackward?: () => void;\n\n // === Time Display ===\n /** Current simulation time */\n currentTime?: Date | string;\n /** Simulation epoch (t=0) */\n epoch?: Date | string;\n /** Current elapsed seconds from epoch */\n elapsedSeconds?: number;\n /** Show elapsed time display */\n showElapsedTime?: boolean;\n /** Show current time display */\n showCurrentTime?: boolean;\n /** Show date in time display */\n showDate?: boolean;\n\n // === Time Scale ===\n /** Current time scale factor (1 = realtime) */\n timeScale?: number;\n /** Callback when time scale changes */\n onTimeScaleChange?: (scale: number) => void;\n /** Available time scale presets */\n timeScalePresets?: number[];\n /** Show time scale controls */\n showTimeScale?: boolean;\n\n // === Step Size ===\n /** Step size in seconds */\n stepSize?: number;\n /** Callback when step size changes */\n onStepSizeChange?: (size: number) => void;\n /** Available step size presets in seconds */\n stepSizePresets?: number[];\n /** Show step controls */\n showStepControls?: boolean;\n\n // === Appearance ===\n /** Compact display mode */\n compact?: boolean;\n /** Small size variant */\n small?: boolean;\n /** Custom className */\n className?: string;\n /** Disabled state */\n disabled?: boolean;\n /** Show simulation status indicator */\n showStatus?: boolean;\n /** Custom label for simulation mode */\n label?: string;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DEFAULT_TIME_SCALE_PRESETS = [0.1, 0.25, 0.5, 1, 2, 5, 10, 60, 3600];\nconst DEFAULT_STEP_SIZE_PRESETS = [1, 5, 10, 30, 60, 300, 600, 3600];\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Format elapsed time as DD:HH:MM:SS\n */\nfunction formatElapsedTime(seconds: number): string {\n const absSeconds = Math.abs(seconds);\n const days = Math.floor(absSeconds / 86400);\n const hours = Math.floor((absSeconds % 86400) / 3600);\n const mins = Math.floor((absSeconds % 3600) / 60);\n const secs = Math.floor(absSeconds % 60);\n\n const sign = seconds < 0 ? \"-\" : \"\";\n\n if (days > 0) {\n return `${sign}${days}:${hours.toString().padStart(2, \"0\")}:${mins.toString().padStart(2, \"0\")}:${secs.toString().padStart(2, \"0\")}`;\n }\n return `${sign}${hours.toString().padStart(2, \"0\")}:${mins.toString().padStart(2, \"0\")}:${secs.toString().padStart(2, \"0\")}`;\n}\n\n/**\n * Format time scale for display\n */\nfunction formatTimeScale(scale: number): string {\n if (scale >= 3600) return `${scale / 3600}h/s`;\n if (scale >= 60) return `${scale / 60}m/s`;\n if (scale === 1) return \"1×\";\n if (scale < 1) return `${scale}×`;\n return `${scale}×`;\n}\n\n/**\n * Format step size for display\n */\nfunction formatStepSize(seconds: number): string {\n if (seconds >= 3600) return `${seconds / 3600}h`;\n if (seconds >= 60) return `${seconds / 60}m`;\n return `${seconds}s`;\n}\n\n/**\n * Format time for display\n */\nfunction formatTimeDisplay(date: Date): string {\n const hours = date.getUTCHours().toString().padStart(2, \"0\");\n const mins = date.getUTCMinutes().toString().padStart(2, \"0\");\n const secs = date.getUTCSeconds().toString().padStart(2, \"0\");\n return `${hours}:${mins}:${secs}`;\n}\n\n/**\n * Format date for display\n */\nfunction formatDateDisplay(date: Date): string {\n const year = date.getUTCFullYear();\n const month = (date.getUTCMonth() + 1).toString().padStart(2, \"0\");\n const day = date.getUTCDate().toString().padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\n// ============================================================================\n// Sub-components\n// ============================================================================\n\ninterface ControlButtonProps {\n icon: string;\n label: string;\n onClick?: () => void;\n active?: boolean;\n disabled?: boolean;\n danger?: boolean;\n tokens: ReturnType<typeof useTheme>[\"tokens\"];\n small?: boolean;\n}\n\nconst ControlButton = memo(function ControlButton({\n icon,\n label,\n onClick,\n active = false,\n disabled = false,\n danger = false,\n tokens,\n small = false,\n}: ControlButtonProps) {\n const size = small ? 28 : 32;\n const iconSize = small ? 14 : 16;\n\n const baseColor = danger\n ? tokens.colors.status.critical\n : active\n ? tokens.colors.status.normal\n : tokens.colors.text.primary;\n\n const bgColor = active\n ? `${tokens.colors.status.normal}25`\n : \"rgba(255,255,255,0.14)\";\n\n return (\n <Tooltip content={label} placement=\"bottom\">\n <button\n onClick={onClick}\n disabled={disabled}\n aria-label={label}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: size,\n height: size,\n padding: 0,\n border: \"none\",\n borderRadius: tokens.borderRadius.md,\n backgroundColor: disabled ? \"rgba(255,255,255,0.06)\" : bgColor,\n color: disabled ? tokens.colors.text.tertiary : baseColor,\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n transition: \"all 0.15s ease\",\n opacity: disabled ? 0.5 : 1,\n }}\n onMouseEnter={(e) => {\n if (!disabled) {\n e.currentTarget.style.backgroundColor = active\n ? `${tokens.colors.status.normal}35`\n : \"rgba(255,255,255,0.22)\";\n }\n }}\n onMouseLeave={(e) => {\n if (!disabled) {\n e.currentTarget.style.backgroundColor = bgColor;\n }\n }}\n >\n <AstroIcon\n name={icon as any}\n size={iconSize}\n color={disabled ? tokens.colors.text.tertiary : baseColor}\n />\n </button>\n </Tooltip>\n );\n});\n\ninterface TimeScaleSelectorProps {\n value: number;\n presets: number[];\n onChange?: (scale: number) => void;\n disabled?: boolean;\n tokens: ReturnType<typeof useTheme>[\"tokens\"];\n small?: boolean;\n}\n\nconst TimeScaleSelector = memo(function TimeScaleSelector({\n value,\n presets,\n onChange,\n disabled = false,\n tokens,\n small = false,\n}: TimeScaleSelectorProps) {\n const [isOpen, setIsOpen] = useState(false);\n\n return (\n <div style={{ position: \"relative\" }}>\n <Tooltip content=\"Time Scale / Playback Speed\" placement=\"bottom\">\n <button\n onClick={() => !disabled && setIsOpen(!isOpen)}\n disabled={disabled}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 4,\n padding: small ? \"4px 8px\" : \"6px 10px\",\n border: \"none\",\n borderRadius: tokens.borderRadius.sm,\n backgroundColor: \"rgba(255,255,255,0.08)\",\n color:\n value !== 1\n ? tokens.colors.status.caution\n : tokens.colors.text.secondary,\n fontSize: small ? tokens.typography.fontSize.xxs : \"0.6875rem\", // 10px/11px in rem\n fontWeight: 700, // AstroUXDS bold for time display (critical info)\n fontFamily: tokens.typography.fontFamily.mono,\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n transition: \"all 0.15s ease\",\n opacity: disabled ? 0.5 : 1,\n ...tabularNumsStyle,\n }}\n >\n <AstroIcon\n name=\"fast-forward\"\n size={small ? 10 : 12}\n color=\"inherit\"\n />\n {formatTimeScale(value)}\n </button>\n </Tooltip>\n\n {isOpen && !disabled && (\n <>\n <div\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: 99,\n }}\n onClick={() => setIsOpen(false)}\n />\n <div\n style={{\n position: \"absolute\",\n top: \"100%\",\n left: \"50%\",\n transform: \"translateX(-50%)\",\n marginTop: 4,\n padding: 4,\n backgroundColor: tokens.colors.background.elevated,\n border: `1px solid ${tokens.colors.border.default}`,\n borderRadius: tokens.borderRadius.md,\n boxShadow: tokens.shadows.lg,\n zIndex: 100,\n minWidth: 80,\n }}\n >\n {presets.map((scale) => (\n <button\n key={scale}\n onClick={() => {\n onChange?.(scale);\n setIsOpen(false);\n }}\n style={{\n display: \"block\",\n width: \"100%\",\n padding: \"6px 12px\",\n border: \"none\",\n borderRadius: tokens.borderRadius.sm,\n backgroundColor:\n value === scale\n ? tokens.colors.accent.primary\n : \"transparent\",\n color:\n value === scale ? \"#ffffff\" : tokens.colors.text.secondary,\n fontSize: \"0.6875rem\", // 11px in rem\n fontWeight: value === scale ? 500 : 400, // AstroUXDS: 500 (medium) not 600\n fontFamily: tokens.typography.fontFamily.mono,\n textAlign: \"center\",\n cursor: \"pointer\",\n transition: \"all 0.1s ease\",\n ...tabularNumsStyle,\n }}\n onMouseEnter={(e) => {\n if (value !== scale) {\n e.currentTarget.style.backgroundColor =\n \"rgba(255,255,255,0.1)\";\n }\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.backgroundColor =\n value === scale\n ? tokens.colors.accent.primary\n : \"transparent\";\n }}\n >\n {formatTimeScale(scale)}\n </button>\n ))}\n </div>\n </>\n )}\n </div>\n );\n});\n\ninterface SimulationTimeDisplayProps {\n currentTime?: Date;\n elapsedSeconds?: number;\n showElapsed?: boolean;\n showCurrent?: boolean;\n showDate?: boolean;\n isPlaying?: boolean;\n tokens: ReturnType<typeof useTheme>[\"tokens\"];\n small?: boolean;\n}\n\nconst SimulationTimeDisplay = memo(function SimulationTimeDisplay({\n currentTime,\n elapsedSeconds,\n showElapsed = true,\n showCurrent = true,\n showDate = false,\n isPlaying = false,\n tokens,\n small = false,\n}: SimulationTimeDisplayProps) {\n return (\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: 2,\n padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,\n }}\n >\n {showDate && currentTime && (\n <span\n style={{\n fontSize: tokens.typography.fontSize.xs,\n fontFamily: tokens.typography.fontFamily.mono,\n color: tokens.colors.text.tertiary,\n ...tabularNumsStyle,\n }}\n >\n {formatDateDisplay(currentTime)}\n </span>\n )}\n\n {showCurrent && currentTime && (\n <span\n style={{\n fontSize: small\n ? tokens.typography.fontSize.lg\n : tokens.typography.fontSize.xl,\n fontWeight: tokens.typography.fontWeight.bold, // Bold (700) to match MissionClock\n fontFamily: tokens.typography.fontFamily.mono,\n color: tokens.colors.text.primary,\n ...tabularNumsStyle,\n }}\n >\n {formatTimeDisplay(currentTime)}\n </span>\n )}\n\n {showElapsed && elapsedSeconds !== undefined && (\n <span\n style={{\n fontSize: tokens.typography.fontSize.sm,\n fontFamily: tokens.typography.fontFamily.mono,\n color: tokens.colors.status.caution,\n ...tabularNumsStyle,\n }}\n >\n T{elapsedSeconds >= 0 ? \"+\" : \"\"}\n {formatElapsedTime(elapsedSeconds)}\n </span>\n )}\n\n <span\n style={{\n fontSize: tokens.typography.fontSize.xs,\n fontWeight: tokens.typography.fontWeight.medium,\n color: isPlaying\n ? safeAccentText(tokens.colors.accent.primary)\n : tokens.colors.status.standby,\n textTransform: \"uppercase\",\n letterSpacing: tokens.typography.letterSpacing.wide,\n display: \"flex\",\n alignItems: \"center\",\n gap: 4,\n }}\n >\n {isPlaying && (\n <span\n style={{\n width: 6,\n height: 6,\n borderRadius: \"50%\",\n backgroundColor: tokens.colors.accent.primary,\n animation: \"pulse 1.5s ease-in-out infinite\",\n }}\n />\n )}\n SIM\n </span>\n </div>\n );\n});\n\n// ============================================================================\n// Main Component\n// ============================================================================\n\n/**\n * SimulationControls - Playback controls for space simulation\n *\n * Provides comprehensive simulation time control following Astro UX patterns.\n * Designed for mission control and space operations applications.\n *\n * @example\n * ```tsx\n * // Basic usage\n * <SimulationControls\n * isPlaying={isPlaying}\n * onPlay={() => setIsPlaying(true)}\n * onPause={() => setIsPlaying(false)}\n * onStop={handleReset}\n * />\n *\n * // Full featured\n * <SimulationControls\n * isPlaying={isPlaying}\n * onPlay={handlePlay}\n * onPause={handlePause}\n * onStop={handleStop}\n * onStepForward={handleStepForward}\n * onStepBackward={handleStepBackward}\n * currentTime={simulationTime}\n * epoch={epoch}\n * elapsedSeconds={elapsed}\n * timeScale={timeScale}\n * onTimeScaleChange={setTimeScale}\n * showTimeScale\n * showStepControls\n * showElapsedTime\n * />\n *\n * // In AppBar center slot\n * <AppBar\n * appName=\"Mission Control\"\n * centerSlot={\n * <SimulationControls\n * compact\n * isPlaying={isPlaying}\n * onPlay={handlePlay}\n * onPause={handlePause}\n * currentTime={simTime}\n * />\n * }\n * />\n * ```\n */\nexport const SimulationControls = memo(function SimulationControls({\n state,\n isPlaying: isPlayingProp,\n isPaused,\n onPlay,\n onPause,\n onStop,\n onStepForward,\n onStepBackward,\n currentTime: currentTimeProp,\n epoch,\n elapsedSeconds: elapsedSecondsProp,\n showElapsedTime = true,\n showCurrentTime = true,\n showDate = true,\n timeScale = 1,\n onTimeScaleChange,\n timeScalePresets = DEFAULT_TIME_SCALE_PRESETS,\n showTimeScale = true,\n stepSize = 60,\n onStepSizeChange: _onStepSizeChange,\n stepSizePresets: _stepSizePresets = DEFAULT_STEP_SIZE_PRESETS,\n showStepControls = true,\n compact = false,\n small = false,\n className = \"\",\n disabled = false,\n showStatus = true,\n label,\n}: SimulationControlsProps): React.ReactElement {\n const { tokens, theme } = useTheme();\n const isTransparentTheme =\n theme === \"transparent\" ||\n theme === \"transparent-bold\" ||\n theme === \"transparent-minimal\";\n\n // Derive playing state from props\n const isPlaying = useMemo(() => {\n if (state) return state === \"playing\";\n return isPlayingProp ?? false;\n }, [state, isPlayingProp]);\n\n const isStopped = useMemo(() => {\n if (state) return state === \"stopped\";\n return !isPlayingProp && !isPaused;\n }, [state, isPlayingProp, isPaused]);\n\n // Parse current time\n const currentTime = useMemo(() => {\n if (!currentTimeProp) return undefined;\n return typeof currentTimeProp === \"string\"\n ? new Date(currentTimeProp)\n : currentTimeProp;\n }, [currentTimeProp]);\n\n // Calculate elapsed seconds\n const elapsedSeconds = useMemo(() => {\n if (elapsedSecondsProp !== undefined) return elapsedSecondsProp;\n if (currentTime && epoch) {\n const epochDate = typeof epoch === \"string\" ? new Date(epoch) : epoch;\n return (currentTime.getTime() - epochDate.getTime()) / 1000;\n }\n return undefined;\n }, [elapsedSecondsProp, currentTime, epoch]);\n\n // Handle play/pause toggle\n const handlePlayPause = useCallback(() => {\n if (isPlaying) {\n onPause?.();\n } else {\n onPlay?.();\n }\n }, [isPlaying, onPlay, onPause]);\n\n // Handle keyboard shortcuts\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (disabled) return;\n\n switch (e.key) {\n case \" \":\n e.preventDefault();\n handlePlayPause();\n break;\n case \"Escape\":\n e.preventDefault();\n onStop?.();\n break;\n case \"ArrowRight\":\n e.preventDefault();\n onStepForward?.();\n break;\n case \"ArrowLeft\":\n e.preventDefault();\n onStepBackward?.();\n break;\n }\n },\n [disabled, handlePlayPause, onStop, onStepForward, onStepBackward],\n );\n\n return (\n <div\n className={classNames(\"zendir-simulation-controls\", className)}\n role=\"group\"\n aria-label={label || \"Simulation Controls\"}\n onKeyDown={handleKeyDown}\n tabIndex={0}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: compact ? tokens.spacing.sm : tokens.spacing.md,\n padding: compact\n ? `${tokens.spacing.xs} ${tokens.spacing.sm}`\n : `${tokens.spacing.sm} ${tokens.spacing.md}`,\n backgroundColor: isTransparentTheme\n ? \"rgba(0,0,0,0.3)\"\n : tokens.colors.background.surface,\n ...(isTransparentTheme && {\n backdropFilter: \"blur(12px)\",\n WebkitBackdropFilter: \"blur(12px)\" as const,\n }),\n ...(tokens.colors.border.cardStyle ?? {\n border: `1px solid ${tokens.colors.border.muted}`,\n }),\n borderRadius: tokens.borderRadius.lg,\n outline: \"none\",\n transition: tokens.animation.fast,\n }}\n >\n {/* Time Display (if showing time) */}\n {(showCurrentTime || showElapsedTime) && !compact && (\n <SimulationTimeDisplay\n currentTime={currentTime}\n elapsedSeconds={elapsedSeconds}\n showElapsed={showElapsedTime}\n showCurrent={showCurrentTime}\n showDate={showDate}\n isPlaying={isPlaying}\n tokens={tokens}\n small={small}\n />\n )}\n\n {/* Compact time display */}\n {compact && currentTime && (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 8,\n }}\n >\n {showDate && (\n <span\n style={{\n fontSize: tokens.typography.fontSize.xs,\n fontFamily: tokens.typography.fontFamily.mono,\n color: tokens.colors.text.tertiary,\n ...tabularNumsStyle,\n }}\n >\n {formatDateDisplay(currentTime)}\n </span>\n )}\n <span\n style={{\n fontSize: tokens.typography.fontSize.lg,\n fontWeight: tokens.typography.fontWeight.bold, // Bold (700) to match MissionClock\n fontFamily: tokens.typography.fontFamily.mono,\n color: tokens.colors.text.primary,\n ...tabularNumsStyle,\n }}\n >\n {formatTimeDisplay(currentTime)}\n </span>\n {showStatus && (\n <span\n style={{\n fontSize: tokens.typography.fontSize.xs,\n fontWeight: tokens.typography.fontWeight.medium,\n color: isPlaying\n ? safeAccentText(tokens.colors.accent.primary)\n : tokens.colors.status.standby,\n textTransform: \"uppercase\",\n display: \"flex\",\n alignItems: \"center\",\n gap: 4,\n }}\n >\n {isPlaying && (\n <span\n style={{\n width: 6,\n height: 6,\n borderRadius: \"50%\",\n backgroundColor: tokens.colors.accent.primary,\n }}\n />\n )}\n SIM\n </span>\n )}\n </div>\n )}\n\n {/* Separator */}\n {(showCurrentTime || showElapsedTime || (compact && currentTime)) && (\n <div\n style={{\n width: 1,\n height: 24,\n backgroundColor: tokens.colors.border.muted,\n }}\n />\n )}\n\n {/* Transport Controls */}\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 4,\n }}\n >\n {/* Step Backward */}\n {showStepControls && (\n <ControlButton\n icon=\"skip-previous\"\n label={`Step backward ${formatStepSize(stepSize)}`}\n onClick={onStepBackward}\n disabled={disabled || !onStepBackward}\n tokens={tokens}\n small={small}\n />\n )}\n\n {/* Stop */}\n <ControlButton\n icon=\"stop\"\n label=\"Stop / Reset\"\n onClick={onStop}\n disabled={disabled || !onStop}\n danger={!isStopped}\n tokens={tokens}\n small={small}\n />\n\n {/* Play/Pause */}\n <Tooltip\n content={isPlaying ? \"Pause (Space)\" : \"Play (Space)\"}\n placement=\"bottom\"\n >\n <button\n onClick={handlePlayPause}\n disabled={disabled}\n aria-label={isPlaying ? \"Pause simulation\" : \"Play simulation\"}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: small ? 36 : 44,\n height: small ? 36 : 44,\n padding: 0,\n border: isPlaying\n ? `2px solid ${tokens.colors.accent.primary}`\n : \"none\",\n borderRadius: \"50%\",\n // Use accent primary for both states - playing has outline style\n backgroundColor: isPlaying\n ? \"rgba(138, 43, 226, 0.15)\" // Subtle purple fill when playing\n : tokens.colors.accent.primary,\n color: isPlaying\n ? safeAccentText(tokens.colors.accent.primary)\n : \"#ffffff\",\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n transition: \"all 0.15s ease\",\n opacity: disabled ? 0.5 : 1,\n boxShadow: isPlaying\n ? `0 0 16px ${tokens.colors.accent.primary}50`\n : `0 0 8px ${tokens.colors.accent.primary}40`,\n }}\n onMouseEnter={(e) => {\n if (!disabled) {\n e.currentTarget.style.transform = \"scale(1.05)\";\n }\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n >\n <AstroIcon\n name={isPlaying ? \"pause\" : \"play-arrow\"}\n size={small ? 18 : 22}\n color={isPlaying ? tokens.colors.accent.primary : \"#ffffff\"}\n />\n </button>\n </Tooltip>\n\n {/* Step Forward */}\n {showStepControls && (\n <ControlButton\n icon=\"skip-next\"\n label={`Step forward ${formatStepSize(stepSize)}`}\n onClick={onStepForward}\n disabled={disabled || !onStepForward}\n tokens={tokens}\n small={small}\n />\n )}\n </div>\n\n {/* Time Scale */}\n {showTimeScale && (\n <>\n <div\n style={{\n width: 1,\n height: 24,\n backgroundColor: tokens.colors.border.muted,\n }}\n />\n <TimeScaleSelector\n value={timeScale}\n presets={timeScalePresets}\n onChange={onTimeScaleChange}\n disabled={disabled || !onTimeScaleChange}\n tokens={tokens}\n small={small}\n />\n </>\n )}\n </div>\n );\n});\n\n// ============================================================================\n// Convenience Components\n// ============================================================================\n\n/**\n * MiniSimulationControls - Ultra-compact controls for AppBar\n */\nexport interface MiniSimulationControlsProps {\n isPlaying?: boolean;\n onPlay?: () => void;\n onPause?: () => void;\n onStop?: () => void;\n timeScale?: number;\n onTimeScaleChange?: (scale: number) => void;\n disabled?: boolean;\n className?: string;\n}\n\nexport const MiniSimulationControls = memo(function MiniSimulationControls({\n isPlaying = false,\n onPlay,\n onPause,\n onStop,\n timeScale = 1,\n onTimeScaleChange,\n disabled = false,\n className = \"\",\n}: MiniSimulationControlsProps): React.ReactElement {\n return (\n <SimulationControls\n isPlaying={isPlaying}\n onPlay={onPlay}\n onPause={onPause}\n onStop={onStop}\n timeScale={timeScale}\n onTimeScaleChange={onTimeScaleChange}\n disabled={disabled}\n compact\n small\n showCurrentTime={false}\n showElapsedTime={false}\n showDate={false}\n showStepControls={false}\n showTimeScale={Boolean(onTimeScaleChange)}\n className={className}\n />\n );\n});\n\n/**\n * SimulationControlsWithClock - Controls combined with MissionClock\n */\nexport interface SimulationControlsWithClockProps extends Omit<\n SimulationControlsProps,\n \"showCurrentTime\" | \"showElapsedTime\"\n> {\n /** Clock format */\n clockFormat?: \"12h\" | \"24h\";\n /** Timezone label */\n timezone?: string;\n}\n\nexport const SimulationControlsWithClock = memo(\n function SimulationControlsWithClock({\n clockFormat: _clockFormat = \"24h\",\n timezone = \"SIM\",\n ...props\n }: SimulationControlsWithClockProps): React.ReactElement {\n return (\n <SimulationControls\n {...props}\n showCurrentTime\n showElapsedTime\n showDate\n label={`Simulation Controls - ${timezone}`}\n />\n );\n },\n);\n\nexport default SimulationControls;\n"],"mappings":"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"}
1
+ {"version":3,"file":"SimulationControls.js","names":[],"sources":["../../../src/react/astro/SimulationControls.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - SimulationControls Component\r\n *\r\n * Space operations simulation playback controls following Astro UX patterns.\r\n * Provides play/pause/stop/step controls for simulation time management.\r\n *\r\n * Features:\r\n * - Play/Pause toggle with visual state feedback\r\n * - Stop (reset) functionality\r\n * - Step forward/backward controls\r\n * - Time scale (playback rate) adjustment\r\n * - Simulation time display integration\r\n * - AOS/LOS timer integration\r\n * - Keyboard accessibility\r\n * - AstroUXDS compliant styling\r\n *\r\n * Space Operations Terminology:\r\n * - SIM: Simulation mode indicator\r\n * - MET: Mission Elapsed Time\r\n * - Epoch: Simulation start time (t=0)\r\n * - Time Scale: Playback speed multiplier\r\n *\r\n * @example\r\n * ```tsx\r\n * import { SimulationControls } from '@zendir/ui/react';\r\n *\r\n * <SimulationControls\r\n * isPlaying={isPlaying}\r\n * onPlay={handlePlay}\r\n * onPause={handlePause}\r\n * onStop={handleStop}\r\n * timeScale={1}\r\n * onTimeScaleChange={setTimeScale}\r\n * currentTime={simulationTime}\r\n * epoch={epoch}\r\n * />\r\n * ```\r\n */\r\n\r\nimport React, { memo, useState, useCallback, useMemo } from \"react\";\r\nimport { useTheme } from \"../theme\";\r\nimport { classNames, tabularNumsStyle, safeAccentText } from \"../utils\";\r\nimport { AstroIcon } from \"../core/display/AstroIcon\";\r\nimport { Tooltip } from \"../core/overlays/Tooltip\";\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\nexport type SimulationState = \"stopped\" | \"playing\" | \"paused\";\r\n\r\nexport interface SimulationControlsProps {\r\n /** Current simulation state */\r\n state?: SimulationState;\r\n /** Whether simulation is playing (alternative to state) */\r\n isPlaying?: boolean;\r\n /** Whether simulation is paused (used with isPlaying for 3-state) */\r\n isPaused?: boolean;\r\n\r\n // === Playback Callbacks ===\r\n /** Called when play is triggered */\r\n onPlay?: () => void;\r\n /** Called when pause is triggered */\r\n onPause?: () => void;\r\n /** Called when stop (reset) is triggered */\r\n onStop?: () => void;\r\n /** Called when step forward is triggered */\r\n onStepForward?: () => void;\r\n /** Called when step backward is triggered */\r\n onStepBackward?: () => void;\r\n\r\n // === Time Display ===\r\n /** Current simulation time */\r\n currentTime?: Date | string;\r\n /** Simulation epoch (t=0) */\r\n epoch?: Date | string;\r\n /** Current elapsed seconds from epoch */\r\n elapsedSeconds?: number;\r\n /** Show elapsed time display */\r\n showElapsedTime?: boolean;\r\n /** Show current time display */\r\n showCurrentTime?: boolean;\r\n /** Show date in time display */\r\n showDate?: boolean;\r\n\r\n // === Time Scale ===\r\n /** Current time scale factor (1 = realtime) */\r\n timeScale?: number;\r\n /** Callback when time scale changes */\r\n onTimeScaleChange?: (scale: number) => void;\r\n /** Available time scale presets */\r\n timeScalePresets?: number[];\r\n /** Show time scale controls */\r\n showTimeScale?: boolean;\r\n\r\n // === Step Size ===\r\n /** Step size in seconds */\r\n stepSize?: number;\r\n /** Callback when step size changes */\r\n onStepSizeChange?: (size: number) => void;\r\n /** Available step size presets in seconds */\r\n stepSizePresets?: number[];\r\n /** Show step controls */\r\n showStepControls?: boolean;\r\n\r\n // === Appearance ===\r\n /** Compact display mode */\r\n compact?: boolean;\r\n /** Small size variant */\r\n small?: boolean;\r\n /** Custom className */\r\n className?: string;\r\n /** Disabled state */\r\n disabled?: boolean;\r\n /** Show simulation status indicator */\r\n showStatus?: boolean;\r\n /** Custom label for simulation mode */\r\n label?: string;\r\n}\r\n\r\n// ============================================================================\r\n// Constants\r\n// ============================================================================\r\n\r\nconst DEFAULT_TIME_SCALE_PRESETS = [0.1, 0.25, 0.5, 1, 2, 5, 10, 60, 3600];\r\nconst DEFAULT_STEP_SIZE_PRESETS = [1, 5, 10, 30, 60, 300, 600, 3600];\r\n\r\n// ============================================================================\r\n// Utility Functions\r\n// ============================================================================\r\n\r\n/**\r\n * Format elapsed time as DD:HH:MM:SS\r\n */\r\nfunction formatElapsedTime(seconds: number): string {\r\n const absSeconds = Math.abs(seconds);\r\n const days = Math.floor(absSeconds / 86400);\r\n const hours = Math.floor((absSeconds % 86400) / 3600);\r\n const mins = Math.floor((absSeconds % 3600) / 60);\r\n const secs = Math.floor(absSeconds % 60);\r\n\r\n const sign = seconds < 0 ? \"-\" : \"\";\r\n\r\n if (days > 0) {\r\n return `${sign}${days}:${hours.toString().padStart(2, \"0\")}:${mins.toString().padStart(2, \"0\")}:${secs.toString().padStart(2, \"0\")}`;\r\n }\r\n return `${sign}${hours.toString().padStart(2, \"0\")}:${mins.toString().padStart(2, \"0\")}:${secs.toString().padStart(2, \"0\")}`;\r\n}\r\n\r\n/**\r\n * Format time scale for display\r\n */\r\nfunction formatTimeScale(scale: number): string {\r\n if (scale >= 3600) return `${scale / 3600}h/s`;\r\n if (scale >= 60) return `${scale / 60}m/s`;\r\n if (scale === 1) return \"1×\";\r\n if (scale < 1) return `${scale}×`;\r\n return `${scale}×`;\r\n}\r\n\r\n/**\r\n * Format step size for display\r\n */\r\nfunction formatStepSize(seconds: number): string {\r\n if (seconds >= 3600) return `${seconds / 3600}h`;\r\n if (seconds >= 60) return `${seconds / 60}m`;\r\n return `${seconds}s`;\r\n}\r\n\r\n/**\r\n * Format time for display\r\n */\r\nfunction formatTimeDisplay(date: Date): string {\r\n const hours = date.getUTCHours().toString().padStart(2, \"0\");\r\n const mins = date.getUTCMinutes().toString().padStart(2, \"0\");\r\n const secs = date.getUTCSeconds().toString().padStart(2, \"0\");\r\n return `${hours}:${mins}:${secs}`;\r\n}\r\n\r\n/**\r\n * Format date for display\r\n */\r\nfunction formatDateDisplay(date: Date): string {\r\n const year = date.getUTCFullYear();\r\n const month = (date.getUTCMonth() + 1).toString().padStart(2, \"0\");\r\n const day = date.getUTCDate().toString().padStart(2, \"0\");\r\n return `${year}-${month}-${day}`;\r\n}\r\n\r\n// ============================================================================\r\n// Sub-components\r\n// ============================================================================\r\n\r\ninterface ControlButtonProps {\r\n icon: string;\r\n label: string;\r\n onClick?: () => void;\r\n active?: boolean;\r\n disabled?: boolean;\r\n danger?: boolean;\r\n tokens: ReturnType<typeof useTheme>[\"tokens\"];\r\n small?: boolean;\r\n}\r\n\r\nconst ControlButton = memo(function ControlButton({\r\n icon,\r\n label,\r\n onClick,\r\n active = false,\r\n disabled = false,\r\n danger = false,\r\n tokens,\r\n small = false,\r\n}: ControlButtonProps) {\r\n const size = small ? 28 : 32;\r\n const iconSize = small ? 14 : 16;\r\n\r\n const baseColor = danger\r\n ? tokens.colors.status.critical\r\n : active\r\n ? tokens.colors.status.normal\r\n : tokens.colors.text.primary;\r\n\r\n const bgColor = active\r\n ? `${tokens.colors.status.normal}25`\r\n : \"rgba(255,255,255,0.14)\";\r\n\r\n return (\r\n <Tooltip content={label} placement=\"bottom\">\r\n <button\r\n onClick={onClick}\r\n disabled={disabled}\r\n aria-label={label}\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n width: size,\r\n height: size,\r\n padding: 0,\r\n border: \"none\",\r\n borderRadius: tokens.borderRadius.md,\r\n backgroundColor: disabled ? \"rgba(255,255,255,0.06)\" : bgColor,\r\n color: disabled ? tokens.colors.text.tertiary : baseColor,\r\n cursor: disabled ? \"not-allowed\" : \"pointer\",\r\n transition: \"all 0.15s ease\",\r\n opacity: disabled ? 0.5 : 1,\r\n }}\r\n onMouseEnter={(e) => {\r\n if (!disabled) {\r\n e.currentTarget.style.backgroundColor = active\r\n ? `${tokens.colors.status.normal}35`\r\n : \"rgba(255,255,255,0.22)\";\r\n }\r\n }}\r\n onMouseLeave={(e) => {\r\n if (!disabled) {\r\n e.currentTarget.style.backgroundColor = bgColor;\r\n }\r\n }}\r\n >\r\n <AstroIcon\r\n name={icon as any}\r\n size={iconSize}\r\n color={disabled ? tokens.colors.text.tertiary : baseColor}\r\n />\r\n </button>\r\n </Tooltip>\r\n );\r\n});\r\n\r\ninterface TimeScaleSelectorProps {\r\n value: number;\r\n presets: number[];\r\n onChange?: (scale: number) => void;\r\n disabled?: boolean;\r\n tokens: ReturnType<typeof useTheme>[\"tokens\"];\r\n small?: boolean;\r\n}\r\n\r\nconst TimeScaleSelector = memo(function TimeScaleSelector({\r\n value,\r\n presets,\r\n onChange,\r\n disabled = false,\r\n tokens,\r\n small = false,\r\n}: TimeScaleSelectorProps) {\r\n const [isOpen, setIsOpen] = useState(false);\r\n\r\n return (\r\n <div style={{ position: \"relative\" }}>\r\n <Tooltip content=\"Time Scale / Playback Speed\" placement=\"bottom\">\r\n <button\r\n onClick={() => !disabled && setIsOpen(!isOpen)}\r\n disabled={disabled}\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: 4,\r\n padding: small ? \"4px 8px\" : \"6px 10px\",\r\n border: \"none\",\r\n borderRadius: tokens.borderRadius.sm,\r\n backgroundColor: \"rgba(255,255,255,0.08)\",\r\n color:\r\n value !== 1\r\n ? tokens.colors.status.caution\r\n : tokens.colors.text.secondary,\r\n fontSize: small ? tokens.typography.fontSize.xxs : \"0.6875rem\", // 10px/11px in rem\r\n fontWeight: 700, // AstroUXDS bold for time display (critical info)\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n cursor: disabled ? \"not-allowed\" : \"pointer\",\r\n transition: \"all 0.15s ease\",\r\n opacity: disabled ? 0.5 : 1,\r\n ...tabularNumsStyle,\r\n }}\r\n >\r\n <AstroIcon\r\n name=\"fast-forward\"\r\n size={small ? 10 : 12}\r\n color=\"inherit\"\r\n />\r\n {formatTimeScale(value)}\r\n </button>\r\n </Tooltip>\r\n\r\n {isOpen && !disabled && (\r\n <>\r\n <div\r\n style={{\r\n position: \"fixed\",\r\n inset: 0,\r\n zIndex: 99,\r\n }}\r\n onClick={() => setIsOpen(false)}\r\n />\r\n <div\r\n style={{\r\n position: \"absolute\",\r\n top: \"100%\",\r\n left: \"50%\",\r\n transform: \"translateX(-50%)\",\r\n marginTop: 4,\r\n padding: 4,\r\n backgroundColor: tokens.colors.background.elevated,\r\n border: `1px solid ${tokens.colors.border.default}`,\r\n borderRadius: tokens.borderRadius.md,\r\n boxShadow: tokens.shadows.lg,\r\n zIndex: 100,\r\n minWidth: 80,\r\n }}\r\n >\r\n {presets.map((scale) => (\r\n <button\r\n key={scale}\r\n onClick={() => {\r\n onChange?.(scale);\r\n setIsOpen(false);\r\n }}\r\n style={{\r\n display: \"block\",\r\n width: \"100%\",\r\n padding: \"6px 12px\",\r\n border: \"none\",\r\n borderRadius: tokens.borderRadius.sm,\r\n backgroundColor:\r\n value === scale\r\n ? tokens.colors.accent.primary\r\n : \"transparent\",\r\n color:\r\n value === scale ? \"#ffffff\" : tokens.colors.text.secondary,\r\n fontSize: \"0.6875rem\", // 11px in rem\r\n fontWeight: value === scale ? 500 : 400, // AstroUXDS: 500 (medium) not 600\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n textAlign: \"center\",\r\n cursor: \"pointer\",\r\n transition: \"all 0.1s ease\",\r\n ...tabularNumsStyle,\r\n }}\r\n onMouseEnter={(e) => {\r\n if (value !== scale) {\r\n e.currentTarget.style.backgroundColor =\r\n \"rgba(255,255,255,0.1)\";\r\n }\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.backgroundColor =\r\n value === scale\r\n ? tokens.colors.accent.primary\r\n : \"transparent\";\r\n }}\r\n >\r\n {formatTimeScale(scale)}\r\n </button>\r\n ))}\r\n </div>\r\n </>\r\n )}\r\n </div>\r\n );\r\n});\r\n\r\ninterface SimulationTimeDisplayProps {\r\n currentTime?: Date;\r\n elapsedSeconds?: number;\r\n showElapsed?: boolean;\r\n showCurrent?: boolean;\r\n showDate?: boolean;\r\n isPlaying?: boolean;\r\n tokens: ReturnType<typeof useTheme>[\"tokens\"];\r\n small?: boolean;\r\n}\r\n\r\nconst SimulationTimeDisplay = memo(function SimulationTimeDisplay({\r\n currentTime,\r\n elapsedSeconds,\r\n showElapsed = true,\r\n showCurrent = true,\r\n showDate = false,\r\n isPlaying = false,\r\n tokens,\r\n small = false,\r\n}: SimulationTimeDisplayProps) {\r\n return (\r\n <div\r\n style={{\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n alignItems: \"center\",\r\n gap: 2,\r\n padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,\r\n }}\r\n >\r\n {showDate && currentTime && (\r\n <span\r\n style={{\r\n fontSize: tokens.typography.fontSize.xs,\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n color: tokens.colors.text.tertiary,\r\n ...tabularNumsStyle,\r\n }}\r\n >\r\n {formatDateDisplay(currentTime)}\r\n </span>\r\n )}\r\n\r\n {showCurrent && currentTime && (\r\n <span\r\n style={{\r\n fontSize: small\r\n ? tokens.typography.fontSize.lg\r\n : tokens.typography.fontSize.xl,\r\n fontWeight: tokens.typography.fontWeight.bold, // Bold (700) to match MissionClock\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n color: tokens.colors.text.primary,\r\n ...tabularNumsStyle,\r\n }}\r\n >\r\n {formatTimeDisplay(currentTime)}\r\n </span>\r\n )}\r\n\r\n {showElapsed && elapsedSeconds !== undefined && (\r\n <span\r\n style={{\r\n fontSize: tokens.typography.fontSize.sm,\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n color: tokens.colors.status.caution,\r\n ...tabularNumsStyle,\r\n }}\r\n >\r\n T{elapsedSeconds >= 0 ? \"+\" : \"\"}\r\n {formatElapsedTime(elapsedSeconds)}\r\n </span>\r\n )}\r\n\r\n <span\r\n style={{\r\n fontSize: tokens.typography.fontSize.xs,\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n color: isPlaying\r\n ? safeAccentText(tokens.colors.accent.primary)\r\n : tokens.colors.status.standby,\r\n textTransform: \"uppercase\",\r\n letterSpacing: tokens.typography.letterSpacing.wide,\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: 4,\r\n }}\r\n >\r\n {isPlaying && (\r\n <span\r\n style={{\r\n width: 6,\r\n height: 6,\r\n borderRadius: \"50%\",\r\n backgroundColor: tokens.colors.accent.primary,\r\n animation: \"pulse 1.5s ease-in-out infinite\",\r\n }}\r\n />\r\n )}\r\n SIM\r\n </span>\r\n </div>\r\n );\r\n});\r\n\r\n// ============================================================================\r\n// Main Component\r\n// ============================================================================\r\n\r\n/**\r\n * SimulationControls - Playback controls for space simulation\r\n *\r\n * Provides comprehensive simulation time control following Astro UX patterns.\r\n * Designed for mission control and space operations applications.\r\n *\r\n * @example\r\n * ```tsx\r\n * // Basic usage\r\n * <SimulationControls\r\n * isPlaying={isPlaying}\r\n * onPlay={() => setIsPlaying(true)}\r\n * onPause={() => setIsPlaying(false)}\r\n * onStop={handleReset}\r\n * />\r\n *\r\n * // Full featured\r\n * <SimulationControls\r\n * isPlaying={isPlaying}\r\n * onPlay={handlePlay}\r\n * onPause={handlePause}\r\n * onStop={handleStop}\r\n * onStepForward={handleStepForward}\r\n * onStepBackward={handleStepBackward}\r\n * currentTime={simulationTime}\r\n * epoch={epoch}\r\n * elapsedSeconds={elapsed}\r\n * timeScale={timeScale}\r\n * onTimeScaleChange={setTimeScale}\r\n * showTimeScale\r\n * showStepControls\r\n * showElapsedTime\r\n * />\r\n *\r\n * // In AppBar center slot\r\n * <AppBar\r\n * appName=\"Mission Control\"\r\n * centerSlot={\r\n * <SimulationControls\r\n * compact\r\n * isPlaying={isPlaying}\r\n * onPlay={handlePlay}\r\n * onPause={handlePause}\r\n * currentTime={simTime}\r\n * />\r\n * }\r\n * />\r\n * ```\r\n */\r\nexport const SimulationControls = memo(function SimulationControls({\r\n state,\r\n isPlaying: isPlayingProp,\r\n isPaused,\r\n onPlay,\r\n onPause,\r\n onStop,\r\n onStepForward,\r\n onStepBackward,\r\n currentTime: currentTimeProp,\r\n epoch,\r\n elapsedSeconds: elapsedSecondsProp,\r\n showElapsedTime = true,\r\n showCurrentTime = true,\r\n showDate = true,\r\n timeScale = 1,\r\n onTimeScaleChange,\r\n timeScalePresets = DEFAULT_TIME_SCALE_PRESETS,\r\n showTimeScale = true,\r\n stepSize = 60,\r\n onStepSizeChange: _onStepSizeChange,\r\n stepSizePresets: _stepSizePresets = DEFAULT_STEP_SIZE_PRESETS,\r\n showStepControls = true,\r\n compact = false,\r\n small = false,\r\n className = \"\",\r\n disabled = false,\r\n showStatus = true,\r\n label,\r\n}: SimulationControlsProps): React.ReactElement {\r\n const { tokens, theme } = useTheme();\r\n const isTransparentTheme =\r\n theme === \"transparent\" ||\r\n theme === \"transparent-bold\" ||\r\n theme === \"transparent-minimal\";\r\n\r\n // Derive playing state from props\r\n const isPlaying = useMemo(() => {\r\n if (state) return state === \"playing\";\r\n return isPlayingProp ?? false;\r\n }, [state, isPlayingProp]);\r\n\r\n const isStopped = useMemo(() => {\r\n if (state) return state === \"stopped\";\r\n return !isPlayingProp && !isPaused;\r\n }, [state, isPlayingProp, isPaused]);\r\n\r\n // Parse current time\r\n const currentTime = useMemo(() => {\r\n if (!currentTimeProp) return undefined;\r\n return typeof currentTimeProp === \"string\"\r\n ? new Date(currentTimeProp)\r\n : currentTimeProp;\r\n }, [currentTimeProp]);\r\n\r\n // Calculate elapsed seconds\r\n const elapsedSeconds = useMemo(() => {\r\n if (elapsedSecondsProp !== undefined) return elapsedSecondsProp;\r\n if (currentTime && epoch) {\r\n const epochDate = typeof epoch === \"string\" ? new Date(epoch) : epoch;\r\n return (currentTime.getTime() - epochDate.getTime()) / 1000;\r\n }\r\n return undefined;\r\n }, [elapsedSecondsProp, currentTime, epoch]);\r\n\r\n // Handle play/pause toggle\r\n const handlePlayPause = useCallback(() => {\r\n if (isPlaying) {\r\n onPause?.();\r\n } else {\r\n onPlay?.();\r\n }\r\n }, [isPlaying, onPlay, onPause]);\r\n\r\n // Handle keyboard shortcuts\r\n const handleKeyDown = useCallback(\r\n (e: React.KeyboardEvent) => {\r\n if (disabled) return;\r\n\r\n switch (e.key) {\r\n case \" \":\r\n e.preventDefault();\r\n handlePlayPause();\r\n break;\r\n case \"Escape\":\r\n e.preventDefault();\r\n onStop?.();\r\n break;\r\n case \"ArrowRight\":\r\n e.preventDefault();\r\n onStepForward?.();\r\n break;\r\n case \"ArrowLeft\":\r\n e.preventDefault();\r\n onStepBackward?.();\r\n break;\r\n }\r\n },\r\n [disabled, handlePlayPause, onStop, onStepForward, onStepBackward],\r\n );\r\n\r\n return (\r\n <div\r\n className={classNames(\"zendir-simulation-controls\", className)}\r\n role=\"group\"\r\n aria-label={label || \"Simulation Controls\"}\r\n onKeyDown={handleKeyDown}\r\n tabIndex={0}\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: compact ? tokens.spacing.sm : tokens.spacing.md,\r\n padding: compact\r\n ? `${tokens.spacing.xs} ${tokens.spacing.sm}`\r\n : `${tokens.spacing.sm} ${tokens.spacing.md}`,\r\n backgroundColor: isTransparentTheme\r\n ? \"rgba(0,0,0,0.3)\"\r\n : tokens.colors.background.surface,\r\n ...(isTransparentTheme && {\r\n backdropFilter: \"blur(12px)\",\r\n WebkitBackdropFilter: \"blur(12px)\" as const,\r\n }),\r\n ...(tokens.colors.border.cardStyle ?? {\r\n border: `1px solid ${tokens.colors.border.muted}`,\r\n }),\r\n borderRadius: tokens.borderRadius.lg,\r\n outline: \"none\",\r\n transition: tokens.animation.fast,\r\n }}\r\n >\r\n {/* Time Display (if showing time) */}\r\n {(showCurrentTime || showElapsedTime) && !compact && (\r\n <SimulationTimeDisplay\r\n currentTime={currentTime}\r\n elapsedSeconds={elapsedSeconds}\r\n showElapsed={showElapsedTime}\r\n showCurrent={showCurrentTime}\r\n showDate={showDate}\r\n isPlaying={isPlaying}\r\n tokens={tokens}\r\n small={small}\r\n />\r\n )}\r\n\r\n {/* Compact time display */}\r\n {compact && currentTime && (\r\n <div\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: 8,\r\n }}\r\n >\r\n {showDate && (\r\n <span\r\n style={{\r\n fontSize: tokens.typography.fontSize.xs,\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n color: tokens.colors.text.tertiary,\r\n ...tabularNumsStyle,\r\n }}\r\n >\r\n {formatDateDisplay(currentTime)}\r\n </span>\r\n )}\r\n <span\r\n style={{\r\n fontSize: tokens.typography.fontSize.lg,\r\n fontWeight: tokens.typography.fontWeight.bold, // Bold (700) to match MissionClock\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n color: tokens.colors.text.primary,\r\n ...tabularNumsStyle,\r\n }}\r\n >\r\n {formatTimeDisplay(currentTime)}\r\n </span>\r\n {showStatus && (\r\n <span\r\n style={{\r\n fontSize: tokens.typography.fontSize.xs,\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n color: isPlaying\r\n ? safeAccentText(tokens.colors.accent.primary)\r\n : tokens.colors.status.standby,\r\n textTransform: \"uppercase\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: 4,\r\n }}\r\n >\r\n {isPlaying && (\r\n <span\r\n style={{\r\n width: 6,\r\n height: 6,\r\n borderRadius: \"50%\",\r\n backgroundColor: tokens.colors.accent.primary,\r\n }}\r\n />\r\n )}\r\n SIM\r\n </span>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* Separator */}\r\n {(showCurrentTime || showElapsedTime || (compact && currentTime)) && (\r\n <div\r\n style={{\r\n width: 1,\r\n height: 24,\r\n backgroundColor: tokens.colors.border.muted,\r\n }}\r\n />\r\n )}\r\n\r\n {/* Transport Controls */}\r\n <div\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: 4,\r\n }}\r\n >\r\n {/* Step Backward */}\r\n {showStepControls && (\r\n <ControlButton\r\n icon=\"skip-previous\"\r\n label={`Step backward ${formatStepSize(stepSize)}`}\r\n onClick={onStepBackward}\r\n disabled={disabled || !onStepBackward}\r\n tokens={tokens}\r\n small={small}\r\n />\r\n )}\r\n\r\n {/* Stop */}\r\n <ControlButton\r\n icon=\"stop\"\r\n label=\"Stop / Reset\"\r\n onClick={onStop}\r\n disabled={disabled || !onStop}\r\n danger={!isStopped}\r\n tokens={tokens}\r\n small={small}\r\n />\r\n\r\n {/* Play/Pause */}\r\n <Tooltip\r\n content={isPlaying ? \"Pause (Space)\" : \"Play (Space)\"}\r\n placement=\"bottom\"\r\n >\r\n <button\r\n onClick={handlePlayPause}\r\n disabled={disabled}\r\n aria-label={isPlaying ? \"Pause simulation\" : \"Play simulation\"}\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n width: small ? 36 : 44,\r\n height: small ? 36 : 44,\r\n padding: 0,\r\n border: isPlaying\r\n ? `2px solid ${tokens.colors.accent.primary}`\r\n : \"none\",\r\n borderRadius: \"50%\",\r\n // Use accent primary for both states - playing has outline style\r\n backgroundColor: isPlaying\r\n ? \"rgba(138, 43, 226, 0.15)\" // Subtle purple fill when playing\r\n : tokens.colors.accent.primary,\r\n color: isPlaying\r\n ? safeAccentText(tokens.colors.accent.primary)\r\n : \"#ffffff\",\r\n cursor: disabled ? \"not-allowed\" : \"pointer\",\r\n transition: \"all 0.15s ease\",\r\n opacity: disabled ? 0.5 : 1,\r\n boxShadow: isPlaying\r\n ? `0 0 16px ${tokens.colors.accent.primary}50`\r\n : `0 0 8px ${tokens.colors.accent.primary}40`,\r\n }}\r\n onMouseEnter={(e) => {\r\n if (!disabled) {\r\n e.currentTarget.style.transform = \"scale(1.05)\";\r\n }\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.transform = \"scale(1)\";\r\n }}\r\n >\r\n <AstroIcon\r\n name={isPlaying ? \"pause\" : \"play-arrow\"}\r\n size={small ? 18 : 22}\r\n color={isPlaying ? tokens.colors.accent.primary : \"#ffffff\"}\r\n />\r\n </button>\r\n </Tooltip>\r\n\r\n {/* Step Forward */}\r\n {showStepControls && (\r\n <ControlButton\r\n icon=\"skip-next\"\r\n label={`Step forward ${formatStepSize(stepSize)}`}\r\n onClick={onStepForward}\r\n disabled={disabled || !onStepForward}\r\n tokens={tokens}\r\n small={small}\r\n />\r\n )}\r\n </div>\r\n\r\n {/* Time Scale */}\r\n {showTimeScale && (\r\n <>\r\n <div\r\n style={{\r\n width: 1,\r\n height: 24,\r\n backgroundColor: tokens.colors.border.muted,\r\n }}\r\n />\r\n <TimeScaleSelector\r\n value={timeScale}\r\n presets={timeScalePresets}\r\n onChange={onTimeScaleChange}\r\n disabled={disabled || !onTimeScaleChange}\r\n tokens={tokens}\r\n small={small}\r\n />\r\n </>\r\n )}\r\n </div>\r\n );\r\n});\r\n\r\n// ============================================================================\r\n// Convenience Components\r\n// ============================================================================\r\n\r\n/**\r\n * MiniSimulationControls - Ultra-compact controls for AppBar\r\n */\r\nexport interface MiniSimulationControlsProps {\r\n isPlaying?: boolean;\r\n onPlay?: () => void;\r\n onPause?: () => void;\r\n onStop?: () => void;\r\n timeScale?: number;\r\n onTimeScaleChange?: (scale: number) => void;\r\n disabled?: boolean;\r\n className?: string;\r\n}\r\n\r\nexport const MiniSimulationControls = memo(function MiniSimulationControls({\r\n isPlaying = false,\r\n onPlay,\r\n onPause,\r\n onStop,\r\n timeScale = 1,\r\n onTimeScaleChange,\r\n disabled = false,\r\n className = \"\",\r\n}: MiniSimulationControlsProps): React.ReactElement {\r\n return (\r\n <SimulationControls\r\n isPlaying={isPlaying}\r\n onPlay={onPlay}\r\n onPause={onPause}\r\n onStop={onStop}\r\n timeScale={timeScale}\r\n onTimeScaleChange={onTimeScaleChange}\r\n disabled={disabled}\r\n compact\r\n small\r\n showCurrentTime={false}\r\n showElapsedTime={false}\r\n showDate={false}\r\n showStepControls={false}\r\n showTimeScale={Boolean(onTimeScaleChange)}\r\n className={className}\r\n />\r\n );\r\n});\r\n\r\n/**\r\n * SimulationControlsWithClock - Controls combined with MissionClock\r\n */\r\nexport interface SimulationControlsWithClockProps extends Omit<\r\n SimulationControlsProps,\r\n \"showCurrentTime\" | \"showElapsedTime\"\r\n> {\r\n /** Clock format */\r\n clockFormat?: \"12h\" | \"24h\";\r\n /** Timezone label */\r\n timezone?: string;\r\n}\r\n\r\nexport const SimulationControlsWithClock = memo(\r\n function SimulationControlsWithClock({\r\n clockFormat: _clockFormat = \"24h\",\r\n timezone = \"SIM\",\r\n ...props\r\n }: SimulationControlsWithClockProps): React.ReactElement {\r\n return (\r\n <SimulationControls\r\n {...props}\r\n showCurrentTime\r\n showElapsedTime\r\n showDate\r\n label={`Simulation Controls - ${timezone}`}\r\n />\r\n );\r\n },\r\n);\r\n\r\nexport default SimulationControls;\r\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"StatusIndicator.js","names":[],"sources":["../../../src/react/astro/StatusIndicator.tsx"],"sourcesContent":["/**\n * @zendir/ui - StatusIndicator Component (Pure React)\n *\n * Displays status symbols following Astro UX Design System patterns.\n * Uses the same 6-level status system: off, standby, normal, caution, serious, critical.\n *\n * Shapes per Astro UX (https://www.astrouxds.com/patterns/status-system/):\n * - Off: Circle (grey)\n * - Standby: Ring (cyan)\n * - Normal: Circle (green)\n * - Caution: Square (yellow)\n * - Serious: Diamond (orange)\n * - Critical: Triangle pointing down (red)\n *\n * Features:\n * - Dual coding (color + shape) for accessibility (WCAG 2.1 AA)\n * - Animated pulse for active states\n * - Light theme border support\n * - Theme-connected via useTheme()\n * - Keyboard accessible\n * - Screen reader support\n */\n\nimport React, { memo } from \"react\";\nimport { useTheme } from \"../theme\";\nimport { classNames, type StatusLevel } from \"../utils\";\n\nexport type StatusVariant = StatusLevel;\n\nexport interface StatusIndicatorProps {\n /** Status variant - determines color and shape */\n status: StatusVariant;\n /** Status label text */\n label?: string;\n /** Smaller size variant */\n small?: boolean;\n /** Show pulse animation for active states */\n pulse?: boolean;\n /** Show border (required for light theme per Astro UX) */\n bordered?: boolean;\n /** Custom className */\n className?: string;\n /** Click handler */\n onClick?: () => void;\n}\n\n/**\n * Astro UX Status Colors (Dark Theme)\n * https://www.astrouxds.com/patterns/status-system/\n */\nconst STATUS_COLORS_DARK: Record<StatusVariant, string> = {\n off: \"#a4abb6\",\n standby: \"#2dccff\",\n normal: \"#56f000\",\n caution: \"#fce83a\",\n serious: \"#ffb302\",\n critical: \"#ff3838\",\n};\n\n/**\n * Astro UX Status Colors (Light Theme - Fill)\n * Requires border for WCAG AA compliance\n */\nconst STATUS_COLORS_LIGHT_FILL: Record<StatusVariant, string> = {\n off: \"#7b8089\",\n standby: \"#64d9ff\",\n normal: \"#00e200\",\n caution: \"#fad800\",\n serious: \"#ffaf3d\",\n critical: \"#ff2a04\",\n};\n\n/**\n * Astro UX Status Colors (Light Theme - Border)\n * Required for light backgrounds per Astro UX\n */\nconst STATUS_COLORS_LIGHT_BORDER: Record<StatusVariant, string> = {\n off: \"#3c3e42\",\n standby: \"#285766\",\n normal: \"#005a00\",\n caution: \"#645600\",\n serious: \"#664618\",\n critical: \"#661102\",\n};\n\nconst STATUS_ARIA_LABELS: Record<StatusVariant, string> = {\n off: \"Status: Off\",\n standby: \"Status: Standby\",\n normal: \"Status: Normal\",\n caution: \"Status: Caution\",\n serious: \"Status: Serious\",\n critical: \"Status: Critical\",\n};\n\n/**\n * Render Astro-compliant status shape\n * Shapes per https://www.astrouxds.com/patterns/status-system/\n */\nconst renderStatusShape = (\n status: StatusVariant,\n size: number,\n fillColor: string,\n borderColor?: string,\n): React.ReactNode => {\n const hasBorder = !!borderColor;\n const strokeWidth = hasBorder ? 1.5 : 0;\n\n switch (status) {\n // Small filled circle: off\n case \"off\":\n return (\n <svg viewBox=\"0 0 12 12\" width={size} height={size}>\n <circle\n cx=\"6\"\n cy=\"6\"\n r={hasBorder ? 2.5 : 3}\n fill={fillColor}\n stroke={borderColor}\n strokeWidth={strokeWidth}\n />\n </svg>\n );\n\n // Ring (donut): standby\n case \"standby\":\n return (\n <svg viewBox=\"0 0 12 12\" width={size} height={size}>\n <circle\n cx=\"6\"\n cy=\"6\"\n r=\"3.5\"\n fill=\"none\"\n stroke={fillColor}\n strokeWidth=\"2\"\n />\n {hasBorder && (\n <circle\n cx=\"6\"\n cy=\"6\"\n r=\"4.75\"\n fill=\"none\"\n stroke={borderColor}\n strokeWidth={strokeWidth}\n />\n )}\n </svg>\n );\n\n // Filled circle: normal\n case \"normal\":\n return (\n <svg viewBox=\"0 0 12 12\" width={size} height={size}>\n <circle\n cx=\"6\"\n cy=\"6\"\n r={hasBorder ? 4.5 : 5}\n fill={fillColor}\n stroke={borderColor}\n strokeWidth={strokeWidth}\n />\n </svg>\n );\n\n // Square shape: caution\n case \"caution\":\n return (\n <svg viewBox=\"0 0 12 12\" width={size} height={size}>\n <rect\n x={hasBorder ? 1.5 : 1}\n y={hasBorder ? 1.5 : 1}\n width={hasBorder ? 9 : 10}\n height={hasBorder ? 9 : 10}\n fill={fillColor}\n stroke={borderColor}\n strokeWidth={strokeWidth}\n />\n </svg>\n );\n\n // Diamond shape: serious (rotated square)\n case \"serious\":\n return (\n <svg viewBox=\"0 0 12 12\" width={size} height={size}>\n <polygon\n points=\"6,1 11,6 6,11 1,6\"\n fill={fillColor}\n stroke={borderColor}\n strokeWidth={strokeWidth}\n />\n </svg>\n );\n\n // Triangle pointing down: critical\n case \"critical\":\n return (\n <svg viewBox=\"0 0 12 12\" width={size} height={size}>\n <polygon\n points=\"6,11 1,2 11,2\"\n fill={fillColor}\n stroke={borderColor}\n strokeWidth={strokeWidth}\n />\n </svg>\n );\n\n default:\n return null;\n }\n};\n\n/**\n * StatusIndicator - Pure React status component\n *\n * Displays standardized status symbols following Astro UX design patterns.\n * Uses dual-coding (color + shape) for WCAG 2.1 AA accessibility compliance.\n *\n * @example\n * ```tsx\n * <StatusIndicator status=\"normal\" label=\"Power System\" />\n * <StatusIndicator status=\"caution\" label=\"Battery Low\" />\n * <StatusIndicator status=\"critical\" label=\"Comm Loss\" pulse />\n * ```\n */\nexport const StatusIndicator = memo(function StatusIndicator({\n status,\n label,\n small = false,\n pulse = false,\n bordered,\n className = \"\",\n onClick,\n}: StatusIndicatorProps): React.ReactElement {\n const { tokens, mode } = useTheme();\n\n // Determine if we need borders (light theme or explicitly requested)\n const isLightTheme = mode === \"light\";\n const showBorder = bordered ?? isLightTheme;\n\n // Get colors from theme or use Astro defaults\n const fillColor = isLightTheme\n ? STATUS_COLORS_LIGHT_FILL[status]\n : tokens.colors.status[status] || STATUS_COLORS_DARK[status];\n const borderColor = showBorder\n ? STATUS_COLORS_LIGHT_BORDER[status]\n : undefined;\n\n const size = small ? 10 : 14;\n const shouldPulse = pulse || status === \"critical\";\n const isCircle =\n status === \"off\" || status === \"standby\" || status === \"normal\";\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (onClick && (e.key === \"Enter\" || e.key === \" \")) {\n e.preventDefault();\n onClick();\n }\n };\n\n return (\n <div\n className={classNames(\"zendir-status-indicator\", className)}\n onClick={onClick}\n onKeyDown={onClick ? handleKeyDown : undefined}\n tabIndex={onClick ? 0 : undefined}\n role=\"status\"\n aria-label={`${STATUS_ARIA_LABELS[status]}${label ? `: ${label}` : \"\"}`}\n style={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: small ? 4 : 6,\n cursor: onClick ? \"pointer\" : \"default\",\n outline: \"none\",\n }}\n >\n {/* Status symbol with dual-coding */}\n <span\n style={{\n position: \"relative\",\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: size,\n height: size,\n flexShrink: 0,\n }}\n >\n {/* Pulse animation ring */}\n {shouldPulse && (\n <span\n style={{\n position: \"absolute\",\n inset: -3,\n borderRadius: isCircle ? \"50%\" : 0,\n backgroundColor: fillColor,\n opacity: 0.3,\n animation: \"statusPulse 2s ease-out infinite\",\n }}\n />\n )}\n\n {/* Astro-compliant status shape */}\n {renderStatusShape(status, size, fillColor, borderColor)}\n </span>\n\n {/* Label with theme typography */}\n {label && (\n <span\n style={{\n fontSize: small\n ? tokens.typography.fontSize.xs\n : tokens.typography.fontSize.sm,\n fontWeight: tokens.typography.fontWeight.medium,\n color: \"inherit\",\n whiteSpace: \"nowrap\",\n letterSpacing: tokens.typography.letterSpacing.normal,\n }}\n >\n {label}\n </span>\n )}\n\n {/* Keyframe animation — paused when prefers-reduced-motion is active */}\n <style>{`\n @keyframes statusPulse {\n 0% { transform: scale(1); opacity: 0.3; }\n 50% { transform: scale(1.5); opacity: 0; }\n 100% { transform: scale(1); opacity: 0; }\n }\n @media (prefers-reduced-motion: reduce) {\n .zendir-status-indicator span[style*=\"statusPulse\"] {\n animation: none !important;\n }\n }\n `}</style>\n </div>\n );\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"StatusIndicator.js","names":[],"sources":["../../../src/react/astro/StatusIndicator.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - StatusIndicator Component (Pure React)\r\n *\r\n * Displays status symbols following Astro UX Design System patterns.\r\n * Uses the same 6-level status system: off, standby, normal, caution, serious, critical.\r\n *\r\n * Shapes per Astro UX (https://www.astrouxds.com/patterns/status-system/):\r\n * - Off: Circle (grey)\r\n * - Standby: Ring (cyan)\r\n * - Normal: Circle (green)\r\n * - Caution: Square (yellow)\r\n * - Serious: Diamond (orange)\r\n * - Critical: Triangle pointing down (red)\r\n *\r\n * Features:\r\n * - Dual coding (color + shape) for accessibility (WCAG 2.1 AA)\r\n * - Animated pulse for active states\r\n * - Light theme border support\r\n * - Theme-connected via useTheme()\r\n * - Keyboard accessible\r\n * - Screen reader support\r\n */\r\n\r\nimport React, { memo } from \"react\";\r\nimport { useTheme } from \"../theme\";\r\nimport { classNames, type StatusLevel } from \"../utils\";\r\n\r\nexport type StatusVariant = StatusLevel;\r\n\r\nexport interface StatusIndicatorProps {\r\n /** Status variant - determines color and shape */\r\n status: StatusVariant;\r\n /** Status label text */\r\n label?: string;\r\n /** Smaller size variant */\r\n small?: boolean;\r\n /** Show pulse animation for active states */\r\n pulse?: boolean;\r\n /** Show border (required for light theme per Astro UX) */\r\n bordered?: boolean;\r\n /** Custom className */\r\n className?: string;\r\n /** Click handler */\r\n onClick?: () => void;\r\n}\r\n\r\n/**\r\n * Astro UX Status Colors (Dark Theme)\r\n * https://www.astrouxds.com/patterns/status-system/\r\n */\r\nconst STATUS_COLORS_DARK: Record<StatusVariant, string> = {\r\n off: \"#a4abb6\",\r\n standby: \"#2dccff\",\r\n normal: \"#56f000\",\r\n caution: \"#fce83a\",\r\n serious: \"#ffb302\",\r\n critical: \"#ff3838\",\r\n};\r\n\r\n/**\r\n * Astro UX Status Colors (Light Theme - Fill)\r\n * Requires border for WCAG AA compliance\r\n */\r\nconst STATUS_COLORS_LIGHT_FILL: Record<StatusVariant, string> = {\r\n off: \"#7b8089\",\r\n standby: \"#64d9ff\",\r\n normal: \"#00e200\",\r\n caution: \"#fad800\",\r\n serious: \"#ffaf3d\",\r\n critical: \"#ff2a04\",\r\n};\r\n\r\n/**\r\n * Astro UX Status Colors (Light Theme - Border)\r\n * Required for light backgrounds per Astro UX\r\n */\r\nconst STATUS_COLORS_LIGHT_BORDER: Record<StatusVariant, string> = {\r\n off: \"#3c3e42\",\r\n standby: \"#285766\",\r\n normal: \"#005a00\",\r\n caution: \"#645600\",\r\n serious: \"#664618\",\r\n critical: \"#661102\",\r\n};\r\n\r\nconst STATUS_ARIA_LABELS: Record<StatusVariant, string> = {\r\n off: \"Status: Off\",\r\n standby: \"Status: Standby\",\r\n normal: \"Status: Normal\",\r\n caution: \"Status: Caution\",\r\n serious: \"Status: Serious\",\r\n critical: \"Status: Critical\",\r\n};\r\n\r\n/**\r\n * Render Astro-compliant status shape\r\n * Shapes per https://www.astrouxds.com/patterns/status-system/\r\n */\r\nconst renderStatusShape = (\r\n status: StatusVariant,\r\n size: number,\r\n fillColor: string,\r\n borderColor?: string,\r\n): React.ReactNode => {\r\n const hasBorder = !!borderColor;\r\n const strokeWidth = hasBorder ? 1.5 : 0;\r\n\r\n switch (status) {\r\n // Small filled circle: off\r\n case \"off\":\r\n return (\r\n <svg viewBox=\"0 0 12 12\" width={size} height={size}>\r\n <circle\r\n cx=\"6\"\r\n cy=\"6\"\r\n r={hasBorder ? 2.5 : 3}\r\n fill={fillColor}\r\n stroke={borderColor}\r\n strokeWidth={strokeWidth}\r\n />\r\n </svg>\r\n );\r\n\r\n // Ring (donut): standby\r\n case \"standby\":\r\n return (\r\n <svg viewBox=\"0 0 12 12\" width={size} height={size}>\r\n <circle\r\n cx=\"6\"\r\n cy=\"6\"\r\n r=\"3.5\"\r\n fill=\"none\"\r\n stroke={fillColor}\r\n strokeWidth=\"2\"\r\n />\r\n {hasBorder && (\r\n <circle\r\n cx=\"6\"\r\n cy=\"6\"\r\n r=\"4.75\"\r\n fill=\"none\"\r\n stroke={borderColor}\r\n strokeWidth={strokeWidth}\r\n />\r\n )}\r\n </svg>\r\n );\r\n\r\n // Filled circle: normal\r\n case \"normal\":\r\n return (\r\n <svg viewBox=\"0 0 12 12\" width={size} height={size}>\r\n <circle\r\n cx=\"6\"\r\n cy=\"6\"\r\n r={hasBorder ? 4.5 : 5}\r\n fill={fillColor}\r\n stroke={borderColor}\r\n strokeWidth={strokeWidth}\r\n />\r\n </svg>\r\n );\r\n\r\n // Square shape: caution\r\n case \"caution\":\r\n return (\r\n <svg viewBox=\"0 0 12 12\" width={size} height={size}>\r\n <rect\r\n x={hasBorder ? 1.5 : 1}\r\n y={hasBorder ? 1.5 : 1}\r\n width={hasBorder ? 9 : 10}\r\n height={hasBorder ? 9 : 10}\r\n fill={fillColor}\r\n stroke={borderColor}\r\n strokeWidth={strokeWidth}\r\n />\r\n </svg>\r\n );\r\n\r\n // Diamond shape: serious (rotated square)\r\n case \"serious\":\r\n return (\r\n <svg viewBox=\"0 0 12 12\" width={size} height={size}>\r\n <polygon\r\n points=\"6,1 11,6 6,11 1,6\"\r\n fill={fillColor}\r\n stroke={borderColor}\r\n strokeWidth={strokeWidth}\r\n />\r\n </svg>\r\n );\r\n\r\n // Triangle pointing down: critical\r\n case \"critical\":\r\n return (\r\n <svg viewBox=\"0 0 12 12\" width={size} height={size}>\r\n <polygon\r\n points=\"6,11 1,2 11,2\"\r\n fill={fillColor}\r\n stroke={borderColor}\r\n strokeWidth={strokeWidth}\r\n />\r\n </svg>\r\n );\r\n\r\n default:\r\n return null;\r\n }\r\n};\r\n\r\n/**\r\n * StatusIndicator - Pure React status component\r\n *\r\n * Displays standardized status symbols following Astro UX design patterns.\r\n * Uses dual-coding (color + shape) for WCAG 2.1 AA accessibility compliance.\r\n *\r\n * @example\r\n * ```tsx\r\n * <StatusIndicator status=\"normal\" label=\"Power System\" />\r\n * <StatusIndicator status=\"caution\" label=\"Battery Low\" />\r\n * <StatusIndicator status=\"critical\" label=\"Comm Loss\" pulse />\r\n * ```\r\n */\r\nexport const StatusIndicator = memo(function StatusIndicator({\r\n status,\r\n label,\r\n small = false,\r\n pulse = false,\r\n bordered,\r\n className = \"\",\r\n onClick,\r\n}: StatusIndicatorProps): React.ReactElement {\r\n const { tokens, mode } = useTheme();\r\n\r\n // Determine if we need borders (light theme or explicitly requested)\r\n const isLightTheme = mode === \"light\";\r\n const showBorder = bordered ?? isLightTheme;\r\n\r\n // Get colors from theme or use Astro defaults\r\n const fillColor = isLightTheme\r\n ? STATUS_COLORS_LIGHT_FILL[status]\r\n : tokens.colors.status[status] || STATUS_COLORS_DARK[status];\r\n const borderColor = showBorder\r\n ? STATUS_COLORS_LIGHT_BORDER[status]\r\n : undefined;\r\n\r\n const size = small ? 10 : 14;\r\n const shouldPulse = pulse || status === \"critical\";\r\n const isCircle =\r\n status === \"off\" || status === \"standby\" || status === \"normal\";\r\n\r\n const handleKeyDown = (e: React.KeyboardEvent) => {\r\n if (onClick && (e.key === \"Enter\" || e.key === \" \")) {\r\n e.preventDefault();\r\n onClick();\r\n }\r\n };\r\n\r\n return (\r\n <div\r\n className={classNames(\"zendir-status-indicator\", className)}\r\n onClick={onClick}\r\n onKeyDown={onClick ? handleKeyDown : undefined}\r\n tabIndex={onClick ? 0 : undefined}\r\n role=\"status\"\r\n aria-label={`${STATUS_ARIA_LABELS[status]}${label ? `: ${label}` : \"\"}`}\r\n style={{\r\n display: \"inline-flex\",\r\n alignItems: \"center\",\r\n gap: small ? 4 : 6,\r\n cursor: onClick ? \"pointer\" : \"default\",\r\n outline: \"none\",\r\n }}\r\n >\r\n {/* Status symbol with dual-coding */}\r\n <span\r\n style={{\r\n position: \"relative\",\r\n display: \"inline-flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n width: size,\r\n height: size,\r\n flexShrink: 0,\r\n }}\r\n >\r\n {/* Pulse animation ring */}\r\n {shouldPulse && (\r\n <span\r\n style={{\r\n position: \"absolute\",\r\n inset: -3,\r\n borderRadius: isCircle ? \"50%\" : 0,\r\n backgroundColor: fillColor,\r\n opacity: 0.3,\r\n animation: \"statusPulse 2s ease-out infinite\",\r\n }}\r\n />\r\n )}\r\n\r\n {/* Astro-compliant status shape */}\r\n {renderStatusShape(status, size, fillColor, borderColor)}\r\n </span>\r\n\r\n {/* Label with theme typography */}\r\n {label && (\r\n <span\r\n style={{\r\n fontSize: small\r\n ? tokens.typography.fontSize.xs\r\n : tokens.typography.fontSize.sm,\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n color: \"inherit\",\r\n whiteSpace: \"nowrap\",\r\n letterSpacing: tokens.typography.letterSpacing.normal,\r\n }}\r\n >\r\n {label}\r\n </span>\r\n )}\r\n\r\n {/* Keyframe animation — paused when prefers-reduced-motion is active */}\r\n <style>{`\r\n @keyframes statusPulse {\r\n 0% { transform: scale(1); opacity: 0.3; }\r\n 50% { transform: scale(1.5); opacity: 0; }\r\n 100% { transform: scale(1); opacity: 0; }\r\n }\r\n @media (prefers-reduced-motion: reduce) {\r\n .zendir-status-indicator span[style*=\"statusPulse\"] {\r\n animation: none !important;\r\n }\r\n }\r\n `}</style>\r\n </div>\r\n );\r\n});\r\n"],"mappings":"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"}