Skip to content

Commit c71ed7c

Browse files
authored
feat: collection card enhancements (#5894)
1 parent 1efced8 commit c71ed7c

File tree

10 files changed

+152
-34
lines changed

10 files changed

+152
-34
lines changed

packages/shared/src/components/cards/collection/CollectionGrid.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import PostMetadata from '../common/PostMetadata';
1616
import { usePostImage } from '../../../hooks/post/usePostImage';
1717
import CardOverlay from '../common/CardOverlay';
1818
import PostTags from '../common/PostTags';
19+
import { isPostUpdated } from '../../../graphql/posts';
20+
import { TimeFormatType } from '../../../lib/dateFormat';
1921

2022
export const CollectionGrid = forwardRef(function CollectionCard(
2123
{
@@ -35,14 +37,19 @@ export const CollectionGrid = forwardRef(function CollectionCard(
3537
) {
3638
const { pinnedAt, trending } = post;
3739
const image = usePostImage(post);
38-
const onPostCardClick = () => onPostClick(post);
39-
const onPostCardAuxClick = () => onPostAuxClick(post);
40+
const wasUpdated = isPostUpdated(post);
41+
const onPostCardClick = () => onPostClick?.(post);
42+
const onPostCardAuxClick = () => onPostAuxClick?.(post);
4043

4144
return (
4245
<FeedItemContainer
4346
domProps={{
4447
...domProps,
45-
className: getPostClassNames(post, domProps.className, 'min-h-card'),
48+
className: getPostClassNames(
49+
post,
50+
domProps.className ?? '',
51+
'min-h-card',
52+
),
4653
}}
4754
ref={ref}
4855
flagProps={{ pinnedAt, trending }}
@@ -70,8 +77,11 @@ export const CollectionGrid = forwardRef(function CollectionCard(
7077
<PostTags post={post} className="!items-end" />
7178
</CardTextContainer>
7279
<PostMetadata
73-
createdAt={post.createdAt}
80+
createdAt={wasUpdated ? post.updatedAt : post.createdAt}
81+
dateLabel={wasUpdated ? 'Updated' : undefined}
82+
dateType={wasUpdated ? TimeFormatType.PostUpdated : TimeFormatType.Post}
7483
readTime={post.readTime}
84+
numSources={post.numCollectionSources}
7585
className={classNames('mx-4', post.image ? 'my-0' : 'mb-4 mt-2')}
7686
/>
7787
<Container>

packages/shared/src/components/cards/collection/CollectionList.tsx

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import { useTruncatedSummary, useViewSize, ViewSize } from '../../../hooks';
1818
import PostTags from '../common/PostTags';
1919
import { CardCoverList } from '../common/list/CardCover';
2020
import { HIGH_PRIORITY_IMAGE_PROPS } from '../../image/Image';
21+
import { isPostUpdated } from '../../../graphql/posts';
22+
import { TimeFormatType } from '../../../lib/dateFormat';
2123

2224
export const CollectionList = forwardRef(function CollectionCard(
2325
{
@@ -37,7 +39,8 @@ export const CollectionList = forwardRef(function CollectionCard(
3739
) {
3840
const isMobile = useViewSize(ViewSize.MobileL);
3941
const image = usePostImage(post);
40-
const { title } = useTruncatedSummary(post?.title);
42+
const { title } = useTruncatedSummary(post?.title ?? '');
43+
const wasUpdated = isPostUpdated(post);
4144
const actionButtons = (
4245
<Container className="pointer-events-none mt-2">
4346
<ActionButtons
@@ -60,23 +63,37 @@ export const CollectionList = forwardRef(function CollectionCard(
6063
className: domProps.className,
6164
}}
6265
ref={ref}
63-
flagProps={{ pinnedAt: post.pinnedAt, type: post.type }}
66+
flagProps={{
67+
pinnedAt: post.pinnedAt,
68+
type: post.type,
69+
trending: post.trending,
70+
}}
6471
linkProps={{
6572
title: post.title,
66-
onClick: () => onPostClick(post),
73+
onClick: () => onPostClick?.(post),
6774
href: post.commentsPermalink,
6875
}}
6976
bookmarked={post.bookmarked}
7077
>
7178
<CardContainer>
72-
<PostCardHeader post={post}>
79+
<PostCardHeader
80+
post={post}
81+
metadata={{
82+
createdAt: wasUpdated ? post.updatedAt : post.createdAt,
83+
dateLabel: wasUpdated ? 'Updated' : undefined,
84+
dateType: wasUpdated
85+
? TimeFormatType.PostUpdated
86+
: TimeFormatType.Post,
87+
numSources: post.numCollectionSources,
88+
}}
89+
>
7390
<CollectionPillSources
7491
className={{
7592
main: classNames(!!post.collectionSources?.length && '-my-0.5'),
7693
avatar: 'group-hover:border-background-subtle',
7794
}}
78-
sources={post.collectionSources}
79-
totalSources={post.numCollectionSources}
95+
sources={post.collectionSources ?? []}
96+
totalSources={post.numCollectionSources ?? 0}
8097
alwaysShowSources
8198
/>
8299
</PostCardHeader>

packages/shared/src/components/cards/common/PostMetadata.tsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Separator } from './common';
66
import type { Post } from '../../../graphql/posts';
77
import { formatReadTime, DateFormat } from '../../utilities';
88
import { largeNumberFormat } from '../../../lib';
9+
import { pluralize } from '../../../lib/strings';
910
import { useFeedCardContext } from '../../../features/posts/FeedCardContext';
1011
import { Tooltip } from '../../tooltip/Tooltip';
1112
import type { PollMetadataProps } from './PollMetadata';
@@ -20,6 +21,9 @@ interface PostMetadataProps
2021
isVideoType?: boolean;
2122
domain?: ReactNode;
2223
pollMetadata?: PollMetadataProps;
24+
numSources?: number;
25+
dateLabel?: string;
26+
dateType?: TimeFormatType;
2327
}
2428

2529
export default function PostMetadata({
@@ -32,6 +36,9 @@ export default function PostMetadata({
3236
isVideoType,
3337
domain,
3438
pollMetadata,
39+
numSources,
40+
dateLabel,
41+
dateType = TimeFormatType.Post,
3542
}: PostMetadataProps): ReactElement {
3643
const hasUpvoteCount = typeof numUpvotes === 'number';
3744
const upvoteCount = numUpvotes ?? 0;
@@ -62,7 +69,13 @@ export default function PostMetadata({
6269
!!createdAt &&
6370
!boostedBy && {
6471
key: 'date',
65-
node: <DateFormat date={createdAt} type={TimeFormatType.Post} />,
72+
node: (
73+
<DateFormat
74+
date={createdAt}
75+
type={dateType}
76+
prefix={dateLabel ? `${dateLabel} ` : undefined}
77+
/>
78+
),
6679
},
6780
showReadTime && {
6881
key: 'readTime',
@@ -72,6 +85,15 @@ export default function PostMetadata({
7285
</span>
7386
),
7487
},
88+
!!numSources &&
89+
numSources > 0 && {
90+
key: 'sources',
91+
node: (
92+
<span data-testid="numSources">
93+
{numSources} {pluralize('source', numSources)}
94+
</span>
95+
),
96+
},
7597
!!showReadTime && domain && { key: 'domain', node: domain },
7698
hasUpvoteCount &&
7799
upvoteCount > 0 && {
@@ -87,7 +109,7 @@ export default function PostMetadata({
87109
return (
88110
<div
89111
className={classNames(
90-
'flex items-center text-text-tertiary typo-footnote',
112+
'flex min-w-0 items-center overflow-hidden text-text-tertiary typo-footnote',
91113
className,
92114
)}
93115
>

packages/shared/src/components/cards/common/RaisedLabel.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export function RaisedLabel({
5656
)}
5757
>
5858
<ConditionalWrapper
59-
condition={description?.length > 0}
59+
condition={!!description?.length}
6060
wrapper={(children) => (
6161
<Tooltip content={description}>{children}</Tooltip>
6262
)}
@@ -84,7 +84,7 @@ export function RaisedLabel({
8484
)}
8585
>
8686
<ConditionalWrapper
87-
condition={description?.length > 0}
87+
condition={!!description?.length}
8888
wrapper={(children) => (
8989
<Tooltip content={description}>{children}</Tooltip>
9090
)}

packages/shared/src/components/cards/common/list/FeedItemContainer.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,13 @@ function FeedItemContainer(
4747
? RaisedLabelType.Pinned
4848
: RaisedLabelType.Hot;
4949
const description =
50-
[RaisedLabelType.Hot].includes(raisedLabelType) && trending > 0
50+
raisedLabelType === RaisedLabelType.Hot && trending && trending > 0
5151
? `${trending} devs read it last hour`
5252
: undefined;
5353
const isFeedPreview = useFeedPreviewMode();
5454
const showFlag = (!!pinnedAt || !!trending) && !isFeedPreview;
55-
const showTypeLabel = !!adAttribution || !!type;
55+
const typeLabelValue = adAttribution ?? type;
56+
const showTypeLabel = !!typeLabelValue;
5657
const [focus, setFocus] = useState(false);
5758

5859
return (
@@ -70,7 +71,7 @@ function FeedItemContainer(
7071
...(highlightBookmarkedPost && { background: bookmarkProviderListBg }),
7172
}}
7273
>
73-
{linkProps && (
74+
{linkProps?.href && (
7475
<Link href={linkProps.href}>
7576
<CardLink
7677
{...linkProps}
@@ -81,10 +82,10 @@ function FeedItemContainer(
8182
)}
8283
{(showTypeLabel || showFlag) && (
8384
<fieldset>
84-
{showTypeLabel && (
85+
{typeLabelValue && (
8586
<TypeLabel
8687
focus={focus}
87-
type={adAttribution ?? type}
88+
type={typeLabelValue}
8889
className="absolute left-3"
8990
/>
9091
)}

packages/shared/src/components/cards/common/list/PostCardHeader.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ interface CardHeaderProps {
4242
topLabel?: PostMetadataProps['topLabel'];
4343
bottomLabel?: PostMetadataProps['bottomLabel'];
4444
dateFirst?: PostMetadataProps['dateFirst'];
45+
createdAt?: PostMetadataProps['createdAt'];
46+
dateLabel?: PostMetadataProps['dateLabel'];
47+
numSources?: PostMetadataProps['numSources'];
48+
dateType?: PostMetadataProps['dateType'];
4549
};
4650
}
4751

@@ -113,11 +117,11 @@ export const PostCardHeader = ({
113117
<>
114118
{showCTA && (
115119
<ReadArticleButton
116-
content={readButtonContent ?? postButtonText}
120+
content={readButtonContent ?? postButtonText ?? ''}
117121
className="mr-2"
118122
variant={ButtonVariant.Tertiary}
119123
icon={readButtonIcon ?? <OpenLinkIcon />}
120-
href={postLink}
124+
href={postLink ?? ''}
121125
onClick={onReadArticleClick}
122126
openNewTab={openNewTab}
123127
/>

packages/shared/src/components/cards/common/list/PostMetadata.tsx

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,17 @@ import {
1212
TypographyColor,
1313
} from '../../../typography/Typography';
1414
import { useScrambler } from '../../../../hooks/useScrambler';
15+
import { pluralize } from '../../../../lib/strings';
1516

1617
export interface PostMetadataProps {
1718
className?: string;
1819
topLabel?: ReactElement | string;
1920
bottomLabel?: ReactElement | string;
2021
createdAt?: string;
2122
dateFirst?: boolean;
23+
dateLabel?: string;
24+
numSources?: number;
25+
dateType?: TimeFormatType;
2226
}
2327

2428
export default function PostMetadata({
@@ -27,11 +31,27 @@ export default function PostMetadata({
2731
topLabel,
2832
bottomLabel,
2933
dateFirst,
34+
dateLabel,
35+
numSources,
36+
dateType = TimeFormatType.Post,
3037
}: PostMetadataProps): ReactElement {
3138
const { boostedBy } = useFeedCardContext();
3239
const promotedText = useScrambler(
3340
boostedBy ? `Promoted by @${boostedBy.username}` : undefined,
3441
);
42+
const hasSources = !!numSources && numSources > 0;
43+
const dateNode = !!createdAt && (
44+
<DateFormat
45+
date={createdAt}
46+
type={dateType}
47+
prefix={dateLabel ? `${dateLabel} ` : undefined}
48+
/>
49+
);
50+
const sourcesNode = hasSources && (
51+
<span data-testid="numSources">
52+
{numSources} {pluralize('source', numSources)}
53+
</span>
54+
);
3555

3656
return (
3757
<div className={classNames('grid items-center', className)}>
@@ -51,21 +71,21 @@ export default function PostMetadata({
5171
{!!boostedBy && !!bottomLabel && <Separator />}
5272
{dateFirst ? (
5373
<>
54-
{!!createdAt && (
55-
<DateFormat date={createdAt} type={TimeFormatType.Post} />
56-
)}
74+
{dateNode}
5775
{!!createdAt && !!bottomLabel && <Separator />}
5876
{bottomLabel}
5977
</>
6078
) : (
6179
<>
6280
{bottomLabel}
6381
{(!!bottomLabel || !!boostedBy) && !!createdAt && <Separator />}
64-
{!!createdAt && (
65-
<DateFormat date={createdAt} type={TimeFormatType.Post} />
66-
)}
82+
{dateNode}
6783
</>
6884
)}
85+
{(!!createdAt || !!bottomLabel || !!boostedBy) && sourcesNode && (
86+
<Separator />
87+
)}
88+
{sourcesNode}
6989
</div>
7090
</div>
7191
);

packages/shared/src/components/post/collection/CollectionPostContent.tsx

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ import { DateFormat } from '../../utilities';
2323
import { withPostById } from '../withPostById';
2424
import { PostTagList } from '../tags/PostTagList';
2525
import { CollectionPostHeaderActions } from './CollectionPostHeaderActions';
26-
import type { Post } from '../../../graphql/posts';
26+
import { isPostUpdated, type Post } from '../../../graphql/posts';
27+
import { pluralize } from '../../../lib/strings';
2728

2829
type CollectionPostContentRawProps = Omit<PostContentProps, 'post'> & {
2930
post: Post;
@@ -53,7 +54,11 @@ const CollectionPostContentRaw = ({
5354
origin,
5455
post,
5556
});
56-
const { updatedAt, contentHtml, image } = post;
57+
const { createdAt, updatedAt, contentHtml, image, numCollectionSources } =
58+
post;
59+
const wasUpdated = isPostUpdated(post);
60+
const dateToShow = wasUpdated ? updatedAt : createdAt;
61+
const hasSources = !!numCollectionSources && numCollectionSources > 0;
5762
const { onCopyPostLink, onReadArticle } = engagementActions;
5863

5964
const hasNavigation = !!onPreviousPost || !!onNextPost;
@@ -130,7 +135,7 @@ const CollectionPostContentRaw = ({
130135
>
131136
<div className="mb-6 flex flex-col gap-6">
132137
<CollectionsIntro className="mt-6 laptop:hidden" />
133-
<div className="flex flex-row items-center pt-6">
138+
<div className="flex flex-row items-center gap-2 pt-6">
134139
<Link href={`${webappUrl}sources/collections`} passHref>
135140
<Pill
136141
tag="a"
@@ -153,10 +158,26 @@ const CollectionPostContentRaw = ({
153158
{post.title}
154159
</h1>
155160
<PostTagList post={post} />
156-
{!!updatedAt && (
157-
<div className="flex items-center text-text-tertiary typo-footnote">
158-
<span>Last updated</span> <Separator />
159-
<DateFormat date={updatedAt} type={TimeFormatType.Post} />
161+
{!!dateToShow && (
162+
<div className="flex min-w-0 items-center overflow-hidden text-text-tertiary typo-footnote">
163+
<DateFormat
164+
date={dateToShow}
165+
type={
166+
wasUpdated
167+
? TimeFormatType.PostUpdated
168+
: TimeFormatType.Post
169+
}
170+
prefix={wasUpdated ? 'Last updated ' : undefined}
171+
/>
172+
{hasSources && (
173+
<>
174+
<Separator />
175+
<span>
176+
{numCollectionSources}{' '}
177+
{pluralize('source', numCollectionSources)}
178+
</span>
179+
</>
180+
)}
160181
</div>
161182
)}
162183
{image && (

0 commit comments

Comments
 (0)