@zendir/ui 0.2.20 → 0.2.21
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 +9 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/react/3d/CesiumCaptureSource.d.ts +119 -0
- package/dist/react/3d/CesiumCaptureSource.js +307 -0
- package/dist/react/3d/CesiumCaptureSource.js.map +1 -0
- package/dist/react/3d/ZenSpace3DUtils.js +9 -0
- package/dist/react/3d/ZenSpace3DUtils.js.map +1 -0
- package/dist/react/3d/capturePngAnalysis.d.ts +16 -0
- package/dist/react/3d/index.d.ts +4 -0
- package/dist/react/core/Capture.d.ts +140 -0
- package/dist/react/core/Capture.js +804 -0
- package/dist/react/core/Capture.js.map +1 -0
- package/dist/react/core/ChatPanel.js +3 -2
- package/dist/react/core/ChatPanel.js.map +1 -1
- package/dist/react/core/SideNav.js +5 -6
- package/dist/react/core/SideNav.js.map +1 -1
- package/dist/react/core/SidePanel.js +4 -4
- package/dist/react/core/SidePanel.js.map +1 -1
- package/dist/react/core/capture-placeholder.png.js +5 -0
- package/dist/react/core/capture-placeholder.png.js.map +1 -0
- package/dist/react/core/index.d.ts +2 -0
- package/dist/react/index.d.ts +3 -2
- package/dist/react.js +7 -0
- package/dist/react.js.map +1 -1
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SideNav.js","sources":["../../../src/react/core/SideNav.tsx"],"sourcesContent":["/**\n * @zendir/ui - SideNav Component\n * \n * Persistent sidebar navigation for operator dashboards. Compound component\n * pattern with Header, Item, Section, Divider, and Footer subcomponents.\n * \n * Responsive: Full sidebar on desktop, hamburger overlay on mobile.\n * \n * Astro UX Compliance:\n * - Persistent side navigation pattern (AstroUXDS Navigation)\n * - Status indicator integration\n * - Active state highlighting with accent color\n * - Keyboard navigation support\n * - Reduced motion support\n * \n * @example\n * ```tsx\n * <SideNav>\n * <SideNav.Header logo={<Icon name=\"satellite\" size={24} />} title=\"Space Range\" badge=\"Operator\" />\n * <SideNav.Section title=\"Operations\">\n * <SideNav.Item icon=\"controls\" label=\"Controls\" description=\"System command interface\" href=\"/controls\" active />\n * <SideNav.Item icon=\"telemetry\" label=\"Telemetry\" href=\"/telemetry\" badge={3} tag=\"LIVE\" tagVariant=\"success\" />\n * <SideNav.Item icon=\"images\" label=\"Images\" href=\"/images\" />\n * </SideNav.Section>\n * <SideNav.Divider />\n * <SideNav.Section title=\"Analysis\">\n * <SideNav.Item icon=\"chart\" label=\"Plots\" href=\"/plots\" />\n * <SideNav.Item icon=\"map\" label=\"Map\" href=\"/map\" />\n * </SideNav.Section>\n * <SideNav.Footer>\n * <SideNav.Item icon=\"settings\" label=\"Settings\" href=\"/settings\" />\n * </SideNav.Footer>\n * </SideNav>\n * ```\n */\n\nimport React, { memo, useState, createContext, useContext, useCallback, useEffect } from 'react';\nimport { useTheme } from '../theme';\nimport { safeAccentText } from '../utils';\nimport { Icon } from './Icon';\nimport type { IconName } from './Icon';\n\n// ─── Astro UX Status Shape ───────────────────────────────────────────────────\n\nconst SIDENAV_STATUS_COLORS: Record<string, string> = {\n off: '#a4abb6',\n standby: '#2dccff',\n normal: '#56f000',\n caution: '#fce83a',\n serious: '#ffb302',\n critical: '#ff3838',\n};\n\n/** Astro UX dual-coded status shape (color + geometry). */\nfunction NavStatusShape({ status, size = 8 }: { status: string; size?: number }) {\n const color = SIDENAV_STATUS_COLORS[status] ?? SIDENAV_STATUS_COLORS.off;\n const glow = `${color}50`;\n const style = { flexShrink: 0 as const, filter: `drop-shadow(0 0 3px ${glow})` };\n\n switch (status) {\n case 'caution':\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>;\n case 'serious':\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>;\n case 'critical':\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>;\n case 'standby':\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>;\n case 'off':\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>;\n default: // normal\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>;\n }\n}\n\n// ─── Responsive Breakpoints ──────────────────────────────────────────────────\n\nconst SIDENAV_BREAKPOINTS = {\n mobile: 768,\n tablet: 1024,\n} as const;\n\ntype SideNavMode = 'desktop' | 'tablet' | 'mobile';\n\nfunction getSideNavMode(width: number): SideNavMode {\n if (width < SIDENAV_BREAKPOINTS.mobile) return 'mobile';\n if (width < SIDENAV_BREAKPOINTS.tablet) return 'tablet';\n return 'desktop';\n}\n\n// ─── Context ─────────────────────────────────────────────────────────────────\n\ninterface SideNavContextValue {\n collapsed: boolean;\n mobileOpen: boolean;\n mode: SideNavMode;\n showCollapseToggle: boolean;\n setMobileOpen: (open: boolean) => void;\n toggleCollapse: () => void;\n}\n\nconst SideNavContext = createContext<SideNavContextValue>({\n collapsed: false,\n mobileOpen: false,\n mode: 'desktop',\n showCollapseToggle: true,\n setMobileOpen: () => {},\n toggleCollapse: () => {},\n});\n\n// ─── SideNav ─────────────────────────────────────────────────────────────────\n\nexport interface SideNavProps {\n /** Collapsed mode (icon-only). When omitted, auto-collapses on tablet viewports. */\n collapsed?: boolean;\n /** Callback when collapsed state changes (via toggle button or responsive breakpoint). */\n onCollapsedChange?: (collapsed: boolean) => void;\n /** Show a collapse/expand toggle button in the sidebar (default true). */\n showCollapseToggle?: boolean;\n /** Width in pixels (default 260) */\n width?: number;\n /** Collapsed width in pixels (default 64) */\n collapsedWidth?: number;\n /**\n * Mobile viewport behavior.\n * - `'drawer'` (default): hamburger button with slide-out overlay drawer.\n * - `'collapsed'`: persistent collapsed icon-only strip (same as tablet).\n */\n mobileVariant?: 'drawer' | 'collapsed';\n /** Children (SideNav.Header, SideNav.Item, SideNav.Section, SideNav.Footer) */\n children?: React.ReactNode;\n /** Custom style */\n style?: React.CSSProperties;\n}\n\nconst SideNavRoot = memo(function SideNav({\n collapsed,\n onCollapsedChange,\n showCollapseToggle = true,\n width = 260,\n collapsedWidth = 64,\n mobileVariant = 'drawer',\n children,\n style,\n}: SideNavProps): React.ReactElement {\n const { tokens, theme } = useTheme();\n const isTransparentTheme = theme === 'transparent' || theme === 'transparent-bold' || theme === 'transparent-minimal';\n const [mobileOpen, setMobileOpen] = useState(false);\n const [userCollapsed, setUserCollapsed] = useState<boolean | null>(null);\n const [viewMode, setViewMode] = useState<SideNavMode>(() => {\n if (typeof window !== 'undefined') return getSideNavMode(window.innerWidth);\n return 'desktop';\n });\n \n useEffect(() => {\n if (typeof window === 'undefined') return;\n let rafId: number;\n const handleResize = () => {\n cancelAnimationFrame(rafId);\n rafId = requestAnimationFrame(() => setViewMode(getSideNavMode(window.innerWidth)));\n };\n window.addEventListener('resize', handleResize, { passive: true });\n return () => {\n window.removeEventListener('resize', handleResize);\n cancelAnimationFrame(rafId);\n };\n }, []);\n\n // When mobileVariant='collapsed', promote mobile to tablet (collapsed inline strip)\n const effectiveMode = mobileVariant === 'collapsed' && viewMode === 'mobile' ? 'tablet' : viewMode;\n\n // Reset user toggle when crossing breakpoints\n useEffect(() => {\n setUserCollapsed(null);\n }, [effectiveMode]);\n\n // Close mobile drawer when switching away from mobile\n useEffect(() => {\n if (effectiveMode !== 'mobile' && mobileOpen) setMobileOpen(false);\n }, [effectiveMode, mobileOpen]);\n \n // Close mobile nav on escape\n useEffect(() => {\n if (!mobileOpen) return;\n const handleEsc = (e: KeyboardEvent) => {\n if (e.key === 'Escape') setMobileOpen(false);\n };\n document.addEventListener('keydown', handleEsc);\n return () => document.removeEventListener('keydown', handleEsc);\n }, [mobileOpen]);\n\n // Resolve collapsed: explicit prop > user toggle > auto (tablet = collapsed)\n const autoCollapsed = effectiveMode === 'tablet';\n const isCollapsed = collapsed !== undefined\n ? collapsed\n : userCollapsed !== null\n ? userCollapsed\n : autoCollapsed;\n const isMobile = effectiveMode === 'mobile';\n const navWidth = isCollapsed ? collapsedWidth : width;\n\n const handleToggleCollapse = useCallback(() => {\n const next = !isCollapsed;\n setUserCollapsed(next);\n onCollapsedChange?.(next);\n }, [isCollapsed, onCollapsedChange]);\n \n const navStyle: React.CSSProperties = {\n display: 'flex',\n flexDirection: 'column',\n width: isMobile ? 280 : navWidth,\n height: '100%',\n backgroundColor: isTransparentTheme\n ? 'rgba(15, 12, 30, 0.7)'\n : tokens.colors.background.surface,\n borderRight: `1px solid ${tokens.colors.border.muted}`,\n transition: 'width 0.25s ease, transform 0.25s ease',\n overflowX: 'hidden',\n overflowY: 'auto',\n flexShrink: 0,\n fontFamily: tokens.typography.fontFamily.primary,\n ...(isTransparentTheme ? {\n backdropFilter: 'blur(16px)',\n WebkitBackdropFilter: 'blur(16px)',\n } : {}),\n ...style,\n };\n \n const contextValue: SideNavContextValue = { collapsed: isCollapsed, mobileOpen, mode: effectiveMode, showCollapseToggle, setMobileOpen, toggleCollapse: handleToggleCollapse };\n \n // Mobile: hamburger button + overlay drawer\n if (isMobile) {\n return (\n <SideNavContext.Provider value={contextValue}>\n {/* Hamburger button */}\n <button\n aria-label=\"Open navigation\"\n onClick={() => setMobileOpen(true)}\n style={{\n position: 'fixed',\n top: 6,\n left: 6,\n zIndex: 1001,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: 32,\n height: 32,\n border: 'none',\n borderRadius: tokens.borderRadius.sm,\n backgroundColor: 'transparent',\n color: tokens.colors.text.primary,\n cursor: 'pointer',\n padding: 0,\n transition: tokens.animation.fast,\n }}\n >\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\">\n <line x1=\"3\" y1=\"6\" x2=\"21\" y2=\"6\" />\n <line x1=\"3\" y1=\"12\" x2=\"21\" y2=\"12\" />\n <line x1=\"3\" y1=\"18\" x2=\"21\" y2=\"18\" />\n </svg>\n </button>\n \n {/* Overlay */}\n <div\n aria-hidden=\"true\"\n style={{\n position: 'fixed',\n inset: 0,\n zIndex: 1002,\n backgroundColor: `${tokens.colors.background.base}99`,\n backdropFilter: 'blur(4px)',\n opacity: mobileOpen ? 1 : 0,\n pointerEvents: mobileOpen ? 'auto' : 'none',\n transition: 'opacity 0.25s ease',\n }}\n onClick={() => setMobileOpen(false)}\n />\n \n {/* Slide-out nav */}\n <nav\n role=\"navigation\"\n aria-label=\"Main navigation\"\n style={{\n ...navStyle,\n position: 'fixed',\n top: 0,\n left: 0,\n zIndex: 1003,\n transform: mobileOpen ? 'translateX(0)' : 'translateX(-100%)',\n boxShadow: mobileOpen ? tokens.shadows.xl : 'none',\n }}\n >\n {children}\n </nav>\n </SideNavContext.Provider>\n );\n }\n \n return (\n <SideNavContext.Provider value={contextValue}>\n <nav role=\"navigation\" aria-label=\"Main navigation\" style={navStyle}>\n {children}\n </nav>\n </SideNavContext.Provider>\n );\n});\n\n// ─── Header ──────────────────────────────────────────────────────────────────\n\n/** Default height for the logo row so dashboard / operator / other apps align the same mark. */\nexport const SIDENAV_HEADER_LOGO_SLOT_HEIGHT_PX = 44;\n\nexport interface SideNavHeaderProps {\n /** Logo element (Icon, image, or ReactNode) */\n logo?: React.ReactNode;\n /** Compact logo shown when sidebar is collapsed or on tablet (e.g., just the icon mark) */\n collapsedLogo?: React.ReactNode;\n /** App title */\n title?: string;\n /** Subtitle or version */\n subtitle?: string;\n /** Role badge (e.g., \"Operator\", \"Admin\") */\n badge?: string;\n /** Badge variant for color */\n badgeVariant?: 'info' | 'success' | 'warning' | 'caution';\n /**\n * Fixed height (px) for the top logo band. Title, subtitle, and badge render below this band\n * so different logo assets still line up across apps (dashboard vs operator).\n */\n logoSlotHeight?: number;\n /** Children override (advanced: replaces default header content entirely) */\n children?: React.ReactNode;\n}\n\n/** Chevron toggle button for collapsing/expanding the sidebar. */\nfunction CollapseToggleButton() {\n const { tokens } = useTheme();\n const { collapsed, toggleCollapse } = useContext(SideNavContext);\n const [hovered, setHovered] = useState(false);\n\n return (\n <button\n type=\"button\"\n aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}\n onClick={toggleCollapse}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: 28,\n height: 28,\n border: `1px solid ${hovered ? tokens.colors.border.focus : tokens.colors.border.muted}`,\n borderRadius: tokens.borderRadius.md,\n backgroundColor: hovered ? `${tokens.colors.accent.primary}15` : 'transparent',\n color: hovered ? tokens.colors.accent.primary : tokens.colors.text.tertiary,\n cursor: 'pointer',\n flexShrink: 0,\n padding: 0,\n transition: tokens.animation.fast,\n outline: 'none',\n }}\n >\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n style={{\n transition: 'transform 0.25s ease',\n transform: collapsed ? 'rotate(180deg)' : 'rotate(0deg)',\n }}\n >\n <polyline points=\"15 18 9 12 15 6\" />\n </svg>\n </button>\n );\n}\n\nconst SideNavHeader = memo(function SideNavHeader({\n logo,\n collapsedLogo,\n title,\n subtitle,\n badge,\n badgeVariant = 'info',\n logoSlotHeight = SIDENAV_HEADER_LOGO_SLOT_HEIGHT_PX,\n children,\n}: SideNavHeaderProps): React.ReactElement {\n const { tokens } = useTheme();\n const { collapsed, mode, showCollapseToggle, toggleCollapse } = useContext(SideNavContext);\n const displayLogo = collapsed && collapsedLogo ? collapsedLogo : logo;\n const showToggle = showCollapseToggle && mode !== 'mobile';\n const hasMeta = Boolean(title || subtitle || badge);\n const slotH = logoSlotHeight;\n\n const badgeColors: Record<string, string> = {\n info: tokens.colors.accent.primary,\n success: tokens.colors.status.normal,\n warning: tokens.colors.status.caution,\n caution: tokens.colors.status.serious,\n };\n\n const metaBlock = !collapsed && hasMeta ? (\n <div style={{ flex: 1, minWidth: 0, width: '100%' }}>\n {title ? (\n <div style={{\n fontSize: tokens.typography.fontSize.sm,\n fontWeight: tokens.typography.fontWeight.bold,\n color: tokens.colors.text.primary,\n lineHeight: tokens.typography.lineHeight.tight,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}>\n {title}\n </div>\n ) : null}\n {(subtitle || badge) ? (\n <div style={{ display: 'flex', alignItems: 'center', gap: tokens.spacing.xs, marginTop: title ? '2px' : 0, flexWrap: 'wrap' }}>\n {subtitle ? (\n <span style={{\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.text.tertiary,\n }}>\n {subtitle}\n </span>\n ) : null}\n {badge ? (\n <span style={{\n fontSize: '0.6rem',\n fontWeight: tokens.typography.fontWeight.bold,\n color: badgeColors[badgeVariant] || safeAccentText(tokens.colors.accent.primary),\n backgroundColor: `${badgeColors[badgeVariant] || tokens.colors.accent.primary}18`,\n border: `1px solid ${badgeColors[badgeVariant] || tokens.colors.accent.primary}30`,\n padding: '1px 6px',\n borderRadius: tokens.borderRadius.sm,\n textTransform: 'uppercase',\n letterSpacing: '0.05em',\n }}>\n {badge}\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n ) : null;\n\n const logoSlot = displayLogo ? (\n <div\n style={{\n height: slotH,\n minHeight: slotH,\n maxHeight: slotH,\n width: '100%',\n display: 'flex',\n alignItems: 'center',\n justifyContent: collapsed ? 'center' : 'flex-start',\n overflow: 'hidden',\n boxSizing: 'border-box',\n marginBottom: !collapsed && hasMeta && displayLogo ? tokens.spacing.sm : 0,\n }}\n >\n <div\n style={{\n maxHeight: '100%',\n maxWidth: '100%',\n display: 'flex',\n alignItems: 'center',\n justifyContent: collapsed ? 'center' : 'flex-start',\n minWidth: 0,\n cursor: collapsed && showToggle ? 'pointer' : undefined,\n }}\n onClick={collapsed && showToggle ? toggleCollapse : undefined}\n >\n {displayLogo}\n </div>\n </div>\n ) : null;\n\n if (children) {\n return (\n <div style={{\n padding: `${tokens.spacing.md} 16px ${tokens.spacing.md} 15px`,\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\n flexShrink: 0,\n boxSizing: 'border-box',\n position: 'relative',\n }}>\n {children}\n </div>\n );\n }\n\n // Collapsed: centered logo in the same fixed slot height as expanded (visual parity with operator).\n if (collapsed) {\n return (\n <div style={{\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n padding: `${tokens.spacing.md} ${tokens.spacing.sm}`,\n minHeight: slotH + 24,\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\n flexShrink: 0,\n boxSizing: 'border-box',\n position: 'relative',\n justifyContent: 'center',\n }}>\n {logoSlot}\n {showToggle ? (\n <div style={{ position: 'absolute', right: 4, top: '50%', transform: 'translateY(-50%)' }}>\n <CollapseToggleButton />\n </div>\n ) : null}\n </div>\n );\n }\n\n return (\n <div style={{\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'stretch',\n padding: `${tokens.spacing.md} 16px ${tokens.spacing.md} 15px`,\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\n flexShrink: 0,\n boxSizing: 'border-box',\n position: 'relative',\n }}>\n {showToggle ? (\n <div style={{ position: 'absolute', top: tokens.spacing.sm, right: tokens.spacing.sm, zIndex: 1 }}>\n <CollapseToggleButton />\n </div>\n ) : null}\n <div style={{ paddingRight: showToggle ? 36 : 0, width: '100%', minWidth: 0 }}>\n {logoSlot}\n {metaBlock}\n </div>\n </div>\n );\n});\n\n// ─── Item ────────────────────────────────────────────────────────────────────\n\nexport interface SideNavItemProps {\n /** Icon name from zendir-ui icon library */\n icon?: IconName | React.ReactNode;\n /** Label text */\n label: string;\n /** Description text displayed beneath the label (hidden in collapsed mode) */\n description?: string;\n /** Small tag/chip displayed after the label (e.g. \"v2\", \"NEW\", \"BETA\") */\n tag?: string;\n /** Tag color variant */\n tagVariant?: 'default' | 'info' | 'success' | 'warning' | 'danger';\n /** Link href (renders <a>) */\n href?: string;\n /** Click handler (renders <button>) */\n onClick?: () => void;\n /** Active state */\n active?: boolean;\n /** Disabled state */\n disabled?: boolean;\n /** Notification badge count */\n badge?: number;\n /** External link indicator */\n external?: boolean;\n /**\n * Astro UX status level — renders a dual-coded indicator (color + shape).\n * Shapes per official Astro UXDS: normal = ● filled circle, standby = ◎ ring,\n * caution = ■ square, serious = ◆ diamond, critical = ▼ triangle, off = · small circle.\n */\n status?: 'off' | 'standby' | 'normal' | 'caution' | 'serious' | 'critical';\n /** Optional status label shown as tooltip or text next to status shape */\n statusLabel?: string;\n /** Right-side slot — arbitrary ReactNode rendered at the end of the item row */\n suffix?: React.ReactNode;\n}\n\nconst TAG_COLORS: Record<string, { bg: string; fg: string; border: string }> = {\n default: { bg: '#ffffff10', fg: '#9590a8', border: '#ffffff15' },\n info: { bg: '#8a2be218', fg: '#c4a0ff', border: '#8a2be230' },\n success: { bg: '#56f00018', fg: '#56f000', border: '#56f00030' },\n warning: { bg: '#fce83a18', fg: '#fce83a', border: '#fce83a30' },\n danger: { bg: '#ff383818', fg: '#ff3838', border: '#ff383830' },\n};\n\nconst SideNavItem = memo(function SideNavItem({\n icon,\n label,\n description,\n tag,\n tagVariant = 'default',\n href,\n onClick,\n active = false,\n disabled = false,\n badge,\n external = false,\n status,\n statusLabel,\n suffix,\n}: SideNavItemProps): React.ReactElement {\n const { tokens } = useTheme();\n const { collapsed, setMobileOpen } = useContext(SideNavContext);\n const [isHovered, setIsHovered] = useState(false);\n \n const handleClick = useCallback((e: React.MouseEvent) => {\n if (disabled) return;\n if (onClick && href && !e.metaKey && !e.ctrlKey && !e.shiftKey) {\n e.preventDefault();\n }\n setMobileOpen(false);\n onClick?.();\n }, [disabled, onClick, href, setMobileOpen]);\n \n // Status-aware accent: when an item has a status, tint active/hover with its color\n const statusColor = status ? (SIDENAV_STATUS_COLORS[status] ?? undefined) : undefined;\n const accentColor = statusColor && active ? statusColor : safeAccentText(tokens.colors.accent.primary);\n \n const hasDescription = !!description && !collapsed;\n \n const iconElement = typeof icon === 'string'\n ? <Icon name={icon as IconName} size={hasDescription ? 22 : 20} color={active ? accentColor : tokens.colors.text.secondary} />\n : icon;\n \n const itemStyle: React.CSSProperties = {\n display: 'flex',\n alignItems: hasDescription ? 'flex-start' : 'center',\n gap: tokens.spacing.sm,\n padding: collapsed\n ? `${tokens.spacing.sm} 0`\n : hasDescription\n ? `10px 16px 10px 12px`\n : `${tokens.spacing.sm} 16px ${tokens.spacing.sm} 12px`,\n justifyContent: collapsed ? 'center' : 'flex-start',\n color: active\n ? accentColor\n : disabled\n ? tokens.colors.text.tertiary\n : tokens.colors.text.secondary,\n backgroundColor: active\n ? `${accentColor}12`\n : isHovered && !disabled\n ? `${accentColor}08`\n : 'transparent',\n fontSize: tokens.typography.fontSize.sm,\n fontWeight: active ? tokens.typography.fontWeight.semibold : tokens.typography.fontWeight.normal,\n fontFamily: tokens.typography.fontFamily.primary,\n cursor: disabled ? 'not-allowed' : 'pointer',\n textDecoration: 'none',\n borderTop: 'none',\n borderRight: 'none',\n borderBottom: 'none',\n borderLeft: active\n ? `3px solid ${accentColor}`\n : '3px solid transparent',\n outline: 'none',\n width: '100%',\n boxSizing: 'border-box',\n transition: tokens.animation.fast,\n position: 'relative',\n opacity: disabled ? 0.5 : 1,\n };\n \n const tagColors = TAG_COLORS[tagVariant] ?? TAG_COLORS.default;\n \n const content = (\n <>\n {/* Icon + collapsed status overlay */}\n {iconElement && (\n <span style={{ flexShrink: 0, display: 'flex', position: 'relative', marginTop: hasDescription ? '2px' : 0 }}>\n {iconElement}\n {/* In collapsed mode, show a small status shape overlaid on the icon (top-left) */}\n {collapsed && status && (\n <span\n style={{\n position: 'absolute',\n top: -3,\n left: -4,\n lineHeight: 0,\n }}\n title={statusLabel ?? status}\n >\n <NavStatusShape status={status} size={7} />\n </span>\n )}\n </span>\n )}\n {!collapsed && (\n <>\n {/* Label + description block */}\n <span style={{ flex: 1, minWidth: 0, overflow: 'hidden' }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>\n {label}\n </span>\n {tag && (\n <span style={{\n fontSize: '0.6rem',\n fontWeight: tokens.typography.fontWeight.bold,\n color: tagColors.fg,\n backgroundColor: tagColors.bg,\n border: `1px solid ${tagColors.border}`,\n padding: '1px 5px',\n borderRadius: tokens.borderRadius.sm,\n textTransform: 'uppercase',\n letterSpacing: '0.04em',\n flexShrink: 0,\n lineHeight: '1.3',\n whiteSpace: 'nowrap',\n }}>\n {tag}\n </span>\n )}\n </span>\n {description && (\n <span style={{\n display: 'block',\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.text.tertiary,\n lineHeight: tokens.typography.lineHeight.normal,\n marginTop: '2px',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n fontWeight: tokens.typography.fontWeight.normal,\n }}>\n {description}\n </span>\n )}\n </span>\n {/* Right-side trailing elements — badge, status, suffix, external icon */}\n {(badge !== undefined && badge > 0 || status || suffix || external) && (\n <span style={{\n display: 'inline-flex',\n alignItems: 'center',\n gap: 8,\n flexShrink: 0,\n marginTop: hasDescription ? '2px' : 0,\n }}>\n {/* Badge */}\n {badge !== undefined && badge > 0 && (\n <span style={{\n fontSize: '0.65rem',\n fontWeight: tokens.typography.fontWeight.bold,\n color: '#fff',\n backgroundColor: tokens.colors.status.critical,\n borderRadius: tokens.borderRadius.full,\n minWidth: '18px',\n height: '18px',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: '0 5px',\n flexShrink: 0,\n lineHeight: 1,\n boxSizing: 'border-box',\n }}>\n {badge > 99 ? '99+' : badge}\n </span>\n )}\n {/* Status */}\n {status && (\n <span\n style={{ display: 'inline-flex', alignItems: 'center', flexShrink: 0 }}\n role=\"status\"\n aria-label={`Status: ${statusLabel ?? status}`}\n title={statusLabel ?? status}\n >\n <NavStatusShape status={status} size={8} />\n </span>\n )}\n {/* Suffix */}\n {suffix && (\n <span style={{ flexShrink: 0, display: 'inline-flex', alignItems: 'center' }}>\n {suffix}\n </span>\n )}\n {/* External */}\n {external && (\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 }}>\n <path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\" />\n <polyline points=\"15 3 21 3 21 9\" />\n <line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\" />\n </svg>\n )}\n </span>\n )}\n </>\n )}\n </>\n );\n \n const props = {\n style: itemStyle,\n onMouseEnter: () => setIsHovered(true),\n onMouseLeave: () => setIsHovered(false),\n title: collapsed ? label : undefined,\n 'aria-current': active ? ('page' as const) : undefined,\n 'aria-disabled': disabled,\n };\n \n if (href && !disabled) {\n return (\n <a href={href} onClick={handleClick} target={external ? '_blank' : undefined} rel={external ? 'noopener noreferrer' : undefined} {...props}>\n {content}\n </a>\n );\n }\n \n return (\n <button type=\"button\" onClick={handleClick} disabled={disabled} {...props}>\n {content}\n </button>\n );\n});\n\n// ─── Section ─────────────────────────────────────────────────────────────────\n\nexport interface SideNavSectionProps {\n /** Section title */\n title?: string;\n /** Children (SideNav.Item elements) */\n children?: React.ReactNode;\n}\n\nconst SideNavSection = memo(function SideNavSection({\n title,\n children,\n}: SideNavSectionProps): React.ReactElement {\n const { tokens } = useTheme();\n const { collapsed } = useContext(SideNavContext);\n \n return (\n <div style={{ marginTop: tokens.spacing.sm }}>\n {title && !collapsed && (\n <div style={{\n padding: `${tokens.spacing.xs} 16px ${tokens.spacing.xs} 15px`,\n fontSize: tokens.typography.fontSize.xxs,\n fontWeight: tokens.typography.fontWeight.bold,\n color: tokens.colors.text.tertiary,\n textTransform: 'uppercase',\n letterSpacing: '0.08em',\n }}>\n {title}\n </div>\n )}\n {collapsed && title && (\n <div style={{\n width: '60%',\n height: '1px',\n backgroundColor: tokens.colors.border.muted,\n margin: `${tokens.spacing.xs} auto`,\n }} />\n )}\n {children}\n </div>\n );\n});\n\n// ─── Divider ─────────────────────────────────────────────────────────────────\n\nexport interface SideNavDividerProps {\n /** Optional label shown in the center of the divider line */\n label?: string;\n}\n\nconst SideNavDivider = memo(function SideNavDivider({\n label,\n}: SideNavDividerProps): React.ReactElement {\n const { tokens } = useTheme();\n const { collapsed } = useContext(SideNavContext);\n\n if (collapsed) {\n return (\n <div style={{\n width: '60%',\n height: '1px',\n backgroundColor: tokens.colors.border.muted,\n margin: `${tokens.spacing.sm} auto`,\n }} />\n );\n }\n\n if (label) {\n return (\n <div style={{\n display: 'flex',\n alignItems: 'center',\n gap: tokens.spacing.sm,\n padding: `${tokens.spacing.sm} 16px ${tokens.spacing.sm} 15px`,\n }}>\n <div style={{ flex: 1, height: '1px', backgroundColor: tokens.colors.border.muted }} />\n <span style={{\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.text.tertiary,\n textTransform: 'uppercase',\n letterSpacing: '0.06em',\n whiteSpace: 'nowrap',\n }}>\n {label}\n </span>\n <div style={{ flex: 1, height: '1px', backgroundColor: tokens.colors.border.muted }} />\n </div>\n );\n }\n\n return (\n <div style={{\n height: '1px',\n backgroundColor: tokens.colors.border.muted,\n margin: `${tokens.spacing.sm} 16px ${tokens.spacing.sm} 15px`,\n }} />\n );\n});\n\n// ─── Footer ──────────────────────────────────────────────────────────────────\n\nexport interface SideNavFooterProps {\n children?: React.ReactNode;\n}\n\nconst SideNavFooter = memo(function SideNavFooter({\n children,\n}: SideNavFooterProps): React.ReactElement {\n const { tokens } = useTheme();\n \n return (\n <div style={{\n marginTop: 'auto',\n borderTop: `1px solid ${tokens.colors.border.muted}`,\n paddingTop: tokens.spacing.xs,\n paddingBottom: tokens.spacing.xs,\n }}>\n {children}\n </div>\n );\n});\n\n// ─── Compound Export ─────────────────────────────────────────────────────────\n\ntype SideNavComponent = typeof SideNavRoot & {\n Header: typeof SideNavHeader;\n Item: typeof SideNavItem;\n Section: typeof SideNavSection;\n Divider: typeof SideNavDivider;\n Footer: typeof SideNavFooter;\n};\n\nexport const SideNav: SideNavComponent = Object.assign(SideNavRoot, {\n Header: SideNavHeader,\n Item: SideNavItem,\n Section: SideNavSection,\n Divider: SideNavDivider,\n Footer: SideNavFooter,\n});\n\nexport default SideNav;\n"],"names":["SideNav","SideNavHeader","SideNavItem","SideNavSection","SideNavDivider","SideNavFooter"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SideNav.js","sources":["../../../src/react/core/SideNav.tsx"],"sourcesContent":["/**\n * @zendir/ui - SideNav Component\n * \n * Persistent sidebar navigation for operator dashboards. Compound component\n * pattern with Header, Item, Section, Divider, and Footer subcomponents.\n * \n * Responsive: Full sidebar on desktop, hamburger overlay on mobile.\n * \n * Astro UX Compliance:\n * - Persistent side navigation pattern (AstroUXDS Navigation)\n * - Status indicator integration\n * - Active state highlighting with accent color\n * - Keyboard navigation support\n * - Reduced motion support\n * \n * @example\n * ```tsx\n * <SideNav>\n * <SideNav.Header logo={<Icon name=\"satellite\" size={24} />} title=\"Space Range\" badge=\"Operator\" />\n * <SideNav.Section title=\"Operations\">\n * <SideNav.Item icon=\"controls\" label=\"Controls\" description=\"System command interface\" href=\"/controls\" active />\n * <SideNav.Item icon=\"telemetry\" label=\"Telemetry\" href=\"/telemetry\" badge={3} tag=\"LIVE\" tagVariant=\"success\" />\n * <SideNav.Item icon=\"images\" label=\"Images\" href=\"/images\" />\n * </SideNav.Section>\n * <SideNav.Divider />\n * <SideNav.Section title=\"Analysis\">\n * <SideNav.Item icon=\"chart\" label=\"Plots\" href=\"/plots\" />\n * <SideNav.Item icon=\"map\" label=\"Map\" href=\"/map\" />\n * </SideNav.Section>\n * <SideNav.Footer>\n * <SideNav.Item icon=\"settings\" label=\"Settings\" href=\"/settings\" />\n * </SideNav.Footer>\n * </SideNav>\n * ```\n */\n\nimport React, { memo, useState, createContext, useContext, useCallback, useEffect, useLayoutEffect } from 'react';\n\nconst useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;\nimport { useTheme } from '../theme';\nimport { safeAccentText } from '../utils';\nimport { Icon } from './Icon';\nimport type { IconName } from './Icon';\n\n// ─── Astro UX Status Shape ───────────────────────────────────────────────────\n\nconst SIDENAV_STATUS_COLORS: Record<string, string> = {\n off: '#a4abb6',\n standby: '#2dccff',\n normal: '#56f000',\n caution: '#fce83a',\n serious: '#ffb302',\n critical: '#ff3838',\n};\n\n/** Astro UX dual-coded status shape (color + geometry). */\nfunction NavStatusShape({ status, size = 8 }: { status: string; size?: number }) {\n const color = SIDENAV_STATUS_COLORS[status] ?? SIDENAV_STATUS_COLORS.off;\n const glow = `${color}50`;\n const style = { flexShrink: 0 as const, filter: `drop-shadow(0 0 3px ${glow})` };\n\n switch (status) {\n case 'caution':\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>;\n case 'serious':\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>;\n case 'critical':\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>;\n case 'standby':\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>;\n case 'off':\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>;\n default: // normal\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>;\n }\n}\n\n// ─── Responsive Breakpoints ──────────────────────────────────────────────────\n\nconst SIDENAV_BREAKPOINTS = {\n mobile: 768,\n tablet: 1024,\n} as const;\n\ntype SideNavMode = 'desktop' | 'tablet' | 'mobile';\n\nfunction getSideNavMode(width: number): SideNavMode {\n if (width < SIDENAV_BREAKPOINTS.mobile) return 'mobile';\n if (width < SIDENAV_BREAKPOINTS.tablet) return 'tablet';\n return 'desktop';\n}\n\n// ─── Context ─────────────────────────────────────────────────────────────────\n\ninterface SideNavContextValue {\n collapsed: boolean;\n mobileOpen: boolean;\n mode: SideNavMode;\n showCollapseToggle: boolean;\n setMobileOpen: (open: boolean) => void;\n toggleCollapse: () => void;\n}\n\nconst SideNavContext = createContext<SideNavContextValue>({\n collapsed: false,\n mobileOpen: false,\n mode: 'desktop',\n showCollapseToggle: true,\n setMobileOpen: () => {},\n toggleCollapse: () => {},\n});\n\n// ─── SideNav ─────────────────────────────────────────────────────────────────\n\nexport interface SideNavProps {\n /** Collapsed mode (icon-only). When omitted, auto-collapses on tablet viewports. */\n collapsed?: boolean;\n /** Callback when collapsed state changes (via toggle button or responsive breakpoint). */\n onCollapsedChange?: (collapsed: boolean) => void;\n /** Show a collapse/expand toggle button in the sidebar (default true). */\n showCollapseToggle?: boolean;\n /** Width in pixels (default 260) */\n width?: number;\n /** Collapsed width in pixels (default 64) */\n collapsedWidth?: number;\n /**\n * Mobile viewport behavior.\n * - `'drawer'` (default): hamburger button with slide-out overlay drawer.\n * - `'collapsed'`: persistent collapsed icon-only strip (same as tablet).\n */\n mobileVariant?: 'drawer' | 'collapsed';\n /** Children (SideNav.Header, SideNav.Item, SideNav.Section, SideNav.Footer) */\n children?: React.ReactNode;\n /** Custom style */\n style?: React.CSSProperties;\n}\n\nconst SideNavRoot = memo(function SideNav({\n collapsed,\n onCollapsedChange,\n showCollapseToggle = true,\n width = 260,\n collapsedWidth = 64,\n mobileVariant = 'drawer',\n children,\n style,\n}: SideNavProps): React.ReactElement {\n const { tokens, theme } = useTheme();\n const isTransparentTheme = theme === 'transparent' || theme === 'transparent-bold' || theme === 'transparent-minimal';\n const [mobileOpen, setMobileOpen] = useState(false);\n const [userCollapsed, setUserCollapsed] = useState<boolean | null>(null);\n \n // Initialize to 'desktop' to guarantee the first client render matches SSR\n // (where window is undefined), preventing a React hydration mismatch if the\n // client happens to be on a mobile/tablet width at initial load.\n const [viewMode, setViewMode] = useState<SideNavMode>('desktop');\n \n useIsomorphicLayoutEffect(() => {\n if (typeof window === 'undefined') return;\n\n // Synchronously correct the viewMode right after hydration but before paint\n setViewMode(getSideNavMode(window.innerWidth));\n\n let rafId: number;\n const handleResize = () => {\n cancelAnimationFrame(rafId);\n rafId = requestAnimationFrame(() => setViewMode(getSideNavMode(window.innerWidth)));\n };\n window.addEventListener('resize', handleResize, { passive: true });\n return () => {\n window.removeEventListener('resize', handleResize);\n cancelAnimationFrame(rafId);\n };\n }, []);\n\n // When mobileVariant='collapsed', promote mobile to tablet (collapsed inline strip)\n const effectiveMode = mobileVariant === 'collapsed' && viewMode === 'mobile' ? 'tablet' : viewMode;\n\n // Reset user toggle when crossing breakpoints\n useEffect(() => {\n setUserCollapsed(null);\n }, [effectiveMode]);\n\n // Close mobile drawer when switching away from mobile\n useEffect(() => {\n if (effectiveMode !== 'mobile' && mobileOpen) setMobileOpen(false);\n }, [effectiveMode, mobileOpen]);\n \n // Close mobile nav on escape\n useEffect(() => {\n if (!mobileOpen) return;\n const handleEsc = (e: KeyboardEvent) => {\n if (e.key === 'Escape') setMobileOpen(false);\n };\n document.addEventListener('keydown', handleEsc);\n return () => document.removeEventListener('keydown', handleEsc);\n }, [mobileOpen]);\n\n // Resolve collapsed: explicit prop > user toggle > auto (tablet = collapsed)\n const autoCollapsed = effectiveMode === 'tablet';\n const isCollapsed = collapsed !== undefined\n ? collapsed\n : userCollapsed !== null\n ? userCollapsed\n : autoCollapsed;\n const isMobile = effectiveMode === 'mobile';\n const navWidth = isCollapsed ? collapsedWidth : width;\n\n const handleToggleCollapse = useCallback(() => {\n const next = !isCollapsed;\n setUserCollapsed(next);\n onCollapsedChange?.(next);\n }, [isCollapsed, onCollapsedChange]);\n \n const navStyle: React.CSSProperties = {\n display: 'flex',\n flexDirection: 'column',\n width: isMobile ? 280 : navWidth,\n height: '100%',\n backgroundColor: isTransparentTheme\n ? 'rgba(15, 12, 30, 0.7)'\n : tokens.colors.background.surface,\n borderRight: `1px solid ${tokens.colors.border.muted}`,\n transition: 'width 0.25s ease, transform 0.25s ease',\n overflowX: 'hidden',\n overflowY: 'auto',\n flexShrink: 0,\n fontFamily: tokens.typography.fontFamily.primary,\n ...(isTransparentTheme ? {\n backdropFilter: 'blur(16px)',\n WebkitBackdropFilter: 'blur(16px)',\n } : {}),\n ...style,\n };\n \n const contextValue: SideNavContextValue = { collapsed: isCollapsed, mobileOpen, mode: effectiveMode, showCollapseToggle, setMobileOpen, toggleCollapse: handleToggleCollapse };\n \n // Mobile: hamburger button + overlay drawer\n if (isMobile) {\n return (\n <SideNavContext.Provider value={contextValue}>\n {/* Hamburger button */}\n <button\n aria-label=\"Open navigation\"\n onClick={() => setMobileOpen(true)}\n style={{\n position: 'fixed',\n top: 6,\n left: 6,\n zIndex: 1001,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: 32,\n height: 32,\n border: 'none',\n borderRadius: tokens.borderRadius.sm,\n backgroundColor: 'transparent',\n color: tokens.colors.text.primary,\n cursor: 'pointer',\n padding: 0,\n transition: tokens.animation.fast,\n }}\n >\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\">\n <line x1=\"3\" y1=\"6\" x2=\"21\" y2=\"6\" />\n <line x1=\"3\" y1=\"12\" x2=\"21\" y2=\"12\" />\n <line x1=\"3\" y1=\"18\" x2=\"21\" y2=\"18\" />\n </svg>\n </button>\n \n {/* Overlay */}\n <div\n aria-hidden=\"true\"\n style={{\n position: 'fixed',\n inset: 0,\n zIndex: 1002,\n backgroundColor: `${tokens.colors.background.base}99`,\n backdropFilter: 'blur(4px)',\n opacity: mobileOpen ? 1 : 0,\n pointerEvents: mobileOpen ? 'auto' : 'none',\n transition: 'opacity 0.25s ease',\n }}\n onClick={() => setMobileOpen(false)}\n />\n \n {/* Slide-out nav */}\n <nav\n role=\"navigation\"\n aria-label=\"Main navigation\"\n style={{\n ...navStyle,\n position: 'fixed',\n top: 0,\n left: 0,\n zIndex: 1003,\n transform: mobileOpen ? 'translateX(0)' : 'translateX(-100%)',\n boxShadow: mobileOpen ? tokens.shadows.xl : 'none',\n }}\n >\n {children}\n </nav>\n </SideNavContext.Provider>\n );\n }\n \n return (\n <SideNavContext.Provider value={contextValue}>\n <nav role=\"navigation\" aria-label=\"Main navigation\" style={navStyle}>\n {children}\n </nav>\n </SideNavContext.Provider>\n );\n});\n\n// ─── Header ──────────────────────────────────────────────────────────────────\n\n/** Default height for the logo row so dashboard / operator / other apps align the same mark. */\nexport const SIDENAV_HEADER_LOGO_SLOT_HEIGHT_PX = 44;\n\nexport interface SideNavHeaderProps {\n /** Logo element (Icon, image, or ReactNode) */\n logo?: React.ReactNode;\n /** Compact logo shown when sidebar is collapsed or on tablet (e.g., just the icon mark) */\n collapsedLogo?: React.ReactNode;\n /** App title */\n title?: string;\n /** Subtitle or version */\n subtitle?: string;\n /** Role badge (e.g., \"Operator\", \"Admin\") */\n badge?: string;\n /** Badge variant for color */\n badgeVariant?: 'info' | 'success' | 'warning' | 'caution';\n /**\n * Fixed height (px) for the top logo band. Title, subtitle, and badge render below this band\n * so different logo assets still line up across apps (dashboard vs operator).\n */\n logoSlotHeight?: number;\n /** Children override (advanced: replaces default header content entirely) */\n children?: React.ReactNode;\n}\n\n/** Chevron toggle button for collapsing/expanding the sidebar. */\nfunction CollapseToggleButton() {\n const { tokens } = useTheme();\n const { collapsed, toggleCollapse } = useContext(SideNavContext);\n const [hovered, setHovered] = useState(false);\n\n return (\n <button\n type=\"button\"\n aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}\n onClick={toggleCollapse}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: 28,\n height: 28,\n border: `1px solid ${hovered ? tokens.colors.border.focus : tokens.colors.border.muted}`,\n borderRadius: tokens.borderRadius.md,\n backgroundColor: hovered ? `${tokens.colors.accent.primary}15` : 'transparent',\n color: hovered ? tokens.colors.accent.primary : tokens.colors.text.tertiary,\n cursor: 'pointer',\n flexShrink: 0,\n padding: 0,\n transition: tokens.animation.fast,\n outline: 'none',\n }}\n >\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n style={{\n transition: 'transform 0.25s ease',\n transform: collapsed ? 'rotate(180deg)' : 'rotate(0deg)',\n }}\n >\n <polyline points=\"15 18 9 12 15 6\" />\n </svg>\n </button>\n );\n}\n\nconst SideNavHeader = memo(function SideNavHeader({\n logo,\n collapsedLogo,\n title,\n subtitle,\n badge,\n badgeVariant = 'info',\n logoSlotHeight = SIDENAV_HEADER_LOGO_SLOT_HEIGHT_PX,\n children,\n}: SideNavHeaderProps): React.ReactElement {\n const { tokens } = useTheme();\n const { collapsed, mode, showCollapseToggle, toggleCollapse } = useContext(SideNavContext);\n const displayLogo = collapsed && collapsedLogo ? collapsedLogo : logo;\n const showToggle = showCollapseToggle && mode !== 'mobile';\n const hasMeta = Boolean(title || subtitle || badge);\n const slotH = logoSlotHeight;\n\n const badgeColors: Record<string, string> = {\n info: tokens.colors.accent.primary,\n success: tokens.colors.status.normal,\n warning: tokens.colors.status.caution,\n caution: tokens.colors.status.serious,\n };\n\n const metaBlock = !collapsed && hasMeta ? (\n <div style={{ flex: 1, minWidth: 0, width: '100%' }}>\n {title ? (\n <div style={{\n fontSize: tokens.typography.fontSize.sm,\n fontWeight: tokens.typography.fontWeight.bold,\n color: tokens.colors.text.primary,\n lineHeight: tokens.typography.lineHeight.tight,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}>\n {title}\n </div>\n ) : null}\n {(subtitle || badge) ? (\n <div style={{ display: 'flex', alignItems: 'center', gap: tokens.spacing.xs, marginTop: title ? '2px' : 0, flexWrap: 'wrap' }}>\n {subtitle ? (\n <span style={{\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.text.tertiary,\n }}>\n {subtitle}\n </span>\n ) : null}\n {badge ? (\n <span style={{\n fontSize: '0.6rem',\n fontWeight: tokens.typography.fontWeight.bold,\n color: badgeColors[badgeVariant] || safeAccentText(tokens.colors.accent.primary),\n backgroundColor: `${badgeColors[badgeVariant] || tokens.colors.accent.primary}18`,\n border: `1px solid ${badgeColors[badgeVariant] || tokens.colors.accent.primary}30`,\n padding: '1px 6px',\n borderRadius: tokens.borderRadius.sm,\n textTransform: 'uppercase',\n letterSpacing: '0.05em',\n }}>\n {badge}\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n ) : null;\n\n const logoSlot = displayLogo ? (\n <div\n style={{\n height: slotH,\n minHeight: slotH,\n maxHeight: slotH,\n width: '100%',\n display: 'flex',\n alignItems: 'center',\n justifyContent: collapsed ? 'center' : 'flex-start',\n overflow: 'hidden',\n boxSizing: 'border-box',\n marginBottom: !collapsed && hasMeta && displayLogo ? tokens.spacing.sm : 0,\n }}\n >\n <div\n style={{\n maxHeight: '100%',\n maxWidth: '100%',\n display: 'flex',\n alignItems: 'center',\n justifyContent: collapsed ? 'center' : 'flex-start',\n minWidth: 0,\n cursor: collapsed && showToggle ? 'pointer' : undefined,\n }}\n onClick={collapsed && showToggle ? toggleCollapse : undefined}\n >\n {displayLogo}\n </div>\n </div>\n ) : null;\n\n if (children) {\n return (\n <div style={{\n padding: `${tokens.spacing.md} 16px ${tokens.spacing.md} 15px`,\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\n flexShrink: 0,\n boxSizing: 'border-box',\n position: 'relative',\n }}>\n {children}\n </div>\n );\n }\n\n // Collapsed: centered logo in the same fixed slot height as expanded (visual parity with operator).\n if (collapsed) {\n return (\n <div style={{\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n padding: `${tokens.spacing.md} ${tokens.spacing.sm}`,\n minHeight: slotH + 24,\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\n flexShrink: 0,\n boxSizing: 'border-box',\n position: 'relative',\n justifyContent: 'center',\n }}>\n {logoSlot}\n {showToggle ? (\n <div style={{ position: 'absolute', right: 4, top: '50%', transform: 'translateY(-50%)' }}>\n <CollapseToggleButton />\n </div>\n ) : null}\n </div>\n );\n }\n\n return (\n <div style={{\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'stretch',\n padding: `${tokens.spacing.md} 16px ${tokens.spacing.md} 15px`,\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\n flexShrink: 0,\n boxSizing: 'border-box',\n position: 'relative',\n }}>\n {showToggle ? (\n <div style={{ position: 'absolute', top: tokens.spacing.sm, right: tokens.spacing.sm, zIndex: 1 }}>\n <CollapseToggleButton />\n </div>\n ) : null}\n <div style={{ paddingRight: showToggle ? 36 : 0, width: '100%', minWidth: 0 }}>\n {logoSlot}\n {metaBlock}\n </div>\n </div>\n );\n});\n\n// ─── Item ────────────────────────────────────────────────────────────────────\n\nexport interface SideNavItemProps {\n /** Icon name from zendir-ui icon library */\n icon?: IconName | React.ReactNode;\n /** Label text */\n label: string;\n /** Description text displayed beneath the label (hidden in collapsed mode) */\n description?: string;\n /** Small tag/chip displayed after the label (e.g. \"v2\", \"NEW\", \"BETA\") */\n tag?: string;\n /** Tag color variant */\n tagVariant?: 'default' | 'info' | 'success' | 'warning' | 'danger';\n /** Link href (renders <a>) */\n href?: string;\n /** Click handler (renders <button>) */\n onClick?: () => void;\n /** Active state */\n active?: boolean;\n /** Disabled state */\n disabled?: boolean;\n /** Notification badge count */\n badge?: number;\n /** External link indicator */\n external?: boolean;\n /**\n * Astro UX status level — renders a dual-coded indicator (color + shape).\n * Shapes per official Astro UXDS: normal = ● filled circle, standby = ◎ ring,\n * caution = ■ square, serious = ◆ diamond, critical = ▼ triangle, off = · small circle.\n */\n status?: 'off' | 'standby' | 'normal' | 'caution' | 'serious' | 'critical';\n /** Optional status label shown as tooltip or text next to status shape */\n statusLabel?: string;\n /** Right-side slot — arbitrary ReactNode rendered at the end of the item row */\n suffix?: React.ReactNode;\n}\n\nconst TAG_COLORS: Record<string, { bg: string; fg: string; border: string }> = {\n default: { bg: '#ffffff10', fg: '#9590a8', border: '#ffffff15' },\n info: { bg: '#8a2be218', fg: '#c4a0ff', border: '#8a2be230' },\n success: { bg: '#56f00018', fg: '#56f000', border: '#56f00030' },\n warning: { bg: '#fce83a18', fg: '#fce83a', border: '#fce83a30' },\n danger: { bg: '#ff383818', fg: '#ff3838', border: '#ff383830' },\n};\n\nconst SideNavItem = memo(function SideNavItem({\n icon,\n label,\n description,\n tag,\n tagVariant = 'default',\n href,\n onClick,\n active = false,\n disabled = false,\n badge,\n external = false,\n status,\n statusLabel,\n suffix,\n}: SideNavItemProps): React.ReactElement {\n const { tokens } = useTheme();\n const { collapsed, setMobileOpen } = useContext(SideNavContext);\n const [isHovered, setIsHovered] = useState(false);\n \n const handleClick = useCallback((e: React.MouseEvent) => {\n if (disabled) return;\n if (onClick && href && !e.metaKey && !e.ctrlKey && !e.shiftKey) {\n e.preventDefault();\n }\n setMobileOpen(false);\n onClick?.();\n }, [disabled, onClick, href, setMobileOpen]);\n \n // Status-aware accent: when an item has a status, tint active/hover with its color\n const statusColor = status ? (SIDENAV_STATUS_COLORS[status] ?? undefined) : undefined;\n const accentColor = statusColor && active ? statusColor : safeAccentText(tokens.colors.accent.primary);\n \n const hasDescription = !!description && !collapsed;\n \n const iconElement = typeof icon === 'string'\n ? <Icon name={icon as IconName} size={hasDescription ? 22 : 20} color={active ? accentColor : tokens.colors.text.secondary} />\n : icon;\n \n const itemStyle: React.CSSProperties = {\n display: 'flex',\n alignItems: hasDescription ? 'flex-start' : 'center',\n gap: tokens.spacing.sm,\n padding: collapsed\n ? `${tokens.spacing.sm} 0`\n : hasDescription\n ? `10px 16px 10px 12px`\n : `${tokens.spacing.sm} 16px ${tokens.spacing.sm} 12px`,\n justifyContent: collapsed ? 'center' : 'flex-start',\n color: active\n ? accentColor\n : disabled\n ? tokens.colors.text.tertiary\n : tokens.colors.text.secondary,\n backgroundColor: active\n ? `${accentColor}12`\n : isHovered && !disabled\n ? `${accentColor}08`\n : 'transparent',\n fontSize: tokens.typography.fontSize.sm,\n fontWeight: active ? tokens.typography.fontWeight.semibold : tokens.typography.fontWeight.normal,\n fontFamily: tokens.typography.fontFamily.primary,\n cursor: disabled ? 'not-allowed' : 'pointer',\n textDecoration: 'none',\n borderTop: 'none',\n borderRight: 'none',\n borderBottom: 'none',\n borderLeft: active\n ? `3px solid ${accentColor}`\n : '3px solid transparent',\n outline: 'none',\n width: '100%',\n boxSizing: 'border-box',\n transition: tokens.animation.fast,\n position: 'relative',\n opacity: disabled ? 0.5 : 1,\n };\n \n const tagColors = TAG_COLORS[tagVariant] ?? TAG_COLORS.default;\n \n const content = (\n <>\n {/* Icon + collapsed status overlay */}\n {iconElement && (\n <span style={{ flexShrink: 0, display: 'flex', position: 'relative', marginTop: hasDescription ? '2px' : 0 }}>\n {iconElement}\n {/* In collapsed mode, show a small status shape overlaid on the icon (top-left) */}\n {collapsed && status && (\n <span\n style={{\n position: 'absolute',\n top: -3,\n left: -4,\n lineHeight: 0,\n }}\n title={statusLabel ?? status}\n >\n <NavStatusShape status={status} size={7} />\n </span>\n )}\n </span>\n )}\n {!collapsed && (\n <>\n {/* Label + description block */}\n <span style={{ flex: 1, minWidth: 0, overflow: 'hidden' }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>\n {label}\n </span>\n {tag && (\n <span style={{\n fontSize: '0.6rem',\n fontWeight: tokens.typography.fontWeight.bold,\n color: tagColors.fg,\n backgroundColor: tagColors.bg,\n border: `1px solid ${tagColors.border}`,\n padding: '1px 5px',\n borderRadius: tokens.borderRadius.sm,\n textTransform: 'uppercase',\n letterSpacing: '0.04em',\n flexShrink: 0,\n lineHeight: '1.3',\n whiteSpace: 'nowrap',\n }}>\n {tag}\n </span>\n )}\n </span>\n {description && (\n <span style={{\n display: 'block',\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.text.tertiary,\n lineHeight: tokens.typography.lineHeight.normal,\n marginTop: '2px',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n fontWeight: tokens.typography.fontWeight.normal,\n }}>\n {description}\n </span>\n )}\n </span>\n {/* Right-side trailing elements — badge, status, suffix, external icon */}\n {(badge !== undefined && badge > 0 || status || suffix || external) && (\n <span style={{\n display: 'inline-flex',\n alignItems: 'center',\n gap: 8,\n flexShrink: 0,\n marginTop: hasDescription ? '2px' : 0,\n }}>\n {/* Badge */}\n {badge !== undefined && badge > 0 && (\n <span style={{\n fontSize: '0.65rem',\n fontWeight: tokens.typography.fontWeight.bold,\n color: '#fff',\n backgroundColor: tokens.colors.status.critical,\n borderRadius: tokens.borderRadius.full,\n minWidth: '18px',\n height: '18px',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: '0 5px',\n flexShrink: 0,\n lineHeight: 1,\n boxSizing: 'border-box',\n }}>\n {badge > 99 ? '99+' : badge}\n </span>\n )}\n {/* Status */}\n {status && (\n <span\n style={{ display: 'inline-flex', alignItems: 'center', flexShrink: 0 }}\n role=\"status\"\n aria-label={`Status: ${statusLabel ?? status}`}\n title={statusLabel ?? status}\n >\n <NavStatusShape status={status} size={8} />\n </span>\n )}\n {/* Suffix */}\n {suffix && (\n <span style={{ flexShrink: 0, display: 'inline-flex', alignItems: 'center' }}>\n {suffix}\n </span>\n )}\n {/* External */}\n {external && (\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 }}>\n <path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\" />\n <polyline points=\"15 3 21 3 21 9\" />\n <line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\" />\n </svg>\n )}\n </span>\n )}\n </>\n )}\n </>\n );\n \n const props = {\n style: itemStyle,\n onMouseEnter: () => setIsHovered(true),\n onMouseLeave: () => setIsHovered(false),\n title: collapsed ? label : undefined,\n 'aria-current': active ? ('page' as const) : undefined,\n 'aria-disabled': disabled,\n };\n \n if (href && !disabled) {\n return (\n <a href={href} onClick={handleClick} target={external ? '_blank' : undefined} rel={external ? 'noopener noreferrer' : undefined} {...props}>\n {content}\n </a>\n );\n }\n \n return (\n <button type=\"button\" onClick={handleClick} disabled={disabled} {...props}>\n {content}\n </button>\n );\n});\n\n// ─── Section ─────────────────────────────────────────────────────────────────\n\nexport interface SideNavSectionProps {\n /** Section title */\n title?: string;\n /** Children (SideNav.Item elements) */\n children?: React.ReactNode;\n}\n\nconst SideNavSection = memo(function SideNavSection({\n title,\n children,\n}: SideNavSectionProps): React.ReactElement {\n const { tokens } = useTheme();\n const { collapsed } = useContext(SideNavContext);\n \n return (\n <div style={{ marginTop: tokens.spacing.sm }}>\n {title && !collapsed && (\n <div style={{\n padding: `${tokens.spacing.xs} 16px ${tokens.spacing.xs} 15px`,\n fontSize: tokens.typography.fontSize.xxs,\n fontWeight: tokens.typography.fontWeight.bold,\n color: tokens.colors.text.tertiary,\n textTransform: 'uppercase',\n letterSpacing: '0.08em',\n }}>\n {title}\n </div>\n )}\n {collapsed && title && (\n <div style={{\n width: '60%',\n height: '1px',\n backgroundColor: tokens.colors.border.muted,\n margin: `${tokens.spacing.xs} auto`,\n }} />\n )}\n {children}\n </div>\n );\n});\n\n// ─── Divider ─────────────────────────────────────────────────────────────────\n\nexport interface SideNavDividerProps {\n /** Optional label shown in the center of the divider line */\n label?: string;\n}\n\nconst SideNavDivider = memo(function SideNavDivider({\n label,\n}: SideNavDividerProps): React.ReactElement {\n const { tokens } = useTheme();\n const { collapsed } = useContext(SideNavContext);\n\n if (collapsed) {\n return (\n <div style={{\n width: '60%',\n height: '1px',\n backgroundColor: tokens.colors.border.muted,\n margin: `${tokens.spacing.sm} auto`,\n }} />\n );\n }\n\n if (label) {\n return (\n <div style={{\n display: 'flex',\n alignItems: 'center',\n gap: tokens.spacing.sm,\n padding: `${tokens.spacing.sm} 16px ${tokens.spacing.sm} 15px`,\n }}>\n <div style={{ flex: 1, height: '1px', backgroundColor: tokens.colors.border.muted }} />\n <span style={{\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.text.tertiary,\n textTransform: 'uppercase',\n letterSpacing: '0.06em',\n whiteSpace: 'nowrap',\n }}>\n {label}\n </span>\n <div style={{ flex: 1, height: '1px', backgroundColor: tokens.colors.border.muted }} />\n </div>\n );\n }\n\n return (\n <div style={{\n height: '1px',\n backgroundColor: tokens.colors.border.muted,\n margin: `${tokens.spacing.sm} 16px ${tokens.spacing.sm} 15px`,\n }} />\n );\n});\n\n// ─── Footer ──────────────────────────────────────────────────────────────────\n\nexport interface SideNavFooterProps {\n children?: React.ReactNode;\n}\n\nconst SideNavFooter = memo(function SideNavFooter({\n children,\n}: SideNavFooterProps): React.ReactElement {\n const { tokens } = useTheme();\n \n return (\n <div style={{\n marginTop: 'auto',\n borderTop: `1px solid ${tokens.colors.border.muted}`,\n paddingTop: tokens.spacing.xs,\n paddingBottom: tokens.spacing.xs,\n }}>\n {children}\n </div>\n );\n});\n\n// ─── Compound Export ─────────────────────────────────────────────────────────\n\ntype SideNavComponent = typeof SideNavRoot & {\n Header: typeof SideNavHeader;\n Item: typeof SideNavItem;\n Section: typeof SideNavSection;\n Divider: typeof SideNavDivider;\n Footer: typeof SideNavFooter;\n};\n\nexport const SideNav: SideNavComponent = Object.assign(SideNavRoot, {\n Header: SideNavHeader,\n Item: SideNavItem,\n Section: SideNavSection,\n Divider: SideNavDivider,\n Footer: SideNavFooter,\n});\n\nexport default SideNav;\n"],"names":["SideNav","SideNavHeader","SideNavItem","SideNavSection","SideNavDivider","SideNavFooter"],"mappings":"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"}
|
|
@@ -81,12 +81,12 @@ const SidePanel = memo(function SidePanel2({
|
|
|
81
81
|
ref: panelRef,
|
|
82
82
|
className: classNames("zendir-sidepanel", className),
|
|
83
83
|
style: {
|
|
84
|
-
width: isVisible
|
|
85
|
-
minWidth: isVisible
|
|
84
|
+
width: isVisible ? resolvedWidth : "0px",
|
|
85
|
+
minWidth: isVisible ? resolvedWidth : "0px",
|
|
86
86
|
maxWidth: resolvedWidth,
|
|
87
87
|
height: "100%",
|
|
88
88
|
overflow: "hidden",
|
|
89
|
-
transition: "width 0.
|
|
89
|
+
transition: "width 0.6s cubic-bezier(0.2, 0.8, 0.2, 1), min-width 0.6s cubic-bezier(0.2, 0.8, 0.2, 1)",
|
|
90
90
|
flexShrink: 0,
|
|
91
91
|
position: "relative",
|
|
92
92
|
zIndex: overlay ? 1e3 : "auto",
|
|
@@ -109,7 +109,7 @@ const SidePanel = memo(function SidePanel2({
|
|
|
109
109
|
borderLeft: position === "right" ? tokens.borders.divider : void 0,
|
|
110
110
|
borderRight: position === "left" ? tokens.borders.divider : void 0,
|
|
111
111
|
transform: isVisible ? "translateX(0)" : slideFrom,
|
|
112
|
-
transition: "transform 0.
|
|
112
|
+
transition: "transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1)",
|
|
113
113
|
overflow: "hidden"
|
|
114
114
|
},
|
|
115
115
|
children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SidePanel.js","sources":["../../../src/react/core/SidePanel.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - SidePanel Component\r\n * \r\n * A sliding panel that pushes content instead of overlaying it.\r\n * Designed to be placed as a flex sibling so the parent layout\r\n * naturally adjusts when the panel opens/closes.\r\n * \r\n * Features:\r\n * - Push-content behavior (flex sibling, content adjusts automatically)\r\n * - Smooth slide-in/out animation\r\n * - Configurable width, position (left/right)\r\n * - Optional overlay backdrop (for modal-like behavior)\r\n * - Theme-integrated via useTheme()\r\n * - Accessible (focus trap, keyboard support)\r\n * - Close on escape key\r\n * \r\n * @example\r\n * ```tsx\r\n * // Push-content pattern: SidePanel as flex sibling\r\n * <div style={{ display: 'flex', height: '100vh' }}>\r\n * <main style={{ flex: 1, transition: 'all 0.3s ease' }}>\r\n * {children}\r\n * </main>\r\n * <SidePanel\r\n * open={isChatOpen}\r\n * onClose={() => setIsChatOpen(false)}\r\n * title=\"Chat Assistant\"\r\n * width={380}\r\n * >\r\n * <ChatPanel messages={messages} onSend={handleSend} />\r\n * </SidePanel>\r\n * </div>\r\n * ```\r\n */\r\n\r\nimport React, { memo, useEffect, useRef, useCallback, useState } from 'react';\r\nimport { useTheme } from '../theme';\r\nimport { classNames } from '../utils';\r\nimport { useBreakpoint } from './layout/useBreakpoint';\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\nexport type SidePanelPosition = 'left' | 'right';\r\n\r\nexport interface SidePanelProps {\r\n /** Whether the panel is open */\r\n open: boolean;\r\n /** Called when the panel should close */\r\n onClose: () => void;\r\n /** Panel title displayed in the header */\r\n title?: string;\r\n /** Panel width in px or CSS string (default: 380) */\r\n width?: number | string;\r\n /** Which side the panel slides in from (default: 'right') */\r\n position?: SidePanelPosition;\r\n /** Show a close button in the header (default: true) */\r\n showCloseButton?: boolean;\r\n /** Close when pressing Escape (default: true) */\r\n closeOnEscape?: boolean;\r\n /** Show a semi-transparent overlay behind the panel (default: false) */\r\n overlay?: boolean;\r\n /** Close when clicking the overlay (default: true, only applies when overlay=true) */\r\n closeOnOverlayClick?: boolean;\r\n /** Panel content */\r\n children: React.ReactNode;\r\n /** Optional header actions (rendered in header, right of title) */\r\n headerActions?: React.ReactNode;\r\n /** Optional leading element (rendered in header, left of title — useful for collapse icons) */\r\n headerLeading?: React.ReactNode;\r\n /** Called when the header bar is clicked (useful for collapse-on-click) */\r\n onHeaderClick?: () => void;\r\n /** Custom style overrides for the header bar */\r\n headerStyle?: React.CSSProperties;\r\n /** Custom className */\r\n className?: string;\r\n /** Custom style overrides for the panel container */\r\n style?: React.CSSProperties;\r\n}\r\n\r\n// ============================================================================\r\n// Unique animation ID\r\n// ============================================================================\r\n\r\nlet sidePanelInstanceCount = 0;\r\n\r\n// ============================================================================\r\n// Component\r\n// ============================================================================\r\n\r\nexport const SidePanel = memo(function SidePanel({\r\n open,\r\n onClose,\r\n title,\r\n width = 380,\r\n position = 'right',\r\n showCloseButton = true,\r\n closeOnEscape = true,\r\n overlay = false,\r\n closeOnOverlayClick = true,\r\n children,\r\n headerActions,\r\n headerLeading,\r\n onHeaderClick,\r\n headerStyle: headerStyleProp,\r\n className = '',\r\n style: styleProp,\r\n}: SidePanelProps): React.ReactElement | null {\r\n const { tokens, theme } = useTheme();\r\n const isTransparentTheme = theme === 'transparent' || theme === 'transparent-bold' || theme === 'transparent-minimal';\r\n const panelRef = useRef<HTMLDivElement>(null);\r\n const [animState, setAnimState] = useState<'closed' | 'opening' | 'open' | 'closing'>('closed');\r\n const _instanceId = useRef(++sidePanelInstanceCount);\r\n\r\n const { isMobile } = useBreakpoint();\r\n const resolvedWidth = isMobile ? '100%' : (typeof width === 'number' ? `${width}px` : width);\r\n\r\n // Animate open/close\r\n useEffect(() => {\r\n if (open) {\r\n setAnimState('opening');\r\n const timer = setTimeout(() => setAnimState('open'), 30);\r\n return () => clearTimeout(timer);\r\n } else {\r\n if (animState === 'open' || animState === 'opening') {\r\n setAnimState('closing');\r\n const timer = setTimeout(() => setAnimState('closed'), 550);\r\n return () => clearTimeout(timer);\r\n }\r\n }\r\n }, [open]);\r\n\r\n // Escape key handler\r\n const handleKeyDown = useCallback((e: KeyboardEvent) => {\r\n if (closeOnEscape && e.key === 'Escape') {\r\n onClose();\r\n }\r\n }, [closeOnEscape, onClose]);\r\n\r\n useEffect(() => {\r\n if (open) {\r\n document.addEventListener('keydown', handleKeyDown);\r\n return () => document.removeEventListener('keydown', handleKeyDown);\r\n }\r\n }, [open, handleKeyDown]);\r\n\r\n // Don't render at all when fully closed\r\n if (animState === 'closed') return null;\r\n\r\n const isVisible = animState === 'open';\r\n const slideFrom = position === 'right' ? 'translateX(100%)' : 'translateX(-100%)';\r\n\r\n return (\r\n <>\r\n {/* Optional overlay */}\r\n {overlay && (\r\n <div\r\n className=\"zendir-sidepanel-overlay\"\r\n onClick={closeOnOverlayClick ? onClose : undefined}\r\n style={{\r\n position: 'fixed',\r\n inset: 0,\r\n backgroundColor: isTransparentTheme ? `${tokens.colors.background.base}4D` : `${tokens.colors.background.base}66`,\r\n backdropFilter: isTransparentTheme ? 'blur(2px)' : undefined,\r\n WebkitBackdropFilter: isTransparentTheme ? 'blur(2px)' : undefined,\r\n zIndex: 999,\r\n opacity: isVisible ? 1 : 0,\r\n transition: 'opacity 0.3s ease',\r\n pointerEvents: isVisible ? 'auto' : 'none',\r\n }}\r\n />\r\n )}\r\n\r\n {/* Panel */}\r\n <div\r\n ref={panelRef}\r\n className={classNames('zendir-sidepanel', className)}\r\n style={{\r\n width: isVisible || animState === 'opening' ? resolvedWidth : '0px',\r\n minWidth: isVisible || animState === 'opening' ? resolvedWidth : '0px',\r\n maxWidth: resolvedWidth,\r\n height: '100%',\r\n overflow: 'hidden',\r\n transition: 'width 0.5s cubic-bezier(0.4, 0, 0.2, 1), min-width 0.5s cubic-bezier(0.4, 0, 0.2, 1)',\r\n flexShrink: 0,\r\n position: 'relative',\r\n zIndex: overlay ? 1000 : 'auto',\r\n ...(overlay && { position: 'fixed', top: 0, [position]: 0 }),\r\n ...styleProp,\r\n }}\r\n >\r\n <div\r\n style={{\r\n width: resolvedWidth,\r\n height: '100%',\r\n display: 'flex',\r\n flexDirection: 'column',\r\n backgroundColor: isTransparentTheme\r\n ? 'rgba(15, 15, 35, 0.85)'\r\n : tokens.colors.background.surface,\r\n ...(isTransparentTheme && {\r\n backdropFilter: 'blur(16px)',\r\n WebkitBackdropFilter: 'blur(16px)',\r\n }),\r\n borderLeft: position === 'right' ? tokens.borders.divider : undefined,\r\n borderRight: position === 'left' ? tokens.borders.divider : undefined,\r\n transform: isVisible ? 'translateX(0)' : slideFrom,\r\n transition: 'transform 0.5s cubic-bezier(0.4, 0, 0.2, 1)',\r\n overflow: 'hidden',\r\n }}\r\n >\r\n {/* Header */}\r\n {(title || showCloseButton || headerActions || headerLeading) && (\r\n <div\r\n onClick={onHeaderClick}\r\n role={onHeaderClick ? 'button' : undefined}\r\n tabIndex={onHeaderClick ? 0 : undefined}\r\n onKeyDown={onHeaderClick ? (e: React.KeyboardEvent) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onHeaderClick(); } } : undefined}\r\n style={{\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'space-between',\r\n padding: `${tokens.spacing.md} ${tokens.spacing.md}`,\r\n borderBottom: tokens.borders.divider,\r\n flexShrink: 0,\r\n gap: tokens.spacing.sm,\r\n ...(onHeaderClick && { cursor: 'pointer' }),\r\n ...headerStyleProp,\r\n }}\r\n >\r\n <div style={{ display: 'flex', alignItems: 'center', gap: tokens.spacing.sm, flex: 1, minWidth: 0 }}>\r\n {headerLeading}\r\n {title && (\r\n <h3\r\n style={{\r\n margin: 0,\r\n fontSize: tokens.typography.fontSize.base,\r\n fontWeight: tokens.typography.fontWeight.semibold,\r\n color: tokens.colors.text.primary,\r\n whiteSpace: 'nowrap',\r\n overflow: 'hidden',\r\n textOverflow: 'ellipsis',\r\n }}\r\n >\r\n {title}\r\n </h3>\r\n )}\r\n </div>\r\n <div style={{ display: 'flex', alignItems: 'center', gap: tokens.spacing.xs, flexShrink: 0 }}>\r\n {headerActions}\r\n {showCloseButton && (\r\n <button\r\n type=\"button\"\r\n onClick={onClose}\r\n aria-label=\"Close panel\"\r\n style={{\r\n padding: tokens.spacing.sm,\r\n minWidth: 44,\r\n minHeight: 44,\r\n backgroundColor: 'transparent',\r\n border: 'none',\r\n borderRadius: tokens.borderRadius.sm,\r\n cursor: 'pointer',\r\n color: tokens.colors.text.muted ?? tokens.colors.text.tertiary,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n transition: `color 0.15s ease, background-color 0.15s ease`,\r\n }}\r\n onMouseEnter={(e) => {\r\n e.currentTarget.style.color = tokens.colors.text.primary;\r\n e.currentTarget.style.backgroundColor = tokens.colors.background.base;\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.color = tokens.colors.text.muted ?? tokens.colors.text.tertiary;\r\n e.currentTarget.style.backgroundColor = 'transparent';\r\n }}\r\n >\r\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\r\n <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\" />\r\n </svg>\r\n </button>\r\n )}\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Content */}\r\n <div\r\n style={{\r\n flex: 1,\r\n overflow: 'auto',\r\n minHeight: 0,\r\n }}\r\n >\r\n {children}\r\n </div>\r\n </div>\r\n </div>\r\n </>\r\n );\r\n});\r\n\r\nexport default SidePanel;\r\n"],"names":["SidePanel"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SidePanel.js","sources":["../../../src/react/core/SidePanel.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - SidePanel Component\r\n * \r\n * A sliding panel that pushes content instead of overlaying it.\r\n * Designed to be placed as a flex sibling so the parent layout\r\n * naturally adjusts when the panel opens/closes.\r\n * \r\n * Features:\r\n * - Push-content behavior (flex sibling, content adjusts automatically)\r\n * - Smooth slide-in/out animation\r\n * - Configurable width, position (left/right)\r\n * - Optional overlay backdrop (for modal-like behavior)\r\n * - Theme-integrated via useTheme()\r\n * - Accessible (focus trap, keyboard support)\r\n * - Close on escape key\r\n * \r\n * @example\r\n * ```tsx\r\n * // Push-content pattern: SidePanel as flex sibling\r\n * <div style={{ display: 'flex', height: '100vh' }}>\r\n * <main style={{ flex: 1, transition: 'all 0.3s ease' }}>\r\n * {children}\r\n * </main>\r\n * <SidePanel\r\n * open={isChatOpen}\r\n * onClose={() => setIsChatOpen(false)}\r\n * title=\"Chat Assistant\"\r\n * width={380}\r\n * >\r\n * <ChatPanel messages={messages} onSend={handleSend} />\r\n * </SidePanel>\r\n * </div>\r\n * ```\r\n */\r\n\r\nimport React, { memo, useEffect, useRef, useCallback, useState } from 'react';\r\nimport { useTheme } from '../theme';\r\nimport { classNames } from '../utils';\r\nimport { useBreakpoint } from './layout/useBreakpoint';\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\nexport type SidePanelPosition = 'left' | 'right';\r\n\r\nexport interface SidePanelProps {\r\n /** Whether the panel is open */\r\n open: boolean;\r\n /** Called when the panel should close */\r\n onClose: () => void;\r\n /** Panel title displayed in the header */\r\n title?: string;\r\n /** Panel width in px or CSS string (default: 380) */\r\n width?: number | string;\r\n /** Which side the panel slides in from (default: 'right') */\r\n position?: SidePanelPosition;\r\n /** Show a close button in the header (default: true) */\r\n showCloseButton?: boolean;\r\n /** Close when pressing Escape (default: true) */\r\n closeOnEscape?: boolean;\r\n /** Show a semi-transparent overlay behind the panel (default: false) */\r\n overlay?: boolean;\r\n /** Close when clicking the overlay (default: true, only applies when overlay=true) */\r\n closeOnOverlayClick?: boolean;\r\n /** Panel content */\r\n children: React.ReactNode;\r\n /** Optional header actions (rendered in header, right of title) */\r\n headerActions?: React.ReactNode;\r\n /** Optional leading element (rendered in header, left of title — useful for collapse icons) */\r\n headerLeading?: React.ReactNode;\r\n /** Called when the header bar is clicked (useful for collapse-on-click) */\r\n onHeaderClick?: () => void;\r\n /** Custom style overrides for the header bar */\r\n headerStyle?: React.CSSProperties;\r\n /** Custom className */\r\n className?: string;\r\n /** Custom style overrides for the panel container */\r\n style?: React.CSSProperties;\r\n}\r\n\r\n// ============================================================================\r\n// Unique animation ID\r\n// ============================================================================\r\n\r\nlet sidePanelInstanceCount = 0;\r\n\r\n// ============================================================================\r\n// Component\r\n// ============================================================================\r\n\r\nexport const SidePanel = memo(function SidePanel({\r\n open,\r\n onClose,\r\n title,\r\n width = 380,\r\n position = 'right',\r\n showCloseButton = true,\r\n closeOnEscape = true,\r\n overlay = false,\r\n closeOnOverlayClick = true,\r\n children,\r\n headerActions,\r\n headerLeading,\r\n onHeaderClick,\r\n headerStyle: headerStyleProp,\r\n className = '',\r\n style: styleProp,\r\n}: SidePanelProps): React.ReactElement | null {\r\n const { tokens, theme } = useTheme();\r\n const isTransparentTheme = theme === 'transparent' || theme === 'transparent-bold' || theme === 'transparent-minimal';\r\n const panelRef = useRef<HTMLDivElement>(null);\r\n const [animState, setAnimState] = useState<'closed' | 'opening' | 'open' | 'closing'>('closed');\r\n const _instanceId = useRef(++sidePanelInstanceCount);\r\n\r\n const { isMobile } = useBreakpoint();\r\n const resolvedWidth = isMobile ? '100%' : (typeof width === 'number' ? `${width}px` : width);\r\n\r\n // Animate open/close\r\n useEffect(() => {\r\n if (open) {\r\n setAnimState('opening');\r\n const timer = setTimeout(() => setAnimState('open'), 30);\r\n return () => clearTimeout(timer);\r\n } else {\r\n if (animState === 'open' || animState === 'opening') {\r\n setAnimState('closing');\r\n const timer = setTimeout(() => setAnimState('closed'), 550);\r\n return () => clearTimeout(timer);\r\n }\r\n }\r\n }, [open]);\r\n\r\n // Escape key handler\r\n const handleKeyDown = useCallback((e: KeyboardEvent) => {\r\n if (closeOnEscape && e.key === 'Escape') {\r\n onClose();\r\n }\r\n }, [closeOnEscape, onClose]);\r\n\r\n useEffect(() => {\r\n if (open) {\r\n document.addEventListener('keydown', handleKeyDown);\r\n return () => document.removeEventListener('keydown', handleKeyDown);\r\n }\r\n }, [open, handleKeyDown]);\r\n\r\n // Don't render at all when fully closed\r\n if (animState === 'closed') return null;\r\n\r\n const isVisible = animState === 'open';\r\n const slideFrom = position === 'right' ? 'translateX(100%)' : 'translateX(-100%)';\r\n\r\n return (\r\n <>\r\n {/* Optional overlay */}\r\n {overlay && (\r\n <div\r\n className=\"zendir-sidepanel-overlay\"\r\n onClick={closeOnOverlayClick ? onClose : undefined}\r\n style={{\r\n position: 'fixed',\r\n inset: 0,\r\n backgroundColor: isTransparentTheme ? `${tokens.colors.background.base}4D` : `${tokens.colors.background.base}66`,\r\n backdropFilter: isTransparentTheme ? 'blur(2px)' : undefined,\r\n WebkitBackdropFilter: isTransparentTheme ? 'blur(2px)' : undefined,\r\n zIndex: 999,\r\n opacity: isVisible ? 1 : 0,\r\n transition: 'opacity 0.3s ease',\r\n pointerEvents: isVisible ? 'auto' : 'none',\r\n }}\r\n />\r\n )}\r\n\r\n {/* Panel */}\r\n <div\r\n ref={panelRef}\r\n className={classNames('zendir-sidepanel', className)}\r\n style={{\r\n width: isVisible ? resolvedWidth : '0px',\r\n minWidth: isVisible ? resolvedWidth : '0px',\r\n maxWidth: resolvedWidth,\r\n height: '100%',\r\n overflow: 'hidden',\r\n transition: 'width 0.6s cubic-bezier(0.2, 0.8, 0.2, 1), min-width 0.6s cubic-bezier(0.2, 0.8, 0.2, 1)',\r\n flexShrink: 0,\r\n position: 'relative',\r\n zIndex: overlay ? 1000 : 'auto',\r\n ...(overlay && { position: 'fixed', top: 0, [position]: 0 }),\r\n ...styleProp,\r\n }}\r\n >\r\n <div\r\n style={{\r\n width: resolvedWidth,\r\n height: '100%',\r\n display: 'flex',\r\n flexDirection: 'column',\r\n backgroundColor: isTransparentTheme\r\n ? 'rgba(15, 15, 35, 0.85)'\r\n : tokens.colors.background.surface,\r\n ...(isTransparentTheme && {\r\n backdropFilter: 'blur(16px)',\r\n WebkitBackdropFilter: 'blur(16px)',\r\n }),\r\n borderLeft: position === 'right' ? tokens.borders.divider : undefined,\r\n borderRight: position === 'left' ? tokens.borders.divider : undefined,\r\n transform: isVisible ? 'translateX(0)' : slideFrom,\r\n transition: 'transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1)',\r\n overflow: 'hidden',\r\n }}\r\n >\r\n {/* Header */}\r\n {(title || showCloseButton || headerActions || headerLeading) && (\r\n <div\r\n onClick={onHeaderClick}\r\n role={onHeaderClick ? 'button' : undefined}\r\n tabIndex={onHeaderClick ? 0 : undefined}\r\n onKeyDown={onHeaderClick ? (e: React.KeyboardEvent) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onHeaderClick(); } } : undefined}\r\n style={{\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'space-between',\r\n padding: `${tokens.spacing.md} ${tokens.spacing.md}`,\r\n borderBottom: tokens.borders.divider,\r\n flexShrink: 0,\r\n gap: tokens.spacing.sm,\r\n ...(onHeaderClick && { cursor: 'pointer' }),\r\n ...headerStyleProp,\r\n }}\r\n >\r\n <div style={{ display: 'flex', alignItems: 'center', gap: tokens.spacing.sm, flex: 1, minWidth: 0 }}>\r\n {headerLeading}\r\n {title && (\r\n <h3\r\n style={{\r\n margin: 0,\r\n fontSize: tokens.typography.fontSize.base,\r\n fontWeight: tokens.typography.fontWeight.semibold,\r\n color: tokens.colors.text.primary,\r\n whiteSpace: 'nowrap',\r\n overflow: 'hidden',\r\n textOverflow: 'ellipsis',\r\n }}\r\n >\r\n {title}\r\n </h3>\r\n )}\r\n </div>\r\n <div style={{ display: 'flex', alignItems: 'center', gap: tokens.spacing.xs, flexShrink: 0 }}>\r\n {headerActions}\r\n {showCloseButton && (\r\n <button\r\n type=\"button\"\r\n onClick={onClose}\r\n aria-label=\"Close panel\"\r\n style={{\r\n padding: tokens.spacing.sm,\r\n minWidth: 44,\r\n minHeight: 44,\r\n backgroundColor: 'transparent',\r\n border: 'none',\r\n borderRadius: tokens.borderRadius.sm,\r\n cursor: 'pointer',\r\n color: tokens.colors.text.muted ?? tokens.colors.text.tertiary,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n transition: `color 0.15s ease, background-color 0.15s ease`,\r\n }}\r\n onMouseEnter={(e) => {\r\n e.currentTarget.style.color = tokens.colors.text.primary;\r\n e.currentTarget.style.backgroundColor = tokens.colors.background.base;\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.color = tokens.colors.text.muted ?? tokens.colors.text.tertiary;\r\n e.currentTarget.style.backgroundColor = 'transparent';\r\n }}\r\n >\r\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\r\n <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\" />\r\n </svg>\r\n </button>\r\n )}\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Content */}\r\n <div\r\n style={{\r\n flex: 1,\r\n overflow: 'auto',\r\n minHeight: 0,\r\n }}\r\n >\r\n {children}\r\n </div>\r\n </div>\r\n </div>\r\n </>\r\n );\r\n});\r\n\r\nexport default SidePanel;\r\n"],"names":["SidePanel"],"mappings":"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"}
|