@veristone/nuxt-v-app 0.1.0

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 (136) hide show
  1. package/README.md +42 -0
  2. package/app/app.vue +7 -0
  3. package/app/assets/css/v-app.css +313 -0
  4. package/app/components/V/A/Badge.vue +75 -0
  5. package/app/components/V/A/Btn/Add.vue +17 -0
  6. package/app/components/V/A/Btn/Back.vue +25 -0
  7. package/app/components/V/A/Btn/ConfirmDelete.vue +45 -0
  8. package/app/components/V/A/Btn/Edit.vue +35 -0
  9. package/app/components/V/A/Btn/Export.vue +28 -0
  10. package/app/components/V/A/Btn/Refresh.vue +21 -0
  11. package/app/components/V/A/Btn/Submit.vue +45 -0
  12. package/app/components/V/A/Btn/View.vue +23 -0
  13. package/app/components/V/A/Card.legacy.vue +291 -0
  14. package/app/components/V/A/Card.vue +108 -0
  15. package/app/components/V/A/CompanyMenu.vue +83 -0
  16. package/app/components/V/A/Data/KeyValue.vue +98 -0
  17. package/app/components/V/A/Data/StatusBadge.vue +44 -0
  18. package/app/components/V/A/DataField.vue +140 -0
  19. package/app/components/V/A/DataGrid.vue +43 -0
  20. package/app/components/V/A/DataTable.vue +144 -0
  21. package/app/components/V/A/EmptyState.vue +154 -0
  22. package/app/components/V/A/Fmt/Currency.vue +36 -0
  23. package/app/components/V/A/Fmt/DateTime.vue +34 -0
  24. package/app/components/V/A/Fmt/Percent.vue +47 -0
  25. package/app/components/V/A/LoadingState.vue +140 -0
  26. package/app/components/V/A/MetricCard.vue +129 -0
  27. package/app/components/V/A/Modal/Base.vue +195 -0
  28. package/app/components/V/A/Modal/Confirm.vue +92 -0
  29. package/app/components/V/A/Modal/Form.vue +105 -0
  30. package/app/components/V/A/Navigation.vue +110 -0
  31. package/app/components/V/A/QuickActions.vue +169 -0
  32. package/app/components/V/A/Slide.vue +109 -0
  33. package/app/components/V/A/Slideover.vue +259 -0
  34. package/app/components/V/A/State/Empty.vue +20 -0
  35. package/app/components/V/A/State/Error.vue +34 -0
  36. package/app/components/V/A/State/Loading.vue +33 -0
  37. package/app/components/V/A/StatsCard.vue +215 -0
  38. package/app/components/V/A/StatusBadge.vue +215 -0
  39. package/app/components/V/A/Table.vue +674 -0
  40. package/app/components/V/A/UserMenu.vue +127 -0
  41. package/app/components/V/A/WelcomeHeader.vue +96 -0
  42. package/app/components/V/Modal.vue +36 -0
  43. package/app/components/Va/Blocks/VaBlockGridCharts.vue +32 -0
  44. package/app/components/Va/Blocks/VaBlockGridKPI.vue +32 -0
  45. package/app/components/Va/Blocks/VaBlockGridTables.vue +23 -0
  46. package/app/components/Va/Blocks/VaBlockKpiGrid.vue +8 -0
  47. package/app/components/Va/Blocks/VaBlockSessionFilterBar.vue +8 -0
  48. package/app/components/Va/Cards/VaCardDonutChart.vue +59 -0
  49. package/app/components/Va/Cards/VaCardHeader.vue +10 -0
  50. package/app/components/Va/Cards/VaCardKpi.vue +17 -0
  51. package/app/components/Va/Cards/VaCardKpi2.vue +55 -0
  52. package/app/components/Va/Cards/VaCardLatestOrders.vue +82 -0
  53. package/app/components/Va/Cards/VaCardPopularProducts.vue +88 -0
  54. package/app/components/Va/Cards/VaCardRevenueBarChart.vue +49 -0
  55. package/app/components/Va/Cards/VaCardSubtitle.vue +5 -0
  56. package/app/components/Va/Cards/VaCardTitle.vue +5 -0
  57. package/app/components/Va/Cards/VaCardWithActiveUsers.vue +41 -0
  58. package/app/components/Va/Cards/VaCardWithChart.vue +135 -0
  59. package/app/components/Va/Cards/VaCardWithChartBlock.vue +26 -0
  60. package/app/components/Va/Cards/VaCardWithIndicator.vue +39 -0
  61. package/app/components/Va/Cards/VaCardWithProgressCircle.vue +34 -0
  62. package/app/components/Va/Cards/types.ts +11 -0
  63. package/app/components/Va/Charts/VaChartAppPerformanceBar.vue +118 -0
  64. package/app/components/Va/Charts/VaChartAppPerformanceBarChart.vue +118 -0
  65. package/app/components/Va/Charts/VaChartAreaMini.vue +127 -0
  66. package/app/components/Va/Charts/VaChartBarMini.vue +68 -0
  67. package/app/components/Va/Charts/VaChartCardinalMulti.vue +108 -0
  68. package/app/components/Va/Charts/VaChartColorBarChart.vue +78 -0
  69. package/app/components/Va/Charts/VaChartDonutHalf.vue +35 -0
  70. package/app/components/Va/Charts/VaChartDonutMini.vue +77 -0
  71. package/app/components/Va/Charts/VaChartExpensesBar.vue +58 -0
  72. package/app/components/Va/Charts/VaChartFinanceSummary.vue +96 -0
  73. package/app/components/Va/Charts/VaChartGoogleSearchConsole.vue +90 -0
  74. package/app/components/Va/Charts/VaChartIncomeBar.vue +82 -0
  75. package/app/components/Va/Charts/VaChartLegend.vue +25 -0
  76. package/app/components/Va/Charts/VaChartLineMini.vue +205 -0
  77. package/app/components/Va/Charts/VaChartRealtimeTraffic.vue +182 -0
  78. package/app/components/Va/Charts/VaChartRevenue.vue +43 -0
  79. package/app/components/Va/Charts/VaChartRevenueLine.vue +42 -0
  80. package/app/components/Va/Charts/VaChartRevenuevsCost.vue +84 -0
  81. package/app/components/Va/Charts/VaChartSearchIntent.vue +179 -0
  82. package/app/components/Va/Charts/VaChartSpendingTrend.vue +127 -0
  83. package/app/components/Va/Charts/VaChartStackedHorizontal.vue +64 -0
  84. package/app/components/Va/Charts/VaChartStepMinimal.vue +109 -0
  85. package/app/components/Va/Charts/VaChartStockComparisonLine.vue +86 -0
  86. package/app/components/Va/Charts/VaChartStocksPortfolioLine.vue +161 -0
  87. package/app/components/Va/Charts/VaChartStocksSectorLine.vue +223 -0
  88. package/app/components/Va/Charts/VaChartTasksCategories.vue +96 -0
  89. package/app/components/Va/Charts/VaChartTasksProgress.vue +130 -0
  90. package/app/components/Va/Charts/VaChartTrafficOverview.vue +112 -0
  91. package/app/components/Va/Charts/VaChartWebPerformanceLineChart.vue +114 -0
  92. package/app/components/Va/Charts/VaChartWinLostBar.vue +110 -0
  93. package/app/components/Va/Charts/VaChartWinLostDonut.vue +107 -0
  94. package/app/components/Va/Charts/VaChartWinLostLine.vue +111 -0
  95. package/app/components/Va/Charts/types.ts +10 -0
  96. package/app/components/Va/Dashboard/Navigation/types.ts +8 -0
  97. package/app/components/Va/Dashboard/VaDashboardKPICard.vue +31 -0
  98. package/app/components/Va/Dashboard/VaDashboardNavigation.vue +50 -0
  99. package/app/components/Va/Dashboard/VaDashboardPricePlan.vue +102 -0
  100. package/app/components/Va/Dashboard/VaDashboardUsageChart.vue +84 -0
  101. package/app/components/Va/Dashboard/VaDashboardUsageRequestChart.vue +46 -0
  102. package/app/components/Va/Layout/NotificationsSlideover.vue +169 -0
  103. package/app/components/Va/Layout/SideNav/types.ts +5 -0
  104. package/app/components/Va/Layout/SideNav.vue +108 -0
  105. package/app/components/Va/Layout/TeamsMenu.vue +57 -0
  106. package/app/components/Va/Layout/UserMenu.vue +57 -0
  107. package/app/composables/useDashboard.ts +25 -0
  108. package/app/composables/useVAAnimation.ts +324 -0
  109. package/app/composables/useVAUtils.ts +118 -0
  110. package/app/composables/useVCrud.ts +647 -0
  111. package/app/composables/useVFetch.ts +46 -0
  112. package/app/composables/useVFileUpload.ts +45 -0
  113. package/app/composables/useVToast.ts +73 -0
  114. package/app/composables/useXATableColumns.ts +456 -0
  115. package/app/data/BillingStats.ts +65 -0
  116. package/app/data/SearchData.ts +58 -0
  117. package/app/data/TasksData.ts +101 -0
  118. package/app/data/dashboardData.ts +113 -0
  119. package/app/layouts/default.vue +171 -0
  120. package/app/layouts/legacy.vue +61 -0
  121. package/app/pages/playground/base.vue +498 -0
  122. package/app/pages/playground/blocks.vue +108 -0
  123. package/app/pages/playground/buttons.vue +237 -0
  124. package/app/pages/playground/cards.vue +326 -0
  125. package/app/pages/playground/charts.vue +338 -0
  126. package/app/pages/playground/dashboard.vue +315 -0
  127. package/app/pages/playground/formatters.vue +329 -0
  128. package/app/pages/playground/index.vue +109 -0
  129. package/app/pages/playground/layout.vue +159 -0
  130. package/app/pages/playground/modals.vue +606 -0
  131. package/app/pages/playground/states.vue +282 -0
  132. package/app/pages/playground/tables.vue +618 -0
  133. package/app/pages/test-layout.vue +10 -0
  134. package/nuxt.config.ts +12 -0
  135. package/package.json +71 -0
  136. package/tsconfig.json +18 -0
package/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # @veristone/nuxt-v-app
2
+
3
+ Veristone Nuxt App Layer — shared components, composables, and layouts for Nuxt 4 apps.
4
+
5
+ ## Requirements
6
+
7
+ - **Nuxt** ^4.0.0
8
+ - **@nuxt/ui** ^4.0.0
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install @veristone/nuxt-v-app
14
+ # or
15
+ pnpm add @veristone/nuxt-v-app
16
+ # or
17
+ bun add @veristone/nuxt-v-app
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ Extend the layer in your Nuxt config:
23
+
24
+ ```ts
25
+ // nuxt.config.ts
26
+ export default defineNuxtConfig({
27
+ extends: ['@veristone/nuxt-v-app'],
28
+ })
29
+ ```
30
+
31
+ The layer adds:
32
+
33
+ - **Components** — `V/*`, `Va/*` (e.g. `V.A.Card`, `VaCardKpi`, `VaChartRevenue`)
34
+ - **Composables** — `useDashboard`, `useVAUtils`, `useVCrud`, `useVFetch`, etc.
35
+ - **Layouts** — `default`, `legacy`
36
+ - **Styles** — `v-app.css` (Tailwind-based)
37
+
38
+ Ensure `@nuxt/ui` and `nuxt` are installed in your app (they are peer dependencies).
39
+
40
+ ## License
41
+
42
+ MIT
package/app/app.vue ADDED
@@ -0,0 +1,7 @@
1
+ <template>
2
+ <UApp>
3
+ <NuxtLayout>
4
+ <NuxtPage />
5
+ </NuxtLayout>
6
+ </UApp>
7
+ </template>
@@ -0,0 +1,313 @@
1
+ @import "tailwindcss";
2
+ @import "@nuxt/ui";
3
+
4
+ /* ============================================
5
+ Veristone Design System - HubSpot Style
6
+ ============================================ */
7
+
8
+ :root {
9
+ /* HubSpot Color Palette */
10
+ --va-coral-50: #fff5f2;
11
+ --va-coral-100: #ffe8e1;
12
+ --va-coral-200: #ffc9b8;
13
+ --va-coral-300: #ffa285;
14
+ --va-coral-400: #ff7a52;
15
+ --va-coral-500: #ff5c35;
16
+ --va-coral-600: #e8491d;
17
+ --va-coral-700: #c23a14;
18
+ --va-coral-800: #9a2f11;
19
+ --va-coral-900: #7d2810;
20
+
21
+ --va-teal-50: #e6f8fa;
22
+ --va-teal-100: #b3ecf3;
23
+ --va-teal-200: #80e0ec;
24
+ --va-teal-300: #4dd4e5;
25
+ --va-teal-400: #26c9de;
26
+ --va-teal-500: #00a4bd;
27
+ --va-teal-600: #008da3;
28
+ --va-teal-700: #007589;
29
+ --va-teal-800: #005e6f;
30
+ --va-teal-900: #004655;
31
+
32
+ --va-navy-500: #2d3e50;
33
+ --va-navy-600: #213343;
34
+ --va-navy-700: #1a2836;
35
+ --va-navy-800: #131d29;
36
+ --va-navy-900: #0d131c;
37
+
38
+ /* Semantic Colors */
39
+ --va-success: #00a4bd;
40
+ --va-warning: #f5a623;
41
+ --va-error: #f2545b;
42
+ --va-info: #5c6ac4;
43
+
44
+ /* Shadow System - HubSpot inspired */
45
+ --va-shadow-xs: 0 1px 2px rgba(33, 51, 67, 0.04);
46
+ --va-shadow-sm: 0 1px 3px rgba(33, 51, 67, 0.06), 0 1px 2px rgba(33, 51, 67, 0.04);
47
+ --va-shadow-md: 0 4px 6px rgba(33, 51, 67, 0.06), 0 2px 4px rgba(33, 51, 67, 0.04);
48
+ --va-shadow-lg: 0 10px 15px rgba(33, 51, 67, 0.08), 0 4px 6px rgba(33, 51, 67, 0.04);
49
+ --va-shadow-xl: 0 20px 25px rgba(33, 51, 67, 0.10), 0 10px 10px rgba(33, 51, 67, 0.04);
50
+
51
+ /* Card-specific shadows */
52
+ --va-card-shadow: 0 1px 3px rgba(33, 51, 67, 0.06), 0 1px 2px rgba(33, 51, 67, 0.04);
53
+ --va-card-shadow-hover: 0 6px 16px rgba(0, 164, 189, 0.10), 0 2px 6px rgba(33, 51, 67, 0.06);
54
+
55
+ /* Timing Functions */
56
+ --va-ease-smooth: cubic-bezier(0.4, 0, 0.2, 1);
57
+ --va-ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
58
+ --va-ease-snap: cubic-bezier(0.22, 1, 0.36, 1);
59
+
60
+ /* Duration */
61
+ --va-duration-fast: 150ms;
62
+ --va-duration-normal: 200ms;
63
+ --va-duration-slow: 300ms;
64
+
65
+ /* Border Radius */
66
+ --va-radius-sm: 6px;
67
+ --va-radius-md: 8px;
68
+ --va-radius-lg: 12px;
69
+ --va-radius-xl: 16px;
70
+
71
+ /* Font families */
72
+ --va-font-display: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
73
+ --va-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
74
+ }
75
+
76
+ /* Dark mode overrides */
77
+ .dark {
78
+ --va-card-shadow: 0 1px 3px rgba(0, 0, 0, 0.2), 0 1px 2px rgba(0, 0, 0, 0.12);
79
+ --va-card-shadow-hover: 0 6px 16px rgba(0, 164, 189, 0.15), 0 2px 6px rgba(0, 0, 0, 0.12);
80
+ }
81
+
82
+ /* ============================================
83
+ Animations
84
+ ============================================ */
85
+
86
+ @keyframes va-fade-in {
87
+ from { opacity: 0; }
88
+ to { opacity: 1; }
89
+ }
90
+
91
+ @keyframes va-fade-in-up {
92
+ from {
93
+ opacity: 0;
94
+ transform: translateY(8px);
95
+ }
96
+ to {
97
+ opacity: 1;
98
+ transform: translateY(0);
99
+ }
100
+ }
101
+
102
+ @keyframes va-fade-in-down {
103
+ from {
104
+ opacity: 0;
105
+ transform: translateY(-8px);
106
+ }
107
+ to {
108
+ opacity: 1;
109
+ transform: translateY(0);
110
+ }
111
+ }
112
+
113
+ @keyframes va-scale-in {
114
+ from {
115
+ opacity: 0;
116
+ transform: scale(0.95);
117
+ }
118
+ to {
119
+ opacity: 1;
120
+ transform: scale(1);
121
+ }
122
+ }
123
+
124
+ @keyframes va-slide-in-left {
125
+ from {
126
+ opacity: 0;
127
+ transform: translateX(-16px);
128
+ }
129
+ to {
130
+ opacity: 1;
131
+ transform: translateX(0);
132
+ }
133
+ }
134
+
135
+ @keyframes va-pulse {
136
+ 0%, 100% { opacity: 1; }
137
+ 50% { opacity: 0.5; }
138
+ }
139
+
140
+ @keyframes va-shimmer {
141
+ 0% { background-position: -200% 0; }
142
+ 100% { background-position: 200% 0; }
143
+ }
144
+
145
+ @keyframes va-count-up {
146
+ from { opacity: 0; transform: translateY(4px); }
147
+ to { opacity: 1; transform: translateY(0); }
148
+ }
149
+
150
+ /* Animation utility classes */
151
+ .animate-fade-in {
152
+ animation: va-fade-in var(--va-duration-normal) var(--va-ease-smooth) forwards;
153
+ }
154
+
155
+ .animate-fade-in-up {
156
+ animation: va-fade-in-up var(--va-duration-slow) var(--va-ease-smooth) forwards;
157
+ }
158
+
159
+ .animate-fade-in-down {
160
+ animation: va-fade-in-down var(--va-duration-slow) var(--va-ease-smooth) forwards;
161
+ }
162
+
163
+ .animate-scale-in {
164
+ animation: va-scale-in var(--va-duration-normal) var(--va-ease-smooth) forwards;
165
+ }
166
+
167
+ .animate-slide-in-left {
168
+ animation: va-slide-in-left var(--va-duration-slow) var(--va-ease-smooth) forwards;
169
+ }
170
+
171
+ /* ============================================
172
+ Component Utilities
173
+ ============================================ */
174
+
175
+ /* Card base styles */
176
+ .va-card-base {
177
+ background: white;
178
+ border: 1px solid rgb(229 231 235);
179
+ border-radius: var(--va-radius-lg);
180
+ box-shadow: var(--va-card-shadow);
181
+ transition: box-shadow var(--va-duration-normal) var(--va-ease-smooth),
182
+ transform var(--va-duration-normal) var(--va-ease-smooth);
183
+ }
184
+
185
+ .va-card-base:hover {
186
+ box-shadow: var(--va-card-shadow-hover);
187
+ transform: translateY(-2px);
188
+ }
189
+
190
+ .dark .va-card-base {
191
+ background: rgb(17 24 39);
192
+ border-color: rgb(31 41 55);
193
+ }
194
+
195
+ /* Accent bar for cards */
196
+ .va-accent-bar {
197
+ position: absolute;
198
+ left: 0;
199
+ top: 0;
200
+ bottom: 0;
201
+ width: 3px;
202
+ border-radius: var(--va-radius-lg) 0 0 var(--va-radius-lg);
203
+ background: var(--va-coral-500);
204
+ opacity: 0;
205
+ transition: opacity var(--va-duration-fast) var(--va-ease-smooth);
206
+ }
207
+
208
+ .va-card-base:hover .va-accent-bar {
209
+ opacity: 1;
210
+ }
211
+
212
+ /* Table row hover effect */
213
+ .va-table-row-hover {
214
+ transition: background-color var(--va-duration-fast) var(--va-ease-smooth);
215
+ }
216
+
217
+ .va-table-row-hover:hover {
218
+ background-color: rgb(249 250 251);
219
+ }
220
+
221
+ .dark .va-table-row-hover:hover {
222
+ background-color: rgb(31 41 55);
223
+ }
224
+
225
+ /* Sidebar active indicator */
226
+ .va-nav-indicator {
227
+ position: absolute;
228
+ left: 0;
229
+ top: 50%;
230
+ transform: translateY(-50%);
231
+ width: 3px;
232
+ height: 24px;
233
+ background: var(--va-coral-500);
234
+ border-radius: 0 2px 2px 0;
235
+ opacity: 0;
236
+ transition: opacity var(--va-duration-fast) var(--va-ease-smooth);
237
+ }
238
+
239
+ .va-nav-active .va-nav-indicator {
240
+ opacity: 1;
241
+ }
242
+
243
+ /* Skeleton loading */
244
+ .va-skeleton {
245
+ background: linear-gradient(
246
+ 90deg,
247
+ rgb(229 231 235) 0%,
248
+ rgb(243 244 246) 50%,
249
+ rgb(229 231 235) 100%
250
+ );
251
+ background-size: 200% 100%;
252
+ animation: va-shimmer 1.5s ease-in-out infinite;
253
+ border-radius: var(--va-radius-sm);
254
+ }
255
+
256
+ .dark .va-skeleton {
257
+ background: linear-gradient(
258
+ 90deg,
259
+ rgb(55 65 81) 0%,
260
+ rgb(75 85 99) 50%,
261
+ rgb(55 65 81) 100%
262
+ );
263
+ background-size: 200% 100%;
264
+ }
265
+
266
+ /* Action buttons fade-in on row hover */
267
+ .va-row-actions {
268
+ opacity: 0;
269
+ transition: opacity var(--va-duration-fast) var(--va-ease-smooth);
270
+ }
271
+
272
+ .va-table-row-hover:hover .va-row-actions {
273
+ opacity: 1;
274
+ }
275
+
276
+ /* Focus ring utility */
277
+ .va-focus-ring:focus-visible {
278
+ outline: 2px solid var(--va-coral-500);
279
+ outline-offset: 2px;
280
+ }
281
+
282
+ /* ============================================
283
+ Dashboard 2 Chart & Visualization Variables
284
+ Source: reference-repos/nuxt-dashboard-2-main/app/assets/css/main.css
285
+ ============================================ */
286
+
287
+ :root {
288
+ /* Chart Tooltip Styling */
289
+ --vis-tooltip-title-color: var(--ui-text) !important;
290
+ --vis-tooltip-title-border-bottom: 1px solid var(--ui-border) !important;
291
+ --vis-tooltip-label-color: var(--ui-text-muted) !important;
292
+
293
+ /* Chart Background & Donut */
294
+ --vis-donut-background-color: var(--ui-bg-elevated) !important;
295
+
296
+ /* Legend Spacing */
297
+ --vis-legend-spacing: 0.75rem;
298
+
299
+ /* Axis & Grid Styling */
300
+ --vis-axis-grid-color: var(--ui-border) !important;
301
+ --vis-axis-tick-label-color: var(--ui-text-muted) !important;
302
+ --vis-axis-label-color: var(--ui-text-toned) !important;
303
+ --vis-legend-label-color: var(--ui-text-toned) !important;
304
+
305
+ /* Tooltip Styling */
306
+ --vis-tooltip-padding: 0 !important;
307
+ --vis-tooltip-background-color: var(--ui-bg) !important;
308
+ --vis-tooltip-border-color: var(--ui-border) !important;
309
+ --vis-tooltip-text-color: var(--ui-text-toned) !important;
310
+
311
+ /* Legend Spacing (override) */
312
+ --vis-legend-spacing: 1rem;
313
+ }
@@ -0,0 +1,75 @@
1
+ <template>
2
+ <div
3
+ class="va-badge"
4
+ :class="badgeClass"
5
+ >
6
+ <span v-if="showDot" class="va-badge-dot" />
7
+ <span class="va-badge-text">{{ label }}</span>
8
+ </div>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ const props = withDefaults(defineProps<{
13
+ label: string
14
+ status?: string
15
+ showDot?: boolean
16
+ }>(), {
17
+ status: '',
18
+ showDot: true
19
+ })
20
+
21
+ const badgeClass = computed(() => {
22
+ if (props.status) {
23
+ return `va-badge-${props.status.toLowerCase().replace(/\s+/g, '-')}`
24
+ }
25
+ return ''
26
+ })
27
+ </script>
28
+
29
+ <style scoped>
30
+ .va-badge {
31
+ display: flex;
32
+ align-items: center;
33
+ gap: 0.5rem;
34
+ padding: 0.625rem 1.25rem;
35
+ background: rgba(255, 255, 255, 0.05);
36
+ backdrop-filter: blur(20px);
37
+ border: 1px solid rgba(212, 175, 55, 0.15);
38
+ border-radius: 100px;
39
+ transition: all 0.3s ease;
40
+ }
41
+
42
+ .va-badge:hover {
43
+ background: rgba(255, 255, 255, 0.08);
44
+ border-color: #d4af37;
45
+ transform: translateY(-1px);
46
+ }
47
+
48
+ .va-badge-dot {
49
+ width: 6px;
50
+ height: 6px;
51
+ border-radius: 50%;
52
+ background: #d4af37;
53
+ box-shadow: 0 0 8px #d4af37;
54
+ animation: pulse 2s ease-in-out infinite;
55
+ }
56
+
57
+ .va-badge-text {
58
+ font-size: 0.8125rem;
59
+ font-weight: 600;
60
+ color: #e8eaed;
61
+ text-transform: uppercase;
62
+ letter-spacing: 0.08em;
63
+ }
64
+
65
+ @keyframes pulse {
66
+ 0%, 100% {
67
+ opacity: 1;
68
+ transform: scale(1);
69
+ }
70
+ 50% {
71
+ opacity: 0.6;
72
+ transform: scale(0.95);
73
+ }
74
+ }
75
+ </style>
@@ -0,0 +1,17 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ label?: string
4
+ to?: string
5
+ icon?: string
6
+ }>()
7
+ </script>
8
+
9
+ <template>
10
+ <UButton
11
+ :label="label || 'Add New'"
12
+ :to="to"
13
+ :icon="icon || 'i-lucide-plus'"
14
+ color="primary"
15
+ variant="soft"
16
+ />
17
+ </template>
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps<{
3
+ to?: string | object
4
+ label?: string
5
+ icon?: string
6
+ }>()
7
+
8
+ const router = useRouter()
9
+ const goBack = () => {
10
+ if (props.to) return router.push(props.to)
11
+ router.back()
12
+ }
13
+ </script>
14
+
15
+ <template>
16
+ <UButton
17
+ :icon="icon || 'i-lucide-arrow-left'"
18
+ :label="label || 'Back'"
19
+ color="gray"
20
+ variant="ghost"
21
+ size="sm"
22
+ class="-ml-2"
23
+ @click="goBack"
24
+ />
25
+ </template>
@@ -0,0 +1,45 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps<{
3
+ endpoint: string
4
+ id: string | number
5
+ itemName?: string
6
+ triggerIcon?: string
7
+ }>()
8
+
9
+ const emit = defineEmits(['success', 'deleted'])
10
+ const { remove, loading } = useVCrud(props.endpoint)
11
+ const isOpen = ref(false)
12
+
13
+ const onConfirm = async () => {
14
+ try {
15
+ await remove(props.id)
16
+ emit('success')
17
+ emit('deleted', props.id)
18
+ isOpen.value = false
19
+ } catch (e) {
20
+ // Handled
21
+ }
22
+ }
23
+ </script>
24
+
25
+ <template>
26
+ <div class="inline-block">
27
+ <UButton
28
+ :icon="triggerIcon || 'i-lucide-trash-2'"
29
+ color="red"
30
+ variant="ghost"
31
+ size="xs"
32
+ @click="isOpen = true"
33
+ />
34
+
35
+ <VAModalConfirm
36
+ v-model="isOpen"
37
+ title="Delete Item"
38
+ :message="`Are you sure you want to delete this ${itemName || 'item'}? This cannot be undone.`"
39
+ variant="danger"
40
+ confirm-text="Delete"
41
+ :loading="loading"
42
+ @confirm="onConfirm"
43
+ />
44
+ </div>
45
+ </template>
@@ -0,0 +1,35 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps<{
3
+ endpoint?: string
4
+ id?: string | number
5
+ title?: string
6
+ }>()
7
+
8
+ const isOpen = ref(false)
9
+ </script>
10
+
11
+ <template>
12
+ <div class="inline-block">
13
+ <slot name="trigger" :open="() => isOpen = true">
14
+ <UButton
15
+ icon="i-lucide-pencil"
16
+ color="gray"
17
+ variant="ghost"
18
+ size="xs"
19
+ @click="isOpen = true"
20
+ />
21
+ </slot>
22
+
23
+ <VAModalForm
24
+ v-model="isOpen"
25
+ :title="title || 'Edit Record'"
26
+ :endpoint="endpoint"
27
+ :record-id="id"
28
+ v-bind="$attrs"
29
+ >
30
+ <template #default="slotProps">
31
+ <slot v-bind="slotProps" />
32
+ </template>
33
+ </VAModalForm>
34
+ </div>
35
+ </template>
@@ -0,0 +1,28 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps<{
3
+ loading?: boolean
4
+ formats?: string[]
5
+ }>()
6
+
7
+ const emit = defineEmits(['export'])
8
+ const items = computed(() => [
9
+ (props.formats || ['csv', 'excel', 'pdf']).map(f => ({
10
+ label: f.toUpperCase(),
11
+ icon: f === 'pdf' ? 'i-lucide-file-text' : 'i-lucide-table',
12
+ onSelect: () => emit('export', f)
13
+ }))
14
+ ])
15
+ </script>
16
+
17
+ <template>
18
+ <UDropdownMenu :items="items" :content="{ align: 'end' }">
19
+ <UButton
20
+ icon="i-lucide-download"
21
+ label="Export"
22
+ color="neutral"
23
+ variant="soft"
24
+ size="sm"
25
+ :loading="loading"
26
+ />
27
+ </UDropdownMenu>
28
+ </template>
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps<{
3
+ loading?: boolean
4
+ label?: string
5
+ }>()
6
+
7
+ defineEmits(['refresh'])
8
+ </script>
9
+
10
+ <template>
11
+ <UButton
12
+ icon="i-lucide-refresh-cw"
13
+ :loading="loading"
14
+ :label="label"
15
+ color="gray"
16
+ variant="ghost"
17
+ size="sm"
18
+ :class="{ 'animate-spin': loading && !label }"
19
+ @click="$emit('refresh')"
20
+ />
21
+ </template>
@@ -0,0 +1,45 @@
1
+ <script setup lang="ts">
2
+ withDefaults(defineProps<{
3
+ label?: string
4
+ loading?: boolean
5
+ disabled?: boolean
6
+ color?: string
7
+ variant?: string
8
+ icon?: string
9
+ align?: string
10
+ showCancel?: boolean
11
+ cancelLabel?: string
12
+ }>(), {
13
+ label: 'Save',
14
+ loading: false,
15
+ disabled: false,
16
+ color: 'primary',
17
+ variant: 'solid',
18
+ showCancel: false,
19
+ cancelLabel: 'Cancel'
20
+ })
21
+
22
+ defineEmits(['cancel'])
23
+ </script>
24
+
25
+ <template>
26
+ <div class="flex items-center gap-3" :class="{ 'justify-end': align === 'right', 'justify-center': align === 'center', 'justify-start': align === 'left' || !align }">
27
+ <UButton
28
+ v-if="showCancel"
29
+ :label="cancelLabel"
30
+ color="gray"
31
+ variant="ghost"
32
+ :disabled="loading"
33
+ @click="$emit('cancel')"
34
+ />
35
+ <UButton
36
+ type="submit"
37
+ :label="label"
38
+ :loading="loading"
39
+ :disabled="disabled || loading"
40
+ :color="color"
41
+ :variant="variant"
42
+ :icon="icon"
43
+ />
44
+ </div>
45
+ </template>
@@ -0,0 +1,23 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps<{
3
+ to?: string | object
4
+ id?: string | number
5
+ triggerIcon?: string
6
+ triggerColor?: string
7
+ triggerVariant?: string
8
+ triggerSize?: string
9
+ }>()
10
+
11
+ // Simple view button that links to a detail page
12
+ </script>
13
+
14
+ <template>
15
+ <UButton
16
+ :to="to"
17
+ :icon="triggerIcon || 'i-lucide-eye'"
18
+ :color="triggerColor || 'gray'"
19
+ :variant="triggerVariant || 'ghost'"
20
+ :size="triggerSize || 'xs'"
21
+ aria-label="View"
22
+ />
23
+ </template>