(null);
@@ -92,16 +102,15 @@ function HomePageContent() {
>
{/* Quick Access Bar */}
{/* Left: Tool Picker or Selected Tool Panel */}
-
+
+
+
);
}
diff --git a/frontend/src/styles/theme.css b/frontend/src/styles/theme.css
index 71443411f..1cf3581c4 100644
--- a/frontend/src/styles/theme.css
+++ b/frontend/src/styles/theme.css
@@ -103,6 +103,13 @@
--icon-config-bg: #9CA3AF;
--icon-config-color: #FFFFFF;
+ /* Colors for tooltips */
+ --tooltip-title-bg: #DBEFFF;
+ --tooltip-title-color: #31528E;
+ --tooltip-header-bg: #31528E;
+ --tooltip-header-color: white;
+ --tooltip-border: var(--border-default);
+
/* Inactive icon colors for light mode */
--icon-inactive-bg: #9CA3AF;
--icon-inactive-color: #FFFFFF;
@@ -201,6 +208,13 @@
--icon-inactive-bg: #2A2F36;
--icon-inactive-color: #6E7581;
+ /* Dark mode tooltip colors */
+ --tooltip-title-bg: #4B525A;
+ --tooltip-title-color: #fff;
+ --tooltip-header-bg: var(--bg-raised);
+ --tooltip-header-color: var(--text-primary);
+ --tooltip-border: var(--border-default);
+
--accent-interactive: #ffffff;
--text-instruction: #ffffff;
--text-brand: var(--color-gray-800);
@@ -224,6 +238,7 @@
--drop-shadow-color: rgba(255, 255, 255, 0.08);
--drop-shadow-color-strong: rgba(255, 255, 255, 0.04);
--drop-shadow-filter: drop-shadow(0 0.2rem 0.4rem rgba(200, 200, 200, 0.08)) drop-shadow(0 0.6rem 0.6rem rgba(200, 200, 200, 0.06)) drop-shadow(0 1.2rem 1rem rgba(200, 200, 200, 0.04));
+
/* Adjust shadows for dark mode */
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.3);
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4);
diff --git a/frontend/src/tools/Compress.tsx b/frontend/src/tools/Compress.tsx
index cc0cd5cbc..f4b50b264 100644
--- a/frontend/src/tools/Compress.tsx
+++ b/frontend/src/tools/Compress.tsx
@@ -17,6 +17,7 @@ import CompressSettings from "../components/tools/compress/CompressSettings";
import { useCompressParameters } from "../hooks/tools/compress/useCompressParameters";
import { useCompressOperation } from "../hooks/tools/compress/useCompressOperation";
import { BaseToolProps } from "../types/tool";
+import { CompressTips } from "../components/tooltips/CompressTips";
const Compress = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const { t } = useTranslation();
@@ -25,6 +26,7 @@ const Compress = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const compressParams = useCompressParameters();
const compressOperation = useCompressOperation();
+ const compressTips = CompressTips();
// Endpoint validation
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled("compress-pdf");
@@ -104,6 +106,7 @@ const Compress = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
isCompleted={settingsCollapsed}
onCollapsedClick={settingsCollapsed ? handleSettingsReset : undefined}
completedMessage={settingsCollapsed ? "Compression completed" : undefined}
+ tooltip={compressTips}
>
{
const { t } = useTranslation();
@@ -26,6 +27,7 @@ const OCR = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const ocrParams = useOCRParameters();
const ocrOperation = useOCROperation();
+ const ocrTips = OcrTips();
// Step expansion state management
const [expandedStep, setExpandedStep] = useState<'files' | 'settings' | 'advanced' | null>('files');
@@ -126,6 +128,7 @@ const OCR = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
setExpandedStep(expandedStep === 'settings' ? null : 'settings');
}}
completedMessage={hasFiles && hasValidSettings && settingsCollapsed ? "Basic settings configured" : undefined}
+ tooltip={ocrTips}
>
;
+ toolPanelRef: React.RefObject;
+}
+
+export interface SidebarInfo {
+ rect: DOMRect | null;
+ isToolPanelActive: boolean;
+ sidebarState: SidebarState;
+}
+
+// Context-related interfaces
+export interface SidebarContextValue {
+ sidebarState: SidebarState;
+ sidebarRefs: SidebarRefs;
+ setSidebarsVisible: React.Dispatch>;
+ setLeftPanelView: React.Dispatch>;
+ setReaderMode: React.Dispatch>;
+}
+
+export interface SidebarProviderProps {
+ children: React.ReactNode;
+}
+
+// QuickAccessBar related interfaces
+export interface QuickAccessBarProps {
+ onToolsClick: () => void;
+ onReaderToggle: () => void;
+}
+
+export interface ButtonConfig {
+ id: string;
+ name: string;
+ icon: React.ReactNode;
+ tooltip: string;
+ isRound?: boolean;
+ size?: 'sm' | 'md' | 'lg' | 'xl';
+ onClick: () => void;
+ type?: 'navigation' | 'modal' | 'action';
+}
diff --git a/frontend/src/types/tips.ts b/frontend/src/types/tips.ts
new file mode 100644
index 000000000..58519e114
--- /dev/null
+++ b/frontend/src/types/tips.ts
@@ -0,0 +1,13 @@
+export interface TooltipContent {
+ header?: {
+ title: string;
+ logo?: string | React.ReactNode;
+ };
+ tips?: Array<{
+ title?: string;
+ description?: string;
+ bullets?: string[];
+ body?: React.ReactNode;
+ }>;
+ content?: React.ReactNode;
+}
\ No newline at end of file
diff --git a/frontend/src/utils/genericUtils.ts b/frontend/src/utils/genericUtils.ts
new file mode 100644
index 000000000..253346292
--- /dev/null
+++ b/frontend/src/utils/genericUtils.ts
@@ -0,0 +1,42 @@
+/**
+ * DOM utility functions for common operations
+ */
+
+/**
+ * Clamps a value between a minimum and maximum
+ * @param value - The value to clamp
+ * @param min - The minimum allowed value
+ * @param max - The maximum allowed value
+ * @returns The clamped value
+ */
+export function clamp(value: number, min: number, max: number): number {
+ return Math.min(Math.max(value, min), max);
+}
+
+/**
+ * Safely adds an event listener with proper cleanup
+ * @param target - The target element or window/document
+ * @param event - The event type
+ * @param handler - The event handler function
+ * @param options - Event listener options
+ * @returns A cleanup function to remove the listener
+ */
+export function addEventListenerWithCleanup(
+ target: EventTarget,
+ event: string,
+ handler: EventListener,
+ options?: boolean | AddEventListenerOptions
+): () => void {
+ target.addEventListener(event, handler, options);
+ return () => target.removeEventListener(event, handler, options);
+}
+
+/**
+ * Checks if a click event occurred outside of a specified element
+ * @param event - The click event
+ * @param element - The element to check against
+ * @returns True if the click was outside the element
+ */
+export function isClickOutside(event: MouseEvent, element: HTMLElement | null): boolean {
+ return element ? !element.contains(event.target as Node) : true;
+}
diff --git a/frontend/src/utils/sidebarUtils.ts b/frontend/src/utils/sidebarUtils.ts
new file mode 100644
index 000000000..cef144971
--- /dev/null
+++ b/frontend/src/utils/sidebarUtils.ts
@@ -0,0 +1,34 @@
+import { SidebarRefs, SidebarState, SidebarInfo } from '../types/sidebar';
+
+/**
+ * Gets the All tools sidebar information using React refs and state
+ * @param refs - Object containing refs to sidebar elements
+ * @param state - Current sidebar state
+ * @returns Object containing the sidebar rect and whether the tool panel is active
+ */
+export function getSidebarInfo(refs: SidebarRefs, state: SidebarState): SidebarInfo {
+ const { quickAccessRef, toolPanelRef } = refs;
+ const { sidebarsVisible, readerMode } = state;
+
+ // Determine if tool panel should be active based on state
+ const isToolPanelActive = sidebarsVisible && !readerMode;
+
+ let rect: DOMRect | null = null;
+
+ if (isToolPanelActive && toolPanelRef.current) {
+ // Tool panel is expanded: use its rect
+ rect = toolPanelRef.current.getBoundingClientRect();
+ } else if (quickAccessRef.current) {
+ // Fall back to quick access bar
+ // This probably isn't needed but if we ever have tooltips or modals that need to be positioned relative to the quick access bar, we can use this
+ rect = quickAccessRef.current.getBoundingClientRect();
+ }
+
+ return {
+ rect,
+ isToolPanelActive,
+ sidebarState: state
+ };
+}
+
+
\ No newline at end of file