From 9b3659504e13f08f3e9eb2c27342d32b9c781080 Mon Sep 17 00:00:00 2001 From: Erik Silva Date: Mon, 1 Dec 2025 01:45:24 -0300 Subject: [PATCH] feat(components): Create UI components - Button, Input, Card, Checkbox --- frontend-next/components/Button.tsx | 107 ++++++++++++++++++++++++++ frontend-next/components/Card.tsx | 100 ++++++++++++++++++++++++ frontend-next/components/Checkbox.tsx | 48 ++++++++++++ frontend-next/components/Input.tsx | 82 ++++++++++++++++++++ frontend-next/components/index.ts | 14 ++++ 5 files changed, 351 insertions(+) create mode 100644 frontend-next/components/Button.tsx create mode 100644 frontend-next/components/Card.tsx create mode 100644 frontend-next/components/Checkbox.tsx create mode 100644 frontend-next/components/Input.tsx create mode 100644 frontend-next/components/index.ts diff --git a/frontend-next/components/Button.tsx b/frontend-next/components/Button.tsx new file mode 100644 index 0000000..2066fa8 --- /dev/null +++ b/frontend-next/components/Button.tsx @@ -0,0 +1,107 @@ +/** + * 🔘 Button Component + * Componente de botão reutilizável com múltiplas variações + */ + +import React from 'react'; + +interface ButtonProps extends React.ButtonHTMLAttributes { + variant?: 'primary' | 'secondary' | 'ghost' | 'danger'; + size?: 'sm' | 'md' | 'lg'; + isLoading?: boolean; + fullWidth?: boolean; + children: React.ReactNode; +} + +const Button = React.forwardRef( + ( + { + variant = 'primary', + size = 'md', + isLoading = false, + fullWidth = false, + disabled, + className = '', + children, + ...props + }, + ref, + ) => { + // Estilos base + const baseStyles = + 'font-semibold rounded-lg transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2'; + + // Estilos por tamanho + const sizeStyles = { + sm: 'px-3 py-1.5 text-sm', + md: 'px-4 py-2 text-base', + lg: 'px-6 py-3 text-lg', + }; + + // Estilos por variante + const variantStyles = { + primary: + 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500 disabled:bg-blue-400', + secondary: + 'bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-400 disabled:bg-gray-100', + ghost: + 'bg-transparent text-gray-700 hover:bg-gray-100 focus:ring-gray-400 disabled:text-gray-400', + danger: + 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500 disabled:bg-red-400', + }; + + // Largura total + const widthStyle = fullWidth ? 'w-full' : ''; + + // Estado desabilitado + const isDisabled = disabled || isLoading; + + return ( + + ); + }, +); + +Button.displayName = 'Button'; + +export default Button; diff --git a/frontend-next/components/Card.tsx b/frontend-next/components/Card.tsx new file mode 100644 index 0000000..ef80354 --- /dev/null +++ b/frontend-next/components/Card.tsx @@ -0,0 +1,100 @@ +/** + * 🎯 Card Component + * Componente de card/painel reutilizável + */ + +import React from 'react'; + +interface CardProps { + children: React.ReactNode; + className?: string; + variant?: 'default' | 'outlined'; + clickable?: boolean; + onClick?: () => void; +} + +export const Card: React.FC = ({ + children, + className = '', + variant = 'default', + clickable = false, + onClick, +}) => { + const variantStyles = { + default: 'bg-white border border-gray-200 shadow-sm', + outlined: 'bg-transparent border border-gray-200', + }; + + return ( +
+ {children} +
+ ); +}; + +interface CardHeaderProps { + children: React.ReactNode; + className?: string; +} + +export const CardHeader: React.FC = ({ + children, + className = '', +}) => ( +
+ {children} +
+); + +interface CardTitleProps { + children: React.ReactNode; + className?: string; + as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; +} + +export const CardTitle: React.FC = ({ + children, + className = '', + as: Component = 'h2', +}) => ( + + {children} + +); + +interface CardContentProps { + children: React.ReactNode; + className?: string; +} + +export const CardContent: React.FC = ({ + children, + className = '', +}) =>
{children}
; + +interface CardFooterProps { + children: React.ReactNode; + className?: string; +} + +export const CardFooter: React.FC = ({ + children, + className = '', +}) => ( +
+ {children} +
+); + +export default Card; diff --git a/frontend-next/components/Checkbox.tsx b/frontend-next/components/Checkbox.tsx new file mode 100644 index 0000000..33b89d4 --- /dev/null +++ b/frontend-next/components/Checkbox.tsx @@ -0,0 +1,48 @@ +/** + * ✓ Checkbox Component + */ + +import React from 'react'; + +interface CheckboxProps extends React.InputHTMLAttributes { + label?: string; + error?: string; +} + +const Checkbox = React.forwardRef( + ({ label, error, id, className = '', ...props }, ref) => { + const checkboxId = id || `checkbox-${Math.random()}`; + + return ( +
+ + {label && ( + + )} + {error &&

{error}

} +
+ ); + }, +); + +Checkbox.displayName = 'Checkbox'; + +export default Checkbox; diff --git a/frontend-next/components/Input.tsx b/frontend-next/components/Input.tsx new file mode 100644 index 0000000..8a283bf --- /dev/null +++ b/frontend-next/components/Input.tsx @@ -0,0 +1,82 @@ +/** + * 📝 Input Component + * Componente de input reutilizável com validação + */ + +import React from 'react'; + +interface InputProps extends React.InputHTMLAttributes { + label?: string; + error?: string; + helperText?: string; + required?: boolean; +} + +const Input = React.forwardRef( + ( + { + label, + error, + helperText, + required = false, + id, + className = '', + type = 'text', + ...props + }, + ref, + ) => { + const inputId = id || `input-${Math.random()}`; + + return ( +
+ {label && ( + + )} + + + + {error &&

{error}

} + {helperText && !error && ( +

{helperText}

+ )} +
+ ); + }, +); + +Input.displayName = 'Input'; + +export default Input; diff --git a/frontend-next/components/index.ts b/frontend-next/components/index.ts new file mode 100644 index 0000000..699f32f --- /dev/null +++ b/frontend-next/components/index.ts @@ -0,0 +1,14 @@ +/** + * 🎨 UI Components Exports + */ + +export { default as Button } from './Button'; +export { default as Input } from './Input'; +export { default as Checkbox } from './Checkbox'; +export { + default as Card, + CardHeader, + CardTitle, + CardContent, + CardFooter, +} from './Card';