Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 14 additions & 25 deletions packages/shared/src/components/cards/Freeform/FreeformGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type { ReactElement, Ref } from 'react';
import React, { forwardRef, useRef } from 'react';
import classNames from 'classnames';
import type { PostCardProps } from '../common/common';
import { Container, generateTitleClamp } from '../common/common';
import { Container } from '../common/common';
import { usePostImage } from '../../../hooks/post/usePostImage';
import FeedItemContainer from '../common/FeedItemContainer';
import {
Expand All @@ -21,6 +20,7 @@ import {
glassCoverImageClassName,
} from '../common/FeedCardGlassActions';
import { ClickbaitShield } from '../common/ClickbaitShield';
import PostTags from '../common/PostTags';
import { useSmartTitle } from '../../../hooks/post/useSmartTitle';
import { useFeedCardGlassActions } from '../../../hooks/useFeedCardGlassActions';
import { useHiddenFeedbackPanel } from '../../../hooks/post/useHiddenFeedbackPanel';
Expand Down Expand Up @@ -90,36 +90,22 @@ export const FreeformGrid = forwardRef(function SharePostCard(
post={post}
enableSourceHeader={enableSourceHeader}
/>
<FreeformCardTitle
className={classNames(
generateTitleClamp({
hasImage: !!image,
hasHtmlContent: !!post.contentHtml,
}),
)}
>
{title}
</FreeformCardTitle>
<FreeformCardTitle className="line-clamp-3">{title}</FreeformCardTitle>
</CardTextContainer>
<>
{image && <CardSpace />}
<div
className={classNames(
'mx-4 mb-2 flex items-center',
!image && 'mt-1',
)}
>
{/* Match the collection card: push the tags + date to the bottom of the
text area, just above the footer (cover image or text preview). */}
<Container>
<CardSpace />
<div className="mx-4 flex items-center">
{post.clickbaitTitleDetected && <ClickbaitShield post={post} />}
<PostTags post={post} />
</div>
<PostMetadata
className={classNames(
'mx-4 line-clamp-1 break-words',
image ? 'mt-0' : 'mt-1',
)}
className="mx-4"
createdAt={post.createdAt}
readTime={post.readTime}
/>
</>
</Container>
<Container
ref={containerRef}
className={useGlass && image ? 'flex-none' : undefined}
Expand All @@ -132,6 +118,9 @@ export const FreeformGrid = forwardRef(function SharePostCard(
imageClassName={
useGlass && image ? glassCoverImageClassName : undefined
}
// pt-2 gives the text a bit of breathing room below the date without
// growing the footer (box-border keeps min-h), matching the collection.
contentClassName="min-h-[10.5rem] pt-2"
/>
{useGlass ? (
<FeedCardGlassActions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import FeedItemContainer from '../common/list/FeedItemContainer';
import { CardContainer, CardContent, CardTitle } from '../common/list/ListCard';
import { PostCardHeader } from '../common/list/PostCardHeader';
import { CardCoverList } from '../common/list/CardCover';
import PostTags from '../common/PostTags';
import ActionButtons from '../common/ActionButtons';
import { HIGH_PRIORITY_IMAGE_PROPS } from '../../image/Image';
import { ClickbaitShield } from '../common/ClickbaitShield';
Expand Down Expand Up @@ -167,6 +168,8 @@ export const FreeformList = forwardRef(function SharePostCard(
</CardTitle>

{post.clickbaitTitleDetected && <ClickbaitShield post={post} />}
<div className="flex flex-1 tablet:hidden" />
<PostTags post={post} />
<div className="hidden flex-1 tablet:flex" />
{!isMobile && actionButtons}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type { ReactElement } from 'react';
import React from 'react';
import classNames from 'classnames';
import { CollectionPillSources } from '../../post/collection/CollectionPillSources';
import { getCollectionPillLabel } from '../../post/collection/common';
import { CollectionSourceStack } from '../../post/collection/CollectionSourceStack';
import {
BookmakProviderHeader,
headerHiddenClassName,
Expand Down Expand Up @@ -31,23 +30,24 @@ export const CollectionCardHeader = ({
return (
<>
{highlightBookmarkedPost && <BookmakProviderHeader />}
<CollectionPillSources
className={{
main: classNames(
'mb-1 mt-3',
highlightBookmarkedPost && headerHiddenClassName,
),
}}
sources={sources ?? []}
totalSources={totalSources ?? 0}
label={getCollectionPillLabel(post)}
<div
className={classNames(
// mt-4 matches the article card's CardHeader top margin so the title
// (and the tags/date below it) start on the same row.
'mb-1 mt-4 flex flex-row items-center',
highlightBookmarkedPost && headerHiddenClassName,
)}
>
<CollectionSourceStack
sources={sources ?? []}
totalSources={totalSources ?? 0}
/>
<div className="flex-1" />
<PostOptionButton
post={post}
triggerClassName="laptop:mouse:invisible laptop:mouse:group-hover:visible"
/>
</CollectionPillSources>
</div>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ export const CollectionFeaturedWideGridCard = forwardRef(
wasUpdated ? TimeFormatType.PostUpdated : TimeFormatType.Post
}
readTime={post.readTime}
numSources={post.numCollectionSources}
className="mt-1"
/>
{!!post.summary && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,20 +107,31 @@ it('should hide read time when not available', async () => {
expect(screen.queryByTestId('readTime')).not.toBeInTheDocument();
});

it('should display the `collection` pill in the header', async () => {
renderComponent();
await screen.findByText('Collection');
});

it('should display the `Trend` pill when the post source is trends', async () => {
it('should display the collection source stack in the header', async () => {
renderComponent({
post: {
...post,
source: { ...post.source, id: 'trends' } as Post['source'],
},
collectionSources: [
{
id: 'src1',
handle: 'src1',
name: 'Source One',
image: 'https://daily.dev/src1.png',
permalink: 'https://daily.dev/sources/src1',
},
{
id: 'src2',
handle: 'src2',
name: 'Source Two',
image: 'https://daily.dev/src2.png',
permalink: 'https://daily.dev/sources/src2',
},
],
numCollectionSources: 2,
} as Post,
});
await screen.findByText('Trend');
expect(screen.queryByText('Collection')).not.toBeInTheDocument();
await screen.findByAltText('Avatar of src1');
await screen.findByAltText('Avatar of src2');
});

it('should show options button on hover when in laptop size', async () => {
Expand Down
46 changes: 30 additions & 16 deletions packages/shared/src/components/cards/collection/CollectionGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import type { Ref } from 'react';
import React, { forwardRef } from 'react';
import classNames from 'classnames';
import type { PostCardProps } from '../common/common';
import { Container, generateTitleClamp } from '../common/common';
import { Container } from '../common/common';
import FeedItemContainer from '../common/FeedItemContainer';
import { CollectionCardHeader } from './CollectionCardHeader';
import {
getPostClassNames,
FreeformCardTitle,
CardTextContainer,
CardSpace,
} from '../common/Card';
import { WelcomePostCardFooter } from '../common/WelcomePostCardFooter';
import ActionButtons from '../common/ActionButtons';
Expand Down Expand Up @@ -69,6 +70,16 @@ export const CollectionGrid = forwardRef(function CollectionCard(
);
}

const postMetadata = (
<PostMetadata
createdAt={wasUpdated ? post.updatedAt : post.createdAt}
dateLabel={wasUpdated ? 'Updated' : undefined}
dateType={wasUpdated ? TimeFormatType.PostUpdated : TimeFormatType.Post}
readTime={post.readTime}
className="mx-4"
/>
);

return (
<FeedItemContainer
domProps={{
Expand All @@ -88,30 +99,27 @@ export const CollectionGrid = forwardRef(function CollectionCard(
onPostCardClick={onPostCardClick}
onPostCardAuxClick={onPostCardAuxClick}
/>
<CardTextContainer className="mx-4 flex-1">
<CardTextContainer className="mx-4">
<CollectionCardHeader post={post} />
<FreeformCardTitle
className={classNames(
generateTitleClamp({
hasImage: !!image,
hasHtmlContent: !!post.contentHtml,
}),
// Match the default article card's title guideline: clamp to 3 lines
// at natural height (no fixed reserve).
'line-clamp-3',
'font-bold text-text-primary typo-title3',
)}
>
{post.title}
</FreeformCardTitle>

<PostTags post={post} className="!items-end" />
</CardTextContainer>
<PostMetadata
createdAt={wasUpdated ? post.updatedAt : post.createdAt}
dateLabel={wasUpdated ? 'Updated' : undefined}
dateType={wasUpdated ? TimeFormatType.PostUpdated : TimeFormatType.Post}
readTime={post.readTime}
numSources={post.numCollectionSources}
className={classNames('mx-4', post.image ? 'my-0' : 'mb-4 mt-2')}
/>
{/* Push the tags + date to the bottom of the text area, just above the
footer (cover image or freeform text preview), matching the default
article card so they align regardless of title length. */}
<Container>
<CardSpace />
<PostTags post={post} className="mx-4" />
{postMetadata}
</Container>
<Container className={useGlass && image ? 'flex-none' : undefined}>
<WelcomePostCardFooter
image={image}
Expand All @@ -122,6 +130,12 @@ export const CollectionGrid = forwardRef(function CollectionCard(
imageClassName={
useGlass && image ? glassCoverImageClassName : undefined
}
// Give the freeform text preview the same footer height as the cover
// image slot (h-40 image + its mt-2/mb-1 margins = 10.5rem) so the
// tags/date bottom-anchor to the same row as the image cards and the
// default article card. pt-2 adds a bit of breathing room between the
// date and the text without growing the footer (box-border keeps min-h).
contentClassName="min-h-[10.5rem] pt-2"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the min-h-[10.5rem] concerns me, are we sure there's no impact here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — it's intentional and precisely derived, not arbitrary, and scoped only to here + FreeformGrid (no other caller passes contentClassName). The value makes the text footer's total height equal the cover-image footer, so the tag/date row bottom-anchors to the same row across both variants:

  • text footer: mt-1 (4px) + min-h-[10.5rem] (168px) = 172px
  • image footer: mt-2 (8px) + h-40 (160px) + mb-1 (4px) = 172px

min-height only sets a floor and the text is line-clamp-6, so there's no overflow/squash — a short post just reserves the same footer space an image card already does, keeping the grid rows aligned. Happy to switch to natural heights if you'd rather text cards not reserve it.

/>
{useGlass ? (
<FeedCardGlassActions
Expand Down
17 changes: 7 additions & 10 deletions packages/shared/src/components/cards/collection/CollectionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import {
import ActionButtons from '../common/ActionButtons';
import { usePostImage } from '../../../hooks/post/usePostImage';
import { PostCardHeader } from '../common/list/PostCardHeader';
import { CollectionPillSources } from '../../post/collection/CollectionPillSources';
import { getCollectionPillLabel } from '../../post/collection/common';
import { CollectionSourceStack } from '../../post/collection/CollectionSourceStack';
import { useTruncatedSummary, useViewSize, ViewSize } from '../../../hooks';
import PostTags from '../common/PostTags';
import { CardCoverList } from '../common/list/CardCover';
Expand Down Expand Up @@ -108,18 +107,16 @@ export const CollectionList = forwardRef(function CollectionCard(
dateType: wasUpdated
? TimeFormatType.PostUpdated
: TimeFormatType.Post,
numSources: post.numCollectionSources,
}}
>
<CollectionPillSources
className={{
main: classNames(!!post.collectionSources?.length && '-my-0.5'),
avatar: 'group-hover:border-background-subtle',
}}
<CollectionSourceStack
className={classNames(
!!post.collectionSources?.length && '-my-0.5',
)}
sources={post.collectionSources ?? []}
totalSources={post.numCollectionSources ?? 0}
alwaysShowSources
label={getCollectionPillLabel(post)}
alwaysExpanded
enableHoverCard={false}
/>
</PostCardHeader>

Expand Down
Loading
Loading