From dcb6fefb930eef464002e561de0435d3c98e6285 Mon Sep 17 00:00:00 2001 From: Mike Perrotti Date: Thu, 31 Oct 2019 23:45:55 -0400 Subject: [PATCH] feat: adds FullscreenView and refactors FullscreenModal --- packages/fullscreenView/README.md | 5 + .../components/FullscreenViewHeader.tsx} | 0 .../components/FullscrenView.tsx | 67 ++++ packages/fullscreenView/index.ts | 1 + .../stories/fullscreenView.stories.tsx | 107 ++++++ packages/fullscreenView/style.ts | 29 ++ .../fullscreenView.test.tsx.snap | 333 ++++++++++++++++++ .../tests/fullscreenView.test.tsx | 26 ++ packages/index.ts | 1 + packages/modal/README.md | 8 + packages/modal/components/FullscreenModal.tsx | 42 +-- packages/modal/stories/Modal.stories.tsx | 2 +- packages/modal/style.ts | 20 +- .../tests/__snapshots__/Modal.test.tsx.snap | 292 +++++++-------- 14 files changed, 744 insertions(+), 189 deletions(-) create mode 100644 packages/fullscreenView/README.md rename packages/{modal/components/FullscreenModalHeader.tsx => fullscreenView/components/FullscreenViewHeader.tsx} (100%) create mode 100644 packages/fullscreenView/components/FullscrenView.tsx create mode 100644 packages/fullscreenView/index.ts create mode 100644 packages/fullscreenView/stories/fullscreenView.stories.tsx create mode 100644 packages/fullscreenView/style.ts create mode 100644 packages/fullscreenView/tests/__snapshots__/fullscreenView.test.tsx.snap create mode 100644 packages/fullscreenView/tests/fullscreenView.test.tsx diff --git a/packages/fullscreenView/README.md b/packages/fullscreenView/README.md new file mode 100644 index 000000000..b53b92b7d --- /dev/null +++ b/packages/fullscreenView/README.md @@ -0,0 +1,5 @@ +# FullscreenView + +The FullscreenView component should be used when we want to bring the user into a flow that does not have any of the usual app's chrome. + +The FullscreenModal component can be used in cases where we don't want to remove all other content from the page. diff --git a/packages/modal/components/FullscreenModalHeader.tsx b/packages/fullscreenView/components/FullscreenViewHeader.tsx similarity index 100% rename from packages/modal/components/FullscreenModalHeader.tsx rename to packages/fullscreenView/components/FullscreenViewHeader.tsx diff --git a/packages/fullscreenView/components/FullscrenView.tsx b/packages/fullscreenView/components/FullscrenView.tsx new file mode 100644 index 000000000..2f255a9df --- /dev/null +++ b/packages/fullscreenView/components/FullscrenView.tsx @@ -0,0 +1,67 @@ +import * as React from "react"; +import { cx } from "emotion"; +import Delegate from "react-delegate-component"; +import { ButtonProps } from "../../button/components/ButtonBase"; +import { flex, padding, flexItem } from "../../shared/styles/styleUtils"; +import { modalContent, fullscreenModalHeader } from "../style"; +import FullscreenViewHeader from "./FullscreenViewHeader"; + +interface FullscreenViewProps { + /** The primary button */ + ctaButton?: React.ReactElement; + /** The text for the button that secondary button, which closes the modal */ + closeText: React.ReactNode; + /** The title that appears in the header */ + title: React.ReactNode; + /** The subtitle that appears in the header */ + subtitle?: React.ReactNode; + /** Whether we automatically add padding to the body of the modal. */ + isContentFlush?: boolean; + /** Custom header content component. ⚠️Use rarely and with caution⚠️ */ + headerComponent?: React.ReactNode; + /** Function that gets called when the modal is closed */ + onClose: (event?: React.SyntheticEvent) => void; + /** The base of the `data-cy` value. This is used for writing selectors in Cypress. */ + cypressSelectorBase?: string; +} + +class FullscreenView extends React.PureComponent { + public render() { + const { + children, + ctaButton, + closeText, + isContentFlush, + onClose, + title, + subtitle, + headerComponent, + cypressSelectorBase = "fullscreenView" + } = this.props; + + return ( +
+
+ +
+
+ {children} +
+
+ ); + } +} + +export default FullscreenView; diff --git a/packages/fullscreenView/index.ts b/packages/fullscreenView/index.ts new file mode 100644 index 000000000..6b0960c00 --- /dev/null +++ b/packages/fullscreenView/index.ts @@ -0,0 +1 @@ +export { default as FullscreenView } from "./components/FullscrenView"; diff --git a/packages/fullscreenView/stories/fullscreenView.stories.tsx b/packages/fullscreenView/stories/fullscreenView.stories.tsx new file mode 100644 index 000000000..c1cba7ac8 --- /dev/null +++ b/packages/fullscreenView/stories/fullscreenView.stories.tsx @@ -0,0 +1,107 @@ +import * as React from "react"; +import { storiesOf } from "@storybook/react"; +import { withReadme } from "storybook-readme"; +import FullscreenView from "../components/FullscrenView"; +import { + flex, + flexItem, + textSize, + tintContentSecondary, + padding +} from "../../shared/styles/styleUtils"; +import { cx } from "emotion"; +import { fullscreenModalTitle } from "../style"; +import { SecondaryButton, PrimaryButton } from "../../button"; +import { action } from "@storybook/addon-actions"; +import { + ModalContent, + BorderedModalContent +} from "../../modal/stories/helpers/modalContents"; + +const onClose = () => { + alert("calling onClose"); +}; + +const readme = require("../README.md"); + +storiesOf("FullscreenView", module) + .addDecorator(withReadme([readme])) + .add("default", () => ( +
+ + + +
+ )) + .add("with additional header content", () => { + const HeaderContent = ({ ctaButton, closeText, title, onClose }) => ( +
+
+ {closeText} +
+
+
{title}
+
+ Some subheader +
+
+
+
+
+ + +
+
+ {ctaButton} +
+
+
+
+ ); + return ( +
+ + Continue + + } + headerComponent={HeaderContent} + > + + +
+ ); + }) + .add("with flushed content", () => ( +
+ + Continue + + } + > + + +
+ )) + .add("scrolling body", () => ( +
+ + + + + +
+ )); diff --git a/packages/fullscreenView/style.ts b/packages/fullscreenView/style.ts new file mode 100644 index 000000000..5c93c66fd --- /dev/null +++ b/packages/fullscreenView/style.ts @@ -0,0 +1,29 @@ +import { css } from "emotion"; +import { + themeBgSecondary, + borderColorDefault +} from "../design-tokens/build/js/designTokens"; + +export const fullscreenModalHeader = css` + background-color: ${themeBgSecondary}; + box-sizing: border-box; + border-bottom: 1px solid ${borderColorDefault}; +`; + +export const fullscreenModalTitle = css` + text-align: center; +`; + +export const fullscreenModalAction = { + dismiss: css` + text-align: left; + `, + cta: css` + text-align: right; + ` +}; + +export const modalContent = css` + box-sizing: border-box; + overflow: auto; +`; diff --git a/packages/fullscreenView/tests/__snapshots__/fullscreenView.test.tsx.snap b/packages/fullscreenView/tests/__snapshots__/fullscreenView.test.tsx.snap new file mode 100644 index 000000000..30d2fd69f --- /dev/null +++ b/packages/fullscreenView/tests/__snapshots__/fullscreenView.test.tsx.snap @@ -0,0 +1,333 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FullscreenView renders FullscreenView 1`] = ` +.emotion-10 { + height: 100%; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + box-sizing: border-box; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + min-height: 0; +} + +.emotion-10 > div { + width: 100%; +} + +.emotion-8 { + background-color: var(--themeBgSecondary,#F7F8F9); + box-sizing: border-box; + border-bottom: 1px solid #DADDE2; + padding: 32px; +} + +.emotion-4 { + box-sizing: border-box; + -webkit-flex-basis: 0; + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; + min-width: 0; + width: auto; +} + +.emotion-9 { + box-sizing: border-box; + overflow: auto; + box-sizing: border-box; + -webkit-flex-basis: 0; + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; + min-width: 0; + width: auto; + padding: 32px; +} + +.emotion-7 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + height: auto; + box-sizing: border-box; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + min-height: 0; + box-sizing: border-box; + -webkit-flex-basis: auto; + -ms-flex-preferred-size: auto; + flex-basis: auto; + -webkit-box-flex: 0; + -webkit-flex-grow: 0; + -ms-flex-positive: 0; + flex-grow: 0; + -webkit-flex-shrink: 0; + -ms-flex-negative: 0; + flex-shrink: 0; + width: initial; +} + +.emotion-7 > div { + width: auto; +} + +.emotion-1 { + text-align: left; + box-sizing: border-box; + -webkit-flex-basis: 0; + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; + min-width: 0; + width: auto; +} + +.emotion-2 { + text-align: center; + margin-top: 0; + margin-bottom: 0; + padding-bottom: 8px; + font-size: 18px; + font-weight: 400; +} + +.emotion-3 { + text-align: center; + margin-top: 0; + margin-bottom: 0; + font-size: 12px; + font-weight: 400; + color: #76797E; + fill: #76797E; +} + +.emotion-6 { + text-align: right; + box-sizing: border-box; + -webkit-flex-basis: 0; + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; + min-width: 0; + width: auto; +} + +.emotion-0 { + background: none; + border: 0; + color: inherit; + font: inherit; + line-height: normal; + overflow: visible; + padding: 0; + text-align: inherit; + color: var(--themeTextColorInteractive,#7D58FF); + fill: var(--themeTextColorInteractive,#7D58FF); + cursor: pointer; + display: inline-block; + outline: none; + -webkit-text-decoration: none; + text-decoration: none; + font-weight: 500; +} + +.emotion-0::-moz-focus-inner { + border: 0; + padding: 0; +} + +.emotion-0:hover { + color: #704fe5; + fill: #704fe5; +} + +.emotion-0:active { + color: #6446cc; + fill: #6446cc; +} + +.emotion-5 { + background: none; + border: 0; + color: inherit; + font: inherit; + line-height: normal; + overflow: visible; + padding: 0; + text-align: inherit; + color: #FFFFFF; + fill: #FFFFFF; + background-color: #7D58FF; + border-radius: 4px; + padding: 10px 18px; + cursor: pointer; + display: inline-block; + outline: none; + -webkit-text-decoration: none; + text-decoration: none; + font-weight: 500; +} + +.emotion-5::-moz-focus-inner { + border: 0; + padding: 0; +} + +.emotion-5:hover { + background-color: #704fe5; +} + +.emotion-5:active { + background-color: #6446cc; +} + + + CTA + + } + onClose={[MockFunction]} + title="Title" +> +
+
+ + CTA + , + "onClose": [MockFunction], + "subtitle": undefined, + "title": "Title", + } + } + > + + CTA + + } + onClose={[MockFunction]} + title="Title" + > +
+
+ + + + + + + +
+
+

+ Title +

+

+

+
+ + + + + + + +
+
+
+
+
+
+
+ I am modal content +
+
+
+
+`; diff --git a/packages/fullscreenView/tests/fullscreenView.test.tsx b/packages/fullscreenView/tests/fullscreenView.test.tsx new file mode 100644 index 000000000..58f36ff63 --- /dev/null +++ b/packages/fullscreenView/tests/fullscreenView.test.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { mount } from "enzyme"; +import * as emotion from "emotion"; +import { createSerializer } from "jest-emotion"; +import toJson from "enzyme-to-json"; +import FullscreenView from "../components/FullscrenView"; +import { PrimaryButton } from "../../button"; + +expect.addSnapshotSerializer(createSerializer(emotion)); + +describe("FullscreenView", () => { + it("renders FullscreenView", () => { + const closeAction = jest.fn(); + const component = mount( + CTA} + closeText="Close" + title="Title" + > +
I am modal content
+
+ ); + expect(toJson(component)).toMatchSnapshot(); + }); +}); diff --git a/packages/index.ts b/packages/index.ts index 935b0596b..ced1eebfb 100644 --- a/packages/index.ts +++ b/packages/index.ts @@ -52,6 +52,7 @@ export { DropdownMenuItemAvatar, DropdownMenuItemAppearances } from "./dropdownMenu"; +export { FullscreenView } from "./fullscreenView"; export { Icon } from "./icon"; export { InfoBoxInline, InfoBoxBanner } from "./infobox"; export { InputAppearance } from "./shared/types/inputAppearance"; diff --git a/packages/modal/README.md b/packages/modal/README.md index 1daa48008..7806386d2 100644 --- a/packages/modal/README.md +++ b/packages/modal/README.md @@ -10,6 +10,14 @@ Designer/Developer can choose whether to use a large or small modal depending on If the modal is complex and requires more than 4 navigation tabs then consider switching this to a full screen form. +## FullscreenModal vs FullscreenView + +The FullscreenModal component is a layer that appears on top of all page content. +The FullscreenView component renders in the normal document flow. + +The FullscreenModal uses the FullscreenView component, so the designs will always be in sync. The FullscreenView should be used when we want the same layout and style as a FullscreenModal, but we don't want any content to be rendered underneath. + + ## Closing modals Modals can be dismissed by: diff --git a/packages/modal/components/FullscreenModal.tsx b/packages/modal/components/FullscreenModal.tsx index 9055dcd61..8590ba488 100644 --- a/packages/modal/components/FullscreenModal.tsx +++ b/packages/modal/components/FullscreenModal.tsx @@ -1,12 +1,8 @@ import * as React from "react"; -import { cx } from "emotion"; import ModalBase from "../components/ModalBase"; import { ModalBaseProps, ModalSizes } from "./ModalBase"; -import Delegate from "react-delegate-component"; import { ButtonProps } from "../../button/components/ButtonBase"; -import { flex, padding, flexItem } from "../../shared/styles/styleUtils"; -import { modalContent, modalHeader, fullscreenModalHeader } from "../style"; -import FullscreenModalHeader from "./FullscreenModalHeader"; +import FullscreenView from "../../fullscreenView/components/FullscrenView"; interface FullscreenModalProps extends ModalBaseProps { /** The primary button */ @@ -45,30 +41,18 @@ class FullscreenModal extends React.PureComponent { dataCy="fullscreenModal" {...other} > -
-
- -
-
- {children} -
-
+ + {children} + ); } diff --git a/packages/modal/stories/Modal.stories.tsx b/packages/modal/stories/Modal.stories.tsx index 0d3fe9dbd..c75ecac23 100644 --- a/packages/modal/stories/Modal.stories.tsx +++ b/packages/modal/stories/Modal.stories.tsx @@ -24,7 +24,7 @@ import { } from "../../shared/styles/styleUtils"; import { SecondaryButton } from "../../button"; import { TextInput } from "../../textInput"; -import { fullscreenModalTitle } from "../style"; +import { fullscreenModalTitle } from "../../fullscreenView/style"; const readme = require("../README.md"); diff --git a/packages/modal/style.ts b/packages/modal/style.ts index 83828eb09..a8c42e6be 100644 --- a/packages/modal/style.ts +++ b/packages/modal/style.ts @@ -5,8 +5,7 @@ import { zIndexModal, borderRadiusDefault, themeBgScrim, - themeBgPrimary, - themeBgSecondary + themeBgPrimary } from "../design-tokens/build/js/designTokens"; import { atMediaUp } from "../shared/styles/breakpoints"; @@ -107,27 +106,10 @@ export const modalHeader = css` border-bottom: 1px solid ${borderColorDefault}; `; -export const fullscreenModalHeader = css` - background-color: ${themeBgSecondary}; -`; - export const modalCloseWrapper = css` cursor: pointer; `; -export const fullscreenModalTitle = css` - text-align: center; -`; - -export const fullscreenModalAction = { - dismiss: css` - text-align: left; - `, - cta: css` - text-align: right; - ` -}; - export const modalPreTransitionStyle = duration => css` transition: opacity ${duration}ms ease-in-out, transform ${duration}ms ease-in-out; diff --git a/packages/modal/tests/__snapshots__/Modal.test.tsx.snap b/packages/modal/tests/__snapshots__/Modal.test.tsx.snap index b9f09e701..01ea80d3b 100644 --- a/packages/modal/tests/__snapshots__/Modal.test.tsx.snap +++ b/packages/modal/tests/__snapshots__/Modal.test.tsx.snap @@ -1874,29 +1874,6 @@ exports[`Modal FullscreenModal renders FullscreenModal 1`] = ` background-color: #6446cc; } -.emotion-9 { - box-sizing: border-box; - border-bottom: 1px solid #DADDE2; - background-color: var(--themeBgSecondary,#F7F8F9); - padding: 32px; -} - -.emotion-10 { - box-sizing: border-box; - overflow: auto; - box-sizing: border-box; - -webkit-flex-basis: 0; - -ms-flex-preferred-size: 0; - flex-basis: 0; - -webkit-box-flex: 1; - -webkit-flex-grow: 1; - -ms-flex-positive: 1; - flex-grow: 1; - min-width: 0; - width: auto; - padding: 32px; -} - .emotion-13 { border-radius: 6px; background-color: var(--themeBgPrimary,#FFFFFF); @@ -1923,6 +1900,29 @@ exports[`Modal FullscreenModal renders FullscreenModal 1`] = ` width: 100vw; } +.emotion-9 { + background-color: var(--themeBgSecondary,#F7F8F9); + box-sizing: border-box; + border-bottom: 1px solid #DADDE2; + padding: 32px; +} + +.emotion-10 { + box-sizing: border-box; + overflow: auto; + box-sizing: border-box; + -webkit-flex-basis: 0; + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; + min-width: 0; + width: auto; + padding: 32px; +} + .emotion-8 { -webkit-align-items: center; -webkit-box-align: center; @@ -2936,29 +2936,6 @@ exports[`Modal FullscreenModal renders FullscreenModal 1`] = ` top: -3px; } -.emotion-11 { - box-sizing: border-box; - border-bottom: 1px solid #DADDE2; - background-color: var(--themeBgSecondary,#F7F8F9); - padding: 32px; -} - -.emotion-12 { - box-sizing: border-box; - overflow: auto; - box-sizing: border-box; - -webkit-flex-basis: 0; - -ms-flex-preferred-size: 0; - flex-basis: 0; - -webkit-box-flex: 1; - -webkit-flex-grow: 1; - -ms-flex-positive: 1; - flex-grow: 1; - min-width: 0; - width: auto; - padding: 32px; -} - .emotion-15 { border-radius: 6px; background-color: var(--themeBgPrimary,#FFFFFF); @@ -2985,6 +2962,29 @@ exports[`Modal FullscreenModal renders FullscreenModal 1`] = ` width: 100vw; } +.emotion-11 { + background-color: var(--themeBgSecondary,#F7F8F9); + box-sizing: border-box; + border-bottom: 1px solid #DADDE2; + padding: 32px; +} + +.emotion-12 { + box-sizing: border-box; + overflow: auto; + box-sizing: border-box; + -webkit-flex-basis: 0; + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; + min-width: 0; + width: auto; + padding: 32px; +} + .emotion-10 { -webkit-align-items: center; -webkit-box-align: center; @@ -3564,125 +3564,137 @@ exports[`Modal FullscreenModal renders FullscreenModal 1`] = ` className="emotion-12" data-cy="fullscreenModal" > -
+ CTA + + } + cypressSelectorBase="fullscreenModal" + onClose={[MockFunction]} + title="Title" >
- - CTA - , - "onClose": [MockFunction], - "subtitle": undefined, - "title": "Title", - } - } +
- - CTA - + + CTA + , + "onClose": [MockFunction], + "subtitle": undefined, + "title": "Title", + } } - onClose={[MockFunction]} - title="Title" > -
+ CTA + + } + onClose={[MockFunction]} + title="Title" >
- - - - - - - -
-
-

+ Close + + + + + +

+
- Title - -

-

-
- - - +

+

+
+ + - - - - + + CTA + + + + + +
-
- -
-
-
+ + +
- I am modal content +
+ I am modal content +
- +