Skip to content

Commit a546cb8

Browse files
authored
chore: add stories to Search (coder#12457)
1 parent 83af867 commit a546cb8

File tree

6 files changed

+160
-125
lines changed

6 files changed

+160
-125
lines changed

site/src/components/Filter/filter.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import {
3434
SearchEmpty,
3535
SearchInput,
3636
searchStyles,
37-
} from "components/Menu/Search";
37+
} from "components/Search/Search";
3838
import { useDebouncedFunction } from "hooks/debounce";
3939
import type { useFilterMenu } from "./menu";
4040
import type { BaseOption } from "./options";
@@ -612,7 +612,7 @@ function SearchMenu<TOption extends BaseOption>({
612612
<SearchInput
613613
autoFocus
614614
value={query}
615-
ref={searchInputRef}
615+
$$ref={searchInputRef}
616616
onChange={(e) => {
617617
onQueryChange(e.target.value);
618618
}}

site/src/components/Menu/Search.tsx

Lines changed: 0 additions & 105 deletions
This file was deleted.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { Search, SearchInput } from "./Search";
3+
4+
const meta: Meta<typeof SearchInput> = {
5+
title: "components/Search",
6+
component: SearchInput,
7+
decorators: [
8+
(Story) => (
9+
<Search>
10+
<Story />
11+
</Search>
12+
),
13+
],
14+
};
15+
16+
export default meta;
17+
type Story = StoryObj<typeof SearchInput>;
18+
19+
export const Example: Story = {};
20+
21+
export const WithPlaceholder: Story = {
22+
args: {
23+
label: "uwu",
24+
placeholder: "uwu",
25+
},
26+
};

site/src/components/Search/Search.tsx

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import type { Interpolation, Theme } from "@emotion/react";
2+
import SearchOutlined from "@mui/icons-material/SearchOutlined";
3+
// eslint-disable-next-line no-restricted-imports -- use it to have the component prop
4+
import Box, { type BoxProps } from "@mui/material/Box";
5+
import visuallyHidden from "@mui/utils/visuallyHidden";
6+
import type { FC, HTMLAttributes, InputHTMLAttributes, Ref } from "react";
7+
8+
interface SearchProps extends Omit<BoxProps, "ref"> {
9+
$$ref?: Ref<unknown>;
10+
}
11+
12+
/**
13+
* A container component meant for `SearchInput`
14+
*
15+
* ```
16+
* <Search>
17+
* <SearchInput />
18+
* </Search>
19+
* ```
20+
*/
21+
export const Search: FC<SearchProps> = ({ children, $$ref, ...boxProps }) => {
22+
return (
23+
<Box ref={$$ref} {...boxProps} css={SearchStyles.container}>
24+
<SearchOutlined css={SearchStyles.icon} />
25+
{children}
26+
</Box>
27+
);
28+
};
29+
30+
const SearchStyles = {
31+
container: (theme) => ({
32+
display: "flex",
33+
alignItems: "center",
34+
paddingLeft: 16,
35+
height: 40,
36+
borderBottom: `1px solid ${theme.palette.divider}`,
37+
}),
38+
39+
icon: (theme) => ({
40+
fontSize: 14,
41+
color: theme.palette.text.secondary,
42+
}),
43+
} satisfies Record<string, Interpolation<Theme>>;
44+
45+
type SearchInputProps = InputHTMLAttributes<HTMLInputElement> & {
46+
label?: string;
47+
$$ref?: Ref<HTMLInputElement>;
48+
};
49+
50+
export const SearchInput: FC<SearchInputProps> = ({
51+
label,
52+
$$ref,
53+
...inputProps
54+
}) => {
55+
return (
56+
<>
57+
<label css={{ ...visuallyHidden }} htmlFor={inputProps.id}>
58+
{label}
59+
</label>
60+
<input
61+
ref={$$ref}
62+
tabIndex={-1}
63+
type="text"
64+
placeholder="Search..."
65+
css={SearchInputStyles.input}
66+
{...inputProps}
67+
/>
68+
</>
69+
);
70+
};
71+
72+
const SearchInputStyles = {
73+
input: (theme) => ({
74+
color: "inherit",
75+
height: "100%",
76+
border: 0,
77+
background: "none",
78+
flex: 1,
79+
marginLeft: 16,
80+
outline: 0,
81+
"&::placeholder": {
82+
color: theme.palette.text.secondary,
83+
},
84+
}),
85+
} satisfies Record<string, Interpolation<Theme>>;
86+
87+
export const SearchEmpty: FC<HTMLAttributes<HTMLDivElement>> = ({
88+
children = "Not found",
89+
...props
90+
}) => {
91+
return (
92+
<div css={SearchEmptyStyles.empty} {...props}>
93+
{children}
94+
</div>
95+
);
96+
};
97+
98+
const SearchEmptyStyles = {
99+
empty: (theme) => ({
100+
fontSize: 13,
101+
color: theme.palette.text.secondary,
102+
textAlign: "center",
103+
paddingTop: 8,
104+
paddingBottom: 8,
105+
}),
106+
} satisfies Record<string, Interpolation<Theme>>;
107+
108+
/**
109+
* Reusable styles for consumers of the base components
110+
*/
111+
export const searchStyles = {
112+
content: {
113+
width: 320,
114+
padding: 0,
115+
borderRadius: 4,
116+
},
117+
} satisfies Record<string, Interpolation<Theme>>;

site/src/pages/WorkspacesPage/WorkspacesButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ import {
1111
import type { Template } from "api/typesGenerated";
1212
import { Avatar } from "components/Avatar/Avatar";
1313
import { Loader } from "components/Loader/Loader";
14-
import { SearchEmpty, searchStyles } from "components/Menu/Search";
1514
import { OverflowY } from "components/OverflowY/OverflowY";
1615
import {
1716
Popover,
1817
PopoverContent,
1918
PopoverTrigger,
2019
} from "components/Popover/Popover";
20+
import { SearchEmpty, searchStyles } from "components/Search/Search";
2121
import { SearchBox } from "./WorkspacesSearchBox";
2222

2323
const ICON_SIZE = 18;

site/src/pages/WorkspacesPage/WorkspacesSearchBox.tsx

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,41 +5,38 @@
55
* reusable this is outside of workspace dropdowns.
66
*/
77
import {
8-
type ForwardedRef,
8+
type FC,
99
type KeyboardEvent,
1010
type InputHTMLAttributes,
11-
forwardRef,
11+
type Ref,
1212
useId,
1313
} from "react";
14-
import { Search, SearchInput } from "components/Menu/Search";
14+
import { Search, SearchInput } from "components/Search/Search";
1515

1616
interface SearchBoxProps extends InputHTMLAttributes<HTMLInputElement> {
1717
label?: string;
1818
value: string;
1919
onKeyDown?: (event: KeyboardEvent) => void;
2020
onValueChange: (newValue: string) => void;
21+
$$ref?: Ref<HTMLInputElement>;
2122
}
2223

23-
export const SearchBox = forwardRef(function SearchBox(
24-
props: SearchBoxProps,
25-
ref?: ForwardedRef<HTMLInputElement>,
26-
) {
27-
const {
28-
onValueChange,
29-
onKeyDown,
30-
label = "Search",
31-
placeholder = "Search...",
32-
...attrs
33-
} = props;
34-
24+
export const SearchBox: FC<SearchBoxProps> = ({
25+
onValueChange,
26+
onKeyDown,
27+
label = "Search",
28+
placeholder = "Search...",
29+
$$ref,
30+
...attrs
31+
}) => {
3532
const hookId = useId();
3633
const inputId = `${hookId}-${SearchBox.name}-input`;
3734

3835
return (
3936
<Search>
4037
<SearchInput
4138
label={label}
42-
ref={ref}
39+
$$ref={$$ref}
4340
id={inputId}
4441
autoFocus
4542
tabIndex={0}
@@ -50,4 +47,4 @@ export const SearchBox = forwardRef(function SearchBox(
5047
/>
5148
</Search>
5249
);
53-
});
50+
};

0 commit comments

Comments
 (0)