diff --git a/packages/perseus-editor/src/components/__docs__/blur-input.stories.tsx b/packages/perseus-editor/src/components/__docs__/blur-input.stories.tsx new file mode 100644 index 00000000000..0b944f2e49e --- /dev/null +++ b/packages/perseus-editor/src/components/__docs__/blur-input.stories.tsx @@ -0,0 +1,21 @@ +import {action} from "storybook/actions"; + +import BlurInput from "../blur-input"; + +import type {Meta, StoryObj} from "@storybook/react-vite"; + +const meta: Meta = { + title: "Editors/Components/Blur Input", + component: BlurInput, + args: { + onChange: action("onChange"), + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Example: Story = { + args: {value: "Test input"}, +}; diff --git a/packages/perseus-editor/src/components/__stories__/color-select.stories.tsx b/packages/perseus-editor/src/components/__docs__/color-select.stories.tsx similarity index 95% rename from packages/perseus-editor/src/components/__stories__/color-select.stories.tsx rename to packages/perseus-editor/src/components/__docs__/color-select.stories.tsx index 7acd925132e..ef6b85537e1 100644 --- a/packages/perseus-editor/src/components/__stories__/color-select.stories.tsx +++ b/packages/perseus-editor/src/components/__docs__/color-select.stories.tsx @@ -7,7 +7,7 @@ import type {LockedFigureColor} from "@khanacademy/perseus-core"; import type {Meta} from "@storybook/react-vite"; export default { - title: "PerseusEditor/Components/Color Select", + title: "Editors/Components/Color Select", component: ColorSelect, } as Meta; diff --git a/packages/perseus-editor/src/components/__docs__/color-swatch.stories.tsx b/packages/perseus-editor/src/components/__docs__/color-swatch.stories.tsx new file mode 100644 index 00000000000..6a43284630b --- /dev/null +++ b/packages/perseus-editor/src/components/__docs__/color-swatch.stories.tsx @@ -0,0 +1,21 @@ +import {getDefaultFigureForType} from "@khanacademy/perseus-core"; + +import ColorSwatch from "../../widgets/interactive-graph-editor/locked-figures/color-swatch"; + +import type {Meta, StoryObj} from "@storybook/react-vite"; + +const meta: Meta = { + title: "Editors/Components/Color Swatch", + component: ColorSwatch, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + color: getDefaultFigureForType("point").color, + filled: true, + }, +}; diff --git a/packages/perseus-editor/src/components/__stories__/device-framer.stories.tsx b/packages/perseus-editor/src/components/__docs__/device-framer.stories.tsx similarity index 96% rename from packages/perseus-editor/src/components/__stories__/device-framer.stories.tsx rename to packages/perseus-editor/src/components/__docs__/device-framer.stories.tsx index c1043a8f3e7..f20180f1201 100644 --- a/packages/perseus-editor/src/components/__stories__/device-framer.stories.tsx +++ b/packages/perseus-editor/src/components/__docs__/device-framer.stories.tsx @@ -7,7 +7,7 @@ import type {Meta, StoryObj} from "@storybook/react-vite"; const meta: Meta = { component: DeviceFramer, - title: "PerseusEditor/Components/Device Framer", + title: "Editors/Components/Device Framer", }; export default meta; diff --git a/packages/perseus-editor/src/components/__stories__/graph-settings.argtypes.ts b/packages/perseus-editor/src/components/__docs__/graph-settings.argtypes.ts similarity index 54% rename from packages/perseus-editor/src/components/__stories__/graph-settings.argtypes.ts rename to packages/perseus-editor/src/components/__docs__/graph-settings.argtypes.ts index 9ca08e807c2..97254befba2 100644 --- a/packages/perseus-editor/src/components/__stories__/graph-settings.argtypes.ts +++ b/packages/perseus-editor/src/components/__docs__/graph-settings.argtypes.ts @@ -1,83 +1,82 @@ export default { editableSettings: { control: { - type: "array", + type: "multi-select" as const, options: ["canvas", "graph", "snap", "image", "measure"], }, }, box: { control: { - type: "array", + type: "object" as const, }, }, range: { control: { - type: "object", + type: "object" as const, }, }, labels: { control: { - type: "object", + type: "object" as const, }, }, step: { control: { - type: "object", + type: "object" as const, }, }, gridStep: { control: { - type: "object", + type: "object" as const, }, }, snapStep: { control: { - type: "object", + type: "object" as const, }, }, valid: { control: { - type: "text", + type: "boolean" as const, }, }, backgroundImage: { control: { - type: "object", + type: "object" as const, }, }, markings: { control: { - type: "select", - }, - table: { - type: { - summary: '"axes" | "graph" | "grid" | "none"', - }, - }, - type: { - name: "enum", - value: ["graph", "grid", "none"], - required: false, + type: "select" as const, }, + options: ["axes", "graph", "grid", "none"], }, rulerLabel: { control: { - type: "text", + type: "text" as const, }, }, rulerTicks: { control: { - type: "number", + type: "number" as const, }, }, - showTooltips: { + showProtractor: { control: { - type: "boolean", + type: "boolean" as const, }, }, - onChange: { + showRuler: { + control: { + type: "boolean" as const, + }, + }, + showTooltips: { control: { - type: "function", + type: "boolean" as const, }, }, + onChange: { + control: false as const, + }, }; diff --git a/packages/perseus-editor/src/components/__docs__/graph-settings.stories.tsx b/packages/perseus-editor/src/components/__docs__/graph-settings.stories.tsx new file mode 100644 index 00000000000..1f48ccf9852 --- /dev/null +++ b/packages/perseus-editor/src/components/__docs__/graph-settings.stories.tsx @@ -0,0 +1,26 @@ +import GraphSettings from "../graph-settings"; + +import GraphSettingsArgTypes from "./graph-settings.argtypes"; + +import type {Meta, StoryObj} from "@storybook/react-vite"; + +const meta: Meta = { + title: "Editors/Components/Graph Settings", + component: GraphSettings, + argTypes: GraphSettingsArgTypes, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + // Separating the array props out because trying to editing them in + // the controls panel without a default value causes the story to crash. + range: [ + [-10, 10], + [-10, 10], + ], + }, +}; diff --git a/packages/perseus-editor/src/components/__docs__/interactive-graph-settings.argtypes.ts b/packages/perseus-editor/src/components/__docs__/interactive-graph-settings.argtypes.ts new file mode 100644 index 00000000000..ebf3346d6c7 --- /dev/null +++ b/packages/perseus-editor/src/components/__docs__/interactive-graph-settings.argtypes.ts @@ -0,0 +1,91 @@ +export default { + box: { + control: { + type: "object" as const, + }, + }, + + labels: { + control: { + type: "object" as const, + }, + }, + + range: { + control: { + type: "object" as const, + }, + }, + + step: { + control: { + type: "object" as const, + }, + }, + + gridStep: { + control: { + type: "object" as const, + }, + }, + + snapStep: { + control: { + type: "object" as const, + }, + }, + + valid: { + control: { + type: "text" as const, + }, + }, + + backgroundImage: { + control: { + type: "object" as const, + }, + }, + + markings: { + control: { + type: "select" as const, + }, + options: ["axes", "graph", "grid", "none"], + }, + + showProtractor: { + control: { + type: "boolean" as const, + }, + }, + + showRuler: { + control: { + type: "boolean" as const, + }, + }, + + showTooltips: { + control: { + type: "boolean" as const, + }, + }, + + rulerLabel: { + control: { + type: "select" as const, + }, + options: ["", "mm", "cm", "m", "km", "in", "ft", "yd", "mi"], + }, + + rulerTicks: { + control: { + type: "number" as const, + }, + }, + + onChange: { + control: false as const, + }, +}; diff --git a/packages/perseus-editor/src/components/__docs__/interactive-graph-settings.stories.tsx b/packages/perseus-editor/src/components/__docs__/interactive-graph-settings.stories.tsx new file mode 100644 index 00000000000..3474c929dce --- /dev/null +++ b/packages/perseus-editor/src/components/__docs__/interactive-graph-settings.stories.tsx @@ -0,0 +1,32 @@ +import InteractiveGraphSettings from "../../widgets/interactive-graph-editor/components/interactive-graph-settings"; + +import InteractiveGraphSettingsArgTypes from "./interactive-graph-settings.argtypes"; + +import type {Meta, StoryObj} from "@storybook/react-vite"; + +const meta: Meta = { + title: "Editors/Components/Interactive Graph Settings", + component: InteractiveGraphSettings, + argTypes: InteractiveGraphSettingsArgTypes, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + box: [288, 288] as const, + gridStep: [1, 1], + labels: ["x", "y"], + markings: "graph", + range: [ + [-10, 10], + [-10, 10], + ], + showProtractor: false, + showTooltips: false, + snapStep: [1, 1], + step: [1, 1], + }, +}; diff --git a/packages/perseus-editor/src/components/__docs__/locked-ellipse-settings.stories.tsx b/packages/perseus-editor/src/components/__docs__/locked-ellipse-settings.stories.tsx new file mode 100644 index 00000000000..917beded60e --- /dev/null +++ b/packages/perseus-editor/src/components/__docs__/locked-ellipse-settings.stories.tsx @@ -0,0 +1,70 @@ +import {getDefaultFigureForType} from "@khanacademy/perseus-core"; +import * as React from "react"; + +import LockedEllipseSettings from "../../widgets/interactive-graph-editor/locked-figures/locked-ellipse-settings"; + +import type {Meta, StoryFn, StoryObj} from "@storybook/react-vite"; + +const meta: Meta = { + title: "Editors/Components/Locked Ellipse Settings", + component: LockedEllipseSettings, +}; + +export default meta; + +const defaultProps = { + ...getDefaultFigureForType("ellipse"), + onChangeProps: () => {}, + onMove: () => {}, + onRemove: () => {}, +}; + +type Story = StoryFn; + +export const Default: StoryObj = { + args: defaultProps, +}; + +export const Controlled: Story = () => { + const [props, setProps] = React.useState(defaultProps); + + const handlePropsUpdate = (newProps) => { + setProps({ + ...props, + ...newProps, + }); + }; + + return ( + + ); +}; + +Controlled.parameters = { + chromatic: { + // Disabling because this is testing behavior, not visuals. + disableSnapshot: true, + }, +}; + +// Fully expanded view of the locked ellipse settings to allow snapshot testing. +export const Expanded: Story = () => { + const [expanded, setExpanded] = React.useState(true); + const [props, setProps] = React.useState(defaultProps); + + const handlePropsUpdate = (newProps) => { + setProps({ + ...props, + ...newProps, + }); + }; + + return ( + + ); +}; diff --git a/packages/perseus-editor/src/components/__docs__/locked-figures-section.stories.tsx b/packages/perseus-editor/src/components/__docs__/locked-figures-section.stories.tsx new file mode 100644 index 00000000000..3b0ef48fa49 --- /dev/null +++ b/packages/perseus-editor/src/components/__docs__/locked-figures-section.stories.tsx @@ -0,0 +1,76 @@ +import {ApiOptions} from "@khanacademy/perseus"; +import {getDefaultFigureForType} from "@khanacademy/perseus-core"; +import {View} from "@khanacademy/wonder-blocks-core"; +import {color, spacing} from "@khanacademy/wonder-blocks-tokens"; +import {StyleSheet} from "aphrodite"; +import * as React from "react"; +import {useState} from "react"; + +import LockedFiguresSection from "../../widgets/interactive-graph-editor/locked-figures/locked-figures-section"; + +import type {Meta, StoryFn, StoryObj} from "@storybook/react-vite"; + +const meta: Meta = { + title: "Editors/Components/Locked Figures Section", + component: LockedFiguresSection, +}; + +export default meta; + +export const Default: StoryObj = { + args: {}, +}; + +type Story = StoryFn; + +export const Controlled: Story = () => { + const [figures, setFigures] = useState([]); + + const handlePropsUpdate = (newProps) => { + setFigures(newProps.lockedFigures); + }; + + return ( + + ); +}; + +export const WithProdWidth: Story = () => { + const [figures, setFigures] = useState([ + getDefaultFigureForType("point"), + getDefaultFigureForType("line"), + ]); + + const handlePropsUpdate = (newProps) => { + setFigures(newProps.lockedFigures); + }; + + return ( + + + + ); +}; + +const contentSize = 310; +const padding = 10; +// Padding on each side +const containerSize = contentSize + 2 * padding; + +const styles = StyleSheet.create({ + prodSizeContainer: { + width: containerSize, + padding: padding, + marginInlineStart: spacing.medium_16, + border: `1px solid ${color.offBlack32}`, + borderRadius: spacing.xxxSmall_4, + }, +}); diff --git a/packages/perseus-editor/src/components/__docs__/locked-function-settings.stories.tsx b/packages/perseus-editor/src/components/__docs__/locked-function-settings.stories.tsx new file mode 100644 index 00000000000..28416f2b2b2 --- /dev/null +++ b/packages/perseus-editor/src/components/__docs__/locked-function-settings.stories.tsx @@ -0,0 +1,48 @@ +import {getDefaultFigureForType} from "@khanacademy/perseus-core"; +import * as React from "react"; + +import LockedFunctionSettings from "../../widgets/interactive-graph-editor/locked-figures/locked-function-settings"; + +import type {Meta, StoryFn, StoryObj} from "@storybook/react-vite"; + +const meta: Meta = { + title: "Editors/Components/Locked Function Settings", + component: LockedFunctionSettings, +}; + +export default meta; + +const defaultProps = { + ...getDefaultFigureForType("function"), + onChangeProps: () => {}, + onMove: () => {}, + onRemove: () => {}, +}; + +type Story = StoryFn; + +export const Default: StoryObj = { + args: defaultProps, +}; + +// Fully expanded view of the locked function settings to allow snapshot testing. +export const Expanded: Story = () => { + const [expanded, setExpanded] = React.useState(true); + const [props, setProps] = React.useState(defaultProps); + + const handlePropsUpdate = (newProps) => { + setProps({ + ...props, + ...newProps, + }); + }; + + return ( + + ); +}; diff --git a/packages/perseus-editor/src/components/__docs__/locked-label-settings.stories.tsx b/packages/perseus-editor/src/components/__docs__/locked-label-settings.stories.tsx new file mode 100644 index 00000000000..8da97fbdb35 --- /dev/null +++ b/packages/perseus-editor/src/components/__docs__/locked-label-settings.stories.tsx @@ -0,0 +1,48 @@ +import {getDefaultFigureForType} from "@khanacademy/perseus-core"; +import * as React from "react"; + +import LockedLabelSettings from "../../widgets/interactive-graph-editor/locked-figures/locked-label-settings"; + +import type {Meta, StoryFn, StoryObj} from "@storybook/react-vite"; + +const meta: Meta = { + title: "Editors/Components/Locked Label Settings", + component: LockedLabelSettings, +}; + +export default meta; + +const defaultProps = { + ...getDefaultFigureForType("label"), + onChangeProps: () => {}, + onMove: () => {}, + onRemove: () => {}, +}; + +type Story = StoryFn; + +export const Default: StoryObj = { + args: defaultProps, +}; + +// Fully expanded view of the locked label settings to allow snapshot testing. +export const Expanded: Story = () => { + const [expanded, setExpanded] = React.useState(true); + const [props, setProps] = React.useState(defaultProps); + + const handlePropsUpdate = (newProps) => { + setProps({ + ...props, + ...newProps, + }); + }; + + return ( + + ); +}; diff --git a/packages/perseus-editor/src/components/__docs__/locked-line-settings.stories.tsx b/packages/perseus-editor/src/components/__docs__/locked-line-settings.stories.tsx new file mode 100644 index 00000000000..9eed8039ea3 --- /dev/null +++ b/packages/perseus-editor/src/components/__docs__/locked-line-settings.stories.tsx @@ -0,0 +1,123 @@ +import {getDefaultFigureForType} from "@khanacademy/perseus-core"; +import * as React from "react"; + +import LockedLineSettings from "../../widgets/interactive-graph-editor/locked-figures/locked-line-settings"; + +import type {Meta, StoryFn, StoryObj} from "@storybook/react-vite"; + +const meta: Meta = { + title: "Editors/Components/Locked Line Settings", + component: LockedLineSettings, +}; + +export default meta; + +const defaultProps = { + ...getDefaultFigureForType("line"), + onChangeProps: () => {}, + onMove: () => {}, + onRemove: () => {}, +}; + +type Story = StoryFn; + +export const Default: StoryObj = { + args: defaultProps, +}; + +export const Controlled: Story = () => { + const [props, setProps] = React.useState(defaultProps); + + const handlePropsUpdate = (newProps) => { + setProps({ + ...props, + ...newProps, + }); + }; + + return ; +}; + +Controlled.parameters = { + chromatic: { + // Disabling because this is testing behavior, not visuals. + disableSnapshot: true, + }, +}; + +/** + * If the two points defining the line are the same, the line is invalid + * as that would give it a length of 0. An error message is displayed + * in this case. + */ +export const WithInvalidPoints: Story = () => { + const [props, setProps] = React.useState(defaultProps); + + const handlePropsUpdate = (newProps) => { + setProps({ + ...props, + ...newProps, + }); + }; + + return ( + + ); +}; + +// Fully expanded view of the locked line settings to allow snapshot testing. +export const Expanded: Story = () => { + const [expanded, setExpanded] = React.useState(true); + const [props, setProps] = React.useState(defaultProps); + + const handlePropsUpdate = (newProps) => { + setProps({ + ...props, + ...newProps, + }); + }; + + return ( + + ); +}; + +// Fully expanded view of the locked line settings to allow snapshot testing. +export const ExpandedNondefaultProps: Story = () => { + const [expanded, setExpanded] = React.useState(true); + const [props, setProps] = React.useState({ + ...defaultProps, + kind: "segment" as const, + color: "green" as const, + lineStyle: "dashed" as const, + }); + + const handlePropsUpdate = (newProps) => { + setProps({ + ...props, + ...newProps, + }); + }; + + return ( + + ); +}; diff --git a/packages/perseus-editor/src/components/__docs__/locked-point-settings.stories.tsx b/packages/perseus-editor/src/components/__docs__/locked-point-settings.stories.tsx new file mode 100644 index 00000000000..34646ea0318 --- /dev/null +++ b/packages/perseus-editor/src/components/__docs__/locked-point-settings.stories.tsx @@ -0,0 +1,87 @@ +import {getDefaultFigureForType} from "@khanacademy/perseus-core"; +import * as React from "react"; + +import LockedPointSettings from "../../widgets/interactive-graph-editor/locked-figures/locked-point-settings"; + +import type {Meta, StoryFn, StoryObj} from "@storybook/react-vite"; + +const meta: Meta = { + title: "Editors/Components/Locked Point Settings", + component: LockedPointSettings, +}; + +export default meta; + +const defaultProps = { + ...getDefaultFigureForType("point"), + onChangeProps: () => {}, + onMove: () => {}, + onRemove: () => {}, +}; + +type Story = StoryFn; + +export const Default: StoryObj = { + args: defaultProps, +}; + +export const Controlled: Story = () => { + const [props, setProps] = React.useState({ + ...defaultProps, + // Disabling because this doesn't test anything visual, just behavior. + chromatic: {disableSnapshot: true}, + }); + + const handlePropsUpdate = (newProps) => { + setProps({ + ...props, + ...newProps, + }); + }; + + return ; +}; + +// Fully expanded view of the locked point settings to allow snapshot testing. +export const Expanded: Story = () => { + const [expanded, setExpanded] = React.useState(true); + const [props, setProps] = React.useState(defaultProps); + + const handlePropsUpdate = (newProps) => { + setProps({ + ...props, + ...newProps, + }); + }; + + return ( + + ); +}; + +// Fully expanded view of the locked point settings to allow snapshot testing. +export const ExpandedNondefaultProps: Story = () => { + const [expanded, setExpanded] = React.useState(true); + const [props, setProps] = React.useState(defaultProps); + + const handlePropsUpdate = (newProps) => { + setProps({ + ...props, + ...newProps, + }); + }; + + return ( + + ); +}; diff --git a/packages/perseus-editor/src/components/__docs__/locked-polygon-settings.stories.tsx b/packages/perseus-editor/src/components/__docs__/locked-polygon-settings.stories.tsx new file mode 100644 index 00000000000..f180898b070 --- /dev/null +++ b/packages/perseus-editor/src/components/__docs__/locked-polygon-settings.stories.tsx @@ -0,0 +1,67 @@ +import {getDefaultFigureForType} from "@khanacademy/perseus-core"; +import * as React from "react"; + +import LockedPolygonSettings from "../../widgets/interactive-graph-editor/locked-figures/locked-polygon-settings"; + +import type {Meta, StoryFn, StoryObj} from "@storybook/react-vite"; + +const meta: Meta = { + title: "Editors/Components/Locked Polygon Settings", + component: LockedPolygonSettings, +}; + +export default meta; + +const defaultProps = { + ...getDefaultFigureForType("polygon"), + onChangeProps: () => {}, + onMove: () => {}, + onRemove: () => {}, +}; + +type Story = StoryFn; + +export const Default: StoryObj = { + args: defaultProps, +}; + +export const Controlled: Story = () => { + const [props, setProps] = React.useState({ + ...defaultProps, + // Disabling because this is testing behavior, not visuals. + chromatic: {disableSnapshot: true}, + }); + + const handlePropsUpdate = (newProps) => { + setProps({ + ...props, + ...newProps, + }); + }; + + return ( + + ); +}; + +// Fully expanded view of the locked polygon settings to allow snapshot testing. +export const Expanded: Story = () => { + const [expanded, setExpanded] = React.useState(true); + const [props, setProps] = React.useState(defaultProps); + + const handlePropsUpdate = (newProps) => { + setProps({ + ...props, + ...newProps, + }); + }; + + return ( + + ); +}; diff --git a/packages/perseus-editor/src/components/__docs__/locked-vector-settings.stories.tsx b/packages/perseus-editor/src/components/__docs__/locked-vector-settings.stories.tsx new file mode 100644 index 00000000000..742cacbad77 --- /dev/null +++ b/packages/perseus-editor/src/components/__docs__/locked-vector-settings.stories.tsx @@ -0,0 +1,76 @@ +import {getDefaultFigureForType} from "@khanacademy/perseus-core"; +import * as React from "react"; + +import LockedVectorSettings from "../../widgets/interactive-graph-editor/locked-figures/locked-vector-settings"; + +import type {Meta, StoryFn, StoryObj} from "@storybook/react-vite"; + +const meta: Meta = { + title: "Editors/Components/Locked Vector Settings", + component: LockedVectorSettings, +}; + +export default meta; + +const defaultProps = { + ...getDefaultFigureForType("vector"), + onChangeProps: () => {}, + onMove: () => {}, + onRemove: () => {}, +}; + +type Story = StoryFn; + +export const Default: StoryObj = { + args: defaultProps, +}; + +// Fully expanded view of the locked vector settings to allow snapshot testing. +export const Expanded: Story = () => { + const [expanded, setExpanded] = React.useState(true); + const [props, setProps] = React.useState(defaultProps); + + const handlePropsUpdate = (newProps) => { + setProps({ + ...props, + ...newProps, + }); + }; + + return ( + + ); +}; + +/** + * If the two points defining the vector are the same, the vector is invalid + * as that would give it a length of 0. An error message is displayed + * in this case. + */ +export const WithInvalidPoints: Story = () => { + const [props, setProps] = React.useState(defaultProps); + + const handlePropsUpdate = (newProps) => { + setProps({ + ...props, + ...newProps, + }); + }; + + return ( + + ); +}; diff --git a/packages/perseus-editor/src/components/__docs__/scrollless-number-text-field.stories.tsx b/packages/perseus-editor/src/components/__docs__/scrollless-number-text-field.stories.tsx new file mode 100644 index 00000000000..195f56254ee --- /dev/null +++ b/packages/perseus-editor/src/components/__docs__/scrollless-number-text-field.stories.tsx @@ -0,0 +1,61 @@ +import {View} from "@khanacademy/wonder-blocks-core"; +import {LabelLarge} from "@khanacademy/wonder-blocks-typography"; +import * as React from "react"; + +import ScrolllessNumberTextField from "../scrollless-number-text-field"; + +import type {Meta, StoryFn, StoryObj} from "@storybook/react-vite"; + +const meta: Meta = { + title: "Editors/Components/Scrollless Number Text Field", + component: ScrolllessNumberTextField, +}; + +export default meta; + +const defaultProps = { + value: "", + onChange: () => {}, +}; + +type Story = StoryFn; + +/** + * Uncontrolled story. Interact with the control panel to see the component + * reflect the props. + */ +export const Default: StoryObj = { + args: defaultProps, +}; + +/** + * Controlled story. The text field's state is managed by its parent. + * Typing in the input field should work as expected. + */ +export const Controlled: Story = () => { + const [value, setValue] = React.useState(""); + + return ; +}; + +/** + * In this example, we can see how the input field behaves when it is placed + * in a long page. Scrolling on the input field with a mouse wheel or trackpad + * changes the number, but does not scroll the page. + */ +export const LongPageScroll: Story = () => { + const [value, setValue] = React.useState(""); + + return ( + <> + Scroll down to see the input. + + + Observe that scrolling on the input field with a mouse wheel + changes the number, but does not scroll the page. + + + + + ); +}; diff --git a/packages/perseus-editor/src/components/__docs__/section-control-button.stories.tsx b/packages/perseus-editor/src/components/__docs__/section-control-button.stories.tsx new file mode 100644 index 00000000000..46575dc3c36 --- /dev/null +++ b/packages/perseus-editor/src/components/__docs__/section-control-button.stories.tsx @@ -0,0 +1,23 @@ +import trashIcon from "@phosphor-icons/core/bold/trash-bold.svg"; + +import SectionControlButton from "../section-control-button"; + +import type {Meta, StoryObj} from "@storybook/react-vite"; + +const meta: Meta = { + title: "Editors/Components/Section Control Button", + component: SectionControlButton, +}; + +export default meta; + +type Story = StoryObj; + +export const Example: Story = { + args: { + icon: trashIcon, + disabled: false, + onClick: () => {}, + title: "Remove image widget", + }, +}; diff --git a/packages/perseus-editor/src/components/__stories__/toggleable-caret.stories.tsx b/packages/perseus-editor/src/components/__docs__/toggleable-caret.stories.tsx similarity index 79% rename from packages/perseus-editor/src/components/__stories__/toggleable-caret.stories.tsx rename to packages/perseus-editor/src/components/__docs__/toggleable-caret.stories.tsx index 55e59b8b2c7..de5c0dd8dbc 100644 --- a/packages/perseus-editor/src/components/__stories__/toggleable-caret.stories.tsx +++ b/packages/perseus-editor/src/components/__docs__/toggleable-caret.stories.tsx @@ -3,16 +3,16 @@ import * as React from "react"; import ToggleableCaret from "../toggleable-caret"; -import type {StoryObj, Meta} from "@storybook/react-vite"; +import type {StoryObj, Meta, StoryFn} from "@storybook/react-vite"; type Story = StoryObj; export default { - title: "PerseusEditor/Components/Toggleable Caret", + title: "Editors/Components/Toggleable Caret", component: ToggleableCaret, } satisfies Meta; -export function Transitions() { +export const Transitions: StoryFn = () => { const [expanded, setExpanded] = React.useState(false); const toggler = React.useCallback(() => { setExpanded(!expanded); @@ -20,7 +20,7 @@ export function Transitions() { useInterval(toggler, 500, {schedulePolicy: SchedulePolicy.Immediately}); return ; -} +}; export const Expanded: Story = { args: {isExpanded: true}, diff --git a/packages/perseus-editor/src/components/__stories__/viewport-resizer.stories.tsx b/packages/perseus-editor/src/components/__docs__/viewport-resizer.stories.tsx similarity index 93% rename from packages/perseus-editor/src/components/__stories__/viewport-resizer.stories.tsx rename to packages/perseus-editor/src/components/__docs__/viewport-resizer.stories.tsx index 88ef9852425..d542f52271e 100644 --- a/packages/perseus-editor/src/components/__stories__/viewport-resizer.stories.tsx +++ b/packages/perseus-editor/src/components/__docs__/viewport-resizer.stories.tsx @@ -8,10 +8,11 @@ import type {Meta, StoryFn} from "@storybook/react-vite"; const meta: Meta = { component: ViewportResizer, - title: "PerseusEditor/Components/Viewport Resizer", + title: "Editors/Components/Viewport Resizer", }; export default meta; + type Story = StoryFn; export const Controlled: Story = () => { diff --git a/packages/perseus-editor/src/components/__stories__/blur-input.stories.tsx b/packages/perseus-editor/src/components/__stories__/blur-input.stories.tsx deleted file mode 100644 index 3b3d0d2ed0f..00000000000 --- a/packages/perseus-editor/src/components/__stories__/blur-input.stories.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import * as React from "react"; -import {action} from "storybook/actions"; - -import BlurInput from "../blur-input"; - -export default { - title: "PerseusEditor/Components/Blur Input", -}; - -export const Default = (): React.ReactElement => { - const [value, setValue] = React.useState(""); - - return ( - { - action("onChange")(newValue); - setValue(newValue); - }} - /> - ); -}; diff --git a/packages/perseus-editor/src/components/__stories__/color-swatch.stories.tsx b/packages/perseus-editor/src/components/__stories__/color-swatch.stories.tsx deleted file mode 100644 index 25c06d4a4fd..00000000000 --- a/packages/perseus-editor/src/components/__stories__/color-swatch.stories.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import {getDefaultFigureForType} from "@khanacademy/perseus-core"; -import * as React from "react"; - -import ColorSwatch from "../../widgets/interactive-graph-editor/locked-figures/color-swatch"; - -import type {Meta} from "@storybook/react-vite"; - -export default { - title: "PerseusEditor/Components/Color Swatch", - component: ColorSwatch, -} as Meta; - -export const Default = (args): React.ReactElement => { - return ; -}; - -// Set the default values in the control panel. -Default.args = { - color: getDefaultFigureForType("point").color, - filled: true, -}; diff --git a/packages/perseus-editor/src/components/__stories__/graph-settings.stories.tsx b/packages/perseus-editor/src/components/__stories__/graph-settings.stories.tsx deleted file mode 100644 index 91d5884ee39..00000000000 --- a/packages/perseus-editor/src/components/__stories__/graph-settings.stories.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import * as React from "react"; - -import GraphSettings from "../graph-settings"; - -import GraphSettingsArgTypes from "./graph-settings.argtypes"; - -export default { - title: "PerseusEditor/Components/Graph Settings", - component: GraphSettings, - argTypes: GraphSettingsArgTypes, -}; - -export const Default = (args): React.ReactElement => { - return ; -}; - -Default.args = { - // Separating the array props out because trying to editing them in - // the controls panel without a default value causes the story to crash. - range: [ - [-10, 10], - [-10, 10], - ], -}; diff --git a/packages/perseus-editor/src/components/__stories__/interactive-graph-settings.argtypes.ts b/packages/perseus-editor/src/components/__stories__/interactive-graph-settings.argtypes.ts deleted file mode 100644 index 42d9c22e67f..00000000000 --- a/packages/perseus-editor/src/components/__stories__/interactive-graph-settings.argtypes.ts +++ /dev/null @@ -1,149 +0,0 @@ -export default { - box: { - control: { - type: "array", - }, - }, - - labels: { - control: { - type: "array", - }, - type: { - name: "ReadonlyArray", - required: false, - }, - }, - - range: { - control: { - type: "array", - }, - type: { - name: "[Range, Range]", - required: false, - }, - }, - - step: { - control: { - type: "array", - }, - type: { - name: "[number, number]", - required: false, - }, - }, - - gridStep: { - control: { - type: "array", - }, - type: { - name: "[number, number]", - required: true, - }, - }, - - snapStep: { - control: { - type: "array", - }, - type: { - name: "[number, number]", - required: true, - }, - }, - - valid: { - control: { - type: "text", - }, - type: { - name: "string", - required: false, - }, - }, - - backgroundImage: { - control: { - type: "object", - }, - type: { - name: "PerseusImageBackground", - required: false, - }, - }, - - markings: { - control: { - type: "select", - }, - table: { - type: { - summary: '"graph" | "grid" | "none"', - }, - }, - type: { - name: "enum", - value: ["graph", "grid", "none"], - required: false, - }, - }, - - showProtractor: { - control: { - type: "boolean", - }, - type: { - name: "boolean", - required: false, - }, - }, - - showRuler: { - control: { - type: "boolean", - }, - type: { - name: "boolean", - required: false, - }, - }, - - showTooltips: { - control: { - type: "boolean", - }, - type: { - name: "boolean", - required: false, - }, - }, - - rulerLabel: { - control: { - type: "select", - }, - table: { - type: { - summary: '"mm", "cm", "m", "km", "in", "ft", "yd", "mi"', - }, - }, - type: { - name: "enum", - value: ["mm", "cm", "m", "km", "in", "ft", "yd", "mi"], - required: false, - }, - }, - - rulerTicks: { - control: { - type: "number", - }, - type: { - name: "number", - required: false, - }, - }, -}; diff --git a/packages/perseus-editor/src/components/__stories__/interactive-graph-settings.stories.tsx b/packages/perseus-editor/src/components/__stories__/interactive-graph-settings.stories.tsx deleted file mode 100644 index fcb0d785ba6..00000000000 --- a/packages/perseus-editor/src/components/__stories__/interactive-graph-settings.stories.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import * as React from "react"; - -import InteractiveGraphSettings from "../../widgets/interactive-graph-editor/components/interactive-graph-settings"; - -import InteractiveGraphSettingsArgTypes from "./interactive-graph-settings.argtypes"; - -import type {StoryObj} from "@storybook/react-vite"; - -export default { - title: "PerseusEditor/Components/Interactive Graph Settings", - component: InteractiveGraphSettings, - argTypes: InteractiveGraphSettingsArgTypes, -}; - -export const Default = (args): React.ReactElement => { - return ; -}; - -type StoryComponentType = StoryObj; - -// Set the default values in the control panel. -Default.args = { - box: [288, 288], - gridStep: [1, 1], - labels: ["x", "y"], - markings: "graph", - range: [ - [-10, 10], - [-10, 10], - ], - rulerLabel: "", - rulerTicks: 10, - showProtractor: false, - showRuler: false, - showTooltips: false, - snapStep: [1, 1], - step: [1, 1], -}; - -/** - * Example of what the InteractiveGraphEditor experience is when all - * props are controlled by the parent. (Checkboxes update when clicked, etc.) - */ -export const Controlled: StoryComponentType = { - render: function Render() { - const reducer = (state, newState) => { - return { - ...state, - ...newState, - }; - }; - - const [state, dispatch] = React.useReducer(reducer, {}); - - return ; - }, -}; diff --git a/packages/perseus-editor/src/components/__stories__/locked-ellipse-settings.stories.tsx b/packages/perseus-editor/src/components/__stories__/locked-ellipse-settings.stories.tsx deleted file mode 100644 index 8cbaffbe437..00000000000 --- a/packages/perseus-editor/src/components/__stories__/locked-ellipse-settings.stories.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import {getDefaultFigureForType} from "@khanacademy/perseus-core"; -import * as React from "react"; - -import LockedEllipseSettings from "../../widgets/interactive-graph-editor/locked-figures/locked-ellipse-settings"; - -import type {Meta, StoryObj} from "@storybook/react-vite"; - -export default { - title: "PerseusEditor/Components/Locked Ellipse Settings", - component: LockedEllipseSettings, -} as Meta; - -export const Default = (args): React.ReactElement => { - return ; -}; - -const defaultProps = { - ...getDefaultFigureForType("ellipse"), - onChangeProps: () => {}, - onMove: () => {}, - onRemove: () => {}, -}; - -type StoryComponentType = StoryObj; - -// Set the default values in the control panel. -Default.args = defaultProps; - -export const Controlled: StoryComponentType = { - render: function Render() { - const [props, setProps] = React.useState(defaultProps); - - const handlePropsUpdate = (newProps) => { - setProps({ - ...props, - ...newProps, - }); - }; - - return ( - - ); - }, -}; - -Controlled.parameters = { - chromatic: { - // Disabling because this is testing behavior, not visuals. - disableSnapshot: true, - }, -}; - -// Fully expanded view of the locked ellipse settings to allow snapshot testing. -export const Expanded: StoryComponentType = { - render: function Render() { - const [expanded, setExpanded] = React.useState(true); - const [props, setProps] = React.useState(defaultProps); - - const handlePropsUpdate = (newProps) => { - setProps({ - ...props, - ...newProps, - }); - }; - - return ( - - ); - }, -}; diff --git a/packages/perseus-editor/src/components/__stories__/locked-figures-section.stories.tsx b/packages/perseus-editor/src/components/__stories__/locked-figures-section.stories.tsx deleted file mode 100644 index 3935e631c9f..00000000000 --- a/packages/perseus-editor/src/components/__stories__/locked-figures-section.stories.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import {ApiOptions} from "@khanacademy/perseus"; -import {getDefaultFigureForType} from "@khanacademy/perseus-core"; -import {View} from "@khanacademy/wonder-blocks-core"; -import {color, spacing} from "@khanacademy/wonder-blocks-tokens"; -import {StyleSheet} from "aphrodite"; -import * as React from "react"; - -import LockedFiguresSection from "../../widgets/interactive-graph-editor/locked-figures/locked-figures-section"; - -import type {Meta, StoryObj} from "@storybook/react-vite"; - -export default { - title: "PerseusEditor/Components/Locked Figures Section", - component: LockedFiguresSection, -} as Meta; - -export const Default = (args): React.ReactElement => { - return ; -}; - -type StoryComponentType = StoryObj; - -// Set the default values in the control panel. -Default.args = {}; - -export const Controlled: StoryComponentType = { - render: function Render() { - const [figures, setFigures] = React.useState([]); - - const handlePropsUpdate = (newProps) => { - setFigures(newProps.lockedFigures); - }; - - return ( - - ); - }, -}; - -export const WithProdWidth: StoryComponentType = { - render: function Render() { - const [figures, setFigures] = React.useState([ - getDefaultFigureForType("point"), - getDefaultFigureForType("line"), - ]); - - const handlePropsUpdate = (newProps) => { - setFigures(newProps.lockedFigures); - }; - - return ( - - - - ); - }, -}; - -const contentSize = 310; -const padding = 10; -// Padding on each side -const containerSize = contentSize + 2 * padding; - -const styles = StyleSheet.create({ - prodSizeContainer: { - width: containerSize, - padding: padding, - marginInlineStart: spacing.medium_16, - border: `1px solid ${color.offBlack32}`, - borderRadius: spacing.xxxSmall_4, - }, -}); diff --git a/packages/perseus-editor/src/components/__stories__/locked-function-settings.stories.tsx b/packages/perseus-editor/src/components/__stories__/locked-function-settings.stories.tsx deleted file mode 100644 index c56db6d01df..00000000000 --- a/packages/perseus-editor/src/components/__stories__/locked-function-settings.stories.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import {getDefaultFigureForType} from "@khanacademy/perseus-core"; -import * as React from "react"; - -import LockedFunctionSettings from "../../widgets/interactive-graph-editor/locked-figures/locked-function-settings"; - -import type {Meta, StoryObj} from "@storybook/react-vite"; - -export default { - title: "PerseusEditor/Components/Locked Function Settings", - component: LockedFunctionSettings, -} as Meta; - -export const Default = (args): React.ReactElement => { - return ; -}; - -const defaultProps = { - ...getDefaultFigureForType("function"), - onChangeProps: () => {}, - onMove: () => {}, - onRemove: () => {}, -}; - -type StoryComponentType = StoryObj; - -// Set the default values in the control panel. -Default.args = defaultProps; - -export const Expanded: StoryComponentType = { - render: function Render() { - const [props, setProps] = React.useState(defaultProps); - - const handlePropsUpdate = (newProps) => { - setProps({ - ...props, - ...newProps, - }); - }; - - return ( - - ); - }, -}; diff --git a/packages/perseus-editor/src/components/__stories__/locked-label-settings.stories.tsx b/packages/perseus-editor/src/components/__stories__/locked-label-settings.stories.tsx deleted file mode 100644 index c37141c45c7..00000000000 --- a/packages/perseus-editor/src/components/__stories__/locked-label-settings.stories.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import {getDefaultFigureForType} from "@khanacademy/perseus-core"; -import * as React from "react"; - -import LockedLabelSettings from "../../widgets/interactive-graph-editor/locked-figures/locked-label-settings"; - -import type {Meta, StoryObj} from "@storybook/react-vite"; - -export default { - title: "PerseusEditor/Components/Locked Label Settings", - component: LockedLabelSettings, -} as Meta; - -export const Default = (args): React.ReactElement => { - return ; -}; - -const defaultProps = { - ...getDefaultFigureForType("label"), - onChangeProps: () => {}, - onMove: () => {}, - onRemove: () => {}, -}; - -type StoryComponentType = StoryObj; - -// Set the default values in the control panel. -Default.args = defaultProps; - -export const Expanded: StoryComponentType = { - render: function Render() { - const [props, setProps] = React.useState(defaultProps); - - const handlePropsUpdate = (newProps) => { - setProps({ - ...props, - ...newProps, - }); - }; - - return ( - - ); - }, -}; diff --git a/packages/perseus-editor/src/components/__stories__/locked-line-settings.stories.tsx b/packages/perseus-editor/src/components/__stories__/locked-line-settings.stories.tsx deleted file mode 100644 index aa78b0e6f2e..00000000000 --- a/packages/perseus-editor/src/components/__stories__/locked-line-settings.stories.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import {getDefaultFigureForType} from "@khanacademy/perseus-core"; -import * as React from "react"; - -import LockedLineSettings from "../../widgets/interactive-graph-editor/locked-figures/locked-line-settings"; - -import type {Meta, StoryObj} from "@storybook/react-vite"; - -export default { - title: "PerseusEditor/Components/Locked Line Settings", - component: LockedLineSettings, -} as Meta; - -export const Default = (args): React.ReactElement => { - return ; -}; - -const defaultProps = { - ...getDefaultFigureForType("line"), - onChangeProps: () => {}, - onMove: () => {}, - onRemove: () => {}, -}; - -type StoryComponentType = StoryObj; - -// Set the default values in the control panel. -Default.args = defaultProps; - -export const Controlled: StoryComponentType = { - render: function Render() { - const [props, setProps] = React.useState(defaultProps); - - const handlePropsUpdate = (newProps) => { - setProps({ - ...props, - ...newProps, - }); - }; - - return ( - - ); - }, -}; - -Controlled.parameters = { - chromatic: { - // Disabling because this is testing behavior, not visuals. - disableSnapshot: true, - }, -}; - -/** - * If the two points defining the line are the same, the line is invalid - * as that would give it a length of 0. An error message is displayed - * in this case. - */ -export const WithInvalidPoints: StoryComponentType = { - render: function Render() { - const [props, setProps] = React.useState(defaultProps); - - const handlePropsUpdate = (newProps) => { - setProps({ - ...props, - ...newProps, - }); - }; - - return ( - - ); - }, -}; - -// Fully expanded view of the locked point settings to allow snapshot testing. -export const Expanded: StoryComponentType = { - render: function Render() { - const [expanded, setExpanded] = React.useState(true); - const [props, setProps] = React.useState(defaultProps); - - const handlePropsUpdate = (newProps) => { - setProps({ - ...props, - ...newProps, - }); - }; - - return ( - - ); - }, -}; - -// Fully expanded view of the locked point settings to allow snapshot testing. -export const ExpandedNondefaultProps: StoryComponentType = { - render: function Render() { - const [expanded, setExpanded] = React.useState(true); - const [props, setProps] = React.useState({ - ...defaultProps, - kind: "segment" as const, - color: "green" as const, - lineStyle: "dashed" as const, - }); - - const handlePropsUpdate = (newProps) => { - setProps({ - ...props, - ...newProps, - }); - }; - - return ( - - ); - }, -}; diff --git a/packages/perseus-editor/src/components/__stories__/locked-point-settings.stories.tsx b/packages/perseus-editor/src/components/__stories__/locked-point-settings.stories.tsx deleted file mode 100644 index b2f2c631175..00000000000 --- a/packages/perseus-editor/src/components/__stories__/locked-point-settings.stories.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import {getDefaultFigureForType} from "@khanacademy/perseus-core"; -import * as React from "react"; - -import LockedPointSettings from "../../widgets/interactive-graph-editor/locked-figures/locked-point-settings"; - -import type {Meta, StoryObj} from "@storybook/react-vite"; - -export default { - title: "PerseusEditor/Components/Locked Point Settings", - component: LockedPointSettings, -} as Meta; - -export const Default = (args): React.ReactElement => { - return ; -}; - -const defaultProps = { - ...getDefaultFigureForType("point"), - onChangeProps: () => {}, - onMove: () => {}, - onRemove: () => {}, -}; - -type StoryComponentType = StoryObj; - -// Set the default values in the control panel. -Default.args = defaultProps; - -export const Controlled: StoryComponentType = { - render: function Render() { - const [props, setProps] = React.useState(defaultProps); - - const handlePropsUpdate = (newProps) => { - setProps({ - ...props, - ...newProps, - }); - }; - - return ( - - ); - }, -}; - -Controlled.parameters = { - chromatic: { - // Disabling because this doesn't test anything visual, just behavior. - disableSnapshot: true, - }, -}; - -// Fully expanded view of the locked point settings to allow snapshot testing. -export const Expanded: StoryComponentType = { - render: function Render() { - const [expanded, setExpanded] = React.useState(true); - const [props, setProps] = React.useState(defaultProps); - - const handlePropsUpdate = (newProps) => { - setProps({ - ...props, - ...newProps, - }); - }; - - return ( - - ); - }, -}; - -// Fully expanded view of the locked point settings to allow snapshot testing. -export const ExpandedNondefaultProps: StoryComponentType = { - render: function Render() { - const [expanded, setExpanded] = React.useState(true); - const [props, setProps] = React.useState(defaultProps); - - const handlePropsUpdate = (newProps) => { - setProps({ - ...props, - ...newProps, - }); - }; - - return ( - - ); - }, -}; diff --git a/packages/perseus-editor/src/components/__stories__/locked-polygon-settings.stories.tsx b/packages/perseus-editor/src/components/__stories__/locked-polygon-settings.stories.tsx deleted file mode 100644 index 83699ae3bd1..00000000000 --- a/packages/perseus-editor/src/components/__stories__/locked-polygon-settings.stories.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import {getDefaultFigureForType} from "@khanacademy/perseus-core"; -import * as React from "react"; - -import LockedPolygonSettings from "../../widgets/interactive-graph-editor/locked-figures/locked-polygon-settings"; - -import type {Meta, StoryObj} from "@storybook/react-vite"; - -export default { - title: "PerseusEditor/Components/Locked Polygon Settings", - component: LockedPolygonSettings, -} as Meta; - -export const Default = (args): React.ReactElement => { - return ; -}; - -const defaultProps = { - ...getDefaultFigureForType("polygon"), - onChangeProps: () => {}, - onMove: () => {}, - onRemove: () => {}, -}; - -type StoryComponentType = StoryObj; - -// Set the default values in the control panel. -Default.args = defaultProps; - -export const Controlled: StoryComponentType = { - render: function Render() { - const [props, setProps] = React.useState(defaultProps); - - const handlePropsUpdate = (newProps) => { - setProps({ - ...props, - ...newProps, - }); - }; - - return ( - - ); - }, -}; - -Controlled.parameters = { - chromatic: { - // Disabling because this is testing behavior, not visuals. - disableSnapshot: true, - }, -}; - -// Fully expanded view of the locked ellipse settings to allow snapshot testing. -export const Expanded: StoryComponentType = { - render: function Render() { - const [expanded, setExpanded] = React.useState(true); - const [props, setProps] = React.useState(defaultProps); - - const handlePropsUpdate = (newProps) => { - setProps({ - ...props, - ...newProps, - }); - }; - - return ( - - ); - }, -}; diff --git a/packages/perseus-editor/src/components/__stories__/locked-vector-settings.stories.tsx b/packages/perseus-editor/src/components/__stories__/locked-vector-settings.stories.tsx deleted file mode 100644 index 9db64af36f6..00000000000 --- a/packages/perseus-editor/src/components/__stories__/locked-vector-settings.stories.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import {getDefaultFigureForType} from "@khanacademy/perseus-core"; -import * as React from "react"; - -import LockedVectorSettings from "../../widgets/interactive-graph-editor/locked-figures/locked-vector-settings"; - -import type {Meta, StoryObj} from "@storybook/react-vite"; - -export default { - title: "PerseusEditor/Components/Locked Vector Settings", - component: LockedVectorSettings, -} as Meta; - -export const Default = (args): React.ReactElement => { - return ; -}; - -const defaultProps = { - ...getDefaultFigureForType("vector"), - onChangeProps: () => {}, - onMove: () => {}, - onRemove: () => {}, -}; - -type StoryComponentType = StoryObj; - -// Set the default values in the control panel. -Default.args = defaultProps; - -export const Expanded: StoryComponentType = { - render: function Render() { - const [props, setProps] = React.useState(defaultProps); - - const handlePropsUpdate = (newProps) => { - setProps({ - ...props, - ...newProps, - }); - }; - - return ( - - ); - }, -}; - -/** - * If the two points defining the vector are the same, the vector is invalid - * as that would give it a length of 0. An error message is displayed - * in this case. - */ -export const WithInvalidPoints: StoryComponentType = { - render: function Render() { - const [props, setProps] = React.useState(defaultProps); - - const handlePropsUpdate = (newProps) => { - setProps({ - ...props, - ...newProps, - }); - }; - - return ( - - ); - }, -}; diff --git a/packages/perseus-editor/src/components/__stories__/scrollless-number-text-field.stories.tsx b/packages/perseus-editor/src/components/__stories__/scrollless-number-text-field.stories.tsx deleted file mode 100644 index f410296a404..00000000000 --- a/packages/perseus-editor/src/components/__stories__/scrollless-number-text-field.stories.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import {View} from "@khanacademy/wonder-blocks-core"; -import {LabelLarge} from "@khanacademy/wonder-blocks-typography"; -import * as React from "react"; - -import ScrolllessNumberTextField from "../scrollless-number-text-field"; - -import type {StoryObj, Meta} from "@storybook/react-vite"; - -export default { - title: "PerseusEditor/Components/Scrollless Number Text Field", - component: ScrolllessNumberTextField, -} as Meta; - -/** - * Uncontrolled story. Interact with the control panel to see the component - * reflect the props. - */ -export const Default = (args): React.ReactElement => { - return ; -}; - -const defaultProps = { - value: "", - onChange: () => {}, -}; - -type StoryComponentType = StoryObj; - -// Set the default values in the control panel. -Default.args = defaultProps; - -/** - * Controlled story. The text field's state is managed by its parent. - * Typing in the input field should work as expected. - */ -export const Controlled: StoryComponentType = { - render: function Render() { - const [value, setValue] = React.useState(""); - - return ; - }, -}; - -Controlled.parameters = { - chromatic: { - // Disable the snapshot for this story because it's testing - // behavior, not visuals. - disableSnapshot: true, - }, -}; - -/** - * In this example, we can see how the input field behaves when it is placed - * in a long page. Scrolling on the input field with a mouse wheel or trackpad - * changes the number, but does not scroll the page. - */ -export const LongPageScroll: StoryComponentType = { - render: function Render() { - const [value, setValue] = React.useState(""); - - return ( - <> - Scroll down to see the input. - - - Observe that scrolling on the input field with a mouse wheel - changes the number, but does not scroll the page. - - - - - ); - }, -}; - -LongPageScroll.parameters = { - chromatic: { - // Disable the snapshot for this story because it's testing - // behavior, not visuals. - disableSnapshot: true, - }, -}; diff --git a/packages/perseus-editor/src/components/__stories__/section-control-button.stories.tsx b/packages/perseus-editor/src/components/__stories__/section-control-button.stories.tsx deleted file mode 100644 index 9e628bf4ae0..00000000000 --- a/packages/perseus-editor/src/components/__stories__/section-control-button.stories.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import trashIcon from "@phosphor-icons/core/bold/trash-bold.svg"; -import * as React from "react"; - -import SectionControlButton from "../section-control-button"; - -type StoryArgs = Record; - -type Story = { - title: string; -}; - -export default { - title: "PerseusEditor/Components/Section Control Button", -} as Story; - -export const ButtonForEditingSectionsOfContentWithInArticleEditor = ( - args: StoryArgs, -): React.ReactElement => { - return ( - {}} - title="Remove image widget" - /> - ); -}; diff --git a/packages/perseus-editor/src/components/blur-input.tsx b/packages/perseus-editor/src/components/blur-input.tsx index e7a71a98c45..956cf5de669 100644 --- a/packages/perseus-editor/src/components/blur-input.tsx +++ b/packages/perseus-editor/src/components/blur-input.tsx @@ -39,13 +39,13 @@ class BlurInput extends React.Component { this.setState({value: nextProps.value}); } - handleChange: (e: any) => void = (e) => { + handleChange(e: React.ChangeEvent) { this.setState({value: e.target.value}); - }; + } - handleBlur: (e: any) => void = (e) => { + handleBlur(e: React.FocusEvent) { this.props.onChange(e.target.value); - }; + } focus() { this.input.current?.focus(); diff --git a/packages/perseus-editor/src/components/drag-target.tsx b/packages/perseus-editor/src/components/drag-target.tsx index 6319ccbcb41..4144ebba0a0 100644 --- a/packages/perseus-editor/src/components/drag-target.tsx +++ b/packages/perseus-editor/src/components/drag-target.tsx @@ -29,7 +29,7 @@ type Props = { component?: any; shouldDragHighlight: (any) => boolean; style?: any; - children?: any; + children?: React.ReactNode | React.ReactNode[]; className?: string; }; diff --git a/packages/perseus-editor/src/components/dropdown-option.tsx b/packages/perseus-editor/src/components/dropdown-option.tsx index 79ea833ccee..893adf27deb 100644 --- a/packages/perseus-editor/src/components/dropdown-option.tsx +++ b/packages/perseus-editor/src/components/dropdown-option.tsx @@ -63,18 +63,18 @@ class Option extends React.Component { // @ts-expect-error - TS2564 - Property 'node' has no initializer and is not definitely assigned in the constructor. node: HTMLDivElement; - handleKeyDown(event: any): void { + handleKeyDown(event: React.KeyboardEvent): void { const {onDropdownClose} = this.props; const pressedKey = event.key; - const focusedElement = event.target; + const focusedElement = event.target as HTMLElement; if (pressedKey === "ArrowDown" && focusedElement.nextSibling) { event.preventDefault(); - focusedElement.nextSibling.focus(); + (focusedElement.nextSibling as HTMLElement).focus(); } if (pressedKey === "ArrowUp" && focusedElement.previousSibling) { event.preventDefault(); - focusedElement.previousSibling.focus(); + (focusedElement.previousSibling as HTMLElement).focus(); } if ( pressedKey === "ArrowUp" && @@ -116,15 +116,14 @@ class Option extends React.Component { disabled && styles.cursorDefault, hideFocusState && styles.noFocus, )} - // @ts-expect-error - TS2322 - Type '(value: string) => void' is not assignable to type 'MouseEventHandler'. - onClick={(value: string) => { + onClick={() => { if (!disabled && onClick) { - // @ts-expect-error - TS2554 - Expected 0 arguments, but got 1. - onClick(value); + onClick(); } }} - // @ts-expect-error - TS2322 - Type '(event: KeyboardEvent) => void' is not assignable to type 'KeyboardEventHandler'. - onKeyDown={(event: KeyboardEvent) => this.handleKeyDown(event)} + onKeyDown={(event: React.KeyboardEvent) => + this.handleKeyDown(event) + } aria-disabled={disabled} aria-label={ariaLabel} data-testid={testId} @@ -148,11 +147,7 @@ class Option extends React.Component { } } -// TODO(kevinb): -// - keep the the option group within the page bounds -// - do we really want to dismiss this on scroll -// - how does the drop down interact with tooltips? what's the z-index order? -class OptionGroup extends React.Component<{ +type OptionGroupProps = { // An event when an option is selected onSelected: (value: string) => void; // The list of options to display @@ -167,14 +162,27 @@ class OptionGroup extends React.Component<{ // Use caution when using this - keyboard users need to know what // they're focused on in order to interact with the product! hideFocusState?: boolean; -}> { - // @ts-expect-error - TS2564 - Property 'focusedElement' has no initializer and is not definitely assigned in the constructor. - focusedElement: Element; +}; + +type OptionGroupState = { + focusedElement: Element | undefined; +}; + +// TODO(kevinb): +// - keep the the option group within the page bounds +// - do we really want to dismiss this on scroll +// - how does the drop down interact with tooltips? what's the z-index order? +class OptionGroup extends React.Component { + constructor(props: OptionGroupProps) { + super(props); + this.state = { + focusedElement: undefined, + }; + } componentDidMount() { - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - if (this.focusedElement) { - findAndFocusElement(this.focusedElement); + if (this.state.focusedElement !== undefined) { + findAndFocusElement(this.state.focusedElement); } } @@ -190,31 +198,23 @@ class OptionGroup extends React.Component<{ return (
| undefined'. - style={{top}} + style={{top: 0}} className={css( styles.optionGroup, noMargin && styles.optionGroupNoMargin, )} > {React.Children.map(children, (child, index) => { - // @ts-expect-error - TS2532 - Object is possibly 'undefined'. + if (child === undefined) { + return null; + } const selected = selectedValues.includes(child.props.value); - const reference = - selected || index === 0 - ? (node: Element) => (this.focusedElement = node) - : null; - - // @ts-expect-error - TS2769 - No overload matches this call. return React.cloneElement(child, { - // @ts-expect-error - TS2532 - Object is possibly 'undefined'. ...child.props, key: index, selected: selected, - // @ts-expect-error - TS2532 - Object is possibly 'undefined'. onClick: () => onSelected(child.props.value), - ref: reference, onDropdownClose: onDropdownClose, hideFocusState: hideFocusState, }); diff --git a/packages/perseus-editor/src/components/form-wrapped-text-field.tsx b/packages/perseus-editor/src/components/form-wrapped-text-field.tsx index 28faa74024c..4b40129f119 100644 --- a/packages/perseus-editor/src/components/form-wrapped-text-field.tsx +++ b/packages/perseus-editor/src/components/form-wrapped-text-field.tsx @@ -47,8 +47,8 @@ type Props = { style?: React.CSSProperties | undefined; className?: string; width?: number | string; - grow?: boolean | number; - shrink?: boolean | number; + grow?: number; + shrink?: number; // HACK(mattmorgan) to enforce WB LabelMedium-like styles on this older // component. labelMediumInputText?: boolean; @@ -123,22 +123,19 @@ class FormWrappedTextField extends React.Component { } = this.props; const {focused} = this.state; - const extraStyles: any = {}; + const extraStyles: React.CSSProperties = {}; const spanStyle = [styles.input, styles.container]; - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - if (width) { + if (width !== undefined) { extraStyles.width = width; } - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - if (grow) { - extraStyles.flexGrow = grow === true ? 1 : grow; + if (grow !== undefined) { + extraStyles.flexGrow = grow === undefined ? 1 : grow; } - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (shrink || shrink === 0) { - extraStyles.flexShrink = shrink === true ? 0 : shrink; + extraStyles.flexShrink = shrink === undefined ? 0 : shrink; } if (backgroundColor) { diff --git a/packages/perseus-editor/src/components/widget-editor.tsx b/packages/perseus-editor/src/components/widget-editor.tsx index 5e32e0a4ff5..dd1350da859 100644 --- a/packages/perseus-editor/src/components/widget-editor.tsx +++ b/packages/perseus-editor/src/components/widget-editor.tsx @@ -42,7 +42,7 @@ const _upgradeWidgetInfo = (props: WidgetEditorProps): PerseusWidget => { // We can't call serialize here because this.refs.widget // doesn't exist before this component is mounted. const filteredProps = excludeDenylistKeys(props); - return applyDefaultsToWidget(filteredProps as any); + return applyDefaultsToWidget(filteredProps as PerseusWidget); }; // This component handles upgading widget editor props via prop diff --git a/packages/perseus-editor/src/widgets/interactive-graph-editor/components/interactive-graph-settings.tsx b/packages/perseus-editor/src/widgets/interactive-graph-editor/components/interactive-graph-settings.tsx index b52e96edba2..49baac87ac6 100644 --- a/packages/perseus-editor/src/widgets/interactive-graph-editor/components/interactive-graph-settings.tsx +++ b/packages/perseus-editor/src/widgets/interactive-graph-editor/components/interactive-graph-settings.tsx @@ -127,6 +127,22 @@ type State = { backgroundImage: PerseusImageBackground; }; +type DefaultProps = { + box: Props["box"]; + labels: Props["labels"]; + labelLocation: Props["labelLocation"]; + range: Props["range"]; + step: Props["step"]; + gridStep: Props["gridStep"]; + snapStep: Props["snapStep"]; + valid: Props["valid"]; + backgroundImage: Props["backgroundImage"]; + markings: Props["markings"]; + showProtractor: Props["showProtractor"]; + showTooltips: Props["showTooltips"]; + showAxisArrows: Props["showAxisArrows"]; +}; + class InteractiveGraphSettings extends React.Component { _isMounted = false; @@ -156,7 +172,7 @@ class InteractiveGraphSettings extends React.Component { }; } - static defaultProps = { + static defaultProps: DefaultProps = { box: [ interactiveSizes.defaultBoxSizeSmall, interactiveSizes.defaultBoxSizeSmall, diff --git a/packages/perseus-editor/src/widgets/label-image-editor/marker.tsx b/packages/perseus-editor/src/widgets/label-image-editor/marker.tsx index f40cb6713f2..7544e81e451 100644 --- a/packages/perseus-editor/src/widgets/label-image-editor/marker.tsx +++ b/packages/perseus-editor/src/widgets/label-image-editor/marker.tsx @@ -167,7 +167,7 @@ export default class Marker extends React.Component { styles.dropdownPositionWithArrow, )} > -