@wyxos/vibe 1.6.29 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -287
- package/lib/index.cjs +1 -0
- package/lib/index.js +795 -1791
- package/lib/logo-dark.svg +36 -36
- package/lib/logo-light.svg +29 -29
- package/lib/logo.svg +32 -32
- package/lib/manifest.json +1 -1
- package/package.json +82 -96
- package/LICENSE +0 -21
- package/lib/vibe.css +0 -1
- package/lib/vite.svg +0 -1
- package/src/App.vue +0 -35
- package/src/Masonry.vue +0 -1030
- package/src/archive/App.vue +0 -96
- package/src/archive/InfiniteMansonry.spec.ts +0 -10
- package/src/archive/InfiniteMasonry.vue +0 -218
- package/src/assets/vue.svg +0 -1
- package/src/calculateLayout.ts +0 -194
- package/src/components/CodeTabs.vue +0 -158
- package/src/components/MasonryItem.vue +0 -499
- package/src/components/examples/BasicExample.vue +0 -46
- package/src/components/examples/CustomItemExample.vue +0 -87
- package/src/components/examples/HeaderFooterExample.vue +0 -79
- package/src/components/examples/ManualInitExample.vue +0 -78
- package/src/components/examples/SwipeModeExample.vue +0 -40
- package/src/createMasonryTransitions.ts +0 -176
- package/src/main.ts +0 -6
- package/src/masonryUtils.ts +0 -96
- package/src/pages.json +0 -36402
- package/src/router/index.ts +0 -20
- package/src/style.css +0 -32
- package/src/types.ts +0 -101
- package/src/useMasonryDimensions.ts +0 -59
- package/src/useMasonryItems.ts +0 -231
- package/src/useMasonryLayout.ts +0 -164
- package/src/useMasonryPagination.ts +0 -539
- package/src/useMasonryScroll.ts +0 -61
- package/src/useMasonryVirtualization.ts +0 -140
- package/src/useSwipeMode.ts +0 -233
- package/src/utils/errorHandler.ts +0 -8
- package/src/views/Examples.vue +0 -323
- package/src/views/Home.vue +0 -321
- package/toggle-link.mjs +0 -92
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { ref, computed, nextTick, type Ref, type ComputedRef } from 'vue'
|
|
2
|
-
import { calculateColumnHeights } from './masonryUtils'
|
|
3
|
-
|
|
4
|
-
export interface UseMasonryVirtualizationOptions {
|
|
5
|
-
masonry: Ref<any[]>
|
|
6
|
-
container: Ref<HTMLElement | null>
|
|
7
|
-
columns: Ref<number>
|
|
8
|
-
virtualBufferPx: number
|
|
9
|
-
loadThresholdPx: number
|
|
10
|
-
handleScroll: (precomputedHeights?: number[]) => void
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function useMasonryVirtualization(options: UseMasonryVirtualizationOptions) {
|
|
14
|
-
const {
|
|
15
|
-
masonry,
|
|
16
|
-
container,
|
|
17
|
-
columns,
|
|
18
|
-
virtualBufferPx,
|
|
19
|
-
loadThresholdPx
|
|
20
|
-
} = options
|
|
21
|
-
|
|
22
|
-
// Use a ref for handleScroll so it can be updated after initialization
|
|
23
|
-
const handleScrollRef = ref<(precomputedHeights?: number[]) => void>(options.handleScroll)
|
|
24
|
-
|
|
25
|
-
// Virtualization viewport state
|
|
26
|
-
const viewportTop = ref(0)
|
|
27
|
-
const viewportHeight = ref(0)
|
|
28
|
-
const VIRTUAL_BUFFER_PX = virtualBufferPx
|
|
29
|
-
|
|
30
|
-
// Gate transitions during virtualization-only DOM churn
|
|
31
|
-
const virtualizing = ref(false)
|
|
32
|
-
|
|
33
|
-
// Scroll progress tracking
|
|
34
|
-
const scrollProgress = ref<{ distanceToTrigger: number; isNearTrigger: boolean }>({
|
|
35
|
-
distanceToTrigger: 0,
|
|
36
|
-
isNearTrigger: false
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
// Visible window of items (virtualization)
|
|
40
|
-
const visibleMasonry = computed(() => {
|
|
41
|
-
const top = viewportTop.value - VIRTUAL_BUFFER_PX
|
|
42
|
-
const bottom = viewportTop.value + viewportHeight.value + VIRTUAL_BUFFER_PX
|
|
43
|
-
const items = masonry.value as any[]
|
|
44
|
-
if (!items || items.length === 0) return [] as any[]
|
|
45
|
-
|
|
46
|
-
// Filter items that have valid positions and are within viewport
|
|
47
|
-
const visible = items.filter((it: any) => {
|
|
48
|
-
// If item doesn't have position yet, include it (will be filtered once layout is calculated)
|
|
49
|
-
if (typeof it.top !== 'number' || typeof it.columnHeight !== 'number') {
|
|
50
|
-
return true // Include items without positions to avoid hiding them prematurely
|
|
51
|
-
}
|
|
52
|
-
const itemTop = it.top
|
|
53
|
-
const itemBottom = it.top + it.columnHeight
|
|
54
|
-
return itemBottom >= top && itemTop <= bottom
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
// Log if we're filtering out items (for debugging)
|
|
58
|
-
if (import.meta.env.DEV && items.length > 0 && visible.length === 0 && viewportHeight.value > 0) {
|
|
59
|
-
const itemsWithPositions = items.filter((it: any) =>
|
|
60
|
-
typeof it.top === 'number' && typeof it.columnHeight === 'number'
|
|
61
|
-
)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return visible
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
function updateScrollProgress(precomputedHeights?: number[]) {
|
|
68
|
-
if (!container.value) return
|
|
69
|
-
|
|
70
|
-
const { scrollTop, clientHeight } = container.value
|
|
71
|
-
const visibleBottom = scrollTop + clientHeight
|
|
72
|
-
|
|
73
|
-
const columnHeights = precomputedHeights ?? calculateColumnHeights(masonry.value as any, columns.value)
|
|
74
|
-
const tallest = columnHeights.length ? Math.max(...columnHeights) : 0
|
|
75
|
-
const threshold = typeof loadThresholdPx === 'number' ? loadThresholdPx : 200
|
|
76
|
-
const triggerPoint = threshold >= 0
|
|
77
|
-
? Math.max(0, tallest - threshold)
|
|
78
|
-
: Math.max(0, tallest + threshold)
|
|
79
|
-
|
|
80
|
-
const distanceToTrigger = Math.max(0, triggerPoint - visibleBottom)
|
|
81
|
-
const isNearTrigger = distanceToTrigger <= 100
|
|
82
|
-
|
|
83
|
-
scrollProgress.value = {
|
|
84
|
-
distanceToTrigger: Math.round(distanceToTrigger),
|
|
85
|
-
isNearTrigger
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async function updateViewport() {
|
|
90
|
-
if (container.value) {
|
|
91
|
-
const scrollTop = container.value.scrollTop
|
|
92
|
-
const clientHeight = container.value.clientHeight || window.innerHeight
|
|
93
|
-
// Ensure viewportHeight is never 0 (fallback to window height if container height is 0)
|
|
94
|
-
const safeClientHeight = clientHeight > 0 ? clientHeight : window.innerHeight
|
|
95
|
-
viewportTop.value = scrollTop
|
|
96
|
-
viewportHeight.value = safeClientHeight
|
|
97
|
-
// Log when scroll handler runs (helpful for debugging viewport issues)
|
|
98
|
-
if (import.meta.env.DEV) {
|
|
99
|
-
console.log('[Masonry] scroll: viewport updated', {
|
|
100
|
-
scrollTop,
|
|
101
|
-
clientHeight: safeClientHeight,
|
|
102
|
-
itemsCount: masonry.value.length,
|
|
103
|
-
visibleItemsCount: visibleMasonry.value.length
|
|
104
|
-
})
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
// Gate transitions for virtualization-only DOM changes
|
|
108
|
-
virtualizing.value = true
|
|
109
|
-
await nextTick()
|
|
110
|
-
await nextTick()
|
|
111
|
-
virtualizing.value = false
|
|
112
|
-
|
|
113
|
-
const heights = calculateColumnHeights(masonry.value as any, columns.value)
|
|
114
|
-
handleScrollRef.value(heights as any)
|
|
115
|
-
updateScrollProgress(heights)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function reset() {
|
|
119
|
-
viewportTop.value = 0
|
|
120
|
-
viewportHeight.value = 0
|
|
121
|
-
virtualizing.value = false
|
|
122
|
-
scrollProgress.value = {
|
|
123
|
-
distanceToTrigger: 0,
|
|
124
|
-
isNearTrigger: false
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return {
|
|
129
|
-
viewportTop,
|
|
130
|
-
viewportHeight,
|
|
131
|
-
virtualizing,
|
|
132
|
-
scrollProgress,
|
|
133
|
-
visibleMasonry,
|
|
134
|
-
updateScrollProgress,
|
|
135
|
-
updateViewport,
|
|
136
|
-
reset,
|
|
137
|
-
handleScroll: handleScrollRef
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
package/src/useSwipeMode.ts
DELETED
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
import { computed, ref, type Ref, type ComputedRef } from 'vue'
|
|
2
|
-
|
|
3
|
-
export interface UseSwipeModeOptions {
|
|
4
|
-
useSwipeMode: ComputedRef<boolean>
|
|
5
|
-
masonry: Ref<any[]>
|
|
6
|
-
isLoading: Ref<boolean>
|
|
7
|
-
loadNext: () => Promise<any>
|
|
8
|
-
loadPage: (page: any) => Promise<any>
|
|
9
|
-
paginationHistory: Ref<any[]>
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface UseSwipeModeReturn {
|
|
13
|
-
// State
|
|
14
|
-
currentSwipeIndex: Ref<number>
|
|
15
|
-
swipeOffset: Ref<number>
|
|
16
|
-
isDragging: Ref<boolean>
|
|
17
|
-
swipeContainer: Ref<HTMLElement | null>
|
|
18
|
-
|
|
19
|
-
// Computed
|
|
20
|
-
currentItem: ComputedRef<any | null>
|
|
21
|
-
nextItem: ComputedRef<any | null>
|
|
22
|
-
previousItem: ComputedRef<any | null>
|
|
23
|
-
|
|
24
|
-
// Functions
|
|
25
|
-
handleTouchStart: (e: TouchEvent) => void
|
|
26
|
-
handleTouchMove: (e: TouchEvent) => void
|
|
27
|
-
handleTouchEnd: (e: TouchEvent) => void
|
|
28
|
-
handleMouseDown: (e: MouseEvent) => void
|
|
29
|
-
handleMouseMove: (e: MouseEvent) => void
|
|
30
|
-
handleMouseUp: (e: MouseEvent) => void
|
|
31
|
-
goToNextItem: () => void
|
|
32
|
-
goToPreviousItem: () => void
|
|
33
|
-
snapToCurrentItem: () => void
|
|
34
|
-
handleWindowResize: () => void
|
|
35
|
-
reset: () => void
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function useSwipeMode(options: UseSwipeModeOptions): UseSwipeModeReturn {
|
|
39
|
-
const { useSwipeMode, masonry, isLoading, loadNext, loadPage, paginationHistory } = options
|
|
40
|
-
|
|
41
|
-
// Swipe mode state
|
|
42
|
-
const currentSwipeIndex = ref<number>(0)
|
|
43
|
-
const swipeOffset = ref<number>(0)
|
|
44
|
-
const isDragging = ref<boolean>(false)
|
|
45
|
-
const dragStartY = ref<number>(0)
|
|
46
|
-
const dragStartOffset = ref<number>(0)
|
|
47
|
-
const swipeContainer = ref<HTMLElement | null>(null)
|
|
48
|
-
|
|
49
|
-
// Get current item index for swipe mode
|
|
50
|
-
const currentItem = computed(() => {
|
|
51
|
-
if (!useSwipeMode.value || masonry.value.length === 0) return null
|
|
52
|
-
const index = Math.max(0, Math.min(currentSwipeIndex.value, masonry.value.length - 1))
|
|
53
|
-
return masonry.value[index] || null
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
// Get next/previous items for preloading in swipe mode
|
|
57
|
-
const nextItem = computed(() => {
|
|
58
|
-
if (!useSwipeMode.value || !currentItem.value) return null
|
|
59
|
-
const nextIndex = currentSwipeIndex.value + 1
|
|
60
|
-
if (nextIndex >= masonry.value.length) return null
|
|
61
|
-
return masonry.value[nextIndex] || null
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
const previousItem = computed(() => {
|
|
65
|
-
if (!useSwipeMode.value || !currentItem.value) return null
|
|
66
|
-
const prevIndex = currentSwipeIndex.value - 1
|
|
67
|
-
if (prevIndex < 0) return null
|
|
68
|
-
return masonry.value[prevIndex] || null
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
function snapToCurrentItem() {
|
|
72
|
-
if (!swipeContainer.value) return
|
|
73
|
-
|
|
74
|
-
// Use container height for swipe mode instead of window height
|
|
75
|
-
const viewportHeight = swipeContainer.value.clientHeight
|
|
76
|
-
swipeOffset.value = -currentSwipeIndex.value * viewportHeight
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function goToNextItem() {
|
|
80
|
-
if (!nextItem.value) {
|
|
81
|
-
// Try to load next page
|
|
82
|
-
loadNext()
|
|
83
|
-
return
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
currentSwipeIndex.value++
|
|
87
|
-
snapToCurrentItem()
|
|
88
|
-
|
|
89
|
-
// Preload next item if we're near the end
|
|
90
|
-
if (currentSwipeIndex.value >= masonry.value.length - 5) {
|
|
91
|
-
loadNext()
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function goToPreviousItem() {
|
|
96
|
-
if (!previousItem.value) return
|
|
97
|
-
|
|
98
|
-
currentSwipeIndex.value--
|
|
99
|
-
snapToCurrentItem()
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Swipe gesture handlers
|
|
103
|
-
function handleTouchStart(e: TouchEvent) {
|
|
104
|
-
if (!useSwipeMode.value) return
|
|
105
|
-
isDragging.value = true
|
|
106
|
-
dragStartY.value = e.touches[0].clientY
|
|
107
|
-
dragStartOffset.value = swipeOffset.value
|
|
108
|
-
e.preventDefault()
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function handleTouchMove(e: TouchEvent) {
|
|
112
|
-
if (!useSwipeMode.value || !isDragging.value) return
|
|
113
|
-
const deltaY = e.touches[0].clientY - dragStartY.value
|
|
114
|
-
swipeOffset.value = dragStartOffset.value + deltaY
|
|
115
|
-
e.preventDefault()
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function handleTouchEnd(e: TouchEvent) {
|
|
119
|
-
if (!useSwipeMode.value || !isDragging.value) return
|
|
120
|
-
isDragging.value = false
|
|
121
|
-
|
|
122
|
-
const deltaY = swipeOffset.value - dragStartOffset.value
|
|
123
|
-
const threshold = 100 // Minimum swipe distance to trigger navigation
|
|
124
|
-
|
|
125
|
-
if (Math.abs(deltaY) > threshold) {
|
|
126
|
-
if (deltaY > 0 && previousItem.value) {
|
|
127
|
-
// Swipe down - go to previous
|
|
128
|
-
goToPreviousItem()
|
|
129
|
-
} else if (deltaY < 0 && nextItem.value) {
|
|
130
|
-
// Swipe up - go to next
|
|
131
|
-
goToNextItem()
|
|
132
|
-
} else {
|
|
133
|
-
// Snap back
|
|
134
|
-
snapToCurrentItem()
|
|
135
|
-
}
|
|
136
|
-
} else {
|
|
137
|
-
// Snap back if swipe wasn't far enough
|
|
138
|
-
snapToCurrentItem()
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
e.preventDefault()
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Mouse drag handlers for desktop testing
|
|
145
|
-
function handleMouseDown(e: MouseEvent) {
|
|
146
|
-
if (!useSwipeMode.value) return
|
|
147
|
-
isDragging.value = true
|
|
148
|
-
dragStartY.value = e.clientY
|
|
149
|
-
dragStartOffset.value = swipeOffset.value
|
|
150
|
-
e.preventDefault()
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function handleMouseMove(e: MouseEvent) {
|
|
154
|
-
if (!useSwipeMode.value || !isDragging.value) return
|
|
155
|
-
const deltaY = e.clientY - dragStartY.value
|
|
156
|
-
swipeOffset.value = dragStartOffset.value + deltaY
|
|
157
|
-
e.preventDefault()
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function handleMouseUp(e: MouseEvent) {
|
|
161
|
-
if (!useSwipeMode.value || !isDragging.value) return
|
|
162
|
-
isDragging.value = false
|
|
163
|
-
|
|
164
|
-
const deltaY = swipeOffset.value - dragStartOffset.value
|
|
165
|
-
const threshold = 100
|
|
166
|
-
|
|
167
|
-
if (Math.abs(deltaY) > threshold) {
|
|
168
|
-
if (deltaY > 0 && previousItem.value) {
|
|
169
|
-
goToPreviousItem()
|
|
170
|
-
} else if (deltaY < 0 && nextItem.value) {
|
|
171
|
-
goToNextItem()
|
|
172
|
-
} else {
|
|
173
|
-
snapToCurrentItem()
|
|
174
|
-
}
|
|
175
|
-
} else {
|
|
176
|
-
snapToCurrentItem()
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
e.preventDefault()
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Watch for container/window resize to update swipe mode
|
|
183
|
-
function handleWindowResize() {
|
|
184
|
-
// If switching from swipe to masonry, reset swipe state
|
|
185
|
-
if (!useSwipeMode.value && currentSwipeIndex.value > 0) {
|
|
186
|
-
currentSwipeIndex.value = 0
|
|
187
|
-
swipeOffset.value = 0
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// If switching to swipe mode, ensure we have items loaded
|
|
191
|
-
if (useSwipeMode.value && masonry.value.length === 0 && !isLoading.value) {
|
|
192
|
-
loadPage(paginationHistory.value[0] as any)
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Re-snap to current item on resize to adjust offset
|
|
196
|
-
if (useSwipeMode.value) {
|
|
197
|
-
snapToCurrentItem()
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function reset() {
|
|
202
|
-
currentSwipeIndex.value = 0
|
|
203
|
-
swipeOffset.value = 0
|
|
204
|
-
isDragging.value = false
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return {
|
|
208
|
-
// State
|
|
209
|
-
currentSwipeIndex,
|
|
210
|
-
swipeOffset,
|
|
211
|
-
isDragging,
|
|
212
|
-
swipeContainer,
|
|
213
|
-
|
|
214
|
-
// Computed
|
|
215
|
-
currentItem,
|
|
216
|
-
nextItem,
|
|
217
|
-
previousItem,
|
|
218
|
-
|
|
219
|
-
// Functions
|
|
220
|
-
handleTouchStart,
|
|
221
|
-
handleTouchMove,
|
|
222
|
-
handleTouchEnd,
|
|
223
|
-
handleMouseDown,
|
|
224
|
-
handleMouseMove,
|
|
225
|
-
handleMouseUp,
|
|
226
|
-
goToNextItem,
|
|
227
|
-
goToPreviousItem,
|
|
228
|
-
snapToCurrentItem,
|
|
229
|
-
handleWindowResize,
|
|
230
|
-
reset
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
package/src/views/Examples.vue
DELETED
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="min-h-screen bg-slate-50 pt-20">
|
|
3
|
-
<div class="max-w-7xl mx-auto px-4 py-8">
|
|
4
|
-
<div class="flex gap-8">
|
|
5
|
-
<!-- Side Menu -->
|
|
6
|
-
<aside class="hidden lg:block w-64 flex-shrink-0 sticky top-24 h-fit">
|
|
7
|
-
<nav class="bg-white rounded-lg shadow-sm p-4 border border-slate-200">
|
|
8
|
-
<h3 class="text-sm font-semibold text-slate-400 uppercase tracking-wider mb-4">Examples</h3>
|
|
9
|
-
<ul class="space-y-2">
|
|
10
|
-
<li v-for="example in examples" :key="example.id">
|
|
11
|
-
<a :href="`#${example.id}`" @click.prevent="scrollTo(example.id)"
|
|
12
|
-
class="block px-3 py-2 text-sm rounded-md transition-colors" :class="activeSection === example.id
|
|
13
|
-
? 'bg-blue-50 text-blue-600 font-medium'
|
|
14
|
-
: 'text-slate-600 hover:bg-slate-50 hover:text-slate-900'">
|
|
15
|
-
{{ example.title }}
|
|
16
|
-
</a>
|
|
17
|
-
</li>
|
|
18
|
-
</ul>
|
|
19
|
-
</nav>
|
|
20
|
-
</aside>
|
|
21
|
-
|
|
22
|
-
<!-- Main Content -->
|
|
23
|
-
<main class="flex-1 min-w-0">
|
|
24
|
-
<div class="space-y-16">
|
|
25
|
-
<!-- Basic Example -->
|
|
26
|
-
<section id="basic" ref="basicRef" class="scroll-mt-24">
|
|
27
|
-
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
|
28
|
-
<div class="px-6 py-4 border-b border-slate-200 bg-slate-50">
|
|
29
|
-
<h2 class="text-2xl font-bold text-slate-800">Basic Usage</h2>
|
|
30
|
-
<p class="text-slate-600 mt-1">Simple masonry grid with default MasonryItem component</p>
|
|
31
|
-
</div>
|
|
32
|
-
|
|
33
|
-
<!-- Live Example -->
|
|
34
|
-
<div class="p-6 bg-slate-50">
|
|
35
|
-
<div class="bg-white rounded-lg border border-slate-200 p-4" style="height: 500px;">
|
|
36
|
-
<BasicExample />
|
|
37
|
-
</div>
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
<!-- Code Tabs -->
|
|
41
|
-
<div class="px-6 pb-6">
|
|
42
|
-
<CodeTabs vue='<script setup>
|
|
43
|
-
import { ref } from "vue";
|
|
44
|
-
import { Masonry } from "@wyxos/vibe";
|
|
45
|
-
|
|
46
|
-
const items = ref([]);
|
|
47
|
-
|
|
48
|
-
async function getPage(page) {
|
|
49
|
-
const response = await fetch(`/api/items?page=${page}`);
|
|
50
|
-
const data = await response.json();
|
|
51
|
-
return {
|
|
52
|
-
items: data.items,
|
|
53
|
-
nextPage: page + 1
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
</script>
|
|
57
|
-
|
|
58
|
-
<template>
|
|
59
|
-
<Masonry
|
|
60
|
-
v-model:items="items"
|
|
61
|
-
:get-page="getPage"
|
|
62
|
-
:load-at-page="1"
|
|
63
|
-
/>
|
|
64
|
-
</template>' />
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
</section>
|
|
68
|
-
|
|
69
|
-
<!-- Manual Init Example -->
|
|
70
|
-
<section id="manual-init" ref="manualInitRef" class="scroll-mt-24">
|
|
71
|
-
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
|
72
|
-
<div class="px-6 py-4 border-b border-slate-200 bg-slate-50">
|
|
73
|
-
<h2 class="text-2xl font-bold text-slate-800">Manual Init CTA</h2>
|
|
74
|
-
<p class="text-slate-600 mt-1">Delay initialization until the user clicks a call-to-action</p>
|
|
75
|
-
</div>
|
|
76
|
-
|
|
77
|
-
<!-- Live Example -->
|
|
78
|
-
<div class="p-6 bg-slate-50">
|
|
79
|
-
<div class="bg-white rounded-lg border border-slate-200 p-4" style="height: 500px;">
|
|
80
|
-
<ManualInitExample />
|
|
81
|
-
</div>
|
|
82
|
-
</div>
|
|
83
|
-
|
|
84
|
-
<!-- Code Tabs -->
|
|
85
|
-
<div class="px-6 pb-6">
|
|
86
|
-
<CodeTabs vue='<script setup>
|
|
87
|
-
import { ref } from "vue";
|
|
88
|
-
import { Masonry } from "@wyxos/vibe";
|
|
89
|
-
|
|
90
|
-
const items = ref([]);
|
|
91
|
-
const masonryRef = ref(null);
|
|
92
|
-
|
|
93
|
-
async function getPage(page) {
|
|
94
|
-
const response = await fetch(`/api/items?page=${page}`);
|
|
95
|
-
const data = await response.json();
|
|
96
|
-
return {
|
|
97
|
-
items: data.items,
|
|
98
|
-
nextPage: page + 1
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function handleInit() {
|
|
103
|
-
masonryRef.value?.loadPage(1);
|
|
104
|
-
}
|
|
105
|
-
</script>
|
|
106
|
-
|
|
107
|
-
<template>
|
|
108
|
-
<button @click="handleInit">Load gallery</button>
|
|
109
|
-
|
|
110
|
-
<Masonry
|
|
111
|
-
ref="masonryRef"
|
|
112
|
-
v-model:items="items"
|
|
113
|
-
:get-page="getPage"
|
|
114
|
-
:load-at-page="1"
|
|
115
|
-
init="manual"
|
|
116
|
-
/>
|
|
117
|
-
</template>' />
|
|
118
|
-
</div>
|
|
119
|
-
</div>
|
|
120
|
-
</section>
|
|
121
|
-
|
|
122
|
-
<!-- Custom Masonry Item Example -->
|
|
123
|
-
<section id="custom-item" ref="customItemRef" class="scroll-mt-24">
|
|
124
|
-
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
|
125
|
-
<div class="px-6 py-4 border-b border-slate-200 bg-slate-50">
|
|
126
|
-
<h2 class="text-2xl font-bold text-slate-800">Custom Masonry Item</h2>
|
|
127
|
-
<p class="text-slate-600 mt-1">Customize item rendering with scoped slots</p>
|
|
128
|
-
</div>
|
|
129
|
-
|
|
130
|
-
<!-- Live Example -->
|
|
131
|
-
<div class="p-6 bg-slate-50">
|
|
132
|
-
<div class="bg-white rounded-lg border border-slate-200 p-4" style="height: 500px;">
|
|
133
|
-
<CustomItemExample />
|
|
134
|
-
</div>
|
|
135
|
-
</div>
|
|
136
|
-
|
|
137
|
-
<!-- Code Tabs -->
|
|
138
|
-
<div class="px-6 pb-6">
|
|
139
|
-
<CodeTabs vue='<script setup>
|
|
140
|
-
import { ref } from "vue";
|
|
141
|
-
import { Masonry } from "@wyxos/vibe";
|
|
142
|
-
|
|
143
|
-
const items = ref([]);
|
|
144
|
-
|
|
145
|
-
async function getPage(page) {
|
|
146
|
-
const response = await fetch(`/api/items?page=${page}`);
|
|
147
|
-
const data = await response.json();
|
|
148
|
-
return {
|
|
149
|
-
items: data.items,
|
|
150
|
-
nextPage: page + 1
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
</script>
|
|
154
|
-
|
|
155
|
-
<template>
|
|
156
|
-
<Masonry
|
|
157
|
-
v-model:items="items"
|
|
158
|
-
:get-page="getPage"
|
|
159
|
-
:load-at-page="1"
|
|
160
|
-
>
|
|
161
|
-
<template #item="{ item, remove }">
|
|
162
|
-
<div class="custom-card">
|
|
163
|
-
<img :src="item.src" :alt="item.title" />
|
|
164
|
-
<div class="overlay">
|
|
165
|
-
<h3>{{ item.title }}</h3>
|
|
166
|
-
<button @click="remove">Remove</button>
|
|
167
|
-
</div>
|
|
168
|
-
</div>
|
|
169
|
-
</template>
|
|
170
|
-
</Masonry>
|
|
171
|
-
</template>' />
|
|
172
|
-
</div>
|
|
173
|
-
-->
|
|
174
|
-
</div>
|
|
175
|
-
</section>
|
|
176
|
-
|
|
177
|
-
<!-- Header & Footer Example -->
|
|
178
|
-
<section id="header-footer" ref="headerFooterRef" class="scroll-mt-24">
|
|
179
|
-
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
|
180
|
-
<div class="px-6 py-4 border-b border-slate-200 bg-slate-50">
|
|
181
|
-
<h2 class="text-2xl font-bold text-slate-800">Header & Footer</h2>
|
|
182
|
-
<p class="text-slate-600 mt-1">
|
|
183
|
-
Use `layout.header` and `layout.footer` with slots to add per-item UI like badges, titles and
|
|
184
|
-
actions.
|
|
185
|
-
</p>
|
|
186
|
-
</div>
|
|
187
|
-
|
|
188
|
-
<!-- Live Example -->
|
|
189
|
-
<div class="p-6 bg-slate-50">
|
|
190
|
-
<div class="bg-white rounded-lg border border-slate-200 p-4" style="height: 500px;">
|
|
191
|
-
<HeaderFooterExample />
|
|
192
|
-
</div>
|
|
193
|
-
</div>
|
|
194
|
-
|
|
195
|
-
<!-- Code Tabs omitted for this example to keep docs build-safe.
|
|
196
|
-
See the Header & Footer example component for full code. -->
|
|
197
|
-
</div>
|
|
198
|
-
</section>
|
|
199
|
-
|
|
200
|
-
<!-- Swipe Mode Example -->
|
|
201
|
-
<section id="swipe-mode" ref="swipeModeRef" class="scroll-mt-24">
|
|
202
|
-
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
|
203
|
-
<div class="px-6 py-4 border-b border-slate-200 bg-slate-50">
|
|
204
|
-
<h2 class="text-2xl font-bold text-slate-800">Swipe Mode</h2>
|
|
205
|
-
<p class="text-slate-600 mt-1">Vertical swipe feed for mobile devices</p>
|
|
206
|
-
</div>
|
|
207
|
-
|
|
208
|
-
<!-- Live Example -->
|
|
209
|
-
<div class="p-6 bg-slate-50">
|
|
210
|
-
<div class="bg-white rounded-lg border border-slate-200 p-4" style="height: 500px;">
|
|
211
|
-
<SwipeModeExample />
|
|
212
|
-
</div>
|
|
213
|
-
</div>
|
|
214
|
-
|
|
215
|
-
<!-- Code Tabs -->
|
|
216
|
-
<div class="px-6 pb-6">
|
|
217
|
-
<CodeTabs vue='<script setup>
|
|
218
|
-
import { ref } from "vue";
|
|
219
|
-
import { Masonry } from "@wyxos/vibe";
|
|
220
|
-
|
|
221
|
-
const items = ref([]);
|
|
222
|
-
|
|
223
|
-
async function getPage(page) {
|
|
224
|
-
const response = await fetch(`/api/items?page=${page}`);
|
|
225
|
-
const data = await response.json();
|
|
226
|
-
return {
|
|
227
|
-
items: data.items,
|
|
228
|
-
nextPage: page + 1
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
</script>
|
|
232
|
-
|
|
233
|
-
<template>
|
|
234
|
-
<!-- Auto mode: switches to swipe on mobile -->
|
|
235
|
-
<Masonry
|
|
236
|
-
v-model:items="items"
|
|
237
|
-
:get-page="getPage"
|
|
238
|
-
layout-mode="auto"
|
|
239
|
-
:mobile-breakpoint="768"
|
|
240
|
-
/>
|
|
241
|
-
|
|
242
|
-
<!-- Force swipe mode on all devices -->
|
|
243
|
-
<Masonry
|
|
244
|
-
v-model:items="items"
|
|
245
|
-
:get-page="getPage"
|
|
246
|
-
layout-mode="swipe"
|
|
247
|
-
/>
|
|
248
|
-
</template>' />
|
|
249
|
-
</div>
|
|
250
|
-
</div>
|
|
251
|
-
</section>
|
|
252
|
-
</div>
|
|
253
|
-
</main>
|
|
254
|
-
</div>
|
|
255
|
-
</div>
|
|
256
|
-
</div>
|
|
257
|
-
</template>
|
|
258
|
-
|
|
259
|
-
<script setup lang="ts">
|
|
260
|
-
// @ts-nocheck - documentation page with inline string code samples that Vue/TS can't statically analyze cleanly
|
|
261
|
-
import { ref, onMounted, onUnmounted } from 'vue'
|
|
262
|
-
import CodeTabs from '../components/CodeTabs.vue'
|
|
263
|
-
import BasicExample from '../components/examples/BasicExample.vue'
|
|
264
|
-
import ManualInitExample from '../components/examples/ManualInitExample.vue'
|
|
265
|
-
import CustomItemExample from '../components/examples/CustomItemExample.vue'
|
|
266
|
-
import SwipeModeExample from '../components/examples/SwipeModeExample.vue'
|
|
267
|
-
import HeaderFooterExample from '../components/examples/HeaderFooterExample.vue'
|
|
268
|
-
|
|
269
|
-
const examples = [
|
|
270
|
-
{ id: 'basic', title: 'Basic Usage' },
|
|
271
|
-
{ id: 'manual-init', title: 'Manual Init CTA' },
|
|
272
|
-
{ id: 'custom-item', title: 'Custom Masonry Item' },
|
|
273
|
-
{ id: 'header-footer', title: 'Header & Footer' },
|
|
274
|
-
{ id: 'swipe-mode', title: 'Swipe Mode' }
|
|
275
|
-
]
|
|
276
|
-
|
|
277
|
-
const activeSection = ref('basic')
|
|
278
|
-
const basicRef = ref<HTMLElement | null>(null)
|
|
279
|
-
const manualInitRef = ref<HTMLElement | null>(null)
|
|
280
|
-
const customItemRef = ref<HTMLElement | null>(null)
|
|
281
|
-
const headerFooterRef = ref<HTMLElement | null>(null)
|
|
282
|
-
const swipeModeRef = ref<HTMLElement | null>(null)
|
|
283
|
-
|
|
284
|
-
function scrollTo(id: string) {
|
|
285
|
-
const element = document.getElementById(id)
|
|
286
|
-
if (element) {
|
|
287
|
-
element.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
288
|
-
activeSection.value = id
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function updateActiveSection() {
|
|
293
|
-
const sections = [
|
|
294
|
-
{ id: 'basic', ref: basicRef },
|
|
295
|
-
{ id: 'manual-init', ref: manualInitRef },
|
|
296
|
-
{ id: 'custom-item', ref: customItemRef },
|
|
297
|
-
{ id: 'header-footer', ref: headerFooterRef },
|
|
298
|
-
{ id: 'swipe-mode', ref: swipeModeRef }
|
|
299
|
-
]
|
|
300
|
-
|
|
301
|
-
const scrollPosition = window.scrollY + 100
|
|
302
|
-
|
|
303
|
-
for (let i = sections.length - 1; i >= 0; i--) {
|
|
304
|
-
const section = sections[i]
|
|
305
|
-
if (section.ref.value) {
|
|
306
|
-
const top = section.ref.value.offsetTop
|
|
307
|
-
if (scrollPosition >= top) {
|
|
308
|
-
activeSection.value = section.id
|
|
309
|
-
break
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
onMounted(() => {
|
|
316
|
-
window.addEventListener('scroll', updateActiveSection)
|
|
317
|
-
updateActiveSection()
|
|
318
|
-
})
|
|
319
|
-
|
|
320
|
-
onUnmounted(() => {
|
|
321
|
-
window.removeEventListener('scroll', updateActiveSection)
|
|
322
|
-
})
|
|
323
|
-
</script>
|