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

113 lines
3.0 KiB
TypeScript

/**
* Input 输入框组件
* 设计稿参考: UIDesignSpec.md
*/
import React, { forwardRef } from 'react';
import { LucideIcon, Search, Eye, EyeOff } from 'lucide-react';
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string;
error?: string;
hint?: string;
leftIcon?: LucideIcon;
rightIcon?: LucideIcon;
onRightIconClick?: () => void;
fullWidth?: boolean;
}
export const Input = forwardRef<HTMLInputElement, InputProps>(({
label,
error,
hint,
leftIcon: LeftIcon,
rightIcon: RightIcon,
onRightIconClick,
fullWidth = true,
className = '',
disabled,
...props
}, ref) => {
return (
<div className={`${fullWidth ? 'w-full' : ''}`}>
{label && (
<label className="block mb-1.5 text-caption text-text-secondary">
{label}
</label>
)}
<div className="relative">
{LeftIcon && (
<LeftIcon
size={18}
className="absolute left-3 top-1/2 -translate-y-1/2 text-text-tertiary"
/>
)}
<input
ref={ref}
className={`
w-full bg-bg-elevated text-text-primary
border border-border-subtle rounded-btn
px-4 py-2.5 text-body
transition-colors duration-200
placeholder:text-text-tertiary
focus:outline-none focus:border-accent-indigo
disabled:opacity-50 disabled:cursor-not-allowed
${LeftIcon ? 'pl-10' : ''}
${RightIcon ? 'pr-10' : ''}
${error ? 'border-accent-coral focus:border-accent-coral' : ''}
${className}
`}
disabled={disabled}
{...props}
/>
{RightIcon && (
<button
type="button"
onClick={onRightIconClick}
className="absolute right-3 top-1/2 -translate-y-1/2 text-text-tertiary hover:text-text-secondary"
>
<RightIcon size={18} />
</button>
)}
</div>
{error && (
<p className="mt-1 text-small text-accent-coral">{error}</p>
)}
{hint && !error && (
<p className="mt-1 text-small text-text-tertiary">{hint}</p>
)}
</div>
);
});
Input.displayName = 'Input';
// 搜索输入框
export const SearchInput = forwardRef<HTMLInputElement, Omit<InputProps, 'leftIcon'>>(
(props, ref) => (
<Input ref={ref} leftIcon={Search} placeholder="搜索..." {...props} />
)
);
SearchInput.displayName = 'SearchInput';
// 密码输入框
export const PasswordInput = forwardRef<HTMLInputElement, Omit<InputProps, 'type' | 'rightIcon'>>(
(props, ref) => {
const [showPassword, setShowPassword] = React.useState(false);
return (
<Input
ref={ref}
type={showPassword ? 'text' : 'password'}
rightIcon={showPassword ? EyeOff : Eye}
onRightIconClick={() => setShowPassword(!showPassword)}
{...props}
/>
);
}
);
PasswordInput.displayName = 'PasswordInput';
export default Input;