@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":"NumberInput.js","names":[],"sources":["../../../../src/react/core/inputs/NumberInput.tsx"],"sourcesContent":["/**\n * @zendir/ui - NumberInput Component\n *\n * Numeric input with optional slider, min/max bounds, step, precision,\n * and unit display. Essential for operator dashboards (frequency, power,\n * aperture, field of view, etc.).\n *\n * Astro UX Compliance:\n * - Consistent sizing with Input component (small/medium/large)\n * - Focus ring for accessibility (WCAG 2.1 AA)\n * - Status-based border colors\n * - Tabular numbers for value alignment\n *\n * @example\n * ```tsx\n * // Each instance can set precision (decimal places); default is 2\n * <NumberInput value={500} onChange={setFreq} min={300} max={900} unit=\"MHz\" label=\"Frequency\" />\n * <NumberInput value={45.2} onChange={setPower} min={0.1} max={100} step={0.1} precision={1} unit=\"W\" slider />\n * <NumberInput value={1024} onChange={setRes} min={128} max={2048} step={128} precision={0} unit=\"px\" label=\"Resolution\" slider />\n * // Status mode with automatic threshold-based coloring:\n * <NumberInput value={temp} onChange={setTemp} min={0} max={100} unit=\"°C\" slider\n * statusMode statusThresholds={{ critical: 90, serious: 75, caution: 50, normal: 20, standby: 0 }} />\n * ```\n */\n\nimport React, {\n memo,\n forwardRef,\n useState,\n useCallback,\n useRef,\n useEffect,\n useId,\n useMemo,\n} from \"react\";\nimport { useTheme } from \"../../theme\";\nimport type { LabelPlacement } from \"./Input\";\nimport type { StatusLevel } from \"../../utils\";\nimport type { StatusThresholds } from \"../data/propertyConfig\";\nimport { deriveStatus } from \"../data/propertyConfig\";\nimport { StatusIndicator } from \"../../astro/StatusIndicator\";\n\nexport type NumberInputSize = \"small\" | \"medium\" | \"large\";\n\n/**\n * Re-export SliderStatus as an alias for StatusLevel for convenience.\n * Includes: 'normal' | 'standby' | 'caution' | 'serious' | 'critical' | 'off'\n */\nexport type SliderStatus = StatusLevel;\n\n/** Default decimal places when precision is not specified: 0 = integer display. Set precision (e.g. 2) to show decimals. */\nconst DEFAULT_DECIMAL_PLACES = 0;\n\nexport interface NumberInputProps {\n /** Current value */\n value: number;\n /** Change handler */\n onChange: (value: number) => void;\n /** Minimum value */\n min?: number;\n /** Maximum value */\n max?: number;\n /** Step increment */\n step?: number;\n /** Number of decimal places to show and round to. Omit or 0 = integer. Use 2 (or any positive number) to show decimals; 2 is typical when decimals are needed. */\n precision?: number;\n /** Unit label displayed after the input (e.g., \"MHz\", \"W\", \"°C\") */\n unit?: string;\n /** Label text or ReactNode (supports rich content like tooltip icons) */\n label?: React.ReactNode;\n /** Helper text below the input */\n helperText?: string;\n /** Error state / message */\n error?: boolean | string;\n /** Show slider below the input */\n slider?: boolean;\n /** Size variant */\n size?: NumberInputSize;\n /** Disabled state */\n disabled?: boolean;\n /** Full width */\n fullWidth?: boolean;\n /** Custom width */\n width?: string | number;\n /** ID for accessibility */\n id?: string;\n /** Custom className */\n className?: string;\n /** Custom style */\n style?: React.CSSProperties;\n /** Text alignment inside the input field */\n textAlign?: \"left\" | \"center\" | \"right\";\n /**\n * Label placement style.\n * - `'outlined'` — label sits on the input border (notched/Material-style). **Default.**\n * - `'above'` — label sits above the input with a gap (classic stack).\n */\n labelPlacement?: LabelPlacement;\n /** Required indicator */\n required?: boolean;\n\n // ── Status Mode ──────────────────────────────────────────────────────\n /**\n * Enable status-based coloring on the slider track, thumb, and glow.\n * When enabled, the slider color reflects the current status instead of\n * the default accent color.\n */\n statusMode?: boolean;\n /**\n * Manual status override. When provided, this status is used directly\n * instead of computing from thresholds.\n */\n status?: SliderStatus;\n /**\n * Threshold map for automatic status resolution based on current value.\n * Supports both high thresholds (critical, serious, caution) and low\n * thresholds (criticalLow, seriousLow, cautionLow) from the shared\n * Astro UX StatusThresholds type.\n * Requires `statusMode` to be enabled.\n */\n statusThresholds?: StatusThresholds;\n /**\n * Callback fired whenever the computed/resolved status changes.\n * Useful for parent components to react to status transitions.\n */\n onStatusChange?: (status: SliderStatus) => void;\n}\n\n// Status resolution is delegated to the shared deriveStatus() utility from propertyConfig.\n\nexport const NumberInput = memo(\n forwardRef<HTMLInputElement, NumberInputProps>(function NumberInput(\n {\n value,\n onChange,\n min,\n max,\n step = 1,\n precision,\n unit,\n label,\n helperText,\n error,\n slider = false,\n size = \"medium\",\n disabled = false,\n fullWidth = false,\n width,\n id,\n className,\n style,\n textAlign = \"right\",\n labelPlacement = \"outlined\",\n required,\n statusMode = false,\n status: statusOverride,\n statusThresholds,\n onStatusChange,\n },\n ref,\n ): React.ReactElement {\n const { tokens, theme } = useTheme();\n const isTransparentTheme =\n theme === \"transparent\" ||\n theme === \"transparent-bold\" ||\n theme === \"transparent-minimal\";\n const isOutlined = labelPlacement === \"outlined\";\n const decimalPlaces = precision ?? DEFAULT_DECIMAL_PLACES;\n const [isFocused, setIsFocused] = useState(false);\n const [isHovered, setIsHovered] = useState(false);\n const [isSliderActive, setIsSliderActive] = useState(false);\n const [localValue, setLocalValue] = useState(() =>\n value.toFixed(decimalPlaces),\n );\n const inputRef = useRef<HTMLInputElement>(null);\n const sliderContainerRef = useRef<HTMLDivElement>(null);\n const sliderId = useId().replace(/:/g, \"\");\n\n // ── Status resolution ──────────────────────────────────────────────────\n const resolvedStatus: SliderStatus | undefined = useMemo(() => {\n if (!statusMode) return undefined;\n if (statusOverride) return statusOverride;\n return deriveStatus(value, statusThresholds);\n }, [statusMode, statusOverride, statusThresholds, value]);\n\n // Fire onStatusChange when status changes\n const prevStatusRef = useRef<SliderStatus | undefined>(undefined);\n useEffect(() => {\n if (\n resolvedStatus !== undefined &&\n resolvedStatus !== prevStatusRef.current\n ) {\n prevStatusRef.current = resolvedStatus;\n onStatusChange?.(resolvedStatus);\n }\n }, [resolvedStatus, onStatusChange]);\n\n /** Resolve the active color: status color or accent */\n const activeColor = useMemo(() => {\n if (resolvedStatus && tokens.colors.status[resolvedStatus]) {\n return tokens.colors.status[resolvedStatus];\n }\n return tokens.colors.accent.primary;\n }, [resolvedStatus, tokens.colors.status, tokens.colors.accent.primary]);\n\n // Sync external value to local state\n useEffect(() => {\n if (!isFocused) {\n setLocalValue(value.toFixed(decimalPlaces));\n }\n }, [value, decimalPlaces, isFocused]);\n\n const clamp = useCallback(\n (v: number): number => {\n let result = v;\n if (min !== undefined) result = Math.max(min, result);\n if (max !== undefined) result = Math.min(max, result);\n result = Number(result.toFixed(decimalPlaces));\n return result;\n },\n [min, max, decimalPlaces],\n );\n\n const handleInputChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const raw = e.target.value;\n setLocalValue(raw);\n\n const parsed = parseFloat(raw);\n if (!isNaN(parsed)) {\n onChange(clamp(parsed));\n }\n },\n [onChange, clamp],\n );\n\n const handleBlur = useCallback(() => {\n setIsFocused(false);\n const parsed = parseFloat(localValue);\n if (isNaN(parsed)) {\n setLocalValue(value.toFixed(decimalPlaces));\n } else {\n const clamped = clamp(parsed);\n onChange(clamped);\n setLocalValue(clamped.toFixed(decimalPlaces));\n }\n }, [localValue, value, onChange, clamp, decimalPlaces]);\n\n const handleSliderChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const parsed = parseFloat(e.target.value);\n const clamped = clamp(parsed);\n onChange(clamped);\n setLocalValue(clamped.toFixed(decimalPlaces));\n },\n [onChange, clamp, decimalPlaces],\n );\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === \"ArrowUp\") {\n e.preventDefault();\n onChange(clamp(value + step));\n } else if (e.key === \"ArrowDown\") {\n e.preventDefault();\n onChange(clamp(value - step));\n }\n },\n [value, step, onChange, clamp],\n );\n\n // Wheel handler: scroll on slider or input changes value\n const handleWheel = useCallback(\n (e: WheelEvent) => {\n if (disabled || min === undefined || max === undefined) return;\n e.preventDefault();\n const direction = e.deltaY < 0 ? 1 : -1;\n onChange(clamp(value + step * direction));\n },\n [value, step, onChange, clamp, disabled, min, max],\n );\n\n // Attach wheel listener to slider container AND input element\n useEffect(() => {\n const sliderContainer = sliderContainerRef.current;\n const inputElement = inputRef.current;\n\n // Always attach to input if min/max defined, regardless of slider prop\n if (min !== undefined && max !== undefined) {\n if (slider && sliderContainer) {\n sliderContainer.addEventListener(\"wheel\", handleWheel, {\n passive: false,\n });\n }\n if (inputElement) {\n inputElement.addEventListener(\"wheel\", handleWheel, {\n passive: false,\n });\n }\n }\n\n return () => {\n if (sliderContainer)\n sliderContainer.removeEventListener(\"wheel\", handleWheel);\n if (inputElement)\n inputElement.removeEventListener(\"wheel\", handleWheel);\n };\n }, [handleWheel, slider, min, max]);\n\n // Release slider active state when mouse is released anywhere\n useEffect(() => {\n if (!isSliderActive) return;\n const handleUp = () => setIsSliderActive(false);\n window.addEventListener(\"mouseup\", handleUp);\n window.addEventListener(\"touchend\", handleUp);\n return () => {\n window.removeEventListener(\"mouseup\", handleUp);\n window.removeEventListener(\"touchend\", handleUp);\n };\n }, [isSliderActive]);\n\n const [isUnitDragging, setIsUnitDragging] = useState(false);\n const dragStartValueRef = useRef<number>(0);\n const dragStartYRef = useRef<number>(0);\n const unitRef = useRef<HTMLSpanElement>(null);\n\n // Unit Drag Handler\n const handleUnitMouseDown = useCallback(\n (e: React.MouseEvent) => {\n if (disabled) return;\n\n e.preventDefault();\n setIsUnitDragging(true);\n dragStartValueRef.current = value;\n dragStartYRef.current = e.clientY;\n\n // Lock cursor if supported for infinite drag\n if (document.body.requestPointerLock) {\n document.body.requestPointerLock();\n }\n },\n [value, disabled],\n );\n\n const handleUnitMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!isUnitDragging) return;\n\n // Use movementY if pointer is locked (infinite drag), otherwise fallback to delta\n // Invert Y because dragging UP (negative Y) should INCREASE value\n const deltaY = document.pointerLockElement\n ? -e.movementY\n : dragStartYRef.current - e.clientY;\n\n if (deltaY === 0) return;\n\n // Velocity / Precision Logic\n // Shift key = Precision mode (0.1x speed)\n // Ctrl/Cmd key = Fast mode (10x speed)\n let speedMultiplier = 1;\n if (e.shiftKey) speedMultiplier = 0.1;\n else if (e.ctrlKey || e.metaKey) speedMultiplier = 10;\n\n // Scale sensitivity: 1 pixel = 1 step * multiplier\n // For non-locked pointer, we might want to dampen it slightly to 0.5 step per pixel\n const sensitivity = document.pointerLockElement ? 1 : 0.5;\n\n const change = deltaY * sensitivity * (step || 1) * speedMultiplier;\n\n // If pointer is NOT locked, we update reference to prevent jumpiness\n if (!document.pointerLockElement) {\n dragStartYRef.current = e.clientY;\n dragStartValueRef.current = clamp(value + change);\n onChange(clamp(value + change));\n } else {\n // For locked pointer, we accumulate on the start value or current value\n // Better to accumulate on current value to handle continuous flow\n onChange(clamp(value + change));\n }\n },\n [isUnitDragging, value, step, onChange, clamp],\n );\n\n const handleUnitMouseUp = useCallback(() => {\n if (isUnitDragging) {\n setIsUnitDragging(false);\n if (document.exitPointerLock) {\n document.exitPointerLock();\n }\n }\n }, [isUnitDragging]);\n\n // Global mouse listeners for drag\n useEffect(() => {\n if (isUnitDragging) {\n window.addEventListener(\"mousemove\", handleUnitMouseMove);\n window.addEventListener(\"mouseup\", handleUnitMouseUp);\n }\n return () => {\n window.removeEventListener(\"mousemove\", handleUnitMouseMove);\n window.removeEventListener(\"mouseup\", handleUnitMouseUp);\n };\n }, [isUnitDragging, handleUnitMouseMove, handleUnitMouseUp]);\n\n // Attach wheel listener to unit element too\n useEffect(() => {\n const unitEl = unitRef.current;\n if (unitEl && !disabled) {\n unitEl.addEventListener(\"wheel\", handleWheel, { passive: false });\n }\n return () => {\n if (unitEl) unitEl.removeEventListener(\"wheel\", handleWheel);\n };\n }, [handleWheel, disabled]);\n\n // Size config — heights match Input & Select via elementSize tokens\n const sizeConfig = {\n small: {\n height: tokens.elementSize.sm,\n fontSize: tokens.typography.fontSize.xs,\n padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,\n sliderHeight: 3,\n inputPaddingLeft: 8,\n },\n medium: {\n height: tokens.elementSize.md,\n fontSize: tokens.typography.fontSize.sm,\n padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,\n sliderHeight: 4,\n inputPaddingLeft: 12,\n },\n large: {\n height: tokens.elementSize.lg,\n fontSize: tokens.typography.fontSize.base,\n padding: `${tokens.spacing.sm} ${tokens.spacing.md}`,\n sliderHeight: 5,\n inputPaddingLeft: 16,\n },\n };\n const config = sizeConfig[size];\n\n const hasError = !!error;\n const errorMessage = typeof error === \"string\" ? error : undefined;\n\n const computedBorder = hasError\n ? tokens.borders.input.error\n : isFocused\n ? tokens.borders.input.focus\n : isHovered\n ? tokens.borders.input.hover\n : tokens.borders.input.default;\n\n const inputBg = isTransparentTheme\n ? tokens.colors.interactive.transparentInputBg ||\n \"rgba(139, 92, 246, 0.08)\"\n : tokens.colors.background.surface;\n\n // Slider track gradient (filled portion)\n const sliderPercent =\n min !== undefined && max !== undefined\n ? ((value - min) / (max - min)) * 100\n : 50;\n\n const inputId =\n id ||\n (typeof label === \"string\"\n ? `zendir-number-${label.replace(/\\s+/g, \"-\").toLowerCase()}`\n : undefined);\n\n // ── Thumb dimensions ─────────────────────────────────────────────────\n const thumbWidth = 24;\n const thumbHeight = config.sliderHeight + 8;\n const trackH = config.sliderHeight;\n\n return (\n <div\n className={className}\n style={{\n display: \"inline-flex\",\n flexDirection: \"column\",\n // No gap when slider is present — slider sits flush on the input bottom edge\n gap: slider ? 0 : tokens.spacing.xs,\n width: fullWidth\n ? \"100%\"\n : typeof width === \"number\"\n ? `${width}px`\n : width,\n fontFamily: tokens.typography.fontFamily.primary,\n ...style,\n }}\n >\n {/* Label: 'above' variant */}\n {label && !isOutlined && (\n <label\n htmlFor={inputId}\n style={{\n fontSize: tokens.typography.fontSize.xs,\n fontWeight: tokens.typography.fontWeight.medium,\n color: tokens.colors.text.secondary,\n letterSpacing: tokens.typography.letterSpacing.wide,\n }}\n >\n {label}\n {required && (\n <span\n style={{\n color: tokens.colors.status.critical,\n marginLeft: \"2px\",\n }}\n >\n *\n </span>\n )}\n </label>\n )}\n\n {/* Input row */}\n <div\n style={{\n position: \"relative\",\n display: \"flex\",\n alignItems: \"center\",\n height: config.height,\n backgroundColor: inputBg,\n border: computedBorder,\n borderRadius: tokens.borderRadius.md,\n transition: tokens.animation.fast,\n opacity: disabled ? 0.5 : 1,\n overflow: isOutlined ? \"visible\" : \"hidden\",\n }}\n onMouseEnter={() => !disabled && setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n {/* Label: 'outlined' variant — positioned on the input row border */}\n {label && isOutlined && (\n <label\n htmlFor={inputId}\n style={{\n position: \"absolute\",\n top: 0,\n left: `${config.inputPaddingLeft - 6}px`,\n transform: \"translateY(-50%)\",\n backgroundColor: isTransparentTheme\n ? tokens.colors.background.base\n : tokens.colors.background.surface,\n padding: \"0 6px\",\n fontSize: {\n small: \"0.625rem\",\n medium: \"0.6875rem\",\n large: \"0.75rem\",\n }[size],\n fontWeight: tokens.typography.fontWeight.medium,\n color: isFocused\n ? hasError\n ? tokens.colors.status.critical\n : tokens.colors.accent.primary\n : hasError\n ? tokens.colors.status.critical\n : tokens.colors.text.secondary,\n letterSpacing: tokens.typography.letterSpacing.wide,\n zIndex: 1,\n pointerEvents: \"none\",\n transition: tokens.animation.fast,\n lineHeight: 1.2,\n whiteSpace: \"nowrap\",\n }}\n >\n {label}\n {required && (\n <span\n style={{\n color: tokens.colors.status.critical,\n marginLeft: \"2px\",\n }}\n >\n *\n </span>\n )}\n </label>\n )}\n <input\n ref={ref || inputRef}\n id={inputId}\n type=\"text\"\n inputMode=\"decimal\"\n value={localValue}\n onChange={handleInputChange}\n onFocus={() => setIsFocused(true)}\n onBlur={handleBlur}\n onKeyDown={handleKeyDown}\n disabled={disabled}\n aria-invalid={hasError}\n aria-describedby={\n errorMessage\n ? `${inputId}-error`\n : helperText\n ? `${inputId}-helper`\n : undefined\n }\n style={{\n flex: 1,\n height: \"100%\",\n padding: config.padding,\n fontSize: config.fontSize,\n fontWeight: tokens.typography.fontWeight.medium,\n fontFamily: tokens.typography.fontFamily.mono,\n fontVariantNumeric: \"tabular-nums\",\n color: tokens.colors.text.primary,\n backgroundColor: \"transparent\",\n border: \"none\",\n outline: \"none\",\n minWidth: 0,\n textAlign,\n }}\n />\n\n {/* Unit */}\n {unit && (\n <span\n ref={unitRef}\n onMouseDown={handleUnitMouseDown}\n style={{\n padding: `0 ${tokens.spacing.sm}`,\n fontSize: tokens.typography.fontSize.xs,\n fontWeight: tokens.typography.fontWeight.medium,\n color: isUnitDragging\n ? activeColor\n : tokens.colors.text.tertiary,\n fontFamily: tokens.typography.fontFamily.primary,\n flexShrink: 0,\n borderLeft: tokens.borders.separator,\n height: \"100%\",\n display: \"flex\",\n alignItems: \"center\",\n backgroundColor: isTransparentTheme\n ? `${tokens.colors.accent.primary}0a`\n : \"rgba(0, 0, 0, 0.15)\",\n cursor: disabled ? \"not-allowed\" : \"ns-resize\",\n userSelect: \"none\",\n transition: \"color 0.2s ease\",\n }}\n title={disabled ? undefined : \"Drag to adjust value\"}\n >\n {unit}\n </span>\n )}\n </div>\n\n {/* Slider — directly below input with no gap */}\n {slider &&\n min !== undefined &&\n max !== undefined &&\n (() => {\n const sliderH = thumbHeight;\n const cy = sliderH / 2;\n const pct = sliderPercent / 100; // 0..1\n\n // Thumb left-edge position: maps 0% → 0px, 100% → (containerWidth - thumbWidth)\n // CSS calc: pct * (100% - thumbWidth)\n const thumbLeftCalc = `calc(${pct} * (100% - ${thumbWidth}px))`;\n\n // Filled track width = thumb left edge + 1px overlap for seamless join\n const filledWidthCalc = `calc(${pct} * (100% - ${thumbWidth}px) + 1px)`;\n\n return (\n <div\n ref={sliderContainerRef}\n style={{\n position: \"relative\",\n height: `${sliderH}px`,\n marginTop: `-${sliderH / 1.42}px`,\n }}\n >\n {/* Unfilled track */}\n <div\n style={{\n position: \"absolute\",\n top: `${cy}px`,\n left: 0,\n right: 0,\n height: `${trackH}px`,\n transform: \"translateY(-50%)\",\n borderRadius: `${trackH / 2}px`,\n backgroundColor: `${tokens.colors.border.muted}60`,\n }}\n />\n {/* Filled track — flat right edge for flush join with thumb */}\n <div\n style={{\n position: \"absolute\",\n top: `${cy}px`,\n left: 0,\n width: filledWidthCalc,\n height: `${trackH}px`,\n transform: \"translateY(-50%)\",\n borderRadius: `${trackH / 2}px 0 0 ${trackH / 2}px`,\n background: activeColor,\n transition: \"background 300ms ease\",\n }}\n />\n {/* Invisible range input */}\n <input\n type=\"range\"\n className={`zendir-slider-${sliderId}`}\n min={min}\n max={max}\n step={step}\n value={value}\n onChange={handleSliderChange}\n onMouseDown={() => setIsSliderActive(true)}\n onMouseUp={() => setIsSliderActive(false)}\n onTouchStart={() => setIsSliderActive(true)}\n onTouchEnd={() => setIsSliderActive(false)}\n disabled={disabled}\n aria-label={\n typeof label === \"string\" ? label : \"Value slider\"\n }\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n margin: 0,\n padding: 0,\n opacity: 0,\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n zIndex: 2,\n }}\n />\n {/* Smooth-blob thumb — left edge flush with filled track end */}\n <div\n style={{\n position: \"absolute\",\n top: `${cy}px`,\n left: thumbLeftCalc,\n width: `${thumbWidth}px`,\n height: `${thumbHeight}px`,\n transform: \"translateY(-50%)\",\n pointerEvents: \"none\",\n zIndex: 1,\n }}\n >\n <svg\n width={thumbWidth}\n height={thumbHeight}\n viewBox={`0 0 ${thumbWidth} ${thumbHeight}`}\n xmlns=\"http://www.w3.org/2000/svg\"\n style={{ display: \"block\", overflow: \"visible\" }}\n >\n {(() => {\n const w = thumbWidth;\n const h = thumbHeight;\n const svgCy = h / 2;\n const trackHalf = trackH / 2;\n const r = 2;\n\n const ltop = svgCy - trackHalf;\n const lbot = svgCy + trackHalf;\n const rtop = r;\n const rbot = h - r;\n\n const cp1x = w * 0.28;\n const cp2x = w * 0.48;\n\n const d = [\n `M 0 ${ltop}`,\n `C ${cp1x} ${ltop}, ${cp2x} ${rtop}, ${w - r} ${rtop}`,\n `A ${r} ${r} 0 0 1 ${w} ${rtop + r}`,\n `L ${w} ${rbot - r}`,\n `A ${r} ${r} 0 0 1 ${w - r} ${rbot}`,\n `C ${cp2x} ${rbot}, ${cp1x} ${lbot}, 0 ${lbot}`,\n `Z`,\n ].join(\" \");\n\n return <path d={d} fill={activeColor} />;\n })()}\n </svg>\n </div>\n\n {/* Astro UXDS status symbol (shape-coded: circle/square/diamond/triangle) */}\n {statusMode && resolvedStatus && (\n <div\n style={{\n position: \"absolute\",\n bottom: `-${size === \"large\" ? 10 : 8}px`,\n left: `calc(${pct} * (100% - ${thumbWidth}px) + ${thumbWidth / 2}px)`,\n transform: \"translateX(-50%)\",\n zIndex: 1,\n lineHeight: 0,\n }}\n >\n <StatusIndicator status={resolvedStatus} small />\n </div>\n )}\n </div>\n );\n })()}\n\n {/* Helper / Error text */}\n {(helperText || errorMessage) && (\n <div\n id={errorMessage ? `${inputId}-error` : `${inputId}-helper`}\n role={errorMessage ? \"alert\" : undefined}\n style={{\n fontSize: tokens.typography.fontSize.xs,\n color: hasError\n ? tokens.colors.status.critical\n : tokens.colors.text.tertiary,\n }}\n >\n {errorMessage || helperText}\n </div>\n )}\n </div>\n );\n }),\n);\n\nexport default NumberInput;\n"],"mappings":"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"}
1
+ {"version":3,"file":"NumberInput.js","names":[],"sources":["../../../../src/react/core/inputs/NumberInput.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - NumberInput Component\r\n *\r\n * Numeric input with optional slider, min/max bounds, step, precision,\r\n * and unit display. Essential for operator dashboards (frequency, power,\r\n * aperture, field of view, etc.).\r\n *\r\n * Astro UX Compliance:\r\n * - Consistent sizing with Input component (small/medium/large)\r\n * - Focus ring for accessibility (WCAG 2.1 AA)\r\n * - Status-based border colors\r\n * - Tabular numbers for value alignment\r\n *\r\n * @example\r\n * ```tsx\r\n * // Each instance can set precision (decimal places); default is 2\r\n * <NumberInput value={500} onChange={setFreq} min={300} max={900} unit=\"MHz\" label=\"Frequency\" />\r\n * <NumberInput value={45.2} onChange={setPower} min={0.1} max={100} step={0.1} precision={1} unit=\"W\" slider />\r\n * <NumberInput value={1024} onChange={setRes} min={128} max={2048} step={128} precision={0} unit=\"px\" label=\"Resolution\" slider />\r\n * // Status mode with automatic threshold-based coloring:\r\n * <NumberInput value={temp} onChange={setTemp} min={0} max={100} unit=\"°C\" slider\r\n * statusMode statusThresholds={{ critical: 90, serious: 75, caution: 50, normal: 20, standby: 0 }} />\r\n * ```\r\n */\r\n\r\nimport React, {\r\n memo,\r\n forwardRef,\r\n useState,\r\n useCallback,\r\n useRef,\r\n useEffect,\r\n useId,\r\n useMemo,\r\n} from \"react\";\r\nimport { useTheme } from \"../../theme\";\r\nimport type { LabelPlacement } from \"./Input\";\r\nimport type { StatusLevel } from \"../../utils\";\r\nimport type { StatusThresholds } from \"../data/propertyConfig\";\r\nimport { deriveStatus } from \"../data/propertyConfig\";\r\nimport { StatusIndicator } from \"../../astro/StatusIndicator\";\r\n\r\nexport type NumberInputSize = \"small\" | \"medium\" | \"large\";\r\n\r\n/**\r\n * Re-export SliderStatus as an alias for StatusLevel for convenience.\r\n * Includes: 'normal' | 'standby' | 'caution' | 'serious' | 'critical' | 'off'\r\n */\r\nexport type SliderStatus = StatusLevel;\r\n\r\n/** Default decimal places when precision is not specified: 0 = integer display. Set precision (e.g. 2) to show decimals. */\r\nconst DEFAULT_DECIMAL_PLACES = 0;\r\n\r\nexport interface NumberInputProps {\r\n /** Current value */\r\n value: number;\r\n /** Change handler */\r\n onChange: (value: number) => void;\r\n /** Minimum value */\r\n min?: number;\r\n /** Maximum value */\r\n max?: number;\r\n /** Step increment */\r\n step?: number;\r\n /** Number of decimal places to show and round to. Omit or 0 = integer. Use 2 (or any positive number) to show decimals; 2 is typical when decimals are needed. */\r\n precision?: number;\r\n /** Unit label displayed after the input (e.g., \"MHz\", \"W\", \"°C\") */\r\n unit?: string;\r\n /** Label text or ReactNode (supports rich content like tooltip icons) */\r\n label?: React.ReactNode;\r\n /** Helper text below the input */\r\n helperText?: string;\r\n /** Error state / message */\r\n error?: boolean | string;\r\n /** Show slider below the input */\r\n slider?: boolean;\r\n /** Size variant */\r\n size?: NumberInputSize;\r\n /** Disabled state */\r\n disabled?: boolean;\r\n /** Full width */\r\n fullWidth?: boolean;\r\n /** Custom width */\r\n width?: string | number;\r\n /** ID for accessibility */\r\n id?: string;\r\n /** Custom className */\r\n className?: string;\r\n /** Custom style */\r\n style?: React.CSSProperties;\r\n /** Text alignment inside the input field */\r\n textAlign?: \"left\" | \"center\" | \"right\";\r\n /**\r\n * Label placement style.\r\n * - `'outlined'` — label sits on the input border (notched/Material-style). **Default.**\r\n * - `'above'` — label sits above the input with a gap (classic stack).\r\n */\r\n labelPlacement?: LabelPlacement;\r\n /** Required indicator */\r\n required?: boolean;\r\n\r\n // ── Status Mode ──────────────────────────────────────────────────────\r\n /**\r\n * Enable status-based coloring on the slider track, thumb, and glow.\r\n * When enabled, the slider color reflects the current status instead of\r\n * the default accent color.\r\n */\r\n statusMode?: boolean;\r\n /**\r\n * Manual status override. When provided, this status is used directly\r\n * instead of computing from thresholds.\r\n */\r\n status?: SliderStatus;\r\n /**\r\n * Threshold map for automatic status resolution based on current value.\r\n * Supports both high thresholds (critical, serious, caution) and low\r\n * thresholds (criticalLow, seriousLow, cautionLow) from the shared\r\n * Astro UX StatusThresholds type.\r\n * Requires `statusMode` to be enabled.\r\n */\r\n statusThresholds?: StatusThresholds;\r\n /**\r\n * Callback fired whenever the computed/resolved status changes.\r\n * Useful for parent components to react to status transitions.\r\n */\r\n onStatusChange?: (status: SliderStatus) => void;\r\n}\r\n\r\n// Status resolution is delegated to the shared deriveStatus() utility from propertyConfig.\r\n\r\nexport const NumberInput = memo(\r\n forwardRef<HTMLInputElement, NumberInputProps>(function NumberInput(\r\n {\r\n value,\r\n onChange,\r\n min,\r\n max,\r\n step = 1,\r\n precision,\r\n unit,\r\n label,\r\n helperText,\r\n error,\r\n slider = false,\r\n size = \"medium\",\r\n disabled = false,\r\n fullWidth = false,\r\n width,\r\n id,\r\n className,\r\n style,\r\n textAlign = \"right\",\r\n labelPlacement = \"outlined\",\r\n required,\r\n statusMode = false,\r\n status: statusOverride,\r\n statusThresholds,\r\n onStatusChange,\r\n },\r\n ref,\r\n ): 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 const isOutlined = labelPlacement === \"outlined\";\r\n const decimalPlaces = precision ?? DEFAULT_DECIMAL_PLACES;\r\n const [isFocused, setIsFocused] = useState(false);\r\n const [isHovered, setIsHovered] = useState(false);\r\n const [isSliderActive, setIsSliderActive] = useState(false);\r\n const [localValue, setLocalValue] = useState(() =>\r\n value.toFixed(decimalPlaces),\r\n );\r\n const inputRef = useRef<HTMLInputElement>(null);\r\n const sliderContainerRef = useRef<HTMLDivElement>(null);\r\n const sliderId = useId().replace(/:/g, \"\");\r\n\r\n // ── Status resolution ──────────────────────────────────────────────────\r\n const resolvedStatus: SliderStatus | undefined = useMemo(() => {\r\n if (!statusMode) return undefined;\r\n if (statusOverride) return statusOverride;\r\n return deriveStatus(value, statusThresholds);\r\n }, [statusMode, statusOverride, statusThresholds, value]);\r\n\r\n // Fire onStatusChange when status changes\r\n const prevStatusRef = useRef<SliderStatus | undefined>(undefined);\r\n useEffect(() => {\r\n if (\r\n resolvedStatus !== undefined &&\r\n resolvedStatus !== prevStatusRef.current\r\n ) {\r\n prevStatusRef.current = resolvedStatus;\r\n onStatusChange?.(resolvedStatus);\r\n }\r\n }, [resolvedStatus, onStatusChange]);\r\n\r\n /** Resolve the active color: status color or accent */\r\n const activeColor = useMemo(() => {\r\n if (resolvedStatus && tokens.colors.status[resolvedStatus]) {\r\n return tokens.colors.status[resolvedStatus];\r\n }\r\n return tokens.colors.accent.primary;\r\n }, [resolvedStatus, tokens.colors.status, tokens.colors.accent.primary]);\r\n\r\n // Sync external value to local state\r\n useEffect(() => {\r\n if (!isFocused) {\r\n setLocalValue(value.toFixed(decimalPlaces));\r\n }\r\n }, [value, decimalPlaces, isFocused]);\r\n\r\n const clamp = useCallback(\r\n (v: number): number => {\r\n let result = v;\r\n if (min !== undefined) result = Math.max(min, result);\r\n if (max !== undefined) result = Math.min(max, result);\r\n result = Number(result.toFixed(decimalPlaces));\r\n return result;\r\n },\r\n [min, max, decimalPlaces],\r\n );\r\n\r\n const handleInputChange = useCallback(\r\n (e: React.ChangeEvent<HTMLInputElement>) => {\r\n const raw = e.target.value;\r\n setLocalValue(raw);\r\n\r\n const parsed = parseFloat(raw);\r\n if (!isNaN(parsed)) {\r\n onChange(clamp(parsed));\r\n }\r\n },\r\n [onChange, clamp],\r\n );\r\n\r\n const handleBlur = useCallback(() => {\r\n setIsFocused(false);\r\n const parsed = parseFloat(localValue);\r\n if (isNaN(parsed)) {\r\n setLocalValue(value.toFixed(decimalPlaces));\r\n } else {\r\n const clamped = clamp(parsed);\r\n onChange(clamped);\r\n setLocalValue(clamped.toFixed(decimalPlaces));\r\n }\r\n }, [localValue, value, onChange, clamp, decimalPlaces]);\r\n\r\n const handleSliderChange = useCallback(\r\n (e: React.ChangeEvent<HTMLInputElement>) => {\r\n const parsed = parseFloat(e.target.value);\r\n const clamped = clamp(parsed);\r\n onChange(clamped);\r\n setLocalValue(clamped.toFixed(decimalPlaces));\r\n },\r\n [onChange, clamp, decimalPlaces],\r\n );\r\n\r\n const handleKeyDown = useCallback(\r\n (e: React.KeyboardEvent<HTMLInputElement>) => {\r\n if (e.key === \"ArrowUp\") {\r\n e.preventDefault();\r\n onChange(clamp(value + step));\r\n } else if (e.key === \"ArrowDown\") {\r\n e.preventDefault();\r\n onChange(clamp(value - step));\r\n }\r\n },\r\n [value, step, onChange, clamp],\r\n );\r\n\r\n // Wheel handler: scroll on slider or input changes value\r\n const handleWheel = useCallback(\r\n (e: WheelEvent) => {\r\n if (disabled || min === undefined || max === undefined) return;\r\n e.preventDefault();\r\n const direction = e.deltaY < 0 ? 1 : -1;\r\n onChange(clamp(value + step * direction));\r\n },\r\n [value, step, onChange, clamp, disabled, min, max],\r\n );\r\n\r\n // Attach wheel listener to slider container AND input element\r\n useEffect(() => {\r\n const sliderContainer = sliderContainerRef.current;\r\n const inputElement = inputRef.current;\r\n\r\n // Always attach to input if min/max defined, regardless of slider prop\r\n if (min !== undefined && max !== undefined) {\r\n if (slider && sliderContainer) {\r\n sliderContainer.addEventListener(\"wheel\", handleWheel, {\r\n passive: false,\r\n });\r\n }\r\n if (inputElement) {\r\n inputElement.addEventListener(\"wheel\", handleWheel, {\r\n passive: false,\r\n });\r\n }\r\n }\r\n\r\n return () => {\r\n if (sliderContainer)\r\n sliderContainer.removeEventListener(\"wheel\", handleWheel);\r\n if (inputElement)\r\n inputElement.removeEventListener(\"wheel\", handleWheel);\r\n };\r\n }, [handleWheel, slider, min, max]);\r\n\r\n // Release slider active state when mouse is released anywhere\r\n useEffect(() => {\r\n if (!isSliderActive) return;\r\n const handleUp = () => setIsSliderActive(false);\r\n window.addEventListener(\"mouseup\", handleUp);\r\n window.addEventListener(\"touchend\", handleUp);\r\n return () => {\r\n window.removeEventListener(\"mouseup\", handleUp);\r\n window.removeEventListener(\"touchend\", handleUp);\r\n };\r\n }, [isSliderActive]);\r\n\r\n const [isUnitDragging, setIsUnitDragging] = useState(false);\r\n const dragStartValueRef = useRef<number>(0);\r\n const dragStartYRef = useRef<number>(0);\r\n const unitRef = useRef<HTMLSpanElement>(null);\r\n\r\n // Unit Drag Handler\r\n const handleUnitMouseDown = useCallback(\r\n (e: React.MouseEvent) => {\r\n if (disabled) return;\r\n\r\n e.preventDefault();\r\n setIsUnitDragging(true);\r\n dragStartValueRef.current = value;\r\n dragStartYRef.current = e.clientY;\r\n\r\n // Lock cursor if supported for infinite drag\r\n if (document.body.requestPointerLock) {\r\n document.body.requestPointerLock();\r\n }\r\n },\r\n [value, disabled],\r\n );\r\n\r\n const handleUnitMouseMove = useCallback(\r\n (e: MouseEvent) => {\r\n if (!isUnitDragging) return;\r\n\r\n // Use movementY if pointer is locked (infinite drag), otherwise fallback to delta\r\n // Invert Y because dragging UP (negative Y) should INCREASE value\r\n const deltaY = document.pointerLockElement\r\n ? -e.movementY\r\n : dragStartYRef.current - e.clientY;\r\n\r\n if (deltaY === 0) return;\r\n\r\n // Velocity / Precision Logic\r\n // Shift key = Precision mode (0.1x speed)\r\n // Ctrl/Cmd key = Fast mode (10x speed)\r\n let speedMultiplier = 1;\r\n if (e.shiftKey) speedMultiplier = 0.1;\r\n else if (e.ctrlKey || e.metaKey) speedMultiplier = 10;\r\n\r\n // Scale sensitivity: 1 pixel = 1 step * multiplier\r\n // For non-locked pointer, we might want to dampen it slightly to 0.5 step per pixel\r\n const sensitivity = document.pointerLockElement ? 1 : 0.5;\r\n\r\n const change = deltaY * sensitivity * (step || 1) * speedMultiplier;\r\n\r\n // If pointer is NOT locked, we update reference to prevent jumpiness\r\n if (!document.pointerLockElement) {\r\n dragStartYRef.current = e.clientY;\r\n dragStartValueRef.current = clamp(value + change);\r\n onChange(clamp(value + change));\r\n } else {\r\n // For locked pointer, we accumulate on the start value or current value\r\n // Better to accumulate on current value to handle continuous flow\r\n onChange(clamp(value + change));\r\n }\r\n },\r\n [isUnitDragging, value, step, onChange, clamp],\r\n );\r\n\r\n const handleUnitMouseUp = useCallback(() => {\r\n if (isUnitDragging) {\r\n setIsUnitDragging(false);\r\n if (document.exitPointerLock) {\r\n document.exitPointerLock();\r\n }\r\n }\r\n }, [isUnitDragging]);\r\n\r\n // Global mouse listeners for drag\r\n useEffect(() => {\r\n if (isUnitDragging) {\r\n window.addEventListener(\"mousemove\", handleUnitMouseMove);\r\n window.addEventListener(\"mouseup\", handleUnitMouseUp);\r\n }\r\n return () => {\r\n window.removeEventListener(\"mousemove\", handleUnitMouseMove);\r\n window.removeEventListener(\"mouseup\", handleUnitMouseUp);\r\n };\r\n }, [isUnitDragging, handleUnitMouseMove, handleUnitMouseUp]);\r\n\r\n // Attach wheel listener to unit element too\r\n useEffect(() => {\r\n const unitEl = unitRef.current;\r\n if (unitEl && !disabled) {\r\n unitEl.addEventListener(\"wheel\", handleWheel, { passive: false });\r\n }\r\n return () => {\r\n if (unitEl) unitEl.removeEventListener(\"wheel\", handleWheel);\r\n };\r\n }, [handleWheel, disabled]);\r\n\r\n // Size config — heights match Input & Select via elementSize tokens\r\n const sizeConfig = {\r\n small: {\r\n height: tokens.elementSize.sm,\r\n fontSize: tokens.typography.fontSize.xs,\r\n padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,\r\n sliderHeight: 3,\r\n inputPaddingLeft: 8,\r\n },\r\n medium: {\r\n height: tokens.elementSize.md,\r\n fontSize: tokens.typography.fontSize.sm,\r\n padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,\r\n sliderHeight: 4,\r\n inputPaddingLeft: 12,\r\n },\r\n large: {\r\n height: tokens.elementSize.lg,\r\n fontSize: tokens.typography.fontSize.base,\r\n padding: `${tokens.spacing.sm} ${tokens.spacing.md}`,\r\n sliderHeight: 5,\r\n inputPaddingLeft: 16,\r\n },\r\n };\r\n const config = sizeConfig[size];\r\n\r\n const hasError = !!error;\r\n const errorMessage = typeof error === \"string\" ? error : undefined;\r\n\r\n const computedBorder = hasError\r\n ? tokens.borders.input.error\r\n : isFocused\r\n ? tokens.borders.input.focus\r\n : isHovered\r\n ? tokens.borders.input.hover\r\n : tokens.borders.input.default;\r\n\r\n const inputBg = isTransparentTheme\r\n ? tokens.colors.interactive.transparentInputBg ||\r\n \"rgba(139, 92, 246, 0.08)\"\r\n : tokens.colors.background.surface;\r\n\r\n // Slider track gradient (filled portion)\r\n const sliderPercent =\r\n min !== undefined && max !== undefined\r\n ? ((value - min) / (max - min)) * 100\r\n : 50;\r\n\r\n const inputId =\r\n id ||\r\n (typeof label === \"string\"\r\n ? `zendir-number-${label.replace(/\\s+/g, \"-\").toLowerCase()}`\r\n : undefined);\r\n\r\n // ── Thumb dimensions ─────────────────────────────────────────────────\r\n const thumbWidth = 24;\r\n const thumbHeight = config.sliderHeight + 8;\r\n const trackH = config.sliderHeight;\r\n\r\n return (\r\n <div\r\n className={className}\r\n style={{\r\n display: \"inline-flex\",\r\n flexDirection: \"column\",\r\n // No gap when slider is present — slider sits flush on the input bottom edge\r\n gap: slider ? 0 : tokens.spacing.xs,\r\n width: fullWidth\r\n ? \"100%\"\r\n : typeof width === \"number\"\r\n ? `${width}px`\r\n : width,\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n ...style,\r\n }}\r\n >\r\n {/* Label: 'above' variant */}\r\n {label && !isOutlined && (\r\n <label\r\n htmlFor={inputId}\r\n style={{\r\n fontSize: tokens.typography.fontSize.xs,\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n color: tokens.colors.text.secondary,\r\n letterSpacing: tokens.typography.letterSpacing.wide,\r\n }}\r\n >\r\n {label}\r\n {required && (\r\n <span\r\n style={{\r\n color: tokens.colors.status.critical,\r\n marginLeft: \"2px\",\r\n }}\r\n >\r\n *\r\n </span>\r\n )}\r\n </label>\r\n )}\r\n\r\n {/* Input row */}\r\n <div\r\n style={{\r\n position: \"relative\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n height: config.height,\r\n backgroundColor: inputBg,\r\n border: computedBorder,\r\n borderRadius: tokens.borderRadius.md,\r\n transition: tokens.animation.fast,\r\n opacity: disabled ? 0.5 : 1,\r\n overflow: isOutlined ? \"visible\" : \"hidden\",\r\n }}\r\n onMouseEnter={() => !disabled && setIsHovered(true)}\r\n onMouseLeave={() => setIsHovered(false)}\r\n >\r\n {/* Label: 'outlined' variant — positioned on the input row border */}\r\n {label && isOutlined && (\r\n <label\r\n htmlFor={inputId}\r\n style={{\r\n position: \"absolute\",\r\n top: 0,\r\n left: `${config.inputPaddingLeft - 6}px`,\r\n transform: \"translateY(-50%)\",\r\n backgroundColor: isTransparentTheme\r\n ? tokens.colors.background.base\r\n : tokens.colors.background.surface,\r\n padding: \"0 6px\",\r\n fontSize: {\r\n small: \"0.625rem\",\r\n medium: \"0.6875rem\",\r\n large: \"0.75rem\",\r\n }[size],\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n color: isFocused\r\n ? hasError\r\n ? tokens.colors.status.critical\r\n : tokens.colors.accent.primary\r\n : hasError\r\n ? tokens.colors.status.critical\r\n : tokens.colors.text.secondary,\r\n letterSpacing: tokens.typography.letterSpacing.wide,\r\n zIndex: 1,\r\n pointerEvents: \"none\",\r\n transition: tokens.animation.fast,\r\n lineHeight: 1.2,\r\n whiteSpace: \"nowrap\",\r\n }}\r\n >\r\n {label}\r\n {required && (\r\n <span\r\n style={{\r\n color: tokens.colors.status.critical,\r\n marginLeft: \"2px\",\r\n }}\r\n >\r\n *\r\n </span>\r\n )}\r\n </label>\r\n )}\r\n <input\r\n ref={ref || inputRef}\r\n id={inputId}\r\n type=\"text\"\r\n inputMode=\"decimal\"\r\n value={localValue}\r\n onChange={handleInputChange}\r\n onFocus={() => setIsFocused(true)}\r\n onBlur={handleBlur}\r\n onKeyDown={handleKeyDown}\r\n disabled={disabled}\r\n aria-invalid={hasError}\r\n aria-describedby={\r\n errorMessage\r\n ? `${inputId}-error`\r\n : helperText\r\n ? `${inputId}-helper`\r\n : undefined\r\n }\r\n style={{\r\n flex: 1,\r\n height: \"100%\",\r\n padding: config.padding,\r\n fontSize: config.fontSize,\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n fontVariantNumeric: \"tabular-nums\",\r\n color: tokens.colors.text.primary,\r\n backgroundColor: \"transparent\",\r\n border: \"none\",\r\n outline: \"none\",\r\n minWidth: 0,\r\n textAlign,\r\n }}\r\n />\r\n\r\n {/* Unit */}\r\n {unit && (\r\n <span\r\n ref={unitRef}\r\n onMouseDown={handleUnitMouseDown}\r\n style={{\r\n padding: `0 ${tokens.spacing.sm}`,\r\n fontSize: tokens.typography.fontSize.xs,\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n color: isUnitDragging\r\n ? activeColor\r\n : tokens.colors.text.tertiary,\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n flexShrink: 0,\r\n borderLeft: tokens.borders.separator,\r\n height: \"100%\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n backgroundColor: isTransparentTheme\r\n ? `${tokens.colors.accent.primary}0a`\r\n : \"rgba(0, 0, 0, 0.15)\",\r\n cursor: disabled ? \"not-allowed\" : \"ns-resize\",\r\n userSelect: \"none\",\r\n transition: \"color 0.2s ease\",\r\n }}\r\n title={disabled ? undefined : \"Drag to adjust value\"}\r\n >\r\n {unit}\r\n </span>\r\n )}\r\n </div>\r\n\r\n {/* Slider — directly below input with no gap */}\r\n {slider &&\r\n min !== undefined &&\r\n max !== undefined &&\r\n (() => {\r\n const sliderH = thumbHeight;\r\n const cy = sliderH / 2;\r\n const pct = sliderPercent / 100; // 0..1\r\n\r\n // Thumb left-edge position: maps 0% → 0px, 100% → (containerWidth - thumbWidth)\r\n // CSS calc: pct * (100% - thumbWidth)\r\n const thumbLeftCalc = `calc(${pct} * (100% - ${thumbWidth}px))`;\r\n\r\n // Filled track width = thumb left edge + 1px overlap for seamless join\r\n const filledWidthCalc = `calc(${pct} * (100% - ${thumbWidth}px) + 1px)`;\r\n\r\n return (\r\n <div\r\n ref={sliderContainerRef}\r\n style={{\r\n position: \"relative\",\r\n height: `${sliderH}px`,\r\n marginTop: `-${sliderH / 1.42}px`,\r\n }}\r\n >\r\n {/* Unfilled track */}\r\n <div\r\n style={{\r\n position: \"absolute\",\r\n top: `${cy}px`,\r\n left: 0,\r\n right: 0,\r\n height: `${trackH}px`,\r\n transform: \"translateY(-50%)\",\r\n borderRadius: `${trackH / 2}px`,\r\n backgroundColor: `${tokens.colors.border.muted}60`,\r\n }}\r\n />\r\n {/* Filled track — flat right edge for flush join with thumb */}\r\n <div\r\n style={{\r\n position: \"absolute\",\r\n top: `${cy}px`,\r\n left: 0,\r\n width: filledWidthCalc,\r\n height: `${trackH}px`,\r\n transform: \"translateY(-50%)\",\r\n borderRadius: `${trackH / 2}px 0 0 ${trackH / 2}px`,\r\n background: activeColor,\r\n transition: \"background 300ms ease\",\r\n }}\r\n />\r\n {/* Invisible range input */}\r\n <input\r\n type=\"range\"\r\n className={`zendir-slider-${sliderId}`}\r\n min={min}\r\n max={max}\r\n step={step}\r\n value={value}\r\n onChange={handleSliderChange}\r\n onMouseDown={() => setIsSliderActive(true)}\r\n onMouseUp={() => setIsSliderActive(false)}\r\n onTouchStart={() => setIsSliderActive(true)}\r\n onTouchEnd={() => setIsSliderActive(false)}\r\n disabled={disabled}\r\n aria-label={\r\n typeof label === \"string\" ? label : \"Value slider\"\r\n }\r\n style={{\r\n position: \"absolute\",\r\n top: 0,\r\n left: 0,\r\n width: \"100%\",\r\n height: \"100%\",\r\n margin: 0,\r\n padding: 0,\r\n opacity: 0,\r\n cursor: disabled ? \"not-allowed\" : \"pointer\",\r\n zIndex: 2,\r\n }}\r\n />\r\n {/* Smooth-blob thumb — left edge flush with filled track end */}\r\n <div\r\n style={{\r\n position: \"absolute\",\r\n top: `${cy}px`,\r\n left: thumbLeftCalc,\r\n width: `${thumbWidth}px`,\r\n height: `${thumbHeight}px`,\r\n transform: \"translateY(-50%)\",\r\n pointerEvents: \"none\",\r\n zIndex: 1,\r\n }}\r\n >\r\n <svg\r\n width={thumbWidth}\r\n height={thumbHeight}\r\n viewBox={`0 0 ${thumbWidth} ${thumbHeight}`}\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n style={{ display: \"block\", overflow: \"visible\" }}\r\n >\r\n {(() => {\r\n const w = thumbWidth;\r\n const h = thumbHeight;\r\n const svgCy = h / 2;\r\n const trackHalf = trackH / 2;\r\n const r = 2;\r\n\r\n const ltop = svgCy - trackHalf;\r\n const lbot = svgCy + trackHalf;\r\n const rtop = r;\r\n const rbot = h - r;\r\n\r\n const cp1x = w * 0.28;\r\n const cp2x = w * 0.48;\r\n\r\n const d = [\r\n `M 0 ${ltop}`,\r\n `C ${cp1x} ${ltop}, ${cp2x} ${rtop}, ${w - r} ${rtop}`,\r\n `A ${r} ${r} 0 0 1 ${w} ${rtop + r}`,\r\n `L ${w} ${rbot - r}`,\r\n `A ${r} ${r} 0 0 1 ${w - r} ${rbot}`,\r\n `C ${cp2x} ${rbot}, ${cp1x} ${lbot}, 0 ${lbot}`,\r\n `Z`,\r\n ].join(\" \");\r\n\r\n return <path d={d} fill={activeColor} />;\r\n })()}\r\n </svg>\r\n </div>\r\n\r\n {/* Astro UXDS status symbol (shape-coded: circle/square/diamond/triangle) */}\r\n {statusMode && resolvedStatus && (\r\n <div\r\n style={{\r\n position: \"absolute\",\r\n bottom: `-${size === \"large\" ? 10 : 8}px`,\r\n left: `calc(${pct} * (100% - ${thumbWidth}px) + ${thumbWidth / 2}px)`,\r\n transform: \"translateX(-50%)\",\r\n zIndex: 1,\r\n lineHeight: 0,\r\n }}\r\n >\r\n <StatusIndicator status={resolvedStatus} small />\r\n </div>\r\n )}\r\n </div>\r\n );\r\n })()}\r\n\r\n {/* Helper / Error text */}\r\n {(helperText || errorMessage) && (\r\n <div\r\n id={errorMessage ? `${inputId}-error` : `${inputId}-helper`}\r\n role={errorMessage ? \"alert\" : undefined}\r\n style={{\r\n fontSize: tokens.typography.fontSize.xs,\r\n color: hasError\r\n ? tokens.colors.status.critical\r\n : tokens.colors.text.tertiary,\r\n }}\r\n >\r\n {errorMessage || helperText}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n }),\r\n);\r\n\r\nexport default NumberInput;\r\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"PinInput.js","names":[],"sources":["../../../../src/react/core/inputs/PinInput.tsx"],"sourcesContent":["/**\n * @zendir/ui - PinInput Component\n *\n * Multi-character code entry for passwords, verification codes, and team IDs.\n * Auto-advances focus between cells. Supports numeric and alphanumeric modes.\n *\n * Astro UX Compliance:\n * - Consistent styling with Input component\n * - Focus ring for accessibility\n * - Monospace tabular numbers\n *\n * @example\n * ```tsx\n * <PinInput length={6} type=\"alphanumeric\" value={password} onChange={setPassword} label=\"Team Password\" />\n * <PinInput length={4} type=\"numeric\" value={code} onChange={setCode} label=\"Verification Code\" />\n * <PinInput length={8} value={token} onChange={setToken} label=\"API Token\" />\n * ```\n */\n\nimport React, { memo, useState, useRef, useCallback, useEffect } from \"react\";\nimport { useTheme } from \"../../theme\";\nimport type { LabelPlacement } from \"./Input\";\nimport { useBreakpoint } from \"../layout/useBreakpoint\";\n\nexport interface PinInputProps {\n /** Number of character cells (default 6; any positive number, e.g. 4, 6, 8) */\n length?: number;\n /** Input type */\n type?: \"numeric\" | \"alphanumeric\";\n /** Current value */\n value: string;\n /** Change handler */\n onChange: (value: string) => void;\n /** Label text or ReactNode (supports rich content like tooltip icons) */\n label?: React.ReactNode;\n /** Helper text */\n helperText?: string;\n /** Error state */\n error?: boolean | string;\n /** Disabled */\n disabled?: boolean;\n /** Mask characters (password mode) */\n mask?: boolean;\n /** Size */\n size?: \"small\" | \"medium\" | \"large\";\n /** Auto-focus first cell on mount */\n autoFocus?: boolean;\n /** Callback when all cells filled */\n onComplete?: (value: string) => void;\n /**\n * Label placement style.\n * - `'outlined'` — label sits on the border of a surrounding container. **Default.**\n * - `'above'` — label sits above the cells with a gap (classic stack).\n */\n labelPlacement?: LabelPlacement;\n /** Required indicator */\n required?: boolean;\n /** Stretch to full width of container (cells share width equally, same as standard inputs) */\n fullWidth?: boolean;\n}\n\nexport const PinInput = memo(function PinInput({\n length = 6,\n type = \"alphanumeric\",\n value,\n onChange,\n label,\n helperText,\n error,\n disabled = false,\n mask = false,\n size = \"medium\",\n autoFocus = false,\n onComplete,\n labelPlacement = \"outlined\",\n required,\n fullWidth = false,\n}: PinInputProps): React.ReactElement {\n const { tokens, theme } = useTheme();\n const { isMobile } = useBreakpoint();\n const isTransparentTheme =\n theme === \"transparent\" ||\n theme === \"transparent-bold\" ||\n theme === \"transparent-minimal\";\n const refs = useRef<(HTMLInputElement | null)[]>([]);\n const [focusedIndex, setFocusedIndex] = useState<number | null>(null);\n\n const chars = value.split(\"\").concat(Array(length).fill(\"\")).slice(0, length);\n\n useEffect(() => {\n if (autoFocus && refs.current[0]) {\n refs.current[0].focus();\n }\n }, [autoFocus]);\n\n // Between original tokens and the reduced set: slightly more height than 24/32/40, still less than 28/36/44\n const reducedHeight =\n size === \"small\" ? \"26px\" : size === \"medium\" ? \"34px\" : \"42px\";\n const controlHeight = isMobile ? \"44px\" : reducedHeight;\n const sizeConfig = {\n small: { fontSize: tokens.typography.fontSize.xs },\n medium: { fontSize: tokens.typography.fontSize.sm },\n large: { fontSize: tokens.typography.fontSize.base },\n };\n const config = { ...sizeConfig[size], height: controlHeight } as {\n height: string;\n fontSize: string;\n };\n\n const isValid = (char: string): boolean => {\n if (type === \"numeric\") return /^[0-9]$/.test(char);\n return /^[a-zA-Z0-9]$/.test(char);\n };\n\n const handleChange = useCallback(\n (index: number, inputValue: string) => {\n // Handle paste (multiple characters). Strip spaces so pasted codes like \"12 34 56\" work.\n if (inputValue.length > 1) {\n const normalized = inputValue.replace(/\\s/g, \"\");\n const filtered = normalized.split(\"\").filter(isValid).slice(0, length);\n const newValue = filtered.join(\"\");\n onChange(newValue);\n const nextIndex = Math.min(filtered.length, length - 1);\n refs.current[nextIndex]?.focus();\n if (filtered.length === length) onComplete?.(newValue);\n return;\n }\n\n const char = inputValue;\n if (char && !isValid(char)) return;\n\n const newChars = [...chars];\n newChars[index] = char.toUpperCase();\n const newValue = newChars.join(\"\").replace(/\\s/g, \"\");\n onChange(newValue);\n\n // Auto-advance\n if (char && index < length - 1) {\n refs.current[index + 1]?.focus();\n }\n\n if (newValue.length === length) {\n onComplete?.(newValue);\n }\n },\n [chars, length, onChange, onComplete, type],\n );\n\n const handleKeyDown = useCallback(\n (index: number, e: React.KeyboardEvent) => {\n if (e.key === \"Backspace\") {\n if (!chars[index] && index > 0) {\n refs.current[index - 1]?.focus();\n const newChars = [...chars];\n newChars[index - 1] = \"\";\n onChange(newChars.join(\"\"));\n }\n } else if (e.key === \"ArrowLeft\" && index > 0) {\n refs.current[index - 1]?.focus();\n } else if (e.key === \"ArrowRight\" && index < length - 1) {\n refs.current[index + 1]?.focus();\n }\n },\n [chars, length, onChange],\n );\n\n const hasError = !!error;\n const errorMessage = typeof error === \"string\" ? error : undefined;\n\n const inputBg = isTransparentTheme\n ? tokens.colors.interactive.transparentInputBg || \"rgba(139, 92, 246, 0.08)\"\n : tokens.colors.background.surface;\n\n const isOutlined = labelPlacement === \"outlined\";\n const isFocused = focusedIndex !== null;\n\n // Background for the outlined label \"notch\"\n const labelBg = isTransparentTheme\n ? tokens.colors.background.base\n : tokens.colors.background.surface;\n\n // Outlined label font sizes\n const outlinedLabelSizeMap = {\n small: \"0.625rem\",\n medium: \"0.6875rem\",\n large: \"0.75rem\",\n };\n // Container always uses spacing.sm (8px) horizontal padding, so label left is always 8-6=2px\n const outlinedLabelLeftMap = { small: \"2px\", medium: \"2px\", large: \"2px\" };\n\n // Shared label content\n const labelContent = (\n <>\n {label}\n {required && (\n <span\n style={{ color: tokens.colors.status.critical, marginLeft: \"2px\" }}\n >\n *\n </span>\n )}\n </>\n );\n\n // Cells renderer: fullWidth = cells share space (same total width as standard inputs); otherwise fixed square cells\n const cellsEl = (\n <div\n style={{\n display: \"flex\",\n gap: tokens.spacing.xs,\n width: fullWidth ? \"100%\" : undefined,\n }}\n >\n {Array.from({ length }, (_, i) => (\n <input\n key={i}\n ref={(el) => {\n refs.current[i] = el;\n }}\n type={mask ? \"password\" : \"text\"}\n inputMode={type === \"numeric\" ? \"numeric\" : \"text\"}\n maxLength={1}\n value={chars[i] || \"\"}\n onChange={(e) => handleChange(i, e.target.value)}\n onKeyDown={(e) => handleKeyDown(i, e)}\n onFocus={() => setFocusedIndex(i)}\n onBlur={() => setFocusedIndex(null)}\n onPaste={(e) => {\n e.preventDefault();\n handleChange(i, e.clipboardData.getData(\"text\"));\n }}\n disabled={disabled}\n aria-label={`Character ${i + 1} of ${length}`}\n aria-invalid={hasError || undefined}\n aria-describedby={\n errorMessage\n ? \"zendir-pin-error\"\n : helperText\n ? \"zendir-pin-helper\"\n : undefined\n }\n style={{\n ...(fullWidth\n ? { flex: 1, minWidth: 0 }\n : { width: controlHeight }),\n height: config.height,\n textAlign: \"center\",\n fontSize: config.fontSize,\n fontWeight: tokens.typography.fontWeight.bold,\n fontFamily: tokens.typography.fontFamily.mono,\n fontVariantNumeric: \"tabular-nums\",\n color: tokens.colors.text.primary,\n backgroundColor: isOutlined ? \"transparent\" : inputBg,\n border: isOutlined\n ? \"none\"\n : hasError\n ? `${tokens.borders.width.medium} solid ${tokens.colors.status.critical}`\n : focusedIndex === i\n ? `${tokens.borders.width.medium} solid ${tokens.colors.accent.primary}`\n : chars[i]\n ? `${tokens.borders.width.medium} solid ${tokens.colors.border.default}`\n : `${tokens.borders.width.medium} solid ${tokens.colors.border.muted}`,\n borderRadius: isOutlined ? 0 : tokens.borderRadius.md,\n outline: \"none\",\n transition: tokens.animation.fast,\n opacity: disabled ? 0.5 : 1,\n cursor: disabled ? \"not-allowed\" : \"text\",\n ...(!isOutlined && focusedIndex === i\n ? {\n boxShadow: tokens.borders.focusRing.subtle,\n }\n : {}),\n }}\n />\n ))}\n </div>\n );\n\n return (\n <div\n style={{\n fontFamily: tokens.typography.fontFamily.primary,\n width: fullWidth ? \"100%\" : undefined,\n }}\n >\n {/* Label: 'above' variant */}\n {label && !isOutlined && (\n <div\n style={{\n fontSize: tokens.typography.fontSize.xs,\n fontWeight: tokens.typography.fontWeight.medium,\n color: tokens.colors.text.secondary,\n letterSpacing: tokens.typography.letterSpacing.wide,\n marginBottom: tokens.spacing.xs,\n }}\n >\n {labelContent}\n </div>\n )}\n\n {/* Outlined variant: wrap cells in a bordered container with the label on the border */}\n {isOutlined ? (\n <div\n style={{\n position: \"relative\",\n width: fullWidth ? \"100%\" : undefined,\n minHeight: controlHeight,\n border: hasError\n ? tokens.borders.input.error\n : isFocused\n ? tokens.borders.input.focus\n : tokens.borders.input.default,\n borderRadius: tokens.borderRadius.md,\n padding: `0 ${tokens.spacing.sm}`,\n display: \"flex\",\n alignItems: \"center\",\n backgroundColor: inputBg,\n transition: tokens.animation.fast,\n boxSizing: \"border-box\",\n ...(isFocused\n ? {\n boxShadow: tokens.borders.focusRing.subtle,\n }\n : {}),\n }}\n >\n {label && (\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: outlinedLabelLeftMap[size],\n transform: \"translateY(-50%)\",\n backgroundColor: labelBg,\n padding: `0 ${tokens.spacing.xs}`,\n fontSize: outlinedLabelSizeMap[size],\n fontWeight: tokens.typography.fontWeight.medium,\n color: isFocused\n ? hasError\n ? tokens.colors.status.critical\n : tokens.colors.accent.primary\n : hasError\n ? tokens.colors.status.critical\n : tokens.colors.text.secondary,\n letterSpacing: tokens.typography.letterSpacing.wide,\n zIndex: 1,\n pointerEvents: \"none\",\n transition: tokens.animation.fast,\n lineHeight: 1.2,\n whiteSpace: \"nowrap\",\n }}\n >\n {labelContent}\n </div>\n )}\n {cellsEl}\n </div>\n ) : (\n cellsEl\n )}\n\n {(helperText || errorMessage) && (\n <div\n id={errorMessage ? \"zendir-pin-error\" : \"zendir-pin-helper\"}\n role={errorMessage ? \"alert\" : undefined}\n style={{\n fontSize: tokens.typography.fontSize.xs,\n color: hasError\n ? tokens.colors.status.critical\n : tokens.colors.text.tertiary,\n marginTop: tokens.spacing.xs,\n }}\n >\n {errorMessage || helperText}\n </div>\n )}\n </div>\n );\n});\n\nexport default PinInput;\n"],"mappings":"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"}
1
+ {"version":3,"file":"PinInput.js","names":[],"sources":["../../../../src/react/core/inputs/PinInput.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - PinInput Component\r\n *\r\n * Multi-character code entry for passwords, verification codes, and team IDs.\r\n * Auto-advances focus between cells. Supports numeric and alphanumeric modes.\r\n *\r\n * Astro UX Compliance:\r\n * - Consistent styling with Input component\r\n * - Focus ring for accessibility\r\n * - Monospace tabular numbers\r\n *\r\n * @example\r\n * ```tsx\r\n * <PinInput length={6} type=\"alphanumeric\" value={password} onChange={setPassword} label=\"Team Password\" />\r\n * <PinInput length={4} type=\"numeric\" value={code} onChange={setCode} label=\"Verification Code\" />\r\n * <PinInput length={8} value={token} onChange={setToken} label=\"API Token\" />\r\n * ```\r\n */\r\n\r\nimport React, { memo, useState, useRef, useCallback, useEffect } from \"react\";\r\nimport { useTheme } from \"../../theme\";\r\nimport type { LabelPlacement } from \"./Input\";\r\nimport { useBreakpoint } from \"../layout/useBreakpoint\";\r\n\r\nexport interface PinInputProps {\r\n /** Number of character cells (default 6; any positive number, e.g. 4, 6, 8) */\r\n length?: number;\r\n /** Input type */\r\n type?: \"numeric\" | \"alphanumeric\";\r\n /** Current value */\r\n value: string;\r\n /** Change handler */\r\n onChange: (value: string) => void;\r\n /** Label text or ReactNode (supports rich content like tooltip icons) */\r\n label?: React.ReactNode;\r\n /** Helper text */\r\n helperText?: string;\r\n /** Error state */\r\n error?: boolean | string;\r\n /** Disabled */\r\n disabled?: boolean;\r\n /** Mask characters (password mode) */\r\n mask?: boolean;\r\n /** Size */\r\n size?: \"small\" | \"medium\" | \"large\";\r\n /** Auto-focus first cell on mount */\r\n autoFocus?: boolean;\r\n /** Callback when all cells filled */\r\n onComplete?: (value: string) => void;\r\n /**\r\n * Label placement style.\r\n * - `'outlined'` — label sits on the border of a surrounding container. **Default.**\r\n * - `'above'` — label sits above the cells with a gap (classic stack).\r\n */\r\n labelPlacement?: LabelPlacement;\r\n /** Required indicator */\r\n required?: boolean;\r\n /** Stretch to full width of container (cells share width equally, same as standard inputs) */\r\n fullWidth?: boolean;\r\n}\r\n\r\nexport const PinInput = memo(function PinInput({\r\n length = 6,\r\n type = \"alphanumeric\",\r\n value,\r\n onChange,\r\n label,\r\n helperText,\r\n error,\r\n disabled = false,\r\n mask = false,\r\n size = \"medium\",\r\n autoFocus = false,\r\n onComplete,\r\n labelPlacement = \"outlined\",\r\n required,\r\n fullWidth = false,\r\n}: PinInputProps): React.ReactElement {\r\n const { tokens, theme } = useTheme();\r\n const { isMobile } = useBreakpoint();\r\n const isTransparentTheme =\r\n theme === \"transparent\" ||\r\n theme === \"transparent-bold\" ||\r\n theme === \"transparent-minimal\";\r\n const refs = useRef<(HTMLInputElement | null)[]>([]);\r\n const [focusedIndex, setFocusedIndex] = useState<number | null>(null);\r\n\r\n const chars = value.split(\"\").concat(Array(length).fill(\"\")).slice(0, length);\r\n\r\n useEffect(() => {\r\n if (autoFocus && refs.current[0]) {\r\n refs.current[0].focus();\r\n }\r\n }, [autoFocus]);\r\n\r\n // Between original tokens and the reduced set: slightly more height than 24/32/40, still less than 28/36/44\r\n const reducedHeight =\r\n size === \"small\" ? \"26px\" : size === \"medium\" ? \"34px\" : \"42px\";\r\n const controlHeight = isMobile ? \"44px\" : reducedHeight;\r\n const sizeConfig = {\r\n small: { fontSize: tokens.typography.fontSize.xs },\r\n medium: { fontSize: tokens.typography.fontSize.sm },\r\n large: { fontSize: tokens.typography.fontSize.base },\r\n };\r\n const config = { ...sizeConfig[size], height: controlHeight } as {\r\n height: string;\r\n fontSize: string;\r\n };\r\n\r\n const isValid = (char: string): boolean => {\r\n if (type === \"numeric\") return /^[0-9]$/.test(char);\r\n return /^[a-zA-Z0-9]$/.test(char);\r\n };\r\n\r\n const handleChange = useCallback(\r\n (index: number, inputValue: string) => {\r\n // Handle paste (multiple characters). Strip spaces so pasted codes like \"12 34 56\" work.\r\n if (inputValue.length > 1) {\r\n const normalized = inputValue.replace(/\\s/g, \"\");\r\n const filtered = normalized.split(\"\").filter(isValid).slice(0, length);\r\n const newValue = filtered.join(\"\");\r\n onChange(newValue);\r\n const nextIndex = Math.min(filtered.length, length - 1);\r\n refs.current[nextIndex]?.focus();\r\n if (filtered.length === length) onComplete?.(newValue);\r\n return;\r\n }\r\n\r\n const char = inputValue;\r\n if (char && !isValid(char)) return;\r\n\r\n const newChars = [...chars];\r\n newChars[index] = char.toUpperCase();\r\n const newValue = newChars.join(\"\").replace(/\\s/g, \"\");\r\n onChange(newValue);\r\n\r\n // Auto-advance\r\n if (char && index < length - 1) {\r\n refs.current[index + 1]?.focus();\r\n }\r\n\r\n if (newValue.length === length) {\r\n onComplete?.(newValue);\r\n }\r\n },\r\n [chars, length, onChange, onComplete, type],\r\n );\r\n\r\n const handleKeyDown = useCallback(\r\n (index: number, e: React.KeyboardEvent) => {\r\n if (e.key === \"Backspace\") {\r\n if (!chars[index] && index > 0) {\r\n refs.current[index - 1]?.focus();\r\n const newChars = [...chars];\r\n newChars[index - 1] = \"\";\r\n onChange(newChars.join(\"\"));\r\n }\r\n } else if (e.key === \"ArrowLeft\" && index > 0) {\r\n refs.current[index - 1]?.focus();\r\n } else if (e.key === \"ArrowRight\" && index < length - 1) {\r\n refs.current[index + 1]?.focus();\r\n }\r\n },\r\n [chars, length, onChange],\r\n );\r\n\r\n const hasError = !!error;\r\n const errorMessage = typeof error === \"string\" ? error : undefined;\r\n\r\n const inputBg = isTransparentTheme\r\n ? tokens.colors.interactive.transparentInputBg || \"rgba(139, 92, 246, 0.08)\"\r\n : tokens.colors.background.surface;\r\n\r\n const isOutlined = labelPlacement === \"outlined\";\r\n const isFocused = focusedIndex !== null;\r\n\r\n // Background for the outlined label \"notch\"\r\n const labelBg = isTransparentTheme\r\n ? tokens.colors.background.base\r\n : tokens.colors.background.surface;\r\n\r\n // Outlined label font sizes\r\n const outlinedLabelSizeMap = {\r\n small: \"0.625rem\",\r\n medium: \"0.6875rem\",\r\n large: \"0.75rem\",\r\n };\r\n // Container always uses spacing.sm (8px) horizontal padding, so label left is always 8-6=2px\r\n const outlinedLabelLeftMap = { small: \"2px\", medium: \"2px\", large: \"2px\" };\r\n\r\n // Shared label content\r\n const labelContent = (\r\n <>\r\n {label}\r\n {required && (\r\n <span\r\n style={{ color: tokens.colors.status.critical, marginLeft: \"2px\" }}\r\n >\r\n *\r\n </span>\r\n )}\r\n </>\r\n );\r\n\r\n // Cells renderer: fullWidth = cells share space (same total width as standard inputs); otherwise fixed square cells\r\n const cellsEl = (\r\n <div\r\n style={{\r\n display: \"flex\",\r\n gap: tokens.spacing.xs,\r\n width: fullWidth ? \"100%\" : undefined,\r\n }}\r\n >\r\n {Array.from({ length }, (_, i) => (\r\n <input\r\n key={i}\r\n ref={(el) => {\r\n refs.current[i] = el;\r\n }}\r\n type={mask ? \"password\" : \"text\"}\r\n inputMode={type === \"numeric\" ? \"numeric\" : \"text\"}\r\n maxLength={1}\r\n value={chars[i] || \"\"}\r\n onChange={(e) => handleChange(i, e.target.value)}\r\n onKeyDown={(e) => handleKeyDown(i, e)}\r\n onFocus={() => setFocusedIndex(i)}\r\n onBlur={() => setFocusedIndex(null)}\r\n onPaste={(e) => {\r\n e.preventDefault();\r\n handleChange(i, e.clipboardData.getData(\"text\"));\r\n }}\r\n disabled={disabled}\r\n aria-label={`Character ${i + 1} of ${length}`}\r\n aria-invalid={hasError || undefined}\r\n aria-describedby={\r\n errorMessage\r\n ? \"zendir-pin-error\"\r\n : helperText\r\n ? \"zendir-pin-helper\"\r\n : undefined\r\n }\r\n style={{\r\n ...(fullWidth\r\n ? { flex: 1, minWidth: 0 }\r\n : { width: controlHeight }),\r\n height: config.height,\r\n textAlign: \"center\",\r\n fontSize: config.fontSize,\r\n fontWeight: tokens.typography.fontWeight.bold,\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n fontVariantNumeric: \"tabular-nums\",\r\n color: tokens.colors.text.primary,\r\n backgroundColor: isOutlined ? \"transparent\" : inputBg,\r\n border: isOutlined\r\n ? \"none\"\r\n : hasError\r\n ? `${tokens.borders.width.medium} solid ${tokens.colors.status.critical}`\r\n : focusedIndex === i\r\n ? `${tokens.borders.width.medium} solid ${tokens.colors.accent.primary}`\r\n : chars[i]\r\n ? `${tokens.borders.width.medium} solid ${tokens.colors.border.default}`\r\n : `${tokens.borders.width.medium} solid ${tokens.colors.border.muted}`,\r\n borderRadius: isOutlined ? 0 : tokens.borderRadius.md,\r\n outline: \"none\",\r\n transition: tokens.animation.fast,\r\n opacity: disabled ? 0.5 : 1,\r\n cursor: disabled ? \"not-allowed\" : \"text\",\r\n ...(!isOutlined && focusedIndex === i\r\n ? {\r\n boxShadow: tokens.borders.focusRing.subtle,\r\n }\r\n : {}),\r\n }}\r\n />\r\n ))}\r\n </div>\r\n );\r\n\r\n return (\r\n <div\r\n style={{\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n width: fullWidth ? \"100%\" : undefined,\r\n }}\r\n >\r\n {/* Label: 'above' variant */}\r\n {label && !isOutlined && (\r\n <div\r\n style={{\r\n fontSize: tokens.typography.fontSize.xs,\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n color: tokens.colors.text.secondary,\r\n letterSpacing: tokens.typography.letterSpacing.wide,\r\n marginBottom: tokens.spacing.xs,\r\n }}\r\n >\r\n {labelContent}\r\n </div>\r\n )}\r\n\r\n {/* Outlined variant: wrap cells in a bordered container with the label on the border */}\r\n {isOutlined ? (\r\n <div\r\n style={{\r\n position: \"relative\",\r\n width: fullWidth ? \"100%\" : undefined,\r\n minHeight: controlHeight,\r\n border: hasError\r\n ? tokens.borders.input.error\r\n : isFocused\r\n ? tokens.borders.input.focus\r\n : tokens.borders.input.default,\r\n borderRadius: tokens.borderRadius.md,\r\n padding: `0 ${tokens.spacing.sm}`,\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n backgroundColor: inputBg,\r\n transition: tokens.animation.fast,\r\n boxSizing: \"border-box\",\r\n ...(isFocused\r\n ? {\r\n boxShadow: tokens.borders.focusRing.subtle,\r\n }\r\n : {}),\r\n }}\r\n >\r\n {label && (\r\n <div\r\n style={{\r\n position: \"absolute\",\r\n top: 0,\r\n left: outlinedLabelLeftMap[size],\r\n transform: \"translateY(-50%)\",\r\n backgroundColor: labelBg,\r\n padding: `0 ${tokens.spacing.xs}`,\r\n fontSize: outlinedLabelSizeMap[size],\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n color: isFocused\r\n ? hasError\r\n ? tokens.colors.status.critical\r\n : tokens.colors.accent.primary\r\n : hasError\r\n ? tokens.colors.status.critical\r\n : tokens.colors.text.secondary,\r\n letterSpacing: tokens.typography.letterSpacing.wide,\r\n zIndex: 1,\r\n pointerEvents: \"none\",\r\n transition: tokens.animation.fast,\r\n lineHeight: 1.2,\r\n whiteSpace: \"nowrap\",\r\n }}\r\n >\r\n {labelContent}\r\n </div>\r\n )}\r\n {cellsEl}\r\n </div>\r\n ) : (\r\n cellsEl\r\n )}\r\n\r\n {(helperText || errorMessage) && (\r\n <div\r\n id={errorMessage ? \"zendir-pin-error\" : \"zendir-pin-helper\"}\r\n role={errorMessage ? \"alert\" : undefined}\r\n style={{\r\n fontSize: tokens.typography.fontSize.xs,\r\n color: hasError\r\n ? tokens.colors.status.critical\r\n : tokens.colors.text.tertiary,\r\n marginTop: tokens.spacing.xs,\r\n }}\r\n >\r\n {errorMessage || helperText}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n});\r\n\r\nexport default PinInput;\r\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"Select.js","names":[],"sources":["../../../../src/react/core/inputs/Select.tsx"],"sourcesContent":["/**\n * @zendir/ui - Select Component\n *\n * Dropdown select following Astro UX Design System.\n *\n * @example\n * ```tsx\n * <Select\n * label=\"Spacecraft\"\n * options={[\n * { value: 'sat-001', label: 'Explorer-1' },\n * { value: 'sat-002', label: 'Voyager-2' },\n * ]}\n * value={selected}\n * onChange={setSelected}\n * />\n * ```\n */\n\nimport React, {\n memo,\n forwardRef,\n useState,\n useId,\n useRef,\n useEffect,\n useLayoutEffect,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useTheme } from \"../../theme\";\nimport { classNames } from \"../../utils\";\nimport type { LabelPlacement } from \"./Input\";\nimport { useBreakpoint } from \"../layout/useBreakpoint\";\n\nexport interface SelectOption {\n value: string;\n label: string;\n disabled?: boolean;\n}\n\nexport interface SelectProps {\n /** Label text or ReactNode (supports rich content like tooltip icons) */\n label?: React.ReactNode;\n /** Options array */\n options: SelectOption[];\n /** Selected value */\n value?: string;\n /** Change handler */\n onChange?: (value: string) => void;\n /** Placeholder text */\n placeholder?: string;\n /** Helper text */\n helperText?: string;\n /** Error message */\n error?: string;\n /** Disabled state */\n disabled?: boolean;\n /** Full width */\n fullWidth?: boolean;\n /** Size variant */\n size?: \"small\" | \"medium\" | \"large\";\n /** Custom className */\n className?: string;\n /**\n * Label placement style.\n * - `'outlined'` — label sits on the input border (notched/Material-style). **Default.**\n * - `'above'` — label sits above the input with a gap (classic stack).\n */\n labelPlacement?: LabelPlacement;\n /** Required indicator */\n required?: boolean;\n}\n\nexport const Select = memo(\n forwardRef<HTMLDivElement, SelectProps>(function Select(\n {\n label,\n options,\n value,\n onChange,\n placeholder = \"Select...\",\n helperText,\n error,\n disabled = false,\n fullWidth = false,\n size = \"medium\",\n className = \"\",\n labelPlacement = \"outlined\",\n required,\n },\n ref,\n ): React.ReactElement {\n const { tokens, theme } = useTheme();\n const { isMobile } = useBreakpoint();\n const [isOpen, setIsOpen] = useState(false);\n const [focused, setFocused] = useState(false);\n const [dropdownRect, setDropdownRect] = useState<{\n top: number;\n left: number;\n width: number;\n openUp?: boolean;\n } | null>(null);\n const selectRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const dropdownRef = useRef<HTMLUListElement>(null);\n const generatedId = useId();\n\n // Transparent theme detection (purple-hue transparent bg)\n const isTransparentTheme =\n theme === \"transparent\" ||\n theme === \"transparent-bold\" ||\n theme === \"transparent-minimal\";\n const _transparentDefault = tokens.colors.interactive.transparentDefault;\n const _transparentInputBg = tokens.colors.interactive.transparentInputBg;\n\n const hasError = !!error;\n const selectedOption = options.find((o) => o.value === value);\n const isOutlined = labelPlacement === \"outlined\";\n\n const sizeConfig = {\n small: {\n height: tokens.elementSize.sm,\n fontSize: tokens.typography.fontSize.xs,\n padding: tokens.spacing.sm,\n inputPaddingLeft: 8,\n },\n medium: {\n height: tokens.elementSize.md,\n fontSize: tokens.typography.fontSize.sm,\n padding: tokens.spacing.md,\n inputPaddingLeft: 16,\n },\n large: {\n height: tokens.elementSize.lg,\n fontSize: tokens.typography.fontSize.base,\n padding: tokens.spacing.lg,\n inputPaddingLeft: 24,\n },\n };\n const config = sizeConfig[size];\n const controlHeight = isMobile ? \"44px\" : config.height;\n const optionRowHeight = isMobile\n ? 40\n : size === \"small\"\n ? 30\n : size === \"medium\"\n ? 34\n : 38;\n\n // Position dropdown when open (for portal) - keep in viewport\n const DROPDOWN_MAX_HEIGHT = 200;\n const GAP = 4;\n useLayoutEffect(() => {\n if (!isOpen || !triggerRef.current) {\n setDropdownRect(null);\n return;\n }\n const update = () => {\n if (triggerRef.current) {\n const rect = triggerRef.current.getBoundingClientRect();\n const spaceBelow = window.innerHeight - rect.bottom - GAP;\n const spaceAbove = rect.top - GAP;\n const openUp =\n spaceBelow <\n Math.min(DROPDOWN_MAX_HEIGHT, options.length * optionRowHeight) &&\n spaceAbove > spaceBelow;\n setDropdownRect({\n top: openUp ? rect.top - GAP : rect.bottom + GAP,\n left: rect.left,\n width: rect.width,\n openUp,\n });\n }\n };\n update();\n window.addEventListener(\"scroll\", update, true);\n window.addEventListener(\"resize\", update);\n return () => {\n window.removeEventListener(\"scroll\", update, true);\n window.removeEventListener(\"resize\", update);\n };\n }, [isOpen, options.length, optionRowHeight]);\n\n // Close dropdown on outside click (include portaled dropdown)\n useEffect(() => {\n if (!isOpen) return;\n\n const handleClickOutside = (e: MouseEvent) => {\n const target = e.target as Node;\n // Check if click is inside the trigger or the portaled dropdown\n if (\n selectRef.current?.contains(target) ||\n dropdownRef.current?.contains(target)\n ) {\n return;\n }\n setIsOpen(false);\n };\n // Use click instead of mousedown to avoid race condition with option onClick\n document.addEventListener(\"click\", handleClickOutside, true);\n return () =>\n document.removeEventListener(\"click\", handleClickOutside, true);\n }, [isOpen]);\n\n const computedBorder = hasError\n ? tokens.borders.input.error\n : focused || isOpen\n ? tokens.borders.input.focus\n : tokens.borders.input.default;\n\n // Background for the outlined label \"notch\"\n const labelBg = isTransparentTheme\n ? tokens.colors.background.base\n : tokens.colors.background.surface;\n\n // Outlined label font sizes\n const outlinedLabelSizeMap = {\n small: \"0.625rem\",\n medium: \"0.6875rem\",\n large: \"0.75rem\",\n };\n const outlinedLabelLeftMap = {\n small: \"2px\",\n medium: \"10px\",\n large: \"18px\",\n };\n\n // Shared label content\n const labelContent = (\n <>\n {label}\n {required && (\n <span\n style={{ color: tokens.colors.status.critical, marginLeft: \"2px\" }}\n >\n *\n </span>\n )}\n </>\n );\n\n return (\n <div\n ref={ref}\n className={classNames(\"zendir-select-wrapper\", className)}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: tokens.spacing.xs,\n width: fullWidth ? \"100%\" : undefined,\n }}\n >\n {/* Label: 'above' variant */}\n {label && !isOutlined && (\n <span\n id={`${generatedId}-label`}\n style={{\n fontSize: tokens.typography.fontSize.xs,\n fontWeight: tokens.typography.fontWeight.medium,\n color: tokens.colors.text.secondary,\n }}\n >\n {labelContent}\n </span>\n )}\n\n <div ref={selectRef} style={{ position: \"relative\" }}>\n {/* Label: 'outlined' variant — positioned on the trigger border */}\n {label && isOutlined && (\n <span\n id={`${generatedId}-label`}\n style={{\n position: \"absolute\",\n top: 0,\n left: outlinedLabelLeftMap[size],\n transform: \"translateY(-50%)\",\n backgroundColor: labelBg,\n padding: `0 ${tokens.spacing.xs}`,\n fontSize: outlinedLabelSizeMap[size],\n fontWeight: tokens.typography.fontWeight.medium,\n color:\n focused || isOpen\n ? hasError\n ? tokens.colors.status.critical\n : tokens.colors.accent.primary\n : hasError\n ? tokens.colors.status.critical\n : tokens.colors.text.secondary,\n zIndex: 1,\n pointerEvents: \"none\",\n transition: `color ${tokens.animation.fast}`,\n lineHeight: 1.2,\n whiteSpace: \"nowrap\",\n letterSpacing: \"0.01em\",\n }}\n >\n {labelContent}\n </span>\n )}\n\n <button\n ref={triggerRef}\n type=\"button\"\n disabled={disabled}\n onClick={() => !disabled && setIsOpen(!isOpen)}\n onFocus={() => setFocused(true)}\n onBlur={() => setFocused(false)}\n aria-haspopup=\"listbox\"\n aria-expanded={isOpen}\n aria-labelledby={label ? `${generatedId}-label` : undefined}\n aria-describedby={\n helperText || error ? `${generatedId}-hint` : undefined\n }\n style={{\n width: \"100%\",\n height: controlHeight,\n padding: `0 ${config.padding}`,\n fontSize: config.fontSize,\n fontFamily: tokens.typography.fontFamily.primary,\n color: selectedOption\n ? tokens.colors.text.primary\n : tokens.colors.text.muted,\n backgroundColor: isTransparentTheme\n ? \"rgba(10, 15, 25, 0.4)\"\n : tokens.colors.background.surface,\n border: computedBorder,\n borderRadius: tokens.borderRadius.md,\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n opacity: disabled ? 0.5 : 1,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n textAlign: \"left\",\n outline: \"none\",\n transition: `border-color ${tokens.animation.fast}`,\n boxShadow:\n focused || isOpen ? tokens.borders.focusRing.default : \"none\",\n }}\n >\n <span>{selectedOption?.label ?? placeholder}</span>\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill={tokens.colors.text.muted}\n style={{\n transform: isOpen ? \"rotate(180deg)\" : \"rotate(0)\",\n transition: `transform ${tokens.animation.fast}`,\n }}\n >\n <path d=\"M7 10l5 5 5-5z\" />\n </svg>\n </button>\n\n {isOpen &&\n dropdownRect &&\n createPortal(\n <ul\n ref={dropdownRef}\n role=\"listbox\"\n style={{\n position: \"fixed\",\n top: dropdownRect.openUp ? undefined : dropdownRect.top,\n bottom: dropdownRect.openUp\n ? window.innerHeight - dropdownRect.top\n : undefined,\n left: dropdownRect.left,\n width: dropdownRect.width,\n padding: tokens.spacing.xs,\n backgroundColor: isTransparentTheme\n ? `${tokens.colors.accent.primary}1f`\n : tokens.colors.background.elevated,\n backdropFilter: isTransparentTheme ? \"blur(12px)\" : undefined,\n WebkitBackdropFilter: isTransparentTheme\n ? \"blur(12px)\"\n : undefined,\n border: tokens.borders.dropdown,\n borderRadius: tokens.borderRadius.md,\n boxShadow: `${tokens.shadows.lg}, 0 0 30px ${tokens.colors.accent.primary}10`,\n zIndex: 10000,\n maxHeight: \"200px\",\n overflowY: \"auto\",\n listStyle: \"none\",\n margin: 0,\n animation: `zendir-dropdown ${tokens.animation.duration.normal}ms ${tokens.animation.easing.default} both`,\n }}\n >\n <style>{`@keyframes zendir-dropdown { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } }`}</style>\n {options.map((option) => (\n <li\n key={option.value}\n role=\"option\"\n aria-selected={option.value === value}\n tabIndex={option.disabled ? -1 : 0}\n onMouseDown={(e) => {\n // Prevent the document click handler from firing before we process this\n e.stopPropagation();\n }}\n onClick={(e) => {\n e.stopPropagation();\n if (!option.disabled) {\n onChange?.(option.value);\n setIsOpen(false);\n }\n }}\n onKeyDown={(e) => {\n if (option.disabled) return;\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onChange?.(option.value);\n setIsOpen(false);\n }\n }}\n style={{\n padding: `0 ${tokens.spacing.md}`,\n height: optionRowHeight,\n minHeight: optionRowHeight,\n display: \"flex\",\n alignItems: \"center\",\n fontSize: config.fontSize,\n lineHeight: 1.2,\n whiteSpace: \"nowrap\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n color: option.disabled\n ? tokens.colors.text.muted\n : tokens.colors.text.primary,\n backgroundColor:\n option.value === value\n ? `${tokens.colors.accent.primary}15`\n : \"transparent\",\n borderRadius: tokens.borderRadius.sm,\n cursor: option.disabled ? \"not-allowed\" : \"pointer\",\n opacity: option.disabled ? 0.5 : 1,\n transition: `background-color ${tokens.animation.fast}`,\n }}\n onMouseEnter={(e) => {\n if (!option.disabled) {\n e.currentTarget.style.backgroundColor = `${tokens.colors.accent.primary}10`;\n }\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.backgroundColor =\n option.value === value\n ? `${tokens.colors.accent.primary}15`\n : \"transparent\";\n }}\n >\n {option.label}\n </li>\n ))}\n </ul>,\n document.body,\n )}\n </div>\n\n {(helperText || error) && (\n <span\n id={`${generatedId}-hint`}\n role={hasError ? \"alert\" : undefined}\n style={{\n fontSize: tokens.typography.fontSize.xs,\n color: hasError\n ? tokens.colors.status.critical\n : tokens.colors.text.muted,\n }}\n >\n {error || helperText}\n </span>\n )}\n </div>\n );\n }),\n);\n\nexport default Select;\n"],"mappings":"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"}
1
+ {"version":3,"file":"Select.js","names":[],"sources":["../../../../src/react/core/inputs/Select.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - Select Component\r\n *\r\n * Dropdown select following Astro UX Design System.\r\n *\r\n * @example\r\n * ```tsx\r\n * <Select\r\n * label=\"Spacecraft\"\r\n * options={[\r\n * { value: 'sat-001', label: 'Explorer-1' },\r\n * { value: 'sat-002', label: 'Voyager-2' },\r\n * ]}\r\n * value={selected}\r\n * onChange={setSelected}\r\n * />\r\n * ```\r\n */\r\n\r\nimport React, {\r\n memo,\r\n forwardRef,\r\n useState,\r\n useId,\r\n useRef,\r\n useEffect,\r\n useLayoutEffect,\r\n} from \"react\";\r\nimport { createPortal } from \"react-dom\";\r\nimport { useTheme } from \"../../theme\";\r\nimport { classNames } from \"../../utils\";\r\nimport type { LabelPlacement } from \"./Input\";\r\nimport { useBreakpoint } from \"../layout/useBreakpoint\";\r\n\r\nexport interface SelectOption {\r\n value: string;\r\n label: string;\r\n disabled?: boolean;\r\n}\r\n\r\nexport interface SelectProps {\r\n /** Label text or ReactNode (supports rich content like tooltip icons) */\r\n label?: React.ReactNode;\r\n /** Options array */\r\n options: SelectOption[];\r\n /** Selected value */\r\n value?: string;\r\n /** Change handler */\r\n onChange?: (value: string) => void;\r\n /** Placeholder text */\r\n placeholder?: string;\r\n /** Helper text */\r\n helperText?: string;\r\n /** Error message */\r\n error?: string;\r\n /** Disabled state */\r\n disabled?: boolean;\r\n /** Full width */\r\n fullWidth?: boolean;\r\n /** Size variant */\r\n size?: \"small\" | \"medium\" | \"large\";\r\n /** Custom className */\r\n className?: string;\r\n /**\r\n * Label placement style.\r\n * - `'outlined'` — label sits on the input border (notched/Material-style). **Default.**\r\n * - `'above'` — label sits above the input with a gap (classic stack).\r\n */\r\n labelPlacement?: LabelPlacement;\r\n /** Required indicator */\r\n required?: boolean;\r\n}\r\n\r\nexport const Select = memo(\r\n forwardRef<HTMLDivElement, SelectProps>(function Select(\r\n {\r\n label,\r\n options,\r\n value,\r\n onChange,\r\n placeholder = \"Select...\",\r\n helperText,\r\n error,\r\n disabled = false,\r\n fullWidth = false,\r\n size = \"medium\",\r\n className = \"\",\r\n labelPlacement = \"outlined\",\r\n required,\r\n },\r\n ref,\r\n ): React.ReactElement {\r\n const { tokens, theme } = useTheme();\r\n const { isMobile } = useBreakpoint();\r\n const [isOpen, setIsOpen] = useState(false);\r\n const [focused, setFocused] = useState(false);\r\n const [dropdownRect, setDropdownRect] = useState<{\r\n top: number;\r\n left: number;\r\n width: number;\r\n openUp?: boolean;\r\n } | null>(null);\r\n const selectRef = useRef<HTMLDivElement>(null);\r\n const triggerRef = useRef<HTMLButtonElement>(null);\r\n const dropdownRef = useRef<HTMLUListElement>(null);\r\n const generatedId = useId();\r\n\r\n // Transparent theme detection (purple-hue transparent bg)\r\n const isTransparentTheme =\r\n theme === \"transparent\" ||\r\n theme === \"transparent-bold\" ||\r\n theme === \"transparent-minimal\";\r\n const _transparentDefault = tokens.colors.interactive.transparentDefault;\r\n const _transparentInputBg = tokens.colors.interactive.transparentInputBg;\r\n\r\n const hasError = !!error;\r\n const selectedOption = options.find((o) => o.value === value);\r\n const isOutlined = labelPlacement === \"outlined\";\r\n\r\n const sizeConfig = {\r\n small: {\r\n height: tokens.elementSize.sm,\r\n fontSize: tokens.typography.fontSize.xs,\r\n padding: tokens.spacing.sm,\r\n inputPaddingLeft: 8,\r\n },\r\n medium: {\r\n height: tokens.elementSize.md,\r\n fontSize: tokens.typography.fontSize.sm,\r\n padding: tokens.spacing.md,\r\n inputPaddingLeft: 16,\r\n },\r\n large: {\r\n height: tokens.elementSize.lg,\r\n fontSize: tokens.typography.fontSize.base,\r\n padding: tokens.spacing.lg,\r\n inputPaddingLeft: 24,\r\n },\r\n };\r\n const config = sizeConfig[size];\r\n const controlHeight = isMobile ? \"44px\" : config.height;\r\n const optionRowHeight = isMobile\r\n ? 40\r\n : size === \"small\"\r\n ? 30\r\n : size === \"medium\"\r\n ? 34\r\n : 38;\r\n\r\n // Position dropdown when open (for portal) - keep in viewport\r\n const DROPDOWN_MAX_HEIGHT = 200;\r\n const GAP = 4;\r\n useLayoutEffect(() => {\r\n if (!isOpen || !triggerRef.current) {\r\n setDropdownRect(null);\r\n return;\r\n }\r\n const update = () => {\r\n if (triggerRef.current) {\r\n const rect = triggerRef.current.getBoundingClientRect();\r\n const spaceBelow = window.innerHeight - rect.bottom - GAP;\r\n const spaceAbove = rect.top - GAP;\r\n const openUp =\r\n spaceBelow <\r\n Math.min(DROPDOWN_MAX_HEIGHT, options.length * optionRowHeight) &&\r\n spaceAbove > spaceBelow;\r\n setDropdownRect({\r\n top: openUp ? rect.top - GAP : rect.bottom + GAP,\r\n left: rect.left,\r\n width: rect.width,\r\n openUp,\r\n });\r\n }\r\n };\r\n update();\r\n window.addEventListener(\"scroll\", update, true);\r\n window.addEventListener(\"resize\", update);\r\n return () => {\r\n window.removeEventListener(\"scroll\", update, true);\r\n window.removeEventListener(\"resize\", update);\r\n };\r\n }, [isOpen, options.length, optionRowHeight]);\r\n\r\n // Close dropdown on outside click (include portaled dropdown)\r\n useEffect(() => {\r\n if (!isOpen) return;\r\n\r\n const handleClickOutside = (e: MouseEvent) => {\r\n const target = e.target as Node;\r\n // Check if click is inside the trigger or the portaled dropdown\r\n if (\r\n selectRef.current?.contains(target) ||\r\n dropdownRef.current?.contains(target)\r\n ) {\r\n return;\r\n }\r\n setIsOpen(false);\r\n };\r\n // Use click instead of mousedown to avoid race condition with option onClick\r\n document.addEventListener(\"click\", handleClickOutside, true);\r\n return () =>\r\n document.removeEventListener(\"click\", handleClickOutside, true);\r\n }, [isOpen]);\r\n\r\n const computedBorder = hasError\r\n ? tokens.borders.input.error\r\n : focused || isOpen\r\n ? tokens.borders.input.focus\r\n : tokens.borders.input.default;\r\n\r\n // Background for the outlined label \"notch\"\r\n const labelBg = isTransparentTheme\r\n ? tokens.colors.background.base\r\n : tokens.colors.background.surface;\r\n\r\n // Outlined label font sizes\r\n const outlinedLabelSizeMap = {\r\n small: \"0.625rem\",\r\n medium: \"0.6875rem\",\r\n large: \"0.75rem\",\r\n };\r\n const outlinedLabelLeftMap = {\r\n small: \"2px\",\r\n medium: \"10px\",\r\n large: \"18px\",\r\n };\r\n\r\n // Shared label content\r\n const labelContent = (\r\n <>\r\n {label}\r\n {required && (\r\n <span\r\n style={{ color: tokens.colors.status.critical, marginLeft: \"2px\" }}\r\n >\r\n *\r\n </span>\r\n )}\r\n </>\r\n );\r\n\r\n return (\r\n <div\r\n ref={ref}\r\n className={classNames(\"zendir-select-wrapper\", className)}\r\n style={{\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n gap: tokens.spacing.xs,\r\n width: fullWidth ? \"100%\" : undefined,\r\n }}\r\n >\r\n {/* Label: 'above' variant */}\r\n {label && !isOutlined && (\r\n <span\r\n id={`${generatedId}-label`}\r\n style={{\r\n fontSize: tokens.typography.fontSize.xs,\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n color: tokens.colors.text.secondary,\r\n }}\r\n >\r\n {labelContent}\r\n </span>\r\n )}\r\n\r\n <div ref={selectRef} style={{ position: \"relative\" }}>\r\n {/* Label: 'outlined' variant — positioned on the trigger border */}\r\n {label && isOutlined && (\r\n <span\r\n id={`${generatedId}-label`}\r\n style={{\r\n position: \"absolute\",\r\n top: 0,\r\n left: outlinedLabelLeftMap[size],\r\n transform: \"translateY(-50%)\",\r\n backgroundColor: labelBg,\r\n padding: `0 ${tokens.spacing.xs}`,\r\n fontSize: outlinedLabelSizeMap[size],\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n color:\r\n focused || isOpen\r\n ? hasError\r\n ? tokens.colors.status.critical\r\n : tokens.colors.accent.primary\r\n : hasError\r\n ? tokens.colors.status.critical\r\n : tokens.colors.text.secondary,\r\n zIndex: 1,\r\n pointerEvents: \"none\",\r\n transition: `color ${tokens.animation.fast}`,\r\n lineHeight: 1.2,\r\n whiteSpace: \"nowrap\",\r\n letterSpacing: \"0.01em\",\r\n }}\r\n >\r\n {labelContent}\r\n </span>\r\n )}\r\n\r\n <button\r\n ref={triggerRef}\r\n type=\"button\"\r\n disabled={disabled}\r\n onClick={() => !disabled && setIsOpen(!isOpen)}\r\n onFocus={() => setFocused(true)}\r\n onBlur={() => setFocused(false)}\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={isOpen}\r\n aria-labelledby={label ? `${generatedId}-label` : undefined}\r\n aria-describedby={\r\n helperText || error ? `${generatedId}-hint` : undefined\r\n }\r\n style={{\r\n width: \"100%\",\r\n height: controlHeight,\r\n padding: `0 ${config.padding}`,\r\n fontSize: config.fontSize,\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n color: selectedOption\r\n ? tokens.colors.text.primary\r\n : tokens.colors.text.muted,\r\n backgroundColor: isTransparentTheme\r\n ? \"rgba(10, 15, 25, 0.4)\"\r\n : tokens.colors.background.surface,\r\n border: computedBorder,\r\n borderRadius: tokens.borderRadius.md,\r\n cursor: disabled ? \"not-allowed\" : \"pointer\",\r\n opacity: disabled ? 0.5 : 1,\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"space-between\",\r\n textAlign: \"left\",\r\n outline: \"none\",\r\n transition: `border-color ${tokens.animation.fast}`,\r\n boxShadow:\r\n focused || isOpen ? tokens.borders.focusRing.default : \"none\",\r\n }}\r\n >\r\n <span>{selectedOption?.label ?? placeholder}</span>\r\n <svg\r\n width=\"16\"\r\n height=\"16\"\r\n viewBox=\"0 0 24 24\"\r\n fill={tokens.colors.text.muted}\r\n style={{\r\n transform: isOpen ? \"rotate(180deg)\" : \"rotate(0)\",\r\n transition: `transform ${tokens.animation.fast}`,\r\n }}\r\n >\r\n <path d=\"M7 10l5 5 5-5z\" />\r\n </svg>\r\n </button>\r\n\r\n {isOpen &&\r\n dropdownRect &&\r\n createPortal(\r\n <ul\r\n ref={dropdownRef}\r\n role=\"listbox\"\r\n style={{\r\n position: \"fixed\",\r\n top: dropdownRect.openUp ? undefined : dropdownRect.top,\r\n bottom: dropdownRect.openUp\r\n ? window.innerHeight - dropdownRect.top\r\n : undefined,\r\n left: dropdownRect.left,\r\n width: dropdownRect.width,\r\n padding: tokens.spacing.xs,\r\n backgroundColor: isTransparentTheme\r\n ? `${tokens.colors.accent.primary}1f`\r\n : tokens.colors.background.elevated,\r\n backdropFilter: isTransparentTheme ? \"blur(12px)\" : undefined,\r\n WebkitBackdropFilter: isTransparentTheme\r\n ? \"blur(12px)\"\r\n : undefined,\r\n border: tokens.borders.dropdown,\r\n borderRadius: tokens.borderRadius.md,\r\n boxShadow: `${tokens.shadows.lg}, 0 0 30px ${tokens.colors.accent.primary}10`,\r\n zIndex: 10000,\r\n maxHeight: \"200px\",\r\n overflowY: \"auto\",\r\n listStyle: \"none\",\r\n margin: 0,\r\n animation: `zendir-dropdown ${tokens.animation.duration.normal}ms ${tokens.animation.easing.default} both`,\r\n }}\r\n >\r\n <style>{`@keyframes zendir-dropdown { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } }`}</style>\r\n {options.map((option) => (\r\n <li\r\n key={option.value}\r\n role=\"option\"\r\n aria-selected={option.value === value}\r\n tabIndex={option.disabled ? -1 : 0}\r\n onMouseDown={(e) => {\r\n // Prevent the document click handler from firing before we process this\r\n e.stopPropagation();\r\n }}\r\n onClick={(e) => {\r\n e.stopPropagation();\r\n if (!option.disabled) {\r\n onChange?.(option.value);\r\n setIsOpen(false);\r\n }\r\n }}\r\n onKeyDown={(e) => {\r\n if (option.disabled) return;\r\n if (e.key === \"Enter\" || e.key === \" \") {\r\n e.preventDefault();\r\n onChange?.(option.value);\r\n setIsOpen(false);\r\n }\r\n }}\r\n style={{\r\n padding: `0 ${tokens.spacing.md}`,\r\n height: optionRowHeight,\r\n minHeight: optionRowHeight,\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n fontSize: config.fontSize,\r\n lineHeight: 1.2,\r\n whiteSpace: \"nowrap\",\r\n overflow: \"hidden\",\r\n textOverflow: \"ellipsis\",\r\n color: option.disabled\r\n ? tokens.colors.text.muted\r\n : tokens.colors.text.primary,\r\n backgroundColor:\r\n option.value === value\r\n ? `${tokens.colors.accent.primary}15`\r\n : \"transparent\",\r\n borderRadius: tokens.borderRadius.sm,\r\n cursor: option.disabled ? \"not-allowed\" : \"pointer\",\r\n opacity: option.disabled ? 0.5 : 1,\r\n transition: `background-color ${tokens.animation.fast}`,\r\n }}\r\n onMouseEnter={(e) => {\r\n if (!option.disabled) {\r\n e.currentTarget.style.backgroundColor = `${tokens.colors.accent.primary}10`;\r\n }\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.backgroundColor =\r\n option.value === value\r\n ? `${tokens.colors.accent.primary}15`\r\n : \"transparent\";\r\n }}\r\n >\r\n {option.label}\r\n </li>\r\n ))}\r\n </ul>,\r\n document.body,\r\n )}\r\n </div>\r\n\r\n {(helperText || error) && (\r\n <span\r\n id={`${generatedId}-hint`}\r\n role={hasError ? \"alert\" : undefined}\r\n style={{\r\n fontSize: tokens.typography.fontSize.xs,\r\n color: hasError\r\n ? tokens.colors.status.critical\r\n : tokens.colors.text.muted,\r\n }}\r\n >\r\n {error || helperText}\r\n </span>\r\n )}\r\n </div>\r\n );\r\n }),\r\n);\r\n\r\nexport default Select;\r\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"Toggle.js","names":[],"sources":["../../../../src/react/core/inputs/Toggle.tsx"],"sourcesContent":["/**\n * @zendir/ui - Toggle/Switch Component\n *\n * Toggle switch following Astro UX Design System with Zendir purple accents.\n *\n * Astro UX Compliance:\n * - Binary on/off control\n * - Clear visual states\n * - Accessible keyboard support\n *\n * Zendir Enhancements:\n * - Purple accent when active\n * - Subtle glow effect\n * - Smooth spring animation\n *\n * @example\n * ```tsx\n * <Toggle checked={enabled} onChange={setEnabled} label=\"Enable telemetry\" />\n * ```\n */\n\nimport React, { memo, forwardRef, useId, useState } from \"react\";\nimport { useTheme } from \"../../theme\";\nimport { classNames } from \"../../utils\";\n\nexport interface ToggleProps {\n /** Checked state */\n checked?: boolean;\n /** Change handler */\n onChange?: (checked: boolean) => void;\n /** Label text */\n label?: string;\n /** Label position */\n labelPosition?: \"left\" | \"right\";\n /** Disabled state */\n disabled?: boolean;\n /** Size variant */\n size?: \"small\" | \"medium\";\n /** Custom className */\n className?: string;\n /** Name attribute */\n name?: string;\n}\n\nexport const Toggle = memo(\n forwardRef<HTMLInputElement, ToggleProps>(function Toggle(\n {\n checked = false,\n onChange,\n label,\n labelPosition = \"right\",\n disabled = false,\n size = \"medium\",\n className = \"\",\n name,\n },\n ref,\n ): React.ReactElement {\n const { tokens, theme } = useTheme();\n const [focused, setFocused] = useState(false);\n const [hovered, setHovered] = useState(false);\n const id = useId();\n\n const sizeConfig = {\n small: { width: 34, height: 18, knobSize: 14 },\n medium: { width: 40, height: 22, knobSize: 16 },\n };\n const config = sizeConfig[size];\n // Touch target: 44px minimum via padding around the visual toggle\n const touchPadV = Math.max(0, (44 - config.height) / 2);\n const touchPadH = Math.max(0, (44 - config.width) / 2);\n\n const handleClick = () => {\n if (!disabled) {\n onChange?.(!checked);\n }\n };\n\n const isTransparentTheme =\n theme === \"transparent\" ||\n theme === \"transparent-bold\" ||\n theme === \"transparent-minimal\";\n const transparentDefault = tokens.colors.interactive.transparentDefault;\n const transparentHover = tokens.colors.interactive.transparentHover;\n const bgColor = checked\n ? tokens.colors.accent.primary\n : isTransparentTheme && transparentDefault && transparentHover\n ? hovered && !disabled\n ? transparentHover\n : transparentDefault\n : hovered && !disabled\n ? `${tokens.colors.border.muted}`\n : tokens.colors.border.muted;\n\n const toggle = (\n <div\n role=\"switch\"\n aria-checked={checked}\n aria-disabled={disabled}\n aria-label={!label ? (name ?? \"Toggle\") : undefined}\n tabIndex={disabled ? -1 : 0}\n onClick={handleClick}\n onKeyDown={(e) =>\n (e.key === \" \" || e.key === \"Enter\") &&\n (e.preventDefault(), handleClick())\n }\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n onFocus={() => setFocused(true)}\n onBlur={() => setFocused(false)}\n style={{\n padding: `${touchPadV}px ${touchPadH}px`,\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n flexShrink: 0,\n outline: \"none\",\n display: \"inline-flex\",\n alignItems: \"center\",\n }}\n >\n <div\n style={{\n width: config.width,\n height: config.height,\n backgroundColor: bgColor,\n ...(isTransparentTheme &&\n !checked && {\n backdropFilter: \"blur(8px)\",\n WebkitBackdropFilter: \"blur(8px)\",\n }),\n borderRadius: config.height / 2,\n opacity: disabled ? 0.5 : 1,\n position: \"relative\",\n transition: `${tokens.animation.normal}, opacity ${tokens.animation.duration.normal}ms ${tokens.animation.easing.default}`,\n flexShrink: 0,\n boxShadow: checked\n ? `0 0 10px ${tokens.colors.accent.primary}25, inset 0 1px 1px rgba(255,255,255,0.08)`\n : focused\n ? `0 0 0 2px ${tokens.colors.accent.primary}25`\n : \"inset 0 1px 2px rgba(0,0,0,0.15)\",\n }}\n >\n <div\n style={{\n position: \"absolute\",\n top: (config.height - config.knobSize) / 2,\n left: checked ? config.width - config.knobSize - 3 : 3,\n width: config.knobSize,\n height: config.knobSize,\n backgroundColor: \"#ffffff\",\n borderRadius: \"50%\",\n boxShadow: checked\n ? \"0 1px 4px rgba(0,0,0,0.25)\"\n : \"0 1px 3px rgba(0,0,0,0.2)\",\n transition: tokens.animation.spring,\n transform: hovered && !disabled ? \"scale(1.05)\" : \"scale(1)\",\n }}\n />\n </div>\n <input\n ref={ref}\n type=\"checkbox\"\n id={id}\n name={name}\n checked={checked}\n disabled={disabled}\n onChange={() => {}}\n tabIndex={-1}\n aria-hidden=\"true\"\n style={{\n position: \"absolute\",\n opacity: 0,\n width: 0,\n height: 0,\n pointerEvents: \"none\",\n }}\n />\n </div>\n );\n\n if (!label) {\n return (\n <div className={classNames(\"zendir-toggle\", className)}>{toggle}</div>\n );\n }\n\n return (\n <label\n htmlFor={id}\n className={classNames(\"zendir-toggle\", className)}\n style={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: tokens.spacing.sm,\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n flexDirection: labelPosition === \"left\" ? \"row-reverse\" : \"row\",\n }}\n >\n {toggle}\n <span\n style={{\n fontSize: tokens.typography.fontSize.sm,\n color: disabled\n ? tokens.colors.text.muted\n : tokens.colors.text.primary,\n }}\n >\n {label}\n </span>\n </label>\n );\n }),\n);\n\nexport default Toggle;\n"],"mappings":"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"}
1
+ {"version":3,"file":"Toggle.js","names":[],"sources":["../../../../src/react/core/inputs/Toggle.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - Toggle/Switch Component\r\n *\r\n * Toggle switch following Astro UX Design System with Zendir purple accents.\r\n *\r\n * Astro UX Compliance:\r\n * - Binary on/off control\r\n * - Clear visual states\r\n * - Accessible keyboard support\r\n *\r\n * Zendir Enhancements:\r\n * - Purple accent when active\r\n * - Subtle glow effect\r\n * - Smooth spring animation\r\n *\r\n * @example\r\n * ```tsx\r\n * <Toggle checked={enabled} onChange={setEnabled} label=\"Enable telemetry\" />\r\n * ```\r\n */\r\n\r\nimport React, { memo, forwardRef, useId, useState } from \"react\";\r\nimport { useTheme } from \"../../theme\";\r\nimport { classNames } from \"../../utils\";\r\n\r\nexport interface ToggleProps {\r\n /** Checked state */\r\n checked?: boolean;\r\n /** Change handler */\r\n onChange?: (checked: boolean) => void;\r\n /** Label text */\r\n label?: string;\r\n /** Label position */\r\n labelPosition?: \"left\" | \"right\";\r\n /** Disabled state */\r\n disabled?: boolean;\r\n /** Size variant */\r\n size?: \"small\" | \"medium\";\r\n /** Custom className */\r\n className?: string;\r\n /** Name attribute */\r\n name?: string;\r\n}\r\n\r\nexport const Toggle = memo(\r\n forwardRef<HTMLInputElement, ToggleProps>(function Toggle(\r\n {\r\n checked = false,\r\n onChange,\r\n label,\r\n labelPosition = \"right\",\r\n disabled = false,\r\n size = \"medium\",\r\n className = \"\",\r\n name,\r\n },\r\n ref,\r\n ): React.ReactElement {\r\n const { tokens, theme } = useTheme();\r\n const [focused, setFocused] = useState(false);\r\n const [hovered, setHovered] = useState(false);\r\n const id = useId();\r\n\r\n const sizeConfig = {\r\n small: { width: 34, height: 18, knobSize: 14 },\r\n medium: { width: 40, height: 22, knobSize: 16 },\r\n };\r\n const config = sizeConfig[size];\r\n // Touch target: 44px minimum via padding around the visual toggle\r\n const touchPadV = Math.max(0, (44 - config.height) / 2);\r\n const touchPadH = Math.max(0, (44 - config.width) / 2);\r\n\r\n const handleClick = () => {\r\n if (!disabled) {\r\n onChange?.(!checked);\r\n }\r\n };\r\n\r\n const isTransparentTheme =\r\n theme === \"transparent\" ||\r\n theme === \"transparent-bold\" ||\r\n theme === \"transparent-minimal\";\r\n const transparentDefault = tokens.colors.interactive.transparentDefault;\r\n const transparentHover = tokens.colors.interactive.transparentHover;\r\n const bgColor = checked\r\n ? tokens.colors.accent.primary\r\n : isTransparentTheme && transparentDefault && transparentHover\r\n ? hovered && !disabled\r\n ? transparentHover\r\n : transparentDefault\r\n : hovered && !disabled\r\n ? `${tokens.colors.border.muted}`\r\n : tokens.colors.border.muted;\r\n\r\n const toggle = (\r\n <div\r\n role=\"switch\"\r\n aria-checked={checked}\r\n aria-disabled={disabled}\r\n aria-label={!label ? (name ?? \"Toggle\") : undefined}\r\n tabIndex={disabled ? -1 : 0}\r\n onClick={handleClick}\r\n onKeyDown={(e) =>\r\n (e.key === \" \" || e.key === \"Enter\") &&\r\n (e.preventDefault(), handleClick())\r\n }\r\n onMouseEnter={() => setHovered(true)}\r\n onMouseLeave={() => setHovered(false)}\r\n onFocus={() => setFocused(true)}\r\n onBlur={() => setFocused(false)}\r\n style={{\r\n padding: `${touchPadV}px ${touchPadH}px`,\r\n cursor: disabled ? \"not-allowed\" : \"pointer\",\r\n flexShrink: 0,\r\n outline: \"none\",\r\n display: \"inline-flex\",\r\n alignItems: \"center\",\r\n }}\r\n >\r\n <div\r\n style={{\r\n width: config.width,\r\n height: config.height,\r\n backgroundColor: bgColor,\r\n ...(isTransparentTheme &&\r\n !checked && {\r\n backdropFilter: \"blur(8px)\",\r\n WebkitBackdropFilter: \"blur(8px)\",\r\n }),\r\n borderRadius: config.height / 2,\r\n opacity: disabled ? 0.5 : 1,\r\n position: \"relative\",\r\n transition: `${tokens.animation.normal}, opacity ${tokens.animation.duration.normal}ms ${tokens.animation.easing.default}`,\r\n flexShrink: 0,\r\n boxShadow: checked\r\n ? `0 0 10px ${tokens.colors.accent.primary}25, inset 0 1px 1px rgba(255,255,255,0.08)`\r\n : focused\r\n ? `0 0 0 2px ${tokens.colors.accent.primary}25`\r\n : \"inset 0 1px 2px rgba(0,0,0,0.15)\",\r\n }}\r\n >\r\n <div\r\n style={{\r\n position: \"absolute\",\r\n top: (config.height - config.knobSize) / 2,\r\n left: checked ? config.width - config.knobSize - 3 : 3,\r\n width: config.knobSize,\r\n height: config.knobSize,\r\n backgroundColor: \"#ffffff\",\r\n borderRadius: \"50%\",\r\n boxShadow: checked\r\n ? \"0 1px 4px rgba(0,0,0,0.25)\"\r\n : \"0 1px 3px rgba(0,0,0,0.2)\",\r\n transition: tokens.animation.spring,\r\n transform: hovered && !disabled ? \"scale(1.05)\" : \"scale(1)\",\r\n }}\r\n />\r\n </div>\r\n <input\r\n ref={ref}\r\n type=\"checkbox\"\r\n id={id}\r\n name={name}\r\n checked={checked}\r\n disabled={disabled}\r\n onChange={() => {}}\r\n tabIndex={-1}\r\n aria-hidden=\"true\"\r\n style={{\r\n position: \"absolute\",\r\n opacity: 0,\r\n width: 0,\r\n height: 0,\r\n pointerEvents: \"none\",\r\n }}\r\n />\r\n </div>\r\n );\r\n\r\n if (!label) {\r\n return (\r\n <div className={classNames(\"zendir-toggle\", className)}>{toggle}</div>\r\n );\r\n }\r\n\r\n return (\r\n <label\r\n htmlFor={id}\r\n className={classNames(\"zendir-toggle\", className)}\r\n style={{\r\n display: \"inline-flex\",\r\n alignItems: \"center\",\r\n gap: tokens.spacing.sm,\r\n cursor: disabled ? \"not-allowed\" : \"pointer\",\r\n flexDirection: labelPosition === \"left\" ? \"row-reverse\" : \"row\",\r\n }}\r\n >\r\n {toggle}\r\n <span\r\n style={{\r\n fontSize: tokens.typography.fontSize.sm,\r\n color: disabled\r\n ? tokens.colors.text.muted\r\n : tokens.colors.text.primary,\r\n }}\r\n >\r\n {label}\r\n </span>\r\n </label>\r\n );\r\n }),\r\n);\r\n\r\nexport default Toggle;\r\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"Box.js","names":[],"sources":["../../../../src/react/core/layout/Box.tsx"],"sourcesContent":["/**\n * @zendir/ui - Box Layout Primitive\n *\n * The foundational layout building block. A themed <div> with shorthand props\n * for padding, margin, background, border, and display properties.\n *\n * Follows Astro UX spacing tokens (4px grid system).\n * All spacing props accept SpacingTokens ('xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'),\n * numbers (px), or CSS strings.\n *\n * @example\n * ```tsx\n * <Box padding=\"md\" background=\"surface\">\n * <H3>Section Title</H3>\n * <Body1>Content here</Body1>\n * </Box>\n *\n * <Box padding={['sm', 'md', 'lg']} borderRadius=\"md\">\n * Responsive padding\n * </Box>\n * ```\n */\n\nimport React, { memo, forwardRef } from \"react\";\nimport { useTheme } from \"../../theme\";\nimport { useBreakpoint } from \"./useBreakpoint\";\nimport { resolveResponsiveSpacing, resolveResponsive } from \"./responsive\";\nimport type { ResponsiveValue } from \"./useBreakpoint\";\nimport type { SpacingToken } from \"./responsive\";\n\ntype SpacingInput = SpacingToken | string | number;\n\nexport interface BoxProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Element type to render */\n as?: React.ElementType;\n\n // Spacing\n /** Padding (all sides) */\n padding?: ResponsiveValue<SpacingInput>;\n /** Shorthand alias for padding */\n p?: ResponsiveValue<SpacingInput>;\n /** Padding horizontal (left + right) */\n px?: ResponsiveValue<SpacingInput>;\n /** Padding vertical (top + bottom) */\n py?: ResponsiveValue<SpacingInput>;\n /** Padding top */\n pt?: ResponsiveValue<SpacingInput>;\n /** Padding right */\n pr?: ResponsiveValue<SpacingInput>;\n /** Padding bottom */\n pb?: ResponsiveValue<SpacingInput>;\n /** Padding left */\n pl?: ResponsiveValue<SpacingInput>;\n /** Margin (all sides) */\n margin?: ResponsiveValue<SpacingInput>;\n /** Shorthand alias for margin */\n m?: ResponsiveValue<SpacingInput>;\n /** Margin horizontal (left + right) */\n mx?: ResponsiveValue<SpacingInput>;\n /** Margin vertical (top + bottom) */\n my?: ResponsiveValue<SpacingInput>;\n /** Margin top */\n mt?: ResponsiveValue<SpacingInput>;\n /** Margin right */\n mr?: ResponsiveValue<SpacingInput>;\n /** Margin bottom */\n mb?: ResponsiveValue<SpacingInput>;\n /** Margin left */\n ml?: ResponsiveValue<SpacingInput>;\n\n // Visual\n /** Background color: 'base' | 'surface' | 'elevated' | 'overlay' | custom CSS color */\n background?: string;\n /** Border: true for default border, or custom CSS border */\n border?: boolean | string;\n /** Border radius token or CSS value */\n borderRadius?: \"none\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"full\" | string;\n /** Box shadow token or CSS value */\n shadow?: \"none\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | string;\n\n // Layout\n /** CSS display property */\n display?: ResponsiveValue<string>;\n /** CSS width */\n width?: ResponsiveValue<string | number>;\n /** CSS height */\n height?: ResponsiveValue<string | number>;\n /** CSS min-width */\n minWidth?: string | number;\n /** CSS min-height */\n minHeight?: string | number;\n /** CSS max-width */\n maxWidth?: string | number;\n /** CSS max-height */\n maxHeight?: string | number;\n /** CSS overflow */\n overflow?: string;\n /** CSS position */\n position?: \"static\" | \"relative\" | \"absolute\" | \"fixed\" | \"sticky\";\n /** CSS flex property */\n flex?: string | number;\n /** CSS flex-grow */\n flexGrow?: number;\n /** CSS flex-shrink */\n flexShrink?: number;\n\n /** Children */\n children?: React.ReactNode;\n}\n\nexport const Box = memo(\n forwardRef<HTMLDivElement, BoxProps>(function Box(\n {\n as: Component = \"div\",\n padding,\n p,\n px,\n py,\n pt,\n pr,\n pb,\n pl,\n margin,\n m,\n mx,\n my,\n mt,\n mr,\n mb,\n ml,\n background,\n border,\n borderRadius,\n shadow,\n display,\n width,\n height,\n minWidth,\n minHeight,\n maxWidth,\n maxHeight,\n overflow,\n position,\n flex,\n flexGrow,\n flexShrink,\n style,\n children,\n ...rest\n },\n ref,\n ): React.ReactElement {\n const { tokens } = useTheme();\n const { breakpoint } = useBreakpoint();\n\n const resolve = (val: ResponsiveValue<SpacingInput> | undefined) =>\n resolveResponsiveSpacing(val, breakpoint, tokens);\n\n // Resolve padding (p is alias for padding)\n const paddingAll = resolve(padding ?? p);\n const paddingX = resolve(px);\n const paddingY = resolve(py);\n\n // Resolve margin (m is alias for margin)\n const marginAll = resolve(margin ?? m);\n const marginX = resolve(mx);\n const marginY = resolve(my);\n\n // Background color resolution\n const resolveBackground = (bg: string | undefined): string | undefined => {\n if (!bg) return undefined;\n const bgMap: Record<string, string> = {\n base: tokens.colors.background.base,\n surface: tokens.colors.background.surface,\n elevated: tokens.colors.background.elevated,\n overlay: tokens.colors.background.overlay,\n };\n return bgMap[bg] || bg;\n };\n\n // Border resolution\n const resolveBorder = (\n b: boolean | string | undefined,\n ): string | undefined => {\n if (!b) return undefined;\n if (b === true) return `1px solid ${tokens.colors.border.default}`;\n return b;\n };\n\n // Border radius resolution\n const resolveBorderRadius = (\n br: string | undefined,\n ): string | undefined => {\n if (!br) return undefined;\n if (br in tokens.borderRadius)\n return tokens.borderRadius[br as keyof typeof tokens.borderRadius];\n return br;\n };\n\n // Shadow resolution\n const resolveShadow = (s: string | undefined): string | undefined => {\n if (!s) return undefined;\n if (s in tokens.shadows && s !== \"glow\")\n return tokens.shadows[s as keyof Omit<typeof tokens.shadows, \"glow\">];\n return s;\n };\n\n // Size resolution (number → px)\n const resolveSize = (\n val: string | number | undefined,\n ): string | undefined => {\n if (val === undefined) return undefined;\n if (typeof val === \"number\") return `${val}px`;\n return val;\n };\n\n const computedStyle: React.CSSProperties = {\n // Padding\n ...(paddingAll ? { padding: paddingAll } : {}),\n ...(paddingX ? { paddingLeft: paddingX, paddingRight: paddingX } : {}),\n ...(paddingY ? { paddingTop: paddingY, paddingBottom: paddingY } : {}),\n ...(resolve(pt) ? { paddingTop: resolve(pt) } : {}),\n ...(resolve(pr) ? { paddingRight: resolve(pr) } : {}),\n ...(resolve(pb) ? { paddingBottom: resolve(pb) } : {}),\n ...(resolve(pl) ? { paddingLeft: resolve(pl) } : {}),\n\n // Margin\n ...(marginAll ? { margin: marginAll } : {}),\n ...(marginX ? { marginLeft: marginX, marginRight: marginX } : {}),\n ...(marginY ? { marginTop: marginY, marginBottom: marginY } : {}),\n ...(resolve(mt) ? { marginTop: resolve(mt) } : {}),\n ...(resolve(mr) ? { marginRight: resolve(mr) } : {}),\n ...(resolve(mb) ? { marginBottom: resolve(mb) } : {}),\n ...(resolve(ml) ? { marginLeft: resolve(ml) } : {}),\n\n // Visual\n backgroundColor: resolveBackground(background),\n border: resolveBorder(border),\n borderRadius: resolveBorderRadius(borderRadius),\n boxShadow: resolveShadow(shadow),\n\n // Layout\n display: resolveResponsive(display, breakpoint),\n width: resolveSize(resolveResponsive(width, breakpoint)),\n height: resolveSize(resolveResponsive(height, breakpoint)),\n minWidth: resolveSize(minWidth),\n minHeight: resolveSize(minHeight),\n maxWidth: resolveSize(maxWidth),\n maxHeight: resolveSize(maxHeight),\n overflow,\n position,\n flex,\n flexGrow,\n flexShrink,\n\n // User overrides\n ...style,\n };\n\n return (\n <Component ref={ref} style={computedStyle} {...rest}>\n {children}\n </Component>\n );\n }),\n);\n\nexport default Box;\n"],"mappings":"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"}
1
+ {"version":3,"file":"Box.js","names":[],"sources":["../../../../src/react/core/layout/Box.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - Box Layout Primitive\r\n *\r\n * The foundational layout building block. A themed <div> with shorthand props\r\n * for padding, margin, background, border, and display properties.\r\n *\r\n * Follows Astro UX spacing tokens (4px grid system).\r\n * All spacing props accept SpacingTokens ('xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'),\r\n * numbers (px), or CSS strings.\r\n *\r\n * @example\r\n * ```tsx\r\n * <Box padding=\"md\" background=\"surface\">\r\n * <H3>Section Title</H3>\r\n * <Body1>Content here</Body1>\r\n * </Box>\r\n *\r\n * <Box padding={['sm', 'md', 'lg']} borderRadius=\"md\">\r\n * Responsive padding\r\n * </Box>\r\n * ```\r\n */\r\n\r\nimport React, { memo, forwardRef } from \"react\";\r\nimport { useTheme } from \"../../theme\";\r\nimport { useBreakpoint } from \"./useBreakpoint\";\r\nimport { resolveResponsiveSpacing, resolveResponsive } from \"./responsive\";\r\nimport type { ResponsiveValue } from \"./useBreakpoint\";\r\nimport type { SpacingToken } from \"./responsive\";\r\n\r\ntype SpacingInput = SpacingToken | string | number;\r\n\r\nexport interface BoxProps extends React.HTMLAttributes<HTMLDivElement> {\r\n /** Element type to render */\r\n as?: React.ElementType;\r\n\r\n // Spacing\r\n /** Padding (all sides) */\r\n padding?: ResponsiveValue<SpacingInput>;\r\n /** Shorthand alias for padding */\r\n p?: ResponsiveValue<SpacingInput>;\r\n /** Padding horizontal (left + right) */\r\n px?: ResponsiveValue<SpacingInput>;\r\n /** Padding vertical (top + bottom) */\r\n py?: ResponsiveValue<SpacingInput>;\r\n /** Padding top */\r\n pt?: ResponsiveValue<SpacingInput>;\r\n /** Padding right */\r\n pr?: ResponsiveValue<SpacingInput>;\r\n /** Padding bottom */\r\n pb?: ResponsiveValue<SpacingInput>;\r\n /** Padding left */\r\n pl?: ResponsiveValue<SpacingInput>;\r\n /** Margin (all sides) */\r\n margin?: ResponsiveValue<SpacingInput>;\r\n /** Shorthand alias for margin */\r\n m?: ResponsiveValue<SpacingInput>;\r\n /** Margin horizontal (left + right) */\r\n mx?: ResponsiveValue<SpacingInput>;\r\n /** Margin vertical (top + bottom) */\r\n my?: ResponsiveValue<SpacingInput>;\r\n /** Margin top */\r\n mt?: ResponsiveValue<SpacingInput>;\r\n /** Margin right */\r\n mr?: ResponsiveValue<SpacingInput>;\r\n /** Margin bottom */\r\n mb?: ResponsiveValue<SpacingInput>;\r\n /** Margin left */\r\n ml?: ResponsiveValue<SpacingInput>;\r\n\r\n // Visual\r\n /** Background color: 'base' | 'surface' | 'elevated' | 'overlay' | custom CSS color */\r\n background?: string;\r\n /** Border: true for default border, or custom CSS border */\r\n border?: boolean | string;\r\n /** Border radius token or CSS value */\r\n borderRadius?: \"none\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"full\" | string;\r\n /** Box shadow token or CSS value */\r\n shadow?: \"none\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | string;\r\n\r\n // Layout\r\n /** CSS display property */\r\n display?: ResponsiveValue<string>;\r\n /** CSS width */\r\n width?: ResponsiveValue<string | number>;\r\n /** CSS height */\r\n height?: ResponsiveValue<string | number>;\r\n /** CSS min-width */\r\n minWidth?: string | number;\r\n /** CSS min-height */\r\n minHeight?: string | number;\r\n /** CSS max-width */\r\n maxWidth?: string | number;\r\n /** CSS max-height */\r\n maxHeight?: string | number;\r\n /** CSS overflow */\r\n overflow?: string;\r\n /** CSS position */\r\n position?: \"static\" | \"relative\" | \"absolute\" | \"fixed\" | \"sticky\";\r\n /** CSS flex property */\r\n flex?: string | number;\r\n /** CSS flex-grow */\r\n flexGrow?: number;\r\n /** CSS flex-shrink */\r\n flexShrink?: number;\r\n\r\n /** Children */\r\n children?: React.ReactNode;\r\n}\r\n\r\nexport const Box = memo(\r\n forwardRef<HTMLDivElement, BoxProps>(function Box(\r\n {\r\n as: Component = \"div\",\r\n padding,\r\n p,\r\n px,\r\n py,\r\n pt,\r\n pr,\r\n pb,\r\n pl,\r\n margin,\r\n m,\r\n mx,\r\n my,\r\n mt,\r\n mr,\r\n mb,\r\n ml,\r\n background,\r\n border,\r\n borderRadius,\r\n shadow,\r\n display,\r\n width,\r\n height,\r\n minWidth,\r\n minHeight,\r\n maxWidth,\r\n maxHeight,\r\n overflow,\r\n position,\r\n flex,\r\n flexGrow,\r\n flexShrink,\r\n style,\r\n children,\r\n ...rest\r\n },\r\n ref,\r\n ): React.ReactElement {\r\n const { tokens } = useTheme();\r\n const { breakpoint } = useBreakpoint();\r\n\r\n const resolve = (val: ResponsiveValue<SpacingInput> | undefined) =>\r\n resolveResponsiveSpacing(val, breakpoint, tokens);\r\n\r\n // Resolve padding (p is alias for padding)\r\n const paddingAll = resolve(padding ?? p);\r\n const paddingX = resolve(px);\r\n const paddingY = resolve(py);\r\n\r\n // Resolve margin (m is alias for margin)\r\n const marginAll = resolve(margin ?? m);\r\n const marginX = resolve(mx);\r\n const marginY = resolve(my);\r\n\r\n // Background color resolution\r\n const resolveBackground = (bg: string | undefined): string | undefined => {\r\n if (!bg) return undefined;\r\n const bgMap: Record<string, string> = {\r\n base: tokens.colors.background.base,\r\n surface: tokens.colors.background.surface,\r\n elevated: tokens.colors.background.elevated,\r\n overlay: tokens.colors.background.overlay,\r\n };\r\n return bgMap[bg] || bg;\r\n };\r\n\r\n // Border resolution\r\n const resolveBorder = (\r\n b: boolean | string | undefined,\r\n ): string | undefined => {\r\n if (!b) return undefined;\r\n if (b === true) return `1px solid ${tokens.colors.border.default}`;\r\n return b;\r\n };\r\n\r\n // Border radius resolution\r\n const resolveBorderRadius = (\r\n br: string | undefined,\r\n ): string | undefined => {\r\n if (!br) return undefined;\r\n if (br in tokens.borderRadius)\r\n return tokens.borderRadius[br as keyof typeof tokens.borderRadius];\r\n return br;\r\n };\r\n\r\n // Shadow resolution\r\n const resolveShadow = (s: string | undefined): string | undefined => {\r\n if (!s) return undefined;\r\n if (s in tokens.shadows && s !== \"glow\")\r\n return tokens.shadows[s as keyof Omit<typeof tokens.shadows, \"glow\">];\r\n return s;\r\n };\r\n\r\n // Size resolution (number → px)\r\n const resolveSize = (\r\n val: string | number | undefined,\r\n ): string | undefined => {\r\n if (val === undefined) return undefined;\r\n if (typeof val === \"number\") return `${val}px`;\r\n return val;\r\n };\r\n\r\n const computedStyle: React.CSSProperties = {\r\n // Padding\r\n ...(paddingAll ? { padding: paddingAll } : {}),\r\n ...(paddingX ? { paddingLeft: paddingX, paddingRight: paddingX } : {}),\r\n ...(paddingY ? { paddingTop: paddingY, paddingBottom: paddingY } : {}),\r\n ...(resolve(pt) ? { paddingTop: resolve(pt) } : {}),\r\n ...(resolve(pr) ? { paddingRight: resolve(pr) } : {}),\r\n ...(resolve(pb) ? { paddingBottom: resolve(pb) } : {}),\r\n ...(resolve(pl) ? { paddingLeft: resolve(pl) } : {}),\r\n\r\n // Margin\r\n ...(marginAll ? { margin: marginAll } : {}),\r\n ...(marginX ? { marginLeft: marginX, marginRight: marginX } : {}),\r\n ...(marginY ? { marginTop: marginY, marginBottom: marginY } : {}),\r\n ...(resolve(mt) ? { marginTop: resolve(mt) } : {}),\r\n ...(resolve(mr) ? { marginRight: resolve(mr) } : {}),\r\n ...(resolve(mb) ? { marginBottom: resolve(mb) } : {}),\r\n ...(resolve(ml) ? { marginLeft: resolve(ml) } : {}),\r\n\r\n // Visual\r\n backgroundColor: resolveBackground(background),\r\n border: resolveBorder(border),\r\n borderRadius: resolveBorderRadius(borderRadius),\r\n boxShadow: resolveShadow(shadow),\r\n\r\n // Layout\r\n display: resolveResponsive(display, breakpoint),\r\n width: resolveSize(resolveResponsive(width, breakpoint)),\r\n height: resolveSize(resolveResponsive(height, breakpoint)),\r\n minWidth: resolveSize(minWidth),\r\n minHeight: resolveSize(minHeight),\r\n maxWidth: resolveSize(maxWidth),\r\n maxHeight: resolveSize(maxHeight),\r\n overflow,\r\n position,\r\n flex,\r\n flexGrow,\r\n flexShrink,\r\n\r\n // User overrides\r\n ...style,\r\n };\r\n\r\n return (\r\n <Component ref={ref} style={computedStyle} {...rest}>\r\n {children}\r\n </Component>\r\n );\r\n }),\r\n);\r\n\r\nexport default Box;\r\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"Center.js","names":[],"sources":["../../../../src/react/core/layout/Center.tsx"],"sourcesContent":["/**\n * @zendir/ui - Center Layout Primitive\n *\n * Centers content horizontally and vertically using flexbox.\n * Common in operator dashboards for loading states, empty states, and hero sections.\n *\n * @example\n * ```tsx\n * <Center height=\"100vh\">\n * <VStack spacing=\"md\" align=\"center\">\n * <Icon name=\"satellite\" size={64} />\n * <H2>No Telemetry Data</H2>\n * <Body2>Connect to a spacecraft to begin monitoring.</Body2>\n * <Button>Connect</Button>\n * </VStack>\n * </Center>\n * ```\n */\n\nimport React, { memo, forwardRef } from \"react\";\n\nexport interface CenterProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Element type to render */\n as?: React.ElementType;\n /** Width (CSS value or number for px) */\n width?: string | number;\n /** Height (CSS value or number for px) */\n height?: string | number;\n /** Max width constraint */\n maxWidth?: string | number;\n /** Also center text */\n textAlign?: boolean;\n /** Inline-flex instead of flex */\n inline?: boolean;\n /** Children */\n children?: React.ReactNode;\n}\n\nexport const Center = memo(\n forwardRef<HTMLDivElement, CenterProps>(function Center(\n {\n as: Component = \"div\",\n width,\n height,\n maxWidth,\n textAlign = false,\n inline = false,\n style,\n children,\n ...rest\n },\n ref,\n ): React.ReactElement {\n const resolveSize = (\n val: string | number | undefined,\n ): string | undefined => {\n if (val === undefined) return undefined;\n if (typeof val === \"number\") return `${val}px`;\n return val;\n };\n\n const computedStyle: React.CSSProperties = {\n display: inline ? \"inline-flex\" : \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: resolveSize(width),\n height: resolveSize(height),\n maxWidth: resolveSize(maxWidth),\n textAlign: textAlign ? \"center\" : undefined,\n ...style,\n };\n\n return (\n <Component ref={ref} style={computedStyle} {...rest}>\n {children}\n </Component>\n );\n }),\n);\n\nexport default Center;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsCA,IAAa,SAAS,KACpB,WAAwC,SAAS,OAC/C,EACE,IAAI,YAAY,OAChB,OACA,QACA,UACA,YAAY,OACZ,SAAS,OACT,OACA,UACA,GAAG,QAEL,KACoB;CACpB,MAAM,eACJ,QACuB;EACvB,IAAI,QAAQ,KAAA,GAAW,OAAO,KAAA;EAC9B,IAAI,OAAO,QAAQ,UAAU,OAAO,GAAG,IAAI;EAC3C,OAAO;CACT;CAaA,OACE,oBAAC,WAAD;EAAgB;EAAK,OAAO;GAX5B,SAAS,SAAS,gBAAgB;GAClC,YAAY;GACZ,gBAAgB;GAChB,OAAO,YAAY,KAAK;GACxB,QAAQ,YAAY,MAAM;GAC1B,UAAU,YAAY,QAAQ;GAC9B,WAAW,YAAY,WAAW,KAAA;GAClC,GAAG;EAIyB;EAAe,GAAI;EAC5C;CACQ,CAAA;AAEf,CAAC,CACH"}
1
+ {"version":3,"file":"Center.js","names":[],"sources":["../../../../src/react/core/layout/Center.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - Center Layout Primitive\r\n *\r\n * Centers content horizontally and vertically using flexbox.\r\n * Common in operator dashboards for loading states, empty states, and hero sections.\r\n *\r\n * @example\r\n * ```tsx\r\n * <Center height=\"100vh\">\r\n * <VStack spacing=\"md\" align=\"center\">\r\n * <Icon name=\"satellite\" size={64} />\r\n * <H2>No Telemetry Data</H2>\r\n * <Body2>Connect to a spacecraft to begin monitoring.</Body2>\r\n * <Button>Connect</Button>\r\n * </VStack>\r\n * </Center>\r\n * ```\r\n */\r\n\r\nimport React, { memo, forwardRef } from \"react\";\r\n\r\nexport interface CenterProps extends React.HTMLAttributes<HTMLDivElement> {\r\n /** Element type to render */\r\n as?: React.ElementType;\r\n /** Width (CSS value or number for px) */\r\n width?: string | number;\r\n /** Height (CSS value or number for px) */\r\n height?: string | number;\r\n /** Max width constraint */\r\n maxWidth?: string | number;\r\n /** Also center text */\r\n textAlign?: boolean;\r\n /** Inline-flex instead of flex */\r\n inline?: boolean;\r\n /** Children */\r\n children?: React.ReactNode;\r\n}\r\n\r\nexport const Center = memo(\r\n forwardRef<HTMLDivElement, CenterProps>(function Center(\r\n {\r\n as: Component = \"div\",\r\n width,\r\n height,\r\n maxWidth,\r\n textAlign = false,\r\n inline = false,\r\n style,\r\n children,\r\n ...rest\r\n },\r\n ref,\r\n ): React.ReactElement {\r\n const resolveSize = (\r\n val: string | number | undefined,\r\n ): string | undefined => {\r\n if (val === undefined) return undefined;\r\n if (typeof val === \"number\") return `${val}px`;\r\n return val;\r\n };\r\n\r\n const computedStyle: React.CSSProperties = {\r\n display: inline ? \"inline-flex\" : \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n width: resolveSize(width),\r\n height: resolveSize(height),\r\n maxWidth: resolveSize(maxWidth),\r\n textAlign: textAlign ? \"center\" : undefined,\r\n ...style,\r\n };\r\n\r\n return (\r\n <Component ref={ref} style={computedStyle} {...rest}>\r\n {children}\r\n </Component>\r\n );\r\n }),\r\n);\r\n\r\nexport default Center;\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsCA,IAAa,SAAS,KACpB,WAAwC,SAAS,OAC/C,EACE,IAAI,YAAY,OAChB,OACA,QACA,UACA,YAAY,OACZ,SAAS,OACT,OACA,UACA,GAAG,QAEL,KACoB;CACpB,MAAM,eACJ,QACuB;EACvB,IAAI,QAAQ,KAAA,GAAW,OAAO,KAAA;EAC9B,IAAI,OAAO,QAAQ,UAAU,OAAO,GAAG,IAAI;EAC3C,OAAO;CACT;CAaA,OACE,oBAAC,WAAD;EAAgB;EAAK,OAAO;GAX5B,SAAS,SAAS,gBAAgB;GAClC,YAAY;GACZ,gBAAgB;GAChB,OAAO,YAAY,KAAK;GACxB,QAAQ,YAAY,MAAM;GAC1B,UAAU,YAAY,QAAQ;GAC9B,WAAW,YAAY,WAAW,KAAA;GAClC,GAAG;EAIyB;EAAe,GAAI;EAC5C;CACQ,CAAA;AAEf,CAAC,CACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"Divider.js","names":[],"sources":["../../../../src/react/core/layout/Divider.tsx"],"sourcesContent":["/**\n * @zendir/ui - Divider\n *\n * Visual separator line following Astro UX Design System.\n * Theme-aware with muted border colors. Supports horizontal and vertical orientation.\n *\n * @example\n * ```tsx\n * <VStack spacing=\"md\">\n * <Section1 />\n * <Divider />\n * <Section2 />\n * </VStack>\n *\n * <HStack spacing=\"md\">\n * <Panel1 />\n * <Divider orientation=\"vertical\" />\n * <Panel2 />\n * </HStack>\n *\n * <Divider label=\"OR\" />\n * ```\n */\n\nimport React, { memo } from \"react\";\nimport { useTheme } from \"../../theme\";\n\nexport interface DividerProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Divider orientation */\n orientation?: \"horizontal\" | \"vertical\";\n /** Optional centered label text */\n label?: string;\n /** Spacing around the divider (CSS value) */\n spacing?: string;\n /** Color override */\n color?: string;\n /** Dashed style */\n dashed?: boolean;\n}\n\nexport const Divider = memo(function Divider({\n orientation = \"horizontal\",\n label,\n spacing,\n color,\n dashed = false,\n style,\n ...rest\n}: DividerProps): React.ReactElement {\n const { tokens } = useTheme();\n\n const lineColor = color || tokens.colors.border.muted;\n const borderStyle = dashed ? \"dashed\" : \"solid\";\n\n if (orientation === \"vertical\") {\n return (\n <div\n role=\"separator\"\n aria-orientation=\"vertical\"\n style={{\n width: \"1px\",\n alignSelf: \"stretch\",\n backgroundColor: lineColor,\n flexShrink: 0,\n ...(spacing ? { margin: `0 ${spacing}` } : {}),\n ...(dashed\n ? {\n backgroundColor: \"transparent\",\n borderLeft: `1px ${borderStyle} ${lineColor}`,\n }\n : {}),\n ...style,\n }}\n {...rest}\n />\n );\n }\n\n // Horizontal with optional label\n if (label) {\n return (\n <div\n role=\"separator\"\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: tokens.spacing.sm,\n ...(spacing ? { margin: `${spacing} 0` } : {}),\n ...style,\n }}\n {...rest}\n >\n <div\n style={{\n flex: 1,\n height: \"1px\",\n backgroundColor: lineColor,\n ...(dashed\n ? {\n backgroundColor: \"transparent\",\n borderTop: `1px ${borderStyle} ${lineColor}`,\n }\n : {}),\n }}\n />\n <span\n style={{\n fontSize: tokens.typography.fontSize.xs,\n fontWeight: tokens.typography.fontWeight.medium,\n color: tokens.colors.text.tertiary,\n fontFamily: tokens.typography.fontFamily.primary,\n letterSpacing: tokens.typography.letterSpacing.wide,\n textTransform: \"uppercase\",\n flexShrink: 0,\n padding: `0 ${tokens.spacing.xs}`,\n }}\n >\n {label}\n </span>\n <div\n style={{\n flex: 1,\n height: \"1px\",\n backgroundColor: lineColor,\n ...(dashed\n ? {\n backgroundColor: \"transparent\",\n borderTop: `1px ${borderStyle} ${lineColor}`,\n }\n : {}),\n }}\n />\n </div>\n );\n }\n\n return (\n <div\n role=\"separator\"\n aria-orientation=\"horizontal\"\n style={{\n width: \"100%\",\n height: \"1px\",\n backgroundColor: lineColor,\n flexShrink: 0,\n ...(spacing ? { margin: `${spacing} 0` } : {}),\n ...(dashed\n ? {\n backgroundColor: \"transparent\",\n borderTop: `1px ${borderStyle} ${lineColor}`,\n }\n : {}),\n ...style,\n }}\n {...rest}\n />\n );\n});\n\nexport default Divider;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,IAAa,UAAU,KAAK,SAAS,QAAQ,EAC3C,cAAc,cACd,OACA,SACA,OACA,SAAS,OACT,OACA,GAAG,QACgC;CACnC,MAAM,EAAE,WAAW,SAAS;CAE5B,MAAM,YAAY,SAAS,OAAO,OAAO,OAAO;CAChD,MAAM,cAAc,SAAS,WAAW;CAExC,IAAI,gBAAgB,YAClB,OACE,oBAAC,OAAD;EACE,MAAK;EACL,oBAAiB;EACjB,OAAO;GACL,OAAO;GACP,WAAW;GACX,iBAAiB;GACjB,YAAY;GACZ,GAAI,UAAU,EAAE,QAAQ,KAAK,UAAU,IAAI,CAAC;GAC5C,GAAI,SACA;IACE,iBAAiB;IACjB,YAAY,OAAO,YAAY,GAAG;GACpC,IACA,CAAC;GACL,GAAG;EACL;EACA,GAAI;CACL,CAAA;CAKL,IAAI,OACF,OACE,qBAAC,OAAD;EACE,MAAK;EACL,OAAO;GACL,SAAS;GACT,YAAY;GACZ,KAAK,OAAO,QAAQ;GACpB,GAAI,UAAU,EAAE,QAAQ,GAAG,QAAQ,IAAI,IAAI,CAAC;GAC5C,GAAG;EACL;EACA,GAAI;YATN;GAWE,oBAAC,OAAD,EACE,OAAO;IACL,MAAM;IACN,QAAQ;IACR,iBAAiB;IACjB,GAAI,SACA;KACE,iBAAiB;KACjB,WAAW,OAAO,YAAY,GAAG;IACnC,IACA,CAAC;GACP,EACD,CAAA;GACD,oBAAC,QAAD;IACE,OAAO;KACL,UAAU,OAAO,WAAW,SAAS;KACrC,YAAY,OAAO,WAAW,WAAW;KACzC,OAAO,OAAO,OAAO,KAAK;KAC1B,YAAY,OAAO,WAAW,WAAW;KACzC,eAAe,OAAO,WAAW,cAAc;KAC/C,eAAe;KACf,YAAY;KACZ,SAAS,KAAK,OAAO,QAAQ;IAC/B;cAEC;GACG,CAAA;GACN,oBAAC,OAAD,EACE,OAAO;IACL,MAAM;IACN,QAAQ;IACR,iBAAiB;IACjB,GAAI,SACA;KACE,iBAAiB;KACjB,WAAW,OAAO,YAAY,GAAG;IACnC,IACA,CAAC;GACP,EACD,CAAA;EACE;;CAIT,OACE,oBAAC,OAAD;EACE,MAAK;EACL,oBAAiB;EACjB,OAAO;GACL,OAAO;GACP,QAAQ;GACR,iBAAiB;GACjB,YAAY;GACZ,GAAI,UAAU,EAAE,QAAQ,GAAG,QAAQ,IAAI,IAAI,CAAC;GAC5C,GAAI,SACA;IACE,iBAAiB;IACjB,WAAW,OAAO,YAAY,GAAG;GACnC,IACA,CAAC;GACL,GAAG;EACL;EACA,GAAI;CACL,CAAA;AAEL,CAAC"}
1
+ {"version":3,"file":"Divider.js","names":[],"sources":["../../../../src/react/core/layout/Divider.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - Divider\r\n *\r\n * Visual separator line following Astro UX Design System.\r\n * Theme-aware with muted border colors. Supports horizontal and vertical orientation.\r\n *\r\n * @example\r\n * ```tsx\r\n * <VStack spacing=\"md\">\r\n * <Section1 />\r\n * <Divider />\r\n * <Section2 />\r\n * </VStack>\r\n *\r\n * <HStack spacing=\"md\">\r\n * <Panel1 />\r\n * <Divider orientation=\"vertical\" />\r\n * <Panel2 />\r\n * </HStack>\r\n *\r\n * <Divider label=\"OR\" />\r\n * ```\r\n */\r\n\r\nimport React, { memo } from \"react\";\r\nimport { useTheme } from \"../../theme\";\r\n\r\nexport interface DividerProps extends React.HTMLAttributes<HTMLDivElement> {\r\n /** Divider orientation */\r\n orientation?: \"horizontal\" | \"vertical\";\r\n /** Optional centered label text */\r\n label?: string;\r\n /** Spacing around the divider (CSS value) */\r\n spacing?: string;\r\n /** Color override */\r\n color?: string;\r\n /** Dashed style */\r\n dashed?: boolean;\r\n}\r\n\r\nexport const Divider = memo(function Divider({\r\n orientation = \"horizontal\",\r\n label,\r\n spacing,\r\n color,\r\n dashed = false,\r\n style,\r\n ...rest\r\n}: DividerProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n\r\n const lineColor = color || tokens.colors.border.muted;\r\n const borderStyle = dashed ? \"dashed\" : \"solid\";\r\n\r\n if (orientation === \"vertical\") {\r\n return (\r\n <div\r\n role=\"separator\"\r\n aria-orientation=\"vertical\"\r\n style={{\r\n width: \"1px\",\r\n alignSelf: \"stretch\",\r\n backgroundColor: lineColor,\r\n flexShrink: 0,\r\n ...(spacing ? { margin: `0 ${spacing}` } : {}),\r\n ...(dashed\r\n ? {\r\n backgroundColor: \"transparent\",\r\n borderLeft: `1px ${borderStyle} ${lineColor}`,\r\n }\r\n : {}),\r\n ...style,\r\n }}\r\n {...rest}\r\n />\r\n );\r\n }\r\n\r\n // Horizontal with optional label\r\n if (label) {\r\n return (\r\n <div\r\n role=\"separator\"\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: tokens.spacing.sm,\r\n ...(spacing ? { margin: `${spacing} 0` } : {}),\r\n ...style,\r\n }}\r\n {...rest}\r\n >\r\n <div\r\n style={{\r\n flex: 1,\r\n height: \"1px\",\r\n backgroundColor: lineColor,\r\n ...(dashed\r\n ? {\r\n backgroundColor: \"transparent\",\r\n borderTop: `1px ${borderStyle} ${lineColor}`,\r\n }\r\n : {}),\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: tokens.colors.text.tertiary,\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n letterSpacing: tokens.typography.letterSpacing.wide,\r\n textTransform: \"uppercase\",\r\n flexShrink: 0,\r\n padding: `0 ${tokens.spacing.xs}`,\r\n }}\r\n >\r\n {label}\r\n </span>\r\n <div\r\n style={{\r\n flex: 1,\r\n height: \"1px\",\r\n backgroundColor: lineColor,\r\n ...(dashed\r\n ? {\r\n backgroundColor: \"transparent\",\r\n borderTop: `1px ${borderStyle} ${lineColor}`,\r\n }\r\n : {}),\r\n }}\r\n />\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <div\r\n role=\"separator\"\r\n aria-orientation=\"horizontal\"\r\n style={{\r\n width: \"100%\",\r\n height: \"1px\",\r\n backgroundColor: lineColor,\r\n flexShrink: 0,\r\n ...(spacing ? { margin: `${spacing} 0` } : {}),\r\n ...(dashed\r\n ? {\r\n backgroundColor: \"transparent\",\r\n borderTop: `1px ${borderStyle} ${lineColor}`,\r\n }\r\n : {}),\r\n ...style,\r\n }}\r\n {...rest}\r\n />\r\n );\r\n});\r\n\r\nexport default Divider;\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,IAAa,UAAU,KAAK,SAAS,QAAQ,EAC3C,cAAc,cACd,OACA,SACA,OACA,SAAS,OACT,OACA,GAAG,QACgC;CACnC,MAAM,EAAE,WAAW,SAAS;CAE5B,MAAM,YAAY,SAAS,OAAO,OAAO,OAAO;CAChD,MAAM,cAAc,SAAS,WAAW;CAExC,IAAI,gBAAgB,YAClB,OACE,oBAAC,OAAD;EACE,MAAK;EACL,oBAAiB;EACjB,OAAO;GACL,OAAO;GACP,WAAW;GACX,iBAAiB;GACjB,YAAY;GACZ,GAAI,UAAU,EAAE,QAAQ,KAAK,UAAU,IAAI,CAAC;GAC5C,GAAI,SACA;IACE,iBAAiB;IACjB,YAAY,OAAO,YAAY,GAAG;GACpC,IACA,CAAC;GACL,GAAG;EACL;EACA,GAAI;CACL,CAAA;CAKL,IAAI,OACF,OACE,qBAAC,OAAD;EACE,MAAK;EACL,OAAO;GACL,SAAS;GACT,YAAY;GACZ,KAAK,OAAO,QAAQ;GACpB,GAAI,UAAU,EAAE,QAAQ,GAAG,QAAQ,IAAI,IAAI,CAAC;GAC5C,GAAG;EACL;EACA,GAAI;YATN;GAWE,oBAAC,OAAD,EACE,OAAO;IACL,MAAM;IACN,QAAQ;IACR,iBAAiB;IACjB,GAAI,SACA;KACE,iBAAiB;KACjB,WAAW,OAAO,YAAY,GAAG;IACnC,IACA,CAAC;GACP,EACD,CAAA;GACD,oBAAC,QAAD;IACE,OAAO;KACL,UAAU,OAAO,WAAW,SAAS;KACrC,YAAY,OAAO,WAAW,WAAW;KACzC,OAAO,OAAO,OAAO,KAAK;KAC1B,YAAY,OAAO,WAAW,WAAW;KACzC,eAAe,OAAO,WAAW,cAAc;KAC/C,eAAe;KACf,YAAY;KACZ,SAAS,KAAK,OAAO,QAAQ;IAC/B;cAEC;GACG,CAAA;GACN,oBAAC,OAAD,EACE,OAAO;IACL,MAAM;IACN,QAAQ;IACR,iBAAiB;IACjB,GAAI,SACA;KACE,iBAAiB;KACjB,WAAW,OAAO,YAAY,GAAG;IACnC,IACA,CAAC;GACP,EACD,CAAA;EACE;;CAIT,OACE,oBAAC,OAAD;EACE,MAAK;EACL,oBAAiB;EACjB,OAAO;GACL,OAAO;GACP,QAAQ;GACR,iBAAiB;GACjB,YAAY;GACZ,GAAI,UAAU,EAAE,QAAQ,GAAG,QAAQ,IAAI,IAAI,CAAC;GAC5C,GAAI,SACA;IACE,iBAAiB;IACjB,WAAW,OAAO,YAAY,GAAG;GACnC,IACA,CAAC;GACL,GAAG;EACL;EACA,GAAI;CACL,CAAA;AAEL,CAAC"}