@zendir/ui 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @zendir/ui
2
2
 
3
+ ## 0.4.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Fix Popover first-open positioning flash: panel now renders with
8
+ `visibility: hidden` until the initial `requestAnimationFrame` resolves
9
+ real coordinates, preventing the one-frame misposition on first open.
10
+
3
11
  ## 0.4.0
4
12
 
5
13
  ### Minor Changes
@@ -50,6 +50,10 @@ const Popover = memo(function Popover2({
50
50
  };
51
51
  }, []);
52
52
  const [position, setPosition] = useState({});
53
+ const [positioned, setPositioned] = useState(false);
54
+ useEffect(() => {
55
+ if (!isOpen) setPositioned(false);
56
+ }, [isOpen]);
53
57
  useEffect(() => {
54
58
  if (!isOpen || !triggerRef.current) return;
55
59
  const gap = 8;
@@ -103,6 +107,7 @@ const Popover = memo(function Popover2({
103
107
  pos.width = width;
104
108
  }
105
109
  setPosition(pos);
110
+ setPositioned(true);
106
111
  };
107
112
  const raf = requestAnimationFrame(updatePosition);
108
113
  window.addEventListener("resize", updatePosition);
@@ -142,6 +147,7 @@ const Popover = memo(function Popover2({
142
147
  ...popoverHoverProps,
143
148
  style: {
144
149
  ...position,
150
+ visibility: positioned ? "visible" : "hidden",
145
151
  backgroundColor: isTransparentTheme ? "rgba(15, 12, 30, 0.85)" : tokens.colors.background.elevated,
146
152
  border: `1px solid ${tokens.colors.border.muted}`,
147
153
  borderRadius: tokens.borderRadius.md,
@@ -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\r\n const [position, setPosition] = useState<React.CSSProperties>({});\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 };\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 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 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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zendir/ui",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
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",