diff --git a/site/src/components/Filter/filter.tsx b/site/src/components/Filter/filter.tsx index 44e2101a1635e..8335408c11733 100644 --- a/site/src/components/Filter/filter.tsx +++ b/site/src/components/Filter/filter.tsx @@ -34,7 +34,7 @@ import { SearchEmpty, SearchInput, searchStyles, -} from "components/Menu/Search"; +} from "components/Search/Search"; import { useDebouncedFunction } from "hooks/debounce"; import type { useFilterMenu } from "./menu"; import type { BaseOption } from "./options"; @@ -612,7 +612,7 @@ function SearchMenu({ { onQueryChange(e.target.value); }} diff --git a/site/src/components/Menu/Search.tsx b/site/src/components/Menu/Search.tsx deleted file mode 100644 index 19c3ea97330d8..0000000000000 --- a/site/src/components/Menu/Search.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { type Interpolation, type Theme, useTheme } from "@emotion/react"; -import SearchOutlined from "@mui/icons-material/SearchOutlined"; -// eslint-disable-next-line no-restricted-imports -- use it to have the component prop -import Box, { type BoxProps } from "@mui/material/Box"; -import visuallyHidden from "@mui/utils/visuallyHidden"; -import { - type FC, - type HTMLAttributes, - type InputHTMLAttributes, - forwardRef, -} from "react"; - -export const Search = forwardRef( - ({ children, ...boxProps }, ref) => { - const theme = useTheme(); - - return ( - - - {children} - - ); - }, -); - -type SearchInputProps = InputHTMLAttributes & { - label?: string; -}; - -export const SearchInput = forwardRef( - ({ label, ...inputProps }, ref) => { - const theme = useTheme(); - - return ( - <> - - - - ); - }, -); - -export const SearchEmpty: FC> = ({ - children = "Not found", - ...props -}) => { - const theme = useTheme(); - - return ( -
- {children} -
- ); -}; - -export const searchStyles = { - content: { - width: 320, - padding: 0, - borderRadius: 4, - }, -} satisfies Record>; diff --git a/site/src/components/Search/Search.stories.tsx b/site/src/components/Search/Search.stories.tsx new file mode 100644 index 0000000000000..dd2d866a83638 --- /dev/null +++ b/site/src/components/Search/Search.stories.tsx @@ -0,0 +1,26 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { Search, SearchInput } from "./Search"; + +const meta: Meta = { + title: "components/Search", + component: SearchInput, + decorators: [ + (Story) => ( + + + + ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const Example: Story = {}; + +export const WithPlaceholder: Story = { + args: { + label: "uwu", + placeholder: "uwu", + }, +}; diff --git a/site/src/components/Search/Search.tsx b/site/src/components/Search/Search.tsx new file mode 100644 index 0000000000000..88305d5d87486 --- /dev/null +++ b/site/src/components/Search/Search.tsx @@ -0,0 +1,117 @@ +import type { Interpolation, Theme } from "@emotion/react"; +import SearchOutlined from "@mui/icons-material/SearchOutlined"; +// eslint-disable-next-line no-restricted-imports -- use it to have the component prop +import Box, { type BoxProps } from "@mui/material/Box"; +import visuallyHidden from "@mui/utils/visuallyHidden"; +import type { FC, HTMLAttributes, InputHTMLAttributes, Ref } from "react"; + +interface SearchProps extends Omit { + $$ref?: Ref; +} + +/** + * A container component meant for `SearchInput` + * + * ``` + * + * + * + * ``` + */ +export const Search: FC = ({ children, $$ref, ...boxProps }) => { + return ( + + + {children} + + ); +}; + +const SearchStyles = { + container: (theme) => ({ + display: "flex", + alignItems: "center", + paddingLeft: 16, + height: 40, + borderBottom: `1px solid ${theme.palette.divider}`, + }), + + icon: (theme) => ({ + fontSize: 14, + color: theme.palette.text.secondary, + }), +} satisfies Record>; + +type SearchInputProps = InputHTMLAttributes & { + label?: string; + $$ref?: Ref; +}; + +export const SearchInput: FC = ({ + label, + $$ref, + ...inputProps +}) => { + return ( + <> + + + + ); +}; + +const SearchInputStyles = { + input: (theme) => ({ + color: "inherit", + height: "100%", + border: 0, + background: "none", + flex: 1, + marginLeft: 16, + outline: 0, + "&::placeholder": { + color: theme.palette.text.secondary, + }, + }), +} satisfies Record>; + +export const SearchEmpty: FC> = ({ + children = "Not found", + ...props +}) => { + return ( +
+ {children} +
+ ); +}; + +const SearchEmptyStyles = { + empty: (theme) => ({ + fontSize: 13, + color: theme.palette.text.secondary, + textAlign: "center", + paddingTop: 8, + paddingBottom: 8, + }), +} satisfies Record>; + +/** + * Reusable styles for consumers of the base components + */ +export const searchStyles = { + content: { + width: 320, + padding: 0, + borderRadius: 4, + }, +} satisfies Record>; diff --git a/site/src/pages/WorkspacesPage/WorkspacesButton.tsx b/site/src/pages/WorkspacesPage/WorkspacesButton.tsx index b8e03a2582886..aef8f7518331a 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesButton.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesButton.tsx @@ -11,13 +11,13 @@ import { import type { Template } from "api/typesGenerated"; import { Avatar } from "components/Avatar/Avatar"; import { Loader } from "components/Loader/Loader"; -import { SearchEmpty, searchStyles } from "components/Menu/Search"; import { OverflowY } from "components/OverflowY/OverflowY"; import { Popover, PopoverContent, PopoverTrigger, } from "components/Popover/Popover"; +import { SearchEmpty, searchStyles } from "components/Search/Search"; import { SearchBox } from "./WorkspacesSearchBox"; const ICON_SIZE = 18; diff --git a/site/src/pages/WorkspacesPage/WorkspacesSearchBox.tsx b/site/src/pages/WorkspacesPage/WorkspacesSearchBox.tsx index 09274b743d58a..d92d27c6ee238 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesSearchBox.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesSearchBox.tsx @@ -5,33 +5,30 @@ * reusable this is outside of workspace dropdowns. */ import { - type ForwardedRef, + type FC, type KeyboardEvent, type InputHTMLAttributes, - forwardRef, + type Ref, useId, } from "react"; -import { Search, SearchInput } from "components/Menu/Search"; +import { Search, SearchInput } from "components/Search/Search"; interface SearchBoxProps extends InputHTMLAttributes { label?: string; value: string; onKeyDown?: (event: KeyboardEvent) => void; onValueChange: (newValue: string) => void; + $$ref?: Ref; } -export const SearchBox = forwardRef(function SearchBox( - props: SearchBoxProps, - ref?: ForwardedRef, -) { - const { - onValueChange, - onKeyDown, - label = "Search", - placeholder = "Search...", - ...attrs - } = props; - +export const SearchBox: FC = ({ + onValueChange, + onKeyDown, + label = "Search", + placeholder = "Search...", + $$ref, + ...attrs +}) => { const hookId = useId(); const inputId = `${hookId}-${SearchBox.name}-input`; @@ -39,7 +36,7 @@ export const SearchBox = forwardRef(function SearchBox( ); -}); +};