UNPKG

@amaui/ui-react

Version:
438 lines (437 loc) 26.3 kB
"use strict"; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const jsx_runtime_1 = require("react/jsx-runtime"); const react_1 = __importDefault(require("react")); const utils_1 = require("@amaui/utils"); const style_react_1 = require("@amaui/style-react"); const IconMaterialExpandMoreW100_1 = __importDefault(require("@amaui/icons-material-rounded-react/IconMaterialExpandMoreW100")); const Grow_1 = __importDefault(require("../Grow")); const Append_1 = __importDefault(require("../Append")); const Transitions_1 = __importDefault(require("../Transitions")); const ClickListener_1 = __importDefault(require("../ClickListener")); const Transition_1 = __importDefault(require("../Transition")); const Surface_1 = __importDefault(require("../Surface")); const Type_1 = __importDefault(require("../Type")); const Line_1 = __importDefault(require("../Line")); const useMediaQuery_1 = __importDefault(require("../useMediaQuery")); const utils_2 = require("../utils"); const useStyle = (0, style_react_1.style)(theme => ({ root: { position: 'relative' }, menuWrapper: { position: 'relative', overflow: 'hidden', borderRadius: theme.methods.shape.radius.value(1.5, 'px'), zIndex: theme.z_index.menu_modal, '& > *': { transition: theme.methods.transitions.make(['width', 'height', 'opacity', 'transform']) } }, menu: { position: 'absolute', inset: '0', '&.enter': { opacity: '0', transform: 'translateX(-100%)', }, '&.entering': { opacity: '1', transform: 'translateX(0%)' }, '&.exit': { opacity: '1', transform: 'translateX(0%)', }, '&.exiting': { opacity: '0', transition: 'none' } }, menu_reverse: { position: 'absolute', inset: '0', '&.enter': { opacity: '0', transform: 'translateX(100%)', }, '&.entering': { opacity: '1', transform: 'translateX(0%)' }, '&.exit': { opacity: '1', transform: 'translateX(0%)', }, '&.exiting': { opacity: '0', transition: 'none' } }, item: { maxWidth: '140px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', padding: theme.methods.space.value(1, 'px'), cursor: 'pointer', userSelect: 'none' }, item_disabled: { opacity: theme.palette.visual_contrast.default.opacity.disabled, cursor: 'default', pointerEvents: 'none' }, indicator: { color: [theme.palette.text.default.secondary, '!important'], transition: theme.methods.transitions.make('transform') }, indicator_open: { transform: 'rotate(-180deg)' } }), { name: 'amaui-MenuDesktop' }); const Wrapper = react_1.default.forwardRef((props, ref) => { const theme = (0, style_react_1.useAmauiTheme)(); const { onMouseEnter, onMouseLeave, style } = props; return ((0, jsx_runtime_1.jsx)("div", Object.assign({ ref: ref, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, style: Object.assign({ zIndex: theme.z_index.menu_modal }, style) }, { children: props.children }))); }); const MenuDesktop = react_1.default.forwardRef((props_, ref) => { const theme = (0, style_react_1.useAmauiTheme)(); const props = react_1.default.useMemo(() => { var _a, _b, _c, _d, _e, _f, _g, _h; return (Object.assign(Object.assign(Object.assign({}, (_d = (_c = (_b = (_a = theme === null || theme === void 0 ? void 0 : theme.ui) === null || _a === void 0 ? void 0 : _a.elements) === null || _b === void 0 ? void 0 : _b.all) === null || _c === void 0 ? void 0 : _c.props) === null || _d === void 0 ? void 0 : _d.default), (_h = (_g = (_f = (_e = theme === null || theme === void 0 ? void 0 : theme.ui) === null || _e === void 0 ? void 0 : _e.elements) === null || _f === void 0 ? void 0 : _f.amauiMenuDesktop) === null || _g === void 0 ? void 0 : _g.props) === null || _h === void 0 ? void 0 : _h.default), props_)); }, [props_]); const Line = react_1.default.useMemo(() => { var _a; return ((_a = theme === null || theme === void 0 ? void 0 : theme.elements) === null || _a === void 0 ? void 0 : _a.Line) || Line_1.default; }, [theme]); const Grow = react_1.default.useMemo(() => { var _a; return ((_a = theme === null || theme === void 0 ? void 0 : theme.elements) === null || _a === void 0 ? void 0 : _a.Grow) || Grow_1.default; }, [theme]); const Append = react_1.default.useMemo(() => { var _a; return ((_a = theme === null || theme === void 0 ? void 0 : theme.elements) === null || _a === void 0 ? void 0 : _a.Append) || Append_1.default; }, [theme]); const Transitions = react_1.default.useMemo(() => { var _a; return ((_a = theme === null || theme === void 0 ? void 0 : theme.elements) === null || _a === void 0 ? void 0 : _a.Transitions) || Transitions_1.default; }, [theme]); const ClickListener = react_1.default.useMemo(() => { var _a; return ((_a = theme === null || theme === void 0 ? void 0 : theme.elements) === null || _a === void 0 ? void 0 : _a.ClickListener) || ClickListener_1.default; }, [theme]); const Transition = react_1.default.useMemo(() => { var _a; return ((_a = theme === null || theme === void 0 ? void 0 : theme.elements) === null || _a === void 0 ? void 0 : _a.Transition) || Transition_1.default; }, [theme]); const Surface = react_1.default.useMemo(() => { var _a; return ((_a = theme === null || theme === void 0 ? void 0 : theme.elements) === null || _a === void 0 ? void 0 : _a.Surface) || Surface_1.default; }, [theme]); const Type = react_1.default.useMemo(() => { var _a; return ((_a = theme === null || theme === void 0 ? void 0 : theme.elements) === null || _a === void 0 ? void 0 : _a.Type) || Type_1.default; }, [theme]); const { tonal = true, color = 'primary', version = 'filled', items, valueDefault, value_, openOnHover = true, openOnClick = true, openOnFocus = true, renderItem, indicator, menuTransition = true, menuTransitionClassName, menuTransitionStyle, onOpen, onClose: onClose_, onChange, TransitionComponent = Grow, IconIndicator = IconMaterialExpandMoreW100_1.default, AppendProps, TypeProps, ItemProps, IconProps, TransitionComponentProps, WrapperProps, WrapperMenuProps, className } = props, other = __rest(props, ["tonal", "color", "version", "items", "valueDefault", "value_", "openOnHover", "openOnClick", "openOnFocus", "renderItem", "indicator", "menuTransition", "menuTransitionClassName", "menuTransitionStyle", "onOpen", "onClose", "onChange", "TransitionComponent", "IconIndicator", "AppendProps", "TypeProps", "ItemProps", "IconProps", "TransitionComponentProps", "WrapperProps", "WrapperMenuProps", "className"]); const { classes } = useStyle(); const [init, setInit] = react_1.default.useState(false); const [open, setOpen] = react_1.default.useState(); const [inProp, setInProp] = react_1.default.useState(); const [openItem, setOpenItem] = react_1.default.useState(); const [focus, setFocus] = react_1.default.useState(); const [append, setAppend] = react_1.default.useState(); const [menuOpened, setMenuOpened] = react_1.default.useState(); const [menu, setMenu] = react_1.default.useState(); const refs = { root: react_1.default.useRef(undefined), open: react_1.default.useRef(undefined), focus: react_1.default.useRef(undefined), direction: react_1.default.useRef(undefined), props: react_1.default.useRef(undefined), items: react_1.default.useRef([]), menu: react_1.default.useRef(undefined), menus: react_1.default.useRef([]), menuRects: react_1.default.useRef([]), previousOpen: react_1.default.useRef(undefined), previousOpenIndex: react_1.default.useRef(undefined), anchorElement: react_1.default.useRef(undefined), wrapper: react_1.default.useRef(undefined) }; const mobile = (0, useMediaQuery_1.default)('(pointer: coarse)', { element: refs.root.current }); refs.open.current = open; refs.focus.current = focus; refs.direction.current = theme.direction; refs.props.current = props; const close = () => { setOpen(false); if ((0, utils_1.is)('function', onClose_)) onClose_(); }; const onClose = () => { setInProp(false); }; const updateOpenItem = (valueNew) => { setOpenItem(valueNew); if ((0, utils_1.is)('function', onChange)) onChange(valueNew); }; const updateOpen = (value__) => { if (value__) { const item = refs.props.current.items.find(item_ => item_.value === value__); if (item) { const itemHTML = refs.items.current.filter(Boolean).find(item_ => item_.dataset.value === item.value); if (itemHTML) { refs.anchorElement.current = itemHTML; if (item.menu) setMenu(item.menu); } } } if (value__) updateOpenItem(value__); if (refs.open.current) { refs.previousOpenIndex.current = refs.props.current.items.findIndex(item_ => item_.value === refs.open.current); refs.previousOpen.current = refs.props.current.items[refs.previousOpenIndex.current]; } if (!value__) onClose(); else { setInProp(true); setOpen(value__); if ((0, utils_1.is)('function', onOpen)) onOpen(); } }; const updateOpenPure = () => { setInProp(true); }; react_1.default.useEffect(() => { refs.menus.current.forEach((item, index) => { if (item) { refs.menuRects.current[index] = item.getBoundingClientRect(); } }); setInit(true); }, []); react_1.default.useEffect(() => { var _a; const method = (event) => { var _a; switch (event.key) { case 'Enter': if (refs.focus.current) { if (refs.open.current) updateOpen(''); else updateOpen((_a = refs.focus.current) === null || _a === void 0 ? void 0 : _a.value); } break; case 'Escape': if (refs.open.current) updateOpen(''); break; case 'Home': // If any item is in the focus if (refs.focus.current) { event.preventDefault(); const tabIndexItems = Array.from(refs.root.current.querySelectorAll(`[tabindex='0']`)); refs.props.current.items.reverse(); let index = refs.props.current.items.findIndex(item_ => (!item_.disabled && item_.menu)); refs.props.current.items.reverse(); if (index > -1) { index = refs.props.current.items.length - 1 - index; const item = refs.props.current.items[index]; if (item) { const itemHTML = tabIndexItems.find((item_) => item_.dataset.value === item.value); updateOpen(item.value); if (itemHTML) itemHTML.focus(); } } } break; case 'End': // If any item is in the focus if (refs.focus.current) { event.preventDefault(); const tabIndexItems = Array.from(refs.root.current.querySelectorAll(`[tabindex='0']`)); const index = refs.props.current.items.findIndex(item_ => (!item_.disabled && item_.menu)); if (index > -1) { const item = refs.props.current.items[index]; if (item) { const itemHTML = tabIndexItems.find((item_) => item_.dataset.value === item.value); updateOpen(item.value); if (itemHTML) itemHTML.focus(); } } } break; case 'ArrowUp': case 'ArrowDown': case 'ArrowLeft': case 'ArrowRight': // If any item is in the focus if (refs.focus.current) { event.preventDefault(); event.stopPropagation(); const tabIndexItems = Array.from(refs.root.current.querySelectorAll(`[tabindex='0']`)); const index = refs.props.current.items.findIndex(item_ => item_.value === refs.focus.current.value); let indexNew; let i = index; let item; while (indexNew === undefined && i >= 0 && i < refs.props.current.items.length) { (['ArrowDown'].includes(event.key) || (theme.direction === 'ltr' && event.key === 'ArrowLeft') || (theme.direction === 'rtl' && event.key === 'ArrowRight')) ? i-- : i++; item = refs.props.current.items[i]; if (!(item === null || item === void 0 ? void 0 : item.disabled) && (item === null || item === void 0 ? void 0 : item.menu)) indexNew = i; } if (item) { const itemHTML = tabIndexItems.find((item_) => item_.dataset.value === item.value); updateOpen(item.value); if (itemHTML) itemHTML.focus(); } } break; default: break; } }; const rootDocument = (0, utils_1.isEnvironment)('browser') ? (((_a = refs.root.current) === null || _a === void 0 ? void 0 : _a.ownerDocument) || window.document) : undefined; rootDocument.addEventListener('keydown', method); return () => { rootDocument.removeEventListener('keydown', method); }; }, []); react_1.default.useEffect(() => { if (open) { const index = refs.props.current.items.findIndex(item => (item === null || item === void 0 ? void 0 : item.value) === open); const rect = refs.menuRects.current[index]; if (rect) { setAppend({ width: rect.width, height: rect.height }); } } }, [open]); const onBlur = react_1.default.useCallback(() => { if (refs.focus.current) { updateOpen(''); setFocus(''); } }, []); const onFocus = react_1.default.useCallback((item) => { if (item && !item.disabled && item.menu) { setFocus(item); if (open !== item.value) updateOpen(item.value); } }, [open]); const onMouseLeave = react_1.default.useCallback((item) => { updateOpen(''); }, []); const onMouseEnter = react_1.default.useCallback((item) => { if (item && !item.disabled && item.menu) { updateOpen(item.value); } }, []); const onClick = react_1.default.useCallback((item) => { if (item && !item.disabled && item.menu) { open ? updateOpen('') : updateOpen(item.value); setTimeout(() => setFocus('')); } }, [open]); const onClickOutside = react_1.default.useCallback(() => { if (open) updateOpen(''); }, [open]); // If no items don't render anything // not to waste html space if (!(items === null || items === void 0 ? void 0 : items.length)) return; const openIndex = items.findIndex(item_ => (item_ === null || item_ === void 0 ? void 0 : item_.value) === open); return ((0, jsx_runtime_1.jsx)(ClickListener, Object.assign({ onClickOutside: onClickOutside }, { children: (0, jsx_runtime_1.jsxs)(Line, Object.assign({ ref: item => { if (ref) { if ((0, utils_1.is)('function', ref)) ref(item); else ref.current = item; } refs.root.current = item; }, gap: 0, direction: 'column', align: 'unset', justify: 'unset', className: (0, style_react_1.classNames)([ (0, utils_2.staticClassName)('MenuDesktop', theme) && [ 'amaui-MenuDesktop-root' ], className, classes.root ]) }, other, { children: [(0, jsx_runtime_1.jsx)(Line, Object.assign({ gap: 0, direction: 'row', align: 'unset', justify: 'unset' }, { children: items.map((item, index) => ( // hovered and onClick add to the method (0, utils_1.is)('function', renderItem) ? renderItem(item, index) : ((0, jsx_runtime_1.jsxs)(Line, Object.assign({ ref: item_ => { if (!refs.items.current.includes(item)) refs.items.current.push(item_); }, tabIndex: !item.disabled && item.menu ? 0 : undefined, "data-value": item.value, gap: 0.5, direction: 'row', align: 'center' }, ItemProps, ((!!item.menu && openOnClick) && { onClick: () => onClick(item) }), ((!!item.menu && !mobile && openOnFocus) && { onFocus: () => onFocus(item), onBlur: () => onBlur() }), ((!!item.menu && openOnHover) && { onMouseEnter: () => onMouseEnter(item), onMouseLeave: () => onMouseLeave(item) }), { className: (0, style_react_1.classNames)([ (0, utils_2.staticClassName)('MenuDesktop', theme) && [ 'amaui-MenuDesktop-item', open === item.value && `amaui-MenuDesktop-open`, item.disabled && `amaui-MenuDesktop-disabled` ], ItemProps === null || ItemProps === void 0 ? void 0 : ItemProps.className, classes.item, item.disabled && classes.item_disabled ]) }, { children: [(0, jsx_runtime_1.jsx)(Type, Object.assign({ version: 'l1' }, TypeProps, { children: item.name !== undefined ? item.name : item.label })), !!item.menu && ((0, jsx_runtime_1.jsx)(IconIndicator, Object.assign({ size: 20, className: (0, style_react_1.classNames)([ (0, utils_2.staticClassName)('MenuDesktop', theme) && [ 'amaui-MenuDesktop-indicator' ], classes.indicator, inProp && open === item.value && classes.indicator_open ]) }, IconProps)))] }), index)))) })), !init && (items.map((item, index) => ((0, jsx_runtime_1.jsx)("div", Object.assign({ ref: item_ => refs.menus.current[index] = item_, style: { position: 'absolute', visibility: 'hidden' } }, { children: item.menu }), index)))), (0, jsx_runtime_1.jsx)(Append, Object.assign({ open: !!open, anchorElement: refs.anchorElement.current, position: 'bottom', alignment: 'start', element: ({ ref: refAppend, values, style: styleAppend }) => { var _a, _b; return ((0, jsx_runtime_1.jsx)(Wrapper, Object.assign({ ref: refAppend, onMouseEnter: updateOpenPure, onMouseLeave: onMouseLeave }, WrapperProps, { style: Object.assign(Object.assign(Object.assign(Object.assign({}, append), styleAppend), { transition: 'none' }), WrapperProps === null || WrapperProps === void 0 ? void 0 : WrapperProps.style) }, { children: (0, jsx_runtime_1.jsx)(TransitionComponent, Object.assign({ in: inProp, onEntered: () => { if (refAppend.current) setTimeout(() => { refAppend.current.style.transition = theme.methods.transitions.make(['width', 'height', 'transform']); }, 14); }, onExited: () => { close(); if (refAppend.current) refAppend.current.style.transition = 'none'; }, add: true, removeOnExited: true }, TransitionComponentProps, { children: (0, jsx_runtime_1.jsxs)(Surface, Object.assign({ tonal: tonal, color: color, version: version }, WrapperMenuProps, { className: (0, style_react_1.classNames)([ (0, utils_2.staticClassName)('MenuDesktop', theme) && [ 'amaui-MenuDesktop-menu-wrapper' ], WrapperMenuProps === null || WrapperMenuProps === void 0 ? void 0 : WrapperMenuProps.className, classes.menuWrapper, menuOpened && classes.menuWrapper_open ]), style: Object.assign(Object.assign({}, append), WrapperMenuProps === null || WrapperMenuProps === void 0 ? void 0 : WrapperMenuProps.style) }, { children: [menu && menuTransition && ((0, jsx_runtime_1.jsx)(Transitions, Object.assign({ switch: true, mode: 'in-out-follow' }, { children: (0, jsx_runtime_1.jsx)(Transition, { children: (status) => { var _a, _b; return (react_1.default.cloneElement(menu, { // For manual onClose within the element onMenuDesktopClose: onClose, className: (0, style_react_1.classNames)([ (0, utils_2.staticClassName)('MenuDesktop', theme) && [ 'amaui-MenuDesktop-menu', status ], ...((0, utils_1.is)('function', menuTransitionClassName) ? menuTransitionClassName(status, openItem) : []), classes[theme.direction === 'ltr' ? ((openIndex <= 0 || openIndex < refs.previousOpenIndex.current) ? 'menu' : 'menu_reverse') : ((openIndex <= 0 || openIndex < refs.previousOpenIndex.current) ? 'menu_reverse' : 'menu')] ]), style: Object.assign(Object.assign(Object.assign({ position: 'absolute' }, (_a = menu === null || menu === void 0 ? void 0 : menu.props) === null || _a === void 0 ? void 0 : _a.style), (_b = refs.menu.current) === null || _b === void 0 ? void 0 : _b.props.style), ((0, utils_1.is)('function', menuTransitionStyle) && menuTransitionStyle(status, openItem))) })); } }, openItem) }))), menu && !menuTransition && (react_1.default.cloneElement(menu, { // For manual onClose within the element onMenuDesktopClose: onClose, className: (0, style_react_1.classNames)([ (0, utils_2.staticClassName)('MenuDesktop', theme) && [ 'amaui-MenuDesktop-menu' ], classes.menu ]), style: Object.assign(Object.assign(Object.assign({}, (_a = menu === null || menu === void 0 ? void 0 : menu.props) === null || _a === void 0 ? void 0 : _a.style), (_b = refs.menu.current) === null || _b === void 0 ? void 0 : _b.style), append) }))] })) })) }))); } }, AppendProps))] })) }))); }); MenuDesktop.displayName = 'amaui-MenuDesktop'; exports.default = MenuDesktop;