summaryrefslogtreecommitdiff
path: root/components/data-table/data-table-faceted-filter.tsx
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-03-25 15:55:45 +0900
committerjoonhoekim <26rote@gmail.com>2025-03-25 15:55:45 +0900
commit1a2241c40e10193c5ff7008a7b7b36cc1d855d96 (patch)
tree8a5587f10ca55b162d7e3254cb088b323a34c41b /components/data-table/data-table-faceted-filter.tsx
initial commit
Diffstat (limited to 'components/data-table/data-table-faceted-filter.tsx')
-rw-r--r--components/data-table/data-table-faceted-filter.tsx151
1 files changed, 151 insertions, 0 deletions
diff --git a/components/data-table/data-table-faceted-filter.tsx b/components/data-table/data-table-faceted-filter.tsx
new file mode 100644
index 00000000..d89ef03f
--- /dev/null
+++ b/components/data-table/data-table-faceted-filter.tsx
@@ -0,0 +1,151 @@
+"use client"
+
+import type { Option } from "@/types/table"
+import type { Column } from "@tanstack/react-table"
+import { Check, PlusCircle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+ CommandSeparator,
+} from "@/components/ui/command"
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover"
+import { Separator } from "@/components/ui/separator"
+
+interface DataTableFacetedFilterProps<TData, TValue> {
+ column?: Column<TData, TValue>
+ title?: string
+ options: Option[]
+}
+
+export function DataTableFacetedFilter<TData, TValue>({
+ column,
+ title,
+ options,
+}: DataTableFacetedFilterProps<TData, TValue>) {
+ const unknownValue = column?.getFilterValue()
+ const selectedValues = new Set(
+ Array.isArray(unknownValue) ? unknownValue : []
+ )
+
+ return (
+ <Popover>
+ <PopoverTrigger asChild>
+ <Button variant="outline" size="sm" className="h-8 border-dashed">
+ <PlusCircle className="mr-2 size-4" />
+ {title}
+ {selectedValues?.size > 0 && (
+ <>
+ <Separator orientation="vertical" className="mx-2 h-4" />
+ <Badge
+ variant="secondary"
+ className="rounded-sm px-1 font-normal lg:hidden"
+ >
+ {selectedValues.size}
+ </Badge>
+ <div className="hidden space-x-1 lg:flex">
+ {selectedValues.size > 2 ? (
+ <Badge
+ variant="secondary"
+ className="rounded-sm px-1 font-normal"
+ >
+ {selectedValues.size} selected
+ </Badge>
+ ) : (
+ options
+ .filter((option) => selectedValues.has(option.value))
+ .map((option) => (
+ <Badge
+ variant="secondary"
+ key={option.value}
+ className="rounded-sm px-1 font-normal"
+ >
+ {option.label}
+ </Badge>
+ ))
+ )}
+ </div>
+ </>
+ )}
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent className="w-[12.5rem] p-0" align="start">
+ <Command>
+ <CommandInput placeholder={title} />
+ <CommandList className="max-h-full">
+ <CommandEmpty>No results found.</CommandEmpty>
+ <CommandGroup className="max-h-[18.75rem] overflow-y-auto overflow-x-hidden">
+ {options.map((option) => {
+ const isSelected = selectedValues.has(option.value)
+
+ return (
+ <CommandItem
+ key={option.value}
+ onSelect={() => {
+ if (isSelected) {
+ selectedValues.delete(option.value)
+ } else {
+ selectedValues.add(option.value)
+ }
+ const filterValues = Array.from(selectedValues)
+ column?.setFilterValue(
+ filterValues.length ? filterValues : undefined
+ )
+ }}
+ >
+ <div
+ className={cn(
+ "mr-2 flex size-4 items-center justify-center rounded-sm border border-primary",
+ isSelected
+ ? "bg-primary text-primary-foreground"
+ : "opacity-50 [&_svg]:invisible"
+ )}
+ >
+ <Check className="size-4" aria-hidden="true" />
+ </div>
+ {option.icon && (
+ <option.icon
+ className="mr-2 size-4 text-muted-foreground"
+ aria-hidden="true"
+ />
+ )}
+ <span>{option.label}</span>
+ {option.count && (
+ <span className="ml-auto flex size-4 items-center justify-center font-mono text-xs">
+ {option.count}
+ </span>
+ )}
+ </CommandItem>
+ )
+ })}
+ </CommandGroup>
+ {selectedValues.size > 0 && (
+ <>
+ <CommandSeparator />
+ <CommandGroup>
+ <CommandItem
+ onSelect={() => column?.setFilterValue(undefined)}
+ className="justify-center text-center"
+ >
+ Clear filters
+ </CommandItem>
+ </CommandGroup>
+ </>
+ )}
+ </CommandList>
+ </Command>
+ </PopoverContent>
+ </Popover>
+ )
+}