import React, { useState, useEffect } from 'react'; import { cn } from '../../utils/cn'; import { ActionConfig, BaseTableData, ButtonVariant } from '../../types'; export interface TableRowActionsProps { /** * Row data */ rowData: T; /** * Row index */ rowIndex: number; /** * Action configurations */ actions: ActionConfig[]; /** * Maximum number of inline actions before using dropdown */ maxInlineActions?: number; /** * Position of the actions */ position?: 'inline' | 'dropdown'; /** * Whether to show tooltips */ showTooltips?: boolean; /** * Button variant style */ buttonVariant?: ButtonVariant; /** * Custom CSS class */ className?: string; /** * Alignment of actions within the cell */ align?: 'left' | 'center' | 'right'; } export const TableRowActions = ({ rowData, rowIndex, actions = [], maxInlineActions = 3, position = 'inline', showTooltips = true, buttonVariant = 'ghost', className, align = 'center', }: TableRowActionsProps) => { const [dropdownOpen, setDropdownOpen] = useState(false); const [containerWidth, setContainerWidth] = useState(0); const containerRef = React.useRef(null); // Calculate available width and adjust button display accordingly useEffect(() => { if (!containerRef.current) return; const updateWidth = () => { if (containerRef.current) { setContainerWidth(containerRef.current.offsetWidth); } }; updateWidth(); // Create ResizeObserver to detect container width changes const resizeObserver = new ResizeObserver(updateWidth); resizeObserver.observe(containerRef.current); return () => resizeObserver.disconnect(); }, []); // Skip if no actions if (!actions || actions.length === 0) return null; // Filter actions based on hidden callback const visibleActions = actions.filter(action => !action.hidden || !action.hidden(rowData) ); if (visibleActions.length === 0) return null; // Calculate how many buttons can fit based on container width // Each button is approximately 40px wide including gaps const buttonWidth = 40; const availableButtons = containerWidth > 0 ? Math.floor(containerWidth / buttonWidth) : position === 'dropdown' ? 0 : Math.min(maxInlineActions, visibleActions.length); // If position is dropdown, show all actions in dropdown // Otherwise show inline actions up to available space, then rest in dropdown const inlineActions = position === 'dropdown' ? [] : visibleActions.slice(0, Math.min(availableButtons, maxInlineActions)); const dropdownActions = position === 'dropdown' ? visibleActions : visibleActions.length > inlineActions.length ? visibleActions.slice(inlineActions.length) : []; const hasDropdown = dropdownActions.length > 0; // Handle click on an action button const handleActionClick = (action: ActionConfig, e: React.MouseEvent) => { e.stopPropagation(); if (action.onClick && (!action.disabled || typeof action.disabled === 'function' && !action.disabled(rowData))) { action.onClick(rowData, rowIndex); } // Close dropdown after action if (dropdownOpen) { setDropdownOpen(false); } }; // Toggle dropdown menu const toggleDropdown = (e: React.MouseEvent) => { e.stopPropagation(); setDropdownOpen(!dropdownOpen); }; // Close dropdown if clicked outside React.useEffect(() => { if (!dropdownOpen) return; const handleClickOutside = () => setDropdownOpen(false); document.addEventListener('click', handleClickOutside); return () => { document.removeEventListener('click', handleClickOutside); }; }, [dropdownOpen]); // Get correct class for button const getButtonClass = (action: ActionConfig) => { const variant = action.variant || buttonVariant; const type = action.type || 'default'; return cn( 'rpt-action-btn', `rpt-action-btn--${type}`, variant !== 'default' && `rpt-action-btn--${variant}` ); }; // Check if button is disabled const isDisabled = (action: ActionConfig) => { if (typeof action.disabled === 'function') { return action.disabled(rowData); } return !!action.disabled; }; // Get icon for action based on type const getActionIcon = (type: string) => { switch(type) { case 'view': return ( ); case 'edit': return ( ); case 'delete': return ( ); default: return ( ); } }; return (
{/* Inline action buttons */} {inlineActions.map((action) => ( ))} {/* Dropdown menu button */} {hasDropdown && (
{/* Dropdown menu */} {dropdownOpen && (
{dropdownActions.map((action, index) => ( {index < dropdownActions.length - 1 && (
)} ))}
)}
)}
); }; export default TableRowActions;