Initial project structure cleanup

This commit is contained in:
litoral05
2026-05-08 16:57:55 +01:00
commit 8075104243
59 changed files with 22335 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
import { useEffect, useRef, useState } from 'react';
type SelectOption<T extends string> = {
value: T;
label: string;
};
type SelectProps<T extends string> = {
value: T;
options: SelectOption<T>[];
onChange: (value: T) => void;
};
export function Select<T extends string>({
value,
options,
onChange,
}: SelectProps<T>) {
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement | null>(null);
const selected =
options.find((option) => option.value === value) ??
options[0];
useEffect(() => {
function handleClick(event: MouseEvent) {
if (
ref.current &&
!ref.current.contains(event.target as Node)
) {
setOpen(false);
}
}
window.addEventListener('mousedown', handleClick);
return () => {
window.removeEventListener('mousedown', handleClick);
};
}, []);
return (
<div ref={ref} className="relative">
<button
type="button"
onClick={() => setOpen((current) => !current)}
className="flex w-full items-center justify-between rounded-xl border border-white/10 bg-slate-950 px-3 py-3 text-left text-sm text-slate-200 outline-none transition hover:border-blue-500/30 focus:border-blue-500/40"
>
<span>{selected.label}</span>
<span className="text-xs text-slate-500"></span>
</button>
{open && (
<div className="absolute z-50 mt-2 w-full overflow-hidden rounded-xl border border-white/10 bg-slate-950 shadow-2xl">
{options.map((option) => {
const active = option.value === value;
return (
<button
key={option.value}
type="button"
onClick={() => {
onChange(option.value);
setOpen(false);
}}
className={`block w-full px-3 py-2 text-left text-sm transition ${
active
? 'bg-blue-500/15 text-blue-200'
: 'text-slate-300 hover:bg-white/5 hover:text-white'
}`}
>
{option.label}
</button>
);
})}
</div>
)}
</div>
);
}