UNPKG

@yamada-ui/menu

Version:

Yamada UI menu component

271 lines (269 loc) • 7.94 kB
"use client" import { UpstreamMenuItemProvider, useMenu, useMenuDescendant, useUpstreamMenuItem } from "./chunk-IWH3B4PS.mjs"; // src/menu-item.tsx import { forwardRef, ui } from "@yamada-ui/core"; import { useClickable } from "@yamada-ui/use-clickable"; import { cx, dataAttr, funcAll, handlerAll, isActiveElement, isHTMLElement, mergeRefs, useUpdateEffect } from "@yamada-ui/utils"; import { useCallback, useId, useRef, useState } from "react"; import { jsx, jsxs } from "react/jsx-runtime"; var isTargetMenuItem = (target) => { var _a; return isHTMLElement(target) && !!((_a = target.getAttribute("role")) == null ? void 0 : _a.startsWith("menuitem")); }; var MenuItem = forwardRef( ({ id, className, children, closeOnSelect: customCloseOnSelect, command, isDisabled, disabled = isDisabled, isFocusable, focusable = isFocusable, icon, ...props }, ref) => { var _a, _b; const { closeOnSelect, focusedIndex, menuRef, nested, open, requestAnimationFrameId, setFocusedIndex, styles, onClose, onUpstreamClose } = useMenu(); const { onUpstreamRestoreFocus } = (_a = useUpstreamMenuItem()) != null ? _a : {}; const [downstreamOpen, setDownstreamOpen] = useState(false); const uuid = useId(); const itemRef = useRef(null); const hasDownstreamRef = useRef(false); const onKeyDownRef = useRef( () => void 0 ); const trulyDisabled = disabled && !focusable; const type = (_b = itemRef.current) == null ? void 0 : _b.getAttribute("type"); const role = !!type ? type === "checkbox" ? "menuitemcheckbox" : "menuitemradio" : "menuitem"; const { index, register } = useMenuDescendant({ disabled: trulyDisabled }); const focused = index === focusedIndex; const css = { alignItems: "center", color: "inherit", display: "flex", flex: "0 0 auto", gap: "0.75rem", outline: 0, textAlign: "start", textDecoration: "none", userSelect: "none", width: "100%", ...styles.item }; id != null ? id : id = uuid; const onMouseOver = useCallback(() => { if (disabled) return; setFocusedIndex(index); }, [index, disabled, setFocusedIndex]); const onMouseLeave = useCallback(() => { if (disabled) return; setFocusedIndex(-1); }, [setFocusedIndex, disabled]); const onClick = useCallback( (ev) => { if (!isTargetMenuItem(ev.currentTarget)) return; const hasDownstream = hasDownstreamRef.current; if (customCloseOnSelect != null ? customCloseOnSelect : !hasDownstream && closeOnSelect) { onClose(); onUpstreamClose == null ? void 0 : onUpstreamClose(); } }, [customCloseOnSelect, closeOnSelect, onClose, onUpstreamClose] ); const onFocus = useCallback(() => { setFocusedIndex(index); }, [setFocusedIndex, index]); const onRestoreFocus = useCallback(() => { var _a2; (_a2 = itemRef.current) == null ? void 0 : _a2.focus(); setFocusedIndex(index); }, [setFocusedIndex, index]); const onKeyDown = useCallback( (ev) => { if (ev.key === " ") ev.key = ev.code; const actions = { ArrowLeft: nested ? funcAll(onUpstreamRestoreFocus, onClose) : void 0, Space: !hasDownstreamRef.current ? funcAll(onUpstreamClose, onClose) : void 0 }; const action = actions[ev.key]; if (!action) return; ev.preventDefault(); ev.stopPropagation(); action(); }, [nested, onUpstreamRestoreFocus, onClose, onUpstreamClose] ); const rest = useClickable({ clickOnSpace: false, focusOnClick: false, ...props, ref: mergeRefs(register, itemRef, ref), disabled, focusable, onClick: handlerAll(props.onClick, onClick), onFocus: handlerAll(props.onFocus, onFocus), onKeyDown: handlerAll(props.onKeyDown, onKeyDown, onKeyDownRef.current), onMouseLeave: handlerAll(props.onMouseLeave, onMouseLeave), onMouseOver: handlerAll(props.onMouseOver, onMouseOver) }); useUpdateEffect(() => { if (!open) return; const id2 = requestAnimationFrameId.current; if (focused && !trulyDisabled && itemRef.current) { if (id2) cancelAnimationFrame(id2); requestAnimationFrameId.current = requestAnimationFrame(() => { var _a2; (_a2 = itemRef.current) == null ? void 0 : _a2.focus({ preventScroll: true }); requestAnimationFrameId.current = null; }); } else if (menuRef.current && !isActiveElement(menuRef.current)) { menuRef.current.focus({ preventScroll: true }); } return () => { if (id2) cancelAnimationFrame(id2); }; }, [focused, trulyDisabled, menuRef, open]); children = icon || command ? /* @__PURE__ */ jsx(ui.span, { style: { flex: 1 }, children }) : children; return /* @__PURE__ */ jsx( UpstreamMenuItemProvider, { value: { hasDownstreamRef, setDownstreamOpen, onKeyDownRef, onUpstreamRestoreFocus: onRestoreFocus }, children: /* @__PURE__ */ jsxs( ui.div, { id, className: cx("ui-menu__item", className), "data-focus": dataAttr(downstreamOpen), __css: css, ...rest, role, tabIndex: !downstreamOpen && focused ? 0 : -1, children: [ icon ? /* @__PURE__ */ jsx(MenuIcon, { children: icon }) : null, children, command ? /* @__PURE__ */ jsx(MenuCommand, { children: command }) : null ] } ) } ); } ); MenuItem.displayName = "MenuItem"; MenuItem.__ui__ = "MenuItem"; var MenuOptionItem = forwardRef( ({ className, isChecked, checked = isChecked, children, closeOnSelect = false, icon, ...rest }, ref) => { return /* @__PURE__ */ jsxs( MenuItem, { ref, className: cx("ui-menu__item--option", className), "aria-checked": checked, closeOnSelect, ...rest, children: [ icon !== null ? /* @__PURE__ */ jsx(MenuIcon, { opacity: checked ? 1 : 0, children: icon || /* @__PURE__ */ jsx(CheckIcon, {}) }) : null, children ] } ); } ); MenuOptionItem.displayName = "MenuOptionItem"; MenuOptionItem.__ui__ = "MenuOptionItem"; var MenuIcon = forwardRef( ({ className, ...rest }, ref) => { const { styles } = useMenu(); const css = { alignItems: "center", display: "inline-flex", flexShrink: 0, fontSize: "0.85em", justifyContent: "center", ...styles.icon }; return /* @__PURE__ */ jsx( ui.span, { ref, className: cx("ui-menu__item__icon", className), "aria-hidden": true, __css: css, ...rest } ); } ); MenuIcon.displayName = "MenuIcon"; MenuIcon.__ui__ = "MenuIcon"; var MenuCommand = forwardRef( ({ className, ...rest }, ref) => { const { styles } = useMenu(); const css = { ...styles.command }; return /* @__PURE__ */ jsx( ui.span, { ref, className: cx("ui-menu__item__command", className), __css: css, ...rest } ); } ); MenuCommand.displayName = "MenuCommand"; MenuCommand.__ui__ = "MenuCommand"; var CheckIcon = () => /* @__PURE__ */ jsx("svg", { height: "1em", viewBox: "0 0 14 14", width: "1em", children: /* @__PURE__ */ jsx( "polygon", { fill: "currentColor", points: "5.5 11.9993304 14 3.49933039 12.5 2 5.5 8.99933039 1.5 4.9968652 0 6.49933039" } ) }); export { MenuItem, MenuOptionItem, MenuIcon, MenuCommand }; //# sourceMappingURL=chunk-D2UJVOJ7.mjs.map