Events fired by rows.
Row events allow you to respond to user interactions with rows. Handle clicks, double-clicks, hover, and more to create interactive data grids.
<ActiveGrid
onRowClick={(row) => console.log('Clicked:', row)}
onRowDoubleClick={(row) => handleEdit(row)}
onRowHover={(row) => showPreview(row)}
{...}
/>Triggered when a row is clicked.
<ActiveGrid<User>
onRowClick={(row: User) => {
console.log('Clicked user:', row.name);
router.push(`/users/${row.id}`);
}}
{...}
/>import { useRouter } from 'next/navigation';
function UsersTable() {
const router = useRouter();
return (
<ActiveGrid
onRowClick={(row) => {
router.push(`/users/${row.id}`);
}}
{...}
/>
);
}function OrdersTable() {
const [selectedOrder, setSelectedOrder] = useState<Order | null>(null);
return (
<>
<ActiveGrid
onRowClick={(row) => {
setSelectedOrder(row);
openModal();
}}
{...}
/>
{selectedOrder && (
<OrderDetailsModal order={selectedOrder} />
)}
</>
);
}function SelectableTable() {
const [selected, setSelected] = useState<string[]>([]);
return (
<ActiveGrid
onRowClick={(row) => {
setSelected(prev =>
prev.includes(row.id)
? prev.filter(id => id !== row.id)
: [...prev, row.id]
);
}}
{...}
/>
);
}Triggered when a row is double-clicked.
<ActiveGrid<User>
onRowDoubleClick={(row: User) => {
handleEdit(row);
}}
{...}
/>function EditableTable() {
const router = useRouter();
return (
<ActiveGrid
onRowDoubleClick={(row) => {
router.push(`/users/${row.id}/edit`);
}}
{...}
/>
);
}function ProductsTable() {
const [editing, setEditing] = useState<Product | null>(null);
return (
<>
<ActiveGrid
onRowDoubleClick={(row) => {
setEditing(row);
}}
{...}
/>
{editing && (
<EditProductModal
product={editing}
onClose={() => setEditing(null)}
/>
)}
</>
);
}Triggered when mouse enters/leaves a row.
<ActiveGrid<User>
onRowHover={(row: User | null) => {
if (row) {
console.log('Hovering:', row.name);
} else {
console.log('Mouse left row');
}
}}
{...}
/>function ProductsTable() {
const [preview, setPreview] = useState<Product | null>(null);
return (
<div className="relative">
<ActiveGrid
onRowHover={(row) => setPreview(row)}
{...}
/>
{preview && (
<div className="absolute top-0 right-0 p-4 bg-white shadow-lg">
<h3>{preview.name}</h3>
<p>{preview.description}</p>
</div>
)}
</div>
);
}Use multiple events together:
function InteractiveTable() {
const [hovered, setHovered] = useState<string | null>(null);
return (
<ActiveGrid
// Single click for selection
onRowClick={(row) => {
console.log('Selected:', row.id);
}}
// Double click for editing
onRowDoubleClick={(row) => {
router.push(`/edit/${row.id}`);
}}
// Hover for preview
onRowHover={(row) => {
setHovered(row?.id || null);
}}
// Highlight hovered row
settings={{
rowClassRules: {
'bg-blue-50': (row) => row.id === hovered,
},
}}
{...}
/>
);
}Stop event propagation when needed:
{
id: 'actions',
header: 'Actions',
cell: ({ row }) => (
<Button
onClick={(e) => {
e.stopPropagation(); // Prevent row click
handleAction(row.original);
}}
>
Action
</Button>
),
}Execute events based on conditions:
<ActiveGrid
onRowClick={(row) => {
// Only navigate if row is active
if (row.status === 'active') {
router.push(`/users/${row.id}`);
} else {
toast.error('User is inactive');
}
}}
{...}
/>Track row interactions:
<ActiveGrid
onRowClick={(row) => {
analytics.track('Row Clicked', {
rowId: row.id,
rowType: 'user',
timestamp: new Date(),
});
router.push(`/users/${row.id}`);
}}
{...}
/>Confirm before action:
<ActiveGrid
onRowDoubleClick={async (row) => {
const confirmed = confirm(
`Delete ${row.name}? This cannot be undone.`
);
if (confirmed) {
await deleteUser(row.id);
toast.success('User deleted');
}
}}
{...}
/>Ensure keyboard users can trigger actions:
<ActiveGrid
onRowClick={(row) => handleClick(row)}
// Add keyboard handler
onCellActivate={(row, columnId) => {
if (columnId === 'name') {
handleClick(row);
}
}}
{...}
/>import { useState } from 'react';
import { useRouter } from 'next/navigation';
type User = {
id: string;
name: string;
email: string;
status: 'active' | 'inactive';
};
function UsersTable() {
const router = useRouter();
const [hovered, setHovered] = useState<string | null>(null);
const [selected, setSelected] = useState<string[]>([]);
const handleRowClick = (user: User) => {
// Single click for selection
setSelected(prev =>
prev.includes(user.id)
? prev.filter(id => id !== user.id)
: [...prev, user.id]
);
// Track analytics
analytics.track('User Row Clicked', {
userId: user.id,
userName: user.name,
});
};
const handleRowDoubleClick = (user: User) => {
// Double click for navigation
if (user.status === 'active') {
router.push(`/users/${user.id}`);
} else {
toast.error('Cannot view inactive user');
}
};
const handleRowHover = (user: User | null) => {
setHovered(user?.id || null);
};
return (
<div className="relative">
<ActiveGrid<User>
columns={columns}
data={users}
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
onRowHover={handleRowHover}
settings={{
rowClassRules: {
'bg-blue-50 border-l-4 border-l-blue-500': (row) =>
selected.includes(row.id),
'bg-gray-50': (row) =>
hovered === row.id && !selected.includes(row.id),
},
}}
/>
{selected.length > 0 && (
<div className="fixed bottom-4 right-4 p-4 bg-white shadow-lg rounded">
<p className="font-medium mb-2">
{selected.length} user{selected.length > 1 ? 's' : ''} selected
</p>
<div className="flex gap-2">
<Button onClick={() => handleBulkAction(selected)}>
Bulk Action
</Button>
<Button variant="outline" onClick={() => setSelected([])}>
Clear
</Button>
</div>
</div>
)}
</div>
);
}Distinguish between row and cell clicks:
Provide visual feedback for clickable rows:
CSS approach:
Handle touch events appropriately:
{
id: 'name',
header: 'Name',
cell: ({ row, getValue }) => (
<div
onClick={(e) => {
e.stopPropagation(); // Prevent row click
handleCellClick(row.original);
}}
>
{getValue()}
</div>
),
}<ActiveGrid
onRowClick={(row) => {
if (row.isClickable) {
router.push(`/items/${row.id}`);
}
}}
settings={{
rowClassRules: {
'cursor-not-allowed opacity-50': (row) => !row.isClickable,
'cursor-pointer': (row) => row.isClickable,
},
}}
{...}
/><ActiveGrid
onRowClick={handleClick}
settings={{
rowClassRules: {
'cursor-pointer hover:bg-gray-50 transition-colors': () => true,
},
}}
{...}
/>.grid-row {
cursor: pointer;
transition: background-color 0.15s ease;
}
.grid-row:hover {
background-color: #f9fafb;
}
.grid-row:active {
background-color: #f3f4f6;
}<ActiveGrid
onRowClick={(row) => {
// On mobile, single tap opens details
if (isMobile) {
openDetailsSheet(row);
} else {
// On desktop, click selects
toggleSelection(row.id);
}
}}
onRowDoubleClick={(row) => {
// Only on desktop
if (!isMobile) {
router.push(`/edit/${row.id}`);
}
}}
{...}
/>import { ActiveGrid } from '@workspace/active-grid';
type Product = {
id: string;
name: string;
price: number;
};
const handleRowClick = (product: Product) => {
console.log(product.name);
};
const handleRowDoubleClick = (product: Product) => {
router.push(`/products/${product.id}`);
};
<ActiveGrid<Product>
onRowClick={handleRowClick}
onRowDoubleClick={handleRowDoubleClick}
{...}
/>