From e16064b9b0f4587ac7707a62d0bce026fde86a21 Mon Sep 17 00:00:00 2001 From: kay delaney <45561153+kaydelaney@users.noreply.github.com> Date: Thu, 3 Oct 2019 13:30:24 +0100 Subject: [PATCH] UI: Adds Modal component (#19369) * UI: Adds Modal component --- packages/grafana-ui/package.json | 2 + .../src/components/Modal/Modal.story.tsx | 45 +++++++++ .../grafana-ui/src/components/Modal/Modal.tsx | 98 +++++++++++++++++++ .../ThresholdsEditor.test.tsx.snap | 10 ++ packages/grafana-ui/src/components/index.ts | 1 + packages/grafana-ui/src/themes/dark.ts | 5 + packages/grafana-ui/src/themes/light.ts | 5 + packages/grafana-ui/src/types/theme.ts | 6 ++ yarn.lock | 11 ++- 9 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 packages/grafana-ui/src/components/Modal/Modal.story.tsx create mode 100644 packages/grafana-ui/src/components/Modal/Modal.tsx diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 8fffc10764b..51cc6442600 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -59,6 +59,7 @@ "@storybook/react": "5.0.6", "@storybook/theming": "5.0.6", "@types/classnames": "2.2.7", + "@types/common-tags": "^1.8.0", "@types/d3": "5.7.1", "@types/jest": "23.3.14", "@types/jquery": "1.10.35", @@ -75,6 +76,7 @@ "@types/storybook__addon-knobs": "4.0.4", "@types/storybook__react": "4.0.1", "@types/tinycolor2": "1.4.1", + "common-tags": "^1.8.0", "pretty-format": "24.9.0", "react-docgen-typescript-loader": "3.0.1", "react-docgen-typescript-webpack-plugin": "1.1.0", diff --git a/packages/grafana-ui/src/components/Modal/Modal.story.tsx b/packages/grafana-ui/src/components/Modal/Modal.story.tsx new file mode 100644 index 00000000000..cf52cb66921 --- /dev/null +++ b/packages/grafana-ui/src/components/Modal/Modal.story.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { oneLineTrim } from 'common-tags'; +import { text, boolean } from '@storybook/addon-knobs'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { Modal } from './Modal'; + +const getKnobs = () => { + return { + body: text( + 'Body', + oneLineTrim`Id incididunt do pariatur qui labore. Sint culpa irure cillum et ullamco proident. Deserunt ipsum velit dolore est enim proident dolore consectetur. Et cillum tempor pariatur et. Est tempor cillum ad id nulla. Cillum ut proident +magna do cillum consequat reprehenderit excepteur. Pariatur culpa id excepteur reprehenderit consequat qui qui sit +consectetur esse enim mollit incididunt. Ea excepteur nisi mollit reprehenderit eiusmod tempor. Eiusmod incididunt +occaecat velit consectetur dolor cillum anim commodo fugiat cupidatat ut tempor officia. Aliquip fugiat occaecat +excepteur consectetur ullamco consectetur exercitation occaecat sint sint incididunt cillum minim. Sint aliquip ea +pariatur anim. Veniam laboris mollit in voluptate exercitation sint deserunt dolor ullamco ex dolor. Enim +reprehenderit ut Lorem aliquip est laborum in. Aliqua in ut aute elit nulla amet. Ex proident pariatur ex in +aliquip. Labore eu Lorem sint aliqua reprehenderit ipsum veniam aliquip laborum dolor deserunt cupidatat velit +amet.` + ), + visible: boolean('Visible', true), + }; +}; + +const ModalStories = storiesOf('UI/Modal', module); + +ModalStories.addDecorator(withCenteredStory); + +ModalStories.add('default', () => { + const { body, visible } = getKnobs(); + return ( + + + My Modal + + } + isOpen={visible} + > + {body} + + ); +}); diff --git a/packages/grafana-ui/src/components/Modal/Modal.tsx b/packages/grafana-ui/src/components/Modal/Modal.tsx new file mode 100644 index 00000000000..739fa3ea9bf --- /dev/null +++ b/packages/grafana-ui/src/components/Modal/Modal.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import { Portal } from '../Portal/Portal'; +import { css, cx } from 'emotion'; +import { GrafanaTheme, ThemeContext } from '../..'; + +const getStyles = (theme: GrafanaTheme) => ({ + modal: css` + position: fixed; + z-index: ${theme.zIndex.modal}; + width: 100%; + background: ${theme.colors.pageBg}; + box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); + background-clip: padding-box; + outline: none; + + max-width: 750px; + left: 0; + right: 0; + margin-left: auto; + margin-right: auto; + top: 10%; + `, + modalBackdrop: css` + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: ${theme.zIndex.modalBackdrop} + background-color: ${theme.colors.bodyBg} + opacity: 0.8; + backdrop-filter: blur(4px); + `, + modalHeader: css` + background: ${theme.background.pageHeader}; + box-shadow: ${theme.shadow.pageHeader}; + border-bottom: 1px soliod ${theme.colors.pageHeaderBorder}; + display: flex; + `, + modalHeaderTitle: css` + font-size: ${theme.typography.heading.h3}; + padding-top: calc(${theme.spacing.d} * 0.75); + margin: 0 calc(${theme.spacing.d} * 3) 0 calc(${theme.spacing.d} * 1.5); + `, + modalHeaderClose: css` + margin-left: auto; + padding: 9px ${theme.spacing.d}; + `, + modalContent: css` + padding: calc(${theme.spacing.d} * 2); + `, +}); + +interface Props { + title: string | JSX.Element; + isOpen?: boolean; + onDismiss?: () => void; + onClickBackdrop?: () => void; +} + +export class Modal extends React.PureComponent { + static contextType = ThemeContext; + context!: React.ContextType; + + onDismiss = () => { + if (this.props.onDismiss) { + this.props.onDismiss(); + } + }; + + onClickBackdrop = () => { + this.onDismiss(); + }; + + render() { + const { title, isOpen = false } = this.props; + const styles = getStyles(this.context); + + if (!isOpen) { + return null; + } + + return ( + + + + {typeof title === 'string' ? {title} : <>{title}>} + + + + + {this.props.children} + + + + ); + } +} diff --git a/packages/grafana-ui/src/components/ThresholdsEditor/__snapshots__/ThresholdsEditor.test.tsx.snap b/packages/grafana-ui/src/components/ThresholdsEditor/__snapshots__/ThresholdsEditor.test.tsx.snap index 9c498a241f5..1ed5069a9bc 100644 --- a/packages/grafana-ui/src/components/ThresholdsEditor/__snapshots__/ThresholdsEditor.test.tsx.snap +++ b/packages/grafana-ui/src/components/ThresholdsEditor/__snapshots__/ThresholdsEditor.test.tsx.snap @@ -74,6 +74,7 @@ exports[`Render should render with base threshold 1`] = ` Object { "background": Object { "dropdown": "#1f1f20", + "pageHeader": "linear-gradient(90deg, #292a2d, #000000)", "scrollbar": "#343436", "scrollbar2": "#343436", }, @@ -138,6 +139,7 @@ exports[`Render should render with base threshold 1`] = ` "orange": "#eb7b18", "orangeDark": "#ff780a", "pageBg": "#161719", + "pageHeaderBorder": "#343436", "purple": "#9933cc", "queryGreen": "#74e680", "queryKeyword": "#66d9ef", @@ -165,6 +167,9 @@ exports[`Render should render with base threshold 1`] = ` "name": "Grafana Dark", "panelHeaderHeight": 28, "panelPadding": 8, + "shadow": Object { + "pageHeader": "inset 0px -4px 14px #1f1f20", + }, "spacing": Object { "d": "14px", "gutter": "30px", @@ -236,6 +241,7 @@ exports[`Render should render with base threshold 1`] = ` Object { "background": Object { "dropdown": "#1f1f20", + "pageHeader": "linear-gradient(90deg, #292a2d, #000000)", "scrollbar": "#343436", "scrollbar2": "#343436", }, @@ -300,6 +306,7 @@ exports[`Render should render with base threshold 1`] = ` "orange": "#eb7b18", "orangeDark": "#ff780a", "pageBg": "#161719", + "pageHeaderBorder": "#343436", "purple": "#9933cc", "queryGreen": "#74e680", "queryKeyword": "#66d9ef", @@ -327,6 +334,9 @@ exports[`Render should render with base threshold 1`] = ` "name": "Grafana Dark", "panelHeaderHeight": 28, "panelPadding": 8, + "shadow": Object { + "pageHeader": "inset 0px -4px 14px #1f1f20", + }, "spacing": Object { "d": "14px", "gutter": "30px", diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index 4e6505f1b8e..c6a4e7b9d4f 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -37,6 +37,7 @@ export { RefreshPicker } from './RefreshPicker/RefreshPicker'; export { TimePicker } from './TimePicker/TimePicker'; export { TimeOfDayPicker } from './TimePicker/TimeOfDayPicker'; export { List } from './List/List'; +export { Modal } from './Modal/Modal'; // Renderless export { SetInterval } from './SetInterval/SetInterval'; diff --git a/packages/grafana-ui/src/themes/dark.ts b/packages/grafana-ui/src/themes/dark.ts index 8f7727416f3..c78c7556c4e 100644 --- a/packages/grafana-ui/src/themes/dark.ts +++ b/packages/grafana-ui/src/themes/dark.ts @@ -71,11 +71,16 @@ const darkTheme: GrafanaTheme = { linkHover: basicColors.white, linkExternal: basicColors.blue, headingColor: basicColors.gray4, + pageHeaderBorder: basicColors.dark9, }, background: { dropdown: basicColors.dark3, scrollbar: basicColors.dark9, scrollbar2: basicColors.dark9, + pageHeader: `linear-gradient(90deg, ${basicColors.dark7}, ${basicColors.black})`, + }, + shadow: { + pageHeader: `inset 0px -4px 14px ${basicColors.dark3}`, }, }; diff --git a/packages/grafana-ui/src/themes/light.ts b/packages/grafana-ui/src/themes/light.ts index b47f6a26929..595a36f342a 100644 --- a/packages/grafana-ui/src/themes/light.ts +++ b/packages/grafana-ui/src/themes/light.ts @@ -72,11 +72,16 @@ const lightTheme: GrafanaTheme = { linkHover: basicColors.dark1, linkExternal: basicColors.blueLight, headingColor: basicColors.gray1, + pageHeaderBorder: basicColors.gray4, }, background: { dropdown: basicColors.white, scrollbar: basicColors.gray5, scrollbar2: basicColors.gray5, + pageHeader: `linear-gradient(90deg, ${basicColors.white}, ${basicColors.gray7})`, + }, + shadow: { + pageHeader: `inset 0px -3px 10px ${basicColors.gray6}`, }, }; diff --git a/packages/grafana-ui/src/types/theme.ts b/packages/grafana-ui/src/types/theme.ts index 9ad8bcd57c8..6d66bdce3b0 100644 --- a/packages/grafana-ui/src/types/theme.ts +++ b/packages/grafana-ui/src/types/theme.ts @@ -96,6 +96,7 @@ export interface GrafanaTheme extends GrafanaThemeCommons { dropdown: string; scrollbar: string; scrollbar2: string; + pageHeader: string; }; colors: { black: string; @@ -169,6 +170,11 @@ export interface GrafanaTheme extends GrafanaThemeCommons { bodyBg: string; pageBg: string; headingColor: string; + + pageHeaderBorder: string; + }; + shadow: { + pageHeader: string; }; } diff --git a/yarn.lock b/yarn.lock index 12b4b30dd67..15ff5a6c902 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2750,6 +2750,11 @@ resolved "https://registry.yarnpkg.com/@types/command-exists/-/command-exists-1.2.0.tgz#d97e0ed10097090e4ab0367ed425b0312fad86f3" integrity sha512-ugsxEJfsCuqMLSuCD4PIJkp5Uk2z6TCMRCgYVuhRo5cYQY3+1xXTQkSlPtkpGHuvWMjS2KTeVQXxkXRACMbM6A== +"@types/common-tags@^1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@types/common-tags/-/common-tags-1.8.0.tgz#79d55e748d730b997be5b7fce4b74488d8b26a6b" + integrity sha512-htRqZr5qn8EzMelhX/Xmx142z218lLyGaeZ3YR8jlze4TATRU9huKKvuBmAJEW4LCC4pnY1N6JAm6p85fMHjhg== + "@types/connect-history-api-fallback@*": version "1.3.3" resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.3.tgz#4772b79b8b53185f0f4c9deab09236baf76ee3b4" @@ -5266,11 +5271,6 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" 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.30000929, caniuse-lite@^1.0.30000947, caniuse-lite@^1.0.30000957, caniuse-lite@^1.0.30000963: version "1.0.30000966" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000966.tgz#f3c6fefacfbfbfb981df6dfa68f2aae7bff41b64" @@ -5735,6 +5735,7 @@ commander@~2.19.0: common-tags@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" + integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw== commondir@^1.0.1: version "1.0.1"