/* 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, FilterX, FunnelPlusIcon, ListFilterIcon, ListFilterPlusIcon, 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 { Button } from "../ui/button"; import { formattingExample } from "./column-formatting/feature"; import { formatOptions } from "./column-formatting/types"; import { NAMELESS_COLUMN_PREFIX } from "./columns"; export function renderFormatOptions( 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 renderColumnWrapping( 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 renderColumnPinning( 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 renderCopyColumn(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; export function renderSorts( 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 renderSortFilterIcon( column: Column, ) { if (!column.getCanSort()) { return null; } const isSorted = column.getIsSorted(); const isFiltered = column.getFilterValue() !== undefined; let Icon: React.FC>; if (isFiltered && isSorted) { Icon = ListFilterPlusIcon; } else if (isFiltered) { Icon = FunnelPlusIcon; } else if (isSorted) { Icon = isSorted === "desc" ? DescIcon : AscIcon; } else { Icon = ChevronsUpDown; } return ; } export function renderDataType(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 ); export function renderFilterByValues( column: Column, setIsFilterValueOpen: (open: boolean) => void, ) { const canFilter = column.getCanFilter(); if (!canFilter) { return null; } const columnType = column.columnDef.meta?.dataType; // skip boolean as this can be easily filtered through normal filters if (columnType === "boolean") { return null; } // there are some edge cases which do not support filtering (eg. dicts with None values) const filterType = column.columnDef.meta?.filterType; if (!filterType) { return null; } return ( setIsFilterValueOpen(true)}> Filter by values ); } export const FilterButtons = ({ onApply, onClear, clearButtonDisabled, }: { onApply: () => void; onClear: () => void; clearButtonDisabled?: boolean; }) => { return (
); };