Your Name f166c04422 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>
2026-02-03 17:44:22 +08:00

137 lines
3.1 KiB
TypeScript

/**
* 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;