From 876e13de157bbd1c962763eebb749147f1e0a82e Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Wed, 24 Jun 2026 01:54:06 +0000 Subject: [PATCH] feat: add module filter tabs --- .../TemplateBuilder/ModuleSelectStep.tsx | 73 ++++++++++++++++--- 1 file changed, 61 insertions(+), 12 deletions(-) diff --git a/site/src/pages/TemplateBuilder/ModuleSelectStep.tsx b/site/src/pages/TemplateBuilder/ModuleSelectStep.tsx index f70d1f8a56..598f80c41e 100644 --- a/site/src/pages/TemplateBuilder/ModuleSelectStep.tsx +++ b/site/src/pages/TemplateBuilder/ModuleSelectStep.tsx @@ -1,3 +1,4 @@ +import { PackageIcon, SearchIcon } from "lucide-react"; import { type FC, type PropsWithChildren, useMemo, useState } from "react"; import { useQuery } from "react-query"; import { templateBuilderModules } from "#/api/queries/templateBuilder"; @@ -9,6 +10,8 @@ import { Alert, AlertDescription, AlertTitle } from "#/components/Alert/Alert"; import { ErrorAlert } from "#/components/Alert/ErrorAlert"; import { Loader } from "#/components/Loader/Loader"; import { SearchField } from "#/components/SearchField/SearchField"; +import { Tabs, TabsList, TabsTrigger } from "#/components/Tabs/Tabs"; +import { useKebabMenu } from "#/components/Tabs/utils/useKebabMenu"; import { useFuzzySearch } from "#/hooks/useFuzzySearch"; import { ModuleCard } from "./ModuleCard"; import { @@ -72,8 +75,30 @@ export const ModuleSelectStep: FC = ({ const [moduleSearchText, setModuleSearchText] = useState(""); const modules = data?.modules ?? []; + const [selectedFilterTab, setSelectedFilterTab] = useState("All"); + // TODO category counts should update on search + const categoryCounts = new Map(); + for (const module of modules) { + categoryCounts.set( + module.category, + (categoryCounts.get(module.category) ?? 0) + 1, + ); + } + const filterTabs: Array<{ value: string; count: number }> = [ + { value: "All", count: modules.length }, + ...categoryCounts.entries().map(([value, count]) => ({ value, count })), + ]; + const { containerRef, visibleTabs: visibleFilterTabs } = useKebabMenu({ + tabs: filterTabs, + enabled: true, + isActive: true, + }); + const searchedModules = useFuzzySearch({ - allItems: modules, + allItems: + selectedFilterTab === "All" + ? modules + : modules.filter((module) => module.category === selectedFilterTab), searchText: moduleSearchText, searchProperties: ["display_name", "description"], }); @@ -154,18 +179,42 @@ export const ModuleSelectStep: FC = ({ className="my-4" /> + + + {visibleFilterTabs.map((tab) => ( + + + {tab.value} ({tab.count}) + + ))} + + +
- {searchedModules.map((m) => ( - handleToggle(m)} - /> - ))} + {searchedModules.length ? ( + searchedModules.map((m) => ( + handleToggle(m)} + /> + )) + ) : ( +
+ +

+ No module matched your search +

+
+ )}
{conflicts.length > 0 && (