diff --git a/site/src/components/Filter/filter.tsx b/site/src/components/Filter/filter.tsx index 8335408c11733..91d8d78ee1cf4 100644 --- a/site/src/components/Filter/filter.tsx +++ b/site/src/components/Filter/filter.tsx @@ -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 = ({ }, [filter.query]); const shouldDisplayError = hasError(error) && isApiValidationError(error); - const hasFilterQuery = filter.query !== ""; return (
= ({ learnMoreLabel2={learnMoreLabel2} learnMoreLink2={learnMoreLink2} /> - = ({ ? 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 = ({ "&:hover": { zIndex: 2, }, - "& input::placeholder": { - color: theme.palette.text.secondary, - }, - "& .MuiInputAdornment-root": { - marginLeft: 0, - }, "&.Mui-error": { zIndex: 3, }, }, - startAdornment: ( - - - - ), - endAdornment: hasFilterQuery && ( - - - { - filter.update(""); - }} - > - - - - - ), }} />
diff --git a/site/src/components/SearchField/SearchField.stories.tsx b/site/src/components/SearchField/SearchField.stories.tsx new file mode 100644 index 0000000000000..254de5fe637eb --- /dev/null +++ b/site/src/components/SearchField/SearchField.stories.tsx @@ -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 = { + title: "components/SearchField", + component: SearchField, + args: { + placeholder: "Search...", + }, + render: function StatefulWrapper(args) { + const [value, setValue] = useState(args.value); + return ; + }, +}; + +export default meta; +type Story = StoryObj; + +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" })); + }, +}; diff --git a/site/src/components/SearchField/SearchField.tsx b/site/src/components/SearchField/SearchField.tsx new file mode 100644 index 0000000000000..9e81b74e972ac --- /dev/null +++ b/site/src/components/SearchField/SearchField.tsx @@ -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 & { + onChange: (query: string) => void; +}; + +export const SearchField: FC = ({ + value = "", + onChange, + InputProps, + ...textFieldProps +}) => { + const theme = useTheme(); + return ( + onChange(e.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + endAdornment: value !== "" && ( + + + { + onChange(""); + }} + > + + Clear field + + + + ), + ...InputProps, + }} + {...textFieldProps} + /> + ); +}; 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