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,139 @@
1
+ <template>
2
+ <div
3
+ class="avatar"
4
+ :class="[
5
+ sizeClass,
6
+ shapeClass,
7
+ bordered ? 'vts-border-sm vts-border-solid vts-border-color' : 'vts-border-0',
8
+ disabled ? 'avatar--disabled' : ''
9
+ ]"
10
+ :aria-label="ariaLabelComputed"
11
+ >
12
+ <template v-if="showImage">
13
+ <img
14
+ :src="src"
15
+ :alt="alt || ariaLabelComputed"
16
+ class="avatar-img"
17
+ @error="onImgError"
18
+ />
19
+ </template>
20
+ <template v-else>
21
+ <div class="avatar-fallback" aria-hidden="true">{{ initials }}</div>
22
+ </template>
23
+
24
+ <span v-if="status" class="avatar-status vts-border-md vts-border-solid" :class="`status--${status}`" aria-hidden="true" />
25
+
26
+ <slot />
27
+ </div>
28
+ </template>
29
+
30
+ <script setup lang="ts">
31
+ import { computed, ref } from 'vue'
32
+
33
+ export type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
34
+ export type AvatarShape = 'circular' | 'rounded' | 'square'
35
+ export type AvatarStatus = 'online' | 'away' | 'busy' | 'offline'
36
+
37
+ const props = defineProps<{
38
+ src?: string
39
+ alt?: string
40
+ text?: string
41
+ size?: AvatarSize
42
+ shape?: AvatarShape
43
+ bordered?: boolean
44
+ status?: AvatarStatus
45
+ disabled?: boolean
46
+ ariaLabel?: string
47
+ }>()
48
+
49
+ const imgError = ref(false)
50
+ const showImage = computed(() => !!props.src && !imgError.value)
51
+
52
+ function onImgError() {
53
+ imgError.value = true
54
+ }
55
+
56
+ function computeInitials(text?: string) {
57
+ if (!text) return '?'
58
+ const clean = text.trim()
59
+ if (!clean) return '?'
60
+ const parts = clean.split(/\s+/).filter(Boolean)
61
+ const first = parts[0]?.[0] || ''
62
+ const last = parts.length > 1 ? parts[parts.length - 1]?.[0] : ''
63
+ return (first + last).toUpperCase()
64
+ }
65
+
66
+ const initials = computed(() => computeInitials(props.text))
67
+
68
+ const sizeClass = computed(() => `avatar--${props.size || 'md'}`)
69
+ const shapeClass = computed(() => {
70
+ const shape = props.shape || 'circular'
71
+ return `avatar-shape--${shape}`
72
+ })
73
+
74
+ const ariaLabelComputed = computed(() => props.ariaLabel || props.alt || props.text || 'Avatar')
75
+ </script>
76
+
77
+ <style scoped>
78
+ .avatar {
79
+ position: relative;
80
+ display: inline-flex;
81
+ align-items: center;
82
+ justify-content: center;
83
+ background-color: var(--vts-color-surfaceElevated);
84
+ color: var(--vts-color-text);
85
+ overflow: hidden;
86
+ user-select: none;
87
+ }
88
+
89
+ .avatar--disabled {
90
+ opacity: 0.6;
91
+ filter: grayscale(0.1);
92
+ }
93
+
94
+ /* Sizes */
95
+ .avatar--xs { width: 24px; height: 24px; font-size: 0.75rem; }
96
+ .avatar--sm { width: 32px; height: 32px; font-size: 0.75rem; }
97
+ .avatar--md { width: 40px; height: 40px; font-size: 0.875rem; }
98
+ .avatar--lg { width: 48px; height: 48px; font-size: 1rem; }
99
+ .avatar--xl { width: 64px; height: 64px; font-size: 1.25rem; }
100
+
101
+ /* Shapes */
102
+ .avatar-shape--circular { border-radius: 9999px; }
103
+ .avatar-shape--rounded { border-radius: var(--vts-radius-md); }
104
+ .avatar-shape--square { border-radius: 0; }
105
+
106
+ .avatar-img {
107
+ width: 100%;
108
+ height: 100%;
109
+ object-fit: cover;
110
+ display: block;
111
+ }
112
+
113
+ .avatar-fallback {
114
+ width: 100%;
115
+ height: 100%;
116
+ display: flex;
117
+ align-items: center;
118
+ justify-content: center;
119
+ font-weight: 600;
120
+ }
121
+
122
+ /* Status dot */
123
+ .avatar-status {
124
+ position: absolute;
125
+ right: -2px;
126
+ bottom: -2px;
127
+ width: 10px;
128
+ height: 10px;
129
+ border-radius: 9999px;
130
+ /* border set via utilities; keep color here */
131
+ border-color: var(--vts-color-surfaceElevated);
132
+ box-shadow: 0 0 0 1px var(--vts-color-border);
133
+ }
134
+
135
+ .status--online { background-color: var(--vts-color-success, var(--vts-color-primary)); }
136
+ .status--away { background-color: var(--vts-color-warning, var(--vts-color-primary)); }
137
+ .status--busy { background-color: var(--vts-color-error, var(--vts-color-primary)); }
138
+ .status--offline { background-color: var(--vts-color-border); }
139
+ </style>
@@ -0,0 +1,300 @@
1
+ <template>
2
+ <transition
3
+ enter-active-class="transition ease-out duration-300"
4
+ enter-from-class="transform translate-y-[-100%] opacity-0"
5
+ enter-to-class="transform translate-y-0 opacity-100"
6
+ leave-active-class="transition ease-in duration-200"
7
+ leave-from-class="transform translate-y-0 opacity-100"
8
+ leave-to-class="transform translate-y-[-100%] opacity-0"
9
+ >
10
+ <div
11
+ v-if="isVisible"
12
+ :class="bannerClasses"
13
+ role="alert"
14
+ >
15
+ <div :class="contentContainerClasses">
16
+ <!-- Icon/Prepend -->
17
+ <div v-if="$slots.prepend || icon" class="flex-shrink-0">
18
+ <slot name="prepend">
19
+ <component :is="iconComponent" v-if="icon" class="h-5 w-5" />
20
+ </slot>
21
+ </div>
22
+
23
+ <!-- Content -->
24
+ <div class="flex-1 min-w-0">
25
+ <!-- Title -->
26
+ <div v-if="title || $slots.title" :class="titleClasses">
27
+ <slot name="title">{{ title }}</slot>
28
+ </div>
29
+
30
+ <!-- Message -->
31
+ <div v-if="message || $slots.default" :class="messageClasses">
32
+ <slot>{{ message }}</slot>
33
+ </div>
34
+ </div>
35
+
36
+ <!-- Actions -->
37
+ <div v-if="$slots.actions || closeable" class="flex items-center gap-2">
38
+ <slot name="actions" />
39
+
40
+ <!-- Close Button -->
41
+ <button
42
+ v-if="closeable"
43
+ @click="close"
44
+ :class="closeButtonClasses"
45
+ aria-label="Close banner"
46
+ >
47
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
48
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
49
+ </svg>
50
+ </button>
51
+ </div>
52
+ </div>
53
+
54
+ <!-- Progress Bar (for auto-dismiss) -->
55
+ <div
56
+ v-if="timeout && showProgress"
57
+ :class="progressBarClasses"
58
+ :style="progressStyle"
59
+ ></div>
60
+ </div>
61
+ </transition>
62
+ </template>
63
+
64
+ <script setup>
65
+ import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
66
+ import { sizes, iconSizes, roundedMap, colorMap } from '../../pgo-components/lib/componentConfig'
67
+
68
+ const props = defineProps({
69
+ modelValue: { type: Boolean, default: true },
70
+ title: { type: String, default: '' },
71
+ message: { type: String, default: '' },
72
+ color: { type: String, default: 'primary' },
73
+ variant: { type: String, default: 'contained' },
74
+ icon: { type: String, default: '' }, // info | success | warning | error | custom
75
+ closeable: { type: Boolean, default: true },
76
+ sticky: { type: Boolean, default: false },
77
+ position: { type: String, default: 'top' }, // top | bottom
78
+ timeout: { type: Number, default: 0 }, // Auto-dismiss after X ms (0 = no auto-dismiss)
79
+ showProgress: { type: Boolean, default: false },
80
+ rounded: { type: String, default: 'none' }, // none | sm | md | lg
81
+ elevation: { type: Boolean, default: true },
82
+ outlined: { type: Boolean, default: false },
83
+ dense: { type: Boolean, default: false },
84
+ })
85
+
86
+ const emit = defineEmits(['update:modelValue', 'close', 'dismiss'])
87
+
88
+ const isVisible = ref(props.modelValue)
89
+ const progress = ref(100)
90
+ let timeoutId = null
91
+ let intervalId = null
92
+
93
+ // Watch modelValue changes
94
+ watch(() => props.modelValue, (val) => {
95
+ isVisible.value = val
96
+ if (val && props.timeout) {
97
+ startTimeout()
98
+ }
99
+ })
100
+
101
+ // Icon component mapping
102
+ const iconComponent = computed(() => {
103
+ const icons = {
104
+ info: InfoIcon,
105
+ success: SuccessIcon,
106
+ warning: WarningIcon,
107
+ error: ErrorIcon,
108
+ }
109
+ return icons[props.icon] || null
110
+ })
111
+
112
+ // Banner classes
113
+ const bannerClasses = computed(() => {
114
+ const base = [
115
+ 'w-full relative',
116
+ props.sticky ? 'sticky z-45' : 'relative',
117
+ ]
118
+
119
+ // Position
120
+ if (props.sticky) {
121
+ base.push(props.position === 'bottom' ? 'bottom-0' : 'top-0')
122
+ }
123
+
124
+ // Variant colors
125
+ // const variantMap = {
126
+ // primary: 'bg-primary border-primary text-white',
127
+ // primaryLight: 'bg-primary/80 border-primary/15 text-primary',
128
+ // default: 'bg-background border-border text-foreground',
129
+ // info: 'bg-blue-50 border-blue-200 text-blue-900',
130
+ // success: 'bg-green-50 border-green-200 text-green-900',
131
+ // warning: 'bg-yellow-50 border-yellow-200 text-yellow-900',
132
+ // error: 'bg-red-50 border-red-200 text-red-900',
133
+ // }
134
+
135
+ base.push(colorMap[props.color][props.variant])
136
+
137
+ // Border
138
+ if (props.outlined) {
139
+ base.push('border-t-4')
140
+ } else {
141
+ // base.push('border')
142
+ }
143
+
144
+ // Elevation
145
+ if (props.elevation) {
146
+ base.push('shadow-md')
147
+ }
148
+
149
+ // Rounded
150
+ const roundedMap = {
151
+ none: '',
152
+ sm: 'rounded-sm',
153
+ md: 'rounded-md',
154
+ lg: 'rounded-lg',
155
+ }
156
+ base.push(roundedMap[props.rounded])
157
+
158
+ return base
159
+ })
160
+
161
+ // Content container classes
162
+ const contentContainerClasses = computed(() => [
163
+ 'flex items-start gap-3',
164
+ props.dense ? 'p-3 px-6' : 'p-4',
165
+ ])
166
+
167
+ // Title classes
168
+ const titleClasses = computed(() => [
169
+ 'font-semibold',
170
+ props.dense ? 'text-sm' : 'text-base',
171
+ ])
172
+
173
+ // Message classes
174
+ const messageClasses = computed(() => [
175
+ props.dense ? 'text-xs' : 'text-sm',
176
+ props.title ? 'mt-1' : '',
177
+ ])
178
+
179
+ // Close button classes
180
+ const closeButtonClasses = computed(() => [
181
+ 'flex-shrink-0 rounded-md p-1.5',
182
+ 'hover:bg-black/5 dark:hover:bg-white/10',
183
+ 'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
184
+ 'transition-colors',
185
+ ])
186
+
187
+ // Progress bar classes
188
+ const progressBarClasses = computed(() => {
189
+ const variantMap = {
190
+ default: 'bg-gray-400',
191
+ info: 'bg-blue-400',
192
+ success: 'bg-green-400',
193
+ warning: 'bg-yellow-400',
194
+ error: 'bg-red-400',
195
+ }
196
+
197
+ return [
198
+ 'absolute bottom-0 left-0 h-1 transition-all duration-100',
199
+ variantMap[props.variant]
200
+ ]
201
+ })
202
+
203
+ const progressStyle = computed(() => ({
204
+ width: `${progress.value}%`,
205
+ }))
206
+
207
+ // Methods
208
+ const close = () => {
209
+ isVisible.value = false
210
+ emit('update:modelValue', false)
211
+ emit('close')
212
+ clearTimers()
213
+ }
214
+
215
+ const startTimeout = () => {
216
+ if (!props.timeout) return
217
+
218
+ clearTimers()
219
+
220
+ const startTime = Date.now()
221
+ const duration = props.timeout
222
+
223
+ if (props.showProgress) {
224
+ intervalId = setInterval(() => {
225
+ const elapsed = Date.now() - startTime
226
+ progress.value = Math.max(0, 100 - (elapsed / duration) * 100)
227
+ }, 10)
228
+ }
229
+
230
+ timeoutId = setTimeout(() => {
231
+ close()
232
+ emit('dismiss')
233
+ }, props.timeout)
234
+ }
235
+
236
+ const clearTimers = () => {
237
+ if (timeoutId) {
238
+ clearTimeout(timeoutId)
239
+ timeoutId = null
240
+ }
241
+ if (intervalId) {
242
+ clearInterval(intervalId)
243
+ intervalId = null
244
+ }
245
+ progress.value = 100
246
+ }
247
+
248
+ // Lifecycle
249
+ onMounted(() => {
250
+ if (isVisible.value && props.timeout) {
251
+ startTimeout()
252
+ }
253
+ })
254
+
255
+ onBeforeUnmount(() => {
256
+ clearTimers()
257
+ })
258
+
259
+ // Expose methods
260
+ defineExpose({ close })
261
+
262
+ // Icon Components
263
+ const InfoIcon = {
264
+ template: `
265
+ <svg fill="currentColor" viewBox="0 0 20 20" class="h-5 w-5">
266
+ <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
267
+ </svg>
268
+ `
269
+ }
270
+
271
+ const SuccessIcon = {
272
+ template: `
273
+ <svg fill="currentColor" viewBox="0 0 20 20" class="h-5 w-5">
274
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
275
+ </svg>
276
+ `
277
+ }
278
+
279
+ const WarningIcon = {
280
+ template: `
281
+ <svg fill="currentColor" viewBox="0 0 20 20" class="h-5 w-5">
282
+ <path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
283
+ </svg>
284
+ `
285
+ }
286
+
287
+ const ErrorIcon = {
288
+ template: `
289
+ <svg fill="currentColor" viewBox="0 0 20 20" class="h-5 w-5">
290
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
291
+ </svg>
292
+ `
293
+ }
294
+ </script>
295
+
296
+ <style scoped>
297
+ .sticky {
298
+ position: sticky;
299
+ }
300
+ </style>
@@ -0,0 +1,101 @@
1
+ <template>
2
+ <nav
3
+ aria-label="Breadcrumb"
4
+ :class="['flex items-center text-sm text-[var(--vts-color-textSecondary)]', containerClass]"
5
+ >
6
+ <!-- Prepend slot -->
7
+ <slot name="prepend" />
8
+
9
+ <ol class="flex items-center">
10
+ <li
11
+ v-for="(item, index) in itemsFinal"
12
+ :key="index"
13
+ class="flex items-center"
14
+ >
15
+ <!-- Divider -->
16
+ <span
17
+ v-if="index !== 0"
18
+ :class="['vts-mx-2 select-none text-[var(--vts-color-textSecondary)]', dividerClass]"
19
+ >
20
+ <slot name="divider">
21
+ {{ isRTL ? '‹' : '›' }}
22
+ </slot>
23
+ </span>
24
+
25
+ <!-- Item -->
26
+ <span
27
+ :class="[
28
+ 'flex items-center',
29
+ itemClass,
30
+ index === itemsFinal.length - 1 && 'font-medium text-[var(--vts-color-text)]',
31
+ index === itemsFinal.length - 1 && activeClass,
32
+ item.disabled && 'opacity-50 cursor-not-allowed'
33
+ ]"
34
+ >
35
+ <component
36
+ :is="item.to && !item.disabled ? 'RouterLink' : (item.href && !item.disabled ? 'a' : 'span')"
37
+ :to="item.to"
38
+ :href="item.href"
39
+ class="hover:underline"
40
+ >
41
+ {{ item.label }}
42
+ </component>
43
+ </span>
44
+ </li>
45
+ </ol>
46
+ </nav>
47
+ </template>
48
+
49
+ <script setup>
50
+
51
+ import { computed } from 'vue'
52
+ import { useRoute } from 'vue-router'
53
+ defineOptions({ name: 'PgoBreadcrumb' })
54
+
55
+ const props = defineProps({
56
+ items: { type: Array, default: () => [] },
57
+ containerClass: { type: String, default: '' },
58
+ itemClass: { type: String, default: '' },
59
+ activeClass: { type: String, default: '' },
60
+ dividerClass: { type: String, default: '' }
61
+ })
62
+
63
+ const isRTL = computed(() => {
64
+ if (typeof window === 'undefined') return false
65
+ return document.documentElement.dir === 'rtl' || document.documentElement.lang === 'dv'
66
+ })
67
+
68
+ const route = useRoute()
69
+
70
+ /** @type {import('vue').ComputedRef<Array<Record<string, any>>>} */
71
+ const routeItems = computed(() => {
72
+ const matched = route.matched || []
73
+ const items = []
74
+ matched.forEach((rec, idx) => {
75
+ const bc = rec.meta && rec.meta.breadcrumb
76
+
77
+ // Handle function-based breadcrumb
78
+ const breadcrumbData = typeof bc === 'function' ? bc(route) : bc
79
+
80
+ if (Array.isArray(breadcrumbData)) {
81
+ breadcrumbData.forEach((it) => {
82
+ if (typeof it === 'string') {
83
+ items.push({ label: it })
84
+ } else if (it && typeof it === 'object') {
85
+ items.push({ label: it.label, to: it.to, href: it.href, disabled: !!it.disabled })
86
+ }
87
+ })
88
+ } else {
89
+ const label = breadcrumbData || rec.meta?.title || rec.name || rec.path
90
+ const to = idx < matched.length - 1 ? rec.path : undefined
91
+ items.push({ label, to })
92
+ }
93
+ })
94
+ return items
95
+ })
96
+
97
+ /** @type {import('vue').ComputedRef<Array<Record<string, any>>>} */
98
+ const itemsFinal = computed(() => {
99
+ return props.items && props.items.length ? props.items : routeItems.value
100
+ })
101
+ </script>
@@ -0,0 +1,171 @@
1
+ <template>
2
+ <button :class="buttonClasses" :disabled="disabled || loading" @click="handleClick">
3
+ <!-- Loader -->
4
+ <span v-if="loading" class="loader vts-mr-2" />
5
+
6
+ <!-- Prepend Icon -->
7
+ <span v-if="($slots.prepend || prependIcon) && !loading" class="">
8
+ <slot name="prepend">
9
+ <HeroIcon v-if="prependIcon" :name="prependIcon" :type="iconType || 'outline'" :size="iconSizes[props.size]" color="text-current" />
10
+ </slot>
11
+ </span>
12
+
13
+ <!-- Label -->
14
+ <span v-if="!loading">
15
+ <slot>
16
+ <HeroIcon v-if="icon" :name="icon" :type="iconType || 'outline'" :size="iconSizes[props.size]" color="text-current" />
17
+ <div v-if="!icon">
18
+ {{ displayLabel }}
19
+ </div>
20
+ </slot>
21
+ </span>
22
+
23
+ <!-- Append Icon -->
24
+ <span v-if="($slots.append || appendIcon) && !loading" class="">
25
+ <slot name="append">
26
+ <HeroIcon
27
+ v-if="appendIcon"
28
+ :name="appendIcon"
29
+ :type="iconType || 'outline'"
30
+ :size="iconSizes[props.size]"
31
+ stroke="currentColor"
32
+ color="text-current"
33
+ />
34
+ </slot>
35
+ </span>
36
+ </button>
37
+ </template>
38
+
39
+ <script setup>
40
+ import { computed, inject, provide, ref } from 'vue'
41
+ import HeroIcon from './HeroIcon.vue'
42
+ import { sizes, iconSizes, roundedMap, colorMap } from '../../pgo-components/lib/componentConfig'
43
+
44
+ const { t, setLanguage } = inject('i18n')
45
+
46
+ const emit = defineEmits(['click'])
47
+
48
+ const injectedLang = inject('parentLang', '')
49
+ const currentLang = computed(() => {
50
+ return props.lang ? props.lang : injectedLang
51
+ })
52
+
53
+ // provide('parentLang', currentLang.value)
54
+ // console.log("languge in Button.vue", injectedLang)
55
+ // const lang = inject('lang')
56
+
57
+ const props = defineProps({
58
+ variant: {
59
+ type: String,
60
+ default: 'contained' // contained | outlined | text | tonal | plain
61
+ },
62
+ color: {
63
+ type: String,
64
+ default: 'primary'
65
+ },
66
+ size: {
67
+ type: String,
68
+ default: 'md' // xs | sm | md | lg | xl
69
+ },
70
+ rounded: {
71
+ type: String,
72
+ default: 'sm' // none | sm | md | lg | xl | full
73
+ },
74
+ block: {
75
+ type: Boolean,
76
+ default: false
77
+ },
78
+ disabled: {
79
+ type: Boolean,
80
+ default: false
81
+ },
82
+ loading: {
83
+ type: Boolean,
84
+ default: false
85
+ },
86
+ label: {
87
+ type: String,
88
+ default: ''
89
+ },
90
+ icon: {
91
+ type: String,
92
+ default: ''
93
+ },
94
+ prependIcon: {
95
+ type: String,
96
+ default: ''
97
+ },
98
+ appendIcon: {
99
+ type: String,
100
+ default: ''
101
+ },
102
+ iconType: {
103
+ type: String,
104
+ default: 'outline'
105
+ },
106
+ padding: {
107
+ type: Boolean,
108
+ default: false
109
+ },
110
+ lang: {
111
+ type: String,
112
+ default: '' // Component-level language override
113
+ }
114
+ })
115
+
116
+ const variantClasses = computed(() => {
117
+ // Classes only for layout/structure, colors handled by buttonThemeStyles
118
+ return colorMap[props.color] ? colorMap[props.color][props.variant] : colorMap['primary'][props.variant]
119
+ // return 'transition-all'
120
+ })
121
+
122
+ const buttonClasses = computed(() => [
123
+ 'inline-flex items-center gap-2 justify-center font-medium transition-all select-none',
124
+ props.icon && !props.padding ? '' : sizes[props.size],
125
+ roundedMap[props.rounded],
126
+ variantClasses.value,
127
+ props.block ? 'w-full' : '',
128
+ currentLang.value === 'dv' ? 'faruma' : '',
129
+ props.disabled || props.loading ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'
130
+ ])
131
+
132
+ const handleClick = event => {
133
+ if (!props.disabled && !props.loading) emit('click', event)
134
+ }
135
+ // Check if label is a translation key like 'buttons.submit'
136
+ const displayLabel = computed(() => {
137
+ if (!props.label) {
138
+ return t('buttons.submit', currentLang.value)
139
+ }
140
+
141
+ // Check if label starts with 'buttons.' and try to translate it
142
+ if (props.label.startsWith('buttons.')) {
143
+ const translatedValue = t(props.label, currentLang.value)
144
+ // If translation exists and is different from the key, use it
145
+ if (translatedValue !== props.label) {
146
+ return translatedValue
147
+ }
148
+ }
149
+
150
+ // Otherwise, use the label as-is
151
+ return props.label
152
+ })
153
+ </script>
154
+
155
+ <style scoped>
156
+ /* Simple Loader */
157
+ .loader {
158
+ width: 16px;
159
+ height: 16px;
160
+ border: 2px solid transparent;
161
+ border-top-color: white;
162
+ border-radius: 50%;
163
+ animation: spin 0.7s linear infinite;
164
+ }
165
+
166
+ @keyframes spin {
167
+ to {
168
+ transform: rotate(360deg);
169
+ }
170
+ }
171
+ </style>