Skip to content

Commit

Permalink
fix: Forms - Add 'escape' button / key handler in forms (#240)
Browse files Browse the repository at this point in the history
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:
<img width="977" alt="Screen Shot 2022-02-09 at 5 48 03 PM" src="https://user-images.githubusercontent.com/88213859/153321503-af957f2b-d674-4ebf-9ac5-97939cb9153f.png">

In addition, this adds test cases + a storybook story for it.
  • Loading branch information
bryphe-coder committed Feb 16, 2022
1 parent a86f2ee commit 35291d3
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 2 deletions.
16 changes: 16 additions & 0 deletions site/components/Form/FormCloseButton.stories.tsx
Original file line number Diff line number Diff line change
@@ -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<FormCloseButtonProps> = (args) => <FormCloseButton {...args} />

export const Example = Template.bind({})
Example.args = {}
70 changes: 70 additions & 0 deletions site/components/Form/FormCloseButton.test.tsx
Original file line number Diff line number Diff line change
@@ -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(
<FormCloseButton
onClose={() => {
return
}}
/>,
)

// Then
await screen.findByText("ESC")
})

it("calls onClose when clicked", async () => {
// Given
const onClose = jest.fn()

// When
render(<FormCloseButton onClose={onClose} />)

// 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(<FormCloseButton onClose={onClose} />)

// 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(<FormCloseButton onClose={onClose} />)

// Then
const element = await screen.findByText("ESC")

// When
fireEvent.keyDown(element, { key: "Enter", code: "Enter", charCode: 13 })

// Then
expect(onClose).toBeCalledTimes(0)
})
})
55 changes: 55 additions & 0 deletions site/components/Form/FormCloseButton.tsx
Original file line number Diff line number Diff line change
@@ -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<FormCloseButtonProps> = ({ 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 (
<IconButton className={styles.closeButton} onClick={onClose} size="medium">
<CloseIcon />
<Typography variant="caption" className={styles.label}>
ESC
</Typography>
</IconButton>
)
}

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%)",
},
}))
1 change: 1 addition & 0 deletions site/components/Form/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./FormCloseButton"
export * from "./FormSection"
export * from "./FormDropdownField"
export * from "./FormTextField"
Expand Down
8 changes: 8 additions & 0 deletions site/components/Icons/Close.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import SvgIcon from "@material-ui/core/SvgIcon"
import React from "react"

export const CloseIcon: typeof SvgIcon = (props) => (
<SvgIcon {...props} viewBox="0 0 31 31">
<path d="M29.5 1.5l-28 28M29.5 29.5l-28-28" stroke="currentcolor" strokeMiterlimit="10" strokeLinecap="square" />
</SvgIcon>
)
10 changes: 9 additions & 1 deletion site/forms/CreateProjectForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -59,6 +66,7 @@ export const CreateProjectForm: React.FC<CreateProjectFormProps> = ({
return (
<div className={styles.root}>
<FormTitle title="Create Project" />
<FormCloseButton onClose={onCancel} />

<FormSection title="Name">
<FormTextField
Expand Down
4 changes: 3 additions & 1 deletion site/forms/CreateWorkspaceForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { FormikContextType, useFormik } from "formik"
import React from "react"
import * as Yup from "yup"

import { FormTextField, FormTitle, FormSection } from "../components/Form"
import { FormCloseButton, FormTextField, FormTitle, FormSection } from "../components/Form"
import { LoadingButton } from "../components/Button"
import { Project, Workspace, CreateWorkspaceRequest } from "../api"

Expand Down Expand Up @@ -45,6 +45,8 @@ export const CreateWorkspaceForm: React.FC<CreateWorkspaceForm> = ({ project, on
</span>
}
/>
<FormCloseButton onClose={onCancel} />

<FormSection title="Name">
<FormTextField
form={form}
Expand Down

0 comments on commit 35291d3

Please sign in to comment.