import React, { useState } from 'react'; import { cn } from '../../utils/cn'; import { BaseTableData } from '../../types'; export interface TableAction { /** * Unique identifier for the action */ id: string; /** * Label text for the action */ label: string; /** * Icon for the action button */ icon?: React.ReactNode; /** * Action handler function */ onClick?: (data: any, event: React.MouseEvent) => void; /** * Visual variant of the action button */ variant?: 'default' | 'primary' | 'destructive' | 'outline' | 'ghost' | 'link'; /** * Whether the action button is disabled */ disabled?: boolean; /** * Tooltip text to show on hover */ tooltip?: string; /** * Whether to show this action in the dropdown menu */ inDropdown?: boolean; } export interface TableActionsProps { /** * Row data object */ rowData?: T; /** * Actions to show in the row */ actions: TableAction[]; /** * Maximum number of actions to show before using dropdown */ maxVisibleActions?: number; /** * Alignment of actions */ align?: 'left' | 'center' | 'right'; /** * Whether the action buttons are fixed to the right side */ fixed?: boolean; /** * Size of action buttons */ size?: 'sm' | 'md' | 'lg'; /** * Custom class name */ className?: string; /** * Use modern style for action buttons */ modern?: boolean; /** * Display mode: icon only, text only, or both */ displayMode?: 'icon' | 'text' | 'both'; } /** * TableActions component - Displays action buttons for table rows * with dynamic sizing based on the number of buttons */ export const TableActions = ({ rowData, actions = [], maxVisibleActions = 3, align = 'center', fixed = false, size = 'md', className, modern = true, displayMode = 'icon', }: TableActionsProps) => { const [dropdownOpen, setDropdownOpen] = useState(false); const [containerWidth, setContainerWidth] = useState(0); const containerRef = React.useRef(null); // Calculate available width and adjust button display accordingly React.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(); }, []); // Filter out actions that might be null or undefined const validActions = actions.filter(Boolean); // Calculate how many buttons can fit based on container width // Each button is approximately 36px wide including gaps const buttonWidth = 36; const availableButtons = containerWidth > 0 ? Math.floor(containerWidth / buttonWidth) : maxVisibleActions; // Determine which actions to display inline vs. in dropdown const visibleActions = validActions.filter(action => !action.inDropdown).slice(0, availableButtons); const dropdownActions = [ ...validActions.filter(action => action.inDropdown), ...validActions.filter(action => !action.inDropdown).slice(availableButtons) ]; const hasDropdownActions = dropdownActions.length > 0; // Toggle dropdown menu const toggleDropdown = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); setDropdownOpen(!dropdownOpen); }; // Close dropdown when clicking outside React.useEffect(() => { const handleClickOutside = () => { if (dropdownOpen) { setDropdownOpen(false); } }; document.addEventListener('click', handleClickOutside); return () => { document.removeEventListener('click', handleClickOutside); }; }, [dropdownOpen]); // Handle action click const handleActionClick = (action: TableAction, e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); if (action.onClick && !action.disabled) { action.onClick(rowData, e); } if (dropdownOpen) { setDropdownOpen(false); } }; // Size modifier for CSS class const sizeClass = size === 'sm' ? `${modern ? 'rpt-modern-action-btn' : 'rpt-action-btn'}--sm` : size === 'lg' ? `${modern ? 'rpt-modern-action-btn' : 'rpt-action-btn'}--lg` : ''; // Get action button classes based on modern flag const getActionBtnClasses = (action: TableAction) => { if (modern) { return cn( 'rpt-modern-action-btn', `rpt-modern-action-btn--${action.variant || getDefaultVariant(action)}`, sizeClass, displayMode === 'text' && 'rpt-modern-action-btn--text', displayMode === 'both' && 'rpt-modern-action-btn--both' ); } return cn( 'rpt-action-btn', `rpt-action-btn--${action.variant || getDefaultVariant(action)}`, sizeClass, displayMode === 'text' && 'rpt-action-btn--text', displayMode === 'both' && 'rpt-action-btn--both' ); }; // Get default variant based on action ID const getDefaultVariant = (action: TableAction): string => { if (action.id.includes('view') || action.id.includes('detail')) return 'view'; if (action.id.includes('edit') || action.id.includes('update')) return 'edit'; if (action.id.includes('delete') || action.id.includes('remove')) return 'delete'; return 'default'; }; // Get icon wrapper class based on modern flag const getIconClass = () => { return modern ? 'rpt-modern-action-icon' : 'rpt-action-icon'; }; // Get icon based on action type const getActionIcon = (action: TableAction) => { if (action.icon) { return action.icon; } const iconType = action.id.includes('view') ? 'view' : action.id.includes('edit') ? 'edit' : action.id.includes('delete') ? 'delete' : 'default'; switch(iconType) { case 'view': return ( ); case 'edit': return ( ); case 'delete': return ( ); default: return ( ); } }; return (
{/* Visible action buttons */} {visibleActions.map(action => ( ))} {/* Dropdown menu for additional actions */} {hasDropdownActions && (
{dropdownOpen && (
{dropdownActions.map((action, index) => ( {index < dropdownActions.length - 1 &&
} ))}
)}
)}
); }; export default TableActions;