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
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>
|
|
);
|
|
};
|