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,320 @@
1
+ <template>
2
+ <div :class="['relative', margin]">
3
+ <label
4
+ :class="[
5
+ 'inline-flex items-start gap-3 cursor-pointer select-none',
6
+ disabled ? 'opacity-50 cursor-not-allowed' : '',
7
+ labelPosition === 'left' ? 'flex-row-reverse' : ''
8
+ ]"
9
+ @click.prevent="toggle"
10
+ >
11
+ <!-- Checkbox Box -->
12
+ <div
13
+ :class="[
14
+ 'flex-shrink-0 flex items-center justify-center border-2 transition-all duration-200',
15
+ sizeClasses,
16
+ roundedClasses,
17
+ stateClasses,
18
+ !disabled ? 'cursor-pointer' : 'cursor-not-allowed'
19
+ ]"
20
+ >
21
+ <!-- Checkmark Icon -->
22
+ <Transition name="scale">
23
+ <svg
24
+ v-if="isChecked && !indeterminate"
25
+ class="text-white"
26
+ :class="iconSizeClasses"
27
+ fill="none"
28
+ stroke="currentColor"
29
+ viewBox="0 0 24 24"
30
+ >
31
+ <path
32
+ stroke-linecap="round"
33
+ stroke-linejoin="round"
34
+ stroke-width="3"
35
+ d="M5 13l4 4L19 7"
36
+ />
37
+ </svg>
38
+ </Transition>
39
+
40
+ <!-- Indeterminate Icon -->
41
+ <Transition name="scale">
42
+ <svg
43
+ v-if="indeterminate"
44
+ class="text-white"
45
+ :class="iconSizeClasses"
46
+ fill="none"
47
+ stroke="currentColor"
48
+ viewBox="0 0 24 24"
49
+ >
50
+ <path
51
+ stroke-linecap="round"
52
+ stroke-linejoin="round"
53
+ stroke-width="3"
54
+ d="M5 12h14"
55
+ />
56
+ </svg>
57
+ </Transition>
58
+ </div>
59
+
60
+ <!-- Label & Description -->
61
+ <div v-if="label || description || $slots.default" class="flex flex-col">
62
+ <span
63
+ v-if="label || $slots.default"
64
+ :class="[
65
+ 'ui-text-primary leading-tight',
66
+ labelSizeClasses
67
+ ]"
68
+ >
69
+ <slot>{{ label }}</slot>
70
+ <span v-if="required" class="text-red-500 ml-0.5">*</span>
71
+ </span>
72
+ <span
73
+ v-if="description"
74
+ :class="[
75
+ 'ui-text-secondary mt-0.5',
76
+ descriptionSizeClasses
77
+ ]"
78
+ >
79
+ {{ description }}
80
+ </span>
81
+ </div>
82
+ </label>
83
+
84
+ <!-- Error Message -->
85
+ <p v-if="error" class="mt-1 text-sm text-red-500">
86
+ {{ typeof error === 'string' ? error : '' }}
87
+ </p>
88
+
89
+ <!-- Hint Message -->
90
+ <p v-if="hint && !error" class="mt-1 text-sm ui-text-secondary">
91
+ {{ hint }}
92
+ </p>
93
+ </div>
94
+ </template>
95
+
96
+ <script setup>
97
+ import { computed } from 'vue'
98
+
99
+ const props = defineProps({
100
+ modelValue: {
101
+ type: [Boolean, Array],
102
+ default: false
103
+ },
104
+ value: {
105
+ type: [String, Number, Boolean, Object],
106
+ default: true
107
+ },
108
+ label: {
109
+ type: String,
110
+ default: ''
111
+ },
112
+ description: {
113
+ type: String,
114
+ default: ''
115
+ },
116
+ labelPosition: {
117
+ type: String,
118
+ default: 'right', // 'left' | 'right'
119
+ validator: (v) => ['left', 'right'].includes(v)
120
+ },
121
+ disabled: {
122
+ type: Boolean,
123
+ default: false
124
+ },
125
+ required: {
126
+ type: Boolean,
127
+ default: false
128
+ },
129
+ indeterminate: {
130
+ type: Boolean,
131
+ default: false
132
+ },
133
+ error: {
134
+ type: [String, Boolean],
135
+ default: false
136
+ },
137
+ hint: {
138
+ type: String,
139
+ default: ''
140
+ },
141
+ size: {
142
+ type: String,
143
+ default: 'md',
144
+ validator: (v) => ['xs', 'sm', 'md', 'lg', 'xl'].includes(v)
145
+ },
146
+ rounded: {
147
+ type: String,
148
+ default: 'md',
149
+ validator: (v) => ['none', 'sm', 'md', 'lg', 'full'].includes(v)
150
+ },
151
+ color: {
152
+ type: String,
153
+ default: 'primary' // 'primary' | 'success' | 'warning' | 'danger' | 'info'
154
+ },
155
+ margin: {
156
+ type: String,
157
+ default: ''
158
+ }
159
+ })
160
+
161
+ const emit = defineEmits(['update:modelValue', 'change'])
162
+
163
+ // Check if checkbox is checked
164
+ const isChecked = computed(() => {
165
+ if (Array.isArray(props.modelValue)) {
166
+ return props.modelValue.includes(props.value)
167
+ }
168
+ return props.modelValue === true || props.modelValue === props.value
169
+ })
170
+
171
+ // Toggle checkbox
172
+ const toggle = () => {
173
+ if (props.disabled) return
174
+
175
+ let newValue
176
+
177
+ if (Array.isArray(props.modelValue)) {
178
+ // Array mode (multiple checkboxes)
179
+ newValue = [...props.modelValue]
180
+ const index = newValue.indexOf(props.value)
181
+ if (index === -1) {
182
+ newValue.push(props.value)
183
+ } else {
184
+ newValue.splice(index, 1)
185
+ }
186
+ } else {
187
+ // Boolean mode (single checkbox)
188
+ newValue = !props.modelValue
189
+ }
190
+
191
+ emit('update:modelValue', newValue)
192
+ emit('change', newValue)
193
+ }
194
+
195
+ // Size classes for the checkbox box
196
+ const sizeClasses = computed(() => {
197
+ const sizes = {
198
+ xs: 'w-3.5 h-3.5',
199
+ sm: 'w-4 h-4',
200
+ md: 'w-5 h-5',
201
+ lg: 'w-6 h-6',
202
+ xl: 'w-7 h-7'
203
+ }
204
+ return sizes[props.size] || sizes.md
205
+ })
206
+
207
+ // Icon size classes
208
+ const iconSizeClasses = computed(() => {
209
+ const sizes = {
210
+ xs: 'w-2.5 h-2.5',
211
+ sm: 'w-3 h-3',
212
+ md: 'w-3.5 h-3.5',
213
+ lg: 'w-4 h-4',
214
+ xl: 'w-5 h-5'
215
+ }
216
+ return sizes[props.size] || sizes.md
217
+ })
218
+
219
+ // Label size classes
220
+ const labelSizeClasses = computed(() => {
221
+ const sizes = {
222
+ xs: 'text-xs',
223
+ sm: 'text-sm',
224
+ md: 'text-sm',
225
+ lg: 'text-base',
226
+ xl: 'text-lg'
227
+ }
228
+ return sizes[props.size] || sizes.md
229
+ })
230
+
231
+ // Description size classes
232
+ const descriptionSizeClasses = computed(() => {
233
+ const sizes = {
234
+ xs: 'text-xs',
235
+ sm: 'text-xs',
236
+ md: 'text-xs',
237
+ lg: 'text-sm',
238
+ xl: 'text-base'
239
+ }
240
+ return sizes[props.size] || sizes.md
241
+ })
242
+
243
+ // Rounded classes
244
+ const roundedClasses = computed(() => {
245
+ const rounded = {
246
+ none: 'rounded-none',
247
+ sm: 'rounded-sm',
248
+ md: 'rounded',
249
+ lg: 'rounded-lg',
250
+ full: 'rounded-full'
251
+ }
252
+ return rounded[props.rounded] || rounded.md
253
+ })
254
+
255
+ // Color classes based on state
256
+ const colorClasses = computed(() => {
257
+ const colors = {
258
+ primary: {
259
+ checked: 'bg-primary border-primary',
260
+ unchecked: 'border-primary/20 hover:border-primary'
261
+ },
262
+ success: {
263
+ checked: 'bg-green-500 border-green-500',
264
+ unchecked: 'border-green-100 hover:border-green-500'
265
+ },
266
+ warning: {
267
+ checked: 'bg-yellow-500 border-yellow-500',
268
+ unchecked: 'border-yellow-100 hover:border-yellow-500'
269
+ },
270
+ danger: {
271
+ checked: 'bg-red-500 border-red-500',
272
+ unchecked: 'border-red-100 hover:border-red-500'
273
+ },
274
+ info: {
275
+ checked: 'bg-blue-500 border-blue-500',
276
+ unchecked: 'border-blue-100 hover:border-blue-500'
277
+ }
278
+ }
279
+ return colors[props.color] || colors.primary
280
+ })
281
+
282
+ // State classes combining everything
283
+ const stateClasses = computed(() => {
284
+ if (props.error) {
285
+ if (isChecked.value || props.indeterminate) {
286
+ return 'bg-red-500 border-red-500'
287
+ }
288
+ return 'border-red-500 hover:border-red-600'
289
+ }
290
+
291
+ if (isChecked.value || props.indeterminate) {
292
+ return colorClasses.value.checked
293
+ }
294
+
295
+ if (props.disabled) {
296
+ return 'border-color-3 bg-color-2'
297
+ }
298
+
299
+ return colorClasses.value.unchecked
300
+ })
301
+ </script>
302
+
303
+ <style scoped>
304
+ .scale-enter-active,
305
+ .scale-leave-active {
306
+ transition: transform 0.15s ease, opacity 0.15s ease;
307
+ }
308
+
309
+ .scale-enter-from,
310
+ .scale-leave-to {
311
+ transform: scale(0);
312
+ opacity: 0;
313
+ }
314
+
315
+ .scale-enter-to,
316
+ .scale-leave-from {
317
+ transform: scale(1);
318
+ opacity: 1;
319
+ }
320
+ </style>
@@ -0,0 +1,395 @@
1
+ <template>
2
+ <div v-on-click-outside="close" :class="['relative', width]">
3
+ <Base v-bind="$props"
4
+ :model-value="formattedDate"
5
+ @update:model-value="handleUpdate"
6
+ @click:clear="clearSelection"
7
+ v-on="$attrs"
8
+ :dir="computedDir"
9
+ :lang="computedLang"
10
+ :label="label"
11
+ :prepend="prepend || 'calendar'"
12
+ :append="append"
13
+ :isOpen="isOpen"
14
+ :size="size"
15
+ :disabled="disabled"
16
+ :readonly="false"
17
+ :required="required"
18
+ :bg="bg"
19
+ :border="border"
20
+ :text-color="textColor"
21
+ :rounded="rounded"
22
+ :width="width"
23
+ :hint="hint"
24
+ :persistent-hint="!!hint"
25
+ :rules="rules"
26
+ :clearable="true"
27
+ >
28
+ <template #control="{ attrs, events }">
29
+ <div :class="['flex-1', 'flex', 'items-center', 'gap-2', 'justify-between', 'w-full', 'h-full']">
30
+ <!-- Editable date input -->
31
+ <input
32
+ ref="inputRef"
33
+ v-model="displayValue"
34
+ type="text"
35
+ :placeholder="placeholder || ''"
36
+ :disabled="disabled"
37
+ :class="[
38
+ 'w-full bg-transparent outline-none border-none',
39
+ 'placeholder:text-gray-400',
40
+ 'focus:outline-none',
41
+ 'cursor-text'
42
+ ]"
43
+ @click.stop
44
+ @input="handleInputChange"
45
+ @focus="handleFocus"
46
+ @blur="(e) => { handleInputBlur(); events.blur(e); }"
47
+ />
48
+ </div>
49
+ </template>
50
+
51
+ <template #prepend>
52
+ <!-- <div @click="toggleOpen" class="cursor-pointer">
53
+ <HeroIcon
54
+ name="calendar"
55
+ type="outline"
56
+ size="16"
57
+ class="text-input-border"
58
+ />
59
+ </div> -->
60
+ </template>
61
+ </Base>
62
+
63
+ <Transition name="fade">
64
+ <div
65
+ v-if="isOpen"
66
+ :class="[
67
+ 'absolute left-0 right-0 mt-1 z-100 w-fit',
68
+ menuClass
69
+ ]"
70
+ :style="menuStyle"
71
+ >
72
+ <VueDatePicker
73
+ v-model="internalDate"
74
+ inline
75
+ :placeholder="placeholder"
76
+ :range="range"
77
+ :auto-apply="range ? false : autoApply"
78
+ :multi-calendars="multiCalendars"
79
+ :enable-time-picker="enableTimePicker"
80
+ :format="format"
81
+ :week-picker="weekPicker"
82
+ :month-picker="monthPicker"
83
+ :year-picker="yearPicker"
84
+ :min-date="minDate"
85
+ :max-date="maxDate"
86
+ :disabled-dates="disabledDates"
87
+ :multi-dates="multiDates"
88
+ :text-input="textInput"
89
+ @update:model-value="handleDateSelect"
90
+ />
91
+ </div>
92
+ </Transition>
93
+ </div>
94
+ </template>
95
+
96
+ <script setup>
97
+ import { ref, computed, watch, nextTick, inject } from 'vue'
98
+ import { roundedMap } from '../../../pgo-components/lib/componentConfig'
99
+ import { VueDatePicker } from '@vuepic/vue-datepicker'
100
+ import '@vuepic/vue-datepicker/dist/main.css'
101
+ import Base from '../base/Base.vue'
102
+ import HeroIcon from '../HeroIcon.vue'
103
+ import { vOnClickOutside } from '@vueuse/components'
104
+
105
+ const emit = defineEmits(['update:modelValue', 'change', 'clear', 'open', 'close']);
106
+
107
+ const props = defineProps({
108
+ modelValue: {
109
+ type: [String, Date, Number, Array],
110
+ default: null
111
+ },
112
+ label: { type: [Object, String], default: '' },
113
+ dir: { type: String, default: '' },
114
+ placeholder: { type: String, default: '' },
115
+ prepend: { type: String, default: '' },
116
+ append: { type: String, default: '' },
117
+ disabled: { type: Boolean, default: false },
118
+ errorMessages: { type: Array, default: () => [] },
119
+ rounded: { type: String, default: 'sm' },
120
+ size: { type: String },
121
+ border: { type: String },
122
+ textColor: { type: String },
123
+ menuClass: { type: String, default: '' },
124
+ menuStyle: { type: Object, default: () => ({}) },
125
+ bg: { type: String },
126
+ readonly: { type: Boolean, default: false },
127
+ required: { type: Boolean, default: false },
128
+ width: { type: String, default: '' },
129
+ hint: { type: String, default: '' },
130
+ lang: { type: String },
131
+ rules: { type: Array, default: () => [] },
132
+ format: { type: String, default: 'dd/MM/yyyy' }, // Date format for display
133
+ range: { type: Boolean, default: false },
134
+ autoApply: { type: Boolean, default: true },
135
+ multiCalendars: { type: Number },
136
+ enableTimePicker: { type: Boolean, default: true },
137
+ weekPicker: { type: Boolean, default: false },
138
+ monthPicker: { type: Boolean, default: false },
139
+ yearPicker: { type: Boolean, default: false },
140
+ minDate: { type: Date, default: null },
141
+ maxDate: { type: Date, default: null },
142
+ disabledDates: { default: undefined },
143
+ multiDates: { type: Boolean, default: false },
144
+ textInput: { type: Boolean, default: true },
145
+
146
+ });
147
+
148
+ // Inject dir from parent Card (if exists)
149
+ const cardDir = inject('parentDir', '')
150
+ const cardLang = inject('parentLang', '')
151
+ const formContext = inject('formContext', null)
152
+ const isFocused = ref(false)
153
+
154
+ // Use component's dir if provided, otherwise use card's dir
155
+ const computedDir = computed(() => props.dir || cardDir)
156
+ const computedLang = computed(() => props.lang || cardLang)
157
+
158
+ const isOpen = ref(false)
159
+ const internalDate = ref(props.modelValue)
160
+ const displayValue = ref('')
161
+ const inputRef = ref(null)
162
+ const isTyping = ref(false)
163
+
164
+ // Date formatter function
165
+ const formatDate = (date, format) => {
166
+ if (!date) return ''
167
+
168
+ const d = new Date(date)
169
+ if (isNaN(d.getTime())) return ''
170
+
171
+ const year = d.getFullYear()
172
+ const month = String(d.getMonth() + 1).padStart(2, '0')
173
+ const day = String(d.getDate()).padStart(2, '0')
174
+ const hours = String(d.getHours()).padStart(2, '0')
175
+ const minutes = String(d.getMinutes()).padStart(2, '0')
176
+ const seconds = String(d.getSeconds()).padStart(2, '0')
177
+
178
+ let formatted = format
179
+ .replace('yyyy', year)
180
+ .replace('MM', month)
181
+ .replace('dd', day)
182
+
183
+ // Add time if enableTimePicker is true
184
+ if (props.enableTimePicker) {
185
+ formatted += ` ${hours}:${minutes}`
186
+ }
187
+
188
+ return formatted
189
+ }
190
+
191
+ // Parse date from string based on format
192
+ const parseDate = (dateString, format) => {
193
+ if (!dateString) return null
194
+
195
+ const parts = dateString.split(' ')
196
+ const datePart = parts[0]
197
+ const timePart = parts[1]
198
+
199
+ let date = null
200
+
201
+ // Parse based on separator in format
202
+ const separator = format.includes('/') ? '/' : format.includes('-') ? '-' : '/'
203
+ const dateParts = datePart.split(separator)
204
+
205
+ if (format.startsWith('dd')) {
206
+ // dd-MM-yyyy or dd/MM/yyyy
207
+ if (dateParts.length >= 3) {
208
+ date = new Date(dateParts[2], dateParts[1] - 1, dateParts[0])
209
+ }
210
+ } else if (format.startsWith('MM')) {
211
+ // MM/dd/yyyy
212
+ if (dateParts.length >= 3) {
213
+ date = new Date(dateParts[2], dateParts[0] - 1, dateParts[1])
214
+ }
215
+ } else if (format.startsWith('yyyy')) {
216
+ // yyyy-MM-dd
217
+ if (dateParts.length >= 3) {
218
+ date = new Date(dateParts[0], dateParts[1] - 1, dateParts[2])
219
+ }
220
+ }
221
+
222
+ // Parse time if present
223
+ if (date && timePart && props.enableTimePicker) {
224
+ const timeParts = timePart.split(':')
225
+ if (timeParts.length >= 2) {
226
+ date.setHours(parseInt(timeParts[0], 10) || 0)
227
+ date.setMinutes(parseInt(timeParts[1], 10) || 0)
228
+ if (timeParts.length === 3) {
229
+ date.setSeconds(parseInt(timeParts[2], 10) || 0)
230
+ }
231
+ }
232
+ }
233
+
234
+ return date
235
+ }
236
+ // Format date for display
237
+ const formattedDate = computed(() => {
238
+ if (!props.modelValue) return ''
239
+
240
+ if (props.range) {
241
+ if (!Array.isArray(props.modelValue)) return ''
242
+ const [start, end] = props.modelValue
243
+ if (!start || !end) return ''
244
+ return `${formatDate(start, props.format)} - ${formatDate(end, props.format)}`
245
+ }
246
+
247
+ return formatDate(props.modelValue, props.format)
248
+ })
249
+
250
+ // Watch for external modelValue changes
251
+ watch(() => props.modelValue, (newVal) => {
252
+ if (!isTyping.value) { // Only update if not typing
253
+ internalDate.value = newVal
254
+ displayValue.value = formattedDate.value
255
+ }
256
+ }, { immediate: true })
257
+
258
+ watch(internalDate, (newVal) => {
259
+ if (newVal && !isNaN(new Date(newVal).getTime()) && !isTyping.value) { // Only update if not typing
260
+ nextTick(() => {
261
+ displayValue.value = formatDate(newVal, props.format)
262
+ })
263
+ }
264
+ })
265
+
266
+ const handleUpdate = (val) => {
267
+ // This handles Base component updates (not used for DatePicker)
268
+ }
269
+ const handleInput = (e) => {
270
+ emit('update:modelValue', e.target.value)
271
+ }
272
+
273
+ const handleInputChange = (e) => {
274
+ isTyping.value = true // Set typing flag
275
+ displayValue.value = e.target.value
276
+
277
+ // Only parse and update calendar when we have a complete date
278
+ const separator = props.format.includes('/') ? '/' : props.format.includes('-') ? '-' : '/'
279
+ const parts = displayValue.value.split(' ')[0].split(separator)
280
+
281
+ // Wait for complete date with full year (4 digits)
282
+ if (parts.length === 3 && parts.every(part => part.length > 0)) {
283
+ // Find which part is the year based on format
284
+ let yearPart
285
+ if (props.format.startsWith('dd')) {
286
+ yearPart = parts[2] // dd/MM/yyyy
287
+ } else if (props.format.startsWith('MM')) {
288
+ yearPart = parts[2] // MM/dd/yyyy
289
+ } else if (props.format.startsWith('yyyy')) {
290
+ yearPart = parts[0] // yyyy-MM-dd
291
+ }
292
+
293
+ // Only update if year has 4 digits
294
+ if (yearPart && yearPart.length === 4) {
295
+ const parsedDate = parseDate(displayValue.value, props.format)
296
+ if (parsedDate && !isNaN(parsedDate.getTime())) {
297
+ // Create new Date to force reactivity
298
+ internalDate.value = new Date(parsedDate.getTime())
299
+ }
300
+ }
301
+ }
302
+ }
303
+
304
+ const handleFocus = () => {
305
+ // Open picker when focusing on input
306
+ if (!isOpen.value) {
307
+ isOpen.value = true
308
+ emit('open')
309
+ }
310
+ }
311
+
312
+ const handleInputBlur = () => {
313
+ isTyping.value = false // Reset typing flag on blur
314
+
315
+ if (!displayValue.value) {
316
+ clearSelection()
317
+ return
318
+ }
319
+
320
+ const parsedDate = parseDate(displayValue.value, props.format)
321
+ if (parsedDate && !isNaN(parsedDate.getTime())) {
322
+ // JavaScript Date auto-corrects invalid dates (e.g., month 13 becomes month 1 of next year)
323
+ // Update displayValue to show the corrected date
324
+ displayValue.value = formatDate(parsedDate, props.format)
325
+ emit('update:modelValue', parsedDate)
326
+ emit('change', parsedDate)
327
+ internalDate.value = parsedDate
328
+ } else {
329
+ // Invalid date, reset to previous value
330
+ displayValue.value = formattedDate.value
331
+ }
332
+ }
333
+
334
+ const handleDateSelect = (date) => {
335
+ internalDate.value = date
336
+ emit('update:modelValue', date)
337
+ emit('change', date)
338
+
339
+ if (props.range) {
340
+ if (Array.isArray(date) && date[0] && date[1]) {
341
+ displayValue.value =
342
+ `${formatDate(date[0], props.format)} - ${formatDate(date[1], props.format)}`
343
+ isOpen.value = false
344
+ }
345
+ } else {
346
+ displayValue.value = formatDate(date, props.format)
347
+ isOpen.value = false
348
+ }
349
+ }
350
+
351
+ const toggleOpen = () => {
352
+ if (!props.disabled) {
353
+ isOpen.value = !isOpen.value
354
+
355
+ if (isOpen.value) {
356
+ emit('open')
357
+ } else {
358
+ emit('close')
359
+ }
360
+ }
361
+ }
362
+
363
+ const close = () => {
364
+ isOpen.value = false
365
+ emit('close')
366
+ }
367
+
368
+ const clearSelection = () => {
369
+ internalDate.value = null
370
+ displayValue.value = ''
371
+ isTyping.value = false
372
+ emit('update:modelValue', null)
373
+ emit('change', null)
374
+ emit('clear')
375
+ }
376
+
377
+ // Expose methods
378
+ defineExpose({
379
+ close,
380
+ open: () => toggleOpen(),
381
+ clear: clearSelection,
382
+ })
383
+ </script>
384
+
385
+ <style scoped>
386
+ .fade-enter-active,
387
+ .fade-leave-active {
388
+ transition: opacity 0.2s, transform 0.2s;
389
+ }
390
+
391
+ .fade-enter-from,
392
+ .fade-leave-to {
393
+ opacity: 0;
394
+ }
395
+ </style>