styling looks right but this will cause massive conflicts, going to merge this with main once all the other in-flight PRs are finished before I continue with it further

This commit is contained in:
EthanHealy01 2025-08-08 01:05:28 +01:00
parent 546bfe408a
commit ccc0a1a1ec
6 changed files with 492 additions and 215 deletions

View File

@ -1,252 +1,303 @@
import React, { useState, useMemo } from "react"; import React, { useState, useMemo, useRef, useLayoutEffect } from "react";
import { Box, Text, Stack, Button, TextInput, Group, Tooltip, Collapse, ActionIcon } from "@mantine/core"; import { Box, Text, Stack } from "@mantine/core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import { baseToolRegistry, type ToolRegistryEntry } from "../../data/toolRegistry";
import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import ToolSearch from "./toolPicker/ToolSearch";
import SearchIcon from "@mui/icons-material/Search"; import ToolButton from "./toolPicker/ToolButton";
import { baseToolRegistry } from "../../data/toolRegistry"; import "./toolPicker/ToolPicker.css";
import "./ToolPicker.css";
type Tool = {
icon: React.ReactNode;
name: string;
description: string;
};
type ToolRegistry = {
[id: string]: Tool;
};
interface ToolPickerProps { interface ToolPickerProps {
selectedToolKey: string | null; selectedToolKey: string | null;
onSelect: (id: string) => void; onSelect: (id: string) => void;
toolRegistry: ToolRegistry; toolRegistry: Readonly<Record<string, ToolRegistryEntry>>;
} }
interface GroupedTools { interface GroupedTools {
[category: string]: { [category: string]: {
[subcategory: string]: Array<{ id: string; tool: Tool }>; [subcategory: string]: Array<{ id: string; tool: ToolRegistryEntry }>;
}; };
} }
const ToolPicker = ({ selectedToolKey, onSelect, toolRegistry }: ToolPickerProps) => { const ToolPicker = ({ selectedToolKey, onSelect, toolRegistry }: ToolPickerProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [search, setSearch] = useState("");
const [expandedCategories, setExpandedCategories] = useState<Set<string>>(new Set());
// Group tools by category and subcategory in a single pass - O(n) const [search, setSearch] = useState("");
const [quickHeaderHeight, setQuickHeaderHeight] = useState(0);
const [allHeaderHeight, setAllHeaderHeight] = useState(0);
const scrollableRef = useRef<HTMLDivElement>(null);
const quickHeaderRef = useRef<HTMLDivElement>(null);
const allHeaderRef = useRef<HTMLDivElement>(null);
const quickAccessRef = useRef<HTMLDivElement>(null);
const allToolsRef = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
const update = () => {
if (quickHeaderRef.current) {
setQuickHeaderHeight(quickHeaderRef.current.offsetHeight);
}
if (allHeaderRef.current) {
setAllHeaderHeight(allHeaderRef.current.offsetHeight);
}
};
update();
window.addEventListener("resize", update);
return () => window.removeEventListener("resize", update);
}, []);
const groupedTools = useMemo(() => { const groupedTools = useMemo(() => {
const grouped: GroupedTools = {}; const grouped: GroupedTools = {};
Object.entries(toolRegistry).forEach(([id, tool]) => { Object.entries(toolRegistry).forEach(([id, tool]) => {
// Get category and subcategory from the base registry
const baseTool = baseToolRegistry[id as keyof typeof baseToolRegistry]; const baseTool = baseToolRegistry[id as keyof typeof baseToolRegistry];
const category = baseTool?.category || "Other"; const category = baseTool?.category || "OTHER";
const subcategory = baseTool?.subcategory || "General"; const subcategory = baseTool?.subcategory || "General";
if (!grouped[category]) grouped[category] = {};
if (!grouped[category]) { if (!grouped[category][subcategory]) grouped[category][subcategory] = [];
grouped[category] = {};
}
if (!grouped[category][subcategory]) {
grouped[category][subcategory] = [];
}
grouped[category][subcategory].push({ id, tool }); grouped[category][subcategory].push({ id, tool });
}); });
return grouped; return grouped;
}, [toolRegistry]); }, [toolRegistry]);
// Sort categories in custom order and subcategories alphabetically - O(c * s * log(s)) const sections = useMemo(() => {
const sortedCategories = useMemo(() => { const mapping: Record<string, "QUICK ACCESS" | "ALL TOOLS"> = {
const categoryOrder = ['RECOMMENDED TOOLS', 'STANDARD TOOLS', 'ADVANCED TOOLS']; "RECOMMENDED TOOLS": "QUICK ACCESS",
"STANDARD TOOLS": "ALL TOOLS",
"ADVANCED TOOLS": "ALL TOOLS"
};
const quick: Record<string, Array<{ id: string; tool: ToolRegistryEntry }>> = {};
const all: Record<string, Array<{ id: string; tool: ToolRegistryEntry }>> = {};
Object.entries(groupedTools).forEach(([origCat, subs]) => {
const bucket = mapping[origCat.toUpperCase()] || "ALL TOOLS";
const target = bucket === "QUICK ACCESS" ? quick : all;
Object.entries(subs).forEach(([sub, tools]) => {
if (!target[sub]) target[sub] = [];
target[sub].push(...tools);
});
});
return Object.entries(groupedTools) const sortSubs = (obj: Record<string, Array<{ id: string; tool: ToolRegistryEntry }>>) =>
.map(([category, subcategories]) => ({ Object.entries(obj)
category, .sort(([a], [b]) => a.localeCompare(b))
subcategories: Object.entries(subcategories)
.sort(([a], [b]) => a.localeCompare(b)) // Sort subcategories alphabetically
.map(([subcategory, tools]) => ({ .map(([subcategory, tools]) => ({
subcategory, subcategory,
tools: tools.sort((a, b) => a.tool.name.localeCompare(b.tool.name)) // Sort tools alphabetically tools: tools.sort((a, b) => a.tool.name.localeCompare(b.tool.name))
})) }));
}))
.sort((a, b) => {
const aIndex = categoryOrder.indexOf(a.category.toUpperCase());
const bIndex = categoryOrder.indexOf(b.category.toUpperCase());
return aIndex - bIndex;
});
}, [groupedTools, t]);
// Filter tools based on search - O(n) return [
const filteredCategories = useMemo(() => { { title: "QUICK ACCESS", ref: quickAccessRef, subcategories: sortSubs(quick) },
if (!search.trim()) return sortedCategories; { title: "ALL TOOLS", ref: allToolsRef, subcategories: sortSubs(all) }
];
}, [groupedTools]);
return sortedCategories.map(({ category, subcategories }) => ({ const visibleSections = useMemo(() => {
category, if (!search.trim()) return sections;
subcategories: subcategories.map(({ subcategory, tools }) => ({ const term = search.toLowerCase();
subcategory, return sections
tools: tools.filter(({ tool }) => .map(s => ({
tool.name.toLowerCase().includes(search.toLowerCase()) || ...s,
tool.description.toLowerCase().includes(search.toLowerCase()) subcategories: s.subcategories
.map(sc => ({
...sc,
tools: sc.tools.filter(({ tool }) =>
tool.name.toLowerCase().includes(term) ||
tool.description.toLowerCase().includes(term)
) )
})).filter(({ tools }) => tools.length > 0) }))
})).filter(({ subcategories }) => subcategories.length > 0); .filter(sc => sc.tools.length)
}, [sortedCategories, search, t]); }))
.filter(s => s.subcategories.length);
}, [sections, search]);
const toggleCategory = (category: string) => { const quickSection = useMemo(
setExpandedCategories(prev => { () => visibleSections.find(s => s.title === "QUICK ACCESS"),
const newSet = new Set(prev); [visibleSections]
if (newSet.has(category)) { );
newSet.delete(category); const allSection = useMemo(
} else { () => visibleSections.find(s => s.title === "ALL TOOLS"),
newSet.add(category); [visibleSections]
}
return newSet;
});
};
const renderToolButton = (id: string, tool: Tool, index: number) => (
<Tooltip
key={id}
label={tool.description}
position="right"
withArrow
openDelay={500}
>
<Button
variant={selectedToolKey === id ? "filled" : "subtle"}
onClick={() => onSelect(id)}
size="md"
radius="md"
leftSection={
<div style={{ color: 'var(--tools-text-and-icon-color)' }}>
{tool.icon}
</div>
}
fullWidth
justify="flex-start"
style={{
borderRadius: '0',
color: 'var(--tools-text-and-icon-color)'
}}
>
<span style={{
marginRight: '8px',
opacity: 0.6,
fontSize: '0.8em',
color: 'var(--tools-text-and-icon-color)'
}}>
{index + 1}.
</span>
{tool.name}
</Button>
</Tooltip>
); );
const scrollTo = (ref: React.RefObject<HTMLDivElement | null>) => {
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"
});
}
};
return ( return (
<Box style={{
height: '100%',
display: 'flex',
flexDirection: 'column',
backgroundColor: 'var(--bg-toolbar)',
padding: '0'
}}>
<TextInput
placeholder={t("toolPicker.searchPlaceholder", "Search tools...")}
value={search}
radius="md"
onChange={(e) => setSearch(e.currentTarget.value)}
autoComplete="off"
className="search-input rounded-lg"
leftSection={<SearchIcon sx={{ fontSize: 16, color: 'var(--tools-text-and-icon-color)' }} />}
/>
<Box <Box
className="tool-picker-scrollable" h="100vh"
style={{
display: "flex",
flexDirection: "column",
background: "var(--bg-toolbar)"
}}
>
<ToolSearch value={search} onChange={setSearch} toolRegistry={toolRegistry} mode="filter" />
<Box
ref={scrollableRef}
style={{ style={{
flex: 1, flex: 1,
overflowY: 'auto', overflowY: "auto",
overflowX: 'hidden', overflowX: "hidden",
minHeight: 0, minHeight: 0,
maxHeight: 'calc(100vh - 200px)' height: "calc(100vh - 120px)"
}} }}
className="tool-picker-scrollable"
> >
<Stack align="flex-start" gap="xs"> {quickSection && (
{filteredCategories.length === 0 ? ( <>
<Text c="dimmed" size="sm"> <div
{t("toolPicker.noToolsFound", "No tools found")} ref={quickHeaderRef}
</Text>
) : (
filteredCategories.map(({ category, subcategories }) => (
<Box key={category} style={{ width: '100%' }}>
{/* Category Header */}
<Button
variant="subtle"
onClick={() => toggleCategory(category)}
rightSection={
<div style={{
transition: 'transform 0.2s ease',
transform: expandedCategories.has(category) ? 'rotate(90deg)' : 'rotate(0deg)'
}}>
<ChevronRightIcon sx={{ fontSize: 16, color: 'var(--tools-text-and-icon-color)' }} />
</div>
}
fullWidth
justify="space-between"
style={{ style={{
fontWeight: 'bold', position: "sticky",
backgroundColor: 'var(--bg-toolbar)', top: 0,
marginBottom: '0', zIndex: 2,
borderTop: '1px solid var(--border-default)', borderTop: `1px solid var(--tool-header-border)`,
borderBottom: '1px solid var(--border-default)', borderBottom: `1px solid var(--tool-header-border)`,
borderRadius: '0', marginBottom: -1,
padding: '0.75rem 1rem', padding: "0.5rem 1rem",
color: 'var(--tools-text-and-icon-color)' fontWeight: 700,
background: "var(--tool-header-bg)",
color: "var(--tool-header-text)",
cursor: "pointer",
display: "flex",
alignItems: "center",
justifyContent: "space-between"
}}
onClick={() => scrollTo(quickAccessRef)}
>
<span>QUICK ACCESS</span>
<span
style={{
background: "var(--tool-header-badge-bg)",
color: "var(--tool-header-badge-text)",
borderRadius: 8,
padding: "2px 8px",
fontSize: 12,
fontWeight: 700
}} }}
> >
{category.toUpperCase()} {quickSection.subcategories.reduce((acc, sc) => acc + sc.tools.length, 0)}
</Button> </span>
</div>
{/* Subcategories */} <Box ref={quickAccessRef} w="100%">
<Collapse in={expandedCategories.has(category)}> <Stack p="sm" gap="xs">
<Stack gap="xs" style={{ paddingLeft: '1rem', paddingRight: '1rem' }}> {quickSection.subcategories.map(sc => (
{subcategories.map(({ subcategory, tools }) => ( <Box key={sc.subcategory} w="100%">
<Box key={subcategory}> {quickSection.subcategories.length > 1 && (
{/* Subcategory Header (only show if there are multiple subcategories) */}
{subcategories.length > 1 && (
<Text <Text
size="sm" size="sm"
fw={500} fw={500}
style={{ mb="0.25rem"
marginBottom: '4px', mt="1rem"
textTransform: 'uppercase', className="tool-subcategory-title"
fontSize: '0.75rem',
borderBottom: '1px solid var(--border-default)',
paddingBottom: '0.5rem',
marginLeft: '1rem',
marginRight: '1rem',
color: 'var(--tools-text-and-icon-color)'
}}
> >
{subcategory} {sc.subcategory}
</Text> </Text>
)} )}
{/* Tools in this subcategory */}
<Stack gap="xs"> <Stack gap="xs">
{tools.map(({ id, tool }, index) => {sc.tools.map(({ id, tool }) => (
renderToolButton(id, tool, index) <ToolButton
)} key={id}
id={id}
tool={tool}
isSelected={selectedToolKey === id}
onSelect={onSelect}
/>
))}
</Stack> </Stack>
</Box> </Box>
))} ))}
</Stack> </Stack>
</Collapse>
</Box> </Box>
)) </>
)} )}
{allSection && (
<>
<div
ref={allHeaderRef}
style={{
position: "sticky",
top: quickSection ? quickHeaderHeight - 1: 0,
zIndex: 2,
borderTop: `1px solid var(--tool-header-border)`,
borderBottom: `1px solid var(--tool-header-border)`,
padding: "0.5rem 1rem",
fontWeight: 700,
background: "var(--tool-header-bg)",
color: "var(--tool-header-text)",
cursor: "pointer",
display: "flex",
alignItems: "center",
justifyContent: "space-between"
}}
onClick={() => scrollTo(allToolsRef)}
>
<span>ALL TOOLS</span>
<span
style={{
background: "var(--tool-header-badge-bg)",
color: "var(--tool-header-badge-text)",
borderRadius: 8,
padding: "2px 8px",
fontSize: 12,
fontWeight: 700
}}
>
{allSection.subcategories.reduce((acc, sc) => acc + sc.tools.length, 0)}
</span>
</div>
<Box ref={allToolsRef} w="100%">
<Stack p="sm" gap="xs">
{allSection.subcategories.map(sc => (
<Box key={sc.subcategory} w="100%">
{allSection.subcategories.length > 1 && (
<Text
size="sm"
fw={500}
mb="0.25rem"
mt="1rem"
className="tool-subcategory-title"
>
{sc.subcategory}
</Text>
)}
<Stack gap="xs">
{sc.tools.map(({ id, tool }) => (
<ToolButton
key={id}
id={id}
tool={tool}
isSelected={selectedToolKey === id}
onSelect={onSelect}
/>
))}
</Stack> </Stack>
</Box> </Box>
))}
</Stack>
</Box>
</>
)}
{!quickSection && !allSection && (
<Text c="dimmed" size="sm" p="sm">
{t("toolPicker.noToolsFound", "No tools found")}
</Text>
)}
</Box>
</Box> </Box>
); );
}; };

View File

@ -0,0 +1,32 @@
import React from "react";
import { Button, Tooltip } from "@mantine/core";
import { type ToolRegistryEntry } from "../../../data/toolRegistry";
interface ToolButtonProps {
id: string;
tool: ToolRegistryEntry;
isSelected: boolean;
onSelect: (id: string) => void;
}
const ToolButton: React.FC<ToolButtonProps> = ({ id, tool, isSelected, onSelect }) => {
return (
<Tooltip key={id} label={tool.description} withArrow openDelay={500}>
<Button
variant={isSelected ? "filled" : "subtle"}
onClick={() => onSelect(id)}
size="md"
radius="md"
leftSection={<div className="tool-button-icon" style={{ color: "var(--tools-text-and-icon-color)" }}>{tool.icon}</div>}
fullWidth
justify="flex-start"
className="tool-button"
styles={{ root: { borderRadius: 0, color: "var(--tools-text-and-icon-color)" } }}
>
{tool.name}
</Button>
</Tooltip>
);
};
export default ToolButton;

View File

@ -25,3 +25,28 @@
.search-input { .search-input {
margin: 1rem; margin: 1rem;
} }
.tool-subcategory-title {
text-transform: uppercase;
padding-bottom: 0.5rem;
font-size: 0.75rem;
color: var(--tool-subcategory-text-color);
/* Align the text with tool labels to account for icon gutter */
padding-left: 1rem;
}
/* Compact tool buttons */
.tool-button {
font-size: 0.875rem; /* default 1rem - 0.125rem? We'll apply exact -0.25rem via calc below */
padding-top: 0.375rem;
padding-bottom: 0.375rem;
}
.tool-button .mantine-Button-label {
font-size: .85rem;
}
.tool-button-icon {
font-size: 1rem;
line-height: 1;
}

View File

@ -0,0 +1,128 @@
import React, { useState, useRef, useEffect, useMemo } from "react";
import { TextInput, Stack, Button, Text } from "@mantine/core";
import { useTranslation } from "react-i18next";
import SearchIcon from "@mui/icons-material/Search";
import { type ToolRegistryEntry } from "../../../data/toolRegistry";
interface ToolSearchProps {
value: string;
onChange: (value: string) => void;
toolRegistry: Readonly<Record<string, ToolRegistryEntry>>;
onToolSelect?: (toolId: string) => void;
mode: 'filter' | 'dropdown';
selectedToolKey?: string | null;
}
const ToolSearch = ({
value,
onChange,
toolRegistry,
onToolSelect,
mode = 'filter',
selectedToolKey
}: ToolSearchProps) => {
const { t } = useTranslation();
const [dropdownOpen, setDropdownOpen] = useState(false);
const searchRef = useRef<HTMLInputElement>(null);
const filteredTools = useMemo(() => {
if (!value.trim()) return [];
return Object.entries(toolRegistry)
.filter(([id, tool]) => {
if (mode === 'dropdown' && id === selectedToolKey) return false;
return tool.name.toLowerCase().includes(value.toLowerCase()) ||
tool.description.toLowerCase().includes(value.toLowerCase());
})
.slice(0, 6)
.map(([id, tool]) => ({ id, tool }));
}, [value, toolRegistry, mode, selectedToolKey]);
const handleSearchChange = (searchValue: string) => {
onChange(searchValue);
if (mode === 'dropdown') {
setDropdownOpen(searchValue.trim().length > 0 && filteredTools.length > 0);
}
};
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (searchRef.current && !searchRef.current.contains(event.target as Node)) {
setDropdownOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
const searchInput = (
<TextInput
ref={searchRef}
placeholder={t("toolPicker.searchPlaceholder", "Search tools...")}
value={value}
radius="md"
onChange={(e) => handleSearchChange(e.currentTarget.value)}
autoComplete="off"
className="search-input rounded-lg"
leftSection={<SearchIcon sx={{ fontSize: 16, color: 'var(--tools-text-and-icon-color)' }} />}
/>
);
if (mode === 'filter') {
return searchInput;
}
return (
<div ref={searchRef} style={{ position: 'relative' }}>
{searchInput}
{dropdownOpen && filteredTools.length > 0 && (
<div
style={{
position: 'absolute',
top: '100%',
left: 0,
right: 0,
zIndex: 1000,
backgroundColor: 'var(--bg-toolbar)',
border: '1px solid var(--border-default)',
borderRadius: '8px',
marginTop: '4px',
maxHeight: '300px',
overflowY: 'auto',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)'
}}
>
<Stack gap="xs" style={{ padding: '8px' }}>
{filteredTools.map(({ id, tool }) => (
<Button
key={id}
variant="subtle"
onClick={() => onToolSelect && onToolSelect(id)}
leftSection={
<div style={{ color: 'var(--tools-text-and-icon-color)' }}>
{tool.icon}
</div>
}
fullWidth
justify="flex-start"
style={{
borderRadius: '6px',
color: 'var(--tools-text-and-icon-color)',
padding: '8px 12px'
}}
>
<div style={{ textAlign: 'left' }}>
<div style={{ fontWeight: 500 }}>{tool.name}</div>
<Text size="xs" c="dimmed" style={{ marginTop: '2px' }}>
{tool.description}
</Text>
</div>
</Button>
))}
</Stack>
</div>
)}
</div>
);
};
export default ToolSearch;

View File

@ -10,6 +10,7 @@ import { PageEditorFunctions } from "../types/pageEditor";
import rainbowStyles from '../styles/rainbow.module.css'; import rainbowStyles from '../styles/rainbow.module.css';
import ToolPicker from "../components/tools/ToolPicker"; import ToolPicker from "../components/tools/ToolPicker";
import ToolSearch from "../components/tools/toolPicker/ToolSearch";
import TopControls from "../components/shared/TopControls"; import TopControls from "../components/shared/TopControls";
import FileEditor from "../components/fileEditor/FileEditor"; import FileEditor from "../components/fileEditor/FileEditor";
import PageEditor from "../components/pageEditor/PageEditor"; import PageEditor from "../components/pageEditor/PageEditor";
@ -29,6 +30,10 @@ function HomePageContent() {
const { setMaxFiles, setIsToolMode, setSelectedFiles } = useFileSelection(); const { setMaxFiles, setIsToolMode, setSelectedFiles } = useFileSelection();
const { addToActiveFiles } = useFileHandler(); const { addToActiveFiles } = useFileHandler();
const [sidebarsVisible, setSidebarsVisible] = useState(true);
const [leftPanelView, setLeftPanelView] = useState<'toolPicker' | 'toolContent'>('toolPicker');
const [readerMode, setReaderMode] = useState(false);
const { const {
selectedToolKey, selectedToolKey,
selectedTool, selectedTool,
@ -37,11 +42,9 @@ function HomePageContent() {
clearToolSelection, clearToolSelection,
} = useToolManagement(); } = useToolManagement();
const [sidebarsVisible, setSidebarsVisible] = useState(true);
const [leftPanelView, setLeftPanelView] = useState<'toolPicker' | 'toolContent'>('toolPicker');
const [readerMode, setReaderMode] = useState(false);
const [pageEditorFunctions, setPageEditorFunctions] = useState<PageEditorFunctions | null>(null); const [pageEditorFunctions, setPageEditorFunctions] = useState<PageEditorFunctions | null>(null);
const [previewFile, setPreviewFile] = useState<File | null>(null); const [previewFile, setPreviewFile] = useState<File | null>(null);
const [toolSearch, setToolSearch] = useState("");
// Update file selection context when tool changes // Update file selection context when tool changes
useEffect(() => { useEffect(() => {
@ -81,7 +84,13 @@ function HomePageContent() {
setCurrentView(view as any); setCurrentView(view as any);
}, [setCurrentView]); }, [setCurrentView]);
const handleToolSearchSelect = useCallback((toolId: string) => {
selectTool(toolId);
setCurrentView('fileEditor');
setLeftPanelView('toolContent');
setReaderMode(false);
setToolSearch(''); // Clear search after selection
}, [selectTool, setCurrentView]);
return ( return (
@ -129,8 +138,20 @@ function HomePageContent() {
) : ( ) : (
// Selected Tool Content View // Selected Tool Content View
<div className="flex-1 flex flex-col"> <div className="flex-1 flex flex-col">
{/* Search bar for quick tool switching */}
<div className="mb-4 border-b-1 border-b-[var(--border-default)] mb-4" >
<ToolSearch
value={toolSearch}
onChange={setToolSearch}
toolRegistry={toolRegistry}
onToolSelect={handleToolSearchSelect}
mode="dropdown"
selectedToolKey={selectedToolKey}
/>
</div>
{/* Back button */} {/* Back button */}
<div className="mb-4" style={{ padding: '0 1rem' }}> <div className="mb-4" style={{ padding: '0 1rem', marginTop: '1rem'}}>
<Button <Button
variant="subtle" variant="subtle"
size="sm" size="sm"

View File

@ -109,6 +109,16 @@
/* New theme colors for text and icons */ /* New theme colors for text and icons */
--tools-text-and-icon-color: #374151; --tools-text-and-icon-color: #374151;
/* Tool picker sticky header variables (light mode) */
--tool-header-bg: #DBEFFF;
--tool-header-border: #BEE2FF;
--tool-header-text: #1E88E5;
--tool-header-badge-bg: #C0DDFF;
--tool-header-badge-text: #004E99;
/* Subcategory title styling (light mode) */
--tool-subcategory-text-color: #6B7280;
} }
[data-mantine-color-scheme="dark"] { [data-mantine-color-scheme="dark"] {
@ -188,6 +198,16 @@
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.4); --shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.4);
--tools-text-and-icon-color: #D0D6DC; --tools-text-and-icon-color: #D0D6DC;
/* Tool picker sticky header variables (dark mode) */
--tool-header-bg: #2A2F36;
--tool-header-border: #3A4047;
--tool-header-text: #D0D6DC;
--tool-header-badge-bg: #4B525A;
--tool-header-badge-text: #FFFFFF;
/* Subcategory title styling (dark mode) */
--tool-subcategory-text-color: #6B7280;
} }
/* Smooth transitions for theme switching */ /* Smooth transitions for theme switching */