@wyxos/vibe 1.6.11 → 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.
@@ -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
+
@@ -1,10 +1,10 @@
1
1
  import type { LayoutOptions, ProcessedMasonryItem } from './types'
2
2
 
3
3
  /**
4
- * Get responsive column count based on window width and layout sizes
4
+ * Get responsive column count based on container width and layout sizes
5
5
  */
6
- export function getColumnCount(layout: Pick<LayoutOptions, 'sizes'> & { sizes: Required<NonNullable<LayoutOptions['sizes']>> }): number {
7
- const width = window.innerWidth
6
+ export function getColumnCount(layout: Pick<LayoutOptions, 'sizes'> & { sizes: Required<NonNullable<LayoutOptions['sizes']>> }, containerWidth?: number): number {
7
+ const width = containerWidth ?? (typeof window !== 'undefined' ? window.innerWidth : 1024)
8
8
  const sizes = layout.sizes
9
9
 
10
10
  if (width >= 1536 && sizes['2xl']) return sizes['2xl']