diff --git a/frontend/src/components/tools/ToolPicker.tsx b/frontend/src/components/tools/ToolPicker.tsx
index 3875d45ca..6b9bc1244 100644
--- a/frontend/src/components/tools/ToolPicker.tsx
+++ b/frontend/src/components/tools/ToolPicker.tsx
@@ -1,9 +1,10 @@
import React, { useMemo, useRef, useLayoutEffect, useState } from "react";
import { Box, Text, Stack } from "@mantine/core";
import { useTranslation } from "react-i18next";
-import { type ToolRegistryEntry, SUBCATEGORY_ORDER } from "../../data/toolRegistry";
+import { type ToolRegistryEntry } from "../../data/toolRegistry";
import ToolButton from "./toolPicker/ToolButton";
import "./toolPicker/ToolPicker.css";
+import { useToolSections } from "../../hooks/useToolSections";
interface ToolPickerProps {
selectedToolKey: string | null;
@@ -43,6 +44,7 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
return () => window.removeEventListener("resize", update);
}, []);
+ // Lightweight inline component to avoid an extra file
const SubcategoryHeader: React.FC<{ label: string; mt?: string | number; mb?: string | number }> = ({ label, mt = "1rem", mb = "0.25rem" }) => (
@@ -51,68 +53,7 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
);
- const groupedTools = useMemo(() => {
- const grouped: GroupedTools = {};
- filteredTools.forEach(([id, tool]) => {
- const category = tool?.category || "OTHER";
- const subcategory = tool?.subcategory || "General";
- if (!grouped[category]) grouped[category] = {};
- if (!grouped[category][subcategory]) grouped[category][subcategory] = [];
- grouped[category][subcategory].push({ id, tool });
- });
- return grouped;
- }, [filteredTools]);
-
- const sections = useMemo(() => {
-
- const getOrderIndex = (name: string) => {
- const idx = SUBCATEGORY_ORDER.indexOf(name);
- return idx === -1 ? Number.MAX_SAFE_INTEGER : idx;
- };
- // Build two buckets: Quick includes only Recommended; All includes all categories (including Recommended)
- const quick: Record> = {};
- const all: Record> = {};
-
- Object.entries(groupedTools).forEach(([origCat, subs]) => {
- const upperCat = origCat.toUpperCase();
-
- // Always add to ALL
- Object.entries(subs).forEach(([sub, tools]) => {
- if (!all[sub]) all[sub] = [];
- all[sub].push(...tools);
- });
-
- // Add Recommended to QUICK ACCESS
- if (upperCat === 'RECOMMENDED TOOLS') {
- Object.entries(subs).forEach(([sub, tools]) => {
- if (!quick[sub]) quick[sub] = [];
- quick[sub].push(...tools);
- });
- }
- });
-
- const sortSubs = (obj: Record>) =>
- Object.entries(obj)
- .sort(([a], [b]) => {
- const ai = getOrderIndex(a);
- const bi = getOrderIndex(b);
- if (ai !== bi) return ai - bi;
- return a.localeCompare(b);
- })
- .map(([subcategory, tools]) => ({
- subcategory,
- // preserve original insertion order coming from filteredTools
- tools
- }));
-
- // Build sections and filter out any with no tools (avoids empty headers during search)
- const built = [
- { title: "QUICK ACCESS", ref: quickAccessRef, subcategories: sortSubs(quick) },
- { title: "ALL TOOLS", ref: allToolsRef, subcategories: sortSubs(all) }
- ];
-
- return built.filter(section => section.subcategories.some(sc => sc.tools.length > 0));
- }, [groupedTools]);
+ const { sections } = useToolSections(filteredTools);
const visibleSections = sections;
@@ -141,25 +82,7 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
};
// Build flat list by subcategory for search mode
- const searchGroups = useMemo(() => {
- if (!isSearching) return [] as Array<{ subcategory: string; tools: Array<{ id: string; tool: ToolRegistryEntry }> }>;
- const subMap: Record> = {};
- const seen = new Set();
- filteredTools.forEach(([id, tool]) => {
- if (seen.has(id)) return;
- seen.add(id);
- const sub = tool?.subcategory || 'General';
- if (!subMap[sub]) subMap[sub] = [];
- subMap[sub].push({ id, tool });
- });
- return Object.entries(subMap)
- .sort(([a],[b]) => a.localeCompare(b))
- .map(([subcategory, tools]) => ({
- subcategory,
- // preserve insertion order
- tools
- }));
- }, [isSearching, filteredTools]);
+ const { searchGroups } = useToolSections(isSearching ? filteredTools : []);
return (
;
+ };
+};
+
+export function useToolSections(filteredTools: [string, ToolRegistryEntry][]) {
+ const groupedTools = useMemo(() => {
+ const grouped: GroupedTools = {};
+ filteredTools.forEach(([id, tool]) => {
+ const category = tool?.category || 'OTHER';
+ const subcategory = tool?.subcategory || 'General';
+ if (!grouped[category]) grouped[category] = {};
+ if (!grouped[category][subcategory]) grouped[category][subcategory] = [];
+ grouped[category][subcategory].push({ id, tool });
+ });
+ return grouped;
+ }, [filteredTools]);
+
+ const sections = useMemo(() => {
+ const getOrderIndex = (name: string) => {
+ const idx = SUBCATEGORY_ORDER.indexOf(name);
+ return idx === -1 ? Number.MAX_SAFE_INTEGER : idx;
+ };
+
+ const quick: Record> = {};
+ const all: Record> = {};
+
+ Object.entries(groupedTools).forEach(([origCat, subs]) => {
+ const upperCat = origCat.toUpperCase();
+
+ Object.entries(subs).forEach(([sub, tools]) => {
+ if (!all[sub]) all[sub] = [];
+ all[sub].push(...tools);
+ });
+
+ if (upperCat === 'RECOMMENDED TOOLS') {
+ Object.entries(subs).forEach(([sub, tools]) => {
+ if (!quick[sub]) quick[sub] = [];
+ quick[sub].push(...tools);
+ });
+ }
+ });
+
+ const sortSubs = (obj: Record>) =>
+ Object.entries(obj)
+ .sort(([a], [b]) => {
+ const ai = getOrderIndex(a);
+ const bi = getOrderIndex(b);
+ if (ai !== bi) return ai - bi;
+ return a.localeCompare(b);
+ })
+ .map(([subcategory, tools]) => ({ subcategory, tools }));
+
+ const built = [
+ { title: 'QUICK ACCESS', subcategories: sortSubs(quick) },
+ { title: 'ALL TOOLS', subcategories: sortSubs(all) }
+ ];
+
+ return built.filter(section => section.subcategories.some(sc => sc.tools.length > 0));
+ }, [groupedTools]);
+
+ const searchGroups = useMemo(() => {
+ const subMap: Record> = {};
+ const seen = new Set();
+ filteredTools.forEach(([id, tool]) => {
+ if (seen.has(id)) return;
+ seen.add(id);
+ const sub = tool?.subcategory || 'General';
+ if (!subMap[sub]) subMap[sub] = [];
+ subMap[sub].push({ id, tool });
+ });
+ return Object.entries(subMap)
+ .sort(([a], [b]) => a.localeCompare(b))
+ .map(([subcategory, tools]) => ({ subcategory, tools }));
+ }, [filteredTools]);
+
+ return { sections, searchGroups };
+}
+
+