import React, { useEffect } from 'react'; import { areEqual } from 'react-window'; import tw, { TwStyle } from 'twin.macro'; import Linkify from 'linkify-it'; import { cellTypeMap } from '../store'; import { DashIcon, DiffModifiedIcon, PlusIcon } from '@primer/octicons-react'; import DOMPurify from 'dompurify'; import { EditableCell } from './editable-cell'; const linkify = Linkify().add('ftp:', null).add('mailto:', null); interface CellProps { type: string; value: any; rawValue: any; formattedValue?: any; categoryColor?: string | TwStyle; style?: {}; status?: string; isNearRightEdge?: boolean; isNearBottomEdge?: boolean; isFirstColumn: boolean; isExtraBlankRow: boolean; hasStatusIndicator?: boolean; background?: string; isEditable: boolean; onCellChange?: (value: any) => void; onRowDelete?: () => void; isFocused: boolean; onFocusChange: (value: [number, number] | null) => void; onMouseEnter?: Function; } export const Cell = React.memo(function (props: CellProps) { const { type, value, rawValue, formattedValue, categoryColor, status, isFirstColumn, isExtraBlankRow, isNearRightEdge, isNearBottomEdge, isEditable, onCellChange, onRowDelete, isFocused, onFocusChange, background, style = {}, onMouseEnter = () => {}, } = props; // @ts-ignore const cellInfo = cellTypeMap[type]; const { cell: CellComponent } = cellInfo || {}; const displayValue = (formattedValue || value || '').toString(); const isLongValue = (displayValue || '').length > 23; const stringWithLinks = React.useMemo(() => { if (!displayValue) return ''; const sanitized = DOMPurify.sanitize(displayValue); // Does the sanitized string contain any links? if (!linkify.test(sanitized)) return sanitized; // If so, we need to linkify it. const matches = linkify.match(sanitized); // If there are no matches, we can just return the sanitized string. if (!matches || matches.length === 0) return sanitized; // Otherwise, let's naively use the first match. return ` ${matches[0].url} `; }, [value]); useEffect(() => { if (!isFocused) return; onMouseEnter(); }, [isFocused]); if (!cellInfo) return null; const StatusIcon = isFirstColumn && // @ts-ignore { new: PlusIcon, old: DashIcon, modified: DiffModifiedIcon, 'modified-row': DiffModifiedIcon, }[status || '']; const statusColor = (isFirstColumn && // @ts-ignore { new: 'text-green-400', old: 'text-pink-400', modified: 'text-yellow-500', 'modified-row': 'text-yellow-500', }[status || '']) || ''; return (
); }, areEqual); const CellInner = React.memo(function CellInner({ value, isFirstColumn, statusColor, StatusIcon, rawValue, categoryColor, isLongValue, isNearBottomEdge, isNearRightEdge, stringWithLinks, CellComponent, onMouseEnter, }: { value: any; isFirstColumn: boolean; statusColor: string | TwStyle; StatusIcon: any; rawValue: any; categoryColor?: string | TwStyle; isLongValue: boolean; isNearBottomEdge?: boolean; isNearRightEdge?: boolean; stringWithLinks: string; CellComponent?: any; onMouseEnter?: Function; }) { return (
onMouseEnter?.()} > {isFirstColumn && (
{StatusIcon && }
)} {isLongValue && (
)}
); });