Skip to content

Commit

Permalink
Merge pull request #105 from planetscale/heart-hover
Browse files Browse the repository at this point in the history
fix liked by
  • Loading branch information
thejessewinton committed Jan 12, 2024
2 parents c2ba875 + 2ab9031 commit ce0e078
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 64 deletions.
1 change: 1 addition & 0 deletions src/app/(default)/post/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export default async function PostPage({ params }: PostPageParams) {
<HtmlView html={post.contentHtml} className="mt-8" />
<div className="flex gap-4 mt-6">
<ReactionButton
likedBy={post.likedBy}
likeCount={post.likedBy.length}
isLikedByCurrentUser={post.isLikedByCurrentUser}
id={post.id}
Expand Down
103 changes: 56 additions & 47 deletions src/app/_components/like-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,22 @@ import HeartIcon from '../_svg/heart-icon'
import { api } from '~/trpc/react'
import { useRouter } from 'next/navigation'
import { useState } from 'react'
import { LikedBy } from './liked-by'
import { type RouterOutputs } from '~/trpc/shared'

export const MAX_LIKED_BY_SHOWN = 50

type LikeButtonProps = {
likeCount: number
isLikedByCurrentUser: boolean
likedBy: RouterOutputs['post']['detail']['likedBy']
id: number
}

export const ReactionButton = ({
id,
likeCount,
likedBy,
isLikedByCurrentUser,
}: LikeButtonProps) => {
const [isAnimating, setIsAnimating] = useState(false)
Expand Down Expand Up @@ -50,53 +54,58 @@ export const ReactionButton = ({
}

return (
<Button
variant="secondary"
className={classNames(
'transition-colors overflow-hidden [transform:translateZ(0)] space-x-1.5',
{
'border-red-300 !bg-red-100 dark:!bg-red-900 dark:border-red-700':
isLikedByCurrentUser,
'!border-red-600 !bg-red-600 dark:!bg-red-600': isAnimating,
},
)}
onClick={handleReaction}
disabled={like.isLoading}
>
<span className="relative block w-4 h-4 shrink-0">
{isLikedByCurrentUser && !isAnimating ? (
<HeartFilledIcon className="absolute inset-0 text-red scale-1" />
) : (
<>
<HeartIcon
className={classNames(
'absolute inset-0 transition-all text-red fill-transparent transform-gpu',
{ 'scale-[12] fill-red-600': isAnimating },
)}
/>
<span
className={classNames(
'absolute w-4 h-4 top-0 left-[-.5px] rounded-full ring-inset ring-6 ring-gray-50 transition-all duration-300 transform-gpu z-10',
isAnimating ? 'scale-150 !ring-0' : 'scale-0',
)}
></span>
<HeartFilledIcon
className={classNames(
'absolute inset-0 transition-transform delay-200 duration-300 text-gray-50 transform-gpu z-10 ease-spring',
isAnimating ? 'scale-1' : 'scale-0',
)}
/>
</>
)}
</span>
<LikedBy
likedBy={likedBy}
trigger={
<Button
variant="secondary"
className={classNames(
'transition-colors overflow-hidden [transform:translateZ(0)] space-x-1.5',
{
'border-red-300 !bg-red-100 dark:!bg-red-900 dark:border-red-700':
isLikedByCurrentUser,
'!border-red-600 !bg-red-600 dark:!bg-red-600': isAnimating,
},
)}
onClick={handleReaction}
disabled={like.isLoading}
>
<span className="relative block w-4 h-4 shrink-0">
{isLikedByCurrentUser && !isAnimating ? (
<HeartFilledIcon className="absolute inset-0 text-red scale-1" />
) : (
<>
<HeartIcon
className={classNames(
'absolute inset-0 transition-all text-red fill-transparent transform-gpu',
{ 'scale-[12] fill-red-600': isAnimating },
)}
/>
<span
className={classNames(
'absolute w-4 h-4 top-0 left-[-.5px] rounded-full ring-inset ring-6 ring-gray-50 transition-all duration-300 transform-gpu z-10',
isAnimating ? 'scale-150 !ring-0' : 'scale-0',
)}
></span>
<HeartFilledIcon
className={classNames(
'absolute inset-0 transition-transform delay-200 duration-300 text-gray-50 transform-gpu z-10 ease-spring',
isAnimating ? 'scale-1' : 'scale-0',
)}
/>
</>
)}
</span>

<span
className={classNames('relative z-10 tabular-nums', {
'transition-colors duration-100 text-gray-50': like.isLoading,
})}
>
{likeCount}
</span>
</Button>
<span
className={classNames('relative z-10 tabular-nums', {
'transition-colors duration-100 text-gray-50': like.isLoading,
})}
>
{likeCount}
</span>
</Button>
}
/>
)
}
21 changes: 5 additions & 16 deletions src/app/_components/liked-by.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,34 @@ import { classNames } from '~/utils/core'
import * as Tooltip from '@radix-ui/react-tooltip'
import { type RouterOutputs } from '~/trpc/shared'
import { MAX_LIKED_BY_SHOWN } from './like-button'
import HeartFilledIcon from '../_svg/heart-filled-icon'
import HeartIcon from '../_svg/heart-icon'
import { type ReactNode } from 'react'

type LikedByProps = {
trigger: ReactNode
likedBy: RouterOutputs['post']['detail']['likedBy']
isLikedByCurrentUser: RouterOutputs['post']['detail']['isLikedByCurrentUser']
}

export const LikedBy = ({ likedBy, isLikedByCurrentUser }: LikedByProps) => {
export const LikedBy = ({ trigger, likedBy }: LikedByProps) => {
const likeCount = likedBy.length

return (
<Tooltip.TooltipProvider>
<Tooltip.Root delayDuration={300}>
<Tooltip.Trigger
asChild
onClick={(event) => {
event.preventDefault()
}}
onMouseDown={(event) => {
event.preventDefault()
}}
>
<div className="inline-flex items-center gap-1.5">
{isLikedByCurrentUser ? (
<HeartFilledIcon className="w-4 h-4 text-red" />
) : (
<HeartIcon className="w-4 h-4 text-red" />
)}
<span className="text-sm font-semibold tabular-nums">
{likeCount}
</span>
</div>
{trigger}
</Tooltip.Trigger>
<Tooltip.Content
side="bottom"
sideOffset={4}
className={classNames(
'max-w-[260px] px-3 py-1.5 rounded shadow-lg bg-secondary-inverse text-secondary-inverse sm:max-w-sm',
'max-w-[260px] px-3 py-1.5 rounded shadow-lg bg-secondary-inverse text-secondary-inverse sm:max-w-sm z-50',
likeCount === 0 && 'hidden',
)}
>
Expand Down
15 changes: 14 additions & 1 deletion src/app/_components/post-summary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import MessageIcon from '~/app/_svg/message-icon'

import { type Session } from 'next-auth'
import { LikedBy } from './liked-by'
import HeartFilledIcon from '../_svg/heart-filled-icon'
import HeartIcon from '../_svg/heart-icon'

export type PostSummaryProps = {
post: RouterOutputs['post']['feed']['posts'][number]
Expand Down Expand Up @@ -65,7 +67,18 @@ export const PostSummary = ({ post, hideAuthor }: PostSummaryProps) => {
)}
<div className="ml-auto flex gap-6">
<LikedBy
isLikedByCurrentUser={post.isLikedByCurrentUser}
trigger={
<div className="inline-flex items-center gap-1.5">
{post.isLikedByCurrentUser ? (
<HeartFilledIcon className="w-4 h-4 text-red" />
) : (
<HeartIcon className="w-4 h-4 text-red" />
)}
<span className="text-sm font-semibold tabular-nums">
{post.likedBy.length}
</span>
</div>
}
likedBy={post.likedBy}
/>

Expand Down

1 comment on commit ce0e078

@vercel
Copy link

@vercel vercel bot commented on ce0e078 Jan 12, 2024

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

beam – ./

beam-git-main.preview.planetscale.com
beam.psdb.co
beam.preview.planetscale.com

Please sign in to comment.