From 1fd83119ab322de348aa916ba36a2ac672e2526d Mon Sep 17 00:00:00 2001 From: Jeremy Wiebe Date: Thu, 24 Oct 2024 17:28:20 -0700 Subject: [PATCH 01/10] Remove deprecated defaultProps --- .../src/components/keypad/keypad.tsx | 13 ++---- packages/perseus/src/components/tooltip.tsx | 29 +++++++------- .../__stories__/answer-choices.stories.tsx | 6 +-- .../perseus/src/widgets/radio/base-radio.tsx | 40 ++++++++----------- .../src/widgets/radio/choice-none-above.tsx | 15 +++---- .../perseus/src/widgets/radio/focus-ring.tsx | 21 +++++----- 6 files changed, 49 insertions(+), 75 deletions(-) diff --git a/packages/math-input/src/components/keypad/keypad.tsx b/packages/math-input/src/components/keypad/keypad.tsx index 4e21539b2e..94aa85570e 100644 --- a/packages/math-input/src/components/keypad/keypad.tsx +++ b/packages/math-input/src/components/keypad/keypad.tsx @@ -21,7 +21,7 @@ import type {CursorContext} from "../input/cursor-contexts"; import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core"; export type Props = { - extraKeys: ReadonlyArray; + extraKeys?: ReadonlyArray; cursorContext?: (typeof CursorContext)[keyof typeof CursorContext]; showDismiss?: boolean; expandedView?: boolean; @@ -39,10 +39,6 @@ export type Props = { onAnalyticsEvent: AnalyticsEventHandlerFn; }; -const defaultProps = { - extraKeys: [], -}; - function getAvailableTabs(props: Props): ReadonlyArray { // We don't want to show any available tabs on the fractions keypad if (props.fractionsOnly) { @@ -73,7 +69,7 @@ function getAvailableTabs(props: Props): ReadonlyArray { // The main (v2) Keypad. Use this component to present an accessible, onscreen // keypad to learners for entering math expressions. -export default function Keypad(props: Props) { +export default function Keypad({extraKeys = [], ...props}: Props) { // If we're using the Fractions keypad, we want to default select that page // Otherwise, we want to default to the Numbers page const defaultSelectedPage = props.fractionsOnly ? "Fractions" : "Numbers"; @@ -82,12 +78,11 @@ export default function Keypad(props: Props) { const [isMounted, setIsMounted] = React.useState(false); // We don't want any tabs available on mobile fractions keypad - const availableTabs = getAvailableTabs(props); + const availableTabs = getAvailableTabs({...props, extraKeys}); const { onClickKey, cursorContext, - extraKeys, convertDotToTimes, divisionKey, preAlgebra, @@ -197,8 +192,6 @@ export default function Keypad(props: Props) { ); } -Keypad.defaultProps = defaultProps; - const styles = StyleSheet.create({ keypadOuterContainer: { display: "flex", diff --git a/packages/perseus/src/components/tooltip.tsx b/packages/perseus/src/components/tooltip.tsx index 82be250683..285b8e095b 100644 --- a/packages/perseus/src/components/tooltip.tsx +++ b/packages/perseus/src/components/tooltip.tsx @@ -107,10 +107,10 @@ const Triangle = (props: TriangleProps) => { }; type TooltipArrowProps = { - position: Property.Position; - visibility: Property.Visibility; - left: number; - top: number; + position?: Property.Position; + visibility?: Property.Visibility; + left?: number; + top?: number; color: string; // a css color border: string; // a css color width: number; @@ -120,7 +120,13 @@ type TooltipArrowProps = { zIndex?: number; }; -const TooltipArrow = (props: TooltipArrowProps) => { +const TooltipArrow = ({ + position = "relative", + visibility = "visible", + left = 100, + top = 0, + ...props +}: TooltipArrowProps) => { // TODO(aria): Think about adding a box-shadow to the triangle here // See http://css-tricks.com/triangle-with-shadow/ @@ -134,9 +140,9 @@ const TooltipArrow = (props: TooltipArrowProps) => {
{ ); }; -TooltipArrow.defaultProps = { - position: "relative", - visibility: "visible", - left: 0, - top: 0, -}; - const VERTICAL_CORNERS = { top: { top: "-100%", diff --git a/packages/perseus/src/widgets/label-image/__stories__/answer-choices.stories.tsx b/packages/perseus/src/widgets/label-image/__stories__/answer-choices.stories.tsx index de11594693..8f5d1ce78a 100644 --- a/packages/perseus/src/widgets/label-image/__stories__/answer-choices.stories.tsx +++ b/packages/perseus/src/widgets/label-image/__stories__/answer-choices.stories.tsx @@ -44,7 +44,7 @@ const defaultChoices = [ }, ]; -const WithState = ({multipleSelect}) => { +const WithState = ({multipleSelect = false}) => { const [choices, setChoices] = React.useState([...defaultChoices]); const [isOpened, setIsOpened] = React.useState(false); @@ -78,10 +78,6 @@ const WithState = ({multipleSelect}) => { ); }; -WithState.defaultProps = { - multipleSelect: false, -}; - export const SingleSelect = (args: StoryArgs): React.ReactElement => { return ; }; diff --git a/packages/perseus/src/widgets/radio/base-radio.tsx b/packages/perseus/src/widgets/radio/base-radio.tsx index 5a9daf88b1..4ea5b999b9 100644 --- a/packages/perseus/src/widgets/radio/base-radio.tsx +++ b/packages/perseus/src/widgets/radio/base-radio.tsx @@ -49,11 +49,11 @@ type Props = { apiOptions: APIOptions; choices: ReadonlyArray; deselectEnabled?: boolean; - editMode: boolean; + editMode?: boolean; labelWrap: boolean; countChoices: boolean | null | undefined; numCorrect: number; - multipleSelect: boolean; + multipleSelect?: boolean; // the logic checks whether this exists, // so it must be optional reviewModeRubric?: PerseusRadioWidgetOptions | null; @@ -90,21 +90,20 @@ function getInstructionsText( return strings.chooseOneAnswer; } -const BaseRadio = function (props: Props): React.ReactElement { - const { - apiOptions, - reviewModeRubric, - reviewMode, - choices, - editMode, - multipleSelect, - labelWrap, - countChoices, - numCorrect, - isLastUsedWidget, - registerFocusFunction, - } = props; - +const BaseRadio = function ({ + apiOptions, + reviewModeRubric, + reviewMode, + choices, + editMode = false, + multipleSelect = false, + labelWrap, + countChoices, + numCorrect, + isLastUsedWidget, + onChange, + registerFocusFunction, +}: Props): React.ReactElement { const {strings} = usePerseusI18n(); // useEffect doesn't have previous props @@ -174,8 +173,6 @@ const BaseRadio = function (props: Props): React.ReactElement { crossedOut: boolean; }>, ): void { - const {multipleSelect, choices, onChange} = props; - // Get the baseline `checked` values. If we're checking a new answer // and multiple-select is not on, we should clear all choices to be // unchecked. Otherwise, we should copy the old checked values. @@ -405,11 +402,6 @@ const BaseRadio = function (props: Props): React.ReactElement { return
{fieldset}
; }; -BaseRadio.defaultProps = { - editMode: false, - multipleSelect: false, -}; - const styles: StyleDeclaration = StyleSheet.create({ instructions: { display: "block", diff --git a/packages/perseus/src/widgets/radio/choice-none-above.tsx b/packages/perseus/src/widgets/radio/choice-none-above.tsx index 20fd3d33f3..bd23bf0754 100644 --- a/packages/perseus/src/widgets/radio/choice-none-above.tsx +++ b/packages/perseus/src/widgets/radio/choice-none-above.tsx @@ -17,11 +17,12 @@ type WithForwardRef = { type PropsWithForwardRef = Props & WithForwardRef; -const ChoiceNoneAbove = function ( - props: PropsWithForwardRef, -): React.ReactElement { - const {showContent, content, forwardedRef, ...rest} = props; - +const ChoiceNoneAbove = function ({ + content, + forwardedRef, + showContent = true, + ...rest +}: PropsWithForwardRef): React.ReactElement { const {strings} = usePerseusI18n(); const choiceProps = { @@ -50,10 +51,6 @@ const ChoiceNoneAbove = function ( return ; }; -ChoiceNoneAbove.defaultProps = { - showContent: true, -}; - export default React.forwardRef((props, ref) => ( )); diff --git a/packages/perseus/src/widgets/radio/focus-ring.tsx b/packages/perseus/src/widgets/radio/focus-ring.tsx index 492f8dfa93..9fa2c04359 100644 --- a/packages/perseus/src/widgets/radio/focus-ring.tsx +++ b/packages/perseus/src/widgets/radio/focus-ring.tsx @@ -11,16 +11,19 @@ type Props = { children?: React.ReactNode; // Whether the focus ring is visible. Allows for positioning // the child identically regardless of whether the ring is visible. - visible: boolean; + visible?: boolean; // Color of the focus ring - color: string; + color?: string; // Whether a user can select multiple options or not - multipleSelect: boolean; + multipleSelect?: boolean; }; -const FocusRing = function (props: Props): React.ReactElement { - const {visible, color, children, multipleSelect} = props; - +const FocusRing = function ({ + visible = true, + color = styleConstants.kaGreen, + multipleSelect = false, + children, +}: Props): React.ReactElement { const borderColor = visible ? color : "transparent"; const borderRadius = multipleSelect ? 5 : "50%"; const style = { @@ -38,12 +41,6 @@ const FocusRing = function (props: Props): React.ReactElement { ); }; -FocusRing.defaultProps = { - visible: true, - color: styleConstants.kaGreen, - multipleSelect: false, -}; - const styles = StyleSheet.create({ ring: { margin: "auto", From 428c3cf08b4eee983d991ca3d4e435b3bbbc1785 Mon Sep 17 00:00:00 2001 From: Jeremy Wiebe Date: Thu, 24 Oct 2024 17:40:11 -0700 Subject: [PATCH 02/10] Changeset --- .changeset/forty-otters-check.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/forty-otters-check.md diff --git a/.changeset/forty-otters-check.md b/.changeset/forty-otters-check.md new file mode 100644 index 0000000000..e71acfa3f5 --- /dev/null +++ b/.changeset/forty-otters-check.md @@ -0,0 +1,6 @@ +--- +"@khanacademy/math-input": patch +"@khanacademy/perseus": patch +--- + +Change functional components to use default parameters instead of deprecated 'defaultProps' From fb7a521ea5f1ab57166ae9ca51f02c3f1360e14a Mon Sep 17 00:00:00 2001 From: Jeremy Wiebe Date: Fri, 25 Oct 2024 09:13:50 -0700 Subject: [PATCH 03/10] Fix usage of 'top' prop via props["top"] --- packages/perseus/src/components/tooltip.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/perseus/src/components/tooltip.tsx b/packages/perseus/src/components/tooltip.tsx index 285b8e095b..daba9c7fb4 100644 --- a/packages/perseus/src/components/tooltip.tsx +++ b/packages/perseus/src/components/tooltip.tsx @@ -143,7 +143,7 @@ const TooltipArrow = ({ position: position, visibility: visibility, left: left, - top: props["top"], + top: top, width: props.width + 2, height: props.height + 1, marginTop: -1, From 68a4cbdde1cbf25a860cb503e172fa28089541b3 Mon Sep 17 00:00:00 2001 From: Jeremy Wiebe Date: Fri, 25 Oct 2024 08:55:02 -0700 Subject: [PATCH 04/10] Modernize Perseus' components folder stories --- .../components/__stories__/graph.stories.tsx | 27 ++--- .../__stories__/graphie.stories.tsx | 29 ++--- .../components/__stories__/hud.stories.tsx | 38 ++----- .../components/__stories__/icon.stories.tsx | 27 ++--- .../__stories__/image-loader.stories.tsx | 74 +++++------- .../__stories__/info-tip.stories.tsx | 50 +++++---- .../__stories__/inline-icon.stories.tsx | 50 ++++----- .../input-with-examples.stories.tsx | 73 ++++++------ .../components/__stories__/lint.stories.tsx | 106 +++++++----------- .../__stories__/math-input.stories.tsx | 75 +++++++------ .../multi-button-group.stories.tsx | 86 ++++++-------- .../__stories__/number-input.stories.tsx | 61 ++++++---- .../__stories__/range-input.stories.tsx | 41 ++++--- .../simple-keypad-input.stories.tsx | 33 +++--- .../__stories__/sortable.stories.tsx | 94 ++++++---------- .../__stories__/stub-tag-editor.stories.tsx | 62 +++++----- .../__stories__/svg-image.stories.tsx | 85 ++++++-------- .../components/__stories__/tex.stories.tsx | 25 ++--- .../__stories__/text-input.stories.tsx | 44 ++++---- .../__stories__/text-list-editor.stories.tsx | 44 ++++---- .../__stories__/tooltip.stories.tsx | 69 +++++------- .../__stories__/zoomable-tex.stories.tsx | 52 ++++----- .../__stories__/zoomable.stories.tsx | 42 ++++--- packages/perseus/src/components/graph.tsx | 27 ++--- packages/perseus/src/components/hud.tsx | 4 + packages/perseus/src/components/icon.tsx | 61 +++++----- .../perseus/src/components/image-loader.tsx | 18 +-- .../perseus/src/components/inline-icon.tsx | 15 +-- .../src/components/input-with-examples.tsx | 2 - .../src/components/multi-button-group.tsx | 1 + .../perseus/src/components/number-input.tsx | 2 +- .../perseus/src/components/range-input.tsx | 43 +++---- .../src/components/simple-keypad-input.tsx | 31 +++-- .../src/components/text-list-editor.tsx | 2 +- packages/perseus/src/components/tooltip.tsx | 79 ++++++------- packages/perseus/src/icon-paths.ts | 6 +- 36 files changed, 712 insertions(+), 866 deletions(-) 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..264f94f6c9 100644 --- a/packages/perseus/src/components/__stories__/graphie.stories.tsx +++ b/packages/perseus/src/components/__stories__/graphie.stories.tsx @@ -6,28 +6,23 @@ 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 => { +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..094cebdffb 100644 --- a/packages/perseus/src/components/__stories__/image-loader.stories.tsx +++ b/packages/perseus/src/components/__stories__/image-loader.stories.tsx @@ -1,60 +1,40 @@ -/* 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: () => {}, + }, +}; +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..95583794fa 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 {actions} 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: actions("onChange"), + value: "", + }, + argTypes: { + onChange: { + table: {disable: true}, + }, + }, }; +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 = { + 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..8813554429 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: {table: {disable: true}}, + 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..a009812a87 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 {actions} 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: actions("onChange"), + analytics: {onAnalyticsEvent: () => Promise.resolve()}, + labelText: "Math input", + }, + argTypes: { + onChange: { + table: {disable: true}, + }, + analytics: { + table: {disable: true}, + }, + }, + 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..36305c37f5 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,47 @@ 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 ( - - ); +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..6c7757cdef 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: {table: {disable: true}}, + onFormatChange: {table: {disable: true}}, + }, }; +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..de357c34a9 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: {table: {disable: true}}, + }, }; +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..2480fab74a 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 {actions} 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: actions("onChange"), + }, + argTypes: { + onChange: { + table: {disable: true}, + }, + }, }; -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..a9041ecb74 100644 --- a/packages/perseus/src/components/__stories__/svg-image.stories.tsx +++ b/packages/perseus/src/components/__stories__/svg-image.stories.tsx @@ -1,74 +1,61 @@ -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 = {}; -export const SvgImageThatDoesntLoad = (args: StoryArgs): React.ReactElement => { - return ( - - ); +export const SvgImageThatDoesntLoad: Story = { + args: { + height: 100, + width: 500, + src: "http://httpstat.us/200?sleep=1000000", + }, }; -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..ec687be8e6 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 {actions} 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: actions("onChange"), + onBlur: actions("onBlur"), + onFocus: actions("onFocus"), + }, + argTypes: { + onChange: {table: {disable: true}}, + onBlur: {table: {disable: true}}, + onFocus: {table: {disable: true}}, + }, +}; +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..568ffabb11 100644 --- a/packages/perseus/src/components/__stories__/text-list-editor.stories.tsx +++ b/packages/perseus/src/components/__stories__/text-list-editor.stories.tsx @@ -1,32 +1,30 @@ -import {action} from "@storybook/addon-actions"; +import {actions} from "@storybook/addon-actions"; 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: actions("onChange"), }, - options: ["Test option 1", "Test option 2", "Test option 3"], -} as const; + argTypes: { + onChange: {table: {disable: true}}, + }, + 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..80fd1984f5 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,29 @@ const computeChildBounds = ( }; }; -export const ZoomableExample = (args: StoryArgs): React.ReactElement => { - return ( - +const meta: Meta = { + title: "Perseus/Components/Zoomable", + component: Zoomable, + args: { + computeChildBounds, + }, + argTypes: { + children: {table: {disable: 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/graph.tsx b/packages/perseus/src/components/graph.tsx index e8e8af9210..08674fa5ff 100644 --- a/packages/perseus/src/components/graph.tsx +++ b/packages/perseus/src/components/graph.tsx @@ -3,7 +3,6 @@ import {vector as kvector} from "@khanacademy/kmath"; import $ from "jquery"; import * as React from "react"; -import ReactDOM from "react-dom"; import _ from "underscore"; import AssetContext from "../asset-context"; @@ -86,10 +85,9 @@ class Graph extends React.Component { protractor: any; ruler: any; _graphie: any; - // @ts-expect-error - TS2564 - Property '_hasSetupGraphieThisUpdate' has no initializer and is not definitely assigned in the constructor. - _hasSetupGraphieThisUpdate: boolean; - // @ts-expect-error - TS2564 - Property '_shouldSetupGraphie' has no initializer and is not definitely assigned in the constructor. - _shouldSetupGraphie: boolean; + _hasSetupGraphieThisUpdate = false; + _shouldSetupGraphie = true; + graphieDiv = React.createRef(); static defaultProps: DefaultProps = { labels: ["x", "y"], @@ -186,11 +184,11 @@ class Graph extends React.Component { if (this._hasSetupGraphieThisUpdate) { return; } + if (this.graphieDiv.current == null) { + return; + } - // eslint-disable-next-line react/no-string-refs - const graphieDiv = ReactDOM.findDOMNode(this.refs.graphieDiv); - // @ts-expect-error - TS2769 - No overload matches this call. | TS2339 - Property 'empty' does not exist on type 'JQueryStatic'. - $(graphieDiv).empty(); + $(this.graphieDiv.current).empty(); // Content creators may need to explicitly add the dollar signs so the // strings are picked up by our translation tools. However, these math @@ -202,8 +200,9 @@ class Graph extends React.Component { Util.unescapeMathMode(label), ); const range = this.props.range; - // @ts-expect-error - TS2345: Argument of type 'Element | Text | null' is not assignable to parameter of type 'HTMLElement'. - const graphie = (this._graphie = GraphUtils.createGraphie(graphieDiv)); + const graphie = (this._graphie = GraphUtils.createGraphie( + this.graphieDiv.current, + )); const gridConfig: [GridDimensions, GridDimensions] = this._getGridConfig(); @@ -271,8 +270,7 @@ class Graph extends React.Component { }); $instructionsWrapper.append($instructions); - // @ts-expect-error - TS2769 - No overload matches this call. | TS2339 - Property 'append' does not exist on type 'JQueryStatic'. - $(graphieDiv).append($instructionsWrapper); + $(this.graphieDiv.current).append($instructionsWrapper); } else { $instructionsWrapper = undefined; } @@ -431,8 +429,7 @@ class Graph extends React.Component { onClick={this.onClick} > {image} - {/* eslint-disable-next-line react/no-string-refs */} -
+
); } 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 ( { }; render(): React.ReactNode { + console.log(this.props.values, this.props.buttons); const values = this.props.values || []; const buttons = this.props.buttons.map((button, i) => { const selected = values.indexOf(button.value) >= 0; diff --git a/packages/perseus/src/components/number-input.tsx b/packages/perseus/src/components/number-input.tsx index 20027dbbee..ad8cfd1d79 100644 --- a/packages/perseus/src/components/number-input.tsx +++ b/packages/perseus/src/components/number-input.tsx @@ -42,7 +42,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..0226f5dc45 100644 --- a/packages/perseus/src/components/range-input.tsx +++ b/packages/perseus/src/components/range-input.tsx @@ -1,33 +1,26 @@ -/* eslint-disable react/forbid-prop-types */ -import PropTypes from "prop-types"; import * as React from "react"; import NumberInput from "./number-input"; const truth = () => true; -/* A minor abstraction on top of NumberInput for ranges - * - */ -class RangeInput extends React.Component { - static propTypes = { - value: PropTypes.array.isRequired, - onChange: PropTypes.func.isRequired, - placeholder: PropTypes.array, - checkValidity: PropTypes.func, - }; +type Props = { + value: [number, number]; + placeholder: [string, string]; + onChange: (start: number, end: number) => void; + checkValidity: (vals: [number, number]) => boolean; +}; - static defaultProps: any = { - placeholder: [null, null], - }; +type DefaultProps = { + placeholder: Props["placeholder"] | [null, null]; +}; - onChange: (arg1: number, arg2: string) => void = (i, newVal) => { - const value = this.props.value; - if (i === 0) { - this.props.onChange([newVal, value[1]]); - } else { - this.props.onChange([value[0], newVal]); - } +/** + * A minor abstraction on top of NumberInput for ranges. + */ +class RangeInput extends React.Component { + static defaultProps: DefaultProps = { + placeholder: [null, null], }; render(): React.ReactNode { @@ -40,16 +33,14 @@ class RangeInput extends React.Component { {...this.props} value={value[0]} checkValidity={(val) => checkValidity([val, value[1]])} - // eslint-disable-next-line react/jsx-no-bind - onChange={this.onChange.bind(this, 0)} + onChange={(val) => this.props.onChange(val, value[1])} placeholder={this.props.placeholder[0]} /> checkValidity([value[0], val])} - // eslint-disable-next-line react/jsx-no-bind - onChange={this.onChange.bind(this, 1)} + onChange={(val: any) => this.props.onChange(value[0], val)} placeholder={this.props.placeholder[1]} />
diff --git a/packages/perseus/src/components/simple-keypad-input.tsx b/packages/perseus/src/components/simple-keypad-input.tsx index aa6f9e4511..75398fe2f0 100644 --- a/packages/perseus/src/components/simple-keypad-input.tsx +++ b/packages/perseus/src/components/simple-keypad-input.tsx @@ -1,22 +1,24 @@ +import {KeypadInput, KeypadType} from "@khanacademy/math-input"; +import * as React from "react"; + +import type {KeypadAPI} from "@khanacademy/math-input"; + +type Props = { + keypadElement: KeypadAPI; + onFocus: () => void; + value: string | number; +}; + /** * A version of the `math-input` subrepo's KeypadInput component that adheres to - * the same API as Perseus's MathOuput and NumberInput, allowing it to be + * the same API as Perseus's `MathOuput` and `NumberInput`, allowing it to be * dropped in as a replacement for those components without any modifications. * * TODO(charlie): Once the keypad API has stabilized, move this into the * `math-input` subrepo and use it everywhere as a simpler, keypad-coupled * interface to `math-input`'s MathInput component. */ - -import { - KeypadInput, - KeypadType, - keypadElementPropType, -} from "@khanacademy/math-input"; -import PropTypes from "prop-types"; -import * as React from "react"; - -export default class SimpleKeypadInput extends React.Component { +export default class SimpleKeypadInput extends React.Component { _isMounted = false; componentDidMount() { @@ -84,10 +86,3 @@ export default class SimpleKeypadInput extends React.Component { ); } } - -// @ts-expect-error - TS2339 - Property 'propTypes' does not exist on type 'typeof SimpleKeypadInput'. -SimpleKeypadInput.propTypes = { - keypadElement: keypadElementPropType, - onFocus: PropTypes.func, - value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), -}; 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..53a0cc7cb0 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 @@ -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 f38a668057..543a47859c 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 = { From f43ed667541da53d3d51244c9cfc4e6fa4f8dadd Mon Sep 17 00:00:00 2001 From: Jeremy Wiebe Date: Fri, 25 Oct 2024 09:04:09 -0700 Subject: [PATCH 05/10] Changeset --- .changeset/old-lamps-wonder.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/old-lamps-wonder.md 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 From 762e3d70ed66a111c62db307c84f25abc219d8d9 Mon Sep 17 00:00:00 2001 From: Jeremy Wiebe Date: Fri, 25 Oct 2024 09:57:22 -0700 Subject: [PATCH 06/10] Revert type chagnes to two components - rabbit hole --- .../perseus/src/components/range-input.tsx | 43 +++++++++++-------- .../src/components/simple-keypad-input.tsx | 31 +++++++------ 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/packages/perseus/src/components/range-input.tsx b/packages/perseus/src/components/range-input.tsx index 0226f5dc45..ac0ad72368 100644 --- a/packages/perseus/src/components/range-input.tsx +++ b/packages/perseus/src/components/range-input.tsx @@ -1,28 +1,35 @@ +/* eslint-disable react/forbid-prop-types */ +import PropTypes from "prop-types"; import * as React from "react"; import NumberInput from "./number-input"; const truth = () => true; -type Props = { - value: [number, number]; - placeholder: [string, string]; - onChange: (start: number, end: number) => void; - checkValidity: (vals: [number, number]) => boolean; -}; - -type DefaultProps = { - placeholder: Props["placeholder"] | [null, null]; -}; - -/** - * A minor abstraction on top of NumberInput for ranges. +/* A minor abstraction on top of NumberInput for ranges + * */ -class RangeInput extends React.Component { - static defaultProps: DefaultProps = { +class RangeInput extends React.Component { + static propTypes = { + value: PropTypes.array.isRequired, + onChange: PropTypes.func.isRequired, + placeholder: PropTypes.array, + checkValidity: PropTypes.func, + }; + + static defaultProps: any = { placeholder: [null, null], }; + onChange: (arg1: number, arg2: string) => void = (i, newVal) => { + const value = this.props.value; + if (i === 0) { + this.props.onChange([newVal, value[1]]); + } else { + this.props.onChange([value[0], newVal]); + } + }; + render(): React.ReactNode { const value = this.props.value; const checkValidity = this.props.checkValidity || truth; @@ -33,14 +40,16 @@ class RangeInput extends React.Component { {...this.props} value={value[0]} checkValidity={(val) => checkValidity([val, value[1]])} - onChange={(val) => this.props.onChange(val, value[1])} + // eslint-disable-next-line react/jsx-no-bind + onChange={this.onChange.bind(this, 0)} placeholder={this.props.placeholder[0]} /> checkValidity([value[0], val])} - onChange={(val: any) => this.props.onChange(value[0], val)} + // eslint-disable-next-line react/jsx-no-bind + onChange={this.onChange.bind(this, 1)} placeholder={this.props.placeholder[1]} />
diff --git a/packages/perseus/src/components/simple-keypad-input.tsx b/packages/perseus/src/components/simple-keypad-input.tsx index 75398fe2f0..aa6f9e4511 100644 --- a/packages/perseus/src/components/simple-keypad-input.tsx +++ b/packages/perseus/src/components/simple-keypad-input.tsx @@ -1,24 +1,22 @@ -import {KeypadInput, KeypadType} from "@khanacademy/math-input"; -import * as React from "react"; - -import type {KeypadAPI} from "@khanacademy/math-input"; - -type Props = { - keypadElement: KeypadAPI; - onFocus: () => void; - value: string | number; -}; - /** * A version of the `math-input` subrepo's KeypadInput component that adheres to - * the same API as Perseus's `MathOuput` and `NumberInput`, allowing it to be + * the same API as Perseus's MathOuput and NumberInput, allowing it to be * dropped in as a replacement for those components without any modifications. * * TODO(charlie): Once the keypad API has stabilized, move this into the * `math-input` subrepo and use it everywhere as a simpler, keypad-coupled * interface to `math-input`'s MathInput component. */ -export default class SimpleKeypadInput extends React.Component { + +import { + KeypadInput, + KeypadType, + keypadElementPropType, +} from "@khanacademy/math-input"; +import PropTypes from "prop-types"; +import * as React from "react"; + +export default class SimpleKeypadInput extends React.Component { _isMounted = false; componentDidMount() { @@ -86,3 +84,10 @@ export default class SimpleKeypadInput extends React.Component { ); } } + +// @ts-expect-error - TS2339 - Property 'propTypes' does not exist on type 'typeof SimpleKeypadInput'. +SimpleKeypadInput.propTypes = { + keypadElement: keypadElementPropType, + onFocus: PropTypes.func, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), +}; From 224e3450e815a25a780ea38cdd295607e33145d4 Mon Sep 17 00:00:00 2001 From: Jeremy Wiebe Date: Fri, 25 Oct 2024 10:00:29 -0700 Subject: [PATCH 07/10] Remove console.log() --- packages/perseus/src/components/multi-button-group.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/perseus/src/components/multi-button-group.tsx b/packages/perseus/src/components/multi-button-group.tsx index 2a371142eb..f9da59911d 100644 --- a/packages/perseus/src/components/multi-button-group.tsx +++ b/packages/perseus/src/components/multi-button-group.tsx @@ -68,7 +68,6 @@ class MultiButtonGroup extends React.Component { }; render(): React.ReactNode { - console.log(this.props.values, this.props.buttons); const values = this.props.values || []; const buttons = this.props.buttons.map((button, i) => { const selected = values.indexOf(button.value) >= 0; From 35bcb772b66753425e053758d5dba7ae2e352656 Mon Sep 17 00:00:00 2001 From: Jeremy Wiebe Date: Fri, 25 Oct 2024 10:01:32 -0700 Subject: [PATCH 08/10] Use standard method for accessing props --- packages/perseus/src/components/tooltip.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/perseus/src/components/tooltip.tsx b/packages/perseus/src/components/tooltip.tsx index 53a0cc7cb0..d5a13e13ea 100644 --- a/packages/perseus/src/components/tooltip.tsx +++ b/packages/perseus/src/components/tooltip.tsx @@ -58,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, From 2b550b10a83866720107b838f07c254e500162ce Mon Sep 17 00:00:00 2001 From: Jeremy Wiebe Date: Fri, 25 Oct 2024 10:12:33 -0700 Subject: [PATCH 09/10] Simplify passing onAnalyticsEvent - to help Storybook --- .../components/__tests__/math-input.test.tsx | 18 +++++++++--------- packages/perseus/src/components/math-input.tsx | 6 +++--- packages/perseus/src/types.ts | 4 ++-- .../src/widgets/expression/expression.tsx | 7 +++---- 4 files changed, 17 insertions(+), 18 deletions(-) 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/math-input.tsx b/packages/perseus/src/components/math-input.tsx index 995273ae3e..c3af73f33f 100644 --- a/packages/perseus/src/components/math-input.tsx +++ b/packages/perseus/src/components/math-input.tsx @@ -29,6 +29,7 @@ import {PerseusI18nContext} from "./i18n-context"; import type {LegacyButtonSets} from "../perseus-types"; import type {PerseusDependenciesV2} from "../types"; import type {Keys, MathFieldInterface} from "@khanacademy/math-input"; +import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core"; type ButtonsVisibleType = "always" | "never" | "focused"; @@ -68,7 +69,7 @@ type Props = { * - `never` means that the keypad is **never shown**. */ buttonsVisible?: ButtonsVisibleType; - analytics: PerseusDependenciesV2["analytics"]; + onAnalyticsEvent: AnalyticsEventHandlerFn; }; type InnerProps = Props & { @@ -352,8 +353,7 @@ class InnerMathInput extends React.Component { > {}, - } + onAnalyticsEvent={ + this.props.analytics?.onAnalyticsEvent ?? + (async () => {}) } /> From 2f4f7b18ec13f26ee9856e587913ab796a2c8c91 Mon Sep 17 00:00:00 2001 From: Jeremy Wiebe Date: Fri, 25 Oct 2024 10:17:34 -0700 Subject: [PATCH 10/10] Remove unused import --- packages/perseus/src/components/math-input.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/perseus/src/components/math-input.tsx b/packages/perseus/src/components/math-input.tsx index c3af73f33f..37aa4b0b63 100644 --- a/packages/perseus/src/components/math-input.tsx +++ b/packages/perseus/src/components/math-input.tsx @@ -27,7 +27,6 @@ import {debounce} from "../util/debounce"; import {PerseusI18nContext} from "./i18n-context"; import type {LegacyButtonSets} from "../perseus-types"; -import type {PerseusDependenciesV2} from "../types"; import type {Keys, MathFieldInterface} from "@khanacademy/math-input"; import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core";