package webui import "kjol/vdom" // Port of web/kit/Menu.tsx. // // NOTE: floating-ui positioning (FloatingRoot / FloatingTrigger / FloatingContent // / useFloatingContext / useFloatingHover) is dropped. Menu is a `relative` // wrapper and MenuContent is an `absolute` dropdown positioned with static // Tailwind utilities chosen from MenuPlacement; there is no viewport-aware // collision detection. // NOTE: Solid's MenuContext (closeMenu / openOnHover / cancelParentClose) is // dropped. Open state is a passed bool: MenuContent and Submenu render only when // open, and the caller toggles it via MenuTrigger's onToggle. Item clicks no // longer auto-close the menu (closeOnClick / closeMenu removed) — the caller // closes it from its own click handler. // NOTE: hover-open/hover-close timers, the MenuTrigger render-prop (isOpen state) // and asChild, Submenu's getBoundingClientRect positioning with scroll/resize // listeners, and MenuItem's Enter/Space keydown handler (the runtime's Event // exposes no key) are all dropped. // NOTE: the imported "Placement" type is renamed MenuPlacement to avoid colliding // with a future Floating port. const menuCls = "bg-white rounded-default shadow-lg border border-neutral-200 p-1.5 min-w-48 max-h-96 overflow-y-auto" const menuItemCls = "flex items-center gap-2 w-full py-1.5 px-2 rounded-default text-sm text-left text-neutral-700 bg-transparent border-0 cursor-pointer hover:bg-neutral-100 hover:text-neutral-900 focus:bg-neutral-100 focus:outline-hidden disabled:opacity-50 disabled:cursor-not-allowed" const menuDividerCls = "my-1 -mx-1.5 border-0 border-t border-neutral-200" const menuSectionCls = "pt-1.5 pb-0.5 px-2 text-[10px] font-semibold text-neutral-400 uppercase tracking-wide text-left" const menuSubmenuTriggerCls = "flex items-center justify-between gap-2 w-full py-1.5 px-2 rounded-default text-sm text-left text-neutral-700 cursor-pointer hover:bg-neutral-100 hover:text-neutral-900" // MenuPlacement selects where MenuContent is positioned relative to its trigger. type MenuPlacement string const ( MenuPlacementBottomStart MenuPlacement = "bottom-start" MenuPlacementBottomEnd MenuPlacement = "bottom-end" MenuPlacementTopStart MenuPlacement = "top-start" MenuPlacementTopEnd MenuPlacement = "top-end" MenuPlacementLeftStart MenuPlacement = "left-start" MenuPlacementRightStart MenuPlacement = "right-start" ) // menuPlacementClasses maps a placement to the static absolute-position utilities // (the ~4px offset becomes the mt-1/mb-1/ml-1/mr-1 margin). var menuPlacementClasses = map[MenuPlacement]string{ MenuPlacementBottomStart: "top-full left-0 mt-1", MenuPlacementBottomEnd: "top-full right-0 mt-1", MenuPlacementTopStart: "bottom-full left-0 mb-1", MenuPlacementTopEnd: "bottom-full right-0 mb-1", MenuPlacementLeftStart: "right-full top-0 mr-1", MenuPlacementRightStart: "left-full top-0 ml-1", } // Menu is the positioning context: a relative wrapper holding a MenuTrigger and a // MenuContent. func Menu(class string, children ...*vdom.VNode) *vdom.VNode { return vdom.El("div", kids([]vdom.Mod{vdom.Attr("class", cx("relative inline-block", class))}, children)...) } // MenuTrigger wraps the clickable element that toggles the menu open/closed. func MenuTrigger(onToggle func(), class string, children ...*vdom.VNode) *vdom.VNode { mods := []vdom.Mod{ vdom.Attr("class", class), vdom.Attr("aria-haspopup", "menu"), } if onToggle != nil { mods = append(mods, vdom.On(vdom.EVENT_CLICK, onToggle)) } return vdom.El("div", kids(mods, children)...) } // MenuContent is the dropdown panel; it renders only when open, positioned with // static Tailwind utilities for the given placement. func MenuContent(open bool, placement MenuPlacement, class string, children ...*vdom.VNode) *vdom.VNode { if !open { return nil } pos := menuPlacementClasses[placement] if pos == "" { pos = menuPlacementClasses[MenuPlacementBottomStart] } mods := []vdom.Mod{ vdom.Attr("class", cx("absolute z-50", pos, menuCls, class)), vdom.Attr("role", "menu"), } return vdom.El("div", kids(mods, children)...) } // MenuItemProps configures MenuItem. type MenuItemProps struct { Icon string Disabled bool OnClick func() Class string } // MenuItem is a