UNPKG

@grafana/ui

Version:
301 lines (298 loc) • 9.6 kB
import { jsx, jsxs } from 'react/jsx-runtime'; import { cx, css } from '@emotion/css'; import { useFloating, FloatingFocusManager } from '@floating-ui/react'; import RcDrawer from '@rc-component/drawer'; import { useId, useState, useCallback, useEffect } from 'react'; import { selectors } from '@grafana/e2e-selectors'; import { t } from '@grafana/i18n'; import { useStyles2 } from '../../themes/ThemeContext.mjs'; import { getDragStyles } from '../DragHandle/DragHandle.mjs'; import { IconButton } from '../IconButton/IconButton.mjs'; import { Stack } from '../Layout/Stack/Stack.mjs'; import { getPortalContainer } from '../Portal/Portal.mjs'; import { ScrollContainer } from '../ScrollContainer/ScrollContainer.mjs'; import { Text } from '../Text/Text.mjs'; "use strict"; const drawerSizes = { sm: { width: "25vw", minWidth: 384 }, md: { width: "50vw", minWidth: 568 }, lg: { width: "75vw", minWidth: 744 } }; function Drawer({ children, onClose, closeOnMaskClick = true, scrollableContent = true, title, subtitle, width, size = "md", tabs }) { var _a; const [drawerWidth, onMouseDown, onTouchStart] = useResizebleDrawer(); const styles = useStyles2(getStyles); const wrapperStyles = useStyles2(getWrapperStyles, size); const dragStyles = useStyles2(getDragStyles); const titleId = useId(); const { context, refs } = useFloating({ open: true, onOpenChange: (open) => { if (!open) { onClose == null ? void 0 : onClose(); } } }); useBodyClassWhileOpen(); const content = /* @__PURE__ */ jsx("div", { className: styles.content, children }); const overrideWidth = (_a = drawerWidth != null ? drawerWidth : width) != null ? _a : drawerSizes[size].width; const minWidth = drawerSizes[size].minWidth; return /* @__PURE__ */ jsx( RcDrawer, { open: true, onClose, placement: "right", getContainer: ".main-view", className: styles.drawerContent, rootClassName: styles.drawer, classNames: { wrapper: wrapperStyles }, styles: { wrapper: { width: overrideWidth, minWidth } }, "aria-label": typeof title === "string" ? selectors.components.Drawer.General.title(title) : void 0, "aria-labelledby": title ? titleId : void 0, width: "", motion: { motionAppear: true, motionName: styles.drawerMotion }, maskClassName: styles.mask, maskClosable: closeOnMaskClick, maskMotion: { motionAppear: true, motionName: styles.maskMotion }, autoFocus: false, children: /* @__PURE__ */ jsx(FloatingFocusManager, { context, modal: true, getInsideElements: () => [getPortalContainer()], children: /* @__PURE__ */ jsxs("div", { className: styles.container, ref: refs.setFloating, children: [ /* @__PURE__ */ jsx( "div", { className: cx(dragStyles.dragHandleVertical, styles.resizer), onMouseDown, onTouchStart } ), /* @__PURE__ */ jsxs("div", { className: cx(styles.header, Boolean(tabs) && styles.headerWithTabs), children: [ /* @__PURE__ */ jsx("div", { className: styles.actions, children: /* @__PURE__ */ jsx( IconButton, { name: "times", variant: "secondary", onClick: onClose, "data-testid": selectors.components.Drawer.General.close, tooltip: t(`grafana-ui.drawer.close`, "Close") } ) }), typeof title === "string" ? /* @__PURE__ */ jsxs(Stack, { direction: "column", children: [ /* @__PURE__ */ jsx(Text, { element: "h3", id: titleId, truncate: true, children: title }), subtitle && /* @__PURE__ */ jsx("div", { className: styles.subtitle, "data-testid": selectors.components.Drawer.General.subtitle, children: subtitle }) ] }) : /* @__PURE__ */ jsx("div", { id: titleId, children: title }), tabs && /* @__PURE__ */ jsx("div", { className: styles.tabsWrapper, children: tabs }) ] }), !scrollableContent ? content : /* @__PURE__ */ jsx(ScrollContainer, { showScrollIndicators: true, children: content }) ] }) }) } ); } function useResizebleDrawer() { const [drawerWidth, setDrawerWidth] = useState(void 0); const onMouseMove = useCallback((e) => { setDrawerWidth(getCustomDrawerWidth(e.clientX)); }, []); const onTouchMove = useCallback((e) => { const touch = e.touches[0]; setDrawerWidth(getCustomDrawerWidth(touch.clientX)); }, []); const onMouseUp = useCallback( (e) => { document.removeEventListener("mousemove", onMouseMove); document.removeEventListener("mouseup", onMouseUp); }, [onMouseMove] ); const onTouchEnd = useCallback( (e) => { document.removeEventListener("touchmove", onTouchMove); document.removeEventListener("touchend", onTouchEnd); }, [onTouchMove] ); function onMouseDown(e) { e.stopPropagation(); e.preventDefault(); document.addEventListener("mousemove", onMouseMove); document.addEventListener("mouseup", onMouseUp); } function onTouchStart(e) { e.stopPropagation(); e.preventDefault(); document.addEventListener("touchmove", onTouchMove); document.addEventListener("touchend", onTouchEnd); } return [drawerWidth, onMouseDown, onTouchStart]; } function getCustomDrawerWidth(clientX) { let offsetRight = document.body.offsetWidth - (clientX - document.body.offsetLeft); let widthPercent = Math.min(offsetRight / document.body.clientWidth * 100, 98).toFixed(2); return `${widthPercent}vw`; } function useBodyClassWhileOpen() { useEffect(() => { if (!document.body) { return; } document.body.classList.add("body-drawer-open"); return () => { document.body.classList.remove("body-drawer-open"); }; }, []); } const getStyles = (theme) => { var _a, _b; return { container: css({ display: "flex", flexDirection: "column", height: "100%", flex: "1 1 0", minHeight: "100%", position: "relative" }), drawer: css({ inset: 0, position: "fixed", zIndex: theme.zIndex.modalBackdrop, pointerEvents: "none", ".rc-drawer-content-wrapper": { boxShadow: theme.shadows.z3 } }), drawerContent: css({ backgroundColor: theme.colors.background.primary, display: "flex", flexDirection: "column", height: "100%", pointerEvents: "auto", width: "100%" }), drawerMotion: css({ "&-appear": { [theme.transitions.handleMotion("no-preference")]: { transform: "translateX(100%)", transition: "none" }, [theme.transitions.handleMotion("reduce")]: { opacity: 0 }, "&-active": { [theme.transitions.handleMotion("no-preference")]: { transform: "translateX(0)", transition: theme.transitions.create("transform") }, [theme.transitions.handleMotion("reduce")]: { transition: `opacity 0.2s ease-in-out`, opacity: 1 } } } }), // we want the mask itself to span the whole page including the top bar // this ensures trying to click something in the top bar will close the drawer correctly // but we don't want the backdrop styling to apply over the top bar as it looks weird // instead have a child pseudo element to apply the backdrop styling below the top bar mask: css({ inset: 0, pointerEvents: "auto", position: "fixed", zIndex: theme.zIndex.modalBackdrop, "&:before": { backgroundColor: theme.components.overlay.background, bottom: 0, content: '""', left: 0, position: "fixed", right: 0, top: 0 } }), maskMotion: css({ "&-appear": { opacity: 0, "&-active": { opacity: 1, [theme.transitions.handleMotion("no-preference", "reduce")]: { transition: theme.transitions.create("opacity") } } } }), header: css({ label: "drawer-header", flexGrow: 0, padding: theme.spacing(2, 2, 3), borderBottom: `1px solid ${theme.colors.border.weak}` }), headerWithTabs: css({ borderBottom: "none" }), actions: css({ position: "absolute", right: theme.spacing(1), top: theme.spacing(1) }), subtitle: css({ label: "drawer-subtitle", color: theme.colors.text.secondary }), content: css({ padding: theme.spacing((_b = (_a = theme.components.drawer) == null ? void 0 : _a.padding) != null ? _b : 2), height: "100%", flexGrow: 1, minHeight: 0 }), tabsWrapper: css({ label: "drawer-tabs", paddingLeft: theme.spacing(2), margin: theme.spacing(1, -1, -3, -3) }), resizer: css({ top: 0, left: theme.spacing(-1), bottom: 0, position: "absolute", zIndex: theme.zIndex.modal }) }; }; function getWrapperStyles(theme, size) { return css({ bottom: 0, label: `drawer-content-wrapper-${size}`, position: "absolute", right: 0, top: 0, zIndex: theme.zIndex.modalBackdrop, [theme.breakpoints.down("md")]: { width: `calc(100% - ${theme.spacing(2)}) !important`, minWidth: "0 !important" } }); } export { Drawer }; //# sourceMappingURL=Drawer.mjs.map