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,424 @@
1
+ <template>
2
+ <div :class="['relative', margin]">
3
+ <!-- Radio Group Label -->
4
+ <label
5
+ v-if="groupLabel"
6
+ :class="[
7
+ 'block mb-2 font-medium ui-text-primary',
8
+ groupLabelSizeClasses
9
+ ]"
10
+ >
11
+ {{ groupLabel }}
12
+ <span v-if="required" class="text-red-500 ml-0.5">*</span>
13
+ </label>
14
+
15
+ <!-- Radio Items Container -->
16
+ <div
17
+ :class="[
18
+ 'flex',
19
+ inline ? 'flex-row flex-wrap gap-x-6 gap-y-2' : 'flex-col gap-2'
20
+ ]"
21
+ role="radiogroup"
22
+ :aria-label="groupLabel"
23
+ >
24
+ <label
25
+ v-for="(item, index) in normalizedItems"
26
+ :key="item.value"
27
+ :class="[
28
+ 'inline-flex items-start gap-3 cursor-pointer select-none group',
29
+ item.disabled || disabled ? 'opacity-50 cursor-not-allowed' : '',
30
+ labelPosition === 'left' ? 'flex-row-reverse' : ''
31
+ ]"
32
+ @click.prevent="!item.disabled && !disabled && selectItem(item.value)"
33
+ >
34
+ <!-- Radio Circle -->
35
+ <div
36
+ :class="[
37
+ 'relative flex-shrink-0 flex items-center justify-center rounded-full border-2 transition-all duration-200',
38
+ sizeClasses,
39
+ getStateClasses(item.value, item.disabled)
40
+ ]"
41
+ >
42
+ <!-- Inner Dot -->
43
+ <Transition name="scale">
44
+ <div
45
+ v-if="isSelected(item.value)"
46
+ :class="[
47
+ 'rounded-full bg-current transition-all duration-200',
48
+ dotSizeClasses,
49
+ getDotColorClasses(item.disabled)
50
+ ]"
51
+ />
52
+ </Transition>
53
+
54
+ <!-- Focus Ring -->
55
+ <div
56
+ v-if="focusedValue === item.value"
57
+ class="absolute inset-0 rounded-full ring-2 ring-primary/30 ring-offset-2 ring-offset-transparent"
58
+ />
59
+ </div>
60
+
61
+ <!-- Label & Description -->
62
+ <div v-if="item.label || item.description" class="flex flex-col">
63
+ <span
64
+ v-if="item.label"
65
+ :class="[
66
+ 'ui-text-primary leading-tight transition-colors',
67
+ labelSizeClasses,
68
+ !item.disabled && !disabled ? 'group-hover:text-primary' : ''
69
+ ]"
70
+ >
71
+ {{ item.label }}
72
+ </span>
73
+ <span
74
+ v-if="item.description"
75
+ :class="[
76
+ 'ui-text-secondary mt-0.5',
77
+ descriptionSizeClasses
78
+ ]"
79
+ >
80
+ {{ item.description }}
81
+ </span>
82
+ </div>
83
+ </label>
84
+ </div>
85
+
86
+ <!-- Error Message -->
87
+ <p v-if="error" class="mt-2 text-sm text-red-500 flex items-center gap-1">
88
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
89
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
90
+ </svg>
91
+ {{ typeof error === 'string' ? error : 'Please select an option' }}
92
+ </p>
93
+
94
+ <!-- Hint Message -->
95
+ <p v-if="hint && !error" class="mt-2 text-sm ui-text-secondary">
96
+ {{ hint }}
97
+ </p>
98
+ </div>
99
+ </template>
100
+
101
+ <script setup>
102
+ import { ref, computed, watch } from 'vue'
103
+
104
+ const props = defineProps({
105
+ // v-model value
106
+ modelValue: {
107
+ type: [String, Number, Boolean, Object],
108
+ default: null
109
+ },
110
+ // Items array - can be simple values or objects
111
+ items: {
112
+ type: Array,
113
+ default: () => []
114
+ },
115
+ // Keys for object items
116
+ itemLabel: {
117
+ type: String,
118
+ default: 'label'
119
+ },
120
+ itemValue: {
121
+ type: String,
122
+ default: 'value'
123
+ },
124
+ itemDescription: {
125
+ type: String,
126
+ default: 'description'
127
+ },
128
+ itemDisabled: {
129
+ type: String,
130
+ default: 'disabled'
131
+ },
132
+ // Group label
133
+ groupLabel: {
134
+ type: String,
135
+ default: ''
136
+ },
137
+ // Layout
138
+ inline: {
139
+ type: Boolean,
140
+ default: false
141
+ },
142
+ labelPosition: {
143
+ type: String,
144
+ default: 'right', // 'left' | 'right'
145
+ validator: (v) => ['left', 'right'].includes(v)
146
+ },
147
+ // States
148
+ disabled: {
149
+ type: Boolean,
150
+ default: false
151
+ },
152
+ required: {
153
+ type: Boolean,
154
+ default: false
155
+ },
156
+ readonly: {
157
+ type: Boolean,
158
+ default: false
159
+ },
160
+ error: {
161
+ type: [String, Boolean],
162
+ default: false
163
+ },
164
+ hint: {
165
+ type: String,
166
+ default: ''
167
+ },
168
+ // Styling
169
+ size: {
170
+ type: String,
171
+ default: 'md',
172
+ validator: (v) => ['xs', 'sm', 'md', 'lg', 'xl'].includes(v)
173
+ },
174
+ color: {
175
+ type: String,
176
+ default: 'primary', // 'primary' | 'success' | 'warning' | 'danger' | 'info'
177
+ },
178
+ margin: {
179
+ type: String,
180
+ default: ''
181
+ },
182
+ // Validation
183
+ rules: {
184
+ type: Array,
185
+ default: () => []
186
+ }
187
+ })
188
+
189
+ const emit = defineEmits(['update:modelValue', 'change', 'focus', 'blur'])
190
+
191
+ // Track focused item for keyboard navigation
192
+ const focusedValue = ref(null)
193
+
194
+ // Normalize items to consistent format
195
+ const normalizedItems = computed(() => {
196
+ return props.items.map((item) => {
197
+ // If item is a primitive value (string, number, etc.)
198
+ if (typeof item !== 'object' || item === null) {
199
+ return {
200
+ label: String(item),
201
+ value: item,
202
+ description: '',
203
+ disabled: false
204
+ }
205
+ }
206
+
207
+ // If item is an object
208
+ return {
209
+ label: item[props.itemLabel] || '',
210
+ value: item[props.itemValue] ?? item,
211
+ description: item[props.itemDescription] || '',
212
+ disabled: item[props.itemDisabled] || false
213
+ }
214
+ })
215
+ })
216
+
217
+ // Check if item is selected
218
+ const isSelected = (value) => {
219
+ if (props.modelValue === null || props.modelValue === undefined) {
220
+ return false
221
+ }
222
+ // Handle object comparison
223
+ if (typeof value === 'object' && typeof props.modelValue === 'object') {
224
+ return JSON.stringify(value) === JSON.stringify(props.modelValue)
225
+ }
226
+ return props.modelValue === value
227
+ }
228
+
229
+ // Select an item
230
+ const selectItem = (value) => {
231
+ if (props.readonly) return
232
+
233
+ emit('update:modelValue', value)
234
+ emit('change', value)
235
+ }
236
+
237
+ // Size classes for the radio circle
238
+ const sizeClasses = computed(() => {
239
+ const sizes = {
240
+ xs: 'w-3.5 h-3.5',
241
+ sm: 'w-4 h-4',
242
+ md: 'w-5 h-5',
243
+ lg: 'w-6 h-6',
244
+ xl: 'w-7 h-7'
245
+ }
246
+ return sizes[props.size] || sizes.md
247
+ })
248
+
249
+ // Dot size classes
250
+ const dotSizeClasses = computed(() => {
251
+ const sizes = {
252
+ xs: 'w-1.5 h-1.5',
253
+ sm: 'w-2 h-2',
254
+ md: 'w-2.5 h-2.5',
255
+ lg: 'w-3 h-3',
256
+ xl: 'w-3.5 h-3.5'
257
+ }
258
+ return sizes[props.size] || sizes.md
259
+ })
260
+
261
+ // Label size classes
262
+ const labelSizeClasses = computed(() => {
263
+ const sizes = {
264
+ xs: 'text-xs',
265
+ sm: 'text-sm',
266
+ md: 'text-sm',
267
+ lg: 'text-base',
268
+ xl: 'text-lg'
269
+ }
270
+ return sizes[props.size] || sizes.md
271
+ })
272
+
273
+ // Group label size classes
274
+ const groupLabelSizeClasses = computed(() => {
275
+ const sizes = {
276
+ xs: 'text-xs',
277
+ sm: 'text-sm',
278
+ md: 'text-sm',
279
+ lg: 'text-base',
280
+ xl: 'text-lg'
281
+ }
282
+ return sizes[props.size] || sizes.md
283
+ })
284
+
285
+ // Description size classes
286
+ const descriptionSizeClasses = computed(() => {
287
+ const sizes = {
288
+ xs: 'text-xs',
289
+ sm: 'text-xs',
290
+ md: 'text-xs',
291
+ lg: 'text-sm',
292
+ xl: 'text-base'
293
+ }
294
+ return sizes[props.size] || sizes.md
295
+ })
296
+
297
+ // Color classes based on color prop
298
+ const colorClasses = computed(() => {
299
+ const colors = {
300
+ primary: {
301
+ selected: 'border-primary text-primary',
302
+ unselected: 'border-color-3 hover:border-primary',
303
+ dot: 'text-primary'
304
+ },
305
+ success: {
306
+ selected: 'border-green-500 text-green-500',
307
+ unselected: 'border-color-3 hover:border-green-500',
308
+ dot: 'text-green-500'
309
+ },
310
+ warning: {
311
+ selected: 'border-yellow-500 text-yellow-500',
312
+ unselected: 'border-color-3 hover:border-yellow-500',
313
+ dot: 'text-yellow-500'
314
+ },
315
+ danger: {
316
+ selected: 'border-red-500 text-red-500',
317
+ unselected: 'border-color-3 hover:border-red-500',
318
+ dot: 'text-red-500'
319
+ },
320
+ info: {
321
+ selected: 'border-blue-500 text-blue-500',
322
+ unselected: 'border-color-3 hover:border-blue-500',
323
+ dot: 'text-blue-500'
324
+ }
325
+ }
326
+ return colors[props.color] || colors.primary
327
+ })
328
+
329
+ // Get state classes for radio circle
330
+ const getStateClasses = (value, itemDisabled) => {
331
+ const isDisabled = itemDisabled || props.disabled
332
+
333
+ // Error state
334
+ if (props.error) {
335
+ if (isSelected(value)) {
336
+ return 'border-red-500 text-red-500'
337
+ }
338
+ return 'border-red-500 hover:border-red-600'
339
+ }
340
+
341
+ // Disabled state
342
+ if (isDisabled) {
343
+ if (isSelected(value)) {
344
+ return 'border-gray-400 text-gray-400 cursor-not-allowed'
345
+ }
346
+ return 'border-gray-300 bg-gray-100 dark:bg-gray-800 cursor-not-allowed'
347
+ }
348
+
349
+ // Selected state
350
+ if (isSelected(value)) {
351
+ return colorClasses.value.selected
352
+ }
353
+
354
+ // Default unselected state
355
+ return colorClasses.value.unselected
356
+ }
357
+
358
+ // Get dot color classes
359
+ const getDotColorClasses = (itemDisabled) => {
360
+ if (props.error) return 'bg-red-500'
361
+ if (itemDisabled || props.disabled) return 'bg-gray-400'
362
+
363
+ const colors = {
364
+ primary: 'bg-primary',
365
+ success: 'bg-green-500',
366
+ warning: 'bg-yellow-500',
367
+ danger: 'bg-red-500',
368
+ info: 'bg-blue-500'
369
+ }
370
+ return colors[props.color] || colors.primary
371
+ }
372
+
373
+ // Keyboard navigation
374
+ const handleKeydown = (e, index) => {
375
+ const enabledItems = normalizedItems.value.filter(item => !item.disabled)
376
+ const currentIndex = enabledItems.findIndex(item => item.value === props.modelValue)
377
+
378
+ let newIndex = currentIndex
379
+
380
+ switch (e.key) {
381
+ case 'ArrowDown':
382
+ case 'ArrowRight':
383
+ e.preventDefault()
384
+ newIndex = currentIndex < enabledItems.length - 1 ? currentIndex + 1 : 0
385
+ break
386
+ case 'ArrowUp':
387
+ case 'ArrowLeft':
388
+ e.preventDefault()
389
+ newIndex = currentIndex > 0 ? currentIndex - 1 : enabledItems.length - 1
390
+ break
391
+ case ' ':
392
+ case 'Enter':
393
+ e.preventDefault()
394
+ if (focusedValue.value !== null) {
395
+ selectItem(focusedValue.value)
396
+ }
397
+ return
398
+ }
399
+
400
+ if (newIndex !== currentIndex && enabledItems[newIndex]) {
401
+ selectItem(enabledItems[newIndex].value)
402
+ focusedValue.value = enabledItems[newIndex].value
403
+ }
404
+ }
405
+ </script>
406
+
407
+ <style scoped>
408
+ .scale-enter-active,
409
+ .scale-leave-active {
410
+ transition: transform 0.15s ease, opacity 0.15s ease;
411
+ }
412
+
413
+ .scale-enter-from,
414
+ .scale-leave-to {
415
+ transform: scale(0);
416
+ opacity: 0;
417
+ }
418
+
419
+ .scale-enter-to,
420
+ .scale-leave-from {
421
+ transform: scale(1);
422
+ opacity: 1;
423
+ }
424
+ </style>
@@ -0,0 +1,188 @@
1
+ <template>
2
+ <div class="">
3
+ <Form>
4
+ <div :class="`my-2 w-full flex gap-4 items-center ${grid ? 'flex-wrap' : ''}`">
5
+ <div :class="[
6
+ 'w-full',
7
+ grid ? `grid grid-cols-2 gap-4` : 'flex gap-4'
8
+ ]">
9
+ <component
10
+ v-for="(field, index) in filters"
11
+ :key="field.key || index"
12
+ :is="getFieldComponent(field.type)"
13
+ :model-value="modelValue[field.key]"
14
+ v-bind="getFieldProps(field)"
15
+ @update:model-value="updateFieldValue(field.key, $event)"
16
+ />
17
+ </div>
18
+ <!-- Dynamic Buttons -->
19
+ <!-- <hr class="border-input-border my-2" v-if="grid" /> -->
20
+ <div :class="['w-full flex gap-4', grid ? 'justify-end' : 'flex-wrap']">
21
+ <Button
22
+ v-for="(button, index) in visibleButtons"
23
+ :key="index"
24
+ :lang="selectedLang"
25
+ :rounded="button.rounded || 'sm'"
26
+ :icon="button.icon"
27
+ :icon-type="'micro'"
28
+ :prependIcon="button.prependIcon"
29
+ :size="button.size || 'sm'"
30
+ :color="button.color || 'primary'"
31
+ :variant="button.variant || 'contained'"
32
+ :padding="button.padding !== undefined ? button.padding : true"
33
+ :label="button.label || ''"
34
+ @click="handleButtonClick(button.type)"
35
+ />
36
+ </div>
37
+ </div>
38
+ </Form>
39
+ </div>
40
+ </template>
41
+
42
+ <script setup>
43
+ import { computed, defineAsyncComponent } from 'vue'
44
+ import Button from '../Button.vue'
45
+ import Form from '../forms/Form.vue'
46
+
47
+ const emit = defineEmits(["update:modelValue", "search", "submit", "clear", "close"])
48
+
49
+ const props = defineProps({
50
+ modelValue: {
51
+ type: Object,
52
+ default: () => ({})
53
+ },
54
+ filters: {
55
+ type: Array,
56
+ default: () => []
57
+ },
58
+ dir: { type: String, default: '' },
59
+ lang: { type: String, default: '' },
60
+ rounded: { type: String, default: '' },
61
+ labels: { type: [String, Object], default: '' },
62
+ items: { type: [String, Object], default: '' },
63
+ grid: { type: Boolean, default: false },
64
+ gridColumns: { type: Number, default: 1 },
65
+ buttons: {
66
+ type: Array,
67
+ default: () => ([
68
+ { type: 'search', icon: 'magnifying-glass', color: 'primary', variant: 'contained' }
69
+ ])
70
+ },
71
+ })
72
+
73
+ // Dynamic component map - only imports components that are used
74
+ const componentMap = computed(() => {
75
+ const map = {}
76
+ const usedTypes = new Set(props.filters.map(f => f.type?.toLowerCase()))
77
+
78
+ if (usedTypes.has('search') || usedTypes.has('inputsearch')) {
79
+ map['search'] = defineAsyncComponent(() => import('../InputSearch.vue'))
80
+ map['inputsearch'] = defineAsyncComponent(() => import('../InputSearch.vue'))
81
+ }
82
+
83
+ if (usedTypes.has('select')) {
84
+ map['select'] = defineAsyncComponent(() => import('../inputs/Select.vue'))
85
+ }
86
+
87
+ if (usedTypes.has('textfield') || usedTypes.has('text')) {
88
+ map['textfield'] = defineAsyncComponent(() => import('../inputs/TextField.vue'))
89
+ map['text'] = defineAsyncComponent(() => import('../inputs/TextField.vue'))
90
+ }
91
+
92
+ if (usedTypes.has('textarea')) {
93
+ map['textarea'] = defineAsyncComponent(() => import('../inputs/Textarea.vue'))
94
+ }
95
+
96
+ if (usedTypes.has('datepicker') || usedTypes.has('date') || usedTypes.has('Date')) {
97
+ map['datepicker'] = defineAsyncComponent(() => import('../inputs/DatePicker.vue'))
98
+ map['date'] = defineAsyncComponent(() => import('../inputs/DatePicker.vue'))
99
+ }
100
+
101
+ return map
102
+ })
103
+
104
+ const getFieldComponent = (type) => {
105
+ const normalizedType = type?.toLowerCase() || 'search'
106
+ return componentMap.value[normalizedType] || componentMap.value['search']
107
+ }
108
+
109
+ const getFieldProps = (field) => {
110
+ const { type, key, ...restProps } = field
111
+
112
+ // Remove empty values to allow defaults to work
113
+ const cleanProps = Object.fromEntries(
114
+ Object.entries(restProps).filter(([_, value]) => value !== '' && value !== null && value !== undefined)
115
+ )
116
+
117
+ // Convert camelCase props to kebab-case to avoid Vue treating them as event handlers
118
+ if (cleanProps.itemText) {
119
+ cleanProps['item-text'] = cleanProps.itemText
120
+ delete cleanProps.itemText
121
+ }
122
+ if (cleanProps.itemValue) {
123
+ cleanProps['item-value'] = cleanProps.itemValue
124
+ delete cleanProps.itemValue
125
+ }
126
+ if (cleanProps.itemTitle) {
127
+ cleanProps['item-title'] = cleanProps.itemTitle
128
+ delete cleanProps.itemTitle
129
+ }
130
+
131
+ return cleanProps
132
+ }
133
+
134
+ const selectedLang = computed(() => {
135
+ if (props.lang){ return props.lang }
136
+ if (props.dir == 'rtl'){ return 'dv' }
137
+ if (props.dir == 'ltr'){ return 'en' }
138
+ return ''
139
+ })
140
+
141
+ // Filter out buttons that are explicitly disabled
142
+ const visibleButtons = computed(() => {
143
+ return props.buttons.filter(button => button.show !== false)
144
+ })
145
+
146
+ const updateFieldValue = (key, value) => {
147
+ emit('update:modelValue', {
148
+ ...props.modelValue,
149
+ [key]: value
150
+ })
151
+ }
152
+
153
+ const handleButtonClick = (type) => {
154
+ const typeNormalized = type.toLowerCase()
155
+
156
+ switch(typeNormalized) {
157
+ case 'search':
158
+ case 'submit':
159
+ // Emit filter fields for search/submit
160
+ emit(typeNormalized, props.modelValue)
161
+ break
162
+ case 'clear':
163
+ // Clear all filter fields
164
+ handleClear()
165
+ break
166
+ case 'close':
167
+ // Just emit close event
168
+ emit('close')
169
+ break
170
+ default:
171
+ emit(typeNormalized, props.modelValue)
172
+ }
173
+ }
174
+
175
+ const handleClear = () => {
176
+ // Clear all filter fields to their default values
177
+ const clearValues = {}
178
+ props.filters.forEach(filter => {
179
+ // Arrays become empty arrays, everything else becomes empty string
180
+ clearValues[filter.key] = Array.isArray(props.modelValue[filter.key]) ? [] : ''
181
+ })
182
+ emit('update:modelValue', clearValues)
183
+ emit('clear')
184
+ }
185
+ </script>
186
+
187
+ <style scoped>
188
+ </style>