Skip to content

Commit 70de289

Browse files
committed
hover template
1 parent c792f03 commit 70de289

File tree

6 files changed

+57
-0
lines changed

6 files changed

+57
-0
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/* Copyright 2025 Marimo. All rights reserved. */
2+
"use no memo";
3+
4+
import type { InitialTableState, TableFeature } from "@tanstack/react-table";
5+
import type { CellHoverTemplateTableState } from "./types";
6+
7+
export const CellHoverTemplateFeature: TableFeature = {
8+
getInitialState: (state?: InitialTableState): CellHoverTemplateTableState => {
9+
return {
10+
...state,
11+
cellHoverTemplate: null,
12+
};
13+
},
14+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/* Copyright 2025 Marimo. All rights reserved. */
2+
/* eslint-disable @typescript-eslint/no-empty-interface */
3+
4+
export interface CellHoverTemplateTableState {
5+
cellHoverTemplate: string | null;
6+
}
7+
8+
// Use declaration merging to add our new feature APIs
9+
declare module "@tanstack/react-table" {
10+
interface TableState extends CellHoverTemplateTableState {}
11+
}

frontend/src/components/data-table/data-table.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { Table } from "@/components/ui/table";
2424
import type { GetRowIds } from "@/plugins/impl/DataTablePlugin";
2525
import { cn } from "@/utils/cn";
2626
import type { PanelType } from "../editor/chrome/panels/context-aware-panel/context-aware-panel";
27+
import { CellHoverTemplateFeature } from "./cell-hover-template/feature";
2728
import { CellSelectionFeature } from "./cell-selection/feature";
2829
import type { CellSelectionState } from "./cell-selection/types";
2930
import { CellStylingFeature } from "./cell-styling/feature";
@@ -63,6 +64,7 @@ interface DataTableProps<TData> extends Partial<DownloadActionProps> {
6364
rowSelection?: RowSelectionState;
6465
cellSelection?: CellSelectionState;
6566
cellStyling?: CellStyleState | null;
67+
hoverTemplate?: string | null;
6668
onRowSelectionChange?: OnChangeFn<RowSelectionState>;
6769
onCellSelectionChange?: OnChangeFn<CellSelectionState>;
6870
getRowIds?: GetRowIds;
@@ -103,6 +105,7 @@ const DataTableInternal = <TData,>({
103105
rowSelection,
104106
cellSelection,
105107
cellStyling,
108+
hoverTemplate,
106109
paginationState,
107110
setPaginationState,
108111
downloadAs,
@@ -176,6 +179,7 @@ const DataTableInternal = <TData,>({
176179
ColumnFormattingFeature,
177180
CellSelectionFeature,
178181
CellStylingFeature,
182+
CellHoverTemplateFeature,
179183
CopyColumnFeature,
180184
FocusRowFeature,
181185
],
@@ -237,6 +241,7 @@ const DataTableInternal = <TData,>({
237241
cellSelection,
238242
cellStyling,
239243
columnPinning: columnPinning,
244+
cellHoverTemplate: hoverTemplate,
240245
},
241246
});
242247

frontend/src/components/data-table/renderers.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,25 @@ export const DataTableBody = <TData,>({
101101
cell.getUserStyling?.() || {},
102102
pinningstyle,
103103
);
104+
const hoverTemplate = table.getState().cellHoverTemplate || undefined;
105+
let title: string | undefined;
106+
if (hoverTemplate) {
107+
title = hoverTemplate;
108+
const variableRegex = /{{(\w+)}}/g;
109+
const matches = [...hoverTemplate.matchAll(variableRegex)];
110+
for (const match of matches) {
111+
// if match is in the row, replace it in the string
112+
const matchTerm = match[1];
113+
const currentCell = cells.find(
114+
(cell) => cell.column.id === matchTerm,
115+
);
116+
if (currentCell) {
117+
const cellValue = currentCell.getValue();
118+
title = title.replace(match[0], cellValue as string);
119+
}
120+
console.log(matchTerm, columns);
121+
}
122+
}
104123
return (
105124
<TableCell
106125
tabIndex={0}
@@ -114,6 +133,7 @@ export const DataTableBody = <TData,>({
114133
className,
115134
)}
116135
style={style}
136+
title={title}
117137
onMouseDown={(e) => handleCellMouseDown(e, cell)}
118138
onMouseUp={handleCellMouseUp}
119139
onMouseOver={(e) => handleCellMouseOver(e, cell)}

frontend/src/plugins/impl/DataTablePlugin.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ export const DataTablePlugin = createPlugin<S>("marimo-table")
254254
maxColumns: z.union([z.number(), z.literal("all")]).default("all"),
255255
hasStableRowId: z.boolean().default(false),
256256
cellStyles: z.record(z.record(z.object({}).passthrough())).optional(),
257+
hoverTemplate: z.string().optional(),
257258
// Whether to load the data lazily.
258259
lazy: z.boolean().default(false),
259260
// If lazy, this will preload the first page of data
@@ -385,6 +386,7 @@ interface DataTableProps<T> extends Data<T>, DataTableFunctions {
385386
// Filters
386387
enableFilters?: boolean;
387388
cellStyles?: CellStyleState | null;
389+
hoverTemplate?: string | null;
388390
toggleDisplayHeader?: () => void;
389391
host: HTMLElement;
390392
cellId?: CellId | null;
@@ -707,6 +709,7 @@ const DataTableComponent = ({
707709
totalColumns,
708710
get_row_ids,
709711
cellStyles,
712+
hoverTemplate,
710713
toggleDisplayHeader,
711714
calculate_top_k_rows,
712715
preview_column,
@@ -904,6 +907,7 @@ const DataTableComponent = ({
904907
rowSelection={rowSelection}
905908
cellSelection={cellSelection}
906909
cellStyling={cellStyles}
910+
hoverTemplate={hoverTemplate}
907911
downloadAs={showDownload ? downloadAs : undefined}
908912
enableSearch={enableSearch}
909913
searchQuery={searchQuery}

marimo/_plugins/ui/_impl/table.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ def style_cell(_rowId, _columnName, value):
332332
on_change (Callable[[Union[List[JSONType], Dict[str, List[JSONType]], IntoDataFrame, List[TableCell]]], None], optional):
333333
Optional callback to run when this element's value changes.
334334
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.
335+
hover_template (str, optional): A template for the hover text of the table.
335336
max_columns (int, optional): Maximum number of columns to display. Defaults to the
336337
configured default_table_max_columns (50 by default). Set to None to show all columns.
337338
label (str, optional): A descriptive name for the table. Defaults to "".
@@ -439,6 +440,7 @@ def __init__(
439440
]
440441
] = None,
441442
style_cell: Optional[Callable[[str, str, Any], dict[str, Any]]] = None,
443+
hover_template: Optional[str] = None,
442444
# The _internal_* arguments are for overriding and unit tests
443445
# table should take the value unconditionally
444446
_internal_column_charts_row_limit: Optional[int] = None,
@@ -666,6 +668,7 @@ def __init__(
666668
"wrapped-columns": wrapped_columns,
667669
"has-stable-row-id": self._has_stable_row_id,
668670
"cell-styles": search_result_styles,
671+
"hover-template": hover_template,
669672
"lazy": _internal_lazy,
670673
"preload": _internal_preload,
671674
},

0 commit comments

Comments
 (0)