diff --git a/.changeset/six-cars-agree.md b/.changeset/six-cars-agree.md new file mode 100644 index 0000000000..837b86f16b --- /dev/null +++ b/.changeset/six-cars-agree.md @@ -0,0 +1,5 @@ +--- +"@khanacademy/perseus": patch +--- + +Cleanup of Numeric Input stories diff --git a/packages/perseus-editor/src/__stories__/editor.stories.tsx b/packages/perseus-editor/src/__stories__/editor.stories.tsx index e039eeada8..eafc799caf 100644 --- a/packages/perseus-editor/src/__stories__/editor.stories.tsx +++ b/packages/perseus-editor/src/__stories__/editor.stories.tsx @@ -4,7 +4,7 @@ import {action} from "@storybook/addon-actions"; import * as React from "react"; import {Editor} from ".."; -import SideBySide from "../../../../testing/side-by-side"; +import SplitView from "../../../../testing/side-by-side"; import {question1} from "../__testdata__/numeric-input.testdata"; import {registerAllWidgetsAndEditorsForTesting} from "../util/register-all-widgets-and-editors-for-testing"; @@ -85,9 +85,9 @@ export const DemoInteractiveGraph = (): React.ReactElement => { // class to be above it. // TODO: Refactor to aphrodite styles instead of scoped CSS in Less.
- { /> } - rightTitle="Serialized Widget Options" + JSONTitle="Serialized Widget Options" jsonObject={options} />
diff --git a/packages/perseus/src/widgets/numeric-input/numeric-input.stories.tsx b/packages/perseus/src/widgets/numeric-input/numeric-input.stories.tsx index 0222b37690..4863b4772d 100644 --- a/packages/perseus/src/widgets/numeric-input/numeric-input.stories.tsx +++ b/packages/perseus/src/widgets/numeric-input/numeric-input.stories.tsx @@ -1,99 +1,188 @@ -import {action} from "@storybook/addon-actions"; import * as React from "react"; import {RendererWithDebugUI} from "../../../../../testing/renderer-with-debug-ui"; import {NumericInput} from "./numeric-input.class"; -import {question1} from "./numeric-input.testdata"; +import {decimalProblem, question1} from "./numeric-input.testdata"; -type StoryArgs = { - coefficient: boolean; - currentValue: string; - rightAlign: boolean; - size: "normal" | "small"; -}; +import type { + PerseusNumericInputWidgetOptions, + PerseusRenderer, +} from "@khanacademy/perseus-core"; +import type {Meta} from "@storybook/react"; -function generateProps(overwrite) { - const base = { - alignment: "", - answers: [], - containerSizeClass: "medium", - isLastUsedWidget: true, - coefficient: false, - currentValue: "", - problemNum: 0, - rightAlign: false, - size: "normal", - static: false, - widgetId: "widgetId", - findWidgets: action("findWidgets"), - onBlur: action("onBlur"), - onChange: action("onChange"), - onFocus: action("onFocus"), - trackInteraction: action("trackInteraction"), - } as const; +// We're using this format as storybook was not able to infer the type of the options. +// It also gives us a lovely hover view of the JSON structure. +const answerFormsArray: string = `[ + { + simplify: string; + name: string; + } +]`; - return {...base, ...overwrite}; -} +const answersArray: string = `[ + { + message: string; + value: number; + status: string; + answerForms: array; + strict: boolean; + maxError: number; + simplify: string; + } +]`; -export default { - title: "Perseus/Widgets/NumericInput", +const meta: Meta = { + component: NumericInput, + title: "Perseus/Widgets/Numeric Input", args: { coefficient: false, - currentValue: "8675309", + currentValue: "", rightAlign: false, + size: "normal", + answers: [ + { + status: "correct", + maxError: null, + strict: false, + value: 1252, + simplify: "required", + message: "", + }, + ], + answerForms: [ + {simplify: "required", name: "decimal"}, + {simplify: "required", name: "integer"}, + {simplify: "required", name: "mixed"}, + {simplify: "required", name: "percent"}, + {simplify: "required", name: "pi"}, + ], }, argTypes: { + answers: { + control: {type: "object"}, + description: + "A list of all the possible correct and incorrect answers", + table: { + type: { + summary: "array", + detail: answersArray, + }, + }, + }, + answerForms: { + control: {type: "object"}, + description: + "Used by examples, maybe not used and should be removed in the future", + table: { + type: { + summary: "array", + detail: answerFormsArray, + }, + }, + }, + currentValue: { + control: {type: "text"}, + description: "The current value of the input field", + table: { + type: {summary: "string"}, + }, + }, + coefficient: { + control: {type: "boolean"}, + description: + "A coefficient style number allows the student to use - for -1 and an empty string to mean 1.", + table: { + type: {summary: "boolean"}, + }, + }, + labelText: { + control: {type: "text"}, + description: + " Translatable Text; Text to describe this input. This will be shown to users using screenreaders.", + value: "What's the answer?", + table: { + type: {summary: "string"}, + }, + }, + rightAlign: { + control: {type: "boolean"}, + description: "Whether to right-align the text or not", + table: { + type: {summary: "boolean"}, + }, + }, size: { options: ["normal", "small"], control: {type: "radio"}, defaultValue: "normal", + description: + "Use size 'Normal' for all text boxes, unless there are multiple text boxes in one line and the answer area is too narrow to fit them.", + table: { + type: {summary: "string"}, + defaultValue: {summary: "normal"}, + }, + }, + static: { + control: {type: "boolean"}, + description: "Always false. Not used for this widget", + table: { + type: {summary: "boolean"}, + }, + }, + // ApiOptions and linterContext are large objects and not particularly applicable to this story, + // so we're hiding them from view to simplify the UI. + apiOptions: { + table: { + disable: true, + }, + }, + linterContext: { + table: { + disable: true, + }, }, }, }; -export const Question1 = (): React.ReactElement => { - return ; -}; +export default meta; -export const Interactive = (args: StoryArgs): React.ReactElement => { - const props = generateProps(args); - - return ; +const updateWidgetOptions = ( + question: PerseusRenderer, + widgetId: string, + options: PerseusNumericInputWidgetOptions, +): PerseusRenderer => { + const widget = question.widgets[widgetId]; + return { + ...question, + widgets: { + [widgetId]: { + ...widget, + options: { + ...widget.options, + ...options, + }, + }, + }, + }; }; -export const Sizes = (args: StoryArgs): React.ReactElement => { - const smallProps = generateProps({...args, size: "small"}); - const normalProps = generateProps({...args, size: "normal"}); - - return ( -
- - -
- ); +export const Default = ( + args: PerseusNumericInputWidgetOptions, +): React.ReactElement => { + const question = updateWidgetOptions(question1, "numeric-input 1", args); + return ; }; +Default.args = question1.widgets["numeric-input 1"].options; -export const TextAlignment = (args: StoryArgs): React.ReactElement => { - const leftProps = generateProps({...args, rightAlign: false}); - const rightProps = generateProps({...args, rightAlign: true}); - - return ( -
- - -
+export const WithExample = ( + args: PerseusNumericInputWidgetOptions, +): React.ReactElement => { + const question = updateWidgetOptions( + decimalProblem, + "numeric-input 1", + args, ); + return ; }; +WithExample.args = decimalProblem.widgets["numeric-input 1"].options; diff --git a/packages/perseus/src/widgets/numeric-input/numeric-input.testdata.ts b/packages/perseus/src/widgets/numeric-input/numeric-input.testdata.ts index f23d28f667..993395e345 100644 --- a/packages/perseus/src/widgets/numeric-input/numeric-input.testdata.ts +++ b/packages/perseus/src/widgets/numeric-input/numeric-input.testdata.ts @@ -36,6 +36,47 @@ export const question1: PerseusRenderer = { }, }; +export const decimalProblem: PerseusRenderer = { + // Added a floating question mark to keep enough space to show the examples. + content: "$12 + 0.52 =$ [[\u2603 numeric-input 1]] \n\n\n\n\n ?", + images: {}, + widgets: { + "numeric-input 1": { + graded: true, + version: { + major: 0, + minor: 0, + }, + static: false, + type: "numeric-input", + options: { + coefficient: false, + static: false, + answers: [ + { + status: "correct", + maxError: null, + strict: false, + value: 12.52, + simplify: "required", + message: "", + answerForms: ["decimal"], + }, + ], + labelText: "", + size: "normal", + answerForms: [ + { + simplify: "required", + name: "decimal", + }, + ], + }, + alignment: "default", + } as NumericInputWidget, + }, +}; + export const percentageProblem: PerseusRenderer = { content: "$5008 \\div 4 =$ [[\u2603 numeric-input 1]] ", images: {}, @@ -134,6 +175,7 @@ export const multipleAnswersWithDecimals: PerseusRenderer = { value: 12.2, simplify: "required", message: "", + answerForms: ["decimal"], }, { status: "correct", @@ -142,10 +184,17 @@ export const multipleAnswersWithDecimals: PerseusRenderer = { value: 13.4, simplify: "required", message: "", + answerForms: ["decimal"], }, ], labelText: "What's the answer?", size: "normal", + answerforms: [ + { + simplify: "required", + name: "decimal", + }, + ], }, alignment: "default", } as NumericInputWidget, diff --git a/testing/renderer-with-debug-ui.tsx b/testing/renderer-with-debug-ui.tsx index 7db7fb17ef..20f58c4866 100644 --- a/testing/renderer-with-debug-ui.tsx +++ b/testing/renderer-with-debug-ui.tsx @@ -14,7 +14,7 @@ import {scorePerseusItem} from "../packages/perseus/src/renderer-util"; import {mockStrings} from "../packages/perseus/src/strings"; import {registerAllWidgetsForTesting} from "../packages/perseus/src/util/register-all-widgets-for-testing"; -import SideBySide from "./side-by-side"; +import SplitView from "./side-by-side"; import type {PerseusRenderer} from "@khanacademy/perseus-core"; import type {ComponentProps} from "react"; @@ -40,9 +40,15 @@ export const RendererWithDebugUI = ({ const [isMobile, setIsMobile] = React.useState(false); const {strings} = usePerseusI18n(); + const controlledAPIOptions = { + ...apiOptions, + isMobile, + customKeypad: isMobile, // Use the mobile keypad for mobile + }; + return ( - } - left={ + renderer={ { return ( - - {leftTitle} - {left} + + {rendererTitle} + {renderer} - - {rightTitle} + + {JSONTitle} @@ -43,18 +41,10 @@ const styles = { sideBySide: { display: "flex", flexWrap: "wrap", - flexDirection: "row", + flexDirection: "column", gap: spacing.large_24, padding: `0px ${spacing.large_24}px`, }, - leftPanel: { - flexBasis: `${interactiveSizes.defaultBoxSize}px`, - }, - rightPanel: { - flexGrow: 1, - flexBasis: `${interactiveSizes.defaultBoxSize}px`, - maxWidth: "50%", - }, } as const; -export default SideBySide; +export default SplitView;