132
common/resources/client/comments/comment-list/comment-list.tsx
Executable file
132
common/resources/client/comments/comment-list/comment-list.tsx
Executable file
@@ -0,0 +1,132 @@
|
||||
import {Comment} from '@common/comments/comment';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {CommentIcon} from '@common/icons/material/Comment';
|
||||
import {Commentable} from '@common/comments/commentable';
|
||||
import {useComments} from '@common/comments/requests/use-comments';
|
||||
import {InfiniteScrollSentinel} from '@common/ui/infinite-scroll/infinite-scroll-sentinel';
|
||||
import {AnimatePresence, m} from 'framer-motion';
|
||||
import {opacityAnimation} from '@common/ui/animation/opacity-animation';
|
||||
import {FormattedNumber} from '@common/i18n/formatted-number';
|
||||
import {IllustratedMessage} from '@common/ui/images/illustrated-message';
|
||||
import {CommentListItem} from '@common/comments/comment-list/comment-list-item';
|
||||
import {Skeleton} from '@common/ui/skeleton/skeleton';
|
||||
import {ReactNode} from 'react';
|
||||
import {AccountRequiredCard} from '@common/comments/comment-list/account-required-card';
|
||||
import {message} from '@common/i18n/message';
|
||||
|
||||
const accountRequiredMessage = message(
|
||||
'Please <l>login</l> or <r>create account</r> to comment'
|
||||
);
|
||||
|
||||
interface CommentListProps {
|
||||
commentable: Commentable;
|
||||
canDeleteAllComments?: boolean;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
perPage?: number;
|
||||
}
|
||||
export function CommentList({
|
||||
className,
|
||||
commentable,
|
||||
canDeleteAllComments = false,
|
||||
children,
|
||||
perPage = 25,
|
||||
}: CommentListProps) {
|
||||
const {items, totalItems, ...query} = useComments(commentable, {perPage});
|
||||
|
||||
if (query.isError) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="mb-8 pb-8 border-b flex items-center gap-8">
|
||||
<CommentIcon size="sm" className="text-muted" />
|
||||
{query.isInitialLoading ? (
|
||||
<Trans message="Loading comments..." />
|
||||
) : (
|
||||
<Trans
|
||||
message=":count comments"
|
||||
values={{count: <FormattedNumber value={totalItems || 0} />}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{children}
|
||||
<AccountRequiredCard message={accountRequiredMessage} />
|
||||
<AnimatePresence initial={false} mode="wait">
|
||||
{query.isInitialLoading ? (
|
||||
<CommentSkeletons count={4} />
|
||||
) : (
|
||||
<CommentListItems
|
||||
comments={items}
|
||||
canDeleteAllComments={canDeleteAllComments}
|
||||
commentable={commentable}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<InfiniteScrollSentinel query={query} variant="loadMore" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface CommentListItemsProps {
|
||||
comments: Comment[];
|
||||
canDeleteAllComments: boolean;
|
||||
commentable: Commentable;
|
||||
}
|
||||
function CommentListItems({
|
||||
comments,
|
||||
commentable,
|
||||
canDeleteAllComments,
|
||||
}: CommentListItemsProps) {
|
||||
if (!comments.length) {
|
||||
return (
|
||||
<IllustratedMessage
|
||||
className="mt-24"
|
||||
size="sm"
|
||||
title={<Trans message="Seems a little quiet over here" />}
|
||||
description={<Trans message="Be the first to comment" />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<m.div key="comments" {...opacityAnimation}>
|
||||
{comments.map(comment => (
|
||||
<CommentListItem
|
||||
key={comment.id}
|
||||
comment={comment}
|
||||
commentable={commentable}
|
||||
canDelete={canDeleteAllComments}
|
||||
/>
|
||||
))}
|
||||
</m.div>
|
||||
);
|
||||
}
|
||||
|
||||
interface CommentSkeletonsProps {
|
||||
count: number;
|
||||
}
|
||||
function CommentSkeletons({count}: CommentSkeletonsProps) {
|
||||
return (
|
||||
<m.div key="loading-skeleton" {...opacityAnimation}>
|
||||
{[...new Array(count).keys()].map(index => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-start gap-24 py-18 min-h-70 group"
|
||||
>
|
||||
<Skeleton variant="avatar" radius="rounded-full" size="w-60 h-60" />
|
||||
<div className="text-sm flex-auto">
|
||||
<Skeleton className="text-base max-w-184 mb-4" />
|
||||
<Skeleton className="text-sm" />
|
||||
<div className="flex items-center gap-8 mt-10">
|
||||
<Skeleton className="text-sm max-w-70" />
|
||||
<Skeleton className="text-sm max-w-40" />
|
||||
<Skeleton className="text-sm max-w-60" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</m.div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user