pgo-uiux2 1.0.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 (180) hide show
  1. package/.env +1 -0
  2. package/.env.production +1 -0
  3. package/.prettierrc +13 -0
  4. package/.vscode/extensions.json +3 -0
  5. package/BUTTON_GUIDE.md +257 -0
  6. package/README.md +49 -0
  7. package/THEME_REFERENCE.md +310 -0
  8. package/eslint.config.ts +27 -0
  9. package/index.html +13 -0
  10. package/package.json +85 -0
  11. package/public/favicon.ico +0 -0
  12. package/src/App.vue +368 -0
  13. package/src/assets/fonts/Faruma.ttf +0 -0
  14. package/src/components/examples/AppBarExample.vue +101 -0
  15. package/src/components/examples/AvatarExample.vue +47 -0
  16. package/src/components/examples/BannerExample.vue +287 -0
  17. package/src/components/examples/BaseInputExample.vue +25 -0
  18. package/src/components/examples/BreadcrumbExample.vue +53 -0
  19. package/src/components/examples/CardExample.vue +77 -0
  20. package/src/components/examples/ChipExample.vue +225 -0
  21. package/src/components/examples/DatePickerExample.vue +31 -0
  22. package/src/components/examples/DropdownExample.vue +84 -0
  23. package/src/components/examples/EditorExample.vue +200 -0
  24. package/src/components/examples/ExpansionPanelExample.vue +42 -0
  25. package/src/components/examples/FileUploadExample.vue +40 -0
  26. package/src/components/examples/FormExample.vue +121 -0
  27. package/src/components/examples/HugeTest.vue +8 -0
  28. package/src/components/examples/LayoutContainerExample.vue +80 -0
  29. package/src/components/examples/ModalExample.vue +82 -0
  30. package/src/components/examples/NavDrawerExample.vue +170 -0
  31. package/src/components/examples/NumberFieldExample.vue +145 -0
  32. package/src/components/examples/RadioButtonExample.vue +161 -0
  33. package/src/components/examples/SearchExample.vue +322 -0
  34. package/src/components/examples/SelectExample.vue +121 -0
  35. package/src/components/examples/StackedTableViewExample.vue +53 -0
  36. package/src/components/examples/TabExample.vue +336 -0
  37. package/src/components/examples/TableExample.vue +228 -0
  38. package/src/components/examples/TextFieldExample.vue +181 -0
  39. package/src/components/examples/TextareaExample.vue +173 -0
  40. package/src/components/examples/ThemeToggle.vue +50 -0
  41. package/src/components/examples/TimelineExample.vue +66 -0
  42. package/src/components/examples/TipTapEditorExample.vue +20 -0
  43. package/src/components/examples/TooltipExample.vue +53 -0
  44. package/src/components/examples/VueDatePickerShowcase.vue +214 -0
  45. package/src/components/examples/_DatePickerExample.vue +33 -0
  46. package/src/components/examples/__FormExample.vue +77 -0
  47. package/src/components/index.ts +25 -0
  48. package/src/components/pgo/AppBar.vue +347 -0
  49. package/src/components/pgo/Avatar.vue +139 -0
  50. package/src/components/pgo/Banner.vue +300 -0
  51. package/src/components/pgo/Breadcrumb.vue +101 -0
  52. package/src/components/pgo/Button.vue +171 -0
  53. package/src/components/pgo/Card.vue +178 -0
  54. package/src/components/pgo/ConfirmationModel.vue +32 -0
  55. package/src/components/pgo/DataTable.vue +845 -0
  56. package/src/components/pgo/DatePicker/CalendarPanel.vue +43 -0
  57. package/src/components/pgo/DatePicker/__DatePicker.vue +122 -0
  58. package/src/components/pgo/DatePicker/types.ts +11 -0
  59. package/src/components/pgo/DatePicker/useCalendar.ts +39 -0
  60. package/src/components/pgo/DatePicker/useDatePicker.ts +31 -0
  61. package/src/components/pgo/Deprecated/ToastContainer.vue +51 -0
  62. package/src/components/pgo/Deprecated/ToastItem.vue +55 -0
  63. package/src/components/pgo/Dropdown.vue +296 -0
  64. package/src/components/pgo/DropdownItem.vue +40 -0
  65. package/src/components/pgo/Editor.vue +511 -0
  66. package/src/components/pgo/ExpansionPanel.vue +185 -0
  67. package/src/components/pgo/Footer.vue +39 -0
  68. package/src/components/pgo/HeroIcon.vue +124 -0
  69. package/src/components/pgo/InputSearch.vue +194 -0
  70. package/src/components/pgo/LayoutContainer.vue +104 -0
  71. package/src/components/pgo/Main.vue +37 -0
  72. package/src/components/pgo/Modal.vue +273 -0
  73. package/src/components/pgo/NavDrawer.vue +127 -0
  74. package/src/components/pgo/NavDrawerItem.vue +161 -0
  75. package/src/components/pgo/NavigationDrawer.vue +849 -0
  76. package/src/components/pgo/OLDNavDrawer.vue +661 -0
  77. package/src/components/pgo/OldAppBar.vue +223 -0
  78. package/src/components/pgo/PApp.vue +102 -0
  79. package/src/components/pgo/Pagination.vue +242 -0
  80. package/src/components/pgo/Search copy.vue +310 -0
  81. package/src/components/pgo/Search.vue +411 -0
  82. package/src/components/pgo/StackedTableView.vue +167 -0
  83. package/src/components/pgo/Tab.vue +617 -0
  84. package/src/components/pgo/TestInput.vue +395 -0
  85. package/src/components/pgo/Timeline.vue +367 -0
  86. package/src/components/pgo/TimelineItem.vue +80 -0
  87. package/src/components/pgo/TipTapEditor.vue +315 -0
  88. package/src/components/pgo/Tooltip.NOTES.md +12 -0
  89. package/src/components/pgo/Tooltip.PROPS.md +21 -0
  90. package/src/components/pgo/Tooltip.vue +281 -0
  91. package/src/components/pgo/base/Base.vue +444 -0
  92. package/src/components/pgo/buttons/Chip.vue +324 -0
  93. package/src/components/pgo/buttons/ChipGroup.vue +224 -0
  94. package/src/components/pgo/buttons/Radio.vue +424 -0
  95. package/src/components/pgo/filters/FilterSection.vue +188 -0
  96. package/src/components/pgo/filters/Searchbar.vue +216 -0
  97. package/src/components/pgo/forms/DynamicForm.vue +45 -0
  98. package/src/components/pgo/forms/Form.vue +132 -0
  99. package/src/components/pgo/index.ts +15 -0
  100. package/src/components/pgo/inputs/Checkbox.vue +320 -0
  101. package/src/components/pgo/inputs/DatePicker.vue +395 -0
  102. package/src/components/pgo/inputs/FileUpload.vue +326 -0
  103. package/src/components/pgo/inputs/NumberField.vue +243 -0
  104. package/src/components/pgo/inputs/Radio.vue +162 -0
  105. package/src/components/pgo/inputs/RadioGroup.vue +188 -0
  106. package/src/components/pgo/inputs/Select.vue +535 -0
  107. package/src/components/pgo/inputs/TextField.vue +194 -0
  108. package/src/components/pgo/inputs/Textarea.vue +181 -0
  109. package/src/main.js +12 -0
  110. package/src/pgo-components/_index.js +31 -0
  111. package/src/pgo-components/assets/fonts/Faruma.ttf +0 -0
  112. package/src/pgo-components/assets/fonts/logo.png +0 -0
  113. package/src/pgo-components/composables/useTheme.js +10 -0
  114. package/src/pgo-components/directives/tooltip-directive.ts +393 -0
  115. package/src/pgo-components/index.js +96 -0
  116. package/src/pgo-components/lib/componentConfig.js +147 -0
  117. package/src/pgo-components/lib/core/composables/_useCalendar.ts +127 -0
  118. package/src/pgo-components/lib/core/composables/useDefaults.ts +15 -0
  119. package/src/pgo-components/lib/core/composables/useLanguageSelect.js +0 -0
  120. package/src/pgo-components/lib/core/composables/useRtl.ts +12 -0
  121. package/src/pgo-components/lib/core/defaults/createDefaults.ts +5 -0
  122. package/src/pgo-components/lib/core/defaults/defaults.ts +7 -0
  123. package/src/pgo-components/lib/core/rtl/rtl.ts +3 -0
  124. package/src/pgo-components/lib/core/rtl/setRtl.ts +19 -0
  125. package/src/pgo-components/lib/drawerState.ts +3 -0
  126. package/src/pgo-components/lib/i18n/defaultLables.js +71 -0
  127. package/src/pgo-components/lib/i18n/i18nPlugin.js +52 -0
  128. package/src/pgo-components/lib/i18n/useI18n.js +35 -0
  129. package/src/pgo-components/lib/index.ts +38 -0
  130. package/src/pgo-components/pages/Component.vue +7 -0
  131. package/src/pgo-components/pages/ComponentRenderer.vue +85 -0
  132. package/src/pgo-components/pages/Home.vue +130 -0
  133. package/src/pgo-components/pages/ListView.vue +370 -0
  134. package/src/pgo-components/pages/Page1.vue +296 -0
  135. package/src/pgo-components/pages/_Page1.vue +180 -0
  136. package/src/pgo-components/plugins/SnackBar.vue +251 -0
  137. package/src/pgo-components/plugins/SnackBarContainer.vue +53 -0
  138. package/src/pgo-components/plugins/SnackBarPlugin.ts +136 -0
  139. package/src/pgo-components/plugins/theme-plugin.js +114 -0
  140. package/src/pgo-components/plugins/types.ts +46 -0
  141. package/src/pgo-components/plugins/useSnackBar.js +11 -0
  142. package/src/pgo-components/plugins/useSnackBar.ts +21 -0
  143. package/src/pgo-components/plugins/validation-plugin.js +11 -0
  144. package/src/pgo-components/services/Entry.json +813 -0
  145. package/src/pgo-components/services/axios.js +54 -0
  146. package/src/pgo-components/services/data.json +90 -0
  147. package/src/pgo-components/services/person.json +260 -0
  148. package/src/pgo-components/services/toast.ts +44 -0
  149. package/src/pgo-components/styles/global.css +234 -0
  150. package/src/pgo-components/styles/reset.css +96 -0
  151. package/src/pgo-components/styles/tokens.css +18 -0
  152. package/src/pgo-components/styles/utilities/border-radius.css +57 -0
  153. package/src/pgo-components/styles/utilities/borders.css +85 -0
  154. package/src/pgo-components/styles/utilities/colors.css +38 -0
  155. package/src/pgo-components/styles/utilities/cursor.css +19 -0
  156. package/src/pgo-components/styles/utilities/display.css +78 -0
  157. package/src/pgo-components/styles/utilities/elevation.css +33 -0
  158. package/src/pgo-components/styles/utilities/flex.css +403 -0
  159. package/src/pgo-components/styles/utilities/float.css +41 -0
  160. package/src/pgo-components/styles/utilities/hover.css +9 -0
  161. package/src/pgo-components/styles/utilities/index.css +18 -0
  162. package/src/pgo-components/styles/utilities/opacity.css +27 -0
  163. package/src/pgo-components/styles/utilities/overflow.css +26 -0
  164. package/src/pgo-components/styles/utilities/palette.css +515 -0
  165. package/src/pgo-components/styles/utilities/position.css +14 -0
  166. package/src/pgo-components/styles/utilities/sizing.css +70 -0
  167. package/src/pgo-components/styles/utilities/spacing.css +578 -0
  168. package/src/pgo-components/styles/utilities/transitions.css +58 -0
  169. package/src/pgo-components/styles/utilities/typography.css +91 -0
  170. package/src/pgo-components/styles/utilities/z-index.css +11 -0
  171. package/src/pgo-components/tokens/index.js +337 -0
  172. package/src/router/index.js +88 -0
  173. package/src/shims-vue.d.ts +14 -0
  174. package/src/validations/validationRules.js +50 -0
  175. package/tailwind.config.js +73 -0
  176. package/test.php +5 -0
  177. package/tsconfig.json +25 -0
  178. package/ui +31 -0
  179. package/ui.pgo.mv.conf +18 -0
  180. package/vite.config.js +42 -0
@@ -0,0 +1,617 @@
1
+ <template>
2
+ <div
3
+ class="tabs-container"
4
+ :id="tabId"
5
+ :dir="isRtl ? 'rtl' : 'ltr'"
6
+ :class="[variantClass, sizeClass, containerUtilityClasses, { 'tabs-container--vertical': vertical }]"
7
+ >
8
+ <!-- Tab List -->
9
+ <div class="tabs-list relative overflow-x-auto" role="tablist" :aria-orientation="vertical ? 'vertical' : 'horizontal'" :class="listBorderClasses">
10
+ <div class="tabs-header flex vts-ga-1 vts-pa-1" :class="{ 'flex-col': vertical, 'flex-row': !vertical }">
11
+ <button
12
+ v-for="(tab, index) in tabsComputed"
13
+ :id="`tab-${tabId}-${index}`"
14
+ :key="index"
15
+ role="tab"
16
+ :aria-selected="activeTab === index"
17
+ :aria-controls="`panel-${tabId}-${index}`"
18
+ :tabindex="activeTab === index ? 0 : -1"
19
+ :disabled="tab.disabled || disabled"
20
+ :class="['tabs-item', activeTab === index ? 'tabs-item--active' : '', tab.disabled ? 'tabs-item--disabled' : '', itemClasses, 'vts-rounded-sm', itemBorderSpacingClasses(index)]"
21
+ @click="selectTab(index)"
22
+ @keydown="handleKeydown($event, index)"
23
+ >
24
+ <!-- Icon -->
25
+ <span v-if="tab.icon" class="tabs-item-icon">
26
+ <component :is="tab.icon" class="w-5 h-5" />
27
+ </span>
28
+
29
+ <!-- Label -->
30
+ <span class="tabs-item-label">{{ tab.label }}</span>
31
+
32
+ <!-- Badge -->
33
+ <span v-if="tab.badge" class="tabs-item-badge" :class="badgeClass(tab)">
34
+ {{ tab.badge }}
35
+ </span>
36
+
37
+ <!-- Close button -->
38
+ <button
39
+ v-if="closable && !tab.disabled"
40
+ type="button"
41
+ class="tabs-item-close vts-ms-1 vts-pa-0"
42
+ :aria-label="`Close ${tab.label}`"
43
+ @click.stop="closeTab(index)"
44
+ >
45
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
46
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
47
+ </svg>
48
+ </button>
49
+ </button>
50
+ </div>
51
+
52
+ <!-- Scroll buttons for horizontal tabs -->
53
+ <div v-if="!vertical && showScrollButtons" class="tabs-scroll-buttons absolute top-0 right-0 flex vts-ga-1 vts-pa-1">
54
+ <button v-if="canScrollLeft" type="button" class="tabs-scroll-btn vts-pa-2 vts-rounded-md" @click="scrollTabs(-1)">
55
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
56
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
57
+ </svg>
58
+ </button>
59
+ <button v-if="canScrollRight" type="button" class="tabs-scroll-btn vts-pa-2 vts-rounded-md" @click="scrollTabs(1)">
60
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
61
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
62
+ </svg>
63
+ </button>
64
+ </div>
65
+ </div>
66
+
67
+ <!-- Tab Panels -->
68
+ <div class="tabs-panels">
69
+ <div
70
+ v-for="(tab, index) in tabsComputed"
71
+ :key="index"
72
+ :id="`panel-${tabId}-${index}`"
73
+ role="tabpanel"
74
+ :aria-labelledby="`tab-${tabId}-${index}`"
75
+ :hidden="activeTab !== index"
76
+ class="tabs-panel"
77
+ :class="{ hidden: activeTab !== index }"
78
+ >
79
+ <div class="tabs-panel-content" :class="panelPaddingClass">
80
+ <slot :name="`content-${index}`" :tab="tab">
81
+ {{ tab.content }}
82
+ </slot>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ </div>
87
+ </template>
88
+
89
+ <script setup>
90
+ import { ref, computed, watch, onMounted } from 'vue'
91
+ import { globalRtl } from '../../pgo-components/lib/core/rtl/rtl'
92
+
93
+ const props = defineProps({
94
+ // Data
95
+ tabs: {
96
+ type: Array,
97
+ required: true
98
+ },
99
+ modelValue: {
100
+ type: Number,
101
+ default: 0
102
+ },
103
+
104
+ // Styling
105
+ variant: {
106
+ type: String,
107
+ default: 'line' // 'line' | 'pills' | 'enclosed' | 'card'
108
+ },
109
+ size: {
110
+ type: String,
111
+ default: 'md' // 'sm' | 'md' | 'lg'
112
+ },
113
+ color: {
114
+ type: String,
115
+ default: 'primary'
116
+ },
117
+
118
+ // Behavior
119
+ vertical: {
120
+ type: Boolean,
121
+ default: false
122
+ },
123
+ closable: {
124
+ type: Boolean,
125
+ default: false
126
+ },
127
+ disabled: {
128
+ type: Boolean,
129
+ default: false
130
+ },
131
+ lazy: {
132
+ type: Boolean,
133
+ default: false
134
+ },
135
+ showScrollButtons: {
136
+ type: Boolean,
137
+ default: true
138
+ },
139
+ centered: {
140
+ type: Boolean,
141
+ default: false
142
+ },
143
+ justified: {
144
+ type: Boolean,
145
+ default: false
146
+ },
147
+
148
+ // Accessibility
149
+ ariaLabel: {
150
+ type: String,
151
+ default: ''
152
+ }
153
+ })
154
+
155
+ const emit = defineEmits(['update:modelValue', 'change', 'close', 'keydown'])
156
+ // Map optional badgeVariant hints on tab items to token-based classes
157
+ const badgeClass = tab => {
158
+ const v = String(tab?.badgeVariant || '').toLowerCase()
159
+ if (v.includes('error') || v.includes('red')) return 'badge--error'
160
+ if (v.includes('primary') || v.includes('blue')) return 'badge--primary'
161
+ return 'badge--primary'
162
+ }
163
+
164
+ // Work around template type inference for tab items
165
+ const tabsComputed = computed(() => /** @type {any[]} */ (props.tabs))
166
+
167
+ // Generate unique ID for this tab component
168
+ const tabId = ref(`tabs-${Math.random().toString(36).substr(2, 9)}`)
169
+
170
+ // Active tab state
171
+ const activeTab = ref(props.modelValue)
172
+
173
+ // Scroll state for horizontal tabs
174
+ const scrollContainer = ref(null)
175
+ const canScrollLeft = ref(false)
176
+ const canScrollRight = ref(false)
177
+
178
+ // RTL support
179
+ const isRtl = computed(() => globalRtl.value)
180
+
181
+ // Computed classes
182
+ const variantClass = computed(() => {
183
+ const variantMap = {
184
+ line: 'variant-line',
185
+ pills: 'variant-pills',
186
+ enclosed: 'variant-enclosed',
187
+ card: 'variant-card'
188
+ }
189
+ return variantMap[props.variant] || variantMap.line
190
+ })
191
+
192
+ const sizeClass = computed(() => {
193
+ const sizeMap = {
194
+ sm: 'size-sm',
195
+ md: 'size-md',
196
+ lg: 'size-lg'
197
+ }
198
+ return sizeMap[props.size] || sizeMap.md
199
+ })
200
+
201
+ const itemClasses = computed(() => {
202
+ const classes = [
203
+ 'whitespace-nowrap inline-flex items-center vts-ga-2 transition-colors',
204
+ `text-${props.size === 'sm' ? 'sm' : props.size === 'lg' ? 'lg' : 'base'}`
205
+ ]
206
+
207
+ if (props.centered) classes.push('justify-center')
208
+ if (props.justified) classes.push('flex-1')
209
+ if (props.vertical) classes.push('w-full justify-start')
210
+
211
+ return classes.join(' ')
212
+ })
213
+
214
+ // Container utilities for variant-specific borders/spacing
215
+ const containerUtilityClasses = computed(() => {
216
+ const classes = []
217
+ if (props.variant === 'enclosed') classes.push('vts-border', 'vts-rounded-md')
218
+ if (props.variant === 'card') classes.push('vts-ga-4')
219
+ return classes.join(' ')
220
+ })
221
+
222
+ // List border depending on orientation
223
+ const listBorderClasses = computed(() => {
224
+ if (props.vertical) return 'vts-border-e-sm vts-border-solid vts-border-color'
225
+ return 'vts-border-b-sm vts-border-solid vts-border-color'
226
+ })
227
+
228
+ // Per-item spacing and border utilities
229
+ const itemBorderSpacingClasses = index => {
230
+ const classes = []
231
+
232
+ // Size-based padding
233
+ const sizePadMap = {
234
+ sm: ['vts-py-2', 'vts-px-3'],
235
+ md: ['vts-py-3', 'vts-px-4'],
236
+ lg: ['vts-py-4', 'vts-px-5']
237
+ }
238
+ if (props.variant === 'pills' && props.size === 'md') {
239
+ classes.push('vts-py-2', 'vts-px-4')
240
+ } else {
241
+ const pad = sizePadMap[props.size] || sizePadMap.md
242
+ classes.push(...pad)
243
+ }
244
+
245
+ // Variant-specific borders
246
+ if (props.variant === 'line') {
247
+ classes.push('vts-border-b-md', 'vts-border-solid', 'vts-border-color-transparent', 'vts-mb-n1')
248
+ if (props.vertical) classes.push('vts-border-b-0')
249
+ }
250
+
251
+ if (props.variant === 'enclosed') {
252
+ classes.push('vts-border-e-sm', 'vts-border-solid', 'vts-border-color')
253
+ if (index === tabsComputed.value.length - 1) classes.push('vts-border-e-0')
254
+ }
255
+
256
+ if (props.variant === 'card') {
257
+ classes.push('vts-border-sm', 'vts-border-solid', 'vts-border-color', 'vts-rounded-md')
258
+ }
259
+
260
+ if (props.vertical) {
261
+ classes.push('w-full', 'justify-start')
262
+ }
263
+
264
+ return classes.join(' ')
265
+ }
266
+
267
+ // Panel padding per size and layout
268
+ const panelPaddingClass = computed(() => {
269
+ if (props.vertical) return 'vts-py-4 vts-px-6'
270
+ const map = { sm: 'vts-pa-4', md: 'vts-pa-6', lg: 'vts-pa-8' }
271
+ return map[props.size] || map.md
272
+ })
273
+
274
+ // Methods
275
+ const selectTab = index => {
276
+ if (tabsComputed.value[index]?.disabled || props.disabled) return
277
+
278
+ activeTab.value = index
279
+ emit('update:modelValue', index)
280
+ emit('change', index)
281
+ }
282
+
283
+ const closeTab = index => {
284
+ emit('close', index)
285
+ }
286
+
287
+ const handleKeydown = (event, index) => {
288
+ emit('keydown', event, index)
289
+
290
+ const tabsCount = tabsComputed.value.length
291
+
292
+ switch (event.key) {
293
+ case 'ArrowDown':
294
+ if (props.vertical) {
295
+ event.preventDefault()
296
+ selectTab((index + 1) % tabsCount)
297
+ return
298
+ }
299
+ break
300
+ case 'ArrowUp':
301
+ if (props.vertical) {
302
+ event.preventDefault()
303
+ selectTab((index - 1 + tabsCount) % tabsCount)
304
+ return
305
+ }
306
+ break
307
+ case 'ArrowRight':
308
+ if (isRtl.value) {
309
+ event.preventDefault()
310
+ selectTab((index - 1 + tabsCount) % tabsCount)
311
+ } else {
312
+ event.preventDefault()
313
+ selectTab((index + 1) % tabsCount)
314
+ }
315
+ break
316
+ case 'ArrowLeft':
317
+ if (isRtl.value) {
318
+ event.preventDefault()
319
+ selectTab((index + 1) % tabsCount)
320
+ } else {
321
+ event.preventDefault()
322
+ selectTab((index - 1 + tabsCount) % tabsCount)
323
+ }
324
+ break
325
+ case 'Home':
326
+ event.preventDefault()
327
+ selectTab(0)
328
+ break
329
+ case 'End':
330
+ event.preventDefault()
331
+ selectTab(tabsCount - 1)
332
+ break
333
+ }
334
+ }
335
+
336
+ const scrollTabs = direction => {
337
+ if (!scrollContainer.value) return
338
+ const scrollAmount = 200
339
+ const newScroll = scrollContainer.value.scrollLeft + direction * scrollAmount
340
+ scrollContainer.value.scrollLeft = newScroll
341
+ updateScrollButtons()
342
+ }
343
+
344
+ const updateScrollButtons = () => {
345
+ if (!scrollContainer.value || props.vertical) {
346
+ canScrollLeft.value = false
347
+ canScrollRight.value = false
348
+ return
349
+ }
350
+
351
+ const { scrollLeft, scrollWidth, clientWidth } = scrollContainer.value
352
+ canScrollLeft.value = scrollLeft > 0
353
+ canScrollRight.value = scrollLeft < scrollWidth - clientWidth - 10
354
+ }
355
+
356
+ // Watch for model value changes
357
+ watch(
358
+ () => props.modelValue,
359
+ newValue => {
360
+ activeTab.value = newValue
361
+ }
362
+ )
363
+
364
+ // Lifecycle
365
+ onMounted(() => {
366
+ scrollContainer.value = document.querySelector(`#${tabId.value} .tabs-list`)
367
+ updateScrollButtons()
368
+ })
369
+ </script>
370
+
371
+ <style scoped>
372
+ /* Base styles */
373
+ .tabs-container {
374
+ display: flex;
375
+ flex-direction: column;
376
+ }
377
+
378
+ .tabs-list {
379
+ position: relative;
380
+ }
381
+
382
+ .tabs- {
383
+ display: flex;
384
+ /* spacing moved to vts-ga-* in template */
385
+ }
386
+
387
+ .tabs-item {
388
+ position: relative;
389
+ background: transparent;
390
+ cursor: pointer;
391
+ font-weight: 500;
392
+ transition: all 0.2s ease;
393
+ display: inline-flex;
394
+ align-items: center;
395
+ color: var(--vts-color-textSecondary);
396
+ }
397
+
398
+ .tabs-item:hover:not(:disabled) {
399
+ color: var(--vts-color-text);
400
+ }
401
+
402
+ .tabs-item:disabled,
403
+ .tabs-item--disabled {
404
+ opacity: 0.5;
405
+ cursor: not-allowed;
406
+ }
407
+
408
+ .tabs-item--active {
409
+ color: var(--vts-color-text);
410
+ font-weight: 600;
411
+ }
412
+
413
+ /* Dark mode colors handled by CSS variables */
414
+
415
+ /* Icon styling */
416
+ .tabs-item-icon {
417
+ display: inline-flex;
418
+ align-items: center;
419
+ justify-content: center;
420
+ }
421
+
422
+ /* Badge styling */
423
+ .tabs-item-badge {
424
+ display: inline-flex;
425
+ align-items: center;
426
+ justify-content: center;
427
+ min-width: 1.25rem;
428
+ height: 1.25rem;
429
+ border-radius: var(--vts-radius-full);
430
+ font-size: 0.75rem;
431
+ font-weight: 600;
432
+ color: var(--vts-color-primaryText);
433
+ background: var(--vts-color-error);
434
+ }
435
+
436
+ .tabs-item-badge.badge--primary {
437
+ background: var(--vts-color-primary);
438
+ color: var(--vts-color-primaryText);
439
+ }
440
+
441
+ .tabs-item-badge.badge--error {
442
+ background: var(--vts-color-error);
443
+ }
444
+
445
+ /* Close button */
446
+ .tabs-item-close {
447
+ display: inline-flex;
448
+ align-items: center;
449
+ justify-content: center;
450
+ border: none;
451
+ background: transparent;
452
+ cursor: pointer;
453
+ color: inherit;
454
+ opacity: 0.7;
455
+ transition: opacity 0.2s ease;
456
+ }
457
+
458
+ .tabs-item-close:hover {
459
+ opacity: 1;
460
+ }
461
+
462
+ /* Scroll buttons */
463
+ .tabs-scroll-buttons {
464
+ display: flex;
465
+ /* spacing moved to vts-ga-* in template */
466
+ }
467
+
468
+ .tabs-scroll-btn {
469
+ display: inline-flex;
470
+ align-items: center;
471
+ justify-content: center;
472
+ border: none;
473
+ background: transparent;
474
+ cursor: pointer;
475
+ color: var(--vts-color-textSecondary);
476
+ transition: all 0.2s ease;
477
+ }
478
+
479
+ .tabs-scroll-btn:hover {
480
+ color: var(--vts-color-text);
481
+ background: var(--vts-color-surface);
482
+ }
483
+ /* Dark mode via variables */
484
+
485
+ /* Panels */
486
+ .tabs-panels {
487
+ flex: 1;
488
+ }
489
+
490
+ .tabs-panel {
491
+ display: block;
492
+ }
493
+
494
+ .tabs-panel.hidden {
495
+ display: none;
496
+ }
497
+
498
+
499
+
500
+ /* Variant: Line (default) */
501
+
502
+
503
+ .variant-line .tabs-item--active {
504
+ border-bottom-color: var(--vts-color-primary);
505
+ }
506
+ /* Dark handled by variables */
507
+
508
+ /* Variant: Pills */
509
+ .variant-pills .tabs-item {
510
+ background: var(--vts-color-surfaceElevated);
511
+ /* spacing and radius moved to vts-* classes */
512
+ }
513
+
514
+ .variant-pills .tabs-item:hover:not(:disabled) {
515
+ background: var(--vts-color-surface);
516
+ }
517
+
518
+ .variant-pills .tabs-item--active {
519
+ background: var(--vts-color-primary);
520
+ color: var(--vts-color-primaryText);
521
+ }
522
+ /* Dark handled by variables */
523
+
524
+ /* Variant: Enclosed */
525
+ .variant-enclosed {
526
+ /* border and radius moved to vts-* classes */
527
+ overflow: hidden;
528
+ }
529
+
530
+ .variant-enclosed .tabs-item {
531
+ border-radius: 0;
532
+ /* spacing and right border moved to vts-* classes */
533
+ }
534
+
535
+
536
+
537
+ .variant-enclosed .tabs-item--active {
538
+ background: var(--vts-color-surfaceElevated);
539
+ color: var(--vts-color-primary);
540
+ }
541
+ /* Dark handled by variables */
542
+
543
+ /* Variant: Card */
544
+ .variant-card {
545
+ display: flex;
546
+ flex-direction: column;
547
+ /* spacing moved to vts-* classes */
548
+ }
549
+
550
+ .variant-card .tabs- {
551
+ display: flex;
552
+ flex-wrap: wrap;
553
+ }
554
+
555
+ .variant-card .tabs-item {
556
+ background: var(--vts-color-surface);
557
+ }
558
+
559
+ .variant-card .tabs-item:hover:not(:disabled) {
560
+ border-color: var(--vts-color-primary);
561
+ background: var(--vts-color-surfaceElevated);
562
+ }
563
+
564
+ .variant-card .tabs-item--active {
565
+ border-color: var(--vts-color-primary);
566
+ background: var(--vts-color-primary);
567
+ color: var(--vts-color-primaryText);
568
+ }
569
+ /* Dark handled by variables */
570
+
571
+ /* Size variants */
572
+ .size-sm .tabs-item {
573
+ font-size: 0.875rem;
574
+ }
575
+
576
+
577
+
578
+ .size-lg .tabs-item {
579
+ font-size: 1.125rem;
580
+ }
581
+
582
+
583
+
584
+ /* Vertical layout */
585
+
586
+
587
+ /* Two-column vertical layout */
588
+ .tabs-container--vertical {
589
+ display: grid;
590
+ grid-template-columns: minmax(200px, 260px) 1fr;
591
+ align-items: start;
592
+ /* spacing moved to vts-* classes if needed */
593
+ }
594
+
595
+ .tabs-container--vertical .tabs-list {
596
+ /* borders moved to vts-* classes */
597
+ overflow-y: auto;
598
+ max-height: 100%;
599
+ }
600
+
601
+
602
+
603
+ .tabs-container--vertical .tabs- {
604
+ flex-direction: column;
605
+ }
606
+
607
+ .tabs-container--vertical .tabs-item {
608
+ width: 100%;
609
+ justify-content: flex-start;
610
+ }
611
+
612
+ .tabs-container--vertical .tabs-panels {
613
+ min-width: 0; /* allow content to shrink */
614
+ }
615
+
616
+
617
+ </style>