refactor(select): Dropdown via React Portal (fixed overlay), positioniert relativ zum Trigger; verhindert Überlagerungen zuverlässig
This commit is contained in:
parent
a6531aef9d
commit
d4b839f888
1 changed files with 28 additions and 4 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
import { fetchChannels, fetchSounds, playSound, setVolumeLive, getVolume, adminStatus, adminLogin, adminLogout, adminDelete, adminRename, playUrl } from './api';
|
import { fetchChannels, fetchSounds, playSound, setVolumeLive, getVolume, adminStatus, adminLogin, adminLogout, adminDelete, adminRename, playUrl } from './api';
|
||||||
import type { VoiceChannelInfo, Sound } from './types';
|
import type { VoiceChannelInfo, Sound } from './types';
|
||||||
import { getCookie, setCookie } from './cookies';
|
import { getCookie, setCookie } from './cookies';
|
||||||
|
|
@ -336,22 +337,44 @@ type SelectProps = {
|
||||||
function CustomSelect({ channels, value, onChange }: SelectProps) {
|
function CustomSelect({ channels, value, onChange }: SelectProps) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const ref = useRef<HTMLDivElement | null>(null);
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
|
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
||||||
|
const [menuPos, setMenuPos] = useState<{ left: number; top: number; width: number }>({ left: 0, top: 0, width: 0 });
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const close = (e: MouseEvent) => { if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false); };
|
const close = (e: MouseEvent) => { if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false); };
|
||||||
window.addEventListener('click', close);
|
window.addEventListener('click', close);
|
||||||
return () => window.removeEventListener('click', close);
|
return () => window.removeEventListener('click', close);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return;
|
||||||
|
const update = () => {
|
||||||
|
const el = triggerRef.current;
|
||||||
|
if (!el) return;
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
setMenuPos({ left: Math.round(r.left), top: Math.round(r.bottom + 6), width: Math.round(r.width) });
|
||||||
|
};
|
||||||
|
update();
|
||||||
|
window.addEventListener('resize', update);
|
||||||
|
window.addEventListener('scroll', update, true);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', update);
|
||||||
|
window.removeEventListener('scroll', update, true);
|
||||||
|
};
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
const current = channels.find(c => `${c.guildId}:${c.channelId}` === value);
|
const current = channels.find(c => `${c.guildId}:${c.channelId}` === value);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="control select custom-select" ref={ref}>
|
<div className="control select custom-select" ref={ref}>
|
||||||
<button type="button" className="select-trigger" onClick={() => setOpen(v => !v)}>
|
<button ref={triggerRef} type="button" className="select-trigger" onClick={() => setOpen(v => !v)}>
|
||||||
{current ? `${current.guildName} – ${current.channelName}` : 'Channel wählen'}
|
{current ? `${current.guildName} – ${current.channelName}` : 'Channel wählen'}
|
||||||
<span className="chev">▾</span>
|
<span className="chev">▾</span>
|
||||||
</button>
|
</button>
|
||||||
{open && (
|
{open && typeof document !== 'undefined' && ReactDOM.createPortal(
|
||||||
<div className="select-menu">
|
<div
|
||||||
|
className="select-menu"
|
||||||
|
style={{ position: 'fixed', left: menuPos.left, top: menuPos.top, width: menuPos.width, zIndex: 30000 }}
|
||||||
|
>
|
||||||
{channels.map((c) => {
|
{channels.map((c) => {
|
||||||
const v = `${c.guildId}:${c.channelId}`;
|
const v = `${c.guildId}:${c.channelId}`;
|
||||||
const active = v === value;
|
const active = v === value;
|
||||||
|
|
@ -366,7 +389,8 @@ function CustomSelect({ channels, value, onChange }: SelectProps) {
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>,
|
||||||
|
document.body
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue