From 9cabe879404f1ec05dbf4e65d55162b5573aeced Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Fri, 28 Nov 2025 20:30:23 +0900 Subject: (김준회) dynamic table - init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client-table/client-table-column-header.tsx | 179 +++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 components/client-table/client-table-column-header.tsx (limited to 'components/client-table/client-table-column-header.tsx') diff --git a/components/client-table/client-table-column-header.tsx b/components/client-table/client-table-column-header.tsx new file mode 100644 index 00000000..12dc57ac --- /dev/null +++ b/components/client-table/client-table-column-header.tsx @@ -0,0 +1,179 @@ +"use client" + +import * as React from "react" +import { Header } 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, +} from "lucide-react" +import { cn } from "@/lib/utils" +import { ClientTableFilter } from "./client-table-filter" + +interface ClientTableColumnHeaderProps + extends React.HTMLAttributes { + header: Header + enableReordering?: boolean +} + +export function ClientTableColumnHeader({ + header, + enableReordering = true, + className, + ...props +}: ClientTableColumnHeaderProps) { + const column = header.column + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ + id: header.id, + disabled: !enableReordering, + }) + + // -- 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() + if (isPinned === "left") { + style.left = `${column.getStart("left")}px` + style.position = "sticky" + style.zIndex = 20 + } else if (isPinned === "right") { + style.right = `${column.getAfter("right")}px` + style.position = "sticky" + style.zIndex = 20 + } + + // -- Handlers -- + const handleHide = () => column.toggleVisibility(false) + const handlePinLeft = () => column.pin("left") + const handlePinRight = () => column.pin("right") + const handleUnpin = () => column.pin(false) + + // -- Content -- + const content = ( + <> +
+ {flexRender(column.columnDef.header, header.getContext())} + {column.getCanSort() && ( + + {column.getIsSorted() === "desc" ? ( + + ) : column.getIsSorted() === "asc" ? ( + + ) : ( + + )} + + )} +
+ + {/* Resize Handle */} +
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() && } + + ) + + if (header.isPlaceholder) { + return ( + + {null} + + ) + } + + return ( + + + + {content} + + + + + + Hide Column + + + + + Pin Left + + + + Pin Right + + {isPinned && ( + + + Unpin + + )} + + + ) +} -- cgit v1.2.3