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:
85
components/CustomSelect.tsx
Normal file
85
components/CustomSelect.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user