feat: integrate fullscreen catalog into tool panel

This commit is contained in:
Anthony Stirling 2025-10-04 21:26:36 +01:00
parent 130168e171
commit c200e2a189
6 changed files with 318 additions and 337 deletions

View File

@ -1,3 +1,11 @@
.tool-panel {
transition: width 0.35s ease, flex 0.35s ease;
}
.tool-panel--catalog {
background: var(--bg-background);
}
.tool-panel__search-row {
display: flex;
align-items: center;
@ -17,10 +25,6 @@
transform: scale(1.05);
}
.tool-panel--overlay-hidden {
visibility: hidden;
}
.tool-panel__overlay-hint {
padding: 0.5rem 1rem;
border-bottom: 1px solid var(--border-subtle);

View File

@ -4,6 +4,7 @@ import ToolPicker from './ToolPicker';
import SearchResults from './SearchResults';
import ToolRenderer from './ToolRenderer';
import ToolSearch from './toolPicker/ToolSearch';
import ToolPanelOverlay from './ToolPanelOverlay';
import { useSidebarContext } from "../../contexts/SidebarContext";
import rainbowStyles from '../../styles/rainbow.module.css';
import { ActionIcon, Group, ScrollArea, Text, Tooltip } from '@mantine/core';
@ -38,7 +39,7 @@ export default function ToolPanel() {
} = useToolWorkflow();
const isFullscreenMode = toolPanelMode === 'fullscreen';
const overlayActive = isFullscreenMode && leftPanelView === 'toolPicker' && !isMobile;
const isCatalogActive = isFullscreenMode && leftPanelView === 'toolPicker' && !isMobile;
const toggleLabel = isFullscreenMode
? t('toolPanel.modeToggle.sidebar', 'Switch to advanced sidebar')
@ -48,33 +49,42 @@ export default function ToolPanel() {
setToolPanelMode(isFullscreenMode ? 'sidebar' : 'fullscreen');
};
const computedWidth = () => {
if (isMobile) {
return '100%';
}
if (isFullscreenMode) {
if (isCatalogActive) {
return '100%';
}
if (leftPanelView === 'toolContent' && isPanelVisible) {
return '20rem';
}
return isPanelVisible ? '20rem' : '0';
}
return isPanelVisible ? '18.5rem' : '0';
};
return (
<div
ref={toolPanelRef}
data-sidebar="tool-panel"
className={`flex flex-col overflow-hidden bg-[var(--bg-toolbar)] border-r border-[var(--border-subtle)] transition-all duration-300 ease-out ${
className={`tool-panel flex flex-col overflow-hidden bg-[var(--bg-toolbar)] border-r border-[var(--border-subtle)] transition-all duration-300 ease-out ${
isRainbowMode ? rainbowStyles.rainbowPaper : ''
} ${isMobile ? 'h-full border-r-0' : 'h-screen'} ${overlayActive ? 'tool-panel--overlay-hidden' : ''}`}
} ${isMobile ? 'h-full border-r-0' : 'h-screen'} ${isCatalogActive ? 'tool-panel--catalog' : ''}`}
style={{
width: isMobile
? '100%'
: isFullscreenMode
? leftPanelView === 'toolContent' && isPanelVisible
? '20rem'
: overlayActive
? '0'
: '20rem'
: isPanelVisible
? '18.5rem'
: '0',
width: computedWidth(),
padding: '0',
pointerEvents: overlayActive ? 'none' : undefined,
flex: isCatalogActive ? '1 1 auto' : undefined,
}}
aria-hidden={overlayActive}
>
<div
style={{
opacity: isMobile || isPanelVisible ? 1 : 0,
opacity: isMobile || isPanelVisible || isCatalogActive ? 1 : 0,
transition: 'opacity 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)',
height: '100%',
display: 'flex',
@ -82,40 +92,42 @@ export default function ToolPanel() {
}}
>
{/* Search Bar - Always visible at the top */}
<div
className="tool-panel__search-row"
style={{
backgroundColor: 'var(--tool-panel-search-bg)',
borderBottom: '1px solid var(--tool-panel-search-border-bottom)',
}}
>
<ToolSearch
value={searchQuery}
onChange={setSearchQuery}
toolRegistry={toolRegistry}
mode="filter"
/>
{!isMobile && (
<Tooltip label={toggleLabel} position="left" withArrow>
<ActionIcon
variant="subtle"
radius="xl"
color="gray"
onClick={handleToggleMode}
aria-label={toggleLabel}
className="tool-panel__mode-toggle"
>
{isFullscreenMode ? (
<ViewSidebarRoundedIcon fontSize="small" />
) : (
<DashboardCustomizeRoundedIcon fontSize="small" />
)}
</ActionIcon>
</Tooltip>
)}
</div>
{!isCatalogActive && (
<div
className="tool-panel__search-row"
style={{
backgroundColor: 'var(--tool-panel-search-bg)',
borderBottom: '1px solid var(--tool-panel-search-border-bottom)',
}}
>
<ToolSearch
value={searchQuery}
onChange={setSearchQuery}
toolRegistry={toolRegistry}
mode="filter"
/>
{!isMobile && (
<Tooltip label={toggleLabel} position="left" withArrow>
<ActionIcon
variant="subtle"
radius="xl"
color="gray"
onClick={handleToggleMode}
aria-label={toggleLabel}
className="tool-panel__mode-toggle"
>
{isFullscreenMode ? (
<ViewSidebarRoundedIcon fontSize="small" />
) : (
<DashboardCustomizeRoundedIcon fontSize="small" />
)}
</ActionIcon>
</Tooltip>
)}
</div>
)}
{searchQuery.trim().length > 0 ? (
{searchQuery.trim().length > 0 && !isCatalogActive ? (
// Searching view (replaces both picker and content)
<div className="flex-1 flex flex-col overflow-y-auto">
<SearchResults
@ -127,21 +139,27 @@ export default function ToolPanel() {
) : leftPanelView === 'toolPicker' ? (
// Tool Picker View
<div className="flex-1 flex flex-col overflow-auto">
{isFullscreenMode && !isMobile ? (
<div className="tool-panel__overlay-hint">
<Group gap="xs" justify="center">
<Text size="sm" c="dimmed">
{t('toolPanel.overlayHint', 'Select a tool to open it in the workspace.')}
</Text>
</Group>
</div>
) : null}
<ToolPicker
selectedToolKey={selectedToolKey}
onSelect={(id) => handleToolSelect(id as ToolId)}
filteredTools={filteredTools}
isSearching={Boolean(searchQuery && searchQuery.trim().length > 0)}
/>
{isCatalogActive ? (
<ToolPanelOverlay />
) : (
<>
{isFullscreenMode && !isMobile ? (
<div className="tool-panel__overlay-hint">
<Group gap="xs" justify="center">
<Text size="sm" c="dimmed">
{t('toolPanel.overlayHint', 'Select a tool to open it in the workspace.')}
</Text>
</Group>
</div>
) : null}
<ToolPicker
selectedToolKey={selectedToolKey}
onSelect={(id) => handleToolSelect(id as ToolId)}
filteredTools={filteredTools}
isSearching={Boolean(searchQuery && searchQuery.trim().length > 0)}
/>
</>
)}
</div>
) : (
// Selected Tool Content View

View File

@ -1,61 +1,39 @@
.tool-panel-overlay {
position: fixed;
inset: 0;
z-index: 1200;
display: flex;
justify-content: center;
align-items: stretch;
background: rgba(10, 10, 10, 0.45);
backdrop-filter: blur(4px);
transition: opacity 0.32s ease, transform 0.32s ease;
opacity: 1;
}
.tool-panel-overlay--open {
transform: translateX(0);
}
.tool-panel-overlay--closing {
opacity: 0;
transform: translateX(-6%);
pointer-events: none;
}
.tool-panel-overlay__paper {
flex: 1;
max-width: 96rem;
margin: 2rem;
display: flex;
flex-direction: column;
height: 100%;
background: var(--bg-background);
border-radius: 1.5rem;
overflow: hidden;
box-shadow: var(--shadow-overlay, 0 24px 64px rgba(15, 23, 42, 0.32));
color: var(--text-primary);
}
.tool-panel-overlay__header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.75rem;
gap: 1rem;
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border-subtle);
background: var(--bg-elevated);
background: var(--bg-toolbar);
}
.tool-panel-overlay__heading {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.tool-panel-overlay__search {
display: flex;
gap: 1.25rem;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
padding: 1.25rem 1.75rem;
gap: 0.75rem;
padding: 0.75rem 1.5rem;
border-bottom: 1px solid var(--border-subtle);
background: var(--bg-toolbar);
flex-wrap: wrap;
}
.tool-panel-overlay__search-input {
flex: 1 1 22rem;
flex: 1 1 20rem;
min-width: 16rem;
}
@ -66,15 +44,10 @@
.tool-panel-overlay__search-controls {
display: flex;
align-items: center;
gap: 1rem;
gap: 0.75rem;
flex-wrap: wrap;
}
.tool-panel-overlay__layout-toggle {
display: flex;
align-items: center;
}
.tool-panel-overlay__layout-toggle .mantine-SegmentedControl-control {
font-weight: 500;
}
@ -82,7 +55,7 @@
.tool-panel-overlay__body {
flex: 1;
min-height: 0;
position: relative;
background: var(--bg-background);
}
.tool-panel-overlay__scroll {
@ -90,30 +63,28 @@
}
.tool-panel-overlay__content {
padding: 2rem;
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 2.5rem;
gap: 2rem;
}
.tool-panel-overlay__section {
display: flex;
flex-direction: column;
gap: 1.25rem;
gap: 1rem;
}
.tool-panel-overlay__section-header {
display: flex;
align-items: baseline;
gap: 0.5rem;
color: var(--text-strong);
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--tool-subcategory-text-color);
}
.tool-panel-overlay__grid {
display: grid;
gap: 1.25rem;
gap: 1rem;
}
.tool-panel-overlay__grid--compact {
@ -142,31 +113,31 @@
.tool-panel-overlay__tile {
width: 100%;
height: 100%;
background: var(--bg-elevated);
background: var(--tool-card-background, var(--bg-elevated));
border: 1px solid var(--border-subtle);
border-radius: 1.25rem;
padding: 1.25rem;
border-radius: 1rem;
padding: 1rem;
display: flex;
gap: 1rem;
gap: 0.75rem;
color: var(--text-primary);
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.tool-panel-overlay__tile[data-variant="compact"] {
flex-direction: column;
align-items: flex-start;
min-height: 10.5rem;
min-height: 10rem;
}
.tool-panel-overlay__tile[data-variant="detailed"] {
flex-direction: row;
align-items: flex-start;
min-height: 8.75rem;
min-height: 9rem;
}
.tool-panel-overlay__tile[data-selected="true"] {
border-color: var(--accent-primary, var(--mantine-color-pink-6));
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent-primary, var(--mantine-color-pink-6)) 25%, transparent);
border-color: var(--accent-primary, var(--primary-border, var(--mantine-color-pink-6)));
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent-primary, var(--mantine-color-pink-6)) 20%, transparent);
}
.tool-panel-overlay__tile[data-disabled="true"] {
@ -174,9 +145,9 @@
}
.tool-panel-overlay__tile:hover:not([data-disabled="true"]) {
transform: translateY(-4px);
border-color: color-mix(in srgb, var(--accent-primary, var(--mantine-color-pink-6)) 35%, var(--border-subtle));
box-shadow: 0 16px 32px rgba(15, 23, 42, 0.18);
transform: translateY(-2px);
border-color: color-mix(in srgb, var(--accent-primary, var(--mantine-color-pink-6)) 30%, var(--border-subtle));
box-shadow: 0 12px 24px rgba(15, 23, 42, 0.12);
}
.tool-panel-overlay__tile-icon {
@ -185,84 +156,49 @@
justify-content: center;
width: 3rem;
height: 3rem;
border-radius: 0.9rem;
background: color-mix(in srgb, var(--accent-primary, var(--mantine-color-pink-6)) 8%, transparent);
border-radius: 0.75rem;
background: color-mix(in srgb, var(--accent-primary, var(--mantine-color-pink-6)) 10%, transparent);
color: var(--tools-text-and-icon-color);
flex-shrink: 0;
}
.tool-panel-overlay__tile-icon svg,
.tool-panel-overlay__tile-icon span {
font-size: 1.9rem;
font-size: 1.8rem;
}
.tool-panel-overlay__tile-body {
display: flex;
flex-direction: column;
gap: 0.35rem;
gap: 0.4rem;
}
.tool-panel-overlay__tile-name {
font-size: 1rem;
line-height: 1.35;
color: var(--text-primary);
}
.tool-panel-overlay__tile-description {
line-height: 1.45;
line-height: 1.4;
}
.tool-panel-overlay__tile-match {
font-size: 0.75rem;
letter-spacing: 0.02em;
text-transform: uppercase;
font-style: italic;
}
.tool-panel-overlay__tile-hotkey {
display: flex;
align-items: center;
gap: 0.5rem;
gap: 0.35rem;
font-size: 0.75rem;
color: var(--mantine-color-dimmed);
color: var(--text-muted);
}
.tool-panel-overlay__empty {
display: flex;
align-items: center;
justify-content: center;
min-height: 12rem;
border: 1px dashed var(--border-subtle);
border-radius: 1.25rem;
background: rgba(148, 163, 184, 0.04);
}
@media (max-width: 1200px) {
.tool-panel-overlay__paper {
margin: 1.5rem;
}
@media (max-width: 1280px) {
.tool-panel-overlay__content {
padding: 1.5rem;
}
}
@media (max-width: 1024px) {
.tool-panel-overlay__paper {
margin: 0;
border-radius: 0;
}
.tool-panel-overlay__header,
.tool-panel-overlay__search {
padding: 1.25rem;
}
}
@media (max-width: 640px) {
.tool-panel-overlay__grid--compact {
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
}
.tool-panel-overlay__grid--detailed {
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
.tool-panel-overlay__search {
padding-inline: 1.25rem;
}
}

View File

@ -1,5 +1,5 @@
import { useEffect, useMemo, useState } from 'react';
import { ActionIcon, Badge, Group, Paper, ScrollArea, SegmentedControl, Text, Tooltip } from '@mantine/core';
import { ActionIcon, Badge, Group, ScrollArea, SegmentedControl, Text, Tooltip } from '@mantine/core';
import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
import ViewSidebarRoundedIcon from '@mui/icons-material/ViewSidebarRounded';
import DashboardCustomizeRoundedIcon from '@mui/icons-material/DashboardCustomizeRounded';
@ -15,22 +15,17 @@ import './ToolPanelOverlay.css';
type LayoutVariant = 'compact' | 'detailed';
interface ToolPanelOverlayProps {
isOpen: boolean;
}
const EXIT_ANIMATION_MS = 320;
const LAYOUT_STORAGE_KEY = 'toolPanelOverlayLayout';
const getInitialLayout = (): LayoutVariant => {
if (typeof window === 'undefined') {
return 'compact';
return 'detailed';
}
const stored = window.localStorage.getItem(LAYOUT_STORAGE_KEY);
return stored === 'detailed' ? 'detailed' : 'compact';
return stored === 'compact' ? 'compact' : 'detailed';
};
export default function ToolPanelOverlay({ isOpen }: ToolPanelOverlayProps) {
export default function ToolPanelOverlay() {
const { t } = useTranslation();
const {
searchQuery,
@ -44,8 +39,6 @@ export default function ToolPanelOverlay({ isOpen }: ToolPanelOverlayProps) {
setLeftPanelView,
} = useToolWorkflow();
const [shouldRender, setShouldRender] = useState(isOpen);
const [isClosing, setIsClosing] = useState(false);
const [layout, setLayout] = useState<LayoutVariant>(getInitialLayout);
const { sections, searchGroups } = useToolSections(filteredTools, searchQuery);
@ -55,38 +48,6 @@ export default function ToolPanelOverlay({ isOpen }: ToolPanelOverlayProps) {
window.localStorage.setItem(LAYOUT_STORAGE_KEY, layout);
}, [layout]);
useEffect(() => {
if (isOpen) {
setShouldRender(true);
setIsClosing(false);
document.documentElement.style.setProperty('overflow', 'hidden');
return;
}
if (shouldRender) {
setIsClosing(true);
const timeout = window.setTimeout(() => {
setShouldRender(false);
setIsClosing(false);
document.documentElement.style.removeProperty('overflow');
}, EXIT_ANIMATION_MS);
return () => {
window.clearTimeout(timeout);
document.documentElement.style.removeProperty('overflow');
};
}
document.documentElement.style.removeProperty('overflow');
setShouldRender(false);
}, [isOpen, shouldRender]);
useEffect(() => {
if (!isOpen) return;
return () => {
document.documentElement.style.removeProperty('overflow');
};
}, [isOpen]);
const showSearchResults = useMemo(() => searchQuery.trim().length > 0, [searchQuery]);
const totalToolCount = showSearchResults ? filteredTools.length : Object.keys(toolRegistry).length;
@ -108,13 +69,9 @@ export default function ToolPanelOverlay({ isOpen }: ToolPanelOverlayProps) {
return allSection ? allSection.subcategories : [];
}, [searchGroups, sections, showSearchResults]);
if (!shouldRender) {
return null;
}
const handleClose = () => {
setSearchQuery('');
setLeftPanelView('hidden');
setLeftPanelView(selectedToolKey ? 'toolContent' : 'hidden');
};
const toggleLabel = toolPanelMode === 'fullscreen'
@ -125,118 +82,126 @@ export default function ToolPanelOverlay({ isOpen }: ToolPanelOverlayProps) {
return (
<div
className={`tool-panel-overlay ${isClosing || !isOpen ? 'tool-panel-overlay--closing' : 'tool-panel-overlay--open'}`}
role="dialog"
aria-modal
className="tool-panel-overlay"
role="region"
aria-label={t('toolPanel.overlay.title', 'All tools')}
>
<Paper shadow="xl" radius={0} className="tool-panel-overlay__paper">
<header className="tool-panel-overlay__header">
<div>
<Text fw={600} size="lg">
{t('toolPanel.overlay.title', 'All tools')}
</Text>
<Text size="sm" c="dimmed">
{t('toolPanel.overlay.subtitle', 'Browse every tool in the legacy fullscreen catalog.')}
</Text>
</div>
<Group gap="xs">
<Tooltip label={toggleLabel} position="bottom" withArrow>
<ActionIcon
variant="subtle"
radius="xl"
size="lg"
onClick={() => setToolPanelMode(toolPanelMode === 'fullscreen' ? 'sidebar' : 'fullscreen')}
aria-label={toggleLabel}
>
{toolPanelMode === 'fullscreen' ? (
<ViewSidebarRoundedIcon fontSize="small" />
) : (
<DashboardCustomizeRoundedIcon fontSize="small" />
)}
</ActionIcon>
</Tooltip>
<Tooltip label={t('toolPanel.overlay.close', 'Close')} position="bottom" withArrow>
<ActionIcon
variant="subtle"
radius="xl"
size="lg"
onClick={handleClose}
aria-label={t('toolPanel.overlay.close', 'Close')}
>
<CloseRoundedIcon fontSize="small" />
</ActionIcon>
</Tooltip>
</Group>
</header>
<header className="tool-panel-overlay__header">
<div className="tool-panel-overlay__heading">
<Text fw={600} size="lg">
{t('toolPanel.overlay.title', 'All tools')}
</Text>
<Text size="sm" c="dimmed">
{t('toolPanel.overlay.subtitle', 'Browse every tool in the legacy fullscreen catalog.')}
</Text>
</div>
<Group gap="xs">
<Tooltip label={toggleLabel} position="bottom" withArrow>
<ActionIcon
variant="subtle"
radius="xl"
size="lg"
onClick={() => setToolPanelMode(toolPanelMode === 'fullscreen' ? 'sidebar' : 'fullscreen')}
aria-label={toggleLabel}
>
{toolPanelMode === 'fullscreen' ? (
<ViewSidebarRoundedIcon fontSize="small" />
) : (
<DashboardCustomizeRoundedIcon fontSize="small" />
)}
</ActionIcon>
</Tooltip>
<Tooltip label={t('toolPanel.overlay.close', 'Close')} position="bottom" withArrow>
<ActionIcon
variant="subtle"
radius="xl"
size="lg"
onClick={handleClose}
aria-label={t('toolPanel.overlay.close', 'Close')}
>
<CloseRoundedIcon fontSize="small" />
</ActionIcon>
</Tooltip>
</Group>
</header>
<div className="tool-panel-overlay__search">
<div className="tool-panel-overlay__search-input">
<ToolSearch
value={searchQuery}
onChange={setSearchQuery}
toolRegistry={toolRegistry}
mode="filter"
autoFocus
<div className="tool-panel-overlay__search">
<div className="tool-panel-overlay__search-input">
<ToolSearch
value={searchQuery}
onChange={setSearchQuery}
toolRegistry={toolRegistry}
mode="filter"
autoFocus
/>
</div>
<div className="tool-panel-overlay__search-controls">
<div className="tool-panel-overlay__layout-toggle">
<SegmentedControl
value={layout}
onChange={value => setLayout(value as LayoutVariant)}
size="sm"
aria-label={layoutLabel}
data={[
{ label: t('toolPanel.overlay.layoutCompact', 'Compact grid'), value: 'compact' },
{ label: t('toolPanel.overlay.layoutDetailed', 'Detailed cards'), value: 'detailed' },
]}
/>
</div>
<div className="tool-panel-overlay__search-controls">
<div className="tool-panel-overlay__layout-toggle">
<SegmentedControl
value={layout}
onChange={value => setLayout(value as LayoutVariant)}
size="sm"
aria-label={layoutLabel}
data={[
{ label: t('toolPanel.overlay.layoutCompact', 'Compact grid'), value: 'compact' },
{ label: t('toolPanel.overlay.layoutDetailed', 'Detailed cards'), value: 'detailed' },
]}
/>
</div>
<Badge variant="light" size="lg" radius="sm">
{t('toolPanel.overlay.totalLabel', {
count: totalToolCount,
defaultValue: '{{count}} tools available',
})}
</Badge>
</div>
<Badge variant="light" size="lg" radius="sm">
{t('toolPanel.overlay.totalLabel', {
count: totalToolCount,
defaultValue: '{{count}} tools available',
})}
</Badge>
</div>
</div>
<div className="tool-panel-overlay__body">
<ScrollArea className="tool-panel-overlay__scroll" type="always">
<div className="tool-panel-overlay__content">
{subcategoryGroups.length === 0 ? (
<div className="tool-panel-overlay__empty">
<NoToolsFound />
</div>
) : (
subcategoryGroups.map(group => (
<section key={group.subcategoryId} className="tool-panel-overlay__section">
<header className="tool-panel-overlay__section-header">
<Text fw={600} size="sm">
{getSubcategoryLabel(t, group.subcategoryId)}
</Text>
</header>
<div className={`tool-panel-overlay__grid tool-panel-overlay__grid--${layout}`}>
{group.tools.map(({ id, tool }) => (
<div className="tool-panel-overlay__body">
<ScrollArea className="tool-panel-overlay__scroll" offsetScrollbars>
<div className="tool-panel-overlay__content">
{subcategoryGroups.length === 0 ? (
<NoToolsFound
title={t('toolPanel.overlay.noResultsTitle', 'No tools found')}
description={t('toolPanel.overlay.noResultsDescription', 'Try adjusting your filters or search terms.')}
/>
) : (
subcategoryGroups.map(({ key, tools }) => (
<section key={key} className="tool-panel-overlay__section">
<header className="tool-panel-overlay__section-header">
<Text size="sm" fw={600} tt="uppercase" c="dimmed" lts={0.5}>
{getSubcategoryLabel(key, t)}
</Text>
<Badge variant="outline" size="xs" radius="sm" color="gray">
{tools.length}
</Badge>
</header>
<div
className={`tool-panel-overlay__grid tool-panel-overlay__grid--${layout}`}
role="list"
>
{tools.map(tool => {
const [toolId, toolConfig] = tool.item;
const matchedText = matchedTextMap.get(toolId);
return (
<ToolPanelOverlayTile
key={id}
id={id}
tool={tool}
key={toolId}
id={toolId}
tool={toolConfig}
layout={layout}
onSelect={toolId => handleToolSelect(toolId as ToolId)}
isSelected={selectedToolKey === id}
matchedSynonym={matchedTextMap.get(id)}
onSelect={(nextId) => handleToolSelect(nextId as ToolId)}
isSelected={selectedToolKey === toolId}
matchedSynonym={matchedText}
/>
))}
</div>
</section>
))
)}
</div>
</ScrollArea>
</div>
</Paper>
);
})}
</div>
</section>
))
)}
</div>
</ScrollArea>
</div>
</div>
);
}

View File

@ -166,3 +166,54 @@
font-weight: 500;
color: var(--text-muted);
}
.home-desktop-layout {
width: 100%;
transition: background-color 0.3s ease;
}
.home-desktop-layout__quick,
.home-desktop-layout__tool-panel,
.home-desktop-layout__workbench,
.home-desktop-layout__right-rail {
display: flex;
overflow: hidden;
transition: max-width 0.35s ease, transform 0.35s ease, opacity 0.3s ease;
}
.home-desktop-layout__quick {
max-width: 5rem;
}
.home-desktop-layout__tool-panel {
flex: 0 0 auto;
min-width: 0;
}
.home-desktop-layout__workbench {
flex: 1 1 auto;
min-width: 0;
}
.home-desktop-layout__right-rail {
flex: 0 0 auto;
}
.home-desktop-layout--catalog .home-desktop-layout__quick {
max-width: 0;
transform: translateX(-100%);
opacity: 0;
pointer-events: none;
}
.home-desktop-layout--catalog .home-desktop-layout__tool-panel {
flex: 1 1 auto;
}
.home-desktop-layout--catalog .home-desktop-layout__workbench,
.home-desktop-layout--catalog .home-desktop-layout__right-rail {
max-width: 0;
opacity: 0;
transform: translateX(2rem);
pointer-events: none;
}

View File

@ -9,7 +9,6 @@ import { useMediaQuery } from "@mantine/hooks";
import AppsIcon from '@mui/icons-material/AppsRounded';
import ToolPanel from "../components/tools/ToolPanel";
import ToolPanelOverlay from "../components/tools/ToolPanelOverlay";
import ToolPanelModePrompt from "../components/tools/ToolPanelModePrompt";
import Workbench from "../components/layout/Workbench";
import QuickAccessBar from "../components/shared/QuickAccessBar";
@ -133,12 +132,11 @@ export default function HomePage() {
// Note: File selection limits are now handled directly by individual tools
const showFullscreenOverlay = !isMobile && toolPanelMode === 'fullscreen' && leftPanelView === 'toolPicker';
const desktopCatalogActive = !isMobile && toolPanelMode === 'fullscreen' && leftPanelView === 'toolPicker';
return (
<div className="h-screen overflow-hidden">
<ToolPanelModePrompt />
<ToolPanelOverlay isOpen={showFullscreenOverlay} />
{isMobile ? (
<div className="mobile-layout">
<div className="mobile-toggle">
@ -242,13 +240,22 @@ export default function HomePage() {
align="flex-start"
gap={0}
h="100%"
className="flex-nowrap flex"
className={`flex-nowrap flex home-desktop-layout ${desktopCatalogActive ? 'home-desktop-layout--catalog' : ''}`}
>
<QuickAccessBar
ref={quickAccessRef} />
<ToolPanel />
<Workbench />
<RightRail />
<div className="home-desktop-layout__quick">
<QuickAccessBar
ref={quickAccessRef}
/>
</div>
<div className="home-desktop-layout__tool-panel">
<ToolPanel />
</div>
<div className="home-desktop-layout__workbench" aria-hidden={desktopCatalogActive}>
<Workbench />
</div>
<div className="home-desktop-layout__right-rail" aria-hidden={desktopCatalogActive}>
<RightRail />
</div>
<FileManager selectedTool={selectedTool as any /* FIX ME */} />
</Group>
)}