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 }) => ( <>