/* Copyright 2026 Marimo. All rights reserved. */ "use no memo"; import type { ColumnFiltersState, Table } from "@tanstack/react-table"; import { XIcon } from "lucide-react"; import { useState } from "react"; import { type DateFormatter, useDateFormatter } from "react-aria"; import type { CalculateTopKRows } from "@/plugins/impl/DataTablePlugin"; import { logNever } from "@/utils/assertNever"; import { cn } from "@/utils/cn"; import { Badge } from "../ui/badge"; import { Button } from "../ui/button"; import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import { FilterPillEditor } from "./filter-pill-editor"; import { type ColumnFilterValue, dateToISODate, dateToISODateTime, } from "./filters"; import { OPERATOR_LABELS } from "./operator-labels"; import { stringifyUnknownValue } from "./utils"; interface Props { filters: ColumnFiltersState | undefined; table: Table; calculateTopKRows?: CalculateTopKRows; } export const FilterPills = ({ filters, table, calculateTopKRows, }: Props) => { const timeFormatter = useDateFormatter({ hour: "2-digit", minute: "2-digit", second: "2-digit", }); if (!filters || filters.length === 0) { return null; } return (
{filters.map((filter) => ( table.setColumnFilters((filters) => filters.filter((f) => f.id !== filter.id), ) } /> ))}
); }; interface FilterPillProps { columnId: string; value: ColumnFilterValue; timeFormatter: DateFormatter; table: Table; calculateTopKRows?: CalculateTopKRows; onRemove: () => void; } const FilterPill = ({ columnId, value, timeFormatter, table, calculateTopKRows, onRemove, }: FilterPillProps) => { const [open, setOpen] = useState(false); const formatted = formatValue(value, timeFormatter); if (!formatted) { return null; } const twoSegment = formatted.value === undefined; const handleRemove = (e: React.MouseEvent) => { e.stopPropagation(); onRemove(); }; const segments = ( <> {columnId} {formatted.operator} {!twoSegment && ( <> {formatted.value} )} ); const removeButton = ( ); return ( {removeButton} e.preventDefault()} > setOpen(false)} /> ); }; function Separator() { return ( ); } interface FormattedFilter { operator: string; value?: string; } function formatValue( value: ColumnFilterValue, timeFormatter: DateFormatter, ): FormattedFilter | undefined { if (!("type" in value)) { return; } if (value.operator === "is_null") { return { operator: "is null" }; } if (value.operator === "is_not_null") { return { operator: "is not null" }; } if (value.type === "number") { switch (value.operator) { case "between": return { operator: OPERATOR_LABELS.between.toLowerCase(), value: `${value.min} - ${value.max}`, }; case "==": case "!=": case ">": case ">=": case "<": case "<=": return { operator: value.operator, value: String(value.value) }; } } if (value.type === "text") { switch (value.operator) { case "in": case "not_in": { const items = value.values.map((v) => `"${v}"`); return { operator: value.operator === "in" ? "is in" : "not in", value: `[${items.join(", ")}]`, }; } case "is_empty": return { operator: "is empty" }; case "contains": case "equals": case "does_not_equal": case "regex": case "starts_with": case "ends_with": return { operator: OPERATOR_LABELS[value.operator].toLowerCase(), value: `"${value.text}"`, }; } } if ( value.type === "date" || value.type === "datetime" || value.type === "time" ) { const format = value.type === "time" ? (d: Date) => timeFormatter.format(d) : value.type === "date" ? dateToISODate : dateToISODateTime; switch (value.operator) { case "between": return { operator: OPERATOR_LABELS.between.toLowerCase(), value: `${format(value.min)} - ${format(value.max)}`, }; case "==": case "!=": case ">": case ">=": case "<": case "<=": return { operator: value.operator, value: format(value.value) }; } } if (value.type === "boolean") { return { operator: `is ${value.value ? "True" : "False"}` }; } if (value.type === "select") { const stringifiedOptions = value.options.map((o) => stringifyUnknownValue({ value: o }), ); return { operator: value.operator === "in" ? "is in" : "not in", value: `[${stringifiedOptions.join(", ")}]`, }; } logNever(value); return undefined; }