50 lines
1.5 KiB
TypeScript
50 lines
1.5 KiB
TypeScript
// components/ui/input.tsx
|
|
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
label?: string;
|
|
error?: string;
|
|
}
|
|
|
|
export function Input({ label, error, ...props }: InputProps) {
|
|
return (
|
|
<div className="space-y-2">
|
|
{label && (
|
|
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
|
{label}
|
|
</label>
|
|
)}
|
|
<input
|
|
{...props}
|
|
className={`w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500
|
|
dark:bg-gray-800 dark:border-gray-700 dark:text-white ${
|
|
error ? "border-red-500" : "border-gray-300"
|
|
}`}
|
|
/>
|
|
{error && <p className="text-sm text-red-500">{error}</p>}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// components/ui/button.tsx
|
|
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
isLoading?: boolean;
|
|
}
|
|
|
|
export function Button({ children, isLoading, ...props }: ButtonProps) {
|
|
return (
|
|
<button
|
|
{...props}
|
|
className={`w-full px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg
|
|
hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
|
|
disabled:opacity-50 disabled:cursor-not-allowed ${props.className || ""}`}
|
|
>
|
|
{isLoading ? (
|
|
<div className="flex items-center justify-center">
|
|
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
|
</div>
|
|
) : (
|
|
children
|
|
)}
|
|
</button>
|
|
);
|
|
}
|