fix: correct tool panel mode labelling

This commit is contained in:
Anthony Stirling 2025-10-04 19:52:14 +01:00
parent c9e1b8eec5
commit 7a13d42116
9 changed files with 618 additions and 13 deletions

View File

@ -1958,3 +1958,24 @@ viewer.nextPage=Next Page
viewer.pageNavigation=Page Navigation
viewer.currentPage=Current Page
viewer.totalPages=Total Pages
# Tool panel view modes
toolPanel.modeToggle.sidebar=Switch to advanced sidebar
toolPanel.modeToggle.fullscreen=Switch to legacy fullscreen
toolPanel.overlay.title=All tools
toolPanel.overlay.subtitle=Browse and launch tools in the legacy fullscreen catalog.
toolPanel.overlay.close=Close
toolPanel.overlay.totalLabel_one={{count}} tool available
toolPanel.overlay.totalLabel_other={{count}} tools available
toolPanel.overlayHint=Select a tool to open it in the workspace.
toolPanel.modePrompt.title=Choose your tools view
toolPanel.modePrompt.description=Preview both layouts and choose how you want to explore Stirling PDF tools.
toolPanel.modePrompt.legacyTitle=Legacy fullscreen
toolPanel.modePrompt.legacyBadge=Not recommended
toolPanel.modePrompt.legacyDescription=Open a fullscreen catalog of tools that hides the workspace until a tool is chosen.
toolPanel.modePrompt.chooseLegacy=Use legacy fullscreen mode
toolPanel.modePrompt.advancedTitle=Advanced sidebar
toolPanel.modePrompt.advancedBadge=Recommended
toolPanel.modePrompt.advancedDescription=Stay in the enhanced sidebar with quick access to tools alongside your workspace.
toolPanel.modePrompt.chooseAdvanced=Use advanced sidebar mode
toolPanel.modePrompt.dismiss=Maybe later

View File

@ -0,0 +1,28 @@
.tool-panel__search-row {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
}
.tool-panel__search-row .search-input-container {
flex: 1;
}
.tool-panel__mode-toggle {
transition: transform 0.2s ease;
}
.tool-panel__mode-toggle:hover {
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);
background: var(--bg-background);
}

View File

@ -6,20 +6,23 @@ import ToolRenderer from './ToolRenderer';
import ToolSearch from './toolPicker/ToolSearch';
import { useSidebarContext } from "../../contexts/SidebarContext";
import rainbowStyles from '../../styles/rainbow.module.css';
import { ScrollArea } from '@mantine/core';
import { ActionIcon, Group, ScrollArea, Text, Tooltip } from '@mantine/core';
import { ToolId } from '../../types/toolId';
import { useMediaQuery } from '@mantine/hooks';
import ViewSidebarRoundedIcon from '@mui/icons-material/ViewSidebarRounded';
import DashboardCustomizeRoundedIcon from '@mui/icons-material/DashboardCustomizeRounded';
import { useTranslation } from 'react-i18next';
import './ToolPanel.css';
// No props needed - component uses context
export default function ToolPanel() {
const { t } = useTranslation();
const { isRainbowMode } = useRainbowThemeContext();
const { sidebarRefs } = useSidebarContext();
const { toolPanelRef } = sidebarRefs;
const isMobile = useMediaQuery('(max-width: 1024px)');
// Use context-based hooks to eliminate prop drilling
const {
leftPanelView,
isPanelVisible,
@ -27,10 +30,23 @@ export default function ToolPanel() {
filteredTools,
toolRegistry,
setSearchQuery,
toolPanelMode,
setToolPanelMode,
selectedToolKey,
handleToolSelect,
setPreviewFile,
} = useToolWorkflow();
const { selectedToolKey, handleToolSelect } = useToolWorkflow();
const { setPreviewFile } = useToolWorkflow();
const isFullscreenMode = toolPanelMode === 'fullscreen';
const overlayActive = isFullscreenMode && leftPanelView === 'toolPicker' && !isMobile;
const toggleLabel = isFullscreenMode
? t('toolPanel.modeToggle.sidebar', 'Switch to advanced sidebar')
: t('toolPanel.modeToggle.fullscreen', 'Switch to legacy fullscreen');
const handleToggleMode = () => {
setToolPanelMode(isFullscreenMode ? 'sidebar' : 'fullscreen');
};
return (
<div
@ -38,11 +54,23 @@ export default function ToolPanel() {
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 ${
isRainbowMode ? rainbowStyles.rainbowPaper : ''
} ${isMobile ? 'h-full border-r-0' : 'h-screen'}`}
} ${isMobile ? 'h-full border-r-0' : 'h-screen'} ${overlayActive ? 'tool-panel--overlay-hidden' : ''}`}
style={{
width: isMobile ? '100%' : isPanelVisible ? '18.5rem' : '0',
padding: '0'
width: isMobile
? '100%'
: isFullscreenMode
? leftPanelView === 'toolContent' && isPanelVisible
? '20rem'
: overlayActive
? '0'
: '20rem'
: isPanelVisible
? '18.5rem'
: '0',
padding: '0',
pointerEvents: overlayActive ? 'none' : undefined,
}}
aria-hidden={overlayActive}
>
<div
style={{
@ -55,10 +83,10 @@ 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)',
padding: '0.75rem 1rem',
}}
>
<ToolSearch
@ -67,6 +95,24 @@ export default function ToolPanel() {
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 ? (
@ -81,6 +127,15 @@ 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)}

View File

@ -0,0 +1,64 @@
.tool-panel-mode-prompt__previews {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}
.tool-panel-mode-prompt__card {
height: 100%;
}
.tool-panel-mode-prompt__preview {
border-radius: 0.75rem;
border: 1px solid var(--border-subtle);
background: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(240, 240, 255, 0.6));
padding: 0.75rem;
display: flex;
gap: 0.75rem;
min-height: 7.5rem;
}
.tool-panel-mode-prompt__preview--sidebar {
align-items: stretch;
}
.tool-panel-mode-prompt__preview-sidebar {
width: 3.25rem;
border-radius: 0.5rem;
background: linear-gradient(180deg, var(--accent-ghost), rgba(100, 116, 139, 0.35));
}
.tool-panel-mode-prompt__preview-canvas {
flex: 1;
border-radius: 0.5rem;
background: repeating-linear-gradient(
135deg,
rgba(255, 255, 255, 0.9),
rgba(255, 255, 255, 0.9) 12px,
rgba(226, 232, 240, 0.7) 12px,
rgba(226, 232, 240, 0.7) 24px
);
}
.tool-panel-mode-prompt__preview--fullscreen {
justify-content: center;
align-items: center;
}
.tool-panel-mode-prompt__preview-grid {
width: 100%;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.5rem;
}
.tool-panel-mode-prompt__preview-grid span {
display: block;
aspect-ratio: 4 / 3;
border-radius: 0.5rem;
background: linear-gradient(180deg, rgba(79, 70, 229, 0.2), rgba(14, 165, 233, 0.35));
}
.tool-panel-mode-prompt__card--recommended {
border: 1px solid rgba(236, 72, 153, 0.4);
}

View File

@ -0,0 +1,124 @@
import { useEffect, useState } from 'react';
import { Badge, Button, Card, Group, Modal, Stack, Text } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import { useToolWorkflow, TOOL_PANEL_MODE_KEY } from '../../contexts/ToolWorkflowContext';
import './ToolPanelModePrompt.css';
type ToolPanelModeOption = 'sidebar' | 'fullscreen';
const PROMPT_STORAGE_KEY = 'toolPanelModePromptSeen';
const ToolPanelModePrompt = () => {
const { t } = useTranslation();
const { toolPanelMode, setToolPanelMode } = useToolWorkflow();
const [opened, setOpened] = useState(false);
const [hydrated, setHydrated] = useState(false);
useEffect(() => {
if (typeof window === 'undefined') return;
const hasSeenPrompt = window.localStorage.getItem(PROMPT_STORAGE_KEY);
const hasStoredPreference = window.localStorage.getItem(TOOL_PANEL_MODE_KEY);
if (!hasSeenPrompt && !hasStoredPreference) {
setOpened(true);
}
setHydrated(true);
}, []);
const persistPromptState = () => {
if (typeof window !== 'undefined') {
window.localStorage.setItem(PROMPT_STORAGE_KEY, 'true');
}
};
const handleClose = () => {
persistPromptState();
setOpened(false);
};
const handleSelect = (mode: ToolPanelModeOption) => {
setToolPanelMode(mode);
persistPromptState();
setOpened(false);
};
if (!hydrated) {
return null;
}
return (
<Modal
opened={opened}
onClose={handleClose}
size="lg"
centered
title={t('toolPanel.modePrompt.title', 'Choose your tools view')}
overlayProps={{ blur: 4, opacity: 0.45 }}
>
<Stack gap="lg">
<Text size="sm" c="dimmed">
{t('toolPanel.modePrompt.description', 'Preview both layouts and choose how you want to explore Stirling PDF tools.')}
</Text>
<div className="tool-panel-mode-prompt__previews">
<Card shadow="lg" padding="md" radius="lg" withBorder className="tool-panel-mode-prompt__card tool-panel-mode-prompt__card--recommended">
<Stack gap="sm">
<Group justify="space-between" align="center">
<Text fw={600}>{t('toolPanel.modePrompt.advancedTitle', 'Advanced sidebar')}</Text>
<Badge color="pink" variant="filled">
{t('toolPanel.modePrompt.advancedBadge', 'Recommended')}
</Badge>
</Group>
<Text size="sm" c="dimmed">
{t('toolPanel.modePrompt.advancedDescription', 'Stay in the enhanced sidebar with quick access to tools alongside your workspace.')}
</Text>
<div className="tool-panel-mode-prompt__preview tool-panel-mode-prompt__preview--sidebar" aria-hidden>
<div className="tool-panel-mode-prompt__preview-sidebar" />
<div className="tool-panel-mode-prompt__preview-canvas" />
</div>
<Button
variant={toolPanelMode === 'sidebar' ? 'filled' : 'outline'}
color="pink"
onClick={() => handleSelect('sidebar')}
aria-label={t('toolPanel.modePrompt.chooseAdvanced', 'Use advanced sidebar mode')}
>
{t('toolPanel.modePrompt.chooseAdvanced', 'Use advanced sidebar mode')}
</Button>
</Stack>
</Card>
<Card shadow="sm" padding="md" radius="lg" withBorder className="tool-panel-mode-prompt__card">
<Stack gap="sm">
<Group justify="space-between" align="center">
<Text fw={600}>{t('toolPanel.modePrompt.legacyTitle', 'Legacy fullscreen')}</Text>
<Badge color="gray" variant="light">
{t('toolPanel.modePrompt.legacyBadge', 'Not recommended')}
</Badge>
</Group>
<Text size="sm" c="dimmed">
{t('toolPanel.modePrompt.legacyDescription', 'Open a fullscreen catalog of tools that hides the workspace until a tool is chosen.')}
</Text>
<div className="tool-panel-mode-prompt__preview tool-panel-mode-prompt__preview--fullscreen" aria-hidden>
<div className="tool-panel-mode-prompt__preview-grid">
<span />
<span />
<span />
<span />
</div>
</div>
<Button
variant={toolPanelMode === 'fullscreen' ? 'filled' : 'outline'}
onClick={() => handleSelect('fullscreen')}
aria-label={t('toolPanel.modePrompt.chooseLegacy', 'Use legacy fullscreen mode')}
>
{t('toolPanel.modePrompt.chooseLegacy', 'Use legacy fullscreen mode')}
</Button>
</Stack>
</Card>
</div>
<Button variant="subtle" color="gray" onClick={handleClose} aria-label={t('toolPanel.modePrompt.dismiss', 'Maybe later')}>
{t('toolPanel.modePrompt.dismiss', 'Maybe later')}
</Button>
</Stack>
</Modal>
);
};
export default ToolPanelModePrompt;

View File

@ -0,0 +1,86 @@
.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(-10%);
pointer-events: none;
}
.tool-panel-overlay__paper {
flex: 1;
max-width: 72rem;
margin: 2rem;
display: flex;
flex-direction: column;
background: var(--bg-background);
border-radius: 1.25rem;
overflow: hidden;
}
.tool-panel-overlay__header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem;
gap: 1rem;
border-bottom: 1px solid var(--border-subtle);
}
.tool-panel-overlay__search {
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border-subtle);
background: var(--bg-toolbar);
}
.tool-panel-overlay__search-input .search-input-container {
width: min(32rem, 100%);
}
.tool-panel-overlay__body {
flex: 1;
min-height: 0;
position: relative;
}
.tool-panel-overlay__scroll {
height: 100%;
}
.tool-panel-overlay__results,
.tool-panel-overlay__picker {
padding: 1.5rem;
min-height: 100%;
}
.tool-panel-overlay__picker {
display: flex;
flex-direction: column;
background: var(--bg-toolbar);
border-radius: 1rem;
}
.tool-panel-overlay__picker .tool-picker-scrollable {
padding-bottom: 2rem;
}
@media (max-width: 1024px) {
.tool-panel-overlay__paper {
margin: 0;
border-radius: 0;
}
}

View File

@ -0,0 +1,176 @@
import { useEffect, useMemo, useState } from 'react';
import { ActionIcon, Badge, Group, Paper, ScrollArea, 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';
import { useTranslation } from 'react-i18next';
import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
import ToolSearch from './toolPicker/ToolSearch';
import ToolPicker from './ToolPicker';
import SearchResults from './SearchResults';
import { ToolId } from '../../types/toolId';
import './ToolPanelOverlay.css';
interface ToolPanelOverlayProps {
isOpen: boolean;
}
const EXIT_ANIMATION_MS = 320;
export default function ToolPanelOverlay({ isOpen }: ToolPanelOverlayProps) {
const { t } = useTranslation();
const {
searchQuery,
setSearchQuery,
filteredTools,
selectedToolKey,
handleToolSelect,
toolRegistry,
setToolPanelMode,
toolPanelMode,
setLeftPanelView,
} = useToolWorkflow();
const [shouldRender, setShouldRender] = useState(isOpen);
const [isClosing, setIsClosing] = useState(false);
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;
if (!shouldRender) {
return null;
}
const handleClose = () => {
setSearchQuery('');
setLeftPanelView('hidden');
};
const toggleLabel = toolPanelMode === 'fullscreen'
? t('toolPanel.modeToggle.sidebar', 'Switch to advanced sidebar')
: t('toolPanel.modeToggle.fullscreen', 'Switch to legacy fullscreen');
return (
<div
className={`tool-panel-overlay ${isClosing || !isOpen ? 'tool-panel-overlay--closing' : 'tool-panel-overlay--open'}`}
role="dialog"
aria-modal
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 and launch tools 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">
<Group justify="space-between" align="center">
<div className="tool-panel-overlay__search-input">
<ToolSearch
value={searchQuery}
onChange={setSearchQuery}
toolRegistry={toolRegistry}
mode="filter"
autoFocus
/>
</div>
<Badge variant="light" size="lg" radius="sm">
{t('toolPanel.overlay.totalLabel', '{{count}} tools available', {
count: totalToolCount,
})}
</Badge>
</Group>
</div>
<div className="tool-panel-overlay__body">
<ScrollArea className="tool-panel-overlay__scroll" type="always">
{showSearchResults ? (
<div className="tool-panel-overlay__results">
<SearchResults
filteredTools={filteredTools}
onSelect={(id) => handleToolSelect(id as ToolId)}
searchQuery={searchQuery}
/>
</div>
) : (
<div className="tool-panel-overlay__picker">
<ToolPicker
selectedToolKey={selectedToolKey}
onSelect={(id) => handleToolSelect(id as ToolId)}
filteredTools={filteredTools}
isSearching={showSearchResults}
/>
</div>
)}
</ScrollArea>
</div>
</Paper>
</div>
);
}

View File

@ -14,11 +14,14 @@ import { getDefaultWorkbench } from '../types/workbench';
import { filterToolRegistryByQuery } from '../utils/toolSearch';
// State interface
type ToolPanelMode = 'sidebar' | 'fullscreen';
interface ToolWorkflowState {
// UI State
sidebarsVisible: boolean;
leftPanelView: 'toolPicker' | 'toolContent' | 'hidden';
readerMode: boolean;
toolPanelMode: ToolPanelMode;
// File/Preview State
previewFile: File | null;
@ -33,13 +36,25 @@ type ToolWorkflowAction =
| { type: 'SET_SIDEBARS_VISIBLE'; payload: boolean }
| { type: 'SET_LEFT_PANEL_VIEW'; payload: 'toolPicker' | 'toolContent' | 'hidden' }
| { type: 'SET_READER_MODE'; payload: boolean }
| { type: 'SET_TOOL_PANEL_MODE'; payload: ToolPanelMode }
| { type: 'SET_PREVIEW_FILE'; payload: File | null }
| { type: 'SET_PAGE_EDITOR_FUNCTIONS'; payload: PageEditorFunctions | null }
| { type: 'SET_SEARCH_QUERY'; payload: string }
| { type: 'RESET_UI_STATE' };
// Initial state
const initialState: ToolWorkflowState = {
export const TOOL_PANEL_MODE_KEY = 'toolPanelModePreference';
const getInitialToolPanelMode = (): ToolPanelMode => {
if (typeof window === 'undefined') {
return 'sidebar';
}
const storedValue = window.localStorage.getItem(TOOL_PANEL_MODE_KEY);
return storedValue === 'fullscreen' ? 'fullscreen' : 'sidebar';
};
const baseInitialState: Omit<ToolWorkflowState, 'toolPanelMode'> = {
sidebarsVisible: true,
leftPanelView: 'toolPicker',
readerMode: false,
@ -48,6 +63,11 @@ const initialState: ToolWorkflowState = {
searchQuery: '',
};
const createInitialState = (): ToolWorkflowState => ({
...baseInitialState,
toolPanelMode: getInitialToolPanelMode(),
});
// Reducer
function toolWorkflowReducer(state: ToolWorkflowState, action: ToolWorkflowAction): ToolWorkflowState {
switch (action.type) {
@ -57,6 +77,8 @@ function toolWorkflowReducer(state: ToolWorkflowState, action: ToolWorkflowActio
return { ...state, leftPanelView: action.payload };
case 'SET_READER_MODE':
return { ...state, readerMode: action.payload };
case 'SET_TOOL_PANEL_MODE':
return { ...state, toolPanelMode: action.payload };
case 'SET_PREVIEW_FILE':
return { ...state, previewFile: action.payload };
case 'SET_PAGE_EDITOR_FUNCTIONS':
@ -64,7 +86,11 @@ function toolWorkflowReducer(state: ToolWorkflowState, action: ToolWorkflowActio
case 'SET_SEARCH_QUERY':
return { ...state, searchQuery: action.payload };
case 'RESET_UI_STATE':
return { ...initialState, searchQuery: state.searchQuery }; // Preserve search
return {
...baseInitialState,
searchQuery: state.searchQuery,
toolPanelMode: state.toolPanelMode,
};
default:
return state;
}
@ -85,6 +111,7 @@ interface ToolWorkflowContextValue extends ToolWorkflowState {
setPreviewFile: (file: File | null) => void;
setPageEditorFunctions: (functions: PageEditorFunctions | null) => void;
setSearchQuery: (query: string) => void;
setToolPanelMode: (mode: ToolPanelMode) => void;
// Tool Actions
selectTool: (toolId: ToolId | null) => void;
@ -113,7 +140,7 @@ interface ToolWorkflowProviderProps {
}
export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
const [state, dispatch] = useReducer(toolWorkflowReducer, initialState);
const [state, dispatch] = useReducer(toolWorkflowReducer, undefined, createInitialState);
// Store reset functions for tools
const [toolResetFunctions, setToolResetFunctions] = React.useState<Record<string, () => void>>({});
@ -163,6 +190,15 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
dispatch({ type: 'SET_SEARCH_QUERY', payload: query });
}, []);
const setToolPanelMode = useCallback((mode: ToolPanelMode) => {
dispatch({ type: 'SET_TOOL_PANEL_MODE', payload: mode });
}, []);
React.useEffect(() => {
if (typeof window === 'undefined') return;
window.localStorage.setItem(TOOL_PANEL_MODE_KEY, state.toolPanelMode);
}, [state.toolPanelMode]);
// Tool reset methods
const registerToolReset = useCallback((toolId: string, resetFunction: () => void) => {
setToolResetFunctions(prev => ({ ...prev, [toolId]: resetFunction }));
@ -264,6 +300,7 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
setPreviewFile,
setPageEditorFunctions,
setSearchQuery,
setToolPanelMode,
selectTool: actions.setSelectedTool,
clearToolSelection: () => actions.setSelectedTool(null),
@ -292,6 +329,7 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
setPreviewFile,
setPageEditorFunctions,
setSearchQuery,
setToolPanelMode,
actions.setSelectedTool,
registerToolReset,
resetTool,

View File

@ -9,6 +9,8 @@ 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";
import RightRail from "../components/shared/RightRail";
@ -30,7 +32,14 @@ export default function HomePage() {
const { quickAccessRef } = sidebarRefs;
const { selectedTool, selectedToolKey, handleToolSelect, handleBackToTools } = useToolWorkflow();
const {
selectedTool,
selectedToolKey,
handleToolSelect,
handleBackToTools,
leftPanelView,
toolPanelMode,
} = useToolWorkflow();
const { openFilesModal } = useFilesModalContext();
const { colorScheme } = useMantineColorScheme();
@ -124,8 +133,12 @@ export default function HomePage() {
// Note: File selection limits are now handled directly by individual tools
const showFullscreenOverlay = !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">