Skip to content

Commit ad12f22

Browse files
committed
refactor: move to own component
1 parent fa99101 commit ad12f22

File tree

3 files changed

+264
-245
lines changed

3 files changed

+264
-245
lines changed

app/components/Package/Header.vue

Lines changed: 1 addition & 245 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
<script setup lang="ts">
22
import type { RouteLocationRaw } from 'vue-router'
33
import { SCROLL_TO_TOP_THRESHOLD } from '~/composables/useScrollToTop'
4-
import { useModal } from '~/composables/useModal'
5-
import { useAtproto } from '~/composables/atproto/useAtproto'
6-
import { togglePackageLike } from '~/utils/atproto/likes'
74
import { isEditableElement } from '~/utils/input'
85
96
const props = defineProps<{
@@ -64,7 +61,6 @@ const { y: scrollY } = useScroll(window)
6461
const showScrollToTop = computed(() => scrollY.value > SCROLL_TO_TOP_THRESHOLD)
6562
6663
const packageName = computed(() => props.pkg?.name ?? '')
67-
const compactNumberFormatter = useCompactNumberFormatter()
6864
6965
const { copied: copiedPkgName, copy: copyPkgName } = useClipboard({
7066
source: packageName,
@@ -178,101 +174,6 @@ onKeyStroke(
178174
{ dedupe: true },
179175
)
180176
181-
//atproto
182-
// TODO: Maybe set this where it's not loaded here every load?
183-
const { user } = useAtproto()
184-
185-
const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)')
186-
187-
const likeAnimKey = shallowRef(0)
188-
const showLikeFloat = shallowRef(false)
189-
const likeFloatKey = shallowRef(0)
190-
let likeFloatTimer: ReturnType<typeof setTimeout> | null = null
191-
192-
const heartAnimStyle = computed(() => {
193-
if (likeAnimKey.value === 0 || prefersReducedMotion.value) return {}
194-
return {
195-
animation: likesData.value?.userHasLiked
196-
? 'heart-spring 0.55s cubic-bezier(0.34,1.56,0.64,1) forwards'
197-
: 'heart-unlike 0.3s ease forwards',
198-
}
199-
})
200-
201-
const authModal = useModal('auth-modal')
202-
203-
const { data: likesData, status: likeStatus } = useFetch(
204-
() => `/api/social/likes/${packageName.value}`,
205-
{
206-
default: () => ({ totalLikes: 0, userHasLiked: false }),
207-
server: false,
208-
},
209-
)
210-
211-
const isLoadingLikeData = computed(
212-
() => likeStatus.value === 'pending' || likeStatus.value === 'idle',
213-
)
214-
215-
const isLikeActionPending = shallowRef(false)
216-
217-
const likeAction = async () => {
218-
if (user.value?.handle == null) {
219-
authModal.open()
220-
return
221-
}
222-
223-
if (isLikeActionPending.value) return
224-
225-
const currentlyLiked = likesData.value?.userHasLiked ?? false
226-
const currentLikes = likesData.value?.totalLikes ?? 0
227-
228-
likeAnimKey.value++
229-
230-
if (!currentlyLiked && !prefersReducedMotion.value) {
231-
if (likeFloatTimer !== null) {
232-
clearTimeout(likeFloatTimer)
233-
likeFloatTimer = null
234-
}
235-
likeFloatKey.value++
236-
showLikeFloat.value = true
237-
likeFloatTimer = setTimeout(() => {
238-
showLikeFloat.value = false
239-
likeFloatTimer = null
240-
}, 850)
241-
}
242-
243-
// Optimistic update
244-
likesData.value = {
245-
totalLikes: currentlyLiked ? currentLikes - 1 : currentLikes + 1,
246-
userHasLiked: !currentlyLiked,
247-
}
248-
249-
isLikeActionPending.value = true
250-
251-
try {
252-
const result = await togglePackageLike(packageName.value, currentlyLiked, user.value?.handle)
253-
254-
isLikeActionPending.value = false
255-
256-
if (result.success) {
257-
// Update with server response
258-
likesData.value = result.data
259-
} else {
260-
// Revert on error
261-
likesData.value = {
262-
totalLikes: currentLikes,
263-
userHasLiked: currentlyLiked,
264-
}
265-
}
266-
} catch {
267-
// Revert on error
268-
likesData.value = {
269-
totalLikes: currentLikes,
270-
userHasLiked: currentlyLiked,
271-
}
272-
isLikeActionPending.value = false
273-
}
274-
}
275-
276177
const fundingUrl = computed(() => {
277178
let funding = props.displayVersion?.funding
278179
if (Array.isArray(funding)) funding = funding[0]
@@ -318,57 +219,7 @@ const fundingUrl = computed(() => {
318219
>
319220
<span class="max-sm:sr-only">{{ $t('package.links.compare_this_package') }}</span>
320221
</LinkBase>
321-
<!-- Package likes -->
322-
<TooltipApp
323-
:text="
324-
isLoadingLikeData
325-
? $t('common.loading')
326-
: likesData?.userHasLiked
327-
? $t('package.likes.unlike')
328-
: $t('package.likes.like')
329-
"
330-
position="bottom"
331-
class="items-center"
332-
strategy="fixed"
333-
>
334-
<div :class="$style.likeWrapper">
335-
<span
336-
v-if="showLikeFloat"
337-
:key="likeFloatKey"
338-
aria-hidden="true"
339-
:class="$style.likeFloat"
340-
>+1</span
341-
>
342-
<ButtonBase
343-
@click="likeAction"
344-
size="medium"
345-
:aria-label="
346-
likesData?.userHasLiked ? $t('package.likes.unlike') : $t('package.likes.like')
347-
"
348-
:aria-pressed="likesData?.userHasLiked"
349-
>
350-
<span
351-
:key="likeAnimKey"
352-
:class="
353-
likesData?.userHasLiked
354-
? 'i-lucide:heart-minus fill-red-500 text-red-500'
355-
: 'i-lucide:heart-plus'
356-
"
357-
:style="heartAnimStyle"
358-
aria-hidden="true"
359-
class="inline-block w-4 h-4"
360-
/>
361-
<span
362-
v-if="isLoadingLikeData"
363-
class="i-svg-spinners:ring-resize w-3 h-3 my-0.5"
364-
aria-hidden="true"
365-
/>
366-
<span v-else>
367-
{{ compactNumberFormatter.format(likesData?.totalLikes ?? 0) }}
368-
</span>
369-
</ButtonBase>
370-
</div>
371-
</TooltipApp>
222+
<PackageLikes :packageName />
372223

373224
<LinkBase
374225
variant="button-secondary"
@@ -522,99 +373,4 @@ const fundingUrl = computed(() => {
522373
display: none;
523374
}
524375
}
525-
526-
.likeWrapper {
527-
position: relative;
528-
display: inline-flex;
529-
}
530-
531-
.likeFloat {
532-
position: absolute;
533-
top: 0;
534-
left: 50%;
535-
font-size: 12px;
536-
font-weight: 600;
537-
color: var(--color-red-500, #ef4444);
538-
pointer-events: none;
539-
white-space: nowrap;
540-
animation: float-up 0.75s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
541-
}
542-
543-
@media (prefers-reduced-motion: reduce) {
544-
.likeFloat {
545-
display: none;
546-
}
547-
}
548-
549-
@keyframes float-up {
550-
0% {
551-
opacity: 0;
552-
transform: translateX(-50%) translateY(0);
553-
}
554-
15% {
555-
opacity: 1;
556-
transform: translateX(-50%) translateY(-4px);
557-
}
558-
80% {
559-
opacity: 1;
560-
transform: translateX(-50%) translateY(-20px);
561-
}
562-
100% {
563-
opacity: 0;
564-
transform: translateX(-50%) translateY(-28px);
565-
}
566-
}
567-
</style>
568-
569-
<style>
570-
@keyframes heart-spring {
571-
0% {
572-
transform: scale(1);
573-
}
574-
15% {
575-
transform: scale(0.78);
576-
}
577-
45% {
578-
transform: scale(1.55);
579-
}
580-
65% {
581-
transform: scale(0.93);
582-
}
583-
80% {
584-
transform: scale(1.1);
585-
}
586-
100% {
587-
transform: scale(1);
588-
}
589-
}
590-
591-
@keyframes heart-unlike {
592-
0% {
593-
transform: scale(1);
594-
}
595-
30% {
596-
transform: scale(0.85);
597-
}
598-
60% {
599-
transform: scale(1.05);
600-
}
601-
100% {
602-
transform: scale(1);
603-
}
604-
}
605-
606-
@media (prefers-reduced-motion: reduce) {
607-
@keyframes heart-spring {
608-
from,
609-
to {
610-
transform: scale(1);
611-
}
612-
}
613-
@keyframes heart-unlike {
614-
from,
615-
to {
616-
transform: scale(1);
617-
}
618-
}
619-
}
620376
</style>

0 commit comments

Comments
 (0)