Prepara versao dev 1.0
This commit is contained in:
100
front-end-dash.aggios.app/components/ThemeTester.tsx
Normal file
100
front-end-dash.aggios.app/components/ThemeTester.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
@@ -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}
|
||||
|
||||
95
front-end-dash.aggios.app/components/ui/Dialog.tsx
Normal file
95
front-end-dash.aggios.app/components/ui/Dialog.tsx
Normal 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>;
|
||||
};
|
||||
@@ -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">
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user