"use client" import * as React from "react" import { Header, Column } from "@tanstack/react-table" import { useSortable } from "@dnd-kit/sortable" import { CSS } from "@dnd-kit/utilities" import { flexRender } from "@tanstack/react-table" import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger, } from "@/components/ui/context-menu" import { ArrowDown, ArrowUp, ChevronsUpDown, EyeOff, PinOff, MoveLeft, MoveRight, Group, Ungroup, } from "lucide-react" import { cn } from "@/lib/utils" import { ClientTableFilter } from "../client-table/client-table-filter" interface ClientTableColumnHeaderProps extends React.HTMLAttributes { header: Header enableReordering?: boolean renderHeaderVisualFeedback?: (props: { column: Column isPinned: boolean | string isSorted: boolean | string isFiltered: boolean isGrouped: boolean }) => React.ReactNode } export function ClientTableColumnHeader({ header, enableReordering = true, renderHeaderVisualFeedback, className, ...props }: ClientTableColumnHeaderProps) { const column = header.column const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({ id: header.id, disabled: !enableReordering || column.getIsResizing(), }) // -- Styles -- const style: React.CSSProperties = { // Apply transform only if reordering is enabled and active transform: enableReordering ? CSS.Translate.toString(transform) : undefined, transition: enableReordering ? transition : undefined, width: header.getSize(), zIndex: isDragging ? 100 : 0, position: "relative", ...props.style, } // Pinning Styles const isPinned = column.getIsPinned() const isSorted = column.getIsSorted() const isFiltered = column.getFilterValue() !== undefined const isGrouped = column.getIsGrouped() if (isPinned === "left") { style.left = `${column.getStart("left")}px` style.position = "sticky" style.zIndex = 30 // Pinned columns needs to be higher than normal headers } else if (isPinned === "right") { style.right = `${column.getAfter("right")}px` style.position = "sticky" style.zIndex = 30 // Pinned columns needs to be higher than normal headers } // -- Handlers -- const handleHide = () => column.toggleVisibility(false) const handlePinLeft = () => column.pin("left") const handlePinRight = () => column.pin("right") const handleUnpin = () => column.pin(false) const handleToggleGrouping = () => column.toggleGrouping() // -- Content -- const content = ( <>
{flexRender(column.columnDef.header, header.getContext())} {column.getCanSort() && ( {column.getIsSorted() === "desc" ? ( ) : column.getIsSorted() === "asc" ? ( ) : ( )} )} {isGrouped && }
{/* Resize Handle */}
e.stopPropagation()} onClick={(e) => e.stopPropagation()} // Prevent sort trigger className={cn( "absolute right-0 top-0 h-full w-2 cursor-col-resize select-none touch-none z-10", "after:absolute after:right-0 after:top-0 after:h-full after:w-[1px] after:bg-border", // 시각적 구분선 "hover:bg-primary/20 hover:w-4 hover:-right-2", // 호버 시 클릭 영역 확장 header.column.getIsResizing() ? "bg-primary/50 w-1" : "bg-transparent" )} /> {/* Filter */} {column.getCanFilter() && } {/* Visual Feedback Indicators */} {renderHeaderVisualFeedback ? ( renderHeaderVisualFeedback({ column, isPinned, isSorted, isFiltered, isGrouped, }) ) : ( (isPinned || isFiltered || isGrouped) && (
{isPinned &&
} {isFiltered &&
} {isGrouped &&
}
) )} ) if (header.isPlaceholder) { return ( {null} ) } return ( {content} Hide Column {column.getCanGroup() && ( <> {isGrouped ? ( <> Ungroup ) : ( <> Group by {column.id} )} )} Pin Left Pin Right {isPinned && ( Unpin )} ) }