Skip to content
Open
164 changes: 101 additions & 63 deletions packages/shared/src/components/cards/common/ActionButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import InteractionCounter from '../../InteractionCounter';
import { QuaternaryButton } from '../../buttons/QuaternaryButton';
import {
DiscussIcon as CommentIcon,
DiscussIconV2 as CommentIconV2,
LinkIcon,
DownvoteIcon,
} from '../../icons';
import { ButtonColor, ButtonSize, ButtonVariant } from '../../buttons/Button';
import { useFeedPreviewMode } from '../../../hooks';
import { useFeature } from '../../GrowthBookProvider';
import { featureCommentFirstAction } from '../../../lib/featureManagement';
import { UpvoteButtonIcon } from './UpvoteButtonIcon';
import { BookmarkButton } from '../../buttons';
import { IconSize } from '../../Icon';
Expand Down Expand Up @@ -79,6 +82,11 @@ const ActionButtonsV1 = ({
const config = variantConfig[variant];
const isFeedPreview = useFeedPreviewMode();
const { getUpvoteAnimation } = useBrandSponsorship();
// The experiment only reorders the comment action and swaps its icon;
// copy and click behavior stay identical to the control so the only
// variable is order.
const isCommentFirst = useFeature(featureCommentFirstAction);
const CommentIconComponent = isCommentFirst ? CommentIconV2 : CommentIcon;

const {
isUpvoteActive,
Expand Down Expand Up @@ -120,14 +128,25 @@ const ActionButtonsV1 = ({

const commentCount = post.numComments ?? 0;
const upvoteCount = post.numUpvotes ?? 0;
// Keep every count in the action row on the same typography as the upvote
// counter so the numbers match in size/style across variants. `!leading-5`
// forces the footnote line to fill the counter's h-5 box (it must beat
// typo-footnote's own line-height, which is emitted later in the same
// utilities layer) so the digits center against the icons instead of
// riding high.
const counterClassName = classNames(
'tabular-nums !leading-5',
variant === 'grid' && 'typo-footnote',
);
const counterLabelClassName = variant === 'grid' ? '!pl-[1px]' : '!pl-0';

const commentButton = config.useCommentLink ? (
<LinkWithTooltip
tooltip={{ content: 'Comment' }}
href={post.commentsPermalink}
>
<QuaternaryButton
labelClassName="!pl-0"
labelClassName={counterLabelClassName}
id={`post-${post.id}-comment-btn`}
className="btn-tertiary-blueCheese pointer-events-auto"
color={ButtonColor.BlueCheese}
Expand All @@ -136,12 +155,17 @@ const ActionButtonsV1 = ({
pressed={post.commented}
variant={ButtonVariant.Tertiary}
size={config.buttonSize}
icon={<CommentIcon secondary={post.commented} size={config.iconSize} />}
icon={
<CommentIconComponent
secondary={post.commented}
size={config.iconSize}
/>
}
onClick={() => onCommentClick?.(post)}
>
{commentCount > 0 && (
<InteractionCounter
className={classNames('tabular-nums', !commentCount && 'invisible')}
className={counterClassName}
value={commentCount}
/>
)}
Expand All @@ -150,91 +174,105 @@ const ActionButtonsV1 = ({
) : (
<Tooltip content="Comments" side="bottom">
<QuaternaryButton
labelClassName="!pl-[1px]"
labelClassName={counterLabelClassName}
id={`post-${post.id}-comment-btn`}
icon={<CommentIcon secondary={post.commented} size={config.iconSize} />}
icon={
<CommentIconComponent
secondary={post.commented}
size={config.iconSize}
/>
}
pressed={post.commented}
onClick={() => onCommentClick?.(post)}
size={config.buttonSize}
className="btn-tertiary-blueCheese"
>
{commentCount > 0 && (
<InteractionCounter
className={classNames(
'tabular-nums !typo-footnote',
!commentCount && 'invisible',
)}
className={counterClassName}
value={commentCount}
/>
)}
</QuaternaryButton>
</Tooltip>
);

const buttons = (
<div
className={classNames(
'flex flex-row items-center justify-between',
config.containerClassName,
className,
)}
>
<div className="flex flex-1 items-center justify-between">
const voteButtons = (
<>
<Tooltip
content={isUpvoteActive ? 'Remove upvote' : 'More like this'}
side={variant === 'grid' ? 'bottom' : undefined}
>
<QuaternaryButton
labelClassName={counterLabelClassName}
className="btn-tertiary-avocado pointer-events-auto"
id={`post-${post.id}-upvote-btn`}
color={ButtonColor.Avocado}
pressed={isUpvoteActive}
onClick={onToggleUpvote}
variant={ButtonVariant.Tertiary}
size={config.buttonSize}
icon={
<UpvoteButtonIcon
secondary={isUpvoteActive}
size={config.iconSize}
brandAnimation={brandAnimation}
/>
}
>
{upvoteCount > 0 && (
<InteractionCounter
className={counterClassName}
value={upvoteCount}
/>
)}
</QuaternaryButton>
</Tooltip>
{showDownvoteAction && (
<Tooltip
content={isUpvoteActive ? 'Remove upvote' : 'More like this'}
content={isDownvoteActive ? 'Remove downvote' : 'Less like this'}
side={variant === 'grid' ? 'bottom' : undefined}
>
<QuaternaryButton
labelClassName={variant === 'grid' ? '!pl-[1px]' : '!pl-0'}
className="btn-tertiary-avocado pointer-events-auto"
id={`post-${post.id}-upvote-btn`}
color={ButtonColor.Avocado}
pressed={isUpvoteActive}
onClick={onToggleUpvote}
variant={ButtonVariant.Tertiary}
size={config.buttonSize}
className="pointer-events-auto"
id={`post-${post.id}-downvote-btn`}
color={ButtonColor.Ketchup}
icon={
<UpvoteButtonIcon
secondary={isUpvoteActive}
<DownvoteIcon
secondary={isDownvoteActive}
size={config.iconSize}
brandAnimation={brandAnimation}
/>
}
>
{upvoteCount > 0 && (
<InteractionCounter
className={classNames(
'tabular-nums',
variant === 'grid' && 'typo-footnote',
)}
value={upvoteCount}
/>
)}
</QuaternaryButton>
pressed={isDownvoteActive}
onClick={onToggleDownvote}
variant={ButtonVariant.Tertiary}
size={config.buttonSize}
/>
</Tooltip>
{showDownvoteAction && (
<Tooltip
content={isDownvoteActive ? 'Remove downvote' : 'Less like this'}
side={variant === 'grid' ? 'bottom' : undefined}
>
<QuaternaryButton
className="pointer-events-auto"
id={`post-${post.id}-downvote-btn`}
color={ButtonColor.Ketchup}
icon={
<DownvoteIcon
secondary={isDownvoteActive}
size={config.iconSize}
/>
}
pressed={isDownvoteActive}
onClick={onToggleDownvote}
variant={ButtonVariant.Tertiary}
size={config.buttonSize}
/>
</Tooltip>
)}
</>
);

const buttons = (
<div
className={classNames(
'flex flex-row items-center justify-between',
config.containerClassName,
className,
)}
>
<div className="flex flex-1 items-center justify-between">
{isCommentFirst ? (
<>
{commentButton}
{voteButtons}
</>
) : (
<>
{voteButtons}
{commentButton}
</>
)}
{commentButton}
{showAwardAction && (
<PostAwardAction post={post} iconSize={config.iconSize} />
)}
Expand Down
3 changes: 3 additions & 0 deletions packages/shared/src/components/icons/Discuss/filled-v2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions packages/shared/src/components/icons/Discuss/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ import type { IconProps } from '../../Icon';
import Icon from '../../Icon';
import OutlinedIcon from './outlined.svg';
import FilledIcon from './filled.svg';
import OutlinedIconV2 from './outlined-v2.svg';
import FilledIconV2 from './filled-v2.svg';

export const DiscussIcon = (props: IconProps): ReactElement => (
<Icon {...props} IconPrimary={OutlinedIcon} IconSecondary={FilledIcon} />
);

export const DiscussIconV2 = (props: IconProps): ReactElement => (
<Icon {...props} IconPrimary={OutlinedIconV2} IconSecondary={FilledIconV2} />
);
3 changes: 3 additions & 0 deletions packages/shared/src/components/icons/Discuss/outlined-v2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
101 changes: 64 additions & 37 deletions packages/shared/src/components/post/MobilePostFloatingBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import React, { useCallback } from 'react';
import classNames from 'classnames';
import {
DiscussIcon as CommentIcon,
DiscussIconV2 as CommentIconV2,
DownvoteIcon,
LinkIcon,
UpvoteIcon,
} from '../icons';
import { useFeature } from '../GrowthBookProvider';
import { featureCommentFirstAction } from '../../lib/featureManagement';
import type { Post } from '../../graphql/posts';
import { UserVote } from '../../graphql/posts';
import { QuaternaryButton } from '../buttons/QuaternaryButton';
Expand Down Expand Up @@ -63,6 +66,8 @@ function MobilePostFloatingBarV1({
const { onClose, onShowPanel } = useBlockPostPanel(post);
const { toggleUpvote, toggleDownvote } = useVotePost();
const { toggleBookmark } = useBookmarkPost();
const isCommentFirst = useFeature(featureCommentFirstAction);
const CommentIconComponent = isCommentFirst ? CommentIconV2 : CommentIcon;

// Match the desktop `PostActions` copy flow: fetch the short URL imperatively
// on click so anonymous users still get a usable (long) link instead of a no-op.
Expand Down Expand Up @@ -110,45 +115,67 @@ function MobilePostFloatingBarV1({
);
}, [copyLink, getShortUrl, logEvent, origin, post]);

const upvoteButton = (
<QuaternaryButton
id="mobile-upvote-post-btn"
aria-label={isUpvoteActive ? 'Remove upvote' : 'Upvote'}
pressed={isUpvoteActive}
onClick={onToggleUpvote}
icon={<UpvoteIcon secondary={isUpvoteActive} />}
variant={ButtonVariant.Tertiary}
color={ButtonColor.Avocado}
size={ButtonSize.Medium}
>
{upvoteCount > 0 && (
<InteractionCounter className={counterClasses} value={upvoteCount} />
)}
</QuaternaryButton>
);

const downvoteButton = (
<QuaternaryButton
id="mobile-downvote-post-btn"
aria-label={isDownvoteActive ? 'Remove downvote' : 'Downvote'}
pressed={isDownvoteActive}
onClick={onToggleDownvote}
icon={<DownvoteIcon secondary={isDownvoteActive} />}
variant={ButtonVariant.Tertiary}
color={ButtonColor.Ketchup}
size={ButtonSize.Medium}
/>
);

const commentButton = (
<QuaternaryButton
id="mobile-comment-post-btn"
aria-label="Comment"
pressed={post.commented}
onClick={() => onCommentClick(LogOrigin.PostCommentButton)}
icon={<CommentIconComponent secondary={post.commented} />}
size={ButtonSize.Medium}
className="btn-tertiary-blueCheese"
>
{commentCount > 0 && (
<InteractionCounter className={counterClasses} value={commentCount} />
)}
</QuaternaryButton>
);

return (
<div className={classNames(containerClasses, className)}>
<QuaternaryButton
id="mobile-upvote-post-btn"
aria-label={isUpvoteActive ? 'Remove upvote' : 'Upvote'}
pressed={isUpvoteActive}
onClick={onToggleUpvote}
icon={<UpvoteIcon secondary={isUpvoteActive} />}
variant={ButtonVariant.Tertiary}
color={ButtonColor.Avocado}
size={ButtonSize.Medium}
>
{upvoteCount > 0 && (
<InteractionCounter className={counterClasses} value={upvoteCount} />
)}
</QuaternaryButton>
<QuaternaryButton
id="mobile-downvote-post-btn"
aria-label={isDownvoteActive ? 'Remove downvote' : 'Downvote'}
pressed={isDownvoteActive}
onClick={onToggleDownvote}
icon={<DownvoteIcon secondary={isDownvoteActive} />}
variant={ButtonVariant.Tertiary}
color={ButtonColor.Ketchup}
size={ButtonSize.Medium}
/>
<QuaternaryButton
id="mobile-comment-post-btn"
aria-label="Comment"
pressed={post.commented}
onClick={() => onCommentClick(LogOrigin.PostCommentButton)}
icon={<CommentIcon secondary={post.commented} />}
size={ButtonSize.Medium}
className="btn-tertiary-blueCheese"
>
{commentCount > 0 && (
<InteractionCounter className={counterClasses} value={commentCount} />
)}
</QuaternaryButton>
{isCommentFirst ? (
<>
{commentButton}
{upvoteButton}
{downvoteButton}
</>
) : (
<>
{upvoteButton}
{downvoteButton}
{commentButton}
</>
)}
<BookmarkButton
post={post}
iconSize={IconSize.Medium}
Expand Down
Loading
Loading