first commit
This commit is contained in:
77
frontend/src/components/ui/Button.tsx
Normal file
77
frontend/src/components/ui/Button.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
'use client';
|
||||
|
||||
import { ButtonHTMLAttributes, forwardRef } from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant = 'primary', size = 'md', isLoading, children, disabled, ...props }, ref) => {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
disabled={disabled || isLoading}
|
||||
className={clsx(
|
||||
'inline-flex items-center justify-center font-medium transition-all duration-200 rounded-btn focus:outline-none focus:ring-2 focus:ring-offset-2',
|
||||
{
|
||||
// Variants
|
||||
'bg-primary-yellow text-primary-dark hover:bg-yellow-400 focus:ring-yellow-400':
|
||||
variant === 'primary',
|
||||
'bg-primary-dark text-white hover:bg-gray-800 focus:ring-gray-800':
|
||||
variant === 'secondary',
|
||||
'border-2 border-primary-dark text-primary-dark bg-transparent hover:bg-gray-50 focus:ring-gray-400':
|
||||
variant === 'outline',
|
||||
'text-primary-dark hover:bg-gray-100 focus:ring-gray-300':
|
||||
variant === 'ghost',
|
||||
'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500':
|
||||
variant === 'danger',
|
||||
// Sizes
|
||||
'text-sm px-3 py-1.5': size === 'sm',
|
||||
'text-base px-5 py-2.5': size === 'md',
|
||||
'text-lg px-7 py-3': size === 'lg',
|
||||
// States
|
||||
'opacity-50 cursor-not-allowed': disabled || isLoading,
|
||||
},
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<svg
|
||||
className="animate-spin -ml-1 mr-2 h-4 w-4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
Loading...
|
||||
</>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Button.displayName = 'Button';
|
||||
|
||||
export default Button;
|
||||
35
frontend/src/components/ui/Card.tsx
Normal file
35
frontend/src/components/ui/Card.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
'use client';
|
||||
|
||||
import { HTMLAttributes, forwardRef } from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
interface CardProps extends HTMLAttributes<HTMLDivElement> {
|
||||
variant?: 'default' | 'elevated' | 'bordered';
|
||||
}
|
||||
|
||||
const Card = forwardRef<HTMLDivElement, CardProps>(
|
||||
({ className, variant = 'default', children, ...props }, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
'bg-white rounded-card overflow-hidden',
|
||||
{
|
||||
'shadow-card': variant === 'default',
|
||||
'shadow-card-hover hover:shadow-lg transition-shadow duration-200':
|
||||
variant === 'elevated',
|
||||
'border border-secondary-light-gray': variant === 'bordered',
|
||||
},
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Card.displayName = 'Card';
|
||||
|
||||
export default Card;
|
||||
48
frontend/src/components/ui/Input.tsx
Normal file
48
frontend/src/components/ui/Input.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
'use client';
|
||||
|
||||
import { InputHTMLAttributes, forwardRef } from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||
label?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, label, error, id, ...props }, ref) => {
|
||||
return (
|
||||
<div className="w-full">
|
||||
{label && (
|
||||
<label
|
||||
htmlFor={id}
|
||||
className="block text-sm font-medium text-primary-dark mb-1.5"
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<input
|
||||
ref={ref}
|
||||
id={id}
|
||||
className={clsx(
|
||||
'w-full px-4 py-3 rounded-btn border transition-colors duration-200',
|
||||
'focus:outline-none focus:ring-2 focus:ring-primary-yellow focus:border-transparent',
|
||||
'placeholder:text-gray-400',
|
||||
{
|
||||
'border-secondary-light-gray': !error,
|
||||
'border-red-500 focus:ring-red-500': error,
|
||||
},
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
{error && (
|
||||
<p className="mt-1.5 text-sm text-red-600">{error}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Input.displayName = 'Input';
|
||||
|
||||
export default Input;
|
||||
Reference in New Issue
Block a user