diff options
Diffstat (limited to 'components/data-table/data-table-view-options.tsx')
| -rw-r--r-- | components/data-table/data-table-view-options.tsx | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/components/data-table/data-table-view-options.tsx b/components/data-table/data-table-view-options.tsx new file mode 100644 index 00000000..c55617ec --- /dev/null +++ b/components/data-table/data-table-view-options.tsx @@ -0,0 +1,191 @@ +"use client" + +import * as React from "react" +import { RowData, type Table } from "@tanstack/react-table" +import { + Check, + ChevronsUpDown, + GripVertical, + Settings2, +} from "lucide-react" + +import { cn, toSentenceCase } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" + +// Sortable +import { + Sortable, + SortableItem, + SortableDragHandle, +} from "@/components/ui/sortable" + + +/** + * ViewOptionsProps: + * - table: TanStack Table instance + */ +interface DataTableViewOptionsProps<TData> { + table: Table<TData> +} + +declare module "@tanstack/table-core" { + interface ColumnMeta<TData extends RowData, TValue> { + excelHeader?: string + group?: string + type?: string + // ...anything else you want + } +} +/** + * DataTableViewOptions: + * - Renders a Popover with hideable columns + * - Lets user reorder columns (drag & drop) + toggle visibility + */ +export function DataTableViewOptions<TData>({ + table, +}: DataTableViewOptionsProps<TData>) { + const triggerRef = React.useRef<HTMLButtonElement>(null) + + // 1) Identify columns that can be hidden + const hideableCols = React.useMemo(() => { + + + return table + .getAllLeafColumns() + .filter((col) => col.getCanHide()) + }, [table]) + + + // 2) local state for "columnOrder" (just the ID of hideable columns) + // We'll reorder these with drag & drop + const [columnOrder, setColumnOrder] = React.useState<string[]>(() => + hideableCols.map((c) => c.id) + ) + + // 3) onMove: when user finishes drag + // - update local `columnOrder` only (no table.setColumnOrder yet) + const handleMove = React.useCallback( + ({ activeIndex, overIndex }: { activeIndex: number; overIndex: number }) => { + setColumnOrder((prev) => { + const newOrder = [...prev] + const [removed] = newOrder.splice(activeIndex, 1) + newOrder.splice(overIndex, 0, removed) + return newOrder + }) + }, + [] + ) + + // 4) After local state changes, reflect in tanstack table + // - We do this in useEffect to avoid "update a different component" error + React.useEffect(() => { + // Also consider "non-hideable" columns, if any, to keep them in original positions + const nonHideable = table + .getAllColumns() + .filter((col) => !hideableCols.some((hc) => hc.id === col.id)) + .map((c) => c.id) + + // e.g. place nonHideable at the front, then our local hideable order + const finalOrder = [...nonHideable, ...columnOrder] + + // Now we set the table's official column order + table.setColumnOrder(finalOrder) + }, [columnOrder, hideableCols, table]) + + + return ( + <Popover modal> + <PopoverTrigger asChild> + <Button + ref={triggerRef} + aria-label="Toggle columns" + variant="outline" + role="combobox" + size="sm" + className="gap-2" + > + <Settings2 className="size-4" /> + <span className="hidden sm:inline">View</span> + <ChevronsUpDown className="ml-auto size-4 shrink-0 opacity-50 hidden sm:inline" /> + </Button> + </PopoverTrigger> + + <PopoverContent + align="end" + className="w-44 p-0" + onCloseAutoFocus={() => triggerRef.current?.focus()} + > + <Command> + <CommandInput placeholder="Search columns..." /> + <CommandList> + <CommandEmpty>No columns found.</CommandEmpty> + + <CommandGroup> + {/** + * 5) Sortable: we pass an array of { id: string } from `columnOrder`, + * so we can reorder them with drag & drop + */} + <Sortable + value={columnOrder.map((id) => ({ id }))} + onMove={handleMove} + > + {columnOrder.map((colId) => { + // find column instance + const column = hideableCols.find((c) => c.id === colId) + + + if (!column) return null + + return ( + <SortableItem key={colId} value={colId} asChild> + <CommandItem + onSelect={() => + column.toggleVisibility(!column.getIsVisible()) + } + > + {/* Drag handle on the left */} + <SortableDragHandle + variant="outline" + size="icon" + className="mr-2 size-5 shrink-0 rounded cursor-grab active:cursor-grabbing" + > + <GripVertical className="size-3.5" aria-hidden="true" /> + </SortableDragHandle> + + {/* label */} + <span className="truncate"> + {column?.columnDef?.meta?.excelHeader} + </span> + + {/* check if visible */} + <Check + className={cn( + "ml-auto size-4 shrink-0", + column.getIsVisible() ? "opacity-100" : "opacity-0" + )} + /> + </CommandItem> + </SortableItem> + ) + })} + </Sortable> + </CommandGroup> + </CommandList> + </Command> + </PopoverContent> + </Popover> + ) +}
\ No newline at end of file |
