diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/category-selector.tsx | 160 |
1 files changed, 109 insertions, 51 deletions
diff --git a/src/components/category-selector.tsx b/src/components/category-selector.tsx index adb6582..8e6c5a6 100644 --- a/src/components/category-selector.tsx +++ b/src/components/category-selector.tsx @@ -1,7 +1,7 @@ import { ChevronDown } from 'lucide-react' import { CategoryIcon } from '@/app/groups/[groupId]/expenses/category-icon' -import { Button } from '@/components/ui/button' +import { Button, ButtonProps } from '@/components/ui/button' import { Command, CommandEmpty, @@ -9,13 +9,15 @@ import { CommandInput, CommandItem, } from '@/components/ui/command' +import { Drawer, DrawerContent, DrawerTrigger } from '@/components/ui/drawer' import { Popover, PopoverContent, PopoverTrigger, } from '@/components/ui/popover' +import { useMediaQuery } from '@/lib/hooks' import { Category } from '@prisma/client' -import { useState } from 'react' +import { forwardRef, useState } from 'react' type Props = { categories: Category[] @@ -30,7 +32,57 @@ export function CategorySelector({ }: Props) { const [open, setOpen] = useState(false) const [value, setValue] = useState<number>(defaultValue) + const isDesktop = useMediaQuery('(min-width: 768px)') + const selectedCategory = + categories.find((category) => category.id === value) ?? categories[0] + + if (isDesktop) { + return ( + <Popover open={open} onOpenChange={setOpen}> + <PopoverTrigger asChild> + <CategoryButton category={selectedCategory} open={open} /> + </PopoverTrigger> + <PopoverContent className="p-0" align="start"> + <CategoryCommand + categories={categories} + onValueChange={(id) => { + setValue(id) + onValueChange(id) + setOpen(false) + }} + /> + </PopoverContent> + </Popover> + ) + } + + return ( + <Drawer open={open} onOpenChange={setOpen}> + <DrawerTrigger asChild> + <CategoryButton category={selectedCategory} open={open} /> + </DrawerTrigger> + <DrawerContent className="p-0"> + <CategoryCommand + categories={categories} + onValueChange={(id) => { + setValue(id) + onValueChange(id) + setOpen(false) + }} + /> + </DrawerContent> + </Drawer> + ) +} + +function CategoryCommand({ + categories, + onValueChange, +}: { + categories: Category[] + onValueChange: (categoryId: Category['id']) => void +}) { const categoriesByGroup = categories.reduce<Record<string, Category[]>>( (acc, category) => ({ ...acc, @@ -39,56 +91,62 @@ export function CategorySelector({ {}, ) - const selectedCategory = - categories.find((category) => category.id === value) ?? categories[0] + return ( + <Command> + <CommandInput placeholder="Search category..." className="text-base" /> + <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]) + onValueChange(id) + }} + > + <CategoryLabel category={category} /> + </CommandItem> + ))} + </CommandGroup> + ), + )} + </div> + </Command> + ) +} + +type CategoryButtonProps = { + category: Category + open: boolean +} +const CategoryButton = forwardRef<HTMLButtonElement, CategoryButtonProps>( + ({ category, open, ...props }: ButtonProps & CategoryButtonProps, ref) => { + return ( + <Button + variant="outline" + role="combobox" + aria-expanded={open} + className="flex w-full justify-between" + ref={ref} + {...props} + > + <CategoryLabel category={category} /> + <ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" /> + </Button> + ) + }, +) +CategoryButton.displayName = 'CategoryButton' +function CategoryLabel({ category }: { category: Category }) { 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> + <div className="flex items-center gap-3"> + <CategoryIcon category={category} className="w-4 h-4" /> + {category.name} + </div> ) } |
