feat: migra FormField e SelectField com paleta premium + componente Button base com variantes premium

This commit is contained in:
Rodribm10 2026-04-13 23:10:00 -03:00
parent df837e13a2
commit acc305ff21
3 changed files with 160 additions and 0 deletions

View File

@ -0,0 +1,43 @@
import { forwardRef, type InputHTMLAttributes } from 'react'
import { cn } from '@/lib/utils'
interface FormFieldProps extends InputHTMLAttributes<HTMLInputElement> {
label: string
error?: string
required?: boolean
}
export const FormField = forwardRef<HTMLInputElement, FormFieldProps>(
({ label, error, required, className, id, ...props }, ref) => {
const inputId = id ?? `field-${label.toLowerCase().replace(/\s+/g, '-')}`
return (
<div className="flex flex-col gap-2">
<label
htmlFor={inputId}
className="font-sans text-xs uppercase tracking-widest text-champagne"
>
{label}
{required && <span className="text-rose-gold ml-1">*</span>}
</label>
<input
ref={ref}
id={inputId}
aria-invalid={error ? 'true' : 'false'}
className={cn(
'w-full rounded-lg border border-champagne/30 bg-midnight/60 px-4 py-3',
'font-sans text-ivory placeholder:text-slate',
'focus:outline-none focus:border-champagne focus:glow-champagne',
'transition-all duration-200',
error && 'border-ruby focus:border-ruby',
className
)}
{...props}
/>
{error && <span className="text-ruby text-xs font-sans">{error}</span>}
</div>
)
}
)
FormField.displayName = 'FormField'

View File

@ -0,0 +1,69 @@
import { forwardRef, type SelectHTMLAttributes } from 'react'
import { ChevronDown } from 'lucide-react'
import { cn } from '@/lib/utils'
interface Option {
value: string
label: string
}
interface SelectFieldProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>, 'children'> {
label: string
options: Option[]
placeholder?: string
error?: string
required?: boolean
}
export const SelectField = forwardRef<HTMLSelectElement, SelectFieldProps>(
(
{ label, options, placeholder = 'Selecione...', error, required, className, id, ...props },
ref
) => {
const selectId = id ?? `select-${label.toLowerCase().replace(/\s+/g, '-')}`
return (
<div className="flex flex-col gap-2">
<label
htmlFor={selectId}
className="font-sans text-xs uppercase tracking-widest text-champagne"
>
{label}
{required && <span className="text-rose-gold ml-1">*</span>}
</label>
<div className="relative">
<select
ref={ref}
id={selectId}
aria-invalid={error ? 'true' : 'false'}
className={cn(
'w-full appearance-none rounded-lg border border-champagne/30 bg-midnight/60 px-4 py-3 pr-10',
'font-sans text-ivory',
'focus:outline-none focus:border-champagne focus:glow-champagne',
'transition-all duration-200',
error && 'border-ruby focus:border-ruby',
className
)}
{...props}
>
<option value="" disabled>
{placeholder}
</option>
{options.map((opt) => (
<option key={opt.value} value={opt.value} className="bg-midnight text-ivory">
{opt.label}
</option>
))}
</select>
<ChevronDown
aria-hidden
className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-champagne"
/>
</div>
{error && <span className="text-ruby text-xs font-sans">{error}</span>}
</div>
)
}
)
SelectField.displayName = 'SelectField'

View File

@ -0,0 +1,48 @@
import { forwardRef, type ButtonHTMLAttributes } from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const buttonVariants = cva(
'inline-flex items-center justify-center gap-2 rounded-lg font-sans font-semibold transition-all duration-200 disabled:pointer-events-none disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-champagne/60',
{
variants: {
variant: {
primary:
'bg-gradient-to-r from-champagne to-rose-gold text-obsidian hover:glow-champagne hover:scale-[1.02]',
secondary:
'border border-champagne/30 bg-midnight/60 text-ivory hover:border-champagne hover:glow-champagne',
ghost: 'text-champagne hover:bg-champagne/10',
destructive: 'bg-ruby text-ivory hover:bg-ruby/90',
},
size: {
sm: 'h-9 px-4 text-sm',
md: 'h-11 px-6 text-base',
lg: 'h-14 px-8 text-lg',
},
},
defaultVariants: {
variant: 'primary',
size: 'md',
},
}
)
interface ButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button'
return (
<Comp className={cn(buttonVariants({ variant, size }), className)} ref={ref} {...props} />
)
}
)
Button.displayName = 'Button'
export { buttonVariants }