UNPKG

@carbon/react

Version:

React components for the Carbon Design System

310 lines (308 loc) 11.9 kB
/** * Copyright IBM Corp. 2016, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ const require_runtime = require("../../_virtual/_rolldown/runtime.js"); const require_usePrefix = require("../../internal/usePrefix.js"); const require_Text = require("../Text/Text.js"); const require_keys = require("../../internal/keyboard/keys.js"); const require_match = require("../../internal/keyboard/match.js"); const require_useId = require("../../internal/useId.js"); const require_defaultItemToString = require("../../internal/defaultItemToString.js"); const require_useMergedRefs = require("../../internal/useMergedRefs.js"); const require_MenuContext = require("./MenuContext.js"); const require_useLayoutDirection = require("../LayoutDirection/useLayoutDirection.js"); const require_Menu = require("./Menu.js"); const require_useControllableState = require("../../internal/useControllableState.js"); let classnames = require("classnames"); classnames = require_runtime.__toESM(classnames); let react = require("react"); react = require_runtime.__toESM(react); let prop_types = require("prop-types"); prop_types = require_runtime.__toESM(prop_types); let react_jsx_runtime = require("react/jsx-runtime"); let _carbon_icons_react = require("@carbon/icons-react"); let _floating_ui_react = require("@floating-ui/react"); //#region src/components/Menu/MenuItem.tsx /** * Copyright IBM Corp. 2023, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ const MenuItem = (0, react.forwardRef)(function MenuItem({ children, className, dangerDescription = "danger", disabled, kind = "default", label, onClick, renderIcon: IconElement, shortcut, ...rest }, forwardRef) { const [submenuOpen, setSubmenuOpen] = (0, react.useState)(false); const [rtl, setRtl] = (0, react.useState)(false); const { refs, floatingStyles, context: floatingContext } = (0, _floating_ui_react.useFloating)({ open: submenuOpen, onOpenChange: setSubmenuOpen, placement: rtl ? "left-start" : "right-start", whileElementsMounted: _floating_ui_react.autoUpdate, middleware: [(0, _floating_ui_react.offset)({ mainAxis: -6, crossAxis: -6 })], strategy: "fixed" }); const { getReferenceProps, getFloatingProps } = (0, _floating_ui_react.useInteractions)([(0, _floating_ui_react.useHover)(floatingContext, { delay: 100, enabled: true, handleClose: (0, _floating_ui_react.safePolygon)({ requireIntent: false }) })]); const prefix = require_usePrefix.usePrefix(); const context = (0, react.useContext)(require_MenuContext.MenuContext); const menuItem = (0, react.useRef)(null); const ref = require_useMergedRefs.useMergedRefs([ forwardRef, menuItem, refs.setReference ]); const hasChildren = react.default.Children.toArray(children).length > 0; const isDisabled = disabled && !hasChildren; const isDanger = kind === "danger" && !hasChildren; function registerItem() { context.dispatch({ type: "registerItem", payload: { ref: menuItem, disabled: disabled ?? false } }); } function openSubmenu() { if (!menuItem.current) return; setSubmenuOpen(true); } function closeSubmenu() { setSubmenuOpen(false); } function handleClick(e) { if (!isDisabled) if (hasChildren) openSubmenu(); else { context.state.requestCloseRoot(e); if (onClick) onClick(e); } } const pendingKeyboardClick = (0, react.useRef)(false); const keyboardClickEvent = (e) => require_match.match(e, require_keys.Enter) || require_match.match(e, require_keys.Space); function handleKeyDown(e) { if (hasChildren && require_match.match(e, require_keys.ArrowRight)) { openSubmenu(); requestAnimationFrame(() => { refs.floating.current?.focus(); }); e.stopPropagation(); e.preventDefault(); } pendingKeyboardClick.current = keyboardClickEvent(e); if (rest.onKeyDown) rest.onKeyDown(e); } function handleKeyUp(e) { if (pendingKeyboardClick.current && keyboardClickEvent(e)) handleClick(e); pendingKeyboardClick.current = false; } const classNames = (0, classnames.default)(className, `${prefix}--menu-item`, { [`${prefix}--menu-item--disabled`]: isDisabled, [`${prefix}--menu-item--danger`]: isDanger }); (0, react.useEffect)(() => { registerItem(); }, []); const { direction } = require_useLayoutDirection.useLayoutDirection(); (0, react.useEffect)(() => { if (document?.dir === "rtl" || direction === "rtl") setRtl(true); else setRtl(false); }, [direction]); (0, react.useEffect)(() => { if (IconElement && !context.state.hasIcons) context.dispatch({ type: "enableIcons" }); }, [ IconElement, context.state.hasIcons, context ]); (0, react.useEffect)(() => { Object.keys(floatingStyles).forEach((style) => { if (refs.floating.current && style !== "position") refs.floating.current.style[style] = floatingStyles[style]; }); }, [floatingStyles, refs.floating]); const assistiveId = require_useId.useId("danger-description"); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_floating_ui_react.FloatingFocusManager, { context: floatingContext, order: ["reference", "floating"], modal: false, children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { role: "menuitem", ...rest, ref, className: classNames, tabIndex: !disabled ? 0 : -1, "aria-disabled": isDisabled ?? void 0, "aria-haspopup": hasChildren ?? void 0, "aria-expanded": hasChildren ? submenuOpen : void 0, onClick: handleClick, onKeyDown: handleKeyDown, onKeyUp: handleKeyUp, title: label, ...getReferenceProps(), children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: `${prefix}--menu-item__selection-icon`, children: rest["aria-checked"] && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_carbon_icons_react.Checkmark, {}) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: `${prefix}--menu-item__icon`, children: IconElement && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconElement, {}) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Text.Text, { as: "div", className: `${prefix}--menu-item__label`, children: label }), isDanger && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { id: assistiveId, className: `${prefix}--visually-hidden`, children: dangerDescription }), shortcut && !hasChildren && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: `${prefix}--menu-item__shortcut`, children: shortcut }), hasChildren && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: `${prefix}--menu-item__shortcut`, children: rtl ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_carbon_icons_react.CaretLeft, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_carbon_icons_react.CaretRight, {}) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Menu.Menu, { label, open: submenuOpen, onClose: () => { closeSubmenu(); menuItem.current?.focus(); }, ref: refs.setFloating, ...getFloatingProps(), children })] }) ] }) }); }); MenuItem.propTypes = { children: prop_types.default.node, className: prop_types.default.string, dangerDescription: prop_types.default.string, disabled: prop_types.default.bool, kind: prop_types.default.oneOf(["default", "danger"]), label: prop_types.default.string.isRequired, onClick: prop_types.default.func, renderIcon: prop_types.default.oneOfType([prop_types.default.func, prop_types.default.object]), shortcut: prop_types.default.string }; const MenuItemSelectable = (0, react.forwardRef)(function MenuItemSelectable({ className, defaultSelected, label, onChange, selected, ...rest }, forwardRef) { const prefix = require_usePrefix.usePrefix(); const context = (0, react.useContext)(require_MenuContext.MenuContext); const [checked, setChecked] = require_useControllableState.useControllableState({ value: selected, onChange, defaultValue: defaultSelected ?? false }); function handleClick() { setChecked(!checked); } (0, react.useEffect)(() => { if (!context.state.hasSelectableItems) context.dispatch({ type: "enableSelectableItems" }); }, [context.state.hasSelectableItems, context]); const classNames = (0, classnames.default)(className, `${prefix}--menu-item-selectable--selected`); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuItem, { ...rest, ref: forwardRef, label, className: classNames, role: "menuitemcheckbox", "aria-checked": checked, onClick: handleClick }); }); MenuItemSelectable.propTypes = { className: prop_types.default.string, defaultSelected: prop_types.default.bool, label: prop_types.default.string.isRequired, onChange: prop_types.default.func, selected: prop_types.default.bool }; const MenuItemGroup = (0, react.forwardRef)(function MenuItemGroup({ children, className, label, ...rest }, forwardRef) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { className: (0, classnames.default)(className, `${require_usePrefix.usePrefix()}--menu-item-group`), role: "none", ref: forwardRef, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { ...rest, role: "group", "aria-label": label, children }) }); }); MenuItemGroup.propTypes = { children: prop_types.default.node, className: prop_types.default.string, label: prop_types.default.string.isRequired }; const MenuItemRadioGroup = (0, react.forwardRef)(function MenuItemRadioGroup({ className, defaultSelectedItem, items, itemToString = require_defaultItemToString.defaultItemToString, label, onChange, selectedItem, ...rest }, forwardRef) { const prefix = require_usePrefix.usePrefix(); const context = (0, react.useContext)(require_MenuContext.MenuContext); const [selection, setSelection] = require_useControllableState.useControllableState({ value: selectedItem, onChange, defaultValue: defaultSelectedItem ?? {} }); function handleClick(item) { setSelection(item); } (0, react.useEffect)(() => { if (!context.state.hasSelectableItems) context.dispatch({ type: "enableSelectableItems" }); }, [context.state.hasSelectableItems, context]); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { className: (0, classnames.default)(className, `${prefix}--menu-item-radio-group`), role: "none", ref: forwardRef, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { ...rest, role: "group", "aria-label": label, children: items.map((item, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuItem, { label: itemToString(item), role: "menuitemradio", "aria-checked": item === selection, onClick: () => { handleClick(item); } }, i)) }) }); }); MenuItemRadioGroup.propTypes = { className: prop_types.default.string, defaultSelectedItem: prop_types.default.any, itemToString: prop_types.default.func, items: prop_types.default.array, label: prop_types.default.string.isRequired, onChange: prop_types.default.func, selectedItem: prop_types.default.any }; const MenuItemDivider = (0, react.forwardRef)(function MenuItemDivider({ className, ...rest }, forwardRef) { const classNames = (0, classnames.default)(className, `${require_usePrefix.usePrefix()}--menu-item-divider`); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { ...rest, className: classNames, role: "separator", ref: forwardRef }); }); MenuItemDivider.propTypes = { className: prop_types.default.string }; //#endregion exports.MenuItem = MenuItem; exports.MenuItemDivider = MenuItemDivider; exports.MenuItemGroup = MenuItemGroup; exports.MenuItemRadioGroup = MenuItemRadioGroup; exports.MenuItemSelectable = MenuItemSelectable;