wip bsky client for the web & android
at main 79 lines 1.8 kB view raw
1<script setup lang="ts"> 2import { ref, onMounted } from 'vue' 3import type { AppBskyActorDefs } from '@atcute/bluesky' 4 5import Modal from '@/components/UI/BaseModal.vue' 6import Button from '@/components/UI/BaseButton.vue' 7import ProfileRow from '@/components/Profile/ProfileRow.vue' 8import SkeletonLoader from '@/components/UI/SkeletonLoader.vue' 9 10import { useInfiniteScroll } from '@/composables/useInfiniteScroll' 11 12const props = defineProps<{ 13 title: string 14 users: AppBskyActorDefs.ProfileView[] 15 loading?: boolean 16 onReachedBottom?: () => void 17 hasMore?: boolean 18}>() 19 20const emit = defineEmits<{ 21 (e: 'close'): void 22}>() 23 24const sentinel = ref<HTMLElement | null>(null) 25 26const { setup } = useInfiniteScroll(sentinel, () => { 27 if (props.onReachedBottom && !props.loading) { 28 props.onReachedBottom() 29 } 30}) 31 32onMounted(() => { 33 setup() 34}) 35</script> 36 37<template> 38 <Modal :title="title" width="640px" @close="emit('close')" edge-to-edge> 39 <div class="user-list" role="list"> 40 <template v-if="loading && users.length === 0"> 41 <SkeletonLoader v-for="n in 6" :key="n" /> 42 </template> 43 44 <ProfileRow v-for="user in users" :key="user.did" :profile="user" @click="emit('close')" /> 45 46 <div ref="sentinel" class="sentinel"></div> 47 48 <div v-if="loading && users.length > 0" class="loading-more"> 49 <p>loading more...</p> 50 </div> 51 <div v-if="!hasMore && users.length > 0" class="loading-more"> 52 <p>that's all!</p> 53 </div> 54 </div> 55 56 <template #footer> 57 <Button variant="primary" @click="emit('close')">Close</Button> 58 </template> 59 </Modal> 60</template> 61 62<style lang="scss" scoped> 63.user-list { 64 display: flex; 65 flex-direction: column; 66 max-height: 60vh; 67 overflow-y: auto; 68} 69 70.sentinel { 71 height: 1px; 72} 73 74.loading-more { 75 opacity: 0.6; 76 text-align: center; 77 padding: 1rem; 78} 79</style>