Configure row action menus.
Enhanced in v5.1
The row menu provides quick actions for individual rows. Enable built-in features like pinning, editing, and copying, or add custom actions.
interface GridRowMenuConfig<TData = any> {
enabled?: boolean;
hidden?: boolean; // Deprecated, use enabled
features?: {
pin?: boolean;
edit?: boolean;
copy?: boolean;
copyWithHeaders?: boolean;
};
customItems?: (row: TData) => ReactNode;
}<ActiveGrid
columns={columns}
data={data}
rowMenu={{
enabled: true,
features: {
pin: true,
copy: true,
},
}}
{...}
/>booleantruerowMenu={{
enabled: true,
}}booleanenabled: false instead// Old way (deprecated)
rowMenu={{
hidden: true,
}}
// New way
rowMenu={{
enabled: false,
}}New in v5.1
booleantrue (if enableRowPinning is true)Options shown:
booleantrue (if editMode is set)booleanfalseCopies row data to clipboard (without headers).
booleanfalseCopies row data with column headers.
Add custom menu items per row:
Show different items based on row data:
The row menu is accessible via:
User can:
User can:
User can:
Row menu is accessible via:
Row menus include:
<ActiveGrid
enableRowPinning={true}
rowMenu={{
features: {
pin: true,
},
}}
{...}
/><ActiveGrid
editMode="row"
rowMenu={{
features: {
edit: true,
},
}}
{...}
/>rowMenu={{
features: {
copy: true,
},
}}rowMenu={{
features: {
copy: true,
copyWithHeaders: true,
},
}}rowMenu={{
customItems: (row) => (
<>
<DropdownMenuItem onClick={() => handleView(row)}>
<Eye className="mr-2 h-4 w-4" />
View Details
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleDuplicate(row)}>
<Copy className="mr-2 h-4 w-4" />
Duplicate
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => handleDelete(row)}
className="text-destructive"
>
<Trash className="mr-2 h-4 w-4" />
Delete
</DropdownMenuItem>
</>
),
}}import { Eye, Copy, Trash, Pin } from 'lucide-react';
<ActiveGrid
columns={columns}
data={data}
enableRowPinning={true}
editMode="row"
rowMenu={{
enabled: true,
features: {
pin: true,
edit: true,
copy: true,
copyWithHeaders: true,
},
customItems: (row) => (
<>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => viewDetails(row)}>
<Eye className="mr-2 h-4 w-4" />
View Details
</DropdownMenuItem>
<DropdownMenuItem onClick={() => duplicate(row)}>
<Copy className="mr-2 h-4 w-4" />
Duplicate
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => deleteRow(row)}
className="text-destructive"
>
<Trash className="mr-2 h-4 w-4" />
Delete
</DropdownMenuItem>
</>
),
}}
/>rowMenu={{
customItems: (row) => {
const items = [];
// Always show view
items.push(
<DropdownMenuItem key="view" onClick={() => view(row)}>
<Eye className="mr-2 h-4 w-4" />
View
</DropdownMenuItem>
);
// Only show edit for active users
if (row.status === 'active') {
items.push(
<DropdownMenuItem key="edit" onClick={() => edit(row)}>
<Pencil className="mr-2 h-4 w-4" />
Edit
</DropdownMenuItem>
);
}
// Show different delete options
if (row.status === 'archived') {
items.push(
<DropdownMenuSeparator key="sep" />,
<DropdownMenuItem
key="delete"
onClick={() => permanentDelete(row)}
className="text-destructive"
>
<Trash className="mr-2 h-4 w-4" />
Delete Permanently
</DropdownMenuItem>
);
}
return <>{items}</>;
},
}}<ActiveGrid
enableRowPinning={true}
rowMenu={{
features: {
pin: true,
},
}}
{...}
/><ActiveGrid
editMode="row"
rowMenu={{
features: {
edit: true,
},
}}
{...}
/>rowMenu={{
features: {
copy: true,
copyWithHeaders: true,
},
}}import { useRouter } from 'next/navigation';
function UsersTable() {
const router = useRouter();
return (
<ActiveGrid
rowMenu={{
customItems: (row) => (
<>
<DropdownMenuItem onClick={() => router.push(`/users/${row.id}`)}>
<User className="mr-2 h-4 w-4" />
View Profile
</DropdownMenuItem>
<DropdownMenuItem onClick={() => router.push(`/users/${row.id}/edit`)}>
<Pencil className="mr-2 h-4 w-4" />
Edit User
</DropdownMenuItem>
</>
),
}}
{...}
/>
);
}type Order = {
id: string;
status: 'pending' | 'processing' | 'completed';
};
rowMenu={{
customItems: (row) => {
switch (row.status) {
case 'pending':
return (
<DropdownMenuItem onClick={() => startProcessing(row)}>
<Play className="mr-2 h-4 w-4" />
Start Processing
</DropdownMenuItem>
);
case 'processing':
return (
<DropdownMenuItem onClick={() => markComplete(row)}>
<Check className="mr-2 h-4 w-4" />
Mark Complete
</DropdownMenuItem>
);
case 'completed':
return (
<DropdownMenuItem onClick={() => viewInvoice(row)}>
<FileText className="mr-2 h-4 w-4" />
View Invoice
</DropdownMenuItem>
);
}
},
}}function UsersTable() {
const { permissions } = useAuth();
return (
<ActiveGrid
rowMenu={{
customItems: (row) => (
<>
{permissions.canEdit && (
<DropdownMenuItem onClick={() => edit(row)}>
<Pencil className="mr-2 h-4 w-4" />
Edit
</DropdownMenuItem>
)}
{permissions.canDelete && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => deleteRow(row)}
className="text-destructive"
>
<Trash className="mr-2 h-4 w-4" />
Delete
</DropdownMenuItem>
</>
)}
</>
),
}}
{...}
/>
);
}import {
Eye,
Pencil,
Copy,
Download,
Mail,
Archive,
Trash,
Pin,
} from 'lucide-react';
type User = {
id: string;
name: string;
email: string;
status: 'active' | 'inactive' | 'archived';
};
function UsersTable() {
const router = useRouter();
const { user: currentUser } = useAuth();
const handleAction = async (action: string, user: User) => {
switch (action) {
case 'view':
router.push(`/users/${user.id}`);
break;
case 'edit':
router.push(`/users/${user.id}/edit`);
break;
case 'duplicate':
await duplicateUser(user.id);
toast.success('User duplicated');
break;
case 'export':
await exportUser(user.id);
toast.success('Export complete');
break;
case 'email':
window.location.href = `mailto:${user.email}`;
break;
case 'archive':
if (confirm('Archive this user?')) {
await archiveUser(user.id);
toast.success('User archived');
}
break;
case 'delete':
if (confirm('Delete this user?')) {
await deleteUser(user.id);
toast.success('User deleted');
}
break;
}
};
return (
<ActiveGrid
columns={columns}
data={users}
enableRowPinning={true}
rowMenu={{
enabled: true,
features: {
pin: true,
copy: true,
copyWithHeaders: true,
},
customItems: (row) => {
const canEdit = row.id !== currentUser.id;
return (
<>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => handleAction('view', row)}>
<Eye className="mr-2 h-4 w-4" />
View Profile
</DropdownMenuItem>
{canEdit && (
<>
<DropdownMenuItem onClick={() => handleAction('edit', row)}>
<Pencil className="mr-2 h-4 w-4" />
Edit
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleAction('duplicate', row)}>
<Copy className="mr-2 h-4 w-4" />
Duplicate
</DropdownMenuItem>
</>
)}
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => handleAction('export', row)}>
<Download className="mr-2 h-4 w-4" />
Export
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleAction('email', row)}>
<Mail className="mr-2 h-4 w-4" />
Send Email
</DropdownMenuItem>
{canEdit && row.status === 'active' && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => handleAction('archive', row)}>
<Archive className="mr-2 h-4 w-4" />
Archive
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleAction('delete', row)}
className="text-destructive"
>
<Trash className="mr-2 h-4 w-4" />
Delete
</DropdownMenuItem>
</>
)}
</>
);
},
}}
/>
);
}import { GridRowMenuConfig } from '@workspace/active-grid';
type User = {
id: string;
name: string;
};
const rowMenuConfig: GridRowMenuConfig<User> = {
enabled: true,
features: {
pin: true,
copy: true,
},
customItems: (row: User) => (
<DropdownMenuItem onClick={() => console.log(row.id)}>
Custom Action
</DropdownMenuItem>
),
};
<ActiveGrid<User>
rowMenu={rowMenuConfig}
{...}
/>