Spaces:
Running
Running
File size: 4,106 Bytes
d2c65ec 8052574 592d76b 8052574 728283f 165565d 8052574 d2c65ec 9d341c1 8052574 d2c65ec 8052574 728283f 8052574 9d341c1 8052574 d2c65ec 8052574 d2c65ec 8052574 60c1bf8 8052574 165565d 8052574 592d76b 728283f 8052574 165565d 8052574 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | import React, { useEffect } from 'react';
import NavDrawer from './NavDrawer';
import ReaderPanel from './ReaderPanel';
import RightToolbar from '../toolbar/RightToolbar';
import MobileBottomBar from './MobileBottomBar';
import AIPopup from '../ai/AIPopup';
import AIFloatingMenu from '../ai/AIFloatingMenu';
import ErrorBoundary from '../common/ErrorBoundary';
import { Menu } from 'lucide-react';
import { useUIStore, useThemeStore } from '../../stores/appStore';
import { useAIStore } from '../../stores/aiStore';
import { AnimatePresence, motion } from 'framer-motion';
import { useKeyboardNav } from '../../hooks/useKeyboardNav';
// Shell = NavDrawer bg per theme β seamless sidebar integration
const SHELL_BG: Record<string, string> = {
dark: 'theme-dark-bg',
light: 'theme-light-bg',
classic: 'theme-classic-bg',
};
// Menu button bg per theme (matches RightToolbar bg for visual consistency)
const MENU_BTN: Record<string, string> = {
dark: 'bg-[#222242] hover:bg-[#2a2a4e]',
light: 'bg-[#e8e0c8] hover:bg-[#ddd5bd]',
classic: 'bg-[#e0d5b8] hover:bg-[#d5caad]',
};
const AppShell: React.FC = () => {
const { toggleNav, isNavOpen } = useUIStore();
const { isOpen } = useAIStore();
const { theme } = useThemeStore();
// Keyboard shortcut: β β PgUp PgDown Space for page navigation
useKeyboardNav();
const shellBg = SHELL_BG[theme] ?? SHELL_BG.dark;
const menuBtn = MENU_BTN[theme] ?? MENU_BTN.dark;
// ββ Scroll Progress ββββββββββββββββββββββββββββββββββββββββ
const [scrollProgress, setScrollProgress] = React.useState(0);
useEffect(() => {
const el = document.getElementById('reader-scroll-container');
if (!el) return;
const handleScroll = () => {
const total = el.scrollHeight - el.clientHeight;
if (total <= 0) { setScrollProgress(0); return; }
setScrollProgress((el.scrollTop / total) * 100);
};
el.addEventListener('scroll', handleScroll);
return () => el.removeEventListener('scroll', handleScroll);
}, []);
return (
<div className={`flex h-screen overflow-hidden ${shellBg}`}>
{/* Left: NavDrawer */}
<NavDrawer />
{/* Centre: Reader β right-padded to clear RightToolbar (44px) on desktop */}
<div className="flex-1 flex flex-col relative overflow-hidden lg:pr-11">
{/* Scroll Progress Bar */}
<div className="absolute top-0 left-0 right-0 h-0.5 z-40 bg-white/5">
<motion.div
className="h-full bg-[#c8860a]"
initial={{ width: 0 }}
animate={{ width: `${scrollProgress}%` }}
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
/>
</div>
{/* Menu button β visible only when drawer is closed */}
{!isNavOpen && (
<button
onClick={toggleNav}
style={{ top: 'calc(env(safe-area-inset-top) + 12px)' }}
className={`fixed left-4 z-30 w-10 h-10 flex items-center justify-center rounded-full ${menuBtn} text-[#c8860a] transition-colors`}
title="ΰΉΰΈΰΈ΄ΰΈΰΉΰΈ‘ΰΈΰΈΉ"
>
<Menu size={20} />
</button>
)}
{/* Scrollable reader β id is referenced by RightToolbar scroll-to-top */}
<div id="reader-scroll-container" className="flex-1 overflow-y-auto scroll-smooth">
<ErrorBoundary componentName="ReaderPanel">
<ReaderPanel />
</ErrorBoundary>
</div>
</div>
{/* Right: Reading controls toolbar */}
<RightToolbar />
{/* Mobile: bottom navigation bar (hidden on desktop) */}
<MobileBottomBar />
{/* AI Floating Menu */}
<ErrorBoundary componentName="AIFloatingMenu">
<AIFloatingMenu />
</ErrorBoundary>
{/* AI Popup */}
<AnimatePresence>
{isOpen && (
<ErrorBoundary componentName="AIPopup">
<AIPopup />
</ErrorBoundary>
)}
</AnimatePresence>
</div>
);
};
export default AppShell;
|