From ed90abbf4f8084018b483221872cfe1d857775ca Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Tue, 23 Jun 2026 23:08:22 +0000 Subject: [PATCH] feat: add search field for modules --- .../TemplateBuilder/ModuleSelectStep.tsx | 46 +++++++++++++++++-- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/site/src/pages/TemplateBuilder/ModuleSelectStep.tsx b/site/src/pages/TemplateBuilder/ModuleSelectStep.tsx index a0585bdaf5..c02d60860a 100644 --- a/site/src/pages/TemplateBuilder/ModuleSelectStep.tsx +++ b/site/src/pages/TemplateBuilder/ModuleSelectStep.tsx @@ -1,5 +1,6 @@ -import { type FC, type PropsWithChildren, useMemo } from "react"; +import { type FC, type PropsWithChildren, useMemo, useState } from "react"; import { useQuery } from "react-query"; +import uFuzzy from "ufuzzy"; import { templateBuilderModules } from "#/api/queries/templateBuilder"; import type { TemplateBuilderComposeModule, @@ -8,6 +9,7 @@ import type { 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 { ModuleCard } from "./ModuleCard"; import { moduleHasConfigurableVars, @@ -50,6 +52,14 @@ const ModuleName: FC = ({ children }) => { ); }; +const fuzzyFinder = new uFuzzy({ + intraMode: 1, + intraIns: 1, + intraSub: 1, + intraTrn: 1, + intraDel: 1, +}); + const ConflictWarning: FC = ({ moduleA, moduleB }) => { return (
@@ -67,14 +77,35 @@ export const ModuleSelectStep: FC = ({ onChangeModules, }) => { const { data, error, isLoading } = useQuery(templateBuilderModules(baseId)); + const [moduleSearchText, setModuleSearchText] = useState(""); + const modules = data?.modules ?? []; + const searchText = moduleSearchText.trim(); + + const searchedModules = useMemo(() => { + if (!searchText) { + return modules; + } + + const [map, info, sorted] = fuzzyFinder.search( + // Search several string fields by concatenating them together + // https://github.com/leeoniya/uFuzzy/issues/7 + modules.map((module) => `${module.display_name}|${module.description}`), + searchText, + ); + + // We hit an invalid state somehow + if (!map || !info || !sorted) { + return []; + } + + return sorted.map((i) => modules[info.idx[i]]); + }, [modules, searchText]); const selectedSet = useMemo( () => new Set(selectedModuleIds), [selectedModuleIds], ); - const modules = data?.modules ?? []; - const conflicts = useMemo(() => { const warnings: string[] = []; // Loop through the selected modules and check for conflicts. We sort the @@ -139,8 +170,15 @@ export const ModuleSelectStep: FC = ({ selection.

+ +
- {modules.map((m) => ( + {searchedModules.map((m) => (