Skip to content

Commit

Permalink
Migrate ModalDialog to Storybook (#1574)
Browse files Browse the repository at this point in the history
## Summary:
Using Styleguidist as a foundation, migrate the documentation for
ModalDialog to Storybook.

Styleguidist does not have specific examples for each building
block under modal, but I decided to start adding them all with their own
examples (as opposed to using subcomponents), particularly so that
users can experiment with the control panel. This one was a little
tricky since ModalDialog does not have a visual element.

Storybook publish: 
https://5e1bf4b385e3fb0020b7073c-sbvqecqsiq.chromatic.com/?path=/docs/modal-building-blocks-modaldialog--default

Issue: https://khanacademy.atlassian.net/browse/WB-1141

## Test plan:
- Check that all the documentation makes sense.
- Check there are no typos.
- Interact with the modals to confirm they all behave as expected.

Author: nishasy

Reviewers: jandrade, nishasy

Required Reviewers:

Approved By: jandrade, jandrade

Checks: ✅ codecov/project, ✅ Check build sizes (ubuntu-latest, 16.x), ✅ Lint (ubuntu-latest, 16.x), ✅ Test (ubuntu-latest, 16.x, 2/2), ✅ Test (ubuntu-latest, 16.x, 1/2), ✅ Mixed content, ⚪️ Redirect rules, ⚪️ Header rules, ⚪️ Pages changed, ✅ gerald, ✅ Prime node_modules cache for primary configuration (ubuntu-latest, 16.x), ⏭  dependabot, ✅ Chromatic - Build on review PR (push) / chromatic (ubuntu-latest, 16.x), ⏭  Chromatic - Skip on dependabot PRs (push)

Pull Request URL: #1574
  • Loading branch information
nishasy authored Aug 26, 2022
1 parent a126d18 commit 5d16ee8
Show file tree
Hide file tree
Showing 3 changed files with 294 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .changeset/swift-apricots-double.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
// @flow
import * as React from "react";
import {StyleSheet} from "aphrodite";

import Button from "@khanacademy/wonder-blocks-button";
import Color from "@khanacademy/wonder-blocks-color";
import {View} from "@khanacademy/wonder-blocks-core";
import {Strut} from "@khanacademy/wonder-blocks-layout";
import {
ModalLauncher,
ModalDialog,
ModalPanel,
} from "@khanacademy/wonder-blocks-modal";
import Spacing from "@khanacademy/wonder-blocks-spacing";
import {Body, Title} from "@khanacademy/wonder-blocks-typography";

import type {StoryComponentType} from "@storybook/react";

import ComponentInfo from "../../../../../.storybook/components/component-info.js";
import {name, version} from "../../../package.json";

const customViewports = {
phone: {
name: "phone",
styles: {
width: "320px",
height: "568px",
},
},
tablet: {
name: "tablet",
styles: {
width: "640px",
height: "960px",
},
},
desktop: {
name: "desktop",
styles: {
width: "1024px",
height: "768px",
},
},
};

export default {
title: "Modal/Building Blocks/ModalDialog",
component: ModalDialog,
parameters: {
componentSubtitle: ((
<ComponentInfo name={name} version={version} />
): any),
viewport: {
viewports: customViewports,
defaultViewport: "desktop",
},
chromatic: {
viewports: [320, 640, 1024],
},
},
// Make the following props null in the control panel
// because setting an object for a React node is
// not practical to do manually.
argTypes: {
children: {
control: {type: null},
},
above: {
control: {type: null},
},
below: {
control: {type: null},
},
},
};

export const Default: StoryComponentType = (args) => (
<View style={styles.previewSizer}>
<View style={styles.modalPositioner}>
<ModalDialog aria-labelledby="modal-title-0" {...args}>
<ModalPanel
content={
<>
<Title id="modal-title-0">Modal Title</Title>
<Strut size={Spacing.large_24} />
<Body>Here is some text in the modal.</Body>
</>
}
/>
</ModalDialog>
</View>
</View>
);

Default.args = {
style: {
maxWidth: 500,
maxHeight: 500,
},
};

export const Simple: StoryComponentType = () => (
<View style={styles.previewSizer}>
<View style={styles.modalPositioner}>
<ModalDialog
aria-labelledby="modal-title-1"
style={styles.squareDialog}
>
<ModalPanel
content={
<>
<Title id="modal-title-1">Modal Title</Title>
<Strut size={Spacing.large_24} />
<Body>Here is some text in the modal.</Body>
</>
}
/>
</ModalDialog>
</View>
</View>
);

Simple.parameters = {
docs: {
storyDescription: `This is a basic \`<ModalDialog>\` that wraps a
\`<ModalPanel>\` element. The \`<ModalDialog>\` is just a a wrapper
for the visual components of the overall modal. It sets
the modal's role to \`"dialog"\`. If it did not have another
element as a child here (a \`<ModalPanel>\` in this case),
nothing would be visible. If the \`<ModalDialog>\` were not given
a \`maxHeight\` or \`maxWidth\` style, it would take up the
entire viewport. To demonstrate the difference between
the \`<ModalDialog>\` and the \`<ModalPanel>\` elements, the panel
has been given a smaller height and width than \`<ModalDialog>\`,
and \`<ModalDialog>\` has been given a dark blue background.`,
},
};

export const WithAboveAndBelow: StoryComponentType = () => {
const aboveStyle = {
background: "url(./modal-above.png)",
width: 874,
height: 551,
position: "absolute",
top: 40,
left: -140,
};

const belowStyle = {
background: "url(./modal-below.png)",
width: 868,
height: 521,
position: "absolute",
top: -100,
left: -300,
};

return (
<View style={styles.previewSizer}>
<View style={styles.modalPositioner}>
<ModalDialog
aria-labelledby="modal-title-2"
style={styles.squareDialog}
above={<View style={aboveStyle} />}
below={<View style={belowStyle} />}
>
<ModalPanel
content={
<>
<Title id="modal-title-2">Modal Title</Title>
<Strut size={Spacing.large_24} />
<Body>Here is some text in the modal.</Body>
</>
}
/>
</ModalDialog>
</View>
</View>
);
};

WithAboveAndBelow.parameters = {
docs: {
storyDescription: `The \`above\` and \`below\` props work the same
for \`<ModalDialog>\` as they do for \`<OnePaneDialog>\`.
The element passed into the \`above\` prop is rendered in front
of the modal. The element passed into the \`below\` prop is
rendered behind the modal. In this example, a \`<View>\` element
with a background image of a person and an orange blob is passed
into the \`below\` prop. A \`<View>\` element with a background
image of an arc and a blue semicircle is passed into the \`above\`
prop. This results in the person's head and the orange blob
peeking out from behind the modal, and the arc and semicircle
going over the front of the modal.`,
},
};

export const WithLauncher: StoryComponentType = () => {
type MyModalProps = {|
closeModal: () => void,
|};

const MyModal = ({
closeModal,
}: MyModalProps): React.Element<typeof ModalDialog> => (
<ModalDialog
aria-labelledby="modal-title-3"
style={styles.squareDialog}
>
<ModalPanel
content={
<>
<Title id="modal-title-3">Modal Title</Title>
<Strut size={Spacing.large_24} />
<Body>Here is some text in the modal.</Body>
</>
}
/>
</ModalDialog>
);

return (
<ModalLauncher modal={MyModal}>
{({openModal}) => (
<Button onClick={openModal}>Click me to open the modal</Button>
)}
</ModalLauncher>
);
};

WithLauncher.parameters = {
chromatic: {
// Don't take screenshots of this story since it would only show a
// button and not the actual modal.
disableSnapshot: true,
},
docs: {
storyDescription: `A modal can be launched using a launcher. Here,
the launcher is a \`<Button>\` element whose \`onClick\` function
opens the modal. The modal passed into the \`modal\` prop of
the \`<ModalLauncher>\` element is a \`<ModalDialog>\` element.
To turn an element into a launcher, wrap the element in a
\`<ModalLauncher>\` element.`,
},
};

const styles = StyleSheet.create({
modalPositioner: {
// Checkerboard background
backgroundImage:
"linear-gradient(45deg, #ccc 25%, transparent 25%), linear-gradient(-45deg, #ccc 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #ccc 75%), linear-gradient(-45deg, transparent 75%, #ccc 75%)",
backgroundSize: "20px 20px",
backgroundPosition: "0 0, 0 10px, 10px -10px, -10px 0px",

flexDirection: "row",
alignItems: "center",
justifyContent: "center",

position: "absolute",
left: 0,
right: 0,
top: 0,
bottom: 0,
},
previewSizer: {
height: 600,
},
row: {
flexDirection: "row",
justifyContent: "flex-end",
},
footer: {
alignItems: "center",
flexDirection: "row",
justifyContent: "space-between",
width: "100%",
},
squareDialog: {
maxHeight: 500,
maxWidth: 500,
backgroundColor: Color.darkBlue,
},
smallSquarePanel: {
maxHeight: 400,
maxWidth: 400,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -519,9 +519,11 @@ WithLauncher.parameters = {
},
docs: {
storyDescription: `A modal can be launched using a launcher. Here,
the launcher is a \`Button\` element whose \`onClick\` function
opens the modal. To turn an element into a launcher, wrap the
element in a \`<ModalLauncher>\` element.`,
the launcher is a \`<Button>\` element whose \`onClick\` function
opens the modal. The modal passed into the \`modal\` prop of
the \`<ModalLauncher>\` element is a \`<OnePaneDialog>\`.
To turn an element into a launcher, wrap the element in a
\`<ModalLauncher>\` element.`,
},
};

Expand Down

0 comments on commit 5d16ee8

Please sign in to comment.