Skip to content

Commit 9c8079b

Browse files
authored
refactor: Extract workspace filter into a separate component (coder#2601)
1 parent 929227d commit 9c8079b

File tree

3 files changed

+172
-113
lines changed

3 files changed

+172
-113
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { ComponentMeta, Story } from "@storybook/react"
2+
import { workspaceFilterQuery } from "../../util/workspace"
3+
import { SearchBarWithFilter, SearchBarWithFilterProps } from "./SearchBarWithFilter"
4+
5+
export default {
6+
title: "components/SearchBarWithFilter",
7+
component: SearchBarWithFilter,
8+
argTypes: {
9+
filter: {
10+
defaultValue: workspaceFilterQuery.me,
11+
},
12+
},
13+
} as ComponentMeta<typeof SearchBarWithFilter>
14+
15+
const Template: Story<SearchBarWithFilterProps> = (args) => <SearchBarWithFilter {...args} />
16+
17+
export const WithoutPresetFilters = Template.bind({})
18+
19+
export const WithPresetFilters = Template.bind({})
20+
WithPresetFilters.args = {
21+
presetFilters: [
22+
{ query: workspaceFilterQuery.me, name: "Your workspaces" },
23+
{ query: "random query", name: "Random query" },
24+
],
25+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import Button from "@material-ui/core/Button"
2+
import Fade from "@material-ui/core/Fade"
3+
import InputAdornment from "@material-ui/core/InputAdornment"
4+
import Menu from "@material-ui/core/Menu"
5+
import MenuItem from "@material-ui/core/MenuItem"
6+
import { makeStyles } from "@material-ui/core/styles"
7+
import TextField from "@material-ui/core/TextField"
8+
import SearchIcon from "@material-ui/icons/Search"
9+
import { FormikErrors, useFormik } from "formik"
10+
import { useState } from "react"
11+
import { getFormHelpers, onChangeTrimmed } from "../../util/formUtils"
12+
import { CloseDropdown, OpenDropdown } from "../DropdownArrows/DropdownArrows"
13+
import { Stack } from "../Stack/Stack"
14+
15+
export const Language = {
16+
filterName: "Filters",
17+
}
18+
19+
export interface SearchBarWithFilterProps {
20+
filter?: string
21+
onFilter: (query: string) => void
22+
presetFilters?: PresetFilter[]
23+
}
24+
25+
export interface PresetFilter {
26+
name: string
27+
query: string
28+
}
29+
30+
interface FilterFormValues {
31+
query: string
32+
}
33+
34+
export type FilterFormErrors = FormikErrors<FilterFormValues>
35+
36+
export const SearchBarWithFilter: React.FC<SearchBarWithFilterProps> = ({ filter, onFilter, presetFilters }) => {
37+
const styles = useStyles()
38+
39+
const form = useFormik<FilterFormValues>({
40+
enableReinitialize: true,
41+
initialValues: {
42+
query: filter ?? "",
43+
},
44+
onSubmit: ({ query }) => {
45+
onFilter(query)
46+
},
47+
})
48+
49+
const getFieldHelpers = getFormHelpers<FilterFormValues>(form)
50+
51+
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
52+
53+
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
54+
setAnchorEl(event.currentTarget)
55+
}
56+
57+
const handleClose = () => {
58+
setAnchorEl(null)
59+
}
60+
61+
const setPresetFilter = (query: string) => () => {
62+
void form.setFieldValue("query", query)
63+
void form.submitForm()
64+
handleClose()
65+
}
66+
67+
return (
68+
<Stack direction="row" spacing={0} className={styles.filterContainer}>
69+
{presetFilters && presetFilters.length > 0 && (
70+
<Button aria-controls="filter-menu" aria-haspopup="true" onClick={handleClick} className={styles.buttonRoot}>
71+
{Language.filterName} {anchorEl ? <CloseDropdown /> : <OpenDropdown />}
72+
</Button>
73+
)}
74+
75+
<form onSubmit={form.handleSubmit} className={styles.filterForm}>
76+
<TextField
77+
{...getFieldHelpers("query")}
78+
className={styles.textFieldRoot}
79+
onChange={onChangeTrimmed(form)}
80+
fullWidth
81+
variant="outlined"
82+
InputProps={{
83+
startAdornment: (
84+
<InputAdornment position="start">
85+
<SearchIcon fontSize="small" />
86+
</InputAdornment>
87+
),
88+
}}
89+
/>
90+
</form>
91+
92+
{presetFilters && presetFilters.length > 0 && (
93+
<Menu
94+
id="filter-menu"
95+
anchorEl={anchorEl}
96+
keepMounted
97+
open={Boolean(anchorEl)}
98+
onClose={handleClose}
99+
TransitionComponent={Fade}
100+
anchorOrigin={{
101+
vertical: "bottom",
102+
horizontal: "left",
103+
}}
104+
transformOrigin={{
105+
vertical: "top",
106+
horizontal: "left",
107+
}}
108+
>
109+
{presetFilters.map((presetFilter) => (
110+
<MenuItem key={presetFilter.name} onClick={setPresetFilter(presetFilter.query)}>
111+
{presetFilter.name}
112+
</MenuItem>
113+
))}
114+
</Menu>
115+
)}
116+
</Stack>
117+
)
118+
}
119+
120+
const useStyles = makeStyles((theme) => ({
121+
filterContainer: {
122+
border: `1px solid ${theme.palette.divider}`,
123+
borderRadius: theme.shape.borderRadius,
124+
marginBottom: theme.spacing(2),
125+
},
126+
filterForm: {
127+
width: "100%",
128+
},
129+
buttonRoot: {
130+
border: "none",
131+
borderRight: `1px solid ${theme.palette.divider}`,
132+
borderRadius: `${theme.shape.borderRadius}px 0px 0px ${theme.shape.borderRadius}px`,
133+
},
134+
textFieldRoot: {
135+
margin: "0px",
136+
"& fieldset": {
137+
border: "none",
138+
},
139+
},
140+
}))

site/src/pages/WorkspacesPage/WorkspacesPageView.tsx

Lines changed: 7 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,25 @@
11
import Button from "@material-ui/core/Button"
2-
import Fade from "@material-ui/core/Fade"
3-
import InputAdornment from "@material-ui/core/InputAdornment"
42
import Link from "@material-ui/core/Link"
5-
import Menu from "@material-ui/core/Menu"
6-
import MenuItem from "@material-ui/core/MenuItem"
73
import { fade, makeStyles, Theme } from "@material-ui/core/styles"
84
import Table from "@material-ui/core/Table"
95
import TableBody from "@material-ui/core/TableBody"
106
import TableCell from "@material-ui/core/TableCell"
117
import TableHead from "@material-ui/core/TableHead"
128
import TableRow from "@material-ui/core/TableRow"
13-
import TextField from "@material-ui/core/TextField"
149
import AddCircleOutline from "@material-ui/icons/AddCircleOutline"
1510
import KeyboardArrowRight from "@material-ui/icons/KeyboardArrowRight"
1611
import RefreshIcon from "@material-ui/icons/Refresh"
17-
import SearchIcon from "@material-ui/icons/Search"
1812
import useTheme from "@material-ui/styles/useTheme"
1913
import { useActor } from "@xstate/react"
2014
import dayjs from "dayjs"
2115
import relativeTime from "dayjs/plugin/relativeTime"
22-
import { FormikErrors, useFormik } from "formik"
23-
import { FC, useState } from "react"
16+
import { FC } from "react"
2417
import { Link as RouterLink, useNavigate } from "react-router-dom"
2518
import { AvatarData } from "../../components/AvatarData/AvatarData"
26-
import { CloseDropdown, OpenDropdown } from "../../components/DropdownArrows/DropdownArrows"
2719
import { EmptyState } from "../../components/EmptyState/EmptyState"
2820
import { Margins } from "../../components/Margins/Margins"
2921
import { PageHeader, PageHeaderSubtitle, PageHeaderTitle } from "../../components/PageHeader/PageHeader"
22+
import { SearchBarWithFilter } from "../../components/SearchBarWithFilter/SearchBarWithFilter"
3023
import { Stack } from "../../components/Stack/Stack"
3124
import { TableCellLink } from "../../components/TableCellLink/TableCellLink"
3225
import { TableLoader } from "../../components/TableLoader/TableLoader"
@@ -38,7 +31,6 @@ import {
3831
HelpTooltipText,
3932
HelpTooltipTitle,
4033
} from "../../components/Tooltips/HelpTooltip/HelpTooltip"
41-
import { getFormHelpers, onChangeTrimmed } from "../../util/formUtils"
4234
import { getDisplayStatus, workspaceFilterQuery } from "../../util/workspace"
4335
import { WorkspaceItemMachineRef } from "../../xServices/workspaces/workspacesXService"
4436

@@ -49,7 +41,6 @@ export const Language = {
4941
emptyCreateWorkspaceMessage: "Create your first workspace",
5042
emptyCreateWorkspaceDescription: "Start editing your source code and building your software",
5143
emptyResultsMessage: "No results matched your search",
52-
filterName: "Filters",
5344
yourWorkspacesButton: "Your workspaces",
5445
allWorkspacesButton: "All workspaces",
5546
workspaceTooltipTitle: "What is a workspace?",
@@ -154,12 +145,6 @@ const WorkspaceRow: React.FC<{ workspaceRef: WorkspaceItemMachineRef }> = ({ wor
154145
)
155146
}
156147

157-
interface FilterFormValues {
158-
query: string
159-
}
160-
161-
export type FilterFormErrors = FormikErrors<FilterFormValues>
162-
163148
export interface WorkspacesPageViewProps {
164149
loading?: boolean
165150
workspaceRefs?: WorkspaceItemMachineRef[]
@@ -168,41 +153,10 @@ export interface WorkspacesPageViewProps {
168153
}
169154

170155
export const WorkspacesPageView: FC<WorkspacesPageViewProps> = ({ loading, workspaceRefs, filter, onFilter }) => {
171-
const styles = useStyles()
172-
173-
const form = useFormik<FilterFormValues>({
174-
enableReinitialize: true,
175-
initialValues: {
176-
query: filter ?? "",
177-
},
178-
onSubmit: ({ query }) => {
179-
onFilter(query)
180-
},
181-
})
182-
183-
const getFieldHelpers = getFormHelpers<FilterFormValues>(form)
184-
185-
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
186-
187-
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
188-
setAnchorEl(event.currentTarget)
189-
}
190-
191-
const handleClose = () => {
192-
setAnchorEl(null)
193-
}
194-
195-
const setYourWorkspaces = () => {
196-
void form.setFieldValue("query", "owner:me")
197-
void form.submitForm()
198-
handleClose()
199-
}
200-
201-
const setAllWorkspaces = () => {
202-
void form.setFieldValue("query", "")
203-
void form.submitForm()
204-
handleClose()
205-
}
156+
const presetFilters = [
157+
{ query: workspaceFilterQuery.me, name: Language.yourWorkspacesButton },
158+
{ query: workspaceFilterQuery.all, name: Language.allWorkspacesButton },
159+
]
206160

207161
return (
208162
<Margins>
@@ -223,48 +177,7 @@ export const WorkspacesPageView: FC<WorkspacesPageViewProps> = ({ loading, works
223177
</PageHeaderSubtitle>
224178
</PageHeader>
225179

226-
<Stack direction="row" spacing={0} className={styles.filterContainer}>
227-
<Button aria-controls="filter-menu" aria-haspopup="true" onClick={handleClick} className={styles.buttonRoot}>
228-
{Language.filterName} {anchorEl ? <CloseDropdown /> : <OpenDropdown />}
229-
</Button>
230-
231-
<form onSubmit={form.handleSubmit} className={styles.filterForm}>
232-
<TextField
233-
{...getFieldHelpers("query")}
234-
className={styles.textFieldRoot}
235-
onChange={onChangeTrimmed(form)}
236-
fullWidth
237-
variant="outlined"
238-
InputProps={{
239-
startAdornment: (
240-
<InputAdornment position="start">
241-
<SearchIcon fontSize="small" />
242-
</InputAdornment>
243-
),
244-
}}
245-
/>
246-
</form>
247-
248-
<Menu
249-
id="filter-menu"
250-
anchorEl={anchorEl}
251-
keepMounted
252-
open={Boolean(anchorEl)}
253-
onClose={handleClose}
254-
TransitionComponent={Fade}
255-
anchorOrigin={{
256-
vertical: "bottom",
257-
horizontal: "left",
258-
}}
259-
transformOrigin={{
260-
vertical: "top",
261-
horizontal: "left",
262-
}}
263-
>
264-
<MenuItem onClick={setYourWorkspaces}>{Language.yourWorkspacesButton}</MenuItem>
265-
<MenuItem onClick={setAllWorkspaces}>{Language.allWorkspacesButton}</MenuItem>
266-
</Menu>
267-
</Stack>
180+
<SearchBarWithFilter filter={filter} onFilter={onFilter} presetFilters={presetFilters} />
268181

269182
<Table>
270183
<TableHead>
@@ -328,25 +241,6 @@ const useStyles = makeStyles((theme) => ({
328241
lineHeight: `${theme.spacing(3)}px`,
329242
},
330243
},
331-
filterContainer: {
332-
border: `1px solid ${theme.palette.divider}`,
333-
borderRadius: theme.shape.borderRadius,
334-
marginBottom: theme.spacing(2),
335-
},
336-
filterForm: {
337-
width: "100%",
338-
},
339-
buttonRoot: {
340-
border: "none",
341-
borderRight: `1px solid ${theme.palette.divider}`,
342-
borderRadius: `${theme.shape.borderRadius}px 0px 0px ${theme.shape.borderRadius}px`,
343-
},
344-
textFieldRoot: {
345-
margin: "0px",
346-
"& fieldset": {
347-
border: "none",
348-
},
349-
},
350244
clickableTableRow: {
351245
"&:hover td": {
352246
backgroundColor: fade(theme.palette.primary.light, 0.1),

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy