import React, { useEffect, useRef } from 'react'; import { __ } from '@wordpress/i18n'; import { formatDate } from '@/utils/formatting'; import type { RevisionMarker } from '@/api/getPageRevisionsData'; interface PageRevisionsListProps { revisions: RevisionMarker[]; selectedRevisionId: number | null; onRevisionSelect: ( id: number | null ) => void; } /** * Format a revision's timestamp or date string to a localised human-readable label. * * @param {Pick} revision - Revision object with timestamp and/or date. * @return {string} Localised formatted date string. */ export function formatRevisionDate( revision: Pick ): string { const input = revision.timestamp || revision.date; return input ? formatDate( input ) : ''; } /** * Confidence label map for display. */ const CONFIDENCE_LABELS: Record = { high: __( 'High confidence', 'burst-statistics' ), moderate: __( 'Moderate confidence', 'burst-statistics' ), low: __( 'Low confidence', 'burst-statistics' ) }; /** * Single revision list item. */ const RevisionItem = React.forwardRef< HTMLButtonElement, { revision: RevisionMarker; isSelected: boolean; onClick: () => void; } // fallow-ignore-next-line complexity >( ({ revision, isSelected, onClick }, ref ) => { const isPositive = 0 < revision.changePercent; const isSignificant = 0.5 <= Math.abs( revision.changePercent ); const dotColor = isSelected ? 'rgb(34, 197, 94)' : 'var(--color-gray-300)'; const changeColor = ! isSignificant ? 'text-text-gray' : isPositive ? 'text-green-600' : 'text-red-500'; const changeDisplay = ! isSignificant ? `${ 0 < revision.changePercent ? '+' : '' }${ revision.changePercent }%` : `${ isPositive ? '+' : '' }${ revision.changePercent }%`; return ( ); }); RevisionItem.displayName = 'RevisionItem'; /** * PageRevisionsList renders the ranked list of revisions below the graph. * All items render inside a scrollable container with theme scrollbar styling. * Automatically scrolls selected items into view. * * @param {PageRevisionsListProps} props - Component props. * @return {JSX.Element | null} The revision list. */ const scrollToSelectedItem = ( container: HTMLDivElement | null, item: HTMLButtonElement | null ) => { if ( ! container || ! item ) { return; } const itemRelativeTop = item.getBoundingClientRect().top - container.getBoundingClientRect().top + container.scrollTop; container.scrollTo({ top: Math.max( 0, itemRelativeTop - 8 ), behavior: 'smooth' }); }; const PageRevisionsList: React.FC = ({ revisions, selectedRevisionId, onRevisionSelect }) => { const itemRefs = useRef>({}); const containerRef = useRef( null ); const isFirstRender = useRef( true ); // Scroll selected revision into container view without scrolling main window useEffect( () => { if ( isFirstRender.current ) { isFirstRender.current = false; return; } if ( null !== selectedRevisionId ) { scrollToSelectedItem( containerRef.current, itemRefs.current[ selectedRevisionId ]); } }, [ selectedRevisionId ]); if ( ! revisions.length ) { return null; } return (
{revisions.map( ( revision ) => ( { itemRefs.current[ revision.id ] = el; }} revision={revision} isSelected={revision.id === selectedRevisionId} onClick={() => onRevisionSelect( revision.id === selectedRevisionId ? null : revision.id ) } /> ) )}
); }; export default PageRevisionsList;