@grafana/ui
Version:
Grafana Components Library
1,673 lines (1,644 loc) • 328 kB
JavaScript
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var css = require('@emotion/css');
require('react-data-grid/lib/styles.css');
var React = require('react');
var reactDataGrid = require('react-data-grid');
var data = require('@grafana/data');
var i18n = require('@grafana/i18n');
var schema = require('@grafana/schema');
var hoistNonReactStatics = require('hoist-non-react-statics');
var memoize = require('micro-memoize');
var reactUse = require('react-use');
var e2eSelectors = require('@grafana/e2e-selectors');
var lodash = require('lodash');
var tinycolor = require('tinycolor2');
var SVG = require('react-inlinesvg');
var ReactDOM = require('react-dom');
var react = require('@floating-ui/react');
var ReactDOMServer = require('react-dom/server');
var reactTransitionGroup = require('react-transition-group');
var dialog = require('@react-aria/dialog');
var focus = require('@react-aria/focus');
var overlays = require('@react-aria/overlays');
var reactHookForm = require('react-hook-form');
var RcDrawer = require('rc-drawer');
require('rc-drawer/assets/index.css');
var reactWindow = require('react-window');
var format = require('ol/format');
var geom = require('ol/geom');
var uwrap = require('uwrap');
var WKT = require('ol/format/WKT');
var uPlot = require('uplot');
require('uplot/dist/uPlot.min.css');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
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);
var hoistNonReactStatics__default = /*#__PURE__*/_interopDefaultCompat(hoistNonReactStatics);
var memoize__default = /*#__PURE__*/_interopDefaultCompat(memoize);
var tinycolor__default = /*#__PURE__*/_interopDefaultCompat(tinycolor);
var SVG__default = /*#__PURE__*/_interopDefaultCompat(SVG);
var ReactDOM__default = /*#__PURE__*/_interopDefaultCompat(ReactDOM);
var ReactDOMServer__default = /*#__PURE__*/_interopDefaultCompat(ReactDOMServer);
var RcDrawer__default = /*#__PURE__*/_interopDefaultCompat(RcDrawer);
var WKT__default = /*#__PURE__*/_interopDefaultCompat(WKT);
var uPlot__default = /*#__PURE__*/_interopDefaultCompat(uPlot);
const fadeIn = css.keyframes({
"0%": {
opacity: 0
},
"100%": {
opacity: 1
}
});
const skeletonAnimation = {
animationName: fadeIn,
animationDelay: "100ms",
animationTimingFunction: "ease-in",
animationDuration: "100ms",
animationFillMode: "backwards"
};
const attachSkeleton = (Component, Skeleton) => {
const skeletonWrapper = (props) => {
return /* @__PURE__ */ jsxRuntime.jsx(
Skeleton,
{
...props,
rootProps: {
style: skeletonAnimation
}
}
);
};
return Object.assign(Component, { Skeleton: skeletonWrapper });
};
function stylesFactory(stylesCreator) {
return memoize__default.default(stylesCreator);
}
const memoizedStyleCreators = /* @__PURE__ */ new WeakMap();
const withTheme2 = (Component) => {
const WithTheme = (props) => {
const ContextComponent = data.ThemeContext;
return (
// @ts-ignore
/* @__PURE__ */ jsxRuntime.jsx(ContextComponent.Consumer, { children: (theme) => /* @__PURE__ */ jsxRuntime.jsx(Component, { ...props, theme }) })
);
};
WithTheme.displayName = `WithTheme(${Component.displayName})`;
hoistNonReactStatics__default.default(WithTheme, Component);
return WithTheme;
};
function useTheme2() {
return React.useContext(data.ThemeContext);
}
function useStyles2(getStyles, ...additionalArguments) {
const theme = useTheme2();
if (!theme.colors.background.elevated) {
theme.colors.background.elevated = theme.colors.mode === "light" ? theme.colors.background.primary : theme.colors.background.secondary;
}
let memoizedStyleCreator = memoizedStyleCreators.get(getStyles);
if (!memoizedStyleCreator) {
memoizedStyleCreator = memoize__default.default(getStyles, { maxSize: 10 });
memoizedStyleCreators.set(getStyles, memoizedStyleCreator);
}
return memoizedStyleCreator(theme, ...additionalArguments);
}
function breakpointCSS(theme, prop, getCSS, key) {
const value = prop[key];
if (value !== void 0 && value !== null) {
return {
[theme.breakpoints.up(key)]: getCSS(value)
};
}
return;
}
function getResponsiveStyle(theme, prop, getCSS) {
if (prop === void 0 || prop === null) {
return null;
}
if (typeof prop !== "object" || !("xs" in prop)) {
return getCSS(prop);
}
return [
breakpointCSS(theme, prop, getCSS, "xs"),
breakpointCSS(theme, prop, getCSS, "sm"),
breakpointCSS(theme, prop, getCSS, "md"),
breakpointCSS(theme, prop, getCSS, "lg"),
breakpointCSS(theme, prop, getCSS, "xl"),
breakpointCSS(theme, prop, getCSS, "xxl")
];
}
const getSizeStyles = (theme, width, minWidth, maxWidth, height, minHeight, maxHeight) => {
return css.css([
getResponsiveStyle(theme, width, (val) => ({
width: theme.spacing(val)
})),
getResponsiveStyle(theme, minWidth, (val) => ({
minWidth: theme.spacing(val)
})),
getResponsiveStyle(theme, maxWidth, (val) => ({
maxWidth: theme.spacing(val)
})),
getResponsiveStyle(theme, height, (val) => ({
height: theme.spacing(val)
})),
getResponsiveStyle(theme, minHeight, (val) => ({
minHeight: theme.spacing(val)
})),
getResponsiveStyle(theme, maxHeight, (val) => ({
maxHeight: theme.spacing(val)
}))
]);
};
const Box = React.forwardRef((props, ref) => {
const {
children,
margin,
marginX,
marginY,
marginTop,
marginBottom,
marginLeft,
marginRight,
padding,
paddingX,
paddingY,
paddingTop,
paddingBottom,
paddingLeft,
paddingRight,
display,
backgroundColor,
grow,
shrink,
basis,
flex,
borderColor,
borderStyle,
borderRadius,
direction,
justifyContent,
alignItems,
boxShadow,
element,
gap,
width,
minWidth,
maxWidth,
height,
minHeight,
maxHeight,
position,
...rest
} = props;
const styles = useStyles2(
getStyles$K,
margin,
marginX,
marginY,
marginTop,
marginBottom,
marginLeft,
marginRight,
padding,
paddingX,
paddingY,
paddingTop,
paddingBottom,
paddingLeft,
paddingRight,
display,
backgroundColor,
grow,
shrink,
basis,
flex,
borderColor,
borderStyle,
borderRadius,
direction,
justifyContent,
alignItems,
boxShadow,
gap,
position
);
const sizeStyles = useStyles2(getSizeStyles, width, minWidth, maxWidth, height, minHeight, maxHeight);
const Element = element != null ? element : "div";
return /* @__PURE__ */ jsxRuntime.jsx(Element, { ref, className: css.cx(styles.root, sizeStyles), ...rest, children });
});
Box.displayName = "Box";
const customBorderColor = (color, theme) => {
switch (color) {
case "error":
case "success":
case "info":
case "warning":
return theme.colors[color].borderTransparent;
default:
return color ? theme.colors.border[color] : void 0;
}
};
const customBackgroundColor = (color, theme) => {
switch (color) {
case "error":
case "success":
case "info":
case "warning":
return theme.colors[color].transparent;
default:
return color ? theme.colors.background[color] : void 0;
}
};
const getStyles$K = (theme, margin, marginX, marginY, marginTop, marginBottom, marginLeft, marginRight, padding, paddingX, paddingY, paddingTop, paddingBottom, paddingLeft, paddingRight, display, backgroundColor, grow, shrink, basis, flex, borderColor, borderStyle, borderRadius, direction, justifyContent, alignItems, boxShadow, gap, position) => {
return {
root: css.css([
getResponsiveStyle(theme, margin, (val) => ({
margin: theme.spacing(val)
})),
getResponsiveStyle(theme, marginX, (val) => ({
marginLeft: theme.spacing(val),
marginRight: theme.spacing(val)
})),
getResponsiveStyle(theme, marginY, (val) => ({
marginTop: theme.spacing(val),
marginBottom: theme.spacing(val)
})),
getResponsiveStyle(theme, marginTop, (val) => ({
marginTop: theme.spacing(val)
})),
getResponsiveStyle(theme, marginBottom, (val) => ({
marginBottom: theme.spacing(val)
})),
getResponsiveStyle(theme, marginLeft, (val) => ({
marginLeft: theme.spacing(val)
})),
getResponsiveStyle(theme, marginRight, (val) => ({
marginRight: theme.spacing(val)
})),
getResponsiveStyle(theme, padding, (val) => ({
padding: theme.spacing(val)
})),
getResponsiveStyle(theme, paddingX, (val) => ({
paddingLeft: theme.spacing(val),
paddingRight: theme.spacing(val)
})),
getResponsiveStyle(theme, paddingY, (val) => ({
paddingTop: theme.spacing(val),
paddingBottom: theme.spacing(val)
})),
getResponsiveStyle(theme, paddingTop, (val) => ({
paddingTop: theme.spacing(val)
})),
getResponsiveStyle(theme, paddingBottom, (val) => ({
paddingBottom: theme.spacing(val)
})),
getResponsiveStyle(theme, paddingLeft, (val) => ({
paddingLeft: theme.spacing(val)
})),
getResponsiveStyle(theme, paddingRight, (val) => ({
paddingRight: theme.spacing(val)
})),
getResponsiveStyle(theme, display, (val) => ({
display: val
})),
getResponsiveStyle(theme, backgroundColor, (val) => ({
backgroundColor: customBackgroundColor(val, theme)
})),
getResponsiveStyle(theme, direction, (val) => ({
flexDirection: val
})),
getResponsiveStyle(theme, grow, (val) => ({
flexGrow: val
})),
getResponsiveStyle(theme, shrink, (val) => ({
flexShrink: val
})),
getResponsiveStyle(theme, basis, (val) => ({
flexBasis: val
})),
getResponsiveStyle(theme, flex, (val) => ({
flex: val
})),
getResponsiveStyle(theme, borderStyle, (val) => ({
borderStyle: val
})),
getResponsiveStyle(theme, borderColor, (val) => ({
borderColor: customBorderColor(val, theme)
})),
(borderStyle || borderColor) && {
borderWidth: "1px"
},
getResponsiveStyle(theme, justifyContent, (val) => ({
justifyContent: val
})),
getResponsiveStyle(theme, alignItems, (val) => ({
alignItems: val
})),
getResponsiveStyle(theme, borderRadius, (val) => ({
borderRadius: theme.shape.radius[val]
})),
getResponsiveStyle(theme, boxShadow, (val) => ({
boxShadow: theme.shadows[val]
})),
getResponsiveStyle(theme, gap, (val) => ({
gap: theme.spacing(val)
})),
getResponsiveStyle(theme, position, (val) => ({
position: val
}))
])
};
};
function MenuDivider() {
const styles = useStyles2(getStyles$J);
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.divider });
}
const getStyles$J = (theme) => {
return {
divider: css.css({
height: 1,
backgroundColor: theme.colors.border.weak,
margin: theme.spacing(0.5, 0)
})
};
};
const MenuGroup = ({ label, ariaLabel, children }) => {
const styles = useStyles2(getStyles$I);
const labelID = `group-label-${lodash.uniqueId()}`;
return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "group", "aria-labelledby": !ariaLabel && label ? labelID : void 0, "aria-label": ariaLabel, children: [
label && /* @__PURE__ */ jsxRuntime.jsx("label", { id: labelID, className: styles.groupLabel, "aria-hidden": true, children: label }),
children
] });
};
MenuGroup.displayName = "MenuGroup";
const getStyles$I = (theme) => {
return {
groupLabel: css.css({
color: theme.colors.text.secondary,
fontSize: theme.typography.size.sm,
padding: theme.spacing(0.5, 1)
})
};
};
function mediaUp(breakpoint) {
return `only screen and (min-width: ${breakpoint})`;
}
function getMouseFocusStyles(theme) {
return {
outline: "none",
boxShadow: `none`
};
}
function getFocusStyles(theme) {
return {
outline: "2px dotted transparent",
outlineOffset: "2px",
boxShadow: `0 0 0 2px ${theme.colors.background.canvas}, 0 0 0px 4px ${theme.colors.primary.main}`,
transitionTimingFunction: `cubic-bezier(0.19, 1, 0.22, 1)`,
transitionDuration: "0.2s",
transitionProperty: "outline, outline-offset, box-shadow"
};
}
const spin = css.keyframes({
"0%": {
transform: "rotate(0deg)"
},
"100%": {
transform: "rotate(359deg)"
}
});
const alwaysMonoIcons = [
"grafana",
"favorite",
"heart-break",
"heart",
"panel-add",
"library-panel",
"circle-mono"
];
function getIconSubDir(name, type) {
if (name == null ? void 0 : name.startsWith("gf-")) {
return "custom";
} else if (alwaysMonoIcons.includes(name)) {
return "mono";
} else if (type === "default") {
return "unicons";
} else if (type === "solid") {
return "solid";
} else {
return "mono";
}
}
function getSvgSize(size) {
switch (size) {
case "xs":
return 12;
case "sm":
return 14;
case "md":
return 16;
case "lg":
return 18;
case "xl":
return 24;
case "xxl":
return 36;
case "xxxl":
return 48;
}
}
let iconRoot;
function getIconRoot() {
if (iconRoot) {
return iconRoot;
}
const grafanaPublicPath = typeof window !== "undefined" && window.__grafana_public_path__;
if (grafanaPublicPath) {
iconRoot = grafanaPublicPath + "build/img/icons/";
} else {
iconRoot = "public/build/img/icons/";
}
return iconRoot;
}
function getIconPath(name, type = "default") {
const iconRoot2 = getIconRoot();
const subDir = getIconSubDir(name, type);
return `${iconRoot2}${subDir}/${name}.svg`;
}
const getIconStyles = (theme) => {
return {
icon: css.css({
display: "inline-block",
fill: "currentColor",
flexShrink: 0,
label: "Icon",
// line-height: 0; is needed for correct icon alignment in Safari
lineHeight: 0,
verticalAlign: "middle"
}),
orange: css.css({
fill: theme.v1.palette.orange
}),
spin: css.css({
[theme.transitions.handleMotion("no-preference", "reduce")]: {
animation: `${spin} 2s infinite linear`
}
})
};
};
const Icon = React__namespace.forwardRef(
({ size = "md", type = "default", name, className, style, title = "", ...rest }, ref) => {
const styles = useStyles2(getIconStyles);
if (!data.isIconName(name)) {
console.warn("Icon component passed an invalid icon name", name);
}
const iconName = name === "fa fa-spinner" ? "spinner" : name;
const svgSize = getSvgSize(size);
const svgHgt = svgSize;
const svgWid = name.startsWith("gf-bar-align") ? 16 : name.startsWith("gf-interp") ? 30 : svgSize;
const svgPath = getIconPath(iconName, type);
const composedClassName = css.cx(
styles.icon,
className,
type === "mono" ? { [styles.orange]: name === "favorite" } : "",
{
[styles.spin]: iconName === "spinner"
}
);
return /* @__PURE__ */ jsxRuntime.jsx(
SVG__default.default,
{
"aria-hidden": rest.tabIndex === void 0 && !title && !rest["aria-label"] && !rest["aria-labelledby"] && !rest["aria-describedby"],
innerRef: ref,
src: svgPath,
width: svgWid,
height: svgHgt,
title,
className: composedClassName,
style,
loader: /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
className: css.cx(
css.css({
width: svgWid,
height: svgHgt
}),
composedClassName
)
}
),
...rest
}
);
}
);
Icon.displayName = "Icon";
const Stack = React__namespace.forwardRef((props, ref) => {
const {
gap = 1,
rowGap,
columnGap,
alignItems,
justifyContent,
direction,
wrap,
children,
grow,
shrink,
basis,
flex,
width,
minWidth,
maxWidth,
height,
minHeight,
maxHeight,
...rest
} = props;
const styles = useStyles2(
getStyles$H,
gap,
rowGap,
columnGap,
alignItems,
justifyContent,
direction,
wrap,
grow,
shrink,
basis,
flex
);
const sizeStyles = useStyles2(getSizeStyles, width, minWidth, maxWidth, height, minHeight, maxHeight);
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: css.cx(styles.flex, sizeStyles), ...rest, children });
});
Stack.displayName = "Stack";
const getStyles$H = (theme, gap, rowGap, columnGap, alignItems, justifyContent, direction, wrap, grow, shrink, basis, flex) => {
return {
flex: css.css([
{
display: "flex"
},
getResponsiveStyle(theme, direction, (val) => ({
flexDirection: val
})),
getResponsiveStyle(theme, wrap, (val) => ({
flexWrap: typeof val === "boolean" ? val ? "wrap" : "nowrap" : val
})),
getResponsiveStyle(theme, alignItems, (val) => ({
alignItems: val
})),
getResponsiveStyle(theme, justifyContent, (val) => ({
justifyContent: val
})),
getResponsiveStyle(theme, gap, (val) => ({
gap: theme.spacing(val)
})),
getResponsiveStyle(theme, rowGap, (val) => ({
rowGap: theme.spacing(val)
})),
getResponsiveStyle(theme, columnGap, (val) => ({
columnGap: theme.spacing(val)
})),
getResponsiveStyle(theme, grow, (val) => ({
flexGrow: val
})),
getResponsiveStyle(theme, shrink, (val) => ({
flexShrink: val
})),
getResponsiveStyle(theme, basis, (val) => ({
flexBasis: val
})),
getResponsiveStyle(theme, flex, (val) => ({
flex: val
}))
])
};
};
const modulo = (a, n) => (a % n + n) % n;
const UNFOCUSED = -1;
const useMenuFocus = ({
localRef,
isMenuOpen,
close,
onOpen,
onClose,
onKeyDown
}) => {
const [focusedItem, setFocusedItem] = React.useState(UNFOCUSED);
React.useEffect(() => {
if (isMenuOpen) {
setFocusedItem(0);
}
}, [isMenuOpen]);
React.useEffect(() => {
var _a, _b;
const menuItems = (_a = localRef == null ? void 0 : localRef.current) == null ? void 0 : _a.querySelectorAll(
'[data-role="menuitem"]:not([data-disabled])'
);
(_b = menuItems == null ? void 0 : menuItems[focusedItem]) == null ? void 0 : _b.focus();
menuItems == null ? void 0 : menuItems.forEach((menuItem, i) => {
menuItem.tabIndex = i === focusedItem ? 0 : -1;
});
}, [localRef, focusedItem]);
reactUse.useEffectOnce(() => {
onOpen == null ? void 0 : onOpen(setFocusedItem);
});
const handleKeys = (event) => {
var _a, _b, _c;
const menuItems = (_a = localRef == null ? void 0 : localRef.current) == null ? void 0 : _a.querySelectorAll(
'[data-role="menuitem"]:not([data-disabled])'
);
const menuItemsCount = (_b = menuItems == null ? void 0 : menuItems.length) != null ? _b : 0;
switch (event.key) {
case "ArrowUp":
event.preventDefault();
event.stopPropagation();
setFocusedItem(modulo(focusedItem - 1, menuItemsCount));
break;
case "ArrowDown":
event.preventDefault();
event.stopPropagation();
setFocusedItem(modulo(focusedItem + 1, menuItemsCount));
break;
case "ArrowLeft":
event.preventDefault();
event.stopPropagation();
setFocusedItem(UNFOCUSED);
close == null ? void 0 : close();
break;
case "Home":
event.preventDefault();
event.stopPropagation();
setFocusedItem(0);
break;
case "End":
event.preventDefault();
event.stopPropagation();
setFocusedItem(menuItemsCount - 1);
break;
case "Enter":
event.preventDefault();
event.stopPropagation();
(_c = menuItems == null ? void 0 : menuItems[focusedItem]) == null ? void 0 : _c.click();
break;
case "Escape":
onClose == null ? void 0 : onClose();
break;
case "Tab":
event.preventDefault();
onClose == null ? void 0 : onClose();
break;
}
onKeyDown == null ? void 0 : onKeyDown(event);
};
return [handleKeys];
};
const isElementOverflowing = (element) => {
if (!element) {
return false;
}
const wrapperPos = element.parentElement.getBoundingClientRect();
const pos = element.getBoundingClientRect();
return pos.width !== 0 && wrapperPos.right + pos.width + 10 > window.innerWidth;
};
const SubMenu = React.memo(({ items, isOpen, close, customStyle }) => {
const styles = useStyles2(getStyles$G);
const localRef = React.useRef(null);
const [handleKeys] = useMenuFocus({
localRef,
isMenuOpen: isOpen,
close
});
const [pushLeft, setPushLeft] = React.useState(false);
React.useEffect(() => {
if (isOpen && localRef.current) {
setPushLeft(isElementOverflowing(localRef.current));
}
}, [isOpen]);
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
/* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.iconWrapper, "aria-hidden": true, "data-testid": e2eSelectors.selectors.components.Menu.SubMenu.icon, children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "angle-right", className: styles.icon }) }),
isOpen && /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
ref: localRef,
className: css.cx(styles.subMenu, { [styles.pushLeft]: pushLeft }),
"data-testid": e2eSelectors.selectors.components.Menu.SubMenu.container,
style: customStyle,
children: /* @__PURE__ */ jsxRuntime.jsx("div", { tabIndex: -1, className: styles.itemsWrapper, role: "menu", onKeyDown: handleKeys, children: items })
}
)
] });
});
SubMenu.displayName = "SubMenu";
const getStyles$G = (theme) => {
return {
iconWrapper: css.css({
display: "flex",
flex: 1,
justifyContent: "end"
}),
icon: css.css({
opacity: 0.7,
marginLeft: theme.spacing(1),
color: theme.colors.text.secondary
}),
itemsWrapper: css.css({
background: theme.colors.background.elevated,
padding: theme.spacing(0.5),
boxShadow: theme.shadows.z3,
display: "inline-block",
borderRadius: theme.shape.radius.default
}),
pushLeft: css.css({
right: "100%",
left: "unset"
}),
subMenu: css.css({
position: "absolute",
top: 0,
left: "100%",
zIndex: theme.zIndex.dropdown
})
};
};
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
} = props;
const styles = useStyles2(getStyles$F);
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) => {
switch (event.key) {
case "ArrowRight":
event.preventDefault();
event.stopPropagation();
if (hasSubMenu) {
setIsSubMenuOpen(true);
setIsActive(true);
}
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: !url ? role || "menuitem" : role,
"data-role": "menuitem",
ref: localRef,
"data-testid": testId,
"aria-label": ariaLabel,
"aria-checked": ariaChecked,
tabIndex,
...disabledProps,
children: [
/* @__PURE__ */ jsxRuntime.jsxs(Stack, { direction: "row", justifyContent: "flex-start", alignItems: "center", children: [
icon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, className: styles.icon, "aria-hidden": true }),
/* @__PURE__ */ jsxRuntime.jsx("span", { className: styles.ellipsis, 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, { name: "keyboard", title: i18n.t("grafana-ui.menu-item.keyboard-shortcut-label", "Keyboard shortcut") }),
shortcut
] }),
hasSubMenu && /* @__PURE__ */ jsxRuntime.jsx(
SubMenu,
{
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$F = (theme) => {
return {
item: css.css({
background: "none",
cursor: "pointer",
whiteSpace: "nowrap",
color: theme.colors.text.primary,
display: "flex",
flexDirection: "column",
alignItems: "stretch",
justifyContent: "center",
padding: theme.spacing(0.5, 1.5),
minHeight: theme.spacing(4),
borderRadius: theme.shape.radius.default,
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": getFocusStyles(theme)
}),
active: css.css({
background: theme.colors.action.hover
}),
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,
color: theme.colors.text.secondary
}),
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),
color: theme.colors.text.secondary,
opacity: 0.7
}),
description: css.css({
...theme.typography.bodySmall,
color: theme.colors.text.secondary,
textAlign: "start"
}),
descriptionWithIcon: css.css({
marginLeft: theme.spacing(3)
}),
ellipsis: css.css({
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap"
})
};
};
const MenuComp = React__namespace.forwardRef(
({ header, children, ariaLabel, onOpen, onClose, onKeyDown, ...otherProps }, forwardedRef) => {
const styles = useStyles2(getStyles$E);
const localRef = React.useRef(null);
React.useImperativeHandle(forwardedRef, () => localRef.current);
const [handleKeys] = useMenuFocus({ isMenuOpen: true, localRef, onOpen, onClose, onKeyDown });
return /* @__PURE__ */ jsxRuntime.jsxs(
Box,
{
...otherProps,
"aria-label": ariaLabel,
backgroundColor: "elevated",
borderRadius: "default",
boxShadow: "z3",
display: "inline-block",
onKeyDown: handleKeys,
paddingX: 0.5,
paddingY: 0.5,
ref: localRef,
role: "menu",
tabIndex: -1,
children: [
header && /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
className: css.cx(
styles.header,
Boolean(children) && React__namespace.Children.toArray(children).length > 0 && styles.headerBorder
),
children: header
}
),
children
]
}
);
}
);
MenuComp.displayName = "Menu";
const Menu = Object.assign(MenuComp, {
Item: MenuItem,
Divider: MenuDivider,
Group: MenuGroup
});
const getStyles$E = (theme) => {
return {
header: css.css({
padding: theme.spacing(0.5, 0.5, 1, 0.5)
}),
headerBorder: css.css({
borderBottom: `1px solid ${theme.colors.border.weak}`,
marginBottom: theme.spacing(0.5)
})
};
};
function Portal(props) {
const { children, className, root, forwardedRef } = props;
const theme = useTheme2();
const node = React.useRef(null);
const portalRoot = root != null ? root : getPortalContainer();
if (!node.current) {
node.current = document.createElement("div");
if (className) {
node.current.className = className;
}
node.current.style.position = "relative";
node.current.style.zIndex = `${theme.zIndex.portal}`;
}
React.useLayoutEffect(() => {
if (node.current) {
portalRoot.appendChild(node.current);
}
return () => {
if (node.current) {
portalRoot.removeChild(node.current);
}
};
}, [portalRoot]);
return ReactDOM__default.default.createPortal(/* @__PURE__ */ jsxRuntime.jsx("div", { ref: forwardedRef, children }), node.current);
}
function getPortalContainer() {
var _a;
return (_a = window.document.getElementById("grafana-portal-container")) != null ? _a : document.body;
}
const RefForwardingPortal = React__namespace.forwardRef((props, ref) => {
return /* @__PURE__ */ jsxRuntime.jsx(Portal, { ...props, forwardedRef: ref });
});
RefForwardingPortal.displayName = "RefForwardingPortal";
const ContextMenu = React__namespace.memo(
({ x, y, onClose, focusOnOpen = true, renderMenuItems, renderHeader }) => {
const menuRef = React.useRef(null);
const [positionStyles, setPositionStyles] = React.useState({});
React.useLayoutEffect(() => {
const menuElement = menuRef.current;
if (menuElement) {
const rect = menuElement.getBoundingClientRect();
const OFFSET = 5;
const collisions = {
right: window.innerWidth < x + rect.width,
bottom: window.innerHeight < y + rect.height + OFFSET
};
setPositionStyles({
position: "fixed",
left: collisions.right ? x - rect.width - OFFSET : x - OFFSET,
top: Math.max(0, collisions.bottom ? y - rect.height - OFFSET : y + OFFSET)
});
}
}, [x, y]);
reactUse.useClickAway(menuRef, () => {
onClose == null ? void 0 : onClose();
});
const header = renderHeader == null ? void 0 : renderHeader();
const menuItems = renderMenuItems == null ? void 0 : renderMenuItems();
const onOpen = (setFocusedItem) => {
if (focusOnOpen) {
setFocusedItem(0);
}
};
const onKeyDown = (e) => {
if (e.key === "Escape") {
e.preventDefault();
e.stopPropagation();
onClose == null ? void 0 : onClose();
}
};
return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
Menu,
{
header,
ref: menuRef,
style: positionStyles,
ariaLabel: e2eSelectors.selectors.components.Menu.MenuComponent("Context"),
onOpen,
onClick: onClose,
onKeyDown,
children: menuItems
}
) });
}
);
ContextMenu.displayName = "ContextMenu";
const getFocusStyle = (theme) => css.css({
"&:focus": getFocusStyles(theme)
});
const sharedInputStyle = (theme, invalid = false) => {
const borderColor = invalid ? theme.colors.error.border : theme.components.input.borderColor;
const borderColorHover = invalid ? theme.colors.error.shade : theme.components.input.borderHover;
const background = theme.components.input.background;
const textColor = theme.components.input.text;
const autoFillBorder = theme.isDark ? "#2e2f35" : "#bab4ca";
return css.cx(
inputPadding(theme),
css.css({
background,
lineHeight: theme.typography.body.lineHeight,
fontSize: theme.typography.size.md,
color: textColor,
border: `1px solid ${borderColor}`,
"&:-webkit-autofill, &:-webkit-autofill:hover": {
/* Welcome to 2005. This is a HACK to get rid od Chromes default autofill styling */
boxShadow: `inset 0 0 0 1px rgba(255, 255, 255, 0), inset 0 0 0 100px ${background}!important`,
WebkitTextFillColor: `${textColor} !important`,
borderColor: autoFillBorder
},
"&:-webkit-autofill:focus": {
/* Welcome to 2005. This is a HACK to get rid od Chromes default autofill styling */
boxShadow: `0 0 0 2px ${theme.colors.background.primary}, 0 0 0px 4px ${theme.colors.primary.main}, inset 0 0 0 1px rgba(255, 255, 255, 0), inset 0 0 0 100px ${background}!important`,
WebkitTextFillColor: `${textColor} !important`
},
"&:hover": {
borderColor: borderColorHover
},
"&:focus": {
outline: "none"
},
"&:disabled": {
backgroundColor: theme.colors.action.disabledBackground,
color: theme.colors.action.disabledText,
border: `1px solid ${theme.colors.action.disabledBackground}`,
"&:hover": {
borderColor
}
},
"&::placeholder": {
color: theme.colors.text.disabled,
opacity: 1
}
})
);
};
const inputPadding = (theme) => {
return css.css({
padding: theme.spacing(0, 1, 0, 1)
});
};
function getPropertiesForButtonSize(size, theme) {
switch (size) {
case "sm":
return {
padding: 1,
fontSize: theme.typography.size.sm,
height: theme.components.height.sm
};
case "lg":
return {
padding: 3,
fontSize: theme.typography.size.lg,
height: theme.components.height.lg
};
case "md":
default:
return {
padding: 2,
fontSize: theme.typography.size.md,
height: theme.components.height.md
};
}
}
function getPlacement(placement) {
switch (placement) {
case "auto":
return "bottom";
case "auto-start":
return "bottom-start";
case "auto-end":
return "bottom-end";
default:
return placement != null ? placement : "bottom";
}
}
function buildTooltipTheme(theme, tooltipBg, toggletipBorder, tooltipText, tooltipPadding) {
return {
arrow: css.css({
fill: tooltipBg
}),
container: css.css({
backgroundColor: tooltipBg,
borderRadius: theme.shape.radius.default,
border: `1px solid ${toggletipBorder}`,
boxShadow: theme.shadows.z2,
color: tooltipText,
fontSize: theme.typography.bodySmall.fontSize,
padding: theme.spacing(tooltipPadding.topBottom, tooltipPadding.rightLeft),
[theme.transitions.handleMotion("no-preference", "reduce")]: {
transition: "opacity 0.3s"
},
zIndex: theme.zIndex.tooltip,
maxWidth: "400px",
overflowWrap: "break-word",
"&[data-popper-interactive='false']": {
pointerEvents: "none"
}
}),
headerClose: css.css({
color: theme.colors.text.secondary,
position: "absolute",
right: theme.spacing(1),
top: theme.spacing(1.5),
backgroundColor: "transparent",
border: 0
}),
header: css.css({
paddingTop: theme.spacing(1),
paddingBottom: theme.spacing(2)
}),
body: css.css({
paddingTop: theme.spacing(1),
paddingBottom: theme.spacing(1)
}),
footer: css.css({
paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(1)
})
};
}
const Tooltip = React.forwardRef(
({ children, theme, interactive, show, placement, content }, forwardedRef) => {
const arrowRef = React.useRef(null);
const [controlledVisible, setControlledVisible] = React.useState(show);
const isOpen = show != null ? show : controlledVisible;
const middleware = [
react.offset(8),
react.flip({
fallbackAxisSideDirection: "end",
// see https://floating-ui.com/docs/flip#combining-with-shift
crossAxis: false,
boundary: document.body
}),
react.shift(),
react.arrow({
element: arrowRef
})
];
const { context, refs, floatingStyles } = react.useFloating({
open: isOpen,
placement: getPlacement(placement),
onOpenChange: setControlledVisible,
middleware,
whileElementsMounted: react.autoUpdate
});
const tooltipId = React.useId();
const hover = react.useHover(context, {
handleClose: interactive ? react.safePolygon() : void 0,
move: false
});
const focus = react.useFocus(context);
const dismiss = react.useDismiss(context);
const { getReferenceProps, getFloatingProps } = react.useInteractions([dismiss, hover, focus]);
const contentIsFunction = typeof content === "function";
const styles = useStyles2(getStyles$D);
const style = styles[theme != null ? theme : "info"];
const handleRef = React.useCallback(
(ref) => {
refs.setReference(ref);
if (typeof forwardedRef === "function") {
forwardedRef(ref);
} else if (forwardedRef) {
forwardedRef.current = ref;
}
},
[forwardedRef, refs]
);
const childHasMatchingAriaLabel = "aria-label" in children.props && children.props["aria-label"] === content;
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
React.cloneElement(children, {
ref: handleRef,
tabIndex: 0,
// tooltip trigger should be keyboard focusable
"aria-describedby": !childHasMatchingAriaLabel && isOpen ? tooltipId : void 0,
...getReferenceProps()
}),
isOpen && /* @__PURE__ */ jsxRuntime.jsx(Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: refs.setFloating, style: floatingStyles, ...getFloatingProps(), children: [
/* @__PURE__ */ jsxRuntime.jsx(react.FloatingArrow, { className: style.arrow, ref: arrowRef, context }),
/* @__PURE__ */ jsxRuntime.jsxs(
"div",
{
"data-testid": e2eSelectors.selectors.components.Tooltip.container,
id: tooltipId,
role: "tooltip",
className: style.container,
children: [
typeof content === "string" && content,
React.isValidElement(content) && React.cloneElement(content),
contentIsFunction && content({})
]
}
)
] }) })
] });
}
);
Tooltip.displayName = "Tooltip";
const getStyles$D = (theme) => {
const info = buildTooltipTheme(
theme,
theme.components.tooltip.background,
theme.components.tooltip.background,
theme.components.tooltip.text,
{ topBottom: 0.5, rightLeft: 1 }
);
const error = buildTooltipTheme(
theme,
theme.colors.error.main,
theme.colors.error.main,
theme.colors.error.contrastText,
{ topBottom: 0.5, rightLeft: 1 }
);
return {
info,
["info-alt"]: info,
error
};
};
const Button = React__namespace.forwardRef(
({
variant = "primary",
size = "md",
fill = "solid",
icon,
fullWidth,
children,
className,
type = "button",
tooltip,
disabled,
tooltipPlacement,
iconPlacement = "left",
onClick,
...otherProps
}, ref) => {
const theme = useTheme2();
const styles = getButtonStyles({
theme,
size,
variant,
fill,
fullWidth,
iconOnly: !children
});
const buttonStyles = css.cx(
styles.button,
{
[styles.disabled]: disabled
},
className
);
const hasTooltip = Boolean(tooltip);
const iconComponent = icon && /* @__PURE__ */ jsxRuntime.jsx(IconRenderer, { icon, size, className: styles.icon });
const button = /* @__PURE__ */ jsxRuntime.jsxs(
"button",
{
className: buttonStyles,
type,
onClick: disabled ? void 0 : onClick,
...otherProps,
"aria-disabled": hasTooltip && disabled,
disabled: !hasTooltip && disabled,
ref: tooltip ? void 0 : ref,
children: [
iconPlacement === "left" && iconComponent,
children && /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles.content, children }),
iconPlacement === "right" && iconComponent
]
}
);
if (tooltip) {
return /* @__PURE__ */ jsxRuntime.jsx(Tooltip, { ref, content: tooltip, placement: tooltipPlacement, children: button });
}
return button;
}
);
Button.displayName = "Button";
const LinkButton = React__namespace.forwardRef(
({
variant = "primary",
size = "md",
fill = "solid",
icon,
fullWidth,
children,
className,
onBlur,
onFocus,
disabled,
tooltip,
tooltipPlacement,
...otherProps
}, ref) => {
const theme = useTheme2();
const styles = getButtonStyles({
theme,
fullWidth,
size,
variant,
fill,
iconOnly: !children
});
const linkButtonStyles = css.cx(
styles.button,
{
[css.css(styles.disabled, {
pointerEvents: "none"
})]: disabled
},
className
);
const button = /* @__PURE__ */ jsxRuntime.jsxs(
"a",
{
className: linkButtonStyles,
...otherProps,
tabIndex: disabled ? -1 : 0,
"aria-disabled": disabled,
ref: tooltip ? void 0 : ref,
children: [
/* @__PURE__ */ jsxRuntime.jsx(IconRenderer, { icon, size, className: styles.icon }),
children && /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles.content, children })
]
}
);
if (tooltip) {
return /* @__PURE__ */ jsxRuntime.jsx(Tooltip, { ref, content: tooltip, placement: tooltipPlacement, children: button });
}
return button;
}
);
LinkButton.displayName = "LinkButton";
const IconRenderer = ({ icon, size, className, iconType }) => {
if (!icon) {
return null;
}
if (React__namespace.isValidElement(icon)) {
return React__namespace.cloneElement(icon, {
className,
size
});
}
return /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, size, className, type: iconType });
};
const getButtonStyles = (props) => {
const { theme, variant, fill = "solid", size, iconOnly, fullWidth } = props;
const { height, padding, fontSize } = getPropertiesForButtonSize(size, theme);
const variantStyles = getPropertiesForVariant(theme, variant, fill);
const disabledStyles = getPropertiesForDisabled(theme, variant, fill);
const focusStyle = getFocusStyles(theme);
const paddingMinusBorder = theme.spacing.gridSize * padding - 1;
return {
button: css.css({
label: "button",
display: "inline-flex",
alignItems: "center",
gap: theme.spacing(1),
fontSize,
fontWeight: theme.typography.fontWeightMedium,
fontFamily: theme.typography.fontFamily,
padding: `0 ${paddingMinusBorder}px`,
height: theme.spacing(height),
// Deduct border from line-height for perfect vertical centering on windows and linux
lineHeight: `${theme.spacing.gridSize * height - 2}px`,
verticalAlign: "middle",
cursor: "pointer",
borderRadius: theme.shape.radius.default,
"&:focus": focusStyle,
"&:focus-visible": focusStyle,
"&:focus:not(:focus-visible)": getMouseFocusStyles(),
...fullWidth && {
flexGrow: 1,
justifyContent: "center"
},
...variantStyles,
":disabled": disabledStyles,
"&[disabled]": disabledStyles
}),
disabled: css.css(disabledStyles, {
"&:hover": css.css(disabledStyles)
}),
img: css.css({
width: "16px",
height: "16px",
margin: theme.spacing(0, 1, 0, 0.5)
}),
icon: iconOnly ? css.css({
// Important not to set margin bottom here as it would override internal icon bottom margin
marginRight: theme.spacing(-padding / 2),
marginLeft: theme.spacing(-padding / 2)
}) : void 0,
content: css.css({
display: "flex",
flexDirection: "row",
alignItems: "center",
whiteSpace: "nowrap",
overflow: "hidden",
height: "100%"
})
};
};
function getButtonVariantStyles(theme, color, fill) {
let outlineBorderColor = color.border;
let borderColor = "transparent";
let hoverBorderColor = "transparent";
if (color.name === "secondary") {
borderColor = color.border;
hoverBorderColor = theme.colors.emphasize(color.border, 0.25);
outlineBorderColor = theme.colors.border.strong;
}
if (fill === "outline") {
return {
background: "transparent",
color: color.text,
border: `1px solid ${outlineBorderColor}`,
transition: theme.transitions.create(["background-color", "border-color", "color"], {
duration: theme.transitions.duration.short
}),
"&:hover": {
background: color.transparent,
borderColor: theme.colors.emphasize(outlineBorderColor, 0.25),
color: color.text
}
};
}
if (fill === "text") {
return {
background: "transparent",
color: color.text,
border: "1px solid transparent",
transition: theme.transitions.create(["background-color", "color"], {
duration: theme.transitions.duration.short
}),
"&:focus": {
outline: "none",
textDecoration: "none"
},
"&:hover": {
background: color.transparent,
textDecoration: "none"
}
};
}
return {
background: color.main,
color: color.contrastText,
border: `1px solid ${borderColor}`,
transition: theme.transitions.create(["background-color", "box-shadow", "border-color", "color"], {
duration: theme.transitions.duration.short
}),
"&:hover": {
background: color.shade,
color: color.contrastText,
boxShadow: theme.shadows.z1,
borderColor: hoverBorderColor
}
};
}
function getPropertiesForDisabled(theme, variant, fill) {
const disabledStyles = {
cursor: "not-allowed",
boxShadow: "none",
color: theme.colors.text.disabled,
transition: "none"
};
if (fill === "text") {
return {
...disabledStyles,
background: "transparent",
border: `1px solid transparent`
};
}
if (fill === "outline") {
return {
...disabledStyles,
background: "transparent",
border: `1px solid ${theme.colors.border.weak}`
};
}
return {
...disabledStyles,