import * as React from "react" import { ScrollView, View } from "@tarojs/components" import { Check, ChevronRight } from "lucide-react-taro" import { cn } from "@/lib/utils" import { isH5 } from "@/lib/platform" import { computePosition, getRectById } from "@/lib/measure" import { Portal } from "@/components/ui/portal" const DropdownMenuContext = React.createContext<{ open?: boolean onOpenChange?: (open: boolean) => void triggerId: string } | null>(null) interface DropdownMenuProps { children: React.ReactNode open?: boolean defaultOpen?: boolean onOpenChange?: (open: boolean) => void } const DropdownMenu = ({ open: openProp, defaultOpen, onOpenChange, children }: DropdownMenuProps) => { const baseIdRef = React.useRef(`dropdown-menu-${Math.random().toString(36).slice(2, 10)}`) const [openState, setOpenState] = React.useState(defaultOpen || false) const open = openProp !== undefined ? openProp : openState const handleOpenChange = (newOpen: boolean) => { if (openProp === undefined) { setOpenState(newOpen) } onOpenChange?.(newOpen) } return ( {children} ) } const DropdownMenuTrigger = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, children, onClick, ...props }, ref) => { const context = React.useContext(DropdownMenuContext) return ( { e.stopPropagation() context?.onOpenChange?.(!context.open) onClick?.(e) }} > {children} ) }) DropdownMenuTrigger.displayName = "DropdownMenuTrigger" const DropdownMenuGroup = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) DropdownMenuGroup.displayName = "DropdownMenuGroup" const DropdownMenuPortal = ({ children }: { children: React.ReactNode }) => { return <>{children} } const DropdownMenuRadioGroupContext = React.createContext<{ value?: string onValueChange?: (value: string) => void } | null>(null) interface DropdownMenuRadioGroupProps extends React.ComponentPropsWithoutRef { value?: string defaultValue?: string onValueChange?: (value: string) => void } const DropdownMenuRadioGroup = React.forwardRef< React.ElementRef, DropdownMenuRadioGroupProps >(({ value: valueProp, defaultValue, onValueChange, ...props }, ref) => { const [valueState, setValueState] = React.useState(defaultValue) const value = valueProp !== undefined ? valueProp : valueState const handleValueChange = (next: string) => { if (valueProp === undefined) { setValueState(next) } onValueChange?.(next) } return ( ) }) DropdownMenuRadioGroup.displayName = "DropdownMenuRadioGroup" const DropdownMenuSubContext = React.createContext<{ open?: boolean onOpenChange?: (open: boolean) => void triggerId: string } | null>(null) interface DropdownMenuSubProps { children: React.ReactNode open?: boolean defaultOpen?: boolean onOpenChange?: (open: boolean) => void } const DropdownMenuSub = ({ open: openProp, defaultOpen, onOpenChange, children }: DropdownMenuSubProps) => { const parent = React.useContext(DropdownMenuContext) const baseIdRef = React.useRef(`dropdown-menu-sub-${Math.random().toString(36).slice(2, 10)}`) const [openState, setOpenState] = React.useState(defaultOpen || false) const open = openProp !== undefined ? openProp : openState const handleOpenChange = (newOpen: boolean) => { if (openProp === undefined) { setOpenState(newOpen) } onOpenChange?.(newOpen) } React.useEffect(() => { if (parent?.open === false && open) { handleOpenChange(false) } }, [open, parent?.open]) return ( {children} ) } interface DropdownMenuContentProps extends React.ComponentPropsWithoutRef { align?: "start" | "center" | "end" side?: "top" | "bottom" | "left" | "right" sideOffset?: number } const DropdownMenuContent = React.forwardRef< React.ElementRef, DropdownMenuContentProps >(({ className, align = "start", side = "bottom", sideOffset = 4, children, ...props }, ref) => { const context = React.useContext(DropdownMenuContext) const contentId = React.useRef(`dropdown-menu-content-${Math.random().toString(36).slice(2, 10)}`) const [position, setPosition] = React.useState<{ left: number; top: number } | null>(null) React.useEffect(() => { if (!context?.open) { setPosition(null) return } let cancelled = false const compute = async () => { if (!context?.triggerId) return const [triggerRect, contentRect] = await Promise.all([ getRectById(context.triggerId), getRectById(contentId.current), ]) if (cancelled) return if (!triggerRect?.width || !contentRect?.width) return setPosition( computePosition({ triggerRect, contentRect, align, side, sideOffset, }) ) } const raf = (() => { if (typeof requestAnimationFrame !== "undefined") { return requestAnimationFrame(() => compute()) } return setTimeout(() => compute(), 0) as unknown as number })() return () => { cancelled = true if (typeof cancelAnimationFrame !== "undefined") { cancelAnimationFrame(raf) } else { clearTimeout(raf) } } }, [align, context?.open, context?.triggerId, side, sideOffset]) React.useEffect(() => { if (!context?.open) return if (!isH5() || typeof document === "undefined") return const onKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") { context?.onOpenChange?.(false) } } document.addEventListener("keydown", onKeyDown) return () => document.removeEventListener("keydown", onKeyDown) }, [context?.open]) if (!context?.open) return null const baseClassName = "fixed z-50 min-w-32 overflow-hidden rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground ring-opacity-10 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2" const contentStyle = position ? ({ left: position.left, top: position.top } as React.CSSProperties) : ({ left: 0, top: 0, opacity: 0, pointerEvents: "none", } as React.CSSProperties) return ( context.onOpenChange?.(false)} /> e.stopPropagation()} > {children} ) }) DropdownMenuContent.displayName = "DropdownMenuContent" const DropdownMenuItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { inset?: boolean variant?: "default" | "destructive" disabled?: boolean closeOnSelect?: boolean } >(({ className, inset, variant = "default", disabled, closeOnSelect = true, onClick, ...props }, ref) => { const context = React.useContext(DropdownMenuContext) return ( { if (disabled) return onClick?.(e) if (closeOnSelect) context?.onOpenChange?.(false) }} /> ) }) DropdownMenuItem.displayName = "DropdownMenuItem" const DropdownMenuCheckboxItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { checked?: boolean inset?: boolean disabled?: boolean closeOnSelect?: boolean } >(({ className, children, checked, inset, disabled, closeOnSelect = false, onClick, ...props }, ref) => { const context = React.useContext(DropdownMenuContext) return ( { if (disabled) return onClick?.(e) if (closeOnSelect) context?.onOpenChange?.(false) }} > {checked && } {children} ) }) DropdownMenuCheckboxItem.displayName = "DropdownMenuCheckboxItem" const DropdownMenuRadioItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { value: string checked?: boolean inset?: boolean disabled?: boolean closeOnSelect?: boolean } >(({ className, children, value, checked: checkedProp, inset, disabled, closeOnSelect = false, onClick, ...props }, ref) => { const context = React.useContext(DropdownMenuContext) const group = React.useContext(DropdownMenuRadioGroupContext) const checked = checkedProp !== undefined ? checkedProp : group?.value === value return ( { if (disabled) return group?.onValueChange?.(value) onClick?.(e) if (closeOnSelect) context?.onOpenChange?.(false) }} > {checked && } {children} ) }) DropdownMenuRadioItem.displayName = "DropdownMenuRadioItem" const DropdownMenuLabel = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { inset?: boolean } >(({ className, inset, ...props }, ref) => ( )) DropdownMenuLabel.displayName = "DropdownMenuLabel" const DropdownMenuSeparator = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) DropdownMenuSeparator.displayName = "DropdownMenuSeparator" const DropdownMenuShortcut = ({ className, ...props }: React.ComponentPropsWithoutRef) => { return ( ) } DropdownMenuShortcut.displayName = "DropdownMenuShortcut" const DropdownMenuSubTrigger = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { inset?: boolean disabled?: boolean } >(({ className, inset, disabled, children, onClick, ...props }, ref) => { const subContext = React.useContext(DropdownMenuSubContext) return ( { e.stopPropagation() if (disabled) return subContext?.onOpenChange?.(!subContext.open) onClick?.(e) }} > {children} ) }) DropdownMenuSubTrigger.displayName = "DropdownMenuSubTrigger" const DropdownMenuSubContent = React.forwardRef< React.ElementRef, DropdownMenuContentProps >(({ className, align = "start", side = "right", sideOffset = 4, children, ...props }, ref) => { const parent = React.useContext(DropdownMenuContext) const subContext = React.useContext(DropdownMenuSubContext) const contentId = React.useRef(`dropdown-menu-sub-content-${Math.random().toString(36).slice(2, 10)}`) const [position, setPosition] = React.useState<{ left: number; top: number } | null>(null) React.useEffect(() => { if (!parent?.open || !subContext?.open) { setPosition(null) return } let cancelled = false const compute = async () => { if (!subContext?.triggerId) return const [triggerRect, contentRect] = await Promise.all([ getRectById(subContext.triggerId), getRectById(contentId.current), ]) if (cancelled) return if (!triggerRect?.width || !contentRect?.width) return setPosition( computePosition({ triggerRect, contentRect, align, side, sideOffset, }) ) } const raf = (() => { if (typeof requestAnimationFrame !== "undefined") { return requestAnimationFrame(() => compute()) } return setTimeout(() => compute(), 0) as unknown as number })() return () => { cancelled = true if (typeof cancelAnimationFrame !== "undefined") { cancelAnimationFrame(raf) } else { clearTimeout(raf) } } }, [align, parent?.open, side, sideOffset, subContext?.open, subContext?.triggerId]) if (!parent?.open || !subContext?.open) return null const baseClassName = "fixed z-50 min-w-[96px] overflow-hidden rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground ring-opacity-10 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2" const contentStyle = position ? ({ left: position.left, top: position.top } as React.CSSProperties) : ({ left: 0, top: 0, opacity: 0, pointerEvents: "none", } as React.CSSProperties) return ( e.stopPropagation()} > {children} ) }) DropdownMenuSubContent.displayName = "DropdownMenuSubContent" export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuGroup, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup, }