diff --git a/site/src/components/Menu/MenuSearch.tsx b/site/src/components/Menu/MenuSearch.tsx deleted file mode 100644 index d6aa7e8362..0000000000 --- a/site/src/components/Menu/MenuSearch.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import type { FC } from "react"; -import { - SearchField, - type SearchFieldProps, -} from "#/components/SearchField/SearchField"; -export const MenuSearch: FC = (props) => { - return ; -}; diff --git a/site/src/pages/WorkspacesPage/WorkspacesButton.tsx b/site/src/pages/WorkspacesPage/WorkspacesButton.tsx index fe09deed74..707fa0fc34 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesButton.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesButton.tsx @@ -1,24 +1,22 @@ -import Link from "@mui/material/Link"; import { ExternalLinkIcon } from "lucide-react"; -import { type FC, type ReactNode, useState } from "react"; +import { type FC, type ReactNode, useMemo } from "react"; import type { UseQueryResult } from "react-query"; -import { - Link as RouterLink, - type LinkProps as RouterLinkProps, -} from "react-router"; +import { Link as RouterLink, useNavigate } from "react-router"; import type { Template } from "#/api/typesGenerated"; import { ChevronDownIcon } from "#/components/AnimatedIcons/ChevronDown"; import { Avatar } from "#/components/Avatar/Avatar"; import { Button } from "#/components/Button/Button"; -import { Loader } from "#/components/Loader/Loader"; -import { MenuSearch } from "#/components/Menu/MenuSearch"; -import { OverflowY } from "#/components/OverflowY/OverflowY"; import { - Popover, - PopoverContent, - PopoverTrigger, -} from "#/components/Popover/Popover"; -import { SearchEmpty } from "#/components/Search/Search"; + Combobox, + ComboboxContent, + ComboboxEmpty, + ComboboxInput, + ComboboxItem, + ComboboxList, + ComboboxTrigger, +} from "#/components/Combobox/Combobox"; +import { Link } from "#/components/Link/Link"; +import { Spinner } from "#/components/Spinner/Spinner"; import { linkToTemplate, useLinks } from "#/modules/navigation"; type TemplatesQuery = UseQueryResult; @@ -34,177 +32,120 @@ export const WorkspacesButton: FC = ({ templatesFetchStatus, templates, }) => { + const navigate = useNavigate(); + const getLink = useLinks(); + // Dataset should always be small enough that client-side filtering should be // good enough. Can swap out down the line if it becomes an issue - const [searchTerm, setSearchTerm] = useState(""); - const processed = sortTemplatesByUsersDesc(templates ?? [], searchTerm); - - let emptyState: ReactNode; - if (templates?.length === 0) { - emptyState = ( - - No templates yet.{" "} - - Create one now. - - - ); - } else if (processed.length === 0) { - emptyState = No templates found; - } + const sortedTemplates = useMemo( + () => sortTemplatesByUsersDesc(templates ?? []), + [templates], + ); return ( - - + { + if (!templateId || !templates) { + return; + } + const template = templates.find((t) => t.id === templateId); + if (!template) { + return; + } + navigate( + `${getLink(linkToTemplate(template.organization_name, template.name))}/workspace`, + ); + }} + > + - - + - - - - {templatesFetchStatus === "pending" ? ( - - ) : ( - <> - {processed.map((template) => ( - - ))} - - {emptyState} - - )} - - -
({ - padding: "8px 0", - borderTop: `1px solid ${theme.palette.divider}`, - })} + - + +
+ ) : ( + sortedTemplates.map((template) => ( + + +
+ + {template.display_name || template.name || "[Unnamed]"} + + + {activeDeveloperLabel(template.active_user_count)} + +
+
+ )) + )} + + {templatesFetchStatus !== "pending" && + (templates?.length === 0 ? ( + + No templates yet.{" "} + + Create one now. + + + ) : ( + No templates found + ))} +
+ ({ - display: "flex", - alignItems: "center", - columnGap: 12, - color: theme.palette.primary.main, - })} + className="flex items-center gap-3 px-4 py-2 text-sm text-content-link no-underline outline-none hover:bg-surface-tertiary hover:no-underline focus:bg-surface-tertiary" > See all templates - +
-
-
+ + ); }; -interface WorkspaceResultsRowProps { - template: Template; -} - -const WorkspaceResultsRow: FC = ({ template }) => { - const getLink = useLinks(); - const templateLink = getLink( - linkToTemplate(template.organization_name, template.name), - ); - - return ( - - - -
({ - color: theme.palette.text.primary, - display: "flex", - flexDirection: "column", - lineHeight: "140%", - fontSize: 14, - overflow: "hidden", - })} - > - - {template.display_name || template.name || "[Unnamed]"} - - ({ - fontSize: 13, - color: theme.palette.text.secondary, - })} - > - {/* - * There are some templates that have -1 as their user count – - * basically functioning like a null value in JS. Can safely just - * treat them as if they were 0. - */} - {template.active_user_count <= 0 ? "No" : template.active_user_count}{" "} - developer - {template.active_user_count === 1 ? "" : "s"} - -
-
- ); -}; - -const PopoverLink: FC = ({ children, ...linkProps }) => { - return ( - ({ - color: theme.palette.text.primary, - padding: "8px 16px", - fontSize: 14, - outline: "none", - textDecoration: "none", - "&:focus": { - backgroundColor: theme.palette.action.focus, - }, - "&:hover": { - textDecoration: "none", - backgroundColor: theme.palette.action.hover, - }, - })} - > - {children} - - ); -}; - -function sortTemplatesByUsersDesc( - templates: readonly Template[], - searchTerm: string, -) { - const allWhitespace = /^\s+$/.test(searchTerm); - if (allWhitespace) { - return templates; +function activeDeveloperLabel(count: number): string { + // Some templates report -1 as their user count, which is treated like a + // null. Treat those the same as 0. + if (count <= 0) { + return "No developers"; } + return `${count} developer${count === 1 ? "" : "s"}`; +} - const termMatcher = new RegExp(searchTerm.replaceAll(/[^\w]/g, "."), "i"); - return templates - .filter( - (template) => - termMatcher.test(template.display_name) || - termMatcher.test(template.name), - ) - .sort((t1, t2) => t2.active_user_count - t1.active_user_count) - .slice(0, 10); +function sortTemplatesByUsersDesc(templates: readonly Template[]) { + return templates.toSorted( + (t1, t2) => t2.active_user_count - t1.active_user_count, + ); }