Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* Copyright 2025 Marimo. All rights reserved. */
"use no memo";

import type { InitialTableState, TableFeature } from "@tanstack/react-table";
import type { CellHoverTemplateTableState } from "./types";

export const CellHoverTemplateFeature: TableFeature = {
getInitialState: (state?: InitialTableState): CellHoverTemplateTableState => {
return {
...state,
cellHoverTemplate: null,
};
},
};
11 changes: 11 additions & 0 deletions frontend/src/components/data-table/cell-hover-template/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* Copyright 2025 Marimo. All rights reserved. */
/* eslint-disable @typescript-eslint/no-empty-interface */

export interface CellHoverTemplateTableState {
cellHoverTemplate: string | null;
}

// Use declaration merging to add our new feature APIs
declare module "@tanstack/react-table" {
interface TableState extends CellHoverTemplateTableState {}
}
5 changes: 5 additions & 0 deletions frontend/src/components/data-table/data-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { Table } from "@/components/ui/table";
import type { GetRowIds } from "@/plugins/impl/DataTablePlugin";
import { cn } from "@/utils/cn";
import type { PanelType } from "../editor/chrome/panels/context-aware-panel/context-aware-panel";
import { CellHoverTemplateFeature } from "./cell-hover-template/feature";
import { CellSelectionFeature } from "./cell-selection/feature";
import type { CellSelectionState } from "./cell-selection/types";
import { CellStylingFeature } from "./cell-styling/feature";
Expand Down Expand Up @@ -63,6 +64,7 @@ interface DataTableProps<TData> extends Partial<DownloadActionProps> {
rowSelection?: RowSelectionState;
cellSelection?: CellSelectionState;
cellStyling?: CellStyleState | null;
hoverTemplate?: string | null;
onRowSelectionChange?: OnChangeFn<RowSelectionState>;
onCellSelectionChange?: OnChangeFn<CellSelectionState>;
getRowIds?: GetRowIds;
Expand Down Expand Up @@ -103,6 +105,7 @@ const DataTableInternal = <TData,>({
rowSelection,
cellSelection,
cellStyling,
hoverTemplate,
paginationState,
setPaginationState,
downloadAs,
Expand Down Expand Up @@ -176,6 +179,7 @@ const DataTableInternal = <TData,>({
ColumnFormattingFeature,
CellSelectionFeature,
CellStylingFeature,
CellHoverTemplateFeature,
CopyColumnFeature,
FocusRowFeature,
],
Expand Down Expand Up @@ -237,6 +241,7 @@ const DataTableInternal = <TData,>({
cellSelection,
cellStyling,
columnPinning: columnPinning,
cellHoverTemplate: hoverTemplate,
},
});

Expand Down
20 changes: 20 additions & 0 deletions frontend/src/components/data-table/renderers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,25 @@
cell.getUserStyling?.() || {},
pinningstyle,
);
const hoverTemplate = table.getState().cellHoverTemplate || undefined;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mscolnick yeah this works.
But I wonder if we want to support showing something specific for each individual cell?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that seems like a weird user experience, because how are they going to expect what is in each popover without hovering over it. it would just be a surprise. with a template, they at least can infer what fields are being shown and changing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, makes sense. I'll stick to the column names within a row for now.

let title: string | undefined;
if (hoverTemplate) {
title = hoverTemplate;
const variableRegex = /{{(\w+)}}/g;
const matches = [...hoverTemplate.matchAll(variableRegex)];
for (const match of matches) {
// if match is in the row, replace it in the string
const matchTerm = match[1];
const currentCell = cells.find(
(cell) => cell.column.id === matchTerm,
);
if (currentCell) {
const cellValue = currentCell.getValue();
title = title.replace(match[0], cellValue as string);
}
console.log(matchTerm, columns);

Check failure on line 120 in frontend/src/components/data-table/renderers.tsx

View workflow job for this annotation

GitHub Actions / 🧹 Lint frontend

Unexpected console statement
}
}
return (
<TableCell
tabIndex={0}
Expand All @@ -114,6 +133,7 @@
className,
)}
style={style}
title={title}
onMouseDown={(e) => handleCellMouseDown(e, cell)}
onMouseUp={handleCellMouseUp}
onMouseOver={(e) => handleCellMouseOver(e, cell)}
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/plugins/impl/DataTablePlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ export const DataTablePlugin = createPlugin<S>("marimo-table")
maxColumns: z.union([z.number(), z.literal("all")]).default("all"),
hasStableRowId: z.boolean().default(false),
cellStyles: z.record(z.record(z.object({}).passthrough())).optional(),
hoverTemplate: z.string().optional(),
// Whether to load the data lazily.
lazy: z.boolean().default(false),
// If lazy, this will preload the first page of data
Expand Down Expand Up @@ -385,6 +386,7 @@ interface DataTableProps<T> extends Data<T>, DataTableFunctions {
// Filters
enableFilters?: boolean;
cellStyles?: CellStyleState | null;
hoverTemplate?: string | null;
toggleDisplayHeader?: () => void;
host: HTMLElement;
cellId?: CellId | null;
Expand Down Expand Up @@ -707,6 +709,7 @@ const DataTableComponent = ({
totalColumns,
get_row_ids,
cellStyles,
hoverTemplate,
toggleDisplayHeader,
calculate_top_k_rows,
preview_column,
Expand Down Expand Up @@ -904,6 +907,7 @@ const DataTableComponent = ({
rowSelection={rowSelection}
cellSelection={cellSelection}
cellStyling={cellStyles}
hoverTemplate={hoverTemplate}
downloadAs={showDownload ? downloadAs : undefined}
enableSearch={enableSearch}
searchQuery={searchQuery}
Expand Down
3 changes: 3 additions & 0 deletions marimo/_plugins/ui/_impl/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ def style_cell(_rowId, _columnName, value):
on_change (Callable[[Union[List[JSONType], Dict[str, List[JSONType]], IntoDataFrame, List[TableCell]]], None], optional):
Optional callback to run when this element's value changes.
style_cell (Callable[[str, str, Any], Dict[str, Any]], optional): A function that takes the row id, column name and value and returns a dictionary of CSS styles.
hover_template (str, optional): A template for the hover text of the table.
max_columns (int, optional): Maximum number of columns to display. Defaults to the
configured default_table_max_columns (50 by default). Set to None to show all columns.
label (str, optional): A descriptive name for the table. Defaults to "".
Expand Down Expand Up @@ -439,6 +440,7 @@ def __init__(
]
] = None,
style_cell: Optional[Callable[[str, str, Any], dict[str, Any]]] = None,
hover_template: Optional[str] = None,
# The _internal_* arguments are for overriding and unit tests
# table should take the value unconditionally
_internal_column_charts_row_limit: Optional[int] = None,
Expand Down Expand Up @@ -666,6 +668,7 @@ def __init__(
"wrapped-columns": wrapped_columns,
"has-stable-row-id": self._has_stable_row_id,
"cell-styles": search_result_styles,
"hover-template": hover_template,
"lazy": _internal_lazy,
"preload": _internal_preload,
},
Expand Down
Loading