import React, { useState } from 'react'; import { Button } from '../../components/Button/Button'; import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, } from '../../components/DropdownMenu'; import { Icons } from '../../components/Icons/Icons'; import { Tooltip, TooltipTrigger, TooltipContent } from '../../components/Tooltip/Tooltip'; import { cn } from '../../lib/utils'; import { useTranslation } from 'react-i18next'; /** * DataRow is a complex UI component that displays a selectable, interactive row with hierarchical data. * It's designed to show a numbered item with a title, optional color indicator, and expandable details. * The row supports various interactive features like visibility toggling, locking, and contextual actions. * * @component * @example * ```tsx * // Basic usage without status * {}} * onToggleLocked={() => {}} * onRename={() => {}} * onDelete={() => {}} * onColor={() => {}} * /> * * // With warning status using composite pattern * * * * * // With success status using composite pattern * * * * * // Multiple status indicators * * * * * * ``` */ /** * Props for the DataRow component * @interface DataRowProps * @property {number} number - The display number/index of the row * @property {string} title - The main text label for the row * @property {boolean} disableEditing - When true, prevents rename and delete operations * @property {string} [colorHex] - Optional hex color code to display a color indicator * @property {Object} [details] - Optional hierarchical details to display below the row * @property {string[]} details.primary - Primary details shown immediately below the row * @property {string[]} details.secondary - Secondary details (currently unused) * @property {boolean} [isSelected] - Whether the row is currently selected * @property {() => void} [onSelect] - Callback when the row is clicked/selected * @property {boolean} isVisible - Controls the row's visibility state * @property {() => void} onToggleVisibility - Callback to toggle visibility * @property {boolean} isLocked - Controls the row's locked state * @property {() => void} onToggleLocked - Callback to toggle locked state * @property {() => void} onRename - Callback when rename is requested * @property {() => void} onDelete - Callback when delete is requested * @property {() => void} onColor - Callback when color change is requested * @property {React.ReactNode} children - Optional children, including Status components */ interface DataRowProps { number: number | null; disableEditing: boolean; description: string; details?: { primary: string[]; secondary: string[] }; // /** Primary selection: selected and in the active segmentation */ isSelected?: boolean; /** Secondary selection: selected but in an inactive segmentation */ isSecondarySelected?: boolean; onSelect?: (e) => void; // isVisible: boolean; onToggleVisibility: (e) => void; // isLocked: boolean; onToggleLocked: (e) => void; // title: string; onRename: (e) => void; // onDelete: (e) => void; // colorHex?: string; onColor: (e) => void; onCopy?: (e) => void; className?: string; children?: React.ReactNode; } const DataRowComponent = React.forwardRef( ( { number, title, colorHex, details, onSelect, isLocked, onToggleVisibility, onToggleLocked, onRename, onDelete, onColor, onCopy, isSelected = false, isSecondarySelected = false, isVisible = true, disableEditing = false, className, children, }, ref ) => { const { t } = useTranslation('DataRow'); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const isTitleLong = title?.length > 25; // Extract Status components from children const statusComponents = React.Children.toArray(children).filter( child => React.isValidElement(child) && child.type && (child.type as React.ComponentType).displayName?.startsWith('DataRow.Status') ); const handleAction = (action: string, e: React.MouseEvent) => { e.stopPropagation(); switch (action) { case 'Rename': onRename(e); break; case 'Copy': onCopy?.(e); break; case 'Lock': onToggleLocked(e); break; case 'Delete': onDelete(e); break; case 'Color': onColor(e); break; } }; const decodeHTML = (html: string) => { const txt = document.createElement('textarea'); txt.innerHTML = html; return txt.value; }; const renderDetailText = (text: string, indent: number = 0) => { const indentation = ' '.repeat(indent); if (text === '') { return (
); } const cleanText = decodeHTML(text); return (
{indentation} {cleanText}
); }; const renderDetails = (details: string[], variant: 'primary' | 'secondary') => { const visibleLines = details.slice(0, 4); const hiddenLines = details.slice(4); return (
{visibleLines.map((line, lineIndex) => renderDetailText(line, line.startsWith(' ') ? 1 : 0) )}
{hiddenLines.length > 0 && (
...
)}
{details.map((line, lineIndex) => renderDetailText(line, line.startsWith(' ') ? 1 : 0) )}
); }; return (
{/* Secondary Selection Tint (below hover, always visible when secondary-selected) */} {isSecondarySelected && (
)}
{/* Number Box */} {number !== null && (
{number}
)} {/* add some space if there is not segment index */} {number === null &&
} {colorHex && (
)} {/* Label with Conditional Tooltip */}
{isTitleLong ? ( {title} {title} ) : ( {title} )}
{/* Actions and Visibility Toggle */}
{/* Visibility Toggle Icon */} {/* Lock Icon (if needed) */} {isLocked && !disableEditing && ( )} {/* Status Components */} {statusComponents} {/* Actions Dropdown Menu */} {disableEditing &&
} {!disableEditing && ( setIsDropdownOpen(open)}> e.preventDefault()} > <> handleAction('Rename', e)}> {t('Rename')} {onCopy && ( handleAction('Copy', e)}> {t('Duplicate')} )} handleAction('Delete', e)}> {t('Delete')} {onColor && ( handleAction('Color', e)}> {t('Change Color')} )} handleAction('Lock', e)}> {isLocked ? t('Unlock') : t('Lock')} )}
{/* Details Section */} {details && (details.primary?.length > 0 || details.secondary?.length > 0) && (
{details.primary?.length > 0 && renderDetails(details.primary, 'primary')} {details.secondary?.length > 0 && (
{renderDetails(details.secondary, 'secondary')}
)}
)}
); } ); DataRowComponent.displayName = 'DataRow'; interface StatusProps { children: React.ReactNode; } interface StatusIndicatorProps { tooltip?: string; icon: React.ReactNode; defaultTooltip: string; } const StatusIndicator: React.FC = ({ tooltip, icon, defaultTooltip }) => (
{icon}
{tooltip || defaultTooltip}
); const Status: React.FC & { Warning: React.FC<{ tooltip?: string }>; Success: React.FC<{ tooltip?: string }>; Error: React.FC<{ tooltip?: string }>; Info: React.FC<{ tooltip?: string }>; } = ({ children }) => { return <>{children}; }; const StatusWarning: React.FC<{ tooltip?: string }> = ({ tooltip }) => ( } defaultTooltip="Warning" /> ); const StatusSuccess: React.FC<{ tooltip?: string }> = ({ tooltip }) => ( } defaultTooltip="Success" /> ); const StatusError: React.FC<{ tooltip?: string }> = ({ tooltip }) => ( } defaultTooltip="Error" /> ); const StatusInfo: React.FC<{ tooltip?: string }> = ({ tooltip }) => ( } defaultTooltip="Info" /> ); Status.displayName = 'DataRow.Status'; StatusWarning.displayName = 'DataRow.Status.Warning'; StatusSuccess.displayName = 'DataRow.Status.Success'; StatusError.displayName = 'DataRow.Status.Error'; StatusInfo.displayName = 'DataRow.Status.Info'; Status.Warning = StatusWarning; Status.Success = StatusSuccess; Status.Error = StatusError; Status.Info = StatusInfo; const DataRow = DataRowComponent as typeof DataRowComponent & { Status: typeof Status; }; DataRow.Status = Status; export default DataRow; export { DataRow };