@yamada-ui/menu
Version:
Yamada UI menu component
271 lines (269 loc) • 7.94 kB
JavaScript
"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