import React, { useState, useEffect } from 'react'; import { Card, Divider, Button, Switch, Collapse, Typewriter } from '../src'; import { useIsMobile } from './tools'; // ============================================ // Syntax highlighting // ============================================ const HL_TOKENS: { pattern: RegExp; style: React.CSSProperties }[] = [ { pattern: /(\/\/.*$|\/\*[\s\S]*?\*\/)/gm, style: { color: '#6b5e50', fontStyle: 'italic', fontWeight: 400 }, }, { pattern: /("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)/g, style: { color: '#a8d4a0' }, }, { pattern: /(<\/?[\w.]+|\/?>)/g, style: { color: '#f0a870' } }, { pattern: /\b(import|from|const|let|var|function|return|export|default|true|false|null|undefined)\b/g, style: { color: '#d4a0e0' }, }, { pattern: /\b(npm|yarn|pnpm)\b/g, style: { color: '#f0a870' } }, { pattern: /(install|uninstall|run|add|remove)\b/g, style: { color: '#a8d4a0' }, }, { pattern: /(\{|\})/g, style: { color: '#d4b896' } }, { pattern: /(=>)/g, style: { color: '#d4a0e0' } }, { pattern: /(--[\w-]+)(?=\s*:)/g, style: { color: '#e8c87a' } }, { pattern: /(:root)/g, style: { color: '#f0a870' } }, { pattern: /(#[0-9a-fA-F]{3,8})\b/g, style: { color: '#8ab8e0' } }, ]; const highlightCode = (code: string): React.ReactNode[] => { const parts: React.ReactNode[] = []; const lines = code.split('\n'); lines.forEach((line, li) => { type Seg = { start: number; end: number; style: React.CSSProperties }; const segs: Seg[] = []; for (const t of HL_TOKENS) { const re = new RegExp(t.pattern.source, t.pattern.flags); let m: RegExpExecArray | null; while ((m = re.exec(line)) !== null) { const s = m.index + (m[0] !== m[1] && m[1] ? m[0].indexOf(m[1]) : 0); const text = m[1] || m[0]; segs.push({ start: s, end: s + text.length, style: t.style }); } } segs.sort((a, b) => a.start - b.start); const merged: Seg[] = []; for (const seg of segs) { if ( merged.length === 0 || seg.start >= merged[merged.length - 1].end ) merged.push(seg); } let idx = 0; for (const seg of merged) { if (seg.start > idx) parts.push(line.slice(idx, seg.start)); parts.push( {line.slice(seg.start, seg.end)} ); idx = seg.end; } if (idx < line.length) parts.push(line.slice(idx)); if (li < lines.length - 1) parts.push('\n'); }); return parts; }; const CodeBlock: React.FC<{ code: string }> = ({ code }) => (
{highlightCode(code)}
); const FeatureCard: React.FC<{ feature: typeof features[0] }> = ({ feature }) => { const [hovered, setHovered] = useState(false); return ( setHovered(true)} onMouseLeave={() => setHovered(false)} > {feature.title}
{feature.title}
{feature.desc}
); }; // ============================================ // Styles // ============================================ const S = { page: { width: '100%', minHeight: '100vh', overflowY: 'auto', overflowX: 'hidden', } as React.CSSProperties, // Hero hero: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', padding: '60px 40px 40px', position: 'relative', } as React.CSSProperties, heroContent: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 150, alignItems: 'center', maxWidth: 880, width: '100%', } as React.CSSProperties, heroContentMobile: { display: 'grid', gridTemplateColumns: '1fr', gap: 32, alignItems: 'center', maxWidth: 880, width: '100%', } as React.CSSProperties, heroText: { textAlign: 'left' as const, } as React.CSSProperties, heroLogo: { fontSize: 72, lineHeight: 1, marginBottom: 16, display: 'flex', justifyContent: 'center', alignItems: 'center', } as React.CSSProperties, heroTitle: { fontFamily: "Nunito, 'Zen Maru Gothic', -apple-system, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif", fontSize: 55, fontWeight: 800, lineHeight: 1.1, color: '#FFF9E6', textShadow: '0px 4px 1px rgba(0, 0, 0, 0.4)', margin: '0 0 12px', } as React.CSSProperties, heroVersion: { display: 'inline-block', fontSize: 12, fontWeight: 600, padding: '2px 10px', borderRadius: 10, background: '#e6f9f6', color: '#19c8b9', marginLeft: 8, verticalAlign: 'middle', textShadow: 'none', } as React.CSSProperties, heroSubtitle: { fontSize: 17, color: '#7c5734', lineHeight: 1.7, margin: '0 0 28px', maxWidth: 520, } as React.CSSProperties, heroActions: { display: 'flex', gap: 16, alignItems: 'center', } as React.CSSProperties, // Sections section: { padding: '48px 40px', maxWidth: 960, margin: '0 auto', } as React.CSSProperties, sectionTitle: { fontFamily: "Nunito, 'Zen Maru Gothic', -apple-system, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif", fontSize: 24, fontWeight: 700, color: '#725d42', margin: '0 0 8px', textAlign: 'center' as const, } as React.CSSProperties, sectionDesc: { fontSize: 14, color: '#7c5734', textAlign: 'center' as const, marginBottom: 32, } as React.CSSProperties, // Features features: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: 16, } as React.CSSProperties, featureCard: { padding: '24px 20px', textAlign: 'center' as const, } as React.CSSProperties, featureIcon: { fontSize: 32, marginBottom: 12 } as React.CSSProperties, featureTitle: { fontSize: 15, fontWeight: 700, color: '#725d42', marginBottom: 6, } as React.CSSProperties, featureDesc: { fontSize: 13, color: '#7c5734', lineHeight: 1.6, display: '-webkit-box' as const, WebkitLineClamp: 3, WebkitBoxOrient: 'vertical' as const, overflow: 'hidden', textOverflow: 'ellipsis' as const, } as React.CSSProperties, // Component grid compGrid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: 12, } as React.CSSProperties, compCard: { padding: '16px 20px', cursor: 'pointer', } as React.CSSProperties, compName: { fontSize: 15, fontWeight: 700, color: '#725d42', marginBottom: 4, } as React.CSSProperties, compDesc: { fontSize: 12, color: '#7c5734', lineHeight: 1.5, } as React.CSSProperties, // Code block codeBox: { maxWidth: 600, margin: '0 auto', padding: '20px 28px', background: '#2b2118', border: '1px solid #3d3028', borderRadius: 20, fontFamily: "'SF Mono', 'Fira Code', Consolas, monospace", fontSize: 13, fontWeight: 600, color: '#e8d5bc', textAlign: 'left' as const, lineHeight: 1.8, whiteSpace: 'pre' as const, overflow: 'auto' as const, tabSize: 4, } as React.CSSProperties, // Footer footer: { padding: '32px 40px', textAlign: 'center' as const, fontSize: 12, color: '#7c5734', marginTop: 32, } as React.CSSProperties, footerLinks: { display: 'flex', justifyContent: 'center', gap: 20, marginBottom: 12, } as React.CSSProperties, footerLink: { fontSize: 13, color: '#7c5734', cursor: 'pointer', } as React.CSSProperties, }; // ============================================ // Data // ============================================ const features = [ { icon: 'nook1.svg', title: 'Animal风格', desc: 'SVG 有机形状裁切,3D 按压按钮,温暖质朴的自然 UI 质感', }, { icon: 'Property-Shopping.svg', title: '18 个组件', desc: 'Button / Input / Switch / Modal / Typewriter / Card / Collapse / Cursor / Divider / Time / Phone / Footer / Icon / Checkbox / Select / Tabs / CodeBlock / Loading 等', }, { icon: 'Property-Camera.svg', title: '主题定制', desc: '40+ CSS 自定义属性,运行时换肤无需重新构建', }, { icon: 'Property-Recipes.svg', title: '开箱即用', desc: 'ESM + CJS 双格式输出,TypeScript 类型声明完整', }, ]; const components = [ { key: 'button', name: 'Button', desc: '5 种类型、3 种尺寸、加载/危险/幽灵模式', }, { key: 'input', name: 'Input', desc: '前后缀、一键清空、校验状态' }, { key: 'switch', name: 'Switch', desc: '受控/非受控、自定义文案、加载状态', }, { key: 'checkbox', name: 'Checkbox', desc: '多选框组件,支持水平/垂直排列' }, { key: 'select', name: 'Select', desc: '下拉选择器,支持搜索和禁用', }, { key: 'tabs', name: 'Tabs', desc: '标签页组件,支持受控/非受控模式' }, { key: 'modal', name: 'Modal', desc: 'SVG 有机形状弹窗、ESC 关闭' }, { key: 'typewriter', name: 'Typewriter', desc: '逐字打字机效果,支持多行与富内容', }, { key: 'card', name: 'Card', desc: '默认/标题两种卡片风格' }, { key: 'collapse', name: 'Collapse', desc: 'FAQ 折叠面板、平滑展开动画' }, { key: 'cursor', name: 'Cursor', desc: '自定义手指光标,支持多种尺寸' }, { key: 'divider-comp', name: 'Divider', desc: '装饰性水平分割线' }, { key: 'icon', name: 'Icon', desc: 'SVG 图标库' }, { key: 'footer', name: 'Footer', desc: '页脚组件' }, { key: 'time', name: 'Time', desc: '可爱风格时间显示' }, { key: 'phone', name: 'Phone', desc: 'Phone 模拟器' }, { key: 'codeblock', name: 'CodeBlock', desc: '代码语法高亮组件' }, { key: 'loading', name: 'Loading', desc: '动森风格小岛加载动画' }, ]; // ============================================ // HomePage // ============================================ interface HomePageProps { onNavigate?: (path: string) => void; } const HomePage: React.FC = ({ onNavigate }) => { const isMobile = useIsMobile(); const [showScrollHint, setShowScrollHint] = useState(true); const pageRef = React.useRef(null); const handleScroll = () => { if (pageRef.current) { if (pageRef.current.scrollTop > 70) { setShowScrollHint(false); } else { setShowScrollHint(true); } } }; return (
{/* Hero */}
{isMobile && (
logo
)}

{isMobile ? 'Animal Island UI' : <>Animal
Island UI} v0.8.1

Animal风格的 React 组件库,基于 TypeScript + Vite 构建,让 Web 应用充满温暖质感

{!isMobile && (
logo
)}
向下滑动
{/* Features */}
特性
为什么选择 animal-island-ui
{features.map((f) => ( ))}
{/* Components */}
组件一览
点击卡片查看详细文档和在线演示
{components.map((c) => ( onNavigate?.(`/${c.key}`)} >
{c.name}
{c.desc}
))}
{/* Install */}
安装
一行命令即可安装
{/* Quick Start */}
快速上手
引入组件即可使用,样式自动加载
开始;\n}`} />
{/* Theme */}
主题定制
通过覆盖 CSS 自定义属性实现运行时换肤,无需重新构建
{/* Footer */}
onNavigate?.('/button')} > 组件文档 window.open( 'https://github.com/guokaigdg/animal-island-ui', '_blank' ) } > GitHub
MIT License · React + TypeScript + Vite
); } export default HomePage;