@grafana/ui
Version:
Grafana Components Library
306 lines (299 loc) • 9.47 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var jsxRuntime = require('react/jsx-runtime');
var css = require('@emotion/css');
var React = require('react');
var i18n = require('@grafana/i18n');
var ThemeContext = require('../../themes/ThemeContext.cjs');
var mixins = require('../../themes/mixins.cjs');
var Icon = require('../Icon/Icon.cjs');
var Stack = require('../Layout/Stack/Stack.cjs');
var SubMenu = require('./SubMenu.cjs');
function _interopNamespaceCompat(e) {
if (e && typeof e === 'object' && 'default' in e) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceCompat(React);
"use strict";
const MenuItem = React__namespace.memo(
React__namespace.forwardRef((props, ref) => {
const {
url,
icon,
label,
description,
ariaLabel,
ariaChecked,
target,
onClick,
className,
active,
disabled,
destructive,
childItems,
role,
tabIndex = -1,
customSubMenuContainerStyles,
shortcut,
testId,
iconColor
} = props;
const styles = ThemeContext.useStyles2(getStyles);
const resolvedIconColor = iconColor && !destructive && !disabled ? iconColor : void 0;
const [isActive, setIsActive] = React.useState(active);
const [isSubMenuOpen, setIsSubMenuOpen] = React.useState(false);
const onMouseEnter = React.useCallback(() => {
if (disabled) {
return;
}
setIsSubMenuOpen(true);
setIsActive(true);
}, [disabled]);
const onMouseLeave = React.useCallback(() => {
if (disabled) {
return;
}
setIsSubMenuOpen(false);
setIsActive(false);
}, [disabled]);
const hasSubMenu = childItems && childItems.length > 0;
const ItemElement = hasSubMenu ? "div" : url === void 0 ? "button" : "a";
const itemStyle = css.cx(
{
[styles.item]: true,
[styles.active]: isActive,
[styles.disabled]: disabled,
[styles.destructive]: destructive && !disabled
},
className
);
const disabledProps = {
[ItemElement === "button" ? "disabled" : "aria-disabled"]: disabled,
...ItemElement === "a" && disabled && { href: void 0, onClick: void 0 },
...disabled && {
tabIndex: -1,
["data-disabled"]: disabled
// used to identify disabled items in Menu.tsx
}
};
const localRef = React.useRef(null);
React.useImperativeHandle(ref, () => localRef.current);
const handleKeys = (event) => {
var _a;
switch (event.key) {
case " ":
if (ItemElement === "a" && url) {
event.preventDefault();
(_a = localRef.current) == null ? void 0 : _a.click();
}
if (hasSubMenu && !isSubMenuOpen) {
event.preventDefault();
event.stopPropagation();
setIsSubMenuOpen(true);
setIsActive(true);
return;
}
break;
case "ArrowRight":
event.preventDefault();
event.stopPropagation();
if (hasSubMenu) {
setIsSubMenuOpen(true);
setIsActive(true);
}
break;
case "Enter":
if (hasSubMenu && !isSubMenuOpen) {
event.preventDefault();
event.stopPropagation();
setIsSubMenuOpen(true);
setIsActive(true);
return;
}
break;
default:
break;
}
};
const closeSubMenu = () => {
var _a;
setIsSubMenuOpen(false);
setIsActive(false);
(_a = localRef == null ? void 0 : localRef.current) == null ? void 0 : _a.focus();
};
const hasShortcut = Boolean(shortcut && shortcut.length > 0);
return /* @__PURE__ */ jsxRuntime.jsxs(
ItemElement,
{
target,
className: itemStyle,
rel: target === "_blank" ? "noopener noreferrer" : void 0,
href: url,
onClick: (event) => {
if (hasSubMenu && !isSubMenuOpen) {
event.preventDefault();
event.stopPropagation();
}
onClick == null ? void 0 : onClick(event);
},
onMouseEnter,
onMouseLeave,
onKeyDown: handleKeys,
role: role != null ? role : "menuitem",
"data-role": "menuitem",
ref: localRef,
"data-testid": testId,
"aria-label": ariaLabel,
"aria-checked": ariaChecked,
"aria-haspopup": hasSubMenu ? "menu" : void 0,
"aria-expanded": hasSubMenu ? isSubMenuOpen : void 0,
tabIndex,
...disabledProps,
children: [
/* @__PURE__ */ jsxRuntime.jsxs(Stack.Stack, { direction: "row", justifyContent: "flex-start", alignItems: "center", children: [
icon && /* @__PURE__ */ jsxRuntime.jsx(
Icon.Icon,
{
name: icon,
className: css.cx(styles.icon, resolvedIconColor && css.css({ color: resolvedIconColor })),
"aria-hidden": true
}
),
/* @__PURE__ */ jsxRuntime.jsx("span", { className: css.cx(styles.ellipsis, styles.label), children: label }),
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: css.cx(styles.rightWrapper, { [styles.withShortcut]: hasShortcut }), children: [
hasShortcut && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.shortcut, children: [
/* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { name: "keyboard", title: i18n.t("grafana-ui.menu-item.keyboard-shortcut-label", "Keyboard shortcut") }),
shortcut
] }),
hasSubMenu && /* @__PURE__ */ jsxRuntime.jsx(
SubMenu.SubMenu,
{
parentItemRef: localRef,
items: childItems,
isOpen: isSubMenuOpen,
close: closeSubMenu,
customStyle: customSubMenuContainerStyles
}
)
] })
] }),
description && /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
className: css.cx(styles.description, styles.ellipsis, {
[styles.descriptionWithIcon]: icon !== void 0
}),
children: description
}
),
props.component ? /* @__PURE__ */ jsxRuntime.jsx(props.component, {}) : null
]
}
);
})
);
MenuItem.displayName = "MenuItem";
const getStyles = (theme) => {
const menuPadding = theme.components.menu.padding * theme.spacing.gridSize;
return {
item: css.css({
background: "none",
cursor: "pointer",
whiteSpace: "nowrap",
color: theme.colors.text.secondary,
display: "flex",
flexDirection: "column",
alignItems: "stretch",
justifyContent: "center",
padding: theme.spacing(0.5, 1.5),
minHeight: theme.spacing(4),
borderRadius: mixins.getInternalRadius(theme, menuPadding, { parentBorderWidth: 0, parentBorderRadius: "lg" }),
margin: 0,
border: "none",
width: "100%",
position: "relative",
"&:hover, &:focus-visible": {
background: theme.colors.action.hover,
color: theme.colors.text.primary,
textDecoration: "none"
},
"&:focus-visible": mixins.getFocusStyles(theme)
}),
label: css.css({
color: theme.colors.text.primary
}),
active: css.css({
background: theme.colors.action.hover,
// needed to show active items in forced colors mode
"@media (forced-colors: active)": {
outline: "1px solid transparent"
}
}),
destructive: css.css({
color: theme.colors.error.text,
svg: {
color: theme.colors.error.text
},
"&:hover, &:focus, &:focus-visible": {
background: theme.colors.error.main,
color: theme.colors.error.contrastText,
svg: {
color: theme.colors.error.contrastText
}
}
}),
disabled: css.css({
color: theme.colors.action.disabledText,
label: "menu-item-disabled",
"&:hover, &:focus, &:focus-visible": {
cursor: "not-allowed",
background: "none",
color: theme.colors.action.disabledText
}
}),
icon: css.css({
opacity: 0.7
}),
rightWrapper: css.css({
display: "flex",
alignItems: "center",
marginLeft: "auto"
}),
withShortcut: css.css({
minWidth: theme.spacing(10.5)
}),
shortcut: css.css({
display: "flex",
alignItems: "center",
gap: theme.spacing(1),
marginLeft: theme.spacing(2)
}),
description: css.css({
...theme.typography.bodySmall,
textAlign: "start"
}),
descriptionWithIcon: css.css({
marginLeft: theme.spacing(3)
}),
ellipsis: css.css({
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap"
})
};
};
exports.MenuItem = MenuItem;
//# sourceMappingURL=MenuItem.cjs.map