Prepara versao dev 1.0

This commit is contained in:
Erik Silva
2025-12-08 21:47:38 -03:00
parent 512287698e
commit 190fde20c3
85 changed files with 7755 additions and 2317 deletions

View File

@@ -0,0 +1,100 @@
"use client";
import { useState } from 'react';
import { SwatchIcon } from '@heroicons/react/24/outline';
const themePresets = [
{
name: 'Laranja/Rosa (Padrão)',
gradient: 'linear-gradient(90deg, #FF3A05, #FF0080)',
},
{
name: 'Azul/Roxo',
gradient: 'linear-gradient(90deg, #0066FF, #9333EA)',
},
{
name: 'Verde/Esmeralda',
gradient: 'linear-gradient(90deg, #10B981, #059669)',
},
{
name: 'Ciano/Azul',
gradient: 'linear-gradient(90deg, #06B6D4, #3B82F6)',
},
{
name: 'Rosa/Roxo',
gradient: 'linear-gradient(90deg, #EC4899, #A855F7)',
},
{
name: 'Vermelho/Laranja',
gradient: 'linear-gradient(90deg, #EF4444, #F97316)',
},
{
name: 'Índigo/Violeta',
gradient: 'linear-gradient(90deg, #6366F1, #8B5CF6)',
},
{
name: 'Âmbar/Amarelo',
gradient: 'linear-gradient(90deg, #F59E0B, #EAB308)',
},
];
export default function ThemeTester() {
const [isOpen, setIsOpen] = useState(false);
const applyTheme = (gradient: string) => {
document.documentElement.style.setProperty('--gradient-primary', gradient);
document.documentElement.style.setProperty('--gradient', gradient);
document.documentElement.style.setProperty('--gradient-text', gradient.replace('90deg', 'to right'));
document.documentElement.style.setProperty('--color-gradient-brand', gradient.replace('90deg', 'to right'));
};
return (
<div className="fixed bottom-4 right-4 z-50">
{/* Botão flutuante */}
<button
onClick={() => setIsOpen(!isOpen)}
className="w-14 h-14 rounded-full shadow-lg flex items-center justify-center transition-all hover:scale-110"
style={{ background: 'var(--gradient-primary)' }}
title="Testar Temas"
>
<SwatchIcon className="w-6 h-6 text-white" />
</button>
{/* Painel de temas */}
{isOpen && (
<div className="absolute bottom-16 right-0 w-80 bg-white dark:bg-gray-800 rounded-xl shadow-2xl border border-gray-200 dark:border-gray-700 overflow-hidden">
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
<h3 className="font-semibold text-gray-900 dark:text-white">Testar Gradientes</h3>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
Clique para aplicar temporariamente
</p>
</div>
<div className="p-3 max-h-96 overflow-y-auto space-y-2">
{themePresets.map((theme) => (
<button
key={theme.name}
onClick={() => applyTheme(theme.gradient)}
className="w-full flex items-center space-x-3 p-3 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors group"
>
<div
className="w-12 h-12 rounded-lg shrink-0"
style={{ background: theme.gradient }}
/>
<span className="text-sm font-medium text-gray-900 dark:text-white text-left">
{theme.name}
</span>
</button>
))}
</div>
<div className="p-3 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
<p className="text-xs text-gray-500 dark:text-gray-400 text-center">
💡 Recarregue a página para voltar ao tema original
</p>
</div>
</div>
)}
</div>
);
}

View File

@@ -1,20 +1,35 @@
'use client';
import { useTheme } from '@/contexts/ThemeContext';
import { useTheme } from 'next-themes';
import { useEffect, useState } from 'react';
import { MoonIcon, SunIcon } from '@heroicons/react/24/outline';
export default function ThemeToggle() {
const { toggleTheme } = useTheme();
const { resolvedTheme, setTheme } = useTheme();
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return <div className="w-9 h-9 rounded-lg bg-gray-100 dark:bg-gray-800" />;
}
const isDark = resolvedTheme === 'dark';
return (
<button
onClick={toggleTheme}
className="p-2 rounded-lg bg-gradient-to-r from-primary/10 to-secondary/10 hover:from-primary/20 hover:to-secondary/20 transition-all duration-300 border border-primary/20 hover:border-primary/40"
aria-label="Alternar tema"
type="button"
onClick={() => setTheme(isDark ? 'light' : 'dark')}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
aria-label={isDark ? 'Ativar tema claro' : 'Ativar tema escuro'}
title={isDark ? 'Alterar para modo claro' : 'Alterar para modo escuro'}
>
{typeof window !== 'undefined' && document.documentElement.classList.contains('dark') ? (
<i className="ri-sun-line text-lg gradient-text font-bold" />
{isDark ? (
<SunIcon className="w-5 h-5 text-gray-600 dark:text-gray-400" />
) : (
<i className="ri-moon-line text-lg gradient-text font-bold" />
<MoonIcon className="w-5 h-5 text-gray-600 dark:text-gray-400" />
)}
</button>
);

View File

@@ -47,7 +47,7 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
<button
ref={ref}
className={`${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`}
style={variant === 'primary' ? { background: 'var(--gradient)' } : undefined}
style={variant === 'primary' ? { background: 'var(--gradient-primary)' } : undefined}
disabled={disabled || isLoading}
{...props}
>

View File

@@ -36,7 +36,7 @@ const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
${className}
`}
style={{
background: checked ? 'var(--gradient)' : undefined,
background: checked ? 'var(--gradient-primary)' : undefined,
}}
checked={checked}
onChange={handleChange}

View File

@@ -0,0 +1,95 @@
import { Fragment } from 'react';
import { Dialog as HeadlessDialog, Transition } from '@headlessui/react';
import { XMarkIcon } from '@heroicons/react/24/outline';
interface DialogProps {
isOpen: boolean;
onClose: () => void;
title?: string;
children: React.ReactNode;
size?: 'sm' | 'md' | 'lg' | 'xl';
showClose?: boolean;
}
const sizeClasses = {
sm: 'max-w-md',
md: 'max-w-lg',
lg: 'max-w-2xl',
xl: 'max-w-4xl',
};
export default function Dialog({
isOpen,
onClose,
title,
children,
size = 'md',
showClose = true,
}: DialogProps) {
return (
<Transition appear show={isOpen} as={Fragment}>
<HeadlessDialog as="div" className="relative z-50" onClose={onClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<HeadlessDialog.Panel
className={`w-full ${sizeClasses[size]} transform rounded-2xl bg-white dark:bg-gray-800 p-6 text-left align-middle shadow-xl transition-all border border-gray-200 dark:border-gray-700`}
>
{title && (
<div className="flex items-center justify-between mb-4">
<HeadlessDialog.Title
as="h3"
className="text-lg font-semibold text-gray-900 dark:text-white"
>
{title}
</HeadlessDialog.Title>
{showClose && (
<button
onClick={onClose}
className="p-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
>
<XMarkIcon className="w-5 h-5 text-gray-500 dark:text-gray-400" />
</button>
)}
</div>
)}
{children}
</HeadlessDialog.Panel>
</Transition.Child>
</div>
</div>
</HeadlessDialog>
</Transition>
);
}
// Componente auxiliar para o corpo do dialog
Dialog.Body = function DialogBody({ children, className = '' }: { children: React.ReactNode; className?: string }) {
return <div className={`text-sm text-gray-600 dark:text-gray-300 ${className}`}>{children}</div>;
};
// Componente auxiliar para o rodapé do dialog
Dialog.Footer = function DialogFooter({ children, className = '' }: { children: React.ReactNode; className?: string }) {
return <div className={`mt-6 flex items-center justify-end space-x-3 ${className}`}>{children}</div>;
};

View File

@@ -107,7 +107,7 @@ const SearchableSelect = forwardRef<HTMLSelectElement, SearchableSelectProps>(
</select>
{label && (
<label className="block text-[13px] font-semibold text-[#000000] mb-2">
<label className="block text-[13px] font-semibold text-zinc-900 dark:text-white mb-2">
{label}
{required && <span className="text-[#FF3A05] ml-1">*</span>}
</label>
@@ -116,7 +116,7 @@ const SearchableSelect = forwardRef<HTMLSelectElement, SearchableSelectProps>(
<div ref={containerRef} className="relative">
{leftIcon && (
<i
className={`${leftIcon} absolute left-3.5 top-1/2 -translate-y-1/2 text-[#7D7D7D] text-[20px] pointer-events-none z-10`}
className={`${leftIcon} absolute left-3.5 top-1/2 -translate-y-1/2 text-zinc-500 dark:text-zinc-400 text-[20px] pointer-events-none z-10`}
/>
)}
@@ -126,41 +126,41 @@ const SearchableSelect = forwardRef<HTMLSelectElement, SearchableSelectProps>(
onClick={() => setIsOpen(!isOpen)}
className={`
w-full px-3.5 py-3 text-[14px] font-normal
border rounded-md bg-white
text-[#000000] text-left
border rounded-md bg-white dark:bg-zinc-800
text-zinc-900 dark:text-white text-left
transition-all
cursor-pointer
${leftIcon ? "pl-11" : ""}
pr-11
${error
? "border-[#FF3A05]"
: "border-[#E5E5E5] focus:border-[#FF3A05]"
: "border-zinc-200 dark:border-zinc-700 focus:border-[#FF3A05]"
}
outline-none ring-0 focus:ring-0 shadow-none focus:shadow-none
${className}
`}
>
{selectedOption ? selectedOption.label : (
<span className="text-[#7D7D7D]">{placeholder || "Selecione uma opção"}</span>
<span className="text-zinc-500 dark:text-zinc-400">{placeholder || "Selecione uma opção"}</span>
)}
</button>
<i className={`ri-arrow-${isOpen ? 'up' : 'down'}-s-line absolute right-3.5 top-1/2 -translate-y-1/2 text-[#7D7D7D] text-[20px] pointer-events-none transition-transform`} />
<i className={`ri-arrow-${isOpen ? 'up' : 'down'}-s-line absolute right-3.5 top-1/2 -translate-y-1/2 text-zinc-500 dark:text-zinc-400 text-[20px] pointer-events-none transition-transform`} />
{/* Dropdown */}
{isOpen && (
<div className="absolute z-50 w-full mt-2 bg-white border border-[#E5E5E5] rounded-md shadow-lg max-h-[300px] overflow-hidden">
<div className="absolute z-50 w-full mt-2 bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-md shadow-lg max-h-[300px] overflow-hidden">
{/* Search input */}
<div className="p-2 border-b border-[#E5E5E5]">
<div className="p-2 border-b border-zinc-200 dark:border-zinc-700">
<div className="relative">
<i className="ri-search-line absolute left-3 top-1/2 -translate-y-1/2 text-[#7D7D7D] text-[16px]" />
<i className="ri-search-line absolute left-3 top-1/2 -translate-y-1/2 text-zinc-500 dark:text-zinc-400 text-[16px]" />
<input
ref={searchInputRef}
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Buscar..."
className="w-full pl-9 pr-3 py-2 text-[14px] border border-[#E5E5E5] rounded-md outline-none focus:border-[#FF3A05] shadow-none"
className="w-full pl-9 pr-3 py-2 text-[14px] border border-zinc-200 dark:border-zinc-700 rounded-md outline-none focus:border-[#FF3A05] shadow-none bg-white dark:bg-zinc-900 text-zinc-900 dark:text-white placeholder:text-zinc-500 dark:placeholder:text-zinc-400"
/>
</div>
</div>
@@ -175,15 +175,15 @@ const SearchableSelect = forwardRef<HTMLSelectElement, SearchableSelectProps>(
onClick={() => handleSelect(option)}
className={`
w-full px-4 py-2.5 text-left text-[14px] transition-colors
hover:bg-[#FDFDFC] cursor-pointer
${selectedOption?.value === option.value ? 'bg-[#FF3A05]/10 text-[#FF3A05] font-medium' : 'text-[#000000]'}
hover:bg-zinc-100 dark:hover:bg-zinc-700 cursor-pointer
${selectedOption?.value === option.value ? 'bg-[#FF3A05]/10 text-[#FF3A05] font-medium' : 'text-zinc-900 dark:text-white'}
`}
>
{option.label}
</button>
))
) : (
<div className="px-4 py-8 text-center text-[#7D7D7D] text-[14px]">
<div className="px-4 py-8 text-center text-zinc-500 dark:text-zinc-400 text-[14px]">
Nenhum resultado encontrado
</div>
)}
@@ -193,7 +193,7 @@ const SearchableSelect = forwardRef<HTMLSelectElement, SearchableSelectProps>(
</div>
{helperText && !error && (
<p className="mt-1.5 text-[12px] text-[#7D7D7D]">{helperText}</p>
<p className="mt-1.5 text-[12px] text-zinc-600 dark:text-zinc-400">{helperText}</p>
)}
{error && (
<p className="mt-1 text-[13px] text-[#FF3A05] flex items-center gap-1">

View File

@@ -3,3 +3,4 @@ export { default as Input } from "./Input";
export { default as Checkbox } from "./Checkbox";
export { default as Select } from "./Select";
export { default as SearchableSelect } from "./SearchableSelect";
export { default as Dialog } from "./Dialog";