mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-12-18 20:04:17 +01:00
feat: integrate fullscreen catalog into tool panel
This commit is contained in:
parent
130168e171
commit
c200e2a189
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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>
|
||||
)}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user