mirror of https://github.com/coder/coder.git
chore: add stories to `Search` (#12457)
This commit is contained in:
parent
83af8674e8
commit
a546cb8b32
|
@ -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);
|
||||
}}
|
||||
|
|
|
@ -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>>;
|
|
@ -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",
|
||||
},
|
||||
};
|
|
@ -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>>;
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue