From e503482c6ff702d38a3f524de3ff96bb100a9550 Mon Sep 17 00:00:00 2001 From: Alexander Brown Date: Tue, 20 Jan 2026 23:17:06 -0800 Subject: [PATCH] feat: add maxColumns prop to VirtualGrid for responsive column capping (#8210) ## Summary Add `maxColumns` prop to VirtualGrid for responsive column capping. ## Changes - **VirtualGrid**: Add `maxColumns` prop to cap grid columns; refactor inline styles to Tailwind classes; extract spacer height computation to helper function - **AssetGrid**: Use `useBreakpoints` to set responsive `maxColumns` (1-5 based on Tailwind breakpoints) ## Review Focus - `maxColumns` behavior when `Infinity` vs finite: does `gridTemplateColumns` override work correctly? - Tailwind scrollbar classes replacing scoped CSS --- src/components/common/VirtualGrid.vue | 63 +++++++++++++------- src/platform/assets/components/AssetGrid.vue | 19 +++++- 2 files changed, 58 insertions(+), 24 deletions(-) diff --git a/src/components/common/VirtualGrid.vue b/src/components/common/VirtualGrid.vue index 134778778..fb6b4374c 100644 --- a/src/components/common/VirtualGrid.vue +++ b/src/components/common/VirtualGrid.vue @@ -1,16 +1,20 @@ @@ -28,19 +32,22 @@ type GridState = { const { items, + gridStyle, bufferRows = 1, scrollThrottle = 64, resizeDebounce = 64, defaultItemHeight = 200, - defaultItemWidth = 200 + defaultItemWidth = 200, + maxColumns = Infinity } = defineProps<{ items: (T & { key: string })[] - gridStyle: Partial + gridStyle: CSSProperties bufferRows?: number scrollThrottle?: number resizeDebounce?: number defaultItemHeight?: number defaultItemWidth?: number + maxColumns?: number }>() const emit = defineEmits<{ @@ -59,7 +66,18 @@ const { y: scrollY } = useScroll(container, { eventListenerOptions: { passive: true } }) -const cols = computed(() => Math.floor(width.value / itemWidth.value) || 1) +const cols = computed(() => + Math.min(Math.floor(width.value / itemWidth.value) || 1, maxColumns) +) + +const mergedGridStyle = computed(() => { + if (maxColumns === Infinity) return gridStyle + return { + ...gridStyle, + gridTemplateColumns: `repeat(${maxColumns}, minmax(0, 1fr))` + } +}) + 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) @@ -83,6 +101,16 @@ const renderedItems = computed(() => isValidGrid.value ? items.slice(state.value.start, state.value.end) : [] ) +function rowsToHeight(rows: number): string { + return `${(rows / cols.value) * itemHeight.value}px` +} +const topSpacerStyle = computed(() => ({ + height: rowsToHeight(state.value.start) +})) +const bottomSpacerStyle = computed(() => ({ + height: rowsToHeight(items.length - state.value.end) +})) + whenever( () => state.value.isNearEnd, () => { @@ -109,15 +137,6 @@ const onResize = debounce(updateItemSize, resizeDebounce) watch([width, height], onResize, { flush: 'post' }) whenever(() => items, updateItemSize, { flush: 'post' }) onBeforeUnmount(() => { - onResize.cancel() // Clear pending debounced calls + onResize.cancel() }) - - diff --git a/src/platform/assets/components/AssetGrid.vue b/src/platform/assets/components/AssetGrid.vue index da3c609b0..6e91a67fa 100644 --- a/src/platform/assets/components/AssetGrid.vue +++ b/src/platform/assets/components/AssetGrid.vue @@ -26,9 +26,10 @@