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;