DataTableRecetas
Interacción por fila
Columna de acciones con menú, fila expandible y vista móvil de tarjetas.
Columna de acciones con menú
const ActionsCell = memo(({ row, actions }: {
row: OrderRow; actions: OrderActions;
}) => (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => actions.onView(row)}>Ver detalle</DropdownMenuItem>
<DropdownMenuItem onClick={() => actions.onEdit(row)}>Editar</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
));
ActionsCell.displayName = "ActionsCell";
// En el client — el patrón actionsRef mantiene columns estable:
const actionsRef = useRef({ onView, onEdit });
actionsRef.current = { onView, onEdit };
const columns = useMemo(() => createOrderColumns(actionsRef.current), []);El clic en el menú no dispara el onRowClick de la fila: la tabla ignora clics sobre botones, checkboxes, inputs, links y elementos con data-stop-propagation.
Fila expandible
const renderExpanded = useCallback((order: OrderRow) => (
<OrderItemsSummary orderId={order.id} />
), []);
<SmartDataTable
expansion={{ enabled: true, renderContent: renderExpanded, expandOnClick: false }}
/* … */
/>El contenido se monta solo al expandir. canExpand: (row) => boolean quita el chevron a filas sin detalle. No compatible con virtualización.
Vista móvil (tarjetas)
const renderCard = useCallback((order: OrderRow) => (
<div className="flex items-center justify-between gap-3">
<div>
<p className="font-medium">{order.code}</p>
<p className="text-sm text-muted-foreground">{order.clientName}</p>
</div>
<div className="text-right">
<p className="font-mono">{formatCurrency(order.amount)}</p>
<Badge>{order.status}</Badge>
</div>
</div>
), []);
<SmartDataTable mobileCardRender={renderCard} /* … */ />Bajo 768px cada fila usa tu tarjeta; el toolbar y la paginación se conservan. En desktop no cambia nada.