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:
co-authored by
Claude Opus 4.5
parent
dd06502004
commit
f166c04422
@@ -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;
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user