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
12 changes: 12 additions & 0 deletions packages/shared/src/components/cards/Freeform/FreeformGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { ClickbaitShield } from '../common/ClickbaitShield';
import PostTags from '../common/PostTags';
import { useSmartTitle } from '../../../hooks/post/useSmartTitle';
import { useHiddenFeedbackPanel } from '../../../hooks/post/useHiddenFeedbackPanel';
import { useCardSaveOnHover } from '../../../hooks/cards/useCardSaveOnHover';
import { CardHeaderBookmark } from '../common/CardHeaderBookmark';

export const FreeformGrid = forwardRef(function SharePostCard(
{
Expand All @@ -42,6 +44,7 @@ export const FreeformGrid = forwardRef(function SharePostCard(
const containerRef = useRef<HTMLDivElement>();
const image = usePostImage(post);
const { title } = useSmartTitle(post);
const saveOnHover = useCardSaveOnHover();
const { isHidden, content: hiddenPanel } = useHiddenFeedbackPanel(post);

if (isHidden) {
Expand Down Expand Up @@ -79,6 +82,14 @@ export const FreeformGrid = forwardRef(function SharePostCard(
<SquadPostCardHeader
post={post}
enableSourceHeader={enableSourceHeader}
bookmark={
saveOnHover ? (
<CardHeaderBookmark
post={post}
onBookmarkClick={onBookmarkClick}
/>
) : undefined
}
/>
<FreeformCardTitle className="line-clamp-3">{title}</FreeformCardTitle>
</CardTextContainer>
Expand Down Expand Up @@ -111,6 +122,7 @@ export const FreeformGrid = forwardRef(function SharePostCard(
onCommentClick={onCommentClick}
onCopyLinkClick={onCopyLinkClick}
onBookmarkClick={onBookmarkClick}
bookmarkInHeader={saveOnHover}
className="mt-auto"
onDownvoteClick={onDownvoteClick}
/>
Expand Down
12 changes: 12 additions & 0 deletions packages/shared/src/components/cards/article/ArticleGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import ActionButtons from '../common/ActionButtons';
import { FeedbackGrid } from './feedback/FeedbackGrid';
import { ClickbaitShield } from '../common/ClickbaitShield';
import { useSmartTitle } from '../../../hooks/post/useSmartTitle';
import { useCardSaveOnHover } from '../../../hooks/cards/useCardSaveOnHover';
import { CardHeaderBookmark } from '../common/CardHeaderBookmark';

export const ArticleGrid = forwardRef(function ArticleGrid(
{
Expand All @@ -48,6 +50,7 @@ export const ArticleGrid = forwardRef(function ArticleGrid(
): ReactElement {
const { className, style } = domProps;
const { isHidden, content: hiddenPanel } = useHiddenFeedbackPanel(post);
const saveOnHover = useCardSaveOnHover();
const { data } = useBlockPostPanel(post);
const onPostCardClick = () => onPostClick(post);
const onPostCardAuxClick = () => onPostAuxClick(post);
Expand Down Expand Up @@ -130,6 +133,14 @@ export const ArticleGrid = forwardRef(function ArticleGrid(
postLink={post.permalink}
onReadArticleClick={onReadArticleClick}
showFeedback={showFeedback}
bookmark={
saveOnHover ? (
<CardHeaderBookmark
post={post}
onBookmarkClick={onBookmarkClick}
/>
) : undefined
}
/>
<CardTitle lineClamp={showFeedback ? 'line-clamp-2' : undefined}>
{title}
Expand Down Expand Up @@ -168,6 +179,7 @@ export const ArticleGrid = forwardRef(function ArticleGrid(
onCommentClick={onCommentClick}
onCopyLinkClick={onCopyLinkClick}
onBookmarkClick={onBookmarkClick}
bookmarkInHeader={saveOnHover}
onDownvoteClick={onDownvoteClick}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ReactElement } from 'react';
import type { ReactElement, ReactNode } from 'react';
import React from 'react';
import classNames from 'classnames';
import { CollectionSourceStack } from '../../post/collection/CollectionSourceStack';
Expand All @@ -12,10 +12,13 @@ import type { Post } from '../../../graphql/posts';

interface CollectionCardHeaderProps {
post: Post;
/** `card_save_on_hover`: the bookmark, right before ⋯. */
bookmark?: ReactNode;
}

export const CollectionCardHeader = ({
post,
bookmark,
}: CollectionCardHeaderProps): ReactElement => {
const {
collectionSources: sources,
Expand Down Expand Up @@ -43,6 +46,7 @@ export const CollectionCardHeader = ({
totalSources={totalSources ?? 0}
/>
<div className="flex-1" />
{bookmark}
<PostOptionButton
post={post}
triggerClassName="laptop:mouse:invisible laptop:mouse:group-hover:visible"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import PostTags from '../common/PostTags';
import { isPostUpdated } from '../../../graphql/posts';
import { TimeFormatType } from '../../../lib/dateFormat';
import { useHiddenFeedbackPanel } from '../../../hooks/post/useHiddenFeedbackPanel';
import { useCardSaveOnHover } from '../../../hooks/cards/useCardSaveOnHover';
import { CardHeaderBookmark } from '../common/CardHeaderBookmark';

export const CollectionGrid = forwardRef(function CollectionCard(
{
Expand All @@ -43,6 +45,7 @@ export const CollectionGrid = forwardRef(function CollectionCard(
const onPostCardClick = () => onPostClick?.(post);
const onPostCardAuxClick = () => onPostAuxClick?.(post);
const { isHidden, content: hiddenPanel } = useHiddenFeedbackPanel(post);
const saveOnHover = useCardSaveOnHover();

if (isHidden) {
return (
Expand Down Expand Up @@ -94,7 +97,17 @@ export const CollectionGrid = forwardRef(function CollectionCard(
onPostCardAuxClick={onPostCardAuxClick}
/>
<CardTextContainer className="mx-4">
<CollectionCardHeader post={post} />
<CollectionCardHeader
post={post}
bookmark={
saveOnHover ? (
<CardHeaderBookmark
post={post}
onBookmarkClick={onBookmarkClick}
/>
) : undefined
}
/>
<FreeformCardTitle
className={classNames(
// Match the default article card's title guideline: clamp to 3 lines
Expand Down Expand Up @@ -133,6 +146,7 @@ export const CollectionGrid = forwardRef(function CollectionCard(
onCommentClick={onCommentClick}
onCopyLinkClick={onCopyLinkClick}
onBookmarkClick={onBookmarkClick}
bookmarkInHeader={saveOnHover}
className="mt-auto"
onDownvoteClick={onDownvoteClick}
/>
Expand Down
94 changes: 71 additions & 23 deletions packages/shared/src/components/cards/common/ActionButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
DownvoteIcon,
} from '../../icons';
import { ButtonColor, ButtonVariant } from '../../buttons/Button';
import { ButtonSize } from '../../buttons/common';
import { IconSize } from '../../Icon';
import { useFeedPreviewMode } from '../../../hooks';
import { UpvoteButtonIcon } from './UpvoteButtonIcon';
import { BookmarkButton } from '../../buttons';
Expand All @@ -29,6 +31,7 @@ import { useBrandSponsorship } from '../../../hooks/useBrandSponsorship';
import { usePostImpressions } from '../../../hooks/post/usePostImpressions';
import { useEngagementBarV2 } from '../../../hooks/useEngagementBarV2';
import ActionButtonsV2 from './ActionButtons.v2';
import { useCardSaveOnHover } from '../../../hooks/cards/useCardSaveOnHover';
import { getPostPath } from '../../../lib/links';

export type ActionButtonsVariant = 'grid' | 'list' | 'signal';
Expand All @@ -44,6 +47,12 @@ export interface ActionButtonsProps {
/** Controls sizing and behavior. Grid = smaller icons, List = larger icons with link navigation */
variant?: ActionButtonsVariant;
showDownvoteAction?: boolean;
/**
* `card_save_on_hover`: the card renders the bookmark in its header, so the
* bar leaves it out. Only grid cards that do so pass it; it is ignored when
* the flag is off.
*/
bookmarkInHeader?: boolean;
}

const variantConfig = {
Expand Down Expand Up @@ -72,6 +81,14 @@ const variantConfig = {
},
} as const;

interface ActionButtonsV1Props extends ActionButtonsProps {
/**
* `card_save_on_hover` treatment on grid cards: today's bar at 32px with
* 20px icons, in the same 36px row.
*/
large?: boolean;
}

const ActionButtonsV1 = ({
post,
onUpvoteClick,
Expand All @@ -82,10 +99,13 @@ const ActionButtonsV1 = ({
onDownvoteClick,
variant = 'grid',
showDownvoteAction = true,
}: ActionButtonsProps): ReactElement | null => {
bookmarkInHeader = false,
large = false,
}: ActionButtonsV1Props): ReactElement | null => {
const config = variantConfig[variant];
const isFeedPreview = useFeedPreviewMode();
const { buttonSize, iconSize } = config;
const buttonSize = large ? ButtonSize.Small : config.buttonSize;
const iconSize = large ? IconSize.XSmall : config.iconSize;
const { getUpvoteAnimation } = useBrandSponsorship();

const {
Expand Down Expand Up @@ -186,7 +206,8 @@ const ActionButtonsV1 = ({
<div
className={classNames(
'flex flex-row items-center justify-between',
config.containerClassName,
// 32px buttons keep the 36px row: py-0.5 instead of py-1.5.
large ? 'py-0.5 pl-1 pr-2.5' : config.containerClassName,
className,
)}
>
Expand Down Expand Up @@ -240,23 +261,25 @@ const ActionButtonsV1 = ({
/>
</Tooltip>
)}
<BookmarkButton
tooltipSide={variant === 'grid' ? 'bottom' : undefined}
post={post}
buttonProps={{
id: `post-${post.id}-bookmark-btn`,
onClick: onToggleBookmark,
size: buttonSize,
className: classNames(
'btn-tertiary-bun',
variant === 'list' && 'pointer-events-auto',
),
...(variant === 'list' && {
variant: ButtonVariant.Tertiary,
}),
}}
iconSize={iconSize}
/>
{!bookmarkInHeader && (
<BookmarkButton
tooltipSide={variant === 'grid' ? 'bottom' : undefined}
post={post}
buttonProps={{
id: `post-${post.id}-bookmark-btn`,
onClick: onToggleBookmark,
size: buttonSize,
className: classNames(
'btn-tertiary-bun',
variant === 'list' && 'pointer-events-auto',
),
...(variant === 'list' && {
variant: ButtonVariant.Tertiary,
}),
}}
iconSize={iconSize}
/>
)}
<Tooltip
content="Copy link"
side={variant === 'grid' ? 'bottom' : undefined}
Expand Down Expand Up @@ -320,12 +343,37 @@ const ActionButtonsV1 = ({
return buttons;
};

const ActionButtons = (props: ActionButtonsProps): ReactElement => {
const ActionButtons = (props: ActionButtonsProps): ReactElement | null => {
const { variant = 'grid', bookmarkInHeader, ...rest } = props;
// `card_save_on_hover` only changes grid cards, so only they evaluate it:
// list and signal renders must not enroll users who never see a change.
const saveOnHover = useCardSaveOnHover({
shouldEvaluate: variant === 'grid',
});
const useV2 = useEngagementBarV2();
// The treatment keeps the bar control renders (v1, or v2 for
// `engagement_bar_v2` users) and only changes its size and the bookmark's
// place, so the test measures those two things and not a bar swap.
const treatment = saveOnHover && variant === 'grid';
const inHeader = treatment && !!bookmarkInHeader;
if (useV2) {
return <ActionButtonsV2 {...props} />;
return (
<ActionButtonsV2
Comment thread
tsahimatsliah marked this conversation as resolved.
{...rest}
variant={variant}
density={treatment ? 'compact' : undefined}
bookmarkInHeader={inHeader}
/>
);
}
return <ActionButtonsV1 {...props} />;
return (
<ActionButtonsV1
{...rest}
variant={variant}
large={treatment}
bookmarkInHeader={inHeader}
/>
);
};

export default ActionButtons;
Loading
Loading