You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

95 lines
2.9 KiB
JavaScript

'use strict';
const React = require("react");
const useMeasure = require("react-use-measure");
const matchValue = require("match-value");
const insecureNanoid = require("nanoid/non-secure").nanoid;
const defaultStyle = require("./style.css");
const useTheme = require("../../util/themeable");
const useGuaranteedMemo = require("../../util/use-guaranteed-memo");
const isEventInsideRef = require("../../util/is-event-inside-ref");
const comparePath = require("../../util/compare-path");
const MenuItemContext = require("../../contexts/menu-item");
const MenuPositionContext = require("../../contexts/menu-position");
const MenuPathContext = require("../../contexts/menu-path");
module.exports = function MenuItem({ label, title, menu, onClick }) {
let { withTheme } = useTheme({ control: "menu", defaultStyle });
let [ boundsRef, bounds ] = useMeasure({ debounce: 10 });
let submenuRef = React.useRef();
let itemContext = React.useContext(MenuItemContext);
let path = React.useContext(MenuPathContext);
let id = useGuaranteedMemo(() => insecureNanoid()); // insecure is fine here, these IDs are not interesting to an attacker
let ownPath = path.concat([ id ]); // FIXME: memoize?
let isSelected = itemContext.isActive && comparePath(itemContext.selectedPath, ownPath);
let submenuPosition = matchValue(itemContext.menuType, {
menuBar: {
x: bounds.x,
y: bounds.y + bounds.height
},
menu: {
x: bounds.x + bounds.width, /* FIXME: Account for border of containing menu? */
y: bounds.y
}
});
let handlers = {
onClick: (event) => {
if (!isEventInsideRef(submenuRef, event)) {
if (onClick != null) {
onClick(); // TODO: Pass through event? Allow cancellation? To deal with advanced cases like checkboxes within menus, if we want that
}
if (itemContext.onClick != null) {
let hasMenu = (menu != null);
let isInMenu = false; // Let the menu intercept and change this
itemContext.onClick(ownPath, hasMenu, isInMenu);
}
}
},
onMouseEnter: (_event) => {
// FIXME: Only outside of submenu?
if (itemContext.onMouseEnter != null) {
itemContext.onMouseEnter(ownPath);
}
},
onMouseLeave: (_event) => {
// FIXME: Only outside of submenu?
if (itemContext.onMouseLeave != null) {
itemContext.onMouseLeave(ownPath);
}
},
};
let buttonClasses = withTheme([
"item",
{ item_selected: isSelected },
{ item_directPress: (menu == null) }
]);
return (
<MenuPathContext.Provider value={ownPath}>
<MenuPositionContext.Provider value={submenuPosition}>
<div ref={boundsRef} title={title} className={buttonClasses} {...handlers}>
{label}
{(menu != null)
? <span className={withTheme("submenu")} ref={submenuRef}>
<i className={withTheme("submenuArrow")}></i>
{(isSelected)
? menu
: null
}
</span>
: null
}
</div>
</MenuPositionContext.Provider>
</MenuPathContext.Provider>
);
};