@flanksource/clicky-ui 0.2.21 → 0.3.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/dist/components/Combobox.cjs +5 -2
- package/dist/components/Combobox.cjs.map +1 -1
- package/dist/components/Combobox.d.ts.map +1 -1
- package/dist/components/Combobox.js +5 -2
- package/dist/components/Combobox.js.map +1 -1
- package/dist/components/FilterBar.cjs +280 -253
- package/dist/components/FilterBar.cjs.map +1 -1
- package/dist/components/FilterBar.d.ts.map +1 -1
- package/dist/components/FilterBar.js +280 -253
- package/dist/components/FilterBar.js.map +1 -1
- package/dist/components/JsonSchemaForm.cjs +3 -1
- package/dist/components/JsonSchemaForm.cjs.map +1 -1
- package/dist/components/JsonSchemaForm.d.ts.map +1 -1
- package/dist/components/JsonSchemaForm.js +4 -2
- package/dist/components/JsonSchemaForm.js.map +1 -1
- package/dist/components/MultiSelect.cjs +29 -32
- package/dist/components/MultiSelect.cjs.map +1 -1
- package/dist/components/MultiSelect.d.ts.map +1 -1
- package/dist/components/MultiSelect.js +30 -33
- package/dist/components/MultiSelect.js.map +1 -1
- package/dist/components/TimeRange.cjs +29 -37
- package/dist/components/TimeRange.cjs.map +1 -1
- package/dist/components/TimeRange.d.ts.map +1 -1
- package/dist/components/TimeRange.js +30 -38
- package/dist/components/TimeRange.js.map +1 -1
- package/dist/components/TreePickerField.cjs +6 -3
- package/dist/components/TreePickerField.cjs.map +1 -1
- package/dist/components/TreePickerField.d.ts.map +1 -1
- package/dist/components/TreePickerField.js +6 -3
- package/dist/components/TreePickerField.js.map +1 -1
- package/dist/components/icon-menu-picker.cjs +9 -3
- package/dist/components/icon-menu-picker.cjs.map +1 -1
- package/dist/components/icon-menu-picker.d.ts.map +1 -1
- package/dist/components/icon-menu-picker.js +9 -3
- package/dist/components/icon-menu-picker.js.map +1 -1
- package/dist/components/json-schema-form-fields.cjs +80 -2
- package/dist/components/json-schema-form-fields.cjs.map +1 -1
- package/dist/components/json-schema-form-fields.d.ts +15 -0
- package/dist/components/json-schema-form-fields.d.ts.map +1 -1
- package/dist/components/json-schema-form-fields.js +81 -3
- package/dist/components/json-schema-form-fields.js.map +1 -1
- package/dist/components/json-schema-form-refs.cjs +60 -0
- package/dist/components/json-schema-form-refs.cjs.map +1 -0
- package/dist/components/json-schema-form-refs.d.ts +3 -0
- package/dist/components/json-schema-form-refs.d.ts.map +1 -0
- package/dist/components/json-schema-form-refs.js +60 -0
- package/dist/components/json-schema-form-refs.js.map +1 -0
- package/dist/components/json-schema-form-render.cjs +14 -2
- package/dist/components/json-schema-form-render.cjs.map +1 -1
- package/dist/components/json-schema-form-render.d.ts.map +1 -1
- package/dist/components/json-schema-form-render.js +15 -3
- package/dist/components/json-schema-form-render.js.map +1 -1
- package/dist/components/json-schema-form-resolve.cjs +5 -0
- package/dist/components/json-schema-form-resolve.cjs.map +1 -1
- package/dist/components/json-schema-form-resolve.d.ts.map +1 -1
- package/dist/components/json-schema-form-resolve.js +5 -0
- package/dist/components/json-schema-form-resolve.js.map +1 -1
- package/dist/components/json-schema-form-template-menu.cjs +83 -0
- package/dist/components/json-schema-form-template-menu.cjs.map +1 -0
- package/dist/components/json-schema-form-template-menu.d.ts +15 -0
- package/dist/components/json-schema-form-template-menu.d.ts.map +1 -0
- package/dist/components/json-schema-form-template-menu.js +83 -0
- package/dist/components/json-schema-form-template-menu.js.map +1 -0
- package/dist/components/json-schema-form-template.cjs +18 -0
- package/dist/components/json-schema-form-template.cjs.map +1 -0
- package/dist/components/json-schema-form-template.d.ts +13 -0
- package/dist/components/json-schema-form-template.d.ts.map +1 -0
- package/dist/components/json-schema-form-template.js +18 -0
- package/dist/components/json-schema-form-template.js.map +1 -0
- package/dist/components/json-schema-form-types.d.ts +5 -1
- package/dist/components/json-schema-form-types.d.ts.map +1 -1
- package/dist/components.cjs +4 -0
- package/dist/components.cjs.map +1 -1
- package/dist/components.d.ts +3 -0
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +4 -0
- package/dist/components.js.map +1 -1
- package/dist/data/Clicky.cjs +6 -9
- package/dist/data/Clicky.cjs.map +1 -1
- package/dist/data/Clicky.d.ts.map +1 -1
- package/dist/data/Clicky.js +6 -9
- package/dist/data/Clicky.js.map +1 -1
- package/dist/data/DataTable.cjs +23 -17
- package/dist/data/DataTable.cjs.map +1 -1
- package/dist/data/DataTable.d.ts +7 -1
- package/dist/data/DataTable.d.ts.map +1 -1
- package/dist/data/DataTable.js +24 -18
- package/dist/data/DataTable.js.map +1 -1
- package/dist/data/Gauge.cjs +37 -4
- package/dist/data/Gauge.cjs.map +1 -1
- package/dist/data/Gauge.d.ts +6 -1
- package/dist/data/Gauge.d.ts.map +1 -1
- package/dist/data/Gauge.js +37 -4
- package/dist/data/Gauge.js.map +1 -1
- package/dist/data/TimeseriesCoreBars.cjs +127 -29
- package/dist/data/TimeseriesCoreBars.cjs.map +1 -1
- package/dist/data/TimeseriesCoreBars.d.ts +11 -1
- package/dist/data/TimeseriesCoreBars.d.ts.map +1 -1
- package/dist/data/TimeseriesCoreBars.js +128 -30
- package/dist/data/TimeseriesCoreBars.js.map +1 -1
- package/dist/data/TimeseriesGauge.cjs +105 -47
- package/dist/data/TimeseriesGauge.cjs.map +1 -1
- package/dist/data/TimeseriesGauge.d.ts +6 -1
- package/dist/data/TimeseriesGauge.d.ts.map +1 -1
- package/dist/data/TimeseriesGauge.js +105 -47
- package/dist/data/TimeseriesGauge.js.map +1 -1
- package/dist/data/WorkloadCard.cjs +371 -0
- package/dist/data/WorkloadCard.cjs.map +1 -0
- package/dist/data/WorkloadCard.d.ts +24 -0
- package/dist/data/WorkloadCard.d.ts.map +1 -0
- package/dist/data/WorkloadCard.js +371 -0
- package/dist/data/WorkloadCard.js.map +1 -0
- package/dist/data/WorkloadCard.model.cjs +51 -0
- package/dist/data/WorkloadCard.model.cjs.map +1 -0
- package/dist/data/WorkloadCard.model.d.ts +53 -0
- package/dist/data/WorkloadCard.model.d.ts.map +1 -0
- package/dist/data/WorkloadCard.model.js +51 -0
- package/dist/data/WorkloadCard.model.js.map +1 -0
- package/dist/data/ai/ChatFab.cjs +3 -1
- package/dist/data/ai/ChatFab.cjs.map +1 -1
- package/dist/data/ai/ChatFab.d.ts.map +1 -1
- package/dist/data/ai/ChatFab.js +3 -1
- package/dist/data/ai/ChatFab.js.map +1 -1
- package/dist/data/ai/ChatWindow.cjs +25 -7
- package/dist/data/ai/ChatWindow.cjs.map +1 -1
- package/dist/data/ai/ChatWindow.d.ts +11 -3
- package/dist/data/ai/ChatWindow.d.ts.map +1 -1
- package/dist/data/ai/ChatWindow.js +26 -8
- package/dist/data/ai/ChatWindow.js.map +1 -1
- package/dist/data/ai/chat-window-context.cjs +2 -1
- package/dist/data/ai/chat-window-context.cjs.map +1 -1
- package/dist/data/ai/chat-window-context.d.ts +1 -1
- package/dist/data/ai/chat-window-context.d.ts.map +1 -1
- package/dist/data/ai/chat-window-context.js +2 -1
- package/dist/data/ai/chat-window-context.js.map +1 -1
- package/dist/data/ai/context.cjs.map +1 -1
- package/dist/data/ai/context.d.ts +3 -0
- package/dist/data/ai/context.d.ts.map +1 -1
- package/dist/data/ai/context.js.map +1 -1
- package/dist/data/chat/Chat.cjs +9 -1
- package/dist/data/chat/Chat.cjs.map +1 -1
- package/dist/data/chat/Chat.d.ts +4 -2
- package/dist/data/chat/Chat.d.ts.map +1 -1
- package/dist/data/chat/Chat.js +10 -2
- package/dist/data/chat/Chat.js.map +1 -1
- package/dist/data/chat/Message.cjs +13 -4
- package/dist/data/chat/Message.cjs.map +1 -1
- package/dist/data/chat/Message.d.ts +4 -2
- package/dist/data/chat/Message.d.ts.map +1 -1
- package/dist/data/chat/Message.js +13 -4
- package/dist/data/chat/Message.js.map +1 -1
- package/dist/data/chat/ToolCall.cjs +9 -4
- package/dist/data/chat/ToolCall.cjs.map +1 -1
- package/dist/data/chat/ToolCall.d.ts +3 -2
- package/dist/data/chat/ToolCall.d.ts.map +1 -1
- package/dist/data/chat/ToolCall.js +9 -4
- package/dist/data/chat/ToolCall.js.map +1 -1
- package/dist/data/chat/index.d.ts +1 -1
- package/dist/data/chat/index.d.ts.map +1 -1
- package/dist/data/chat/types.cjs.map +1 -1
- package/dist/data/chat/types.d.ts +7 -0
- package/dist/data/chat/types.d.ts.map +1 -1
- package/dist/data/chat/types.js.map +1 -1
- package/dist/data/version-info.cjs +2 -2
- package/dist/data/version-info.js +2 -2
- package/dist/data.cjs +7 -0
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.ts +5 -3
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +8 -1
- package/dist/data.js.map +1 -1
- package/dist/hooks/use-sort.cjs +19 -10
- package/dist/hooks/use-sort.cjs.map +1 -1
- package/dist/hooks/use-sort.d.ts +3 -0
- package/dist/hooks/use-sort.d.ts.map +1 -1
- package/dist/hooks/use-sort.js +19 -10
- package/dist/hooks/use-sort.js.map +1 -1
- package/dist/index.cjs +11 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/layout/AppShell.cjs +6 -10
- package/dist/layout/AppShell.cjs.map +1 -1
- package/dist/layout/AppShell.d.ts +2 -0
- package/dist/layout/AppShell.d.ts.map +1 -1
- package/dist/layout/AppShell.js +6 -10
- package/dist/layout/AppShell.js.map +1 -1
- package/dist/overlay/DropdownMenu.cjs +50 -38
- package/dist/overlay/DropdownMenu.cjs.map +1 -1
- package/dist/overlay/DropdownMenu.d.ts +5 -1
- package/dist/overlay/DropdownMenu.d.ts.map +1 -1
- package/dist/overlay/DropdownMenu.js +50 -38
- package/dist/overlay/DropdownMenu.js.map +1 -1
- package/dist/overlay/HoverCard.cjs +7 -2
- package/dist/overlay/HoverCard.cjs.map +1 -1
- package/dist/overlay/HoverCard.d.ts.map +1 -1
- package/dist/overlay/HoverCard.js +7 -2
- package/dist/overlay/HoverCard.js.map +1 -1
- package/dist/overlay/Modal.cjs +20 -17
- package/dist/overlay/Modal.cjs.map +1 -1
- package/dist/overlay/Modal.d.ts.map +1 -1
- package/dist/overlay/Modal.js +21 -18
- package/dist/overlay/Modal.js.map +1 -1
- package/dist/overlay/Toast.cjs +9 -1
- package/dist/overlay/Toast.cjs.map +1 -1
- package/dist/overlay/Toast.d.ts.map +1 -1
- package/dist/overlay/Toast.js +9 -1
- package/dist/overlay/Toast.js.map +1 -1
- package/dist/overlay/modalStack.cjs +53 -1
- package/dist/overlay/modalStack.cjs.map +1 -1
- package/dist/overlay/modalStack.d.ts +12 -0
- package/dist/overlay/modalStack.d.ts.map +1 -1
- package/dist/overlay/modalStack.js +54 -2
- package/dist/overlay/modalStack.js.map +1 -1
- package/dist/overlay/zIndex.cjs +20 -0
- package/dist/overlay/zIndex.cjs.map +1 -0
- package/dist/overlay/zIndex.d.ts +39 -0
- package/dist/overlay/zIndex.d.ts.map +1 -0
- package/dist/overlay/zIndex.js +20 -0
- package/dist/overlay/zIndex.js.map +1 -0
- package/dist/rpc/AcceptPicker.cjs +28 -26
- package/dist/rpc/AcceptPicker.cjs.map +1 -1
- package/dist/rpc/AcceptPicker.d.ts.map +1 -1
- package/dist/rpc/AcceptPicker.js +29 -27
- package/dist/rpc/AcceptPicker.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownMenu.cjs","sources":["../../src/overlay/DropdownMenu.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport {\n autoUpdate,\n flip,\n FloatingFocusManager,\n FloatingPortal,\n offset,\n shift,\n useClick,\n useDismiss,\n useFloating,\n useInteractions,\n useListNavigation,\n useRole,\n} from \"@floating-ui/react\";\nimport { cn } from \"../lib/utils\";\nimport { Button, type ButtonProps } from \"../components/button\";\nimport { Icon, type StaticIconComponent } from \"../data/Icon\";\nimport { UiChevronDown } from \"../icons\";\n\nexport type DropdownMenuItem = {\n /** Visible label. */\n label: ReactNode;\n /** Called when the item is chosen. */\n onSelect: () => void;\n /** Iconify name or imported icon component rendered before the label. */\n icon?: string | StaticIconComponent;\n /** Browser tooltip for the item. */\n title?: string;\n /** Disable selection. */\n disabled?: boolean;\n};\n\nexport type DropdownMenuProps = {\n /** Trigger label. Ignored when `trigger` is provided. */\n label?: ReactNode;\n /** Icon shown before the trigger label. Ignored when `trigger` is provided. */\n icon?: string | StaticIconComponent;\n /** Hide the trailing chevron on the default trigger. */\n hideChevron?: boolean;\n /** Variant forwarded to the default Button trigger. */\n variant?: ButtonProps[\"variant\"];\n /** Size forwarded to the default Button trigger. */\n size?: ButtonProps[\"size\"];\n /** Fully custom trigger. Receives no props; the menu wraps it with open state. */\n trigger?: ReactNode;\n /** Declarative menu items. Provide this or `children`, not both. */\n items?: DropdownMenuItem[];\n /** Custom menu content. Use `closeMenu` to dismiss after a selection. */\n children?: (closeMenu: () => void) => ReactNode;\n /** Horizontal alignment of the menu relative to the trigger. */\n align?: \"left\" | \"right\";\n /** Notified whenever the menu opens or closes. */\n onOpenChange?: (open: boolean) => void;\n /** Browser tooltip / accessible label for the default trigger. */\n title?: string;\n /** Accessible label (`aria-label`) for the floating menu element itself. */\n menuLabel?: string;\n /** Classes applied to the wrapper. */\n className?: string;\n /** Classes applied to the floating menu. */\n menuClassName?: string;\n};\n\nexport function DropdownMenu({\n label,\n icon,\n hideChevron = false,\n variant = \"outline\",\n size = \"sm\",\n trigger,\n items,\n children,\n align = \"right\",\n onOpenChange,\n title,\n menuLabel,\n className,\n menuClassName,\n}: DropdownMenuProps) {\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState<number | null>(null);\n const listRef = useRef<(HTMLElement | null)[]>([]);\n\n const onOpenChangeRef = useRef(onOpenChange);\n onOpenChangeRef.current = onOpenChange;\n useEffect(() => {\n onOpenChangeRef.current?.(open);\n }, [open]);\n\n const { refs, floatingStyles, context } = useFloating<HTMLDivElement>({\n open,\n onOpenChange: setOpen,\n // bottom-end / bottom-start reproduce the previous right/left alignment.\n placement: align === \"right\" ? \"bottom-end\" : \"bottom-start\",\n whileElementsMounted: autoUpdate,\n // offset(4) ≈ the previous mt-1; flip opens upward at the bottom edge and\n // shift slides the menu back on-screen at the horizontal edges.\n middleware: [offset(4), flip({ padding: 8 }), shift({ padding: 8 })],\n });\n\n const click = useClick(context);\n // useDismiss is React-tree aware: a press inside anything rendered through the\n // menu's `children` render-prop — including a Modal that portals to\n // document.body — propagates via React's synthetic events to the floating\n // element and counts as \"inside\", so it won't dismiss the menu (which would\n // unmount that child Modal). A Modal owned by a sibling/parent instead of this\n // menu correctly does dismiss the menu, which is harmless since closing the\n // menu does not unmount a Modal it doesn't own.\n const dismiss = useDismiss(context);\n const role = useRole(context, { role: \"menu\" });\n const listNav = useListNavigation(context, {\n listRef,\n activeIndex,\n onNavigate: setActiveIndex,\n loop: true,\n });\n\n const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n click,\n dismiss,\n role,\n listNav,\n ]);\n\n const closeMenu = () => setOpen(false);\n\n return (\n <div className={cn(\"relative inline-flex\", className)}>\n {trigger ? (\n <span\n ref={refs.setReference}\n className=\"inline-flex\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n {...getReferenceProps()}\n >\n {trigger}\n </span>\n ) : (\n <Button\n ref={refs.setReference as React.Ref<HTMLButtonElement>}\n variant={variant}\n size={size}\n title={title}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n {...getReferenceProps()}\n >\n {icon && <Icon {...(typeof icon === \"string\" ? { name: icon } : { icon })} />}\n {label}\n {!hideChevron && <Icon icon={UiChevronDown} />}\n </Button>\n )}\n {open && (\n <FloatingPortal>\n <FloatingFocusManager context={context} modal={false}>\n <div\n ref={refs.setFloating}\n role=\"menu\"\n aria-label={menuLabel}\n style={floatingStyles}\n className={cn(\n \"z-[9999] min-w-[8rem] rounded-md border border-border bg-popover py-1 shadow-md\",\n menuClassName,\n )}\n {...getFloatingProps()}\n >\n {children\n ? children(closeMenu)\n : items?.map((item, i) => (\n <button\n key={i}\n ref={(node) => {\n listRef.current[i] = node;\n }}\n type=\"button\"\n role=\"menuitem\"\n tabIndex={i === activeIndex ? 0 : -1}\n disabled={item.disabled}\n title={item.title}\n className=\"flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs text-popover-foreground hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50\"\n {...getItemProps({\n onClick: () => {\n item.onSelect();\n closeMenu();\n },\n onKeyDown: (event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n item.onSelect();\n closeMenu();\n }\n },\n })}\n >\n {item.icon && (\n <Icon\n {...(typeof item.icon === \"string\"\n ? { name: item.icon }\n : { icon: item.icon })}\n />\n )}\n {item.label}\n </button>\n ))}\n </div>\n </FloatingFocusManager>\n </FloatingPortal>\n )}\n </div>\n );\n}\n"],"names":["useState","useRef","useEffect","useFloating","autoUpdate","offset","flip","shift","useClick","useDismiss","useRole","useListNavigation","useInteractions","cn","jsx","jsxs","Button","Icon","UiChevronDown","FloatingPortal","FloatingFocusManager"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DropdownMenu.cjs","sources":["../../src/overlay/DropdownMenu.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport {\n autoUpdate,\n flip,\n FloatingFocusManager,\n FloatingPortal,\n offset,\n shift,\n useClick,\n useDismiss,\n useFloating,\n useInteractions,\n useListNavigation,\n useRole,\n} from \"@floating-ui/react\";\nimport { cn } from \"../lib/utils\";\nimport { Button, type ButtonProps } from \"../components/button\";\nimport { Icon, type StaticIconComponent } from \"../data/Icon\";\nimport { UiChevronDown } from \"../icons\";\nimport { useEscapeLayer, useFloatingZIndex } from \"./modalStack\";\n\nexport type DropdownMenuItem = {\n /** Visible label. */\n label: ReactNode;\n /** Called when the item is chosen. */\n onSelect: () => void;\n /** Iconify name or imported icon component rendered before the label. */\n icon?: string | StaticIconComponent;\n /** Browser tooltip for the item. */\n title?: string;\n /** Disable selection. */\n disabled?: boolean;\n};\n\nexport type DropdownMenuProps = {\n /** Trigger label. Ignored when `trigger` is provided. */\n label?: ReactNode;\n /** Icon shown before the trigger label. Ignored when `trigger` is provided. */\n icon?: string | StaticIconComponent;\n /** Hide the trailing chevron on the default trigger. */\n hideChevron?: boolean;\n /** Variant forwarded to the default Button trigger. */\n variant?: ButtonProps[\"variant\"];\n /** Size forwarded to the default Button trigger. */\n size?: ButtonProps[\"size\"];\n /** Fully custom trigger. Receives no props; the menu wraps it with open state. */\n trigger?: ReactNode;\n /** Declarative menu items. Provide this or `children`, not both. */\n items?: DropdownMenuItem[];\n /** Custom menu content. Use `closeMenu` to dismiss after a selection. */\n children?: (closeMenu: () => void) => ReactNode;\n /** Rendered at the top of the menu, above the items/children. */\n header?: ReactNode;\n /** Rendered at the bottom of the menu, below the items/children (e.g. a \"Show more…\" link). */\n footer?: ReactNode;\n /** Horizontal alignment of the menu relative to the trigger. */\n align?: \"left\" | \"right\";\n /** Notified whenever the menu opens or closes. */\n onOpenChange?: (open: boolean) => void;\n /** Browser tooltip / accessible label for the default trigger. */\n title?: string;\n /** Accessible label (`aria-label`) for the floating menu element itself. */\n menuLabel?: string;\n /** Classes applied to the wrapper. */\n className?: string;\n /** Classes applied to the floating menu. */\n menuClassName?: string;\n};\n\nexport function DropdownMenu({\n label,\n icon,\n hideChevron = false,\n variant = \"outline\",\n size = \"sm\",\n trigger,\n items,\n children,\n header,\n footer,\n align = \"right\",\n onOpenChange,\n title,\n menuLabel,\n className,\n menuClassName,\n}: DropdownMenuProps) {\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState<number | null>(null);\n const floatingZ = useFloatingZIndex();\n const listRef = useRef<(HTMLElement | null)[]>([]);\n\n const onOpenChangeRef = useRef(onOpenChange);\n onOpenChangeRef.current = onOpenChange;\n useEffect(() => {\n onOpenChangeRef.current?.(open);\n }, [open]);\n\n const { refs, floatingStyles, context } = useFloating<HTMLDivElement>({\n open,\n onOpenChange: setOpen,\n // bottom-end / bottom-start reproduce the previous right/left alignment.\n placement: align === \"right\" ? \"bottom-end\" : \"bottom-start\",\n whileElementsMounted: autoUpdate,\n // offset(4) ≈ the previous mt-1; flip opens upward at the bottom edge and\n // shift slides the menu back on-screen at the horizontal edges.\n middleware: [offset(4), flip({ padding: 8 }), shift({ padding: 8 })],\n });\n\n const click = useClick(context);\n // useDismiss is React-tree aware: a press inside anything rendered through the\n // menu's `children` render-prop — including a Modal that portals to\n // document.body — propagates via React's synthetic events to the floating\n // element and counts as \"inside\", so it won't dismiss the menu (which would\n // unmount that child Modal). A Modal owned by a sibling/parent instead of this\n // menu correctly does dismiss the menu, which is harmless since closing the\n // menu does not unmount a Modal it doesn't own.\n const dismiss = useDismiss(context, { escapeKey: false });\n const role = useRole(context, { role: \"menu\" });\n const listNav = useListNavigation(context, {\n listRef,\n activeIndex,\n onNavigate: setActiveIndex,\n loop: true,\n });\n\n const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n click,\n dismiss,\n role,\n listNav,\n ]);\n\n const closeMenu = () => {\n setOpen(false);\n if (refs.domReference.current instanceof HTMLElement) refs.domReference.current.focus();\n };\n useEscapeLayer(open, closeMenu);\n\n return (\n <div className={cn(\"relative inline-flex\", className)}>\n {trigger ? (\n <span\n ref={refs.setReference}\n className=\"inline-flex\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n {...getReferenceProps()}\n >\n {trigger}\n </span>\n ) : (\n <Button\n ref={refs.setReference as React.Ref<HTMLButtonElement>}\n variant={variant}\n size={size}\n title={title}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n {...getReferenceProps()}\n >\n {icon && <Icon {...(typeof icon === \"string\" ? { name: icon } : { icon })} />}\n {label}\n {!hideChevron && <Icon icon={UiChevronDown} />}\n </Button>\n )}\n {open && (\n <FloatingPortal>\n <FloatingFocusManager context={context} modal={false}>\n <div\n ref={refs.setFloating}\n role=\"menu\"\n aria-label={menuLabel}\n style={{ ...floatingStyles, zIndex: floatingZ }}\n className={cn(\n \"min-w-[8rem] rounded-md border border-border bg-popover py-1 shadow-md\",\n menuClassName,\n )}\n {...getFloatingProps()}\n >\n {header != null && (\n <div className=\"border-b border-border px-3 py-1.5\">{header}</div>\n )}\n {children\n ? children(closeMenu)\n : items?.map((item, i) => (\n <button\n key={i}\n ref={(node) => {\n listRef.current[i] = node;\n }}\n type=\"button\"\n role=\"menuitem\"\n tabIndex={i === activeIndex ? 0 : -1}\n disabled={item.disabled}\n title={item.title}\n className=\"flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs text-popover-foreground hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50\"\n {...getItemProps({\n onClick: () => {\n item.onSelect();\n closeMenu();\n },\n onKeyDown: (event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n item.onSelect();\n closeMenu();\n }\n },\n })}\n >\n {item.icon && (\n <Icon\n {...(typeof item.icon === \"string\"\n ? { name: item.icon }\n : { icon: item.icon })}\n />\n )}\n {item.label}\n </button>\n ))}\n {footer != null && (\n <div className=\"border-t border-border px-3 py-1.5\">{footer}</div>\n )}\n </div>\n </FloatingFocusManager>\n </FloatingPortal>\n )}\n </div>\n );\n}\n"],"names":["useState","useFloatingZIndex","useRef","useEffect","useFloating","autoUpdate","offset","flip","shift","useClick","useDismiss","useRole","useListNavigation","useInteractions","useEscapeLayer","cn","jsx","jsxs","Button","Icon","UiChevronDown","FloatingPortal","FloatingFocusManager"],"mappings":"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"}
|
|
@@ -30,6 +30,10 @@ export type DropdownMenuProps = {
|
|
|
30
30
|
items?: DropdownMenuItem[];
|
|
31
31
|
/** Custom menu content. Use `closeMenu` to dismiss after a selection. */
|
|
32
32
|
children?: (closeMenu: () => void) => ReactNode;
|
|
33
|
+
/** Rendered at the top of the menu, above the items/children. */
|
|
34
|
+
header?: ReactNode;
|
|
35
|
+
/** Rendered at the bottom of the menu, below the items/children (e.g. a "Show more…" link). */
|
|
36
|
+
footer?: ReactNode;
|
|
33
37
|
/** Horizontal alignment of the menu relative to the trigger. */
|
|
34
38
|
align?: "left" | "right";
|
|
35
39
|
/** Notified whenever the menu opens or closes. */
|
|
@@ -43,5 +47,5 @@ export type DropdownMenuProps = {
|
|
|
43
47
|
/** Classes applied to the floating menu. */
|
|
44
48
|
menuClassName?: string;
|
|
45
49
|
};
|
|
46
|
-
export declare function DropdownMenu({ label, icon, hideChevron, variant, size, trigger, items, children, align, onOpenChange, title, menuLabel, className, menuClassName, }: DropdownMenuProps): import("react/jsx-runtime").JSX.Element;
|
|
50
|
+
export declare function DropdownMenu({ label, icon, hideChevron, variant, size, trigger, items, children, header, footer, align, onOpenChange, title, menuLabel, className, menuClassName, }: DropdownMenuProps): import("react/jsx-runtime").JSX.Element;
|
|
47
51
|
//# sourceMappingURL=DropdownMenu.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownMenu.d.ts","sourceRoot":"","sources":["../../src/overlay/DropdownMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAgBpE,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAQ,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"DropdownMenu.d.ts","sourceRoot":"","sources":["../../src/overlay/DropdownMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAgBpE,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAQ,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAI9D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qBAAqB;IACrB,KAAK,EAAE,SAAS,CAAC;IACjB,sCAAsC;IACtC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAAC;IACpC,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,yDAAyD;IACzD,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,+EAA+E;IAC/E,IAAI,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAAC;IACpC,wDAAwD;IACxD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uDAAuD;IACvD,OAAO,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IACjC,oDAAoD;IACpD,IAAI,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC3B,kFAAkF;IAClF,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,oEAAoE;IACpE,KAAK,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,IAAI,KAAK,SAAS,CAAC;IAChD,iEAAiE;IACjE,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,+FAA+F;IAC/F,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,kDAAkD;IAClD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,IAAI,EACJ,WAAmB,EACnB,OAAmB,EACnB,IAAW,EACX,OAAO,EACP,KAAK,EACL,QAAQ,EACR,MAAM,EACN,MAAM,EACN,KAAe,EACf,YAAY,EACZ,KAAK,EACL,SAAS,EACT,SAAS,EACT,aAAa,GACd,EAAE,iBAAiB,2CAgJnB"}
|
|
@@ -4,6 +4,7 @@ import { useFloating, autoUpdate, offset, flip, shift, useClick, useDismiss, use
|
|
|
4
4
|
import { cn } from "../lib/utils.js";
|
|
5
5
|
import { Button } from "../components/button.js";
|
|
6
6
|
import { Icon } from "../data/Icon.js";
|
|
7
|
+
import { useFloatingZIndex, useEscapeLayer } from "./modalStack.js";
|
|
7
8
|
import { UiChevronDown } from "../icons/components/UiChevronDown.js";
|
|
8
9
|
function DropdownMenu({
|
|
9
10
|
label,
|
|
@@ -14,6 +15,8 @@ function DropdownMenu({
|
|
|
14
15
|
trigger,
|
|
15
16
|
items,
|
|
16
17
|
children,
|
|
18
|
+
header,
|
|
19
|
+
footer,
|
|
17
20
|
align = "right",
|
|
18
21
|
onOpenChange,
|
|
19
22
|
title,
|
|
@@ -23,6 +26,7 @@ function DropdownMenu({
|
|
|
23
26
|
}) {
|
|
24
27
|
const [open, setOpen] = useState(false);
|
|
25
28
|
const [activeIndex, setActiveIndex] = useState(null);
|
|
29
|
+
const floatingZ = useFloatingZIndex();
|
|
26
30
|
const listRef = useRef([]);
|
|
27
31
|
const onOpenChangeRef = useRef(onOpenChange);
|
|
28
32
|
onOpenChangeRef.current = onOpenChange;
|
|
@@ -41,7 +45,7 @@ function DropdownMenu({
|
|
|
41
45
|
middleware: [offset(4), flip({ padding: 8 }), shift({ padding: 8 })]
|
|
42
46
|
});
|
|
43
47
|
const click = useClick(context);
|
|
44
|
-
const dismiss = useDismiss(context);
|
|
48
|
+
const dismiss = useDismiss(context, { escapeKey: false });
|
|
45
49
|
const role = useRole(context, { role: "menu" });
|
|
46
50
|
const listNav = useListNavigation(context, {
|
|
47
51
|
listRef,
|
|
@@ -55,7 +59,11 @@ function DropdownMenu({
|
|
|
55
59
|
role,
|
|
56
60
|
listNav
|
|
57
61
|
]);
|
|
58
|
-
const closeMenu = () =>
|
|
62
|
+
const closeMenu = () => {
|
|
63
|
+
setOpen(false);
|
|
64
|
+
if (refs.domReference.current instanceof HTMLElement) refs.domReference.current.focus();
|
|
65
|
+
};
|
|
66
|
+
useEscapeLayer(open, closeMenu);
|
|
59
67
|
return /* @__PURE__ */ jsxs("div", { className: cn("relative inline-flex", className), children: [
|
|
60
68
|
trigger ? /* @__PURE__ */ jsx(
|
|
61
69
|
"span",
|
|
@@ -84,55 +92,59 @@ function DropdownMenu({
|
|
|
84
92
|
]
|
|
85
93
|
}
|
|
86
94
|
),
|
|
87
|
-
open && /* @__PURE__ */ jsx(FloatingPortal, { children: /* @__PURE__ */ jsx(FloatingFocusManager, { context, modal: false, children: /* @__PURE__ */
|
|
95
|
+
open && /* @__PURE__ */ jsx(FloatingPortal, { children: /* @__PURE__ */ jsx(FloatingFocusManager, { context, modal: false, children: /* @__PURE__ */ jsxs(
|
|
88
96
|
"div",
|
|
89
97
|
{
|
|
90
98
|
ref: refs.setFloating,
|
|
91
99
|
role: "menu",
|
|
92
100
|
"aria-label": menuLabel,
|
|
93
|
-
style: floatingStyles,
|
|
101
|
+
style: { ...floatingStyles, zIndex: floatingZ },
|
|
94
102
|
className: cn(
|
|
95
|
-
"
|
|
103
|
+
"min-w-[8rem] rounded-md border border-border bg-popover py-1 shadow-md",
|
|
96
104
|
menuClassName
|
|
97
105
|
),
|
|
98
106
|
...getFloatingProps(),
|
|
99
|
-
children:
|
|
100
|
-
"
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
role: "menuitem",
|
|
107
|
-
tabIndex: i === activeIndex ? 0 : -1,
|
|
108
|
-
disabled: item.disabled,
|
|
109
|
-
title: item.title,
|
|
110
|
-
className: "flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs text-popover-foreground hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50",
|
|
111
|
-
...getItemProps({
|
|
112
|
-
onClick: () => {
|
|
113
|
-
item.onSelect();
|
|
114
|
-
closeMenu();
|
|
107
|
+
children: [
|
|
108
|
+
header != null && /* @__PURE__ */ jsx("div", { className: "border-b border-border px-3 py-1.5", children: header }),
|
|
109
|
+
children ? children(closeMenu) : items == null ? void 0 : items.map((item, i) => /* @__PURE__ */ jsxs(
|
|
110
|
+
"button",
|
|
111
|
+
{
|
|
112
|
+
ref: (node) => {
|
|
113
|
+
listRef.current[i] = node;
|
|
115
114
|
},
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
type: "button",
|
|
116
|
+
role: "menuitem",
|
|
117
|
+
tabIndex: i === activeIndex ? 0 : -1,
|
|
118
|
+
disabled: item.disabled,
|
|
119
|
+
title: item.title,
|
|
120
|
+
className: "flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs text-popover-foreground hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50",
|
|
121
|
+
...getItemProps({
|
|
122
|
+
onClick: () => {
|
|
119
123
|
item.onSelect();
|
|
120
124
|
closeMenu();
|
|
125
|
+
},
|
|
126
|
+
onKeyDown: (event) => {
|
|
127
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
item.onSelect();
|
|
130
|
+
closeMenu();
|
|
131
|
+
}
|
|
121
132
|
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
}),
|
|
134
|
+
children: [
|
|
135
|
+
item.icon && /* @__PURE__ */ jsx(
|
|
136
|
+
Icon,
|
|
137
|
+
{
|
|
138
|
+
...typeof item.icon === "string" ? { name: item.icon } : { icon: item.icon }
|
|
139
|
+
}
|
|
140
|
+
),
|
|
141
|
+
item.label
|
|
142
|
+
]
|
|
143
|
+
},
|
|
144
|
+
i
|
|
145
|
+
)),
|
|
146
|
+
footer != null && /* @__PURE__ */ jsx("div", { className: "border-t border-border px-3 py-1.5", children: footer })
|
|
147
|
+
]
|
|
136
148
|
}
|
|
137
149
|
) }) })
|
|
138
150
|
] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownMenu.js","sources":["../../src/overlay/DropdownMenu.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport {\n autoUpdate,\n flip,\n FloatingFocusManager,\n FloatingPortal,\n offset,\n shift,\n useClick,\n useDismiss,\n useFloating,\n useInteractions,\n useListNavigation,\n useRole,\n} from \"@floating-ui/react\";\nimport { cn } from \"../lib/utils\";\nimport { Button, type ButtonProps } from \"../components/button\";\nimport { Icon, type StaticIconComponent } from \"../data/Icon\";\nimport { UiChevronDown } from \"../icons\";\n\nexport type DropdownMenuItem = {\n /** Visible label. */\n label: ReactNode;\n /** Called when the item is chosen. */\n onSelect: () => void;\n /** Iconify name or imported icon component rendered before the label. */\n icon?: string | StaticIconComponent;\n /** Browser tooltip for the item. */\n title?: string;\n /** Disable selection. */\n disabled?: boolean;\n};\n\nexport type DropdownMenuProps = {\n /** Trigger label. Ignored when `trigger` is provided. */\n label?: ReactNode;\n /** Icon shown before the trigger label. Ignored when `trigger` is provided. */\n icon?: string | StaticIconComponent;\n /** Hide the trailing chevron on the default trigger. */\n hideChevron?: boolean;\n /** Variant forwarded to the default Button trigger. */\n variant?: ButtonProps[\"variant\"];\n /** Size forwarded to the default Button trigger. */\n size?: ButtonProps[\"size\"];\n /** Fully custom trigger. Receives no props; the menu wraps it with open state. */\n trigger?: ReactNode;\n /** Declarative menu items. Provide this or `children`, not both. */\n items?: DropdownMenuItem[];\n /** Custom menu content. Use `closeMenu` to dismiss after a selection. */\n children?: (closeMenu: () => void) => ReactNode;\n /** Horizontal alignment of the menu relative to the trigger. */\n align?: \"left\" | \"right\";\n /** Notified whenever the menu opens or closes. */\n onOpenChange?: (open: boolean) => void;\n /** Browser tooltip / accessible label for the default trigger. */\n title?: string;\n /** Accessible label (`aria-label`) for the floating menu element itself. */\n menuLabel?: string;\n /** Classes applied to the wrapper. */\n className?: string;\n /** Classes applied to the floating menu. */\n menuClassName?: string;\n};\n\nexport function DropdownMenu({\n label,\n icon,\n hideChevron = false,\n variant = \"outline\",\n size = \"sm\",\n trigger,\n items,\n children,\n align = \"right\",\n onOpenChange,\n title,\n menuLabel,\n className,\n menuClassName,\n}: DropdownMenuProps) {\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState<number | null>(null);\n const listRef = useRef<(HTMLElement | null)[]>([]);\n\n const onOpenChangeRef = useRef(onOpenChange);\n onOpenChangeRef.current = onOpenChange;\n useEffect(() => {\n onOpenChangeRef.current?.(open);\n }, [open]);\n\n const { refs, floatingStyles, context } = useFloating<HTMLDivElement>({\n open,\n onOpenChange: setOpen,\n // bottom-end / bottom-start reproduce the previous right/left alignment.\n placement: align === \"right\" ? \"bottom-end\" : \"bottom-start\",\n whileElementsMounted: autoUpdate,\n // offset(4) ≈ the previous mt-1; flip opens upward at the bottom edge and\n // shift slides the menu back on-screen at the horizontal edges.\n middleware: [offset(4), flip({ padding: 8 }), shift({ padding: 8 })],\n });\n\n const click = useClick(context);\n // useDismiss is React-tree aware: a press inside anything rendered through the\n // menu's `children` render-prop — including a Modal that portals to\n // document.body — propagates via React's synthetic events to the floating\n // element and counts as \"inside\", so it won't dismiss the menu (which would\n // unmount that child Modal). A Modal owned by a sibling/parent instead of this\n // menu correctly does dismiss the menu, which is harmless since closing the\n // menu does not unmount a Modal it doesn't own.\n const dismiss = useDismiss(context);\n const role = useRole(context, { role: \"menu\" });\n const listNav = useListNavigation(context, {\n listRef,\n activeIndex,\n onNavigate: setActiveIndex,\n loop: true,\n });\n\n const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n click,\n dismiss,\n role,\n listNav,\n ]);\n\n const closeMenu = () => setOpen(false);\n\n return (\n <div className={cn(\"relative inline-flex\", className)}>\n {trigger ? (\n <span\n ref={refs.setReference}\n className=\"inline-flex\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n {...getReferenceProps()}\n >\n {trigger}\n </span>\n ) : (\n <Button\n ref={refs.setReference as React.Ref<HTMLButtonElement>}\n variant={variant}\n size={size}\n title={title}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n {...getReferenceProps()}\n >\n {icon && <Icon {...(typeof icon === \"string\" ? { name: icon } : { icon })} />}\n {label}\n {!hideChevron && <Icon icon={UiChevronDown} />}\n </Button>\n )}\n {open && (\n <FloatingPortal>\n <FloatingFocusManager context={context} modal={false}>\n <div\n ref={refs.setFloating}\n role=\"menu\"\n aria-label={menuLabel}\n style={floatingStyles}\n className={cn(\n \"z-[9999] min-w-[8rem] rounded-md border border-border bg-popover py-1 shadow-md\",\n menuClassName,\n )}\n {...getFloatingProps()}\n >\n {children\n ? children(closeMenu)\n : items?.map((item, i) => (\n <button\n key={i}\n ref={(node) => {\n listRef.current[i] = node;\n }}\n type=\"button\"\n role=\"menuitem\"\n tabIndex={i === activeIndex ? 0 : -1}\n disabled={item.disabled}\n title={item.title}\n className=\"flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs text-popover-foreground hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50\"\n {...getItemProps({\n onClick: () => {\n item.onSelect();\n closeMenu();\n },\n onKeyDown: (event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n item.onSelect();\n closeMenu();\n }\n },\n })}\n >\n {item.icon && (\n <Icon\n {...(typeof item.icon === \"string\"\n ? { name: item.icon }\n : { icon: item.icon })}\n />\n )}\n {item.label}\n </button>\n ))}\n </div>\n </FloatingFocusManager>\n </FloatingPortal>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DropdownMenu.js","sources":["../../src/overlay/DropdownMenu.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport {\n autoUpdate,\n flip,\n FloatingFocusManager,\n FloatingPortal,\n offset,\n shift,\n useClick,\n useDismiss,\n useFloating,\n useInteractions,\n useListNavigation,\n useRole,\n} from \"@floating-ui/react\";\nimport { cn } from \"../lib/utils\";\nimport { Button, type ButtonProps } from \"../components/button\";\nimport { Icon, type StaticIconComponent } from \"../data/Icon\";\nimport { UiChevronDown } from \"../icons\";\nimport { useEscapeLayer, useFloatingZIndex } from \"./modalStack\";\n\nexport type DropdownMenuItem = {\n /** Visible label. */\n label: ReactNode;\n /** Called when the item is chosen. */\n onSelect: () => void;\n /** Iconify name or imported icon component rendered before the label. */\n icon?: string | StaticIconComponent;\n /** Browser tooltip for the item. */\n title?: string;\n /** Disable selection. */\n disabled?: boolean;\n};\n\nexport type DropdownMenuProps = {\n /** Trigger label. Ignored when `trigger` is provided. */\n label?: ReactNode;\n /** Icon shown before the trigger label. Ignored when `trigger` is provided. */\n icon?: string | StaticIconComponent;\n /** Hide the trailing chevron on the default trigger. */\n hideChevron?: boolean;\n /** Variant forwarded to the default Button trigger. */\n variant?: ButtonProps[\"variant\"];\n /** Size forwarded to the default Button trigger. */\n size?: ButtonProps[\"size\"];\n /** Fully custom trigger. Receives no props; the menu wraps it with open state. */\n trigger?: ReactNode;\n /** Declarative menu items. Provide this or `children`, not both. */\n items?: DropdownMenuItem[];\n /** Custom menu content. Use `closeMenu` to dismiss after a selection. */\n children?: (closeMenu: () => void) => ReactNode;\n /** Rendered at the top of the menu, above the items/children. */\n header?: ReactNode;\n /** Rendered at the bottom of the menu, below the items/children (e.g. a \"Show more…\" link). */\n footer?: ReactNode;\n /** Horizontal alignment of the menu relative to the trigger. */\n align?: \"left\" | \"right\";\n /** Notified whenever the menu opens or closes. */\n onOpenChange?: (open: boolean) => void;\n /** Browser tooltip / accessible label for the default trigger. */\n title?: string;\n /** Accessible label (`aria-label`) for the floating menu element itself. */\n menuLabel?: string;\n /** Classes applied to the wrapper. */\n className?: string;\n /** Classes applied to the floating menu. */\n menuClassName?: string;\n};\n\nexport function DropdownMenu({\n label,\n icon,\n hideChevron = false,\n variant = \"outline\",\n size = \"sm\",\n trigger,\n items,\n children,\n header,\n footer,\n align = \"right\",\n onOpenChange,\n title,\n menuLabel,\n className,\n menuClassName,\n}: DropdownMenuProps) {\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState<number | null>(null);\n const floatingZ = useFloatingZIndex();\n const listRef = useRef<(HTMLElement | null)[]>([]);\n\n const onOpenChangeRef = useRef(onOpenChange);\n onOpenChangeRef.current = onOpenChange;\n useEffect(() => {\n onOpenChangeRef.current?.(open);\n }, [open]);\n\n const { refs, floatingStyles, context } = useFloating<HTMLDivElement>({\n open,\n onOpenChange: setOpen,\n // bottom-end / bottom-start reproduce the previous right/left alignment.\n placement: align === \"right\" ? \"bottom-end\" : \"bottom-start\",\n whileElementsMounted: autoUpdate,\n // offset(4) ≈ the previous mt-1; flip opens upward at the bottom edge and\n // shift slides the menu back on-screen at the horizontal edges.\n middleware: [offset(4), flip({ padding: 8 }), shift({ padding: 8 })],\n });\n\n const click = useClick(context);\n // useDismiss is React-tree aware: a press inside anything rendered through the\n // menu's `children` render-prop — including a Modal that portals to\n // document.body — propagates via React's synthetic events to the floating\n // element and counts as \"inside\", so it won't dismiss the menu (which would\n // unmount that child Modal). A Modal owned by a sibling/parent instead of this\n // menu correctly does dismiss the menu, which is harmless since closing the\n // menu does not unmount a Modal it doesn't own.\n const dismiss = useDismiss(context, { escapeKey: false });\n const role = useRole(context, { role: \"menu\" });\n const listNav = useListNavigation(context, {\n listRef,\n activeIndex,\n onNavigate: setActiveIndex,\n loop: true,\n });\n\n const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n click,\n dismiss,\n role,\n listNav,\n ]);\n\n const closeMenu = () => {\n setOpen(false);\n if (refs.domReference.current instanceof HTMLElement) refs.domReference.current.focus();\n };\n useEscapeLayer(open, closeMenu);\n\n return (\n <div className={cn(\"relative inline-flex\", className)}>\n {trigger ? (\n <span\n ref={refs.setReference}\n className=\"inline-flex\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n {...getReferenceProps()}\n >\n {trigger}\n </span>\n ) : (\n <Button\n ref={refs.setReference as React.Ref<HTMLButtonElement>}\n variant={variant}\n size={size}\n title={title}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n {...getReferenceProps()}\n >\n {icon && <Icon {...(typeof icon === \"string\" ? { name: icon } : { icon })} />}\n {label}\n {!hideChevron && <Icon icon={UiChevronDown} />}\n </Button>\n )}\n {open && (\n <FloatingPortal>\n <FloatingFocusManager context={context} modal={false}>\n <div\n ref={refs.setFloating}\n role=\"menu\"\n aria-label={menuLabel}\n style={{ ...floatingStyles, zIndex: floatingZ }}\n className={cn(\n \"min-w-[8rem] rounded-md border border-border bg-popover py-1 shadow-md\",\n menuClassName,\n )}\n {...getFloatingProps()}\n >\n {header != null && (\n <div className=\"border-b border-border px-3 py-1.5\">{header}</div>\n )}\n {children\n ? children(closeMenu)\n : items?.map((item, i) => (\n <button\n key={i}\n ref={(node) => {\n listRef.current[i] = node;\n }}\n type=\"button\"\n role=\"menuitem\"\n tabIndex={i === activeIndex ? 0 : -1}\n disabled={item.disabled}\n title={item.title}\n className=\"flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs text-popover-foreground hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50\"\n {...getItemProps({\n onClick: () => {\n item.onSelect();\n closeMenu();\n },\n onKeyDown: (event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n item.onSelect();\n closeMenu();\n }\n },\n })}\n >\n {item.icon && (\n <Icon\n {...(typeof item.icon === \"string\"\n ? { name: item.icon }\n : { icon: item.icon })}\n />\n )}\n {item.label}\n </button>\n ))}\n {footer != null && (\n <div className=\"border-t border-border px-3 py-1.5\">{footer}</div>\n )}\n </div>\n </FloatingFocusManager>\n </FloatingPortal>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ const jsxRuntime = require("react/jsx-runtime");
|
|
|
4
4
|
const react = require("react");
|
|
5
5
|
const reactDom = require("react-dom");
|
|
6
6
|
const utils = require("../lib/utils.cjs");
|
|
7
|
+
const modalStack = require("./modalStack.cjs");
|
|
7
8
|
const GAP_PX = 6;
|
|
8
9
|
const HOVER_CLOSE_DELAY_MS = 120;
|
|
9
10
|
function HoverCard({
|
|
@@ -17,6 +18,7 @@ function HoverCard({
|
|
|
17
18
|
}) {
|
|
18
19
|
const [open, setOpen] = react.useState(false);
|
|
19
20
|
const [position, setPosition] = react.useState(null);
|
|
21
|
+
const floatingZ = modalStack.useFloatingZIndex();
|
|
20
22
|
const triggerRef = react.useRef(null);
|
|
21
23
|
const cardRef = react.useRef(null);
|
|
22
24
|
const openTimerRef = react.useRef(null);
|
|
@@ -99,9 +101,12 @@ function HoverCard({
|
|
|
99
101
|
role: "tooltip",
|
|
100
102
|
onMouseEnter: cancelClose,
|
|
101
103
|
onMouseLeave: onLeave,
|
|
102
|
-
style:
|
|
104
|
+
style: {
|
|
105
|
+
...position ? { position: "fixed", top: position.top, left: position.left } : { position: "fixed", top: -9999, left: -9999, visibility: "hidden" },
|
|
106
|
+
zIndex: floatingZ
|
|
107
|
+
},
|
|
103
108
|
className: utils.cn(
|
|
104
|
-
"
|
|
109
|
+
"rounded-md border border-border bg-background px-2.5 py-1.5 text-[11px] shadow-lg",
|
|
105
110
|
"whitespace-nowrap",
|
|
106
111
|
cardClassName
|
|
107
112
|
),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HoverCard.cjs","sources":["../../src/overlay/HoverCard.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect, useRef, useState, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"../lib/utils\";\n\nexport type HoverCardPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport type HoverCardProps = {\n /** Trigger content that opens the card on hover/focus. */\n trigger: ReactNode;\n /** Floating card content. */\n children: ReactNode;\n /** Preferred placement around the trigger. */\n placement?: HoverCardPlacement;\n /** Open delay in milliseconds. */\n delay?: number;\n /** Show the directional arrow. */\n arrow?: boolean;\n /** Classes applied to the trigger wrapper. */\n className?: string;\n /** Classes applied to the floating card. */\n cardClassName?: string;\n};\n\nconst GAP_PX = 6;\n\ntype Position = { top: number; left: number };\n\n// Grace period that lets the cursor cross the gap from the trigger to the\n// portaled card without the card disappearing.\nconst HOVER_CLOSE_DELAY_MS = 120;\n\nexport function HoverCard({\n trigger,\n children,\n placement = \"top\",\n delay = 0,\n arrow = true,\n className,\n cardClassName,\n}: HoverCardProps) {\n const [open, setOpen] = useState(false);\n const [position, setPosition] = useState<Position | null>(null);\n const triggerRef = useRef<HTMLSpanElement | null>(null);\n const cardRef = useRef<HTMLDivElement | null>(null);\n const openTimerRef = useRef<number | null>(null);\n const closeTimerRef = useRef<number | null>(null);\n\n const cancelClose = () => {\n if (closeTimerRef.current !== null) {\n window.clearTimeout(closeTimerRef.current);\n closeTimerRef.current = null;\n }\n };\n\n function onEnter() {\n cancelClose();\n if (delay > 0) {\n if (openTimerRef.current !== null) window.clearTimeout(openTimerRef.current);\n openTimerRef.current = window.setTimeout(() => setOpen(true), delay);\n } else {\n setOpen(true);\n }\n }\n function onLeave() {\n if (openTimerRef.current !== null) {\n window.clearTimeout(openTimerRef.current);\n openTimerRef.current = null;\n }\n cancelClose();\n closeTimerRef.current = window.setTimeout(() => setOpen(false), HOVER_CLOSE_DELAY_MS);\n }\n\n // Recompute position whenever the card opens or the viewport scrolls/resizes\n // while it's open. We position via fixed coordinates so the card escapes\n // every overflow-hidden / transform ancestor — important when the trigger\n // sits inside a table row, dropdown, or modal that would otherwise clip it.\n useLayoutEffect(() => {\n if (!open) return;\n const update = () => {\n const trigger = triggerRef.current;\n const card = cardRef.current;\n if (!trigger || !card) return;\n const t = trigger.getBoundingClientRect();\n const c = card.getBoundingClientRect();\n let top = 0;\n let left = 0;\n switch (placement) {\n case \"top\":\n top = t.top - c.height - GAP_PX;\n left = t.left + t.width / 2 - c.width / 2;\n break;\n case \"bottom\":\n top = t.bottom + GAP_PX;\n left = t.left + t.width / 2 - c.width / 2;\n break;\n case \"left\":\n top = t.top + t.height / 2 - c.height / 2;\n left = t.left - c.width - GAP_PX;\n break;\n case \"right\":\n top = t.top + t.height / 2 - c.height / 2;\n left = t.right + GAP_PX;\n break;\n }\n // Clamp to viewport so cards near the edge don't overflow off-screen.\n const margin = 4;\n left = Math.min(Math.max(margin, left), window.innerWidth - c.width - margin);\n top = Math.min(Math.max(margin, top), window.innerHeight - c.height - margin);\n setPosition({ top, left });\n };\n update();\n window.addEventListener(\"scroll\", update, true);\n window.addEventListener(\"resize\", update);\n return () => {\n window.removeEventListener(\"scroll\", update, true);\n window.removeEventListener(\"resize\", update);\n };\n }, [open, placement, children]);\n\n useEffect(() => {\n return () => {\n if (openTimerRef.current !== null) window.clearTimeout(openTimerRef.current);\n if (closeTimerRef.current !== null) window.clearTimeout(closeTimerRef.current);\n };\n }, []);\n\n const card =\n open && typeof document !== \"undefined\"\n ? createPortal(\n <div\n ref={cardRef}\n role=\"tooltip\"\n onMouseEnter={cancelClose}\n onMouseLeave={onLeave}\n style={\n position\n ? { position: \"fixed\", top: position.top, left: position.left }\n : { position: \"fixed\", top: -9999, left: -9999, visibility: \"hidden\" }\n }\n className={cn(\n \"z-[9999] rounded-md border border-border bg-background px-2.5 py-1.5 text-[11px] shadow-lg\",\n \"whitespace-nowrap\",\n cardClassName,\n )}\n >\n {children}\n {arrow && position && <Arrow placement={placement} />}\n </div>,\n document.body,\n )\n : null;\n\n return (\n <span\n ref={triggerRef}\n className={cn(\"relative inline-flex items-center\", className)}\n onMouseEnter={onEnter}\n onMouseLeave={onLeave}\n onFocus={onEnter}\n onBlur={onLeave}\n >\n {trigger}\n {card}\n </span>\n );\n}\n\nfunction Arrow({ placement }: { placement: HoverCardPlacement }) {\n // The arrow is a small rotated square clipped to look like a notch on the\n // card edge that points back at the trigger. Drawn relative to the card.\n const base = \"absolute h-1.5 w-1.5 rotate-45 border bg-background border-border\";\n switch (placement) {\n case \"top\":\n return (\n <span\n aria-hidden\n className={cn(base, \"left-1/2 -translate-x-1/2 -bottom-0.5 border-l-0 border-t-0\")}\n />\n );\n case \"bottom\":\n return (\n <span\n aria-hidden\n className={cn(base, \"left-1/2 -translate-x-1/2 -top-0.5 border-r-0 border-b-0\")}\n />\n );\n case \"left\":\n return (\n <span\n aria-hidden\n className={cn(base, \"top-1/2 -translate-y-1/2 -right-0.5 border-l-0 border-b-0\")}\n />\n );\n case \"right\":\n return (\n <span\n aria-hidden\n className={cn(base, \"top-1/2 -translate-y-1/2 -left-0.5 border-r-0 border-t-0\")}\n />\n );\n }\n}\n"],"names":["useState","useRef","useLayoutEffect","trigger","card","useEffect","createPortal","jsxs","cn","jsx"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HoverCard.cjs","sources":["../../src/overlay/HoverCard.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect, useRef, useState, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"../lib/utils\";\nimport { useFloatingZIndex } from \"./modalStack\";\n\nexport type HoverCardPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport type HoverCardProps = {\n /** Trigger content that opens the card on hover/focus. */\n trigger: ReactNode;\n /** Floating card content. */\n children: ReactNode;\n /** Preferred placement around the trigger. */\n placement?: HoverCardPlacement;\n /** Open delay in milliseconds. */\n delay?: number;\n /** Show the directional arrow. */\n arrow?: boolean;\n /** Classes applied to the trigger wrapper. */\n className?: string;\n /** Classes applied to the floating card. */\n cardClassName?: string;\n};\n\nconst GAP_PX = 6;\n\ntype Position = { top: number; left: number };\n\n// Grace period that lets the cursor cross the gap from the trigger to the\n// portaled card without the card disappearing.\nconst HOVER_CLOSE_DELAY_MS = 120;\n\nexport function HoverCard({\n trigger,\n children,\n placement = \"top\",\n delay = 0,\n arrow = true,\n className,\n cardClassName,\n}: HoverCardProps) {\n const [open, setOpen] = useState(false);\n const [position, setPosition] = useState<Position | null>(null);\n const floatingZ = useFloatingZIndex();\n const triggerRef = useRef<HTMLSpanElement | null>(null);\n const cardRef = useRef<HTMLDivElement | null>(null);\n const openTimerRef = useRef<number | null>(null);\n const closeTimerRef = useRef<number | null>(null);\n\n const cancelClose = () => {\n if (closeTimerRef.current !== null) {\n window.clearTimeout(closeTimerRef.current);\n closeTimerRef.current = null;\n }\n };\n\n function onEnter() {\n cancelClose();\n if (delay > 0) {\n if (openTimerRef.current !== null) window.clearTimeout(openTimerRef.current);\n openTimerRef.current = window.setTimeout(() => setOpen(true), delay);\n } else {\n setOpen(true);\n }\n }\n function onLeave() {\n if (openTimerRef.current !== null) {\n window.clearTimeout(openTimerRef.current);\n openTimerRef.current = null;\n }\n cancelClose();\n closeTimerRef.current = window.setTimeout(() => setOpen(false), HOVER_CLOSE_DELAY_MS);\n }\n\n // Recompute position whenever the card opens or the viewport scrolls/resizes\n // while it's open. We position via fixed coordinates so the card escapes\n // every overflow-hidden / transform ancestor — important when the trigger\n // sits inside a table row, dropdown, or modal that would otherwise clip it.\n useLayoutEffect(() => {\n if (!open) return;\n const update = () => {\n const trigger = triggerRef.current;\n const card = cardRef.current;\n if (!trigger || !card) return;\n const t = trigger.getBoundingClientRect();\n const c = card.getBoundingClientRect();\n let top = 0;\n let left = 0;\n switch (placement) {\n case \"top\":\n top = t.top - c.height - GAP_PX;\n left = t.left + t.width / 2 - c.width / 2;\n break;\n case \"bottom\":\n top = t.bottom + GAP_PX;\n left = t.left + t.width / 2 - c.width / 2;\n break;\n case \"left\":\n top = t.top + t.height / 2 - c.height / 2;\n left = t.left - c.width - GAP_PX;\n break;\n case \"right\":\n top = t.top + t.height / 2 - c.height / 2;\n left = t.right + GAP_PX;\n break;\n }\n // Clamp to viewport so cards near the edge don't overflow off-screen.\n const margin = 4;\n left = Math.min(Math.max(margin, left), window.innerWidth - c.width - margin);\n top = Math.min(Math.max(margin, top), window.innerHeight - c.height - margin);\n setPosition({ top, left });\n };\n update();\n window.addEventListener(\"scroll\", update, true);\n window.addEventListener(\"resize\", update);\n return () => {\n window.removeEventListener(\"scroll\", update, true);\n window.removeEventListener(\"resize\", update);\n };\n }, [open, placement, children]);\n\n useEffect(() => {\n return () => {\n if (openTimerRef.current !== null) window.clearTimeout(openTimerRef.current);\n if (closeTimerRef.current !== null) window.clearTimeout(closeTimerRef.current);\n };\n }, []);\n\n const card =\n open && typeof document !== \"undefined\"\n ? createPortal(\n <div\n ref={cardRef}\n role=\"tooltip\"\n onMouseEnter={cancelClose}\n onMouseLeave={onLeave}\n style={{\n ...(position\n ? { position: \"fixed\", top: position.top, left: position.left }\n : { position: \"fixed\", top: -9999, left: -9999, visibility: \"hidden\" }),\n zIndex: floatingZ,\n }}\n className={cn(\n \"rounded-md border border-border bg-background px-2.5 py-1.5 text-[11px] shadow-lg\",\n \"whitespace-nowrap\",\n cardClassName,\n )}\n >\n {children}\n {arrow && position && <Arrow placement={placement} />}\n </div>,\n document.body,\n )\n : null;\n\n return (\n <span\n ref={triggerRef}\n className={cn(\"relative inline-flex items-center\", className)}\n onMouseEnter={onEnter}\n onMouseLeave={onLeave}\n onFocus={onEnter}\n onBlur={onLeave}\n >\n {trigger}\n {card}\n </span>\n );\n}\n\nfunction Arrow({ placement }: { placement: HoverCardPlacement }) {\n // The arrow is a small rotated square clipped to look like a notch on the\n // card edge that points back at the trigger. Drawn relative to the card.\n const base = \"absolute h-1.5 w-1.5 rotate-45 border bg-background border-border\";\n switch (placement) {\n case \"top\":\n return (\n <span\n aria-hidden\n className={cn(base, \"left-1/2 -translate-x-1/2 -bottom-0.5 border-l-0 border-t-0\")}\n />\n );\n case \"bottom\":\n return (\n <span\n aria-hidden\n className={cn(base, \"left-1/2 -translate-x-1/2 -top-0.5 border-r-0 border-b-0\")}\n />\n );\n case \"left\":\n return (\n <span\n aria-hidden\n className={cn(base, \"top-1/2 -translate-y-1/2 -right-0.5 border-l-0 border-b-0\")}\n />\n );\n case \"right\":\n return (\n <span\n aria-hidden\n className={cn(base, \"top-1/2 -translate-y-1/2 -left-0.5 border-r-0 border-t-0\")}\n />\n );\n }\n}\n"],"names":["useState","useFloatingZIndex","useRef","useLayoutEffect","trigger","card","useEffect","createPortal","jsxs","cn","jsx"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HoverCard.d.ts","sourceRoot":"","sources":["../../src/overlay/HoverCard.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAgD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"HoverCard.d.ts","sourceRoot":"","sources":["../../src/overlay/HoverCard.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAgD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAKrF,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAErE,MAAM,MAAM,cAAc,GAAG;IAC3B,0DAA0D;IAC1D,OAAO,EAAE,SAAS,CAAC;IACnB,6BAA6B;IAC7B,QAAQ,EAAE,SAAS,CAAC;IACpB,8CAA8C;IAC9C,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAUF,wBAAgB,SAAS,CAAC,EACxB,OAAO,EACP,QAAQ,EACR,SAAiB,EACjB,KAAS,EACT,KAAY,EACZ,SAAS,EACT,aAAa,GACd,EAAE,cAAc,2CAgIhB"}
|
|
@@ -2,6 +2,7 @@ import { jsxs, jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { useState, useRef, useLayoutEffect, useEffect } from "react";
|
|
3
3
|
import { createPortal } from "react-dom";
|
|
4
4
|
import { cn } from "../lib/utils.js";
|
|
5
|
+
import { useFloatingZIndex } from "./modalStack.js";
|
|
5
6
|
const GAP_PX = 6;
|
|
6
7
|
const HOVER_CLOSE_DELAY_MS = 120;
|
|
7
8
|
function HoverCard({
|
|
@@ -15,6 +16,7 @@ function HoverCard({
|
|
|
15
16
|
}) {
|
|
16
17
|
const [open, setOpen] = useState(false);
|
|
17
18
|
const [position, setPosition] = useState(null);
|
|
19
|
+
const floatingZ = useFloatingZIndex();
|
|
18
20
|
const triggerRef = useRef(null);
|
|
19
21
|
const cardRef = useRef(null);
|
|
20
22
|
const openTimerRef = useRef(null);
|
|
@@ -97,9 +99,12 @@ function HoverCard({
|
|
|
97
99
|
role: "tooltip",
|
|
98
100
|
onMouseEnter: cancelClose,
|
|
99
101
|
onMouseLeave: onLeave,
|
|
100
|
-
style:
|
|
102
|
+
style: {
|
|
103
|
+
...position ? { position: "fixed", top: position.top, left: position.left } : { position: "fixed", top: -9999, left: -9999, visibility: "hidden" },
|
|
104
|
+
zIndex: floatingZ
|
|
105
|
+
},
|
|
101
106
|
className: cn(
|
|
102
|
-
"
|
|
107
|
+
"rounded-md border border-border bg-background px-2.5 py-1.5 text-[11px] shadow-lg",
|
|
103
108
|
"whitespace-nowrap",
|
|
104
109
|
cardClassName
|
|
105
110
|
),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HoverCard.js","sources":["../../src/overlay/HoverCard.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect, useRef, useState, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"../lib/utils\";\n\nexport type HoverCardPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport type HoverCardProps = {\n /** Trigger content that opens the card on hover/focus. */\n trigger: ReactNode;\n /** Floating card content. */\n children: ReactNode;\n /** Preferred placement around the trigger. */\n placement?: HoverCardPlacement;\n /** Open delay in milliseconds. */\n delay?: number;\n /** Show the directional arrow. */\n arrow?: boolean;\n /** Classes applied to the trigger wrapper. */\n className?: string;\n /** Classes applied to the floating card. */\n cardClassName?: string;\n};\n\nconst GAP_PX = 6;\n\ntype Position = { top: number; left: number };\n\n// Grace period that lets the cursor cross the gap from the trigger to the\n// portaled card without the card disappearing.\nconst HOVER_CLOSE_DELAY_MS = 120;\n\nexport function HoverCard({\n trigger,\n children,\n placement = \"top\",\n delay = 0,\n arrow = true,\n className,\n cardClassName,\n}: HoverCardProps) {\n const [open, setOpen] = useState(false);\n const [position, setPosition] = useState<Position | null>(null);\n const triggerRef = useRef<HTMLSpanElement | null>(null);\n const cardRef = useRef<HTMLDivElement | null>(null);\n const openTimerRef = useRef<number | null>(null);\n const closeTimerRef = useRef<number | null>(null);\n\n const cancelClose = () => {\n if (closeTimerRef.current !== null) {\n window.clearTimeout(closeTimerRef.current);\n closeTimerRef.current = null;\n }\n };\n\n function onEnter() {\n cancelClose();\n if (delay > 0) {\n if (openTimerRef.current !== null) window.clearTimeout(openTimerRef.current);\n openTimerRef.current = window.setTimeout(() => setOpen(true), delay);\n } else {\n setOpen(true);\n }\n }\n function onLeave() {\n if (openTimerRef.current !== null) {\n window.clearTimeout(openTimerRef.current);\n openTimerRef.current = null;\n }\n cancelClose();\n closeTimerRef.current = window.setTimeout(() => setOpen(false), HOVER_CLOSE_DELAY_MS);\n }\n\n // Recompute position whenever the card opens or the viewport scrolls/resizes\n // while it's open. We position via fixed coordinates so the card escapes\n // every overflow-hidden / transform ancestor — important when the trigger\n // sits inside a table row, dropdown, or modal that would otherwise clip it.\n useLayoutEffect(() => {\n if (!open) return;\n const update = () => {\n const trigger = triggerRef.current;\n const card = cardRef.current;\n if (!trigger || !card) return;\n const t = trigger.getBoundingClientRect();\n const c = card.getBoundingClientRect();\n let top = 0;\n let left = 0;\n switch (placement) {\n case \"top\":\n top = t.top - c.height - GAP_PX;\n left = t.left + t.width / 2 - c.width / 2;\n break;\n case \"bottom\":\n top = t.bottom + GAP_PX;\n left = t.left + t.width / 2 - c.width / 2;\n break;\n case \"left\":\n top = t.top + t.height / 2 - c.height / 2;\n left = t.left - c.width - GAP_PX;\n break;\n case \"right\":\n top = t.top + t.height / 2 - c.height / 2;\n left = t.right + GAP_PX;\n break;\n }\n // Clamp to viewport so cards near the edge don't overflow off-screen.\n const margin = 4;\n left = Math.min(Math.max(margin, left), window.innerWidth - c.width - margin);\n top = Math.min(Math.max(margin, top), window.innerHeight - c.height - margin);\n setPosition({ top, left });\n };\n update();\n window.addEventListener(\"scroll\", update, true);\n window.addEventListener(\"resize\", update);\n return () => {\n window.removeEventListener(\"scroll\", update, true);\n window.removeEventListener(\"resize\", update);\n };\n }, [open, placement, children]);\n\n useEffect(() => {\n return () => {\n if (openTimerRef.current !== null) window.clearTimeout(openTimerRef.current);\n if (closeTimerRef.current !== null) window.clearTimeout(closeTimerRef.current);\n };\n }, []);\n\n const card =\n open && typeof document !== \"undefined\"\n ? createPortal(\n <div\n ref={cardRef}\n role=\"tooltip\"\n onMouseEnter={cancelClose}\n onMouseLeave={onLeave}\n style={\n position\n ? { position: \"fixed\", top: position.top, left: position.left }\n : { position: \"fixed\", top: -9999, left: -9999, visibility: \"hidden\" }\n }\n className={cn(\n \"z-[9999] rounded-md border border-border bg-background px-2.5 py-1.5 text-[11px] shadow-lg\",\n \"whitespace-nowrap\",\n cardClassName,\n )}\n >\n {children}\n {arrow && position && <Arrow placement={placement} />}\n </div>,\n document.body,\n )\n : null;\n\n return (\n <span\n ref={triggerRef}\n className={cn(\"relative inline-flex items-center\", className)}\n onMouseEnter={onEnter}\n onMouseLeave={onLeave}\n onFocus={onEnter}\n onBlur={onLeave}\n >\n {trigger}\n {card}\n </span>\n );\n}\n\nfunction Arrow({ placement }: { placement: HoverCardPlacement }) {\n // The arrow is a small rotated square clipped to look like a notch on the\n // card edge that points back at the trigger. Drawn relative to the card.\n const base = \"absolute h-1.5 w-1.5 rotate-45 border bg-background border-border\";\n switch (placement) {\n case \"top\":\n return (\n <span\n aria-hidden\n className={cn(base, \"left-1/2 -translate-x-1/2 -bottom-0.5 border-l-0 border-t-0\")}\n />\n );\n case \"bottom\":\n return (\n <span\n aria-hidden\n className={cn(base, \"left-1/2 -translate-x-1/2 -top-0.5 border-r-0 border-b-0\")}\n />\n );\n case \"left\":\n return (\n <span\n aria-hidden\n className={cn(base, \"top-1/2 -translate-y-1/2 -right-0.5 border-l-0 border-b-0\")}\n />\n );\n case \"right\":\n return (\n <span\n aria-hidden\n className={cn(base, \"top-1/2 -translate-y-1/2 -left-0.5 border-r-0 border-t-0\")}\n />\n );\n }\n}\n"],"names":["trigger","card"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HoverCard.js","sources":["../../src/overlay/HoverCard.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect, useRef, useState, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"../lib/utils\";\nimport { useFloatingZIndex } from \"./modalStack\";\n\nexport type HoverCardPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport type HoverCardProps = {\n /** Trigger content that opens the card on hover/focus. */\n trigger: ReactNode;\n /** Floating card content. */\n children: ReactNode;\n /** Preferred placement around the trigger. */\n placement?: HoverCardPlacement;\n /** Open delay in milliseconds. */\n delay?: number;\n /** Show the directional arrow. */\n arrow?: boolean;\n /** Classes applied to the trigger wrapper. */\n className?: string;\n /** Classes applied to the floating card. */\n cardClassName?: string;\n};\n\nconst GAP_PX = 6;\n\ntype Position = { top: number; left: number };\n\n// Grace period that lets the cursor cross the gap from the trigger to the\n// portaled card without the card disappearing.\nconst HOVER_CLOSE_DELAY_MS = 120;\n\nexport function HoverCard({\n trigger,\n children,\n placement = \"top\",\n delay = 0,\n arrow = true,\n className,\n cardClassName,\n}: HoverCardProps) {\n const [open, setOpen] = useState(false);\n const [position, setPosition] = useState<Position | null>(null);\n const floatingZ = useFloatingZIndex();\n const triggerRef = useRef<HTMLSpanElement | null>(null);\n const cardRef = useRef<HTMLDivElement | null>(null);\n const openTimerRef = useRef<number | null>(null);\n const closeTimerRef = useRef<number | null>(null);\n\n const cancelClose = () => {\n if (closeTimerRef.current !== null) {\n window.clearTimeout(closeTimerRef.current);\n closeTimerRef.current = null;\n }\n };\n\n function onEnter() {\n cancelClose();\n if (delay > 0) {\n if (openTimerRef.current !== null) window.clearTimeout(openTimerRef.current);\n openTimerRef.current = window.setTimeout(() => setOpen(true), delay);\n } else {\n setOpen(true);\n }\n }\n function onLeave() {\n if (openTimerRef.current !== null) {\n window.clearTimeout(openTimerRef.current);\n openTimerRef.current = null;\n }\n cancelClose();\n closeTimerRef.current = window.setTimeout(() => setOpen(false), HOVER_CLOSE_DELAY_MS);\n }\n\n // Recompute position whenever the card opens or the viewport scrolls/resizes\n // while it's open. We position via fixed coordinates so the card escapes\n // every overflow-hidden / transform ancestor — important when the trigger\n // sits inside a table row, dropdown, or modal that would otherwise clip it.\n useLayoutEffect(() => {\n if (!open) return;\n const update = () => {\n const trigger = triggerRef.current;\n const card = cardRef.current;\n if (!trigger || !card) return;\n const t = trigger.getBoundingClientRect();\n const c = card.getBoundingClientRect();\n let top = 0;\n let left = 0;\n switch (placement) {\n case \"top\":\n top = t.top - c.height - GAP_PX;\n left = t.left + t.width / 2 - c.width / 2;\n break;\n case \"bottom\":\n top = t.bottom + GAP_PX;\n left = t.left + t.width / 2 - c.width / 2;\n break;\n case \"left\":\n top = t.top + t.height / 2 - c.height / 2;\n left = t.left - c.width - GAP_PX;\n break;\n case \"right\":\n top = t.top + t.height / 2 - c.height / 2;\n left = t.right + GAP_PX;\n break;\n }\n // Clamp to viewport so cards near the edge don't overflow off-screen.\n const margin = 4;\n left = Math.min(Math.max(margin, left), window.innerWidth - c.width - margin);\n top = Math.min(Math.max(margin, top), window.innerHeight - c.height - margin);\n setPosition({ top, left });\n };\n update();\n window.addEventListener(\"scroll\", update, true);\n window.addEventListener(\"resize\", update);\n return () => {\n window.removeEventListener(\"scroll\", update, true);\n window.removeEventListener(\"resize\", update);\n };\n }, [open, placement, children]);\n\n useEffect(() => {\n return () => {\n if (openTimerRef.current !== null) window.clearTimeout(openTimerRef.current);\n if (closeTimerRef.current !== null) window.clearTimeout(closeTimerRef.current);\n };\n }, []);\n\n const card =\n open && typeof document !== \"undefined\"\n ? createPortal(\n <div\n ref={cardRef}\n role=\"tooltip\"\n onMouseEnter={cancelClose}\n onMouseLeave={onLeave}\n style={{\n ...(position\n ? { position: \"fixed\", top: position.top, left: position.left }\n : { position: \"fixed\", top: -9999, left: -9999, visibility: \"hidden\" }),\n zIndex: floatingZ,\n }}\n className={cn(\n \"rounded-md border border-border bg-background px-2.5 py-1.5 text-[11px] shadow-lg\",\n \"whitespace-nowrap\",\n cardClassName,\n )}\n >\n {children}\n {arrow && position && <Arrow placement={placement} />}\n </div>,\n document.body,\n )\n : null;\n\n return (\n <span\n ref={triggerRef}\n className={cn(\"relative inline-flex items-center\", className)}\n onMouseEnter={onEnter}\n onMouseLeave={onLeave}\n onFocus={onEnter}\n onBlur={onLeave}\n >\n {trigger}\n {card}\n </span>\n );\n}\n\nfunction Arrow({ placement }: { placement: HoverCardPlacement }) {\n // The arrow is a small rotated square clipped to look like a notch on the\n // card edge that points back at the trigger. Drawn relative to the card.\n const base = \"absolute h-1.5 w-1.5 rotate-45 border bg-background border-border\";\n switch (placement) {\n case \"top\":\n return (\n <span\n aria-hidden\n className={cn(base, \"left-1/2 -translate-x-1/2 -bottom-0.5 border-l-0 border-t-0\")}\n />\n );\n case \"bottom\":\n return (\n <span\n aria-hidden\n className={cn(base, \"left-1/2 -translate-x-1/2 -top-0.5 border-r-0 border-b-0\")}\n />\n );\n case \"left\":\n return (\n <span\n aria-hidden\n className={cn(base, \"top-1/2 -translate-y-1/2 -right-0.5 border-l-0 border-b-0\")}\n />\n );\n case \"right\":\n return (\n <span\n aria-hidden\n className={cn(base, \"top-1/2 -translate-y-1/2 -left-0.5 border-r-0 border-t-0\")}\n />\n );\n }\n}\n"],"names":["trigger","card"],"mappings":"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"}
|
package/dist/overlay/Modal.cjs
CHANGED
|
@@ -7,14 +7,15 @@ const utils = require("../lib/utils.cjs");
|
|
|
7
7
|
const Icon = require("../data/Icon.cjs");
|
|
8
8
|
const button = require("../components/button.cjs");
|
|
9
9
|
const modalStack = require("./modalStack.cjs");
|
|
10
|
+
const zIndex = require("./zIndex.cjs");
|
|
10
11
|
const UiFullscreen = require("../icons/components/UiFullscreen.cjs");
|
|
11
12
|
const UiClose = require("../icons/components/UiClose.cjs");
|
|
12
13
|
const sizeClass = {
|
|
13
|
-
sm: "max-w-sm
|
|
14
|
-
md: "max-w-md
|
|
15
|
-
lg: "max-w-2xl
|
|
16
|
-
xl: "max-w-4xl
|
|
17
|
-
full: "max-w-[95vw]
|
|
14
|
+
sm: "max-w-sm",
|
|
15
|
+
md: "max-w-md",
|
|
16
|
+
lg: "max-w-2xl",
|
|
17
|
+
xl: "max-w-4xl",
|
|
18
|
+
full: "max-w-[95vw]"
|
|
18
19
|
};
|
|
19
20
|
const DEFAULT_CONFIRM = {
|
|
20
21
|
title: "Discard changes?",
|
|
@@ -39,7 +40,7 @@ function Modal({
|
|
|
39
40
|
children
|
|
40
41
|
}) {
|
|
41
42
|
const dialogRef = react.useRef(null);
|
|
42
|
-
const {
|
|
43
|
+
const { depth } = modalStack.useModalStack(open);
|
|
43
44
|
const [expanded, setExpanded] = react.useState(false);
|
|
44
45
|
const [confirming, setConfirming] = react.useState(false);
|
|
45
46
|
const requestClose = () => {
|
|
@@ -55,16 +56,14 @@ function Modal({
|
|
|
55
56
|
react.useEffect(() => {
|
|
56
57
|
if (!confirmClose) setConfirming(false);
|
|
57
58
|
}, [confirmClose]);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (e.key !== "Escape") return;
|
|
59
|
+
modalStack.useEscapeLayer(
|
|
60
|
+
open,
|
|
61
|
+
() => {
|
|
62
62
|
if (confirming) setConfirming(false);
|
|
63
63
|
else requestClose();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}, [open, closeOnEsc, isTop, confirming, confirmClose, onClose]);
|
|
64
|
+
},
|
|
65
|
+
closeOnEsc
|
|
66
|
+
);
|
|
68
67
|
react.useEffect(() => {
|
|
69
68
|
var _a;
|
|
70
69
|
if (!open) return;
|
|
@@ -80,11 +79,11 @@ function Modal({
|
|
|
80
79
|
"div",
|
|
81
80
|
{
|
|
82
81
|
className: utils.cn(
|
|
83
|
-
"fixed inset-0 flex items-center justify-center",
|
|
82
|
+
"fixed inset-0 flex items-center justify-center p-density-2 sm:p-density-4",
|
|
84
83
|
// Nested modals dim less so the dialog they opened over stays visible.
|
|
85
84
|
depth === 0 ? "bg-black/40" : "bg-black/20"
|
|
86
85
|
),
|
|
87
|
-
style: { zIndex:
|
|
86
|
+
style: { zIndex: zIndex.zIndex.modal + depth * zIndex.zIndex.modalStep },
|
|
88
87
|
onClick: closeOnBackdrop ? requestClose : void 0,
|
|
89
88
|
role: "presentation",
|
|
90
89
|
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -96,10 +95,14 @@ function Modal({
|
|
|
96
95
|
"aria-modal": "true",
|
|
97
96
|
"aria-label": typeof title === "string" ? title : void 0,
|
|
98
97
|
className: utils.cn(
|
|
99
|
-
"relative
|
|
98
|
+
"relative flex w-full flex-col rounded-lg border border-border bg-background shadow-xl",
|
|
100
99
|
expanded ? sizeClass.full : sizeClass[size],
|
|
101
100
|
className
|
|
102
101
|
),
|
|
102
|
+
style: {
|
|
103
|
+
maxHeight: "calc(100dvh - 2rem)",
|
|
104
|
+
...expanded ? { height: "calc(100dvh - 2rem)" } : {}
|
|
105
|
+
},
|
|
103
106
|
onClick: (e) => e.stopPropagation(),
|
|
104
107
|
children: [
|
|
105
108
|
(title || subtitle || headerSlot || expandable || !hideClose) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-density-4 py-density-3 border-b border-border", children: [
|