[Manager] Add infinite scroll to search results (#3124)

This commit is contained in:
Christian Byrne
2025-03-18 07:52:32 -07:00
committed by GitHub
parent 52bad3d0d1
commit a85a1bf794
3 changed files with 48 additions and 12 deletions

View File

@@ -15,10 +15,16 @@
</template>
<script setup lang="ts" generic="T">
import { useElementSize, useScroll } from '@vueuse/core'
import { useElementSize, useScroll, whenever } from '@vueuse/core'
import { clamp, debounce } from 'lodash'
import { type CSSProperties, computed, onBeforeUnmount, ref, watch } from 'vue'
type GridState = {
start: number
end: number
isNearEnd: boolean
}
const {
items,
bufferRows = 1,
@@ -36,6 +42,13 @@ const {
defaultItemWidth?: number
}>()
const emit = defineEmits<{
/**
* Emitted when `bufferRows` (or fewer) rows remaining between scrollY and grid bottom.
*/
'approach-end': []
}>()
const itemHeight = ref(defaultItemHeight)
const itemWidth = ref(defaultItemWidth)
const container = ref<HTMLElement | null>(null)
@@ -50,22 +63,31 @@ const viewRows = computed(() => Math.ceil(height.value / itemHeight.value))
const offsetRows = computed(() => Math.floor(scrollY.value / itemHeight.value))
const isValidGrid = computed(() => height.value && width.value && items?.length)
const state = computed<{ start: number; end: number }>(() => {
const state = computed<GridState>(() => {
const fromRow = offsetRows.value - bufferRows
const toRow = offsetRows.value + bufferRows + viewRows.value
const fromCol = fromRow * cols.value
const toCol = toRow * cols.value
const remainingCol = items.length - toCol
return {
start: clamp(fromCol, 0, items?.length),
end: clamp(toCol, fromCol, items?.length)
end: clamp(toCol, fromCol, items?.length),
isNearEnd: remainingCol <= cols.value * bufferRows
}
})
const renderedItems = computed(() =>
isValidGrid.value ? items.slice(state.value.start, state.value.end) : []
)
whenever(
() => state.value.isNearEnd,
() => {
emit('approach-end')
}
)
const updateItemSize = () => {
if (container.value) {
const firstItem = container.value.querySelector('[data-virtual-grid-item]')

View File

@@ -34,7 +34,7 @@
/>
<div class="flex-1 overflow-auto">
<div
v-if="isLoading || isInitialLoad"
v-if="(searchResults.length === 0 && isLoading) || isInitialLoad"
class="flex justify-center items-center h-full"
>
<ProgressSpinner />
@@ -55,13 +55,14 @@
<div v-else class="h-full" @click="handleGridContainerClick">
<VirtualGrid
:items="resultsWithKeys"
:buffer-rows="5"
:buffer-rows="3"
:gridStyle="{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(22rem, 1fr))',
gridTemplateColumns: 'repeat(auto-fill, minmax(19rem, 1fr))',
padding: '0.5rem',
gap: '1.5rem'
}"
@approach-end="onApproachEnd"
>
<template #item="{ item }">
<PackCard
@@ -138,6 +139,9 @@ const {
suggestions
} = useRegistrySearch()
pageNumber.value = 0
const onApproachEnd = () => {
pageNumber.value++
}
const isInitialLoad = computed(
() => searchResults.value.length === 0 && searchQuery.value === ''

View File

@@ -49,8 +49,11 @@ export function useRegistrySearch() {
(algoliaNode: AlgoliaNodePack) => algoliaNode.id
)
const onQueryChange = async () => {
const updateSearchResults = async (options: { append?: boolean }) => {
isLoading.value = true
if (!options.append) {
pageNumber.value = 0
}
const { nodePacks, querySuggestions } = await searchPacks(
searchQuery.value,
{
@@ -59,16 +62,23 @@ export function useRegistrySearch() {
restrictSearchableAttributes: searchAttributes.value
}
)
results.value = nodePacks
if (options.append && results.value?.length) {
results.value = results.value.concat(nodePacks)
} else {
results.value = nodePacks
}
suggestions.value = querySuggestions
isLoading.value = false
}
watch([pageNumber, sortField, searchMode], onQueryChange, {
immediate: true
})
const onQueryChange = () => updateSearchResults({ append: false })
const onPageChange = () => updateSearchResults({ append: true })
watch([sortField, searchMode], onQueryChange)
watch(pageNumber, onPageChange)
watchDebounced(searchQuery, onQueryChange, {
debounce: SEARCH_DEBOUNCE_TIME
debounce: SEARCH_DEBOUNCE_TIME,
immediate: true
})
return {