import React, { useState, useEffect, useCallback, Suspense, lazy } from 'react'; import { Cursor, Loading } from '../src'; import '../src/styles/index.less'; import '@fontsource/nunito/latin-500.css'; import '@fontsource/nunito/latin-700.css'; import '@fontsource/nunito/latin-900.css'; import '@fontsource/noto-sans-sc/latin-400.css'; import '@fontsource/noto-sans-sc/latin-500.css'; import '@fontsource/noto-sans-sc/latin-700.css'; import '@fontsource/noto-sans-sc/chinese-simplified-400.css'; import '@fontsource/noto-sans-sc/chinese-simplified-500.css'; import '@fontsource/noto-sans-sc/chinese-simplified-700.css'; import '@fontsource/zen-maru-gothic/latin-500.css'; import '@fontsource/zen-maru-gothic/latin-700.css'; import '@fontsource/zen-maru-gothic/latin-900.css'; import '@fontsource/zen-maru-gothic/japanese-500.css'; import '@fontsource/zen-maru-gothic/japanese-700.css'; import '@fontsource/zen-maru-gothic/japanese-900.css'; import HomePage from './HomePage'; import { PAGE_INFO } from './pageInfo'; import { useIsMobile } from './tools'; // Lazy-load ComponentPage so homepage does not pull in every demo on initial load const ComponentPage = lazy(() => import('./ComponentPage')); // ============================================ // Simple hash router // ============================================ const useHash = () => { const [hash, setHash] = useState( () => window.location.hash.slice(1) || '/' ); useEffect(() => { const onHashChange = () => setHash(window.location.hash.slice(1) || '/'); window.addEventListener('hashchange', onHashChange); return () => window.removeEventListener('hashchange', onHashChange); }, []); const navigate = useCallback((path: string) => { window.location.hash = path; }, []); return { hash, navigate }; }; interface MenuItemChild { key: string; label: string; } interface MenuItem { key: string; label: string; children?: MenuItemChild[]; } // ============================================ // Menu config // ============================================ const MENU_ITEMS: MenuItem[] = [ { key: 'cat-basic', label: '── 基础组件 ──', children: [ { key: 'button', label: 'Button 按钮' }, { key: 'input', label: 'Input 输入框' }, { key: 'switch', label: 'Switch 开关' }, { key: 'card', label: 'Card 卡片' }, { key: 'collapse', label: 'Collapse 折叠面板' }, { key: 'cursor', label: 'Cursor 光标' }, { key: 'modal', label: 'Modal 弹窗' }, { key: 'typewriter', label: 'Typewriter 打字机' }, { key: 'divider-comp', label: 'Divider 分割线' }, { key: 'icon', label: 'Icon 图标' }, { key: 'select', label: 'Select 选择器' }, { key: 'checkbox', label: 'Checkbox 多选框' }, { key: 'tabs', label: 'Tabs 标签页' }, { key: 'footer', label: 'Footer 页脚' }, { key: 'codeblock', label: 'CodeBlock 代码高亮' }, { key: 'loading', label: 'Loading 加载' }, { key: 'table', label: 'Table 表格' }, ], }, { key: 'cat-complex', label: '── 复杂组件 ──', children: [ { key: 'time', label: 'Time 时间' }, { key: 'phone', label: 'Phone 手机' }, ], }, ]; // ============================================ // Shared styles // ============================================ const S = { layout: { display: 'flex', height: '100dvh', overflow: 'hidden', fontFamily: "Nunito, 'Noto Sans SC', 'Zen Maru Gothic', -apple-system, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif", background: `url(${new URL('./img/content_bg_pc.jpg', import.meta.url).href}) center / auto repeat`, } as React.CSSProperties, sidebar: { width: 220, minWidth: 220, background: `url(${new URL('./img/menu_bg.svg', import.meta.url).href}) center/cover no-repeat`, display: 'flex', flexDirection: 'column', overflow: 'hidden', } as React.CSSProperties, homeBg: { background: `url(${new URL('./img/home_bg.webp', import.meta.url).href}) 0 0 / auto repeat, #7DC395`, animation: 'bgScroll 80s linear infinite', } as React.CSSProperties, sidebarHeader: { padding: '20px 16px 12px', borderBottom: '1px solid #e8e2d6', fontWeight: 700, fontSize: 15, color: '#725d42', letterSpacing: -0.3, display: 'flex', alignItems: 'center', } as React.CSSProperties, menuList: { flex: 1, overflow: 'auto', padding: '8px 0', } as React.CSSProperties, menuItem: (active: boolean) => ({ display: 'flex', alignItems: 'center', margin: '1px 5px', height: 40, padding: '0 16px', fontFamily: "Nunito, 'Noto Sans SC', 'Zen Maru Gothic', -apple-system, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif", fontStyle: 'normal', fontWeight: 600, fontSize: 14, paddingLeft: 26, color: active ? '#fff' : '#8a7b66', background: active ? '#B7C6E5' : 'transparent', borderRadius: 12, borderRight: 'none', transition: 'all 0.15s', }) as React.CSSProperties, main: { flex: 1, overflow: 'auto', padding: '32px 40px', } as React.CSSProperties, }; // ============================================ // Sidebar content (shared between desktop & mobile drawer) // ============================================ const SidebarContent: React.FC<{ activeKey: string; onNavigate: (path: string) => void; }> = ({ activeKey, onNavigate }) => ( <>
onNavigate('/')}> nook 集合啦!Animal
); // ============================================ // App // ============================================ const App: React.FC = () => { const { hash, navigate } = useHash(); const isMobile = useIsMobile(); const [drawerOpen, setDrawerOpen] = useState(false); const [loadingActive, setLoadingActive] = useState(false); const [loadingMounted, setLoadingMounted] = useState(false); const mainRef = React.useRef(null); const activeKey = hash.startsWith('/') && hash.length > 1 ? hash.slice(1) : 'home'; const isHomePage = activeKey === 'home'; // Close drawer when switching to desktop useEffect(() => { if (!isMobile) setDrawerOpen(false); }, [isMobile]); // Close drawer when route changes + scroll main to top useEffect(() => { setDrawerOpen(false); mainRef.current?.scrollTo({ top: 0 }); }, [activeKey]); const handleNavigate = useCallback( (path: string) => { navigate(path); setDrawerOpen(false); }, [navigate] ); // 首页跳转到组件页时显示 2s Loading 覆盖层 const handleHomeNavigate = useCallback( (path: string) => { setLoadingMounted(true); setLoadingActive(true); navigate(path); // 2s 后开始关闭,再多留 1.5s 给关闭扩散动画后卸载 window.setTimeout(() => setLoadingActive(false), 2000); window.setTimeout(() => setLoadingMounted(false), 3500); }, [navigate] ); return ( {isHomePage ? ( /* Home page — full screen, no sidebar */
) : ( /* Component page — with sidebar */
{/* Desktop sidebar */} {!isMobile && ( )} {/* Mobile top bar */} {isMobile && (
{PAGE_INFO[activeKey]?.title ?? '组件文档'}
)} {/* Mobile drawer overlay */} {isMobile && drawerOpen && ( <>
setDrawerOpen(false)} /> )}
{!isMobile && ( )}
)} {/* 首页跳转组件页的过场 Loading,全屏覆盖 */} {loadingMounted && (
)} ); }; export default App;