Skip to content

Commit 0c627a4

Browse files
refactor(site): refactor filter search field (#13545)
1 parent a11f8b0 commit 0c627a4

File tree

3 files changed

+112
-47
lines changed

3 files changed

+112
-47
lines changed

site/src/components/Filter/filter.tsx

Lines changed: 9 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
import { useTheme } from "@emotion/react";
22
import CheckOutlined from "@mui/icons-material/CheckOutlined";
3-
import CloseOutlined from "@mui/icons-material/CloseOutlined";
43
import KeyboardArrowDown from "@mui/icons-material/KeyboardArrowDown";
54
import OpenInNewOutlined from "@mui/icons-material/OpenInNewOutlined";
6-
import SearchOutlined from "@mui/icons-material/SearchOutlined";
75
import Button, { type ButtonProps } from "@mui/material/Button";
86
import Divider from "@mui/material/Divider";
9-
import IconButton from "@mui/material/IconButton";
10-
import InputAdornment from "@mui/material/InputAdornment";
117
import Menu, { type MenuProps } from "@mui/material/Menu";
128
import MenuItem from "@mui/material/MenuItem";
139
import MenuList from "@mui/material/MenuList";
1410
import Skeleton, { type SkeletonProps } from "@mui/material/Skeleton";
15-
import TextField from "@mui/material/TextField";
16-
import Tooltip from "@mui/material/Tooltip";
1711
import {
1812
type FC,
1913
type ReactNode,
@@ -35,6 +29,7 @@ import {
3529
SearchInput,
3630
searchStyles,
3731
} from "components/Search/Search";
32+
import { SearchField } from "components/SearchField/SearchField";
3833
import { useDebouncedFunction } from "hooks/debounce";
3934
import type { useFilterMenu } from "./menu";
4035
import type { BaseOption } from "./options";
@@ -199,7 +194,6 @@ export const Filter: FC<FilterProps> = ({
199194
}, [filter.query]);
200195

201196
const shouldDisplayError = hasError(error) && isApiValidationError(error);
202-
const hasFilterQuery = filter.query !== "";
203197

204198
return (
205199
<div
@@ -226,25 +220,23 @@ export const Filter: FC<FilterProps> = ({
226220
learnMoreLabel2={learnMoreLabel2}
227221
learnMoreLink2={learnMoreLink2}
228222
/>
229-
<TextField
223+
<SearchField
230224
fullWidth
231225
error={shouldDisplayError}
232226
helperText={
233227
shouldDisplayError
234228
? getValidationErrorMessage(error)
235229
: undefined
236230
}
237-
size="small"
231+
placeholder="Search..."
232+
value={queryCopy}
233+
onChange={(query) => {
234+
setQueryCopy(query);
235+
filter.debounceUpdate(query);
236+
}}
238237
InputProps={{
239-
"aria-label": "Filter",
240-
name: "query",
241-
placeholder: "Search...",
242-
value: queryCopy,
243238
ref: textboxInputRef,
244-
onChange: (e) => {
245-
setQueryCopy(e.target.value);
246-
filter.debounceUpdate(e.target.value);
247-
},
239+
"aria-label": "Filter",
248240
onBlur: () => {
249241
if (queryCopy !== filter.query) {
250242
setQueryCopy(filter.query);
@@ -258,40 +250,10 @@ export const Filter: FC<FilterProps> = ({
258250
"&:hover": {
259251
zIndex: 2,
260252
},
261-
"& input::placeholder": {
262-
color: theme.palette.text.secondary,
263-
},
264-
"& .MuiInputAdornment-root": {
265-
marginLeft: 0,
266-
},
267253
"&.Mui-error": {
268254
zIndex: 3,
269255
},
270256
},
271-
startAdornment: (
272-
<InputAdornment position="start">
273-
<SearchOutlined
274-
css={{
275-
fontSize: 14,
276-
color: theme.palette.text.secondary,
277-
}}
278-
/>
279-
</InputAdornment>
280-
),
281-
endAdornment: hasFilterQuery && (
282-
<InputAdornment position="end">
283-
<Tooltip title="Clear filter">
284-
<IconButton
285-
size="small"
286-
onClick={() => {
287-
filter.update("");
288-
}}
289-
>
290-
<CloseOutlined css={{ fontSize: 14 }} />
291-
</IconButton>
292-
</Tooltip>
293-
</InputAdornment>
294-
),
295257
}}
296258
/>
297259
</div>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { userEvent, within } from "@storybook/test";
3+
import { useState } from "react";
4+
import { SearchField } from "./SearchField";
5+
6+
const meta: Meta<typeof SearchField> = {
7+
title: "components/SearchField",
8+
component: SearchField,
9+
args: {
10+
placeholder: "Search...",
11+
},
12+
render: function StatefulWrapper(args) {
13+
const [value, setValue] = useState(args.value);
14+
return <SearchField {...args} value={value} onChange={setValue} />;
15+
},
16+
};
17+
18+
export default meta;
19+
type Story = StoryObj<typeof SearchField>;
20+
21+
export const Empty: Story = {};
22+
23+
export const DefaultValue: Story = {
24+
args: {
25+
value: "owner:me",
26+
},
27+
};
28+
29+
export const TypeValue: Story = {
30+
play: async ({ canvasElement }) => {
31+
const canvas = within(canvasElement);
32+
const input = canvas.getByRole("textbox");
33+
await userEvent.type(input, "owner:me");
34+
},
35+
};
36+
37+
export const ClearValue: Story = {
38+
args: {
39+
value: "owner:me",
40+
},
41+
play: async ({ canvasElement }) => {
42+
const canvas = within(canvasElement);
43+
await userEvent.click(canvas.getByRole("button", { name: "Clear field" }));
44+
},
45+
};
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { useTheme } from "@emotion/react";
2+
import CloseIcon from "@mui/icons-material/CloseOutlined";
3+
import SearchIcon from "@mui/icons-material/SearchOutlined";
4+
import IconButton from "@mui/material/IconButton";
5+
import InputAdornment from "@mui/material/InputAdornment";
6+
import TextField, { type TextFieldProps } from "@mui/material/TextField";
7+
import Tooltip from "@mui/material/Tooltip";
8+
import visuallyHidden from "@mui/utils/visuallyHidden";
9+
import type { FC } from "react";
10+
11+
export type SearchFieldProps = Omit<TextFieldProps, "onChange"> & {
12+
onChange: (query: string) => void;
13+
};
14+
15+
export const SearchField: FC<SearchFieldProps> = ({
16+
value = "",
17+
onChange,
18+
InputProps,
19+
...textFieldProps
20+
}) => {
21+
const theme = useTheme();
22+
return (
23+
<TextField
24+
size="small"
25+
value={value}
26+
onChange={(e) => onChange(e.target.value)}
27+
InputProps={{
28+
startAdornment: (
29+
<InputAdornment position="start">
30+
<SearchIcon
31+
css={{
32+
fontSize: 14,
33+
color: theme.palette.text.secondary,
34+
}}
35+
/>
36+
</InputAdornment>
37+
),
38+
endAdornment: value !== "" && (
39+
<InputAdornment position="end">
40+
<Tooltip title="Clear field">
41+
<IconButton
42+
size="small"
43+
onClick={() => {
44+
onChange("");
45+
}}
46+
>
47+
<CloseIcon css={{ fontSize: 14 }} />
48+
<span css={{ ...visuallyHidden }}>Clear field</span>
49+
</IconButton>
50+
</Tooltip>
51+
</InputAdornment>
52+
),
53+
...InputProps,
54+
}}
55+
{...textFieldProps}
56+
/>
57+
);
58+
};

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