Add frontend component library and UI development tasks

- Create Tailwind CSS configuration with design tokens from UIDesignSpec
- Create globals.css with CSS variables and component styles
- Add React component library:
  - UI components: Button, Card, Tag, Input, Select, ProgressBar, Modal
  - Navigation: BottomNav, Sidebar, StatusBar
  - Layout: MobileLayout, DesktopLayout
- Add constants for colors, icons, and layout
- Update tasks.md with 31 UI development tasks linked to design node IDs
- Configure package.json, tsconfig.json, and postcss.config.js

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-02-03 17:44:22 +08:00
co-authored by Claude Opus 4.5
parent dd06502004
commit f166c04422
22 changed files with 2509 additions and 1 deletions
@@ -0,0 +1,81 @@
/**
* BottomNav 底部导航组件 (移动端)
* 设计稿参考: UIDesignSpec.md 3.6
*/
import React from 'react';
import { LucideIcon } from 'lucide-react';
export interface NavItem {
id: string;
label: string;
icon: LucideIcon;
href?: string;
badge?: number;
}
export interface BottomNavProps {
items: NavItem[];
activeId: string;
onItemClick?: (id: string) => void;
className?: string;
}
export const BottomNav: React.FC<BottomNavProps> = ({
items,
activeId,
onItemClick,
className = '',
}) => {
return (
<nav
className={`
fixed bottom-0 left-0 right-0 z-bottom-nav
flex justify-around items-center
h-bottom-nav px-[21px] py-3
safe-area-bottom
${className}
`}
style={{
background: 'linear-gradient(180deg, transparent 0%, #0B0B0E 50%)',
}}
>
{items.map((item) => {
const isActive = item.id === activeId;
const Icon = item.icon;
return (
<button
key={item.id}
onClick={() => onItemClick?.(item.id)}
className={`
flex flex-col items-center gap-1
transition-colors duration-200
${isActive ? 'text-accent-indigo' : 'text-text-secondary'}
`}
>
<div className="relative">
<Icon size={24} />
{item.badge !== undefined && item.badge > 0 && (
<span
className="
absolute -top-1 -right-1
min-w-[16px] h-4 px-1
flex items-center justify-center
bg-accent-coral text-white
text-[10px] font-semibold
rounded-full
"
>
{item.badge > 99 ? '99+' : item.badge}
</span>
)}
</div>
<span className="text-nav">{item.label}</span>
</button>
);
})}
</nav>
);
};
export default BottomNav;
+136
View File
@@ -0,0 +1,136 @@
/**
* Sidebar 侧边栏导航组件 (桌面端)
* 设计稿参考: UIDesignSpec.md 3.7
*/
import React from 'react';
import { LucideIcon } from 'lucide-react';
export interface SidebarItem {
id: string;
label: string;
icon: LucideIcon;
href?: string;
badge?: number;
children?: SidebarItem[];
}
export interface SidebarSection {
title?: string;
items: SidebarItem[];
}
export interface SidebarProps {
logo?: React.ReactNode;
sections: SidebarSection[];
activeId: string;
onItemClick?: (id: string) => void;
footer?: React.ReactNode;
className?: string;
}
export const Sidebar: React.FC<SidebarProps> = ({
logo,
sections,
activeId,
onItemClick,
footer,
className = '',
}) => {
return (
<aside
className={`
fixed left-0 top-0 bottom-0 z-sidebar
w-sidebar bg-bg-card
flex flex-col
border-r border-border-subtle
${className}
`}
>
{/* Logo */}
{logo && (
<div className="px-4 py-5 border-b border-border-subtle">
{logo}
</div>
)}
{/* Navigation */}
<nav className="flex-1 overflow-y-auto py-4 px-3">
{sections.map((section, sectionIndex) => (
<div key={sectionIndex} className="mb-6">
{section.title && (
<h4 className="px-3 mb-2 text-small text-text-tertiary uppercase tracking-wider">
{section.title}
</h4>
)}
<ul className="space-y-1">
{section.items.map((item) => (
<SidebarNavItem
key={item.id}
item={item}
isActive={item.id === activeId}
onClick={() => onItemClick?.(item.id)}
/>
))}
</ul>
</div>
))}
</nav>
{/* Footer */}
{footer && (
<div className="px-4 py-4 border-t border-border-subtle">
{footer}
</div>
)}
</aside>
);
};
interface SidebarNavItemProps {
item: SidebarItem;
isActive: boolean;
onClick: () => void;
}
const SidebarNavItem: React.FC<SidebarNavItemProps> = ({
item,
isActive,
onClick,
}) => {
const Icon = item.icon;
return (
<li>
<button
onClick={onClick}
className={`
w-full flex items-center gap-2.5
px-3 py-2.5 rounded-btn
transition-colors duration-200
${isActive
? 'bg-bg-elevated text-accent-indigo font-semibold'
: 'text-text-secondary hover:bg-bg-elevated'
}
`}
>
<Icon size={20} className="flex-shrink-0" />
<span className="flex-1 text-left text-body">{item.label}</span>
{item.badge !== undefined && item.badge > 0 && (
<span
className="
min-w-[20px] h-5 px-1.5
flex items-center justify-center
bg-accent-coral text-white
text-[11px] font-semibold
rounded-full
"
>
{item.badge > 99 ? '99+' : item.badge}
</span>
)}
</button>
</li>
);
};
export default Sidebar;
@@ -0,0 +1,41 @@
/**
* StatusBar 状态栏组件 (移动端)
* 设计稿参考: UIDesignSpec.md 3.1
*/
import React from 'react';
import { Signal, Wifi, BatteryFull } from 'lucide-react';
export interface StatusBarProps {
time?: string;
className?: string;
}
export const StatusBar: React.FC<StatusBarProps> = ({
time = '9:41',
className = '',
}) => {
return (
<div
className={`
flex items-center justify-between
h-status-bar px-6
bg-bg-page safe-area-top
${className}
`}
>
{/* Time */}
<span className="text-body font-semibold text-text-primary">
{time}
</span>
{/* Status Icons */}
<div className="flex items-center gap-1">
<Signal size={16} className="text-text-primary" />
<Wifi size={16} className="text-text-primary" />
<BatteryFull size={16} className="text-text-primary" />
</div>
</div>
);
};
export default StatusBar;