mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 22:29:24 +00:00
Drop downs appear correctly
This commit is contained in:
parent
4e3bf1251d
commit
f7a4aaa766
@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useMemo, useCallback } from 'react';
|
import React, { useState, useMemo, useCallback, useRef, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Menu, Stack, Text, ScrollArea } from '@mantine/core';
|
import { Stack, Text, ScrollArea } from '@mantine/core';
|
||||||
import { ToolRegistryEntry } from '../../../data/toolsTaxonomy';
|
import { ToolRegistryEntry } from '../../../data/toolsTaxonomy';
|
||||||
import { useToolSections } from '../../../hooks/useToolSections';
|
import { useToolSections } from '../../../hooks/useToolSections';
|
||||||
import { renderToolButtons } from '../shared/renderToolButtons';
|
import { renderToolButtons } from '../shared/renderToolButtons';
|
||||||
@ -24,6 +24,8 @@ export default function ToolSelector({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
const [shouldAutoFocus, setShouldAutoFocus] = useState(false);
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Filter out excluded tools (like 'automate' itself)
|
// Filter out excluded tools (like 'automate' itself)
|
||||||
const baseFilteredTools = useMemo(() => {
|
const baseFilteredTools = useMemo(() => {
|
||||||
@ -66,13 +68,20 @@ export default function ToolSelector({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!sections || sections.length === 0) {
|
if (!sections || sections.length === 0) {
|
||||||
|
// If no sections, create a simple group from filtered tools
|
||||||
|
if (baseFilteredTools.length > 0) {
|
||||||
|
return [{
|
||||||
|
name: 'All Tools',
|
||||||
|
tools: baseFilteredTools.map(([key, tool]) => ({ key, ...tool }))
|
||||||
|
}];
|
||||||
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the "all" section which contains all tools without duplicates
|
// Find the "all" section which contains all tools without duplicates
|
||||||
const allSection = sections.find(s => (s as any).key === 'all');
|
const allSection = sections.find(s => (s as any).key === 'all');
|
||||||
return allSection?.subcategories || [];
|
return allSection?.subcategories || [];
|
||||||
}, [isSearching, searchGroups, sections]);
|
}, [isSearching, searchGroups, sections, baseFilteredTools]);
|
||||||
|
|
||||||
const handleToolSelect = useCallback((toolKey: string) => {
|
const handleToolSelect = useCallback((toolKey: string) => {
|
||||||
onSelect(toolKey);
|
onSelect(toolKey);
|
||||||
@ -88,8 +97,25 @@ export default function ToolSelector({
|
|||||||
|
|
||||||
const handleSearchFocus = () => {
|
const handleSearchFocus = () => {
|
||||||
setOpened(true);
|
setOpened(true);
|
||||||
|
setShouldAutoFocus(true); // Request auto-focus for the input
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle click outside to close dropdown
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
|
||||||
|
setOpened(false);
|
||||||
|
setSearchTerm('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (opened) {
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
}
|
||||||
|
}, [opened]);
|
||||||
|
|
||||||
|
|
||||||
const handleSearchChange = (value: string) => {
|
const handleSearchChange = (value: string) => {
|
||||||
setSearchTerm(value);
|
setSearchTerm(value);
|
||||||
if (!opened) {
|
if (!opened) {
|
||||||
@ -97,6 +123,14 @@ export default function ToolSelector({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleInputFocus = () => {
|
||||||
|
if (!opened) {
|
||||||
|
setOpened(true);
|
||||||
|
}
|
||||||
|
// Clear auto-focus flag since input is now focused
|
||||||
|
setShouldAutoFocus(false);
|
||||||
|
};
|
||||||
|
|
||||||
// Get display value for selected tool
|
// Get display value for selected tool
|
||||||
const getDisplayValue = () => {
|
const getDisplayValue = () => {
|
||||||
if (selectedValue && toolRegistry[selectedValue]) {
|
if (selectedValue && toolRegistry[selectedValue]) {
|
||||||
@ -106,77 +140,75 @@ export default function ToolSelector({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ position: 'relative', width: '100%' }}>
|
<div ref={containerRef} style={{ position: 'relative', width: '100%' }}>
|
||||||
<Menu
|
{/* Always show the target - either selected tool or search input */}
|
||||||
opened={opened}
|
<div style={{ width: '100%' }}>
|
||||||
onChange={(isOpen) => {
|
{selectedValue && toolRegistry[selectedValue] && !opened ? (
|
||||||
setOpened(isOpen);
|
// Show selected tool in AutomationEntry style when tool is selected and dropdown closed
|
||||||
// Clear search term when menu closes to show proper display
|
<div onClick={handleSearchFocus} style={{ cursor: 'pointer' }}>
|
||||||
if (!isOpen) {
|
<div style={{
|
||||||
setSearchTerm('');
|
display: 'flex',
|
||||||
}
|
alignItems: 'center',
|
||||||
}}
|
gap: 'var(--mantine-spacing-sm)',
|
||||||
closeOnClickOutside={true}
|
padding: '0 0.5rem',
|
||||||
closeOnEscape={true}
|
borderRadius: 'var(--mantine-radius-sm)',
|
||||||
position="bottom-start"
|
}}>
|
||||||
offset={4}
|
<div style={{ color: 'var(--mantine-color-text)', fontSize: '1.2rem' }}>
|
||||||
withinPortal={false}
|
{toolRegistry[selectedValue].icon}
|
||||||
trapFocus={false}
|
|
||||||
shadow="sm"
|
|
||||||
transitionProps={{ duration: 0 }}
|
|
||||||
>
|
|
||||||
<Menu.Target>
|
|
||||||
<div style={{ width: '100%' }}>
|
|
||||||
{selectedValue && toolRegistry[selectedValue] && !opened ? (
|
|
||||||
// Show selected tool in AutomationEntry style when tool is selected and not searching
|
|
||||||
<div onClick={handleSearchFocus} style={{ cursor: 'pointer' }}>
|
|
||||||
<div style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 'var(--mantine-spacing-sm)',
|
|
||||||
padding: '0 0.5rem',
|
|
||||||
borderRadius: 'var(--mantine-radius-sm)',
|
|
||||||
}}>
|
|
||||||
<div style={{ color: 'var(--mantine-color-text)', fontSize: '1.2rem' }}>
|
|
||||||
{toolRegistry[selectedValue].icon}
|
|
||||||
</div>
|
|
||||||
<Text size="sm" style={{ flex: 1, color: 'var(--mantine-color-text)' }}>
|
|
||||||
{toolRegistry[selectedValue].name}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
<Text size="sm" style={{ flex: 1, color: 'var(--mantine-color-text)' }}>
|
||||||
// Show search input when no tool selected or actively searching
|
{toolRegistry[selectedValue].name}
|
||||||
<ToolSearch
|
|
||||||
value={searchTerm}
|
|
||||||
onChange={handleSearchChange}
|
|
||||||
toolRegistry={filteredToolRegistry}
|
|
||||||
mode="filter"
|
|
||||||
placeholder={getDisplayValue()}
|
|
||||||
hideIcon={true}
|
|
||||||
onFocus={handleSearchFocus}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Menu.Target>
|
|
||||||
|
|
||||||
<Menu.Dropdown p={0} style={{ minWidth: '16rem' }}>
|
|
||||||
<ScrollArea h={350}>
|
|
||||||
<Stack gap="sm" p="sm">
|
|
||||||
{displayGroups.length === 0 ? (
|
|
||||||
<Text size="sm" c="dimmed" ta="center" p="md">
|
|
||||||
{isSearching
|
|
||||||
? t('tools.noSearchResults', 'No tools found')
|
|
||||||
: t('tools.noTools', 'No tools available')
|
|
||||||
}
|
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
</div>
|
||||||
renderedTools
|
</div>
|
||||||
)}
|
) : (
|
||||||
</Stack>
|
// Show search input when no tool selected OR when dropdown is opened
|
||||||
</ScrollArea>
|
<ToolSearch
|
||||||
</Menu.Dropdown>
|
value={searchTerm}
|
||||||
</Menu>
|
onChange={handleSearchChange}
|
||||||
|
toolRegistry={filteredToolRegistry}
|
||||||
|
mode="filter"
|
||||||
|
placeholder={getDisplayValue()}
|
||||||
|
hideIcon={true}
|
||||||
|
onFocus={handleInputFocus}
|
||||||
|
autoFocus={shouldAutoFocus}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Custom dropdown */}
|
||||||
|
{opened && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '100%',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
zIndex: 1000,
|
||||||
|
backgroundColor: 'var(--mantine-color-body)',
|
||||||
|
border: '1px solid var(--mantine-color-gray-3)',
|
||||||
|
borderRadius: 'var(--mantine-radius-sm)',
|
||||||
|
boxShadow: 'var(--mantine-shadow-sm)',
|
||||||
|
marginTop: '4px',
|
||||||
|
minWidth: '16rem'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ScrollArea h={350}>
|
||||||
|
<Stack gap="sm" p="sm">
|
||||||
|
{displayGroups.length === 0 ? (
|
||||||
|
<Text size="sm" c="dimmed" ta="center" p="md">
|
||||||
|
{isSearching
|
||||||
|
? t('tools.noSearchResults', 'No tools found')
|
||||||
|
: t('tools.noTools', 'No tools available')
|
||||||
|
}
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
renderedTools
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</ScrollArea>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ interface ToolSearchProps {
|
|||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
hideIcon?: boolean;
|
hideIcon?: boolean;
|
||||||
onFocus?: () => void;
|
onFocus?: () => void;
|
||||||
|
autoFocus?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ToolSearch = ({
|
const ToolSearch = ({
|
||||||
@ -26,7 +27,8 @@ const ToolSearch = ({
|
|||||||
selectedToolKey,
|
selectedToolKey,
|
||||||
placeholder,
|
placeholder,
|
||||||
hideIcon = false,
|
hideIcon = false,
|
||||||
onFocus
|
onFocus,
|
||||||
|
autoFocus = false
|
||||||
}: ToolSearchProps) => {
|
}: ToolSearchProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||||
@ -67,6 +69,15 @@ const ToolSearch = ({
|
|||||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Auto-focus the input when requested
|
||||||
|
useEffect(() => {
|
||||||
|
if (autoFocus && searchRef.current) {
|
||||||
|
setTimeout(() => {
|
||||||
|
searchRef.current?.focus();
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
}, [autoFocus]);
|
||||||
|
|
||||||
const searchInput = (
|
const searchInput = (
|
||||||
<div className="search-input-container">
|
<div className="search-input-container">
|
||||||
<TextInput
|
<TextInput
|
||||||
@ -76,7 +87,7 @@ const ToolSearch = ({
|
|||||||
placeholder={placeholder || t("toolPicker.searchPlaceholder", "Search tools...")}
|
placeholder={placeholder || t("toolPicker.searchPlaceholder", "Search tools...")}
|
||||||
icon={hideIcon ? undefined : <span className="material-symbols-rounded">search</span>}
|
icon={hideIcon ? undefined : <span className="material-symbols-rounded">search</span>}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
onFocus={onFocus}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user