diff --git a/.changeset/old-lamps-wonder.md b/.changeset/old-lamps-wonder.md new file mode 100644 index 0000000000..0eaff9da1e --- /dev/null +++ b/.changeset/old-lamps-wonder.md @@ -0,0 +1,6 @@ +--- +"@khanacademy/math-input": patch +"@khanacademy/perseus": patch +--- + +Improve prop types for various components diff --git a/packages/perseus-editor/src/__stories__/interactive-graph-editor.stories.tsx b/packages/perseus-editor/src/__stories__/interactive-graph-editor.stories.tsx index 832b73d6c7..51137cfd45 100644 --- a/packages/perseus-editor/src/__stories__/interactive-graph-editor.stories.tsx +++ b/packages/perseus-editor/src/__stories__/interactive-graph-editor.stories.tsx @@ -172,7 +172,7 @@ MafsWithLockedFiguresCurrent.parameters = { chromatic: { // Disabling because this isn't visually testing anything on the // initial load of the editor page. - disable: true, + disableSnapshot: true, }, }; @@ -413,7 +413,7 @@ WithSaveWarnings.parameters = { // Disabling because this isn't testing anything visually on the // editor page. It's testing the error message, which don't // even show up on the initial load. - disable: true, + disableSnapshot: true, }, }; 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 index 7a00a45a63..6fabb8ce98 100644 --- 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 @@ -45,7 +45,7 @@ Controlled.parameters = { chromatic: { // Disable the snapshot for this story because it's testing // behavior, not visuals. - disable: true, + disableSnapshot: true, }, }; @@ -77,6 +77,6 @@ LongPageScroll.parameters = { chromatic: { // Disable the snapshot for this story because it's testing // behavior, not visuals. - disable: true, + disableSnapshot: true, }, }; diff --git a/packages/perseus/src/components/__stories__/graph.stories.tsx b/packages/perseus/src/components/__stories__/graph.stories.tsx index 5215c417ce..eb9e28702a 100644 --- a/packages/perseus/src/components/__stories__/graph.stories.tsx +++ b/packages/perseus/src/components/__stories__/graph.stories.tsx @@ -1,27 +1,22 @@ -import * as React from "react"; - import Graph from "../graph"; import type {StoryObj, Meta} from "@storybook/react"; -type StoryArgs = StoryObj; - -type Story = Meta; +type Story = StoryObj; const size = 200; -export default { +const meta: Meta = { title: "Perseus/Components/Graph", -} as Story; - -export const SquareBoxSizeAndOtherwiseEmpty = ( - args: StoryArgs, -): React.ReactElement => { - return ; + component: Graph, + args: { + box: [size, size], + }, }; +export default meta; + +export const SquareBoxSizeAndOtherwiseEmpty: Story = {}; -export const LabeledSquaredBox = (args: StoryArgs): React.ReactElement => { - return ( - - ); +export const LabeledSquaredBox: Story = { + args: {labels: ["First label", "Second label"]}, }; diff --git a/packages/perseus/src/components/__stories__/graphie.stories.tsx b/packages/perseus/src/components/__stories__/graphie.stories.tsx index d0ee574c36..e0d779d4ca 100644 --- a/packages/perseus/src/components/__stories__/graphie.stories.tsx +++ b/packages/perseus/src/components/__stories__/graphie.stories.tsx @@ -6,28 +6,27 @@ import Graphie from "../graphie"; import type {StoryObj, Meta} from "@storybook/react"; -type StoryArgs = StoryObj; - -type Story = Meta; +type Story = StoryObj; const size = 200; -export default { +const meta: Meta = { title: "Perseus/Components/Graphie", -} as Story; - -export const SquareBoxSizeAndOtherwiseEmpty = ( - args: StoryArgs, -): React.ReactElement => { - return ( - {}} - setup={() => {}} - /> - ); + component: Graphie, + args: { + box: [size, size], + setup: () => {}, + setDrawingAreaAvailable: () => {}, + }, }; +export default meta; + +export const SquareBoxSizeAndOtherwiseEmpty: Story = {}; -export const PieChartGraphieLabels = (args: StoryArgs): React.ReactElement => { +/** + * A demonstration of a Graphie rendered using the Perseus `Renderer` complete + * with overlaid labels and an image caption below. + */ +export const PieChartGraphieLabels = () => { return ; }; diff --git a/packages/perseus/src/components/__stories__/hud.stories.tsx b/packages/perseus/src/components/__stories__/hud.stories.tsx index cc3a402c76..9ab2812f25 100644 --- a/packages/perseus/src/components/__stories__/hud.stories.tsx +++ b/packages/perseus/src/components/__stories__/hud.stories.tsx @@ -1,35 +1,21 @@ -import * as React from "react"; +import {action} from "@storybook/addon-actions"; import Hud from "../hud"; import type {StoryObj, Meta} from "@storybook/react"; -type StoryArgs = StoryObj; +type Story = StoryObj; -type Story = Meta; - -export default { +const meta: Meta = { title: "Perseus/Components/HUD", -} as Story; - -export const TestMessageDisabled = (args: StoryArgs): React.ReactElement => { - return ( - {}} - /> - ); + component: Hud, + args: { + enabled: true, + fixedPosition: false, + message: "Test message", + onClick: action("onClick"), + }, }; +export default meta; -export const TestMessageEnabled = (args: StoryArgs): React.ReactElement => { - return ( - {}} - /> - ); -}; +export const Default: Story = {}; diff --git a/packages/perseus/src/components/__stories__/icon.stories.tsx b/packages/perseus/src/components/__stories__/icon.stories.tsx index 08cbd36e90..342a4a86bf 100644 --- a/packages/perseus/src/components/__stories__/icon.stories.tsx +++ b/packages/perseus/src/components/__stories__/icon.stories.tsx @@ -1,16 +1,13 @@ -import * as React from "react"; - import * as IconPaths from "../../icon-paths"; import IconComponent from "../icon"; import type {StoryObj, Meta} from "@storybook/react"; -type StoryArgs = StoryObj; - -type Story = Meta; +type Story = StoryObj; -export default { - title: "Perseus/Components", +const meta: Meta = { + title: "Perseus/Components/Icon", + component: IconComponent, args: { color: "#808", size: 25, @@ -27,12 +24,12 @@ export default { control: "select", }, }, -} as Story; +}; +export default meta; -export const Icon = (args: StoryArgs): React.ReactElement => ( - -); +export const Icon: Story = { + args: { + style: {display: "block"}, + icon: IconPaths.iconCheck, + }, +}; diff --git a/packages/perseus/src/components/__stories__/image-loader.stories.tsx b/packages/perseus/src/components/__stories__/image-loader.stories.tsx index 08abacefea..433764a5fd 100644 --- a/packages/perseus/src/components/__stories__/image-loader.stories.tsx +++ b/packages/perseus/src/components/__stories__/image-loader.stories.tsx @@ -1,60 +1,48 @@ -/* eslint-disable @khanacademy/ts-no-error-suppressions */ import * as React from "react"; -type StoryArgs = Record; - -type Story = { - title: string; -}; - import ImageLoader from "../image-loader"; +import type {Meta, StoryObj} from "@storybook/react"; + const svgUrl = "http://www.khanacademy.org/images/ohnoes-concerned.svg"; const imgUrl = "https://www.khanacademy.org/images/hand-tree.new.png"; -export default { +const meta: Meta = { title: "Perseus/Components/Image Loader", -} as Story; - -export const SvgImage = (args: StoryArgs): React.ReactElement => { - return ( - {}} - /> - ); + component: ImageLoader, + args: { + preloader: null, + imgProps: { + alt: "ALT", + }, + onUpdate: () => {}, + }, + parameters: { + chromatic: { + // This component only deals with loading images and providing a + // fallback if it fails. This is not very useful to snapshot so + // we're disabling it. + disableSnapshot: true, + }, + }, +}; +export default meta; + +type Story = StoryObj; + +export const SvgImage: Story = { + args: { + src: svgUrl, + }, }; -export const PngImage = (args: StoryArgs): React.ReactElement => { - return ( - {}} - /> - ); +export const PngImage: Story = { + args: {src: imgUrl}, }; -export const InvalidImageWithChildrenForFailedLoading = ( - args: StoryArgs, -): React.ReactElement => { - return ( - {}} - > - You can see me! The image failed to load. - - ); +export const InvalidImageWithChildrenForFailedLoading: Story = { + args: { + src: "http://abcdefiahofshiaof.noway.badimage.com", + children: You can see me! The image failed to load., + }, }; diff --git a/packages/perseus/src/components/__stories__/info-tip.stories.tsx b/packages/perseus/src/components/__stories__/info-tip.stories.tsx index 2e72878eb1..d4999a6a16 100644 --- a/packages/perseus/src/components/__stories__/info-tip.stories.tsx +++ b/packages/perseus/src/components/__stories__/info-tip.stories.tsx @@ -2,34 +2,40 @@ import * as React from "react"; import InfoTip from "../info-tip"; -type StoryArgs = Record; +import type {Meta, StoryObj} from "@storybook/react"; -type Story = { - title: string; +const meta: Meta = { + title: "Perseus/Components/Info Tip", + component: InfoTip, }; +export default meta; -export default { - title: "Perseus/Components/Info Tip", -} as Story; +type Story = StoryObj; -export const TextOnMouseover = (args: StoryArgs): React.ReactElement => { - return Sample text; +export const TextOnMouseover: Story = { + args: { + children: "Sample text", + }, }; -export const CodeInText = (args: StoryArgs): React.ReactElement => { - return ( - - Settings that you add here are available to the program as an object - returned by Program.settings() - - ); +export const CodeInText: Story = { + args: { + children: ( + <> + Settings that you add here are available to the program as an + object returned by Program.settings() + + ), + }, }; -export const MultipleElements = (args: StoryArgs): React.ReactElement => { - return ( - -

First paragraph

-

Second paragraph

-
- ); +export const MultipleElements: Story = { + args: { + children: ( + <> +

First paragraph

+

Second paragraph

+ + ), + }, }; diff --git a/packages/perseus/src/components/__stories__/inline-icon.stories.tsx b/packages/perseus/src/components/__stories__/inline-icon.stories.tsx index d9e0754a7b..3f4ef5e6e4 100644 --- a/packages/perseus/src/components/__stories__/inline-icon.stories.tsx +++ b/packages/perseus/src/components/__stories__/inline-icon.stories.tsx @@ -1,40 +1,30 @@ -import * as React from "react"; - import InlineIcon from "../inline-icon"; -type StoryArgs = Record; +import type {Meta, StoryObj} from "@storybook/react"; -type Story = { - title: string; +const meta: Meta = { + title: "Perseus/Components/Inline Icon", + component: InlineIcon, + args: { + path: "M62.808 49.728q0 3.36-2.352 5.88l-41.72 41.664q-2.352 2.408-5.768 2.408t-5.768-2.408l-4.872-4.76q-2.352-2.52-2.352-5.88t2.352-5.712l31.08-31.136-31.08-31.024q-2.352-2.52-2.352-5.88t2.352-5.712l4.872-4.76q2.296-2.408 5.768-2.408t5.768 2.408l41.72 41.664q2.352 2.296 2.352 5.656z", + height: 100, + width: 64, + }, }; +export default meta; -const defaultPath = { - path: "M62.808 49.728q0 3.36-2.352 5.88l-41.72 41.664q-2.352 2.408-5.768 2.408t-5.768-2.408l-4.872-4.76q-2.352-2.52-2.352-5.88t2.352-5.712l31.08-31.136-31.08-31.024q-2.352-2.52-2.352-5.88t2.352-5.712l4.872-4.76q2.296-2.408 5.768-2.408t5.768 2.408l41.72 41.664q2.352 2.296 2.352 5.656z", - height: 100, - width: 64, -} as const; +type Story = StoryObj; -export default { - title: "Perseus/Components/Inline Icon", -} as Story; - -export const BasicIconPathAndSizing = (args: StoryArgs): React.ReactElement => { - return ; -}; +export const BasicIconPathAndSizing: Story = {}; -export const BasicIconWithAdditionalStyling = ( - args: StoryArgs, -): React.ReactElement => { - return ( - - ); +export const BasicIconWithAdditionalStyling: Story = { + args: { + style: {color: "red"}, + }, }; -export const BasicIconWithAriaTitle = (args: StoryArgs): React.ReactElement => { - return ; +export const BasicIconWithAriaTitle: Story = { + args: { + title: "Sample ARIA title", + }, }; diff --git a/packages/perseus/src/components/__stories__/input-with-examples.stories.tsx b/packages/perseus/src/components/__stories__/input-with-examples.stories.tsx index 77d0222b2f..884b93c800 100644 --- a/packages/perseus/src/components/__stories__/input-with-examples.stories.tsx +++ b/packages/perseus/src/components/__stories__/input-with-examples.stories.tsx @@ -1,53 +1,48 @@ -import * as React from "react"; +import {action} from "@storybook/addon-actions"; import InputWithExamples from "../input-with-examples"; -type StoryArgs = Record; +import type {Meta, StoryObj} from "@storybook/react"; -type Story = { - title: string; +const meta: Meta = { + title: "Perseus/Components/Input with Examples", + component: InputWithExamples, + args: { + examples: [], + id: "", + onChange: action("onChange"), + value: "", + }, + argTypes: { + onChange: { + control: {type: null}, + }, + }, }; +export default meta; + +type Story = StoryObj; -export default { - title: "Perseus/Components/Input with Examples", -} as Story; - -const defaultObject = { - examples: [], - id: "", - onChange: () => {}, - value: "", -} as const; const testExamples = ["Sample 1", "Sample 2", "Sample 3"]; -export const DefaultAndMostlyEmptyProps = ( - args: StoryArgs, -): React.ReactElement => { - return ; -}; +export const DefaultAndMostlyEmptyProps: Story = {}; -export const ListOfExamples = (args: StoryArgs): React.ReactElement => { - return ; +export const ListOfExamples: Story = { + args: { + examples: testExamples, + }, }; -export const AriaLabelTextWithListOfExamples = ( - args: StoryArgs, -): React.ReactElement => { - return ( - - ); +export const AriaLabelTextWithListOfExamples: Story = { + args: { + examples: testExamples, + labelText: "Test label", + }, }; -export const DisabledInput = (args: StoryArgs): React.ReactElement => { - return ( - - ); +export const DisabledInput: Story = { + args: { + disabled: true, + examples: testExamples, + }, }; diff --git a/packages/perseus/src/components/__stories__/lint.stories.tsx b/packages/perseus/src/components/__stories__/lint.stories.tsx index e34d5a3140..7f4c96463a 100644 --- a/packages/perseus/src/components/__stories__/lint.stories.tsx +++ b/packages/perseus/src/components/__stories__/lint.stories.tsx @@ -2,24 +2,9 @@ import * as React from "react"; import Lint from "../lint"; -import type {Meta} from "@storybook/react"; +import type {Meta, StoryObj} from "@storybook/react"; -const meta: Meta = { - title: "Perseus/Components/Lint", -}; - -export default meta; - -type StoryArgs = Record; - -const defaultObject = { - children:
This is the sample lint child
, - insideTable: false, - message: "Test message", - ruleName: "Test rule", -} as const; - -const Container = ({children}: {children: React.ReactNode}) => { +const Container = (Story) => { return (
{ border: "solid 1px grey", }} > - {children} +
); }; -export const DefaultLintContainerAndMessage = ( - args: StoryArgs, -): React.ReactElement => { - return ( - - - - ); -}; -export const LintSeverity1Error = (args: StoryArgs): React.ReactElement => { - return ( - - - - ); -}; -export const LintSeverity2Warning = (args: StoryArgs): React.ReactElement => { - return ( - - - - ); -}; -export const LintSeverity3Recommendation = ( - args: StoryArgs, -): React.ReactElement => { - return ( - - - - ); -}; -export const LintSeverity4OfflineReportingOnly = ( - args: StoryArgs, -): React.ReactElement => { - return ( - - - - ); +const meta: Meta = { + title: "Perseus/Components/Lint", + component: Lint, + decorators: [Container], + args: { + children:
This is the sample lint child
, + insideTable: false, + severity: 1, + message: "Test message", + ruleName: "Test rule", + }, + argTypes: { + children: {control: {type: null}}, + severity: { + type: "number", + control: { + type: "range", + min: 1, + max: 4, + }, + }, + }, }; -export const InlineLintContainerAndMessage = ( - args: StoryArgs, -): React.ReactElement => { - return ( - - - - ); + +export default meta; + +type Story = StoryObj; + +export const DefaultLintContainerAndMessage: Story = {}; + +export const LintSeverity1Error: Story = {args: {severity: 1}}; +export const LintSeverity2Warning: Story = {args: {severity: 2}}; +export const LintSeverity3Recommendation: Story = {args: {severity: 3}}; +export const LintSeverity4OfflineReportingOnly: Story = {args: {severity: 4}}; +export const InlineLintContainerAndMessage: Story = { + args: { + inline: true, + }, }; diff --git a/packages/perseus/src/components/__stories__/math-input.stories.tsx b/packages/perseus/src/components/__stories__/math-input.stories.tsx index e59b4a4726..3f4264f9a9 100644 --- a/packages/perseus/src/components/__stories__/math-input.stories.tsx +++ b/packages/perseus/src/components/__stories__/math-input.stories.tsx @@ -1,46 +1,53 @@ -import * as React from "react"; +import {action} from "@storybook/addon-actions"; import MathInput from "../math-input"; -type StoryArgs = Record; +import type {Meta, StoryObj} from "@storybook/react"; -type Story = { - title: string; -}; - -export default { +const meta: Meta = { title: "Perseus/Components/Math Input", -} as Story; - -const defaultObject = { - keypadButtonSets: { - advancedRelations: true, - basicRelations: true, - divisionKey: true, - logarithms: true, - preAlgebra: true, - trigonometry: true, + component: MathInput, + args: { + keypadButtonSets: { + advancedRelations: true, + basicRelations: true, + divisionKey: true, + logarithms: true, + preAlgebra: true, + trigonometry: true, + }, + convertDotToTimes: false, + value: "", + onChange: action("onChange"), + analytics: {onAnalyticsEvent: () => Promise.resolve()}, + labelText: "Math input", + }, + argTypes: { + onChange: { + control: {type: null}, + }, + analytics: { + control: {type: null}, + }, + }, + parameters: { + controls: {exclude: ["onChange", "analytics"]}, }, - convertDotToTimes: false, - value: "", - onChange: () => {}, - analytics: {onAnalyticsEvent: () => Promise.resolve()}, - labelText: "Math input", -} as const; - -export const DefaultWithBasicButtonSet = ( - args: StoryArgs, -): React.ReactElement => { - return ; }; -export const DefaultWithAriaLabel = (args: StoryArgs): React.ReactElement => { - return ; +export default meta; + +type Story = StoryObj; + +export const DefaultWithBasicButtonSet: Story = {}; + +export const DefaultWithAriaLabel: Story = { + args: {ariaLabel: "Sample label"}, }; -export const KeypadOpenByDefault = (args: StoryArgs): React.ReactElement => { - return ; +export const KeypadOpenByDefault: Story = { + args: {buttonsVisible: "always"}, }; -export const KeypadNeverVisible = (args: StoryArgs): React.ReactElement => { - return ; +export const KeypadNeverVisible: Story = { + args: {buttonsVisible: "never"}, }; diff --git a/packages/perseus/src/components/__stories__/multi-button-group.stories.tsx b/packages/perseus/src/components/__stories__/multi-button-group.stories.tsx index 72080bd6df..85e3ef479b 100644 --- a/packages/perseus/src/components/__stories__/multi-button-group.stories.tsx +++ b/packages/perseus/src/components/__stories__/multi-button-group.stories.tsx @@ -2,65 +2,50 @@ import * as React from "react"; import MultiButtonGroup from "../multi-button-group"; -type StoryArgs = { - allowEmpty: boolean; -}; - -type Story = { - title: string; - args: StoryArgs; -}; +import type {PropsFor} from "@khanacademy/wonder-blocks-core"; +import type {Meta, StoryObj} from "@storybook/react"; -export default { +const meta: Meta = { title: "Perseus/Components/Muli-Button Group", + component: MultiButtonGroup, args: { allowEmpty: true, + buttons: [], + }, + render: function WithState(props: PropsFor) { + const [values, updateValues] = React.useState(props.values); + return ( + + ); }, -} as Story; - -const HarnassedButtonGroup = ( - props: Pick< - React.ComponentProps, - "buttons" | "allowEmpty" - >, -) => { - const [values, updateValues] = React.useState( - null as ReadonlyArray | null | undefined, - ); - - return ( - { - updateValues(newValues); - }} - /> - ); }; +export default meta; -export const ButtonsWithNoTitles = (args: StoryArgs): React.ReactElement => { - return ( - - ); +type Story = StoryObj; + +export const ButtonsWithNoTitles: Story = { + args: { + buttons: [ + {value: "One", content: "Item #1"}, + {value: "Two", content: "Item #2"}, + {value: "Three", content: "Item #3"}, + ], + }, }; -export const ButtonsWithTitles = (args: StoryArgs): React.ReactElement => { - return ( - - ); +/** + * Hover over the buttons to see their titles. + */ +export const ButtonsWithTitles: Story = { + args: { + buttons: [ + {value: "One", content: "Item #1", title: "The first item"}, + {value: "Two", content: "Item #2", title: "The second item"}, + {value: "Three", content: "Item #3", title: "The third item"}, + ], + }, }; diff --git a/packages/perseus/src/components/__stories__/number-input.stories.tsx b/packages/perseus/src/components/__stories__/number-input.stories.tsx index af0386a046..ee10c62785 100644 --- a/packages/perseus/src/components/__stories__/number-input.stories.tsx +++ b/packages/perseus/src/components/__stories__/number-input.stories.tsx @@ -1,41 +1,54 @@ -import * as React from "react"; +import {action} from "@storybook/addon-actions"; import NumberInput from "../number-input"; -type StoryArgs = Record; +import type {Meta, StoryObj} from "@storybook/react"; -type Story = { - title: string; +const meta: Meta = { + title: "Perseus/Components/Number Input", + component: NumberInput, + args: { + onChange: action("onChange"), + onFormatChange: action("onFormatChange"), + }, + argTypes: { + onChange: {control: {type: null}}, + onFormatChange: {control: {type: null}}, + }, }; +export default meta; -const defaultObject = { - onChange: () => {}, -} as const; - -export default { - title: "Perseus/Components/Number Input", -} as Story; +type Story = StoryObj; -export const EmptyPropsObject = (args: StoryArgs): React.ReactElement => { - return ; -}; +export const EmptyPropsObject: Story = {}; -export const SampleValue = (args: StoryArgs): React.ReactElement => { - return ; +export const SampleValue: Story = { + args: {value: 1234567890}, }; -export const Placeholder = (args: StoryArgs): React.ReactElement => { - return ; +export const Placeholder: Story = { + args: { + placeholder: "Sample placeholder", + }, }; -export const SizeMini = (args: StoryArgs): React.ReactElement => { - return ; +export const SizeMini: Story = { + args: { + size: "mini", + placeholder: "Sample placeholder", + }, }; -export const SizeSmall = (args: StoryArgs): React.ReactElement => { - return ; +export const SizeSmall: Story = { + args: { + size: "small", + placeholder: "Sample placeholder", + }, }; -export const SizeNormal = (args: StoryArgs): React.ReactElement => { - return ; +export const SizeNormal: Story = { + args: { + size: "normal", + placeholder: "Sample placeholder", + }, }; diff --git a/packages/perseus/src/components/__stories__/range-input.stories.tsx b/packages/perseus/src/components/__stories__/range-input.stories.tsx index b25955dd37..fd4a11bb0f 100644 --- a/packages/perseus/src/components/__stories__/range-input.stories.tsx +++ b/packages/perseus/src/components/__stories__/range-input.stories.tsx @@ -1,29 +1,34 @@ -import * as React from "react"; +import {action} from "@storybook/addon-actions"; import RangeInput from "../range-input"; -type StoryArgs = Record; +import type {Meta, StoryObj} from "@storybook/react"; -type Story = { - title: string; +const meta: Meta = { + title: "Perseus/Components/Range Input", + component: RangeInput, + args: { + value: [], + onChange: action("onChange"), + }, + argTypes: { + onChange: {control: {type: null}}, + }, }; +export default meta; -export default { - title: "Perseus/Components/Range Input", -} as Story; +type Story = StoryObj; -export const EmptyValueArray = (args: StoryArgs): React.ReactElement => { - return {}} value={[]} />; -}; +export const EmptyValueArray: Story = {}; -export const SimpleWithSmallValueRanges = ( - args: StoryArgs, -): React.ReactElement => { - return {}} value={[-10, 10]} />; +export const SimpleWithSmallValueRanges: Story = { + args: { + value: [-10, 10], + }, }; -export const Placeholders = (args: StoryArgs): React.ReactElement => { - return ( - {}} placeholder={["?", "!"]} value={[]} /> - ); +export const Placeholders: Story = { + args: { + placeholder: ["?", "!"], + }, }; diff --git a/packages/perseus/src/components/__stories__/simple-keypad-input.stories.tsx b/packages/perseus/src/components/__stories__/simple-keypad-input.stories.tsx index 53dfe44bc3..b642427853 100644 --- a/packages/perseus/src/components/__stories__/simple-keypad-input.stories.tsx +++ b/packages/perseus/src/components/__stories__/simple-keypad-input.stories.tsx @@ -1,27 +1,24 @@ -import * as React from "react"; +import {action} from "@storybook/addon-actions"; import SimpleKeypadInput from "../simple-keypad-input"; -type StoryArgs = Record; +import type {Meta, StoryObj} from "@storybook/react"; -type Story = { - title: string; +const meta: Meta = { + title: "Perseus/Components/Simple Keypad Input", + component: SimpleKeypadInput, + args: { + onChange: action("onChange"), + onFocus: action("onFocus"), + onBlur: action("onBlur"), + }, }; +export default meta; -const defaultObject = { - onChange: () => {}, - onFocus: () => {}, - onBlur: () => {}, -} as const; - -export default { - title: "Perseus/Components/Simple Keypad Input", -} as Story; +type Story = StoryObj; -export const EmptyPropsObject = (args: StoryArgs): React.ReactElement => { - return ; -}; +export const EmptyPropsObject: Story = {}; -export const CustomValue = (args: StoryArgs): React.ReactElement => { - return ; +export const CustomValue: Story = { + args: {value: "Test value"}, }; diff --git a/packages/perseus/src/components/__stories__/sortable.stories.tsx b/packages/perseus/src/components/__stories__/sortable.stories.tsx index 9821b822b0..d0b5723446 100644 --- a/packages/perseus/src/components/__stories__/sortable.stories.tsx +++ b/packages/perseus/src/components/__stories__/sortable.stories.tsx @@ -1,77 +1,53 @@ -import * as React from "react"; - import Sortable from "../sortable"; -type StoryArgs = Record; +import type {Meta, StoryObj} from "@storybook/react"; -type Story = { - title: string; +const meta: Meta = { + title: "Perseus/Components/Sortable", + component: Sortable, + args: { + options: ["Option 1", "Option 2", "Option 3"], + }, }; +export default meta; -const defaultOptions = ["Option 1", "Option 2", "Option 3"]; +type Story = StoryObj; -export default { - title: "Perseus/Components/Sortable", -} as Story; - -export const SortableHorizontalExample = ( - args: StoryArgs, -): React.ReactElement => { - return ( - - ); +export const SortableHorizontalExample: Story = { + args: { + layout: "horizontal", + options: ["a", "b", "c"], + waitForTexRendererToLoad: false, + }, }; -export const SortableVerticalExample = ( - args: StoryArgs, -): React.ReactElement => { - return ( - - ); +export const SortableVerticalExample: Story = { + args: { + layout: "vertical", + options: ["a", "b", "c"], + waitForTexRendererToLoad: false, + }, }; -export const BasicSortableOptionsTest = ( - args: StoryArgs, -): React.ReactElement => { - return ; -}; +export const BasicSortableOptionsTest: Story = {}; -export const BasicSortableOptionsTestWithNoPadding = ( - args: StoryArgs, -): React.ReactElement => { - return ; +export const BasicSortableOptionsTestWithNoPadding: Story = { + args: {padding: false}, }; -export const BasicSortableOptionsTestWithLargeMargin = ( - args: StoryArgs, -): React.ReactElement => { - return ; +export const BasicSortableOptionsTestWithLargeMargin: Story = { + args: {margin: 64}, }; -export const BasicSortableOptionsTestDisabled = ( - args: StoryArgs, -): React.ReactElement => { - return ; +export const BasicSortableOptionsTestDisabled: Story = { + args: {disabled: true}, }; -export const BasicSortableOptionsTestWithWidthAndHeightConstraints = ( - args: StoryArgs, -): React.ReactElement => { - return ( - - ); +export const BasicSortableOptionsTestWithWidthAndHeightConstraints: Story = { + args: { + constraints: { + height: 128, + width: 256, + }, + }, }; diff --git a/packages/perseus/src/components/__stories__/stub-tag-editor.stories.tsx b/packages/perseus/src/components/__stories__/stub-tag-editor.stories.tsx index fa0144c5e8..73ebfb39d1 100644 --- a/packages/perseus/src/components/__stories__/stub-tag-editor.stories.tsx +++ b/packages/perseus/src/components/__stories__/stub-tag-editor.stories.tsx @@ -1,45 +1,47 @@ -import * as React from "react"; +import {action} from "@storybook/addon-actions"; import StubTagEditor from "../stub-tag-editor"; -type StoryArgs = Record; - -type Story = { - title: string; +import type {Meta, StoryObj} from "@storybook/react"; + +const meta: Meta = { + title: "Perseus/Components/Stub Tag Editor", + component: StubTagEditor, + args: { + value: [], + onChange: action("onChange"), + }, + argTypes: { + onChange: { + control: {type: null}, + }, + }, }; -export default { - title: "Perseus/Components/name", -} as Story; +export default meta; + +type Story = StoryObj; const defaultValues = ["Test value 1", "Test value 2", "Test value 3"]; -export const ShowingTitle = (args: StoryArgs): React.ReactElement => { - return {}} showTitle={true} />; +export const ShowingTitle: Story = { + args: {showTitle: true}, }; -export const NotShowingTitle = (args: StoryArgs): React.ReactElement => { - return {}} showTitle={false} />; +export const NotShowingTitle: Story = { + args: {showTitle: false}, }; -export const ShowingTitleWithValue = (args: StoryArgs): React.ReactElement => { - return ( - {}} - showTitle={true} - value={defaultValues} - /> - ); +export const ShowingTitleWithValue: Story = { + args: { + showTitle: true, + value: defaultValues, + }, }; -export const NotShowingTitleWithValue = ( - args: StoryArgs, -): React.ReactElement => { - return ( - {}} - showTitle={false} - value={defaultValues} - /> - ); +export const NotShowingTitleWithValue: Story = { + args: { + showTitle: false, + value: defaultValues, + }, }; diff --git a/packages/perseus/src/components/__stories__/svg-image.stories.tsx b/packages/perseus/src/components/__stories__/svg-image.stories.tsx index 7bacd30ddb..907abbc789 100644 --- a/packages/perseus/src/components/__stories__/svg-image.stories.tsx +++ b/packages/perseus/src/components/__stories__/svg-image.stories.tsx @@ -1,74 +1,72 @@ -import * as React from "react"; - import SvgImage from "../svg-image"; -type StoryArgs = Record; +import type {Meta, StoryObj} from "@storybook/react"; -type Story = { - title: string; +const meta: Meta = { + title: "Perseus/Components/SVG Image", + component: SvgImage, + args: {alt: "ALT"}, }; +export default meta; -export default { - title: "Perseus/Components/SVG Image", -} as Story; +type Story = StoryObj; const svgUrl = "http://www.khanacademy.org/images/ohnoes-concerned.svg"; const imgUrl = "https://www.khanacademy.org/images/hand-tree.new.png"; const graphieUrl = "web+graphie://ka-perseus-graphie.s3.amazonaws.com/1e06f6d4071f30cee2cc3ccb7435b3a66a62fe3f"; -export const MostlyEmptyPropsObject = (args: StoryArgs): React.ReactElement => { - return ; +export const Default: Story = { + parameters: { + /** This story doesn't provide a src url and so just shows a spinner. + * Perhaps not useful, but for now we'll just disable snapshots. */ + chromatic: {disableSnapshot: true}, + }, }; -export const SvgImageThatDoesntLoad = (args: StoryArgs): React.ReactElement => { - return ( - - ); +export const SvgImageThatDoesntLoad: Story = { + args: { + height: 100, + width: 500, + src: "http://httpstat.us/200?sleep=1000000", + }, + parameters: { + /** This story never loads and so just shows a spinner. Perhaps not + * useful, but for now we'll just disable snapshots. */ + chromatic: {disableSnapshot: true}, + }, }; -export const SvgImageBasic = (args: StoryArgs): React.ReactElement => { - return ; +export const SvgImageBasic: Story = { + args: {src: svgUrl}, }; -export const SvgImageWithFixedHeight = ( - args: StoryArgs, -): React.ReactElement => { - return ; +export const SvgImageWithFixedHeight: Story = { + args: {height: 50, src: svgUrl}, }; -export const SvgImageWithFixedWidth = (args: StoryArgs): React.ReactElement => { - return ; +export const SvgImageWithFixedWidth: Story = { + args: {src: svgUrl, width: 50}, }; -export const SvgImageWithExtraGraphieProps = ( - args: StoryArgs, -): React.ReactElement => { - return ( - - ); +export const SvgImageWithExtraGraphieProps: Story = { + args: { + extraGraphie: { + box: [200, 200], + range: [ + [0, 10], + [0, 10], + ], + labels: ["ok"], + }, + src: svgUrl, + }, }; -export const PngImage = (args: StoryArgs): React.ReactElement => { - return ; +export const PngImage: Story = { + args: {src: imgUrl}, }; -export const GraphieImage = (args: StoryArgs): React.ReactElement => { - return ; +export const GraphieImage: Story = { + args: {src: graphieUrl}, }; diff --git a/packages/perseus/src/components/__stories__/tex.stories.tsx b/packages/perseus/src/components/__stories__/tex.stories.tsx index 2f65e3ab21..04d2a4cb12 100644 --- a/packages/perseus/src/components/__stories__/tex.stories.tsx +++ b/packages/perseus/src/components/__stories__/tex.stories.tsx @@ -1,23 +1,16 @@ -import * as React from "react"; - import TeX from "../tex"; -type StoryArgs = { - equation: string; -}; - -type Story = { - title: string; - args: StoryArgs; -}; +import type {Meta, StoryObj} from "@storybook/react"; -export default { +const meta: Meta = { title: "Perseus/Components/Tex", + component: TeX, args: { - equation: "f(x) = x + 1", + children: "f(x) = x + 1", }, -} as Story; - -export const BasicOperation = (args: StoryArgs): React.ReactElement => { - return {}} children={args.equation} />; }; +export default meta; + +type Story = StoryObj; + +export const BasicOperation: Story = {}; diff --git a/packages/perseus/src/components/__stories__/text-input.stories.tsx b/packages/perseus/src/components/__stories__/text-input.stories.tsx index 33e15e390d..94ee0a84c5 100644 --- a/packages/perseus/src/components/__stories__/text-input.stories.tsx +++ b/packages/perseus/src/components/__stories__/text-input.stories.tsx @@ -1,33 +1,37 @@ -import * as React from "react"; +import {action} from "@storybook/addon-actions"; import TextInput from "../text-input"; -type StoryArgs = Record; +import type {Meta, StoryObj} from "@storybook/react"; -type Story = { - title: string; -}; - -export default { +const meta: Meta = { title: "Perseus/Components/Text Input", -} as Story; + component: TextInput, + args: { + onChange: action("onChange"), + onBlur: action("onBlur"), + onFocus: action("onFocus"), + }, + argTypes: { + onChange: {control: {type: null}}, + onBlur: {control: {type: null}}, + onFocus: {control: {type: null}}, + }, +}; +export default meta; -const defaultObject = { - onChange: () => {}, -} as const; +type Story = StoryObj; -export const EmptyPropsObject = (args: StoryArgs): React.ReactElement => { - return ; -}; +export const EmptyPropsObject: Story = {}; -export const TestValueProvided = (args: StoryArgs): React.ReactElement => { - return ; +export const TestValueProvided: Story = { + args: {value: "Test value"}, }; -export const AriaLabelTextProvided = (args: StoryArgs): React.ReactElement => { - return ; +export const AriaLabelTextProvided: Story = { + args: {labelText: "Test label"}, }; -export const Disabled = (args: StoryArgs): React.ReactElement => { - return ; +export const Disabled: Story = { + args: {disabled: true}, }; diff --git a/packages/perseus/src/components/__stories__/text-list-editor.stories.tsx b/packages/perseus/src/components/__stories__/text-list-editor.stories.tsx index c67e99da76..cbf26234c9 100644 --- a/packages/perseus/src/components/__stories__/text-list-editor.stories.tsx +++ b/packages/perseus/src/components/__stories__/text-list-editor.stories.tsx @@ -3,30 +3,28 @@ import * as React from "react"; import TextListEditor from "../text-list-editor"; -type StoryArgs = Record; +import type {Meta, StoryObj} from "@storybook/react"; -type Story = { - title: string; -}; - -export default { +const meta: Meta = { title: "Perseus/Components/Text List Editor", -} as Story; - -const defaultObject = { - onChange: (...args) => { - action("onChange")(...args); + component: TextListEditor, + args: { + options: ["Test option 1", "Test option 2", "Test option 3"], + onChange: action("onChange"), }, - options: ["Test option 1", "Test option 2", "Test option 3"], -} as const; + argTypes: { + onChange: {control: {type: null}}, + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; +export default meta; -const ClassName = "framework-perseus orderer"; +type Story = StoryObj; -export const SimpleListOfOptions = (args: StoryArgs): React.ReactElement => { - return ( - // @ts-expect-error [FEI-5003] - TS2322 - Type '{ children: Element; class: string; }' is not assignable to type 'DetailedHTMLProps, HTMLDivElement>'. -
- -
- ); -}; +export const SimpleListOfOptions: Story = {}; diff --git a/packages/perseus/src/components/__stories__/tooltip.stories.tsx b/packages/perseus/src/components/__stories__/tooltip.stories.tsx index 7a63fa5199..52dacf9dd8 100644 --- a/packages/perseus/src/components/__stories__/tooltip.stories.tsx +++ b/packages/perseus/src/components/__stories__/tooltip.stories.tsx @@ -3,50 +3,39 @@ import * as React from "react"; import Tooltip, {HorizontalDirection, VerticalDirection} from "../tooltip"; -import type {Meta} from "@storybook/react"; +import type {Meta, StoryObj} from "@storybook/react"; -const meta: Meta = { +const meta: Meta = { title: "Perseus/Components/Tooltip", + component: Tooltip, + render(props) { + return ( + + Hover over{" "} + + this + + You can read so much more if you want... + + {" "} + to see more information + + ); + }, }; +export default meta; -export const Shown = () => { - return ( - - Hover over{" "} - - this - - You can read so much more if you want... - - {" "} - to see more information - - ); -}; +type Story = StoryObj; -export const Hidden = () => { - return ( - - Hover over{" "} - - this - - You can read so much more if you want... - - {" "} - to see more information - - ); +export const Shown: Story = { + args: {show: true}, }; -export default meta; +export const Hidden: Story = { + args: {show: false}, +}; diff --git a/packages/perseus/src/components/__stories__/zoomable-tex.stories.tsx b/packages/perseus/src/components/__stories__/zoomable-tex.stories.tsx index 1992575501..b68a9b18fa 100644 --- a/packages/perseus/src/components/__stories__/zoomable-tex.stories.tsx +++ b/packages/perseus/src/components/__stories__/zoomable-tex.stories.tsx @@ -2,40 +2,34 @@ import * as React from "react"; import ZoomableTex from "../zoomable-tex"; -type StoryArgs = Record; +import type {Meta, StoryObj} from "@storybook/react"; -type Story = { - title: string; -}; - -export default { +const meta: Meta = { title: "Perseus/Components/Zoomable Tex", -} as Story; - -type Props = { - children: React.ReactNode; + component: ZoomableTex, + decorators: [ + function ForceZoomWrapper(Story) { + return ( + <> +

Click on equation to zoom/unzoom

+
+ +
+ + ); + }, + ], }; +export default meta; -const ForceZoomWrapper = ({children}: Props): React.ReactElement => ( - <> -

Click on equation to zoom/unzoom

-
{children}
- -); +type Story = StoryObj; -export const Tex = (args: StoryArgs): React.ReactElement => { - return ( - - - - ); +export const Tex: Story = { + args: {children: "\\sum_{i=1}^\\infty\\frac{1}{n^2} = \\frac{\\pi^2}{6}"}, }; -export const ComplexTex = (args: StoryArgs): React.ReactElement => { - return ( - - {" "} - - - ); +export const ComplexTex: Story = { + args: { + children: `\\begin{aligned}h\\blueE{v_1} \\left(\\dfrac{\\partial f}{\\partial x}(x_0, y_0) \\right) + h\\greenE{v_2}\\left( \\dfrac{\\partial f}{\\partial y}(x_0 \\redD{+ h\\blueE{v_1}}, y_0)\\right)\\end{aligned}`, + }, }; diff --git a/packages/perseus/src/components/__stories__/zoomable.stories.tsx b/packages/perseus/src/components/__stories__/zoomable.stories.tsx index 6dc18fa61d..8e7093cc2c 100644 --- a/packages/perseus/src/components/__stories__/zoomable.stories.tsx +++ b/packages/perseus/src/components/__stories__/zoomable.stories.tsx @@ -2,20 +2,7 @@ import * as React from "react"; import Zoomable from "../zoomable"; -type StoryArgs = Record; - -type Story = { - title: string; -}; - -export default { - title: "Perseus/Components/Zoomable", - argTypes: { - disableEntranceAnimation: { - control: {type: "boolean"}, - }, - }, -} as Story; +import type {Meta, StoryObj} from "@storybook/react"; type Bounds = { width: number; @@ -32,18 +19,36 @@ const computeChildBounds = ( }; }; -export const ZoomableExample = (args: StoryArgs): React.ReactElement => { - return ( - +const meta: Meta = { + title: "Perseus/Components/Zoomable", + component: Zoomable, + args: { + computeChildBounds, + }, + argTypes: { + children: {control: {type: null}}, + }, + parameters: { + chromatic: { + // Disable the snapshot for this story because it's testing + // behavior, not visuals. + disableSnapshot: true, + }, + }, +}; +export default meta; + +type Story = StoryObj; + +export const ZoomableExample: Story = { + args: { + children: ( Here's some zoomed-out content.

Click on the content to zoom/unzoom.
-
- ); + ), + }, }; diff --git a/packages/perseus/src/components/__tests__/math-input.test.tsx b/packages/perseus/src/components/__tests__/math-input.test.tsx index f40d6c5966..56dbe88d13 100644 --- a/packages/perseus/src/components/__tests__/math-input.test.tsx +++ b/packages/perseus/src/components/__tests__/math-input.test.tsx @@ -36,7 +36,7 @@ describe("Perseus' MathInput", () => { {}} keypadButtonSets={allButtonSets} - analytics={{onAnalyticsEvent: () => Promise.resolve()}} + onAnalyticsEvent={() => Promise.resolve()} convertDotToTimes={false} value="" />, @@ -55,7 +55,7 @@ describe("Perseus' MathInput", () => { {}} keypadButtonSets={allButtonSets} - analytics={{onAnalyticsEvent: () => Promise.resolve()}} + onAnalyticsEvent={() => Promise.resolve()} convertDotToTimes={false} value="" />, @@ -74,7 +74,7 @@ describe("Perseus' MathInput", () => { {}} keypadButtonSets={allButtonSets} - analytics={{onAnalyticsEvent: () => Promise.resolve()}} + onAnalyticsEvent={() => Promise.resolve()} convertDotToTimes={false} value="" ariaLabel="Hello world" @@ -95,7 +95,7 @@ describe("Perseus' MathInput", () => { Promise.resolve()}} + onAnalyticsEvent={() => Promise.resolve()} convertDotToTimes={false} value="" />, @@ -120,7 +120,7 @@ describe("Perseus' MathInput", () => { Promise.resolve()}} + onAnalyticsEvent={() => Promise.resolve()} convertDotToTimes={false} value="" />, @@ -149,7 +149,7 @@ describe("Perseus' MathInput", () => { Promise.resolve()}} + onAnalyticsEvent={() => Promise.resolve()} convertDotToTimes={false} value="" />, @@ -178,7 +178,7 @@ describe("Perseus' MathInput", () => { {}} keypadButtonSets={allButtonSets} - analytics={{onAnalyticsEvent: () => Promise.resolve()}} + onAnalyticsEvent={() => Promise.resolve()} convertDotToTimes={false} value="" />, @@ -202,7 +202,7 @@ describe("Perseus' MathInput", () => { {}} keypadButtonSets={allButtonSets} - analytics={{onAnalyticsEvent: () => Promise.resolve()}} + onAnalyticsEvent={() => Promise.resolve()} convertDotToTimes={false} value="" />, @@ -231,7 +231,7 @@ describe("Perseus' MathInput", () => { {}} buttonsVisible="always" - analytics={{onAnalyticsEvent: () => Promise.resolve()}} + onAnalyticsEvent={() => Promise.resolve()} convertDotToTimes={false} value="" />, diff --git a/packages/perseus/src/components/hud.tsx b/packages/perseus/src/components/hud.tsx index 03cb916531..6f7cb7550b 100644 --- a/packages/perseus/src/components/hud.tsx +++ b/packages/perseus/src/components/hud.tsx @@ -84,6 +84,10 @@ type Props = { fixedPosition?: boolean; }; +/** + * A "heads-up display" (HUD) indicator that includes a short message (usually + * used for linting errors). The indicator can be disabled. + */ const HUD = ({message, enabled, onClick, fixedPosition = true}: Props) => { let state; let icon; diff --git a/packages/perseus/src/components/icon.tsx b/packages/perseus/src/components/icon.tsx index db4097f88c..fabb1e70d5 100644 --- a/packages/perseus/src/components/icon.tsx +++ b/packages/perseus/src/components/icon.tsx @@ -1,34 +1,4 @@ /* eslint-disable @khanacademy/ts-no-error-suppressions */ -/** - * SVG Icon React Component - * - * This component is designed to take in SVG paths and render icons based upon - * them. If you are looking for an icon that we've used before you should look - * in `icon-paths.js` which is a reference file for all the SVG paths that - * we've used. You'll need to copy the object from that file into whichever - * file you're using the icon and explicitly pass it in to the React - * component. - * - * Sample usage: - * - * const dropdownIcon = `M5,6L0,0L10,0`; - * - * - * Or: - * - * const dropdownIcon = { - * path: `M5,6L0,0L10,0`, - * height: 10, - * width: 10, - * }; - * - * - * A full list of all the existing icons can be seen here: - * http://localhost:8080/react-sandbox/shared-styles-package/icon.jsx.fixture.js - * - * If you want to add an entirely new icon please read the note inside - * the `icon-paths.js` file. - */ import * as React from "react"; @@ -78,6 +48,37 @@ type Props = { alt?: string; }; +/** + * An SVG Icon + * + * This component is designed to take in SVG paths and render icons based upon + * them. If you are looking for an icon that we've used before you should look + * in `icon-paths.js` which is a reference file for all the SVG paths that + * we've used. You'll need to copy the object from that file into whichever + * file you're using the icon and explicitly pass it in to the React + * component. + * + * Sample usage: + * + * ``` + * const dropdownIcon = `M5,6L0,0L10,0`; + * + * ``` + * + * Or: + * + * ``` + * const dropdownIcon = { + * path: `M5,6L0,0L10,0`, + * height: 10, + * width: 10, + * }; + * + * ``` + * + * If you want to add an entirely new icon please read the note inside + * the `icon-paths.ts` file. + */ class Icon extends React.Component { static defaultProps: { color: string; diff --git a/packages/perseus/src/components/image-loader.tsx b/packages/perseus/src/components/image-loader.tsx index 18f7de644b..3baf5615ec 100644 --- a/packages/perseus/src/components/image-loader.tsx +++ b/packages/perseus/src/components/image-loader.tsx @@ -1,15 +1,6 @@ /* eslint-disable @khanacademy/ts-no-error-suppressions */ /* eslint-disable jsx-a11y/alt-text, react/no-unsafe */ // TODO(scottgrant): Enable the alt-text eslint rule above. -/** - * Component to display an image (or other React components) while the desired - * image is loading. - * - * Derived from - * https://github.com/hzdg/react-imageloader/blob/master/src/index.js - * to better suit our environment/build tools. Additionally, this one does - * not introduce a wrapper element, which makes styling easier. - */ import * as React from "react"; @@ -49,6 +40,15 @@ type State = { status: (typeof Status)[keyof typeof Status]; }; +/** + * Component to display an image (or other React components) while the desired + * image is loading. + * + * Derived from + * https://github.com/hzdg/react-imageloader/blob/master/src/index.js + * to better suit our environment/build tools. Additionally, this one does + * not introduce a wrapper element, which makes styling easier. + */ class ImageLoader extends React.Component { img: HTMLImageElement | null | undefined; diff --git a/packages/perseus/src/components/inline-icon.tsx b/packages/perseus/src/components/inline-icon.tsx index 20087309fa..9671911b23 100644 --- a/packages/perseus/src/components/inline-icon.tsx +++ b/packages/perseus/src/components/inline-icon.tsx @@ -17,11 +17,7 @@ type InlineIconProps = { * A stripped version of Icon.jsx from webapp. Takes an SVG icon and renders it * inline like Font Awesome did. * - * If you are looking for an icon that we've used before you should look in - * webapp's `icon-paths.js` which is a reference file for all the SVG paths - * that we've used. You'll need to copy the object from that file into - * whichever file you're using the icon and explicitly pass it in to the - * React component. + * You can refer to `icon-paths.ts` for all available SVG icons. * * We assume that the viewBox is cropped and aligned to (0, 0), but icons can * be defined differently. At some point we might want to add these attributes @@ -29,13 +25,14 @@ type InlineIconProps = { * * Sample usage: * - * const editIcon = { + * ``` + * const editIcon = { * path: "M41.209 53.753l5.39 0l0 5.39l3.136 0l6.468-6.517-8.477-8.526-6.517 6.517l0 3.136zm33.173-34.937q-.882-.882-1.862.049l-19.6 19.6q-.931.98-.049 1.862t1.862-.049l19.6-19.6q.931-.98.049-1.862zm-38.563 45.668l0-16.121l37.632-37.632 16.17 16.121-37.632 37.632l-16.17 0zm43.022-12.397l0 10.633q-.049 6.713-4.753 11.417t-11.368 4.704l-46.599 0q-6.713 0-11.417-4.753t-4.704-11.368l0-46.599q0-6.664 4.753-11.417t11.368-4.704l46.599 0q3.528 0 6.566 1.372.833.392.98 1.323t-.49 1.617l-2.744 2.744q-.784.784-1.96.441t-2.352-.343l-46.599 0q-3.675 0-6.321 2.646t-2.646 6.321l0 46.599q0 3.675 2.646 6.321t6.321 2.646l46.599 0q3.675 0 6.321-2.646t2.646-6.321l0-7.056q0-.735.49-1.225l3.577-3.577q.833-.833 1.96-.392t1.127 1.617zm7.203-51.646q2.254 0 3.773 1.568l8.526 8.526q1.568 1.568 1.568 3.822t-1.568 3.773l-5.145 5.145-16.121-16.121 5.145-5.145q1.568-1.568 3.822-1.568z", * width: 100, * height: 78.912, - * }; - * - * + * }; + * + * ``` */ const InlineIcon = ({ path, diff --git a/packages/perseus/src/components/input-with-examples.tsx b/packages/perseus/src/components/input-with-examples.tsx index 2bc915afb7..da253dfec3 100644 --- a/packages/perseus/src/components/input-with-examples.tsx +++ b/packages/perseus/src/components/input-with-examples.tsx @@ -155,8 +155,6 @@ class InputWithExamples extends React.Component { return ( { > | null | undefined; + /** + * The set of buttons to display in this MultiButtonGroup. + */ buttons: ReadonlyArray<{ - // the value returned when the button is selected + /** + * the value returned when the button is selected + */ value: any; - // the content shown within the button, typically a string that gets - // rendered as the button's display text + /** + * The content shown within the button, typically a string that gets + * rendered as the button's display text. + */ content: React.ReactNode; - // the title-text shown on hover + /** + * The title-text shown on hover + */ title?: string; }>; - // a function that is provided with the updated set of selected value - // (which it then is responsible for updating) + /** + * A function that is provided with the updated set of selected value + * (which it then is responsible for updating) + */ onChange: (values?: any) => unknown; - // if false, at least one button must be selected at all times. - // defaults to true + /** + * If false, at least one button must be selected at all times. + * + * Defaults to `true` + */ allowEmpty?: boolean; }; diff --git a/packages/perseus/src/components/number-input.tsx b/packages/perseus/src/components/number-input.tsx index 20027dbbee..2d7f20efd8 100644 --- a/packages/perseus/src/components/number-input.tsx +++ b/packages/perseus/src/components/number-input.tsx @@ -18,18 +18,24 @@ const {firstNumericalParse, captureScratchpadTouchStart} = Util; const toNumericString = KhanMath.toNumericString; const getNumericFormat = KhanMath.getNumericFormat; -/* An input box that accepts only numeric strings +/** + * An input box that accepts only numeric strings * - * Calls onChange(value, format) for valid numbers. - * Reverts to the current value onBlur or on [ENTER], + * Calls `onChange(value, format)` for valid numbers. + * + * Reverts to the current value `onBlur` or on [ENTER], * but maintains the format (i.e. 3/2, 1 1/2, 150%) - * Accepts empty input and sends it to onChange as null - * if no numeric placeholder is set. - * If given a checkValidity function, will turn - * the background/outline red when invalid - * If useArrowKeys is set to true, up/down arrows will - * increment/decrement integers - * Optionally takes a size ("mini", "small", "normal") + * + * Accepts empty input and sends it to `onChange` as `null` if no numeric + * placeholder is set. + * + * If given a `checkValidity` function, will turn the background/outline red + * when invalid. + * + * If `useArrowKeys` is set to `true`, up/down arrows will increment/decrement + * integers. + * + * Optionally takes a `size` (`"mini"`, `"small"`,` `"normal"`) */ class NumberInput extends React.Component { static contextType = PerseusI18nContext; @@ -42,7 +48,7 @@ class NumberInput extends React.Component { onChange: PropTypes.func.isRequired, onFormatChange: PropTypes.func, checkValidity: PropTypes.func, - size: PropTypes.string, + size: PropTypes.oneOf(["mini", "small", "normal"]), label: PropTypes.oneOf(["put your labels outside your inputs!"]), }; diff --git a/packages/perseus/src/components/range-input.tsx b/packages/perseus/src/components/range-input.tsx index ac0ad72368..b60dcad131 100644 --- a/packages/perseus/src/components/range-input.tsx +++ b/packages/perseus/src/components/range-input.tsx @@ -6,8 +6,8 @@ import NumberInput from "./number-input"; const truth = () => true; -/* A minor abstraction on top of NumberInput for ranges - * +/** + * A minor abstraction on top of `NumberInput` for ranges */ class RangeInput extends React.Component { static propTypes = { diff --git a/packages/perseus/src/components/stub-tag-editor.tsx b/packages/perseus/src/components/stub-tag-editor.tsx index da74557a5f..ca7e2a07df 100644 --- a/packages/perseus/src/components/stub-tag-editor.tsx +++ b/packages/perseus/src/components/stub-tag-editor.tsx @@ -1,3 +1,10 @@ +import PropTypes from "prop-types"; +import * as React from "react"; + +import TextListEditor from "./text-list-editor"; + +const EMPTY_ARRAY = []; + /** * Stub Tag Editor. * @@ -11,13 +18,6 @@ * It also gives a nicer interface for the group metadata editor * in local demo mode. */ -import PropTypes from "prop-types"; -import * as React from "react"; - -import TextListEditor from "./text-list-editor"; - -const EMPTY_ARRAY = []; - class StubTagEditor extends React.Component { static propTypes = { value: PropTypes.arrayOf(PropTypes.string), diff --git a/packages/perseus/src/components/svg-image.tsx b/packages/perseus/src/components/svg-image.tsx index 9324c6150b..affde9dadc 100644 --- a/packages/perseus/src/components/svg-image.tsx +++ b/packages/perseus/src/components/svg-image.tsx @@ -149,34 +149,46 @@ type Props = { labels: ReadonlyArray; }; height?: number; - // When the DOM updates to replace the preloader with the image, or - // vice-versa, we trigger this callback. + /** + * When the DOM updates to replace the preloader with the image, or + * vice-versa, we trigger this callback. + */ onUpdate: () => void; - // If alt is provided, DO NOT set aria-hidden=true unless this override flag - // is set. + /** + * If alt is provided, DO NOT set aria-hidden=true unless this override flag + * is set. + */ overrideAriaHidden?: boolean; preloader?: (dimensions: Dimensions) => React.ReactNode; - // By default, this component attempts to be responsive whenever - // possible (specifically, when width and height are passed in). - // You can expliclty force unresponsive behavior by *either* - // not passing in width/height *or* setting this prop to false. - // The difference is that forcing via this prop will result in - // explicit width and height styles being set on the rendered - // component. + /** + * By default, this component attempts to be responsive whenever + * possible (specifically, when width and height are passed in). + * + * You can expliclty force unresponsive behavior by *either* + * not passing in width/height *or* setting this prop to false. + * + * The difference is that forcing via this prop will result in + * explicit width and height styles being set on the rendered + * component. + */ responsive: boolean; scale: number; src: string; title?: string; trackInteraction?: () => void; width?: number; - // Whether clicking this image will allow it to be fully zoomed in to - // its original size on click, and allow the user to scroll in that - // state. This also does some hacky viewport meta tag changing to - // ensure this works on mobile devices, so I (david@) don't recommend - // enabling this on desktop yet. + /** + * Whether clicking this image will allow it to be fully zoomed in to + * its original size on click, and allow the user to scroll in that + * state. This also does some hacky viewport meta tag changing to + * ensure this works on mobile devices, so I (david@) don't recommend + * enabling this on desktop yet. + */ zoomToFullSizeOnMobile?: boolean; - // If provided, use AssetContext.Consumer, see renderer.jsx. - // If not, it defaults to a no-op. + /** + * If provided, use AssetContext.Consumer, see renderer.jsx. + * If not, it defaults to a no-op. + */ setAssetStatus: (assetKey: string, loaded: boolean) => void; }; diff --git a/packages/perseus/src/components/text-list-editor.tsx b/packages/perseus/src/components/text-list-editor.tsx index 1cefffaca8..db68f4fdaa 100644 --- a/packages/perseus/src/components/text-list-editor.tsx +++ b/packages/perseus/src/components/text-list-editor.tsx @@ -21,7 +21,7 @@ function getTextWidth(text: any) { class TextListEditor extends React.Component { static propTypes = { options: PropTypes.array, - layout: PropTypes.string, + layout: PropTypes.oneOf(["horizontal", "vertical"]), onChange: PropTypes.func.isRequired, }; diff --git a/packages/perseus/src/components/tooltip.tsx b/packages/perseus/src/components/tooltip.tsx index daba9c7fb4..d5a13e13ea 100644 --- a/packages/perseus/src/components/tooltip.tsx +++ b/packages/perseus/src/components/tooltip.tsx @@ -1,42 +1,4 @@ /* eslint-disable react/no-unsafe */ -/** - * A generic tooltip library for React.js - * - * This should eventually end up in react-components - * - * Interface: ({a, b} means one of a or b) - * import Tooltip from "./tooltip"; - * - * - * - * - * - * - * To show/hide the tooltip, the parent component should call the - * .show() and .hide() methods of the tooltip when appropriate. - * (These are usually set up as handlers of events on the target element.) - * - * Notes: - * className should not specify a border; that is handled by borderColor - * so that the arrow and tooltip match - */ - -// __,,--``\\ -// _,,-''`` \\ , -// '----------_.------'-.___|\__ -// _.--''`` `)__ )__ @\__ -// ( .. ''---/___,,E/__,E'------` -// `-''`'' -// Here be dragons. // TODO(joel/aria) fix z-index issues https://s3.amazonaws.com/uploads.hipchat.com/6574/29028/yOApjwmgiMhEZYJ/Screen%20Shot%202014-05-30%20at%203.34.18%20PM.png // z-index: 3 on perseus-formats-tooltip seemed to work @@ -96,7 +58,7 @@ const Triangle = (props: TriangleProps) => { width: 0, position: "absolute", left: props.left, - top: props["top"], + top: props.top, borderLeft: borderLeft, borderRight: borderRight, borderTop: borderTop, @@ -233,6 +195,47 @@ type State = { height: number | null; }; +/** + * DEPRECATED! Use Wonder Blocks tooltip instead. + * + * A generic tooltip library for React.js + * + * ``` + * import Tooltip from "./tooltip"; + * + * + * + * + * + * ``` + * + * To show/hide the tooltip, the parent component should call the + * `.show()` and `.hide()` methods of the tooltip when appropriate. + * (These are usually set up as handlers of events on the target element.) + * + * Notes: + * `className` should not specify a border; that is handled by `borderColor` + * so that the arrow and tooltip match + * + * ``` + * __,,--``\\ + * _,,-''`` \\ , + * '----------_.------'-.___|\__ + * _.--''`` `)__ )__ @\__ + * ( .. ''---/___,,E/__,E'------` + * `-''`'' + * Here be dragons. + * ``` + */ class Tooltip extends React.Component { static defaultProps: DefaultProps = { className: "", diff --git a/packages/perseus/src/icon-paths.ts b/packages/perseus/src/icon-paths.ts index 75e9ec7bd4..65b0facc70 100644 --- a/packages/perseus/src/icon-paths.ts +++ b/packages/perseus/src/icon-paths.ts @@ -1,9 +1,5 @@ /** - * Icon paths to be used with `inline-icon.jsx`. - * - * These paths are taken directly from webapp's `icon-paths.js`. Unlike the - * webapp equivalent, these can be directly required within Perseus files since - * this is all bundled together anyway. + * Icon paths to be used with `inline-icon.tsx` and `icon.tsx`. */ export const iconCheck = { diff --git a/packages/perseus/src/types.ts b/packages/perseus/src/types.ts index 7f23d9f9fd..4630857155 100644 --- a/packages/perseus/src/types.ts +++ b/packages/perseus/src/types.ts @@ -506,9 +506,9 @@ export type PerseusDependencies = { * * Prefer using this type over `PerseusDependencies` when possible. */ -export type PerseusDependenciesV2 = { +export interface PerseusDependenciesV2 { analytics: {onAnalyticsEvent: AnalyticsEventHandlerFn}; -}; +} /** * APIOptionsWithDefaults represents the type that is provided to all widgets. diff --git a/packages/perseus/src/widgets/expression/expression.tsx b/packages/perseus/src/widgets/expression/expression.tsx index 84c3424e50..bbb4d3bbb0 100644 --- a/packages/perseus/src/widgets/expression/expression.tsx +++ b/packages/perseus/src/widgets/expression/expression.tsx @@ -382,10 +382,9 @@ export class Expression extraKeys={ this.props.keypadConfiguration?.extraKeys } - analytics={ - this.props.analytics ?? { - onAnalyticsEvent: async () => {}, - } + onAnalyticsEvent={ + this.props.analytics?.onAnalyticsEvent ?? + (async () => {}) } /> diff --git a/packages/perseus/src/widgets/interactive-graphs/mafs-graph.tsx b/packages/perseus/src/widgets/interactive-graphs/mafs-graph.tsx index 767cd715f3..7041b27cb4 100644 --- a/packages/perseus/src/widgets/interactive-graphs/mafs-graph.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/mafs-graph.tsx @@ -41,11 +41,11 @@ import {SvgDefs} from "./graphs/components/text-label"; import {PointGraph} from "./graphs/point"; import {MIN, X, Y} from "./math"; import {Protractor} from "./protractor"; -import {type InteractiveGraphAction} from "./reducer/interactive-graph-action"; import {actions} from "./reducer/interactive-graph-action"; import {GraphConfigContext} from "./reducer/use-graph-config"; import {isUnlimitedGraphState, REMOVE_BUTTON_ID} from "./utils"; +import type {InteractiveGraphAction} from "./reducer/interactive-graph-action"; import type { InteractiveGraphState, InteractiveGraphProps,