@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
@@ -0,0 +1,291 @@
1
+ <template>
2
+ <article
3
+ class="va-card"
4
+ :class="[
5
+ `va-card--${variant}`,
6
+ { 'va-card--collapsible': collapsible, 'va-card--collapsed': isCollapsed }
7
+ ]"
8
+ :style="{ animationDelay }"
9
+ >
10
+ <div v-if="showAccent" class="va-card__accent" :class="`va-card__accent--${accentColor}`" />
11
+
12
+ <header v-if="title || $slots.header || $slots.actions" class="va-card__header" @click="toggleCollapse">
13
+ <div class="va-card__header-content">
14
+ <div v-if="icon" class="va-card__icon-wrapper">
15
+ <UIcon :name="icon" class="va-card__icon" />
16
+ </div>
17
+ <div class="va-card__title-group">
18
+ <slot name="header">
19
+ <h3 class="va-card__title">{{ title }}</h3>
20
+ <p v-if="subtitle" class="va-card__subtitle">{{ subtitle }}</p>
21
+ </slot>
22
+ </div>
23
+ </div>
24
+ <div class="va-card__header-right">
25
+ <slot name="actions" />
26
+ <button v-if="collapsible" class="va-card__collapse-btn" :aria-expanded="!isCollapsed">
27
+ <UIcon name="i-lucide-chevron-down" class="va-card__collapse-icon" />
28
+ </button>
29
+ </div>
30
+ </header>
31
+
32
+ <div class="va-card__body" :class="{ 'va-card__body--padded': padded }">
33
+ <slot />
34
+ </div>
35
+
36
+ <footer v-if="$slots.footer" class="va-card__footer">
37
+ <slot name="footer" />
38
+ </footer>
39
+ </article>
40
+ </template>
41
+
42
+ <script setup lang="ts">
43
+ type CardVariant = 'default' | 'elevated' | 'outlined' | 'glass'
44
+ type AccentColor = 'gold' | 'success' | 'warning' | 'error' | 'info' | 'primary'
45
+
46
+ const props = withDefaults(defineProps<{
47
+ title?: string
48
+ subtitle?: string
49
+ icon?: string
50
+ variant?: CardVariant
51
+ showAccent?: boolean
52
+ accentColor?: AccentColor
53
+ collapsible?: boolean
54
+ defaultCollapsed?: boolean
55
+ padded?: boolean
56
+ animationDelay?: string
57
+ }>(), {
58
+ title: '',
59
+ subtitle: '',
60
+ icon: '',
61
+ variant: 'default',
62
+ showAccent: false,
63
+ accentColor: 'gold',
64
+ collapsible: false,
65
+ defaultCollapsed: false,
66
+ padded: true,
67
+ animationDelay: '0s'
68
+ })
69
+
70
+ const isCollapsed = ref(props.defaultCollapsed)
71
+
72
+ function toggleCollapse() {
73
+ if (props.collapsible) {
74
+ isCollapsed.value = !isCollapsed.value
75
+ }
76
+ }
77
+ </script>
78
+
79
+ <style scoped>
80
+ .va-card {
81
+ position: relative;
82
+ border-radius: 16px;
83
+ overflow: hidden;
84
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
85
+ animation: vaCardFadeIn 0.5s ease-out backwards;
86
+ }
87
+
88
+ @keyframes vaCardFadeIn {
89
+ from {
90
+ opacity: 0;
91
+ transform: translateY(12px);
92
+ }
93
+ to {
94
+ opacity: 1;
95
+ transform: translateY(0);
96
+ }
97
+ }
98
+
99
+ .va-card--default {
100
+ background: rgba(255, 255, 255, 0.03);
101
+ border: 1px solid rgba(255, 255, 255, 0.06);
102
+ }
103
+
104
+ .va-card--default:hover {
105
+ background: rgba(255, 255, 255, 0.05);
106
+ border-color: rgba(255, 255, 255, 0.1);
107
+ }
108
+
109
+ .va-card--elevated {
110
+ background: rgba(255, 255, 255, 0.04);
111
+ border: 1px solid rgba(212, 175, 55, 0.12);
112
+ box-shadow:
113
+ 0 4px 24px rgba(0, 0, 0, 0.15),
114
+ 0 1px 2px rgba(0, 0, 0, 0.1);
115
+ }
116
+
117
+ .va-card--elevated:hover {
118
+ border-color: rgba(212, 175, 55, 0.25);
119
+ transform: translateY(-2px);
120
+ box-shadow:
121
+ 0 8px 32px rgba(0, 0, 0, 0.2),
122
+ 0 2px 4px rgba(0, 0, 0, 0.1);
123
+ }
124
+
125
+ .va-card--outlined {
126
+ background: transparent;
127
+ border: 1px solid rgba(255, 255, 255, 0.08);
128
+ }
129
+
130
+ .va-card--outlined:hover {
131
+ border-color: rgba(212, 175, 55, 0.3);
132
+ }
133
+
134
+ .va-card--glass {
135
+ background: rgba(255, 255, 255, 0.02);
136
+ backdrop-filter: blur(20px);
137
+ border: 1px solid rgba(255, 255, 255, 0.05);
138
+ }
139
+
140
+ .va-card--glass:hover {
141
+ background: rgba(255, 255, 255, 0.04);
142
+ border-color: rgba(212, 175, 55, 0.2);
143
+ }
144
+
145
+ .va-card__accent {
146
+ position: absolute;
147
+ top: 0;
148
+ left: 0;
149
+ width: 4px;
150
+ height: 100%;
151
+ transition: width 0.3s ease;
152
+ }
153
+
154
+ .va-card:hover .va-card__accent {
155
+ width: 5px;
156
+ }
157
+
158
+ .va-card__accent--gold {
159
+ background: linear-gradient(180deg, #d4af37 0%, #e8c96f 100%);
160
+ }
161
+
162
+ .va-card__accent--primary {
163
+ background: linear-gradient(180deg, var(--ui-primary) 0%, var(--ui-primary) 100%);
164
+ }
165
+
166
+ .va-card__accent--success {
167
+ background: linear-gradient(180deg, #22c55e 0%, #4ade80 100%);
168
+ }
169
+
170
+ .va-card__accent--warning {
171
+ background: linear-gradient(180deg, #f59e0b 0%, #fbbf24 100%);
172
+ }
173
+
174
+ .va-card__accent--error {
175
+ background: linear-gradient(180deg, #ef4444 0%, #f87171 100%);
176
+ }
177
+
178
+ .va-card__accent--info {
179
+ background: linear-gradient(180deg, #3b82f6 0%, #60a5fa 100%);
180
+ }
181
+
182
+ .va-card__header {
183
+ display: flex;
184
+ align-items: center;
185
+ justify-content: space-between;
186
+ padding: 1.25rem 1.5rem;
187
+ border-bottom: 1px solid rgba(255, 255, 255, 0.04);
188
+ }
189
+
190
+ .va-card--collapsible .va-card__header {
191
+ cursor: pointer;
192
+ user-select: none;
193
+ }
194
+
195
+ .va-card--collapsible .va-card__header:hover {
196
+ background: rgba(255, 255, 255, 0.02);
197
+ }
198
+
199
+ .va-card__header-content {
200
+ display: flex;
201
+ align-items: center;
202
+ gap: 1rem;
203
+ }
204
+
205
+ .va-card__icon-wrapper {
206
+ display: flex;
207
+ align-items: center;
208
+ justify-content: center;
209
+ width: 2.5rem;
210
+ height: 2.5rem;
211
+ background: rgba(212, 175, 55, 0.1);
212
+ border: 1px solid rgba(212, 175, 55, 0.2);
213
+ border-radius: 10px;
214
+ }
215
+
216
+ .va-card__icon {
217
+ width: 1.25rem;
218
+ height: 1.25rem;
219
+ color: #d4af37;
220
+ }
221
+
222
+ .va-card__title-group {
223
+ display: flex;
224
+ flex-direction: column;
225
+ gap: 0.125rem;
226
+ }
227
+
228
+ .va-card__title {
229
+ font-size: 1rem;
230
+ font-weight: 700;
231
+ color: var(--color-text, #e8eaed);
232
+ letter-spacing: -0.01em;
233
+ }
234
+
235
+ .va-card__subtitle {
236
+ font-size: 0.8125rem;
237
+ color: var(--color-text-muted, #9ca3af);
238
+ }
239
+
240
+ .va-card__header-right {
241
+ display: flex;
242
+ align-items: center;
243
+ gap: 0.75rem;
244
+ }
245
+
246
+ .va-card__collapse-btn {
247
+ display: flex;
248
+ align-items: center;
249
+ justify-content: center;
250
+ width: 2rem;
251
+ height: 2rem;
252
+ background: rgba(255, 255, 255, 0.03);
253
+ border: 1px solid rgba(255, 255, 255, 0.06);
254
+ border-radius: 8px;
255
+ cursor: pointer;
256
+ transition: all 0.2s ease;
257
+ }
258
+
259
+ .va-card__collapse-btn:hover {
260
+ background: rgba(255, 255, 255, 0.06);
261
+ }
262
+
263
+ .va-card__collapse-icon {
264
+ width: 1rem;
265
+ height: 1rem;
266
+ color: var(--color-text-muted, #9ca3af);
267
+ transition: transform 0.3s ease;
268
+ }
269
+
270
+ .va-card--collapsed .va-card__collapse-icon {
271
+ transform: rotate(-90deg);
272
+ }
273
+
274
+ .va-card__body {
275
+ transition: all 0.3s ease;
276
+ }
277
+
278
+ .va-card__body--padded {
279
+ padding: 1.5rem;
280
+ }
281
+
282
+ .va-card--collapsed .va-card__body {
283
+ display: none;
284
+ }
285
+
286
+ .va-card__footer {
287
+ padding: 1rem 1.5rem;
288
+ border-top: 1px solid rgba(255, 255, 255, 0.04);
289
+ background: rgba(255, 255, 255, 0.01);
290
+ }
291
+ </style>
@@ -0,0 +1,108 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * VACard - Veristone Card
4
+ * Matches XACard API exactly.
5
+ */
6
+ const props = withDefaults(defineProps<{
7
+ title?: string
8
+ description?: string
9
+ icon?: string
10
+ collapsible?: boolean
11
+ collapsed?: boolean
12
+ padding?: boolean
13
+ divider?: boolean
14
+ variant?: 'default' | 'error' | 'warning' | 'success'
15
+ }>(), {
16
+ title: '',
17
+ description: '',
18
+ icon: '',
19
+ collapsible: false,
20
+ collapsed: false,
21
+ padding: true,
22
+ divider: true,
23
+ variant: 'default'
24
+ })
25
+
26
+ const isCollapsed = ref(props.collapsed)
27
+ watch(() => props.collapsed, (val) => isCollapsed.value = val)
28
+
29
+ const toggleCollapse = () => {
30
+ if (props.collapsible) isCollapsed.value = !isCollapsed.value
31
+ }
32
+
33
+ const variantClass = computed(() => {
34
+ switch (props.variant) {
35
+ case 'error': return 'border-red-200 dark:border-red-900 bg-red-50/50 dark:bg-red-900/10'
36
+ case 'warning': return 'border-amber-200 dark:border-amber-900 bg-amber-50/50 dark:bg-amber-900/10'
37
+ case 'success': return 'border-green-200 dark:border-green-900 bg-green-50/50 dark:bg-green-900/10'
38
+ default: return 'border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900'
39
+ }
40
+ })
41
+ </script>
42
+
43
+ <template>
44
+ <div
45
+ class="va-card rounded-xl border transition-all duration-200 shadow-[0_2px_9px_rgba(0,0,0,0.08)] hover:shadow-[0_4px_12px_rgba(61,113,136,0.12)] hover:-translate-y-0.5"
46
+ :class="variantClass"
47
+ >
48
+ <!-- Header -->
49
+ <div
50
+ v-if="title || $slots['header-actions'] || icon"
51
+ class="flex items-center justify-between"
52
+ :class="[
53
+ padding ? 'px-5 py-4' : 'px-4 py-3',
54
+ divider && !isCollapsed ? 'border-b border-gray-100 dark:border-gray-800' : ''
55
+ ]"
56
+ >
57
+ <div class="flex items-center gap-3 overflow-hidden">
58
+ <slot name="header">
59
+ <div v-if="icon" class="flex-shrink-0 p-1.5 rounded-md bg-gray-100 dark:bg-gray-800 text-gray-500">
60
+ <UIcon :name="icon" class="w-5 h-5" />
61
+ </div>
62
+ <div class="min-w-0">
63
+ <div class="flex items-center gap-2">
64
+ <h3 class="font-bold text-gray-900 dark:text-white truncate text-base">{{ title }}</h3>
65
+
66
+ <button
67
+ v-if="collapsible"
68
+ @click="toggleCollapse"
69
+ class="ml-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors focus:outline-none"
70
+ >
71
+ <UIcon
72
+ name="i-lucide-chevron-down"
73
+ class="w-4 h-4 transition-transform duration-300"
74
+ :class="{ '-rotate-180': !isCollapsed }"
75
+ />
76
+ </button>
77
+ </div>
78
+ <p v-if="description" class="text-xs text-gray-500 dark:text-gray-400 truncate mt-0.5 font-medium">{{ description }}</p>
79
+ </div>
80
+ </slot>
81
+ </div>
82
+
83
+ <!-- XACard uses 'header-actions' slot -->
84
+ <div v-if="$slots['header-actions']" class="flex-shrink-0 flex items-center gap-2 ml-4">
85
+ <slot name="header-actions" />
86
+ </div>
87
+ </div>
88
+
89
+ <!-- Body -->
90
+ <div
91
+ v-show="!isCollapsed"
92
+ class="transition-all duration-300 ease-in-out"
93
+ >
94
+ <div :class="{ 'p-5': padding, 'p-0': !padding }">
95
+ <slot />
96
+ </div>
97
+
98
+ <!-- Footer -->
99
+ <div
100
+ v-if="$slots.footer"
101
+ class="bg-gray-50 dark:bg-gray-800/50 border-t border-gray-100 dark:border-gray-800"
102
+ :class="padding ? 'px-5 py-3' : 'px-4 py-2'"
103
+ >
104
+ <slot name="footer" />
105
+ </div>
106
+ </div>
107
+ </div>
108
+ </template>
@@ -0,0 +1,83 @@
1
+ <script setup lang="ts">
2
+ interface Company {
3
+ label: string
4
+ avatar?: {
5
+ src: string
6
+ alt?: string
7
+ }
8
+ }
9
+
10
+ const props = withDefaults(defineProps<{
11
+ companies?: Company[]
12
+ modelValue?: Company
13
+ collapsed?: boolean
14
+ theme?: 'light' | 'dark'
15
+ }>(), {
16
+ companies: () => [
17
+ {
18
+ label: 'Veristone Capital',
19
+ avatar: {
20
+ src: 'https://api.dicebear.com/7.x/initials/svg?seed=VC&backgroundColor=84cc16',
21
+ alt: 'Veristone Capital'
22
+ }
23
+ }
24
+ ],
25
+ collapsed: false,
26
+ theme: 'light'
27
+ })
28
+
29
+ const buttonUi = computed(() => props.theme === 'dark'
30
+ ? {
31
+ root: 'hover:bg-white/5',
32
+ label: 'text-slate-300 group-hover:text-white',
33
+ trailingIcon: 'text-slate-400 group-hover:text-white'
34
+ }
35
+ : { trailingIcon: 'text-dimmed' }
36
+ )
37
+
38
+ const emit = defineEmits<{
39
+ 'update:modelValue': [company: Company]
40
+ }>()
41
+
42
+ const selectedCompany = computed({
43
+ get: () => props.modelValue || props.companies[0],
44
+ set: (value: Company) => emit('update:modelValue', value)
45
+ })
46
+
47
+ const items = computed(() => {
48
+ return [
49
+ props.companies.map((company) => ({
50
+ ...company,
51
+ onSelect() {
52
+ selectedCompany.value = company
53
+ }
54
+ }))
55
+ ]
56
+ })
57
+ </script>
58
+
59
+ <template>
60
+ <UDropdownMenu
61
+ :items="items"
62
+ :content="{ align: 'center', collisionPadding: 12 }"
63
+ :ui="{
64
+ content: collapsed ? 'w-40' : 'w-(--reka-dropdown-menu-trigger-width)',
65
+ }"
66
+ >
67
+ <UButton
68
+ :avatar="selectedCompany?.avatar"
69
+ :label="collapsed ? undefined : selectedCompany?.label"
70
+ :trailing-icon="collapsed ? undefined : 'i-lucide-chevrons-up-down'"
71
+ color="neutral"
72
+ variant="ghost"
73
+ block
74
+ :square="collapsed"
75
+ :class="[
76
+ 'data-[state=open]:bg-elevated group',
77
+ !collapsed && 'py-2',
78
+ theme === 'dark' && 'data-[state=open]:bg-white/5'
79
+ ]"
80
+ :ui="buttonUi"
81
+ />
82
+ </UDropdownMenu>
83
+ </template>
@@ -0,0 +1,98 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * VADataKeyValue - Veristone Key Value Display
4
+ */
5
+ const props = withDefaults(defineProps<{
6
+ label: string
7
+ value?: any
8
+ type?: 'text' | 'badge' | 'boolean' | 'link' | 'email' | 'phone' | 'currency' | 'date' | 'copy' | 'code'
9
+ layout?: 'horizontal' | 'vertical' | 'inline'
10
+ size?: 'sm' | 'md' | 'lg'
11
+ icon?: string
12
+ emptyText?: string
13
+ currency?: string
14
+ dateFormat?: string
15
+ badgeColor?: string
16
+ linkText?: string
17
+ trueLabel?: string
18
+ falseLabel?: string
19
+ showBooleanLabel?: boolean
20
+ noBorder?: boolean
21
+ }>(), {
22
+ type: 'text',
23
+ layout: 'vertical',
24
+ size: 'md',
25
+ emptyText: '-',
26
+ trueLabel: 'Yes',
27
+ falseLabel: 'No'
28
+ })
29
+
30
+ const displayValue = computed(() => {
31
+ if (props.value === null || props.value === undefined || props.value === '') return props.emptyText
32
+ return props.value
33
+ })
34
+ </script>
35
+
36
+ <template>
37
+ <div
38
+ class="flex gap-2 group"
39
+ :class="{
40
+ 'flex-col items-start': layout === 'vertical',
41
+ 'flex-row items-center justify-between': layout === 'horizontal',
42
+ 'inline-flex items-center': layout === 'inline',
43
+ 'text-sm': size === 'sm',
44
+ 'text-base': size === 'md',
45
+ 'text-lg': size === 'lg',
46
+ 'border-b border-gray-100 dark:border-gray-800 pb-3 mb-3 last:border-0 last:pb-0 last:mb-0': !noBorder && layout === 'horizontal'
47
+ }"
48
+ >
49
+ <dt class="font-medium text-gray-500 dark:text-gray-400 flex items-center gap-1.5">
50
+ <UIcon v-if="icon" :name="icon" class="w-4 h-4 opacity-70" />
51
+ {{ label }}
52
+ </dt>
53
+
54
+ <dd class="font-medium text-gray-900 dark:text-white">
55
+ <slot>
56
+ <!-- Text / Default -->
57
+ <span v-if="type === 'text'">{{ displayValue }}</span>
58
+
59
+ <!-- Boolean -->
60
+ <UBadge
61
+ v-else-if="type === 'boolean'"
62
+ :color="value ? 'green' : 'gray'"
63
+ variant="subtle"
64
+ size="xs"
65
+ >
66
+ {{ value ? trueLabel : falseLabel }}
67
+ </UBadge>
68
+
69
+ <!-- Badge -->
70
+ <UBadge
71
+ v-else-if="type === 'badge'"
72
+ :color="badgeColor || 'gray'"
73
+ variant="subtle"
74
+ size="xs"
75
+ >
76
+ {{ displayValue }}
77
+ </UBadge>
78
+
79
+ <!-- Currency -->
80
+ <VAFmtCurrency
81
+ v-else-if="type === 'currency'"
82
+ :value="Number(value)"
83
+ :currency="currency"
84
+ />
85
+
86
+ <!-- Date -->
87
+ <VAFmtDateTime
88
+ v-else-if="type === 'date'"
89
+ :value="value"
90
+ :format="dateFormat"
91
+ />
92
+
93
+ <!-- Code -->
94
+ <code v-else-if="type === 'code'" class="px-1.5 py-0.5 rounded bg-gray-100 dark:bg-gray-800 font-mono text-xs">{{ displayValue }}</code>
95
+ </slot>
96
+ </dd>
97
+ </div>
98
+ </template>
@@ -0,0 +1,44 @@
1
+ <script setup lang="ts">
2
+ const props = withDefaults(defineProps<{
3
+ status: string
4
+ label?: string
5
+ color?: string
6
+ colorMap?: Record<string, string>
7
+ variant?: string
8
+ size?: string
9
+ }>(), {
10
+ variant: 'subtle',
11
+ size: 'xs',
12
+ colorMap: () => ({
13
+ active: 'green',
14
+ success: 'green',
15
+ published: 'green',
16
+ completed: 'green',
17
+ pending: 'orange',
18
+ warning: 'orange',
19
+ error: 'red',
20
+ failed: 'red',
21
+ danger: 'red',
22
+ deleted: 'red',
23
+ inactive: 'gray',
24
+ draft: 'gray',
25
+ archived: 'gray'
26
+ })
27
+ })
28
+
29
+ const computedColor = computed(() => {
30
+ if (props.color) return props.color
31
+ const key = props.status?.toLowerCase()
32
+ return props.colorMap[key] || 'gray'
33
+ })
34
+
35
+ const computedLabel = computed(() => {
36
+ return props.label || props.status || 'Unknown'
37
+ })
38
+ </script>
39
+
40
+ <template>
41
+ <UBadge :color="computedColor" :variant="variant" :size="size" class="capitalize">
42
+ <slot>{{ computedLabel }}</slot>
43
+ </UBadge>
44
+ </template>