Cell Tooltips and Popovers
Display tooltips and expandable content for spreadsheet cells
Enhance your spreadsheet with custom tooltips and expandable cell content. Display additional information, rich media, or interactive components when users hover over or click on cells.
Overview
The spreadsheet provides two main ways to display additional cell content:
Tooltips (
getTooltipContent): Show hover tooltips with custom contentExpandable Content (
getCellExpandContent): Display rich content in a popover when cells are expanded
Cell Tooltips
Display custom tooltips when users hover over cells using the getTooltipContent callback.
Basic Usage
import { SpreadsheetProvider, CanvasGrid } from "@rowsncolumns/spreadsheet";
function SpreadsheetWithTooltips() {
const getTooltipContent = (
sheetId: number,
rowIndex: number,
columnIndex: number
) => {
// Return undefined for no tooltip
if (rowIndex === 0 || columnIndex === 0) {
return undefined;
}
// Return JSX for custom tooltip
return (
<div className="p-2">
<strong>Cell:</strong> {cellToAddress({ rowIndex, columnIndex })}
<br />
<strong>Sheet:</strong> {sheetId}
</div>
);
};
return (
<SpreadsheetProvider>
<CanvasGrid
sheetId={1}
getTooltipContent={getTooltipContent}
// ... other props
/>
</SpreadsheetProvider>
);
}Function Signature
type GetTooltipContent = (
sheetId: number,
rowIndex: number,
columnIndex: number
) => React.ReactNode | undefined;Return Values
React.ReactNode: Display tooltip with custom content
undefined: No tooltip for this cell
Examples
Display Cell Metadata
const getTooltipContent = (sheetId, rowIndex, columnIndex) => {
const cellData = getCellData(sheetId, rowIndex, columnIndex);
if (!cellData) return undefined;
return (
<div className="text-sm">
<div className="font-semibold mb-1">Cell Information</div>
{cellData.note && (
<div className="mb-1">
<strong>Note:</strong> {cellData.note}
</div>
)}
{cellData.userEnteredValue && (
<div>
<strong>Formula:</strong> {cellData.userEnteredValue.formulaValue}
</div>
)}
</div>
);
};Show Validation Rules
const getTooltipContent = (sheetId, rowIndex, columnIndex) => {
const validation = getDataValidation(sheetId, rowIndex, columnIndex);
if (!validation) return undefined;
return (
<div className="p-2 bg-yellow-50 border border-yellow-200 rounded">
<div className="font-semibold text-yellow-800">Validation Rule</div>
<div className="text-sm text-yellow-700 mt-1">
{validation.condition?.type}: {validation.condition?.values?.join(", ")}
</div>
</div>
);
};Display Error Messages
const getTooltipContent = (sheetId, rowIndex, columnIndex) => {
const cellData = getCellData(sheetId, rowIndex, columnIndex);
if (cellData?.effectiveValue?.errorValue) {
return (
<div className="p-2 bg-red-50 border border-red-200 rounded">
<div className="font-semibold text-red-800">Formula Error</div>
<div className="text-sm text-red-700 mt-1">
{cellData.effectiveValue.errorValue}
</div>
</div>
);
}
return undefined;
};Expandable Cell Content
Display rich, interactive content when users expand cells using the getCellExpandContent callback.
Basic Usage
function SpreadsheetWithExpandableContent() {
const getCellExpandContent = () => {
return (
<div className="p-4 max-w-md">
<h3 className="font-bold mb-2">Additional Information</h3>
<p className="text-sm text-gray-700">
This is expandable content that appears when the cell is expanded.
You can include any React component here.
</p>
</div>
);
};
return (
<SpreadsheetProvider>
<CanvasGrid
sheetId={1}
getCellExpandContent={getCellExpandContent}
// ... other props
/>
</SpreadsheetProvider>
);
}Function Signature
type GetCellExpandContent = () => React.ReactNode;Advanced Examples
Rich Text Editor
const getCellExpandContent = () => {
return (
<div className="overflow-auto max-h-96 max-w-2xl p-4">
<div className="prose">
<h3>Project Description</h3>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Detailed information about this cell can be displayed here.
</p>
<ul>
<li>Feature A</li>
<li>Feature B</li>
<li>Feature C</li>
</ul>
</div>
</div>
);
};Image Gallery
const getCellExpandContent = () => {
const images = [
"/images/chart1.png",
"/images/chart2.png",
"/images/chart3.png",
];
return (
<div className="p-4 max-w-4xl">
<h3 className="font-bold mb-4">Related Charts</h3>
<div className="grid grid-cols-3 gap-4">
{images.map((src, index) => (
<img
key={index}
src={src}
alt={`Chart ${index + 1}`}
className="rounded shadow-lg"
/>
))}
</div>
</div>
);
};Interactive Form
const getCellExpandContent = () => {
const [notes, setNotes] = useState("");
return (
<div className="p-4 max-w-md">
<h3 className="font-bold mb-2">Add Notes</h3>
<textarea
className="w-full border rounded p-2 mb-2"
rows={4}
value={notes}
onChange={(e) => setNotes(e.target.value)}
placeholder="Enter your notes here..."
/>
<button
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
onClick={() => console.log("Save notes:", notes)}
>
Save
</button>
</div>
);
};Data Visualization
import { Chart } from "react-chartjs-2";
const getCellExpandContent = () => {
const chartData = {
labels: ["Jan", "Feb", "Mar", "Apr", "May"],
datasets: [
{
label: "Sales",
data: [12, 19, 3, 5, 2],
backgroundColor: "rgba(75, 192, 192, 0.2)",
borderColor: "rgba(75, 192, 192, 1)",
},
],
};
return (
<div className="p-4 max-w-2xl">
<h3 className="font-bold mb-4">Sales Trend</h3>
<Chart type="line" data={chartData} />
</div>
);
};Cell-Specific Content
Customize content based on the cell being expanded:
import { useSpreadsheet } from "@rowsncolumns/spreadsheet";
function SpreadsheetWithDynamicContent() {
const { activeCell } = useSpreadsheet();
const getCellExpandContent = useCallback(() => {
const cellData = getCellData(
activeSheetId,
activeCell.rowIndex,
activeCell.columnIndex
);
// Different content based on cell value
if (cellData?.formattedValue?.includes("Task")) {
return <TaskDetailsPanel cellData={cellData} />;
}
if (cellData?.formattedValue?.includes("User")) {
return <UserProfilePanel cellData={cellData} />;
}
// Default content
return (
<div className="p-4">
<p>No additional information available</p>
</div>
);
}, [activeCell, activeSheetId]);
return (
<CanvasGrid
getCellExpandContent={getCellExpandContent}
// ... other props
/>
);
}Complete Example
import React, { useState, useCallback } from "react";
import {
SpreadsheetProvider,
CanvasGrid,
Sheet,
} from "@rowsncolumns/spreadsheet";
import {
useSpreadsheetState,
SheetData,
CellData,
} from "@rowsncolumns/spreadsheet-state";
import { cellToAddress } from "@rowsncolumns/utils";
function EnhancedSpreadsheet() {
const [sheets, setSheets] = useState<Sheet[]>([
{ sheetId: 1, rowCount: 100, columnCount: 26, title: "Data" }
]);
const [sheetData, setSheetData] = useState<SheetData<CellData>>({});
const {
activeCell,
activeSheetId,
selections,
getCellData,
onChangeActiveCell,
onChangeSelections,
} = useSpreadsheetState({
sheets,
sheetData,
onChangeSheets: setSheets,
onChangeSheetData: setSheetData,
});
// Tooltip handler
const getTooltipContent = useCallback(
(sheetId: number, rowIndex: number, columnIndex: number) => {
// Skip headers
if (rowIndex === 0 || columnIndex === 0) return undefined;
const cellData = getCellData(sheetId, rowIndex, columnIndex);
if (!cellData) return undefined;
const address = cellToAddress({ rowIndex, columnIndex });
return (
<div className="p-3 bg-white shadow-lg rounded-lg border">
<div className="text-xs text-gray-500 mb-1">{address}</div>
{cellData.note && (
<div className="text-sm border-t pt-2 mt-2">
<strong className="text-gray-700">Note:</strong>
<div className="text-gray-600 mt-1">{cellData.note}</div>
</div>
)}
{cellData.hyperlink && (
<div className="text-sm border-t pt-2 mt-2">
<strong className="text-blue-700">Link:</strong>
<a
href={cellData.hyperlink}
className="text-blue-600 hover:underline ml-1"
>
{cellData.hyperlink}
</a>
</div>
)}
</div>
);
},
[getCellData]
);
// Expandable content handler
const getCellExpandContent = useCallback(() => {
const cellData = getCellData(
activeSheetId,
activeCell.rowIndex,
activeCell.columnIndex
);
return (
<div className="overflow-auto max-h-96 max-w-2xl p-6 bg-white rounded-lg">
<h2 className="text-xl font-bold mb-4">Cell Details</h2>
<div className="space-y-4">
<div>
<strong className="text-gray-700">Location:</strong>
<span className="ml-2">
{cellToAddress({
rowIndex: activeCell.rowIndex,
columnIndex: activeCell.columnIndex,
})}
</span>
</div>
{cellData?.formattedValue && (
<div>
<strong className="text-gray-700">Value:</strong>
<div className="mt-1 p-3 bg-gray-50 rounded font-mono text-sm">
{cellData.formattedValue}
</div>
</div>
)}
{cellData?.userEnteredValue?.formulaValue && (
<div>
<strong className="text-gray-700">Formula:</strong>
<div className="mt-1 p-3 bg-blue-50 rounded font-mono text-sm">
{cellData.userEnteredValue.formulaValue}
</div>
</div>
)}
<div className="border-t pt-4">
<button className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
Edit Cell
</button>
</div>
</div>
</div>
);
}, [activeCell, activeSheetId, getCellData]);
return (
<SpreadsheetProvider>
<CanvasGrid
sheetId={activeSheetId}
activeCell={activeCell}
selections={selections}
getCellData={getCellData}
onChangeActiveCell={onChangeActiveCell}
onChangeSelections={onChangeSelections}
getTooltipContent={getTooltipContent}
getCellExpandContent={getCellExpandContent}
/>
</SpreadsheetProvider>
);
}
export default EnhancedSpreadsheet;Styling
Tooltip Styling
Tooltips automatically position themselves to avoid going off-screen. Style them using standard CSS classes:
const getTooltipContent = () => (
<div className="p-3 bg-gray-900 text-white rounded-lg shadow-xl max-w-xs">
<div className="text-sm">Your tooltip content</div>
</div>
);Popover Styling
Expandable content appears in a popover with scrolling support:
const getCellExpandContent = () => (
<div className="overflow-auto max-h-96 max-w-4xl p-6">
{/* Scrollable content */}
</div>
);Use Cases
Data Annotations
Show additional context or metadata for cells containing important data.
Error Explanations
Display helpful error messages and suggestions when formulas fail.
Rich Content Preview
Preview images, charts, or formatted text without cluttering the grid.
Interactive Dialogs
Provide forms or interactive elements for complex data entry.
Audit Information
Display who last edited a cell and when.
Performance Considerations
Memoize Callbacks: Use
useCallbackto prevent unnecessary re-rendersConditional Rendering: Return
undefinedwhen no tooltip is neededLazy Loading: Load heavy content only when the popover is opened
Limit Complexity: Keep tooltip content simple for smooth hover interactions
Best Practices
Keep Tooltips Concise: Display only essential information in tooltips
Use Popovers for Rich Content: Save complex content for expandable popovers
Avoid Heavy Computations: Don't perform expensive calculations in tooltip callbacks
Accessibility: Ensure tooltips and popovers work with keyboard navigation
Consistent Styling: Match your application's design system
Troubleshooting
Tooltips Not Showing
Verify
getTooltipContentreturns valid JSX or undefined (not null)Check that the function is properly passed to CanvasGrid
Ensure there are no JavaScript errors in the console
Popovers Not Opening
Confirm
getCellExpandContentis definedCheck that the cell expansion trigger is working (usually double-click or icon)
Verify the content isn't too large or causing layout issues
Performance Issues
Use
React.memofor complex tooltip componentsImplement lazy loading for heavy content
Consider debouncing tooltip display
Last updated
Was this helpful?