summaryrefslogtreecommitdiff
path: root/components/client-table/client-table-column-header.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'components/client-table/client-table-column-header.tsx')
-rw-r--r--components/client-table/client-table-column-header.tsx70
1 files changed, 63 insertions, 7 deletions
diff --git a/components/client-table/client-table-column-header.tsx b/components/client-table/client-table-column-header.tsx
index 12dc57ac..2d8e5bce 100644
--- a/components/client-table/client-table-column-header.tsx
+++ b/components/client-table/client-table-column-header.tsx
@@ -1,7 +1,7 @@
"use client"
import * as React from "react"
-import { Header } from "@tanstack/react-table"
+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"
@@ -20,19 +20,29 @@ import {
PinOff,
MoveLeft,
MoveRight,
+ Group,
+ Ungroup,
} from "lucide-react"
import { cn } from "@/lib/utils"
-import { ClientTableFilter } from "./client-table-filter"
+import { ClientTableFilter } from "../client-table/client-table-filter"
interface ClientTableColumnHeaderProps<TData, TValue>
extends React.HTMLAttributes<HTMLTableHeaderCellElement> {
header: Header<TData, TValue>
enableReordering?: boolean
+ renderHeaderVisualFeedback?: (props: {
+ column: Column<TData, TValue>
+ isPinned: boolean | string
+ isSorted: boolean | string
+ isFiltered: boolean
+ isGrouped: boolean
+ }) => React.ReactNode
}
export function ClientTableColumnHeader<TData, TValue>({
header,
enableReordering = true,
+ renderHeaderVisualFeedback,
className,
...props
}: ClientTableColumnHeaderProps<TData, TValue>) {
@@ -46,7 +56,7 @@ export function ClientTableColumnHeader<TData, TValue>({
isDragging,
} = useSortable({
id: header.id,
- disabled: !enableReordering,
+ disabled: !enableReordering || column.getIsResizing(),
})
// -- Styles --
@@ -62,14 +72,18 @@ export function ClientTableColumnHeader<TData, TValue>({
// 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 = 20
+ 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 = 20
+ style.zIndex = 30 // Pinned columns needs to be higher than normal headers
}
// -- Handlers --
@@ -77,6 +91,7 @@ export function ClientTableColumnHeader<TData, TValue>({
const handlePinLeft = () => column.pin("left")
const handlePinRight = () => column.pin("right")
const handleUnpin = () => column.pin(false)
+ const handleToggleGrouping = () => column.toggleGrouping()
// -- Content --
const content = (
@@ -100,12 +115,14 @@ export function ClientTableColumnHeader<TData, TValue>({
)}
</span>
)}
+ {isGrouped && <Group className="h-4 w-4 text-blue-500" />}
</div>
{/* Resize Handle */}
<div
onMouseDown={header.getResizeHandler()}
onTouchStart={header.getResizeHandler()}
+ onPointerDown={(e) => 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",
@@ -117,6 +134,25 @@ export function ClientTableColumnHeader<TData, TValue>({
{/* Filter */}
{column.getCanFilter() && <ClientTableFilter column={column} />}
+
+ {/* Visual Feedback Indicators */}
+ {renderHeaderVisualFeedback ? (
+ renderHeaderVisualFeedback({
+ column,
+ isPinned,
+ isSorted,
+ isFiltered,
+ isGrouped,
+ })
+ ) : (
+ (isPinned || isFiltered || isGrouped) && (
+ <div className="absolute top-0.5 right-1 flex gap-1 z-10 pointer-events-none">
+ {isPinned && <div className="h-1.5 w-1.5 rounded-full bg-blue-500" />}
+ {isFiltered && <div className="h-1.5 w-1.5 rounded-full bg-yellow-500" />}
+ {isGrouped && <div className="h-1.5 w-1.5 rounded-full bg-green-500" />}
+ </div>
+ )
+ )}
</>
)
@@ -141,9 +177,9 @@ export function ClientTableColumnHeader<TData, TValue>({
colSpan={header.colSpan}
style={style}
className={cn(
- "border-b px-4 py-2 text-left text-sm font-medium bg-muted group",
+ "border-b px-4 py-2 text-left text-sm font-medium bg-muted group transition-colors",
isDragging ? "opacity-50 bg-accent" : "",
- isPinned ? "bg-muted shadow-[0_0_10px_rgba(0,0,0,0.1)]" : "",
+ isPinned ? "shadow-[0_0_10px_rgba(0,0,0,0.1)]" : "",
className
)}
{...attributes}
@@ -158,6 +194,26 @@ export function ClientTableColumnHeader<TData, TValue>({
<EyeOff className="mr-2 h-4 w-4" />
Hide Column
</ContextMenuItem>
+
+ {column.getCanGroup() && (
+ <>
+ <ContextMenuSeparator />
+ <ContextMenuItem onClick={handleToggleGrouping}>
+ {isGrouped ? (
+ <>
+ <Ungroup className="mr-2 h-4 w-4" />
+ Ungroup
+ </>
+ ) : (
+ <>
+ <Group className="mr-2 h-4 w-4" />
+ Group by {column.id}
+ </>
+ )}
+ </ContextMenuItem>
+ </>
+ )}
+
<ContextMenuSeparator />
<ContextMenuItem onClick={handlePinLeft}>
<MoveLeft className="mr-2 h-4 w-4" />