diff --git a/.changeset/new-pigs-begin.md b/.changeset/new-pigs-begin.md new file mode 100644 index 0000000000..4b03940984 --- /dev/null +++ b/.changeset/new-pigs-begin.md @@ -0,0 +1,6 @@ +--- +"@khanacademy/perseus": minor +"@khanacademy/perseus-editor": minor +--- + +[Interactive Graph + Editor] Add a full graph aria-label and aria-description/describedby to interactive graphs, as well as the UI for content authors to add this in the interactive graph editor 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 a84888f5fc..75a6a5d5ab 100644 --- a/packages/perseus-editor/src/__stories__/interactive-graph-editor.stories.tsx +++ b/packages/perseus-editor/src/__stories__/interactive-graph-editor.stories.tsx @@ -10,6 +10,7 @@ import {EditorPage} from ".."; import { angleWithStartingCoordsQuestion, circleWithStartingCoordsQuestion, + interactiveGraphWithAriaLabel, linearSystemWithStartingCoordsQuestion, linearWithStartingCoordsQuestion, pointQuestionWithStartingCoords, @@ -41,6 +42,10 @@ export default { const onChangeAction = action("onChange"); +export const InteractiveGraphWithAriaLabel = (): React.ReactElement => ( + +); + export const InteractiveGraphSegment = (): React.ReactElement => { return ( { + let userEvent: UserEvent; + beforeEach(() => { + userEvent = userEventForFakeTimers(); + jest.spyOn(Dependencies, "getDependencies").mockReturnValue( + testDependencies, + ); + }); + + test("renders", () => { + // Arrange + render( + , + ); + + // Act + const titleInput = screen.getByRole("textbox", {name: "Title"}); + const descriptionInput = screen.getByRole("textbox", { + name: "Description", + }); + + // Assert + expect(titleInput).toBeInTheDocument(); + expect(titleInput).toHaveValue("Graph Title"); + expect(descriptionInput).toBeInTheDocument(); + expect(descriptionInput).toHaveValue("Graph Description"); + }); + + test("calls onChange when the title is changed", async () => { + // Arrange + const onChange = jest.fn(); + render( + , + ); + + // Act + const titleInput = screen.getByRole("textbox", {name: "Title"}); + await userEvent.clear(titleInput); + await userEvent.type(titleInput, "Zot"); + + // Assert + // Calls are not being accumulated because they're mocked. + expect(onChange.mock.calls).toEqual([ + [{fullGraphAriaLabel: "Z"}], + [{fullGraphAriaLabel: "o"}], + [{fullGraphAriaLabel: "t"}], + ]); + }); + + test("calls onChange when the description is changed", async () => { + // Arrange + const onChange = jest.fn(); + render( + , + ); + + // Act + const descriptionInput = screen.getByRole("textbox", { + name: "Description", + }); + await userEvent.clear(descriptionInput); + await userEvent.type(descriptionInput, "Zot"); + + // Assert + // Calls are not being accumulated because they're mocked. + expect(onChange.mock.calls).toEqual([ + [{fullGraphAriaDescription: "Z"}], + [{fullGraphAriaDescription: "o"}], + [{fullGraphAriaDescription: "t"}], + ]); + }); +}); diff --git a/packages/perseus-editor/src/components/interactive-graph-description.tsx b/packages/perseus-editor/src/components/interactive-graph-description.tsx new file mode 100644 index 0000000000..d84924c0c2 --- /dev/null +++ b/packages/perseus-editor/src/components/interactive-graph-description.tsx @@ -0,0 +1,82 @@ +import {View} from "@khanacademy/wonder-blocks-core"; +import {TextField} from "@khanacademy/wonder-blocks-form"; +import {Strut} from "@khanacademy/wonder-blocks-layout"; +import {color, spacing} from "@khanacademy/wonder-blocks-tokens"; +import {LabelLarge, LabelXSmall} from "@khanacademy/wonder-blocks-typography"; +import {StyleSheet} from "aphrodite"; +import * as React from "react"; + +import Heading from "./heading"; + +import type {Props as EditorProps} from "../widgets/interactive-graph-editor/interactive-graph-editor"; + +type Props = { + ariaLabelValue: string; + ariaDescriptionValue: string; + onChange: (graphProps: Partial) => void; +}; + +export default function InteractiveGraphDescription(props: Props) { + const {ariaLabelValue, ariaDescriptionValue, onChange} = props; + + const [isOpen, setIsOpen] = React.useState(true); + + return ( + <> + + {isOpen && ( + + + Use these fields to describe the graph as a whole. These + are used by screen readers to describe content to users + who are visually impaired. + + + Title + + onChange({fullGraphAriaLabel: newValue}) + } + /> + + + + Description + {/* TODO(LEMS-2332): Change this to a WB TextArea */} +