import React, { useEffect, useRef } from 'react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faBars } from '@fortawesome/free-solid-svg-icons' import { RelayPool } from 'applesauce-relay' import { IEventStore } from 'applesauce-core' import { BookmarkList } from './BookmarkList' import ContentPanel from './ContentPanel' import { HighlightsPanel } from './HighlightsPanel' import Settings from './Settings' import Toast from './Toast' import { HighlightButton } from './HighlightButton' import { RelayStatusIndicator } from './RelayStatusIndicator' import { ViewMode } from './Bookmarks' import { Bookmark } from '../types/bookmarks' import { Highlight } from '../types/highlights' import { ReadableContent } from '../services/readerService' import { UserSettings } from '../services/settingsService' import { HighlightVisibility } from './HighlightsPanel' import { HighlightButtonRef } from './HighlightButton' import { BookmarkReference } from '../utils/contentLoader' import { useIsMobile } from '../hooks/useMediaQuery' interface ThreePaneLayoutProps { // Layout state isCollapsed: boolean isHighlightsCollapsed: boolean isSidebarOpen: boolean showSettings: boolean showExplore?: boolean // Bookmarks pane bookmarks: Bookmark[] bookmarksLoading: boolean viewMode: ViewMode isRefreshing: boolean lastFetchTime?: number | null onToggleSidebar: () => void onLogout: () => void onViewModeChange: (mode: ViewMode) => void onOpenSettings: () => void onRefresh: () => void relayPool: RelayPool | null eventStore: IEventStore | null // Content pane readerLoading: boolean readerContent?: ReadableContent selectedUrl?: string settings: UserSettings onSaveSettings: (settings: UserSettings) => Promise onCloseSettings: () => void classifiedHighlights: Highlight[] showHighlights: boolean selectedHighlightId?: string highlightVisibility: HighlightVisibility onHighlightClick: (id: string) => void onTextSelection: (text: string) => void onClearSelection: () => void currentUserPubkey?: string followedPubkeys: Set // Highlights pane highlights: Highlight[] highlightsLoading: boolean onToggleHighlightsPanel: () => void onSelectUrl: (url: string, bookmark?: BookmarkReference) => void onToggleHighlights: (show: boolean) => void onRefreshHighlights: () => void onHighlightVisibilityChange: (visibility: HighlightVisibility) => void // Highlight button highlightButtonRef: React.RefObject onCreateHighlight: (text: string) => void hasActiveAccount: boolean // Toast toastMessage?: string toastType?: 'success' | 'error' onClearToast: () => void // Optional Explore content explore?: React.ReactNode } const ThreePaneLayout: React.FC = (props) => { const isMobile = useIsMobile() const sidebarRef = useRef(null) // Lock body scroll when mobile sidebar is open useEffect(() => { if (isMobile && props.isSidebarOpen) { document.body.classList.add('mobile-sidebar-open') } else { document.body.classList.remove('mobile-sidebar-open') } return () => { document.body.classList.remove('mobile-sidebar-open') } }, [isMobile, props.isSidebarOpen]) // Handle ESC key to close sidebar useEffect(() => { if (!isMobile || !props.isSidebarOpen) return const handleEscape = (e: KeyboardEvent) => { if (e.key === 'Escape') { props.onToggleSidebar() } } document.addEventListener('keydown', handleEscape) return () => document.removeEventListener('keydown', handleEscape) }, [isMobile, props.isSidebarOpen, props.onToggleSidebar]) // Trap focus in sidebar when open on mobile useEffect(() => { if (!isMobile || !props.isSidebarOpen || !sidebarRef.current) return const sidebar = sidebarRef.current const focusableElements = sidebar.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ) const firstElement = focusableElements[0] const lastElement = focusableElements[focusableElements.length - 1] const handleTab = (e: KeyboardEvent) => { if (e.key !== 'Tab') return if (e.shiftKey) { if (document.activeElement === firstElement) { e.preventDefault() lastElement?.focus() } } else { if (document.activeElement === lastElement) { e.preventDefault() firstElement?.focus() } } } sidebar.addEventListener('keydown', handleTab) firstElement?.focus() return () => { sidebar.removeEventListener('keydown', handleTab) } }, [isMobile, props.isSidebarOpen]) const handleBackdropClick = () => { if (isMobile && props.isSidebarOpen) { props.onToggleSidebar() } } return ( <> {/* Mobile hamburger button */} {isMobile && !props.isSidebarOpen && ( )} {/* Mobile backdrop */} {isMobile && (