Skip to content

Commit

Permalink
WIP on Combobox
Browse files Browse the repository at this point in the history
  • Loading branch information
matt-aitken committed Apr 28, 2024
1 parent ae93d97 commit f0a9b2e
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 0 deletions.
36 changes: 36 additions & 0 deletions apps/webapp/app/components/primitives/ComboBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as Ariakit from "@ariakit/react";
import { ComponentPropsWithRef, ComponentPropsWithoutRef, ElementRef, forwardRef } from "react";
import { cn } from "~/utils/cn";

const variants = {
"secondary/small": {
box: "text-xs h-6 bg-tertiary border border-tertiary group-hover:text-text-bright hover:border-charcoal-600 pr-2 pl-1.5 rounded-sm outline-none focus-visible:outline-none",
},
medium: {
box: "text-sm h-8 bg-tertiary border border-tertiary hover:border-charcoal-600 px-2.5",
},
minimal: { box: "text-xs h-6 bg-transparent hover:bg-tertiary pl-1.5 pr-2" },
};

type Variant = keyof typeof variants;

export const ComboboxProvider = Ariakit.ComboboxProvider;

type ComboboxProps = {
variant?: Variant;
width?: "content" | "full";
};

export const Combobox = forwardRef<
ElementRef<typeof Ariakit.Combobox>,
ComponentPropsWithoutRef<typeof Ariakit.Combobox> & ComboboxProps
>(({ variant = "secondary/small", width = "content", className, ...props }, ref) => {
const box = variants[variant].box;
return <Ariakit.Combobox ref={ref} className={cn(box, className)} {...props} />;
});

export const ComboboxGroup = Ariakit.ComboboxGroup;
export const ComboboxItem = Ariakit.ComboboxItem;
export const ComboboxItemCheck = Ariakit.ComboboxItemCheck;
export const ComboboxLabel = Ariakit.ComboboxLabel;
export const ComboboxPopover = Ariakit.ComboboxPopover;
110 changes: 110 additions & 0 deletions apps/webapp/app/routes/storybook.combobox/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { useState, useTransition } from "react";
import {
Combobox,
ComboboxItem,
ComboboxItemCheck,
ComboboxPopover,
ComboboxProvider,
} from "~/components/primitives/ComboBox";
import { Header1, Header2 } from "~/components/primitives/Headers";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "~/components/primitives/Select";
import { useTextFilter } from "~/hooks/useTextFilter";

const items = [
"Apple",
"Banana",
"Cherry",
"Date",
"Elderberry",
"Fig",
"Grape",
"Honeydew",
"Kiwi",
"Lemon",
"Mango",
"Nectarine",
"Orange",
"Peach",
"Quince",
"Raspberry",
"Strawberry",
"Tangerine",
"Ugli fruit",
"Vanilla bean",
"Watermelon",
"Ximenia",
"Yuzu",
"Zucchini",
];

export default function Story() {
return (
<div className="p-20">
<div className="flex gap-8">
<div className="flex flex-col">
<Header1 className="mb-4">Variants</Header1>
<Header2 className="my-4">size=small width=content</Header2>
<TaskFilter />
</div>
<div>
<SelectGroup>
<Select name="fruit" defaultValue={items[0]}>
<SelectTrigger>
<SelectValue placeholder="Fruit" />
</SelectTrigger>
<SelectContent>
{items.map((item) => (
<SelectItem key={item} value={item}>
{item}
</SelectItem>
))}
</SelectContent>
</Select>
</SelectGroup>
</div>
</div>
</div>
);
}

function TaskFilter() {
const [isPending, startTransition] = useTransition();
const [selectedValues, setSelectedValues] = useState<string[]>([]);

const { filterText, setFilterText, filteredItems } = useTextFilter({
items,
filter: (item, filterText) => {
return item.toLowerCase().includes(filterText.toLowerCase());
},
});

return (
<ComboboxProvider
selectedValue={selectedValues}
setSelectedValue={setSelectedValues}
setValue={(value) => {
startTransition(() => {
setFilterText(value);
});
}}
>
<Combobox placeholder="e.g., Apple, Burger" />
<ComboboxPopover sameWidth gutter={8} aria-busy={isPending}>
{filteredItems.map((item) => (
<ComboboxItem key={item} value={item} focusOnHover>
<ComboboxItemCheck />
{item}
</ComboboxItem>
))}
{!filteredItems.length && <div className="no-results">No results found</div>}
</ComboboxPopover>
</ComboboxProvider>
);
}
4 changes: 4 additions & 0 deletions apps/webapp/app/routes/storybook/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ const stories: Story[] = [
name: "Select",
slug: "select",
},
{
name: "ComboBox",
slug: "combobox",
},
{
name: "Popover",
slug: "popover",
Expand Down
1 change: 1 addition & 0 deletions apps/webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"/public/build"
],
"dependencies": {
"@ariakit/react": "^0.4.6",
"@aws-sdk/client-sqs": "^3.445.0",
"@codemirror/autocomplete": "^6.3.1",
"@codemirror/commands": "^6.1.2",
Expand Down
56 changes: 56 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit f0a9b2e

Please sign in to comment.