mirror of
https://github.com/SrIzan10/spongebin.git
synced 2026-05-01 11:05:09 +00:00
129 lines
3.5 KiB
TypeScript
129 lines
3.5 KiB
TypeScript
import { useEffect, useRef, useState } from "preact/hooks";
|
|
import type { PreinitializedWritableAtom } from "nanostores";
|
|
import { $themeColorsStore } from "../utils/theme";
|
|
import { useStore } from "@nanostores/preact";
|
|
|
|
export interface MenuButtonProps {
|
|
label: string;
|
|
ids: string[];
|
|
minWidth?: string;
|
|
$store: PreinitializedWritableAtom<string>;
|
|
}
|
|
|
|
export default function MenuButton({
|
|
label,
|
|
ids,
|
|
minWidth,
|
|
$store,
|
|
}: MenuButtonProps) {
|
|
const [open, setOpen] = useState(false);
|
|
const [searchTerm, setSearchTerm] = useState("");
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
|
|
const colors = useStore($themeColorsStore);
|
|
|
|
useEffect(() => {
|
|
const listener = () => setOpen(false);
|
|
window.addEventListener("click", listener);
|
|
return () => window.removeEventListener("click", listener);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (open && inputRef.current) {
|
|
inputRef.current.focus();
|
|
}
|
|
}, [open]);
|
|
|
|
function toggleOpen(e: MouseEvent) {
|
|
e.stopPropagation();
|
|
setOpen(!open);
|
|
}
|
|
|
|
function select(e: MouseEvent | KeyboardEvent, id: string) {
|
|
e.stopPropagation();
|
|
setOpen(false);
|
|
$store.set(id);
|
|
}
|
|
|
|
function handleInputClick(e: MouseEvent) {
|
|
e.stopPropagation();
|
|
}
|
|
|
|
function handleKeyDown(e: KeyboardEvent) {
|
|
if (e.key === "Enter") {
|
|
e.preventDefault();
|
|
if (filteredIds.length > 0) {
|
|
select(e, filteredIds[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
const filteredIds = ids.filter((id) =>
|
|
id.toLowerCase().includes(searchTerm.toLowerCase()),
|
|
);
|
|
|
|
return (
|
|
<div class="relative inline-block">
|
|
<button
|
|
class="rounded focus:outline-none"
|
|
style={{ color: colors.primary }}
|
|
onClick={toggleOpen}
|
|
>
|
|
<span class="hidden md:inline">
|
|
[{label}: {$store.get()}]
|
|
</span>
|
|
<span class="hidden sm:inline md:hidden">[{$store.get()}]</span>
|
|
<span class="sm:hidden">[{label}]</span>
|
|
</button>
|
|
{open && (
|
|
<div
|
|
class="absolute right-0 z-10 mt-4 rounded-xl max-h-60 overflow-auto w-full"
|
|
style={{
|
|
backgroundColor: colors.navbar,
|
|
minWidth: minWidth ?? "13rem",
|
|
}}
|
|
>
|
|
<input
|
|
type="text"
|
|
class="w-full px-4 py-2 border-b focus:outline-none"
|
|
style={{
|
|
backgroundColor: colors.navbar,
|
|
borderColor: `${colors.primary}aa`,
|
|
color: colors.primary,
|
|
}}
|
|
placeholder="type to search..."
|
|
value={searchTerm}
|
|
onInput={(e) => setSearchTerm((e.target as HTMLInputElement).value)}
|
|
onClick={handleInputClick}
|
|
onKeyDown={handleKeyDown}
|
|
ref={inputRef}
|
|
/>
|
|
<ul>
|
|
{filteredIds.length > 0 ? (
|
|
filteredIds.map((id) => (
|
|
<li
|
|
key={id}
|
|
class={`px-4 py-2 cursor-pointer ${id === $store.get() ? "font-bold" : ""}`}
|
|
style={{
|
|
backgroundColor:
|
|
id === $store.get()
|
|
? `${colors.background}77`
|
|
: `${colors.navbar}`,
|
|
}}
|
|
onClick={(e) => select(e, id)}
|
|
>
|
|
{id === $store.get() ? `*${id}` : id}
|
|
</li>
|
|
))
|
|
) : (
|
|
<li class="px-4 py-2" style={{ color: colors.primary }}>
|
|
No results
|
|
</li>
|
|
)}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|