Skip to main content
Glama
ColumnHeader.tsx7.61 kB
import { CaretUpIcon, QuestionMarkCircledIcon, DragHandleDots2Icon, } from "@radix-ui/react-icons"; import classNames from "classnames"; import { GenericDocument } from "convex/server"; import { HeaderGroup } from "react-table"; import { useSortable } from "@dnd-kit/sortable"; import { useRef, useState, RefObject } from "react"; import omit from "lodash/omit"; import { useContextMenuTrigger } from "@common/features/data/lib/useContextMenuTrigger"; import { useTableDensity } from "@common/features/data/lib/useTableDensity"; import { Checkbox } from "@ui/Checkbox"; import { identifierNeedsEscape } from "@common/features/data/lib/helpers"; import { emptyColumnName } from "@common/features/data/components/Table/utils/useDataColumns"; import { DataCellProps } from "@common/features/data/components/Table/DataCell/DataCell"; import { columnWidthToString } from "@common/features/data/components/Table/DataRow"; import { Tooltip } from "@ui/Tooltip"; import { cn } from "@ui/cn"; import { documentValidatorForTable } from "@common/features/data/components/Table/utils/validators"; import { Button } from "@ui/Button"; import { ValidatorTooltip } from "./ValidatorTooltip"; type ColumnHeaderProps = { column: HeaderGroup<GenericDocument>; columnIndex: number; allRowsSelected: boolean | "indeterminate"; hasFilters: boolean; isSelectionExhaustive: boolean; toggleAll: () => void; isResizingColumn?: string; isLastColumn: boolean; openContextMenu: DataCellProps["onOpenContextMenu"]; sort?: "asc" | "desc"; activeSchema: any | null; tableName: string; tableContainerRef: RefObject<HTMLDivElement>; }; export function ColumnHeader({ column, columnIndex, allRowsSelected = false, hasFilters, isSelectionExhaustive, toggleAll, isResizingColumn, isLastColumn, openContextMenu, sort, activeSchema, tableName, tableContainerRef, }: ColumnHeaderProps) { const canDragOrDrop = columnIndex !== 0 && !isResizingColumn; const headerNode = useRef<HTMLDivElement | null>(null); const columnName = column.Header as string; const columnId = column.id; const { attributes, listeners, setNodeRef, isDragging, isOver, active } = useSortable({ id: columnId, disabled: !canDragOrDrop, }); // Always drop to the right of the hovered column const direction = isOver && !isDragging && active && active.id !== columnId ? "right" : undefined; const isHovering = isOver && !isDragging && active?.id !== columnId; useContextMenuTrigger( headerNode, (pos) => openContextMenu(pos, null, { column: columnName, value: undefined, }), () => {}, ); const { densityValues } = useTableDensity(); const width = columnWidthToString(column.getHeaderProps().style?.width); // Get the validator information for the tooltip const documentValidator = activeSchema && documentValidatorForTable(activeSchema, tableName); const fieldSchema = documentValidator?.type === "object" ? documentValidator.value[columnName] : undefined; const [isHovered, setIsHovered] = useState(false); return ( <div key={column.getHeaderProps().key} {...omit( column.getHeaderProps({ style: { width, height: densityValues.height }, }), "key", )} ref={setNodeRef} className={classNames( isDragging && "opacity-50", "font-semibold text-left text-xs bg-background-secondary text-content-secondary tracking-wider", "select-none duration-300 transition-colors", "border-r", "relative", )} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} > {/* Show a vertical line on the right side where the column will be dropped */} {isHovering && direction && ( <div className="absolute top-0 right-0 z-10 w-0.5 bg-util-accent" style={{ height: tableContainerRef.current?.offsetHeight || "100%", }} /> )} {/* Show a vertical line when resizing this column */} {isResizingColumn === columnName && ( <div className="absolute top-0 right-0 z-10 w-0.5 bg-util-accent" style={{ height: tableContainerRef.current?.offsetHeight || "100%", }} /> )} <ValidatorTooltip fieldSchema={fieldSchema} columnName={columnName} disableTooltip={!!isResizingColumn || isDragging} > <div ref={headerNode} className="flex w-full items-center space-x-2" style={{ padding: `${densityValues.paddingY}px ${columnIndex === 0 ? "12" : densityValues.paddingX}px`, width, }} > <div className="flex items-center space-x-2"> {columnIndex === 0 ? ( // Disable the "Select all" checkbox when filtering allRowsSelected === false && hasFilters && !isSelectionExhaustive ? null : ( <Checkbox checked={allRowsSelected} onChange={toggleAll} /> ) ) : column.Header === emptyColumnName ? ( <i>empty</i> ) : typeof column.Header === "string" && identifierNeedsEscape(column.Header) ? ( <span className={`before:text-content-primary before:content-['"'] after:text-content-primary after:content-['"']`} > {column.render("Header")} </span> ) : ( <div>{column.render("Header")}</div> )} {column.Header !== "_creationTime" && (column as unknown as { isDate: boolean }).isDate && ( <Tooltip tip="Displaying numbers as dates. Hover or edit the cell by double-clicking see the unformatted value." side="top" align="start" className="flex items-center" > <QuestionMarkCircledIcon /> </Tooltip> )} {sort && ( <Tooltip tip="You may change the sort order in the Filter & Sort menu."> <CaretUpIcon className={cn( "transition-all", sort === "asc" ? "" : "rotate-180", )} /> </Tooltip> )} </div> {canDragOrDrop && isHovered && ( <Button {...attributes} {...listeners} className={cn( "absolute right-1.5 animate-fadeInFromLoading cursor-grab items-center bg-background-secondary/50 text-content-secondary backdrop-blur-[2px]", isDragging && "cursor-grabbing", )} aria-label="Drag column" variant="neutral" inline size="xs" icon={<DragHandleDots2Icon />} /> )} </div> </ValidatorTooltip> {!isHovering && !column.disableResizing && columnName !== "*select" && ( <div {...column.getResizerProps()} className="absolute top-0 z-20 inline-block h-full" style={{ // @ts-expect-error bad typing in react-table ...column.getResizerProps().style, width: densityValues.paddingX * (isLastColumn ? 1 : 2), right: isLastColumn ? 0 : -densityValues.paddingX, }} /> )} </div> ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/get-convex/convex-backend'

If you have feedback or need assistance with the MCP directory API, please join our Discord server