forked from BRETT/BRETT
Initial Commit
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
<script setup lang="ts">
|
||||
import { toast } from 'vue-sonner'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
|
||||
const props = defineProps<{ uploadId: string }>()
|
||||
const emit = defineEmits<{ subscriptionChange: [uploadId: string, enabled: boolean] }>()
|
||||
|
||||
const { user } = useAuth()
|
||||
const { reactionsFor, toggle } = useUploadReactions()
|
||||
|
||||
const busy = ref(false)
|
||||
const pending = ref<string | null>(null)
|
||||
const reactions = computed(() => reactionsFor(props.uploadId))
|
||||
|
||||
const BURST_LINES = Array.from({ length: 12 }, (_, i) => ({
|
||||
angle: i * 30,
|
||||
long: i % 2 === 0,
|
||||
}))
|
||||
const burstEmoji = ref<string | null>(null)
|
||||
const burstNonce = ref(0)
|
||||
|
||||
async function pick(emoji: string) {
|
||||
if (!user.value) {
|
||||
toast.error('Melde dich an, um zu reagieren.')
|
||||
return
|
||||
}
|
||||
if (busy.value) return
|
||||
busy.value = true
|
||||
pending.value = emoji
|
||||
burstEmoji.value = emoji
|
||||
burstNonce.value++
|
||||
try {
|
||||
const uploadId = props.uploadId
|
||||
const result = await toggle(uploadId, emoji)
|
||||
emit('subscriptionChange', uploadId, result.isSubscribed)
|
||||
}
|
||||
catch {
|
||||
toast.error('Reaktion fehlgeschlagen')
|
||||
}
|
||||
finally {
|
||||
busy.value = false
|
||||
pending.value = null
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="reactions.length" class="flex flex-wrap items-center gap-2">
|
||||
<Tooltip v-for="r in reactions" :key="r.emoji">
|
||||
<TooltipTrigger as-child>
|
||||
<button
|
||||
type="button"
|
||||
class="relative flex cursor-pointer items-center gap-2 rounded-md border px-2.5 py-1 transition-colors"
|
||||
:class="cn(
|
||||
r.me
|
||||
? 'border-foreground/40 bg-secondary hover:border-foreground/60'
|
||||
: 'border-border bg-muted/40 hover:border-foreground/30',
|
||||
pending === r.emoji && 'animate-pulse',
|
||||
)"
|
||||
@click="pick(r.emoji)"
|
||||
>
|
||||
<span v-if="burstEmoji === r.emoji" :key="burstNonce" class="pointer-events-none absolute inset-0" aria-hidden="true">
|
||||
<i
|
||||
v-for="l in BURST_LINES"
|
||||
:key="l.angle"
|
||||
class="burst-line"
|
||||
:style="{ '--a': `${l.angle}deg`, '--h': l.long ? '9px' : '6px', '--d': l.long ? '-26px' : '-19px' }"
|
||||
/>
|
||||
</span>
|
||||
<span :key="burstNonce" class="text-xl leading-none" :class="burstEmoji === r.emoji && 'emoji-pop'">{{ r.emoji }}</span>
|
||||
<span class="text-sm font-medium tabular-nums" :class="r.me ? 'text-foreground' : 'text-muted-foreground'">{{ r.count }}</span>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{{ r.names.join(', ') }}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.burst-line {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 3px;
|
||||
height: var(--h);
|
||||
margin-left: -1.5px;
|
||||
margin-top: -3px;
|
||||
border-radius: 1.5px;
|
||||
background: var(--color-primary);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.emoji-pop {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.burst-line {
|
||||
animation: burst-line 0.55s cubic-bezier(0.15, 0.85, 0.35, 1) forwards;
|
||||
}
|
||||
.emoji-pop {
|
||||
animation: emoji-pop 0.3s ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes burst-line {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: rotate(var(--a)) translateY(-4px) scaleY(0.3);
|
||||
}
|
||||
30% {
|
||||
opacity: 1;
|
||||
transform: rotate(var(--a)) translateY(calc(var(--d) * 0.7)) scaleY(1.15);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: rotate(var(--a)) translateY(var(--d)) scaleY(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes emoji-pop {
|
||||
0% { transform: scale(1); }
|
||||
40% { transform: scale(1.35); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user