@zendir/ui 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @zendir/ui
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add a `stat` variant to `DataValue` for compact top-bar telemetry chips
8
+ (value over a small uppercase label, status-tinted).
9
+ - Refresh `SideNav` styling and layout.
10
+
11
+ ### Patch Changes
12
+
13
+ - Harden Popover first-open positioning: seed the panel's position state as
14
+ `fixed` and gate visibility on a `positioned` flag that resets on close, so
15
+ neither the first open nor any reopen flashes at stale coordinates.
16
+
3
17
  ## 0.4.1
4
18
 
5
19
  ### Patch Changes
@@ -3,7 +3,7 @@ import { AstroIconName } from '../display/AstroIcon';
3
3
  import { PropertyKey, StatusThresholds } from './propertyConfig';
4
4
  import { StatusLevel } from '../../utils';
5
5
 
6
- export type DataValueVariant = 'stacked' | 'inline' | 'compact' | 'minimal' | 'card' | 'banner';
6
+ export type DataValueVariant = 'stacked' | 'inline' | 'compact' | 'minimal' | 'card' | 'banner' | 'stat';
7
7
  export type DataValueSize = 'sm' | 'md' | 'lg';
8
8
  export interface DataValueProps {
9
9
  /** Property key for auto-configuration (e.g., 'temperature', 'battery') */
@@ -396,6 +396,39 @@ const DataValue = memo(function DataValue2({
396
396
  ] })
397
397
  ] });
398
398
  }
399
+ case "stat": {
400
+ const statSize = {
401
+ sm: { icon: 13, value: 13, label: 9.5 },
402
+ md: { icon: 15, value: 14, label: 10 },
403
+ lg: { icon: 18, value: 16, label: 11 }
404
+ }[size];
405
+ const statColor = colorProp || statusColor;
406
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "flex-start", gap: 2 }, children: [
407
+ /* @__PURE__ */ jsxs("div", { style: {
408
+ display: "flex",
409
+ alignItems: "center",
410
+ gap: 6,
411
+ fontFamily: tokens.typography.fontFamily.mono,
412
+ fontSize: statSize.value,
413
+ fontWeight: 600,
414
+ color: statColor,
415
+ fontVariantNumeric: "tabular-nums",
416
+ letterSpacing: "0.02em",
417
+ lineHeight: 1.1
418
+ }, children: [
419
+ showIcon && config.icon && /* @__PURE__ */ jsx(AstroIcon, { name: config.icon, size: statSize.icon, color: statColor }),
420
+ /* @__PURE__ */ jsx("span", { children: formattedValue })
421
+ ] }),
422
+ /* @__PURE__ */ jsx("span", { style: {
423
+ fontFamily: tokens.typography.fontFamily.mono,
424
+ fontSize: statSize.label,
425
+ letterSpacing: "0.06em",
426
+ textTransform: "uppercase",
427
+ color: tokens.colors.text.tertiary,
428
+ lineHeight: 1.1
429
+ }, children: config.label })
430
+ ] });
431
+ }
399
432
  case "stacked":
400
433
  default: {
401
434
  const iconMultiplier = 0.7;
@@ -1 +1 @@
1
- {"version":3,"file":"DataValue.js","sources":["../../../../src/react/core/data/DataValue.tsx"],"sourcesContent":["/**\n * @zendir/ui - DataValue Component\n * \n * Enterprise-grade data value display with icon, label, value, and status.\n * The building block for telemetry displays, dashboards, and monitoring UIs.\n * \n * AstroUXDS Compliance:\n * - Status colors per AstroUXDS status system\n * - Dual-coding (color + shape) for accessibility via StatusIndicator\n * - Tabular numbers for alignment\n * - Sentence-case labels\n * \n * Features:\n * - Auto-derive status from value + thresholds\n * - Contextual icons from property presets\n * - Multiple layout variants (stacked, inline, compact)\n * - Theme-aware styling\n * \n * @example\n * ```tsx\n * import { DataValue } from '@zendir/ui';\n * \n * // Simple usage with property preset\n * <DataValue property=\"temperature\" value={72.5} />\n * \n * // Custom configuration\n * <DataValue \n * label=\"Battery\"\n * value={85}\n * unit=\"%\"\n * icon=\"battery-full\"\n * thresholds={{ critical: 10, caution: 30 }}\n * />\n * \n * // Inline layout\n * <DataValue property=\"voltage\" value={28.4} variant=\"inline\" />\n * ```\n */\n\nimport React, { memo, useMemo } from 'react';\nimport { useTheme } from '../../theme';\nimport { AstroIcon, type AstroIconName } from '../display/AstroIcon';\nimport { \n getPropertyConfig, \n deriveStatus, \n formatPropertyValue,\n type PropertyKey,\n type StatusThresholds,\n type PropertyConfig,\n} from './propertyConfig';\nimport type { StatusLevel } from '../../utils';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport type DataValueVariant = 'stacked' | 'inline' | 'compact' | 'minimal' | 'card' | 'banner';\nexport type DataValueSize = 'sm' | 'md' | 'lg';\n\nexport interface DataValueProps {\n /** Property key for auto-configuration (e.g., 'temperature', 'battery') */\n property?: PropertyKey;\n /** Display label (overrides property preset) */\n label?: string;\n /** Current value */\n value: number | string | undefined | null;\n /** Unit of measurement (overrides property preset) */\n unit?: string;\n /** Icon name (overrides property preset) */\n icon?: AstroIconName;\n /** Show icon */\n showIcon?: boolean;\n /** Status thresholds for auto-derivation (overrides property preset) */\n thresholds?: StatusThresholds;\n /** Override status (bypasses auto-derivation) */\n status?: StatusLevel;\n /** Show status indicator */\n showStatus?: boolean;\n /** Precision for number formatting (overrides property preset) */\n precision?: number;\n /** Layout variant */\n variant?: DataValueVariant;\n /** Size */\n size?: DataValueSize;\n /** Custom value color (overrides status color) */\n color?: string;\n /** Additional CSS class */\n className?: string;\n /** Custom inline styles */\n style?: React.CSSProperties;\n /** Click handler */\n onClick?: () => void;\n /** Tooltip text */\n tooltip?: string;\n}\n\n// =============================================================================\n// Astro UX Status Border Colors - Required for light theme WCAG compliance\n// https://www.astrouxds.com/patterns/status-system/\n// =============================================================================\n\nconst STATUS_BORDER_COLORS: Record<StatusLevel, string> = {\n off: '#3c3e42',\n standby: '#285766',\n normal: '#005a00',\n caution: '#645600',\n serious: '#664618',\n critical: '#661102',\n};\n\n// =============================================================================\n// Status Shape SVG - Matches MonitoringIcon exactly\n// - Glow effect in dark mode for visibility\n// - Border in light mode for WCAG AA compliance\n// =============================================================================\n\ninterface StatusShapeProps {\n status: StatusLevel;\n size: number;\n color: string;\n borderColor?: string;\n glow?: boolean;\n}\n\nconst StatusShape = ({ status, size, color, borderColor, glow = true }: StatusShapeProps) => {\n const hasBorder = !!borderColor;\n const strokeWidth = hasBorder ? 1 : 0;\n const glowFilter = glow && !hasBorder ? `drop-shadow(0 0 3px ${color})` : undefined;\n \n const renderShape = () => {\n switch (status) {\n case 'off':\n return <circle cx=\"6\" cy=\"6\" r={hasBorder ? 2.5 : 3} fill={color} stroke={borderColor} strokeWidth={strokeWidth} />;\n case 'standby':\n return <circle cx=\"6\" cy=\"6\" r=\"3.5\" fill=\"none\" stroke={color} strokeWidth=\"2\" />;\n case 'normal':\n return (\n <circle\n cx=\"6\"\n cy=\"6\"\n r={hasBorder ? 4.5 : 5}\n fill={color}\n stroke={borderColor}\n strokeWidth={strokeWidth}\n />\n );\n case 'caution':\n return (\n <rect\n x={hasBorder ? 1.5 : 1}\n y={hasBorder ? 1.5 : 1}\n width={hasBorder ? 9 : 10}\n height={hasBorder ? 9 : 10}\n fill={color}\n stroke={borderColor}\n strokeWidth={strokeWidth}\n />\n );\n case 'serious':\n return (\n <polygon\n points=\"6,1 11,6 6,11 1,6\"\n fill={color}\n stroke={borderColor}\n strokeWidth={strokeWidth}\n />\n );\n case 'critical':\n return (\n <polygon\n points=\"6,11 1,2 11,2\"\n fill={color}\n stroke={borderColor}\n strokeWidth={strokeWidth}\n />\n );\n default:\n return null;\n }\n };\n \n return (\n <svg \n viewBox=\"0 0 12 12\" \n width={size} \n height={size}\n style={{ filter: glowFilter }}\n >\n {renderShape()}\n </svg>\n );\n};\n\n// =============================================================================\n// Icon with Status Badge - Enterprise-grade alignment\n// \n// Design System Principles (Apple HIG / Google Material / AstroUXDS):\n// - 4px-based spacing grid for visual harmony\n// - Explicit container sizing prevents layout shift\n// - Status badge positioned at corner, not extending beyond container bounds\n// - Proportional badge sizing: ~35% of icon size for optimal visibility\n// =============================================================================\n\ninterface IconWithBadgeProps {\n icon: AstroIconName;\n iconSize: number;\n iconColor: string;\n showBadge: boolean;\n status: StatusLevel;\n statusColor: string;\n isLightTheme?: boolean;\n}\n\nconst IconWithBadge = memo(function IconWithBadge({ \n icon, iconSize, iconColor, showBadge, status, statusColor, isLightTheme = false \n}: IconWithBadgeProps) {\n // ═══════════════════════════════════════════════════════════════════════════\n // STATUS BADGE CONTROLS\n // ═══════════════════════════════════════════════════════════════════════════\n // BADGE SIZE: Percentage of icon size (0.25 = 25%)\n const badgeSizeRatio = 0.30;\n const badgeSize = Math.max(7, Math.min(14, Math.round(iconSize * badgeSizeRatio)));\n \n // BADGE POSITION: Offset from top-left corner (negative = away from icon)\n const badgeOffsetX = -2; // px (negative = left, positive = right)\n const badgeOffsetY = -2; // px (negative = up, positive = down)\n \n // ═══════════════════════════════════════════════════════════════════════════\n const borderColor = isLightTheme ? STATUS_BORDER_COLORS[status] : undefined;\n const containerSize = iconSize;\n \n return (\n <div style={{\n position: 'relative',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n flexShrink: 0,\n width: containerSize,\n height: containerSize,\n }}>\n <AstroIcon name={icon} size={iconSize} color={iconColor} />\n {showBadge && (\n <span style={{\n position: 'absolute',\n top: badgeOffsetY,\n left: badgeOffsetX,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}>\n <StatusShape \n status={status} \n size={badgeSize} \n color={statusColor} \n borderColor={borderColor}\n glow={!isLightTheme} \n />\n </span>\n )}\n </div>\n );\n});\n\n// =============================================================================\n// DataValue Component\n// =============================================================================\n\nexport const DataValue = memo(function DataValue({\n property,\n label: labelProp,\n value,\n unit: unitProp,\n icon: iconProp,\n showIcon = true,\n thresholds: thresholdsProp,\n status: statusProp,\n showStatus = true,\n precision: precisionProp,\n variant = 'stacked',\n size = 'md',\n color: colorProp,\n className = '',\n style,\n onClick,\n tooltip,\n}: DataValueProps): React.ReactElement {\n const { tokens, mode } = useTheme();\n const isLightTheme = mode === 'light';\n\n // Merge property preset with explicit props\n const config = useMemo((): PropertyConfig => {\n const preset = property ? getPropertyConfig(property) : {\n label: labelProp || 'Value',\n icon: iconProp || 'info',\n unit: unitProp,\n precision: precisionProp,\n thresholds: thresholdsProp,\n };\n\n return {\n ...preset,\n ...(labelProp !== undefined && { label: labelProp }),\n ...(iconProp !== undefined && { icon: iconProp }),\n ...(unitProp !== undefined && { unit: unitProp }),\n ...(precisionProp !== undefined && { precision: precisionProp }),\n ...(thresholdsProp !== undefined && { thresholds: thresholdsProp }),\n };\n }, [property, labelProp, iconProp, unitProp, precisionProp, thresholdsProp]);\n\n // Derive or use explicit status\n const status = useMemo((): StatusLevel => {\n if (statusProp) return statusProp;\n if (typeof value === 'number') {\n return deriveStatus(value, config.thresholds);\n }\n return 'normal';\n }, [statusProp, value, config.thresholds]);\n\n // Format the value\n const formattedValue = useMemo((): string => {\n if (value === undefined || value === null) return '--';\n if (typeof value === 'string') return value;\n return formatPropertyValue(value, config);\n }, [value, config]);\n\n // Get status color\n const statusColor = tokens.colors.status[status] || tokens.colors.status.off;\n const valueColor = colorProp || (showStatus && status !== 'normal' ? statusColor : tokens.colors.text.primary);\n\n // ==========================================================================\n // Size Configuration - Uses theme tokens for consistency with other cards\n // ==========================================================================\n // Typography uses theme tokens to match SpacecraftCard's DataCell\n // fontSize.xs = 10px (0.625rem), fontSize.sm = 12px (0.75rem)\n const sizeConfig = {\n sm: { \n iconSize: 16, \n labelSize: tokens.typography.fontSize.xs, // 10px - matches DataCell\n valueSize: tokens.typography.fontSize.sm, // 12px - matches DataCell\n lineHeight: 1.4,\n gap: 6, // Increased from 4px to 6px for better icon separation with larger icons\n rowGap: 2, // 2px between label and value\n },\n md: { \n iconSize: 20, \n labelSize: tokens.typography.fontSize.xs, // 10px\n valueSize: tokens.typography.fontSize.base, // 14px\n lineHeight: 1.4,\n gap: 10, // Increased from 8px to 10px\n rowGap: 2, // 2px between label and value\n },\n lg: { \n iconSize: 24, \n labelSize: tokens.typography.fontSize.sm, // 12px\n valueSize: tokens.typography.fontSize.md, // 14px\n lineHeight: 1.4,\n gap: 14, // Increased from 12px to 14px\n rowGap: 4, // 4px between label and value\n },\n }[size];\n\n // ==========================================================================\n // Render Based on Variant\n // \n // Layout principles:\n // - Baseline alignment for inline text\n // - Consistent icon-to-text spacing\n // - Symmetrical padding\n // - Tabular numbers for value alignment in grids\n // ==========================================================================\n const renderContent = () => {\n // Determine if status indicator should show\n const hasStatus = showStatus && status !== 'normal' && status !== 'off';\n \n switch (variant) {\n // ======================================================================\n // INLINE: Icon + Label ... Value (space-between)\n // Best for: Key-value pairs in tables or lists\n // ======================================================================\n case 'inline': {\n return (\n <div style={{ \n display: 'flex', \n alignItems: 'center', \n justifyContent: 'space-between', \n gap: sizeConfig.gap * 2,\n minHeight: sizeConfig.iconSize, // Ensure consistent row height\n }}>\n <div style={{ \n display: 'flex', \n alignItems: 'center', \n gap: sizeConfig.gap,\n }}>\n {showIcon && config.icon && (\n <IconWithBadge\n icon={config.icon}\n iconSize={sizeConfig.iconSize}\n iconColor={hasStatus ? statusColor : tokens.colors.text.tertiary}\n showBadge={hasStatus}\n status={status}\n statusColor={statusColor}\n isLightTheme={isLightTheme}\n />\n )}\n <span style={{\n fontSize: sizeConfig.labelSize,\n lineHeight: sizeConfig.lineHeight,\n color: tokens.colors.text.secondary,\n fontWeight: 500,\n fontFamily: tokens.typography.fontFamily.primary,\n }}>\n {config.label}\n </span>\n </div>\n <span style={{\n fontSize: sizeConfig.valueSize,\n lineHeight: sizeConfig.lineHeight,\n fontWeight: 500, // Match SpacecraftCard DataCell\n fontFamily: tokens.typography.fontFamily.mono,\n fontVariantNumeric: 'tabular-nums',\n color: valueColor,\n }}>\n {formattedValue}\n </span>\n </div>\n );\n }\n\n // ======================================================================\n // COMPACT: Icon + Value (horizontal, no label)\n // Best for: Dense displays, status bars, compact dashboards\n // ======================================================================\n case 'compact': {\n return (\n <div style={{ \n display: 'flex', \n alignItems: 'center', \n gap: sizeConfig.gap,\n minHeight: sizeConfig.iconSize,\n }}>\n {showIcon && config.icon && (\n <IconWithBadge\n icon={config.icon}\n iconSize={sizeConfig.iconSize}\n iconColor={hasStatus ? statusColor : tokens.colors.text.tertiary}\n showBadge={hasStatus}\n status={status}\n statusColor={statusColor}\n isLightTheme={isLightTheme}\n />\n )}\n <span style={{\n fontSize: sizeConfig.valueSize,\n lineHeight: sizeConfig.lineHeight,\n fontWeight: 500, // Match SpacecraftCard DataCell\n fontFamily: tokens.typography.fontFamily.mono,\n fontVariantNumeric: 'tabular-nums',\n color: valueColor,\n }}>\n {formattedValue}\n </span>\n </div>\n );\n }\n\n // ======================================================================\n // MINIMAL: Value only (no icon, no label)\n // Best for: Inline values, table cells\n // ======================================================================\n case 'minimal':\n return (\n <span style={{\n fontSize: sizeConfig.valueSize,\n lineHeight: sizeConfig.lineHeight,\n fontWeight: 500, // Match SpacecraftCard DataCell\n fontFamily: tokens.typography.fontFamily.mono,\n fontVariantNumeric: 'tabular-nums',\n color: valueColor,\n }}>\n {formattedValue}\n </span>\n );\n\n // ======================================================================\n // CARD: Icon EXACTLY matches 2-row text height\n // Best for: Dashboard tiles, prominent metrics\n // ======================================================================\n case 'card': {\n // ═══════════════════════════════════════════════════════════════════════════\n // 2-ROW LAYOUT CONTROLS (card variant)\n // ═══════════════════════════════════════════════════════════════════════════\n // ICON MULTIPLIER: Scale factor (0.5 = half, 1.0 = match text height, 1.5 = 150%)\n const iconMultiplier = 0.7;\n \n // GAP: Pixels between icon and text column\n const iconToTextGap = 4; // px\n \n // ═══════════════════════════════════════════════════════════════════════════\n const baseSize = { sm: 26, md: 32, lg: 40 }[size];\n const iconSize = Math.round(baseSize * iconMultiplier);\n \n return (\n <div style={{ \n display: 'flex', \n alignItems: 'center',\n gap: iconToTextGap,\n }}>\n {showIcon && config.icon && (\n <IconWithBadge\n icon={config.icon}\n iconSize={iconSize}\n iconColor={hasStatus ? statusColor : tokens.colors.text.tertiary}\n showBadge={hasStatus}\n status={status}\n statusColor={statusColor}\n isLightTheme={isLightTheme}\n />\n )}\n <div style={{ \n display: 'flex', \n flexDirection: 'column', \n justifyContent: 'space-between', // Distribute label/value to edges\n gap: sizeConfig.rowGap, \n minWidth: 0,\n }}>\n <span style={{\n fontSize: sizeConfig.labelSize,\n lineHeight: sizeConfig.lineHeight,\n color: tokens.colors.text.secondary,\n fontWeight: 500,\n fontFamily: tokens.typography.fontFamily.primary,\n }}>\n {config.label}\n </span>\n <span style={{\n fontSize: sizeConfig.valueSize,\n lineHeight: sizeConfig.lineHeight,\n fontWeight: 500, // Match SpacecraftCard DataCell\n fontFamily: tokens.typography.fontFamily.mono,\n fontVariantNumeric: 'tabular-nums',\n color: valueColor,\n }}>\n {formattedValue}\n </span>\n </div>\n </div>\n );\n }\n\n // ======================================================================\n // BANNER: Label on top, icon left-aligned + value right-aligned below\n // Best for: TopBar metrics, KPI tiles where heading spans full width\n // ======================================================================\n case 'banner': {\n const bannerIconSize = { sm: 18, md: 22, lg: 28 }[size];\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: sizeConfig.rowGap, minWidth: 0, width: '100%' }}>\n <span style={{\n fontSize: sizeConfig.labelSize,\n lineHeight: sizeConfig.lineHeight,\n color: tokens.colors.text.secondary,\n fontWeight: 500,\n fontFamily: tokens.typography.fontFamily.primary,\n textTransform: 'uppercase' as const,\n letterSpacing: '0.5px',\n }}>\n {config.label}\n </span>\n <div style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'flex-start',\n width: '100%',\n gap: 6,\n minWidth: 0,\n }}>\n {showIcon && config.icon && (\n <IconWithBadge\n icon={config.icon}\n iconSize={bannerIconSize}\n iconColor={hasStatus ? statusColor : tokens.colors.text.tertiary}\n showBadge={hasStatus}\n status={status}\n statusColor={statusColor}\n isLightTheme={isLightTheme}\n />\n )}\n <span style={{\n fontSize: sizeConfig.valueSize,\n lineHeight: sizeConfig.lineHeight,\n fontWeight: 600,\n fontFamily: tokens.typography.fontFamily.mono,\n fontVariantNumeric: 'tabular-nums',\n color: valueColor,\n textAlign: 'left',\n }}>\n {formattedValue}\n </span>\n </div>\n </div>\n );\n }\n\n // ======================================================================\n // STACKED (default): Icon EXACTLY matches 2-row text height\n // Best for: General telemetry displays, standard data values\n // ======================================================================\n case 'stacked':\n default: {\n // ═══════════════════════════════════════════════════════════════════════════\n // 2-ROW LAYOUT CONTROLS (stacked variant)\n // ═══════════════════════════════════════════════════════════════════════════\n // ICON MULTIPLIER: Scale factor (0.5 = half, 1.0 = match text height, 1.5 = 150%)\n const iconMultiplier = 0.7;\n \n // GAP: Pixels between icon and text column\n const iconToTextGap = 4; // px\n \n // ═══════════════════════════════════════════════════════════════════════════\n const baseSize = { sm: 26, md: 32, lg: 40 }[size];\n const iconSize = Math.round(baseSize * iconMultiplier);\n \n return (\n <div style={{ \n display: 'flex', \n alignItems: 'center',\n gap: iconToTextGap,\n }}>\n {showIcon && config.icon && (\n <IconWithBadge\n icon={config.icon}\n iconSize={iconSize}\n iconColor={hasStatus ? statusColor : tokens.colors.text.tertiary}\n showBadge={hasStatus}\n status={status}\n statusColor={statusColor}\n isLightTheme={isLightTheme}\n />\n )}\n <div style={{ \n display: 'flex', \n flexDirection: 'column', \n justifyContent: 'space-between', // Distribute label/value to edges\n gap: sizeConfig.rowGap, \n minWidth: 0,\n }}>\n <span style={{\n fontSize: sizeConfig.labelSize,\n lineHeight: sizeConfig.lineHeight,\n color: tokens.colors.text.secondary,\n fontWeight: 500,\n fontFamily: tokens.typography.fontFamily.primary,\n }}>\n {config.label}\n </span>\n <span style={{\n fontSize: sizeConfig.valueSize,\n lineHeight: sizeConfig.lineHeight,\n fontWeight: 500, // Match SpacecraftCard DataCell\n fontFamily: tokens.typography.fontFamily.mono,\n fontVariantNumeric: 'tabular-nums',\n color: valueColor,\n }}>\n {formattedValue}\n </span>\n </div>\n </div>\n );\n }\n }\n };\n\n return (\n <div\n className={`zendir-data-value zendir-data-value--${variant} zendir-data-value--${size} ${className}`}\n style={{\n cursor: onClick ? 'pointer' : 'default',\n ...(variant === 'banner' ? { flex: 1, minWidth: 0 } : {}),\n ...style,\n }}\n onClick={onClick}\n title={tooltip || config.description}\n role={onClick ? 'button' : undefined}\n tabIndex={onClick ? 0 : undefined}\n onKeyDown={onClick ? (e) => e.key === 'Enter' && onClick() : undefined}\n >\n {renderContent()}\n </div>\n );\n});\n\n// =============================================================================\n// DataValueGroup Component\n// =============================================================================\n\nexport interface DataValueGroupProps {\n /** Title for the group */\n title?: string;\n /** Icon for the title */\n icon?: AstroIconName;\n /** Layout direction */\n direction?: 'row' | 'column' | 'grid';\n /** Number of columns for grid layout */\n columns?: number;\n /** Gap between items */\n gap?: 'sm' | 'md' | 'lg';\n /** Children DataValue components */\n children: React.ReactNode;\n /** Additional CSS class */\n className?: string;\n /** Custom inline styles */\n style?: React.CSSProperties;\n}\n\nexport const DataValueGroup = memo(function DataValueGroup({\n title,\n icon,\n direction = 'grid',\n columns = 2,\n gap = 'md',\n children,\n className = '',\n style,\n}: DataValueGroupProps): React.ReactElement {\n const { tokens } = useTheme();\n\n // Gap sizing on 4px grid\n const gapSize = {\n sm: 8, // 2 × 4px\n md: 16, // 4 × 4px\n lg: 24, // 6 × 4px\n }[gap];\n\n const layoutStyles: Record<typeof direction, React.CSSProperties> = {\n row: { \n display: 'flex', \n flexDirection: 'row' as const, \n flexWrap: 'wrap' as const, \n gap: gapSize,\n alignItems: 'flex-start',\n },\n column: { \n display: 'flex', \n flexDirection: 'column' as const, \n gap: gapSize,\n },\n grid: { \n display: 'grid', \n gridTemplateColumns: `repeat(${columns}, 1fr)`, \n gap: gapSize,\n alignItems: 'start',\n },\n };\n const layoutStyle = layoutStyles[direction];\n\n return (\n <div className={`zendir-data-value-group ${className}`} style={style}>\n {title && (\n <div style={{\n display: 'flex',\n alignItems: 'center',\n gap: 6, // 6px gap for tight header\n marginBottom: 12, // 3 × 4px\n paddingBottom: 8, // 2 × 4px\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\n }}>\n {icon && (\n <AstroIcon \n name={icon} \n size={14} \n color={tokens.colors.text.tertiary}\n style={{ flexShrink: 0 }}\n />\n )}\n <span style={{\n fontSize: '12px',\n fontWeight: 500,\n color: tokens.colors.text.secondary,\n textTransform: 'uppercase',\n letterSpacing: '0.5px',\n }}>\n {title}\n </span>\n </div>\n )}\n <div style={layoutStyle}>\n {children}\n </div>\n </div>\n );\n});\n\nexport default DataValue;\n"],"names":["IconWithBadge","DataValue","DataValueGroup"],"mappings":";;;;;AAqGA,MAAM,uBAAoD;AAAA,EACxD,KAAK;AAAA,EACL,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AACZ;AAgBA,MAAM,cAAc,CAAC,EAAE,QAAQ,MAAM,OAAO,aAAa,OAAO,WAA6B;AAC3F,QAAM,YAAY,CAAC,CAAC;AACpB,QAAM,cAAc,YAAY,IAAI;AACpC,QAAM,aAAa,QAAQ,CAAC,YAAY,uBAAuB,KAAK,MAAM;AAE1E,QAAM,cAAc,MAAM;AACxB,YAAQ,QAAA;AAAA,MACN,KAAK;AACH,eAAO,oBAAC,UAAA,EAAO,IAAG,KAAI,IAAG,KAAI,GAAG,YAAY,MAAM,GAAG,MAAM,OAAO,QAAQ,aAAa,aAA0B;AAAA,MACnH,KAAK;AACH,eAAO,oBAAC,UAAA,EAAO,IAAG,KAAI,IAAG,KAAI,GAAE,OAAM,MAAK,QAAO,QAAQ,OAAO,aAAY,KAAI;AAAA,MAClF,KAAK;AACH,eACE;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,IAAG;AAAA,YACH,IAAG;AAAA,YACH,GAAG,YAAY,MAAM;AAAA,YACrB,MAAM;AAAA,YACN,QAAQ;AAAA,YACR;AAAA,UAAA;AAAA,QAAA;AAAA,MAGN,KAAK;AACH,eACE;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,GAAG,YAAY,MAAM;AAAA,YACrB,GAAG,YAAY,MAAM;AAAA,YACrB,OAAO,YAAY,IAAI;AAAA,YACvB,QAAQ,YAAY,IAAI;AAAA,YACxB,MAAM;AAAA,YACN,QAAQ;AAAA,YACR;AAAA,UAAA;AAAA,QAAA;AAAA,MAGN,KAAK;AACH,eACE;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,QAAO;AAAA,YACP,MAAM;AAAA,YACN,QAAQ;AAAA,YACR;AAAA,UAAA;AAAA,QAAA;AAAA,MAGN,KAAK;AACH,eACE;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,QAAO;AAAA,YACP,MAAM;AAAA,YACN,QAAQ;AAAA,YACR;AAAA,UAAA;AAAA,QAAA;AAAA,MAGN;AACE,eAAO;AAAA,IAAA;AAAA,EAEb;AAEA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,SAAQ;AAAA,MACR,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,OAAO,EAAE,QAAQ,WAAA;AAAA,MAEhB,UAAA,YAAA;AAAA,IAAY;AAAA,EAAA;AAGnB;AAsBA,MAAM,gBAAgB,KAAK,SAASA,eAAc;AAAA,EAChD;AAAA,EAAM;AAAA,EAAU;AAAA,EAAW;AAAA,EAAW;AAAA,EAAQ;AAAA,EAAa,eAAe;AAC5E,GAAuB;AAKrB,QAAM,iBAAiB;AACvB,QAAM,YAAY,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,MAAM,WAAW,cAAc,CAAC,CAAC;AAGjF,QAAM,eAAe;AACrB,QAAM,eAAe;AAGrB,QAAM,cAAc,eAAe,qBAAqB,MAAM,IAAI;AAClE,QAAM,gBAAgB;AAEtB,SACE,qBAAC,SAAI,OAAO;AAAA,IACV,UAAU;AAAA,IACV,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,QAAQ;AAAA,EAAA,GAER,UAAA;AAAA,IAAA,oBAAC,aAAU,MAAM,MAAM,MAAM,UAAU,OAAO,WAAW;AAAA,IACxD,aACC,oBAAC,QAAA,EAAK,OAAO;AAAA,MACX,UAAU;AAAA,MACV,KAAK;AAAA,MACL,MAAM;AAAA,MACN,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,gBAAgB;AAAA,IAAA,GAEhB,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAM;AAAA,QACN,OAAO;AAAA,QACP;AAAA,QACA,MAAM,CAAC;AAAA,MAAA;AAAA,IAAA,EACT,CACF;AAAA,EAAA,GAEJ;AAEJ,CAAC;AAMM,MAAM,YAAY,KAAK,SAASC,WAAU;AAAA,EAC/C;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA,MAAM;AAAA,EACN,MAAM;AAAA,EACN,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,WAAW;AAAA,EACX,UAAU;AAAA,EACV,OAAO;AAAA,EACP,OAAO;AAAA,EACP,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AACF,GAAuC;AACrC,QAAM,EAAE,QAAQ,KAAA,IAAS,SAAA;AACzB,QAAM,eAAe,SAAS;AAG9B,QAAM,SAAS,QAAQ,MAAsB;AAC3C,UAAM,SAAS,WAAW,kBAAkB,QAAQ,IAAI;AAAA,MACtD,OAAO,aAAa;AAAA,MACpB,MAAM,YAAY;AAAA,MAClB,MAAM;AAAA,MACN,WAAW;AAAA,MACX,YAAY;AAAA,IAAA;AAGd,WAAO;AAAA,MACL,GAAG;AAAA,MACH,GAAI,cAAc,UAAa,EAAE,OAAO,UAAA;AAAA,MACxC,GAAI,aAAa,UAAa,EAAE,MAAM,SAAA;AAAA,MACtC,GAAI,aAAa,UAAa,EAAE,MAAM,SAAA;AAAA,MACtC,GAAI,kBAAkB,UAAa,EAAE,WAAW,cAAA;AAAA,MAChD,GAAI,mBAAmB,UAAa,EAAE,YAAY,eAAA;AAAA,IAAe;AAAA,EAErE,GAAG,CAAC,UAAU,WAAW,UAAU,UAAU,eAAe,cAAc,CAAC;AAG3E,QAAM,SAAS,QAAQ,MAAmB;AACxC,QAAI,WAAY,QAAO;AACvB,QAAI,OAAO,UAAU,UAAU;AAC7B,aAAO,aAAa,OAAO,OAAO,UAAU;AAAA,IAC9C;AACA,WAAO;AAAA,EACT,GAAG,CAAC,YAAY,OAAO,OAAO,UAAU,CAAC;AAGzC,QAAM,iBAAiB,QAAQ,MAAc;AAC3C,QAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,QAAI,OAAO,UAAU,SAAU,QAAO;AACtC,WAAO,oBAAoB,OAAO,MAAM;AAAA,EAC1C,GAAG,CAAC,OAAO,MAAM,CAAC;AAGlB,QAAM,cAAc,OAAO,OAAO,OAAO,MAAM,KAAK,OAAO,OAAO,OAAO;AACzE,QAAM,aAAa,cAAc,cAAc,WAAW,WAAW,cAAc,OAAO,OAAO,KAAK;AAOtG,QAAM,aAAa;AAAA,IACjB,IAAI;AAAA,MACF,UAAU;AAAA,MACV,WAAW,OAAO,WAAW,SAAS;AAAA;AAAA,MACtC,WAAW,OAAO,WAAW,SAAS;AAAA;AAAA,MACtC,YAAY;AAAA,MACZ,KAAK;AAAA;AAAA,MACL,QAAQ;AAAA;AAAA,IAAA;AAAA,IAEV,IAAI;AAAA,MACF,UAAU;AAAA,MACV,WAAW,OAAO,WAAW,SAAS;AAAA;AAAA,MACtC,WAAW,OAAO,WAAW,SAAS;AAAA;AAAA,MACtC,YAAY;AAAA,MACZ,KAAK;AAAA;AAAA,MACL,QAAQ;AAAA;AAAA,IAAA;AAAA,IAEV,IAAI;AAAA,MACF,UAAU;AAAA,MACV,WAAW,OAAO,WAAW,SAAS;AAAA;AAAA,MACtC,WAAW,OAAO,WAAW,SAAS;AAAA;AAAA,MACtC,YAAY;AAAA,MACZ,KAAK;AAAA;AAAA,MACL,QAAQ;AAAA;AAAA,IAAA;AAAA,EACV,EACA,IAAI;AAWN,QAAM,gBAAgB,MAAM;AAE1B,UAAM,YAAY,cAAc,WAAW,YAAY,WAAW;AAElE,YAAQ,SAAA;AAAA,MAKN,KAAK,UAAU;AACb,eACE,qBAAC,SAAI,OAAO;AAAA,UACV,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,gBAAgB;AAAA,UAChB,KAAK,WAAW,MAAM;AAAA,UACtB,WAAW,WAAW;AAAA;AAAA,QAAA,GAEtB,UAAA;AAAA,UAAA,qBAAC,SAAI,OAAO;AAAA,YACV,SAAS;AAAA,YACT,YAAY;AAAA,YACZ,KAAK,WAAW;AAAA,UAAA,GAEf,UAAA;AAAA,YAAA,YAAY,OAAO,QAClB;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,MAAM,OAAO;AAAA,gBACb,UAAU,WAAW;AAAA,gBACrB,WAAW,YAAY,cAAc,OAAO,OAAO,KAAK;AAAA,gBACxD,WAAW;AAAA,gBACX;AAAA,gBACA;AAAA,gBACA;AAAA,cAAA;AAAA,YAAA;AAAA,YAGJ,oBAAC,UAAK,OAAO;AAAA,cACX,UAAU,WAAW;AAAA,cACrB,YAAY,WAAW;AAAA,cACvB,OAAO,OAAO,OAAO,KAAK;AAAA,cAC1B,YAAY;AAAA,cACZ,YAAY,OAAO,WAAW,WAAW;AAAA,YAAA,GAExC,iBAAO,MAAA,CACV;AAAA,UAAA,GACF;AAAA,UACA,oBAAC,UAAK,OAAO;AAAA,YACX,UAAU,WAAW;AAAA,YACrB,YAAY,WAAW;AAAA,YACvB,YAAY;AAAA;AAAA,YACZ,YAAY,OAAO,WAAW,WAAW;AAAA,YACzC,oBAAoB;AAAA,YACpB,OAAO;AAAA,UAAA,GAEN,UAAA,eAAA,CACH;AAAA,QAAA,GACF;AAAA,MAEJ;AAAA,MAMA,KAAK,WAAW;AACd,eACE,qBAAC,SAAI,OAAO;AAAA,UACV,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,KAAK,WAAW;AAAA,UAChB,WAAW,WAAW;AAAA,QAAA,GAErB,UAAA;AAAA,UAAA,YAAY,OAAO,QAClB;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAM,OAAO;AAAA,cACb,UAAU,WAAW;AAAA,cACrB,WAAW,YAAY,cAAc,OAAO,OAAO,KAAK;AAAA,cACxD,WAAW;AAAA,cACX;AAAA,cACA;AAAA,cACA;AAAA,YAAA;AAAA,UAAA;AAAA,UAGJ,oBAAC,UAAK,OAAO;AAAA,YACX,UAAU,WAAW;AAAA,YACrB,YAAY,WAAW;AAAA,YACvB,YAAY;AAAA;AAAA,YACZ,YAAY,OAAO,WAAW,WAAW;AAAA,YACzC,oBAAoB;AAAA,YACpB,OAAO;AAAA,UAAA,GAEN,UAAA,eAAA,CACH;AAAA,QAAA,GACF;AAAA,MAEJ;AAAA,MAMA,KAAK;AACH,eACE,oBAAC,UAAK,OAAO;AAAA,UACX,UAAU,WAAW;AAAA,UACrB,YAAY,WAAW;AAAA,UACvB,YAAY;AAAA;AAAA,UACZ,YAAY,OAAO,WAAW,WAAW;AAAA,UACzC,oBAAoB;AAAA,UACpB,OAAO;AAAA,QAAA,GAEN,UAAA,gBACH;AAAA,MAOJ,KAAK,QAAQ;AAKX,cAAM,iBAAiB;AAGvB,cAAM,gBAAgB;AAGtB,cAAM,WAAW,EAAE,IAAI,IAAI,IAAI,IAAI,IAAI,GAAA,EAAK,IAAI;AAChD,cAAM,WAAW,KAAK,MAAM,WAAW,cAAc;AAErD,eACE,qBAAC,SAAI,OAAO;AAAA,UACV,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,KAAK;AAAA,QAAA,GAEJ,UAAA;AAAA,UAAA,YAAY,OAAO,QAClB;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAM,OAAO;AAAA,cACb;AAAA,cACA,WAAW,YAAY,cAAc,OAAO,OAAO,KAAK;AAAA,cACxD,WAAW;AAAA,cACX;AAAA,cACA;AAAA,cACA;AAAA,YAAA;AAAA,UAAA;AAAA,UAGJ,qBAAC,SAAI,OAAO;AAAA,YACV,SAAS;AAAA,YACT,eAAe;AAAA,YACf,gBAAgB;AAAA;AAAA,YAChB,KAAK,WAAW;AAAA,YAChB,UAAU;AAAA,UAAA,GAEV,UAAA;AAAA,YAAA,oBAAC,UAAK,OAAO;AAAA,cACX,UAAU,WAAW;AAAA,cACrB,YAAY,WAAW;AAAA,cACvB,OAAO,OAAO,OAAO,KAAK;AAAA,cAC1B,YAAY;AAAA,cACZ,YAAY,OAAO,WAAW,WAAW;AAAA,YAAA,GAExC,iBAAO,OACV;AAAA,YACA,oBAAC,UAAK,OAAO;AAAA,cACX,UAAU,WAAW;AAAA,cACrB,YAAY,WAAW;AAAA,cACvB,YAAY;AAAA;AAAA,cACZ,YAAY,OAAO,WAAW,WAAW;AAAA,cACzC,oBAAoB;AAAA,cACpB,OAAO;AAAA,YAAA,GAEN,UAAA,eAAA,CACH;AAAA,UAAA,EAAA,CACF;AAAA,QAAA,GACF;AAAA,MAEJ;AAAA,MAMA,KAAK,UAAU;AACb,cAAM,iBAAiB,EAAE,IAAI,IAAI,IAAI,IAAI,IAAI,GAAA,EAAK,IAAI;AACtD,eACE,qBAAC,OAAA,EAAI,OAAO,EAAE,SAAS,QAAQ,eAAe,UAAU,KAAK,WAAW,QAAQ,UAAU,GAAG,OAAO,UAClG,UAAA;AAAA,UAAA,oBAAC,UAAK,OAAO;AAAA,YACX,UAAU,WAAW;AAAA,YACrB,YAAY,WAAW;AAAA,YACvB,OAAO,OAAO,OAAO,KAAK;AAAA,YAC1B,YAAY;AAAA,YACZ,YAAY,OAAO,WAAW,WAAW;AAAA,YACzC,eAAe;AAAA,YACf,eAAe;AAAA,UAAA,GAEd,iBAAO,OACV;AAAA,UACA,qBAAC,SAAI,OAAO;AAAA,YACV,SAAS;AAAA,YACT,YAAY;AAAA,YACZ,gBAAgB;AAAA,YAChB,OAAO;AAAA,YACP,KAAK;AAAA,YACL,UAAU;AAAA,UAAA,GAET,UAAA;AAAA,YAAA,YAAY,OAAO,QAClB;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,MAAM,OAAO;AAAA,gBACb,UAAU;AAAA,gBACV,WAAW,YAAY,cAAc,OAAO,OAAO,KAAK;AAAA,gBACxD,WAAW;AAAA,gBACX;AAAA,gBACA;AAAA,gBACA;AAAA,cAAA;AAAA,YAAA;AAAA,YAGJ,oBAAC,UAAK,OAAO;AAAA,cACX,UAAU,WAAW;AAAA,cACrB,YAAY,WAAW;AAAA,cACvB,YAAY;AAAA,cACZ,YAAY,OAAO,WAAW,WAAW;AAAA,cACzC,oBAAoB;AAAA,cACpB,OAAO;AAAA,cACP,WAAW;AAAA,YAAA,GAEV,UAAA,eAAA,CACH;AAAA,UAAA,EAAA,CACF;AAAA,QAAA,GACF;AAAA,MAEJ;AAAA,MAMA,KAAK;AAAA,MACL,SAAS;AAKP,cAAM,iBAAiB;AAGvB,cAAM,gBAAgB;AAGtB,cAAM,WAAW,EAAE,IAAI,IAAI,IAAI,IAAI,IAAI,GAAA,EAAK,IAAI;AAChD,cAAM,WAAW,KAAK,MAAM,WAAW,cAAc;AAErD,eACE,qBAAC,SAAI,OAAO;AAAA,UACV,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,KAAK;AAAA,QAAA,GAEJ,UAAA;AAAA,UAAA,YAAY,OAAO,QAClB;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAM,OAAO;AAAA,cACb;AAAA,cACA,WAAW,YAAY,cAAc,OAAO,OAAO,KAAK;AAAA,cACxD,WAAW;AAAA,cACX;AAAA,cACA;AAAA,cACA;AAAA,YAAA;AAAA,UAAA;AAAA,UAGJ,qBAAC,SAAI,OAAO;AAAA,YACV,SAAS;AAAA,YACT,eAAe;AAAA,YACf,gBAAgB;AAAA;AAAA,YAChB,KAAK,WAAW;AAAA,YAChB,UAAU;AAAA,UAAA,GAEV,UAAA;AAAA,YAAA,oBAAC,UAAK,OAAO;AAAA,cACX,UAAU,WAAW;AAAA,cACrB,YAAY,WAAW;AAAA,cACvB,OAAO,OAAO,OAAO,KAAK;AAAA,cAC1B,YAAY;AAAA,cACZ,YAAY,OAAO,WAAW,WAAW;AAAA,YAAA,GAExC,iBAAO,OACV;AAAA,YACA,oBAAC,UAAK,OAAO;AAAA,cACX,UAAU,WAAW;AAAA,cACrB,YAAY,WAAW;AAAA,cACvB,YAAY;AAAA;AAAA,cACZ,YAAY,OAAO,WAAW,WAAW;AAAA,cACzC,oBAAoB;AAAA,cACpB,OAAO;AAAA,YAAA,GAEN,UAAA,eAAA,CACH;AAAA,UAAA,EAAA,CACF;AAAA,QAAA,GACF;AAAA,MAEJ;AAAA,IAAA;AAAA,EAEJ;AAEA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,wCAAwC,OAAO,uBAAuB,IAAI,IAAI,SAAS;AAAA,MAClG,OAAO;AAAA,QACL,QAAQ,UAAU,YAAY;AAAA,QAC9B,GAAI,YAAY,WAAW,EAAE,MAAM,GAAG,UAAU,EAAA,IAAM,CAAA;AAAA,QACtD,GAAG;AAAA,MAAA;AAAA,MAEL;AAAA,MACA,OAAO,WAAW,OAAO;AAAA,MACzB,MAAM,UAAU,WAAW;AAAA,MAC3B,UAAU,UAAU,IAAI;AAAA,MACxB,WAAW,UAAU,CAAC,MAAM,EAAE,QAAQ,WAAW,YAAY;AAAA,MAE5D,UAAA,cAAA;AAAA,IAAc;AAAA,EAAA;AAGrB,CAAC;AAyBM,MAAM,iBAAiB,KAAK,SAASC,gBAAe;AAAA,EACzD;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,MAAM;AAAA,EACN;AAAA,EACA,YAAY;AAAA,EACZ;AACF,GAA4C;AAC1C,QAAM,EAAE,OAAA,IAAW,SAAA;AAGnB,QAAM,UAAU;AAAA,IACd,IAAI;AAAA;AAAA,IACJ,IAAI;AAAA;AAAA,IACJ,IAAI;AAAA;AAAA,EAAA,EACJ,GAAG;AAEL,QAAM,eAA8D;AAAA,IAClE,KAAK;AAAA,MACH,SAAS;AAAA,MACT,eAAe;AAAA,MACf,UAAU;AAAA,MACV,KAAK;AAAA,MACL,YAAY;AAAA,IAAA;AAAA,IAEd,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,eAAe;AAAA,MACf,KAAK;AAAA,IAAA;AAAA,IAEP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,qBAAqB,UAAU,OAAO;AAAA,MACtC,KAAK;AAAA,MACL,YAAY;AAAA,IAAA;AAAA,EACd;AAEF,QAAM,cAAc,aAAa,SAAS;AAE1C,8BACG,OAAA,EAAI,WAAW,2BAA2B,SAAS,IAAI,OACrD,UAAA;AAAA,IAAA,SACC,qBAAC,SAAI,OAAO;AAAA,MACV,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,KAAK;AAAA;AAAA,MACL,cAAc;AAAA;AAAA,MACd,eAAe;AAAA;AAAA,MACf,cAAc,aAAa,OAAO,OAAO,OAAO,KAAK;AAAA,IAAA,GAEpD,UAAA;AAAA,MAAA,QACC;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAM;AAAA,UACN,MAAM;AAAA,UACN,OAAO,OAAO,OAAO,KAAK;AAAA,UAC1B,OAAO,EAAE,YAAY,EAAA;AAAA,QAAE;AAAA,MAAA;AAAA,MAG3B,oBAAC,UAAK,OAAO;AAAA,QACX,UAAU;AAAA,QACV,YAAY;AAAA,QACZ,OAAO,OAAO,OAAO,KAAK;AAAA,QAC1B,eAAe;AAAA,QACf,eAAe;AAAA,MAAA,GAEd,UAAA,MAAA,CACH;AAAA,IAAA,GACF;AAAA,IAEF,oBAAC,OAAA,EAAI,OAAO,aACT,SAAA,CACH;AAAA,EAAA,GACF;AAEJ,CAAC;"}
1
+ {"version":3,"file":"DataValue.js","sources":["../../../../src/react/core/data/DataValue.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - DataValue Component\r\n * \r\n * Enterprise-grade data value display with icon, label, value, and status.\r\n * The building block for telemetry displays, dashboards, and monitoring UIs.\r\n * \r\n * AstroUXDS Compliance:\r\n * - Status colors per AstroUXDS status system\r\n * - Dual-coding (color + shape) for accessibility via StatusIndicator\r\n * - Tabular numbers for alignment\r\n * - Sentence-case labels\r\n * \r\n * Features:\r\n * - Auto-derive status from value + thresholds\r\n * - Contextual icons from property presets\r\n * - Multiple layout variants (stacked, inline, compact)\r\n * - Theme-aware styling\r\n * \r\n * @example\r\n * ```tsx\r\n * import { DataValue } from '@zendir/ui';\r\n * \r\n * // Simple usage with property preset\r\n * <DataValue property=\"temperature\" value={72.5} />\r\n * \r\n * // Custom configuration\r\n * <DataValue \r\n * label=\"Battery\"\r\n * value={85}\r\n * unit=\"%\"\r\n * icon=\"battery-full\"\r\n * thresholds={{ critical: 10, caution: 30 }}\r\n * />\r\n * \r\n * // Inline layout\r\n * <DataValue property=\"voltage\" value={28.4} variant=\"inline\" />\r\n * ```\r\n */\r\n\r\nimport React, { memo, useMemo } from 'react';\r\nimport { useTheme } from '../../theme';\r\nimport { AstroIcon, type AstroIconName } from '../display/AstroIcon';\r\nimport { \r\n getPropertyConfig, \r\n deriveStatus, \r\n formatPropertyValue,\r\n type PropertyKey,\r\n type StatusThresholds,\r\n type PropertyConfig,\r\n} from './propertyConfig';\r\nimport type { StatusLevel } from '../../utils';\r\n\r\n// =============================================================================\r\n// Types\r\n// =============================================================================\r\n\r\nexport type DataValueVariant = 'stacked' | 'inline' | 'compact' | 'minimal' | 'card' | 'banner' | 'stat';\r\nexport type DataValueSize = 'sm' | 'md' | 'lg';\r\n\r\nexport interface DataValueProps {\r\n /** Property key for auto-configuration (e.g., 'temperature', 'battery') */\r\n property?: PropertyKey;\r\n /** Display label (overrides property preset) */\r\n label?: string;\r\n /** Current value */\r\n value: number | string | undefined | null;\r\n /** Unit of measurement (overrides property preset) */\r\n unit?: string;\r\n /** Icon name (overrides property preset) */\r\n icon?: AstroIconName;\r\n /** Show icon */\r\n showIcon?: boolean;\r\n /** Status thresholds for auto-derivation (overrides property preset) */\r\n thresholds?: StatusThresholds;\r\n /** Override status (bypasses auto-derivation) */\r\n status?: StatusLevel;\r\n /** Show status indicator */\r\n showStatus?: boolean;\r\n /** Precision for number formatting (overrides property preset) */\r\n precision?: number;\r\n /** Layout variant */\r\n variant?: DataValueVariant;\r\n /** Size */\r\n size?: DataValueSize;\r\n /** Custom value color (overrides status color) */\r\n color?: string;\r\n /** Additional CSS class */\r\n className?: string;\r\n /** Custom inline styles */\r\n style?: React.CSSProperties;\r\n /** Click handler */\r\n onClick?: () => void;\r\n /** Tooltip text */\r\n tooltip?: string;\r\n}\r\n\r\n// =============================================================================\r\n// Astro UX Status Border Colors - Required for light theme WCAG compliance\r\n// https://www.astrouxds.com/patterns/status-system/\r\n// =============================================================================\r\n\r\nconst STATUS_BORDER_COLORS: Record<StatusLevel, string> = {\r\n off: '#3c3e42',\r\n standby: '#285766',\r\n normal: '#005a00',\r\n caution: '#645600',\r\n serious: '#664618',\r\n critical: '#661102',\r\n};\r\n\r\n// =============================================================================\r\n// Status Shape SVG - Matches MonitoringIcon exactly\r\n// - Glow effect in dark mode for visibility\r\n// - Border in light mode for WCAG AA compliance\r\n// =============================================================================\r\n\r\ninterface StatusShapeProps {\r\n status: StatusLevel;\r\n size: number;\r\n color: string;\r\n borderColor?: string;\r\n glow?: boolean;\r\n}\r\n\r\nconst StatusShape = ({ status, size, color, borderColor, glow = true }: StatusShapeProps) => {\r\n const hasBorder = !!borderColor;\r\n const strokeWidth = hasBorder ? 1 : 0;\r\n const glowFilter = glow && !hasBorder ? `drop-shadow(0 0 3px ${color})` : undefined;\r\n \r\n const renderShape = () => {\r\n switch (status) {\r\n case 'off':\r\n return <circle cx=\"6\" cy=\"6\" r={hasBorder ? 2.5 : 3} fill={color} stroke={borderColor} strokeWidth={strokeWidth} />;\r\n case 'standby':\r\n return <circle cx=\"6\" cy=\"6\" r=\"3.5\" fill=\"none\" stroke={color} strokeWidth=\"2\" />;\r\n case 'normal':\r\n return (\r\n <circle\r\n cx=\"6\"\r\n cy=\"6\"\r\n r={hasBorder ? 4.5 : 5}\r\n fill={color}\r\n stroke={borderColor}\r\n strokeWidth={strokeWidth}\r\n />\r\n );\r\n case 'caution':\r\n return (\r\n <rect\r\n x={hasBorder ? 1.5 : 1}\r\n y={hasBorder ? 1.5 : 1}\r\n width={hasBorder ? 9 : 10}\r\n height={hasBorder ? 9 : 10}\r\n fill={color}\r\n stroke={borderColor}\r\n strokeWidth={strokeWidth}\r\n />\r\n );\r\n case 'serious':\r\n return (\r\n <polygon\r\n points=\"6,1 11,6 6,11 1,6\"\r\n fill={color}\r\n stroke={borderColor}\r\n strokeWidth={strokeWidth}\r\n />\r\n );\r\n case 'critical':\r\n return (\r\n <polygon\r\n points=\"6,11 1,2 11,2\"\r\n fill={color}\r\n stroke={borderColor}\r\n strokeWidth={strokeWidth}\r\n />\r\n );\r\n default:\r\n return null;\r\n }\r\n };\r\n \r\n return (\r\n <svg \r\n viewBox=\"0 0 12 12\" \r\n width={size} \r\n height={size}\r\n style={{ filter: glowFilter }}\r\n >\r\n {renderShape()}\r\n </svg>\r\n );\r\n};\r\n\r\n// =============================================================================\r\n// Icon with Status Badge - Enterprise-grade alignment\r\n// \r\n// Design System Principles (Apple HIG / Google Material / AstroUXDS):\r\n// - 4px-based spacing grid for visual harmony\r\n// - Explicit container sizing prevents layout shift\r\n// - Status badge positioned at corner, not extending beyond container bounds\r\n// - Proportional badge sizing: ~35% of icon size for optimal visibility\r\n// =============================================================================\r\n\r\ninterface IconWithBadgeProps {\r\n icon: AstroIconName;\r\n iconSize: number;\r\n iconColor: string;\r\n showBadge: boolean;\r\n status: StatusLevel;\r\n statusColor: string;\r\n isLightTheme?: boolean;\r\n}\r\n\r\nconst IconWithBadge = memo(function IconWithBadge({ \r\n icon, iconSize, iconColor, showBadge, status, statusColor, isLightTheme = false \r\n}: IconWithBadgeProps) {\r\n // ═══════════════════════════════════════════════════════════════════════════\r\n // STATUS BADGE CONTROLS\r\n // ═══════════════════════════════════════════════════════════════════════════\r\n // BADGE SIZE: Percentage of icon size (0.25 = 25%)\r\n const badgeSizeRatio = 0.30;\r\n const badgeSize = Math.max(7, Math.min(14, Math.round(iconSize * badgeSizeRatio)));\r\n \r\n // BADGE POSITION: Offset from top-left corner (negative = away from icon)\r\n const badgeOffsetX = -2; // px (negative = left, positive = right)\r\n const badgeOffsetY = -2; // px (negative = up, positive = down)\r\n \r\n // ═══════════════════════════════════════════════════════════════════════════\r\n const borderColor = isLightTheme ? STATUS_BORDER_COLORS[status] : undefined;\r\n const containerSize = iconSize;\r\n \r\n return (\r\n <div style={{\r\n position: 'relative',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n flexShrink: 0,\r\n width: containerSize,\r\n height: containerSize,\r\n }}>\r\n <AstroIcon name={icon} size={iconSize} color={iconColor} />\r\n {showBadge && (\r\n <span style={{\r\n position: 'absolute',\r\n top: badgeOffsetY,\r\n left: badgeOffsetX,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n }}>\r\n <StatusShape \r\n status={status} \r\n size={badgeSize} \r\n color={statusColor} \r\n borderColor={borderColor}\r\n glow={!isLightTheme} \r\n />\r\n </span>\r\n )}\r\n </div>\r\n );\r\n});\r\n\r\n// =============================================================================\r\n// DataValue Component\r\n// =============================================================================\r\n\r\nexport const DataValue = memo(function DataValue({\r\n property,\r\n label: labelProp,\r\n value,\r\n unit: unitProp,\r\n icon: iconProp,\r\n showIcon = true,\r\n thresholds: thresholdsProp,\r\n status: statusProp,\r\n showStatus = true,\r\n precision: precisionProp,\r\n variant = 'stacked',\r\n size = 'md',\r\n color: colorProp,\r\n className = '',\r\n style,\r\n onClick,\r\n tooltip,\r\n}: DataValueProps): React.ReactElement {\r\n const { tokens, mode } = useTheme();\r\n const isLightTheme = mode === 'light';\r\n\r\n // Merge property preset with explicit props\r\n const config = useMemo((): PropertyConfig => {\r\n const preset = property ? getPropertyConfig(property) : {\r\n label: labelProp || 'Value',\r\n icon: iconProp || 'info',\r\n unit: unitProp,\r\n precision: precisionProp,\r\n thresholds: thresholdsProp,\r\n };\r\n\r\n return {\r\n ...preset,\r\n ...(labelProp !== undefined && { label: labelProp }),\r\n ...(iconProp !== undefined && { icon: iconProp }),\r\n ...(unitProp !== undefined && { unit: unitProp }),\r\n ...(precisionProp !== undefined && { precision: precisionProp }),\r\n ...(thresholdsProp !== undefined && { thresholds: thresholdsProp }),\r\n };\r\n }, [property, labelProp, iconProp, unitProp, precisionProp, thresholdsProp]);\r\n\r\n // Derive or use explicit status\r\n const status = useMemo((): StatusLevel => {\r\n if (statusProp) return statusProp;\r\n if (typeof value === 'number') {\r\n return deriveStatus(value, config.thresholds);\r\n }\r\n return 'normal';\r\n }, [statusProp, value, config.thresholds]);\r\n\r\n // Format the value\r\n const formattedValue = useMemo((): string => {\r\n if (value === undefined || value === null) return '--';\r\n if (typeof value === 'string') return value;\r\n return formatPropertyValue(value, config);\r\n }, [value, config]);\r\n\r\n // Get status color\r\n const statusColor = tokens.colors.status[status] || tokens.colors.status.off;\r\n const valueColor = colorProp || (showStatus && status !== 'normal' ? statusColor : tokens.colors.text.primary);\r\n\r\n // ==========================================================================\r\n // Size Configuration - Uses theme tokens for consistency with other cards\r\n // ==========================================================================\r\n // Typography uses theme tokens to match SpacecraftCard's DataCell\r\n // fontSize.xs = 10px (0.625rem), fontSize.sm = 12px (0.75rem)\r\n const sizeConfig = {\r\n sm: { \r\n iconSize: 16, \r\n labelSize: tokens.typography.fontSize.xs, // 10px - matches DataCell\r\n valueSize: tokens.typography.fontSize.sm, // 12px - matches DataCell\r\n lineHeight: 1.4,\r\n gap: 6, // Increased from 4px to 6px for better icon separation with larger icons\r\n rowGap: 2, // 2px between label and value\r\n },\r\n md: { \r\n iconSize: 20, \r\n labelSize: tokens.typography.fontSize.xs, // 10px\r\n valueSize: tokens.typography.fontSize.base, // 14px\r\n lineHeight: 1.4,\r\n gap: 10, // Increased from 8px to 10px\r\n rowGap: 2, // 2px between label and value\r\n },\r\n lg: { \r\n iconSize: 24, \r\n labelSize: tokens.typography.fontSize.sm, // 12px\r\n valueSize: tokens.typography.fontSize.md, // 14px\r\n lineHeight: 1.4,\r\n gap: 14, // Increased from 12px to 14px\r\n rowGap: 4, // 4px between label and value\r\n },\r\n }[size];\r\n\r\n // ==========================================================================\r\n // Render Based on Variant\r\n // \r\n // Layout principles:\r\n // - Baseline alignment for inline text\r\n // - Consistent icon-to-text spacing\r\n // - Symmetrical padding\r\n // - Tabular numbers for value alignment in grids\r\n // ==========================================================================\r\n const renderContent = () => {\r\n // Determine if status indicator should show\r\n const hasStatus = showStatus && status !== 'normal' && status !== 'off';\r\n \r\n switch (variant) {\r\n // ======================================================================\r\n // INLINE: Icon + Label ... Value (space-between)\r\n // Best for: Key-value pairs in tables or lists\r\n // ======================================================================\r\n case 'inline': {\r\n return (\r\n <div style={{ \r\n display: 'flex', \r\n alignItems: 'center', \r\n justifyContent: 'space-between', \r\n gap: sizeConfig.gap * 2,\r\n minHeight: sizeConfig.iconSize, // Ensure consistent row height\r\n }}>\r\n <div style={{ \r\n display: 'flex', \r\n alignItems: 'center', \r\n gap: sizeConfig.gap,\r\n }}>\r\n {showIcon && config.icon && (\r\n <IconWithBadge\r\n icon={config.icon}\r\n iconSize={sizeConfig.iconSize}\r\n iconColor={hasStatus ? statusColor : tokens.colors.text.tertiary}\r\n showBadge={hasStatus}\r\n status={status}\r\n statusColor={statusColor}\r\n isLightTheme={isLightTheme}\r\n />\r\n )}\r\n <span style={{\r\n fontSize: sizeConfig.labelSize,\r\n lineHeight: sizeConfig.lineHeight,\r\n color: tokens.colors.text.secondary,\r\n fontWeight: 500,\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n }}>\r\n {config.label}\r\n </span>\r\n </div>\r\n <span style={{\r\n fontSize: sizeConfig.valueSize,\r\n lineHeight: sizeConfig.lineHeight,\r\n fontWeight: 500, // Match SpacecraftCard DataCell\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n fontVariantNumeric: 'tabular-nums',\r\n color: valueColor,\r\n }}>\r\n {formattedValue}\r\n </span>\r\n </div>\r\n );\r\n }\r\n\r\n // ======================================================================\r\n // COMPACT: Icon + Value (horizontal, no label)\r\n // Best for: Dense displays, status bars, compact dashboards\r\n // ======================================================================\r\n case 'compact': {\r\n return (\r\n <div style={{ \r\n display: 'flex', \r\n alignItems: 'center', \r\n gap: sizeConfig.gap,\r\n minHeight: sizeConfig.iconSize,\r\n }}>\r\n {showIcon && config.icon && (\r\n <IconWithBadge\r\n icon={config.icon}\r\n iconSize={sizeConfig.iconSize}\r\n iconColor={hasStatus ? statusColor : tokens.colors.text.tertiary}\r\n showBadge={hasStatus}\r\n status={status}\r\n statusColor={statusColor}\r\n isLightTheme={isLightTheme}\r\n />\r\n )}\r\n <span style={{\r\n fontSize: sizeConfig.valueSize,\r\n lineHeight: sizeConfig.lineHeight,\r\n fontWeight: 500, // Match SpacecraftCard DataCell\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n fontVariantNumeric: 'tabular-nums',\r\n color: valueColor,\r\n }}>\r\n {formattedValue}\r\n </span>\r\n </div>\r\n );\r\n }\r\n\r\n // ======================================================================\r\n // MINIMAL: Value only (no icon, no label)\r\n // Best for: Inline values, table cells\r\n // ======================================================================\r\n case 'minimal':\r\n return (\r\n <span style={{\r\n fontSize: sizeConfig.valueSize,\r\n lineHeight: sizeConfig.lineHeight,\r\n fontWeight: 500, // Match SpacecraftCard DataCell\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n fontVariantNumeric: 'tabular-nums',\r\n color: valueColor,\r\n }}>\r\n {formattedValue}\r\n </span>\r\n );\r\n\r\n // ======================================================================\r\n // CARD: Icon EXACTLY matches 2-row text height\r\n // Best for: Dashboard tiles, prominent metrics\r\n // ======================================================================\r\n case 'card': {\r\n // ═══════════════════════════════════════════════════════════════════════════\r\n // 2-ROW LAYOUT CONTROLS (card variant)\r\n // ═══════════════════════════════════════════════════════════════════════════\r\n // ICON MULTIPLIER: Scale factor (0.5 = half, 1.0 = match text height, 1.5 = 150%)\r\n const iconMultiplier = 0.7;\r\n \r\n // GAP: Pixels between icon and text column\r\n const iconToTextGap = 4; // px\r\n \r\n // ═══════════════════════════════════════════════════════════════════════════\r\n const baseSize = { sm: 26, md: 32, lg: 40 }[size];\r\n const iconSize = Math.round(baseSize * iconMultiplier);\r\n \r\n return (\r\n <div style={{ \r\n display: 'flex', \r\n alignItems: 'center',\r\n gap: iconToTextGap,\r\n }}>\r\n {showIcon && config.icon && (\r\n <IconWithBadge\r\n icon={config.icon}\r\n iconSize={iconSize}\r\n iconColor={hasStatus ? statusColor : tokens.colors.text.tertiary}\r\n showBadge={hasStatus}\r\n status={status}\r\n statusColor={statusColor}\r\n isLightTheme={isLightTheme}\r\n />\r\n )}\r\n <div style={{ \r\n display: 'flex', \r\n flexDirection: 'column', \r\n justifyContent: 'space-between', // Distribute label/value to edges\r\n gap: sizeConfig.rowGap, \r\n minWidth: 0,\r\n }}>\r\n <span style={{\r\n fontSize: sizeConfig.labelSize,\r\n lineHeight: sizeConfig.lineHeight,\r\n color: tokens.colors.text.secondary,\r\n fontWeight: 500,\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n }}>\r\n {config.label}\r\n </span>\r\n <span style={{\r\n fontSize: sizeConfig.valueSize,\r\n lineHeight: sizeConfig.lineHeight,\r\n fontWeight: 500, // Match SpacecraftCard DataCell\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n fontVariantNumeric: 'tabular-nums',\r\n color: valueColor,\r\n }}>\r\n {formattedValue}\r\n </span>\r\n </div>\r\n </div>\r\n );\r\n }\r\n\r\n // ======================================================================\r\n // BANNER: Label on top, icon left-aligned + value right-aligned below\r\n // Best for: TopBar metrics, KPI tiles where heading spans full width\r\n // ======================================================================\r\n case 'banner': {\r\n const bannerIconSize = { sm: 18, md: 22, lg: 28 }[size];\r\n return (\r\n <div style={{ display: 'flex', flexDirection: 'column', gap: sizeConfig.rowGap, minWidth: 0, width: '100%' }}>\r\n <span style={{\r\n fontSize: sizeConfig.labelSize,\r\n lineHeight: sizeConfig.lineHeight,\r\n color: tokens.colors.text.secondary,\r\n fontWeight: 500,\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n textTransform: 'uppercase' as const,\r\n letterSpacing: '0.5px',\r\n }}>\r\n {config.label}\r\n </span>\r\n <div style={{\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'flex-start',\r\n width: '100%',\r\n gap: 6,\r\n minWidth: 0,\r\n }}>\r\n {showIcon && config.icon && (\r\n <IconWithBadge\r\n icon={config.icon}\r\n iconSize={bannerIconSize}\r\n iconColor={hasStatus ? statusColor : tokens.colors.text.tertiary}\r\n showBadge={hasStatus}\r\n status={status}\r\n statusColor={statusColor}\r\n isLightTheme={isLightTheme}\r\n />\r\n )}\r\n <span style={{\r\n fontSize: sizeConfig.valueSize,\r\n lineHeight: sizeConfig.lineHeight,\r\n fontWeight: 600,\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n fontVariantNumeric: 'tabular-nums',\r\n color: valueColor,\r\n textAlign: 'left',\r\n }}>\r\n {formattedValue}\r\n </span>\r\n </div>\r\n </div>\r\n );\r\n }\r\n\r\n // ======================================================================\r\n // STAT: icon + value (status-coloured) on top, uppercase mono label below\r\n // Best for: status-header / top-bar telemetry chips (Asset, Link,\r\n // Battery, Storage, Ground). Unlike the other variants, the value AND\r\n // icon take the status colour even when `normal` — a nominal chip\r\n // renders green — matching the SpaceRange status-header spec.\r\n // ======================================================================\r\n case 'stat': {\r\n const statSize = {\r\n sm: { icon: 13, value: 13, label: 9.5 },\r\n md: { icon: 15, value: 14, label: 10 },\r\n lg: { icon: 18, value: 16, label: 11 },\r\n }[size];\r\n // colorProp wins; otherwise the status colour drives value + icon\r\n // (including `normal`, so nominal telemetry reads green per spec).\r\n const statColor = colorProp || statusColor;\r\n return (\r\n <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 2 }}>\r\n <div style={{\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: 6,\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n fontSize: statSize.value,\r\n fontWeight: 600,\r\n color: statColor,\r\n fontVariantNumeric: 'tabular-nums',\r\n letterSpacing: '0.02em',\r\n lineHeight: 1.1,\r\n }}>\r\n {showIcon && config.icon && (\r\n <AstroIcon name={config.icon} size={statSize.icon} color={statColor} />\r\n )}\r\n <span>{formattedValue}</span>\r\n </div>\r\n <span style={{\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n fontSize: statSize.label,\r\n letterSpacing: '0.06em',\r\n textTransform: 'uppercase',\r\n color: tokens.colors.text.tertiary,\r\n lineHeight: 1.1,\r\n }}>\r\n {config.label}\r\n </span>\r\n </div>\r\n );\r\n }\r\n\r\n // ======================================================================\r\n // STACKED (default): Icon EXACTLY matches 2-row text height\r\n // Best for: General telemetry displays, standard data values\r\n // ======================================================================\r\n case 'stacked':\r\n default: {\r\n // ═══════════════════════════════════════════════════════════════════════════\r\n // 2-ROW LAYOUT CONTROLS (stacked variant)\r\n // ═══════════════════════════════════════════════════════════════════════════\r\n // ICON MULTIPLIER: Scale factor (0.5 = half, 1.0 = match text height, 1.5 = 150%)\r\n const iconMultiplier = 0.7;\r\n \r\n // GAP: Pixels between icon and text column\r\n const iconToTextGap = 4; // px\r\n \r\n // ═══════════════════════════════════════════════════════════════════════════\r\n const baseSize = { sm: 26, md: 32, lg: 40 }[size];\r\n const iconSize = Math.round(baseSize * iconMultiplier);\r\n \r\n return (\r\n <div style={{ \r\n display: 'flex', \r\n alignItems: 'center',\r\n gap: iconToTextGap,\r\n }}>\r\n {showIcon && config.icon && (\r\n <IconWithBadge\r\n icon={config.icon}\r\n iconSize={iconSize}\r\n iconColor={hasStatus ? statusColor : tokens.colors.text.tertiary}\r\n showBadge={hasStatus}\r\n status={status}\r\n statusColor={statusColor}\r\n isLightTheme={isLightTheme}\r\n />\r\n )}\r\n <div style={{ \r\n display: 'flex', \r\n flexDirection: 'column', \r\n justifyContent: 'space-between', // Distribute label/value to edges\r\n gap: sizeConfig.rowGap, \r\n minWidth: 0,\r\n }}>\r\n <span style={{\r\n fontSize: sizeConfig.labelSize,\r\n lineHeight: sizeConfig.lineHeight,\r\n color: tokens.colors.text.secondary,\r\n fontWeight: 500,\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n }}>\r\n {config.label}\r\n </span>\r\n <span style={{\r\n fontSize: sizeConfig.valueSize,\r\n lineHeight: sizeConfig.lineHeight,\r\n fontWeight: 500, // Match SpacecraftCard DataCell\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n fontVariantNumeric: 'tabular-nums',\r\n color: valueColor,\r\n }}>\r\n {formattedValue}\r\n </span>\r\n </div>\r\n </div>\r\n );\r\n }\r\n }\r\n };\r\n\r\n return (\r\n <div\r\n className={`zendir-data-value zendir-data-value--${variant} zendir-data-value--${size} ${className}`}\r\n style={{\r\n cursor: onClick ? 'pointer' : 'default',\r\n ...(variant === 'banner' ? { flex: 1, minWidth: 0 } : {}),\r\n ...style,\r\n }}\r\n onClick={onClick}\r\n title={tooltip || config.description}\r\n role={onClick ? 'button' : undefined}\r\n tabIndex={onClick ? 0 : undefined}\r\n onKeyDown={onClick ? (e) => e.key === 'Enter' && onClick() : undefined}\r\n >\r\n {renderContent()}\r\n </div>\r\n );\r\n});\r\n\r\n// =============================================================================\r\n// DataValueGroup Component\r\n// =============================================================================\r\n\r\nexport interface DataValueGroupProps {\r\n /** Title for the group */\r\n title?: string;\r\n /** Icon for the title */\r\n icon?: AstroIconName;\r\n /** Layout direction */\r\n direction?: 'row' | 'column' | 'grid';\r\n /** Number of columns for grid layout */\r\n columns?: number;\r\n /** Gap between items */\r\n gap?: 'sm' | 'md' | 'lg';\r\n /** Children DataValue components */\r\n children: React.ReactNode;\r\n /** Additional CSS class */\r\n className?: string;\r\n /** Custom inline styles */\r\n style?: React.CSSProperties;\r\n}\r\n\r\nexport const DataValueGroup = memo(function DataValueGroup({\r\n title,\r\n icon,\r\n direction = 'grid',\r\n columns = 2,\r\n gap = 'md',\r\n children,\r\n className = '',\r\n style,\r\n}: DataValueGroupProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n\r\n // Gap sizing on 4px grid\r\n const gapSize = {\r\n sm: 8, // 2 × 4px\r\n md: 16, // 4 × 4px\r\n lg: 24, // 6 × 4px\r\n }[gap];\r\n\r\n const layoutStyles: Record<typeof direction, React.CSSProperties> = {\r\n row: { \r\n display: 'flex', \r\n flexDirection: 'row' as const, \r\n flexWrap: 'wrap' as const, \r\n gap: gapSize,\r\n alignItems: 'flex-start',\r\n },\r\n column: { \r\n display: 'flex', \r\n flexDirection: 'column' as const, \r\n gap: gapSize,\r\n },\r\n grid: { \r\n display: 'grid', \r\n gridTemplateColumns: `repeat(${columns}, 1fr)`, \r\n gap: gapSize,\r\n alignItems: 'start',\r\n },\r\n };\r\n const layoutStyle = layoutStyles[direction];\r\n\r\n return (\r\n <div className={`zendir-data-value-group ${className}`} style={style}>\r\n {title && (\r\n <div style={{\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: 6, // 6px gap for tight header\r\n marginBottom: 12, // 3 × 4px\r\n paddingBottom: 8, // 2 × 4px\r\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\r\n }}>\r\n {icon && (\r\n <AstroIcon \r\n name={icon} \r\n size={14} \r\n color={tokens.colors.text.tertiary}\r\n style={{ flexShrink: 0 }}\r\n />\r\n )}\r\n <span style={{\r\n fontSize: '12px',\r\n fontWeight: 500,\r\n color: tokens.colors.text.secondary,\r\n textTransform: 'uppercase',\r\n letterSpacing: '0.5px',\r\n }}>\r\n {title}\r\n </span>\r\n </div>\r\n )}\r\n <div style={layoutStyle}>\r\n {children}\r\n </div>\r\n </div>\r\n );\r\n});\r\n\r\nexport default DataValue;\r\n"],"names":["IconWithBadge","DataValue","DataValueGroup"],"mappings":"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"}
@@ -12,6 +12,13 @@ export interface SideNavProps {
12
12
  width?: number;
13
13
  /** Collapsed width in pixels (default 64) */
14
14
  collapsedWidth?: number;
15
+ /**
16
+ * Row density. `'compact'` tightens row padding, drops the left accent bar
17
+ * in favour of a pill-style (rounded, bg-tinted) active row, and shrinks the
18
+ * label — matching the v2.3 operator/admin secondary-nav mock. Default
19
+ * `'default'`. (TODO: row inset + mono section headings for full mock parity.)
20
+ */
21
+ density?: 'default' | 'compact';
15
22
  /**
16
23
  * Mobile viewport behavior.
17
24
  * - `'drawer'` (default): hamburger button with slide-out overlay drawer.
@@ -4,6 +4,15 @@ import { useTheme } from "../../theme/ThemeProvider.js";
4
4
  import { safeAccentText } from "../../utils/index.js";
5
5
  import { Icon } from "../display/Icon.js";
6
6
  const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
7
+ const SIDENAV_REVEAL_STYLE_ID = "zendir-sidenav-reveal";
8
+ function ensureSideNavReveal() {
9
+ if (typeof document === "undefined") return;
10
+ if (document.getElementById(SIDENAV_REVEAL_STYLE_ID)) return;
11
+ const el = document.createElement("style");
12
+ el.id = SIDENAV_REVEAL_STYLE_ID;
13
+ el.textContent = '@keyframes zsn-reveal{from{opacity:0;transform:translateX(-6px)}to{opacity:1;transform:translateX(0)}}.zsn-reveal{animation:zsn-reveal 220ms cubic-bezier(0.22,1,0.36,1)}.zsn-label{transition:opacity 220ms cubic-bezier(0.22,1,0.36,1),transform 220ms cubic-bezier(0.22,1,0.36,1)}.zsn-label[data-collapsed="true"]{opacity:0;transform:translateX(-6px);pointer-events:none}@media (prefers-reduced-motion: reduce){.zsn-reveal,.zsn-label{animation:none;transition:none}}';
14
+ document.head.appendChild(el);
15
+ }
7
16
  const SIDENAV_STATUS_COLORS = {
8
17
  off: "#a4abb6",
9
18
  standby: "#2dccff",
@@ -44,6 +53,7 @@ const SideNavContext = createContext({
44
53
  collapsed: false,
45
54
  mobileOpen: false,
46
55
  mode: "desktop",
56
+ density: "default",
47
57
  showCollapseToggle: true,
48
58
  setMobileOpen: () => {
49
59
  },
@@ -56,6 +66,7 @@ const SideNavRoot = memo(function SideNav2({
56
66
  showCollapseToggle = true,
57
67
  width = 260,
58
68
  collapsedWidth = 64,
69
+ density = "default",
59
70
  mobileVariant = "drawer",
60
71
  children,
61
72
  style
@@ -79,6 +90,9 @@ const SideNavRoot = memo(function SideNav2({
79
90
  cancelAnimationFrame(rafId);
80
91
  };
81
92
  }, []);
93
+ useIsomorphicLayoutEffect(() => {
94
+ ensureSideNavReveal();
95
+ }, []);
82
96
  const effectiveMode = mobileVariant === "collapsed" && viewMode === "mobile" ? "tablet" : viewMode;
83
97
  useEffect(() => {
84
98
  setUserCollapsed(null);
@@ -110,7 +124,7 @@ const SideNavRoot = memo(function SideNav2({
110
124
  height: "100%",
111
125
  backgroundColor: isTransparentTheme ? "rgba(15, 12, 30, 0.7)" : tokens.colors.background.surface,
112
126
  borderRight: `1px solid ${tokens.colors.border.muted}`,
113
- transition: "width 0.25s ease, transform 0.25s ease",
127
+ transition: "width 0.3s cubic-bezier(0.22, 1, 0.36, 1), transform 0.25s ease",
114
128
  overflowX: "hidden",
115
129
  overflowY: "auto",
116
130
  flexShrink: 0,
@@ -121,7 +135,7 @@ const SideNavRoot = memo(function SideNav2({
121
135
  } : {},
122
136
  ...style
123
137
  };
124
- const contextValue = { collapsed: isCollapsed, mobileOpen, mode: effectiveMode, showCollapseToggle, setMobileOpen, toggleCollapse: handleToggleCollapse };
138
+ const contextValue = { collapsed: isCollapsed, mobileOpen, mode: effectiveMode, density, showCollapseToggle, setMobileOpen, toggleCollapse: handleToggleCollapse };
125
139
  if (isMobile) {
126
140
  return /* @__PURE__ */ jsxs(SideNavContext.Provider, { value: contextValue, children: [
127
141
  /* @__PURE__ */ jsx(
@@ -264,7 +278,7 @@ const SideNavHeader = memo(function SideNavHeader2({
264
278
  warning: tokens.colors.status.caution,
265
279
  caution: tokens.colors.status.serious
266
280
  };
267
- const metaBlock = !collapsed && hasMeta ? /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0, width: "100%" }, children: [
281
+ const metaBlock = !collapsed && hasMeta ? /* @__PURE__ */ jsxs("div", { className: "zsn-reveal", style: { flex: 1, minWidth: 0, width: "100%" }, children: [
268
282
  title ? /* @__PURE__ */ jsx("div", { style: {
269
283
  fontSize: tokens.typography.fontSize.sm,
270
284
  fontWeight: tokens.typography.fontWeight.bold,
@@ -392,7 +406,8 @@ const SideNavItem = memo(function SideNavItem2({
392
406
  suffix
393
407
  }) {
394
408
  const { tokens } = useTheme();
395
- const { collapsed, setMobileOpen } = useContext(SideNavContext);
409
+ const { collapsed, setMobileOpen, density } = useContext(SideNavContext);
410
+ const compact = density === "compact" && !collapsed;
396
411
  const [isHovered, setIsHovered] = useState(false);
397
412
  const handleClick = useCallback((e) => {
398
413
  if (disabled) return;
@@ -410,11 +425,17 @@ const SideNavItem = memo(function SideNavItem2({
410
425
  display: "flex",
411
426
  alignItems: hasDescription ? "flex-start" : "center",
412
427
  gap: tokens.spacing.sm,
413
- padding: collapsed ? `${tokens.spacing.sm} 0` : hasDescription ? `10px 16px 10px 12px` : `${tokens.spacing.sm} 16px ${tokens.spacing.sm} 12px`,
414
- justifyContent: collapsed ? "center" : "flex-start",
428
+ // Collapsed keeps the SAME 12px left inset as the expanded row, so the
429
+ // icon holds its horizontal position when the rail expands — it doesn't
430
+ // jump from a centered slot to a left-aligned one. Size the collapsed
431
+ // width so that fixed inset reads as centered.
432
+ padding: collapsed ? `${tokens.spacing.sm} 12px` : compact ? "7px 8px" : hasDescription ? `10px 16px 10px 12px` : `${tokens.spacing.sm} 16px ${tokens.spacing.sm} 12px`,
433
+ // Compact: pill-style rounded row (mock parity). Default: square row.
434
+ borderRadius: compact ? tokens.borderRadius.md : 0,
435
+ justifyContent: "flex-start",
415
436
  color: active ? accentColor : disabled ? tokens.colors.text.tertiary : tokens.colors.text.secondary,
416
437
  backgroundColor: active ? `${accentColor}12` : isHovered && !disabled ? `${accentColor}08` : "transparent",
417
- fontSize: tokens.typography.fontSize.sm,
438
+ fontSize: compact ? tokens.typography.fontSize.xs : tokens.typography.fontSize.sm,
418
439
  fontWeight: active ? tokens.typography.fontWeight.semibold : tokens.typography.fontWeight.normal,
419
440
  fontFamily: tokens.typography.fontFamily.primary,
420
441
  cursor: disabled ? "not-allowed" : "pointer",
@@ -422,7 +443,9 @@ const SideNavItem = memo(function SideNavItem2({
422
443
  borderTop: "none",
423
444
  borderRight: "none",
424
445
  borderBottom: "none",
425
- borderLeft: active ? `3px solid ${accentColor}` : "3px solid transparent",
446
+ // Compact drops the left accent bar the active row is conveyed by the
447
+ // bg tint + accent text alone (mock parity). Default keeps the 3px bar.
448
+ borderLeft: compact ? "none" : active ? `3px solid ${accentColor}` : "3px solid transparent",
426
449
  outline: "none",
427
450
  width: "100%",
428
451
  boxSizing: "border-box",
@@ -448,76 +471,82 @@ const SideNavItem = memo(function SideNavItem2({
448
471
  }
449
472
  )
450
473
  ] }),
451
- !collapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
452
- /* @__PURE__ */ jsxs("span", { style: { flex: 1, minWidth: 0, overflow: "hidden" }, children: [
453
- /* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
454
- /* @__PURE__ */ jsx("span", { style: { whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: label }),
455
- tag && /* @__PURE__ */ jsx("span", { style: {
456
- fontSize: "0.6rem",
457
- fontWeight: tokens.typography.fontWeight.bold,
458
- color: tagColors.fg,
459
- backgroundColor: tagColors.bg,
460
- border: `1px solid ${tagColors.border}`,
461
- padding: "1px 5px",
462
- borderRadius: tokens.borderRadius.sm,
463
- textTransform: "uppercase",
464
- letterSpacing: "0.04em",
465
- flexShrink: 0,
466
- lineHeight: "1.3",
467
- whiteSpace: "nowrap"
468
- }, children: tag })
469
- ] }),
470
- description && /* @__PURE__ */ jsx("span", { style: {
471
- display: "block",
472
- fontSize: tokens.typography.fontSize.xxs,
473
- color: tokens.colors.text.tertiary,
474
- lineHeight: tokens.typography.lineHeight.normal,
475
- marginTop: "2px",
476
- whiteSpace: "nowrap",
477
- overflow: "hidden",
478
- textOverflow: "ellipsis",
479
- fontWeight: tokens.typography.fontWeight.normal
480
- }, children: description })
481
- ] }),
482
- (badge !== void 0 && badge > 0 || status || suffix || external) && /* @__PURE__ */ jsxs("span", { style: {
474
+ /* @__PURE__ */ jsxs(
475
+ "span",
476
+ {
477
+ className: "zsn-label",
478
+ "data-collapsed": collapsed ? "true" : "false",
479
+ style: { flex: 1, minWidth: 0, overflow: "hidden" },
480
+ children: [
481
+ /* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
482
+ /* @__PURE__ */ jsx("span", { style: { whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: label }),
483
+ tag && /* @__PURE__ */ jsx("span", { style: {
484
+ fontSize: "0.6rem",
485
+ fontWeight: tokens.typography.fontWeight.bold,
486
+ color: tagColors.fg,
487
+ backgroundColor: tagColors.bg,
488
+ border: `1px solid ${tagColors.border}`,
489
+ padding: "1px 5px",
490
+ borderRadius: tokens.borderRadius.sm,
491
+ textTransform: "uppercase",
492
+ letterSpacing: "0.04em",
493
+ flexShrink: 0,
494
+ lineHeight: "1.3",
495
+ whiteSpace: "nowrap"
496
+ }, children: tag })
497
+ ] }),
498
+ description && !collapsed && /* @__PURE__ */ jsx("span", { style: {
499
+ display: "block",
500
+ fontSize: tokens.typography.fontSize.xxs,
501
+ color: tokens.colors.text.tertiary,
502
+ lineHeight: tokens.typography.lineHeight.normal,
503
+ marginTop: "2px",
504
+ whiteSpace: "nowrap",
505
+ overflow: "hidden",
506
+ textOverflow: "ellipsis",
507
+ fontWeight: tokens.typography.fontWeight.normal
508
+ }, children: description })
509
+ ]
510
+ }
511
+ ),
512
+ !collapsed && (badge !== void 0 && badge > 0 || status || suffix || external) && /* @__PURE__ */ jsxs("span", { className: "zsn-reveal", style: {
513
+ display: "inline-flex",
514
+ alignItems: "center",
515
+ gap: 8,
516
+ flexShrink: 0,
517
+ marginTop: hasDescription ? "2px" : 0
518
+ }, children: [
519
+ badge !== void 0 && badge > 0 && /* @__PURE__ */ jsx("span", { style: {
520
+ fontSize: "0.65rem",
521
+ fontWeight: tokens.typography.fontWeight.bold,
522
+ color: "#fff",
523
+ backgroundColor: tokens.colors.status.critical,
524
+ borderRadius: tokens.borderRadius.full,
525
+ minWidth: "18px",
526
+ height: "18px",
483
527
  display: "inline-flex",
484
528
  alignItems: "center",
485
- gap: 8,
529
+ justifyContent: "center",
530
+ padding: "0 5px",
486
531
  flexShrink: 0,
487
- marginTop: hasDescription ? "2px" : 0
488
- }, children: [
489
- badge !== void 0 && badge > 0 && /* @__PURE__ */ jsx("span", { style: {
490
- fontSize: "0.65rem",
491
- fontWeight: tokens.typography.fontWeight.bold,
492
- color: "#fff",
493
- backgroundColor: tokens.colors.status.critical,
494
- borderRadius: tokens.borderRadius.full,
495
- minWidth: "18px",
496
- height: "18px",
497
- display: "inline-flex",
498
- alignItems: "center",
499
- justifyContent: "center",
500
- padding: "0 5px",
501
- flexShrink: 0,
502
- lineHeight: 1,
503
- boxSizing: "border-box"
504
- }, children: badge > 99 ? "99+" : badge }),
505
- status && /* @__PURE__ */ jsx(
506
- "span",
507
- {
508
- style: { display: "inline-flex", alignItems: "center", flexShrink: 0 },
509
- role: "status",
510
- "aria-label": `Status: ${statusLabel ?? status}`,
511
- title: statusLabel ?? status,
512
- children: /* @__PURE__ */ jsx(NavStatusShape, { status, size: 8 })
513
- }
514
- ),
515
- suffix && /* @__PURE__ */ jsx("span", { style: { flexShrink: 0, display: "inline-flex", alignItems: "center" }, children: suffix }),
516
- external && /* @__PURE__ */ jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", style: { flexShrink: 0, opacity: 0.5 }, children: [
517
- /* @__PURE__ */ jsx("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }),
518
- /* @__PURE__ */ jsx("polyline", { points: "15 3 21 3 21 9" }),
519
- /* @__PURE__ */ jsx("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
520
- ] })
532
+ lineHeight: 1,
533
+ boxSizing: "border-box"
534
+ }, children: badge > 99 ? "99+" : badge }),
535
+ status && /* @__PURE__ */ jsx(
536
+ "span",
537
+ {
538
+ style: { display: "inline-flex", alignItems: "center", flexShrink: 0 },
539
+ role: "status",
540
+ "aria-label": `Status: ${statusLabel ?? status}`,
541
+ title: statusLabel ?? status,
542
+ children: /* @__PURE__ */ jsx(NavStatusShape, { status, size: 8 })
543
+ }
544
+ ),
545
+ suffix && /* @__PURE__ */ jsx("span", { style: { flexShrink: 0, display: "inline-flex", alignItems: "center" }, children: suffix }),
546
+ external && /* @__PURE__ */ jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", style: { flexShrink: 0, opacity: 0.5 }, children: [
547
+ /* @__PURE__ */ jsx("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }),
548
+ /* @__PURE__ */ jsx("polyline", { points: "15 3 21 3 21 9" }),
549
+ /* @__PURE__ */ jsx("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
521
550
  ] })
522
551
  ] })
523
552
  ] });
@@ -541,7 +570,7 @@ const SideNavSection = memo(function SideNavSection2({
541
570
  const { tokens } = useTheme();
542
571
  const { collapsed } = useContext(SideNavContext);
543
572
  return /* @__PURE__ */ jsxs("div", { style: { marginTop: tokens.spacing.sm }, children: [
544
- title && !collapsed && /* @__PURE__ */ jsx("div", { style: {
573
+ title && !collapsed && /* @__PURE__ */ jsx("div", { className: "zsn-reveal", style: {
545
574
  padding: `${tokens.spacing.xs} 16px ${tokens.spacing.xs} 15px`,
546
575
  fontSize: tokens.typography.fontSize.xxs,
547
576
  fontWeight: tokens.typography.fontWeight.bold,
@@ -1 +1 @@
1
- {"version":3,"file":"SideNav.js","sources":["../../../../src/react/core/navigation/SideNav.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - SideNav Component\r\n * \r\n * Persistent sidebar navigation for operator dashboards. Compound component\r\n * pattern with Header, Item, Section, Divider, and Footer subcomponents.\r\n * \r\n * Responsive: Full sidebar on desktop, hamburger overlay on mobile.\r\n * \r\n * Astro UX Compliance:\r\n * - Persistent side navigation pattern (AstroUXDS Navigation)\r\n * - Status indicator integration\r\n * - Active state highlighting with accent color\r\n * - Keyboard navigation support\r\n * - Reduced motion support\r\n * \r\n * @example\r\n * ```tsx\r\n * <SideNav>\r\n * <SideNav.Header logo={<Icon name=\"satellite\" size={24} />} title=\"Space Range\" badge=\"Operator\" />\r\n * <SideNav.Section title=\"Operations\">\r\n * <SideNav.Item icon=\"controls\" label=\"Controls\" description=\"System command interface\" href=\"/controls\" active />\r\n * <SideNav.Item icon=\"telemetry\" label=\"Telemetry\" href=\"/telemetry\" badge={3} tag=\"LIVE\" tagVariant=\"success\" />\r\n * <SideNav.Item icon=\"images\" label=\"Images\" href=\"/images\" />\r\n * </SideNav.Section>\r\n * <SideNav.Divider />\r\n * <SideNav.Section title=\"Analysis\">\r\n * <SideNav.Item icon=\"chart\" label=\"Plots\" href=\"/plots\" />\r\n * <SideNav.Item icon=\"map\" label=\"Map\" href=\"/map\" />\r\n * </SideNav.Section>\r\n * <SideNav.Footer>\r\n * <SideNav.Item icon=\"settings\" label=\"Settings\" href=\"/settings\" />\r\n * </SideNav.Footer>\r\n * </SideNav>\r\n * ```\r\n */\r\n\r\nimport React, { memo, useState, createContext, useContext, useCallback, useEffect, useLayoutEffect } from 'react';\r\n\r\nconst useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;\r\nimport { useTheme } from '../../theme';\r\nimport { safeAccentText } from '../../utils';\r\nimport { Icon } from '../display/Icon';\r\nimport type { IconName } from '../display/Icon';\r\n\r\n// ─── Astro UX Status Shape ───────────────────────────────────────────────────\r\n\r\nconst SIDENAV_STATUS_COLORS: Record<string, string> = {\r\n off: '#a4abb6',\r\n standby: '#2dccff',\r\n normal: '#56f000',\r\n caution: '#fce83a',\r\n serious: '#ffb302',\r\n critical: '#ff3838',\r\n};\r\n\r\n/** Astro UX dual-coded status shape (color + geometry). */\r\nfunction NavStatusShape({ status, size = 8 }: { status: string; size?: number }) {\r\n const color = SIDENAV_STATUS_COLORS[status] ?? SIDENAV_STATUS_COLORS.off;\r\n const glow = `${color}50`;\r\n const style = { flexShrink: 0 as const, filter: `drop-shadow(0 0 3px ${glow})` };\r\n\r\n switch (status) {\r\n case 'caution':\r\n return <svg viewBox=\"0 0 12 12\" width={size} height={size} style={style} aria-hidden=\"true\"><rect x=\"1\" y=\"1\" width=\"10\" height=\"10\" fill={color} /></svg>;\r\n case 'serious':\r\n return <svg viewBox=\"0 0 12 12\" width={size} height={size} style={style} aria-hidden=\"true\"><polygon points=\"6,1 11,6 6,11 1,6\" fill={color} /></svg>;\r\n case 'critical':\r\n return <svg viewBox=\"0 0 12 12\" width={size} height={size} style={style} aria-hidden=\"true\"><polygon points=\"6,11 1,2 11,2\" fill={color} /></svg>;\r\n case 'standby':\r\n return <svg viewBox=\"0 0 12 12\" width={size} height={size} style={style} aria-hidden=\"true\"><circle cx=\"6\" cy=\"6\" r=\"3.5\" fill=\"none\" stroke={color} strokeWidth=\"2\" /></svg>;\r\n case 'off':\r\n return <svg viewBox=\"0 0 12 12\" width={size} height={size} style={style} aria-hidden=\"true\"><circle cx=\"6\" cy=\"6\" r=\"3\" fill={color} /></svg>;\r\n default: // normal\r\n return <svg viewBox=\"0 0 12 12\" width={size} height={size} style={style} aria-hidden=\"true\"><circle cx=\"6\" cy=\"6\" r=\"5\" fill={color} /></svg>;\r\n }\r\n}\r\n\r\n// ─── Responsive Breakpoints ──────────────────────────────────────────────────\r\n\r\nconst SIDENAV_BREAKPOINTS = {\r\n mobile: 768,\r\n tablet: 1024,\r\n} as const;\r\n\r\ntype SideNavMode = 'desktop' | 'tablet' | 'mobile';\r\n\r\nfunction getSideNavMode(width: number): SideNavMode {\r\n if (width < SIDENAV_BREAKPOINTS.mobile) return 'mobile';\r\n if (width < SIDENAV_BREAKPOINTS.tablet) return 'tablet';\r\n return 'desktop';\r\n}\r\n\r\n// ─── Context ─────────────────────────────────────────────────────────────────\r\n\r\ninterface SideNavContextValue {\r\n collapsed: boolean;\r\n mobileOpen: boolean;\r\n mode: SideNavMode;\r\n showCollapseToggle: boolean;\r\n setMobileOpen: (open: boolean) => void;\r\n toggleCollapse: () => void;\r\n}\r\n\r\nconst SideNavContext = createContext<SideNavContextValue>({\r\n collapsed: false,\r\n mobileOpen: false,\r\n mode: 'desktop',\r\n showCollapseToggle: true,\r\n setMobileOpen: () => {},\r\n toggleCollapse: () => {},\r\n});\r\n\r\n// ─── SideNav ─────────────────────────────────────────────────────────────────\r\n\r\nexport interface SideNavProps {\r\n /** Collapsed mode (icon-only). When omitted, auto-collapses on tablet viewports. */\r\n collapsed?: boolean;\r\n /** Callback when collapsed state changes (via toggle button or responsive breakpoint). */\r\n onCollapsedChange?: (collapsed: boolean) => void;\r\n /** Show a collapse/expand toggle button in the sidebar (default true). */\r\n showCollapseToggle?: boolean;\r\n /** Width in pixels (default 260) */\r\n width?: number;\r\n /** Collapsed width in pixels (default 64) */\r\n collapsedWidth?: number;\r\n /**\r\n * Mobile viewport behavior.\r\n * - `'drawer'` (default): hamburger button with slide-out overlay drawer.\r\n * - `'collapsed'`: persistent collapsed icon-only strip (same as tablet).\r\n */\r\n mobileVariant?: 'drawer' | 'collapsed';\r\n /** Children (SideNav.Header, SideNav.Item, SideNav.Section, SideNav.Footer) */\r\n children?: React.ReactNode;\r\n /** Custom style */\r\n style?: React.CSSProperties;\r\n}\r\n\r\nconst SideNavRoot = memo(function SideNav({\r\n collapsed,\r\n onCollapsedChange,\r\n showCollapseToggle = true,\r\n width = 260,\r\n collapsedWidth = 64,\r\n mobileVariant = 'drawer',\r\n children,\r\n style,\r\n}: SideNavProps): React.ReactElement {\r\n const { tokens, theme } = useTheme();\r\n const isTransparentTheme = theme === 'transparent' || theme === 'transparent-bold' || theme === 'transparent-minimal';\r\n const [mobileOpen, setMobileOpen] = useState(false);\r\n const [userCollapsed, setUserCollapsed] = useState<boolean | null>(null);\r\n \r\n // Initialize to 'desktop' to guarantee the first client render matches SSR\r\n // (where window is undefined), preventing a React hydration mismatch if the\r\n // client happens to be on a mobile/tablet width at initial load.\r\n const [viewMode, setViewMode] = useState<SideNavMode>('desktop');\r\n \r\n useIsomorphicLayoutEffect(() => {\r\n if (typeof window === 'undefined') return;\r\n\r\n // Synchronously correct the viewMode right after hydration but before paint\r\n setViewMode(getSideNavMode(window.innerWidth));\r\n\r\n let rafId: number;\r\n const handleResize = () => {\r\n cancelAnimationFrame(rafId);\r\n rafId = requestAnimationFrame(() => setViewMode(getSideNavMode(window.innerWidth)));\r\n };\r\n window.addEventListener('resize', handleResize, { passive: true });\r\n return () => {\r\n window.removeEventListener('resize', handleResize);\r\n cancelAnimationFrame(rafId);\r\n };\r\n }, []);\r\n\r\n // When mobileVariant='collapsed', promote mobile to tablet (collapsed inline strip)\r\n const effectiveMode = mobileVariant === 'collapsed' && viewMode === 'mobile' ? 'tablet' : viewMode;\r\n\r\n // Reset user toggle when crossing breakpoints\r\n useEffect(() => {\r\n setUserCollapsed(null);\r\n }, [effectiveMode]);\r\n\r\n // Close mobile drawer when switching away from mobile\r\n useEffect(() => {\r\n if (effectiveMode !== 'mobile' && mobileOpen) setMobileOpen(false);\r\n }, [effectiveMode, mobileOpen]);\r\n \r\n // Close mobile nav on escape\r\n useEffect(() => {\r\n if (!mobileOpen) return;\r\n const handleEsc = (e: KeyboardEvent) => {\r\n if (e.key === 'Escape') setMobileOpen(false);\r\n };\r\n document.addEventListener('keydown', handleEsc);\r\n return () => document.removeEventListener('keydown', handleEsc);\r\n }, [mobileOpen]);\r\n\r\n // Resolve collapsed: explicit prop > user toggle > auto (tablet = collapsed)\r\n const autoCollapsed = effectiveMode === 'tablet';\r\n const isCollapsed = collapsed !== undefined\r\n ? collapsed\r\n : userCollapsed !== null\r\n ? userCollapsed\r\n : autoCollapsed;\r\n const isMobile = effectiveMode === 'mobile';\r\n const navWidth = isCollapsed ? collapsedWidth : width;\r\n\r\n const handleToggleCollapse = useCallback(() => {\r\n const next = !isCollapsed;\r\n setUserCollapsed(next);\r\n onCollapsedChange?.(next);\r\n }, [isCollapsed, onCollapsedChange]);\r\n \r\n const navStyle: React.CSSProperties = {\r\n display: 'flex',\r\n flexDirection: 'column',\r\n width: isMobile ? 280 : navWidth,\r\n height: '100%',\r\n backgroundColor: isTransparentTheme\r\n ? 'rgba(15, 12, 30, 0.7)'\r\n : tokens.colors.background.surface,\r\n borderRight: `1px solid ${tokens.colors.border.muted}`,\r\n transition: 'width 0.25s ease, transform 0.25s ease',\r\n overflowX: 'hidden',\r\n overflowY: 'auto',\r\n flexShrink: 0,\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n ...(isTransparentTheme ? {\r\n backdropFilter: 'blur(16px)',\r\n WebkitBackdropFilter: 'blur(16px)',\r\n } : {}),\r\n ...style,\r\n };\r\n \r\n const contextValue: SideNavContextValue = { collapsed: isCollapsed, mobileOpen, mode: effectiveMode, showCollapseToggle, setMobileOpen, toggleCollapse: handleToggleCollapse };\r\n \r\n // Mobile: hamburger button + overlay drawer\r\n if (isMobile) {\r\n return (\r\n <SideNavContext.Provider value={contextValue}>\r\n {/* Hamburger button */}\r\n <button\r\n aria-label=\"Open navigation\"\r\n onClick={() => setMobileOpen(true)}\r\n style={{\r\n position: 'fixed',\r\n top: 6,\r\n left: 6,\r\n zIndex: 1001,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n width: 32,\r\n height: 32,\r\n border: 'none',\r\n borderRadius: tokens.borderRadius.sm,\r\n backgroundColor: 'transparent',\r\n color: tokens.colors.text.primary,\r\n cursor: 'pointer',\r\n padding: 0,\r\n transition: tokens.animation.fast,\r\n }}\r\n >\r\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\">\r\n <line x1=\"3\" y1=\"6\" x2=\"21\" y2=\"6\" />\r\n <line x1=\"3\" y1=\"12\" x2=\"21\" y2=\"12\" />\r\n <line x1=\"3\" y1=\"18\" x2=\"21\" y2=\"18\" />\r\n </svg>\r\n </button>\r\n \r\n {/* Overlay */}\r\n <div\r\n aria-hidden=\"true\"\r\n style={{\r\n position: 'fixed',\r\n inset: 0,\r\n zIndex: 1002,\r\n backgroundColor: `${tokens.colors.background.base}99`,\r\n backdropFilter: 'blur(4px)',\r\n opacity: mobileOpen ? 1 : 0,\r\n pointerEvents: mobileOpen ? 'auto' : 'none',\r\n transition: 'opacity 0.25s ease',\r\n }}\r\n onClick={() => setMobileOpen(false)}\r\n />\r\n \r\n {/* Slide-out nav */}\r\n <nav\r\n role=\"navigation\"\r\n aria-label=\"Main navigation\"\r\n style={{\r\n ...navStyle,\r\n position: 'fixed',\r\n top: 0,\r\n left: 0,\r\n zIndex: 1003,\r\n transform: mobileOpen ? 'translateX(0)' : 'translateX(-100%)',\r\n boxShadow: mobileOpen ? tokens.shadows.xl : 'none',\r\n }}\r\n >\r\n {children}\r\n </nav>\r\n </SideNavContext.Provider>\r\n );\r\n }\r\n \r\n return (\r\n <SideNavContext.Provider value={contextValue}>\r\n <nav role=\"navigation\" aria-label=\"Main navigation\" style={navStyle}>\r\n {children}\r\n </nav>\r\n </SideNavContext.Provider>\r\n );\r\n});\r\n\r\n// ─── Header ──────────────────────────────────────────────────────────────────\r\n\r\n/** Default height for the logo row so dashboard / operator / other apps align the same mark. */\r\nexport const SIDENAV_HEADER_LOGO_SLOT_HEIGHT_PX = 44;\r\n\r\nexport interface SideNavHeaderProps {\r\n /** Logo element (Icon, image, or ReactNode) */\r\n logo?: React.ReactNode;\r\n /** Compact logo shown when sidebar is collapsed or on tablet (e.g., just the icon mark) */\r\n collapsedLogo?: React.ReactNode;\r\n /** App title */\r\n title?: string;\r\n /** Subtitle or version */\r\n subtitle?: string;\r\n /** Role badge (e.g., \"Operator\", \"Admin\") */\r\n badge?: string;\r\n /** Badge variant for color */\r\n badgeVariant?: 'info' | 'success' | 'warning' | 'caution';\r\n /**\r\n * Fixed height (px) for the top logo band. Title, subtitle, and badge render below this band\r\n * so different logo assets still line up across apps (dashboard vs operator).\r\n */\r\n logoSlotHeight?: number;\r\n /** Children override (advanced: replaces default header content entirely) */\r\n children?: React.ReactNode;\r\n}\r\n\r\n/** Chevron toggle button for collapsing/expanding the sidebar. */\r\nfunction CollapseToggleButton() {\r\n const { tokens } = useTheme();\r\n const { collapsed, toggleCollapse } = useContext(SideNavContext);\r\n const [hovered, setHovered] = useState(false);\r\n\r\n return (\r\n <button\r\n type=\"button\"\r\n aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}\r\n onClick={toggleCollapse}\r\n onMouseEnter={() => setHovered(true)}\r\n onMouseLeave={() => setHovered(false)}\r\n style={{\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n width: 28,\r\n height: 28,\r\n border: `1px solid ${hovered ? tokens.colors.border.focus : tokens.colors.border.muted}`,\r\n borderRadius: tokens.borderRadius.md,\r\n backgroundColor: hovered ? `${tokens.colors.accent.primary}15` : 'transparent',\r\n color: hovered ? tokens.colors.accent.primary : tokens.colors.text.tertiary,\r\n cursor: 'pointer',\r\n flexShrink: 0,\r\n padding: 0,\r\n transition: tokens.animation.fast,\r\n outline: 'none',\r\n }}\r\n >\r\n <svg\r\n width=\"14\"\r\n height=\"14\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n style={{\r\n transition: 'transform 0.25s ease',\r\n transform: collapsed ? 'rotate(180deg)' : 'rotate(0deg)',\r\n }}\r\n >\r\n <polyline points=\"15 18 9 12 15 6\" />\r\n </svg>\r\n </button>\r\n );\r\n}\r\n\r\nconst SideNavHeader = memo(function SideNavHeader({\r\n logo,\r\n collapsedLogo,\r\n title,\r\n subtitle,\r\n badge,\r\n badgeVariant = 'info',\r\n logoSlotHeight = SIDENAV_HEADER_LOGO_SLOT_HEIGHT_PX,\r\n children,\r\n}: SideNavHeaderProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n const { collapsed, mode, showCollapseToggle, toggleCollapse } = useContext(SideNavContext);\r\n const displayLogo = collapsed && collapsedLogo ? collapsedLogo : logo;\r\n const showToggle = showCollapseToggle && mode !== 'mobile';\r\n const hasMeta = Boolean(title || subtitle || badge);\r\n const slotH = logoSlotHeight;\r\n\r\n const badgeColors: Record<string, string> = {\r\n info: tokens.colors.accent.primary,\r\n success: tokens.colors.status.normal,\r\n warning: tokens.colors.status.caution,\r\n caution: tokens.colors.status.serious,\r\n };\r\n\r\n const metaBlock = !collapsed && hasMeta ? (\r\n <div style={{ flex: 1, minWidth: 0, width: '100%' }}>\r\n {title ? (\r\n <div style={{\r\n fontSize: tokens.typography.fontSize.sm,\r\n fontWeight: tokens.typography.fontWeight.bold,\r\n color: tokens.colors.text.primary,\r\n lineHeight: tokens.typography.lineHeight.tight,\r\n whiteSpace: 'nowrap',\r\n overflow: 'hidden',\r\n textOverflow: 'ellipsis',\r\n }}>\r\n {title}\r\n </div>\r\n ) : null}\r\n {(subtitle || badge) ? (\r\n <div style={{ display: 'flex', alignItems: 'center', gap: tokens.spacing.xs, marginTop: title ? '2px' : 0, flexWrap: 'wrap' }}>\r\n {subtitle ? (\r\n <span style={{\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.tertiary,\r\n }}>\r\n {subtitle}\r\n </span>\r\n ) : null}\r\n {badge ? (\r\n <span style={{\r\n fontSize: '0.6rem',\r\n fontWeight: tokens.typography.fontWeight.bold,\r\n color: badgeColors[badgeVariant] || safeAccentText(tokens.colors.accent.primary),\r\n backgroundColor: `${badgeColors[badgeVariant] || tokens.colors.accent.primary}18`,\r\n border: `1px solid ${badgeColors[badgeVariant] || tokens.colors.accent.primary}30`,\r\n padding: '1px 6px',\r\n borderRadius: tokens.borderRadius.sm,\r\n textTransform: 'uppercase',\r\n letterSpacing: '0.05em',\r\n }}>\r\n {badge}\r\n </span>\r\n ) : null}\r\n </div>\r\n ) : null}\r\n </div>\r\n ) : null;\r\n\r\n const logoSlot = displayLogo ? (\r\n <div\r\n style={{\r\n height: slotH,\r\n minHeight: slotH,\r\n maxHeight: slotH,\r\n width: '100%',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: collapsed ? 'center' : 'flex-start',\r\n overflow: 'hidden',\r\n boxSizing: 'border-box',\r\n marginBottom: !collapsed && hasMeta && displayLogo ? tokens.spacing.sm : 0,\r\n }}\r\n >\r\n <div\r\n style={{\r\n maxHeight: '100%',\r\n maxWidth: '100%',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: collapsed ? 'center' : 'flex-start',\r\n minWidth: 0,\r\n cursor: collapsed && showToggle ? 'pointer' : undefined,\r\n }}\r\n onClick={collapsed && showToggle ? toggleCollapse : undefined}\r\n >\r\n {displayLogo}\r\n </div>\r\n </div>\r\n ) : null;\r\n\r\n if (children) {\r\n return (\r\n <div style={{\r\n padding: `${tokens.spacing.md} 16px ${tokens.spacing.md} 15px`,\r\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\r\n flexShrink: 0,\r\n boxSizing: 'border-box',\r\n position: 'relative',\r\n }}>\r\n {children}\r\n </div>\r\n );\r\n }\r\n\r\n // Collapsed: centered logo in the same fixed slot height as expanded (visual parity with operator).\r\n if (collapsed) {\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n flexDirection: 'column',\r\n alignItems: 'center',\r\n padding: `${tokens.spacing.md} ${tokens.spacing.sm}`,\r\n minHeight: slotH + 24,\r\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\r\n flexShrink: 0,\r\n boxSizing: 'border-box',\r\n position: 'relative',\r\n justifyContent: 'center',\r\n }}>\r\n {logoSlot}\r\n {showToggle ? (\r\n <div style={{ position: 'absolute', right: 4, top: '50%', transform: 'translateY(-50%)' }}>\r\n <CollapseToggleButton />\r\n </div>\r\n ) : null}\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n flexDirection: 'column',\r\n alignItems: 'stretch',\r\n padding: `${tokens.spacing.md} 16px ${tokens.spacing.md} 15px`,\r\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\r\n flexShrink: 0,\r\n boxSizing: 'border-box',\r\n position: 'relative',\r\n }}>\r\n {showToggle ? (\r\n <div style={{ position: 'absolute', top: tokens.spacing.sm, right: tokens.spacing.sm, zIndex: 1 }}>\r\n <CollapseToggleButton />\r\n </div>\r\n ) : null}\r\n <div style={{ paddingRight: showToggle ? 36 : 0, width: '100%', minWidth: 0 }}>\r\n {logoSlot}\r\n {metaBlock}\r\n </div>\r\n </div>\r\n );\r\n});\r\n\r\n// ─── Item ────────────────────────────────────────────────────────────────────\r\n\r\nexport interface SideNavItemProps {\r\n /** Icon name from zendir-ui icon library */\r\n icon?: IconName | React.ReactNode;\r\n /** Label text */\r\n label: string;\r\n /** Description text displayed beneath the label (hidden in collapsed mode) */\r\n description?: string;\r\n /** Small tag/chip displayed after the label (e.g. \"v2\", \"NEW\", \"BETA\") */\r\n tag?: string;\r\n /** Tag color variant */\r\n tagVariant?: 'default' | 'info' | 'success' | 'warning' | 'danger';\r\n /** Link href (renders <a>) */\r\n href?: string;\r\n /** Click handler (renders <button>) */\r\n onClick?: () => void;\r\n /** Active state */\r\n active?: boolean;\r\n /** Disabled state */\r\n disabled?: boolean;\r\n /** Notification badge count */\r\n badge?: number;\r\n /** External link indicator */\r\n external?: boolean;\r\n /**\r\n * Astro UX status level — renders a dual-coded indicator (color + shape).\r\n * Shapes per official Astro UXDS: normal = ● filled circle, standby = ◎ ring,\r\n * caution = ■ square, serious = ◆ diamond, critical = ▼ triangle, off = · small circle.\r\n */\r\n status?: 'off' | 'standby' | 'normal' | 'caution' | 'serious' | 'critical';\r\n /** Optional status label shown as tooltip or text next to status shape */\r\n statusLabel?: string;\r\n /** Right-side slot — arbitrary ReactNode rendered at the end of the item row */\r\n suffix?: React.ReactNode;\r\n}\r\n\r\nconst TAG_COLORS: Record<string, { bg: string; fg: string; border: string }> = {\r\n default: { bg: '#ffffff10', fg: '#9590a8', border: '#ffffff15' },\r\n info: { bg: '#8a2be218', fg: '#c4a0ff', border: '#8a2be230' },\r\n success: { bg: '#56f00018', fg: '#56f000', border: '#56f00030' },\r\n warning: { bg: '#fce83a18', fg: '#fce83a', border: '#fce83a30' },\r\n danger: { bg: '#ff383818', fg: '#ff3838', border: '#ff383830' },\r\n};\r\n\r\nconst SideNavItem = memo(function SideNavItem({\r\n icon,\r\n label,\r\n description,\r\n tag,\r\n tagVariant = 'default',\r\n href,\r\n onClick,\r\n active = false,\r\n disabled = false,\r\n badge,\r\n external = false,\r\n status,\r\n statusLabel,\r\n suffix,\r\n}: SideNavItemProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n const { collapsed, setMobileOpen } = useContext(SideNavContext);\r\n const [isHovered, setIsHovered] = useState(false);\r\n \r\n const handleClick = useCallback((e: React.MouseEvent) => {\r\n if (disabled) return;\r\n if (onClick && href && !e.metaKey && !e.ctrlKey && !e.shiftKey) {\r\n e.preventDefault();\r\n }\r\n setMobileOpen(false);\r\n onClick?.();\r\n }, [disabled, onClick, href, setMobileOpen]);\r\n \r\n // Status-aware accent: when an item has a status, tint active/hover with its color\r\n const statusColor = status ? (SIDENAV_STATUS_COLORS[status] ?? undefined) : undefined;\r\n const accentColor = statusColor && active ? statusColor : safeAccentText(tokens.colors.accent.primary);\r\n \r\n const hasDescription = !!description && !collapsed;\r\n \r\n const iconElement = typeof icon === 'string'\r\n ? <Icon name={icon as IconName} size={hasDescription ? 22 : 20} color={active ? accentColor : tokens.colors.text.secondary} />\r\n : icon;\r\n \r\n const itemStyle: React.CSSProperties = {\r\n display: 'flex',\r\n alignItems: hasDescription ? 'flex-start' : 'center',\r\n gap: tokens.spacing.sm,\r\n padding: collapsed\r\n ? `${tokens.spacing.sm} 0`\r\n : hasDescription\r\n ? `10px 16px 10px 12px`\r\n : `${tokens.spacing.sm} 16px ${tokens.spacing.sm} 12px`,\r\n justifyContent: collapsed ? 'center' : 'flex-start',\r\n color: active\r\n ? accentColor\r\n : disabled\r\n ? tokens.colors.text.tertiary\r\n : tokens.colors.text.secondary,\r\n backgroundColor: active\r\n ? `${accentColor}12`\r\n : isHovered && !disabled\r\n ? `${accentColor}08`\r\n : 'transparent',\r\n fontSize: tokens.typography.fontSize.sm,\r\n fontWeight: active ? tokens.typography.fontWeight.semibold : tokens.typography.fontWeight.normal,\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n cursor: disabled ? 'not-allowed' : 'pointer',\r\n textDecoration: 'none',\r\n borderTop: 'none',\r\n borderRight: 'none',\r\n borderBottom: 'none',\r\n borderLeft: active\r\n ? `3px solid ${accentColor}`\r\n : '3px solid transparent',\r\n outline: 'none',\r\n width: '100%',\r\n boxSizing: 'border-box',\r\n transition: tokens.animation.fast,\r\n position: 'relative',\r\n opacity: disabled ? 0.5 : 1,\r\n };\r\n \r\n const tagColors = TAG_COLORS[tagVariant] ?? TAG_COLORS.default;\r\n \r\n const content = (\r\n <>\r\n {/* Icon + collapsed status overlay */}\r\n {iconElement && (\r\n <span style={{ flexShrink: 0, display: 'flex', position: 'relative', marginTop: hasDescription ? '2px' : 0 }}>\r\n {iconElement}\r\n {/* In collapsed mode, show a small status shape overlaid on the icon (top-left) */}\r\n {collapsed && status && (\r\n <span\r\n style={{\r\n position: 'absolute',\r\n top: -3,\r\n left: -4,\r\n lineHeight: 0,\r\n }}\r\n title={statusLabel ?? status}\r\n >\r\n <NavStatusShape status={status} size={7} />\r\n </span>\r\n )}\r\n </span>\r\n )}\r\n {!collapsed && (\r\n <>\r\n {/* Label + description block */}\r\n <span style={{ flex: 1, minWidth: 0, overflow: 'hidden' }}>\r\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\r\n <span style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>\r\n {label}\r\n </span>\r\n {tag && (\r\n <span style={{\r\n fontSize: '0.6rem',\r\n fontWeight: tokens.typography.fontWeight.bold,\r\n color: tagColors.fg,\r\n backgroundColor: tagColors.bg,\r\n border: `1px solid ${tagColors.border}`,\r\n padding: '1px 5px',\r\n borderRadius: tokens.borderRadius.sm,\r\n textTransform: 'uppercase',\r\n letterSpacing: '0.04em',\r\n flexShrink: 0,\r\n lineHeight: '1.3',\r\n whiteSpace: 'nowrap',\r\n }}>\r\n {tag}\r\n </span>\r\n )}\r\n </span>\r\n {description && (\r\n <span style={{\r\n display: 'block',\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.tertiary,\r\n lineHeight: tokens.typography.lineHeight.normal,\r\n marginTop: '2px',\r\n whiteSpace: 'nowrap',\r\n overflow: 'hidden',\r\n textOverflow: 'ellipsis',\r\n fontWeight: tokens.typography.fontWeight.normal,\r\n }}>\r\n {description}\r\n </span>\r\n )}\r\n </span>\r\n {/* Right-side trailing elements — badge, status, suffix, external icon */}\r\n {(badge !== undefined && badge > 0 || status || suffix || external) && (\r\n <span style={{\r\n display: 'inline-flex',\r\n alignItems: 'center',\r\n gap: 8,\r\n flexShrink: 0,\r\n marginTop: hasDescription ? '2px' : 0,\r\n }}>\r\n {/* Badge */}\r\n {badge !== undefined && badge > 0 && (\r\n <span style={{\r\n fontSize: '0.65rem',\r\n fontWeight: tokens.typography.fontWeight.bold,\r\n color: '#fff',\r\n backgroundColor: tokens.colors.status.critical,\r\n borderRadius: tokens.borderRadius.full,\r\n minWidth: '18px',\r\n height: '18px',\r\n display: 'inline-flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n padding: '0 5px',\r\n flexShrink: 0,\r\n lineHeight: 1,\r\n boxSizing: 'border-box',\r\n }}>\r\n {badge > 99 ? '99+' : badge}\r\n </span>\r\n )}\r\n {/* Status */}\r\n {status && (\r\n <span\r\n style={{ display: 'inline-flex', alignItems: 'center', flexShrink: 0 }}\r\n role=\"status\"\r\n aria-label={`Status: ${statusLabel ?? status}`}\r\n title={statusLabel ?? status}\r\n >\r\n <NavStatusShape status={status} size={8} />\r\n </span>\r\n )}\r\n {/* Suffix */}\r\n {suffix && (\r\n <span style={{ flexShrink: 0, display: 'inline-flex', alignItems: 'center' }}>\r\n {suffix}\r\n </span>\r\n )}\r\n {/* External */}\r\n {external && (\r\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" style={{ flexShrink: 0, opacity: 0.5 }}>\r\n <path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\" />\r\n <polyline points=\"15 3 21 3 21 9\" />\r\n <line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\" />\r\n </svg>\r\n )}\r\n </span>\r\n )}\r\n </>\r\n )}\r\n </>\r\n );\r\n \r\n const props = {\r\n style: itemStyle,\r\n onMouseEnter: () => setIsHovered(true),\r\n onMouseLeave: () => setIsHovered(false),\r\n title: collapsed ? label : undefined,\r\n 'aria-current': active ? ('page' as const) : undefined,\r\n 'aria-disabled': disabled,\r\n };\r\n \r\n if (href && !disabled) {\r\n return (\r\n <a href={href} onClick={handleClick} target={external ? '_blank' : undefined} rel={external ? 'noopener noreferrer' : undefined} {...props}>\r\n {content}\r\n </a>\r\n );\r\n }\r\n \r\n return (\r\n <button type=\"button\" onClick={handleClick} disabled={disabled} {...props}>\r\n {content}\r\n </button>\r\n );\r\n});\r\n\r\n// ─── Section ─────────────────────────────────────────────────────────────────\r\n\r\nexport interface SideNavSectionProps {\r\n /** Section title */\r\n title?: string;\r\n /** Children (SideNav.Item elements) */\r\n children?: React.ReactNode;\r\n}\r\n\r\nconst SideNavSection = memo(function SideNavSection({\r\n title,\r\n children,\r\n}: SideNavSectionProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n const { collapsed } = useContext(SideNavContext);\r\n \r\n return (\r\n <div style={{ marginTop: tokens.spacing.sm }}>\r\n {title && !collapsed && (\r\n <div style={{\r\n padding: `${tokens.spacing.xs} 16px ${tokens.spacing.xs} 15px`,\r\n fontSize: tokens.typography.fontSize.xxs,\r\n fontWeight: tokens.typography.fontWeight.bold,\r\n color: tokens.colors.text.tertiary,\r\n textTransform: 'uppercase',\r\n letterSpacing: '0.08em',\r\n }}>\r\n {title}\r\n </div>\r\n )}\r\n {collapsed && title && (\r\n <div style={{\r\n width: '60%',\r\n height: '1px',\r\n backgroundColor: tokens.colors.border.muted,\r\n margin: `${tokens.spacing.xs} auto`,\r\n }} />\r\n )}\r\n {children}\r\n </div>\r\n );\r\n});\r\n\r\n// ─── Divider ─────────────────────────────────────────────────────────────────\r\n\r\nexport interface SideNavDividerProps {\r\n /** Optional label shown in the center of the divider line */\r\n label?: string;\r\n}\r\n\r\nconst SideNavDivider = memo(function SideNavDivider({\r\n label,\r\n}: SideNavDividerProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n const { collapsed } = useContext(SideNavContext);\r\n\r\n if (collapsed) {\r\n return (\r\n <div style={{\r\n width: '60%',\r\n height: '1px',\r\n backgroundColor: tokens.colors.border.muted,\r\n margin: `${tokens.spacing.sm} auto`,\r\n }} />\r\n );\r\n }\r\n\r\n if (label) {\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: tokens.spacing.sm,\r\n padding: `${tokens.spacing.sm} 16px ${tokens.spacing.sm} 15px`,\r\n }}>\r\n <div style={{ flex: 1, height: '1px', backgroundColor: tokens.colors.border.muted }} />\r\n <span style={{\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.tertiary,\r\n textTransform: 'uppercase',\r\n letterSpacing: '0.06em',\r\n whiteSpace: 'nowrap',\r\n }}>\r\n {label}\r\n </span>\r\n <div style={{ flex: 1, height: '1px', backgroundColor: tokens.colors.border.muted }} />\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <div style={{\r\n height: '1px',\r\n backgroundColor: tokens.colors.border.muted,\r\n margin: `${tokens.spacing.sm} 16px ${tokens.spacing.sm} 15px`,\r\n }} />\r\n );\r\n});\r\n\r\n// ─── Footer ──────────────────────────────────────────────────────────────────\r\n\r\nexport interface SideNavFooterProps {\r\n children?: React.ReactNode;\r\n}\r\n\r\nconst SideNavFooter = memo(function SideNavFooter({\r\n children,\r\n}: SideNavFooterProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n \r\n return (\r\n <div style={{\r\n marginTop: 'auto',\r\n borderTop: `1px solid ${tokens.colors.border.muted}`,\r\n paddingTop: tokens.spacing.xs,\r\n paddingBottom: tokens.spacing.xs,\r\n }}>\r\n {children}\r\n </div>\r\n );\r\n});\r\n\r\n// ─── Compound Export ─────────────────────────────────────────────────────────\r\n\r\ntype SideNavComponent = typeof SideNavRoot & {\r\n Header: typeof SideNavHeader;\r\n Item: typeof SideNavItem;\r\n Section: typeof SideNavSection;\r\n Divider: typeof SideNavDivider;\r\n Footer: typeof SideNavFooter;\r\n};\r\n\r\nexport const SideNav: SideNavComponent = Object.assign(SideNavRoot, {\r\n Header: SideNavHeader,\r\n Item: SideNavItem,\r\n Section: SideNavSection,\r\n Divider: SideNavDivider,\r\n Footer: SideNavFooter,\r\n});\r\n\r\nexport default SideNav;\r\n"],"names":["SideNav","SideNavHeader","SideNavItem","SideNavSection","SideNavDivider","SideNavFooter"],"mappings":"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"}
1
+ {"version":3,"file":"SideNav.js","sources":["../../../../src/react/core/navigation/SideNav.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - SideNav Component\r\n * \r\n * Persistent sidebar navigation for operator dashboards. Compound component\r\n * pattern with Header, Item, Section, Divider, and Footer subcomponents.\r\n * \r\n * Responsive: Full sidebar on desktop, hamburger overlay on mobile.\r\n * \r\n * Astro UX Compliance:\r\n * - Persistent side navigation pattern (AstroUXDS Navigation)\r\n * - Status indicator integration\r\n * - Active state highlighting with accent color\r\n * - Keyboard navigation support\r\n * - Reduced motion support\r\n * \r\n * @example\r\n * ```tsx\r\n * <SideNav>\r\n * <SideNav.Header logo={<Icon name=\"satellite\" size={24} />} title=\"Space Range\" badge=\"Operator\" />\r\n * <SideNav.Section title=\"Operations\">\r\n * <SideNav.Item icon=\"controls\" label=\"Controls\" description=\"System command interface\" href=\"/controls\" active />\r\n * <SideNav.Item icon=\"telemetry\" label=\"Telemetry\" href=\"/telemetry\" badge={3} tag=\"LIVE\" tagVariant=\"success\" />\r\n * <SideNav.Item icon=\"images\" label=\"Images\" href=\"/images\" />\r\n * </SideNav.Section>\r\n * <SideNav.Divider />\r\n * <SideNav.Section title=\"Analysis\">\r\n * <SideNav.Item icon=\"chart\" label=\"Plots\" href=\"/plots\" />\r\n * <SideNav.Item icon=\"map\" label=\"Map\" href=\"/map\" />\r\n * </SideNav.Section>\r\n * <SideNav.Footer>\r\n * <SideNav.Item icon=\"settings\" label=\"Settings\" href=\"/settings\" />\r\n * </SideNav.Footer>\r\n * </SideNav>\r\n * ```\r\n */\r\n\r\nimport React, { memo, useState, createContext, useContext, useCallback, useEffect, useLayoutEffect } from 'react';\r\n\r\nconst useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;\r\nimport { useTheme } from '../../theme';\r\nimport { safeAccentText } from '../../utils';\r\nimport { Icon } from '../display/Icon';\r\nimport type { IconName } from '../display/Icon';\r\n\r\n// ─── Reveal animation ────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Content that only renders when expanded (item labels, section titles, the\r\n * header meta block) fades and slides in rather than popping, so widening the\r\n * rail reads as material flowing in. Injected once as a stylesheet because\r\n * keyframes can't be expressed inline; honours `prefers-reduced-motion`.\r\n */\r\nconst SIDENAV_REVEAL_STYLE_ID = 'zendir-sidenav-reveal';\r\nfunction ensureSideNavReveal(): void {\r\n if (typeof document === 'undefined') return;\r\n if (document.getElementById(SIDENAV_REVEAL_STYLE_ID)) return;\r\n const el = document.createElement('style');\r\n el.id = SIDENAV_REVEAL_STYLE_ID;\r\n el.textContent =\r\n '@keyframes zsn-reveal{from{opacity:0;transform:translateX(-6px)}to{opacity:1;transform:translateX(0)}}' +\r\n '.zsn-reveal{animation:zsn-reveal 220ms cubic-bezier(0.22,1,0.36,1)}' +\r\n // Item label is always mounted and toggled via data-collapsed, so it fades\r\n // and slides BOTH on expand and collapse (the icon stays put as the anchor).\r\n '.zsn-label{transition:opacity 220ms cubic-bezier(0.22,1,0.36,1),transform 220ms cubic-bezier(0.22,1,0.36,1)}' +\r\n '.zsn-label[data-collapsed=\"true\"]{opacity:0;transform:translateX(-6px);pointer-events:none}' +\r\n '@media (prefers-reduced-motion: reduce){.zsn-reveal,.zsn-label{animation:none;transition:none}}';\r\n document.head.appendChild(el);\r\n}\r\n\r\n// ─── Astro UX Status Shape ───────────────────────────────────────────────────\r\n\r\nconst SIDENAV_STATUS_COLORS: Record<string, string> = {\r\n off: '#a4abb6',\r\n standby: '#2dccff',\r\n normal: '#56f000',\r\n caution: '#fce83a',\r\n serious: '#ffb302',\r\n critical: '#ff3838',\r\n};\r\n\r\n/** Astro UX dual-coded status shape (color + geometry). */\r\nfunction NavStatusShape({ status, size = 8 }: { status: string; size?: number }) {\r\n const color = SIDENAV_STATUS_COLORS[status] ?? SIDENAV_STATUS_COLORS.off;\r\n const glow = `${color}50`;\r\n const style = { flexShrink: 0 as const, filter: `drop-shadow(0 0 3px ${glow})` };\r\n\r\n switch (status) {\r\n case 'caution':\r\n return <svg viewBox=\"0 0 12 12\" width={size} height={size} style={style} aria-hidden=\"true\"><rect x=\"1\" y=\"1\" width=\"10\" height=\"10\" fill={color} /></svg>;\r\n case 'serious':\r\n return <svg viewBox=\"0 0 12 12\" width={size} height={size} style={style} aria-hidden=\"true\"><polygon points=\"6,1 11,6 6,11 1,6\" fill={color} /></svg>;\r\n case 'critical':\r\n return <svg viewBox=\"0 0 12 12\" width={size} height={size} style={style} aria-hidden=\"true\"><polygon points=\"6,11 1,2 11,2\" fill={color} /></svg>;\r\n case 'standby':\r\n return <svg viewBox=\"0 0 12 12\" width={size} height={size} style={style} aria-hidden=\"true\"><circle cx=\"6\" cy=\"6\" r=\"3.5\" fill=\"none\" stroke={color} strokeWidth=\"2\" /></svg>;\r\n case 'off':\r\n return <svg viewBox=\"0 0 12 12\" width={size} height={size} style={style} aria-hidden=\"true\"><circle cx=\"6\" cy=\"6\" r=\"3\" fill={color} /></svg>;\r\n default: // normal\r\n return <svg viewBox=\"0 0 12 12\" width={size} height={size} style={style} aria-hidden=\"true\"><circle cx=\"6\" cy=\"6\" r=\"5\" fill={color} /></svg>;\r\n }\r\n}\r\n\r\n// ─── Responsive Breakpoints ──────────────────────────────────────────────────\r\n\r\nconst SIDENAV_BREAKPOINTS = {\r\n mobile: 768,\r\n tablet: 1024,\r\n} as const;\r\n\r\ntype SideNavMode = 'desktop' | 'tablet' | 'mobile';\r\n\r\nfunction getSideNavMode(width: number): SideNavMode {\r\n if (width < SIDENAV_BREAKPOINTS.mobile) return 'mobile';\r\n if (width < SIDENAV_BREAKPOINTS.tablet) return 'tablet';\r\n return 'desktop';\r\n}\r\n\r\n// ─── Context ─────────────────────────────────────────────────────────────────\r\n\r\ninterface SideNavContextValue {\r\n collapsed: boolean;\r\n mobileOpen: boolean;\r\n mode: SideNavMode;\r\n density: 'default' | 'compact';\r\n showCollapseToggle: boolean;\r\n setMobileOpen: (open: boolean) => void;\r\n toggleCollapse: () => void;\r\n}\r\n\r\nconst SideNavContext = createContext<SideNavContextValue>({\r\n collapsed: false,\r\n mobileOpen: false,\r\n mode: 'desktop',\r\n density: 'default',\r\n showCollapseToggle: true,\r\n setMobileOpen: () => {},\r\n toggleCollapse: () => {},\r\n});\r\n\r\n// ─── SideNav ─────────────────────────────────────────────────────────────────\r\n\r\nexport interface SideNavProps {\r\n /** Collapsed mode (icon-only). When omitted, auto-collapses on tablet viewports. */\r\n collapsed?: boolean;\r\n /** Callback when collapsed state changes (via toggle button or responsive breakpoint). */\r\n onCollapsedChange?: (collapsed: boolean) => void;\r\n /** Show a collapse/expand toggle button in the sidebar (default true). */\r\n showCollapseToggle?: boolean;\r\n /** Width in pixels (default 260) */\r\n width?: number;\r\n /** Collapsed width in pixels (default 64) */\r\n collapsedWidth?: number;\r\n /**\r\n * Row density. `'compact'` tightens row padding, drops the left accent bar\r\n * in favour of a pill-style (rounded, bg-tinted) active row, and shrinks the\r\n * label — matching the v2.3 operator/admin secondary-nav mock. Default\r\n * `'default'`. (TODO: row inset + mono section headings for full mock parity.)\r\n */\r\n density?: 'default' | 'compact';\r\n /**\r\n * Mobile viewport behavior.\r\n * - `'drawer'` (default): hamburger button with slide-out overlay drawer.\r\n * - `'collapsed'`: persistent collapsed icon-only strip (same as tablet).\r\n */\r\n mobileVariant?: 'drawer' | 'collapsed';\r\n /** Children (SideNav.Header, SideNav.Item, SideNav.Section, SideNav.Footer) */\r\n children?: React.ReactNode;\r\n /** Custom style */\r\n style?: React.CSSProperties;\r\n}\r\n\r\nconst SideNavRoot = memo(function SideNav({\r\n collapsed,\r\n onCollapsedChange,\r\n showCollapseToggle = true,\r\n width = 260,\r\n collapsedWidth = 64,\r\n density = 'default',\r\n mobileVariant = 'drawer',\r\n children,\r\n style,\r\n}: SideNavProps): React.ReactElement {\r\n const { tokens, theme } = useTheme();\r\n const isTransparentTheme = theme === 'transparent' || theme === 'transparent-bold' || theme === 'transparent-minimal';\r\n const [mobileOpen, setMobileOpen] = useState(false);\r\n const [userCollapsed, setUserCollapsed] = useState<boolean | null>(null);\r\n \r\n // Initialize to 'desktop' to guarantee the first client render matches SSR\r\n // (where window is undefined), preventing a React hydration mismatch if the\r\n // client happens to be on a mobile/tablet width at initial load.\r\n const [viewMode, setViewMode] = useState<SideNavMode>('desktop');\r\n \r\n useIsomorphicLayoutEffect(() => {\r\n if (typeof window === 'undefined') return;\r\n\r\n // Synchronously correct the viewMode right after hydration but before paint\r\n setViewMode(getSideNavMode(window.innerWidth));\r\n\r\n let rafId: number;\r\n const handleResize = () => {\r\n cancelAnimationFrame(rafId);\r\n rafId = requestAnimationFrame(() => setViewMode(getSideNavMode(window.innerWidth)));\r\n };\r\n window.addEventListener('resize', handleResize, { passive: true });\r\n return () => {\r\n window.removeEventListener('resize', handleResize);\r\n cancelAnimationFrame(rafId);\r\n };\r\n }, []);\r\n\r\n useIsomorphicLayoutEffect(() => { ensureSideNavReveal(); }, []);\r\n\r\n // When mobileVariant='collapsed', promote mobile to tablet (collapsed inline strip)\r\n const effectiveMode = mobileVariant === 'collapsed' && viewMode === 'mobile' ? 'tablet' : viewMode;\r\n\r\n // Reset user toggle when crossing breakpoints\r\n useEffect(() => {\r\n setUserCollapsed(null);\r\n }, [effectiveMode]);\r\n\r\n // Close mobile drawer when switching away from mobile\r\n useEffect(() => {\r\n if (effectiveMode !== 'mobile' && mobileOpen) setMobileOpen(false);\r\n }, [effectiveMode, mobileOpen]);\r\n \r\n // Close mobile nav on escape\r\n useEffect(() => {\r\n if (!mobileOpen) return;\r\n const handleEsc = (e: KeyboardEvent) => {\r\n if (e.key === 'Escape') setMobileOpen(false);\r\n };\r\n document.addEventListener('keydown', handleEsc);\r\n return () => document.removeEventListener('keydown', handleEsc);\r\n }, [mobileOpen]);\r\n\r\n // Resolve collapsed: explicit prop > user toggle > auto (tablet = collapsed)\r\n const autoCollapsed = effectiveMode === 'tablet';\r\n const isCollapsed = collapsed !== undefined\r\n ? collapsed\r\n : userCollapsed !== null\r\n ? userCollapsed\r\n : autoCollapsed;\r\n const isMobile = effectiveMode === 'mobile';\r\n const navWidth = isCollapsed ? collapsedWidth : width;\r\n\r\n const handleToggleCollapse = useCallback(() => {\r\n const next = !isCollapsed;\r\n setUserCollapsed(next);\r\n onCollapsedChange?.(next);\r\n }, [isCollapsed, onCollapsedChange]);\r\n \r\n const navStyle: React.CSSProperties = {\r\n display: 'flex',\r\n flexDirection: 'column',\r\n width: isMobile ? 280 : navWidth,\r\n height: '100%',\r\n backgroundColor: isTransparentTheme\r\n ? 'rgba(15, 12, 30, 0.7)'\r\n : tokens.colors.background.surface,\r\n borderRight: `1px solid ${tokens.colors.border.muted}`,\r\n transition: 'width 0.3s cubic-bezier(0.22, 1, 0.36, 1), transform 0.25s ease',\r\n overflowX: 'hidden',\r\n overflowY: 'auto',\r\n flexShrink: 0,\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n ...(isTransparentTheme ? {\r\n backdropFilter: 'blur(16px)',\r\n WebkitBackdropFilter: 'blur(16px)',\r\n } : {}),\r\n ...style,\r\n };\r\n \r\n const contextValue: SideNavContextValue = { collapsed: isCollapsed, mobileOpen, mode: effectiveMode, density, showCollapseToggle, setMobileOpen, toggleCollapse: handleToggleCollapse };\r\n \r\n // Mobile: hamburger button + overlay drawer\r\n if (isMobile) {\r\n return (\r\n <SideNavContext.Provider value={contextValue}>\r\n {/* Hamburger button */}\r\n <button\r\n aria-label=\"Open navigation\"\r\n onClick={() => setMobileOpen(true)}\r\n style={{\r\n position: 'fixed',\r\n top: 6,\r\n left: 6,\r\n zIndex: 1001,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n width: 32,\r\n height: 32,\r\n border: 'none',\r\n borderRadius: tokens.borderRadius.sm,\r\n backgroundColor: 'transparent',\r\n color: tokens.colors.text.primary,\r\n cursor: 'pointer',\r\n padding: 0,\r\n transition: tokens.animation.fast,\r\n }}\r\n >\r\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\">\r\n <line x1=\"3\" y1=\"6\" x2=\"21\" y2=\"6\" />\r\n <line x1=\"3\" y1=\"12\" x2=\"21\" y2=\"12\" />\r\n <line x1=\"3\" y1=\"18\" x2=\"21\" y2=\"18\" />\r\n </svg>\r\n </button>\r\n \r\n {/* Overlay */}\r\n <div\r\n aria-hidden=\"true\"\r\n style={{\r\n position: 'fixed',\r\n inset: 0,\r\n zIndex: 1002,\r\n backgroundColor: `${tokens.colors.background.base}99`,\r\n backdropFilter: 'blur(4px)',\r\n opacity: mobileOpen ? 1 : 0,\r\n pointerEvents: mobileOpen ? 'auto' : 'none',\r\n transition: 'opacity 0.25s ease',\r\n }}\r\n onClick={() => setMobileOpen(false)}\r\n />\r\n \r\n {/* Slide-out nav */}\r\n <nav\r\n role=\"navigation\"\r\n aria-label=\"Main navigation\"\r\n style={{\r\n ...navStyle,\r\n position: 'fixed',\r\n top: 0,\r\n left: 0,\r\n zIndex: 1003,\r\n transform: mobileOpen ? 'translateX(0)' : 'translateX(-100%)',\r\n boxShadow: mobileOpen ? tokens.shadows.xl : 'none',\r\n }}\r\n >\r\n {children}\r\n </nav>\r\n </SideNavContext.Provider>\r\n );\r\n }\r\n \r\n return (\r\n <SideNavContext.Provider value={contextValue}>\r\n <nav role=\"navigation\" aria-label=\"Main navigation\" style={navStyle}>\r\n {children}\r\n </nav>\r\n </SideNavContext.Provider>\r\n );\r\n});\r\n\r\n// ─── Header ──────────────────────────────────────────────────────────────────\r\n\r\n/** Default height for the logo row so dashboard / operator / other apps align the same mark. */\r\nexport const SIDENAV_HEADER_LOGO_SLOT_HEIGHT_PX = 44;\r\n\r\nexport interface SideNavHeaderProps {\r\n /** Logo element (Icon, image, or ReactNode) */\r\n logo?: React.ReactNode;\r\n /** Compact logo shown when sidebar is collapsed or on tablet (e.g., just the icon mark) */\r\n collapsedLogo?: React.ReactNode;\r\n /** App title */\r\n title?: string;\r\n /** Subtitle or version */\r\n subtitle?: string;\r\n /** Role badge (e.g., \"Operator\", \"Admin\") */\r\n badge?: string;\r\n /** Badge variant for color */\r\n badgeVariant?: 'info' | 'success' | 'warning' | 'caution';\r\n /**\r\n * Fixed height (px) for the top logo band. Title, subtitle, and badge render below this band\r\n * so different logo assets still line up across apps (dashboard vs operator).\r\n */\r\n logoSlotHeight?: number;\r\n /** Children override (advanced: replaces default header content entirely) */\r\n children?: React.ReactNode;\r\n}\r\n\r\n/** Chevron toggle button for collapsing/expanding the sidebar. */\r\nfunction CollapseToggleButton() {\r\n const { tokens } = useTheme();\r\n const { collapsed, toggleCollapse } = useContext(SideNavContext);\r\n const [hovered, setHovered] = useState(false);\r\n\r\n return (\r\n <button\r\n type=\"button\"\r\n aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}\r\n onClick={toggleCollapse}\r\n onMouseEnter={() => setHovered(true)}\r\n onMouseLeave={() => setHovered(false)}\r\n style={{\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n width: 28,\r\n height: 28,\r\n border: `1px solid ${hovered ? tokens.colors.border.focus : tokens.colors.border.muted}`,\r\n borderRadius: tokens.borderRadius.md,\r\n backgroundColor: hovered ? `${tokens.colors.accent.primary}15` : 'transparent',\r\n color: hovered ? tokens.colors.accent.primary : tokens.colors.text.tertiary,\r\n cursor: 'pointer',\r\n flexShrink: 0,\r\n padding: 0,\r\n transition: tokens.animation.fast,\r\n outline: 'none',\r\n }}\r\n >\r\n <svg\r\n width=\"14\"\r\n height=\"14\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n style={{\r\n transition: 'transform 0.25s ease',\r\n transform: collapsed ? 'rotate(180deg)' : 'rotate(0deg)',\r\n }}\r\n >\r\n <polyline points=\"15 18 9 12 15 6\" />\r\n </svg>\r\n </button>\r\n );\r\n}\r\n\r\nconst SideNavHeader = memo(function SideNavHeader({\r\n logo,\r\n collapsedLogo,\r\n title,\r\n subtitle,\r\n badge,\r\n badgeVariant = 'info',\r\n logoSlotHeight = SIDENAV_HEADER_LOGO_SLOT_HEIGHT_PX,\r\n children,\r\n}: SideNavHeaderProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n const { collapsed, mode, showCollapseToggle, toggleCollapse } = useContext(SideNavContext);\r\n const displayLogo = collapsed && collapsedLogo ? collapsedLogo : logo;\r\n const showToggle = showCollapseToggle && mode !== 'mobile';\r\n const hasMeta = Boolean(title || subtitle || badge);\r\n const slotH = logoSlotHeight;\r\n\r\n const badgeColors: Record<string, string> = {\r\n info: tokens.colors.accent.primary,\r\n success: tokens.colors.status.normal,\r\n warning: tokens.colors.status.caution,\r\n caution: tokens.colors.status.serious,\r\n };\r\n\r\n const metaBlock = !collapsed && hasMeta ? (\r\n <div className=\"zsn-reveal\" style={{ flex: 1, minWidth: 0, width: '100%' }}>\r\n {title ? (\r\n <div style={{\r\n fontSize: tokens.typography.fontSize.sm,\r\n fontWeight: tokens.typography.fontWeight.bold,\r\n color: tokens.colors.text.primary,\r\n lineHeight: tokens.typography.lineHeight.tight,\r\n whiteSpace: 'nowrap',\r\n overflow: 'hidden',\r\n textOverflow: 'ellipsis',\r\n }}>\r\n {title}\r\n </div>\r\n ) : null}\r\n {(subtitle || badge) ? (\r\n <div style={{ display: 'flex', alignItems: 'center', gap: tokens.spacing.xs, marginTop: title ? '2px' : 0, flexWrap: 'wrap' }}>\r\n {subtitle ? (\r\n <span style={{\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.tertiary,\r\n }}>\r\n {subtitle}\r\n </span>\r\n ) : null}\r\n {badge ? (\r\n <span style={{\r\n fontSize: '0.6rem',\r\n fontWeight: tokens.typography.fontWeight.bold,\r\n color: badgeColors[badgeVariant] || safeAccentText(tokens.colors.accent.primary),\r\n backgroundColor: `${badgeColors[badgeVariant] || tokens.colors.accent.primary}18`,\r\n border: `1px solid ${badgeColors[badgeVariant] || tokens.colors.accent.primary}30`,\r\n padding: '1px 6px',\r\n borderRadius: tokens.borderRadius.sm,\r\n textTransform: 'uppercase',\r\n letterSpacing: '0.05em',\r\n }}>\r\n {badge}\r\n </span>\r\n ) : null}\r\n </div>\r\n ) : null}\r\n </div>\r\n ) : null;\r\n\r\n const logoSlot = displayLogo ? (\r\n <div\r\n style={{\r\n height: slotH,\r\n minHeight: slotH,\r\n maxHeight: slotH,\r\n width: '100%',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: collapsed ? 'center' : 'flex-start',\r\n overflow: 'hidden',\r\n boxSizing: 'border-box',\r\n marginBottom: !collapsed && hasMeta && displayLogo ? tokens.spacing.sm : 0,\r\n }}\r\n >\r\n <div\r\n style={{\r\n maxHeight: '100%',\r\n maxWidth: '100%',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: collapsed ? 'center' : 'flex-start',\r\n minWidth: 0,\r\n cursor: collapsed && showToggle ? 'pointer' : undefined,\r\n }}\r\n onClick={collapsed && showToggle ? toggleCollapse : undefined}\r\n >\r\n {displayLogo}\r\n </div>\r\n </div>\r\n ) : null;\r\n\r\n if (children) {\r\n return (\r\n <div style={{\r\n padding: `${tokens.spacing.md} 16px ${tokens.spacing.md} 15px`,\r\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\r\n flexShrink: 0,\r\n boxSizing: 'border-box',\r\n position: 'relative',\r\n }}>\r\n {children}\r\n </div>\r\n );\r\n }\r\n\r\n // Collapsed: centered logo in the same fixed slot height as expanded (visual parity with operator).\r\n if (collapsed) {\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n flexDirection: 'column',\r\n alignItems: 'center',\r\n padding: `${tokens.spacing.md} ${tokens.spacing.sm}`,\r\n minHeight: slotH + 24,\r\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\r\n flexShrink: 0,\r\n boxSizing: 'border-box',\r\n position: 'relative',\r\n justifyContent: 'center',\r\n }}>\r\n {logoSlot}\r\n {showToggle ? (\r\n <div style={{ position: 'absolute', right: 4, top: '50%', transform: 'translateY(-50%)' }}>\r\n <CollapseToggleButton />\r\n </div>\r\n ) : null}\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n flexDirection: 'column',\r\n alignItems: 'stretch',\r\n padding: `${tokens.spacing.md} 16px ${tokens.spacing.md} 15px`,\r\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\r\n flexShrink: 0,\r\n boxSizing: 'border-box',\r\n position: 'relative',\r\n }}>\r\n {showToggle ? (\r\n <div style={{ position: 'absolute', top: tokens.spacing.sm, right: tokens.spacing.sm, zIndex: 1 }}>\r\n <CollapseToggleButton />\r\n </div>\r\n ) : null}\r\n <div style={{ paddingRight: showToggle ? 36 : 0, width: '100%', minWidth: 0 }}>\r\n {logoSlot}\r\n {metaBlock}\r\n </div>\r\n </div>\r\n );\r\n});\r\n\r\n// ─── Item ────────────────────────────────────────────────────────────────────\r\n\r\nexport interface SideNavItemProps {\r\n /** Icon name from zendir-ui icon library */\r\n icon?: IconName | React.ReactNode;\r\n /** Label text */\r\n label: string;\r\n /** Description text displayed beneath the label (hidden in collapsed mode) */\r\n description?: string;\r\n /** Small tag/chip displayed after the label (e.g. \"v2\", \"NEW\", \"BETA\") */\r\n tag?: string;\r\n /** Tag color variant */\r\n tagVariant?: 'default' | 'info' | 'success' | 'warning' | 'danger';\r\n /** Link href (renders <a>) */\r\n href?: string;\r\n /** Click handler (renders <button>) */\r\n onClick?: () => void;\r\n /** Active state */\r\n active?: boolean;\r\n /** Disabled state */\r\n disabled?: boolean;\r\n /** Notification badge count */\r\n badge?: number;\r\n /** External link indicator */\r\n external?: boolean;\r\n /**\r\n * Astro UX status level — renders a dual-coded indicator (color + shape).\r\n * Shapes per official Astro UXDS: normal = ● filled circle, standby = ◎ ring,\r\n * caution = ■ square, serious = ◆ diamond, critical = ▼ triangle, off = · small circle.\r\n */\r\n status?: 'off' | 'standby' | 'normal' | 'caution' | 'serious' | 'critical';\r\n /** Optional status label shown as tooltip or text next to status shape */\r\n statusLabel?: string;\r\n /** Right-side slot — arbitrary ReactNode rendered at the end of the item row */\r\n suffix?: React.ReactNode;\r\n}\r\n\r\nconst TAG_COLORS: Record<string, { bg: string; fg: string; border: string }> = {\r\n default: { bg: '#ffffff10', fg: '#9590a8', border: '#ffffff15' },\r\n info: { bg: '#8a2be218', fg: '#c4a0ff', border: '#8a2be230' },\r\n success: { bg: '#56f00018', fg: '#56f000', border: '#56f00030' },\r\n warning: { bg: '#fce83a18', fg: '#fce83a', border: '#fce83a30' },\r\n danger: { bg: '#ff383818', fg: '#ff3838', border: '#ff383830' },\r\n};\r\n\r\nconst SideNavItem = memo(function SideNavItem({\r\n icon,\r\n label,\r\n description,\r\n tag,\r\n tagVariant = 'default',\r\n href,\r\n onClick,\r\n active = false,\r\n disabled = false,\r\n badge,\r\n external = false,\r\n status,\r\n statusLabel,\r\n suffix,\r\n}: SideNavItemProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n const { collapsed, setMobileOpen, density } = useContext(SideNavContext);\r\n const compact = density === 'compact' && !collapsed;\r\n const [isHovered, setIsHovered] = useState(false);\r\n \r\n const handleClick = useCallback((e: React.MouseEvent) => {\r\n if (disabled) return;\r\n if (onClick && href && !e.metaKey && !e.ctrlKey && !e.shiftKey) {\r\n e.preventDefault();\r\n }\r\n setMobileOpen(false);\r\n onClick?.();\r\n }, [disabled, onClick, href, setMobileOpen]);\r\n \r\n // Status-aware accent: when an item has a status, tint active/hover with its color\r\n const statusColor = status ? (SIDENAV_STATUS_COLORS[status] ?? undefined) : undefined;\r\n const accentColor = statusColor && active ? statusColor : safeAccentText(tokens.colors.accent.primary);\r\n \r\n const hasDescription = !!description && !collapsed;\r\n \r\n const iconElement = typeof icon === 'string'\r\n ? <Icon name={icon as IconName} size={hasDescription ? 22 : 20} color={active ? accentColor : tokens.colors.text.secondary} />\r\n : icon;\r\n \r\n const itemStyle: React.CSSProperties = {\r\n display: 'flex',\r\n alignItems: hasDescription ? 'flex-start' : 'center',\r\n gap: tokens.spacing.sm,\r\n // Collapsed keeps the SAME 12px left inset as the expanded row, so the\r\n // icon holds its horizontal position when the rail expands — it doesn't\r\n // jump from a centered slot to a left-aligned one. Size the collapsed\r\n // width so that fixed inset reads as centered.\r\n padding: collapsed\r\n ? `${tokens.spacing.sm} 12px`\r\n : compact\r\n ? '7px 8px'\r\n : hasDescription\r\n ? `10px 16px 10px 12px`\r\n : `${tokens.spacing.sm} 16px ${tokens.spacing.sm} 12px`,\r\n // Compact: pill-style rounded row (mock parity). Default: square row.\r\n borderRadius: compact ? tokens.borderRadius.md : 0,\r\n justifyContent: 'flex-start',\r\n color: active\r\n ? accentColor\r\n : disabled\r\n ? tokens.colors.text.tertiary\r\n : tokens.colors.text.secondary,\r\n backgroundColor: active\r\n ? `${accentColor}12`\r\n : isHovered && !disabled\r\n ? `${accentColor}08`\r\n : 'transparent',\r\n fontSize: compact ? tokens.typography.fontSize.xs : tokens.typography.fontSize.sm,\r\n fontWeight: active ? tokens.typography.fontWeight.semibold : tokens.typography.fontWeight.normal,\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n cursor: disabled ? 'not-allowed' : 'pointer',\r\n textDecoration: 'none',\r\n borderTop: 'none',\r\n borderRight: 'none',\r\n borderBottom: 'none',\r\n // Compact drops the left accent bar — the active row is conveyed by the\r\n // bg tint + accent text alone (mock parity). Default keeps the 3px bar.\r\n borderLeft: compact\r\n ? 'none'\r\n : active\r\n ? `3px solid ${accentColor}`\r\n : '3px solid transparent',\r\n outline: 'none',\r\n width: '100%',\r\n boxSizing: 'border-box',\r\n transition: tokens.animation.fast,\r\n position: 'relative',\r\n opacity: disabled ? 0.5 : 1,\r\n };\r\n \r\n const tagColors = TAG_COLORS[tagVariant] ?? TAG_COLORS.default;\r\n \r\n const content = (\r\n <>\r\n {/* Icon + collapsed status overlay */}\r\n {iconElement && (\r\n <span style={{ flexShrink: 0, display: 'flex', position: 'relative', marginTop: hasDescription ? '2px' : 0 }}>\r\n {iconElement}\r\n {/* In collapsed mode, show a small status shape overlaid on the icon (top-left) */}\r\n {collapsed && status && (\r\n <span\r\n style={{\r\n position: 'absolute',\r\n top: -3,\r\n left: -4,\r\n lineHeight: 0,\r\n }}\r\n title={statusLabel ?? status}\r\n >\r\n <NavStatusShape status={status} size={7} />\r\n </span>\r\n )}\r\n </span>\r\n )}\r\n {/* Label — always mounted and toggled via data-collapsed so it fades and\r\n slides BOTH on expand and collapse; the icon holds its position as\r\n the anchor and only the label moves. */}\r\n <span\r\n className=\"zsn-label\"\r\n data-collapsed={collapsed ? 'true' : 'false'}\r\n style={{ flex: 1, minWidth: 0, overflow: 'hidden' }}\r\n >\r\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\r\n <span style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>\r\n {label}\r\n </span>\r\n {tag && (\r\n <span style={{\r\n fontSize: '0.6rem',\r\n fontWeight: tokens.typography.fontWeight.bold,\r\n color: tagColors.fg,\r\n backgroundColor: tagColors.bg,\r\n border: `1px solid ${tagColors.border}`,\r\n padding: '1px 5px',\r\n borderRadius: tokens.borderRadius.sm,\r\n textTransform: 'uppercase',\r\n letterSpacing: '0.04em',\r\n flexShrink: 0,\r\n lineHeight: '1.3',\r\n whiteSpace: 'nowrap',\r\n }}>\r\n {tag}\r\n </span>\r\n )}\r\n </span>\r\n {description && !collapsed && (\r\n <span style={{\r\n display: 'block',\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.tertiary,\r\n lineHeight: tokens.typography.lineHeight.normal,\r\n marginTop: '2px',\r\n whiteSpace: 'nowrap',\r\n overflow: 'hidden',\r\n textOverflow: 'ellipsis',\r\n fontWeight: tokens.typography.fontWeight.normal,\r\n }}>\r\n {description}\r\n </span>\r\n )}\r\n </span>\r\n {/* Right-side trailing elements — expanded only; collapsed shows the\r\n status overlay on the icon instead. */}\r\n {!collapsed && (badge !== undefined && badge > 0 || status || suffix || external) && (\r\n <span className=\"zsn-reveal\" style={{\r\n display: 'inline-flex',\r\n alignItems: 'center',\r\n gap: 8,\r\n flexShrink: 0,\r\n marginTop: hasDescription ? '2px' : 0,\r\n }}>\r\n {/* Badge */}\r\n {badge !== undefined && badge > 0 && (\r\n <span style={{\r\n fontSize: '0.65rem',\r\n fontWeight: tokens.typography.fontWeight.bold,\r\n color: '#fff',\r\n backgroundColor: tokens.colors.status.critical,\r\n borderRadius: tokens.borderRadius.full,\r\n minWidth: '18px',\r\n height: '18px',\r\n display: 'inline-flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n padding: '0 5px',\r\n flexShrink: 0,\r\n lineHeight: 1,\r\n boxSizing: 'border-box',\r\n }}>\r\n {badge > 99 ? '99+' : badge}\r\n </span>\r\n )}\r\n {/* Status */}\r\n {status && (\r\n <span\r\n style={{ display: 'inline-flex', alignItems: 'center', flexShrink: 0 }}\r\n role=\"status\"\r\n aria-label={`Status: ${statusLabel ?? status}`}\r\n title={statusLabel ?? status}\r\n >\r\n <NavStatusShape status={status} size={8} />\r\n </span>\r\n )}\r\n {/* Suffix */}\r\n {suffix && (\r\n <span style={{ flexShrink: 0, display: 'inline-flex', alignItems: 'center' }}>\r\n {suffix}\r\n </span>\r\n )}\r\n {/* External */}\r\n {external && (\r\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" style={{ flexShrink: 0, opacity: 0.5 }}>\r\n <path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\" />\r\n <polyline points=\"15 3 21 3 21 9\" />\r\n <line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\" />\r\n </svg>\r\n )}\r\n </span>\r\n )}\r\n </>\r\n );\r\n \r\n const props = {\r\n style: itemStyle,\r\n onMouseEnter: () => setIsHovered(true),\r\n onMouseLeave: () => setIsHovered(false),\r\n title: collapsed ? label : undefined,\r\n 'aria-current': active ? ('page' as const) : undefined,\r\n 'aria-disabled': disabled,\r\n };\r\n \r\n if (href && !disabled) {\r\n return (\r\n <a href={href} onClick={handleClick} target={external ? '_blank' : undefined} rel={external ? 'noopener noreferrer' : undefined} {...props}>\r\n {content}\r\n </a>\r\n );\r\n }\r\n \r\n return (\r\n <button type=\"button\" onClick={handleClick} disabled={disabled} {...props}>\r\n {content}\r\n </button>\r\n );\r\n});\r\n\r\n// ─── Section ─────────────────────────────────────────────────────────────────\r\n\r\nexport interface SideNavSectionProps {\r\n /** Section title */\r\n title?: string;\r\n /** Children (SideNav.Item elements) */\r\n children?: React.ReactNode;\r\n}\r\n\r\nconst SideNavSection = memo(function SideNavSection({\r\n title,\r\n children,\r\n}: SideNavSectionProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n const { collapsed } = useContext(SideNavContext);\r\n \r\n return (\r\n <div style={{ marginTop: tokens.spacing.sm }}>\r\n {title && !collapsed && (\r\n <div className=\"zsn-reveal\" style={{\r\n padding: `${tokens.spacing.xs} 16px ${tokens.spacing.xs} 15px`,\r\n fontSize: tokens.typography.fontSize.xxs,\r\n fontWeight: tokens.typography.fontWeight.bold,\r\n color: tokens.colors.text.tertiary,\r\n textTransform: 'uppercase',\r\n letterSpacing: '0.08em',\r\n }}>\r\n {title}\r\n </div>\r\n )}\r\n {collapsed && title && (\r\n <div style={{\r\n width: '60%',\r\n height: '1px',\r\n backgroundColor: tokens.colors.border.muted,\r\n margin: `${tokens.spacing.xs} auto`,\r\n }} />\r\n )}\r\n {children}\r\n </div>\r\n );\r\n});\r\n\r\n// ─── Divider ─────────────────────────────────────────────────────────────────\r\n\r\nexport interface SideNavDividerProps {\r\n /** Optional label shown in the center of the divider line */\r\n label?: string;\r\n}\r\n\r\nconst SideNavDivider = memo(function SideNavDivider({\r\n label,\r\n}: SideNavDividerProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n const { collapsed } = useContext(SideNavContext);\r\n\r\n if (collapsed) {\r\n return (\r\n <div style={{\r\n width: '60%',\r\n height: '1px',\r\n backgroundColor: tokens.colors.border.muted,\r\n margin: `${tokens.spacing.sm} auto`,\r\n }} />\r\n );\r\n }\r\n\r\n if (label) {\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: tokens.spacing.sm,\r\n padding: `${tokens.spacing.sm} 16px ${tokens.spacing.sm} 15px`,\r\n }}>\r\n <div style={{ flex: 1, height: '1px', backgroundColor: tokens.colors.border.muted }} />\r\n <span style={{\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.tertiary,\r\n textTransform: 'uppercase',\r\n letterSpacing: '0.06em',\r\n whiteSpace: 'nowrap',\r\n }}>\r\n {label}\r\n </span>\r\n <div style={{ flex: 1, height: '1px', backgroundColor: tokens.colors.border.muted }} />\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <div style={{\r\n height: '1px',\r\n backgroundColor: tokens.colors.border.muted,\r\n margin: `${tokens.spacing.sm} 16px ${tokens.spacing.sm} 15px`,\r\n }} />\r\n );\r\n});\r\n\r\n// ─── Footer ──────────────────────────────────────────────────────────────────\r\n\r\nexport interface SideNavFooterProps {\r\n children?: React.ReactNode;\r\n}\r\n\r\nconst SideNavFooter = memo(function SideNavFooter({\r\n children,\r\n}: SideNavFooterProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n \r\n return (\r\n <div style={{\r\n marginTop: 'auto',\r\n borderTop: `1px solid ${tokens.colors.border.muted}`,\r\n paddingTop: tokens.spacing.xs,\r\n paddingBottom: tokens.spacing.xs,\r\n }}>\r\n {children}\r\n </div>\r\n );\r\n});\r\n\r\n// ─── Compound Export ─────────────────────────────────────────────────────────\r\n\r\ntype SideNavComponent = typeof SideNavRoot & {\r\n Header: typeof SideNavHeader;\r\n Item: typeof SideNavItem;\r\n Section: typeof SideNavSection;\r\n Divider: typeof SideNavDivider;\r\n Footer: typeof SideNavFooter;\r\n};\r\n\r\nexport const SideNav: SideNavComponent = Object.assign(SideNavRoot, {\r\n Header: SideNavHeader,\r\n Item: SideNavItem,\r\n Section: SideNavSection,\r\n Divider: SideNavDivider,\r\n Footer: SideNavFooter,\r\n});\r\n\r\nexport default SideNav;\r\n"],"names":["SideNav","SideNavHeader","SideNavItem","SideNavSection","SideNavDivider","SideNavFooter"],"mappings":"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"}
@@ -49,7 +49,7 @@ const Popover = memo(function Popover2({
49
49
  clearTimeout(hoverTimeout.current);
50
50
  };
51
51
  }, []);
52
- const [position, setPosition] = useState({});
52
+ const [position, setPosition] = useState({ position: "fixed", left: 0, top: 0 });
53
53
  const [positioned, setPositioned] = useState(false);
54
54
  useEffect(() => {
55
55
  if (!isOpen) setPositioned(false);
@@ -1 +1 @@
1
- {"version":3,"file":"Popover.js","sources":["../../../../src/react/core/overlays/Popover.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - Popover & Menu Components\r\n * \r\n * General-purpose popover for dropdown menus, sort controls, action menus,\r\n * and contextual information. Portal-rendered with auto-positioning.\r\n * \r\n * Astro UX Compliance:\r\n * - Consistent with Dialog/Tooltip styling\r\n * - Focus trap for keyboard navigation\r\n * - Escape to close\r\n * - Click outside to close\r\n * - Reduced motion support\r\n * \r\n * @example\r\n * ```tsx\r\n * <Popover\r\n * trigger={<Button variant=\"secondary\">Sort</Button>}\r\n * placement=\"bottom-start\"\r\n * >\r\n * <Menu>\r\n * <Menu.Item onClick={...}>Sort by Time</Menu.Item>\r\n * <Menu.Item onClick={...}>Sort by Name</Menu.Item>\r\n * <Menu.Divider />\r\n * <Menu.Item onClick={...} destructive>Clear All</Menu.Item>\r\n * </Menu>\r\n * </Popover>\r\n * ```\r\n */\r\n\r\nimport React, { memo, useState, useRef, useEffect, useCallback, createContext, useContext } from 'react';\r\nimport { useTheme } from '../../theme';\r\n\r\n// ─── Popover ─────────────────────────────────────────────────────────────────\r\n\r\nexport type PopoverPlacement = \r\n | 'top' | 'top-start' | 'top-end'\r\n | 'bottom' | 'bottom-start' | 'bottom-end'\r\n | 'left' | 'left-start' | 'left-end'\r\n | 'right' | 'right-start' | 'right-end';\r\n\r\nexport interface PopoverProps {\r\n /** Trigger element */\r\n trigger: React.ReactElement;\r\n /** Popover content */\r\n children: React.ReactNode;\r\n /** Placement relative to trigger */\r\n placement?: PopoverPlacement;\r\n /** Open on hover instead of click */\r\n hover?: boolean;\r\n /** Controlled open state */\r\n open?: boolean;\r\n /** Controlled open change */\r\n onOpenChange?: (open: boolean) => void;\r\n /** Close on content click */\r\n closeOnClick?: boolean;\r\n /** Width: 'trigger' matches trigger width, number for px, 'auto' */\r\n width?: 'trigger' | 'auto' | number;\r\n /** Custom style for popover panel */\r\n style?: React.CSSProperties;\r\n}\r\n\r\nconst PopoverContext = createContext<{ close: () => void }>({ close: () => {} });\r\n\r\nexport const Popover = memo(function Popover({\r\n trigger,\r\n children,\r\n placement = 'bottom-start',\r\n hover = false,\r\n open: controlledOpen,\r\n onOpenChange,\r\n closeOnClick = true,\r\n width = 'auto',\r\n style,\r\n}: PopoverProps): React.ReactElement {\r\n const { tokens, theme } = useTheme();\r\n const isTransparentTheme = theme === 'transparent' || theme === 'transparent-bold' || theme === 'transparent-minimal';\r\n const [internalOpen, setInternalOpen] = useState(false);\r\n const triggerRef = useRef<HTMLDivElement>(null);\r\n const popoverRef = useRef<HTMLDivElement>(null);\r\n const hoverTimeout = useRef<ReturnType<typeof setTimeout>>();\r\n \r\n const isOpen = controlledOpen !== undefined ? controlledOpen : internalOpen;\r\n \r\n const setOpen = useCallback((val: boolean) => {\r\n if (controlledOpen === undefined) setInternalOpen(val);\r\n onOpenChange?.(val);\r\n }, [controlledOpen, onOpenChange]);\r\n \r\n const close = useCallback(() => setOpen(false), [setOpen]);\r\n \r\n // Click outside\r\n useEffect(() => {\r\n if (!isOpen) return;\r\n const handler = (e: MouseEvent) => {\r\n if (\r\n triggerRef.current && !triggerRef.current.contains(e.target as Node) &&\r\n popoverRef.current && !popoverRef.current.contains(e.target as Node)\r\n ) {\r\n close();\r\n }\r\n };\r\n document.addEventListener('mousedown', handler);\r\n return () => document.removeEventListener('mousedown', handler);\r\n }, [isOpen, close]);\r\n \r\n // Escape key + cleanup hoverTimeout on unmount\r\n useEffect(() => {\r\n if (!isOpen) return;\r\n const handler = (e: KeyboardEvent) => {\r\n if (e.key === 'Escape') close();\r\n };\r\n document.addEventListener('keydown', handler);\r\n return () => document.removeEventListener('keydown', handler);\r\n }, [isOpen, close]);\r\n \r\n useEffect(() => {\r\n return () => { clearTimeout(hoverTimeout.current); };\r\n }, []);\r\n \r\n // Position calculation — `positioned` gates visibility so the panel is never\r\n // painted in document flow before the first RAF resolves coordinates.\r\n const [position, setPosition] = useState<React.CSSProperties>({});\r\n const [positioned, setPositioned] = useState(false);\r\n\r\n // Reset visibility guard whenever the popover closes so the next open starts\r\n // hidden again rather than flashing the stale coordinates for one frame.\r\n useEffect(() => {\r\n if (!isOpen) setPositioned(false);\r\n }, [isOpen]);\r\n\r\n useEffect(() => {\r\n if (!isOpen || !triggerRef.current) return;\r\n const gap = 8;\r\n const viewportPadding = 8;\r\n\r\n const updatePosition = () => {\r\n const rect = triggerRef.current?.getBoundingClientRect();\r\n if (!rect) return;\r\n\r\n const panelWidth = width === 'trigger'\r\n ? rect.width\r\n : typeof width === 'number'\r\n ? width\r\n : (popoverRef.current?.offsetWidth ?? 220);\r\n const panelHeight = popoverRef.current?.offsetHeight ?? 220;\r\n\r\n let left = rect.left;\r\n let top = rect.bottom + gap;\r\n\r\n if (placement.startsWith('top')) {\r\n top = rect.top - panelHeight - gap;\r\n } else if (placement.startsWith('bottom')) {\r\n top = rect.bottom + gap;\r\n } else if (placement.startsWith('left')) {\r\n left = rect.left - panelWidth - gap;\r\n } else if (placement.startsWith('right')) {\r\n left = rect.right + gap;\r\n }\r\n\r\n if (placement.startsWith('top') || placement.startsWith('bottom')) {\r\n if (placement.endsWith('-start')) {\r\n left = rect.left;\r\n } else if (placement.endsWith('-end')) {\r\n left = rect.right - panelWidth;\r\n } else {\r\n left = rect.left + (rect.width - panelWidth) / 2;\r\n }\r\n } else {\r\n if (placement.endsWith('-start')) {\r\n top = rect.top;\r\n } else if (placement.endsWith('-end')) {\r\n top = rect.bottom - panelHeight;\r\n } else {\r\n top = rect.top + (rect.height - panelHeight) / 2;\r\n }\r\n }\r\n\r\n left = Math.max(viewportPadding, Math.min(left, window.innerWidth - panelWidth - viewportPadding));\r\n top = Math.max(viewportPadding, Math.min(top, window.innerHeight - panelHeight - viewportPadding));\r\n\r\n const pos: React.CSSProperties = {\r\n position: 'fixed',\r\n zIndex: 9990,\r\n left,\r\n top,\r\n };\r\n\r\n if (width === 'trigger') {\r\n pos.width = rect.width;\r\n pos.minWidth = rect.width;\r\n } else if (typeof width === 'number') {\r\n pos.width = width;\r\n }\r\n\r\n setPosition(pos);\r\n setPositioned(true);\r\n };\r\n\r\n const raf = requestAnimationFrame(updatePosition);\r\n window.addEventListener('resize', updatePosition);\r\n window.addEventListener('scroll', updatePosition, true);\r\n return () => {\r\n cancelAnimationFrame(raf);\r\n window.removeEventListener('resize', updatePosition);\r\n window.removeEventListener('scroll', updatePosition, true);\r\n };\r\n }, [isOpen, placement, width]);\r\n \r\n const triggerProps: Record<string, (() => void) | undefined> = {};\r\n if (hover) {\r\n triggerProps.onMouseEnter = () => {\r\n clearTimeout(hoverTimeout.current);\r\n setOpen(true);\r\n };\r\n triggerProps.onMouseLeave = () => {\r\n hoverTimeout.current = setTimeout(close, 150);\r\n };\r\n } else {\r\n triggerProps.onClick = () => setOpen(!isOpen);\r\n }\r\n \r\n const popoverHoverProps = hover ? {\r\n onMouseEnter: () => clearTimeout(hoverTimeout.current),\r\n onMouseLeave: () => { hoverTimeout.current = setTimeout(close, 150); },\r\n } : {};\r\n \r\n return (\r\n <PopoverContext.Provider value={{ close }}>\r\n <div ref={triggerRef} style={{ display: 'inline-flex' }} aria-expanded={isOpen} aria-haspopup=\"true\" {...triggerProps}>\r\n {trigger}\r\n </div>\r\n \r\n {isOpen && (\r\n <div\r\n ref={popoverRef}\r\n role=\"dialog\"\r\n onClick={closeOnClick ? close : undefined}\r\n {...popoverHoverProps}\r\n style={{\r\n ...position,\r\n visibility: positioned ? 'visible' : 'hidden',\r\n backgroundColor: isTransparentTheme\r\n ? 'rgba(15, 12, 30, 0.85)'\r\n : tokens.colors.background.elevated,\r\n border: `1px solid ${tokens.colors.border.muted}`,\r\n borderRadius: tokens.borderRadius.md,\r\n boxShadow: tokens.shadows.lg,\r\n padding: tokens.spacing.xs,\r\n maxWidth: 'min(320px, calc(100vw - 16px))',\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n animation: `zendir-popover-enter 150ms ${tokens.animation.easing.default}`,\r\n ...(isTransparentTheme ? {\r\n backdropFilter: 'blur(16px)',\r\n WebkitBackdropFilter: 'blur(16px)',\r\n } : {}),\r\n ...style,\r\n }}\r\n >\r\n {children}\r\n </div>\r\n )}\r\n \r\n <style>{`\r\n @keyframes zendir-popover-enter {\r\n from { opacity: 0; transform: translateY(-4px) scale(0.98); }\r\n to { opacity: 1; transform: translateY(0) scale(1); }\r\n }\r\n `}</style>\r\n </PopoverContext.Provider>\r\n );\r\n});\r\n\r\n// ─── Menu ────────────────────────────────────────────────────────────────────\r\n\r\nexport interface MenuProps {\r\n children: React.ReactNode;\r\n style?: React.CSSProperties;\r\n}\r\n\r\nconst MenuRoot = memo(function Menu({ children, style }: MenuProps): React.ReactElement {\r\n return (\r\n <div role=\"menu\" style={{ minWidth: 'min(160px, calc(100vw - 32px))', ...style }}>\r\n {children}\r\n </div>\r\n );\r\n});\r\n\r\n// ─── Menu.Item ───────────────────────────────────────────────────────────────\r\n\r\nexport interface MenuItemProps {\r\n /** Click handler */\r\n onClick?: () => void;\r\n /** Icon (ReactNode or IconName string) */\r\n icon?: React.ReactNode;\r\n /** Label text */\r\n children: React.ReactNode;\r\n /** Disabled state */\r\n disabled?: boolean;\r\n /** Destructive action (red text) */\r\n destructive?: boolean;\r\n /** Keyboard shortcut hint */\r\n shortcut?: string;\r\n}\r\n\r\nconst MenuItem = memo(function MenuItem({\r\n onClick,\r\n icon,\r\n children,\r\n disabled = false,\r\n destructive = false,\r\n shortcut,\r\n}: MenuItemProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n const { close } = useContext(PopoverContext);\r\n const [isHovered, setIsHovered] = useState(false);\r\n \r\n const handleClick = useCallback(() => {\r\n if (disabled) return;\r\n onClick?.();\r\n close();\r\n }, [disabled, onClick, close]);\r\n \r\n const handleKeyDown = useCallback((e: React.KeyboardEvent) => {\r\n const item = e.currentTarget;\r\n const siblings = item.parentElement?.querySelectorAll('[role=\"menuitem\"]:not([disabled])');\r\n if (!siblings) return;\r\n const items = Array.from(siblings) as HTMLElement[];\r\n const idx = items.indexOf(item as HTMLElement);\r\n \r\n if (e.key === 'ArrowDown') {\r\n e.preventDefault();\r\n items[(idx + 1) % items.length]?.focus();\r\n } else if (e.key === 'ArrowUp') {\r\n e.preventDefault();\r\n items[(idx - 1 + items.length) % items.length]?.focus();\r\n } else if (e.key === 'Home') {\r\n e.preventDefault();\r\n items[0]?.focus();\r\n } else if (e.key === 'End') {\r\n e.preventDefault();\r\n items[items.length - 1]?.focus();\r\n }\r\n }, []);\r\n \r\n const color = destructive\r\n ? tokens.colors.status.critical\r\n : disabled\r\n ? tokens.colors.text.tertiary\r\n : tokens.colors.text.primary;\r\n \r\n return (\r\n <button\r\n role=\"menuitem\"\r\n tabIndex={disabled ? -1 : 0}\r\n onClick={handleClick}\r\n onKeyDown={handleKeyDown}\r\n disabled={disabled}\r\n onMouseEnter={() => setIsHovered(true)}\r\n onMouseLeave={() => setIsHovered(false)}\r\n style={{\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: tokens.spacing.sm,\r\n width: '100%',\r\n padding: `${tokens.spacing.sm} ${tokens.spacing.sm}`,\r\n minHeight: 44,\r\n border: 'none',\r\n background: isHovered && !disabled\r\n ? `${destructive ? tokens.colors.status.critical : tokens.colors.accent.primary}12`\r\n : 'transparent',\r\n color,\r\n fontSize: tokens.typography.fontSize.sm,\r\n fontWeight: tokens.typography.fontWeight.normal,\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n cursor: disabled ? 'not-allowed' : 'pointer',\r\n borderRadius: tokens.borderRadius.sm,\r\n textAlign: 'left',\r\n transition: tokens.animation.fast,\r\n opacity: disabled ? 0.5 : 1,\r\n outline: 'none',\r\n }}\r\n >\r\n {icon && <span style={{ flexShrink: 0, display: 'flex' }}>{icon}</span>}\r\n <span style={{ flex: 1 }}>{children}</span>\r\n {shortcut && (\r\n <span style={{\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.tertiary,\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n flexShrink: 0,\r\n }}>\r\n {shortcut}\r\n </span>\r\n )}\r\n </button>\r\n );\r\n});\r\n\r\n// ─── Menu.Divider ────────────────────────────────────────────────────────────\r\n\r\nconst MenuDivider = memo(function MenuDivider(): React.ReactElement {\r\n const { tokens } = useTheme();\r\n return (\r\n <div\r\n role=\"separator\"\r\n style={{\r\n height: '1px',\r\n backgroundColor: tokens.colors.border.muted,\r\n margin: `${tokens.spacing.xs} 0`,\r\n }}\r\n />\r\n );\r\n});\r\n\r\n// ─── Menu.Label ──────────────────────────────────────────────────────────────\r\n\r\nconst MenuLabel = memo(function MenuLabel({ children }: { children: React.ReactNode }): React.ReactElement {\r\n const { tokens } = useTheme();\r\n return (\r\n <div style={{\r\n padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,\r\n fontSize: tokens.typography.fontSize.xxs,\r\n fontWeight: tokens.typography.fontWeight.bold,\r\n color: tokens.colors.text.tertiary,\r\n textTransform: 'uppercase',\r\n letterSpacing: '0.06em',\r\n }}>\r\n {children}\r\n </div>\r\n );\r\n});\r\n\r\n// ─── Compound Export ─────────────────────────────────────────────────────────\r\n\r\ntype MenuComponent = typeof MenuRoot & {\r\n Item: typeof MenuItem;\r\n Divider: typeof MenuDivider;\r\n Label: typeof MenuLabel;\r\n};\r\n\r\nexport const Menu: MenuComponent = Object.assign(MenuRoot, {\r\n Item: MenuItem,\r\n Divider: MenuDivider,\r\n Label: MenuLabel,\r\n});\r\n\r\nexport default Popover;\r\n"],"names":["Popover","Menu","MenuItem","MenuDivider","MenuLabel"],"mappings":"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"}
1
+ {"version":3,"file":"Popover.js","sources":["../../../../src/react/core/overlays/Popover.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - Popover & Menu Components\r\n * \r\n * General-purpose popover for dropdown menus, sort controls, action menus,\r\n * and contextual information. Portal-rendered with auto-positioning.\r\n * \r\n * Astro UX Compliance:\r\n * - Consistent with Dialog/Tooltip styling\r\n * - Focus trap for keyboard navigation\r\n * - Escape to close\r\n * - Click outside to close\r\n * - Reduced motion support\r\n * \r\n * @example\r\n * ```tsx\r\n * <Popover\r\n * trigger={<Button variant=\"secondary\">Sort</Button>}\r\n * placement=\"bottom-start\"\r\n * >\r\n * <Menu>\r\n * <Menu.Item onClick={...}>Sort by Time</Menu.Item>\r\n * <Menu.Item onClick={...}>Sort by Name</Menu.Item>\r\n * <Menu.Divider />\r\n * <Menu.Item onClick={...} destructive>Clear All</Menu.Item>\r\n * </Menu>\r\n * </Popover>\r\n * ```\r\n */\r\n\r\nimport React, { memo, useState, useRef, useEffect, useCallback, createContext, useContext } from 'react';\r\nimport { useTheme } from '../../theme';\r\n\r\n// ─── Popover ─────────────────────────────────────────────────────────────────\r\n\r\nexport type PopoverPlacement = \r\n | 'top' | 'top-start' | 'top-end'\r\n | 'bottom' | 'bottom-start' | 'bottom-end'\r\n | 'left' | 'left-start' | 'left-end'\r\n | 'right' | 'right-start' | 'right-end';\r\n\r\nexport interface PopoverProps {\r\n /** Trigger element */\r\n trigger: React.ReactElement;\r\n /** Popover content */\r\n children: React.ReactNode;\r\n /** Placement relative to trigger */\r\n placement?: PopoverPlacement;\r\n /** Open on hover instead of click */\r\n hover?: boolean;\r\n /** Controlled open state */\r\n open?: boolean;\r\n /** Controlled open change */\r\n onOpenChange?: (open: boolean) => void;\r\n /** Close on content click */\r\n closeOnClick?: boolean;\r\n /** Width: 'trigger' matches trigger width, number for px, 'auto' */\r\n width?: 'trigger' | 'auto' | number;\r\n /** Custom style for popover panel */\r\n style?: React.CSSProperties;\r\n}\r\n\r\nconst PopoverContext = createContext<{ close: () => void }>({ close: () => {} });\r\n\r\nexport const Popover = memo(function Popover({\r\n trigger,\r\n children,\r\n placement = 'bottom-start',\r\n hover = false,\r\n open: controlledOpen,\r\n onOpenChange,\r\n closeOnClick = true,\r\n width = 'auto',\r\n style,\r\n}: PopoverProps): React.ReactElement {\r\n const { tokens, theme } = useTheme();\r\n const isTransparentTheme = theme === 'transparent' || theme === 'transparent-bold' || theme === 'transparent-minimal';\r\n const [internalOpen, setInternalOpen] = useState(false);\r\n const triggerRef = useRef<HTMLDivElement>(null);\r\n const popoverRef = useRef<HTMLDivElement>(null);\r\n const hoverTimeout = useRef<ReturnType<typeof setTimeout>>();\r\n \r\n const isOpen = controlledOpen !== undefined ? controlledOpen : internalOpen;\r\n \r\n const setOpen = useCallback((val: boolean) => {\r\n if (controlledOpen === undefined) setInternalOpen(val);\r\n onOpenChange?.(val);\r\n }, [controlledOpen, onOpenChange]);\r\n \r\n const close = useCallback(() => setOpen(false), [setOpen]);\r\n \r\n // Click outside\r\n useEffect(() => {\r\n if (!isOpen) return;\r\n const handler = (e: MouseEvent) => {\r\n if (\r\n triggerRef.current && !triggerRef.current.contains(e.target as Node) &&\r\n popoverRef.current && !popoverRef.current.contains(e.target as Node)\r\n ) {\r\n close();\r\n }\r\n };\r\n document.addEventListener('mousedown', handler);\r\n return () => document.removeEventListener('mousedown', handler);\r\n }, [isOpen, close]);\r\n \r\n // Escape key + cleanup hoverTimeout on unmount\r\n useEffect(() => {\r\n if (!isOpen) return;\r\n const handler = (e: KeyboardEvent) => {\r\n if (e.key === 'Escape') close();\r\n };\r\n document.addEventListener('keydown', handler);\r\n return () => document.removeEventListener('keydown', handler);\r\n }, [isOpen, close]);\r\n \r\n useEffect(() => {\r\n return () => { clearTimeout(hoverTimeout.current); };\r\n }, []);\r\n \r\n // Position calculation — `positioned` gates visibility so the panel is never\r\n // painted in document flow before the first RAF resolves coordinates.\r\n // Initializing with position: 'fixed' ensures that the hidden first-frame\r\n // measurement doesn't get constrained by the trigger's parent container.\r\n const [position, setPosition] = useState<React.CSSProperties>({ position: 'fixed', left: 0, top: 0 });\r\n const [positioned, setPositioned] = useState(false);\r\n\r\n // Reset visibility guard whenever the popover closes so the next open starts\r\n // hidden again rather than flashing the stale coordinates for one frame.\r\n useEffect(() => {\r\n if (!isOpen) setPositioned(false);\r\n }, [isOpen]);\r\n\r\n useEffect(() => {\r\n if (!isOpen || !triggerRef.current) return;\r\n const gap = 8;\r\n const viewportPadding = 8;\r\n\r\n const updatePosition = () => {\r\n const rect = triggerRef.current?.getBoundingClientRect();\r\n if (!rect) return;\r\n\r\n const panelWidth = width === 'trigger'\r\n ? rect.width\r\n : typeof width === 'number'\r\n ? width\r\n : (popoverRef.current?.offsetWidth ?? 220);\r\n const panelHeight = popoverRef.current?.offsetHeight ?? 220;\r\n\r\n let left = rect.left;\r\n let top = rect.bottom + gap;\r\n\r\n if (placement.startsWith('top')) {\r\n top = rect.top - panelHeight - gap;\r\n } else if (placement.startsWith('bottom')) {\r\n top = rect.bottom + gap;\r\n } else if (placement.startsWith('left')) {\r\n left = rect.left - panelWidth - gap;\r\n } else if (placement.startsWith('right')) {\r\n left = rect.right + gap;\r\n }\r\n\r\n if (placement.startsWith('top') || placement.startsWith('bottom')) {\r\n if (placement.endsWith('-start')) {\r\n left = rect.left;\r\n } else if (placement.endsWith('-end')) {\r\n left = rect.right - panelWidth;\r\n } else {\r\n left = rect.left + (rect.width - panelWidth) / 2;\r\n }\r\n } else {\r\n if (placement.endsWith('-start')) {\r\n top = rect.top;\r\n } else if (placement.endsWith('-end')) {\r\n top = rect.bottom - panelHeight;\r\n } else {\r\n top = rect.top + (rect.height - panelHeight) / 2;\r\n }\r\n }\r\n\r\n left = Math.max(viewportPadding, Math.min(left, window.innerWidth - panelWidth - viewportPadding));\r\n top = Math.max(viewportPadding, Math.min(top, window.innerHeight - panelHeight - viewportPadding));\r\n\r\n const pos: React.CSSProperties = {\r\n position: 'fixed',\r\n zIndex: 9990,\r\n left,\r\n top,\r\n };\r\n\r\n if (width === 'trigger') {\r\n pos.width = rect.width;\r\n pos.minWidth = rect.width;\r\n } else if (typeof width === 'number') {\r\n pos.width = width;\r\n }\r\n\r\n setPosition(pos);\r\n setPositioned(true);\r\n };\r\n\r\n const raf = requestAnimationFrame(updatePosition);\r\n window.addEventListener('resize', updatePosition);\r\n window.addEventListener('scroll', updatePosition, true);\r\n return () => {\r\n cancelAnimationFrame(raf);\r\n window.removeEventListener('resize', updatePosition);\r\n window.removeEventListener('scroll', updatePosition, true);\r\n };\r\n }, [isOpen, placement, width]);\r\n \r\n const triggerProps: Record<string, (() => void) | undefined> = {};\r\n if (hover) {\r\n triggerProps.onMouseEnter = () => {\r\n clearTimeout(hoverTimeout.current);\r\n setOpen(true);\r\n };\r\n triggerProps.onMouseLeave = () => {\r\n hoverTimeout.current = setTimeout(close, 150);\r\n };\r\n } else {\r\n triggerProps.onClick = () => setOpen(!isOpen);\r\n }\r\n \r\n const popoverHoverProps = hover ? {\r\n onMouseEnter: () => clearTimeout(hoverTimeout.current),\r\n onMouseLeave: () => { hoverTimeout.current = setTimeout(close, 150); },\r\n } : {};\r\n \r\n return (\r\n <PopoverContext.Provider value={{ close }}>\r\n <div ref={triggerRef} style={{ display: 'inline-flex' }} aria-expanded={isOpen} aria-haspopup=\"true\" {...triggerProps}>\r\n {trigger}\r\n </div>\r\n \r\n {isOpen && (\r\n <div\r\n ref={popoverRef}\r\n role=\"dialog\"\r\n onClick={closeOnClick ? close : undefined}\r\n {...popoverHoverProps}\r\n style={{\r\n ...position,\r\n visibility: positioned ? 'visible' : 'hidden',\r\n backgroundColor: isTransparentTheme\r\n ? 'rgba(15, 12, 30, 0.85)'\r\n : tokens.colors.background.elevated,\r\n border: `1px solid ${tokens.colors.border.muted}`,\r\n borderRadius: tokens.borderRadius.md,\r\n boxShadow: tokens.shadows.lg,\r\n padding: tokens.spacing.xs,\r\n maxWidth: 'min(320px, calc(100vw - 16px))',\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n animation: `zendir-popover-enter 150ms ${tokens.animation.easing.default}`,\r\n ...(isTransparentTheme ? {\r\n backdropFilter: 'blur(16px)',\r\n WebkitBackdropFilter: 'blur(16px)',\r\n } : {}),\r\n ...style,\r\n }}\r\n >\r\n {children}\r\n </div>\r\n )}\r\n \r\n <style>{`\r\n @keyframes zendir-popover-enter {\r\n from { opacity: 0; transform: translateY(-4px) scale(0.98); }\r\n to { opacity: 1; transform: translateY(0) scale(1); }\r\n }\r\n `}</style>\r\n </PopoverContext.Provider>\r\n );\r\n});\r\n\r\n// ─── Menu ────────────────────────────────────────────────────────────────────\r\n\r\nexport interface MenuProps {\r\n children: React.ReactNode;\r\n style?: React.CSSProperties;\r\n}\r\n\r\nconst MenuRoot = memo(function Menu({ children, style }: MenuProps): React.ReactElement {\r\n return (\r\n <div role=\"menu\" style={{ minWidth: 'min(160px, calc(100vw - 32px))', ...style }}>\r\n {children}\r\n </div>\r\n );\r\n});\r\n\r\n// ─── Menu.Item ───────────────────────────────────────────────────────────────\r\n\r\nexport interface MenuItemProps {\r\n /** Click handler */\r\n onClick?: () => void;\r\n /** Icon (ReactNode or IconName string) */\r\n icon?: React.ReactNode;\r\n /** Label text */\r\n children: React.ReactNode;\r\n /** Disabled state */\r\n disabled?: boolean;\r\n /** Destructive action (red text) */\r\n destructive?: boolean;\r\n /** Keyboard shortcut hint */\r\n shortcut?: string;\r\n}\r\n\r\nconst MenuItem = memo(function MenuItem({\r\n onClick,\r\n icon,\r\n children,\r\n disabled = false,\r\n destructive = false,\r\n shortcut,\r\n}: MenuItemProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n const { close } = useContext(PopoverContext);\r\n const [isHovered, setIsHovered] = useState(false);\r\n \r\n const handleClick = useCallback(() => {\r\n if (disabled) return;\r\n onClick?.();\r\n close();\r\n }, [disabled, onClick, close]);\r\n \r\n const handleKeyDown = useCallback((e: React.KeyboardEvent) => {\r\n const item = e.currentTarget;\r\n const siblings = item.parentElement?.querySelectorAll('[role=\"menuitem\"]:not([disabled])');\r\n if (!siblings) return;\r\n const items = Array.from(siblings) as HTMLElement[];\r\n const idx = items.indexOf(item as HTMLElement);\r\n \r\n if (e.key === 'ArrowDown') {\r\n e.preventDefault();\r\n items[(idx + 1) % items.length]?.focus();\r\n } else if (e.key === 'ArrowUp') {\r\n e.preventDefault();\r\n items[(idx - 1 + items.length) % items.length]?.focus();\r\n } else if (e.key === 'Home') {\r\n e.preventDefault();\r\n items[0]?.focus();\r\n } else if (e.key === 'End') {\r\n e.preventDefault();\r\n items[items.length - 1]?.focus();\r\n }\r\n }, []);\r\n \r\n const color = destructive\r\n ? tokens.colors.status.critical\r\n : disabled\r\n ? tokens.colors.text.tertiary\r\n : tokens.colors.text.primary;\r\n \r\n return (\r\n <button\r\n role=\"menuitem\"\r\n tabIndex={disabled ? -1 : 0}\r\n onClick={handleClick}\r\n onKeyDown={handleKeyDown}\r\n disabled={disabled}\r\n onMouseEnter={() => setIsHovered(true)}\r\n onMouseLeave={() => setIsHovered(false)}\r\n style={{\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: tokens.spacing.sm,\r\n width: '100%',\r\n padding: `${tokens.spacing.sm} ${tokens.spacing.sm}`,\r\n minHeight: 44,\r\n border: 'none',\r\n background: isHovered && !disabled\r\n ? `${destructive ? tokens.colors.status.critical : tokens.colors.accent.primary}12`\r\n : 'transparent',\r\n color,\r\n fontSize: tokens.typography.fontSize.sm,\r\n fontWeight: tokens.typography.fontWeight.normal,\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n cursor: disabled ? 'not-allowed' : 'pointer',\r\n borderRadius: tokens.borderRadius.sm,\r\n textAlign: 'left',\r\n transition: tokens.animation.fast,\r\n opacity: disabled ? 0.5 : 1,\r\n outline: 'none',\r\n }}\r\n >\r\n {icon && <span style={{ flexShrink: 0, display: 'flex' }}>{icon}</span>}\r\n <span style={{ flex: 1 }}>{children}</span>\r\n {shortcut && (\r\n <span style={{\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.tertiary,\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n flexShrink: 0,\r\n }}>\r\n {shortcut}\r\n </span>\r\n )}\r\n </button>\r\n );\r\n});\r\n\r\n// ─── Menu.Divider ────────────────────────────────────────────────────────────\r\n\r\nconst MenuDivider = memo(function MenuDivider(): React.ReactElement {\r\n const { tokens } = useTheme();\r\n return (\r\n <div\r\n role=\"separator\"\r\n style={{\r\n height: '1px',\r\n backgroundColor: tokens.colors.border.muted,\r\n margin: `${tokens.spacing.xs} 0`,\r\n }}\r\n />\r\n );\r\n});\r\n\r\n// ─── Menu.Label ──────────────────────────────────────────────────────────────\r\n\r\nconst MenuLabel = memo(function MenuLabel({ children }: { children: React.ReactNode }): React.ReactElement {\r\n const { tokens } = useTheme();\r\n return (\r\n <div style={{\r\n padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,\r\n fontSize: tokens.typography.fontSize.xxs,\r\n fontWeight: tokens.typography.fontWeight.bold,\r\n color: tokens.colors.text.tertiary,\r\n textTransform: 'uppercase',\r\n letterSpacing: '0.06em',\r\n }}>\r\n {children}\r\n </div>\r\n );\r\n});\r\n\r\n// ─── Compound Export ─────────────────────────────────────────────────────────\r\n\r\ntype MenuComponent = typeof MenuRoot & {\r\n Item: typeof MenuItem;\r\n Divider: typeof MenuDivider;\r\n Label: typeof MenuLabel;\r\n};\r\n\r\nexport const Menu: MenuComponent = Object.assign(MenuRoot, {\r\n Item: MenuItem,\r\n Divider: MenuDivider,\r\n Label: MenuLabel,\r\n});\r\n\r\nexport default Popover;\r\n"],"names":["Popover","Menu","MenuItem","MenuDivider","MenuLabel"],"mappings":";;;AA6DA,MAAM,iBAAiB,cAAqC,EAAE,OAAO,MAAM;AAAC,GAAG;AAExE,MAAM,UAAU,KAAK,SAASA,SAAQ;AAAA,EAC3C;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,MAAM;AAAA,EACN;AAAA,EACA,eAAe;AAAA,EACf,QAAQ;AAAA,EACR;AACF,GAAqC;AACnC,QAAM,EAAE,QAAQ,MAAA,IAAU,SAAA;AAC1B,QAAM,qBAAqB,UAAU,iBAAiB,UAAU,sBAAsB,UAAU;AAChG,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,KAAK;AACtD,QAAM,aAAa,OAAuB,IAAI;AAC9C,QAAM,aAAa,OAAuB,IAAI;AAC9C,QAAM,eAAe,OAAA;AAErB,QAAM,SAAS,mBAAmB,SAAY,iBAAiB;AAE/D,QAAM,UAAU,YAAY,CAAC,QAAiB;AAC5C,QAAI,mBAAmB,OAAW,iBAAgB,GAAG;AACrD,iDAAe;AAAA,EACjB,GAAG,CAAC,gBAAgB,YAAY,CAAC;AAEjC,QAAM,QAAQ,YAAY,MAAM,QAAQ,KAAK,GAAG,CAAC,OAAO,CAAC;AAGzD,YAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AACb,UAAM,UAAU,CAAC,MAAkB;AACjC,UACE,WAAW,WAAW,CAAC,WAAW,QAAQ,SAAS,EAAE,MAAc,KACnE,WAAW,WAAW,CAAC,WAAW,QAAQ,SAAS,EAAE,MAAc,GACnE;AACA,cAAA;AAAA,MACF;AAAA,IACF;AACA,aAAS,iBAAiB,aAAa,OAAO;AAC9C,WAAO,MAAM,SAAS,oBAAoB,aAAa,OAAO;AAAA,EAChE,GAAG,CAAC,QAAQ,KAAK,CAAC;AAGlB,YAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AACb,UAAM,UAAU,CAAC,MAAqB;AACpC,UAAI,EAAE,QAAQ,SAAU,OAAA;AAAA,IAC1B;AACA,aAAS,iBAAiB,WAAW,OAAO;AAC5C,WAAO,MAAM,SAAS,oBAAoB,WAAW,OAAO;AAAA,EAC9D,GAAG,CAAC,QAAQ,KAAK,CAAC;AAElB,YAAU,MAAM;AACd,WAAO,MAAM;AAAE,mBAAa,aAAa,OAAO;AAAA,IAAG;AAAA,EACrD,GAAG,CAAA,CAAE;AAML,QAAM,CAAC,UAAU,WAAW,IAAI,SAA8B,EAAE,UAAU,SAAS,MAAM,GAAG,KAAK,EAAA,CAAG;AACpG,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAIlD,YAAU,MAAM;AACd,QAAI,CAAC,OAAQ,eAAc,KAAK;AAAA,EAClC,GAAG,CAAC,MAAM,CAAC;AAEX,YAAU,MAAM;AACd,QAAI,CAAC,UAAU,CAAC,WAAW,QAAS;AACpC,UAAM,MAAM;AACZ,UAAM,kBAAkB;AAExB,UAAM,iBAAiB,MAAM;;AAC3B,YAAM,QAAO,gBAAW,YAAX,mBAAoB;AACjC,UAAI,CAAC,KAAM;AAEX,YAAM,aAAa,UAAU,YACzB,KAAK,QACL,OAAO,UAAU,WACf,UACC,gBAAW,YAAX,mBAAoB,gBAAe;AAC1C,YAAM,gBAAc,gBAAW,YAAX,mBAAoB,iBAAgB;AAExD,UAAI,OAAO,KAAK;AAChB,UAAI,MAAM,KAAK,SAAS;AAExB,UAAI,UAAU,WAAW,KAAK,GAAG;AAC/B,cAAM,KAAK,MAAM,cAAc;AAAA,MACjC,WAAW,UAAU,WAAW,QAAQ,GAAG;AACzC,cAAM,KAAK,SAAS;AAAA,MACtB,WAAW,UAAU,WAAW,MAAM,GAAG;AACvC,eAAO,KAAK,OAAO,aAAa;AAAA,MAClC,WAAW,UAAU,WAAW,OAAO,GAAG;AACxC,eAAO,KAAK,QAAQ;AAAA,MACtB;AAEA,UAAI,UAAU,WAAW,KAAK,KAAK,UAAU,WAAW,QAAQ,GAAG;AACjE,YAAI,UAAU,SAAS,QAAQ,GAAG;AAChC,iBAAO,KAAK;AAAA,QACd,WAAW,UAAU,SAAS,MAAM,GAAG;AACrC,iBAAO,KAAK,QAAQ;AAAA,QACtB,OAAO;AACL,iBAAO,KAAK,QAAQ,KAAK,QAAQ,cAAc;AAAA,QACjD;AAAA,MACF,OAAO;AACL,YAAI,UAAU,SAAS,QAAQ,GAAG;AAChC,gBAAM,KAAK;AAAA,QACb,WAAW,UAAU,SAAS,MAAM,GAAG;AACrC,gBAAM,KAAK,SAAS;AAAA,QACtB,OAAO;AACL,gBAAM,KAAK,OAAO,KAAK,SAAS,eAAe;AAAA,QACjD;AAAA,MACF;AAEA,aAAO,KAAK,IAAI,iBAAiB,KAAK,IAAI,MAAM,OAAO,aAAa,aAAa,eAAe,CAAC;AACjG,YAAM,KAAK,IAAI,iBAAiB,KAAK,IAAI,KAAK,OAAO,cAAc,cAAc,eAAe,CAAC;AAEjG,YAAM,MAA2B;AAAA,QAC/B,UAAU;AAAA,QACV,QAAQ;AAAA,QACR;AAAA,QACA;AAAA,MAAA;AAGF,UAAI,UAAU,WAAW;AACvB,YAAI,QAAQ,KAAK;AACjB,YAAI,WAAW,KAAK;AAAA,MACtB,WAAW,OAAO,UAAU,UAAU;AACpC,YAAI,QAAQ;AAAA,MACd;AAEA,kBAAY,GAAG;AACf,oBAAc,IAAI;AAAA,IACpB;AAEA,UAAM,MAAM,sBAAsB,cAAc;AAChD,WAAO,iBAAiB,UAAU,cAAc;AAChD,WAAO,iBAAiB,UAAU,gBAAgB,IAAI;AACtD,WAAO,MAAM;AACX,2BAAqB,GAAG;AACxB,aAAO,oBAAoB,UAAU,cAAc;AACnD,aAAO,oBAAoB,UAAU,gBAAgB,IAAI;AAAA,IAC3D;AAAA,EACF,GAAG,CAAC,QAAQ,WAAW,KAAK,CAAC;AAE7B,QAAM,eAAyD,CAAA;AAC/D,MAAI,OAAO;AACT,iBAAa,eAAe,MAAM;AAChC,mBAAa,aAAa,OAAO;AACjC,cAAQ,IAAI;AAAA,IACd;AACA,iBAAa,eAAe,MAAM;AAChC,mBAAa,UAAU,WAAW,OAAO,GAAG;AAAA,IAC9C;AAAA,EACF,OAAO;AACL,iBAAa,UAAU,MAAM,QAAQ,CAAC,MAAM;AAAA,EAC9C;AAEA,QAAM,oBAAoB,QAAQ;AAAA,IAChC,cAAc,MAAM,aAAa,aAAa,OAAO;AAAA,IACrD,cAAc,MAAM;AAAE,mBAAa,UAAU,WAAW,OAAO,GAAG;AAAA,IAAG;AAAA,EAAA,IACnE,CAAA;AAEJ,8BACG,eAAe,UAAf,EAAwB,OAAO,EAAE,SAChC,UAAA;AAAA,IAAA,oBAAC,OAAA,EAAI,KAAK,YAAY,OAAO,EAAE,SAAS,cAAA,GAAiB,iBAAe,QAAQ,iBAAc,QAAQ,GAAG,cACtG,UAAA,SACH;AAAA,IAEC,UACC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK;AAAA,QACL,MAAK;AAAA,QACL,SAAS,eAAe,QAAQ;AAAA,QAC/B,GAAG;AAAA,QACJ,OAAO;AAAA,UACL,GAAG;AAAA,UACH,YAAY,aAAa,YAAY;AAAA,UACrC,iBAAiB,qBACb,2BACA,OAAO,OAAO,WAAW;AAAA,UAC7B,QAAQ,aAAa,OAAO,OAAO,OAAO,KAAK;AAAA,UAC/C,cAAc,OAAO,aAAa;AAAA,UAClC,WAAW,OAAO,QAAQ;AAAA,UAC1B,SAAS,OAAO,QAAQ;AAAA,UACxB,UAAU;AAAA,UACV,YAAY,OAAO,WAAW,WAAW;AAAA,UACzC,WAAW,8BAA8B,OAAO,UAAU,OAAO,OAAO;AAAA,UACxE,GAAI,qBAAqB;AAAA,YACvB,gBAAgB;AAAA,YAChB,sBAAsB;AAAA,UAAA,IACpB,CAAA;AAAA,UACJ,GAAG;AAAA,QAAA;AAAA,QAGJ;AAAA,MAAA;AAAA,IAAA;AAAA,wBAIJ,SAAA,EAAO,UAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAAA,CAKN;AAAA,EAAA,GACJ;AAEJ,CAAC;AASD,MAAM,WAAW,KAAK,SAASC,MAAK,EAAE,UAAU,SAAwC;AACtF,SACE,oBAAC,OAAA,EAAI,MAAK,QAAO,OAAO,EAAE,UAAU,kCAAkC,GAAG,MAAA,GACtE,SAAA,CACH;AAEJ,CAAC;AAmBD,MAAM,WAAW,KAAK,SAASC,UAAS;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,cAAc;AAAA,EACd;AACF,GAAsC;AACpC,QAAM,EAAE,OAAA,IAAW,SAAA;AACnB,QAAM,EAAE,MAAA,IAAU,WAAW,cAAc;AAC3C,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAEhD,QAAM,cAAc,YAAY,MAAM;AACpC,QAAI,SAAU;AACd;AACA,UAAA;AAAA,EACF,GAAG,CAAC,UAAU,SAAS,KAAK,CAAC;AAE7B,QAAM,gBAAgB,YAAY,CAAC,MAA2B;;AAC5D,UAAM,OAAO,EAAE;AACf,UAAM,YAAW,UAAK,kBAAL,mBAAoB,iBAAiB;AACtD,QAAI,CAAC,SAAU;AACf,UAAM,QAAQ,MAAM,KAAK,QAAQ;AACjC,UAAM,MAAM,MAAM,QAAQ,IAAmB;AAE7C,QAAI,EAAE,QAAQ,aAAa;AACzB,QAAE,eAAA;AACF,mBAAO,MAAM,KAAK,MAAM,MAAM,MAA9B,mBAAiC;AAAA,IACnC,WAAW,EAAE,QAAQ,WAAW;AAC9B,QAAE,eAAA;AACF,mBAAO,MAAM,IAAI,MAAM,UAAU,MAAM,MAAM,MAA7C,mBAAgD;AAAA,IAClD,WAAW,EAAE,QAAQ,QAAQ;AAC3B,QAAE,eAAA;AACF,kBAAM,CAAC,MAAP,mBAAU;AAAA,IACZ,WAAW,EAAE,QAAQ,OAAO;AAC1B,QAAE,eAAA;AACF,kBAAM,MAAM,SAAS,CAAC,MAAtB,mBAAyB;AAAA,IAC3B;AAAA,EACF,GAAG,CAAA,CAAE;AAEL,QAAM,QAAQ,cACV,OAAO,OAAO,OAAO,WACrB,WACE,OAAO,OAAO,KAAK,WACnB,OAAO,OAAO,KAAK;AAEzB,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,UAAU,WAAW,KAAK;AAAA,MAC1B,SAAS;AAAA,MACT,WAAW;AAAA,MACX;AAAA,MACA,cAAc,MAAM,aAAa,IAAI;AAAA,MACrC,cAAc,MAAM,aAAa,KAAK;AAAA,MACtC,OAAO;AAAA,QACL,SAAS;AAAA,QACT,YAAY;AAAA,QACZ,KAAK,OAAO,QAAQ;AAAA,QACpB,OAAO;AAAA,QACP,SAAS,GAAG,OAAO,QAAQ,EAAE,IAAI,OAAO,QAAQ,EAAE;AAAA,QAClD,WAAW;AAAA,QACX,QAAQ;AAAA,QACR,YAAY,aAAa,CAAC,WACtB,GAAG,cAAc,OAAO,OAAO,OAAO,WAAW,OAAO,OAAO,OAAO,OAAO,OAC7E;AAAA,QACJ;AAAA,QACA,UAAU,OAAO,WAAW,SAAS;AAAA,QACrC,YAAY,OAAO,WAAW,WAAW;AAAA,QACzC,YAAY,OAAO,WAAW,WAAW;AAAA,QACzC,QAAQ,WAAW,gBAAgB;AAAA,QACnC,cAAc,OAAO,aAAa;AAAA,QAClC,WAAW;AAAA,QACX,YAAY,OAAO,UAAU;AAAA,QAC7B,SAAS,WAAW,MAAM;AAAA,QAC1B,SAAS;AAAA,MAAA;AAAA,MAGV,UAAA;AAAA,QAAA,QAAQ,oBAAC,UAAK,OAAO,EAAE,YAAY,GAAG,SAAS,OAAA,GAAW,UAAA,KAAA,CAAK;AAAA,4BAC/D,QAAA,EAAK,OAAO,EAAE,MAAM,EAAA,GAAM,UAAS;AAAA,QACnC,YACC,oBAAC,QAAA,EAAK,OAAO;AAAA,UACX,UAAU,OAAO,WAAW,SAAS;AAAA,UACrC,OAAO,OAAO,OAAO,KAAK;AAAA,UAC1B,YAAY,OAAO,WAAW,WAAW;AAAA,UACzC,YAAY;AAAA,QAAA,GAEX,UAAA,SAAA,CACH;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAIR,CAAC;AAID,MAAM,cAAc,KAAK,SAASC,eAAkC;AAClE,QAAM,EAAE,OAAA,IAAW,SAAA;AACnB,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,OAAO;AAAA,QACL,QAAQ;AAAA,QACR,iBAAiB,OAAO,OAAO,OAAO;AAAA,QACtC,QAAQ,GAAG,OAAO,QAAQ,EAAE;AAAA,MAAA;AAAA,IAC9B;AAAA,EAAA;AAGN,CAAC;AAID,MAAM,YAAY,KAAK,SAASC,WAAU,EAAE,YAA+D;AACzG,QAAM,EAAE,OAAA,IAAW,SAAA;AACnB,SACE,oBAAC,SAAI,OAAO;AAAA,IACV,SAAS,GAAG,OAAO,QAAQ,EAAE,IAAI,OAAO,QAAQ,EAAE;AAAA,IAClD,UAAU,OAAO,WAAW,SAAS;AAAA,IACrC,YAAY,OAAO,WAAW,WAAW;AAAA,IACzC,OAAO,OAAO,OAAO,KAAK;AAAA,IAC1B,eAAe;AAAA,IACf,eAAe;AAAA,EAAA,GAEd,SAAA,CACH;AAEJ,CAAC;AAUM,MAAM,OAAsB,OAAO,OAAO,UAAU;AAAA,EACzD,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AACT,CAAC;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zendir/ui",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "React UI components for space operations, built on the Astro UX Design System",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",