mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-22 12:19:24 +00:00
simplify search results & tool picker
This commit is contained in:
parent
19d2b72f65
commit
f2b5a4a1f8
@ -3,6 +3,8 @@ import { Box, Stack, Text } from '@mantine/core';
|
|||||||
import { type ToolRegistryEntry } from '../../data/toolRegistry';
|
import { type ToolRegistryEntry } from '../../data/toolRegistry';
|
||||||
import ToolButton from './toolPicker/ToolButton';
|
import ToolButton from './toolPicker/ToolButton';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useToolSections } from '../../hooks/useToolSections';
|
||||||
|
import SubcategoryHeader from './shared/SubcategoryHeader';
|
||||||
|
|
||||||
interface SearchResultsProps {
|
interface SearchResultsProps {
|
||||||
filteredTools: [string, ToolRegistryEntry][];
|
filteredTools: [string, ToolRegistryEntry][];
|
||||||
@ -11,38 +13,9 @@ interface SearchResultsProps {
|
|||||||
|
|
||||||
const SearchResults: React.FC<SearchResultsProps> = ({ filteredTools, onSelect }) => {
|
const SearchResults: React.FC<SearchResultsProps> = ({ filteredTools, onSelect }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// Group tools by subcategory and remove duplicates
|
const { searchGroups } = useToolSections(filteredTools);
|
||||||
const groupedToolsByCategory = useMemo(() => {
|
|
||||||
const categoryToToolsMap: Record<string, Array<{ id: string; tool: ToolRegistryEntry }>> = {};
|
|
||||||
const processedToolIds = new Set<string>();
|
|
||||||
|
|
||||||
// Process each tool, skipping duplicates and grouping by subcategory
|
if (searchGroups.length === 0) {
|
||||||
filteredTools.forEach(([toolId, toolEntry]) => {
|
|
||||||
// Skip if we've already processed this tool ID (deduplication)
|
|
||||||
if (processedToolIds.has(toolId)) return;
|
|
||||||
processedToolIds.add(toolId);
|
|
||||||
|
|
||||||
// Use subcategory or default to 'General' if not specified
|
|
||||||
const categoryName = toolEntry?.subcategory || 'General';
|
|
||||||
|
|
||||||
// Initialize category array if it doesn't exist
|
|
||||||
if (!categoryToToolsMap[categoryName]) {
|
|
||||||
categoryToToolsMap[categoryName] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
categoryToToolsMap[categoryName].push({ id: toolId, tool: toolEntry });
|
|
||||||
});
|
|
||||||
|
|
||||||
// Convert to sorted array format for rendering
|
|
||||||
return Object.entries(categoryToToolsMap)
|
|
||||||
.sort(([categoryA], [categoryB]) => categoryA.localeCompare(categoryB))
|
|
||||||
.map(([categoryName, toolsInCategory]) => ({
|
|
||||||
categoryName,
|
|
||||||
toolsInCategory
|
|
||||||
}));
|
|
||||||
}, [filteredTools]);
|
|
||||||
|
|
||||||
if (groupedToolsByCategory.length === 0) {
|
|
||||||
return (
|
return (
|
||||||
<Text c="dimmed" size="sm" p="sm">
|
<Text c="dimmed" size="sm" p="sm">
|
||||||
{t('toolPicker.noToolsFound', 'No tools found')}
|
{t('toolPicker.noToolsFound', 'No tools found')}
|
||||||
@ -52,13 +25,11 @@ const SearchResults: React.FC<SearchResultsProps> = ({ filteredTools, onSelect }
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack p="sm" gap="xs">
|
<Stack p="sm" gap="xs">
|
||||||
{groupedToolsByCategory.map(categoryGroup => (
|
{searchGroups.map(group => (
|
||||||
<Box key={categoryGroup.categoryName} w="100%">
|
<Box key={group.subcategory} w="100%">
|
||||||
<Text size="sm" fw={500} mb="0.25rem" mt="1rem" className="tool-subcategory-title">
|
<SubcategoryHeader label={t(`toolPicker.subcategories.${group.subcategory}`, group.subcategory)} />
|
||||||
{t(`toolPicker.subcategories.${categoryGroup.categoryName}`, categoryGroup.categoryName)}
|
|
||||||
</Text>
|
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
{categoryGroup.toolsInCategory.map(({ id, tool }) => (
|
{group.tools.map(({ id, tool }) => (
|
||||||
<ToolButton
|
<ToolButton
|
||||||
key={id}
|
key={id}
|
||||||
id={id}
|
id={id}
|
||||||
@ -68,7 +39,6 @@ const SearchResults: React.FC<SearchResultsProps> = ({ filteredTools, onSelect }
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
{/* bottom spacer within each group not strictly required, outer list can add a spacer if needed */}
|
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
{/* global spacer to allow scrolling past last row in search mode */}
|
{/* global spacer to allow scrolling past last row in search mode */}
|
||||||
|
@ -5,6 +5,7 @@ import { type ToolRegistryEntry } from "../../data/toolRegistry";
|
|||||||
import ToolButton from "./toolPicker/ToolButton";
|
import ToolButton from "./toolPicker/ToolButton";
|
||||||
import "./toolPicker/ToolPicker.css";
|
import "./toolPicker/ToolPicker.css";
|
||||||
import { useToolSections } from "../../hooks/useToolSections";
|
import { useToolSections } from "../../hooks/useToolSections";
|
||||||
|
import SubcategoryHeader from "./shared/SubcategoryHeader";
|
||||||
|
|
||||||
interface ToolPickerProps {
|
interface ToolPickerProps {
|
||||||
selectedToolKey: string | null;
|
selectedToolKey: string | null;
|
||||||
@ -13,12 +14,6 @@ interface ToolPickerProps {
|
|||||||
isSearching?: boolean;
|
isSearching?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GroupedTools {
|
|
||||||
[category: string]: {
|
|
||||||
[subcategory: string]: Array<{ id: string; tool: ToolRegistryEntry }>;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = false }: ToolPickerProps) => {
|
const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = false }: ToolPickerProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [quickHeaderHeight, setQuickHeaderHeight] = useState(0);
|
const [quickHeaderHeight, setQuickHeaderHeight] = useState(0);
|
||||||
@ -45,15 +40,6 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
|
|||||||
return () => window.removeEventListener("resize", update);
|
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" }) => (
|
|
||||||
<div className="tool-subcategory-row" style={{ marginLeft: "1rem", marginRight: "1rem", marginTop: mt, marginBottom: mb }}>
|
|
||||||
<div className="tool-subcategory-row-rule" />
|
|
||||||
<span className="tool-subcategory-row-title">{label}</span>
|
|
||||||
<div className="tool-subcategory-row-rule" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const { sections: visibleSections } = useToolSections(filteredTools);
|
const { sections: visibleSections } = useToolSections(filteredTools);
|
||||||
|
|
||||||
const quickSection = useMemo(
|
const quickSection = useMemo(
|
||||||
|
17
frontend/src/components/tools/shared/SubcategoryHeader.tsx
Normal file
17
frontend/src/components/tools/shared/SubcategoryHeader.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface SubcategoryHeaderProps {
|
||||||
|
label: string;
|
||||||
|
mt?: string | number;
|
||||||
|
mb?: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SubcategoryHeader: React.FC<SubcategoryHeaderProps> = ({ label, mt = '1rem', mb = '0.25rem' }) => (
|
||||||
|
<div className="tool-subcategory-row" style={{ marginLeft: '1rem', marginRight: '1rem', marginTop: mt, marginBottom: mb }}>
|
||||||
|
<div className="tool-subcategory-row-rule" />
|
||||||
|
<span className="tool-subcategory-row-title">{label}</span>
|
||||||
|
<div className="tool-subcategory-row-rule" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default SubcategoryHeader;
|
Loading…
x
Reference in New Issue
Block a user