/* Copyright 2026 Marimo. All rights reserved. */ import { PinLeftIcon, PinRightIcon } from "@radix-ui/react-icons"; import type { Column, SortDirection, Table } from "@tanstack/react-table"; import { AlignJustifyIcon, ArrowDownWideNarrowIcon, ArrowUpNarrowWideIcon, ChevronsUpDown, CopyIcon, EyeOffIcon, FilterX, PinOffIcon, WrapTextIcon, } from "lucide-react"; import { DropdownMenuItem, DropdownMenuPortal, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, } from "@/components/ui/dropdown-menu"; import type { DataType } from "@/core/kernel/messages"; import { cn } from "@/utils/cn"; import { copyToClipboard } from "@/utils/copy"; import { DATA_TYPE_ICON } from "../datasets/icons"; import { formattingExample } from "./column-formatting/feature"; import { formatOptions } from "./column-formatting/types"; import { NAMELESS_COLUMN_PREFIX } from "./columns"; export function FormatOptions({ column, locale, }: { column: Column; locale: string; }) { const dataType: DataType | undefined = column.columnDef.meta?.dataType; const columnFormatOptions = dataType ? formatOptions[dataType] : []; if (columnFormatOptions.length === 0 || !column.getCanFormat?.()) { return null; } const FormatIcon = DATA_TYPE_ICON[dataType || "unknown"]; const currentFormat = column.getColumnFormatting?.(); return ( Format
Locale: {locale}
{Boolean(currentFormat) && ( <> column.setColumnFormatting(undefined)} > Clear )} {columnFormatOptions.map((option) => ( column.setColumnFormatting(option)} > {option} {formattingExample(option, locale)} ))}
); } export function ColumnWrapping({ column, }: { column: Column; }) { if (!column.getCanWrap?.() || !column.getColumnWrapping) { return null; } const wrap = column.getColumnWrapping(); if (wrap === "wrap") { return ( column.toggleColumnWrapping("nowrap")}> No wrap text ); } return ( column.toggleColumnWrapping("wrap")}> Wrap text ); } export function ColumnPinning({ column, }: { column: Column; }) { if (!column.getCanPin?.() || !column.getIsPinned) { return null; } const pinnedPosition = column.getIsPinned(); if (pinnedPosition !== false) { return ( column.pin(false)}> Unfreeze ); } return ( <> column.pin("left")}> Freeze left column.pin("right")}> Freeze right ); } export function HideColumn({ column, }: { column: Column; }) { if (!column.getCanHide()) { return null; } return ( column.toggleVisibility(false)}> Hide column ); } export function CopyColumn({ column, }: { column: Column; }) { if (!column.getCanCopy?.()) { return null; } if (column.id.startsWith(NAMELESS_COLUMN_PREFIX)) { return null; } return ( await copyToClipboard(column.id)}> Copy column name ); } const AscIcon = ArrowUpNarrowWideIcon; const DescIcon = ArrowDownWideNarrowIcon; /** * `table` is optional: it is only needed to detect multi-column sorting and * offer "Clear all sorts". Call sites that build their header inside column * definitions (where the table instance isn't yet in scope) omit it and fall * back to single-column "Clear sort". */ export function Sorts({ column, table, }: { column: Column; table?: Table; }) { if (!column.getCanSort()) { return null; } const sortDirection = column.getIsSorted(); const sortingIndex = column.getSortIndex(); const sortingState = table?.getState().sorting; const hasMultiSort = sortingState?.length && sortingState.length > 1; const renderSortIndex = () => { return ( {sortingIndex + 1} ); }; const renderClearSort = () => { if (!sortDirection) { return null; } if (!hasMultiSort) { // render clear sort for this column return ( column.clearSorting()}> Clear sort ); } // render clear sort for all columns return ( table?.resetSorting()}> Clear all sorts ); }; const toggleSort = (direction: SortDirection) => { // Clear sort if clicking the same direction if (sortDirection === direction) { column.clearSorting(); } else { // Toggle sort direction const descending = direction === "desc"; column.toggleSorting(descending, true); } }; return ( <> toggleSort("asc")} className={sortDirection === "asc" ? "bg-accent" : ""} > Asc {sortDirection === "asc" && renderSortIndex()} toggleSort("desc")} className={sortDirection === "desc" ? "bg-accent" : ""} > Desc {sortDirection === "desc" && renderSortIndex()} {renderClearSort()} ); } export function renderSortIcon(column: Column) { if (!column.getCanSort()) { return null; } const isSorted = column.getIsSorted(); const Icon: React.FC> = isSorted ? isSorted === "desc" ? DescIcon : AscIcon : ChevronsUpDown; return ; } export function DataType({ column, }: { column: Column; }) { const dtype: string | undefined = column.columnDef.meta?.dtype; if (!dtype) { return null; } return ( <>
{dtype}
); } export const ClearFilterMenuItem = ({ column, }: { column: Column; }) => ( column.setFilterValue(undefined)}> Clear filter );