Drawer: Position under nav & minor redesign (#67824)

* Drawer: Redesign WIP

* Fix double margin

* Teak pos

* align paddings

* Hide toolbar actions when drawer is open

* deprecate expandable

* Updated test

* remove expandable var

* Added clickaway
This commit is contained in:
Torkel Ödegaard
2023-05-05 17:00:33 +02:00
committed by GitHub
parent 26fc359933
commit ae1a85b5ad
8 changed files with 75 additions and 87 deletions

View File

@@ -942,9 +942,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "2"]
],
"packages/grafana-ui/src/components/Drawer/Drawer.tsx:5381": [
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"],
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"],
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "2"]
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"]
],
"packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]

View File

@@ -41,8 +41,6 @@ e2e.scenario({
expectDrawerTabsAndContent();
expectDrawerExpandAndContract(viewPortWidth);
expectDrawerClose();
expectSubMenuScenario('Data');
@@ -107,30 +105,6 @@ const expectDrawerClose = () => {
e2e.components.Drawer.General.title(`Inspect: ${PANEL_UNDER_TEST}`).should('not.exist');
};
const expectDrawerExpandAndContract = (viewPortWidth: number) => {
// try expand button
// drawer should take up half the screen
e2e.components.Drawer.General.rcContentWrapper()
.should('be.visible')
.should('have.css', 'width', `${viewPortWidth / 2}px`);
e2e.components.Drawer.General.expand().click();
e2e.components.Drawer.General.contract().should('be.visible');
// drawer should take up the whole screen
e2e.components.Drawer.General.rcContentWrapper()
.should('be.visible')
.should('have.css', 'width', `${viewPortWidth}px`);
// try contract button
e2e.components.Drawer.General.contract().click();
e2e.components.Drawer.General.expand().should('be.visible');
e2e.components.Drawer.General.rcContentWrapper()
.should('be.visible')
.should('have.css', 'width', `${viewPortWidth / 2}px`);
};
const expectSubMenuScenario = (subMenu: string, tabTitle?: string) => {
tabTitle = tabTitle ?? subMenu;
// testing opening inspect drawer from sub menus under Inspect in header menu

View File

@@ -3,14 +3,17 @@ import { useDialog } from '@react-aria/dialog';
import { FocusScope } from '@react-aria/focus';
import { useOverlay } from '@react-aria/overlays';
import RcDrawer from 'rc-drawer';
import React, { ReactNode, useState, useEffect } from 'react';
import React, { ReactNode, useEffect } from 'react';
import { useClickAway } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { useStyles2 } from '../../themes';
import { Button } from '../Button';
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
import { IconButton } from '../IconButton/IconButton';
//import { IconButton } from '../IconButton/IconButton';
import { Text } from '../Text/Text';
export interface Props {
children: ReactNode;
@@ -26,7 +29,9 @@ export interface Props {
* @deprecated use the size property instead
**/
width?: number | string;
/** Should the Drawer be expandable to full width */
/**
* @deprecated use a large size instead if high width is needed
**/
expandable?: boolean;
/**
* Specifies the width and min-width.
@@ -52,39 +57,35 @@ export function Drawer({
subtitle,
width,
size = 'md',
expandable = false,
tabs,
}: Props) {
const styles = useStyles2(getStyles);
const [isExpanded, setIsExpanded] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const overlayRef = React.useRef(null);
const { dialogProps, titleProps } = useDialog({}, overlayRef);
const { overlayProps } = useOverlay(
{
isDismissable: false,
isOpen,
isOpen: true,
onClose,
},
overlayRef
);
// RcDrawer v4.x needs to be mounted in advance for animations to play.
useEffect(() => {
setIsOpen(true);
}, []);
// Adds body class while open so the toolbar nav can hide some actions while drawer is open
useBodyClassWhileOpen();
useClickAway(overlayRef, onClose);
// deprecated width prop now defaults to empty string which make the size prop take over
const fixedWidth = isExpanded ? '100%' : width ?? '';
const rootClass = cx(styles.drawer, !fixedWidth && styles.sizes[size]);
// Apply size styles (unless deprecated width prop is used)
const rootClass = cx(styles.drawer, !width && styles.sizes[size]);
const content = <div className={styles.content}>{children}</div>;
return (
<RcDrawer
open={isOpen}
open={true}
onClose={onClose}
placement="right"
width={fixedWidth}
// Important to set this to empty string so that the width can be controlled by the css
width={width ?? ''}
getContainer={'.main-view'}
className={styles.drawerContent}
rootClassName={rootClass}
@@ -112,35 +113,21 @@ export function Drawer({
ref={overlayRef}
>
{typeof title === 'string' && (
<div className={styles.header}>
<div className={cx(styles.header, Boolean(tabs) && styles.headerWithTabs)}>
<div className={styles.actions}>
{expandable && !isExpanded && (
<IconButton
name="angle-left"
size="xl"
onClick={() => setIsExpanded(true)}
aria-label={selectors.components.Drawer.General.expand}
/>
)}
{expandable && isExpanded && (
<IconButton
name="angle-right"
size="xl"
onClick={() => setIsExpanded(false)}
aria-label={selectors.components.Drawer.General.contract}
/>
)}
<IconButton
name="times"
size="xl"
<Button
icon="times"
variant="secondary"
fill="text"
onClick={onClose}
aria-label={selectors.components.Drawer.General.close}
/>
</div>
<div className={styles.titleWrapper}>
<h3 {...titleProps}>{title}</h3>
{typeof subtitle === 'string' && <div className="muted">{subtitle}</div>}
{typeof subtitle !== 'string' && subtitle}
<Text as="h3" {...titleProps}>
{title}
</Text>
{subtitle && <div className={styles.subtitle}>{subtitle}</div>}
{tabs && <div className={styles.tabsWrapper}>{tabs}</div>}
</div>
</div>
@@ -155,6 +142,20 @@ export function Drawer({
);
}
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: GrafanaTheme2) => {
return {
container: css`
@@ -164,6 +165,14 @@ const getStyles = (theme: GrafanaTheme2) => {
flex: 1 1 0;
`,
drawer: css`
.main-view & {
top: 81px;
}
.main-view--search-bar-hidden & {
top: 41px;
}
.rc-drawer-content-wrapper {
box-shadow: ${theme.shadows.z3};
@@ -185,7 +194,7 @@ const getStyles = (theme: GrafanaTheme2) => {
'.rc-drawer-content-wrapper': {
label: 'drawer-md',
width: '50vw',
minWidth: theme.spacing(66),
minWidth: theme.spacing(60),
},
}),
lg: css({
@@ -233,21 +242,26 @@ const getStyles = (theme: GrafanaTheme2) => {
}
}
`,
header: css`
background-color: ${theme.colors.background.canvas};
flex-grow: 0;
padding-top: ${theme.spacing(0.5)};
`,
actions: css`
display: flex;
align-items: baseline;
justify-content: flex-end;
`,
header: css({
flexGrow: 0,
padding: theme.spacing(3, 2),
borderBottom: `1px solid ${theme.colors.border.weak}`,
}),
headerWithTabs: css({
borderBottom: 'none',
}),
actions: css({
position: 'absolute',
right: theme.spacing(1),
top: theme.spacing(2),
}),
titleWrapper: css`
margin-bottom: ${theme.spacing(3)};
padding: ${theme.spacing(0, 1, 0, 3)};
overflow-wrap: break-word;
`,
subtitle: css({
color: theme.colors.text.secondary,
paddingTop: theme.spacing(1),
}),
content: css({
padding: theme.spacing(2),
height: '100%',
@@ -259,7 +273,7 @@ const getStyles = (theme: GrafanaTheme2) => {
}),
tabsWrapper: css({
paddingLeft: theme.spacing(2),
margin: theme.spacing(3, -1, -3, -3),
margin: theme.spacing(2, -1, -3, -3),
}),
};
};

View File

@@ -1,4 +1,5 @@
import { css, cx } from '@emotion/css';
import classNames from 'classnames';
import React, { PropsWithChildren } from 'react';
import { GrafanaTheme2, PageLayoutType } from '@grafana/data';
@@ -33,7 +34,7 @@ export function AppChrome({ children }: Props) {
// doesn't get re-mounted when chromeless goes from true to false.
return (
<main className="main-view">
<main className={classNames('main-view', searchBarHidden && 'main-view--search-bar-hidden')}>
{!state.chromeless && (
<div className={cx(styles.topNav)}>
{!searchBarHidden && <TopSearchBar />}

View File

@@ -82,7 +82,6 @@ const getStyles = (theme: GrafanaTheme2) => {
display: 'flex',
padding: theme.spacing(0, 1, 0, 2),
alignItems: 'center',
justifyContent: 'space-between',
}),
menuButton: css({
display: 'flex',
@@ -90,6 +89,7 @@ const getStyles = (theme: GrafanaTheme2) => {
marginRight: theme.spacing(1),
}),
actions: css({
label: 'NavToolbar-actions',
display: 'flex',
alignItems: 'center',
flexWrap: 'nowrap',
@@ -98,6 +98,10 @@ const getStyles = (theme: GrafanaTheme2) => {
flexGrow: 1,
gap: theme.spacing(0.5),
minWidth: 0,
'.body-drawer-open &': {
display: 'none',
},
}),
};
};

View File

@@ -74,7 +74,6 @@ export function HelpWizard({ panel, plugin, onClose }: Props) {
title={`Get help with this panel`}
size="lg"
onClose={onClose}
expandable
scrollableContent
subtitle={
<Stack direction="column" gap={1}>

View File

@@ -69,7 +69,6 @@ export const InspectContent = ({
title={title}
subtitle={data && formatStats(data)}
onClose={onClose}
expandable
scrollableContent
tabs={
<TabsBar>

View File

@@ -140,7 +140,6 @@ export const SaveDashboardDrawer = ({ dashboard, onDismiss, onSaveSuccess, isCop
)}
</TabsBar>
}
expandable
scrollableContent
>
{renderSaveBody()}