style(select): Custom Glass Select mit dunklem Hover (besserer Kontrast)
This commit is contained in:
parent
196f473b01
commit
57a06570ef
2 changed files with 80 additions and 12 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { fetchChannels, fetchSounds, playSound, setVolumeLive } from './api';
|
import { fetchChannels, fetchSounds, playSound, setVolumeLive } from './api';
|
||||||
import type { VoiceChannelInfo, Sound } from './types';
|
import type { VoiceChannelInfo, Sound } from './types';
|
||||||
import { getCookie, setCookie } from './cookies';
|
import { getCookie, setCookie } from './cookies';
|
||||||
|
|
@ -103,15 +103,11 @@ export default function App() {
|
||||||
aria-label="Suche"
|
aria-label="Suche"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="control select">
|
<CustomSelect
|
||||||
<select value={selected} onChange={(e) => setSelected(e.target.value)} aria-label="Voice-Channel">
|
channels={channels}
|
||||||
{channels.map((c) => (
|
value={selected}
|
||||||
<option key={`${c.guildId}:${c.channelId}`} value={`${c.guildId}:${c.channelId}`}>
|
onChange={setSelected}
|
||||||
{c.guildName} – {c.channelName}
|
/>
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className="control volume">
|
<div className="control volume">
|
||||||
<label>🔊 {Math.round(volume * 100)}%</label>
|
<label>🔊 {Math.round(volume * 100)}%</label>
|
||||||
<input
|
<input
|
||||||
|
|
@ -192,8 +188,49 @@ export default function App() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePlayWithPathFactory(play: (name: string, rel?: string) => Promise<void>) {
|
type SelectProps = {
|
||||||
return (s: Sound & { relativePath?: string }) => play(s.name, s.relativePath);
|
channels: VoiceChannelInfo[];
|
||||||
|
value: string;
|
||||||
|
onChange: (v: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function CustomSelect({ channels, value, onChange }: SelectProps) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
const close = (e: MouseEvent) => { if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false); };
|
||||||
|
window.addEventListener('click', close);
|
||||||
|
return () => window.removeEventListener('click', close);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const current = channels.find(c => `${c.guildId}:${c.channelId}` === value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="control select custom-select" ref={ref}>
|
||||||
|
<button type="button" className="select-trigger" onClick={() => setOpen(v => !v)}>
|
||||||
|
{current ? `${current.guildName} – ${current.channelName}` : 'Channel wählen'}
|
||||||
|
<span className="chev">▾</span>
|
||||||
|
</button>
|
||||||
|
{open && (
|
||||||
|
<div className="select-menu">
|
||||||
|
{channels.map((c) => {
|
||||||
|
const v = `${c.guildId}:${c.channelId}`;
|
||||||
|
const active = v === value;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
key={v}
|
||||||
|
className={`select-item ${active ? 'active' : ''}`}
|
||||||
|
onClick={() => { onChange(v); setOpen(false); }}
|
||||||
|
>
|
||||||
|
{c.guildName} – {c.channelName}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,37 @@ header p { opacity: .8; }
|
||||||
}
|
}
|
||||||
.control select option { background-color: #0f1530; color: #e7e7ee; }
|
.control select option { background-color: #0f1530; color: #e7e7ee; }
|
||||||
.control select optgroup { background-color: #0f1530; color: #c8c8d8; }
|
.control select optgroup { background-color: #0f1530; color: #c8c8d8; }
|
||||||
|
|
||||||
|
/* Custom Select */
|
||||||
|
.custom-select { position: relative; }
|
||||||
|
.select-trigger {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border-radius: 14px;
|
||||||
|
border: 1px solid rgba(255,255,255,.25);
|
||||||
|
background: linear-gradient(180deg, rgba(255,255,255,.14), rgba(255,255,255,.06));
|
||||||
|
color: #e7e7ee;
|
||||||
|
backdrop-filter: blur(18px);
|
||||||
|
box-shadow: inset 0 1px 0 rgba(255,255,255,.2);
|
||||||
|
}
|
||||||
|
.select-trigger .chev { float: right; opacity: .8; }
|
||||||
|
.select-menu {
|
||||||
|
position: absolute; inset: auto 0 auto 0; top: calc(100% + 6px);
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid rgba(255,255,255,.25);
|
||||||
|
background: rgba(15,21,48,.98);
|
||||||
|
box-shadow: 0 24px 48px rgba(0,0,0,.5);
|
||||||
|
max-height: 280px; overflow-y: auto;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
.select-item {
|
||||||
|
width: 100%; text-align: left; padding: 10px 12px; color: #e7e7ee;
|
||||||
|
background: transparent; border: 0;
|
||||||
|
}
|
||||||
|
.select-item:hover { background: rgba(255,255,255,.08); color: #fff; }
|
||||||
|
.select-item.active { background: rgba(255,255,255,.14); color: #fff; }
|
||||||
.control input::placeholder { color: #c8c8d8; }
|
.control input::placeholder { color: #c8c8d8; }
|
||||||
|
|
||||||
.control.volume { display: grid; grid-template-columns: auto 1fr; gap: 10px; align-items: center; }
|
.control.volume { display: grid; grid-template-columns: auto 1fr; gap: 10px; align-items: center; }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue