@fy-/fws-vue-core 3.0.3 → 3.0.5

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.
Files changed (121) hide show
  1. package/package.json +6 -8
  2. package/src/components/fws/CmsArticleBoxed.vue +247 -0
  3. package/src/components/fws/CmsArticleSingle.vue +201 -0
  4. package/src/components/fws/DataTable.vue +659 -0
  5. package/src/components/fws/FilterData.vue +423 -0
  6. package/src/components/fws/UserData.vue +220 -0
  7. package/src/components/fws/UserFlow.vue +955 -0
  8. package/src/components/fws/UserOAuth2.vue +521 -0
  9. package/src/components/fws/UserProfile.vue +615 -0
  10. package/src/components/fws/UserProfileStrict.vue +233 -0
  11. package/src/components/ssr/ClientOnly.ts +10 -0
  12. package/src/components/ui/DefaultBreadcrumb.vue +99 -0
  13. package/src/components/ui/DefaultConfirm.vue +178 -0
  14. package/src/components/ui/DefaultConfirmWithInput.vue +217 -0
  15. package/src/components/ui/DefaultDropdown.vue +104 -0
  16. package/src/components/ui/DefaultDropdownLink.vue +94 -0
  17. package/src/components/ui/DefaultGallery.vue +1056 -0
  18. package/src/components/ui/DefaultInput.vue +768 -0
  19. package/src/components/ui/DefaultLoader.vue +125 -0
  20. package/src/components/ui/DefaultModal.vue +350 -0
  21. package/src/components/ui/DefaultNotif.vue +332 -0
  22. package/src/components/ui/DefaultPaging.vue +395 -0
  23. package/src/components/ui/DefaultSidebar.vue +267 -0
  24. package/src/components/ui/DefaultTagInput.vue +415 -0
  25. package/src/components/ui/transitions/CollapseTransition.vue +19 -0
  26. package/src/components/ui/transitions/ExpandTransition.vue +19 -0
  27. package/src/components/ui/transitions/FadeTransition.vue +17 -0
  28. package/src/components/ui/transitions/ScaleTransition.vue +21 -0
  29. package/src/components/ui/transitions/SlideTransition.vue +32 -0
  30. package/src/composables/event-bus.ts +15 -0
  31. package/src/composables/rest.ts +165 -0
  32. package/src/composables/seo.ts +142 -0
  33. package/src/composables/ssr.ts +103 -0
  34. package/src/composables/templating.ts +133 -0
  35. package/src/composables/translations.ts +45 -0
  36. package/src/env.d.ts +10 -0
  37. package/{dist/src/index.d.ts → src/index.ts} +71 -45
  38. package/src/plugin.ts +42 -0
  39. package/src/safelist.html +11 -0
  40. package/src/stores/serverRouter.ts +62 -0
  41. package/src/stores/user.ts +118 -0
  42. package/src/types.ts +58 -0
  43. package/dist/index.css +0 -2
  44. package/dist/index.js +0 -5767
  45. package/dist/src/components/fws/CmsArticleBoxed.vue.d.ts +0 -32
  46. package/dist/src/components/fws/CmsArticleBoxed.vue.d.ts.map +0 -1
  47. package/dist/src/components/fws/CmsArticleSingle.vue.d.ts +0 -29
  48. package/dist/src/components/fws/CmsArticleSingle.vue.d.ts.map +0 -1
  49. package/dist/src/components/fws/DataTable.vue.d.ts +0 -52
  50. package/dist/src/components/fws/DataTable.vue.d.ts.map +0 -1
  51. package/dist/src/components/fws/FilterData.vue.d.ts +0 -15
  52. package/dist/src/components/fws/FilterData.vue.d.ts.map +0 -1
  53. package/dist/src/components/fws/UserData.vue.d.ts +0 -8
  54. package/dist/src/components/fws/UserData.vue.d.ts.map +0 -1
  55. package/dist/src/components/fws/UserFlow.vue.d.ts +0 -116
  56. package/dist/src/components/fws/UserFlow.vue.d.ts.map +0 -1
  57. package/dist/src/components/fws/UserOAuth2.vue.d.ts +0 -17
  58. package/dist/src/components/fws/UserOAuth2.vue.d.ts.map +0 -1
  59. package/dist/src/components/fws/UserProfile.vue.d.ts +0 -40
  60. package/dist/src/components/fws/UserProfile.vue.d.ts.map +0 -1
  61. package/dist/src/components/fws/UserProfileStrict.vue.d.ts +0 -12
  62. package/dist/src/components/fws/UserProfileStrict.vue.d.ts.map +0 -1
  63. package/dist/src/components/ssr/ClientOnly.d.ts +0 -4
  64. package/dist/src/components/ssr/ClientOnly.d.ts.map +0 -1
  65. package/dist/src/components/ui/DefaultBreadcrumb.vue.d.ts +0 -11
  66. package/dist/src/components/ui/DefaultBreadcrumb.vue.d.ts.map +0 -1
  67. package/dist/src/components/ui/DefaultConfirm.vue.d.ts +0 -81
  68. package/dist/src/components/ui/DefaultConfirm.vue.d.ts.map +0 -1
  69. package/dist/src/components/ui/DefaultConfirmWithInput.vue.d.ts +0 -81
  70. package/dist/src/components/ui/DefaultConfirmWithInput.vue.d.ts.map +0 -1
  71. package/dist/src/components/ui/DefaultDropdown.vue.d.ts +0 -35
  72. package/dist/src/components/ui/DefaultDropdown.vue.d.ts.map +0 -1
  73. package/dist/src/components/ui/DefaultDropdownLink.vue.d.ts +0 -23
  74. package/dist/src/components/ui/DefaultDropdownLink.vue.d.ts.map +0 -1
  75. package/dist/src/components/ui/DefaultGallery.vue.d.ts +0 -114
  76. package/dist/src/components/ui/DefaultGallery.vue.d.ts.map +0 -1
  77. package/dist/src/components/ui/DefaultInput.vue.d.ts +0 -61
  78. package/dist/src/components/ui/DefaultInput.vue.d.ts.map +0 -1
  79. package/dist/src/components/ui/DefaultLoader.vue.d.ts +0 -12
  80. package/dist/src/components/ui/DefaultLoader.vue.d.ts.map +0 -1
  81. package/dist/src/components/ui/DefaultModal.vue.d.ts +0 -36
  82. package/dist/src/components/ui/DefaultModal.vue.d.ts.map +0 -1
  83. package/dist/src/components/ui/DefaultNotif.vue.d.ts +0 -3
  84. package/dist/src/components/ui/DefaultNotif.vue.d.ts.map +0 -1
  85. package/dist/src/components/ui/DefaultPaging.vue.d.ts +0 -13
  86. package/dist/src/components/ui/DefaultPaging.vue.d.ts.map +0 -1
  87. package/dist/src/components/ui/DefaultSidebar.vue.d.ts +0 -29
  88. package/dist/src/components/ui/DefaultSidebar.vue.d.ts.map +0 -1
  89. package/dist/src/components/ui/DefaultTagInput.vue.d.ts +0 -34
  90. package/dist/src/components/ui/DefaultTagInput.vue.d.ts.map +0 -1
  91. package/dist/src/components/ui/transitions/CollapseTransition.vue.d.ts +0 -18
  92. package/dist/src/components/ui/transitions/CollapseTransition.vue.d.ts.map +0 -1
  93. package/dist/src/components/ui/transitions/ExpandTransition.vue.d.ts +0 -18
  94. package/dist/src/components/ui/transitions/ExpandTransition.vue.d.ts.map +0 -1
  95. package/dist/src/components/ui/transitions/FadeTransition.vue.d.ts +0 -18
  96. package/dist/src/components/ui/transitions/FadeTransition.vue.d.ts.map +0 -1
  97. package/dist/src/components/ui/transitions/ScaleTransition.vue.d.ts +0 -18
  98. package/dist/src/components/ui/transitions/ScaleTransition.vue.d.ts.map +0 -1
  99. package/dist/src/components/ui/transitions/SlideTransition.vue.d.ts +0 -21
  100. package/dist/src/components/ui/transitions/SlideTransition.vue.d.ts.map +0 -1
  101. package/dist/src/composables/event-bus.d.ts +0 -8
  102. package/dist/src/composables/event-bus.d.ts.map +0 -1
  103. package/dist/src/composables/rest.d.ts +0 -24
  104. package/dist/src/composables/rest.d.ts.map +0 -1
  105. package/dist/src/composables/seo.d.ts +0 -26
  106. package/dist/src/composables/seo.d.ts.map +0 -1
  107. package/dist/src/composables/ssr.d.ts +0 -24
  108. package/dist/src/composables/ssr.d.ts.map +0 -1
  109. package/dist/src/composables/templating.d.ts +0 -7
  110. package/dist/src/composables/templating.d.ts.map +0 -1
  111. package/dist/src/composables/translations.d.ts +0 -8
  112. package/dist/src/composables/translations.d.ts.map +0 -1
  113. package/dist/src/index.d.ts.map +0 -1
  114. package/dist/src/plugin.d.ts +0 -3
  115. package/dist/src/plugin.d.ts.map +0 -1
  116. package/dist/src/stores/serverRouter.d.ts +0 -34
  117. package/dist/src/stores/serverRouter.d.ts.map +0 -1
  118. package/dist/src/stores/user.d.ts +0 -139
  119. package/dist/src/stores/user.d.ts.map +0 -1
  120. package/dist/src/types.d.ts +0 -48
  121. package/dist/src/types.d.ts.map +0 -1
@@ -0,0 +1,332 @@
1
+ <script setup lang="ts">
2
+ import type { Component } from 'vue'
3
+ import {
4
+ CheckCircleIcon,
5
+ ExclamationTriangleIcon,
6
+ InformationCircleIcon,
7
+ SparklesIcon,
8
+ XMarkIcon,
9
+ } from '@heroicons/vue/24/solid'
10
+ import { useDebounceFn, useRafFn } from '@vueuse/core'
11
+ import { computed, onMounted, onUnmounted, ref, shallowRef } from 'vue'
12
+ import { useEventBus } from '../../composables/event-bus'
13
+
14
+ interface NotifProps {
15
+ imgSrc?: string
16
+ imgIcon?: Component
17
+ title: string
18
+ content?: string
19
+ ctaText?: string
20
+ ctaLink?: string
21
+ ctaAction?: () => void
22
+ type?: 'info' | 'warning' | 'success' | 'secret'
23
+ time?: number
24
+ }
25
+
26
+ const eventBus = useEventBus()
27
+ const currentNotif = shallowRef<NotifProps | null>(null)
28
+ const progress = ref(0)
29
+ const isPaused = ref(false)
30
+
31
+ let hideTimeout: ReturnType<typeof setTimeout> | null = null
32
+ let rafStop: Function | null = null
33
+
34
+ // Accent colors per type — left stripe + icon + progress
35
+ const typeAccent = {
36
+ info: { stripe: '#3b82f6', icon: InformationCircleIcon },
37
+ success: { stripe: '#22c55e', icon: CheckCircleIcon },
38
+ warning: { stripe: '#f59e0b', icon: ExclamationTriangleIcon },
39
+ secret: { stripe: '#a855f7', icon: SparklesIcon },
40
+ }
41
+
42
+ function hideNotif() {
43
+ currentNotif.value = null
44
+ progress.value = 0
45
+ isPaused.value = false
46
+ if (hideTimeout) { clearTimeout(hideTimeout); hideTimeout = null }
47
+ if (rafStop) { rafStop(); rafStop = null }
48
+ }
49
+
50
+ const onCall = useDebounceFn((data: NotifProps) => {
51
+ hideNotif()
52
+ const type = data.type || 'info'
53
+ if (!data.imgIcon) data.imgIcon = typeAccent[type].icon
54
+ currentNotif.value = { ...data, time: data.time || 3000 }
55
+ hideTimeout = setTimeout(() => hideNotif(), currentNotif.value.time)
56
+ progress.value = 0
57
+ const startTime = performance.now()
58
+ const duration = Number(currentNotif.value.time)
59
+ const { pause } = useRafFn((timestamp) => {
60
+ if (isPaused.value) return
61
+ const elapsed = timestamp.timestamp - startTime
62
+ progress.value = Math.min(100, (elapsed / duration) * 100)
63
+ if (progress.value >= 100) hideNotif()
64
+ })
65
+ rafStop = pause
66
+ }, 50)
67
+
68
+ function pauseTimer() {
69
+ isPaused.value = true
70
+ if (hideTimeout) { clearTimeout(hideTimeout); hideTimeout = null }
71
+ }
72
+
73
+ function resumeTimer() {
74
+ if (!currentNotif.value) return
75
+ isPaused.value = false
76
+ const remainingTime = currentNotif.value.time
77
+ ? Math.max(currentNotif.value.time * (1 - progress.value / 100), 500)
78
+ : 2000
79
+ hideTimeout = setTimeout(() => hideNotif(), remainingTime)
80
+ }
81
+
82
+ const handleCtaClick = useDebounceFn(() => {
83
+ if (currentNotif.value?.ctaAction) currentNotif.value.ctaAction()
84
+ }, 300)
85
+
86
+ const accentColor = computed(() => {
87
+ if (!currentNotif.value) return '#3b82f6'
88
+ return typeAccent[currentNotif.value.type || 'info'].stripe
89
+ })
90
+
91
+ onMounted(() => { eventBus.on('SendNotif', onCall) })
92
+ onUnmounted(() => { eventBus.off('SendNotif', onCall); hideNotif() })
93
+ </script>
94
+
95
+ <template>
96
+ <Transition
97
+ enter-active-class="notif-enter-active"
98
+ enter-from-class="notif-enter-from"
99
+ leave-active-class="notif-leave-active"
100
+ leave-to-class="notif-leave-to"
101
+ >
102
+ <div
103
+ v-if="currentNotif !== null"
104
+ class="fv-notif"
105
+ role="alert"
106
+ @mouseenter="pauseTimer"
107
+ @mouseleave="resumeTimer"
108
+ >
109
+ <!-- Left accent stripe -->
110
+ <div class="fv-notif__stripe" :style="{ backgroundColor: accentColor }" />
111
+
112
+ <!-- Content -->
113
+ <div class="fv-notif__body">
114
+ <div class="fv-notif__header">
115
+ <div class="fv-notif__icon" :style="{ color: accentColor }">
116
+ <img
117
+ v-if="currentNotif.imgSrc"
118
+ class="w-4 h-4 rounded-full"
119
+ :src="currentNotif.imgSrc"
120
+ :alt="currentNotif.title"
121
+ >
122
+ <component
123
+ :is="currentNotif.imgIcon"
124
+ v-else
125
+ class="w-4 h-4"
126
+ aria-hidden="true"
127
+ />
128
+ </div>
129
+ <h3 class="fv-notif__title" v-text="currentNotif.title" />
130
+ <button
131
+ type="button"
132
+ class="fv-notif__close"
133
+ aria-label="Close notification"
134
+ @click="hideNotif"
135
+ >
136
+ <XMarkIcon class="w-3.5 h-3.5" aria-hidden="true" />
137
+ </button>
138
+ </div>
139
+
140
+ <div
141
+ v-if="currentNotif.content"
142
+ class="fv-notif__content"
143
+ v-html="currentNotif.content"
144
+ />
145
+
146
+ <div
147
+ v-if="currentNotif.ctaText || currentNotif.ctaLink || currentNotif.ctaAction"
148
+ class="fv-notif__actions"
149
+ >
150
+ <a
151
+ v-if="currentNotif.ctaLink"
152
+ :href="currentNotif.ctaLink"
153
+ class="fv-notif__cta"
154
+ :style="{ color: accentColor }"
155
+ >
156
+ {{ currentNotif.ctaText || $t("action_cta") }}
157
+ </a>
158
+ <button
159
+ v-else-if="currentNotif.ctaAction"
160
+ type="button"
161
+ class="fv-notif__cta"
162
+ :style="{ color: accentColor }"
163
+ @click="handleCtaClick"
164
+ >
165
+ {{ currentNotif.ctaText || $t("action_cta") }}
166
+ </button>
167
+ </div>
168
+ </div>
169
+
170
+ <!-- Bottom progress -->
171
+ <div class="fv-notif__progress-track">
172
+ <div
173
+ class="fv-notif__progress-bar"
174
+ :style="{ width: `${progress}%`, backgroundColor: accentColor }"
175
+ />
176
+ </div>
177
+ </div>
178
+ </Transition>
179
+ </template>
180
+
181
+ <style scoped>
182
+ /* ═══ Glass notification — Framer/Linear inspired ═══ */
183
+ .fv-notif {
184
+ position: fixed;
185
+ bottom: 1rem;
186
+ right: 1rem;
187
+ z-index: 2000;
188
+ width: min(22rem, calc(100vw - 2rem));
189
+ display: flex;
190
+ overflow: hidden;
191
+ border-radius: 0.75rem;
192
+
193
+ /* Glass effect — Framer: blur + saturate */
194
+ background: rgba(255, 255, 255, 0.85);
195
+ backdrop-filter: blur(20px) saturate(1.3);
196
+ -webkit-backdrop-filter: blur(20px) saturate(1.3);
197
+
198
+ /* Vercel shadow-as-border + elevation */
199
+ box-shadow:
200
+ rgba(0, 0, 0, 0.08) 0 0 0 1px,
201
+ rgba(0, 0, 0, 0.04) 0 4px 6px,
202
+ rgba(0, 0, 0, 0.02) 0 12px 24px;
203
+ }
204
+
205
+ /* Dark: luminance border (Linear) */
206
+ :is(.dark) .fv-notif {
207
+ background: rgba(24, 24, 27, 0.88);
208
+ backdrop-filter: blur(24px) saturate(1.2);
209
+ -webkit-backdrop-filter: blur(24px) saturate(1.2);
210
+ box-shadow: none;
211
+ border: 1px solid rgba(255, 255, 255, 0.08);
212
+ }
213
+
214
+ .fv-notif__stripe {
215
+ width: 3px;
216
+ flex-shrink: 0;
217
+ border-radius: 3px 0 0 3px;
218
+ }
219
+
220
+ .fv-notif__body {
221
+ flex: 1;
222
+ padding: 0.75rem 0.75rem 0.5rem;
223
+ min-width: 0;
224
+ }
225
+
226
+ .fv-notif__header {
227
+ display: flex;
228
+ align-items: flex-start;
229
+ gap: 0.5rem;
230
+ }
231
+
232
+ .fv-notif__icon {
233
+ flex-shrink: 0;
234
+ margin-top: 1px;
235
+ }
236
+
237
+ .fv-notif__title {
238
+ flex: 1;
239
+ font-size: 0.8125rem;
240
+ font-weight: 600;
241
+ line-height: 1.25;
242
+ color: #171717;
243
+ min-width: 0;
244
+ word-break: break-word;
245
+ }
246
+ :is(.dark) .fv-notif__title { color: #f5f5f5; }
247
+
248
+ .fv-notif__close {
249
+ flex-shrink: 0;
250
+ padding: 2px;
251
+ border-radius: 0.25rem;
252
+ color: #a3a3a3;
253
+ cursor: pointer;
254
+ transition: color 150ms, background-color 150ms;
255
+ border: none;
256
+ background: none;
257
+ }
258
+ .fv-notif__close:hover {
259
+ color: #525252;
260
+ background: rgba(0, 0, 0, 0.05);
261
+ }
262
+ :is(.dark) .fv-notif__close:hover {
263
+ color: #e5e5e5;
264
+ background: rgba(255, 255, 255, 0.08);
265
+ }
266
+
267
+ .fv-notif__content {
268
+ margin-top: 0.25rem;
269
+ font-size: 0.75rem;
270
+ line-height: 1.4;
271
+ color: #737373;
272
+ }
273
+ :is(.dark) .fv-notif__content { color: #a3a3a3; }
274
+
275
+ .fv-notif__actions {
276
+ margin-top: 0.5rem;
277
+ display: flex;
278
+ justify-content: flex-end;
279
+ }
280
+
281
+ .fv-notif__cta {
282
+ font-size: 0.75rem;
283
+ font-weight: 600;
284
+ cursor: pointer;
285
+ border: none;
286
+ background: none;
287
+ padding: 0;
288
+ transition: opacity 150ms;
289
+ }
290
+ .fv-notif__cta:hover { opacity: 0.7; }
291
+
292
+ .fv-notif__progress-track {
293
+ position: absolute;
294
+ bottom: 0;
295
+ left: 3px; /* offset for stripe */
296
+ right: 0;
297
+ height: 2px;
298
+ background: rgba(0, 0, 0, 0.05);
299
+ }
300
+ :is(.dark) .fv-notif__progress-track {
301
+ background: rgba(255, 255, 255, 0.05);
302
+ }
303
+
304
+ .fv-notif__progress-bar {
305
+ height: 100%;
306
+ transition: width 100ms linear;
307
+ border-radius: 0 0 2px 0;
308
+ }
309
+
310
+ /* ═══ Entrance/Exit — smooth slide-up + fade (not harsh slide-right) ═══ */
311
+ .notif-enter-active {
312
+ transition: transform 300ms cubic-bezier(0.16, 1, 0.3, 1), opacity 200ms ease-out;
313
+ }
314
+ .notif-leave-active {
315
+ transition: transform 200ms cubic-bezier(0.4, 0, 1, 1), opacity 150ms ease-in;
316
+ }
317
+ .notif-enter-from {
318
+ transform: translateY(1rem) scale(0.96);
319
+ opacity: 0;
320
+ }
321
+ .notif-leave-to {
322
+ transform: translateY(-0.5rem) scale(0.98);
323
+ opacity: 0;
324
+ }
325
+
326
+ @media (prefers-reduced-motion: reduce) {
327
+ .notif-enter-active,
328
+ .notif-leave-active { transition: opacity 100ms; }
329
+ .notif-enter-from,
330
+ .notif-leave-to { transform: none; }
331
+ }
332
+ </style>