mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
fcb14d2548
commit
20217db100
@ -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} />
|
||||||
|
@ -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',
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user