refactor: Extract workspace filter into a separate component (#2601)

This commit is contained in:
Abhineet Jain
2022-06-23 11:30:53 -04:00
committed by GitHub
parent 929227d0f8
commit 9c8079b25e
3 changed files with 172 additions and 113 deletions
@@ -0,0 +1,25 @@
import { ComponentMeta, Story } from "@storybook/react"
import { workspaceFilterQuery } from "../../util/workspace"
import { SearchBarWithFilter, SearchBarWithFilterProps } from "./SearchBarWithFilter"
export default {
title: "components/SearchBarWithFilter",
component: SearchBarWithFilter,
argTypes: {
filter: {
defaultValue: workspaceFilterQuery.me,
},
},
} as ComponentMeta<typeof SearchBarWithFilter>
const Template: Story<SearchBarWithFilterProps> = (args) => <SearchBarWithFilter {...args} />
export const WithoutPresetFilters = Template.bind({})
export const WithPresetFilters = Template.bind({})
WithPresetFilters.args = {
presetFilters: [
{ query: workspaceFilterQuery.me, name: "Your workspaces" },
{ query: "random query", name: "Random query" },
],
}
@@ -0,0 +1,140 @@
import Button from "@material-ui/core/Button"
import Fade from "@material-ui/core/Fade"
import InputAdornment from "@material-ui/core/InputAdornment"
import Menu from "@material-ui/core/Menu"
import MenuItem from "@material-ui/core/MenuItem"
import { makeStyles } from "@material-ui/core/styles"
import TextField from "@material-ui/core/TextField"
import SearchIcon from "@material-ui/icons/Search"
import { FormikErrors, useFormik } from "formik"
import { useState } from "react"
import { getFormHelpers, onChangeTrimmed } from "../../util/formUtils"
import { CloseDropdown, OpenDropdown } from "../DropdownArrows/DropdownArrows"
import { Stack } from "../Stack/Stack"
export const Language = {
filterName: "Filters",
}
export interface SearchBarWithFilterProps {
filter?: string
onFilter: (query: string) => void
presetFilters?: PresetFilter[]
}
export interface PresetFilter {
name: string
query: string
}
interface FilterFormValues {
query: string
}
export type FilterFormErrors = FormikErrors<FilterFormValues>
export const SearchBarWithFilter: React.FC<SearchBarWithFilterProps> = ({ filter, onFilter, presetFilters }) => {
const styles = useStyles()
const form = useFormik<FilterFormValues>({
enableReinitialize: true,
initialValues: {
query: filter ?? "",
},
onSubmit: ({ query }) => {
onFilter(query)
},
})
const getFieldHelpers = getFormHelpers<FilterFormValues>(form)
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setAnchorEl(null)
}
const setPresetFilter = (query: string) => () => {
void form.setFieldValue("query", query)
void form.submitForm()
handleClose()
}
return (
<Stack direction="row" spacing={0} className={styles.filterContainer}>
{presetFilters && presetFilters.length > 0 && (
<Button aria-controls="filter-menu" aria-haspopup="true" onClick={handleClick} className={styles.buttonRoot}>
{Language.filterName} {anchorEl ? <CloseDropdown /> : <OpenDropdown />}
</Button>
)}
<form onSubmit={form.handleSubmit} className={styles.filterForm}>
<TextField
{...getFieldHelpers("query")}
className={styles.textFieldRoot}
onChange={onChangeTrimmed(form)}
fullWidth
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon fontSize="small" />
</InputAdornment>
),
}}
/>
</form>
{presetFilters && presetFilters.length > 0 && (
<Menu
id="filter-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
TransitionComponent={Fade}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
transformOrigin={{
vertical: "top",
horizontal: "left",
}}
>
{presetFilters.map((presetFilter) => (
<MenuItem key={presetFilter.name} onClick={setPresetFilter(presetFilter.query)}>
{presetFilter.name}
</MenuItem>
))}
</Menu>
)}
</Stack>
)
}
const useStyles = makeStyles((theme) => ({
filterContainer: {
border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadius,
marginBottom: theme.spacing(2),
},
filterForm: {
width: "100%",
},
buttonRoot: {
border: "none",
borderRight: `1px solid ${theme.palette.divider}`,
borderRadius: `${theme.shape.borderRadius}px 0px 0px ${theme.shape.borderRadius}px`,
},
textFieldRoot: {
margin: "0px",
"& fieldset": {
border: "none",
},
},
}))
@@ -1,32 +1,25 @@
import Button from "@material-ui/core/Button"
import Fade from "@material-ui/core/Fade"
import InputAdornment from "@material-ui/core/InputAdornment"
import Link from "@material-ui/core/Link"
import Menu from "@material-ui/core/Menu"
import MenuItem from "@material-ui/core/MenuItem"
import { fade, makeStyles, Theme } from "@material-ui/core/styles"
import Table from "@material-ui/core/Table"
import TableBody from "@material-ui/core/TableBody"
import TableCell from "@material-ui/core/TableCell"
import TableHead from "@material-ui/core/TableHead"
import TableRow from "@material-ui/core/TableRow"
import TextField from "@material-ui/core/TextField"
import AddCircleOutline from "@material-ui/icons/AddCircleOutline"
import KeyboardArrowRight from "@material-ui/icons/KeyboardArrowRight"
import RefreshIcon from "@material-ui/icons/Refresh"
import SearchIcon from "@material-ui/icons/Search"
import useTheme from "@material-ui/styles/useTheme"
import { useActor } from "@xstate/react"
import dayjs from "dayjs"
import relativeTime from "dayjs/plugin/relativeTime"
import { FormikErrors, useFormik } from "formik"
import { FC, useState } from "react"
import { FC } from "react"
import { Link as RouterLink, useNavigate } from "react-router-dom"
import { AvatarData } from "../../components/AvatarData/AvatarData"
import { CloseDropdown, OpenDropdown } from "../../components/DropdownArrows/DropdownArrows"
import { EmptyState } from "../../components/EmptyState/EmptyState"
import { Margins } from "../../components/Margins/Margins"
import { PageHeader, PageHeaderSubtitle, PageHeaderTitle } from "../../components/PageHeader/PageHeader"
import { SearchBarWithFilter } from "../../components/SearchBarWithFilter/SearchBarWithFilter"
import { Stack } from "../../components/Stack/Stack"
import { TableCellLink } from "../../components/TableCellLink/TableCellLink"
import { TableLoader } from "../../components/TableLoader/TableLoader"
@@ -38,7 +31,6 @@ import {
HelpTooltipText,
HelpTooltipTitle,
} from "../../components/Tooltips/HelpTooltip/HelpTooltip"
import { getFormHelpers, onChangeTrimmed } from "../../util/formUtils"
import { getDisplayStatus, workspaceFilterQuery } from "../../util/workspace"
import { WorkspaceItemMachineRef } from "../../xServices/workspaces/workspacesXService"
@@ -49,7 +41,6 @@ export const Language = {
emptyCreateWorkspaceMessage: "Create your first workspace",
emptyCreateWorkspaceDescription: "Start editing your source code and building your software",
emptyResultsMessage: "No results matched your search",
filterName: "Filters",
yourWorkspacesButton: "Your workspaces",
allWorkspacesButton: "All workspaces",
workspaceTooltipTitle: "What is a workspace?",
@@ -154,12 +145,6 @@ const WorkspaceRow: React.FC<{ workspaceRef: WorkspaceItemMachineRef }> = ({ wor
)
}
interface FilterFormValues {
query: string
}
export type FilterFormErrors = FormikErrors<FilterFormValues>
export interface WorkspacesPageViewProps {
loading?: boolean
workspaceRefs?: WorkspaceItemMachineRef[]
@@ -168,41 +153,10 @@ export interface WorkspacesPageViewProps {
}
export const WorkspacesPageView: FC<WorkspacesPageViewProps> = ({ loading, workspaceRefs, filter, onFilter }) => {
const styles = useStyles()
const form = useFormik<FilterFormValues>({
enableReinitialize: true,
initialValues: {
query: filter ?? "",
},
onSubmit: ({ query }) => {
onFilter(query)
},
})
const getFieldHelpers = getFormHelpers<FilterFormValues>(form)
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setAnchorEl(null)
}
const setYourWorkspaces = () => {
void form.setFieldValue("query", "owner:me")
void form.submitForm()
handleClose()
}
const setAllWorkspaces = () => {
void form.setFieldValue("query", "")
void form.submitForm()
handleClose()
}
const presetFilters = [
{ query: workspaceFilterQuery.me, name: Language.yourWorkspacesButton },
{ query: workspaceFilterQuery.all, name: Language.allWorkspacesButton },
]
return (
<Margins>
@@ -223,48 +177,7 @@ export const WorkspacesPageView: FC<WorkspacesPageViewProps> = ({ loading, works
</PageHeaderSubtitle>
</PageHeader>
<Stack direction="row" spacing={0} className={styles.filterContainer}>
<Button aria-controls="filter-menu" aria-haspopup="true" onClick={handleClick} className={styles.buttonRoot}>
{Language.filterName} {anchorEl ? <CloseDropdown /> : <OpenDropdown />}
</Button>
<form onSubmit={form.handleSubmit} className={styles.filterForm}>
<TextField
{...getFieldHelpers("query")}
className={styles.textFieldRoot}
onChange={onChangeTrimmed(form)}
fullWidth
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon fontSize="small" />
</InputAdornment>
),
}}
/>
</form>
<Menu
id="filter-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
TransitionComponent={Fade}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
transformOrigin={{
vertical: "top",
horizontal: "left",
}}
>
<MenuItem onClick={setYourWorkspaces}>{Language.yourWorkspacesButton}</MenuItem>
<MenuItem onClick={setAllWorkspaces}>{Language.allWorkspacesButton}</MenuItem>
</Menu>
</Stack>
<SearchBarWithFilter filter={filter} onFilter={onFilter} presetFilters={presetFilters} />
<Table>
<TableHead>
@@ -328,25 +241,6 @@ const useStyles = makeStyles((theme) => ({
lineHeight: `${theme.spacing(3)}px`,
},
},
filterContainer: {
border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadius,
marginBottom: theme.spacing(2),
},
filterForm: {
width: "100%",
},
buttonRoot: {
border: "none",
borderRight: `1px solid ${theme.palette.divider}`,
borderRadius: `${theme.shape.borderRadius}px 0px 0px ${theme.shape.borderRadius}px`,
},
textFieldRoot: {
margin: "0px",
"& fieldset": {
border: "none",
},
},
clickableTableRow: {
"&:hover td": {
backgroundColor: fade(theme.palette.primary.light, 0.1),