Files
ComFi/components/CustomSelect.tsx
MMrp89 1a57ac7754 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.
2026-02-09 20:28:37 -03:00

86 lines
3.1 KiB
TypeScript

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>
);
};