legacy UI

This commit is contained in:
Anthony Stirling 2025-10-05 01:26:04 +01:00
parent c9e1b8eec5
commit 667a9b4867
11 changed files with 1324 additions and 68 deletions

View File

@ -1946,6 +1946,25 @@ viewer.zoomIn=Zoom in
# Tool Picker
toolPicker.searchPlaceholder=Search tools...
toolPicker.noToolsFound=No tools found
toolPanel.toggle.legacy=Switch to legacy mode
toolPanel.toggle.sidebar=Switch to sidebar mode
toolPanel.placeholder=Choose a tool to get started
toolPanel.legacy.heading=All tools (legacy view)
toolPanel.legacy.tagline=Browse and launch tools while keeping the classic full-width gallery.
toolPanel.legacy.descriptionsOn=Showing descriptions
toolPanel.legacy.descriptionsOff=Descriptions hidden
toolPanel.legacy.noResults=Try adjusting your search or toggle descriptions to find what you need.
toolPanel.legacy.matchedSynonym=Matches "{{text}}"
toolPanel.modePrompt.title=Choose how you browse tools
toolPanel.modePrompt.description=Preview both layouts and decide how you want to explore Stirling PDF tools.
toolPanel.modePrompt.sidebarTitle=Advanced sidebar
toolPanel.modePrompt.sidebarDescription=Keep tools alongside your workspace for quick switching.
toolPanel.modePrompt.recommended=Recommended
toolPanel.modePrompt.chooseSidebar=Use advanced sidebar
toolPanel.modePrompt.legacyTitle=Legacy fullscreen
toolPanel.modePrompt.legacyDescription=Browse every tool in a catalogue that covers the workspace until you pick one.
toolPanel.modePrompt.chooseLegacy=Use legacy fullscreen
toolPanel.modePrompt.dismiss=Maybe later
pageEditor.reset=Reset Changes
pageEditor.zoomIn=Zoom In
pageEditor.zoomOut=Zoom Out

View File

@ -176,7 +176,7 @@ export default function RightRail() {
}, [currentView]);
return (
<div className="right-rail">
<div className="right-rail" data-sidebar="right-rail">
<div className="right-rail-inner">
{topButtons.length > 0 && (
<>
@ -481,4 +481,3 @@ export default function RightRail() {
);
}

View File

@ -0,0 +1,214 @@
import React, { useMemo } from 'react';
import { Badge, Text } from '@mantine/core';
import { Tooltip } from '../shared/Tooltip';
import { useTranslation } from 'react-i18next';
import { ToolRegistryEntry } from '../../data/toolsTaxonomy';
import { ToolId } from '../../types/toolId';
import { useToolSections } from '../../hooks/useToolSections';
import { getSubcategoryLabel } from '../../data/toolsTaxonomy';
import NoToolsFound from './shared/NoToolsFound';
import './ToolPanel.css';
interface LegacyToolListProps {
filteredTools: Array<{ item: [string, ToolRegistryEntry]; matchedText?: string }>;
searchQuery: string;
showDescriptions: boolean;
selectedToolKey: string | null;
matchedTextMap: Map<string, string>;
onSelect: (id: ToolId) => void;
}
const LegacyToolList = ({
filteredTools,
searchQuery,
showDescriptions,
selectedToolKey,
matchedTextMap,
onSelect,
}: LegacyToolListProps) => {
const { t } = useTranslation();
const { sections, searchGroups } = useToolSections(filteredTools, searchQuery);
const tooltipPortalTarget = typeof document !== 'undefined' ? document.body : undefined;
const subcategoryGroups = useMemo(() => {
if (searchQuery.trim().length > 0) {
return searchGroups;
}
const allSection = sections.find(section => section.key === 'all');
return allSection ? allSection.subcategories : [];
}, [searchGroups, sections, searchQuery]);
if (subcategoryGroups.length === 0) {
return (
<div className="tool-panel__legacy-empty">
<NoToolsFound />
<Text size="sm" c="dimmed">
{t('toolPanel.legacy.noResults', 'Try adjusting your search or toggle descriptions to find what you need.')}
</Text>
</div>
);
}
const containerClass = showDescriptions
? 'tool-panel__legacy-groups tool-panel__legacy-groups--detailed'
: 'tool-panel__legacy-groups tool-panel__legacy-groups--compact';
return (
<div className={containerClass}>
{subcategoryGroups.map(({ subcategoryId, tools }) => (
<section
key={subcategoryId}
className={`tool-panel__legacy-group ${showDescriptions ? 'tool-panel__legacy-group--detailed' : 'tool-panel__legacy-group--compact'}`}
>
<header className="tool-panel__legacy-section-header">
<Text size="sm" fw={600} tt="uppercase" lts={0.5} c="dimmed">
{getSubcategoryLabel(t, subcategoryId)}
</Text>
<Badge size="sm" variant="light" color="gray">
{tools.length}
</Badge>
</header>
{showDescriptions ? (
<div className="tool-panel__legacy-grid tool-panel__legacy-grid--detailed">
{tools.map(({ id, tool }) => {
const matchedText = matchedTextMap.get(id);
const isSelected = selectedToolKey === id;
const isDisabled = !tool.component && !tool.link && id !== 'read' && id !== 'multiTool';
let iconNode: React.ReactNode = null;
if (React.isValidElement<{ style?: React.CSSProperties }>(tool.icon)) {
const element = tool.icon as React.ReactElement<{ style?: React.CSSProperties }>;
iconNode = React.cloneElement(element, {
style: {
...(element.props.style || {}),
fontSize: '1.75rem',
},
});
} else {
iconNode = tool.icon;
}
const handleClick = () => {
if (isDisabled) return;
if (tool.link) {
window.open(tool.link, '_blank', 'noopener,noreferrer');
return;
}
onSelect(id as ToolId);
};
return (
<button
key={id}
type="button"
className={`tool-panel__legacy-item tool-panel__legacy-item--detailed ${isSelected ? 'tool-panel__legacy-item--selected' : ''}`}
onClick={handleClick}
aria-disabled={isDisabled}
disabled={isDisabled}
>
{tool.icon ? (
<span className="tool-panel__legacy-icon" aria-hidden>
{iconNode}
</span>
) : null}
<span className="tool-panel__legacy-body">
<Text fw={600} size="sm" className="tool-panel__legacy-name">
{tool.name}
</Text>
<Text size="sm" c="dimmed" className="tool-panel__legacy-description">
{tool.description}
</Text>
{matchedText && (
<Text size="xs" c="dimmed" className="tool-panel__legacy-match">
{t('toolPanel.legacy.matchedSynonym', 'Matches "{{text}}"', { text: matchedText })}
</Text>
)}
</span>
</button>
);
})}
</div>
) : (
<div className="tool-panel__legacy-list">
{tools.map(({ id, tool }) => {
const matchedText = matchedTextMap.get(id);
const isSelected = selectedToolKey === id;
const isDisabled = !tool.component && !tool.link && id !== 'read' && id !== 'multiTool';
let iconNode: React.ReactNode = null;
if (React.isValidElement<{ style?: React.CSSProperties }>(tool.icon)) {
const element = tool.icon as React.ReactElement<{ style?: React.CSSProperties }>;
iconNode = React.cloneElement(element, {
style: {
...(element.props.style || {}),
fontSize: '1.5rem',
},
});
} else {
iconNode = tool.icon;
}
const handleClick = () => {
if (isDisabled) return;
if (tool.link) {
window.open(tool.link, '_blank', 'noopener,noreferrer');
return;
}
onSelect(id as ToolId);
};
const baseButton = (
<button
type="button"
className={`tool-panel__legacy-list-item ${isSelected ? 'tool-panel__legacy-list-item--selected' : ''}`}
onClick={handleClick}
aria-disabled={isDisabled}
disabled={isDisabled}
>
{tool.icon ? (
<span className="tool-panel__legacy-list-icon" aria-hidden>
{iconNode}
</span>
) : null}
<span className="tool-panel__legacy-list-body">
<Text fw={600} size="sm" className="tool-panel__legacy-name">
{tool.name}
</Text>
{matchedText && (
<Text size="xs" c="dimmed" className="tool-panel__legacy-match">
{t('toolPanel.legacy.matchedSynonym', 'Matches "{{text}}"', { text: matchedText })}
</Text>
)}
</span>
</button>
);
if (showDescriptions || !tool.description) {
return React.cloneElement(baseButton, { key: id });
}
return (
<Tooltip
key={id}
content={tool.description}
position="top"
portalTarget={tooltipPortalTarget}
arrow
delay={80}
>
{baseButton}
</Tooltip>
);
})}
</div>
)}
</section>
))}
</div>
);
};
export default LegacyToolList;

View File

@ -0,0 +1,131 @@
import { useEffect } from 'react';
import { ActionIcon, Group, ScrollArea, Switch, Text, Tooltip } from '@mantine/core';
import ViewSidebarRoundedIcon from '@mui/icons-material/ViewSidebarRounded';
import { useTranslation } from 'react-i18next';
import ToolSearch from './toolPicker/ToolSearch';
import LegacyToolList from './LegacyToolList';
import { ToolRegistryEntry } from '../../data/toolsTaxonomy';
import { ToolId } from '../../types/toolId';
import './ToolPanel.css';
interface LegacyToolSurfaceProps {
searchQuery: string;
toolRegistry: Record<string, ToolRegistryEntry>;
filteredTools: Array<{ item: [string, ToolRegistryEntry]; matchedText?: string }>;
selectedToolKey: string | null;
showDescriptions: boolean;
matchedTextMap: Map<string, string>;
onSearchChange: (value: string) => void;
onSelect: (id: ToolId) => void;
onToggleDescriptions: () => void;
onExitLegacyMode: () => void;
toggleLabel: string;
geometry: {
left: number;
top: number;
width: number;
height: number;
} | null;
}
const LegacyToolSurface = ({
searchQuery,
toolRegistry,
filteredTools,
selectedToolKey,
showDescriptions,
matchedTextMap,
onSearchChange,
onSelect,
onToggleDescriptions,
onExitLegacyMode,
toggleLabel,
geometry,
}: LegacyToolSurfaceProps) => {
const { t } = useTranslation();
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
onExitLegacyMode();
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [onExitLegacyMode]);
const style = geometry
? {
left: `${geometry.left}px`,
top: `${geometry.top}px`,
width: `${geometry.width}px`,
height: `${geometry.height}px`,
}
: undefined;
return (
<div
className="tool-panel__legacy-surface"
style={style}
role="region"
aria-label={t('toolPanel.legacy.heading', 'All tools (legacy view)')}
>
<div className="tool-panel__legacy-surface-inner">
<header className="tool-panel__legacy-header">
<div className="tool-panel__legacy-heading">
<Text fw={700} size="lg">
{t('toolPanel.legacy.heading', 'All tools (legacy view)')}
</Text>
<Text size="sm" c="dimmed">
{t('toolPanel.legacy.tagline', 'Browse and launch tools while keeping the classic full-width gallery.')}
</Text>
</div>
<Tooltip label={toggleLabel} position="bottom" withArrow>
<ActionIcon
variant="subtle"
radius="xl"
size="lg"
onClick={onExitLegacyMode}
aria-label={toggleLabel}
>
<ViewSidebarRoundedIcon fontSize="small" />
</ActionIcon>
</Tooltip>
</header>
<div className="tool-panel__legacy-controls">
<ToolSearch
value={searchQuery}
onChange={onSearchChange}
toolRegistry={toolRegistry}
mode="filter"
autoFocus
/>
<Switch
checked={showDescriptions}
onChange={() => onToggleDescriptions()}
size="md"
labelPosition="left"
label={showDescriptions ? t('toolPanel.legacy.descriptionsOn', 'Showing descriptions') : t('toolPanel.legacy.descriptionsOff', 'Descriptions hidden')}
/>
</div>
<div className="tool-panel__legacy-body">
<ScrollArea className="tool-panel__legacy-scroll" offsetScrollbars>
<LegacyToolList
filteredTools={filteredTools}
searchQuery={searchQuery}
showDescriptions={showDescriptions}
selectedToolKey={selectedToolKey}
matchedTextMap={matchedTextMap}
onSelect={onSelect}
/>
</ScrollArea>
</div>
</div>
</div>
);
};
export default LegacyToolSurface;

View File

@ -0,0 +1,351 @@
.tool-panel {
position: relative;
transition: width 0.3s ease, max-width 0.3s ease;
}
.tool-panel--legacy-active {
overflow: visible !important;
}
.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 1 auto;
}
.tool-panel__mode-toggle {
transition: transform 0.2s ease;
}
.tool-panel__mode-toggle:hover {
transform: scale(1.04);
}
.tool-panel--legacy {
background: var(--bg-toolbar);
}
.tool-panel__placeholder {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-muted);
font-size: 0.9rem;
padding: 1.5rem;
text-align: center;
}
.tool-panel__legacy-surface {
position: fixed;
display: flex;
pointer-events: none;
z-index: 1200;
top: 0;
left: 0;
width: 0;
height: 0;
}
.tool-panel__legacy-surface-inner {
pointer-events: auto;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
border-radius: 0 1.25rem 1.25rem 0;
background:
linear-gradient(
140deg,
color-mix(in srgb, var(--bg-toolbar) 96%, transparent),
color-mix(in srgb, var(--bg-background) 90%, transparent)
)
padding-box;
border: 1px solid color-mix(in srgb, var(--border-subtle) 75%, transparent);
box-shadow:
0 24px 64px color-mix(in srgb, var(--shadow-color, rgba(15, 23, 42, 0.55)) 25%, transparent),
0 6px 18px color-mix(in srgb, var(--shadow-color, rgba(15, 23, 42, 0.35)) 30%, transparent);
backdrop-filter: blur(18px);
overflow: hidden;
animation: tool-panel-legacy-slide 0.28s ease forwards;
}
.tool-panel__legacy-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 1rem;
padding: 1.25rem 1.75rem;
border-bottom: 1px solid color-mix(in srgb, var(--border-subtle) 70%, transparent);
background: linear-gradient(
180deg,
color-mix(in srgb, var(--bg-toolbar) 86%, transparent),
transparent 85%
);
}
.tool-panel__legacy-heading {
display: flex;
flex-direction: column;
gap: 0.3rem;
}
.tool-panel__legacy-controls {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem 1.75rem;
border-bottom: 1px solid color-mix(in srgb, var(--border-subtle) 70%, transparent);
background: linear-gradient(
180deg,
color-mix(in srgb, var(--bg-toolbar) 84%, transparent),
color-mix(in srgb, var(--bg-background) 72%, transparent)
);
}
.tool-panel__legacy-controls .search-input-container {
flex: 1 1 auto;
}
.tool-panel__legacy-body {
flex: 1;
min-height: 0;
background: linear-gradient(
180deg,
color-mix(in srgb, var(--bg-background) 86%, transparent),
color-mix(in srgb, var(--bg-toolbar) 78%, transparent)
);
}
.tool-panel__legacy-scroll {
height: 100%;
}
/* legacy group layout */
.tool-panel__legacy-groups {
padding: 1.5rem 1.75rem;
column-width: 18rem;
column-gap: 1.5rem;
}
.tool-panel__legacy-groups--compact {
column-width: 17rem;
}
.tool-panel__legacy-groups--detailed {
column-width: auto;
}
.tool-panel__legacy-group {
display: flex;
flex-direction: column;
gap: 0.75rem;
width: 100%;
margin: 0 0 1.5rem;
padding: 0.65rem 0.75rem 1rem;
border-radius: 1rem;
background: color-mix(in srgb, var(--bg-toolbar) 82%, transparent);
border: 1px solid color-mix(in srgb, var(--border-subtle) 65%, transparent);
box-shadow: 0 14px 32px color-mix(in srgb, var(--shadow-color, rgba(15, 23, 42, 0.45)) 18%, transparent);
break-inside: avoid;
backdrop-filter: blur(10px);
}
.tool-panel__legacy-section-header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.1rem 0.15rem 0.35rem;
}
.tool-panel__legacy-grid {
display: grid;
gap: 0.75rem;
}
.tool-panel__legacy-grid--detailed {
grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
}
.tool-panel__legacy-item {
all: unset;
display: flex;
flex-direction: row;
gap: 0.75rem;
align-items: flex-start;
padding: 0.85rem 0.95rem;
border: 1px solid color-mix(in srgb, var(--border-subtle) 70%, transparent);
border-radius: 0.95rem;
background: color-mix(in srgb, var(--bg-toolbar) 88%, transparent);
backdrop-filter: blur(6px);
cursor: pointer;
transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
width: 100%;
box-sizing: border-box;
}
.tool-panel__legacy-item:focus-visible {
outline: 2px solid var(--accent-primary, var(--mantine-color-pink-6));
outline-offset: 3px;
}
.tool-panel__legacy-item[aria-disabled="true"],
.tool-panel__legacy-item:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.tool-panel__legacy-item:hover:not([aria-disabled="true"]):not(:disabled) {
transform: translateY(-2px);
border-color: color-mix(in srgb, var(--accent-primary, var(--mantine-color-pink-6)) 38%, var(--border-subtle));
box-shadow: var(--shadow-xl, 0 18px 34px rgba(15, 23, 42, 0.14));
}
.tool-panel__legacy-item--selected {
border-color: color-mix(in srgb, var(--accent-primary, var(--mantine-color-pink-6)) 52%, var(--border-subtle));
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent-primary, var(--mantine-color-pink-6)) 28%, transparent);
}
.tool-panel__legacy-item--detailed {
min-height: 7.5rem;
}
.tool-panel__legacy-icon {
display: flex;
align-items: center;
justify-content: center;
width: 2.75rem;
height: 2.75rem;
border-radius: 0.75rem;
background: color-mix(in srgb, var(--bg-muted) 75%, transparent);
color: color-mix(in srgb, var(--text-primary) 90%, var(--text-muted));
flex-shrink: 0;
}
.tool-panel__legacy-icon svg {
font-size: 1.65rem;
}
.tool-panel__legacy-body {
display: flex;
flex-direction: column;
gap: 0.4rem;
text-align: left;
}
.tool-panel__legacy-name {
color: var(--text-primary);
}
.tool-panel__legacy-description {
line-height: 1.45;
}
.tool-panel__legacy-match {
font-style: italic;
}
.tool-panel__legacy-list {
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.tool-panel__legacy-list-item {
all: unset;
display: flex;
align-items: center;
gap: 0.6rem;
padding: 0.55rem 0.5rem 0.55rem 0.65rem;
border-radius: 0.65rem;
cursor: pointer;
transition: background 0.2s ease, transform 0.2s ease;
background: color-mix(in srgb, var(--bg-toolbar) 86%, transparent);
border: 1px solid transparent;
width: 100%;
box-sizing: border-box;
}
.tool-panel__legacy-list-item[aria-disabled="true"],
.tool-panel__legacy-list-item:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.tool-panel__legacy-list-item:hover:not([aria-disabled="true"]):not(:disabled),
.tool-panel__legacy-list-item--selected {
background: color-mix(in srgb, var(--accent-primary, var(--mantine-color-pink-6)) 22%, var(--bg-toolbar));
border-color: color-mix(in srgb, var(--accent-primary, var(--mantine-color-pink-6)) 30%, var(--border-subtle));
}
.tool-panel__legacy-list-item--selected {
transform: translateX(2px);
}
.tool-panel__legacy-list-icon {
display: flex;
align-items: center;
justify-content: center;
width: 2.1rem;
height: 2.1rem;
border-radius: 0.6rem;
background: color-mix(in srgb, var(--bg-muted) 70%, transparent);
color: color-mix(in srgb, var(--text-primary) 88%, var(--text-muted));
flex-shrink: 0;
}
.tool-panel__legacy-list-body {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.2rem;
text-align: left;
}
.tool-panel__legacy-empty {
padding: 2rem 1.75rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
@keyframes tool-panel-legacy-slide {
from {
transform: translateX(-6%) scaleX(0.85);
opacity: 0;
}
to {
transform: translateX(0) scaleX(1);
opacity: 1;
}
}
@media (max-width: 1440px) {
.tool-panel__legacy-content {
padding-inline: 1.5rem;
}
.tool-panel__legacy-grid--compact {
column-width: 15rem;
}
}
@media (max-width: 1280px) {
.tool-panel__legacy-controls {
flex-direction: column;
align-items: stretch;
}
.tool-panel__legacy-controls .mantine-Switch-root {
justify-content: flex-end;
}
.tool-panel__legacy-grid--compact {
column-width: 14rem;
}
}

View File

@ -1,3 +1,4 @@
import { useEffect, useMemo, useState, useLayoutEffect } from 'react';
import { useRainbowThemeContext } from '../shared/RainbowThemeProvider';
import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
import ToolPicker from './ToolPicker';
@ -6,20 +7,24 @@ 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, ScrollArea, 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 LegacyToolSurface from './LegacyToolSurface';
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 { toolPanelRef, quickAccessRef } = sidebarRefs;
const isMobile = useMediaQuery('(max-width: 1024px)');
// Use context-based hooks to eliminate prop drilling
const {
leftPanelView,
isPanelVisible,
@ -27,84 +32,235 @@ export default function ToolPanel() {
filteredTools,
toolRegistry,
setSearchQuery,
selectedToolKey,
handleToolSelect,
setPreviewFile,
toolPanelMode,
setToolPanelMode,
setLeftPanelView,
} = useToolWorkflow();
const { selectedToolKey, handleToolSelect } = useToolWorkflow();
const { setPreviewFile } = useToolWorkflow();
const isLegacyMode = toolPanelMode === 'legacy';
const legacyExpanded = isLegacyMode && leftPanelView === 'toolPicker' && !isMobile;
const [legacyGeometry, setLegacyGeometry] = useState<{ left: number; top: number; width: number; height: number } | null>(null);
const LEGACY_DESCRIPTION_STORAGE_KEY = 'legacyToolDescriptions';
const [showLegacyDescriptions, setShowLegacyDescriptions] = useState(() => {
if (typeof window === 'undefined') {
return true;
}
const stored = window.localStorage.getItem(LEGACY_DESCRIPTION_STORAGE_KEY);
if (stored === null) {
return true;
}
return stored === 'true';
});
useEffect(() => {
if (typeof window === 'undefined') {
return;
}
window.localStorage.setItem(LEGACY_DESCRIPTION_STORAGE_KEY, String(showLegacyDescriptions));
}, [showLegacyDescriptions]);
useLayoutEffect(() => {
if (!legacyExpanded) {
setLegacyGeometry(null);
return;
}
const panelEl = toolPanelRef.current;
if (!panelEl) {
setLegacyGeometry(null);
return;
}
const rightRailEl = () => document.querySelector('[data-sidebar="right-rail"]') as HTMLElement | null;
const updateGeometry = () => {
const rect = panelEl.getBoundingClientRect();
const rail = rightRailEl();
const rightOffset = rail ? Math.max(0, window.innerWidth - rail.getBoundingClientRect().left) : 0;
const width = Math.max(360, window.innerWidth - rect.left - rightOffset);
const height = Math.max(rect.height, window.innerHeight - rect.top);
setLegacyGeometry({
left: rect.left,
top: rect.top,
width,
height,
});
};
updateGeometry();
const handleResize = () => updateGeometry();
window.addEventListener('resize', handleResize);
let resizeObserver: ResizeObserver | null = null;
if (typeof ResizeObserver !== 'undefined') {
resizeObserver = new ResizeObserver(() => updateGeometry());
resizeObserver.observe(panelEl);
if (quickAccessRef.current) {
resizeObserver.observe(quickAccessRef.current);
}
const rail = rightRailEl();
if (rail) {
resizeObserver.observe(rail);
}
}
return () => {
window.removeEventListener('resize', handleResize);
resizeObserver?.disconnect();
};
}, [legacyExpanded, quickAccessRef, toolPanelRef]);
const toggleLabel = isLegacyMode
? t('toolPanel.toggle.sidebar', 'Switch to sidebar mode')
: t('toolPanel.toggle.legacy', 'Switch to legacy mode');
const handleModeToggle = () => {
const nextMode = isLegacyMode ? 'sidebar' : 'legacy';
setToolPanelMode(nextMode);
if (nextMode === 'legacy' && leftPanelView !== 'toolPicker') {
setLeftPanelView('toolPicker');
}
};
const computedWidth = () => {
if (isMobile) {
return '100%';
}
if (!isPanelVisible) {
return '0';
}
return '18.5rem';
};
const matchedTextMap = useMemo(() => {
const map = new Map<string, string>();
filteredTools.forEach(({ item: [id], matchedText }) => {
if (matchedText) {
map.set(id, matchedText);
}
});
return map;
}, [filteredTools]);
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 ${legacyExpanded ? 'tool-panel--legacy-active' : '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'} ${legacyExpanded ? 'tool-panel--legacy' : ''}`}
style={{
width: isMobile ? '100%' : isPanelVisible ? '18.5rem' : '0',
width: computedWidth(),
padding: '0'
}}
>
<div
style={{
opacity: isMobile || isPanelVisible ? 1 : 0,
transition: 'opacity 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)',
height: '100%',
display: 'flex',
flexDirection: 'column'
}}
>
{/* Search Bar - Always visible at the top */}
{!legacyExpanded && (
<div
style={{
backgroundColor: 'var(--tool-panel-search-bg)',
borderBottom: '1px solid var(--tool-panel-search-border-bottom)',
padding: '0.75rem 1rem',
opacity: isMobile || isPanelVisible ? 1 : 0,
transition: 'opacity 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)',
height: '100%',
display: 'flex',
flexDirection: 'column'
}}
>
<ToolSearch
value={searchQuery}
onChange={setSearchQuery}
toolRegistry={toolRegistry}
mode="filter"
/>
</div>
<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="bottom" withArrow>
<ActionIcon
variant="subtle"
radius="xl"
color="gray"
onClick={handleModeToggle}
aria-label={toggleLabel}
className="tool-panel__mode-toggle"
>
{isLegacyMode ? (
<ViewSidebarRoundedIcon fontSize="small" />
) : (
<DashboardCustomizeRoundedIcon fontSize="small" />
)}
</ActionIcon>
</Tooltip>
)}
</div>
{searchQuery.trim().length > 0 ? (
// Searching view (replaces both picker and content)
<div className="flex-1 flex flex-col overflow-y-auto">
{searchQuery.trim().length > 0 ? (
<div className="flex-1 flex flex-col overflow-y-auto">
<SearchResults
filteredTools={filteredTools}
onSelect={(id) => handleToolSelect(id as ToolId)}
searchQuery={searchQuery}
/>
</div>
) : leftPanelView === 'toolPicker' ? (
// Tool Picker View
<div className="flex-1 flex flex-col overflow-auto">
<ToolPicker
selectedToolKey={selectedToolKey}
onSelect={(id) => handleToolSelect(id as ToolId)}
filteredTools={filteredTools}
isSearching={Boolean(searchQuery && searchQuery.trim().length > 0)}
/>
</div>
) : (
// Selected Tool Content View
<div className="flex-1 flex flex-col overflow-hidden">
{/* Tool content */}
<div className="flex-1 min-h-0 overflow-hidden">
<ScrollArea h="100%">
{selectedToolKey && (
<ToolRenderer
selectedToolKey={selectedToolKey}
onPreviewFile={setPreviewFile}
/>
)}
</ScrollArea>
</div>
</div>
)}
</div>
) : leftPanelView === 'toolPicker' ? (
<div className="flex-1 flex flex-col overflow-auto">
<ToolPicker
selectedToolKey={selectedToolKey}
onSelect={(id) => handleToolSelect(id as ToolId)}
filteredTools={filteredTools}
isSearching={Boolean(searchQuery && searchQuery.trim().length > 0)}
/>
</div>
) : (
<div className="flex-1 flex flex-col overflow-hidden">
<div className="flex-1 min-h-0 overflow-hidden">
<ScrollArea h="100%">
{selectedToolKey ? (
<ToolRenderer
selectedToolKey={selectedToolKey}
onPreviewFile={setPreviewFile}
/>
) : (
<div className="tool-panel__placeholder">
{t('toolPanel.placeholder', 'Choose a tool to get started')}
</div>
)}
</ScrollArea>
</div>
</div>
)}
</div>
)}
{legacyExpanded && (
<LegacyToolSurface
searchQuery={searchQuery}
toolRegistry={toolRegistry}
filteredTools={filteredTools}
selectedToolKey={selectedToolKey}
showDescriptions={showLegacyDescriptions}
matchedTextMap={matchedTextMap}
onSearchChange={setSearchQuery}
onSelect={(id) => handleToolSelect(id as ToolId)}
onToggleDescriptions={() => setShowLegacyDescriptions((prev) => !prev)}
onExitLegacyMode={() => setToolPanelMode('sidebar')}
toggleLabel={toggleLabel}
geometry={legacyGeometry}
/>
)}
</div>
);
}

View File

@ -0,0 +1,167 @@
.tool-panel-mode-prompt__modal {
background: color-mix(in srgb, var(--bg-toolbar) 94%, transparent);
border: 1px solid color-mix(in srgb, var(--border-subtle) 70%, transparent);
box-shadow: 0 32px 64px color-mix(in srgb, var(--shadow-color, rgba(15, 23, 42, 0.55)) 20%, transparent);
max-width: min(46rem, 100%);
}
.tool-panel-mode-prompt__options {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1.5rem;
}
.tool-panel-mode-prompt__card {
display: flex;
flex-direction: column;
gap: 1rem;
background: linear-gradient(145deg,
color-mix(in srgb, var(--bg-surface) 96%, transparent),
color-mix(in srgb, var(--bg-muted) 70%, transparent)
);
width: 100%;
max-width: 19rem;
}
.tool-panel-mode-prompt__card--sidebar {
border: 1px solid color-mix(in srgb, var(--accent-primary, var(--mantine-color-blue-6, #228be6)) 18%, var(--border-subtle));
background: linear-gradient(165deg,
color-mix(in srgb, var(--bg-surface) 96%, transparent),
color-mix(in srgb, var(--accent-primary, var(--mantine-color-blue-6, #228be6)) 8%, transparent)
);
}
.tool-panel-mode-prompt__preview {
border-radius: 0.9rem;
border: 1px solid color-mix(in srgb, var(--border-subtle) 70%, transparent);
background: linear-gradient(135deg, color-mix(in srgb, var(--bg-muted) 82%, transparent), transparent 75%);
padding: 0.75rem;
width: 100%;
display: flex;
gap: 0.65rem;
min-height: 6.5rem;
margin-top: auto;
}
.tool-panel-mode-prompt__preview--sidebar {
align-items: stretch;
}
.tool-panel-mode-prompt__sidebar-panel {
width: 3rem;
border-radius: 0.65rem;
padding: 0.45rem;
display: flex;
flex-direction: column;
gap: 0.45rem;
background: linear-gradient(180deg,
color-mix(in srgb, var(--bg-muted) 88%, transparent),
color-mix(in srgb, var(--bg-muted) 72%, transparent)
);
border: 1px solid color-mix(in srgb, var(--border-subtle) 65%, transparent);
}
.tool-panel-mode-prompt__sidebar-search {
height: 0.5rem;
border-radius: 0.4rem;
background: color-mix(in srgb, var(--bg-background) 90%, transparent);
border: 1px solid color-mix(in srgb, var(--border-subtle) 60%, transparent);
}
.tool-panel-mode-prompt__sidebar-item {
height: 0.55rem;
border-radius: 0.35rem;
background: color-mix(in srgb, var(--accent-primary, var(--mantine-color-blue-6, #228be6)) 32%, var(--bg-muted));
}
.tool-panel-mode-prompt__sidebar-item--muted {
background: color-mix(in srgb, var(--bg-background) 88%, transparent);
}
.tool-panel-mode-prompt__workspace {
flex: 1;
border-radius: 0.65rem;
border: 1px solid color-mix(in srgb, var(--border-subtle) 65%, transparent);
padding: 0.5rem;
display: grid;
gap: 0.35rem;
grid-template-rows: 1.4fr 0.6fr;
background: linear-gradient(160deg,
color-mix(in srgb, var(--bg-background) 94%, transparent),
color-mix(in srgb, var(--bg-muted) 68%, transparent)
);
}
.tool-panel-mode-prompt__workspace-page {
border-radius: 0.45rem;
background: color-mix(in srgb, var(--bg-surface) 96%, transparent);
border: 1px solid color-mix(in srgb, var(--border-subtle) 55%, transparent);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bg-background) 60%, transparent);
}
.tool-panel-mode-prompt__workspace-page--secondary {
opacity: 0.55;
}
.tool-panel-mode-prompt__preview--legacy {
align-items: flex-start;
justify-content: center;
padding: 0.65rem 0.6rem;
}
.tool-panel-mode-prompt__legacy-columns {
width: 100%;
display: flex;
gap: 0.45rem;
justify-content: space-between;
}
.tool-panel-mode-prompt__legacy-column {
flex: 1;
display: grid;
gap: 0.3rem;
}
.tool-panel-mode-prompt__legacy-card {
border-radius: 0.45rem;
border: 1px solid color-mix(in srgb, var(--border-subtle) 55%, transparent);
background: linear-gradient(150deg,
color-mix(in srgb, var(--bg-muted) 88%, transparent),
color-mix(in srgb, var(--bg-background) 76%, transparent)
);
height: 1.2rem;
}
.tool-panel-mode-prompt__legacy-card--muted {
opacity: 0.55;
}
.tool-panel-mode-prompt__card-content {
display: flex;
flex-direction: column;
gap: 1rem;
flex: 1;
}
.tool-panel-mode-prompt__action {
box-shadow: none;
}
.tool-panel-mode-prompt__action:hover {
box-shadow: 0 10px 18px color-mix(in srgb, var(--accent-primary, var(--mantine-color-blue-6, #228be6)) 25%, transparent);
}
.tool-panel-mode-prompt__maybe-later {
color: color-mix(in srgb, var(--text-secondary) 90%, var(--text-muted));
}
.tool-panel-mode-prompt__maybe-later:hover {
background: color-mix(in srgb, var(--bg-muted) 78%, transparent);
}
@media (max-width: 600px) {
.tool-panel-mode-prompt__options {
grid-template-columns: 1fr;
justify-items: stretch;
}
}

View File

@ -0,0 +1,161 @@
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_STORAGE_KEY } from '../../contexts/ToolWorkflowContext';
import './ToolPanelModePrompt.css';
type ToolPanelModeOption = 'sidebar' | 'legacy';
const PROMPT_SEEN_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_SEEN_KEY);
const storedPreference = window.localStorage.getItem(TOOL_PANEL_MODE_STORAGE_KEY);
if (!hasSeenPrompt && !storedPreference) {
setOpened(true);
}
setHydrated(true);
}, []);
const persistSeen = () => {
if (typeof window === 'undefined') {
return;
}
window.localStorage.setItem(PROMPT_SEEN_KEY, 'true');
};
const handleSelect = (mode: ToolPanelModeOption) => {
setToolPanelMode(mode);
persistSeen();
setOpened(false);
};
const handleDismiss = () => {
persistSeen();
setOpened(false);
};
if (!hydrated) {
return null;
}
return (
<Modal
opened={opened}
onClose={handleDismiss}
centered
size="xl"
radius="lg"
overlayProps={{ blur: 6, opacity: 0.35 }}
classNames={{ content: 'tool-panel-mode-prompt__modal' }}
title={t('toolPanel.modePrompt.title', 'Choose how you browse tools')}
>
<Stack gap="lg">
<Text size="sm" c="dimmed">
{t('toolPanel.modePrompt.description', 'Preview both layouts and decide how you want to explore Stirling PDF tools.')}
</Text>
<div className="tool-panel-mode-prompt__options">
<Card withBorder radius="lg" shadow="sm" padding="lg" className="tool-panel-mode-prompt__card tool-panel-mode-prompt__card--sidebar">
<Stack gap="md" className="tool-panel-mode-prompt__card-content">
<Group justify="space-between">
<Stack gap={2}>
<Text fw={600}>{t('toolPanel.modePrompt.sidebarTitle', 'Advanced sidebar')}</Text>
<Text size="sm" c="dimmed">
{t('toolPanel.modePrompt.sidebarDescription', 'Keep tools alongside your workspace for quick switching.')}
</Text>
</Stack>
<Badge color="blue" variant="filled">
{t('toolPanel.modePrompt.recommended', 'Recommended')}
</Badge>
</Group>
<div className="tool-panel-mode-prompt__preview tool-panel-mode-prompt__preview--sidebar" aria-hidden>
<div className="tool-panel-mode-prompt__sidebar-panel">
<span className="tool-panel-mode-prompt__sidebar-search" />
<span className="tool-panel-mode-prompt__sidebar-item" />
<span className="tool-panel-mode-prompt__sidebar-item" />
<span className="tool-panel-mode-prompt__sidebar-item" />
<span className="tool-panel-mode-prompt__sidebar-item tool-panel-mode-prompt__sidebar-item--muted" />
</div>
<div className="tool-panel-mode-prompt__workspace" aria-hidden>
<div className="tool-panel-mode-prompt__workspace-page" />
<div className="tool-panel-mode-prompt__workspace-page tool-panel-mode-prompt__workspace-page--secondary" />
</div>
</div>
<Button
variant={toolPanelMode === 'sidebar' ? 'filled' : 'light'}
color="blue"
radius="md"
className="tool-panel-mode-prompt__action"
onClick={() => handleSelect('sidebar')}
>
{t('toolPanel.modePrompt.chooseSidebar', 'Use advanced sidebar')}
</Button>
</Stack>
</Card>
<Card withBorder radius="lg" shadow="xs" padding="lg" className="tool-panel-mode-prompt__card">
<Stack gap="md" className="tool-panel-mode-prompt__card-content">
<Stack gap={2}>
<Text fw={600}>{t('toolPanel.modePrompt.legacyTitle', 'Legacy fullscreen')}</Text>
<Text size="sm" c="dimmed">
{t('toolPanel.modePrompt.legacyDescription', 'Browse every tool in a catalogue that covers the workspace until you pick one.')}
</Text>
</Stack>
<div className="tool-panel-mode-prompt__preview tool-panel-mode-prompt__preview--legacy" aria-hidden>
<div className="tool-panel-mode-prompt__legacy-columns">
<div className="tool-panel-mode-prompt__legacy-column">
<span className="tool-panel-mode-prompt__legacy-card" />
<span className="tool-panel-mode-prompt__legacy-card" />
<span className="tool-panel-mode-prompt__legacy-card tool-panel-mode-prompt__legacy-card--muted" />
</div>
<div className="tool-panel-mode-prompt__legacy-column">
<span className="tool-panel-mode-prompt__legacy-card" />
<span className="tool-panel-mode-prompt__legacy-card" />
<span className="tool-panel-mode-prompt__legacy-card tool-panel-mode-prompt__legacy-card--muted" />
</div>
<div className="tool-panel-mode-prompt__legacy-column">
<span className="tool-panel-mode-prompt__legacy-card" />
<span className="tool-panel-mode-prompt__legacy-card" />
<span className="tool-panel-mode-prompt__legacy-card tool-panel-mode-prompt__legacy-card--muted" />
</div>
</div>
</div>
<Button
variant={toolPanelMode === 'legacy' ? 'filled' : 'outline'}
color="blue"
radius="md"
className="tool-panel-mode-prompt__action"
onClick={() => handleSelect('legacy')}
>
{t('toolPanel.modePrompt.chooseLegacy', 'Use legacy fullscreen')}
</Button>
</Stack>
</Card>
</div>
<Button
variant="subtle"
color="gray"
radius="md"
className="tool-panel-mode-prompt__maybe-later"
onClick={handleDismiss}
>
{t('toolPanel.modePrompt.dismiss', 'Maybe later')}
</Button>
</Stack>
</Modal>
);
};
export default ToolPanelModePrompt;

View File

@ -14,11 +14,14 @@ import { getDefaultWorkbench } from '../types/workbench';
import { filterToolRegistryByQuery } from '../utils/toolSearch';
// State interface
type ToolPanelMode = 'sidebar' | 'legacy';
interface ToolWorkflowState {
// UI State
sidebarsVisible: boolean;
leftPanelView: 'toolPicker' | 'toolContent' | 'hidden';
readerMode: boolean;
toolPanelMode: ToolPanelMode;
// File/Preview State
previewFile: File | null;
@ -33,13 +36,29 @@ 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_STORAGE_KEY = 'toolPanelModePreference';
const getStoredToolPanelMode = (): ToolPanelMode => {
if (typeof window === 'undefined') {
return 'sidebar';
}
const stored = window.localStorage.getItem(TOOL_PANEL_MODE_STORAGE_KEY);
if (stored === 'legacy' || stored === 'fullscreen') {
return 'legacy';
}
return 'sidebar';
};
const baseState: Omit<ToolWorkflowState, 'toolPanelMode'> = {
sidebarsVisible: true,
leftPanelView: 'toolPicker',
readerMode: false,
@ -48,6 +67,11 @@ const initialState: ToolWorkflowState = {
searchQuery: '',
};
const createInitialState = (): ToolWorkflowState => ({
...baseState,
toolPanelMode: getStoredToolPanelMode(),
});
// Reducer
function toolWorkflowReducer(state: ToolWorkflowState, action: ToolWorkflowAction): ToolWorkflowState {
switch (action.type) {
@ -57,6 +81,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 +90,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 {
...baseState,
toolPanelMode: state.toolPanelMode,
searchQuery: state.searchQuery,
};
default:
return state;
}
@ -82,6 +112,7 @@ interface ToolWorkflowContextValue extends ToolWorkflowState {
setSidebarsVisible: (visible: boolean) => void;
setLeftPanelView: (view: 'toolPicker' | 'toolContent' | 'hidden') => void;
setReaderMode: (mode: boolean) => void;
setToolPanelMode: (mode: ToolPanelMode) => void;
setPreviewFile: (file: File | null) => void;
setPageEditorFunctions: (functions: PageEditorFunctions | null) => void;
setSearchQuery: (query: string) => void;
@ -113,7 +144,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>>({});
@ -148,6 +179,10 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
dispatch({ type: 'SET_READER_MODE', payload: mode });
}, [actions]);
const setToolPanelMode = useCallback((mode: ToolPanelMode) => {
dispatch({ type: 'SET_TOOL_PANEL_MODE', payload: mode });
}, []);
const setPreviewFile = useCallback((file: File | null) => {
dispatch({ type: 'SET_PREVIEW_FILE', payload: file });
if (file) {
@ -163,6 +198,14 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
dispatch({ type: 'SET_SEARCH_QUERY', payload: query });
}, []);
React.useEffect(() => {
if (typeof window === 'undefined') {
return;
}
window.localStorage.setItem(TOOL_PANEL_MODE_STORAGE_KEY, state.toolPanelMode);
}, [state.toolPanelMode]);
// Tool reset methods
const registerToolReset = useCallback((toolId: string, resetFunction: () => void) => {
setToolResetFunctions(prev => ({ ...prev, [toolId]: resetFunction }));
@ -261,6 +304,7 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
setSidebarsVisible,
setLeftPanelView,
setReaderMode,
setToolPanelMode,
setPreviewFile,
setPageEditorFunctions,
setSearchQuery,
@ -289,6 +333,7 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
setSidebarsVisible,
setLeftPanelView,
setReaderMode,
setToolPanelMode,
setPreviewFile,
setPageEditorFunctions,
setSearchQuery,

View File

@ -16,6 +16,7 @@ import FileManager from "../components/FileManager";
import LocalIcon from "../components/shared/LocalIcon";
import { useFilesModalContext } from "../contexts/FilesModalContext";
import AppConfigModal from "../components/shared/AppConfigModal";
import ToolPanelModePrompt from "../components/tools/ToolPanelModePrompt";
import "./HomePage.css";
@ -30,7 +31,12 @@ export default function HomePage() {
const { quickAccessRef } = sidebarRefs;
const { selectedTool, selectedToolKey, handleToolSelect, handleBackToTools } = useToolWorkflow();
const {
selectedTool,
selectedToolKey,
handleToolSelect,
handleBackToTools,
} = useToolWorkflow();
const { openFilesModal } = useFilesModalContext();
const { colorScheme } = useMantineColorScheme();
@ -126,6 +132,7 @@ export default function HomePage() {
return (
<div className="h-screen overflow-hidden">
<ToolPanelModePrompt />
{isMobile ? (
<div className="mobile-layout">
<div className="mobile-toggle">
@ -231,8 +238,7 @@ export default function HomePage() {
h="100%"
className="flex-nowrap flex"
>
<QuickAccessBar
ref={quickAccessRef} />
<QuickAccessBar ref={quickAccessRef} />
<ToolPanel />
<Workbench />
<RightRail />

View File

@ -445,4 +445,11 @@
/* Smooth transitions for theme switching */
* {
transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}
}
:root {
--shadow-color: rgba(15, 23, 42, 0.55);
}
[data-theme="dark"] {
--shadow-color: rgba(0, 0, 0, 0.75);
}