UNPKG

@grafana/ui

Version:
492 lines (485 loc) • 17.5 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var jsxRuntime = require('react/jsx-runtime'); var css = require('@emotion/css'); var React = require('react'); var reactUse = require('react-use'); var data = require('@grafana/data'); var e2eSelectors = require('@grafana/e2e-selectors'); var i18n = require('@grafana/i18n'); var ThemeContext = require('../../themes/ThemeContext.cjs'); var mixins = require('../../themes/mixins.cjs'); var DelayRender = require('../../utils/DelayRender.cjs'); var usePointerDistance = require('../../utils/usePointerDistance.cjs'); var ElementSelectionContext = require('../ElementSelectionContext/ElementSelectionContext.cjs'); var Icon = require('../Icon/Icon.cjs'); var LoadingBar = require('../LoadingBar/LoadingBar.cjs'); var Text = require('../Text/Text.cjs'); var Tooltip = require('../Tooltip/Tooltip.cjs'); var HoverWidget = require('./HoverWidget.cjs'); var PanelDescription = require('./PanelDescription.cjs'); var PanelMenu = require('./PanelMenu.cjs'); var PanelStatus = require('./PanelStatus.cjs'); var TitleItem = require('./TitleItem.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"; function PanelChrome({ width, height, children, padding = "md", title = "", description = "", displayMode = "default", titleItems, menu, dragClass, dragClassCancel, hoverHeader = false, hoverHeaderOffset, loadingState, statusMessage, statusMessageOnClick, leftItems, actions, selectionId, onCancelQuery, onOpenMenu, collapsible = false, collapsed, onToggleCollapse, onFocus, onMouseMove, onMouseEnter, onDragStart, showMenuAlways = false, subHeaderContent }) { const theme = ThemeContext.useTheme2(); const styles = ThemeContext.useStyles2(getStyles); const panelContentId = React.useId(); const panelTitleId = React.useId().replace(/:/g, "_"); const { isSelected, onSelect, isSelectable } = ElementSelectionContext.useElementSelection(selectionId); const pointerDistance = usePointerDistance.usePointerDistance(); const [subHeaderRef, { height: measuredSubHeaderHeight }] = reactUse.useMeasure(); const hasHeader = !hoverHeader; const [isOpen, toggleOpen] = reactUse.useToggle(true); const [selectableHighlight, setSelectableHighlight] = React.useState(false); const onHeaderEnter = React__namespace.useCallback(() => setSelectableHighlight(true), []); const onHeaderLeave = React__namespace.useCallback(() => setSelectableHighlight(false), []); if (collapsed === void 0) { collapsed = !isOpen; } const showOnHoverClass = showMenuAlways ? "always-show" : "show-on-hover"; const isPanelTransparent = displayMode === "transparent"; const headerHeight = getHeaderHeight(theme, hasHeader); const subHeaderHeight = Math.min(measuredSubHeaderHeight, headerHeight); const { contentStyle, innerWidth, innerHeight } = getContentStyle( padding, theme, headerHeight, collapsed, subHeaderHeight, height, width ); const headerStyles = { height: headerHeight, cursor: dragClass ? "move" : "auto" }; const containerStyles = { width, height: collapsed ? void 0 : height }; const [ref, { width: loadingBarWidth }] = reactUse.useMeasure(); if (leftItems) { actions = leftItems; } const testid = typeof title === "string" ? e2eSelectors.selectors.components.Panels.Panel.title(title) : "Panel"; const onPointerUp = React__namespace.useCallback( (evt) => { if (pointerDistance.check(evt) || dragClassCancel && evt.target instanceof Element && evt.target.closest(`.${dragClassCancel}`)) { return; } setTimeout(() => onSelect == null ? void 0 : onSelect(evt)); }, [dragClassCancel, onSelect, pointerDistance] ); const onPointerDown = React__namespace.useCallback( (evt) => { evt.stopPropagation(); pointerDistance.set(evt); onDragStart == null ? void 0 : onDragStart(evt); }, [pointerDistance, onDragStart] ); const onContentPointerDown = React__namespace.useCallback( (evt) => { if (evt.target instanceof Element && (evt.target.closest( 'button,a,canvas,svg,[role="button"],[role="combobox"],#grafana-portal-container,[role="columnheader"]' ) || evt.target.classList.contains("u-over") || evt.target.classList.contains("u-axis"))) { evt.stopPropagation(); return; } onSelect == null ? void 0 : onSelect(evt); }, [onSelect] ); const headerContent = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ !collapsible && title && /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.title, children: /* @__PURE__ */ jsxRuntime.jsx( Text.Text, { element: "h2", variant: "h6", truncate: true, title: typeof title === "string" ? title : void 0, id: panelTitleId, children: title } ) }), collapsible && /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.title, children: /* @__PURE__ */ jsxRuntime.jsx(Text.Text, { element: "h2", variant: "h6", children: /* @__PURE__ */ jsxRuntime.jsxs( "button", { type: "button", className: styles.clearButtonStyles, onClick: () => { toggleOpen(); if (onToggleCollapse) { onToggleCollapse(!collapsed); } }, "aria-expanded": !collapsed, "aria-controls": !collapsed ? panelContentId : void 0, children: [ /* @__PURE__ */ jsxRuntime.jsx( Icon.Icon, { name: !collapsed ? "angle-down" : "angle-right", "aria-hidden": !!title, "aria-label": !title ? i18n.t("grafana-ui.panel-chrome.aria-label-toggle-collapse", "toggle collapse panel") : void 0 } ), /* @__PURE__ */ jsxRuntime.jsx(Text.Text, { variant: "h6", truncate: true, id: panelTitleId, children: title }) ] } ) }) }), (titleItems || description) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: css.cx(styles.titleItems, dragClassCancel), "data-testid": "title-items-container", children: [ /* @__PURE__ */ jsxRuntime.jsx(PanelDescription.PanelDescription, { description, className: dragClassCancel }), titleItems ] }), loadingState === data.LoadingState.Streaming && /* @__PURE__ */ jsxRuntime.jsx( Tooltip.Tooltip, { content: onCancelQuery ? i18n.t("grafana-ui.panel-chrome.tooltip-stop-streaming", "Stop streaming") : i18n.t("grafana-ui.panel-chrome.tooltip-streaming", "Streaming"), children: /* @__PURE__ */ jsxRuntime.jsx(TitleItem.TitleItem, { className: dragClassCancel, "data-testid": "panel-streaming", onClick: onCancelQuery, children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { name: "circle-mono", size: "md", className: styles.streaming }) }) } ), loadingState === data.LoadingState.Loading && onCancelQuery && /* @__PURE__ */ jsxRuntime.jsx(DelayRender.DelayRender, { delay: 2e3, children: /* @__PURE__ */ jsxRuntime.jsx(Tooltip.Tooltip, { content: i18n.t("grafana-ui.panel-chrome.tooltip-cancel", "Cancel query"), children: /* @__PURE__ */ jsxRuntime.jsx( TitleItem.TitleItem, { className: css.cx(dragClassCancel, styles.pointer), "data-testid": "panel-cancel-query", onClick: onCancelQuery, children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { name: "sync-slash", size: "md" }) } ) }) }), !hoverHeader && /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.flexGrow }), actions && itemsRenderer(actions, (item) => item) ] }); const hasHeaderContent = title || description || titleItems || menu || dragClass || actions; return /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.container, children: /* @__PURE__ */ jsxRuntime.jsxs( "section", { className: css.cx( styles.panel, isPanelTransparent && styles.panelTransparent, isSelected && "dashboard-selected-element", !isSelected && isSelectable && selectableHighlight && "dashboard-selectable-element" ), style: containerStyles, "aria-labelledby": !!title ? panelTitleId : void 0, "data-testid": testid, tabIndex: 0, onFocus, onMouseMove, onMouseEnter, ref, children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.loadingBarContainer, children: loadingState === data.LoadingState.Loading ? /* @__PURE__ */ jsxRuntime.jsx( LoadingBar.LoadingBar, { width: loadingBarWidth, ariaLabel: i18n.t("grafana-ui.panel-chrome.ariaLabel-panel-loading", "Panel loading bar") } ) : null }), hoverHeader && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ hasHeaderContent && /* @__PURE__ */ jsxRuntime.jsx( HoverWidget.HoverWidget, { menu, title: typeof title === "string" ? title : void 0, dragClass, onDragStart, offset: hoverHeaderOffset, onOpenMenu, children: headerContent } ), statusMessage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.errorContainerFloating, children: /* @__PURE__ */ jsxRuntime.jsx( PanelStatus.PanelStatus, { message: statusMessage, onClick: statusMessageOnClick, ariaLabel: i18n.t("grafana-ui.panel-chrome.ariaLabel-panel-status", "Panel status") } ) }) ] }), hasHeader && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ /* @__PURE__ */ jsxRuntime.jsxs( "div", { className: css.cx(styles.headerContainer, dragClass), style: headerStyles, "data-testid": e2eSelectors.selectors.components.Panels.Panel.headerContainer, onPointerDown, onMouseEnter: isSelectable ? onHeaderEnter : void 0, onMouseLeave: isSelectable ? onHeaderLeave : void 0, onPointerUp, children: [ statusMessage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: dragClassCancel, children: /* @__PURE__ */ jsxRuntime.jsx( PanelStatus.PanelStatus, { message: statusMessage, onClick: statusMessageOnClick, ariaLabel: i18n.t("grafana-ui.panel-chrome.ariaLabel-panel-status", "Panel status") } ) }), headerContent, menu && /* @__PURE__ */ jsxRuntime.jsx( PanelMenu.PanelMenu, { menu, title: typeof title === "string" ? title : void 0, placement: "bottom-end", menuButtonClass: css.cx(styles.menuItem, dragClassCancel, showOnHoverClass), onOpenMenu, dragClassCancel } ) ] } ), !collapsed && subHeaderContent && /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.subHeader, ref: subHeaderRef, children: subHeaderContent }) ] }), !collapsed && /* @__PURE__ */ jsxRuntime.jsx( "div", { id: panelContentId, "data-testid": e2eSelectors.selectors.components.Panels.Panel.content, className: css.cx(styles.content, height === void 0 && styles.containNone), style: contentStyle, onPointerDown: onContentPointerDown, children: typeof children === "function" ? children(innerWidth, innerHeight) : children } ) ] } ) }); } const itemsRenderer = (items, renderer) => { const toRender = React__namespace.Children.toArray(items).filter(Boolean); return toRender.length > 0 ? renderer(toRender) : null; }; const getHeaderHeight = (theme, hasHeader) => { if (hasHeader) { return theme.spacing.gridSize * theme.components.panel.headerHeight; } return 0; }; const getContentStyle = (padding, theme, headerHeight, collapsed, subHeaderHeight, height, width) => { const chromePadding = (padding === "md" ? theme.components.panel.padding : 0) * theme.spacing.gridSize; const panelPadding = chromePadding * 2; const panelBorder = 1 * 2; let innerWidth = 0; if (width) { innerWidth = width - panelPadding - panelBorder; } let innerHeight = 0; if (height) { innerHeight = height - headerHeight - panelPadding - panelBorder - subHeaderHeight; } if (collapsed) { innerHeight = headerHeight; } const contentStyle = { padding: chromePadding }; return { contentStyle, innerWidth, innerHeight }; }; const getStyles = (theme) => { const { background, borderColor } = theme.components.panel; return { container: css.css({ height: "100%", position: "relative" }), panel: css.css({ label: "panel-container", backgroundColor: background, border: `1px solid ${borderColor}`, position: "unset", borderRadius: theme.shape.radius.lg, height: "100%", display: "flex", flexDirection: "column", overflow: "hidden", ".always-show": { background: "none", "&:focus-visible, &:hover": { background: theme.colors.secondary.shade } }, ".show-on-hover": { opacity: "0", visibility: "hidden" }, "&:focus-visible, &:hover": { // only show menu icon on hover or focused panel ".show-on-hover": { opacity: "1", visibility: "visible" } }, "&:focus-visible": mixins.getFocusStyles(theme), // The not:(:focus) clause is so that this rule is only applied when decendants are focused (important otherwise the hover header is visible when panel is clicked). "&:focus-within:not(:focus)": { ".show-on-hover": { visibility: "visible", opacity: "1" } } }), panelTransparent: css.css({ label: "panel-transparent-container", backgroundColor: "transparent", border: "1px solid transparent", boxSizing: "border-box", "&:hover": { border: `1px solid ${borderColor}` } }), loadingBarContainer: css.css({ label: "panel-loading-bar-container", position: "absolute", top: 0, width: "100%", // this is to force the loading bar container to create a new stacking context // otherwise, in webkit browsers on windows/linux, the aliasing of panel text changes when the loading bar is shown // see https://github.com/grafana/grafana/issues/88104 zIndex: 1 }), containNone: css.css({ contain: "none" }), content: css.css({ label: "panel-content", flexGrow: 1, contain: "size layout" }), headerContainer: css.css({ label: "panel-header", display: "flex", alignItems: "center", padding: theme.spacing(0, 1, 0, 1), gap: theme.spacing(1) }), subHeader: css.css({ label: "panel-sub-header", display: "flex", alignItems: "center", maxHeight: theme.spacing.gridSize * theme.components.panel.headerHeight, padding: theme.spacing(0, 1, 0, 1.5), overflow: "hidden", gap: theme.spacing(1) }), pointer: css.css({ cursor: "pointer" }), streaming: css.css({ label: "panel-streaming", marginRight: 0, color: theme.colors.success.text, "&:hover": { color: theme.colors.success.text } }), title: css.css({ label: "panel-title", display: "flex", minWidth: 0, paddingLeft: theme.spacing.x0_5, "& > h2": { minWidth: 0 } }), items: css.css({ display: "flex" }), item: css.css({ display: "flex", justifyContent: "center", alignItems: "center" }), hiddenMenu: css.css({ visibility: "hidden" }), menuItem: css.css({ label: "panel-menu", border: "none", background: theme.colors.secondary.main, "&:hover": { background: theme.colors.secondary.shade } }), errorContainerFloating: css.css({ label: "error-container", position: "absolute", left: 0, top: 0, zIndex: 1 }), titleItems: css.css({ display: "flex", height: "100%", alignItems: "center" }), clearButtonStyles: css.css({ alignItems: "center", display: "flex", gap: theme.spacing(0.5), background: "transparent", border: "none", padding: 0, maxWidth: "100%" }), flexGrow: css.css({ flexGrow: 1 }) }; }; exports.PanelChrome = PanelChrome; //# sourceMappingURL=PanelChrome.cjs.map