Replace native selects with custom dropdown for mobile usability
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>
This commit is contained in:
69
components/CustomSelect.tsx
Normal file
69
components/CustomSelect.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user