From 35291d358ab8d72965da28bfd7c3e529c41de179 Mon Sep 17 00:00:00 2001 From: Bryan Date: Tue, 15 Feb 2022 16:43:27 -0800 Subject: [PATCH] fix: Forms - Add 'escape' button / key handler in forms (#240) In v1, we had a quick and easy way to leave creation forms - an escape button (and key handler) in the top right corner. This was especially helpful in cases where the form was long enough that the 'Cancel' button was off-screen. This ports over that component into v2 and hooks up into our two existing forms: Screen Shot 2022-02-09 at 5 48 03 PM In addition, this adds test cases + a storybook story for it. --- .../Form/FormCloseButton.stories.tsx | 16 +++++ site/components/Form/FormCloseButton.test.tsx | 70 +++++++++++++++++++ site/components/Form/FormCloseButton.tsx | 55 +++++++++++++++ site/components/Form/index.ts | 1 + site/components/Icons/Close.tsx | 8 +++ site/forms/CreateProjectForm.tsx | 10 ++- site/forms/CreateWorkspaceForm.tsx | 4 +- 7 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 site/components/Form/FormCloseButton.stories.tsx create mode 100644 site/components/Form/FormCloseButton.test.tsx create mode 100644 site/components/Form/FormCloseButton.tsx create mode 100644 site/components/Icons/Close.tsx diff --git a/site/components/Form/FormCloseButton.stories.tsx b/site/components/Form/FormCloseButton.stories.tsx new file mode 100644 index 0000000000..6e011a2536 --- /dev/null +++ b/site/components/Form/FormCloseButton.stories.tsx @@ -0,0 +1,16 @@ +import { Story } from "@storybook/react" +import React from "react" +import { FormCloseButton, FormCloseButtonProps } from "./FormCloseButton" + +export default { + title: "Form/FormCloseButton", + component: FormCloseButton, + argTypes: { + onClose: { action: "onClose" }, + }, +} + +const Template: Story = (args) => + +export const Example = Template.bind({}) +Example.args = {} diff --git a/site/components/Form/FormCloseButton.test.tsx b/site/components/Form/FormCloseButton.test.tsx new file mode 100644 index 0000000000..393f7bcc65 --- /dev/null +++ b/site/components/Form/FormCloseButton.test.tsx @@ -0,0 +1,70 @@ +import { fireEvent, render, screen } from "@testing-library/react" +import React from "react" +import { FormCloseButton } from "./FormCloseButton" + +describe("FormCloseButton", () => { + it("renders", async () => { + // When + render( + { + return + }} + />, + ) + + // Then + await screen.findByText("ESC") + }) + + it("calls onClose when clicked", async () => { + // Given + const onClose = jest.fn() + + // When + render() + + // Then + const element = await screen.findByText("ESC") + + // When + fireEvent.click(element) + + // Then + expect(onClose).toBeCalledTimes(1) + }) + + it("calls onClose when escape is pressed", async () => { + // Given + const onClose = jest.fn() + + // When + render() + + // Then + const element = await screen.findByText("ESC") + + // When + fireEvent.keyDown(element, { key: "Escape", code: "Esc", charCode: 27 }) + + // Then + expect(onClose).toBeCalledTimes(1) + }) + + it("doesn't call onClose if another key is pressed", async () => { + // Given + const onClose = jest.fn() + + // When + render() + + // Then + const element = await screen.findByText("ESC") + + // When + fireEvent.keyDown(element, { key: "Enter", code: "Enter", charCode: 13 }) + + // Then + expect(onClose).toBeCalledTimes(0) + }) +}) diff --git a/site/components/Form/FormCloseButton.tsx b/site/components/Form/FormCloseButton.tsx new file mode 100644 index 0000000000..a22f71e8c5 --- /dev/null +++ b/site/components/Form/FormCloseButton.tsx @@ -0,0 +1,55 @@ +import IconButton from "@material-ui/core/IconButton" +import { makeStyles } from "@material-ui/core/styles" +import Typography from "@material-ui/core/Typography" +import React, { useEffect } from "react" +import { CloseIcon } from "../Icons/Close" + +export interface FormCloseButtonProps { + onClose: () => void +} + +export const FormCloseButton: React.FC = ({ onClose }) => { + const styles = useStyles() + + useEffect(() => { + const handleKeyPress = (event: KeyboardEvent) => { + if (event.key === "Escape") { + onClose() + } + } + + document.body.addEventListener("keydown", handleKeyPress, false) + + return () => { + document.body.removeEventListener("keydown", handleKeyPress, false) + } + }, []) + + return ( + + + + ESC + + + ) +} + +const useStyles = makeStyles((theme) => ({ + closeButton: { + position: "fixed", + top: theme.spacing(3), + right: theme.spacing(6), + opacity: 0.5, + color: theme.palette.text.primary, + "&:hover": { + opacity: 1, + }, + }, + label: { + position: "absolute", + left: "50%", + top: "100%", + transform: "translate(-50%, 50%)", + }, +})) diff --git a/site/components/Form/index.ts b/site/components/Form/index.ts index 80ddbbac74..08456432c7 100644 --- a/site/components/Form/index.ts +++ b/site/components/Form/index.ts @@ -1,3 +1,4 @@ +export * from "./FormCloseButton" export * from "./FormSection" export * from "./FormDropdownField" export * from "./FormTextField" diff --git a/site/components/Icons/Close.tsx b/site/components/Icons/Close.tsx new file mode 100644 index 0000000000..ab9a44a898 --- /dev/null +++ b/site/components/Icons/Close.tsx @@ -0,0 +1,8 @@ +import SvgIcon from "@material-ui/core/SvgIcon" +import React from "react" + +export const CloseIcon: typeof SvgIcon = (props) => ( + + + +) diff --git a/site/forms/CreateProjectForm.tsx b/site/forms/CreateProjectForm.tsx index 1a4558e9b2..a930cf487e 100644 --- a/site/forms/CreateProjectForm.tsx +++ b/site/forms/CreateProjectForm.tsx @@ -4,7 +4,14 @@ import { FormikContextType, useFormik } from "formik" import React from "react" import * as Yup from "yup" -import { DropdownItem, FormDropdownField, FormTextField, FormTitle, FormSection } from "../components/Form" +import { + DropdownItem, + FormDropdownField, + FormTextField, + FormTitle, + FormSection, + FormCloseButton, +} from "../components/Form" import { LoadingButton } from "../components/Button" import { Organization, Project, Provisioner, CreateProjectRequest } from "./../api" @@ -59,6 +66,7 @@ export const CreateProjectForm: React.FC = ({ return (
+ = ({ project, on } /> + +