From 8966083f5d35c84939258555bcd4acd4421a795f Mon Sep 17 00:00:00 2001 From: Nisha Yerunkar Date: Fri, 21 Jun 2024 14:59:59 -0700 Subject: [PATCH 1/2] [Storybook: Editor Page] Add a working preview for the editor page in storybook The preview iframe doesn't work in storybook, because in links to a local URL to webapp when it's in prod. It has never worked in storybook. We need to be able to see what the initial state of the questions on the editor page would look like without making any changes to the actual preview (yet). I've added a preview here that shows the question and the hints in the editor page. This works for all widgets. It's sticky, so it follows as you scroll down a long editor. You can also close it if it's taking too much space. This is a prerequisite to viewing an interactive graph with initial coordinates. Issue: https://khanacademy.atlassian.net/browse/LEMS-2051 Test plan: http://localhost:6006/?path=/story/perseuseditor-editorpage--demo --- .changeset/sweet-zebras-impress.md | 5 + .../src/__stories__/editor-page-util.tsx | 168 +++++++++++++++++ .../src/__stories__/editor-page.stories.tsx | 173 +----------------- 3 files changed, 180 insertions(+), 166 deletions(-) create mode 100644 .changeset/sweet-zebras-impress.md create mode 100644 packages/perseus-editor/src/__stories__/editor-page-util.tsx diff --git a/.changeset/sweet-zebras-impress.md b/.changeset/sweet-zebras-impress.md new file mode 100644 index 0000000000..1d94e5f0f4 --- /dev/null +++ b/.changeset/sweet-zebras-impress.md @@ -0,0 +1,5 @@ +--- +"@khanacademy/perseus-editor": patch +--- + +[Storybook: Editor Page] Add a storybook-only preview for the questions and hints in the EditorPage component diff --git a/packages/perseus-editor/src/__stories__/editor-page-util.tsx b/packages/perseus-editor/src/__stories__/editor-page-util.tsx new file mode 100644 index 0000000000..4fb8d783ce --- /dev/null +++ b/packages/perseus-editor/src/__stories__/editor-page-util.tsx @@ -0,0 +1,168 @@ +import { + Renderer, + type APIOptions, + type DeviceType, + type Hint, + type PerseusAnswerArea, + type PerseusRenderer, +} from "@khanacademy/perseus"; +import Button from "@khanacademy/wonder-blocks-button"; +import {View} from "@khanacademy/wonder-blocks-core"; +import IconButton from "@khanacademy/wonder-blocks-icon-button"; +import {Strut} from "@khanacademy/wonder-blocks-layout"; +import {color, spacing} from "@khanacademy/wonder-blocks-tokens"; +import {LabelLarge} from "@khanacademy/wonder-blocks-typography"; +import xIcon from "@phosphor-icons/core/regular/x.svg"; +import {action} from "@storybook/addon-actions"; +import {StyleSheet} from "aphrodite"; +import * as React from "react"; + +// eslint-disable-next-line import/no-relative-packages +import {mockStrings} from "../../../perseus/src/strings"; +import EditorPage from "../editor-page"; + +import {flags} from "./flags-for-api-options"; + +type Props = { + apiOptions?: APIOptions; + question?: PerseusRenderer; + hints?: ReadonlyArray; +}; + +const onChangeAction = action("onChange"); + +function EditorPageUtil(props: Props) { + const [previewDevice, setPreviewDevice] = + React.useState("phone"); + const [jsonMode, setJsonMode] = React.useState(false); + const [answerArea, setAnswerArea] = React.useState< + PerseusAnswerArea | undefined | null + >(); + const [question, setQuestion] = React.useState( + props.question, + ); + const [hints, setHints] = React.useState | undefined>( + props.hints, + ); + + const [panelOpen, setPanelOpen] = React.useState(true); + + const apiOptions = props.apiOptions ?? { + isMobile: false, + flags, + }; + + return ( + + + setPreviewDevice(newDevice) + } + developerMode={true} + jsonMode={jsonMode} + answerArea={answerArea} + question={question} + hints={hints} + frameSource="about:blank" + previewURL="about:blank" + itemId="1" + onChange={(props) => { + onChangeAction(props); + + if ("jsonMode" in props) { + setJsonMode(props.jsonMode); + } + if ("answerArea" in props) { + setAnswerArea(props.answerArea); + } + if ("question" in props) { + setQuestion(props.question); + } + if ("hints" in props) { + setHints(props.hints); + } + }} + /> + + {/* Button to open panel */} + {!panelOpen && ( + + )} + + {/* Panel to show the question/hint previews */} + {panelOpen && ( + + {/* Close button */} + setPanelOpen(!panelOpen)} + style={styles.closeButton} + /> + + + {/* Question preview */} + + + + {/* Hints preview */} + {hints?.map((hint, index) => ( + + + {`Hint ${index + 1}`} + + + ))} + + )} + + ); +} + +const styles = StyleSheet.create({ + panel: { + position: "fixed", + right: 0, + height: "90vh", + overflow: "auto", + flex: "none", + backgroundColor: color.fadedBlue16, + padding: spacing.medium_16, + borderRadius: spacing.small_12, + alignItems: "end", + }, + panelInner: { + flex: "none", + backgroundColor: color.white, + borderRadius: spacing.xSmall_8, + marginTop: spacing.medium_16, + width: "100%", + padding: spacing.xSmall_8, + }, + closeButton: { + margin: 0, + }, + openPanelButton: { + position: "fixed", + right: spacing.medium_16, + // Extra space so it doesn't get covered up by storybook's + // "Style warnings" button. + bottom: spacing.xxxLarge_64, + }, +}); + +export default EditorPageUtil; diff --git a/packages/perseus-editor/src/__stories__/editor-page.stories.tsx b/packages/perseus-editor/src/__stories__/editor-page.stories.tsx index 0af4a00cdd..0469585946 100644 --- a/packages/perseus-editor/src/__stories__/editor-page.stories.tsx +++ b/packages/perseus-editor/src/__stories__/editor-page.stories.tsx @@ -10,6 +10,7 @@ import {EditorPage} from ".."; import {segmentWithLockedFigures} from "../../../perseus/src/widgets/__testdata__/interactive-graph.testdata"; import {registerAllWidgetsAndEditorsForTesting} from "../util/register-all-widgets-and-editors-for-testing"; +import EditorPageUtil from "./editor-page-util"; import {flags} from "./flags-for-api-options"; import type { @@ -28,67 +29,12 @@ export default { const onChangeAction = action("onChange"); export const Demo = (): React.ReactElement => { - const [previewDevice, setPreviewDevice] = - React.useState("phone"); - const [jsonMode, setJsonMode] = React.useState(false); - const [answerArea, setAnswerArea] = React.useState< - PerseusAnswerArea | undefined | null - >(); - const [question, setQuestion] = React.useState< - PerseusRenderer | undefined - >(); - const [hints, setHints] = React.useState | undefined>(); - - return ( - setPreviewDevice(newDevice)} - developerMode={true} - jsonMode={jsonMode} - answerArea={answerArea} - question={question} - hints={hints} - frameSource="about:blank" - previewURL="about:blank" - itemId="1" - onChange={(props) => { - onChangeAction(props); - - if ("jsonMode" in props) { - setJsonMode(props.jsonMode); - } - if ("answerArea" in props) { - setAnswerArea(props.answerArea); - } - if ("question" in props) { - setQuestion(props.question); - } - if ("hints" in props) { - setHints(props.hints); - } - }} - /> - ); + return ; }; export const MafsWithLockedFiguresCurrent = (): React.ReactElement => { - const [previewDevice, setPreviewDevice] = - React.useState("phone"); - const [jsonMode, setJsonMode] = React.useState(false); - const [answerArea, setAnswerArea] = React.useState< - PerseusAnswerArea | undefined | null - >(); - const [question, setQuestion] = React.useState( - segmentWithLockedFigures, - ); - const [hints, setHints] = React.useState | undefined>(); - return ( - { }, }, }} - previewDevice={previewDevice} - onPreviewDeviceChange={(newDevice) => setPreviewDevice(newDevice)} - developerMode={true} - jsonMode={jsonMode} - answerArea={answerArea} - question={question} - hints={hints} - frameSource="about:blank" - previewURL="about:blank" - itemId="1" - onChange={(props) => { - onChangeAction(props); - - if ("jsonMode" in props) { - setJsonMode(props.jsonMode); - } - if ("answerArea" in props) { - setAnswerArea(props.answerArea); - } - if ("question" in props) { - setQuestion(props.question); - } - if ("hints" in props) { - setHints(props.hints); - } - }} + question={segmentWithLockedFigures} /> ); }; @@ -138,19 +59,8 @@ MafsWithLockedFiguresCurrent.parameters = { }; export const MafsWithLockedFiguresM2Flag = (): React.ReactElement => { - const [previewDevice, setPreviewDevice] = - React.useState("phone"); - const [jsonMode, setJsonMode] = React.useState(false); - const [answerArea, setAnswerArea] = React.useState< - PerseusAnswerArea | undefined | null - >(); - const [question, setQuestion] = React.useState( - segmentWithLockedFigures, - ); - const [hints, setHints] = React.useState | undefined>(); - return ( - { }, }, }} - previewDevice={previewDevice} - onPreviewDeviceChange={(newDevice) => setPreviewDevice(newDevice)} - developerMode={true} - jsonMode={jsonMode} - answerArea={answerArea} - question={question} - hints={hints} - frameSource="about:blank" - previewURL="about:blank" - itemId="1" - onChange={(props) => { - onChangeAction(props); - - if ("jsonMode" in props) { - setJsonMode(props.jsonMode); - } - if ("answerArea" in props) { - setAnswerArea(props.answerArea); - } - if ("question" in props) { - setQuestion(props.question); - } - if ("hints" in props) { - setHints(props.hints); - } - }} + question={segmentWithLockedFigures} /> ); }; @@ -200,51 +85,7 @@ MafsWithLockedFiguresM2Flag.parameters = { }; export const MafsWithLockedFiguresM2bFlag = (): React.ReactElement => { - const [previewDevice, setPreviewDevice] = - React.useState("phone"); - const [jsonMode, setJsonMode] = React.useState(false); - const [answerArea, setAnswerArea] = React.useState< - PerseusAnswerArea | undefined | null - >(); - const [question, setQuestion] = React.useState( - segmentWithLockedFigures, - ); - const [hints, setHints] = React.useState | undefined>(); - - return ( - setPreviewDevice(newDevice)} - developerMode={true} - jsonMode={jsonMode} - answerArea={answerArea} - question={question} - hints={hints} - frameSource="about:blank" - previewURL="about:blank" - itemId="1" - onChange={(props) => { - onChangeAction(props); - - if ("jsonMode" in props) { - setJsonMode(props.jsonMode); - } - if ("answerArea" in props) { - setAnswerArea(props.answerArea); - } - if ("question" in props) { - setQuestion(props.question); - } - if ("hints" in props) { - setHints(props.hints); - } - }} - /> - ); + return ; }; MafsWithLockedFiguresM2bFlag.parameters = { From cf5ad6d4dabe42b7cfc5a50997bd2793a05680e5 Mon Sep 17 00:00:00 2001 From: Nisha Yerunkar Date: Fri, 21 Jun 2024 16:25:17 -0700 Subject: [PATCH 2/2] rename EditorPageUtil to EditorPageWithStorybookPreview --- ...il.tsx => editor-page-with-storybook-preview.tsx} | 4 ++-- .../src/__stories__/editor-page.stories.tsx | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) rename packages/perseus-editor/src/__stories__/{editor-page-util.tsx => editor-page-with-storybook-preview.tsx} (98%) diff --git a/packages/perseus-editor/src/__stories__/editor-page-util.tsx b/packages/perseus-editor/src/__stories__/editor-page-with-storybook-preview.tsx similarity index 98% rename from packages/perseus-editor/src/__stories__/editor-page-util.tsx rename to packages/perseus-editor/src/__stories__/editor-page-with-storybook-preview.tsx index 4fb8d783ce..b2e859b1ec 100644 --- a/packages/perseus-editor/src/__stories__/editor-page-util.tsx +++ b/packages/perseus-editor/src/__stories__/editor-page-with-storybook-preview.tsx @@ -31,7 +31,7 @@ type Props = { const onChangeAction = action("onChange"); -function EditorPageUtil(props: Props) { +function EditorPageWithStorybookPreview(props: Props) { const [previewDevice, setPreviewDevice] = React.useState("phone"); const [jsonMode, setJsonMode] = React.useState(false); @@ -165,4 +165,4 @@ const styles = StyleSheet.create({ }, }); -export default EditorPageUtil; +export default EditorPageWithStorybookPreview; diff --git a/packages/perseus-editor/src/__stories__/editor-page.stories.tsx b/packages/perseus-editor/src/__stories__/editor-page.stories.tsx index 0469585946..286452723b 100644 --- a/packages/perseus-editor/src/__stories__/editor-page.stories.tsx +++ b/packages/perseus-editor/src/__stories__/editor-page.stories.tsx @@ -10,7 +10,7 @@ import {EditorPage} from ".."; import {segmentWithLockedFigures} from "../../../perseus/src/widgets/__testdata__/interactive-graph.testdata"; import {registerAllWidgetsAndEditorsForTesting} from "../util/register-all-widgets-and-editors-for-testing"; -import EditorPageUtil from "./editor-page-util"; +import EditorPageWithStorybookPreview from "./editor-page-with-storybook-preview"; import {flags} from "./flags-for-api-options"; import type { @@ -29,12 +29,12 @@ export default { const onChangeAction = action("onChange"); export const Demo = (): React.ReactElement => { - return ; + return ; }; export const MafsWithLockedFiguresCurrent = (): React.ReactElement => { return ( - { return ( - { - return ; + return ( + + ); }; MafsWithLockedFiguresM2bFlag.parameters = {