Files
timeline-frontend/src/components/Comments/CommentList.tsx

130 lines
3.4 KiB
TypeScript
Raw Normal View History

/**
* CommentList Component
* Feature: personal-user-enhancements
*
* Displays a list of comments with virtualization for long lists
*/
import React, { useMemo } from 'react';
import { Empty, Divider, Spin } from 'antd';
import { FixedSizeList as List } from 'react-window';
import CommentItem from './CommentItem';
export interface CommentListProps {
/** Array of comments to display */
comments: API.Comment[];
/** Loading state */
loading?: boolean;
/** Callback when comment is edited */
onEdit?: (id: string, text: string) => void | Promise<void>;
/** Callback when comment is deleted */
onDelete?: (id: string) => void | Promise<void>;
/** Loading state for edit operation */
editLoading?: boolean;
/** Loading state for delete operation */
deleteLoading?: boolean;
/** Enable virtualization (default: true for >20 comments) */
enableVirtualization?: boolean;
/** Height of the list container (for virtualization) */
height?: number;
/** Item height estimate (for virtualization) */
itemHeight?: number;
}
const VIRTUALIZATION_THRESHOLD = 20;
const DEFAULT_ITEM_HEIGHT = 100;
const DEFAULT_LIST_HEIGHT = 600;
const CommentList: React.FC<CommentListProps> = ({
comments,
loading = false,
onEdit,
onDelete,
editLoading = false,
deleteLoading = false,
enableVirtualization,
height = DEFAULT_LIST_HEIGHT,
itemHeight = DEFAULT_ITEM_HEIGHT,
}) => {
// Sort comments chronologically (oldest first)
const sortedComments = useMemo(() => {
return [...comments].sort((a, b) => {
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
});
}, [comments]);
// Determine if virtualization should be used
const shouldVirtualize = useMemo(() => {
if (enableVirtualization !== undefined) {
return enableVirtualization;
}
return sortedComments.length > VIRTUALIZATION_THRESHOLD;
}, [enableVirtualization, sortedComments.length]);
// Show loading state
if (loading) {
return (
<div style={{ textAlign: 'center', padding: '40px 0' }}>
<Spin size="large" />
</div>
);
}
// Show empty state
if (sortedComments.length === 0) {
return (
<Empty
description="No comments yet"
style={{ padding: '40px 0' }}
/>
);
}
// Render a single comment item
const renderCommentItem = (comment: API.Comment, index: number) => (
<div key={comment.id}>
<CommentItem
comment={comment}
onEdit={onEdit}
onDelete={onDelete}
editLoading={editLoading}
deleteLoading={deleteLoading}
/>
{index < sortedComments.length - 1 && <Divider style={{ margin: '8px 0' }} />}
</div>
);
// Render with virtualization for long lists
if (shouldVirtualize) {
const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => {
const comment = sortedComments[index];
return (
<div style={style}>
{renderCommentItem(comment, index)}
</div>
);
};
return (
<List
height={height}
itemCount={sortedComments.length}
itemSize={itemHeight}
width="100%"
overscanCount={5}
>
{Row}
</List>
);
}
// Render without virtualization for short lists
return (
<div>
{sortedComments.map((comment, index) => renderCommentItem(comment, index))}
</div>
);
};
export default CommentList;