Drawer: Introduce a size property that set's width percentage and minWidth (#67809)

* Drawer: Introduce drawer size that sets width and min-width

* media queries

* Change large drawer to 75%

* Change news drawer to medium as the news items have better layout then with images on the side

* Tweaks and fixed inline drawer issue

* review fixes

* Deprecate inline, update mdx docs

* remove inline var
This commit is contained in:
Torkel Ödegaard 2023-05-05 11:31:02 +02:00 committed by GitHub
parent fcb14d2548
commit 20217db100
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 82 additions and 70 deletions

View File

@ -21,10 +21,20 @@ onClose = () => {
}; };
return ( return (
<Drawer title="This a Drawer" width="60%" onClose={this.onClose}> <Drawer title="This a Drawer" size="md" onClose={this.onClose}>
<div>Put your Drawer content here</div> <div>Put your Drawer content here</div>
</Drawer> </Drawer>
); );
``` ```
## Sizes
The Drawer supports 3 sizes: `sm`, `md`, and `lg`. This option defines a width in percentage and as well as a min-width.
- sm: width = 25vh and min-width = 384px
- md: width = 50vh and min-width = 568px
- lg: width = 75vh and min-width = 744px
## Props
<Props of={Drawer} /> <Props of={Drawer} />

View File

@ -22,7 +22,6 @@ const meta: ComponentMeta<typeof Drawer> = {
args: { args: {
closeOnMaskClick: true, closeOnMaskClick: true,
scrollableContent: false, scrollableContent: false,
width: '40%',
expandable: false, expandable: false,
subtitle: 'This is a subtitle.', subtitle: 'This is a subtitle.',
}, },
@ -142,42 +141,6 @@ LongContent.args = {
title: 'Drawer title with long content', title: 'Drawer title with long content',
}; };
export const InLine: ComponentStory<typeof Drawer> = (args) => {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<div
style={{
height: '300px',
width: '500px',
border: '1px solid white',
position: 'relative',
overflow: 'hidden',
}}
>
<Button onClick={() => setIsOpen(true)}>Open drawer</Button>
{isOpen && (
<Drawer {...args} onClose={() => setIsOpen(false)}>
<ul>
<li>this</li>
<li>is</li>
<li>a</li>
<li>list</li>
<li>of</li>
<li>menu</li>
<li>items</li>
</ul>
</Drawer>
)}
</div>
</>
);
};
InLine.args = {
title: 'Drawer title inline',
inline: true,
};
export const WithTabs: ComponentStory<typeof Drawer> = (args) => { export const WithTabs: ComponentStory<typeof Drawer> = (args) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [activeTab, setActiveTab] = useState('options'); const [activeTab, setActiveTab] = useState('options');
@ -206,6 +169,7 @@ export const WithTabs: ComponentStory<typeof Drawer> = (args) => {
</> </>
); );
}; };
WithTabs.args = { WithTabs.args = {
title: 'Drawer title with tabs', title: 'Drawer title with tabs',
}; };

View File

@ -1,9 +1,9 @@
import { css } from '@emotion/css'; import { css, cx } from '@emotion/css';
import { useDialog } from '@react-aria/dialog'; import { useDialog } from '@react-aria/dialog';
import { FocusScope } from '@react-aria/focus'; import { FocusScope } from '@react-aria/focus';
import { useOverlay } from '@react-aria/overlays'; import { useOverlay } from '@react-aria/overlays';
import RcDrawer from 'rc-drawer'; import RcDrawer from 'rc-drawer';
import React, { CSSProperties, ReactNode, useState, useEffect } from 'react'; import React, { ReactNode, useState, useEffect } from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
@ -20,12 +20,21 @@ export interface Props {
subtitle?: ReactNode; subtitle?: ReactNode;
/** Should the Drawer be closable by clicking on the mask, defaults to true */ /** Should the Drawer be closable by clicking on the mask, defaults to true */
closeOnMaskClick?: boolean; closeOnMaskClick?: boolean;
/** Render the drawer inside a container on the page */ /** @deprecated */
inline?: boolean; inline?: boolean;
/** Either a number in px or a string with unit postfix */ /**
* @deprecated use the size property instead
**/
width?: number | string; width?: number | string;
/** Should the Drawer be expandable to full width */ /** Should the Drawer be expandable to full width */
expandable?: boolean; expandable?: boolean;
/**
* Specifies the width and min-width.
* sm = width 25vw & min-width 384px
* md = width 50vw & min-width 568px
* lg = width 75vw & min-width 744px
**/
size?: 'sm' | 'md' | 'lg';
/** Tabs */ /** Tabs */
tabs?: React.ReactNode; tabs?: React.ReactNode;
/** Set to true if the component rendered within in drawer content has its own scroll */ /** Set to true if the component rendered within in drawer content has its own scroll */
@ -36,20 +45,19 @@ export interface Props {
export function Drawer({ export function Drawer({
children, children,
inline = false,
onClose, onClose,
closeOnMaskClick = true, closeOnMaskClick = true,
scrollableContent = false, scrollableContent = false,
title, title,
subtitle, subtitle,
width = '40%', width,
size = 'md',
expandable = false, expandable = false,
tabs, tabs,
}: Props) { }: Props) {
const drawerStyles = useStyles2(getStyles); const styles = useStyles2(getStyles);
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const currentWidth = isExpanded ? '100%' : width;
const overlayRef = React.useRef(null); const overlayRef = React.useRef(null);
const { dialogProps, titleProps } = useDialog({}, overlayRef); const { dialogProps, titleProps } = useDialog({}, overlayRef);
const { overlayProps } = useOverlay( const { overlayProps } = useOverlay(
@ -66,31 +74,29 @@ export function Drawer({
setIsOpen(true); setIsOpen(true);
}, []); }, []);
const content = <div className={drawerStyles.content}>{children}</div>; // deprecated width prop now defaults to empty string which make the size prop take over
const style: CSSProperties = {}; const fixedWidth = isExpanded ? '100%' : width ?? '';
if (inline) { const rootClass = cx(styles.drawer, !fixedWidth && styles.sizes[size]);
style.position = 'absolute'; const content = <div className={styles.content}>{children}</div>;
}
return ( return (
<RcDrawer <RcDrawer
open={isOpen} open={isOpen}
onClose={onClose} onClose={onClose}
placement="right" placement="right"
width={currentWidth} width={fixedWidth}
getContainer={inline ? undefined : 'body'} getContainer={'.main-view'}
style={style} className={styles.drawerContent}
className={drawerStyles.drawerContent} rootClassName={rootClass}
rootClassName={drawerStyles.drawer}
motion={{ motion={{
motionAppear: true, motionAppear: true,
motionName: drawerStyles.drawerMotion, motionName: styles.drawerMotion,
}} }}
maskClassName={drawerStyles.mask} maskClassName={styles.mask}
maskClosable={closeOnMaskClick} maskClosable={closeOnMaskClick}
maskMotion={{ maskMotion={{
motionAppear: true, motionAppear: true,
motionName: drawerStyles.maskMotion, motionName: styles.maskMotion,
}} }}
> >
<FocusScope restoreFocus contain autoFocus> <FocusScope restoreFocus contain autoFocus>
@ -100,14 +106,14 @@ export function Drawer({
? selectors.components.Drawer.General.title(title) ? selectors.components.Drawer.General.title(title)
: selectors.components.Drawer.General.title('no title') : selectors.components.Drawer.General.title('no title')
} }
className={drawerStyles.container} className={styles.container}
{...overlayProps} {...overlayProps}
{...dialogProps} {...dialogProps}
ref={overlayRef} ref={overlayRef}
> >
{typeof title === 'string' && ( {typeof title === 'string' && (
<div className={drawerStyles.header}> <div className={styles.header}>
<div className={drawerStyles.actions}> <div className={styles.actions}>
{expandable && !isExpanded && ( {expandable && !isExpanded && (
<IconButton <IconButton
name="angle-left" name="angle-left"
@ -131,16 +137,16 @@ export function Drawer({
aria-label={selectors.components.Drawer.General.close} aria-label={selectors.components.Drawer.General.close}
/> />
</div> </div>
<div className={drawerStyles.titleWrapper}> <div className={styles.titleWrapper}>
<h3 {...titleProps}>{title}</h3> <h3 {...titleProps}>{title}</h3>
{typeof subtitle === 'string' && <div className="muted">{subtitle}</div>} {typeof subtitle === 'string' && <div className="muted">{subtitle}</div>}
{typeof subtitle !== 'string' && subtitle} {typeof subtitle !== 'string' && subtitle}
{tabs && <div className={drawerStyles.tabsWrapper}>{tabs}</div>} {tabs && <div className={styles.tabsWrapper}>{tabs}</div>}
</div> </div>
</div> </div>
)} )}
{typeof title !== 'string' && title} {typeof title !== 'string' && title}
<div className={drawerStyles.contentScroll}> <div className={styles.contentScroll}>
{!scrollableContent ? content : <CustomScrollbar autoHeightMin="100%">{content}</CustomScrollbar>} {!scrollableContent ? content : <CustomScrollbar autoHeightMin="100%">{content}</CustomScrollbar>}
</div> </div>
</div> </div>
@ -162,10 +168,39 @@ const getStyles = (theme: GrafanaTheme2) => {
box-shadow: ${theme.shadows.z3}; box-shadow: ${theme.shadows.z3};
${theme.breakpoints.down('sm')} { ${theme.breakpoints.down('sm')} {
width: 100% !important; width: calc(100% - ${theme.spacing(2)}) !important;
min-width: 0 !important;
} }
} }
`, `,
sizes: {
sm: css({
'.rc-drawer-content-wrapper': {
label: 'drawer-sm',
width: '25vw',
minWidth: theme.spacing(48),
},
}),
md: css({
'.rc-drawer-content-wrapper': {
label: 'drawer-md',
width: '50vw',
minWidth: theme.spacing(66),
},
}),
lg: css({
'.rc-drawer-content-wrapper': {
label: 'drawer-lg',
width: '75vw',
minWidth: theme.spacing(93),
[theme.breakpoints.down('md')]: {
width: `calc(100% - ${theme.spacing(2)}) !important`,
minWidth: 0,
},
},
}),
},
drawerContent: css` drawerContent: css`
background-color: ${theme.colors.background.primary} !important; background-color: ${theme.colors.background.primary} !important;
display: flex; display: flex;

View File

@ -22,7 +22,12 @@ export function NewsContainer({ className }: NewsContainerProps) {
<> <>
<ToolbarButton className={className} onClick={onChildClick} iconOnly icon="rss" aria-label="News" /> <ToolbarButton className={className} onClick={onChildClick} iconOnly icon="rss" aria-label="News" />
{showNewsDrawer && ( {showNewsDrawer && (
<Drawer title={t('news.title', 'Latest from the blog')} scrollableContent onClose={onToggleShowNewsDrawer}> <Drawer
title={t('news.title', 'Latest from the blog')}
scrollableContent
onClose={onToggleShowNewsDrawer}
size="md"
>
<NewsWrapper feedUrl={DEFAULT_FEED_URL} /> <NewsWrapper feedUrl={DEFAULT_FEED_URL} />
</Drawer> </Drawer>
)} )}

View File

@ -72,7 +72,7 @@ export function HelpWizard({ panel, plugin, onClose }: Props) {
return ( return (
<Drawer <Drawer
title={`Get help with this panel`} title={`Get help with this panel`}
width="90%" size="lg"
onClose={onClose} onClose={onClose}
expandable expandable
scrollableContent scrollableContent

View File

@ -68,7 +68,6 @@ export const InspectContent = ({
<Drawer <Drawer
title={title} title={title}
subtitle={data && formatStats(data)} subtitle={data && formatStats(data)}
width="50%"
onClose={onClose} onClose={onClose}
expandable expandable
scrollableContent scrollableContent

View File

@ -131,7 +131,6 @@ export const SaveDashboardDrawer = ({ dashboard, onDismiss, onSaveSuccess, isCop
<Drawer <Drawer
title={title} title={title}
onClose={onDismiss} onClose={onDismiss}
width={'40%'}
subtitle={dashboard.title} subtitle={dashboard.title}
tabs={ tabs={
<TabsBar> <TabsBar>