import React, { useMemo, useRef, useLayoutEffect, useState } from "react"; import { Box, Stack } from "@mantine/core"; import { useTranslation } from "react-i18next"; import { ToolRegistryEntry } from "../../data/toolsTaxonomy"; import "./toolPicker/ToolPicker.css"; import { useToolSections } from "../../hooks/useToolSections"; import NoToolsFound from "./shared/NoToolsFound"; import { renderToolButtons } from "./shared/renderToolButtons"; interface ToolPickerProps { selectedToolKey: string | null; onSelect: (id: string) => void; filteredTools: [string, ToolRegistryEntry][]; isSearching?: boolean; } const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = false }: ToolPickerProps) => { const { t } = useTranslation(); const [quickHeaderHeight, setQuickHeaderHeight] = useState(0); const [allHeaderHeight, setAllHeaderHeight] = useState(0); const scrollableRef = useRef(null); const quickHeaderRef = useRef(null); const allHeaderRef = useRef(null); const quickAccessRef = useRef(null); const allToolsRef = useRef(null); // Keep header heights in sync with any dynamic size changes useLayoutEffect(() => { const update = () => { if (quickHeaderRef.current) { setQuickHeaderHeight(quickHeaderRef.current.offsetHeight || 0); } if (allHeaderRef.current) { setAllHeaderHeight(allHeaderRef.current.offsetHeight || 0); } }; update(); // Update on window resize window.addEventListener("resize", update); // Update on element resize (e.g., font load, badge count change, zoom) const observers: ResizeObserver[] = []; if (typeof ResizeObserver !== "undefined") { const observe = (el: HTMLDivElement | null, cb: () => void) => { if (!el) return; const ro = new ResizeObserver(() => cb()); ro.observe(el); observers.push(ro); }; observe(quickHeaderRef.current, update); observe(allHeaderRef.current, update); } return () => { window.removeEventListener("resize", update); observers.forEach(o => o.disconnect()); }; }, []); const { sections: visibleSections } = useToolSections(filteredTools); const quickSection = useMemo( () => visibleSections.find(s => s.key === 'quick'), [visibleSections] ); const allSection = useMemo( () => visibleSections.find(s => s.key === 'all'), [visibleSections] ); const scrollTo = (ref: React.RefObject) => { const container = scrollableRef.current; const target = ref.current; if (container && target) { const stackedOffset = ref === allToolsRef ? (quickHeaderHeight + allHeaderHeight) : quickHeaderHeight; const top = target.offsetTop - container.offsetTop - (stackedOffset || 0); container.scrollTo({ top: Math.max(0, top), behavior: "smooth" }); } }; // Build flat list by subcategory for search mode const { searchGroups } = useToolSections(isSearching ? filteredTools : []); return ( {isSearching ? ( {searchGroups.length === 0 ? ( ) : ( searchGroups.map(group => renderToolButtons(t, group, selectedToolKey, onSelect)) )} ) : ( <> {quickSection && ( <>
scrollTo(quickAccessRef)} > {t("toolPicker.quickAccess", "QUICK ACCESS")} {quickSection?.subcategories.reduce((acc, sc) => acc + sc.tools.length, 0)}
{quickSection?.subcategories.map(sc => renderToolButtons(t, sc, selectedToolKey, onSelect, false) )} )} {allSection && ( <>
scrollTo(allToolsRef)} > {t("toolPicker.allTools", "ALL TOOLS")} {allSection?.subcategories.reduce((acc, sc) => acc + sc.tools.length, 0)}
{allSection?.subcategories.map(sc => renderToolButtons(t, sc, selectedToolKey, onSelect, true) )} )} {!quickSection && !allSection && } {/* bottom spacer to allow scrolling past the last row */}
)} ); }; export default ToolPicker;