Spaces:
Running
Running
feat(ai): design improvements for chat popup, speeddial menu, resizable answer card, and centered header disclaimer
728283f | 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; | |