Native <select> popups ignore CSS on iOS/Android. CustomSelect renders a styled div-based dropdown with full-width touch-friendly option buttons (py-3, text-base). Used in BeoSelector and ObjektSelector. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
70 lines
2.1 KiB
TypeScript
70 lines
2.1 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useRef, useState } from 'react';
|
|
|
|
export interface SelectOption {
|
|
value: string;
|
|
label: string;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
interface Props {
|
|
options: SelectOption[];
|
|
placeholder: string;
|
|
onChange: (value: string) => void;
|
|
}
|
|
|
|
export default function CustomSelect({ options, placeholder, onChange }: Props) {
|
|
const [open, setOpen] = useState(false);
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
function handleOutside(e: MouseEvent) {
|
|
if (ref.current && !ref.current.contains(e.target as Node)) {
|
|
setOpen(false);
|
|
}
|
|
}
|
|
if (open) document.addEventListener('mousedown', handleOutside);
|
|
return () => document.removeEventListener('mousedown', handleOutside);
|
|
}, [open]);
|
|
|
|
function select(value: string) {
|
|
setOpen(false);
|
|
onChange(value);
|
|
}
|
|
|
|
return (
|
|
<div ref={ref} className="relative">
|
|
<button
|
|
type="button"
|
|
onClick={() => setOpen((v) => !v)}
|
|
className="w-full flex items-center justify-between px-4 py-3 border-2 border-gray-400 rounded-lg bg-white text-base text-gray-700 focus:border-blue-500 focus:outline-none"
|
|
>
|
|
<span>{placeholder}</span>
|
|
<svg
|
|
className={`w-5 h-5 text-gray-500 transition-transform ${open ? 'rotate-180' : ''}`}
|
|
fill="none" viewBox="0 0 24 24" stroke="currentColor"
|
|
>
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
</svg>
|
|
</button>
|
|
|
|
{open && (
|
|
<div className="absolute z-50 left-0 right-0 mt-1 bg-white border-2 border-gray-400 rounded-lg shadow-lg max-h-72 overflow-y-auto">
|
|
{options.map((opt) => (
|
|
<button
|
|
key={opt.value}
|
|
type="button"
|
|
disabled={opt.disabled}
|
|
onClick={() => select(opt.value)}
|
|
className="w-full text-left px-4 py-3 text-base hover:bg-blue-50 active:bg-blue-100 border-b border-gray-100 last:border-0 disabled:text-gray-400 disabled:bg-gray-50"
|
|
>
|
|
{opt.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|