rename legacy -> full screen, add a badge component to be used in both tool pickers, disable right rail buttons when full screen tool picker is active to minamize confusion, change initial startup prompt wording, create themes for colors

This commit is contained in:
EthanHealy01
2025-10-07 17:08:04 +01:00
parent 3c0714107b
commit d666672e30
24 changed files with 937 additions and 724 deletions

View File

@@ -1,4 +1,21 @@
{
"toolPanel": {
"modePrompt": {
"title": "Choose how you browse tools",
"description": "Preview both layouts and decide how you want to explore Stirling PDF tools.",
"sidebarTitle": "Sidebar mode",
"sidebarDescription": "Keep tools alongside your workspace for quick switching.",
"recommended": "Recommended",
"chooseSidebar": "Use sidebar mode",
"fullscreenTitle": "Fullscreen mode - (legacy)",
"fullscreenDescription": "Browse every tool in a catalogue that covers the workspace until you pick one.",
"chooseFullscreen": "Use fullscreen mode",
"dismiss": "Maybe later"
},
"fullscreen": {
"showDetails": "Show Details"
}
},
"unsavedChanges": "You have unsaved changes to your PDF.",
"areYouSure": "Are you sure you want to leave?",
"unsavedChangesTitle": "Unsaved Changes",

View File

@@ -2,6 +2,23 @@
"language": {
"direction": "ltr"
},
"toolPanel": {
"modePrompt": {
"title": "Choose how you browse tools",
"description": "Preview both layouts and decide how you want to explore Stirling PDF tools.",
"sidebarTitle": "Sidebar mode",
"sidebarDescription": "Keep tools alongside your workspace for quick switching.",
"recommended": "Recommended",
"chooseSidebar": "Use sidebar mode",
"fullscreenTitle": "Fullscreen mode - (legacy)",
"fullscreenDescription": "Browse every tool in a catalogue that covers the workspace until you pick one.",
"chooseFullscreen": "Use fullscreen mode",
"dismiss": "Maybe later"
},
"fullscreen": {
"showDetails": "Show Details"
}
},
"addPageNumbers": {
"fontSize": "Font Size",
"fontName": "Font Name",

View File

@@ -0,0 +1,101 @@
import React from 'react';
import { Box } from '@mantine/core';
interface BadgeProps {
children: React.ReactNode;
size?: 'sm' | 'md' | 'lg';
variant?: 'default' | 'colored';
color?: string;
textColor?: string;
backgroundColor?: string;
className?: string;
style?: React.CSSProperties;
}
const Badge: React.FC<BadgeProps> = ({
children,
size = 'sm',
variant = 'default',
color,
textColor,
backgroundColor,
className,
style
}) => {
const getSizeStyles = () => {
switch (size) {
case 'sm':
return {
padding: '0.125rem 0.5rem',
fontSize: '0.75rem',
fontWeight: 700,
borderRadius: '0.5rem',
};
case 'md':
return {
padding: '0.25rem 0.75rem',
fontSize: '0.875rem',
fontWeight: 700,
borderRadius: '0.625rem',
};
case 'lg':
return {
padding: '0.375rem 1rem',
fontSize: '1rem',
fontWeight: 700,
borderRadius: '0.75rem',
};
default:
return {};
}
};
const getVariantStyles = () => {
// If explicit colors are provided, use them
if (textColor && backgroundColor) {
return {
backgroundColor,
color: textColor,
};
}
// If a single color is provided, use it for text and 20% opacity for background
if (color) {
return {
backgroundColor: `color-mix(in srgb, ${color} 20%, transparent)`,
color: color,
};
}
// If variant is colored but no color provided, use default colored styling
if (variant === 'colored') {
return {
backgroundColor: `color-mix(in srgb, var(--category-color-default) 15%, transparent)`,
color: 'var(--category-color-default)',
borderColor: `color-mix(in srgb, var(--category-color-default) 30%, transparent)`,
border: '1px solid',
};
}
// Default styling
return {
background: 'var(--tool-header-badge-bg)',
color: 'var(--tool-header-badge-text)',
};
};
return (
<Box
className={className}
style={{
...getSizeStyles(),
...getVariantStyles(),
...style,
}}
>
{children}
</Box>
);
};
export default Badge;

View File

@@ -209,6 +209,7 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({ position = 'bottom-
width={600}
position={position}
offset={offset}
zIndex={1400}
transitionProps={{
transition: 'scale-y',
duration: 200,
@@ -264,6 +265,7 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({ position = 'bottom-
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
backgroundColor: 'light-dark(var(--mantine-color-white), var(--mantine-color-dark-6))',
border: 'light-dark(1px solid var(--mantine-color-gray-3), 1px solid var(--mantine-color-dark-4))',
zIndex: 1400,
}}
>
<ScrollArea h={190} type="scroll">

View File

@@ -98,24 +98,21 @@ const QuickAccessBar = forwardRef<HTMLDivElement>((_, ref) => {
type: 'navigation',
onClick: () => {
setActiveButton('read');
handleBackToTools();
handleReaderToggle();
setToolPanelMode('sidebar');
}
},
// TODO: Add sign
//{
// id: 'sign',
// name: t("quickAccess.sign", "Sign"),
// icon: <LocalIcon icon="signature-rounded" width="1.25rem" height="1.25rem" />,
// size: 'lg',
// isRound: false,
// type: 'navigation',
// onClick: () => {
// setActiveButton('sign');
// handleToolSelect('sign');
// }
//},
{
id: 'sign',
name: t("quickAccess.sign", "Sign"),
icon: <LocalIcon icon="signature-rounded" width="1.25rem" height="1.25rem" />,
size: 'lg',
isRound: false,
type: 'navigation',
onClick: () => {
setActiveButton('sign');
handleToolSelect('sign');
}
},
{
id: 'automate',
name: t("quickAccess.automate", "Automate"),

View File

@@ -27,7 +27,8 @@ export default function RightRail() {
// Viewer context for PDF controls - safely handle when not available
const viewerContext = React.useContext(ViewerContext);
const { toggleTheme } = useRainbowThemeContext();
const { buttons, actions } = useRightRail();
const { buttons, actions, allButtonsDisabled } = useRightRail();
const topButtons = useMemo(() => buttons.filter(b => (b.section || 'top') === 'top' && (b.visible ?? true)), [buttons]);
// Access PageEditor functions for page-editor-specific actions
@@ -176,7 +177,7 @@ export default function RightRail() {
}, [currentView]);
return (
<div className="right-rail" data-sidebar="right-rail">
<div className={`right-rail`} data-sidebar="right-rail">
<div className="right-rail-inner">
{topButtons.length > 0 && (
<>
@@ -188,7 +189,7 @@ export default function RightRail() {
radius="md"
className="right-rail-icon"
onClick={() => actions[btn.id]?.()}
disabled={btn.disabled}
disabled={btn.disabled || allButtonsDisabled}
>
{btn.icon}
</ActionIcon>
@@ -214,7 +215,7 @@ export default function RightRail() {
variant="subtle"
radius="md"
className="right-rail-icon"
disabled={currentView !== 'viewer'}
disabled={currentView !== 'viewer' || allButtonsDisabled}
aria-label={typeof t === 'function' ? t('rightRail.search', 'Search PDF') : 'Search PDF'}
>
<LocalIcon icon="search" width="1.5rem" height="1.5rem" />
@@ -244,7 +245,7 @@ export default function RightRail() {
viewerContext?.panActions.togglePan();
setIsPanning(!isPanning);
}}
disabled={currentView !== 'viewer'}
disabled={currentView !== 'viewer' || allButtonsDisabled}
>
<LocalIcon icon="pan-tool-rounded" width="1.5rem" height="1.5rem" />
</ActionIcon>
@@ -259,7 +260,7 @@ export default function RightRail() {
onClick={() => {
viewerContext?.rotationActions.rotateBackward();
}}
disabled={currentView !== 'viewer'}
disabled={currentView !== 'viewer' || allButtonsDisabled}
>
<LocalIcon icon="rotate-left" width="1.5rem" height="1.5rem" />
</ActionIcon>
@@ -274,7 +275,7 @@ export default function RightRail() {
onClick={() => {
viewerContext?.rotationActions.rotateForward();
}}
disabled={currentView !== 'viewer'}
disabled={currentView !== 'viewer' || allButtonsDisabled}
>
<LocalIcon icon="rotate-right" width="1.5rem" height="1.5rem" />
</ActionIcon>
@@ -289,7 +290,7 @@ export default function RightRail() {
onClick={() => {
viewerContext?.toggleThumbnailSidebar();
}}
disabled={currentView !== 'viewer'}
disabled={currentView !== 'viewer' || allButtonsDisabled}
>
<LocalIcon icon="view-list" width="1.5rem" height="1.5rem" />
</ActionIcon>
@@ -315,7 +316,7 @@ export default function RightRail() {
radius="md"
className="right-rail-icon"
onClick={handleSelectAll}
disabled={currentView === 'viewer' || totalItems === 0 || selectedCount === totalItems}
disabled={currentView === 'viewer' || totalItems === 0 || selectedCount === totalItems || allButtonsDisabled}
>
<LocalIcon icon="select-all" width="1.5rem" height="1.5rem" />
</ActionIcon>
@@ -330,7 +331,7 @@ export default function RightRail() {
radius="md"
className="right-rail-icon"
onClick={handleDeselectAll}
disabled={currentView === 'viewer' || selectedCount === 0}
disabled={currentView === 'viewer' || selectedCount === 0 || allButtonsDisabled}
>
<LocalIcon icon="crop-square-outline" width="1.5rem" height="1.5rem" />
</ActionIcon>
@@ -349,7 +350,7 @@ export default function RightRail() {
variant="subtle"
radius="md"
className="right-rail-icon"
disabled={!pageControlsVisible || totalItems === 0}
disabled={!pageControlsVisible || totalItems === 0 || allButtonsDisabled}
aria-label={typeof t === 'function' ? t('rightRail.selectByNumber', 'Select by Page Numbers') : 'Select by Page Numbers'}
>
<LocalIcon icon="pin-end" width="1.5rem" height="1.5rem" />
@@ -385,7 +386,7 @@ export default function RightRail() {
radius="md"
className="right-rail-icon"
onClick={() => { pageEditorFunctions?.handleDelete?.(); }}
disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPageIds?.length || 0) === 0}
disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPageIds?.length || 0) === 0 || allButtonsDisabled}
aria-label={typeof t === 'function' ? t('rightRail.deleteSelected', 'Delete Selected Pages') : 'Delete Selected Pages'}
>
<LocalIcon icon="delete-outline-rounded" width="1.5rem" height="1.5rem" />
@@ -406,7 +407,7 @@ export default function RightRail() {
radius="md"
className="right-rail-icon"
onClick={() => { pageEditorFunctions?.onExportSelected?.(); }}
disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPageIds?.length || 0) === 0 || pageEditorFunctions?.exportLoading}
disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPageIds?.length || 0) === 0 || pageEditorFunctions?.exportLoading || allButtonsDisabled}
aria-label={typeof t === 'function' ? t('rightRail.exportSelected', 'Export Selected Pages') : 'Export Selected Pages'}
>
<LocalIcon icon="download" width="1.5rem" height="1.5rem" />
@@ -427,7 +428,8 @@ export default function RightRail() {
disabled={
currentView === 'viewer' ||
(currentView === 'fileEditor' && selectedCount === 0) ||
(currentView === 'pageEditor' && (activeFiles.length === 0 || !pageEditorFunctions?.closePdf))
(currentView === 'pageEditor' && (activeFiles.length === 0 || !pageEditorFunctions?.closePdf)) ||
allButtonsDisabled
}
>
<LocalIcon icon="close-rounded" width="1.5rem" height="1.5rem" />
@@ -441,7 +443,8 @@ export default function RightRail() {
{/* Theme toggle and Language dropdown */}
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '1rem' }}>
<Tooltip content={t('rightRail.toggleTheme', 'Toggle Theme')} position="left" offset={12} arrow>
<Tooltip content={t('rightRail.toggleTheme', 'Toggle Theme')} position="left" offset={12} arrow portalTarget={document.body}
>
<ActionIcon
variant="subtle"
radius="md"
@@ -452,7 +455,11 @@ export default function RightRail() {
</ActionIcon>
</Tooltip>
<LanguageSelector position="left-start" offset={6} compact />
<Tooltip content={t('rightRail.language', 'Language')} position="left" offset={12} arrow portalTarget={document.body}>
<div style={{ display: 'inline-flex' }}>
<LanguageSelector position="left-start" offset={6} compact />
</div>
</Tooltip>
<Tooltip content={
currentView === 'pageEditor'
@@ -466,7 +473,7 @@ export default function RightRail() {
className="right-rail-icon"
onClick={handleExportAll}
disabled={
currentView === 'viewer' ? !exportState?.canExport : totalItems === 0
currentView === 'viewer' ? !exportState?.canExport : totalItems === 0 || allButtonsDisabled
}
>
<LocalIcon icon="download" width="1.5rem" height="1.5rem" />

View File

@@ -46,6 +46,13 @@
background-color: transparent !important;
}
/* When all buttons are disabled via context */
.right-rail--all-disabled .right-rail-icon {
color: var(--right-rail-icon-disabled) !important;
background-color: transparent !important;
pointer-events: none !important;
}
.right-rail-spacer {
flex: 1;
}

View File

@@ -1,5 +1,5 @@
import React, { useMemo } from 'react';
import { ActionIcon, Badge, Text } from '@mantine/core';
import { ActionIcon, Text } from '@mantine/core';
import { Tooltip } from '../shared/Tooltip';
import { useTranslation } from 'react-i18next';
import { ToolRegistryEntry, getSubcategoryLabel, getSubcategoryColor, getSubcategoryIcon } from '../../data/toolsTaxonomy';
@@ -12,9 +12,10 @@ import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
import StarRoundedIcon from '@mui/icons-material/StarRounded';
import StarBorderRoundedIcon from '@mui/icons-material/StarBorderRounded';
import HistoryRoundedIcon from '@mui/icons-material/HistoryRounded';
import Badge from '../shared/Badge';
import './ToolPanel.css';
interface LegacyToolListProps {
interface FullscreenToolListProps {
filteredTools: Array<{ item: [string, ToolRegistryEntry]; matchedText?: string }>;
searchQuery: string;
showDescriptions: boolean;
@@ -23,17 +24,17 @@ interface LegacyToolListProps {
onSelect: (id: ToolId) => void;
}
const LegacyToolList = ({
const FullscreenToolList = ({
filteredTools,
searchQuery,
showDescriptions,
selectedToolKey,
matchedTextMap,
onSelect,
}: LegacyToolListProps) => {
}: FullscreenToolListProps) => {
const { t } = useTranslation();
const { hotkeys } = useHotkeys();
const { toolRegistry, recentTools, favoriteTools, toggleFavorite, isFavorite, legacyToolSettings } = useToolWorkflow();
const { toolRegistry, recentTools, favoriteTools, toggleFavorite, isFavorite, fullscreenToolSettings } = useToolWorkflow();
const { sections, searchGroups } = useToolSections(filteredTools, searchQuery);
@@ -72,32 +73,32 @@ const LegacyToolList = ({
if (subcategoryGroups.length === 0 && !showRecentFavorites) {
return (
<div className="tool-panel__legacy-empty">
<div className="tool-panel__fullscreen-empty">
<NoToolsFound />
<Text size="sm" c="dimmed">
{t('toolPanel.legacy.noResults', 'Try adjusting your search or toggle descriptions to find what you need.')}
{t('toolPanel.fullscreen.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';
? 'tool-panel__fullscreen-groups tool-panel__fullscreen-groups--detailed'
: 'tool-panel__fullscreen-groups tool-panel__fullscreen-groups--compact';
const getItemClasses = (isDetailed: boolean) => {
const base = isDetailed ? 'tool-panel__legacy-item--detailed' : '';
const border = legacyToolSettings.toolItemBorder === 'hidden' ? 'tool-panel__legacy-item--no-border' : '';
const hover = `tool-panel__legacy-item--hover-${legacyToolSettings.hoverIntensity}`;
const base = isDetailed ? 'tool-panel__fullscreen-item--detailed' : '';
const border = fullscreenToolSettings.toolItemBorder === 'hidden' ? 'tool-panel__fullscreen-item--no-border' : '';
const hover = `tool-panel__fullscreen-item--hover-${fullscreenToolSettings.hoverIntensity}`;
return [base, border, hover].filter(Boolean).join(' ');
};
const getIconBackground = (categoryColor: string, isDetailed: boolean) => {
if (legacyToolSettings.iconBackground === 'none' || legacyToolSettings.iconBackground === 'hover') {
if (fullscreenToolSettings.iconBackground === 'none' || fullscreenToolSettings.iconBackground === 'hover') {
return 'transparent';
}
const baseColor = isDetailed ? 'var(--legacy-bg-icon-detailed)' : 'var(--legacy-bg-icon-compact)';
const baseColor = isDetailed ? 'var(--fullscreen-bg-icon-detailed)' : 'var(--fullscreen-bg-icon-compact)';
const blend1 = isDetailed ? '18%' : '15%';
const blend2 = isDetailed ? '8%' : '6%';
@@ -108,10 +109,10 @@ const LegacyToolList = ({
};
const getIconStyle = () => {
if (legacyToolSettings.iconColorScheme === 'monochrome') {
if (fullscreenToolSettings.iconColorScheme === 'monochrome') {
return { filter: 'grayscale(1) opacity(0.8)' };
}
if (legacyToolSettings.iconColorScheme === 'vibrant') {
if (fullscreenToolSettings.iconColorScheme === 'vibrant') {
return { filter: 'saturate(1.5) brightness(1.1)' };
}
return {};
@@ -156,29 +157,29 @@ const LegacyToolList = ({
// Detailed view
if (showDescriptions) {
const iconBg = getIconBackground(categoryColor, true);
const iconClasses = legacyToolSettings.iconBackground === 'hover'
? 'tool-panel__legacy-icon tool-panel__legacy-icon--hover-bg'
: 'tool-panel__legacy-icon';
const iconClasses = fullscreenToolSettings.iconBackground === 'hover'
? 'tool-panel__fullscreen-icon tool-panel__fullscreen-icon--hover-bg'
: 'tool-panel__fullscreen-icon';
const hoverBgDetailed = legacyToolSettings.iconBackground === 'hover'
const hoverBgDetailed = fullscreenToolSettings.iconBackground === 'hover'
? `linear-gradient(135deg,
color-mix(in srgb, ${categoryColor} 18%, var(--legacy-bg-icon-detailed)),
color-mix(in srgb, ${categoryColor} 8%, var(--legacy-bg-icon-detailed))
color-mix(in srgb, ${categoryColor} 18%, var(--fullscreen-bg-icon-detailed)),
color-mix(in srgb, ${categoryColor} 8%, var(--fullscreen-bg-icon-detailed))
)`
: undefined;
return (
<button
key={id}
type="button"
className={`tool-panel__legacy-item ${getItemClasses(true)} ${isSelected ? 'tool-panel__legacy-item--selected' : ''} tool-panel__legacy-item--with-star`}
onClick={handleClick}
aria-disabled={isDisabled}
disabled={isDisabled}
style={{
['--legacy-icon-hover-bg' as any]: hoverBgDetailed,
}}
>
return (
<button
key={id}
type="button"
className={`tool-panel__fullscreen-item ${getItemClasses(true)} ${isSelected ? 'tool-panel__fullscreen-item--selected' : ''} tool-panel__fullscreen-item--with-star`}
onClick={handleClick}
aria-disabled={isDisabled}
disabled={isDisabled}
style={{
['--fullscreen-icon-hover-bg' as any]: hoverBgDetailed,
}}
>
{tool.icon ? (
<span
className={iconClasses}
@@ -191,15 +192,15 @@ const LegacyToolList = ({
{iconNode}
</span>
) : null}
<span className="tool-panel__legacy-body">
<Text fw={600} size="sm" className="tool-panel__legacy-name">
<span className="tool-panel__fullscreen-body">
<Text fw={600} size="sm" className="tool-panel__fullscreen-name">
{tool.name}
</Text>
<Text size="sm" c="dimmed" className="tool-panel__legacy-description">
<Text size="sm" c="dimmed" className="tool-panel__fullscreen-description">
{tool.description}
</Text>
{binding && (
<div className="tool-panel__legacy-shortcut">
<div className="tool-panel__fullscreen-shortcut">
<span style={{ color: 'var(--mantine-color-dimmed)', fontSize: '0.75rem' }}>
{t('settings.hotkeys.shortcut', 'Shortcut')}
</span>
@@ -207,8 +208,8 @@ const LegacyToolList = ({
</div>
)}
{matchedText && (
<Text size="xs" c="dimmed" className="tool-panel__legacy-match">
{t('toolPanel.legacy.matchedSynonym', 'Matches "{{text}}"', { text: matchedText })}
<Text size="xs" c="dimmed" className="tool-panel__fullscreen-match">
{t('toolPanel.fullscreen.matchedSynonym', 'Matches "{{text}}"', { text: matchedText })}
</Text>
)}
</span>
@@ -218,8 +219,8 @@ const LegacyToolList = ({
radius="xl"
size="sm"
onClick={handleStarClick}
className="tool-panel__legacy-star"
aria-label={isFav ? t('toolPanel.legacy.unfavorite', 'Remove from favourites') : t('toolPanel.legacy.favorite', 'Add to favourites')}
className="tool-panel__fullscreen-star"
aria-label={isFav ? t('toolPanel.fullscreen.unfavorite', 'Remove from favourites') : t('toolPanel.fullscreen.favorite', 'Add to favourites')}
>
{isFav ? (
<StarRoundedIcon fontSize="small" style={{ color: '#FFC107' }} />
@@ -234,29 +235,29 @@ const LegacyToolList = ({
// Compact view
const iconBg = getIconBackground(categoryColor, false);
const iconClasses = legacyToolSettings.iconBackground === 'hover'
? 'tool-panel__legacy-list-icon tool-panel__legacy-list-icon--hover-bg'
: 'tool-panel__legacy-list-icon';
const iconClasses = fullscreenToolSettings.iconBackground === 'hover'
? 'tool-panel__fullscreen-list-icon tool-panel__fullscreen-list-icon--hover-bg'
: 'tool-panel__fullscreen-list-icon';
const hoverBgCompact = legacyToolSettings.iconBackground === 'hover'
const hoverBgCompact = fullscreenToolSettings.iconBackground === 'hover'
? `linear-gradient(135deg,
color-mix(in srgb, ${categoryColor} 15%, var(--legacy-bg-icon-compact)),
color-mix(in srgb, ${categoryColor} 6%, var(--legacy-bg-icon-compact))
color-mix(in srgb, ${categoryColor} 15%, var(--fullscreen-bg-icon-compact)),
color-mix(in srgb, ${categoryColor} 6%, var(--fullscreen-bg-icon-compact))
)`
: undefined;
const compactButton = (
<button
key={id}
type="button"
className={`tool-panel__legacy-list-item ${getItemClasses(false)} ${isSelected ? 'tool-panel__legacy-list-item--selected' : ''} ${!isDisabled ? 'tool-panel__legacy-list-item--with-star' : ''}`}
onClick={handleClick}
aria-disabled={isDisabled}
disabled={isDisabled}
style={{
['--legacy-icon-hover-bg' as any]: hoverBgCompact,
}}
>
const compactButton = (
<button
key={id}
type="button"
className={`tool-panel__fullscreen-list-item ${getItemClasses(false)} ${isSelected ? 'tool-panel__fullscreen-list-item--selected' : ''} ${!isDisabled ? 'tool-panel__fullscreen-list-item--with-star' : ''}`}
onClick={handleClick}
aria-disabled={isDisabled}
disabled={isDisabled}
style={{
['--fullscreen-icon-hover-bg' as any]: hoverBgCompact,
}}
>
{tool.icon ? (
<span
className={iconClasses}
@@ -269,13 +270,13 @@ const LegacyToolList = ({
{iconNode}
</span>
) : null}
<span className="tool-panel__legacy-list-body">
<Text fw={600} size="sm" className="tool-panel__legacy-name">
<span className="tool-panel__fullscreen-list-body">
<Text fw={600} size="sm" className="tool-panel__fullscreen-name">
{tool.name}
</Text>
{matchedText && (
<Text size="xs" c="dimmed" className="tool-panel__legacy-match">
{t('toolPanel.legacy.matchedSynonym', 'Matches "{{text}}"', { text: matchedText})}
<Text size="xs" c="dimmed" className="tool-panel__fullscreen-match">
{t('toolPanel.fullscreen.matchedSynonym', 'Matches "{{text}}"', { text: matchedText})}
</Text>
)}
</span>
@@ -285,8 +286,8 @@ const LegacyToolList = ({
radius="xl"
size="xs"
onClick={handleStarClick}
className="tool-panel__legacy-star-compact"
aria-label={isFav ? t('toolPanel.legacy.unfavorite', 'Remove from favourites') : t('toolPanel.legacy.favorite', 'Add to favourites')}
className="tool-panel__fullscreen-star-compact"
aria-label={isFav ? t('toolPanel.fullscreen.unfavorite', 'Remove from favourites') : t('toolPanel.fullscreen.favorite', 'Add to favourites')}
>
{isFav ? (
<StarRoundedIcon fontSize="inherit" style={{ color: '#FFC107', fontSize: '1rem' }} />
@@ -331,13 +332,13 @@ const LegacyToolList = ({
{showRecentFavorites && (
<>
{favoriteToolItems.length > 0 && (
<section className="tool-panel__legacy-group tool-panel__legacy-group--special">
<header className="tool-panel__legacy-section-header">
<div className="tool-panel__legacy-section-title">
<section className="tool-panel__fullscreen-group tool-panel__fullscreen-group--special">
<header className="tool-panel__fullscreen-section-header">
<div className="tool-panel__fullscreen-section-title">
<span
className="tool-panel__legacy-section-icon"
className="tool-panel__fullscreen-section-icon"
style={{
color: legacyToolSettings.headerIconColor === 'colored' ? '#FFC107' : 'var(--mantine-color-dimmed)',
color: fullscreenToolSettings.headerIconColor === 'colored' ? '#FFC107' : 'var(--mantine-color-dimmed)',
...getIconStyle(),
}}
aria-hidden
@@ -345,23 +346,23 @@ const LegacyToolList = ({
<StarRoundedIcon />
</span>
<Text size="sm" fw={600} tt="uppercase" lts={0.5} c="dimmed">
{t('toolPanel.legacy.favorites', 'Favourites')}
{t('toolPanel.fullscreen.favorites', 'Favourites')}
</Text>
</div>
<Badge
size="sm"
variant="light"
color={legacyToolSettings.headerBadgeColor === 'colored' ? 'yellow' : 'gray'}
variant={fullscreenToolSettings.headerBadgeColor === 'colored' ? 'colored' : 'default'}
color={fullscreenToolSettings.headerBadgeColor === 'colored' ? '#FFC107' : undefined}
>
{favoriteToolItems.length}
</Badge>
</header>
{showDescriptions ? (
<div className="tool-panel__legacy-grid tool-panel__legacy-grid--detailed">
<div className="tool-panel__fullscreen-grid tool-panel__fullscreen-grid--detailed">
{favoriteToolItems.map((item: any) => renderToolItem(item.id, item.tool))}
</div>
) : (
<div className="tool-panel__legacy-list">
<div className="tool-panel__fullscreen-list">
{favoriteToolItems.map((item: any) => renderToolItem(item.id, item.tool))}
</div>
)}
@@ -369,13 +370,13 @@ const LegacyToolList = ({
)}
{recentToolItems.length > 0 && (
<section className="tool-panel__legacy-group tool-panel__legacy-group--special">
<header className="tool-panel__legacy-section-header">
<div className="tool-panel__legacy-section-title">
<section className="tool-panel__fullscreen-group tool-panel__fullscreen-group--special">
<header className="tool-panel__fullscreen-section-header">
<div className="tool-panel__fullscreen-section-title">
<span
className="tool-panel__legacy-section-icon"
className="tool-panel__fullscreen-section-icon"
style={{
color: legacyToolSettings.headerIconColor === 'colored' ? '#1BB1D4' : 'var(--mantine-color-dimmed)',
color: fullscreenToolSettings.headerIconColor === 'colored' ? '#1BB1D4' : 'var(--mantine-color-dimmed)',
...getIconStyle(),
}}
aria-hidden
@@ -383,23 +384,23 @@ const LegacyToolList = ({
<HistoryRoundedIcon />
</span>
<Text size="sm" fw={600} tt="uppercase" lts={0.5} c="dimmed">
{t('toolPanel.legacy.recent', 'Recently used')}
{t('toolPanel.fullscreen.recent', 'Recently used')}
</Text>
</div>
<Badge
size="sm"
variant="light"
color={legacyToolSettings.headerBadgeColor === 'colored' ? 'cyan' : 'gray'}
variant={fullscreenToolSettings.headerBadgeColor === 'colored' ? 'colored' : 'default'}
color={fullscreenToolSettings.headerBadgeColor === 'colored' ? '#1BB1D4' : undefined}
>
{recentToolItems.length}
</Badge>
</header>
{showDescriptions ? (
<div className="tool-panel__legacy-grid tool-panel__legacy-grid--detailed">
<div className="tool-panel__fullscreen-grid tool-panel__fullscreen-grid--detailed">
{recentToolItems.map((item: any) => renderToolItem(item.id, item.tool))}
</div>
) : (
<div className="tool-panel__legacy-list">
<div className="tool-panel__fullscreen-list">
{recentToolItems.map((item: any) => renderToolItem(item.id, item.tool))}
</div>
)}
@@ -414,17 +415,17 @@ const LegacyToolList = ({
return (
<section
key={subcategoryId}
className={`tool-panel__legacy-group ${showDescriptions ? 'tool-panel__legacy-group--detailed' : 'tool-panel__legacy-group--compact'}`}
className={`tool-panel__fullscreen-group ${showDescriptions ? 'tool-panel__fullscreen-group--detailed' : 'tool-panel__fullscreen-group--compact'}`}
style={{
borderColor: `color-mix(in srgb, ${categoryColor} 25%, var(--legacy-border-subtle-65))`,
borderColor: `color-mix(in srgb, ${categoryColor} 25%, var(--fullscreen-border-subtle-65))`,
}}
>
<header className="tool-panel__legacy-section-header">
<div className="tool-panel__legacy-section-title">
<header className="tool-panel__fullscreen-section-header">
<div className="tool-panel__fullscreen-section-title">
<span
className="tool-panel__legacy-section-icon"
className="tool-panel__fullscreen-section-icon"
style={{
color: legacyToolSettings.sectionTitleColor === 'colored' ? categoryColor : 'var(--mantine-color-dimmed)',
color: fullscreenToolSettings.sectionTitleColor === 'colored' ? categoryColor : 'var(--mantine-color-dimmed)',
...getIconStyle(),
}}
aria-hidden
@@ -437,33 +438,28 @@ const LegacyToolList = ({
tt="uppercase"
lts={0.5}
style={{
color: legacyToolSettings.sectionTitleColor === 'colored' ? categoryColor : undefined,
color: fullscreenToolSettings.sectionTitleColor === 'colored' ? categoryColor : undefined,
}}
c={legacyToolSettings.sectionTitleColor === 'neutral' ? 'dimmed' : undefined}
c={fullscreenToolSettings.sectionTitleColor === 'neutral' ? 'dimmed' : undefined}
>
{getSubcategoryLabel(t, subcategoryId)}
</Text>
</div>
<Badge
size="sm"
variant="light"
style={legacyToolSettings.sectionTitleColor === 'colored' ? {
backgroundColor: `color-mix(in srgb, ${categoryColor} 15%, transparent)`,
color: categoryColor,
borderColor: `color-mix(in srgb, ${categoryColor} 30%, transparent)`
} : undefined}
color={legacyToolSettings.sectionTitleColor === 'neutral' ? 'gray' : undefined}
variant={fullscreenToolSettings.sectionTitleColor === 'colored' ? 'colored' : 'default'}
color={fullscreenToolSettings.sectionTitleColor === 'colored' ? categoryColor : undefined}
>
{tools.length}
</Badge>
</header>
{showDescriptions ? (
<div className="tool-panel__legacy-grid tool-panel__legacy-grid--detailed">
<div className="tool-panel__fullscreen-grid tool-panel__fullscreen-grid--detailed">
{tools.map(({ id, tool }) => renderToolItem(id, tool))}
</div>
) : (
<div className="tool-panel__legacy-list">
<div className="tool-panel__fullscreen-list">
{tools.map(({ id, tool }) => renderToolItem(id, tool))}
</div>
)}
@@ -474,4 +470,6 @@ const LegacyToolList = ({
);
};
export default LegacyToolList;
export default FullscreenToolList;

View File

@@ -0,0 +1,204 @@
import { ActionIcon, Drawer, Radio, SegmentedControl, Stack, Text } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import TuneRoundedIcon from '@mui/icons-material/TuneRounded';
import { useState } from 'react';
export interface FullscreenToolStyleSettings {
iconBackground: 'none' | 'hover' | 'always';
iconColorScheme: 'colored' | 'vibrant' | 'monochrome';
sectionTitleColor: 'colored' | 'neutral';
headerIconColor: 'colored' | 'monochrome';
headerBadgeColor: 'colored' | 'neutral';
toolItemBorder: 'visible' | 'hidden';
hoverIntensity: 'subtle' | 'moderate' | 'prominent';
}
export const defaultFullscreenToolSettings: FullscreenToolStyleSettings = {
iconBackground: 'always',
iconColorScheme: 'colored',
sectionTitleColor: 'colored',
headerIconColor: 'colored',
headerBadgeColor: 'colored',
toolItemBorder: 'visible',
hoverIntensity: 'moderate',
};
interface FullscreenToolSettingsProps {
settings: FullscreenToolStyleSettings;
onChange: (settings: FullscreenToolStyleSettings) => void;
}
const FullscreenToolSettings = ({ settings, onChange }: FullscreenToolSettingsProps) => {
const { t } = useTranslation();
const [opened, setOpened] = useState(false);
const updateSetting = <K extends keyof FullscreenToolStyleSettings>(
key: K,
value: FullscreenToolStyleSettings[K]
) => {
onChange({ ...settings, [key]: value });
};
return (
<>
<ActionIcon
variant="subtle"
radius="xl"
size="md"
onClick={() => setOpened(true)}
aria-label={t('toolPanel.fullscreen.settings.title', 'Customize appearance')}
style={{ color: 'var(--right-rail-icon)' }}
>
<TuneRoundedIcon fontSize="small" />
</ActionIcon>
<Drawer
opened={opened}
onClose={() => setOpened(false)}
title={t('toolPanel.fullscreen.settings.title', 'Customize appearance')}
position="right"
size="md"
styles={{
root: { zIndex: 1300 },
overlay: { zIndex: 1300 },
inner: { zIndex: 1300 },
}}
>
<Stack gap="xl">
<div>
<Text size="sm" fw={600} mb="xs">
{t('toolPanel.fullscreen.settings.iconBackground.label', 'Tool icon background')}
</Text>
<Text size="xs" c="dimmed" mb="sm">
{t('toolPanel.fullscreen.settings.iconBackground.description', 'When to show colored backgrounds behind tool icons')}
</Text>
<SegmentedControl
fullWidth
value={settings.iconBackground}
onChange={(value) => updateSetting('iconBackground', value as any)}
data={[
{ label: t('toolPanel.fullscreen.settings.iconBackground.none', 'None'), value: 'none' },
{ label: t('toolPanel.fullscreen.settings.iconBackground.hover', 'On hover'), value: 'hover' },
{ label: t('toolPanel.fullscreen.settings.iconBackground.always', 'Always'), value: 'always' },
]}
/>
</div>
<div>
<Text size="sm" fw={600} mb="xs">
{t('toolPanel.fullscreen.settings.iconColor.label', 'Tool icon color')}
</Text>
<Text size="xs" c="dimmed" mb="sm">
{t('toolPanel.fullscreen.settings.iconColor.description', 'Color scheme for tool icons')}
</Text>
<SegmentedControl
fullWidth
value={settings.iconColorScheme}
onChange={(value) => updateSetting('iconColorScheme', value as any)}
data={[
{ label: t('toolPanel.fullscreen.settings.iconColor.colored', 'Colored'), value: 'colored' },
{ label: t('toolPanel.fullscreen.settings.iconColor.vibrant', 'Vibrant'), value: 'vibrant' },
{ label: t('toolPanel.fullscreen.settings.iconColor.monochrome', 'Monochrome'), value: 'monochrome' },
]}
/>
</div>
<div>
<Text size="sm" fw={600} mb="xs">
{t('toolPanel.fullscreen.settings.sectionTitle.label', 'Section titles')}
</Text>
<Text size="xs" c="dimmed" mb="sm">
{t('toolPanel.fullscreen.settings.sectionTitle.description', 'Color for category section titles')}
</Text>
<SegmentedControl
fullWidth
value={settings.sectionTitleColor}
onChange={(value) => updateSetting('sectionTitleColor', value as any)}
data={[
{ label: t('toolPanel.fullscreen.settings.sectionTitle.colored', 'Colored'), value: 'colored' },
{ label: t('toolPanel.fullscreen.settings.sectionTitle.neutral', 'Neutral'), value: 'neutral' },
]}
/>
</div>
<div>
<Text size="sm" fw={600} mb="xs">
{t('toolPanel.fullscreen.settings.headerIcon.label', 'Section header icons')}
</Text>
<Text size="xs" c="dimmed" mb="sm">
{t('toolPanel.fullscreen.settings.headerIcon.description', 'Color for Favorites/Recent icons')}
</Text>
<SegmentedControl
fullWidth
value={settings.headerIconColor}
onChange={(value) => updateSetting('headerIconColor', value as any)}
data={[
{ label: t('toolPanel.fullscreen.settings.headerIcon.colored', 'Colored'), value: 'colored' },
{ label: t('toolPanel.fullscreen.settings.headerIcon.monochrome', 'Monochrome'), value: 'monochrome' },
]}
/>
</div>
<div>
<Text size="sm" fw={600} mb="xs">
{t('toolPanel.fullscreen.settings.headerBadge.label', 'Section header badges')}
</Text>
<Text size="xs" c="dimmed" mb="sm">
{t('toolPanel.fullscreen.settings.headerBadge.description', 'Color for count badges in section headers')}
</Text>
<SegmentedControl
fullWidth
value={settings.headerBadgeColor}
onChange={(value) => updateSetting('headerBadgeColor', value as any)}
data={[
{ label: t('toolPanel.fullscreen.settings.headerBadge.colored', 'Colored'), value: 'colored' },
{ label: t('toolPanel.fullscreen.settings.headerBadge.neutral', 'Neutral'), value: 'neutral' },
]}
/>
</div>
<div>
<Text size="sm" fw={600} mb="xs">
{t('toolPanel.fullscreen.settings.border.label', 'Tool item borders')}
</Text>
<Text size="xs" c="dimmed" mb="sm">
{t('toolPanel.fullscreen.settings.border.description', 'Show borders around tool items')}
</Text>
<SegmentedControl
fullWidth
value={settings.toolItemBorder}
onChange={(value) => updateSetting('toolItemBorder', value as any)}
data={[
{ label: t('toolPanel.fullscreen.settings.border.visible', 'Visible'), value: 'visible' },
{ label: t('toolPanel.fullscreen.settings.border.hidden', 'Hidden'), value: 'hidden' },
]}
/>
</div>
<div>
<Text size="sm" fw={600} mb="xs">
{t('toolPanel.fullscreen.settings.hover.label', 'Hover effect intensity')}
</Text>
<Text size="xs" c="dimmed" mb="sm">
{t('toolPanel.fullscreen.settings.hover.description', 'How prominent the hover effect should be')}
</Text>
<Radio.Group
value={settings.hoverIntensity}
onChange={(value) => updateSetting('hoverIntensity', value as any)}
>
<Stack gap="xs">
<Radio value="subtle" label={t('toolPanel.fullscreen.settings.hover.subtle', 'Subtle')} />
<Radio value="moderate" label={t('toolPanel.fullscreen.settings.hover.moderate', 'Moderate')} />
<Radio value="prominent" label={t('toolPanel.fullscreen.settings.hover.prominent', 'Prominent')} />
</Stack>
</Radio.Group>
</div>
</Stack>
</Drawer>
</>
);
};
export default FullscreenToolSettings;

View File

@@ -3,8 +3,8 @@ import { ActionIcon, ScrollArea, Switch, Tooltip, useMantineColorScheme } from '
import ViewSidebarRoundedIcon from '@mui/icons-material/ViewSidebarRounded';
import { useTranslation } from 'react-i18next';
import ToolSearch from './toolPicker/ToolSearch';
import LegacyToolList from './LegacyToolList';
import LegacyToolSettings from './LegacyToolSettings';
import FullscreenToolList from './FullscreenToolList';
import FullscreenToolSettings from './FullscreenToolSettings';
import { ToolRegistryEntry } from '../../data/toolsTaxonomy';
import { ToolId } from '../../types/toolId';
import { useFocusTrap } from '../../hooks/tools/useFocusTrap';
@@ -12,7 +12,7 @@ import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
import { BASE_PATH } from '../../constants/app';
import './ToolPanel.css';
interface LegacyToolSurfaceProps {
interface FullscreenToolSurfaceProps {
searchQuery: string;
toolRegistry: Record<string, ToolRegistryEntry>;
filteredTools: Array<{ item: [string, ToolRegistryEntry]; matchedText?: string }>;
@@ -22,7 +22,7 @@ interface LegacyToolSurfaceProps {
onSearchChange: (value: string) => void;
onSelect: (id: ToolId) => void;
onToggleDescriptions: () => void;
onExitLegacyMode: () => void;
onExitFullscreenMode: () => void;
toggleLabel: string;
geometry: {
left: number;
@@ -32,7 +32,7 @@ interface LegacyToolSurfaceProps {
} | null;
}
const LegacyToolSurface = ({
const FullscreenToolSurface = ({
searchQuery,
toolRegistry,
filteredTools,
@@ -42,13 +42,13 @@ const LegacyToolSurface = ({
onSearchChange,
onSelect,
onToggleDescriptions,
onExitLegacyMode,
onExitFullscreenMode,
toggleLabel,
geometry,
}: LegacyToolSurfaceProps) => {
}: FullscreenToolSurfaceProps) => {
const { t } = useTranslation();
const { colorScheme } = useMantineColorScheme();
const { legacyToolSettings, setLegacyToolSettings } = useToolWorkflow();
const { fullscreenToolSettings, setFullscreenToolSettings } = useToolWorkflow();
const [isExiting, setIsExiting] = useState(false);
const surfaceRef = useRef<HTMLDivElement>(null);
@@ -67,13 +67,13 @@ const LegacyToolSurface = ({
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReducedMotion) {
onExitLegacyMode();
onExitFullscreenMode();
return;
}
setIsExiting(true);
setTimeout(() => {
onExitLegacyMode();
onExitFullscreenMode();
}, 220); // Match animation duration (0.22s)
};
@@ -99,24 +99,24 @@ const LegacyToolSurface = ({
return (
<div
className="tool-panel__legacy-surface"
className="tool-panel__fullscreen-surface"
style={style}
role="region"
aria-label={t('toolPanel.legacy.heading', 'All tools (legacy view)')}
aria-label={t('toolPanel.fullscreen.heading', 'All tools (fullscreen view)')}
>
<div
ref={surfaceRef}
className={`tool-panel__legacy-surface-inner ${isExiting ? 'tool-panel__legacy-surface-inner--exiting' : ''}`}
className={`tool-panel__fullscreen-surface-inner ${isExiting ? 'tool-panel__fullscreen-surface-inner--exiting' : ''}`}
>
<header className="tool-panel__legacy-header">
<div className="tool-panel__legacy-brand">
<img src={brandIconSrc} alt="" className="tool-panel__legacy-brand-icon" />
<img src={brandTextSrc} alt={brandAltText} className="tool-panel__legacy-brand-text" />
<header className="tool-panel__fullscreen-header">
<div className="tool-panel__fullscreen-brand">
<img src={brandIconSrc} alt="" className="tool-panel__fullscreen-brand-icon" />
<img src={brandTextSrc} alt={brandAltText} className="tool-panel__fullscreen-brand-text" />
</div>
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
<LegacyToolSettings
settings={legacyToolSettings}
onChange={setLegacyToolSettings}
<FullscreenToolSettings
settings={fullscreenToolSettings}
onChange={setFullscreenToolSettings}
/>
<Tooltip label={toggleLabel} position="bottom" withArrow>
<ActionIcon
@@ -125,6 +125,7 @@ const LegacyToolSurface = ({
size="md"
onClick={handleExit}
aria-label={toggleLabel}
style={{ color: 'var(--right-rail-icon)' }}
>
<ViewSidebarRoundedIcon fontSize="small" />
</ActionIcon>
@@ -132,7 +133,7 @@ const LegacyToolSurface = ({
</div>
</header>
<div className="tool-panel__legacy-controls">
<div className="tool-panel__fullscreen-controls">
<ToolSearch
value={searchQuery}
onChange={onSearchChange}
@@ -145,13 +146,13 @@ const LegacyToolSurface = ({
onChange={() => onToggleDescriptions()}
size="md"
labelPosition="left"
label={showDescriptions ? t('toolPanel.legacy.descriptionsOn', 'Showing descriptions') : t('toolPanel.legacy.descriptionsOff', 'Descriptions hidden')}
label={t('toolPanel.fullscreen.showDetails', 'Show Details')}
/>
</div>
<div className="tool-panel__legacy-body">
<ScrollArea className="tool-panel__legacy-scroll" offsetScrollbars>
<LegacyToolList
<div className="tool-panel__fullscreen-body">
<ScrollArea className="tool-panel__fullscreen-scroll" offsetScrollbars>
<FullscreenToolList
filteredTools={filteredTools}
searchQuery={searchQuery}
showDescriptions={showDescriptions}
@@ -166,4 +167,6 @@ const LegacyToolSurface = ({
);
};
export default LegacyToolSurface;
export default FullscreenToolSurface;

View File

@@ -1,252 +0,0 @@
import { ActionIcon, Drawer, Radio, SegmentedControl, Stack, Text } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import TuneRoundedIcon from '@mui/icons-material/TuneRounded';
import { useState } from 'react';
export interface LegacyToolStyleSettings {
iconBackground: 'none' | 'hover' | 'always';
iconColorScheme: 'colored' | 'vibrant' | 'monochrome';
sectionTitleColor: 'colored' | 'neutral';
headerIconColor: 'colored' | 'monochrome';
headerBadgeColor: 'colored' | 'neutral';
toolItemBorder: 'visible' | 'hidden';
hoverIntensity: 'subtle' | 'moderate' | 'prominent';
}
export const defaultLegacyToolSettings: LegacyToolStyleSettings = {
iconBackground: 'always',
iconColorScheme: 'colored',
sectionTitleColor: 'colored',
headerIconColor: 'colored',
headerBadgeColor: 'colored',
toolItemBorder: 'visible',
hoverIntensity: 'moderate',
};
interface LegacyToolSettingsProps {
settings: LegacyToolStyleSettings;
onChange: (settings: LegacyToolStyleSettings) => void;
}
const LegacyToolSettings = ({ settings, onChange }: LegacyToolSettingsProps) => {
const { t } = useTranslation();
const [opened, setOpened] = useState(false);
const updateSetting = <K extends keyof LegacyToolStyleSettings>(
key: K,
value: LegacyToolStyleSettings[K]
) => {
onChange({ ...settings, [key]: value });
};
return (
<>
<ActionIcon
variant="subtle"
radius="xl"
size="md"
onClick={() => setOpened(true)}
aria-label={t('toolPanel.legacy.settings.title', 'Customize appearance')}
>
<TuneRoundedIcon fontSize="small" />
</ActionIcon>
<Drawer
opened={opened}
onClose={() => setOpened(false)}
title={t('toolPanel.legacy.settings.title', 'Customize appearance')}
position="right"
size="md"
styles={{
root: { zIndex: 1300 },
overlay: { zIndex: 1300 },
inner: { zIndex: 1300 },
}}
>
<Stack gap="xl">
<div>
<Text size="sm" fw={600} mb="xs">
{t('toolPanel.legacy.settings.iconBackground.label', 'Tool icon background')}
</Text>
<Text size="xs" c="dimmed" mb="sm">
{t('toolPanel.legacy.settings.iconBackground.description', 'When to show colored backgrounds behind tool icons')}
</Text>
<SegmentedControl
fullWidth
value={settings.iconBackground}
onChange={(value) => updateSetting('iconBackground', value as any)}
data={[
{
label: t('toolPanel.legacy.settings.iconBackground.none', 'None'),
value: 'none',
},
{
label: t('toolPanel.legacy.settings.iconBackground.hover', 'On hover'),
value: 'hover',
},
{
label: t('toolPanel.legacy.settings.iconBackground.always', 'Always'),
value: 'always',
},
]}
/>
</div>
<div>
<Text size="sm" fw={600} mb="xs">
{t('toolPanel.legacy.settings.iconColor.label', 'Tool icon color')}
</Text>
<Text size="xs" c="dimmed" mb="sm">
{t('toolPanel.legacy.settings.iconColor.description', 'Color scheme for tool icons')}
</Text>
<SegmentedControl
fullWidth
value={settings.iconColorScheme}
onChange={(value) => updateSetting('iconColorScheme', value as any)}
data={[
{
label: t('toolPanel.legacy.settings.iconColor.colored', 'Colored'),
value: 'colored',
},
{
label: t('toolPanel.legacy.settings.iconColor.vibrant', 'Vibrant'),
value: 'vibrant',
},
{
label: t('toolPanel.legacy.settings.iconColor.monochrome', 'Monochrome'),
value: 'monochrome',
},
]}
/>
</div>
<div>
<Text size="sm" fw={600} mb="xs">
{t('toolPanel.legacy.settings.sectionTitle.label', 'Section titles')}
</Text>
<Text size="xs" c="dimmed" mb="sm">
{t('toolPanel.legacy.settings.sectionTitle.description', 'Color for category section titles')}
</Text>
<SegmentedControl
fullWidth
value={settings.sectionTitleColor}
onChange={(value) => updateSetting('sectionTitleColor', value as any)}
data={[
{
label: t('toolPanel.legacy.settings.sectionTitle.colored', 'Colored'),
value: 'colored',
},
{
label: t('toolPanel.legacy.settings.sectionTitle.neutral', 'Neutral'),
value: 'neutral',
},
]}
/>
</div>
<div>
<Text size="sm" fw={600} mb="xs">
{t('toolPanel.legacy.settings.headerIcon.label', 'Section header icons')}
</Text>
<Text size="xs" c="dimmed" mb="sm">
{t('toolPanel.legacy.settings.headerIcon.description', 'Color for Favorites/Recent icons')}
</Text>
<SegmentedControl
fullWidth
value={settings.headerIconColor}
onChange={(value) => updateSetting('headerIconColor', value as any)}
data={[
{
label: t('toolPanel.legacy.settings.headerIcon.colored', 'Colored'),
value: 'colored',
},
{
label: t('toolPanel.legacy.settings.headerIcon.monochrome', 'Monochrome'),
value: 'monochrome',
},
]}
/>
</div>
<div>
<Text size="sm" fw={600} mb="xs">
{t('toolPanel.legacy.settings.headerBadge.label', 'Section header badges')}
</Text>
<Text size="xs" c="dimmed" mb="sm">
{t('toolPanel.legacy.settings.headerBadge.description', 'Color for count badges in section headers')}
</Text>
<SegmentedControl
fullWidth
value={settings.headerBadgeColor}
onChange={(value) => updateSetting('headerBadgeColor', value as any)}
data={[
{
label: t('toolPanel.legacy.settings.headerBadge.colored', 'Colored'),
value: 'colored',
},
{
label: t('toolPanel.legacy.settings.headerBadge.neutral', 'Neutral'),
value: 'neutral',
},
]}
/>
</div>
<div>
<Text size="sm" fw={600} mb="xs">
{t('toolPanel.legacy.settings.border.label', 'Tool item borders')}
</Text>
<Text size="xs" c="dimmed" mb="sm">
{t('toolPanel.legacy.settings.border.description', 'Show borders around tool items')}
</Text>
<SegmentedControl
fullWidth
value={settings.toolItemBorder}
onChange={(value) => updateSetting('toolItemBorder', value as any)}
data={[
{
label: t('toolPanel.legacy.settings.border.visible', 'Visible'),
value: 'visible',
},
{
label: t('toolPanel.legacy.settings.border.hidden', 'Hidden'),
value: 'hidden',
},
]}
/>
</div>
<div>
<Text size="sm" fw={600} mb="xs">
{t('toolPanel.legacy.settings.hover.label', 'Hover effect intensity')}
</Text>
<Text size="xs" c="dimmed" mb="sm">
{t('toolPanel.legacy.settings.hover.description', 'How prominent the hover effect should be')}
</Text>
<Radio.Group
value={settings.hoverIntensity}
onChange={(value) => updateSetting('hoverIntensity', value as any)}
>
<Stack gap="xs">
<Radio
value="subtle"
label={t('toolPanel.legacy.settings.hover.subtle', 'Subtle')}
/>
<Radio
value="moderate"
label={t('toolPanel.legacy.settings.hover.moderate', 'Moderate')}
/>
<Radio
value="prominent"
label={t('toolPanel.legacy.settings.hover.prominent', 'Prominent')}
/>
</Stack>
</Radio.Group>
</div>
</Stack>
</Drawer>
</>
);
};
export default LegacyToolSettings;

View File

@@ -48,14 +48,14 @@ const SearchResults: React.FC<SearchResultsProps> = ({ filteredTools, onSelect,
) : undefined;
return (
<ToolButton
key={id}
id={id}
tool={tool}
isSelected={false}
onSelect={onSelect}
matchedSynonym={matchedSynonym}
/>
<ToolButton
key={id}
id={id}
tool={tool}
isSelected={false}
onSelect={onSelect}
matchedSynonym={matchedSynonym}
/>
);
})}
</Stack>

View File

@@ -1,30 +1,34 @@
/* CSS Custom Properties for Legacy Mode */
.tool-panel__legacy-surface-inner {
--legacy-bg-surface-1: color-mix(in srgb, var(--bg-toolbar) 96%, transparent);
--legacy-bg-surface-2: color-mix(in srgb, var(--bg-background) 90%, transparent);
--legacy-bg-header: var(--bg-toolbar);
--legacy-bg-controls-1: var(--bg-toolbar);
--legacy-bg-controls-2: color-mix(in srgb, var(--bg-toolbar) 95%, var(--bg-background));
--legacy-bg-body-1: color-mix(in srgb, var(--bg-background) 86%, transparent);
--legacy-bg-body-2: color-mix(in srgb, var(--bg-toolbar) 78%, transparent);
--legacy-bg-group: color-mix(in srgb, var(--bg-toolbar) 82%, transparent);
--legacy-bg-item: color-mix(in srgb, var(--bg-toolbar) 88%, transparent);
--legacy-bg-list-item: color-mix(in srgb, var(--bg-toolbar) 86%, transparent);
--legacy-bg-icon-detailed: color-mix(in srgb, var(--bg-muted) 75%, transparent);
--legacy-bg-icon-compact: color-mix(in srgb, var(--bg-muted) 70%, transparent);
--legacy-border-subtle-75: color-mix(in srgb, var(--border-subtle) 75%, transparent);
--legacy-border-subtle-70: color-mix(in srgb, var(--border-subtle) 70%, transparent);
--legacy-border-subtle-65: color-mix(in srgb, var(--border-subtle) 65%, transparent);
--legacy-shadow-primary: color-mix(in srgb, var(--shadow-color, rgba(15, 23, 42, 0.55)) 25%, transparent);
--legacy-shadow-secondary: color-mix(in srgb, var(--shadow-color, rgba(15, 23, 42, 0.35)) 30%, transparent);
--legacy-shadow-group: color-mix(in srgb, var(--shadow-color, rgba(15, 23, 42, 0.45)) 18%, transparent);
--legacy-accent-hover: color-mix(in srgb, var(--text-primary) 20%, var(--border-subtle));
--legacy-accent-selected: color-mix(in srgb, var(--text-primary) 30%, var(--border-subtle));
--legacy-accent-ring: color-mix(in srgb, var(--text-primary) 15%, transparent);
--legacy-accent-list-bg: color-mix(in srgb, var(--text-primary) 8%, var(--bg-toolbar));
--legacy-accent-list-border: color-mix(in srgb, var(--text-primary) 20%, var(--border-subtle));
--legacy-text-icon: color-mix(in srgb, var(--text-primary) 90%, var(--text-muted));
--legacy-text-icon-compact: color-mix(in srgb, var(--text-primary) 88%, var(--text-muted));
/* CSS Custom Properties for Fullscreen Mode */
.tool-panel__fullscreen-surface-inner {
--fullscreen-bg-surface-1: color-mix(in srgb, var(--bg-toolbar) 96%, transparent);
--fullscreen-bg-surface-2: color-mix(in srgb, var(--bg-background) 90%, transparent);
--fullscreen-bg-header: var(--bg-toolbar);
--fullscreen-bg-controls-1: var(--bg-toolbar);
--fullscreen-bg-controls-2: color-mix(in srgb, var(--bg-toolbar) 95%, var(--bg-background));
--fullscreen-bg-body-1: color-mix(in srgb, var(--bg-background) 86%, transparent);
--fullscreen-bg-body-2: color-mix(in srgb, var(--bg-toolbar) 78%, transparent);
--fullscreen-bg-group: color-mix(in srgb, var(--bg-toolbar) 82%, transparent);
--fullscreen-bg-item: color-mix(in srgb, var(--bg-toolbar) 88%, transparent);
--fullscreen-bg-list-item: color-mix(in srgb, var(--bg-toolbar) 86%, transparent);
--fullscreen-bg-icon-detailed: color-mix(in srgb, var(--bg-muted) 75%, transparent);
--fullscreen-bg-icon-compact: color-mix(in srgb, var(--bg-muted) 70%, transparent);
--fullscreen-border-subtle-75: color-mix(in srgb, var(--border-subtle) 75%, transparent);
--fullscreen-border-subtle-70: color-mix(in srgb, var(--border-subtle) 70%, transparent);
--fullscreen-border-subtle-65: color-mix(in srgb, var(--border-subtle) 65%, transparent);
--fullscreen-shadow-primary: color-mix(in srgb, var(--shadow-color, rgba(15, 23, 42, 0.55)) 25%, transparent);
--fullscreen-shadow-secondary: color-mix(in srgb, var(--shadow-color, rgba(15, 23, 42, 0.35)) 30%, transparent);
--fullscreen-shadow-group: color-mix(in srgb, var(--shadow-color, rgba(15, 23, 42, 0.45)) 18%, transparent);
--fullscreen-accent-hover: color-mix(in srgb, var(--text-primary) 20%, var(--border-subtle));
--fullscreen-accent-selected: color-mix(in srgb, var(--text-primary) 30%, var(--border-subtle));
--fullscreen-accent-ring: color-mix(in srgb, var(--text-primary) 15%, transparent);
--fullscreen-accent-list-bg: color-mix(in srgb, var(--text-primary) 8%, var(--bg-toolbar));
--fullscreen-accent-list-border: color-mix(in srgb, var(--text-primary) 20%, var(--border-subtle));
--fullscreen-text-icon: color-mix(in srgb, var(--text-primary) 90%, var(--text-muted));
--fullscreen-text-icon-compact: color-mix(in srgb, var(--text-primary) 88%, var(--text-muted));
--fullscreen-hover-subtle: color-mix(in srgb, var(--text-primary) 5%, var(--bg-toolbar));
--fullscreen-hover: color-mix(in srgb, var(--text-primary) 8%, var(--bg-toolbar));
--fullscreen-hover-prominent: color-mix(in srgb, var(--text-primary) 12%, var(--bg-toolbar));
--fullscreen-icon-hover-bg: color-mix(in srgb, var(--text-primary) 10%, var(--bg-muted));
}
.tool-panel {
@@ -32,7 +36,7 @@
transition: width 0.3s ease, max-width 0.3s ease;
}
.tool-panel--legacy-active {
.tool-panel--fullscreen-active {
overflow: visible !important;
}
@@ -55,7 +59,7 @@
transform: scale(1.04);
}
.tool-panel--legacy {
.tool-panel--fullscreen {
background: var(--bg-toolbar);
}
@@ -70,7 +74,7 @@
text-align: center;
}
.tool-panel__legacy-surface {
.tool-panel__fullscreen-surface {
position: fixed;
display: flex;
pointer-events: none;
@@ -81,7 +85,7 @@
height: 0;
}
.tool-panel__legacy-surface-inner {
.tool-panel__fullscreen-surface-inner {
pointer-events: auto;
width: 100%;
height: 100%;
@@ -91,24 +95,22 @@
background:
linear-gradient(
140deg,
var(--legacy-bg-surface-1),
var(--legacy-bg-surface-2)
var(--fullscreen-bg-surface-1),
var(--fullscreen-bg-surface-2)
)
padding-box;
border: 1px solid var(--legacy-border-subtle-75);
box-shadow:
0 24px 64px var(--legacy-shadow-primary),
0 6px 18px var(--legacy-shadow-secondary);
border: 1px solid var(--fullscreen-border-subtle-75);
box-shadow: none;
backdrop-filter: blur(18px);
overflow: hidden;
animation: tool-panel-legacy-slide-in 0.28s ease forwards;
animation: tool-panel-fullscreen-slide-in 0.28s ease forwards;
}
.tool-panel__legacy-surface-inner--exiting {
animation: tool-panel-legacy-slide-out 0.22s ease forwards;
.tool-panel__fullscreen-surface-inner--exiting {
animation: tool-panel-fullscreen-slide-out 0.22s ease forwards;
}
.tool-panel__legacy-header {
.tool-panel__fullscreen-header {
display: flex;
justify-content: space-between;
align-items: center;
@@ -118,23 +120,23 @@
background: var(--bg-toolbar);
}
.tool-panel__legacy-brand {
.tool-panel__fullscreen-brand {
display: flex;
align-items: center;
gap: 0.625rem;
}
.tool-panel__legacy-brand-icon {
.tool-panel__fullscreen-brand-icon {
height: 1.75rem;
width: auto;
}
.tool-panel__legacy-brand-text {
.tool-panel__fullscreen-brand-text {
height: 1.25rem;
width: auto;
}
.tool-panel__legacy-controls {
.tool-panel__fullscreen-controls {
display: flex;
align-items: center;
gap: 1rem;
@@ -143,36 +145,36 @@
background: var(--tool-panel-search-bg);
}
.tool-panel__legacy-controls .search-input-container {
.tool-panel__fullscreen-controls .search-input-container {
flex: 1 1 auto;
}
.tool-panel__legacy-body {
.tool-panel__fullscreen-body {
flex: 1;
min-height: 0;
background: transparent;
}
.tool-panel__legacy-scroll {
.tool-panel__fullscreen-scroll {
height: 100%;
}
/* legacy group layout */
.tool-panel__legacy-groups {
/* fullscreen group layout */
.tool-panel__fullscreen-groups {
padding: 1.5rem 1.75rem;
column-width: 18rem;
column-gap: 1.5rem;
}
.tool-panel__legacy-groups--compact {
.tool-panel__fullscreen-groups--compact {
column-width: 17rem;
}
.tool-panel__legacy-groups--detailed {
.tool-panel__fullscreen-groups--detailed {
column-width: auto;
}
.tool-panel__legacy-group {
.tool-panel__fullscreen-group {
display: flex;
flex-direction: column;
gap: 0.75rem;
@@ -180,14 +182,14 @@
margin: 0 0 1.5rem;
padding: 0.65rem 0.75rem 1rem;
border-radius: 1rem;
background: var(--legacy-bg-group);
border: 1px solid var(--legacy-border-subtle-65);
box-shadow: 0 14px 32px var(--legacy-shadow-group);
background: var(--fullscreen-bg-group);
border: 1px solid var(--fullscreen-border-subtle-65);
box-shadow: 0 14px 32px var(--fullscreen-shadow-group);
break-inside: avoid;
backdrop-filter: blur(10px);
}
.tool-panel__legacy-section-header {
.tool-panel__fullscreen-section-header {
display: flex;
align-items: center;
justify-content: space-between;
@@ -195,38 +197,38 @@
padding: 0.1rem 0.15rem 0.35rem;
}
.tool-panel__legacy-section-title {
.tool-panel__fullscreen-section-title {
display: flex;
align-items: center;
gap: 0.5rem;
}
.tool-panel__legacy-section-icon {
.tool-panel__fullscreen-section-icon {
display: flex;
align-items: center;
justify-content: center;
font-size: 1.1rem;
}
.tool-panel__legacy-grid {
.tool-panel__fullscreen-grid {
display: grid;
gap: 0.75rem;
}
.tool-panel__legacy-grid--detailed {
.tool-panel__fullscreen-grid--detailed {
grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
}
.tool-panel__legacy-item {
.tool-panel__fullscreen-item {
all: unset;
display: flex;
flex-direction: row;
gap: 0.75rem;
align-items: flex-start;
padding: 0.85rem 0.95rem;
border: 1px solid var(--legacy-border-subtle-70);
border: 1px solid var(--fullscreen-border-subtle-70);
border-radius: 0.95rem;
background: var(--legacy-bg-item);
background: var(--fullscreen-bg-item);
backdrop-filter: blur(6px);
cursor: pointer;
transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
@@ -234,37 +236,37 @@
box-sizing: border-box;
}
.tool-panel__legacy-item:focus-visible {
outline: 2px solid var(--legacy-accent-selected);
.tool-panel__fullscreen-item:focus-visible {
outline: 2px solid var(--fullscreen-accent-selected);
outline-offset: 3px;
}
.tool-panel__legacy-item[aria-disabled="true"],
.tool-panel__legacy-item:disabled {
.tool-panel__fullscreen-item[aria-disabled="true"],
.tool-panel__fullscreen-item:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.tool-panel__legacy-item:hover:not([aria-disabled="true"]):not(:disabled) {
.tool-panel__fullscreen-item:hover:not([aria-disabled="true"]):not(:disabled) {
transform: translateY(-2px);
border-color: var(--legacy-accent-hover);
border-color: var(--fullscreen-accent-hover);
box-shadow: var(--shadow-xl, 0 18px 34px rgba(15, 23, 42, 0.14));
}
.tool-panel__legacy-item--selected {
border-color: var(--legacy-accent-selected);
box-shadow: 0 0 0 2px var(--legacy-accent-ring);
.tool-panel__fullscreen-item--selected {
border-color: var(--fullscreen-accent-selected);
box-shadow: 0 0 0 2px var(--fullscreen-accent-ring);
}
.tool-panel__legacy-item--detailed {
.tool-panel__fullscreen-item--detailed {
min-height: 7.5rem;
}
.tool-panel__legacy-item--with-star {
.tool-panel__fullscreen-item--with-star {
position: relative;
}
.tool-panel__legacy-star {
.tool-panel__fullscreen-star {
position: absolute;
top: 0.5rem;
right: 0.5rem;
@@ -273,16 +275,16 @@
z-index: 2;
}
.tool-panel__legacy-item:hover .tool-panel__legacy-star,
.tool-panel__legacy-star:focus {
.tool-panel__fullscreen-item:hover .tool-panel__fullscreen-star,
.tool-panel__fullscreen-star:focus {
opacity: 1;
}
.tool-panel__legacy-list-item--with-star {
.tool-panel__fullscreen-list-item--with-star {
position: relative;
}
.tool-panel__legacy-star-compact {
.tool-panel__fullscreen-star-compact {
position: absolute;
top: 0.35rem;
right: 0.35rem;
@@ -291,36 +293,36 @@
z-index: 2;
}
.tool-panel__legacy-list-item:hover .tool-panel__legacy-star-compact,
.tool-panel__legacy-star-compact:focus {
.tool-panel__fullscreen-list-item:hover .tool-panel__fullscreen-star-compact,
.tool-panel__fullscreen-star-compact:focus {
opacity: 1;
}
.tool-panel__legacy-group--special {
.tool-panel__fullscreen-group--special {
background: linear-gradient(
135deg,
color-mix(in srgb, var(--legacy-bg-group) 95%, var(--text-primary) 5%),
var(--legacy-bg-group)
color-mix(in srgb, var(--fullscreen-bg-group) 95%, var(--text-primary) 5%),
var(--fullscreen-bg-group)
);
border-width: 1.5px;
}
.tool-panel__legacy-icon {
.tool-panel__fullscreen-icon {
display: flex;
align-items: center;
justify-content: center;
width: 2.75rem;
height: 2.75rem;
border-radius: 0.75rem;
background: var(--legacy-bg-icon-detailed);
color: var(--legacy-text-icon);
background: var(--fullscreen-bg-icon-detailed);
color: var(--fullscreen-text-icon);
flex-shrink: 0;
transition: transform 0.2s ease, box-shadow 0.2s ease;
position: relative;
overflow: hidden;
}
.tool-panel__legacy-icon::before {
.tool-panel__fullscreen-icon::before {
content: '';
position: absolute;
inset: 0;
@@ -333,54 +335,54 @@
border-radius: 0.75rem;
}
.tool-panel__legacy-item:hover:not([aria-disabled="true"]) .tool-panel__legacy-icon {
.tool-panel__fullscreen-item:hover:not([aria-disabled="true"]) .tool-panel__fullscreen-icon {
transform: scale(1.08);
box-shadow: 0 4px 12px color-mix(in srgb, var(--text-primary) 15%, transparent);
}
.tool-panel__legacy-item:hover:not([aria-disabled="true"]) .tool-panel__legacy-icon::before {
.tool-panel__fullscreen-item:hover:not([aria-disabled="true"]) .tool-panel__fullscreen-icon::before {
opacity: 1;
}
.tool-panel__legacy-icon svg {
.tool-panel__fullscreen-icon svg {
font-size: 1.65rem;
position: relative;
z-index: 1;
}
.tool-panel__legacy-body {
.tool-panel__fullscreen-body {
display: flex;
flex-direction: column;
gap: 0.4rem;
text-align: left;
}
.tool-panel__legacy-name {
.tool-panel__fullscreen-name {
color: var(--text-primary);
}
.tool-panel__legacy-description {
.tool-panel__fullscreen-description {
line-height: 1.45;
}
.tool-panel__legacy-match {
.tool-panel__fullscreen-match {
font-style: italic;
}
.tool-panel__legacy-shortcut {
.tool-panel__fullscreen-shortcut {
display: flex;
align-items: center;
gap: 0.5rem;
margin-top: 0.2rem;
}
.tool-panel__legacy-list {
.tool-panel__fullscreen-list {
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.tool-panel__legacy-list-item {
.tool-panel__fullscreen-list-item {
all: unset;
display: flex;
align-items: center;
@@ -389,45 +391,45 @@
border-radius: 0.65rem;
cursor: pointer;
transition: background 0.2s ease, transform 0.2s ease;
background: var(--legacy-bg-list-item);
background: var(--fullscreen-bg-list-item);
border: 1px solid transparent;
width: 100%;
box-sizing: border-box;
position: relative;
}
.tool-panel__legacy-list-item[aria-disabled="true"],
.tool-panel__legacy-list-item:disabled {
.tool-panel__fullscreen-list-item[aria-disabled="true"],
.tool-panel__fullscreen-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: var(--legacy-accent-list-bg);
border-color: var(--legacy-accent-list-border);
.tool-panel__fullscreen-list-item:hover:not([aria-disabled="true"]):not(:disabled),
.tool-panel__fullscreen-list-item--selected {
background: var(--fullscreen-accent-list-bg);
border-color: var(--fullscreen-accent-list-border);
}
.tool-panel__legacy-list-item--selected {
.tool-panel__fullscreen-list-item--selected {
transform: translateX(2px);
}
.tool-panel__legacy-list-icon {
.tool-panel__fullscreen-list-icon {
display: flex;
align-items: center;
justify-content: center;
width: 2.1rem;
height: 2.1rem;
border-radius: 0.6rem;
background: var(--legacy-bg-icon-compact);
color: var(--legacy-text-icon-compact);
background: var(--fullscreen-bg-icon-compact);
color: var(--fullscreen-text-icon-compact);
flex-shrink: 0;
transition: transform 0.2s ease, box-shadow 0.2s ease;
position: relative;
overflow: hidden;
}
.tool-panel__legacy-list-icon::before {
.tool-panel__fullscreen-list-icon::before {
content: '';
position: absolute;
inset: 0;
@@ -440,21 +442,21 @@
border-radius: 0.6rem;
}
.tool-panel__legacy-list-item:hover:not([aria-disabled="true"]) .tool-panel__legacy-list-icon {
.tool-panel__fullscreen-list-item:hover:not([aria-disabled="true"]) .tool-panel__fullscreen-list-icon {
transform: scale(1.06);
box-shadow: 0 2px 8px color-mix(in srgb, var(--text-primary) 12%, transparent);
}
.tool-panel__legacy-list-item:hover:not([aria-disabled="true"]) .tool-panel__legacy-list-icon::before {
.tool-panel__fullscreen-list-item:hover:not([aria-disabled="true"]) .tool-panel__fullscreen-list-icon::before {
opacity: 1;
}
.tool-panel__legacy-list-icon svg {
.tool-panel__fullscreen-list-icon svg {
position: relative;
z-index: 1;
}
.tool-panel__legacy-list-body {
.tool-panel__fullscreen-list-body {
display: flex;
flex-direction: column;
align-items: flex-start;
@@ -462,14 +464,14 @@
text-align: left;
}
.tool-panel__legacy-empty {
.tool-panel__fullscreen-empty {
padding: 2rem 1.75rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
@keyframes tool-panel-legacy-slide-in {
@keyframes tool-panel-fullscreen-slide-in {
from {
transform: translateX(-6%) scaleX(0.85);
opacity: 0;
@@ -480,7 +482,7 @@
}
}
@keyframes tool-panel-legacy-slide-out {
@keyframes tool-panel-fullscreen-slide-out {
from {
transform: translateX(0) scaleX(1);
opacity: 1;
@@ -492,7 +494,7 @@
}
@media (prefers-reduced-motion: reduce) {
.tool-panel__legacy-surface-inner {
.tool-panel__fullscreen-surface-inner {
animation: none !important;
}
@@ -500,33 +502,33 @@
transition: none;
}
.tool-panel__legacy-item,
.tool-panel__legacy-list-item {
.tool-panel__fullscreen-item,
.tool-panel__fullscreen-list-item {
transition: none;
}
}
@media (max-width: 1440px) {
.tool-panel__legacy-content {
.tool-panel__fullscreen-content {
padding-inline: 1.5rem;
}
.tool-panel__legacy-grid--compact {
.tool-panel__fullscreen-grid--compact {
column-width: 15rem;
}
}
@media (max-width: 1280px) {
.tool-panel__legacy-controls {
.tool-panel__fullscreen-controls {
flex-direction: column;
align-items: stretch;
}
.tool-panel__legacy-controls .mantine-Switch-root {
.tool-panel__fullscreen-controls .mantine-Switch-root {
justify-content: flex-end;
}
.tool-panel__legacy-grid--compact {
.tool-panel__fullscreen-grid--compact {
column-width: 14rem;
}
}
@@ -534,38 +536,39 @@
/* Dynamic settings support */
/* No border variant */
.tool-panel__legacy-item--no-border,
.tool-panel__legacy-list-item--no-border {
.tool-panel__fullscreen-item--no-border,
.tool-panel__fullscreen-list-item--no-border {
border: none !important;
}
/* Hover intensity variants */
.tool-panel__legacy-item--hover-subtle:hover,
.tool-panel__legacy-list-item--hover-subtle:hover {
background: var(--legacy-hover-subtle);
.tool-panel__fullscreen-item--hover-subtle:hover,
.tool-panel__fullscreen-list-item--hover-subtle:hover {
background: var(--fullscreen-hover-subtle);
transform: none;
}
.tool-panel__legacy-item--hover-moderate:hover,
.tool-panel__legacy-list-item--hover-moderate:hover {
background: var(--legacy-hover);
.tool-panel__fullscreen-item--hover-moderate:hover,
.tool-panel__fullscreen-list-item--hover-moderate:hover {
background: var(--fullscreen-hover);
transform: translateY(-2px);
}
.tool-panel__legacy-item--hover-prominent:hover,
.tool-panel__legacy-list-item--hover-prominent:hover {
background: var(--legacy-hover-prominent);
.tool-panel__fullscreen-item--hover-prominent:hover,
.tool-panel__fullscreen-list-item--hover-prominent:hover {
background: var(--fullscreen-hover-prominent);
transform: translateY(-3px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
/* Hover-only icon background */
.tool-panel__legacy-icon--hover-bg,
.tool-panel__legacy-list-icon--hover-bg {
.tool-panel__fullscreen-icon--hover-bg,
.tool-panel__fullscreen-list-icon--hover-bg {
transition: background 0.2s ease;
}
.tool-panel__legacy-item:hover .tool-panel__legacy-icon--hover-bg,
.tool-panel__legacy-list-item:hover .tool-panel__legacy-list-icon--hover-bg {
background: var(--legacy-icon-hover-bg) !important;
.tool-panel__fullscreen-item:hover .tool-panel__fullscreen-icon--hover-bg,
.tool-panel__fullscreen-list-item:hover .tool-panel__fullscreen-list-icon--hover-bg {
background: var(--fullscreen-icon-hover-bg) !important;
}

View File

@@ -1,4 +1,4 @@
import { useMemo } from 'react';
import React, { useMemo } from 'react';
import { useRainbowThemeContext } from '../shared/RainbowThemeProvider';
import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
import ToolPicker from './ToolPicker';
@@ -13,9 +13,10 @@ 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 FullscreenToolSurface from './FullscreenToolSurface';
import { useToolPanelGeometry } from '../../hooks/tools/useToolPanelGeometry';
import { useLocalStorageState } from '../../hooks/tools/useLocalStorageState';
import { useRightRail } from '../../contexts/RightRailContext';
import './ToolPanel.css';
// No props needed - component uses context
@@ -40,28 +41,49 @@ export default function ToolPanel() {
toolPanelMode,
setToolPanelMode,
setLeftPanelView,
readerMode,
} = useToolWorkflow();
const isLegacyMode = toolPanelMode === 'legacy';
const legacyExpanded = isLegacyMode && leftPanelView === 'toolPicker' && !isMobile;
const { setAllRightRailButtonsDisabled } = useRightRail();
const isFullscreenMode = toolPanelMode === 'fullscreen';
const toolPickerVisible = !readerMode;
const fullscreenExpanded = isFullscreenMode && leftPanelView === 'toolPicker' && !isMobile && toolPickerVisible;
// Debug logging for troubleshooting
React.useEffect(() => {
console.log('ToolPanel debug:', {
isFullscreenMode,
leftPanelView,
isMobile,
toolPickerVisible,
fullscreenExpanded
});
}, [isFullscreenMode, leftPanelView, isMobile, toolPickerVisible, fullscreenExpanded]);
// Disable right rail buttons when fullscreen mode is active
React.useEffect(() => {
setAllRightRailButtonsDisabled(fullscreenExpanded);
}, [fullscreenExpanded, setAllRightRailButtonsDisabled]);
// Use custom hooks for state management
const [showLegacyDescriptions, setShowLegacyDescriptions] = useLocalStorageState('legacyToolDescriptions', false);
const legacyGeometry = useToolPanelGeometry({
enabled: legacyExpanded,
const fullscreenGeometry = useToolPanelGeometry({
enabled: fullscreenExpanded,
toolPanelRef,
quickAccessRef,
});
const toggleLabel = isLegacyMode
const toggleLabel = isFullscreenMode
? t('toolPanel.toggle.sidebar', 'Switch to sidebar mode')
: t('toolPanel.toggle.legacy', 'Switch to legacy mode');
: t('toolPanel.toggle.fullscreen', 'Switch to fullscreen mode');
const handleModeToggle = () => {
const nextMode = isLegacyMode ? 'sidebar' : 'legacy';
const nextMode = isFullscreenMode ? 'sidebar' : 'fullscreen';
setToolPanelMode(nextMode);
if (nextMode === 'legacy' && leftPanelView !== 'toolPicker') {
if (nextMode === 'fullscreen' && leftPanelView !== 'toolPicker') {
setLeftPanelView('toolPicker');
}
};
@@ -92,15 +114,15 @@ export default function ToolPanel() {
<div
ref={toolPanelRef}
data-sidebar="tool-panel"
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 ${
className={`tool-panel flex flex-col ${fullscreenExpanded ? 'tool-panel--fullscreen-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'} ${legacyExpanded ? 'tool-panel--legacy' : ''}`}
} ${isMobile ? 'h-full border-r-0' : 'h-screen'} ${fullscreenExpanded ? 'tool-panel--fullscreen' : ''}`}
style={{
width: computedWidth(),
padding: '0'
}}
>
{!legacyExpanded && (
{!fullscreenExpanded && (
<div
style={{
opacity: isMobile || isPanelVisible ? 1 : 0,
@@ -123,17 +145,26 @@ export default function ToolPanel() {
toolRegistry={toolRegistry}
mode="filter"
/>
{!isMobile && (
<Tooltip label={toggleLabel} position="bottom" withArrow>
{!isMobile && leftPanelView === 'toolPicker' && (
<Tooltip
label={toggleLabel}
position="bottom"
withArrow
styles={{
tooltip: {
zIndex: 1400, // Higher than fullscreen surface
}
}}
>
<ActionIcon
variant="subtle"
radius="xl"
color="gray"
style={{ color: 'var(--right-rail-icon)' }}
onClick={handleModeToggle}
aria-label={toggleLabel}
className="tool-panel__mode-toggle"
>
{isLegacyMode ? (
{isFullscreenMode ? (
<ViewSidebarRoundedIcon fontSize="small" />
) : (
<DashboardCustomizeRoundedIcon fontSize="small" />
@@ -143,23 +174,23 @@ export default function ToolPanel() {
)}
</div>
{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' ? (
<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>
{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' ? (
<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">
@@ -181,8 +212,8 @@ export default function ToolPanel() {
</div>
)}
{legacyExpanded && (
<LegacyToolSurface
{fullscreenExpanded && (
<FullscreenToolSurface
searchQuery={searchQuery}
toolRegistry={toolRegistry}
filteredTools={filteredTools}
@@ -190,11 +221,11 @@ export default function ToolPanel() {
showDescriptions={showLegacyDescriptions}
matchedTextMap={matchedTextMap}
onSearchChange={setSearchQuery}
onSelect={(id) => handleToolSelect(id as ToolId)}
onSelect={(id: ToolId) => handleToolSelect(id)}
onToggleDescriptions={() => setShowLegacyDescriptions((prev) => !prev)}
onExitLegacyMode={() => setToolPanelMode('sidebar')}
onExitFullscreenMode={() => setToolPanelMode('sidebar')}
toggleLabel={toggleLabel}
geometry={legacyGeometry}
geometry={fullscreenGeometry}
/>
)}
</div>

View File

@@ -136,6 +136,39 @@
opacity: 0.55;
}
.tool-panel-mode-prompt__preview--fullscreen {
align-items: flex-start;
justify-content: center;
padding: 0.65rem 0.6rem;
}
.tool-panel-mode-prompt__fullscreen-columns {
width: 100%;
display: flex;
gap: 0.45rem;
justify-content: space-between;
}
.tool-panel-mode-prompt__fullscreen-column {
flex: 1;
display: grid;
gap: 0.3rem;
}
.tool-panel-mode-prompt__fullscreen-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__fullscreen-card--muted {
opacity: 0.55;
}
.tool-panel-mode-prompt__card-content {
display: flex;
flex-direction: column;

View File

@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
import { useToolWorkflow, TOOL_PANEL_MODE_STORAGE_KEY } from '../../contexts/ToolWorkflowContext';
import './ToolPanelModePrompt.css';
type ToolPanelModeOption = 'sidebar' | 'legacy';
type ToolPanelModeOption = 'sidebar' | 'fullscreen';
const PROMPT_SEEN_KEY = 'toolPanelModePromptSeen';
@@ -72,10 +72,10 @@ const ToolPanelModePrompt = () => {
<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>
<Text fw={600}>{t('toolPanel.modePrompt.sidebarTitle', 'Sidebar mode')}</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')}
@@ -101,45 +101,45 @@ const ToolPanelModePrompt = () => {
className="tool-panel-mode-prompt__action"
onClick={() => handleSelect('sidebar')}
>
{t('toolPanel.modePrompt.chooseSidebar', 'Use advanced sidebar')}
{t('toolPanel.modePrompt.chooseSidebar', 'Use sidebar mode')}
</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 fw={600}>{t('toolPanel.modePrompt.fullscreenTitle', 'Fullscreen mode - (legacy)')}</Text>
<Text size="sm" c="dimmed">
{t('toolPanel.modePrompt.legacyDescription', 'Browse every tool in a catalogue that covers the workspace until you pick one.')}
{t('toolPanel.modePrompt.fullscreenDescription', '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 className="tool-panel-mode-prompt__preview tool-panel-mode-prompt__preview--fullscreen" aria-hidden>
<div className="tool-panel-mode-prompt__fullscreen-columns">
<div className="tool-panel-mode-prompt__fullscreen-column">
<span className="tool-panel-mode-prompt__fullscreen-card" />
<span className="tool-panel-mode-prompt__fullscreen-card" />
<span className="tool-panel-mode-prompt__fullscreen-card tool-panel-mode-prompt__fullscreen-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 className="tool-panel-mode-prompt__fullscreen-column">
<span className="tool-panel-mode-prompt__fullscreen-card" />
<span className="tool-panel-mode-prompt__fullscreen-card" />
<span className="tool-panel-mode-prompt__fullscreen-card tool-panel-mode-prompt__fullscreen-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 className="tool-panel-mode-prompt__fullscreen-column">
<span className="tool-panel-mode-prompt__fullscreen-card" />
<span className="tool-panel-mode-prompt__fullscreen-card" />
<span className="tool-panel-mode-prompt__fullscreen-card tool-panel-mode-prompt__fullscreen-card--muted" />
</div>
</div>
</div>
<Button
variant={toolPanelMode === 'legacy' ? 'filled' : 'outline'}
variant={toolPanelMode === 'fullscreen' ? 'filled' : 'outline'}
color="blue"
radius="md"
className="tool-panel-mode-prompt__action"
onClick={() => handleSelect('legacy')}
onClick={() => handleSelect('fullscreen')}
>
{t('toolPanel.modePrompt.chooseLegacy', 'Use legacy fullscreen')}
{t('toolPanel.modePrompt.chooseFullscreen', 'Use fullscreen mode')}
</Button>
</Stack>
</Card>

View File

@@ -6,6 +6,7 @@ import "./toolPicker/ToolPicker.css";
import { useToolSections } from "../../hooks/useToolSections";
import NoToolsFound from "./shared/NoToolsFound";
import { renderToolButtons } from "./shared/renderToolButtons";
import Badge from "../shared/Badge";
interface ToolPickerProps {
selectedToolKey: string | null;
@@ -115,7 +116,7 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
{searchGroups.length === 0 ? (
<NoToolsFound />
) : (
searchGroups.map(group => renderToolButtons(t, group, selectedToolKey, onSelect))
searchGroups.map(group => renderToolButtons(t, group, selectedToolKey, onSelect, true, false, filteredTools))
)}
</Stack>
) : (
@@ -142,25 +143,16 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
onClick={() => scrollTo(quickAccessRef)}
>
<span style={{ fontSize: "1rem" }}>{t("toolPicker.quickAccess", "QUICK ACCESS")}</span>
<span
style={{
background: "var(--tool-header-badge-bg)",
color: "var(--tool-header-badge-text)",
borderRadius: ".5rem",
padding: "0.125rem 0.5rem",
fontSize: ".75rem",
fontWeight: 700
}}
>
<Badge>
{quickSection?.subcategories.reduce((acc, sc) => acc + sc.tools.length, 0)}
</span>
</Badge>
</div>
<Box ref={quickAccessRef} w="100%" my="sm">
<Stack p="sm" gap="xs">
{quickSection?.subcategories.map(sc =>
renderToolButtons(t, sc, selectedToolKey, onSelect, false)
)}
{quickSection?.subcategories.map(sc =>
renderToolButtons(t, sc, selectedToolKey, onSelect, false, false)
)}
</Stack>
</Box>
</>
@@ -188,25 +180,16 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
onClick={() => scrollTo(allToolsRef)}
>
<span style={{ fontSize: "1rem" }}>{t("toolPicker.allTools", "ALL TOOLS")}</span>
<span
style={{
background: "var(--tool-header-badge-bg)",
color: "var(--tool-header-badge-text)",
borderRadius: ".5rem",
padding: "0.125rem 0.5rem",
fontSize: ".75rem",
fontWeight: 700
}}
>
<Badge>
{allSection?.subcategories.reduce((acc, sc) => acc + sc.tools.length, 0)}
</span>
</Badge>
</div>
<Box ref={allToolsRef} w="100%">
<Stack p="sm" gap="xs">
{allSection?.subcategories.map(sc =>
renderToolButtons(t, sc, selectedToolKey, onSelect, true)
)}
{allSection?.subcategories.map(sc =>
renderToolButtons(t, sc, selectedToolKey, onSelect, true, false)
)}
</Stack>
</Box>
</>

View File

@@ -30,20 +30,20 @@ export const renderToolButtons = (
<SubcategoryHeader label={getSubcategoryLabel(t, subcategory.subcategoryId)} />
)}
<div>
{subcategory.tools.map(({ id, tool }) => {
const matchedSynonym = matchedTextMap.get(id);
return (
<ToolButton
key={id}
id={id}
tool={tool}
isSelected={selectedToolKey === id}
onSelect={onSelect}
disableNavigation={disableNavigation}
matchedSynonym={matchedSynonym}
/>
);
{subcategory.tools.map(({ id, tool }, index) => {
const matchedSynonym = matchedTextMap.get(id);
return (
<ToolButton
key={id}
id={id}
tool={tool}
isSelected={selectedToolKey === id}
onSelect={onSelect}
disableNavigation={disableNavigation}
matchedSynonym={matchedSynonym}
/>
);
})}
</div>
</Box>

View File

@@ -103,7 +103,11 @@ const ToolButton: React.FC<ToolButtonProps> = ({ id, tool, isSelected, onSelect,
justify="flex-start"
className="tool-button"
styles={{
root: { borderRadius: 0, color: "var(--tools-text-and-icon-color)", overflow: 'visible' },
root: {
borderRadius: 0,
color: "var(--tools-text-and-icon-color)",
overflow: 'visible'
},
label: { overflow: 'visible' }
}}
>
@@ -124,7 +128,11 @@ const ToolButton: React.FC<ToolButtonProps> = ({ id, tool, isSelected, onSelect,
justify="flex-start"
className="tool-button"
styles={{
root: { borderRadius: 0, color: "var(--tools-text-and-icon-color)", overflow: 'visible' },
root: {
borderRadius: 0,
color: "var(--tools-text-and-icon-color)",
overflow: 'visible'
},
label: { overflow: 'visible' }
}}
>
@@ -141,7 +149,15 @@ const ToolButton: React.FC<ToolButtonProps> = ({ id, tool, isSelected, onSelect,
justify="flex-start"
className="tool-button"
aria-disabled={isUnavailable}
styles={{ root: { borderRadius: 0, color: "var(--tools-text-and-icon-color)", cursor: isUnavailable ? 'not-allowed' : undefined, overflow: 'visible' }, label: { overflow: 'visible' } }}
styles={{
root: {
borderRadius: 0,
color: "var(--tools-text-and-icon-color)",
cursor: isUnavailable ? 'not-allowed' : undefined,
overflow: 'visible'
},
label: { overflow: 'visible' }
}}
>
{buttonContent}
</Button>

View File

@@ -20,7 +20,7 @@ interface ToolSearchProps {
autoFocus?: boolean;
}
const ToolSearch = ({
const ToolSearch: React.FC<ToolSearchProps> = ({
value,
onChange,
toolRegistry,
@@ -31,7 +31,7 @@ const ToolSearch = ({
hideIcon = false,
onFocus,
autoFocus = false,
}: ToolSearchProps) => {
}) => {
const { t } = useTranslation();
const [dropdownOpen, setDropdownOpen] = useState(false);
const searchRef = useRef<HTMLInputElement>(null);
@@ -81,15 +81,15 @@ const ToolSearch = ({
}, [autoFocus]);
const searchInput = (
<TextInput
ref={searchRef}
value={value}
onChange={handleSearchChange}
placeholder={placeholder || t("toolPicker.searchPlaceholder", "Search tools...")}
icon={hideIcon ? undefined : <LocalIcon icon="search-rounded" width="1.5rem" height="1.5rem" />}
autoComplete="off"
onFocus={onFocus}
/>
<TextInput
ref={searchRef}
value={value}
onChange={handleSearchChange}
placeholder={placeholder || t("toolPicker.searchPlaceholder", "Search tools...")}
icon={hideIcon ? undefined : <LocalIcon icon="search-rounded" width="1.5rem" height="1.5rem" />}
autoComplete="off"
onFocus={onFocus}
/>
);
if (mode === "filter") {

View File

@@ -4,9 +4,11 @@ import { RightRailAction, RightRailButtonConfig } from '../types/rightRail';
interface RightRailContextValue {
buttons: RightRailButtonConfig[];
actions: Record<string, RightRailAction>;
allButtonsDisabled: boolean;
registerButtons: (buttons: RightRailButtonConfig[]) => void;
unregisterButtons: (ids: string[]) => void;
setAction: (id: string, action: RightRailAction) => void;
setAllRightRailButtonsDisabled: (disabled: boolean) => void;
clear: () => void;
}
@@ -15,6 +17,7 @@ const RightRailContext = createContext<RightRailContextValue | undefined>(undefi
export function RightRailProvider({ children }: { children: React.ReactNode }) {
const [buttons, setButtons] = useState<RightRailButtonConfig[]>([]);
const [actions, setActions] = useState<Record<string, RightRailAction>>({});
const [allButtonsDisabled, setAllButtonsDisabled] = useState<boolean>(false);
const registerButtons = useCallback((newButtons: RightRailButtonConfig[]) => {
setButtons(prev => {
@@ -43,12 +46,25 @@ export function RightRailProvider({ children }: { children: React.ReactNode }) {
setActions(prev => ({ ...prev, [id]: action }));
}, []);
const setAllRightRailButtonsDisabled = useCallback((disabled: boolean) => {
setAllButtonsDisabled(disabled);
}, []);
const clear = useCallback(() => {
setButtons([]);
setActions({});
}, []);
const value = useMemo<RightRailContextValue>(() => ({ buttons, actions, registerButtons, unregisterButtons, setAction, clear }), [buttons, actions, registerButtons, unregisterButtons, setAction, clear]);
const value = useMemo<RightRailContextValue>(() => ({
buttons,
actions,
allButtonsDisabled,
registerButtons,
unregisterButtons,
setAction,
setAllRightRailButtonsDisabled,
clear
}), [buttons, actions, allButtonsDisabled, registerButtons, unregisterButtons, setAction, setAllRightRailButtonsDisabled, clear]);
return (
<RightRailContext.Provider value={value}>

View File

@@ -13,10 +13,10 @@ import { useNavigationUrlSync } from '../hooks/useUrlSync';
import { getDefaultWorkbench } from '../types/workbench';
import { filterToolRegistryByQuery } from '../utils/toolSearch';
import { useToolHistory } from '../hooks/tools/useToolHistory';
import { LegacyToolStyleSettings, defaultLegacyToolSettings } from '../components/tools/LegacyToolSettings';
import { FullscreenToolStyleSettings, defaultFullscreenToolSettings } from '../components/tools/FullscreenToolSettings';
// State interface
type ToolPanelMode = 'sidebar' | 'legacy';
type ToolPanelMode = 'sidebar' | 'fullscreen';
interface ToolWorkflowState {
// UI State
@@ -24,7 +24,7 @@ interface ToolWorkflowState {
leftPanelView: 'toolPicker' | 'toolContent' | 'hidden';
readerMode: boolean;
toolPanelMode: ToolPanelMode;
legacyToolSettings: LegacyToolStyleSettings;
fullscreenToolSettings: FullscreenToolStyleSettings;
// File/Preview State
previewFile: File | null;
@@ -40,7 +40,7 @@ type ToolWorkflowAction =
| { type: 'SET_LEFT_PANEL_VIEW'; payload: 'toolPicker' | 'toolContent' | 'hidden' }
| { type: 'SET_READER_MODE'; payload: boolean }
| { type: 'SET_TOOL_PANEL_MODE'; payload: ToolPanelMode }
| { type: 'SET_LEGACY_TOOL_SETTINGS'; payload: LegacyToolStyleSettings }
| { type: 'SET_FULLSCREEN_TOOL_SETTINGS'; payload: FullscreenToolStyleSettings }
| { type: 'SET_PREVIEW_FILE'; payload: File | null }
| { type: 'SET_PAGE_EDITOR_FUNCTIONS'; payload: PageEditorFunctions | null }
| { type: 'SET_SEARCH_QUERY'; payload: string }
@@ -48,6 +48,7 @@ type ToolWorkflowAction =
// Initial state
export const TOOL_PANEL_MODE_STORAGE_KEY = 'toolPanelModePreference';
export const FULLSCREEN_TOOL_SETTINGS_STORAGE_KEY = 'fullscreenToolStyleSettings';
export const LEGACY_TOOL_SETTINGS_STORAGE_KEY = 'legacyToolStyleSettings';
const getStoredToolPanelMode = (): ToolPanelMode => {
@@ -57,30 +58,34 @@ const getStoredToolPanelMode = (): ToolPanelMode => {
const stored = window.localStorage.getItem(TOOL_PANEL_MODE_STORAGE_KEY);
if (stored === 'legacy' || stored === 'fullscreen') {
return 'legacy';
return 'fullscreen';
}
return 'sidebar';
};
const getStoredLegacyToolSettings = (): LegacyToolStyleSettings => {
const getStoredFullscreenToolSettings = (): FullscreenToolStyleSettings => {
if (typeof window === 'undefined') {
return defaultLegacyToolSettings;
return defaultFullscreenToolSettings;
}
try {
const stored = window.localStorage.getItem(LEGACY_TOOL_SETTINGS_STORAGE_KEY);
if (stored) {
return { ...defaultLegacyToolSettings, ...JSON.parse(stored) };
const storedNew = window.localStorage.getItem(FULLSCREEN_TOOL_SETTINGS_STORAGE_KEY);
if (storedNew) {
return { ...defaultFullscreenToolSettings, ...JSON.parse(storedNew) };
}
const storedLegacy = window.localStorage.getItem(LEGACY_TOOL_SETTINGS_STORAGE_KEY);
if (storedLegacy) {
return { ...defaultFullscreenToolSettings, ...JSON.parse(storedLegacy) };
}
} catch (e) {
console.error('Failed to parse legacy tool settings:', e);
console.error('Failed to parse fullscreen tool settings:', e);
}
return defaultLegacyToolSettings;
return defaultFullscreenToolSettings;
};
const baseState: Omit<ToolWorkflowState, 'toolPanelMode' | 'legacyToolSettings'> = {
const baseState: Omit<ToolWorkflowState, 'toolPanelMode' | 'fullscreenToolSettings'> = {
sidebarsVisible: true,
leftPanelView: 'toolPicker',
readerMode: false,
@@ -92,7 +97,7 @@ const baseState: Omit<ToolWorkflowState, 'toolPanelMode' | 'legacyToolSettings'>
const createInitialState = (): ToolWorkflowState => ({
...baseState,
toolPanelMode: getStoredToolPanelMode(),
legacyToolSettings: getStoredLegacyToolSettings(),
fullscreenToolSettings: getStoredFullscreenToolSettings(),
});
// Reducer
@@ -106,8 +111,8 @@ function toolWorkflowReducer(state: ToolWorkflowState, action: ToolWorkflowActio
return { ...state, readerMode: action.payload };
case 'SET_TOOL_PANEL_MODE':
return { ...state, toolPanelMode: action.payload };
case 'SET_LEGACY_TOOL_SETTINGS':
return { ...state, legacyToolSettings: action.payload };
case 'SET_FULLSCREEN_TOOL_SETTINGS':
return { ...state, fullscreenToolSettings: action.payload };
case 'SET_PREVIEW_FILE':
return { ...state, previewFile: action.payload };
case 'SET_PAGE_EDITOR_FUNCTIONS':
@@ -118,7 +123,7 @@ function toolWorkflowReducer(state: ToolWorkflowState, action: ToolWorkflowActio
return {
...baseState,
toolPanelMode: state.toolPanelMode,
legacyToolSettings: state.legacyToolSettings,
fullscreenToolSettings: state.fullscreenToolSettings,
searchQuery: state.searchQuery,
};
default:
@@ -139,12 +144,12 @@ interface ToolWorkflowContextValue extends ToolWorkflowState {
setLeftPanelView: (view: 'toolPicker' | 'toolContent' | 'hidden') => void;
setReaderMode: (mode: boolean) => void;
setToolPanelMode: (mode: ToolPanelMode) => void;
setLegacyToolSettings: (settings: LegacyToolStyleSettings) => void;
setFullscreenToolSettings: (settings: FullscreenToolStyleSettings) => void;
setPreviewFile: (file: File | null) => void;
setPageEditorFunctions: (functions: PageEditorFunctions | null) => void;
setSearchQuery: (query: string) => void;
// Tool Actions
selectTool: (toolId: ToolId | null) => void;
clearToolSelection: () => void;
@@ -226,8 +231,9 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
dispatch({ type: 'SET_TOOL_PANEL_MODE', payload: mode });
}, []);
const setLegacyToolSettings = useCallback((settings: LegacyToolStyleSettings) => {
dispatch({ type: 'SET_LEGACY_TOOL_SETTINGS', payload: settings });
const setFullscreenToolSettings = useCallback((settings: FullscreenToolStyleSettings) => {
dispatch({ type: 'SET_FULLSCREEN_TOOL_SETTINGS', payload: settings });
}, []);
const setPreviewFile = useCallback((file: File | null) => {
@@ -258,8 +264,10 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
return;
}
window.localStorage.setItem(LEGACY_TOOL_SETTINGS_STORAGE_KEY, JSON.stringify(state.legacyToolSettings));
}, [state.legacyToolSettings]);
const serialized = JSON.stringify(state.fullscreenToolSettings);
window.localStorage.setItem(FULLSCREEN_TOOL_SETTINGS_STORAGE_KEY, serialized);
window.localStorage.setItem(LEGACY_TOOL_SETTINGS_STORAGE_KEY, serialized);
}, [state.fullscreenToolSettings]);
// Tool reset methods
const registerToolReset = useCallback((toolId: string, resetFunction: () => void) => {
@@ -288,8 +296,6 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
actions.setSelectedTool('read');
actions.setWorkbench('viewer');
setSearchQuery('');
setToolPanelMode('sidebar'); // Close legacy mode when switching to reader
setLeftPanelView('toolPicker'); // Show tool picker when navigating back to tools
return;
}
@@ -365,7 +371,7 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
setLeftPanelView,
setReaderMode,
setToolPanelMode,
setLegacyToolSettings,
setFullscreenToolSettings,
setPreviewFile,
setPageEditorFunctions,
setSearchQuery,
@@ -402,7 +408,7 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
setLeftPanelView,
setReaderMode,
setToolPanelMode,
setLegacyToolSettings,
setFullscreenToolSettings,
setPreviewFile,
setPageEditorFunctions,
setSearchQuery,

View File

@@ -77,17 +77,17 @@ export const SUBCATEGORY_ORDER: SubcategoryId[] = [
];
export const SUBCATEGORY_COLOR_MAP: Record<SubcategoryId, string> = {
[SubcategoryId.SIGNING]: '#E91E63', // Rose Pink
[SubcategoryId.DOCUMENT_SECURITY]: '#D32F2F', // Deep Red
[SubcategoryId.VERIFICATION]: '#1976D2', // Medium Blue
[SubcategoryId.DOCUMENT_REVIEW]: '#388E3C', // Forest Green
[SubcategoryId.PAGE_FORMATTING]: '#5E35B1', // Deep Purple
[SubcategoryId.EXTRACTION]: '#F57C00', // Amber/Gold
[SubcategoryId.REMOVAL]: '#E64A19', // Rust Orange
[SubcategoryId.AUTOMATION]: '#00897B', // Teal
[SubcategoryId.GENERAL]: '#689F38', // Olive Green
[SubcategoryId.ADVANCED_FORMATTING]: '#8E24AA', // Deep Magenta
[SubcategoryId.DEVELOPER_TOOLS]: '#455A64', // Slate Grey
[SubcategoryId.SIGNING]: 'var(--category-color-signing)', // Green
[SubcategoryId.DOCUMENT_SECURITY]: 'var(--category-color-security)', // Orange
[SubcategoryId.VERIFICATION]: 'var(--category-color-verification)', // Orange
[SubcategoryId.DOCUMENT_REVIEW]: 'var(--category-color-general)', // Blue
[SubcategoryId.PAGE_FORMATTING]: 'var(--category-color-formatting)', // Purple
[SubcategoryId.EXTRACTION]: 'var(--category-color-extraction)', // Cyan
[SubcategoryId.REMOVAL]: 'var(--category-color-removal)', // Red
[SubcategoryId.AUTOMATION]: 'var(--category-color-automation)', // Pink
[SubcategoryId.GENERAL]: 'var(--category-color-general)', // Blue
[SubcategoryId.ADVANCED_FORMATTING]: 'var(--category-color-formatting)', // Purple
[SubcategoryId.DEVELOPER_TOOLS]: 'var(--category-color-developer)', // Gray
};
export const getSubcategoryIcon = (subcategory: SubcategoryId): React.ReactNode => {

View File

@@ -48,6 +48,18 @@
--color-yellow-200: #fef08a;
--color-yellow-300: #fde047;
--color-yellow-400: #facc15;
/* Category colors - consistent across light and dark modes */
--category-color-removal: #ef4444; /* Red for removal tools */
--category-color-security: #f59e0b; /* Orange for security tools */
--category-color-formatting: #8b5cf6; /* Purple for formatting tools */
--category-color-extraction: #06b6d4; /* Cyan for extraction tools */
--category-color-signing: #10b981; /* Green for signing tools */
--category-color-general: #3b82f6; /* Blue for general tools */
--category-color-verification: #f97316; /* Orange for verification tools */
--category-color-automation: #ec4899; /* Pink for automation tools */
--category-color-developer: #6b7280; /* Gray for developer tools */
--category-color-default: #6b7280; /* Default gray */
--color-yellow-500: #eab308;
--color-yellow-600: #ca8a04;
--color-yellow-700: #a16207;
@@ -277,6 +289,18 @@
--color-gray-800: #e5e7eb;
--color-gray-900: #f3f4f6;
/* Category colors - same as light mode for consistency */
--category-color-removal: #ef4444; /* Red for removal tools */
--category-color-security: #f59e0b; /* Orange for security tools */
--category-color-formatting: #8b5cf6; /* Purple for formatting tools */
--category-color-extraction: #06b6d4; /* Cyan for extraction tools */
--category-color-signing: #10b981; /* Green for signing tools */
--category-color-general: #3b82f6; /* Blue for general tools */
--category-color-verification: #f97316; /* Orange for verification tools */
--category-color-automation: #ec4899; /* Pink for automation tools */
--category-color-developer: #6b7280; /* Gray for developer tools */
--category-color-default: #6b7280; /* Default gray */
/* Success (green) - dark */
--color-green-50: #052e16;
--color-green-100: #064e3b;