Add Combobox

This commit is contained in:
Kalle
2022-05-31 22:31:29 +03:00
parent e4526e8d82
commit 9bc252b950
7 changed files with 120 additions and 14 deletions

View File

@@ -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 (
<HeadlessCombobox value="" onChange={onChange}>
<HeadlessCombobox.Input
onChange={(event) => setQuery(event.target.value)}
placeholder={placeholder}
className="combobox-input"
name={inputName}
/>
<HeadlessCombobox.Options
className={clsx("combobox-options", {
invisible: filteredOptions.length === 0,
})}
>
{filteredOptions.map((option) => (
<HeadlessCombobox.Option
key={option}
value={option}
as={React.Fragment}
>
{({ active }) => (
<li className={clsx("combobox-item", { active })}>{option}</li>
)}
</HeadlessCombobox.Option>
))}
</HeadlessCombobox.Options>
</HeadlessCombobox>
);
}

View File

@@ -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() {
<Button type="submit" data-cy="submit-button">
Submit
</Button>
<Button
onClick={() => navigate(PLUS_SUGGESTIONS_PAGE)}
<LinkButton
to={PLUS_SUGGESTIONS_PAGE}
variant="minimal-destructive"
tiny
>
Cancel
</Button>
</LinkButton>
</div>
</Form>
</Dialog>

View File

@@ -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() {
<div className="stack md">
<h2 className="plus__modal-title">Adding a new suggestion</h2>
<div>
<label>Tier</label>
<select className="plus__modal-select">
<label htmlFor="tier">Tier</label>
<select id="tier" name="tier" className="plus__modal-select">
{PLUS_TIERS.filter((tier) => {
invariant(user?.plusTier);
return tier >= user.plusTier;
@@ -42,18 +42,27 @@ export default function PlusNewSuggestionModalPage() {
))}
</select>
</div>
<div>
<label htmlFor="user">User</label>
<Combobox
options={["Seppo", "Heppo"]}
onChange={(val) => console.log(val)}
inputName="user"
placeholder="Sendou#0043"
/>
</div>
<CommentTextarea />
<div className="plus__modal-buttons">
<Button type="submit" data-cy="submit-button">
Submit
</Button>
<Button
onClick={() => navigate(PLUS_SUGGESTIONS_PAGE)}
<LinkButton
to={PLUS_SUGGESTIONS_PAGE}
variant="minimal-destructive"
tiny
>
Cancel
</Button>
</LinkButton>
</div>
</div>
</Dialog>

View File

@@ -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");

View File

@@ -303,7 +303,7 @@ select {
/* TODO: Get color from CSS var */
background-image: url('data:image/svg+xml;utf8,<svg width="1rem" color="rgb(255 255 255 / 55%)" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /></svg>');
background-position-x: 95%;
background-position-x: 92%;
background-position-y: 50%;
background-repeat: no-repeat;
border-radius: var(--rounded);

14
package-lock.json generated
View File

@@ -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",

View File

@@ -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",