@amaui/ui-react
Version:
UI for React
438 lines (437 loc) • 26.3 kB
JavaScript
"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;