feat: Initialize ComFi project with Vite

Setup project structure, dependencies, and basic configuration for the ComFi application. Includes initial setup for Vite, React, TypeScript, Tailwind CSS, and essential development tools. Defines core types and provides a basic README for local development.
This commit is contained in:
MMrp89
2026-02-09 20:28:37 -03:00
parent 1e6a56d866
commit 1a57ac7754
28 changed files with 6070 additions and 8 deletions

View File

@@ -0,0 +1,85 @@
import React, { useState, useRef, useEffect } from 'react';
import { ChevronDown, Check } from 'lucide-react';
export interface Option {
value: string | number;
label: string;
}
interface CustomSelectProps {
value: string | number;
onChange: (value: any) => void;
options: Option[];
placeholder?: string;
icon?: React.ReactNode;
className?: string;
disabled?: boolean;
}
export const CustomSelect: React.FC<CustomSelectProps> = ({
value,
onChange,
options,
placeholder = 'Selecione...',
icon,
className = '',
disabled = false
}) => {
const [isOpen, setIsOpen] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
const selectedOption = options.find(o => o.value === value);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
return (
<div className={`relative ${className}`} ref={containerRef}>
<div
onClick={() => !disabled && setIsOpen(!isOpen)}
className={`w-full p-3 bg-white border rounded-xl flex justify-between items-center cursor-pointer text-sm transition-all shadow-sm
${isOpen ? 'border-primary-500 ring-2 ring-primary-100' : 'border-slate-200 hover:border-primary-300'}
${disabled ? 'opacity-50 cursor-not-allowed bg-slate-50' : ''}
`}
>
<div className="flex items-center gap-2 truncate pr-2">
{icon && <span className="text-slate-400 shrink-0">{icon}</span>}
<span className={`truncate ${!selectedOption ? 'text-slate-400' : 'font-medium text-slate-700'}`}>
{selectedOption ? selectedOption.label : placeholder}
</span>
</div>
<ChevronDown size={18} className={`text-slate-400 shrink-0 transition-transform duration-200 ${isOpen ? 'rotate-180 text-primary-500' : ''}`} />
</div>
{isOpen && !disabled && (
<div className="absolute top-full left-0 w-full mt-2 bg-white border border-slate-100 rounded-xl shadow-2xl z-50 max-h-60 overflow-y-auto animate-fade-in">
{options.map((option) => (
<div
key={option.value}
onClick={() => { onChange(option.value); setIsOpen(false); }}
className={`p-3 text-sm cursor-pointer flex justify-between items-center transition-colors border-b border-slate-50 last:border-0
${option.value === value
? 'bg-primary-50 text-primary-700 font-bold'
: 'text-slate-600 hover:bg-slate-50 hover:text-slate-900'}
`}
>
<span className="truncate">{option.label}</span>
{option.value === value && <Check size={16} className="text-primary-500 shrink-0 ml-2" />}
</div>
))}
{options.length === 0 && (
<div className="p-3 text-sm text-slate-400 text-center italic">Sem opções</div>
)}
</div>
)}
</div>
);
};