grafana/ui: Drawer component (#20078)
* basic setup * onclose fix * styling and inline drawer * add options and mdx * remove blank line * disable pushing page when opening drawer, re add mask opacity * small fixes from pr review * Add knobs and story with long content * set redone markup and made title sticky * Minor style update * Minor style updates 2 * More explicit width prop * fix import
This commit is contained in:
@@ -39,6 +39,7 @@
|
|||||||
"lodash": "4.17.15",
|
"lodash": "4.17.15",
|
||||||
"moment": "2.24.0",
|
"moment": "2.24.0",
|
||||||
"papaparse": "4.6.3",
|
"papaparse": "4.6.3",
|
||||||
|
"rc-drawer": "3.0.2",
|
||||||
"rc-time-picker": "^3.7.2",
|
"rc-time-picker": "^3.7.2",
|
||||||
"react": "16.8.6",
|
"react": "16.8.6",
|
||||||
"react-calendar": "2.18.1",
|
"react-calendar": "2.18.1",
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { Props } from '@storybook/addon-docs/blocks';
|
||||||
|
import { Drawer } from './Drawer';
|
||||||
|
|
||||||
|
<Props of={Drawer} />
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { text } from '@storybook/addon-knobs';
|
||||||
|
import { Drawer } from './Drawer';
|
||||||
|
import { UseState } from '../../utils/storybook/UseState';
|
||||||
|
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||||
|
import mdx from './Drawer.mdx';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'UI/Drawer',
|
||||||
|
component: Drawer,
|
||||||
|
decorators: [withCenteredStory],
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
page: mdx,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const global = () => {
|
||||||
|
const drawerTitle = text('title', 'Storybook');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UseState initialState={{ isOpen: false }}>
|
||||||
|
{(state, updateValue) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{ border: '1px solid gray', borderRadius: '4px', padding: '10px', cursor: 'pointer' }}
|
||||||
|
onClick={() => updateValue({ isOpen: !state.isOpen })}
|
||||||
|
>
|
||||||
|
Open drawer
|
||||||
|
</div>
|
||||||
|
{state.isOpen && (
|
||||||
|
<Drawer
|
||||||
|
title={drawerTitle}
|
||||||
|
onClose={() => {
|
||||||
|
updateValue({ isOpen: !state.isOpen });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ul>
|
||||||
|
<li>this</li>
|
||||||
|
<li>is</li>
|
||||||
|
<li>a</li>
|
||||||
|
<li>list</li>
|
||||||
|
<li>of</li>
|
||||||
|
<li>menu</li>
|
||||||
|
<li>items</li>
|
||||||
|
</ul>
|
||||||
|
</Drawer>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</UseState>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const longContent = () => {
|
||||||
|
return (
|
||||||
|
<UseState initialState={{ isOpen: true }}>
|
||||||
|
{(state, updateValue) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{ border: '1px solid gray', borderRadius: '4px', padding: '10px', cursor: 'pointer' }}
|
||||||
|
onClick={() => updateValue({ isOpen: !state.isOpen })}
|
||||||
|
>
|
||||||
|
Open drawer
|
||||||
|
</div>
|
||||||
|
{state.isOpen && (
|
||||||
|
<Drawer
|
||||||
|
title="Drawer with long content"
|
||||||
|
onClose={() => {
|
||||||
|
updateValue({ isOpen: !state.isOpen });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
|
||||||
|
dolore magna aliqua. Iaculis nunc sed augue lacus viverra vitae. Malesuada pellentesque elit eget
|
||||||
|
gravida cum sociis. Pretium vulputate sapien nec sagittis aliquam malesuada bibendum arcu. Cras
|
||||||
|
adipiscing enim eu turpis egestas. Ut lectus arcu bibendum at varius. Nulla pellentesque dignissim
|
||||||
|
enim sit amet venenatis urna. Tempus urna et pharetra pharetra massa massa ultricies mi quis. Vitae
|
||||||
|
congue mauris rhoncus aenean. Enim ut tellus elementum sagittis vitae et.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Arcu non odio euismod lacinia at quis risus sed vulputate. Sit amet consectetur adipiscing elit ut.
|
||||||
|
Dictum fusce ut placerat orci nulla pellentesque dignissim. Lectus nulla at volutpat diam ut venenatis
|
||||||
|
tellus. Sed cras ornare arcu dui. Eget mauris pharetra et ultrices neque ornare aenean euismod. Mi
|
||||||
|
quis hendrerit dolor magna. Commodo viverra maecenas accumsan lacus vel facilisis. Eget mi proin sed
|
||||||
|
libero enim sed. Magna ac placerat vestibulum lectus mauris ultrices eros in. Mattis nunc sed blandit
|
||||||
|
libero volutpat.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Cursus in hac habitasse platea dictumst quisque sagittis purus. Viverra adipiscing at in tellus.
|
||||||
|
Semper eget duis at tellus at urna condimentum. Egestas fringilla phasellus faucibus scelerisque
|
||||||
|
eleifend. Sem nulla pharetra diam sit amet nisl. Ut ornare lectus sit amet est placerat in egestas
|
||||||
|
erat. Id neque aliquam vestibulum morbi blandit cursus risus. In iaculis nunc sed augue. Eu volutpat
|
||||||
|
odio facilisis mauris sit. Quisque egestas diam in arcu cursus euismod. At quis risus sed vulputate
|
||||||
|
odio ut enim blandit volutpat. Cursus risus at ultrices mi tempus imperdiet nulla malesuada. Sed
|
||||||
|
adipiscing diam donec adipiscing tristique risus nec. Id neque aliquam vestibulum morbi. Pretium nibh
|
||||||
|
ipsum consequat nisl vel pretium lectus quam. Platea dictumst quisque sagittis purus sit. Nascetur
|
||||||
|
ridiculus mus mauris vitae ultricies leo.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Cursus in hac habitasse platea dictumst quisque sagittis purus. Viverra adipiscing at in tellus.
|
||||||
|
Semper eget duis at tellus at urna condimentum. Egestas fringilla phasellus faucibus scelerisque
|
||||||
|
eleifend. Sem nulla pharetra diam sit amet nisl. Ut ornare lectus sit amet est placerat in egestas
|
||||||
|
erat. Id neque aliquam vestibulum morbi blandit cursus risus. In iaculis nunc sed augue. Eu volutpat
|
||||||
|
odio facilisis mauris sit. Quisque egestas diam in arcu cursus euismod. At quis risus sed vulputate
|
||||||
|
odio ut enim blandit volutpat. Cursus risus at ultrices mi tempus imperdiet nulla malesuada. Sed
|
||||||
|
adipiscing diam donec adipiscing tristique risus nec. Id neque aliquam vestibulum morbi. Pretium nibh
|
||||||
|
ipsum consequat nisl vel pretium lectus quam. Platea dictumst quisque sagittis purus sit. Nascetur
|
||||||
|
ridiculus mus mauris vitae ultricies leo.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Cursus in hac habitasse platea dictumst quisque sagittis purus. Viverra adipiscing at in tellus.
|
||||||
|
Semper eget duis at tellus at urna condimentum. Egestas fringilla phasellus faucibus scelerisque
|
||||||
|
eleifend. Sem nulla pharetra diam sit amet nisl. Ut ornare lectus sit amet est placerat in egestas
|
||||||
|
erat. Id neque aliquam vestibulum morbi blandit cursus risus. In iaculis nunc sed augue. Eu volutpat
|
||||||
|
odio facilisis mauris sit. Quisque egestas diam in arcu cursus euismod. At quis risus sed vulputate
|
||||||
|
odio ut enim blandit volutpat. Cursus risus at ultrices mi tempus imperdiet nulla malesuada. Sed
|
||||||
|
adipiscing diam donec adipiscing tristique risus nec. Id neque aliquam vestibulum morbi. Pretium nibh
|
||||||
|
ipsum consequat nisl vel pretium lectus quam. Platea dictumst quisque sagittis purus sit. Nascetur
|
||||||
|
ridiculus mus mauris vitae ultricies leo.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Cursus in hac habitasse platea dictumst quisque sagittis purus. Viverra adipiscing at in tellus.
|
||||||
|
Semper eget duis at tellus at urna condimentum. Egestas fringilla phasellus faucibus scelerisque
|
||||||
|
eleifend. Sem nulla pharetra diam sit amet nisl. Ut ornare lectus sit amet est placerat in egestas
|
||||||
|
erat. Id neque aliquam vestibulum morbi blandit cursus risus. In iaculis nunc sed augue. Eu volutpat
|
||||||
|
odio facilisis mauris sit. Quisque egestas diam in arcu cursus euismod. At quis risus sed vulputate
|
||||||
|
odio ut enim blandit volutpat. Cursus risus at ultrices mi tempus imperdiet nulla malesuada. Sed
|
||||||
|
adipiscing diam donec adipiscing tristique risus nec. Id neque aliquam vestibulum morbi. Pretium nibh
|
||||||
|
ipsum consequat nisl vel pretium lectus quam. Platea dictumst quisque sagittis purus sit. Nascetur
|
||||||
|
ridiculus mus mauris vitae ultricies leo.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Cursus in hac habitasse platea dictumst quisque sagittis purus. Viverra adipiscing at in tellus.
|
||||||
|
Semper eget duis at tellus at urna condimentum. Egestas fringilla phasellus faucibus scelerisque
|
||||||
|
eleifend. Sem nulla pharetra diam sit amet nisl. Ut ornare lectus sit amet est placerat in egestas
|
||||||
|
erat. Id neque aliquam vestibulum morbi blandit cursus risus. In iaculis nunc sed augue. Eu volutpat
|
||||||
|
odio facilisis mauris sit. Quisque egestas diam in arcu cursus euismod. At quis risus sed vulputate
|
||||||
|
odio ut enim blandit volutpat. Cursus risus at ultrices mi tempus imperdiet nulla malesuada. Sed
|
||||||
|
adipiscing diam donec adipiscing tristique risus nec. Id neque aliquam vestibulum morbi. Pretium nibh
|
||||||
|
ipsum consequat nisl vel pretium lectus quam. Platea dictumst quisque sagittis purus sit. Nascetur
|
||||||
|
ridiculus mus mauris vitae ultricies leo.
|
||||||
|
</p>
|
||||||
|
</Drawer>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</UseState>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const inLine = () => {
|
||||||
|
const drawerTitle = text('title', 'Storybook');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UseState initialState={{ isOpen: false }}>
|
||||||
|
{(state, updateValue) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: '300px',
|
||||||
|
width: '500px',
|
||||||
|
border: '1px solid white',
|
||||||
|
position: 'relative',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{ border: '1px solid gray', borderRadius: '4px', padding: '10px', cursor: 'pointer' }}
|
||||||
|
onClick={() => updateValue({ isOpen: !state.isOpen })}
|
||||||
|
>
|
||||||
|
Open drawer
|
||||||
|
</div>
|
||||||
|
{state.isOpen && (
|
||||||
|
<Drawer
|
||||||
|
inline={true}
|
||||||
|
title={drawerTitle}
|
||||||
|
onClose={() => {
|
||||||
|
updateValue({ isOpen: !state.isOpen });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</UseState>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import React, { CSSProperties, FC, ReactNode } from 'react';
|
||||||
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
|
import RcDrawer from 'rc-drawer';
|
||||||
|
import { css } from 'emotion';
|
||||||
|
import { stylesFactory, useTheme, selectThemeVariant } from '../../themes';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: ReactNode;
|
||||||
|
/** Title shown at the top of the drawer */
|
||||||
|
title?: string;
|
||||||
|
/** Should the Drawer be closable by clicking on the mask */
|
||||||
|
closeOnMaskClick?: boolean;
|
||||||
|
/** Render the drawer inside a container on the page */
|
||||||
|
inline?: boolean;
|
||||||
|
/** Either a number in px or a string with unit postfix */
|
||||||
|
width?: number | string;
|
||||||
|
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
|
const closeButtonWidth = '50px';
|
||||||
|
const borderColor = selectThemeVariant(
|
||||||
|
{
|
||||||
|
light: theme.colors.gray4,
|
||||||
|
dark: theme.colors.dark9,
|
||||||
|
},
|
||||||
|
theme.type
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
drawer: css`
|
||||||
|
.drawer-content {
|
||||||
|
background-color: ${theme.colors.bodyBg};
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
titleWrapper: css`
|
||||||
|
font-size: ${theme.typography.size.lg};
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-bottom: 1px solid ${borderColor};
|
||||||
|
padding: ${theme.spacing.sm} 0 ${theme.spacing.sm} ${theme.spacing.md};
|
||||||
|
background-color: ${theme.colors.bodyBg};
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
`,
|
||||||
|
close: css`
|
||||||
|
cursor: pointer;
|
||||||
|
width: ${closeButtonWidth};
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
justify-content: center;
|
||||||
|
`,
|
||||||
|
content: css`
|
||||||
|
padding: ${theme.spacing.md};
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Drawer: FC<Props> = ({
|
||||||
|
children,
|
||||||
|
inline = false,
|
||||||
|
onClose,
|
||||||
|
closeOnMaskClick = false,
|
||||||
|
title,
|
||||||
|
width = '40%',
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const drawerStyles = getStyles(theme);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RcDrawer
|
||||||
|
level={null}
|
||||||
|
handler={false}
|
||||||
|
open={true}
|
||||||
|
onClose={onClose}
|
||||||
|
maskClosable={closeOnMaskClick}
|
||||||
|
placement="right"
|
||||||
|
width={width}
|
||||||
|
getContainer={inline ? false : 'body'}
|
||||||
|
style={{ position: `${inline && 'absolute'}` } as CSSProperties}
|
||||||
|
className={drawerStyles.drawer}
|
||||||
|
>
|
||||||
|
<div className={drawerStyles.titleWrapper}>
|
||||||
|
<div>{title}</div>
|
||||||
|
<div className={drawerStyles.close} onClick={onClose}>
|
||||||
|
<i className="fa fa-close" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={drawerStyles.content}>{children}</div>
|
||||||
|
</RcDrawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
// Need to import this to get default styles from rc-drawer
|
||||||
|
@import '../../node_modules/rc-drawer/assets/index.css';
|
||||||
@@ -15,3 +15,4 @@
|
|||||||
@import 'RefreshPicker/RefreshPicker';
|
@import 'RefreshPicker/RefreshPicker';
|
||||||
@import 'TimePicker/TimePicker';
|
@import 'TimePicker/TimePicker';
|
||||||
@import 'TimePicker/TimeOfDayPicker';
|
@import 'TimePicker/TimeOfDayPicker';
|
||||||
|
@import 'Drawer/Drawer';
|
||||||
|
|||||||
@@ -5862,6 +5862,11 @@ caniuse-api@^3.0.0:
|
|||||||
lodash.memoize "^4.1.2"
|
lodash.memoize "^4.1.2"
|
||||||
lodash.uniq "^4.5.0"
|
lodash.uniq "^4.5.0"
|
||||||
|
|
||||||
|
caniuse-db@1.0.30000772:
|
||||||
|
version "1.0.30000772"
|
||||||
|
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000772.tgz#51aae891768286eade4a3d8319ea76d6a01b512b"
|
||||||
|
integrity sha1-UarokXaChureSj2DGep21qAbUSs=
|
||||||
|
|
||||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000939, caniuse-lite@^1.0.30000947, caniuse-lite@^1.0.30000989, caniuse-lite@^1.0.30000999:
|
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000939, caniuse-lite@^1.0.30000947, caniuse-lite@^1.0.30000989, caniuse-lite@^1.0.30000999:
|
||||||
version "1.0.30000999"
|
version "1.0.30000999"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000999.tgz#427253a69ad7bea4aa8d8345687b8eec51ca0e43"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000999.tgz#427253a69ad7bea4aa8d8345687b8eec51ca0e43"
|
||||||
@@ -16835,6 +16840,16 @@ rc-cascader@0.17.5:
|
|||||||
shallow-equal "^1.0.0"
|
shallow-equal "^1.0.0"
|
||||||
warning "^4.0.1"
|
warning "^4.0.1"
|
||||||
|
|
||||||
|
rc-drawer@3.0.2:
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/rc-drawer/-/rc-drawer-3.0.2.tgz#1c42b2b7790040344f8f05f1d132b1ef0e97b783"
|
||||||
|
integrity sha512-oPScGXB/8/ov9gEFLxPH8RBv/9jLTZboZtyF/GgrrnCAvbFwUxXdELH6n6XIowmuDKKvTGIMgZdnao0T46Yv3A==
|
||||||
|
dependencies:
|
||||||
|
babel-runtime "^6.26.0"
|
||||||
|
classnames "^2.2.6"
|
||||||
|
rc-util "^4.11.2"
|
||||||
|
react-lifecycles-compat "^3.0.4"
|
||||||
|
|
||||||
rc-time-picker@^3.7.2:
|
rc-time-picker@^3.7.2:
|
||||||
version "3.7.2"
|
version "3.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/rc-time-picker/-/rc-time-picker-3.7.2.tgz#fabe5501adf1374d31a2d3b47f1ba89fc2dc2467"
|
resolved "https://registry.yarnpkg.com/rc-time-picker/-/rc-time-picker-3.7.2.tgz#fabe5501adf1374d31a2d3b47f1ba89fc2dc2467"
|
||||||
@@ -16860,7 +16875,7 @@ rc-trigger@^2.2.0:
|
|||||||
rc-util "^4.4.0"
|
rc-util "^4.4.0"
|
||||||
react-lifecycles-compat "^3.0.4"
|
react-lifecycles-compat "^3.0.4"
|
||||||
|
|
||||||
rc-util@^4.0.4, rc-util@^4.4.0, rc-util@^4.8.0:
|
rc-util@^4.0.4, rc-util@^4.11.2, rc-util@^4.4.0, rc-util@^4.8.0:
|
||||||
version "4.13.0"
|
version "4.13.0"
|
||||||
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-4.13.0.tgz#75682e50a934e7d32ada7ee48fc9f5b20fc0143f"
|
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-4.13.0.tgz#75682e50a934e7d32ada7ee48fc9f5b20fc0143f"
|
||||||
integrity sha512-rjfPy+afc2n40APHp6GYScXfgwHuUnYLz/4SCEWRaF8CHXKR8xw598LtPA36J3fEXENuMm6liO/CoKBoSrYCDw==
|
integrity sha512-rjfPy+afc2n40APHp6GYScXfgwHuUnYLz/4SCEWRaF8CHXKR8xw598LtPA36J3fEXENuMm6liO/CoKBoSrYCDw==
|
||||||
|
|||||||
Reference in New Issue
Block a user