import * as React from "react" import { View, ScrollView } 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 MenubarContext = React.createContext<{ openMenu?: string setOpenMenu?: (id: string | undefined) => void } | null>(null) const Menubar = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => { const [openMenu, setOpenMenu] = React.useState() return ( ) }) Menubar.displayName = "Menubar" const MenubarMenuContext = React.createContext<{ id: string open: boolean onOpenChange: (open: boolean) => void triggerId: string } | null>(null) let menubarMenuIdCounter = 0 const MenubarMenu = ({ children }: { children: React.ReactNode }) => { const id = React.useMemo(() => `menubar-menu-${menubarMenuIdCounter++}`, []) const triggerId = React.useMemo(() => `${id}-trigger`, [id]) const context = React.useContext(MenubarContext) const open = context?.openMenu === id const onOpenChange = (isOpen: boolean) => { if (isOpen) { context?.setOpenMenu?.(id) } else { context?.setOpenMenu?.(undefined) } } return ( {children} ) } const MenubarTrigger = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => { const context = React.useContext(MenubarMenuContext) return ( context?.onOpenChange(!context.open)} {...props} /> ) }) MenubarTrigger.displayName = "MenubarTrigger" const MenubarPortal = ({ children }: { children: React.ReactNode }) => { return <>{children} } const MenubarContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { align?: "start" | "center" | "end" side?: "top" | "bottom" | "left" | "right" sideOffset?: number } >(({ className, align = "start", side = "bottom", sideOffset = 4, children, ...props }, ref) => { const context = React.useContext(MenubarMenuContext) const contentId = React.useRef(`menubar-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()} {...props} > {children} ) }) MenubarContent.displayName = "MenubarContent" const MenubarItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { inset?: boolean variant?: "default" | "destructive" disabled?: boolean closeOnSelect?: boolean } >(({ className, inset, variant = "default", disabled, closeOnSelect = true, children, onClick, ...props }, ref) => { const context = React.useContext(MenubarMenuContext) return ( { if (disabled) return onClick?.(e) if (closeOnSelect) context?.onOpenChange(false) }} {...props} > {children} ) }) MenubarItem.displayName = "MenubarItem" const MenubarCheckboxItem = 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(MenubarMenuContext) return ( { if (disabled) return onClick?.(e) if (closeOnSelect) context?.onOpenChange(false) }} {...props} > {checked && } {children} ) }) MenubarCheckboxItem.displayName = "MenubarCheckboxItem" const MenubarRadioGroupContext = React.createContext<{ value?: string onValueChange?: (value: string) => void } | null>(null) interface MenubarRadioGroupProps extends React.ComponentPropsWithoutRef { value?: string defaultValue?: string onValueChange?: (value: string) => void } const MenubarRadioGroup = React.forwardRef< React.ElementRef, MenubarRadioGroupProps >(({ 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 ( ) }) MenubarRadioGroup.displayName = "MenubarRadioGroup" const MenubarRadioItem = 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(MenubarMenuContext) const group = React.useContext(MenubarRadioGroupContext) const checked = checkedProp !== undefined ? checkedProp : group?.value === value return ( { if (disabled) return group?.onValueChange?.(value) onClick?.(e) if (closeOnSelect) context?.onOpenChange(false) }} {...props} > {checked && } {children} ) }) MenubarRadioItem.displayName = "MenubarRadioItem" const MenubarLabel = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { inset?: boolean } >(({ className, inset, ...props }, ref) => ( )) MenubarLabel.displayName = "MenubarLabel" const MenubarSeparator = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) MenubarSeparator.displayName = "MenubarSeparator" const MenubarShortcut = ({ className, ...props }: React.ComponentPropsWithoutRef) => { return ( ) } MenubarShortcut.displayName = "MenubarShortcut" const MenubarGroup = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) MenubarGroup.displayName = "MenubarGroup" const MenubarSubContext = React.createContext<{ open?: boolean onOpenChange?: (open: boolean) => void triggerId: string } | null>(null) interface MenubarSubProps { children: React.ReactNode open?: boolean defaultOpen?: boolean onOpenChange?: (open: boolean) => void } const MenubarSub = ({ open: openProp, defaultOpen, onOpenChange, children }: MenubarSubProps) => { const parent = React.useContext(MenubarMenuContext) const baseIdRef = React.useRef(`menubar-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} ) } const MenubarSubTrigger = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { inset?: boolean; disabled?: boolean } >(({ className, inset, disabled, children, onClick, ...props }, ref) => { const subContext = React.useContext(MenubarSubContext) return ( { e.stopPropagation() if (disabled) return subContext?.onOpenChange?.(!subContext.open) onClick?.(e) }} > {children} ) }) MenubarSubTrigger.displayName = "MenubarSubTrigger" const MenubarSubContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { align?: "start" | "center" | "end" side?: "top" | "bottom" | "left" | "right" sideOffset?: number } >(({ className, align = "start", side = "right", sideOffset = 4, children, ...props }, ref) => { const parent = React.useContext(MenubarMenuContext) const subContext = React.useContext(MenubarSubContext) const contentId = React.useRef(`menubar-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} ) }) MenubarSubContent.displayName = "MenubarSubContent" export { Menubar, MenubarMenu, MenubarTrigger, MenubarContent, MenubarItem, MenubarSeparator, MenubarLabel, MenubarCheckboxItem, MenubarRadioGroup, MenubarRadioItem, MenubarPortal, MenubarSubContent, MenubarSubTrigger, MenubarGroup, MenubarSub, MenubarShortcut, }