chore: add stories to `Search` (#12457)

This commit is contained in:
Kayla Washburn-Love 2024-03-11 12:16:31 -06:00 committed by GitHub
parent 83af8674e8
commit a546cb8b32
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 160 additions and 125 deletions

View File

@ -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<TOption extends BaseOption>({
<SearchInput
autoFocus
value={query}
ref={searchInputRef}
$$ref={searchInputRef}
onChange={(e) => {
onQueryChange(e.target.value);
}}

View File

@ -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<HTMLElement, BoxProps>(
({ children, ...boxProps }, ref) => {
const theme = useTheme();
return (
<Box
ref={ref}
{...boxProps}
css={{
display: "flex",
alignItems: "center",
paddingLeft: 16,
height: 40,
borderBottom: `1px solid ${theme.palette.divider}`,
}}
>
<SearchOutlined
css={{
fontSize: 14,
color: theme.palette.text.secondary,
}}
/>
{children}
</Box>
);
},
);
type SearchInputProps = InputHTMLAttributes<HTMLInputElement> & {
label?: string;
};
export const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(
({ label, ...inputProps }, ref) => {
const theme = useTheme();
return (
<>
<label css={{ ...visuallyHidden }} htmlFor={inputProps.id}>
{label}
</label>
<input
ref={ref}
tabIndex={-1}
type="text"
placeholder="Search..."
css={{
height: "100%",
border: 0,
background: "none",
flex: 1,
marginLeft: 16,
outline: 0,
"&::placeholder": {
color: theme.palette.text.secondary,
},
}}
{...inputProps}
/>
</>
);
},
);
export const SearchEmpty: FC<HTMLAttributes<HTMLDivElement>> = ({
children = "Not found",
...props
}) => {
const theme = useTheme();
return (
<div
css={{
fontSize: 13,
color: theme.palette.text.secondary,
textAlign: "center",
paddingTop: 8,
paddingBottom: 8,
}}
{...props}
>
{children}
</div>
);
};
export const searchStyles = {
content: {
width: 320,
padding: 0,
borderRadius: 4,
},
} satisfies Record<string, Interpolation<Theme>>;

View File

@ -0,0 +1,26 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Search, SearchInput } from "./Search";
const meta: Meta<typeof SearchInput> = {
title: "components/Search",
component: SearchInput,
decorators: [
(Story) => (
<Search>
<Story />
</Search>
),
],
};
export default meta;
type Story = StoryObj<typeof SearchInput>;
export const Example: Story = {};
export const WithPlaceholder: Story = {
args: {
label: "uwu",
placeholder: "uwu",
},
};

View File

@ -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<BoxProps, "ref"> {
$$ref?: Ref<unknown>;
}
/**
* A container component meant for `SearchInput`
*
* ```
* <Search>
* <SearchInput />
* </Search>
* ```
*/
export const Search: FC<SearchProps> = ({ children, $$ref, ...boxProps }) => {
return (
<Box ref={$$ref} {...boxProps} css={SearchStyles.container}>
<SearchOutlined css={SearchStyles.icon} />
{children}
</Box>
);
};
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<string, Interpolation<Theme>>;
type SearchInputProps = InputHTMLAttributes<HTMLInputElement> & {
label?: string;
$$ref?: Ref<HTMLInputElement>;
};
export const SearchInput: FC<SearchInputProps> = ({
label,
$$ref,
...inputProps
}) => {
return (
<>
<label css={{ ...visuallyHidden }} htmlFor={inputProps.id}>
{label}
</label>
<input
ref={$$ref}
tabIndex={-1}
type="text"
placeholder="Search..."
css={SearchInputStyles.input}
{...inputProps}
/>
</>
);
};
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<string, Interpolation<Theme>>;
export const SearchEmpty: FC<HTMLAttributes<HTMLDivElement>> = ({
children = "Not found",
...props
}) => {
return (
<div css={SearchEmptyStyles.empty} {...props}>
{children}
</div>
);
};
const SearchEmptyStyles = {
empty: (theme) => ({
fontSize: 13,
color: theme.palette.text.secondary,
textAlign: "center",
paddingTop: 8,
paddingBottom: 8,
}),
} satisfies Record<string, Interpolation<Theme>>;
/**
* Reusable styles for consumers of the base components
*/
export const searchStyles = {
content: {
width: 320,
padding: 0,
borderRadius: 4,
},
} satisfies Record<string, Interpolation<Theme>>;

View File

@ -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;

View File

@ -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<HTMLInputElement> {
label?: string;
value: string;
onKeyDown?: (event: KeyboardEvent) => void;
onValueChange: (newValue: string) => void;
$$ref?: Ref<HTMLInputElement>;
}
export const SearchBox = forwardRef(function SearchBox(
props: SearchBoxProps,
ref?: ForwardedRef<HTMLInputElement>,
) {
const {
onValueChange,
onKeyDown,
label = "Search",
placeholder = "Search...",
...attrs
} = props;
export const SearchBox: FC<SearchBoxProps> = ({
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(
<Search>
<SearchInput
label={label}
ref={ref}
$$ref={$$ref}
id={inputId}
autoFocus
tabIndex={0}
@ -50,4 +47,4 @@ export const SearchBox = forwardRef(function SearchBox(
/>
</Search>
);
});
};