diff --git a/src/components/Contact/ContactForm.tsx b/src/components/Contact/ContactForm.tsx
index 58f664c..6f99903 100644
--- a/src/components/Contact/ContactForm.tsx
+++ b/src/components/Contact/ContactForm.tsx
@@ -4,6 +4,7 @@ import React, { useActionState } from "react";
import { useSearchParams } from "next/navigation";
import { validateEmail } from "@/utils/auth";
import { sendContact, type ContactActionState } from "@/app/contact/actions";
+import Select from "@/components/Primitives/Select";
type Topic =
| "general"
@@ -70,17 +71,16 @@ export default function ContactForm() {
)}
-
+ onChange={(value) => setTopic(value as Topic)}
+ options={(Object.keys(topicLabels) as Topic[]).map((key) => ({
+ value: key,
+ label: topicLabels[key],
+ }))}
+ />
Choose the most relevant topic so we can help you better.
diff --git a/src/components/Dashboard/ArchivesList.tsx b/src/components/Dashboard/ArchivesList.tsx
index 199961d..dde0afc 100644
--- a/src/components/Dashboard/ArchivesList.tsx
+++ b/src/components/Dashboard/ArchivesList.tsx
@@ -5,6 +5,7 @@ import Link from "next/link";
import { FiExternalLink, FiEdit2, FiTrash2, FiChevronLeft, FiChevronRight, FiArrowDown, FiSearch, FiLoader, FiDownload, FiInfo, FiBarChart2 } from "react-icons/fi";
import { getArchives, deleteArchive } from "@/app/dashboard/archives/actions";
import { baseRoms } from "@/data/baseRoms";
+import Select from "@/components/Primitives/Select";
type Archive = {
slug: string;
@@ -118,24 +119,26 @@ export default function ArchivesList({ initialData, isAdmin = false }: { initial
)}
-
-
)}
- setLanguage(e.target.value)} className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset focus:outline-none focus:ring-2 ${languageChanged ? 'ring-[var(--ring)] bg-[var(--surface-2)]' : 'bg-[var(--surface-2)] ring-[var(--border)]'}`}>
- {['English','Spanish','French','German','Italian','Portuguese','Japanese','Chinese','Korean','Other'].map(l => (
-
- ))}
-
+ ({
+ value: l,
+ label: l,
+ }))}
+ />
diff --git a/src/components/Hack/HackSubmitForm.tsx b/src/components/Hack/HackSubmitForm.tsx
index 762a34a..2f40781 100644
--- a/src/components/Hack/HackSubmitForm.tsx
+++ b/src/components/Hack/HackSubmitForm.tsx
@@ -18,6 +18,7 @@ import { useAuthContext } from "@/contexts/AuthContext";
import { useBaseRoms } from "@/contexts/BaseRomContext";
import TagSelector from "@/components/Submit/TagSelector";
import BinFile from "rom-patcher-js/rom-patcher-js/modules/BinFile.js";
+import Select from "@/components/Primitives/Select";
import BPS from "rom-patcher-js/rom-patcher-js/modules/RomPatcher.format.bps.js";
import { sha1Hex } from "@/utils/hash";
import { platformAccept, setDraftCovers, getDraftCovers, deleteDraftCovers } from "@/utils/idb";
@@ -733,17 +734,16 @@ export default function HackSubmitForm({
{!isDummy ? (
-
{ if ((newCoverFiles.length) > 0) return; setPlatform(e.target.value as any); setBaseRom(""); }}
+ onChange={(value) => { if ((newCoverFiles.length) > 0) return; setPlatform(value as any); setBaseRom(""); }}
disabled={newCoverFiles.length > 0}
- className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] focus:outline-none focus:ring-2 focus:ring-[var(--ring)] disabled:opacity-50"
- >
-
- {(["GB","GBC","GBA","NDS"] as const).map(p => (
-
- ))}
-
+ placeholder="Select platform"
+ options={(["GB","GBC","GBA","NDS"] as const).map(p => ({
+ value: p,
+ label: p,
+ }))}
+ />
) : (
{platform || ""}
)}
@@ -755,19 +755,16 @@ export default function HackSubmitForm({
{!isDummy ? (
-
setBaseRom(e.target.value)}
+ onChange={setBaseRom}
disabled={!platform}
- className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] focus:outline-none focus:ring-2 focus:ring-[var(--ring)] disabled:opacity-50"
- >
-
- {baseRoms.filter(r => !platform || r.platform === platform).map(({ id, name, region }) => (
-
- ))}
-
+ placeholder={platform ? "Select base rom" : "Select platform first"}
+ options={baseRoms.filter(r => !platform || r.platform === platform).map(({ id, name, region }) => ({
+ value: id,
+ label: `${name.replace('Pokémon ', '')} (${region})`,
+ }))}
+ />
) : (
{baseRoms.find(r=>r.id===baseRom)?.name || baseRom}
)}
@@ -776,16 +773,15 @@ export default function HackSubmitForm({
{!isDummy ? (
-
setLanguage(e.target.value)}
- className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] focus:outline-none focus:ring-2 focus:ring-[var(--ring)]"
- >
-
- {['English','Spanish','French','German','Italian','Portuguese','Japanese','Chinese','Korean','Other'].map(l => (
-
- ))}
-
+ onChange={setLanguage}
+ placeholder="Select language"
+ options={['English','Spanish','French','German','Italian','Portuguese','Japanese','Chinese','Korean','Other'].map(l => ({
+ value: l,
+ label: l,
+ }))}
+ />
) : (
{language}
)}
diff --git a/src/components/Primitives/Select.tsx b/src/components/Primitives/Select.tsx
new file mode 100644
index 0000000..93e0bbf
--- /dev/null
+++ b/src/components/Primitives/Select.tsx
@@ -0,0 +1,90 @@
+"use client";
+
+import React, { Fragment } from "react";
+import { Listbox, ListboxButton, ListboxOptions, ListboxOption, Transition } from "@headlessui/react";
+import { FiChevronDown, FiCheck } from "react-icons/fi";
+
+export interface SelectOption {
+ value: string;
+ label: string;
+ disabled?: boolean;
+}
+
+interface SelectProps {
+ value: string;
+ onChange: (value: string) => void;
+ options: SelectOption[];
+ placeholder?: string;
+ disabled?: boolean;
+ className?: string;
+ id?: string;
+ name?: string;
+}
+
+export default function Select({
+ value,
+ onChange,
+ options,
+ placeholder = "Select an option",
+ disabled = false,
+ className = "",
+ id,
+ name,
+}: SelectProps) {
+ const selectedOption = options.find((opt) => opt.value === value);
+
+ return (
+
+ {({ open }) => (
+
+
+
+ {selectedOption?.label || placeholder}
+
+
+
+
+
+ {name && }
+
+
+ {options.map((option) => (
+
+ `relative cursor-pointer select-none py-2 pl-10 pr-4 ${
+ focus ? "bg-black/5 dark:bg-white/10" : ""
+ } ${option.disabled ? "opacity-50 cursor-not-allowed" : ""}`
+ }
+ >
+ {({ selected }) => (
+ <>
+
+ {option.label}
+
+ {selected && (
+
+
+
+ )}
+ >
+ )}
+
+ ))}
+
+
+
+ )}
+
+ );
+}