Mentions
Add @mentions functionality to spreadsheet cells
The mentions feature allows users to reference people, tags, or entities within cells using the @ symbol, similar to social media platforms. This is useful for collaborative spreadsheets, task management, and commenting systems.
Overview
Mentions enable:
User tagging: Reference team members in cells
Entity references: Link to external resources or data
Autocomplete: Dropdown suggestions as users type
Custom rendering: Display mentions with custom styling
Data binding: Connect mentions to your application's data model
Basic Usage
import { SpreadsheetProvider, CanvasGrid } from "@rowsncolumns/spreadsheet";
import { useCallback } from "react";
function SpreadsheetWithMentions() {
const getMentions = useCallback(async (query?: string) => {
// Fetch mentions from your API or database
return [
{ label: "John Doe", value: "user-123" },
{ label: "Jane Smith", value: "user-456" },
{ label: "Marketing Team", value: "team-789" },
];
}, []);
return (
<SpreadsheetProvider>
<CanvasGrid
sheetId={1}
getMentions={getMentions}
// ... other props
/>
</SpreadsheetProvider>
);
}getMentions Function
The getMentions callback is called when users type @ in a cell. It should return a Promise that resolves to an array of mention objects.
Function Signature
type MentionItem = {
label: string; // Display text in dropdown
value: string; // Unique identifier
[key: string]: any; // Additional custom properties
};
type GetMentions = (query?: string) => Promise<MentionItem[]>;Parameters
query (optional): The search text entered by the user after
@
Example with Search
const getMentions = useCallback(async (query?: string) => {
// Filter mentions based on query
const allMentions = [
{ label: "Alice Johnson", value: "user-1", email: "[email protected]" },
{ label: "Bob Wilson", value: "user-2", email: "[email protected]" },
{ label: "Charlie Brown", value: "user-3", email: "[email protected]" },
];
if (!query) {
return allMentions;
}
// Filter by query
const filtered = allMentions.filter((mention) =>
mention.label.toLowerCase().includes(query.toLowerCase())
);
return filtered;
}, []);Custom Dropdown Rendering
Customize how mentions appear in the autocomplete dropdown using MentionDropdownItemComponent:
import { SpreadsheetProvider, CanvasGrid } from "@rowsncolumns/spreadsheet";
function SpreadsheetWithCustomMentions() {
const MentionItem = ({ mention }) => {
return (
<div className="flex items-center gap-2 p-2">
<img
src={mention.avatar}
alt={mention.label}
className="w-8 h-8 rounded-full"
/>
<div>
<div className="font-semibold">{mention.label}</div>
<div className="text-xs text-gray-500">{mention.email}</div>
</div>
</div>
);
};
return (
<SpreadsheetProvider>
<CanvasGrid
sheetId={1}
getMentions={getMentions}
MentionDropdownItemComponent={MentionItem}
// ... other props
/>
</SpreadsheetProvider>
);
}MentionDropdownItemComponent Props
type MentionDropdownItemProps = {
mention: MentionItem;
isSelected?: boolean;
onClick?: () => void;
};Async Data Loading
Fetch mentions from an API:
const getMentions = useCallback(async (query?: string) => {
try {
const response = await fetch(
`/api/mentions?query=${encodeURIComponent(query || '')}`
);
const data = await response.json();
return data.mentions;
} catch (error) {
console.error("Failed to fetch mentions:", error);
return [];
}
}, []);Complete Example
import React, { useCallback, useState } from "react";
import {
SpreadsheetProvider,
CanvasGrid,
Sheet,
} from "@rowsncolumns/spreadsheet";
import {
useSpreadsheetState,
SheetData,
CellData,
} from "@rowsncolumns/spreadsheet-state";
// Mock user database
const users = [
{
label: "Alice Johnson",
value: "user-1",
email: "[email protected]",
department: "Engineering",
avatar: "/avatars/alice.jpg",
},
{
label: "Bob Smith",
value: "user-2",
email: "[email protected]",
department: "Marketing",
avatar: "/avatars/bob.jpg",
},
{
label: "Charlie Davis",
value: "user-3",
email: "[email protected]",
department: "Sales",
avatar: "/avatars/charlie.jpg",
},
];
function TaskSpreadsheet() {
const [sheets, setSheets] = useState<Sheet[]>([
{ sheetId: 1, rowCount: 100, columnCount: 10, title: "Tasks" }
]);
const [sheetData, setSheetData] = useState<SheetData<CellData>>({});
const {
activeCell,
activeSheetId,
selections,
getCellData,
onChangeActiveCell,
onChangeSelections,
onChange,
} = useSpreadsheetState({
sheets,
sheetData,
onChangeSheets: setSheets,
onChangeSheetData: setSheetData,
});
// Fetch mentions with search
const getMentions = useCallback(async (query?: string) => {
// Simulate API delay
await new Promise((resolve) => setTimeout(resolve, 100));
if (!query) {
return users;
}
// Filter by name or email
return users.filter((user) =>
user.label.toLowerCase().includes(query.toLowerCase()) ||
user.email.toLowerCase().includes(query.toLowerCase()) ||
user.department.toLowerCase().includes(query.toLowerCase())
);
}, []);
// Custom mention dropdown item
const MentionDropdownItem = ({ mention }) => {
return (
<div className="flex items-center gap-3 px-3 py-2 hover:bg-gray-100">
<img
src={mention.avatar}
alt={mention.label}
className="w-10 h-10 rounded-full"
/>
<div className="flex-1">
<div className="font-medium text-sm">{mention.label}</div>
<div className="text-xs text-gray-600">{mention.email}</div>
</div>
<span className="text-xs text-gray-500 bg-gray-200 px-2 py-1 rounded">
{mention.department}
</span>
</div>
);
};
return (
<SpreadsheetProvider>
<CanvasGrid
sheetId={activeSheetId}
activeCell={activeCell}
selections={selections}
getCellData={getCellData}
onChangeActiveCell={onChangeActiveCell}
onChangeSelections={onChangeSelections}
onChange={onChange}
getMentions={getMentions}
MentionDropdownItemComponent={MentionDropdownItem}
/>
</SpreadsheetProvider>
);
}
export default TaskSpreadsheet;Accessing Mention Data
When a mention is selected, it's stored in the cell data. You can access it through the cell's data structure:
const cellData = getCellData(sheetId, rowIndex, columnIndex);
// Check if cell contains mentions
if (cellData?.mentions) {
cellData.mentions.forEach((mention) => {
console.log("Mentioned user:", mention.label, mention.value);
});
}Use Cases
Team Collaboration
// Track task assignments
const getMentions = async () => [
{ label: "@alice", value: "user-1", role: "Developer" },
{ label: "@bob", value: "user-2", role: "Designer" },
];Project Management
// Reference project resources
const getMentions = async (query) => {
return [
{ label: "@project-alpha", value: "proj-1", type: "project" },
{ label: "@milestone-q1", value: "mile-1", type: "milestone" },
{ label: "@alice", value: "user-1", type: "user" },
];
};Comments and Notes
// Add comments with user mentions
const getMentions = async () => {
const teamMembers = await fetchTeamMembers();
return teamMembers.map((member) => ({
label: `@${member.username}`,
value: member.id,
name: member.fullName,
}));
};Styling
Mentions in cells can be styled using custom text format runs. The spreadsheet automatically formats mentioned text when rendered.
Performance Optimization
Debounced Search
import { useMemo } from "react";
import debounce from "lodash/debounce";
function SpreadsheetWithMentions() {
const fetchMentions = async (query?: string) => {
const response = await fetch(`/api/mentions?q=${query}`);
return response.json();
};
const getMentions = useMemo(
() => debounce(fetchMentions, 300),
[]
);
return (
<CanvasGrid
getMentions={getMentions}
// ... other props
/>
);
}Caching
const mentionCache = new Map();
const getMentions = useCallback(async (query?: string) => {
const cacheKey = query || "all";
if (mentionCache.has(cacheKey)) {
return mentionCache.get(cacheKey);
}
const mentions = await fetchMentions(query);
mentionCache.set(cacheKey, mentions);
return mentions;
}, []);Best Practices
Limit Results: Return a maximum of 10-20 mentions to keep the dropdown manageable
Fast Response: Keep the
getMentionsfunction fast (< 300ms) for good UXError Handling: Handle API failures gracefully and return an empty array
Unique Values: Ensure each mention has a unique
valuepropertyClear Labels: Use descriptive labels that help users identify the right mention
Type Safety: Use TypeScript to define your mention structure
Troubleshooting
Mentions Not Appearing
Verify
getMentionsreturns an array of objects withlabelandvaluepropertiesCheck that the function returns a Promise
Ensure there are no JavaScript errors in the console
Search Not Working
Confirm the
queryparameter is being used in your filter logicTest the search function independently
Check for case sensitivity issues
Slow Performance
Implement debouncing for API calls
Add caching for frequently accessed results
Limit the number of returned results
Use pagination for large datasets
Last updated
Was this helpful?