@@ -184,6 +184,18 @@ onKeyStroke(
184184// TODO: Maybe set this where it's not loaded here every load?
185185const { user } = useAtproto ()
186186
187+ const likeAnimKey = shallowRef (0 )
188+ const showLikeFloat = shallowRef (false )
189+
190+ const heartAnimStyle = computed (() => {
191+ if (likeAnimKey .value === 0 ) return {}
192+ return {
193+ animation: likesData .value ?.userHasLiked
194+ ? ' heart-spring 0.55s cubic-bezier(0.34,1.56,0.64,1) forwards'
195+ : ' heart-unlike 0.3s ease forwards' ,
196+ }
197+ })
198+
187199const authModal = useModal (' auth-modal' )
188200
189201const { data : likesData, status : likeStatus } = useFetch (
@@ -211,6 +223,15 @@ const likeAction = async () => {
211223 const currentlyLiked = likesData .value ?.userHasLiked ?? false
212224 const currentLikes = likesData .value ?.totalLikes ?? 0
213225
226+ likeAnimKey .value ++
227+
228+ if (! currentlyLiked ) {
229+ showLikeFloat .value = true
230+ setTimeout (() => {
231+ showLikeFloat .value = false
232+ }, 850 )
233+ }
234+
214235 // Optimistic update
215236 likesData .value = {
216237 totalLikes: currentlyLiked ? currentLikes - 1 : currentLikes + 1 ,
@@ -293,26 +314,37 @@ const likeAction = async () => {
293314 class =" items-center"
294315 strategy =" fixed"
295316 >
296- <ButtonBase
297- @click =" likeAction"
298- size =" medium"
299- :aria-label ="
300- likesData?.userHasLiked ? $t('package.likes.unlike') : $t('package.likes.like')
301- "
302- :aria-pressed =" likesData?.userHasLiked"
303- :classicon ="
304- likesData?.userHasLiked ? 'i-lucide:heart-minus text-red-500' : 'i-lucide:heart-plus'
305- "
306- >
307- <span
308- v-if =" isLoadingLikeData"
309- class =" i-svg-spinners:ring-resize w-3 h-3 my-0.5"
310- aria-hidden =" true"
311- />
312- <span v-else >
313- {{ compactNumberFormatter.format(likesData?.totalLikes ?? 0) }}
314- </span >
315- </ButtonBase >
317+ <div :class =" $style.likeWrapper" >
318+ <span v-if =" showLikeFloat" aria-hidden =" true" :class =" $style.likeFloat" >+1</span >
319+ <ButtonBase
320+ @click =" likeAction"
321+ size =" medium"
322+ :aria-label ="
323+ likesData?.userHasLiked ? $t('package.likes.unlike') : $t('package.likes.like')
324+ "
325+ :aria-pressed =" likesData?.userHasLiked"
326+ >
327+ <span
328+ :key =" likeAnimKey"
329+ :class ="
330+ likesData?.userHasLiked
331+ ? 'i-lucide:heart-minus fill-red-500 text-red-500'
332+ : 'i-lucide:heart-plus'
333+ "
334+ :style =" heartAnimStyle"
335+ aria-hidden =" true"
336+ class =" inline-block w-4 h-4"
337+ />
338+ <span
339+ v-if =" isLoadingLikeData"
340+ class =" i-svg-spinners:ring-resize w-3 h-3 my-0.5"
341+ aria-hidden =" true"
342+ />
343+ <span v-else >
344+ {{ compactNumberFormatter.format(likesData?.totalLikes ?? 0) }}
345+ </span >
346+ </ButtonBase >
347+ </div >
316348 </TooltipApp >
317349 </div >
318350 </div >
@@ -458,4 +490,78 @@ const likeAction = async () => {
458490 display : none ;
459491 }
460492}
493+
494+ .likeWrapper {
495+ position : relative ;
496+ display : inline-flex ;
497+ }
498+
499+ .likeFloat {
500+ position : absolute ;
501+ top : 0 ;
502+ left : 50% ;
503+ font-size : 12px ;
504+ font-weight : 600 ;
505+ color : var (--color-red-500 , #ef4444 );
506+ pointer-events : none ;
507+ white-space : nowrap ;
508+ animation : float-up 0.75s cubic-bezier (0.25 , 0.46 , 0.45 , 0.94 ) forwards ;
509+ }
510+
511+ @keyframes float-up {
512+ 0% {
513+ opacity : 0 ;
514+ transform : translateX (-50% ) translateY (0 );
515+ }
516+ 15% {
517+ opacity : 1 ;
518+ transform : translateX (-50% ) translateY (-4px );
519+ }
520+ 80% {
521+ opacity : 1 ;
522+ transform : translateX (-50% ) translateY (-20px );
523+ }
524+ 100% {
525+ opacity : 0 ;
526+ transform : translateX (-50% ) translateY (-28px );
527+ }
528+ }
529+ </style >
530+
531+ <style >
532+ @keyframes heart-spring {
533+ 0% {
534+ transform : scale (1 );
535+ }
536+ 15% {
537+ transform : scale (0.78 );
538+ }
539+ 45% {
540+ transform : scale (1.55 );
541+ }
542+ 65% {
543+ transform : scale (0.93 );
544+ }
545+ 80% {
546+ transform : scale (1.1 );
547+ }
548+ 100% {
549+ transform : scale (1 );
550+ }
551+ }
552+
553+ @keyframes heart-unlike {
554+ 0% {
555+ transform : scale (1 );
556+ }
557+ 30% {
558+ transform : scale (0.85 );
559+ }
560+ 60% {
561+ transform : scale (1.05 );
562+ }
563+ 100% {
564+ transform : scale (1 );
565+ }
566+ }
461567 </style >
0 commit comments