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.
- package/.env +1 -0
- package/.env.production +1 -0
- package/.prettierrc +13 -0
- package/.vscode/extensions.json +3 -0
- package/BUTTON_GUIDE.md +257 -0
- package/README.md +49 -0
- package/THEME_REFERENCE.md +310 -0
- package/eslint.config.ts +27 -0
- package/index.html +13 -0
- package/package.json +85 -0
- package/public/favicon.ico +0 -0
- package/src/App.vue +368 -0
- package/src/assets/fonts/Faruma.ttf +0 -0
- package/src/components/examples/AppBarExample.vue +101 -0
- package/src/components/examples/AvatarExample.vue +47 -0
- package/src/components/examples/BannerExample.vue +287 -0
- package/src/components/examples/BaseInputExample.vue +25 -0
- package/src/components/examples/BreadcrumbExample.vue +53 -0
- package/src/components/examples/CardExample.vue +77 -0
- package/src/components/examples/ChipExample.vue +225 -0
- package/src/components/examples/DatePickerExample.vue +31 -0
- package/src/components/examples/DropdownExample.vue +84 -0
- package/src/components/examples/EditorExample.vue +200 -0
- package/src/components/examples/ExpansionPanelExample.vue +42 -0
- package/src/components/examples/FileUploadExample.vue +40 -0
- package/src/components/examples/FormExample.vue +121 -0
- package/src/components/examples/HugeTest.vue +8 -0
- package/src/components/examples/LayoutContainerExample.vue +80 -0
- package/src/components/examples/ModalExample.vue +82 -0
- package/src/components/examples/NavDrawerExample.vue +170 -0
- package/src/components/examples/NumberFieldExample.vue +145 -0
- package/src/components/examples/RadioButtonExample.vue +161 -0
- package/src/components/examples/SearchExample.vue +322 -0
- package/src/components/examples/SelectExample.vue +121 -0
- package/src/components/examples/StackedTableViewExample.vue +53 -0
- package/src/components/examples/TabExample.vue +336 -0
- package/src/components/examples/TableExample.vue +228 -0
- package/src/components/examples/TextFieldExample.vue +181 -0
- package/src/components/examples/TextareaExample.vue +173 -0
- package/src/components/examples/ThemeToggle.vue +50 -0
- package/src/components/examples/TimelineExample.vue +66 -0
- package/src/components/examples/TipTapEditorExample.vue +20 -0
- package/src/components/examples/TooltipExample.vue +53 -0
- package/src/components/examples/VueDatePickerShowcase.vue +214 -0
- package/src/components/examples/_DatePickerExample.vue +33 -0
- package/src/components/examples/__FormExample.vue +77 -0
- package/src/components/index.ts +25 -0
- package/src/components/pgo/AppBar.vue +347 -0
- package/src/components/pgo/Avatar.vue +139 -0
- package/src/components/pgo/Banner.vue +300 -0
- package/src/components/pgo/Breadcrumb.vue +101 -0
- package/src/components/pgo/Button.vue +171 -0
- package/src/components/pgo/Card.vue +178 -0
- package/src/components/pgo/ConfirmationModel.vue +32 -0
- package/src/components/pgo/DataTable.vue +845 -0
- package/src/components/pgo/DatePicker/CalendarPanel.vue +43 -0
- package/src/components/pgo/DatePicker/__DatePicker.vue +122 -0
- package/src/components/pgo/DatePicker/types.ts +11 -0
- package/src/components/pgo/DatePicker/useCalendar.ts +39 -0
- package/src/components/pgo/DatePicker/useDatePicker.ts +31 -0
- package/src/components/pgo/Deprecated/ToastContainer.vue +51 -0
- package/src/components/pgo/Deprecated/ToastItem.vue +55 -0
- package/src/components/pgo/Dropdown.vue +296 -0
- package/src/components/pgo/DropdownItem.vue +40 -0
- package/src/components/pgo/Editor.vue +511 -0
- package/src/components/pgo/ExpansionPanel.vue +185 -0
- package/src/components/pgo/Footer.vue +39 -0
- package/src/components/pgo/HeroIcon.vue +124 -0
- package/src/components/pgo/InputSearch.vue +194 -0
- package/src/components/pgo/LayoutContainer.vue +104 -0
- package/src/components/pgo/Main.vue +37 -0
- package/src/components/pgo/Modal.vue +273 -0
- package/src/components/pgo/NavDrawer.vue +127 -0
- package/src/components/pgo/NavDrawerItem.vue +161 -0
- package/src/components/pgo/NavigationDrawer.vue +849 -0
- package/src/components/pgo/OLDNavDrawer.vue +661 -0
- package/src/components/pgo/OldAppBar.vue +223 -0
- package/src/components/pgo/PApp.vue +102 -0
- package/src/components/pgo/Pagination.vue +242 -0
- package/src/components/pgo/Search copy.vue +310 -0
- package/src/components/pgo/Search.vue +411 -0
- package/src/components/pgo/StackedTableView.vue +167 -0
- package/src/components/pgo/Tab.vue +617 -0
- package/src/components/pgo/TestInput.vue +395 -0
- package/src/components/pgo/Timeline.vue +367 -0
- package/src/components/pgo/TimelineItem.vue +80 -0
- package/src/components/pgo/TipTapEditor.vue +315 -0
- package/src/components/pgo/Tooltip.NOTES.md +12 -0
- package/src/components/pgo/Tooltip.PROPS.md +21 -0
- package/src/components/pgo/Tooltip.vue +281 -0
- package/src/components/pgo/base/Base.vue +444 -0
- package/src/components/pgo/buttons/Chip.vue +324 -0
- package/src/components/pgo/buttons/ChipGroup.vue +224 -0
- package/src/components/pgo/buttons/Radio.vue +424 -0
- package/src/components/pgo/filters/FilterSection.vue +188 -0
- package/src/components/pgo/filters/Searchbar.vue +216 -0
- package/src/components/pgo/forms/DynamicForm.vue +45 -0
- package/src/components/pgo/forms/Form.vue +132 -0
- package/src/components/pgo/index.ts +15 -0
- package/src/components/pgo/inputs/Checkbox.vue +320 -0
- package/src/components/pgo/inputs/DatePicker.vue +395 -0
- package/src/components/pgo/inputs/FileUpload.vue +326 -0
- package/src/components/pgo/inputs/NumberField.vue +243 -0
- package/src/components/pgo/inputs/Radio.vue +162 -0
- package/src/components/pgo/inputs/RadioGroup.vue +188 -0
- package/src/components/pgo/inputs/Select.vue +535 -0
- package/src/components/pgo/inputs/TextField.vue +194 -0
- package/src/components/pgo/inputs/Textarea.vue +181 -0
- package/src/main.js +12 -0
- package/src/pgo-components/_index.js +31 -0
- package/src/pgo-components/assets/fonts/Faruma.ttf +0 -0
- package/src/pgo-components/assets/fonts/logo.png +0 -0
- package/src/pgo-components/composables/useTheme.js +10 -0
- package/src/pgo-components/directives/tooltip-directive.ts +393 -0
- package/src/pgo-components/index.js +96 -0
- package/src/pgo-components/lib/componentConfig.js +147 -0
- package/src/pgo-components/lib/core/composables/_useCalendar.ts +127 -0
- package/src/pgo-components/lib/core/composables/useDefaults.ts +15 -0
- package/src/pgo-components/lib/core/composables/useLanguageSelect.js +0 -0
- package/src/pgo-components/lib/core/composables/useRtl.ts +12 -0
- package/src/pgo-components/lib/core/defaults/createDefaults.ts +5 -0
- package/src/pgo-components/lib/core/defaults/defaults.ts +7 -0
- package/src/pgo-components/lib/core/rtl/rtl.ts +3 -0
- package/src/pgo-components/lib/core/rtl/setRtl.ts +19 -0
- package/src/pgo-components/lib/drawerState.ts +3 -0
- package/src/pgo-components/lib/i18n/defaultLables.js +71 -0
- package/src/pgo-components/lib/i18n/i18nPlugin.js +52 -0
- package/src/pgo-components/lib/i18n/useI18n.js +35 -0
- package/src/pgo-components/lib/index.ts +38 -0
- package/src/pgo-components/pages/Component.vue +7 -0
- package/src/pgo-components/pages/ComponentRenderer.vue +85 -0
- package/src/pgo-components/pages/Home.vue +130 -0
- package/src/pgo-components/pages/ListView.vue +370 -0
- package/src/pgo-components/pages/Page1.vue +296 -0
- package/src/pgo-components/pages/_Page1.vue +180 -0
- package/src/pgo-components/plugins/SnackBar.vue +251 -0
- package/src/pgo-components/plugins/SnackBarContainer.vue +53 -0
- package/src/pgo-components/plugins/SnackBarPlugin.ts +136 -0
- package/src/pgo-components/plugins/theme-plugin.js +114 -0
- package/src/pgo-components/plugins/types.ts +46 -0
- package/src/pgo-components/plugins/useSnackBar.js +11 -0
- package/src/pgo-components/plugins/useSnackBar.ts +21 -0
- package/src/pgo-components/plugins/validation-plugin.js +11 -0
- package/src/pgo-components/services/Entry.json +813 -0
- package/src/pgo-components/services/axios.js +54 -0
- package/src/pgo-components/services/data.json +90 -0
- package/src/pgo-components/services/person.json +260 -0
- package/src/pgo-components/services/toast.ts +44 -0
- package/src/pgo-components/styles/global.css +234 -0
- package/src/pgo-components/styles/reset.css +96 -0
- package/src/pgo-components/styles/tokens.css +18 -0
- package/src/pgo-components/styles/utilities/border-radius.css +57 -0
- package/src/pgo-components/styles/utilities/borders.css +85 -0
- package/src/pgo-components/styles/utilities/colors.css +38 -0
- package/src/pgo-components/styles/utilities/cursor.css +19 -0
- package/src/pgo-components/styles/utilities/display.css +78 -0
- package/src/pgo-components/styles/utilities/elevation.css +33 -0
- package/src/pgo-components/styles/utilities/flex.css +403 -0
- package/src/pgo-components/styles/utilities/float.css +41 -0
- package/src/pgo-components/styles/utilities/hover.css +9 -0
- package/src/pgo-components/styles/utilities/index.css +18 -0
- package/src/pgo-components/styles/utilities/opacity.css +27 -0
- package/src/pgo-components/styles/utilities/overflow.css +26 -0
- package/src/pgo-components/styles/utilities/palette.css +515 -0
- package/src/pgo-components/styles/utilities/position.css +14 -0
- package/src/pgo-components/styles/utilities/sizing.css +70 -0
- package/src/pgo-components/styles/utilities/spacing.css +578 -0
- package/src/pgo-components/styles/utilities/transitions.css +58 -0
- package/src/pgo-components/styles/utilities/typography.css +91 -0
- package/src/pgo-components/styles/utilities/z-index.css +11 -0
- package/src/pgo-components/tokens/index.js +337 -0
- package/src/router/index.js +88 -0
- package/src/shims-vue.d.ts +14 -0
- package/src/validations/validationRules.js +50 -0
- package/tailwind.config.js +73 -0
- package/test.php +5 -0
- package/tsconfig.json +25 -0
- package/ui +31 -0
- package/ui.pgo.mv.conf +18 -0
- 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>
|