diff --git a/app/components/Combobox.tsx b/app/components/Combobox.tsx
new file mode 100644
index 000000000..887ce0fff
--- /dev/null
+++ b/app/components/Combobox.tsx
@@ -0,0 +1,58 @@
+import { Combobox as HeadlessCombobox } from "@headlessui/react";
+import * as React from "react";
+import Fuse from "fuse.js";
+import clsx from "clsx";
+
+const MAX_RESULTS_SHOWN = 6;
+
+export function Combobox({
+ options,
+ onChange,
+ inputName,
+ placeholder,
+}: {
+ options: string[] | readonly string[];
+ onChange: (value: string) => void;
+ inputName: string;
+ placeholder: string;
+}) {
+ const [query, setQuery] = React.useState("");
+
+ const filteredOptions = (() => {
+ if (!query) return [];
+
+ const fuse = new Fuse(options);
+ return fuse
+ .search(query)
+ .slice(0, MAX_RESULTS_SHOWN)
+ .map((res) => res.item);
+ })();
+
+ return (
+
+ setQuery(event.target.value)}
+ placeholder={placeholder}
+ className="combobox-input"
+ name={inputName}
+ />
+
+ {filteredOptions.map((option) => (
+
+ {({ active }) => (
+ {option}
+ )}
+
+ ))}
+
+
+ );
+}
diff --git a/app/routes/plus/suggestions/comment.$tier.$userId.tsx b/app/routes/plus/suggestions/comment.$tier.$userId.tsx
index ec7ca30b1..e425f6ac5 100644
--- a/app/routes/plus/suggestions/comment.$tier.$userId.tsx
+++ b/app/routes/plus/suggestions/comment.$tier.$userId.tsx
@@ -1,9 +1,8 @@
import type { ActionFunction } from "@remix-run/node";
import { redirect } from "@remix-run/node";
import { Form, useMatches, useNavigate, useParams } from "@remix-run/react";
-import * as React from "react";
import { z } from "zod";
-import { Button } from "~/components/Button";
+import { Button, LinkButton } from "~/components/Button";
import { Dialog } from "~/components/Dialog";
import { Redirect } from "~/components/Redirect";
import { PlUS_SUGGESTION_COMMENT_MAX_LENGTH } from "~/constants";
@@ -96,13 +95,13 @@ export default function PlusCommentModalPage() {
-
+
diff --git a/app/routes/plus/suggestions/new.tsx b/app/routes/plus/suggestions/new.tsx
index 6889f4b8d..7443e1302 100644
--- a/app/routes/plus/suggestions/new.tsx
+++ b/app/routes/plus/suggestions/new.tsx
@@ -1,5 +1,5 @@
-import { useMatches, useNavigate } from "@remix-run/react";
-import { Button } from "~/components/Button";
+import { useMatches } from "@remix-run/react";
+import { Button, LinkButton } from "~/components/Button";
import { Dialog } from "~/components/Dialog";
import { Redirect } from "~/components/Redirect";
import { useUser } from "~/hooks/useUser";
@@ -10,9 +10,9 @@ import * as React from "react";
import { Label } from "~/components/Label";
import { PlUS_SUGGESTION_COMMENT_MAX_LENGTH, PLUS_TIERS } from "~/constants";
import invariant from "tiny-invariant";
+import { Combobox } from "~/components/Combobox";
export default function PlusNewSuggestionModalPage() {
- const navigate = useNavigate();
const user = useUser();
const matches = useMatches();
const data = matches.at(-2)!.data as PlusSuggestionsLoaderData;
@@ -32,8 +32,8 @@ export default function PlusNewSuggestionModalPage() {
Adding a new suggestion
-
-
+
+
+ console.log(val)}
+ inputName="user"
+ placeholder="Sendou#0043"
+ />
+
-
+
diff --git a/app/styles/common.css b/app/styles/common.css
index 540cf1eec..e70b77c1c 100644
--- a/app/styles/common.css
+++ b/app/styles/common.css
@@ -60,6 +60,31 @@
}
}
+.combobox-input {
+ width: 12rem;
+}
+
+.combobox-options {
+ position: absolute;
+ width: 12rem;
+ margin-top: var(--s-2);
+ background-color: var(--bg-darker);
+ border-radius: var(--rounded);
+ color: var(--text);
+ font-size: var(--fonts-sm);
+ padding-block: var(--s-3);
+ padding-inline: 0;
+}
+
+.combobox-item {
+ padding: var(--s-1) var(--s-3);
+ list-style: none;
+}
+
+.combobox-item.active {
+ background-color: var(--theme-transparent);
+}
+
.avatar {
background-color: var(--bg-lighter);
background-image: url("/svg/background-pattern.svg");
diff --git a/app/styles/global.css b/app/styles/global.css
index fb62d0d0b..09ee30241 100644
--- a/app/styles/global.css
+++ b/app/styles/global.css
@@ -303,7 +303,7 @@ select {
/* TODO: Get color from CSS var */
background-image: url('data:image/svg+xml;utf8,');
- background-position-x: 95%;
+ background-position-x: 92%;
background-position-y: 50%;
background-repeat: no-repeat;
border-radius: var(--rounded);
diff --git a/package-lock.json b/package-lock.json
index 628890a82..4810ced59 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,7 @@
"better-sqlite3": "^7.5.3",
"clsx": "^1.1.1",
"countries-list": "^2.6.1",
+ "fuse.js": "^6.6.2",
"randomcolor": "^0.6.2",
"react": "^18.1.0",
"react-dom": "^18.1.0",
@@ -6714,6 +6715,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/fuse.js": {
+ "version": "6.6.2",
+ "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.6.2.tgz",
+ "integrity": "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/gauge": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
@@ -19072,6 +19081,11 @@
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="
},
+ "fuse.js": {
+ "version": "6.6.2",
+ "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.6.2.tgz",
+ "integrity": "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA=="
+ },
"gauge": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
diff --git a/package.json b/package.json
index 0d7e13fcc..f54428009 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
"better-sqlite3": "^7.5.3",
"clsx": "^1.1.1",
"countries-list": "^2.6.1",
+ "fuse.js": "^6.6.2",
"randomcolor": "^0.6.2",
"react": "^18.1.0",
"react-dom": "^18.1.0",