Skip to content

Commit 1be119b

Browse files
fix(site): fix search menu for creating workspace and templates filter (#11674)
1 parent b246f08 commit 1be119b

File tree

4 files changed

+131
-103
lines changed

4 files changed

+131
-103
lines changed

site/src/components/Filter/filter.tsx

+12-48
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ import { Loader } from "components/Loader/Loader";
3232
import { useDebouncedFunction } from "hooks/debounce";
3333
import { useFilterMenu } from "./menu";
3434
import type { BaseOption } from "./options";
35+
import {
36+
Search,
37+
SearchEmpty,
38+
SearchInput,
39+
searchStyles,
40+
} from "components/Menu/Search";
3541

3642
export type PresetFilter = {
3743
name: string;
@@ -489,7 +495,7 @@ export const FilterSearchMenu = <TOption extends BaseOption>({
489495
onQueryChange={menu.setQuery}
490496
renderOption={(option) => (
491497
<MenuItem
492-
key={option.label}
498+
key={option.value}
493499
selected={option.value === menu.selectedOption?.value}
494500
onClick={() => {
495501
menu.selectOption(option);
@@ -576,7 +582,6 @@ function SearchMenu<TOption extends BaseOption>({
576582
}: SearchMenuProps<TOption>) {
577583
const menuListRef = useRef<HTMLUListElement>(null);
578584
const searchInputRef = useRef<HTMLInputElement>(null);
579-
const theme = useTheme();
580585

581586
return (
582587
<Menu
@@ -586,10 +591,7 @@ function SearchMenu<TOption extends BaseOption>({
586591
onQueryChange("");
587592
}}
588593
css={{
589-
"& .MuiPaper-root": {
590-
width: 320,
591-
padding: 0,
592-
},
594+
"& .MuiPaper-root": searchStyles.content,
593595
}}
594596
// Disabled this so when we clear the filter and do some sorting in the
595597
// search items it does not look strange. Github removes exit transitions
@@ -606,44 +608,16 @@ function SearchMenu<TOption extends BaseOption>({
606608
}
607609
}}
608610
>
609-
<li
610-
css={{
611-
display: "flex",
612-
alignItems: "center",
613-
paddingLeft: 16,
614-
height: 40,
615-
borderBottom: `1px solid ${theme.palette.divider}`,
616-
}}
617-
>
618-
<SearchOutlined
619-
css={{
620-
fontSize: 14,
621-
color: theme.palette.text.secondary,
622-
}}
623-
/>
624-
<input
625-
tabIndex={-1}
626-
type="text"
627-
placeholder="Search..."
611+
<Search component="li">
612+
<SearchInput
628613
autoFocus
629614
value={query}
630615
ref={searchInputRef}
631616
onChange={(e) => {
632617
onQueryChange(e.target.value);
633618
}}
634-
css={{
635-
height: "100%",
636-
border: 0,
637-
background: "none",
638-
width: "100%",
639-
marginLeft: 16,
640-
outline: 0,
641-
"&::placeholder": {
642-
color: theme.palette.text.secondary,
643-
},
644-
}}
645619
/>
646-
</li>
620+
</Search>
647621

648622
<li css={{ maxHeight: 480, overflowY: "auto" }}>
649623
<MenuList
@@ -660,17 +634,7 @@ function SearchMenu<TOption extends BaseOption>({
660634
options.length > 0 ? (
661635
options.map(renderOption)
662636
) : (
663-
<div
664-
css={{
665-
fontSize: 13,
666-
color: theme.palette.text.secondary,
667-
textAlign: "center",
668-
paddingTop: 8,
669-
paddingBottom: 8,
670-
}}
671-
>
672-
No results
673-
</div>
637+
<SearchEmpty />
674638
)
675639
) : (
676640
<Loader size={14} />

site/src/components/Menu/Search.tsx

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

site/src/pages/WorkspacesPage/WorkspacesButton.tsx

+14-11
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ import AddIcon from "@mui/icons-material/AddOutlined";
1111
import OpenIcon from "@mui/icons-material/OpenInNewOutlined";
1212
import { Loader } from "components/Loader/Loader";
1313
import { OverflowY } from "components/OverflowY/OverflowY";
14-
import { EmptyState } from "components/EmptyState/EmptyState";
1514
import { Avatar } from "components/Avatar/Avatar";
1615
import { SearchBox } from "./WorkspacesSearchBox";
1716
import {
1817
Popover,
1918
PopoverContent,
2019
PopoverTrigger,
2120
} from "components/Popover/Popover";
21+
import { SearchEmpty, searchStyles } from "components/Menu/Search";
2222

2323
const ICON_SIZE = 18;
2424

@@ -43,17 +43,15 @@ export const WorkspacesButton: FC<WorkspacesButtonProps> = ({
4343
let emptyState: ReactNode = undefined;
4444
if (templates?.length === 0) {
4545
emptyState = (
46-
<EmptyState
47-
message="No templates yet"
48-
cta={
49-
<Link to="/templates" component={RouterLink}>
50-
Create one now.
51-
</Link>
52-
}
53-
/>
46+
<SearchEmpty>
47+
No templates yet.{" "}
48+
<Link to="/templates" component={RouterLink}>
49+
Create one now.
50+
</Link>
51+
</SearchEmpty>
5452
);
5553
} else if (processed.length === 0) {
56-
emptyState = <EmptyState message="No templates match your text" />;
54+
emptyState = <SearchEmpty>No templates found</SearchEmpty>;
5755
}
5856

5957
return (
@@ -63,7 +61,12 @@ export const WorkspacesButton: FC<WorkspacesButtonProps> = ({
6361
{children}
6462
</Button>
6563
</PopoverTrigger>
66-
<PopoverContent horizontal="right">
64+
<PopoverContent
65+
horizontal="right"
66+
css={{
67+
".MuiPaper-root": searchStyles.content,
68+
}}
69+
>
6770
<SearchBox
6871
value={searchTerm}
6972
onValueChange={(newValue) => setSearchTerm(newValue)}

site/src/pages/WorkspacesPage/WorkspacesSearchBox.tsx

+5-44
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ import {
1111
forwardRef,
1212
useId,
1313
} from "react";
14-
import SearchIcon from "@mui/icons-material/SearchOutlined";
15-
import { visuallyHidden } from "@mui/utils";
16-
import { useTheme } from "@emotion/react";
14+
import { Search, SearchInput } from "components/Menu/Search";
1715

1816
interface SearchBoxProps extends InputHTMLAttributes<HTMLInputElement> {
1917
label?: string;
@@ -35,39 +33,12 @@ export const SearchBox = forwardRef(function SearchBox(
3533
} = props;
3634

3735
const hookId = useId();
38-
const theme = useTheme();
39-
4036
const inputId = `${hookId}-${SearchBox.name}-input`;
4137

4238
return (
43-
<div
44-
css={{
45-
display: "flex",
46-
flexFlow: "row nowrap",
47-
alignItems: "center",
48-
padding: "0 8px",
49-
height: "40px",
50-
borderBottom: `1px solid ${theme.palette.divider}`,
51-
}}
52-
>
53-
<div css={{ width: 18 }}>
54-
<SearchIcon
55-
css={{
56-
display: "block",
57-
fontSize: "14px",
58-
marginLeft: "auto",
59-
marginRight: "auto",
60-
color: theme.palette.text.secondary,
61-
}}
62-
/>
63-
</div>
64-
65-
<label css={{ ...visuallyHidden }} htmlFor={inputId}>
66-
{label}
67-
</label>
68-
69-
<input
70-
type="text"
39+
<Search>
40+
<SearchInput
41+
label={label}
7142
ref={ref}
7243
id={inputId}
7344
autoFocus
@@ -76,17 +47,7 @@ export const SearchBox = forwardRef(function SearchBox(
7647
{...attrs}
7748
onKeyDown={onKeyDown}
7849
onChange={(e) => onValueChange(e.target.value)}
79-
css={{
80-
height: "100%",
81-
border: 0,
82-
background: "none",
83-
width: "100%",
84-
outline: 0,
85-
"&::placeholder": {
86-
color: theme.palette.text.secondary,
87-
},
88-
}}
8950
/>
90-
</div>
51+
</Search>
9152
);
9253
});

0 commit comments

Comments
 (0)