bug-fix/filter dropdowns in dark mode
This commit is contained in:
@@ -60,6 +60,81 @@ function getProjectFinancials(project) {
|
||||
const fmt = (amt) =>
|
||||
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(Number(amt) || 0);
|
||||
|
||||
// Dark-mode-aware dropdown — replaces native <select> so the option list
|
||||
// doesn't render with the OS default white background in dark mode.
|
||||
function FilterPopover({ id, value, onChange, options, allLabel, ariaLabel }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const boxRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const onClick = (e) => {
|
||||
if (boxRef.current && !boxRef.current.contains(e.target)) setOpen(false);
|
||||
};
|
||||
const onKey = (e) => { if (e.key === 'Escape') setOpen(false); };
|
||||
document.addEventListener('mousedown', onClick);
|
||||
document.addEventListener('keydown', onKey);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', onClick);
|
||||
document.removeEventListener('keydown', onKey);
|
||||
};
|
||||
}, [open]);
|
||||
|
||||
const display = value === 'all' ? allLabel : value;
|
||||
|
||||
return (
|
||||
<div className="relative" ref={boxRef}>
|
||||
<button
|
||||
id={id}
|
||||
type="button"
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={open}
|
||||
aria-label={ariaLabel}
|
||||
onClick={() => setOpen(o => !o)}
|
||||
className="w-full flex items-center justify-between gap-2 pl-3 pr-2 py-2 text-sm rounded-xl bg-zinc-100 dark:bg-black/40 border border-zinc-200 dark:border-white/10 text-zinc-900 dark:text-white outline-none focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500/40 transition-colors"
|
||||
>
|
||||
<span className="truncate text-left">{display}</span>
|
||||
<ChevronDown size={14} className={`shrink-0 text-zinc-400 transition-transform ${open ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
{open && (
|
||||
<div
|
||||
role="listbox"
|
||||
className="absolute z-30 left-0 right-0 mt-1 rounded-xl bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-white/10 shadow-xl shadow-black/10 dark:shadow-black/50 overflow-hidden"
|
||||
>
|
||||
<div className="max-h-56 overflow-y-auto custom-scrollbar py-1">
|
||||
<button
|
||||
type="button"
|
||||
role="option"
|
||||
aria-selected={value === 'all'}
|
||||
onClick={() => { onChange('all'); setOpen(false); }}
|
||||
className={`w-full flex items-center justify-between px-3 py-2 text-sm text-left transition-colors ${value === 'all' ? 'text-emerald-600 dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-500/10' : 'text-zinc-700 dark:text-zinc-200 hover:bg-zinc-50 dark:hover:bg-white/5'}`}
|
||||
>
|
||||
<span>{allLabel}</span>
|
||||
{value === 'all' && <Check size={12} strokeWidth={2.5} className="shrink-0 opacity-70" />}
|
||||
</button>
|
||||
{options.map(opt => {
|
||||
const isSelected = opt === value;
|
||||
return (
|
||||
<button
|
||||
key={opt}
|
||||
type="button"
|
||||
role="option"
|
||||
aria-selected={isSelected}
|
||||
onClick={() => { onChange(opt); setOpen(false); }}
|
||||
className={`w-full flex items-center justify-between px-3 py-2 text-sm text-left transition-colors ${isSelected ? 'text-emerald-600 dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-500/10' : 'text-zinc-700 dark:text-zinc-200 hover:bg-zinc-50 dark:hover:bg-white/5'}`}
|
||||
>
|
||||
<span className="truncate">{opt}</span>
|
||||
{isSelected && <Check size={12} strokeWidth={2.5} className="shrink-0 opacity-70" />}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const CommissionDistributionPanel = ({
|
||||
ownerProjects,
|
||||
orgCommissionDefaults,
|
||||
@@ -173,9 +248,6 @@ const CommissionDistributionPanel = ({
|
||||
setProjectQuery('');
|
||||
};
|
||||
|
||||
const selectClass =
|
||||
"appearance-none w-full pl-3 pr-8 py-2 text-sm rounded-xl bg-zinc-100 dark:bg-black/40 border border-zinc-200 dark:border-white/10 text-zinc-900 dark:text-white outline-none focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500/40 transition-colors cursor-pointer";
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
{/* Header */}
|
||||
@@ -220,21 +292,14 @@ const CommissionDistributionPanel = ({
|
||||
</div>
|
||||
|
||||
{/* Role filter */}
|
||||
<div className="relative">
|
||||
<label htmlFor="commission-role-filter" className="sr-only">Filter by role</label>
|
||||
<select
|
||||
<FilterPopover
|
||||
id="commission-role-filter"
|
||||
ariaLabel="Filter by role"
|
||||
value={roleFilter}
|
||||
onChange={(e) => setRoleFilter(e.target.value)}
|
||||
className={selectClass}
|
||||
>
|
||||
<option value="all">All Roles</option>
|
||||
{roleOptions.map(r => (
|
||||
<option key={r} value={r}>{r}</option>
|
||||
))}
|
||||
</select>
|
||||
<ChevronDown size={14} className="absolute right-2.5 top-1/2 -translate-y-1/2 text-zinc-400 pointer-events-none" />
|
||||
</div>
|
||||
onChange={setRoleFilter}
|
||||
options={roleOptions}
|
||||
allLabel="All Roles"
|
||||
/>
|
||||
|
||||
{/* Project searchable dropdown */}
|
||||
<div className="relative" ref={projectBoxRef}>
|
||||
@@ -295,21 +360,14 @@ const CommissionDistributionPanel = ({
|
||||
</div>
|
||||
|
||||
{/* Commission Type filter */}
|
||||
<div className="relative">
|
||||
<label htmlFor="commission-type-filter" className="sr-only">Filter by commission type</label>
|
||||
<select
|
||||
<FilterPopover
|
||||
id="commission-type-filter"
|
||||
ariaLabel="Filter by commission type"
|
||||
value={typeFilter}
|
||||
onChange={(e) => setTypeFilter(e.target.value)}
|
||||
className={selectClass}
|
||||
>
|
||||
<option value="all">All Commission Types</option>
|
||||
{typeOptions.map(t => (
|
||||
<option key={t} value={t}>{t}</option>
|
||||
))}
|
||||
</select>
|
||||
<ChevronDown size={14} className="absolute right-2.5 top-1/2 -translate-y-1/2 text-zinc-400 pointer-events-none" />
|
||||
</div>
|
||||
onChange={setTypeFilter}
|
||||
options={typeOptions}
|
||||
allLabel="All Commission Types"
|
||||
/>
|
||||
|
||||
{hasActiveFilters && (
|
||||
<div className="sm:col-span-2 lg:col-span-4 flex justify-end">
|
||||
|
||||
Reference in New Issue
Block a user