@wyxos/vibe 1.6.12 → 1.6.13
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/lib/index.js +483 -501
- package/lib/vibe.css +1 -1
- package/package.json +93 -67
- package/src/Masonry.vue +3 -21
- package/src/components/CodeTabs.vue +158 -0
- package/src/components/examples/BasicExample.vue +45 -0
- package/src/components/examples/CustomItemExample.vue +86 -0
- package/src/components/examples/SwipeModeExample.vue +39 -0
- package/src/pages.json +36401 -36401
- package/src/views/Examples.vue +247 -20
- package/toggle-link.mjs +92 -92
package/lib/vibe.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.masonry-container[data-v-
|
|
1
|
+
.masonry-container[data-v-9c79a3fb]{overflow-anchor:none}.masonry-item[data-v-9c79a3fb]{will-change:transform,opacity;contain:layout paint;transition:transform var(--masonry-duration, .45s) var(--masonry-ease, cubic-bezier(.22, .61, .36, 1)),opacity var(--masonry-leave-duration, .16s) ease-out var(--masonry-opacity-delay, 0ms);backface-visibility:hidden}.masonry-move[data-v-9c79a3fb]{transition:transform var(--masonry-duration, .45s) var(--masonry-ease, cubic-bezier(.22, .61, .36, 1))}@media (prefers-reduced-motion: reduce){.masonry-container:not(.force-motion) .masonry-item[data-v-9c79a3fb],.masonry-container:not(.force-motion) .masonry-move[data-v-9c79a3fb]{transition-duration:1ms!important}}
|
package/package.json
CHANGED
|
@@ -1,67 +1,93 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@wyxos/vibe",
|
|
3
|
-
"version": "1.6.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
|
|
67
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@wyxos/vibe",
|
|
3
|
+
"version": "1.6.13",
|
|
4
|
+
"description": "A high-performance, responsive masonry layout engine for Vue 3 with built-in infinite scrolling and virtualization.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"vue",
|
|
7
|
+
"vue3",
|
|
8
|
+
"masonry",
|
|
9
|
+
"grid",
|
|
10
|
+
"layout",
|
|
11
|
+
"infinite-scroll",
|
|
12
|
+
"virtualization",
|
|
13
|
+
"responsive",
|
|
14
|
+
"performance",
|
|
15
|
+
"swipe",
|
|
16
|
+
"feed",
|
|
17
|
+
"mobile"
|
|
18
|
+
],
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/wyxos/vibe.git"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://wyxos.github.io/vibe/",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/wyxos/vibe/issues"
|
|
27
|
+
},
|
|
28
|
+
"main": "lib/index.js",
|
|
29
|
+
"module": "lib/index.js",
|
|
30
|
+
"types": "lib/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"import": "./lib/index.js",
|
|
34
|
+
"types": "./lib/index.d.ts"
|
|
35
|
+
},
|
|
36
|
+
"./vibe.css": "./lib/vibe.css",
|
|
37
|
+
"./package.json": "./package.json"
|
|
38
|
+
},
|
|
39
|
+
"bin": {
|
|
40
|
+
"vibe": "./toggle-link.mjs"
|
|
41
|
+
},
|
|
42
|
+
"type": "module",
|
|
43
|
+
"files": [
|
|
44
|
+
"lib",
|
|
45
|
+
"src"
|
|
46
|
+
],
|
|
47
|
+
"sideEffects": [
|
|
48
|
+
"./lib/vibe.css"
|
|
49
|
+
],
|
|
50
|
+
"scripts": {
|
|
51
|
+
"dev": "vite",
|
|
52
|
+
"build": "vue-tsc --noEmit && vite build && node write-cname.js",
|
|
53
|
+
"build:demo": "vue-tsc --noEmit && vite build && node write-cname.js",
|
|
54
|
+
"build:lib": "vite build --config vite.lib.config.ts && vue-tsc --declaration --emitDeclarationOnly --outDir lib",
|
|
55
|
+
"preview": "vite preview",
|
|
56
|
+
"watch": "vite build --watch",
|
|
57
|
+
"release": "node release.mjs",
|
|
58
|
+
"test": "vitest",
|
|
59
|
+
"type-check": "vue-tsc --noEmit",
|
|
60
|
+
"prepublishOnly": "npm run build:lib"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@types/highlight.js": "^9.12.4",
|
|
64
|
+
"highlight.js": "^11.11.1",
|
|
65
|
+
"lodash": "^4.17.21",
|
|
66
|
+
"lodash-es": "^4.17.21",
|
|
67
|
+
"vue": "^3.0.0",
|
|
68
|
+
"vue-router": "^4.6.3"
|
|
69
|
+
},
|
|
70
|
+
"peerDependencies": {
|
|
71
|
+
"vue": "^3.0.0"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@resvg/resvg-js": "^2.6.2",
|
|
75
|
+
"@tailwindcss/vite": "^4.0.15",
|
|
76
|
+
"@types/lodash-es": "^4.17.12",
|
|
77
|
+
"@types/node": "^24.5.2",
|
|
78
|
+
"@vitejs/plugin-vue": "^5.2.1",
|
|
79
|
+
"@vue/test-utils": "^2.4.6",
|
|
80
|
+
"chalk": "^5.3.0",
|
|
81
|
+
"inquirer": "^10.1.8",
|
|
82
|
+
"jsdom": "^26.0.0",
|
|
83
|
+
"sharp": "^0.34.5",
|
|
84
|
+
"simple-git": "^3.27.0",
|
|
85
|
+
"tailwindcss": "^4.0.15",
|
|
86
|
+
"typescript": "^5.9.2",
|
|
87
|
+
"uuid": "^11.1.0",
|
|
88
|
+
"vite": "^6.2.0",
|
|
89
|
+
"vite-plugin-vue-mcp": "^0.3.2",
|
|
90
|
+
"vitest": "^3.0.9",
|
|
91
|
+
"vue-tsc": "^3.1.0"
|
|
92
|
+
}
|
|
93
|
+
}
|
package/src/Masonry.vue
CHANGED
|
@@ -1137,20 +1137,9 @@ onUnmounted(() => {
|
|
|
1137
1137
|
</div>
|
|
1138
1138
|
</div>
|
|
1139
1139
|
|
|
1140
|
-
<!-- Swipe indicator dots -->
|
|
1141
|
-
<div v-if="masonry.length > 1" class="fixed bottom-4 left-1/2 transform -translate-x-1/2 flex gap-2 z-10 pointer-events-none">
|
|
1142
|
-
<div
|
|
1143
|
-
v-for="(item, index) in masonry.slice(0, Math.min(10, masonry.length))"
|
|
1144
|
-
:key="`dot-${item.id}`"
|
|
1145
|
-
class="w-1.5 h-1.5 rounded-full transition-all duration-300"
|
|
1146
|
-
:class="index === currentSwipeIndex ? 'bg-white w-4' : 'bg-white/40'"
|
|
1147
|
-
/>
|
|
1148
|
-
</div>
|
|
1149
1140
|
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
{{ currentSwipeIndex + 1 }} / {{ masonry.length }}
|
|
1153
|
-
</div>
|
|
1141
|
+
|
|
1142
|
+
|
|
1154
1143
|
</div>
|
|
1155
1144
|
|
|
1156
1145
|
<!-- Masonry Grid Mode (Desktop) -->
|
|
@@ -1174,14 +1163,7 @@ onUnmounted(() => {
|
|
|
1174
1163
|
</div>
|
|
1175
1164
|
</transition-group>
|
|
1176
1165
|
|
|
1177
|
-
|
|
1178
|
-
<div v-if="containerHeight > 0"
|
|
1179
|
-
class="fixed bottom-4 right-4 bg-gray-800 text-white text-xs rounded-full px-3 py-1.5 shadow-lg z-10 transition-opacity duration-300"
|
|
1180
|
-
:class="{'opacity-50 hover:opacity-100': !scrollProgress.isNearTrigger, 'opacity-100': scrollProgress.isNearTrigger}">
|
|
1181
|
-
<span>{{ masonry.length }} items</span>
|
|
1182
|
-
<span class="mx-2">|</span>
|
|
1183
|
-
<span>{{ scrollProgress.distanceToTrigger }}px to load</span>
|
|
1184
|
-
</div>
|
|
1166
|
+
|
|
1185
1167
|
</div>
|
|
1186
1168
|
</div>
|
|
1187
1169
|
</div>
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="bg-slate-900 rounded-lg overflow-hidden shadow-xl">
|
|
3
|
+
<!-- Tabs -->
|
|
4
|
+
<div class="flex items-center gap-1 bg-slate-800/50 px-4 py-2 border-b border-slate-700">
|
|
5
|
+
<button
|
|
6
|
+
v-for="tab in tabs"
|
|
7
|
+
:key="tab"
|
|
8
|
+
@click="activeTab = tab"
|
|
9
|
+
class="px-4 py-2 text-sm font-medium rounded-md transition-colors"
|
|
10
|
+
:class="activeTab === tab
|
|
11
|
+
? 'bg-slate-700 text-white'
|
|
12
|
+
: 'text-slate-400 hover:text-slate-200 hover:bg-slate-800'"
|
|
13
|
+
>
|
|
14
|
+
{{ tab.toUpperCase() }}
|
|
15
|
+
</button>
|
|
16
|
+
<div class="flex-1"></div>
|
|
17
|
+
<button
|
|
18
|
+
@click="copyCode"
|
|
19
|
+
class="px-3 py-1.5 text-xs font-medium text-slate-400 hover:text-white hover:bg-slate-700 rounded-md transition-colors flex items-center gap-2"
|
|
20
|
+
:title="copied ? 'Copied!' : 'Copy code'"
|
|
21
|
+
>
|
|
22
|
+
<i :class="copied ? 'fas fa-check' : 'fas fa-copy'"></i>
|
|
23
|
+
<span>{{ copied ? 'Copied' : 'Copy' }}</span>
|
|
24
|
+
</button>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<!-- Code Content -->
|
|
28
|
+
<div class="relative">
|
|
29
|
+
<pre class="p-4 overflow-x-auto text-sm"><code :class="`language-${activeTab} hljs`" v-html="highlightedCode"></code></pre>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<script setup lang="ts">
|
|
35
|
+
import { ref, computed, watch } from 'vue'
|
|
36
|
+
import hljs from 'highlight.js/lib/core'
|
|
37
|
+
import javascript from 'highlight.js/lib/languages/javascript'
|
|
38
|
+
import xml from 'highlight.js/lib/languages/xml'
|
|
39
|
+
import css from 'highlight.js/lib/languages/css'
|
|
40
|
+
import 'highlight.js/styles/tokyo-night-dark.css'
|
|
41
|
+
|
|
42
|
+
// Register languages
|
|
43
|
+
hljs.registerLanguage('javascript', javascript)
|
|
44
|
+
hljs.registerLanguage('js', javascript)
|
|
45
|
+
hljs.registerLanguage('xml', xml)
|
|
46
|
+
hljs.registerLanguage('html', xml)
|
|
47
|
+
hljs.registerLanguage('vue', xml) // Vue uses XML/HTML highlighting
|
|
48
|
+
hljs.registerLanguage('css', css)
|
|
49
|
+
|
|
50
|
+
const props = defineProps<{
|
|
51
|
+
html?: string
|
|
52
|
+
js?: string
|
|
53
|
+
vue?: string
|
|
54
|
+
css?: string
|
|
55
|
+
}>()
|
|
56
|
+
|
|
57
|
+
const activeTab = ref<'html' | 'js' | 'vue' | 'css'>('vue')
|
|
58
|
+
const copied = ref(false)
|
|
59
|
+
|
|
60
|
+
const tabs = computed(() => {
|
|
61
|
+
const available: ('html' | 'js' | 'vue' | 'css')[] = []
|
|
62
|
+
if (props.html) available.push('html')
|
|
63
|
+
if (props.js) available.push('js')
|
|
64
|
+
if (props.vue) available.push('vue')
|
|
65
|
+
if (props.css) available.push('css')
|
|
66
|
+
return available
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const currentCode = computed(() => {
|
|
70
|
+
switch (activeTab.value) {
|
|
71
|
+
case 'html': return props.html || ''
|
|
72
|
+
case 'js': return props.js || ''
|
|
73
|
+
case 'vue': return props.vue || ''
|
|
74
|
+
case 'css': return props.css || ''
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const highlightedCode = computed(() => {
|
|
79
|
+
if (!currentCode.value) return ''
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// Map tab names to highlight.js language names
|
|
83
|
+
const langMap: Record<string, string> = {
|
|
84
|
+
'vue': 'xml',
|
|
85
|
+
'html': 'xml',
|
|
86
|
+
'js': 'javascript',
|
|
87
|
+
'css': 'css'
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const lang = langMap[activeTab.value] || activeTab.value
|
|
91
|
+
const result = hljs.highlight(currentCode.value, { language: lang })
|
|
92
|
+
return result.value
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error('Highlighting error:', error)
|
|
95
|
+
return escapeHtml(currentCode.value)
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
function escapeHtml(text: string): string {
|
|
100
|
+
const div = document.createElement('div')
|
|
101
|
+
div.textContent = text
|
|
102
|
+
return div.innerHTML
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function copyCode() {
|
|
106
|
+
try {
|
|
107
|
+
await navigator.clipboard.writeText(currentCode.value)
|
|
108
|
+
copied.value = true
|
|
109
|
+
setTimeout(() => {
|
|
110
|
+
copied.value = false
|
|
111
|
+
}, 2000)
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.error('Failed to copy:', err)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Set initial tab to first available
|
|
118
|
+
watch(() => tabs.value, (newTabs) => {
|
|
119
|
+
if (newTabs.length > 0 && !newTabs.includes(activeTab.value)) {
|
|
120
|
+
activeTab.value = newTabs[0]
|
|
121
|
+
}
|
|
122
|
+
}, { immediate: true })
|
|
123
|
+
</script>
|
|
124
|
+
|
|
125
|
+
<style scoped>
|
|
126
|
+
code {
|
|
127
|
+
font-family: 'Fira Code', 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
128
|
+
line-height: 1.6;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
pre {
|
|
132
|
+
max-height: 500px;
|
|
133
|
+
overflow-y: auto;
|
|
134
|
+
overflow-x: auto;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
pre code {
|
|
138
|
+
display: block;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
pre::-webkit-scrollbar {
|
|
142
|
+
width: 8px;
|
|
143
|
+
height: 8px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
pre::-webkit-scrollbar-track {
|
|
147
|
+
background: #1e293b;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
pre::-webkit-scrollbar-thumb {
|
|
151
|
+
background: #475569;
|
|
152
|
+
border-radius: 4px;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
pre::-webkit-scrollbar-thumb:hover {
|
|
156
|
+
background: #64748b;
|
|
157
|
+
}
|
|
158
|
+
</style>
|
|
@@ -0,0 +1,45 @@
|
|
|
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>
|
|
9
|
+
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
import { ref } from 'vue'
|
|
12
|
+
import Masonry from '../../Masonry.vue'
|
|
13
|
+
import fixture from '../../pages.json'
|
|
14
|
+
import type { MasonryItem, GetPageResult } from '../../types'
|
|
15
|
+
|
|
16
|
+
const items = ref<MasonryItem[]>([])
|
|
17
|
+
|
|
18
|
+
const layout = {
|
|
19
|
+
sizes: { base: 1, sm: 2, md: 3, lg: 4 },
|
|
20
|
+
gutterX: 10,
|
|
21
|
+
gutterY: 10
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const getPage = async (page: number): Promise<GetPageResult> => {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
setTimeout(() => {
|
|
27
|
+
const pageData = (fixture as any[])[page - 1] as { items: MasonryItem[] } | undefined
|
|
28
|
+
|
|
29
|
+
if (!pageData) {
|
|
30
|
+
resolve({
|
|
31
|
+
items: [],
|
|
32
|
+
nextPage: null
|
|
33
|
+
})
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
resolve({
|
|
38
|
+
items: pageData.items,
|
|
39
|
+
nextPage: page < (fixture as any[]).length ? page + 1 : null
|
|
40
|
+
})
|
|
41
|
+
}, 300)
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
</script>
|
|
45
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
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="{ item, remove }">
|
|
9
|
+
<div class="custom-card">
|
|
10
|
+
<img v-if="item.src" :src="item.src" :alt="item.title || 'Item'" />
|
|
11
|
+
<div class="overlay">
|
|
12
|
+
<h3 class="text-white font-semibold text-sm mb-2">{{ item.title || 'Untitled' }}</h3>
|
|
13
|
+
<button
|
|
14
|
+
@click="remove"
|
|
15
|
+
class="px-3 py-1 bg-white/20 hover:bg-white/30 border border-white/30 rounded text-white text-xs transition-colors"
|
|
16
|
+
>
|
|
17
|
+
Remove
|
|
18
|
+
</button>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
22
|
+
</Masonry>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script setup lang="ts">
|
|
26
|
+
import { ref } from 'vue'
|
|
27
|
+
import Masonry from '../../Masonry.vue'
|
|
28
|
+
import fixture from '../../pages.json'
|
|
29
|
+
import type { MasonryItem, GetPageResult } from '../../types'
|
|
30
|
+
|
|
31
|
+
const items = ref<MasonryItem[]>([])
|
|
32
|
+
|
|
33
|
+
const layout = {
|
|
34
|
+
sizes: { base: 1, sm: 2, md: 3, lg: 4 },
|
|
35
|
+
gutterX: 10,
|
|
36
|
+
gutterY: 10
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const getPage = async (page: number): Promise<GetPageResult> => {
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
const pageData = (fixture as any[])[page - 1] as { items: MasonryItem[] } | undefined
|
|
43
|
+
|
|
44
|
+
if (!pageData) {
|
|
45
|
+
resolve({
|
|
46
|
+
items: [],
|
|
47
|
+
nextPage: null
|
|
48
|
+
})
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
resolve({
|
|
53
|
+
items: pageData.items,
|
|
54
|
+
nextPage: page < (fixture as any[]).length ? page + 1 : null
|
|
55
|
+
})
|
|
56
|
+
}, 300)
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<style scoped>
|
|
62
|
+
.custom-card {
|
|
63
|
+
position: relative;
|
|
64
|
+
width: 100%;
|
|
65
|
+
height: 100%;
|
|
66
|
+
border-radius: 8px;
|
|
67
|
+
overflow: hidden;
|
|
68
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.custom-card img {
|
|
72
|
+
width: 100%;
|
|
73
|
+
height: 100%;
|
|
74
|
+
object-fit: cover;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.custom-card .overlay {
|
|
78
|
+
position: absolute;
|
|
79
|
+
bottom: 0;
|
|
80
|
+
left: 0;
|
|
81
|
+
right: 0;
|
|
82
|
+
background: linear-gradient(to top, rgba(0,0,0,0.8), transparent);
|
|
83
|
+
padding: 16px;
|
|
84
|
+
}
|
|
85
|
+
</style>
|
|
86
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Masonry
|
|
3
|
+
v-model:items="items"
|
|
4
|
+
:get-next-page="getPage"
|
|
5
|
+
:load-at-page="1"
|
|
6
|
+
layout-mode="swipe"
|
|
7
|
+
/>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
import { ref } from 'vue'
|
|
12
|
+
import Masonry from '../../Masonry.vue'
|
|
13
|
+
import fixture from '../../pages.json'
|
|
14
|
+
import type { MasonryItem, GetPageResult } from '../../types'
|
|
15
|
+
|
|
16
|
+
const items = ref<MasonryItem[]>([])
|
|
17
|
+
|
|
18
|
+
const getPage = async (page: number): Promise<GetPageResult> => {
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
setTimeout(() => {
|
|
21
|
+
const pageData = (fixture as any[])[page - 1] as { items: MasonryItem[] } | undefined
|
|
22
|
+
|
|
23
|
+
if (!pageData) {
|
|
24
|
+
resolve({
|
|
25
|
+
items: [],
|
|
26
|
+
nextPage: null
|
|
27
|
+
})
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
resolve({
|
|
32
|
+
items: pageData.items,
|
|
33
|
+
nextPage: page < (fixture as any[]).length ? page + 1 : null
|
|
34
|
+
})
|
|
35
|
+
}, 300)
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
</script>
|
|
39
|
+
|