refactor(site): refactor filter search field (#13545)

This commit is contained in:
Bruno Quaresma
2024-06-12 10:22:20 -03:00
committed by GitHub
parent a11f8b003b
commit 0c627a4cb9
3 changed files with 112 additions and 47 deletions
+9 -47
View File
@@ -1,19 +1,13 @@
import { useTheme } from "@emotion/react";
import CheckOutlined from "@mui/icons-material/CheckOutlined";
import CloseOutlined from "@mui/icons-material/CloseOutlined";
import KeyboardArrowDown from "@mui/icons-material/KeyboardArrowDown";
import OpenInNewOutlined from "@mui/icons-material/OpenInNewOutlined";
import SearchOutlined from "@mui/icons-material/SearchOutlined";
import Button, { type ButtonProps } from "@mui/material/Button";
import Divider from "@mui/material/Divider";
import IconButton from "@mui/material/IconButton";
import InputAdornment from "@mui/material/InputAdornment";
import Menu, { type MenuProps } from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import MenuList from "@mui/material/MenuList";
import Skeleton, { type SkeletonProps } from "@mui/material/Skeleton";
import TextField from "@mui/material/TextField";
import Tooltip from "@mui/material/Tooltip";
import {
type FC,
type ReactNode,
@@ -35,6 +29,7 @@ import {
SearchInput,
searchStyles,
} from "components/Search/Search";
import { SearchField } from "components/SearchField/SearchField";
import { useDebouncedFunction } from "hooks/debounce";
import type { useFilterMenu } from "./menu";
import type { BaseOption } from "./options";
@@ -199,7 +194,6 @@ export const Filter: FC<FilterProps> = ({
}, [filter.query]);
const shouldDisplayError = hasError(error) && isApiValidationError(error);
const hasFilterQuery = filter.query !== "";
return (
<div
@@ -226,7 +220,7 @@ export const Filter: FC<FilterProps> = ({
learnMoreLabel2={learnMoreLabel2}
learnMoreLink2={learnMoreLink2}
/>
<TextField
<SearchField
fullWidth
error={shouldDisplayError}
helperText={
@@ -234,17 +228,15 @@ export const Filter: FC<FilterProps> = ({
? getValidationErrorMessage(error)
: undefined
}
size="small"
placeholder="Search..."
value={queryCopy}
onChange={(query) => {
setQueryCopy(query);
filter.debounceUpdate(query);
}}
InputProps={{
"aria-label": "Filter",
name: "query",
placeholder: "Search...",
value: queryCopy,
ref: textboxInputRef,
onChange: (e) => {
setQueryCopy(e.target.value);
filter.debounceUpdate(e.target.value);
},
"aria-label": "Filter",
onBlur: () => {
if (queryCopy !== filter.query) {
setQueryCopy(filter.query);
@@ -258,40 +250,10 @@ export const Filter: FC<FilterProps> = ({
"&:hover": {
zIndex: 2,
},
"& input::placeholder": {
color: theme.palette.text.secondary,
},
"& .MuiInputAdornment-root": {
marginLeft: 0,
},
"&.Mui-error": {
zIndex: 3,
},
},
startAdornment: (
<InputAdornment position="start">
<SearchOutlined
css={{
fontSize: 14,
color: theme.palette.text.secondary,
}}
/>
</InputAdornment>
),
endAdornment: hasFilterQuery && (
<InputAdornment position="end">
<Tooltip title="Clear filter">
<IconButton
size="small"
onClick={() => {
filter.update("");
}}
>
<CloseOutlined css={{ fontSize: 14 }} />
</IconButton>
</Tooltip>
</InputAdornment>
),
}}
/>
</div>
@@ -0,0 +1,45 @@
import type { Meta, StoryObj } from "@storybook/react";
import { userEvent, within } from "@storybook/test";
import { useState } from "react";
import { SearchField } from "./SearchField";
const meta: Meta<typeof SearchField> = {
title: "components/SearchField",
component: SearchField,
args: {
placeholder: "Search...",
},
render: function StatefulWrapper(args) {
const [value, setValue] = useState(args.value);
return <SearchField {...args} value={value} onChange={setValue} />;
},
};
export default meta;
type Story = StoryObj<typeof SearchField>;
export const Empty: Story = {};
export const DefaultValue: Story = {
args: {
value: "owner:me",
},
};
export const TypeValue: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input = canvas.getByRole("textbox");
await userEvent.type(input, "owner:me");
},
};
export const ClearValue: Story = {
args: {
value: "owner:me",
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(canvas.getByRole("button", { name: "Clear field" }));
},
};
@@ -0,0 +1,58 @@
import { useTheme } from "@emotion/react";
import CloseIcon from "@mui/icons-material/CloseOutlined";
import SearchIcon from "@mui/icons-material/SearchOutlined";
import IconButton from "@mui/material/IconButton";
import InputAdornment from "@mui/material/InputAdornment";
import TextField, { type TextFieldProps } from "@mui/material/TextField";
import Tooltip from "@mui/material/Tooltip";
import visuallyHidden from "@mui/utils/visuallyHidden";
import type { FC } from "react";
export type SearchFieldProps = Omit<TextFieldProps, "onChange"> & {
onChange: (query: string) => void;
};
export const SearchField: FC<SearchFieldProps> = ({
value = "",
onChange,
InputProps,
...textFieldProps
}) => {
const theme = useTheme();
return (
<TextField
size="small"
value={value}
onChange={(e) => onChange(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon
css={{
fontSize: 14,
color: theme.palette.text.secondary,
}}
/>
</InputAdornment>
),
endAdornment: value !== "" && (
<InputAdornment position="end">
<Tooltip title="Clear field">
<IconButton
size="small"
onClick={() => {
onChange("");
}}
>
<CloseIcon css={{ fontSize: 14 }} />
<span css={{ ...visuallyHidden }}>Clear field</span>
</IconButton>
</Tooltip>
</InputAdornment>
),
...InputProps,
}}
{...textFieldProps}
/>
);
};