aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/category-selector.tsx
diff options
context:
space:
mode:
authorSebastien Castiel <sebastien@castiel.me>2024-01-17 12:07:03 -0500
committerSebastien Castiel <sebastien@castiel.me>2024-01-17 12:07:03 -0500
commit92156b29cbcc496af705982bdf18dd558e765acb (patch)
treec58086e5f2e5f5a40f6c32cbc203641d34ae935e /src/components/category-selector.tsx
parentc4de3f605c2c6ffa505ad098c72292e706359c0e (diff)
Use combobox for category selector
Diffstat (limited to 'src/components/category-selector.tsx')
-rw-r--r--src/components/category-selector.tsx94
1 files changed, 94 insertions, 0 deletions
diff --git a/src/components/category-selector.tsx b/src/components/category-selector.tsx
new file mode 100644
index 0000000..adb6582
--- /dev/null
+++ b/src/components/category-selector.tsx
@@ -0,0 +1,94 @@
+import { ChevronDown } from 'lucide-react'
+
+import { CategoryIcon } from '@/app/groups/[groupId]/expenses/category-icon'
+import { Button } from '@/components/ui/button'
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+} from '@/components/ui/command'
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from '@/components/ui/popover'
+import { Category } from '@prisma/client'
+import { useState } from 'react'
+
+type Props = {
+ categories: Category[]
+ onValueChange: (categoryId: Category['id']) => void
+ defaultValue: Category['id']
+}
+
+export function CategorySelector({
+ categories,
+ onValueChange,
+ defaultValue,
+}: Props) {
+ const [open, setOpen] = useState(false)
+ const [value, setValue] = useState<number>(defaultValue)
+
+ const categoriesByGroup = categories.reduce<Record<string, Category[]>>(
+ (acc, category) => ({
+ ...acc,
+ [category.grouping]: [...(acc[category.grouping] ?? []), category],
+ }),
+ {},
+ )
+
+ const selectedCategory =
+ categories.find((category) => category.id === value) ?? categories[0]
+
+ return (
+ <Popover open={open} onOpenChange={setOpen}>
+ <PopoverTrigger asChild>
+ <Button
+ variant="outline"
+ role="combobox"
+ aria-expanded={open}
+ className="flex w-full justify-between"
+ >
+ <div className="flex items-center gap-3">
+ <CategoryIcon category={selectedCategory} className="w-4 h-4" />
+ {selectedCategory.name}
+ </div>
+ <ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent className="p-0" align="start">
+ <Command>
+ <CommandInput placeholder="Search category..." />
+ <CommandEmpty>No category found.</CommandEmpty>
+ <div className="w-full max-h-[300px] overflow-y-auto">
+ {Object.entries(categoriesByGroup).map(
+ ([group, groupCategories], index) => (
+ <CommandGroup key={index} heading={group}>
+ {groupCategories.map((category) => (
+ <CommandItem
+ key={category.id}
+ value={`${category.id} ${category.grouping} ${category.name}`}
+ onSelect={(currentValue) => {
+ const id = Number(currentValue.split(' ')[0])
+ setValue(id)
+ onValueChange(id)
+ setOpen(false)
+ }}
+ >
+ <div className="flex items-center gap-3">
+ <CategoryIcon category={category} className="w-4 h-4" />
+ {category.name}
+ </div>
+ </CommandItem>
+ ))}
+ </CommandGroup>
+ ),
+ )}
+ </div>
+ </Command>
+ </PopoverContent>
+ </Popover>
+ )
+}