@xenterprises/nuxt-x-marketing 1.0.0 → 1.0.1
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/.playground/pages/blog/detail.vue +341 -249
- package/.playground/pages/notifications/social-proof-toast.vue +330 -70
- package/app/assets/css/x-marketing.css +1 -0
- package/app/components/X/Mark/Blog/Detail.vue +169 -222
- package/app/components/X/Mark/CodeBlock/index.vue +69 -0
- package/app/components/X/Mark/SocialProof/Toast.vue +61 -16
- package/nuxt.config.ts +12 -3
- package/package.json +4 -2
- package/.claude/settings.local.json +0 -22
- package/.playground/package-lock.json +0 -6
|
@@ -1,240 +1,187 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
2
|
+
<article class="blog-detail">
|
|
3
|
+
<!-- Hero -->
|
|
4
|
+
<header class="mb-16 lg:mb-20">
|
|
5
|
+
<div class="max-w-3xl mx-auto text-center mb-10">
|
|
6
|
+
<UBadge
|
|
7
|
+
v-if="post.category"
|
|
8
|
+
:label="post.category"
|
|
9
|
+
color="primary"
|
|
10
|
+
variant="subtle"
|
|
11
|
+
class="mb-6"
|
|
12
|
+
/>
|
|
13
|
+
<h1
|
|
14
|
+
class="text-3xl sm:text-4xl lg:text-5xl font-bold leading-tight tracking-tight text-neutral-900 dark:text-white"
|
|
15
|
+
>
|
|
16
|
+
{{ post.title }}
|
|
17
|
+
</h1>
|
|
18
|
+
<p
|
|
19
|
+
v-if="post.description"
|
|
20
|
+
class="mt-6 text-lg sm:text-xl text-neutral-600 dark:text-neutral-400 leading-relaxed max-w-2xl mx-auto"
|
|
21
|
+
>
|
|
22
|
+
{{ post.description }}
|
|
23
|
+
</p>
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
25
|
+
<!-- Meta -->
|
|
26
|
+
<div
|
|
27
|
+
class="mt-8 flex items-center justify-center gap-3 sm:gap-4 flex-wrap text-sm"
|
|
28
|
+
>
|
|
29
|
+
<!-- Author -->
|
|
30
|
+
<div v-if="post.author" class="flex items-center gap-2.5">
|
|
31
|
+
<img
|
|
32
|
+
v-if="post.author.avatar"
|
|
33
|
+
:src="post.author.avatar"
|
|
34
|
+
:alt="post.author.name"
|
|
35
|
+
class="w-10 h-10 rounded-full object-cover ring-2 ring-white dark:ring-neutral-800"
|
|
36
|
+
/>
|
|
37
|
+
<span class="font-medium text-neutral-900 dark:text-white">
|
|
38
|
+
{{ post.author.name }}
|
|
39
|
+
</span>
|
|
40
|
+
</div>
|
|
41
|
+
<span class="text-neutral-300 dark:text-neutral-600">•</span>
|
|
42
|
+
<time
|
|
43
|
+
class="text-neutral-500 dark:text-neutral-400"
|
|
44
|
+
:datetime="post.date"
|
|
45
|
+
>
|
|
46
|
+
{{ formattedDate }}
|
|
47
|
+
</time>
|
|
48
|
+
<span class="text-neutral-300 dark:text-neutral-600">•</span>
|
|
49
|
+
<span class="text-neutral-500 dark:text-neutral-400">{{
|
|
50
|
+
post.readingTime
|
|
51
|
+
}}</span>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
43
54
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
<!-- Featured Image -->
|
|
56
|
+
<figure
|
|
57
|
+
v-if="post.img?.src"
|
|
58
|
+
class="aspect-2/1 sm:aspect-21/9 rounded-2xl lg:rounded-3xl overflow-hidden shadow-xl"
|
|
59
|
+
>
|
|
60
|
+
<img
|
|
61
|
+
:src="post.img.src"
|
|
62
|
+
:alt="post.img.alt || post.title"
|
|
63
|
+
class="w-full h-full object-cover"
|
|
64
|
+
/>
|
|
65
|
+
</figure>
|
|
66
|
+
</header>
|
|
56
67
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
<!-- Content Grid -->
|
|
69
|
+
<div class="grid lg:grid-cols-[1fr_300px] gap-12 lg:gap-16">
|
|
70
|
+
<!-- Main Content -->
|
|
71
|
+
<div class="min-w-0">
|
|
72
|
+
<!-- Prose Content -->
|
|
73
|
+
<div
|
|
74
|
+
class="prose prose-lg prose-neutral dark:prose-invert max-w-none prose-headings:scroll-mt-20 prose-a:text-primary-600 dark:prose-a:text-primary-400 prose-a:no-underline hover:prose-a:underline prose-pre:bg-neutral-900 dark:prose-pre:bg-neutral-950 prose-pre:border prose-pre:border-neutral-800 prose-img:rounded-xl"
|
|
75
|
+
>
|
|
76
|
+
<slot />
|
|
77
|
+
</div>
|
|
65
78
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
<!-- Tags -->
|
|
80
|
+
<div
|
|
81
|
+
v-if="post.tags?.length"
|
|
82
|
+
class="mt-16 pt-10 border-t border-neutral-200 dark:border-neutral-800"
|
|
83
|
+
>
|
|
84
|
+
<h4
|
|
85
|
+
class="text-xs font-semibold uppercase tracking-wider text-neutral-500 mb-4"
|
|
86
|
+
>
|
|
87
|
+
Tags
|
|
88
|
+
</h4>
|
|
89
|
+
<div class="flex flex-wrap gap-2">
|
|
90
|
+
<UBadge
|
|
91
|
+
v-for="tag in post.tags"
|
|
92
|
+
:key="tag"
|
|
93
|
+
:label="tag"
|
|
94
|
+
variant="subtle"
|
|
95
|
+
color="neutral"
|
|
96
|
+
size="md"
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
77
100
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
:src="post.author.avatar"
|
|
86
|
-
:alt="post.author.name"
|
|
87
|
-
class="w-20 h-20 rounded-full object-cover flex-shrink-0"
|
|
88
|
-
/>
|
|
89
|
-
<div>
|
|
90
|
-
<p class="font-semibold text-lg text-neutral-900 dark:text-white">
|
|
91
|
-
{{ post.author.name }}
|
|
92
|
-
</p>
|
|
93
|
-
<p v-if="post.author.title" class="text-sm text-neutral-500 mb-3">
|
|
94
|
-
{{ post.author.title }}
|
|
95
|
-
</p>
|
|
96
|
-
<p class="text-neutral-600 dark:text-neutral-400">
|
|
97
|
-
{{ post.author.bio }}
|
|
98
|
-
</p>
|
|
99
|
-
<div v-if="post.author.social" class="flex gap-2 mt-4">
|
|
100
|
-
<UButton
|
|
101
|
-
v-for="(url, platform) in post.author.social"
|
|
102
|
-
:key="platform"
|
|
103
|
-
:to="url"
|
|
104
|
-
:icon="getSocialIcon(platform)"
|
|
105
|
-
variant="ghost"
|
|
106
|
-
color="neutral"
|
|
107
|
-
size="sm"
|
|
108
|
-
target="_blank"
|
|
109
|
-
:aria-label="platform"
|
|
110
|
-
/>
|
|
111
|
-
</div>
|
|
112
|
-
</div>
|
|
113
|
-
</div>
|
|
101
|
+
<!-- Author Bio -->
|
|
102
|
+
<XMarkBlogAuthor
|
|
103
|
+
v-if="post.author"
|
|
104
|
+
:author="post.author"
|
|
105
|
+
variant="full"
|
|
106
|
+
class="mt-12"
|
|
107
|
+
/>
|
|
114
108
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
:to="`/blog/${previousPost.slug}`"
|
|
124
|
-
class="group flex items-center gap-4 p-4 rounded-xl hover:bg-neutral-100 dark:hover:bg-neutral-900 transition-colors"
|
|
125
|
-
>
|
|
126
|
-
<UIcon
|
|
127
|
-
name="i-lucide-arrow-left"
|
|
128
|
-
class="w-5 h-5 text-neutral-400 group-hover:text-primary-500 transition-colors"
|
|
129
|
-
/>
|
|
130
|
-
<div>
|
|
131
|
-
<span class="text-xs text-neutral-500 uppercase tracking-wide">Previous</span>
|
|
132
|
-
<p class="font-medium text-neutral-900 dark:text-white group-hover:text-primary-500 transition-colors line-clamp-1">
|
|
133
|
-
{{ previousPost.title }}
|
|
134
|
-
</p>
|
|
135
|
-
</div>
|
|
136
|
-
</NuxtLink>
|
|
137
|
-
<div v-else />
|
|
109
|
+
<!-- Navigation -->
|
|
110
|
+
<XMarkBlogNavigation
|
|
111
|
+
v-if="previousPost || nextPost"
|
|
112
|
+
:previous="previousPost"
|
|
113
|
+
:next="nextPost"
|
|
114
|
+
class="mt-12"
|
|
115
|
+
/>
|
|
116
|
+
</div>
|
|
138
117
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
118
|
+
<!-- Sidebar -->
|
|
119
|
+
<aside class="hidden lg:block">
|
|
120
|
+
<div class="sticky top-24 space-y-8">
|
|
121
|
+
<!-- Table of Contents -->
|
|
122
|
+
<div
|
|
123
|
+
v-if="toc?.length"
|
|
124
|
+
class="p-5 rounded-xl bg-neutral-50 dark:bg-neutral-900/50 border border-neutral-200/60 dark:border-neutral-800"
|
|
125
|
+
>
|
|
126
|
+
<h4
|
|
127
|
+
class="font-semibold text-neutral-900 dark:text-white mb-4 text-sm"
|
|
128
|
+
>
|
|
129
|
+
On this page
|
|
130
|
+
</h4>
|
|
131
|
+
<nav class="space-y-1">
|
|
132
|
+
<a
|
|
133
|
+
v-for="item in toc"
|
|
134
|
+
:key="item.id"
|
|
135
|
+
:href="`#${item.id}`"
|
|
136
|
+
class="block py-1.5 text-sm text-neutral-600 dark:text-neutral-400 hover:text-primary-600 dark:hover:text-primary-400 transition-colors leading-snug"
|
|
137
|
+
:class="{ 'pl-4 text-[13px]': item.depth > 2 }"
|
|
138
|
+
>
|
|
139
|
+
{{ item.text }}
|
|
140
|
+
</a>
|
|
141
|
+
</nav>
|
|
142
|
+
</div>
|
|
158
143
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
:class="{ 'pl-4': item.depth > 2 }"
|
|
172
|
-
>
|
|
173
|
-
{{ item.text }}
|
|
174
|
-
</a>
|
|
175
|
-
</nav>
|
|
176
|
-
</div>
|
|
177
|
-
|
|
178
|
-
<!-- Newsletter CTA -->
|
|
179
|
-
<slot name="sidebar">
|
|
180
|
-
<XMarkNewsletterForm
|
|
181
|
-
variant="compact"
|
|
182
|
-
title="Subscribe"
|
|
183
|
-
description="Get notified about new posts."
|
|
184
|
-
/>
|
|
185
|
-
</slot>
|
|
186
|
-
</div>
|
|
187
|
-
</aside>
|
|
188
|
-
</div>
|
|
189
|
-
</article>
|
|
144
|
+
<!-- Newsletter CTA -->
|
|
145
|
+
<slot name="sidebar">
|
|
146
|
+
<XMarkNewsletterForm
|
|
147
|
+
variant="compact"
|
|
148
|
+
title="Subscribe to our newsletter"
|
|
149
|
+
description="Get the latest posts and insights directly to your inbox."
|
|
150
|
+
/>
|
|
151
|
+
</slot>
|
|
152
|
+
</div>
|
|
153
|
+
</aside>
|
|
154
|
+
</div>
|
|
155
|
+
</article>
|
|
190
156
|
</template>
|
|
191
157
|
|
|
192
158
|
<script setup>
|
|
193
159
|
const props = defineProps({
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
toc: {
|
|
211
|
-
type: Array,
|
|
212
|
-
default: () => [],
|
|
213
|
-
},
|
|
160
|
+
post: {
|
|
161
|
+
type: Object,
|
|
162
|
+
required: true,
|
|
163
|
+
},
|
|
164
|
+
previousPost: {
|
|
165
|
+
type: Object,
|
|
166
|
+
default: null,
|
|
167
|
+
},
|
|
168
|
+
nextPost: {
|
|
169
|
+
type: Object,
|
|
170
|
+
default: null,
|
|
171
|
+
},
|
|
172
|
+
toc: {
|
|
173
|
+
type: Array,
|
|
174
|
+
default: () => [],
|
|
175
|
+
},
|
|
214
176
|
});
|
|
215
177
|
|
|
216
178
|
const formattedDate = computed(() => {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
179
|
+
if (!props.post.date) return "";
|
|
180
|
+
const date = new Date(props.post.date);
|
|
181
|
+
return date.toLocaleDateString("en-US", {
|
|
182
|
+
year: "numeric",
|
|
183
|
+
month: "long",
|
|
184
|
+
day: "numeric",
|
|
185
|
+
});
|
|
224
186
|
});
|
|
225
|
-
|
|
226
|
-
const getSocialIcon = (platform) => {
|
|
227
|
-
const icons = {
|
|
228
|
-
twitter: 'i-lucide-twitter',
|
|
229
|
-
x: 'i-lucide-twitter',
|
|
230
|
-
linkedin: 'i-lucide-linkedin',
|
|
231
|
-
github: 'i-lucide-github',
|
|
232
|
-
website: 'i-lucide-globe',
|
|
233
|
-
instagram: 'i-lucide-instagram',
|
|
234
|
-
facebook: 'i-lucide-facebook',
|
|
235
|
-
youtube: 'i-lucide-youtube',
|
|
236
|
-
email: 'i-lucide-mail',
|
|
237
|
-
};
|
|
238
|
-
return icons[platform.toLowerCase()] || 'i-lucide-link';
|
|
239
|
-
};
|
|
240
187
|
</script>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="group relative my-6">
|
|
3
|
+
<!-- Language badge -->
|
|
4
|
+
<div
|
|
5
|
+
v-if="language"
|
|
6
|
+
class="absolute top-3 right-3 z-10 px-2 py-1 text-xs font-medium uppercase tracking-wider text-neutral-400 bg-neutral-800/80 rounded"
|
|
7
|
+
>
|
|
8
|
+
{{ language }}
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<!-- Copy button -->
|
|
12
|
+
<UButton
|
|
13
|
+
v-if="showCopy"
|
|
14
|
+
:icon="copied ? 'i-lucide-check' : 'i-lucide-copy'"
|
|
15
|
+
:color="copied ? 'success' : 'neutral'"
|
|
16
|
+
variant="ghost"
|
|
17
|
+
size="xs"
|
|
18
|
+
class="absolute top-3 z-10"
|
|
19
|
+
:class="language ? 'right-28' : 'right-3'"
|
|
20
|
+
@click="copyCode"
|
|
21
|
+
:aria-label="copied ? 'Copied!' : 'Copy code'"
|
|
22
|
+
/>
|
|
23
|
+
|
|
24
|
+
<!-- Shiki Highlighter - wrapped in ClientOnly for SSR compatibility -->
|
|
25
|
+
<ClientOnly>
|
|
26
|
+
<Shiki :code="code" :lang="(language as any)" />
|
|
27
|
+
<template #fallback>
|
|
28
|
+
<pre class="shiki-fallback"><code>{{ code }}</code></pre>
|
|
29
|
+
</template>
|
|
30
|
+
</ClientOnly>
|
|
31
|
+
</div>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<script setup lang="ts">
|
|
35
|
+
const props = defineProps<{
|
|
36
|
+
code: string;
|
|
37
|
+
language?: string;
|
|
38
|
+
showCopy?: boolean;
|
|
39
|
+
}>();
|
|
40
|
+
|
|
41
|
+
const copied = ref(false);
|
|
42
|
+
|
|
43
|
+
const copyCode = async () => {
|
|
44
|
+
try {
|
|
45
|
+
await navigator.clipboard.writeText(props.code);
|
|
46
|
+
copied.value = true;
|
|
47
|
+
setTimeout(() => {
|
|
48
|
+
copied.value = false;
|
|
49
|
+
}, 2000);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.error("Failed to copy:", err);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<style scoped>
|
|
57
|
+
/* Fallback styling while Shiki loads */
|
|
58
|
+
.shiki-fallback {
|
|
59
|
+
margin: 0;
|
|
60
|
+
padding: 1.25rem 1.5rem;
|
|
61
|
+
border-radius: 0.75rem;
|
|
62
|
+
overflow-x: auto;
|
|
63
|
+
background-color: #0d1117;
|
|
64
|
+
color: #c9d1d9;
|
|
65
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
66
|
+
font-size: 0.875rem;
|
|
67
|
+
line-height: 1.7;
|
|
68
|
+
}
|
|
69
|
+
</style>
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
class="fixed z-40"
|
|
11
11
|
:class="placementClasses"
|
|
12
12
|
>
|
|
13
|
-
<div
|
|
13
|
+
<div
|
|
14
|
+
class="bg-white dark:bg-gray-900 rounded-xl shadow-lg border border-gray-200 dark:border-gray-800 p-4 flex items-center gap-3"
|
|
15
|
+
>
|
|
14
16
|
<!-- Avatar -->
|
|
15
17
|
<img
|
|
16
18
|
v-if="currentNotification.avatar"
|
|
@@ -38,10 +40,11 @@
|
|
|
38
40
|
</p>
|
|
39
41
|
</div>
|
|
40
42
|
|
|
41
|
-
<!--
|
|
43
|
+
<!-- Status icon -->
|
|
42
44
|
<UIcon
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
v-if="showIcon && iconName"
|
|
46
|
+
:name="iconName"
|
|
47
|
+
:class="iconClasses"
|
|
45
48
|
/>
|
|
46
49
|
</div>
|
|
47
50
|
</aside>
|
|
@@ -74,33 +77,75 @@ const props = defineProps({
|
|
|
74
77
|
/** Toast placement */
|
|
75
78
|
placement: {
|
|
76
79
|
type: String,
|
|
77
|
-
default:
|
|
78
|
-
validator: (v) =>
|
|
80
|
+
default: "topRight",
|
|
81
|
+
validator: (v) =>
|
|
82
|
+
["topLeft", "topRight", "bottomLeft", "bottomRight"].includes(v),
|
|
83
|
+
},
|
|
84
|
+
/** Toast type - determines icon and color */
|
|
85
|
+
type: {
|
|
86
|
+
type: String,
|
|
87
|
+
default: "success",
|
|
88
|
+
validator: (v) => ["success", "error", "warning", "info"].includes(v),
|
|
89
|
+
},
|
|
90
|
+
/** Whether to show the status icon */
|
|
91
|
+
showIcon: {
|
|
92
|
+
type: Boolean,
|
|
93
|
+
default: true,
|
|
79
94
|
},
|
|
80
95
|
});
|
|
81
96
|
|
|
82
97
|
const placementClasses = computed(() => {
|
|
83
98
|
const placements = {
|
|
84
|
-
topLeft:
|
|
85
|
-
topRight:
|
|
86
|
-
bottomLeft:
|
|
87
|
-
bottomRight:
|
|
99
|
+
topLeft: "top-6 left-6",
|
|
100
|
+
topRight: "top-6 right-6",
|
|
101
|
+
bottomLeft: "bottom-6 left-6",
|
|
102
|
+
bottomRight: "bottom-6 right-6",
|
|
88
103
|
};
|
|
89
104
|
return placements[props.placement];
|
|
90
105
|
});
|
|
91
106
|
|
|
107
|
+
const iconConfig = {
|
|
108
|
+
success: {
|
|
109
|
+
name: "i-lucide-badge-check",
|
|
110
|
+
color: "text-green-500",
|
|
111
|
+
},
|
|
112
|
+
error: {
|
|
113
|
+
name: "i-lucide-x-circle",
|
|
114
|
+
color: "text-red-500",
|
|
115
|
+
},
|
|
116
|
+
warning: {
|
|
117
|
+
name: "i-lucide-alert-triangle",
|
|
118
|
+
color: "text-yellow-500",
|
|
119
|
+
},
|
|
120
|
+
info: {
|
|
121
|
+
name: "i-lucide-info",
|
|
122
|
+
color: "text-blue-500",
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const iconName = computed(() => {
|
|
127
|
+
if (!props.showIcon) return null;
|
|
128
|
+
return iconConfig[props.type]?.name || null;
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const iconClasses = computed(() => {
|
|
132
|
+
const baseClasses = "w-5 h-5 flex-shrink-0";
|
|
133
|
+
const colorClass = iconConfig[props.type]?.color || "text-gray-500";
|
|
134
|
+
return `${baseClasses} ${colorClass}`;
|
|
135
|
+
});
|
|
136
|
+
|
|
92
137
|
const enterFromClass = computed(() => {
|
|
93
|
-
if (props.placement.includes(
|
|
94
|
-
return
|
|
138
|
+
if (props.placement.includes("Left")) {
|
|
139
|
+
return "-translate-x-full opacity-0";
|
|
95
140
|
}
|
|
96
|
-
return
|
|
141
|
+
return "translate-x-full opacity-0";
|
|
97
142
|
});
|
|
98
143
|
|
|
99
144
|
const leaveToClass = computed(() => {
|
|
100
|
-
if (props.placement.includes(
|
|
101
|
-
return
|
|
145
|
+
if (props.placement.includes("Left")) {
|
|
146
|
+
return "-translate-x-full opacity-0";
|
|
102
147
|
}
|
|
103
|
-
return
|
|
148
|
+
return "translate-x-full opacity-0";
|
|
104
149
|
});
|
|
105
150
|
|
|
106
151
|
const currentNotification = ref(null);
|
package/nuxt.config.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { resolve } from "path";
|
|
3
3
|
|
|
4
4
|
export default defineNuxtConfig({
|
|
5
|
-
compatibilityDate: "2026-01-04",
|
|
6
5
|
ssr: true,
|
|
7
6
|
devtools: { enabled: process.env.NODE_ENV === "development" },
|
|
8
7
|
runtimeConfig: {
|
|
@@ -10,6 +9,16 @@ export default defineNuxtConfig({
|
|
|
10
9
|
|
|
11
10
|
},
|
|
12
11
|
},
|
|
13
|
-
modules: ["@nuxt/ui"],
|
|
12
|
+
modules: ["@nuxt/ui", "nuxt-shiki"],
|
|
13
|
+
shiki: {
|
|
14
|
+
bundledThemes: ["github-dark-default", "github-light-default"],
|
|
15
|
+
bundledLangs: ["javascript", "typescript", "vue", "html", "css", "json", "yaml", "sql", "markdown", "bash", "shellscript"],
|
|
16
|
+
defaultTheme: "github-dark-default",
|
|
17
|
+
langAlias: {
|
|
18
|
+
shell: "bash",
|
|
19
|
+
sh: "bash",
|
|
20
|
+
zsh: "bash",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
14
23
|
css: [resolve(__dirname, "app/assets/css/x-marketing.css")],
|
|
15
|
-
});
|
|
24
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xenterprises/nuxt-x-marketing",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.1",
|
|
5
5
|
"main": "./nuxt.config.ts",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "nuxi dev .playground",
|
|
@@ -23,11 +23,13 @@
|
|
|
23
23
|
"@axe-core/playwright": "^4.8.4",
|
|
24
24
|
"@iconify-json/lucide": "^1.2.82",
|
|
25
25
|
"@playwright/test": "^1.41.1",
|
|
26
|
+
"@tailwindcss/typography": "^0.5.19",
|
|
26
27
|
"@vitejs/plugin-vue": "^5.0.3",
|
|
27
28
|
"@vitest/coverage-v8": "^2.1.8",
|
|
28
29
|
"@vue/test-utils": "^2.4.4",
|
|
29
30
|
"happy-dom": "^13.3.8",
|
|
30
31
|
"nuxt": "^4.2.1",
|
|
32
|
+
"nuxt-shiki": "^0.3.2",
|
|
31
33
|
"typescript": "^5.6.3",
|
|
32
34
|
"vitest": "^2.1.8",
|
|
33
35
|
"vue": "latest"
|
|
@@ -37,4 +39,4 @@
|
|
|
37
39
|
"@nuxt/ui": "^4.3.0",
|
|
38
40
|
"zod": "^3.23.8"
|
|
39
41
|
}
|
|
40
|
-
}
|
|
42
|
+
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(curl:*)",
|
|
5
|
-
"Bash(npm run dev:*)",
|
|
6
|
-
"Bash(cat:*)",
|
|
7
|
-
"Bash(npm install:*)",
|
|
8
|
-
"Bash(find:*)",
|
|
9
|
-
"Bash(ls:*)",
|
|
10
|
-
"Skill(frontend-design)",
|
|
11
|
-
"Bash(git log:*)",
|
|
12
|
-
"Bash(test:*)",
|
|
13
|
-
"Bash(timeout 300 npx playwright test:*)",
|
|
14
|
-
"Bash(npx playwright test:*)",
|
|
15
|
-
"Bash(npx playwright install:*)",
|
|
16
|
-
"Bash({} ;)",
|
|
17
|
-
"mcp__plugin_context7_context7__resolve-library-id",
|
|
18
|
-
"mcp__plugin_context7_context7__query-docs",
|
|
19
|
-
"Bash(git reset:*)"
|
|
20
|
-
]
|
|
21
|
-
}
|
|
22
|
-
}
|