feat: migra FormField e SelectField com paleta premium + componente Button base com variantes premium
This commit is contained in:
parent
df837e13a2
commit
acc305ff21
43
src/components/FormField.tsx
Normal file
43
src/components/FormField.tsx
Normal 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'
|
||||||
69
src/components/SelectField.tsx
Normal file
69
src/components/SelectField.tsx
Normal 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'
|
||||||
48
src/components/ui/button.tsx
Normal file
48
src/components/ui/button.tsx
Normal 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 }
|
||||||
Loading…
Reference in New Issue
Block a user