- 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>
130 lines
3.3 KiB
TypeScript
130 lines
3.3 KiB
TypeScript
/**
|
|
* ProgressBar 进度条组件
|
|
* 用于审核进度展示
|
|
*/
|
|
import React from 'react';
|
|
|
|
export interface ProgressBarProps {
|
|
value: number; // 0-100
|
|
max?: number;
|
|
showLabel?: boolean;
|
|
size?: 'sm' | 'md' | 'lg';
|
|
variant?: 'default' | 'success' | 'warning' | 'error';
|
|
className?: string;
|
|
}
|
|
|
|
const sizeStyles = {
|
|
sm: 'h-1',
|
|
md: 'h-2',
|
|
lg: 'h-3',
|
|
};
|
|
|
|
const variantStyles = {
|
|
default: 'bg-accent-indigo',
|
|
success: 'bg-accent-green',
|
|
warning: 'bg-accent-amber',
|
|
error: 'bg-accent-coral',
|
|
};
|
|
|
|
export const ProgressBar: React.FC<ProgressBarProps> = ({
|
|
value,
|
|
max = 100,
|
|
showLabel = false,
|
|
size = 'md',
|
|
variant = 'default',
|
|
className = '',
|
|
}) => {
|
|
const percentage = Math.min(100, Math.max(0, (value / max) * 100));
|
|
|
|
return (
|
|
<div className={`w-full ${className}`}>
|
|
{showLabel && (
|
|
<div className="flex justify-between mb-1">
|
|
<span className="text-small text-text-secondary">进度</span>
|
|
<span className="text-small text-text-primary">{Math.round(percentage)}%</span>
|
|
</div>
|
|
)}
|
|
<div className={`w-full bg-bg-elevated rounded-full overflow-hidden ${sizeStyles[size]}`}>
|
|
<div
|
|
className={`h-full rounded-full transition-all duration-300 ${variantStyles[variant]}`}
|
|
style={{ width: `${percentage}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// 环形进度条 (用于审核中状态)
|
|
export interface CircularProgressProps {
|
|
value: number; // 0-100
|
|
size?: number;
|
|
strokeWidth?: number;
|
|
variant?: 'default' | 'success' | 'warning' | 'error';
|
|
showLabel?: boolean;
|
|
label?: string;
|
|
className?: string;
|
|
}
|
|
|
|
const circularVariantColors = {
|
|
default: '#6366F1',
|
|
success: '#32D583',
|
|
warning: '#F59E0B',
|
|
error: '#E85A4F',
|
|
};
|
|
|
|
export const CircularProgress: React.FC<CircularProgressProps> = ({
|
|
value,
|
|
size = 120,
|
|
strokeWidth = 8,
|
|
variant = 'default',
|
|
showLabel = true,
|
|
label,
|
|
className = '',
|
|
}) => {
|
|
const percentage = Math.min(100, Math.max(0, value));
|
|
const radius = (size - strokeWidth) / 2;
|
|
const circumference = radius * 2 * Math.PI;
|
|
const offset = circumference - (percentage / 100) * circumference;
|
|
|
|
return (
|
|
<div className={`relative inline-flex items-center justify-center ${className}`}>
|
|
<svg width={size} height={size} className="-rotate-90">
|
|
{/* Background circle */}
|
|
<circle
|
|
cx={size / 2}
|
|
cy={size / 2}
|
|
r={radius}
|
|
fill="none"
|
|
stroke="#27272A"
|
|
strokeWidth={strokeWidth}
|
|
/>
|
|
{/* Progress circle */}
|
|
<circle
|
|
cx={size / 2}
|
|
cy={size / 2}
|
|
r={radius}
|
|
fill="none"
|
|
stroke={circularVariantColors[variant]}
|
|
strokeWidth={strokeWidth}
|
|
strokeLinecap="round"
|
|
strokeDasharray={circumference}
|
|
strokeDashoffset={offset}
|
|
className="transition-all duration-500"
|
|
/>
|
|
</svg>
|
|
{showLabel && (
|
|
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
|
<span className="text-card-title text-text-primary">
|
|
{Math.round(percentage)}%
|
|
</span>
|
|
{label && (
|
|
<span className="text-small text-text-tertiary mt-1">{label}</span>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ProgressBar;
|