dhammawatthumpra's picture
feat(ai): design improvements for chat popup, speeddial menu, resizable answer card, and centered header disclaimer
728283f
Raw
History Blame Contribute Delete
4.11 kB
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;