@wyxos/vibe 1.6.13 → 1.6.14

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.
@@ -0,0 +1,78 @@
1
+ <template>
2
+ <Masonry
3
+ v-model:items="items"
4
+ :get-next-page="getPage"
5
+ :load-at-page="1"
6
+ :layout="layout"
7
+ >
8
+ <template #item-header="{ item }">
9
+ <div class="h-full flex items-center justify-between px-3">
10
+ <div class="flex items-center gap-2">
11
+ <div class="w-6 h-6 rounded-full bg-white/80 backdrop-blur-sm flex items-center justify-center shadow-sm">
12
+ <i :class="item.type === 'video' ? 'fas fa-video text-[10px] text-slate-500' : 'fas fa-image text-[10px] text-slate-500'"></i>
13
+ </div>
14
+ <span class="text-xs font-medium text-slate-700">#{{ String(item.id).split('-')[0] }}</span>
15
+ </div>
16
+ <span v-if="item.title" class="text-[11px] text-slate-600 truncate max-w-[160px]">{{ item.title }}</span>
17
+ </div>
18
+ </template>
19
+
20
+ <template #item-footer="{ item, remove }">
21
+ <div class="h-full flex items-center justify-between px-3">
22
+ <div class="flex items-center gap-2">
23
+ <button
24
+ v-if="remove"
25
+ class="px-2.5 py-1 rounded-full bg-white/90 text-slate-700 text-[11px] shadow-sm hover:bg-red-500 hover:text-white transition-colors"
26
+ @click.stop="remove(item)"
27
+ >
28
+ Remove
29
+ </button>
30
+ </div>
31
+ <div class="text-[11px] text-slate-600">
32
+ {{ item.width }}×{{ item.height }}
33
+ </div>
34
+ </div>
35
+ </template>
36
+ </Masonry>
37
+ </template>
38
+
39
+ <script setup lang="ts">
40
+ import { ref } from 'vue'
41
+ import Masonry from '../../Masonry.vue'
42
+ import fixture from '../../pages.json'
43
+ import type { MasonryItem, GetPageResult } from '../../types'
44
+
45
+ const items = ref<MasonryItem[]>([])
46
+
47
+ const layout = {
48
+ sizes: { base: 1, sm: 2, md: 3, lg: 4 },
49
+ gutterX: 10,
50
+ gutterY: 10,
51
+ header: 36,
52
+ footer: 40
53
+ }
54
+
55
+ const getPage = async (page: number): Promise<GetPageResult> => {
56
+ return new Promise((resolve) => {
57
+ setTimeout(() => {
58
+ const pageData = (fixture as any[])[page - 1] as { items: MasonryItem[] } | undefined
59
+
60
+ if (!pageData) {
61
+ resolve({
62
+ items: [],
63
+ nextPage: null
64
+ })
65
+ return
66
+ }
67
+
68
+ resolve({
69
+ items: pageData.items,
70
+ nextPage: page < (fixture as any[]).length ? page + 1 : null
71
+ })
72
+ }, 300)
73
+ })
74
+ }
75
+ </script>
76
+
77
+
78
+
@@ -1,60 +1,60 @@
1
- import { nextTick, type Ref } from 'vue'
2
- import { calculateColumnHeights } from './masonryUtils'
3
- import type { ProcessedMasonryItem } from './types'
4
-
5
- /**
6
- * Composable for handling masonry scroll behavior and item cleanup
7
- */
8
- export function useMasonryScroll({
9
- container,
10
- masonry,
11
- columns,
12
- containerHeight,
13
- isLoading,
14
- pageSize,
15
- refreshLayout,
16
- setItemsRaw,
17
- loadNext,
18
- loadThresholdPx
19
- }: {
20
- container: Ref<HTMLElement | null>
21
- masonry: Ref<ProcessedMasonryItem[]>
22
- columns: Ref<number>
23
- containerHeight: Ref<number>
24
- isLoading: Ref<boolean>
25
- pageSize: number
26
- refreshLayout: (items: ProcessedMasonryItem[]) => void
27
- setItemsRaw: (items: ProcessedMasonryItem[]) => void
28
- loadNext: () => Promise<any>
29
- loadThresholdPx?: number
30
- }) {
31
- let cleanupInProgress = false
32
- let lastScrollTop = 0
33
-
34
- async function handleScroll(precomputedHeights?: number[]) {
35
- if (!container.value) return
36
-
37
- const columnHeights = precomputedHeights ?? calculateColumnHeights(masonry.value, columns.value)
38
- const tallest = columnHeights.length ? Math.max(...columnHeights) : 0
39
- const scrollerBottom = container.value.scrollTop + container.value.clientHeight
40
-
41
- const isScrollingDown = container.value.scrollTop > lastScrollTop + 1 // tolerate tiny jitter
42
- lastScrollTop = container.value.scrollTop
43
-
44
- const threshold = typeof loadThresholdPx === 'number' ? loadThresholdPx : 200
45
- const triggerPoint = threshold >= 0
46
- ? Math.max(0, tallest - threshold)
47
- : Math.max(0, tallest + threshold)
48
- const nearBottom = scrollerBottom >= triggerPoint
49
-
50
- if (nearBottom && isScrollingDown && !isLoading.value) {
51
- await loadNext()
52
- await nextTick()
53
- return
54
- }
55
- }
56
-
57
- return {
58
- handleScroll
59
- }
60
- }
1
+ import { nextTick, type Ref } from 'vue'
2
+ import { calculateColumnHeights } from './masonryUtils'
3
+ import type { ProcessedMasonryItem } from './types'
4
+
5
+ /**
6
+ * Composable for handling masonry scroll behavior and item cleanup
7
+ */
8
+ export function useMasonryScroll({
9
+ container,
10
+ masonry,
11
+ columns,
12
+ containerHeight,
13
+ isLoading,
14
+ pageSize,
15
+ refreshLayout,
16
+ setItemsRaw,
17
+ loadNext,
18
+ loadThresholdPx
19
+ }: {
20
+ container: Ref<HTMLElement | null>
21
+ masonry: Ref<ProcessedMasonryItem[]>
22
+ columns: Ref<number>
23
+ containerHeight: Ref<number>
24
+ isLoading: Ref<boolean>
25
+ pageSize: number
26
+ refreshLayout: (items: ProcessedMasonryItem[]) => void
27
+ setItemsRaw: (items: ProcessedMasonryItem[]) => void
28
+ loadNext: () => Promise<any>
29
+ loadThresholdPx?: number
30
+ }) {
31
+ let cleanupInProgress = false
32
+ let lastScrollTop = 0
33
+
34
+ async function handleScroll(precomputedHeights?: number[]) {
35
+ if (!container.value) return
36
+
37
+ const columnHeights = precomputedHeights ?? calculateColumnHeights(masonry.value, columns.value)
38
+ const tallest = columnHeights.length ? Math.max(...columnHeights) : 0
39
+ const scrollerBottom = container.value.scrollTop + container.value.clientHeight
40
+
41
+ const isScrollingDown = container.value.scrollTop > lastScrollTop + 1 // tolerate tiny jitter
42
+ lastScrollTop = container.value.scrollTop
43
+
44
+ const threshold = typeof loadThresholdPx === 'number' ? loadThresholdPx : 200
45
+ const triggerPoint = threshold >= 0
46
+ ? Math.max(0, tallest - threshold)
47
+ : Math.max(0, tallest + threshold)
48
+ const nearBottom = scrollerBottom >= triggerPoint
49
+
50
+ if (nearBottom && isScrollingDown && !isLoading.value) {
51
+ await loadNext()
52
+ await nextTick()
53
+ return
54
+ }
55
+ }
56
+
57
+ return {
58
+ handleScroll
59
+ }
60
+ }
@@ -125,6 +125,29 @@ async function getNextPage(page) {
125
125
  </template>'
126
126
  />
127
127
  </div>
128
+ -->
129
+ </div>
130
+ </section>
131
+
132
+ <!-- Header & Footer Example -->
133
+ <section id="header-footer" ref="headerFooterRef" class="scroll-mt-24">
134
+ <div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
135
+ <div class="px-6 py-4 border-b border-slate-200 bg-slate-50">
136
+ <h2 class="text-2xl font-bold text-slate-800">Header & Footer</h2>
137
+ <p class="text-slate-600 mt-1">
138
+ Use `layout.header` and `layout.footer` with slots to add per-item UI like badges, titles and actions.
139
+ </p>
140
+ </div>
141
+
142
+ <!-- Live Example -->
143
+ <div class="p-6 bg-slate-50">
144
+ <div class="bg-white rounded-lg border border-slate-200 p-4" style="height: 500px;">
145
+ <HeaderFooterExample />
146
+ </div>
147
+ </div>
148
+
149
+ <!-- Code Tabs omitted for this example to keep docs build-safe.
150
+ See the Header & Footer example component for full code. -->
128
151
  </div>
129
152
  </section>
130
153
 
@@ -190,21 +213,25 @@ async function getNextPage(page) {
190
213
  </template>
191
214
 
192
215
  <script setup lang="ts">
216
+ // @ts-nocheck - documentation page with inline string code samples that Vue/TS can't statically analyze cleanly
193
217
  import { ref, onMounted, onUnmounted } from 'vue'
194
218
  import CodeTabs from '../components/CodeTabs.vue'
195
219
  import BasicExample from '../components/examples/BasicExample.vue'
196
220
  import CustomItemExample from '../components/examples/CustomItemExample.vue'
197
221
  import SwipeModeExample from '../components/examples/SwipeModeExample.vue'
222
+ import HeaderFooterExample from '../components/examples/HeaderFooterExample.vue'
198
223
 
199
224
  const examples = [
200
225
  { id: 'basic', title: 'Basic Usage' },
201
226
  { id: 'custom-item', title: 'Custom Masonry Item' },
227
+ { id: 'header-footer', title: 'Header & Footer' },
202
228
  { id: 'swipe-mode', title: 'Swipe Mode' }
203
229
  ]
204
230
 
205
231
  const activeSection = ref('basic')
206
232
  const basicRef = ref<HTMLElement | null>(null)
207
233
  const customItemRef = ref<HTMLElement | null>(null)
234
+ const headerFooterRef = ref<HTMLElement | null>(null)
208
235
  const swipeModeRef = ref<HTMLElement | null>(null)
209
236
 
210
237
  function scrollTo(id: string) {
@@ -219,6 +246,7 @@ function updateActiveSection() {
219
246
  const sections = [
220
247
  { id: 'basic', ref: basicRef },
221
248
  { id: 'custom-item', ref: customItemRef },
249
+ { id: 'header-footer', ref: headerFooterRef },
222
250
  { id: 'swipe-mode', ref: swipeModeRef }
223
251
  ]
224
252