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,216 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Card
|
|
3
|
+
v-if="modelValue"
|
|
4
|
+
:model-value="modelValue"
|
|
5
|
+
:bg="''"
|
|
6
|
+
:border="''"
|
|
7
|
+
:margin="'m-0'"
|
|
8
|
+
:padding="'py-0 px-2'"
|
|
9
|
+
@update:modelValue="(val) => emit('update:modelValue', val)"
|
|
10
|
+
>
|
|
11
|
+
<div class="w-full flex gap-4 justify-between items-center">
|
|
12
|
+
<!-- searchbar title -->
|
|
13
|
+
<div class="flex gap-2 font-semibold text-lg">
|
|
14
|
+
<h1>{{ title }}</h1>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="flex gap-2 items-center">
|
|
17
|
+
<!-- create btn -->
|
|
18
|
+
<Button
|
|
19
|
+
v-if="createButton"
|
|
20
|
+
label="Create"
|
|
21
|
+
color="primary"
|
|
22
|
+
prepend-icon="plus"
|
|
23
|
+
variant="outlined"
|
|
24
|
+
size="xs"
|
|
25
|
+
class="stroke-2"
|
|
26
|
+
@click="$emit('createButton')"
|
|
27
|
+
/>
|
|
28
|
+
|
|
29
|
+
<!-- Dynamic searchbar filters -->
|
|
30
|
+
<div class="flex gap-2">
|
|
31
|
+
<component
|
|
32
|
+
v-for="(field, index) in quickFilters"
|
|
33
|
+
:key="field.key || index"
|
|
34
|
+
:is="getFieldComponent(field.type)"
|
|
35
|
+
:model-value="quickFilterValues[field.key]"
|
|
36
|
+
v-bind="getFieldProps(field)"
|
|
37
|
+
@update:model-value="updateQuickFilter(field.key, $event)"
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<!-- FilterSection component related -->
|
|
42
|
+
<div v-if="filterSection" class="flex gap-2">
|
|
43
|
+
<!-- for filterSection selected filter count -->
|
|
44
|
+
<Chip
|
|
45
|
+
v-if="filterCountMessage"
|
|
46
|
+
:label="filterCountMessage"
|
|
47
|
+
variant="outlined"
|
|
48
|
+
color="gray"
|
|
49
|
+
size="small"
|
|
50
|
+
:closable="false"
|
|
51
|
+
:disabled="true"
|
|
52
|
+
/>
|
|
53
|
+
<!-- show filterSection -->
|
|
54
|
+
<Button
|
|
55
|
+
label=""
|
|
56
|
+
v-tooltip="showFilterSection ? 'Hide Filters' : 'Show Filters'"
|
|
57
|
+
color="secondary"
|
|
58
|
+
icon="funnel"
|
|
59
|
+
variant="text"
|
|
60
|
+
size="md"
|
|
61
|
+
@click="handlefilterSectionToggle"
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="flex gap-2">
|
|
65
|
+
<Button
|
|
66
|
+
v-tooltip="'Refresh'"
|
|
67
|
+
label=""
|
|
68
|
+
color="gray"
|
|
69
|
+
icon="arrow-path"
|
|
70
|
+
variant="text"
|
|
71
|
+
size="md"
|
|
72
|
+
@click="$emit('refresh')"
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</Card>
|
|
78
|
+
</template>
|
|
79
|
+
|
|
80
|
+
<script setup>
|
|
81
|
+
import { ref, computed, defineAsyncComponent } from 'vue'
|
|
82
|
+
import Banner from '../Banner.vue'
|
|
83
|
+
import Button from '../Button.vue'
|
|
84
|
+
import HeroIcon from '../HeroIcon.vue'
|
|
85
|
+
import Chip from '../buttons/Chip.vue'
|
|
86
|
+
import Card from '../Card.vue'
|
|
87
|
+
import { margins } from '../../../pgo-components/lib/componentConfig'
|
|
88
|
+
|
|
89
|
+
const props = defineProps({
|
|
90
|
+
modelValue: { type: Boolean, default: true },
|
|
91
|
+
title: { type: String, default: '' },
|
|
92
|
+
margin: { type: String, default: '' },
|
|
93
|
+
bg: { type: String, default: 'primary' },
|
|
94
|
+
position: { type: String, default: 'top' },
|
|
95
|
+
border: { type: String, default: '' },
|
|
96
|
+
closeable: { type: Boolean, default: false },
|
|
97
|
+
sticky: { type: Boolean, default: false },
|
|
98
|
+
timeout: { type: Number, default: 0 },
|
|
99
|
+
showProgress: { type: Boolean, default: false },
|
|
100
|
+
rounded: { type: String, default: 'none' },
|
|
101
|
+
elevation: { type: Boolean, default: true },
|
|
102
|
+
outlined: { type: Boolean, default: false },
|
|
103
|
+
dense: { type: Boolean, default: true },
|
|
104
|
+
filterSection: { type: Boolean, default: true },
|
|
105
|
+
createButton: { type: Boolean, default: true },
|
|
106
|
+
quickFilters: {
|
|
107
|
+
type: Array,
|
|
108
|
+
default: () => [] // Quick access filters shown in searchbar
|
|
109
|
+
},
|
|
110
|
+
filterValues: {
|
|
111
|
+
type: Object,
|
|
112
|
+
default: () => ({}) // Values for quick filters
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const emit = defineEmits(['update:modelValue', 'update:filterValues', 'createButton', 'close', 'dismiss', 'showFilterSection', 'search', 'refresh'])
|
|
117
|
+
|
|
118
|
+
const showFilterSection = ref(false)
|
|
119
|
+
const handlefilterSectionToggle = () => {
|
|
120
|
+
showFilterSection.value = !showFilterSection.value
|
|
121
|
+
emit('showFilterSection', showFilterSection.value)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const quickFilterValues = computed({
|
|
125
|
+
get: () => props.filterValues,
|
|
126
|
+
set: (val) => emit('update:filterValues', val)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
// Dynamic component map - only imports components that are used
|
|
130
|
+
const componentMap = computed(() => {
|
|
131
|
+
const map = {}
|
|
132
|
+
const usedTypes = new Set(props.quickFilters.map(f => f.type?.toLowerCase()))
|
|
133
|
+
|
|
134
|
+
if (usedTypes.has('search') || usedTypes.has('inputsearch')) {
|
|
135
|
+
map['search'] = defineAsyncComponent(() => import('../InputSearch.vue'))
|
|
136
|
+
map['inputsearch'] = defineAsyncComponent(() => import('../InputSearch.vue'))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (usedTypes.has('select')) {
|
|
140
|
+
map['select'] = defineAsyncComponent(() => import('../inputs/Select.vue'))
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (usedTypes.has('textfield') || usedTypes.has('text')) {
|
|
144
|
+
map['textfield'] = defineAsyncComponent(() => import('../inputs/TextField.vue'))
|
|
145
|
+
map['text'] = defineAsyncComponent(() => import('../inputs/TextField.vue'))
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (usedTypes.has('datepicker') || usedTypes.has('date')) {
|
|
149
|
+
map['datepicker'] = defineAsyncComponent(() => import('../inputs/DatePicker.vue'))
|
|
150
|
+
map['date'] = defineAsyncComponent(() => import('../inputs/DatePicker.vue'))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (usedTypes.has('chipgroup')) {
|
|
154
|
+
map['chipgroup'] = defineAsyncComponent(() => import('../buttons/ChipGroup.vue'))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return map
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
const getFieldComponent = (type) => {
|
|
161
|
+
const normalizedType = type?.toLowerCase() || 'search'
|
|
162
|
+
return componentMap.value[normalizedType] || componentMap.value['search']
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const getFieldProps = (field) => {
|
|
166
|
+
const { type, key, ...restProps } = field
|
|
167
|
+
|
|
168
|
+
// Remove empty values to allow defaults to work
|
|
169
|
+
const cleanProps = Object.fromEntries(
|
|
170
|
+
Object.entries(restProps).filter(([_, value]) => value !== '' && value !== null && value !== undefined)
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
// Convert camelCase props to kebab-case
|
|
174
|
+
if (cleanProps.itemText) {
|
|
175
|
+
cleanProps['item-text'] = cleanProps.itemText
|
|
176
|
+
delete cleanProps.itemText
|
|
177
|
+
}
|
|
178
|
+
if (cleanProps.itemValue) {
|
|
179
|
+
cleanProps['item-value'] = cleanProps.itemValue
|
|
180
|
+
delete cleanProps.itemValue
|
|
181
|
+
}
|
|
182
|
+
if (cleanProps.itemTitle) {
|
|
183
|
+
cleanProps['item-title'] = cleanProps.itemTitle
|
|
184
|
+
delete cleanProps.itemTitle
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return cleanProps
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const updateQuickFilter = (key, value) => {
|
|
191
|
+
const normalizedValue = typeof value === 'object' && value !== null ? value.value : value
|
|
192
|
+
|
|
193
|
+
emit('update:filterValues', {
|
|
194
|
+
...props.filterValues,
|
|
195
|
+
[key]: normalizedValue
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
// Auto-trigger search on quick filter change
|
|
199
|
+
emit('search', {
|
|
200
|
+
...props.filterValues,
|
|
201
|
+
[key]: normalizedValue
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Count active filters
|
|
206
|
+
const filterCountMessage = computed(() => {
|
|
207
|
+
if (props.message) return props.message
|
|
208
|
+
|
|
209
|
+
const count = Object.values(props.filterValues).filter(val => {
|
|
210
|
+
if (Array.isArray(val)) return val.length > 0
|
|
211
|
+
return val !== '' && val !== null && val !== undefined
|
|
212
|
+
}).length
|
|
213
|
+
|
|
214
|
+
return count > 0 ? `${count} filter${count > 1 ? 's' : ''} selected` : ''
|
|
215
|
+
})
|
|
216
|
+
</script>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Modal v-model="open" persistent>
|
|
3
|
+
<template #header>
|
|
4
|
+
<div>
|
|
5
|
+
<h1 class="text-lg font-semibold" > create</h1>
|
|
6
|
+
</div>
|
|
7
|
+
</template>
|
|
8
|
+
<Form
|
|
9
|
+
v-model="valid"
|
|
10
|
+
>
|
|
11
|
+
|
|
12
|
+
</Form>
|
|
13
|
+
<template #footer>
|
|
14
|
+
<div class="flex justify-end">
|
|
15
|
+
<Button label="Close" color="primary" @click="open = false"/>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
</Modal>
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
</template>
|
|
23
|
+
<script setup >
|
|
24
|
+
import { ref, computed, watch } from 'vue'
|
|
25
|
+
import Form from './Form.vue';
|
|
26
|
+
import Modal from '../Modal.vue'
|
|
27
|
+
import Button from '../Button.vue'
|
|
28
|
+
|
|
29
|
+
const valid = ref(true);
|
|
30
|
+
const open = ref(true);
|
|
31
|
+
|
|
32
|
+
const emit = defineEmits(['update:modelValue']);
|
|
33
|
+
const props = defineProps({
|
|
34
|
+
modelValue: { type: Boolean, default: false },
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
watch(() => props.modelValue, (newVal) => {
|
|
38
|
+
open.value = newVal;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
watch(open, (newVal) => {
|
|
42
|
+
emit('update:modelValue', newVal);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
</script>
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<form @submit.prevent="handleSubmit">
|
|
3
|
+
<slot
|
|
4
|
+
:isValid="isValid"
|
|
5
|
+
:validate="validate"
|
|
6
|
+
:reset="reset"
|
|
7
|
+
:resetValidation="resetValidation"
|
|
8
|
+
/>
|
|
9
|
+
</form>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup>
|
|
13
|
+
import { reactive, ref, provide, computed, nextTick } from 'vue'
|
|
14
|
+
|
|
15
|
+
const props = defineProps({
|
|
16
|
+
modelValue: { type: Boolean, default: null },
|
|
17
|
+
validateOn: {
|
|
18
|
+
type: String,
|
|
19
|
+
default: 'blur'
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const emit = defineEmits(['update:modelValue', 'submit'])
|
|
24
|
+
|
|
25
|
+
const fields = reactive(new Map())
|
|
26
|
+
const errors = reactive({})
|
|
27
|
+
const hasValidated = reactive({}) // Track which fields have been validated
|
|
28
|
+
|
|
29
|
+
const register = (uid, field) => {
|
|
30
|
+
fields.set(uid, field)
|
|
31
|
+
// Mark as not validated initially
|
|
32
|
+
hasValidated[uid] = false
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const unregister = (uid) => {
|
|
36
|
+
fields.delete(uid)
|
|
37
|
+
delete errors[uid]
|
|
38
|
+
delete hasValidated[uid]
|
|
39
|
+
updateModelValue()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const validate = async () => {
|
|
43
|
+
let allValid = true
|
|
44
|
+
Object.keys(errors).forEach(k => delete errors[k])
|
|
45
|
+
|
|
46
|
+
for (const [uid, field] of fields.entries()) {
|
|
47
|
+
if (field?.validate) {
|
|
48
|
+
hasValidated[uid] = true
|
|
49
|
+
const valid = await field.validate()
|
|
50
|
+
if (!valid) {
|
|
51
|
+
allValid = false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
updateModelValue()
|
|
57
|
+
return { valid: allValid, errors: { ...errors } }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const reset = () => {
|
|
61
|
+
for (const [uid, field] of fields.entries()) {
|
|
62
|
+
if (field?.reset) {
|
|
63
|
+
field.reset()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
resetValidation()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const resetValidation = () => {
|
|
70
|
+
for (const [uid, field] of fields.entries()) {
|
|
71
|
+
if (field?.resetValidation) {
|
|
72
|
+
field.resetValidation()
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
Object.keys(errors).forEach(k => delete errors[k])
|
|
76
|
+
Object.keys(hasValidated).forEach(k => hasValidated[k] = false)
|
|
77
|
+
updateModelValue()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Called by Base input when validating
|
|
82
|
+
*/
|
|
83
|
+
const report = (uid, valid, fieldErrors) => {
|
|
84
|
+
hasValidated[uid] = true
|
|
85
|
+
|
|
86
|
+
if (!valid) {
|
|
87
|
+
errors[uid] = fieldErrors
|
|
88
|
+
} else {
|
|
89
|
+
delete errors[uid]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
updateModelValue()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Form is valid only if:
|
|
96
|
+
// 1. All registered fields have been validated (hasValidated[uid] = true)
|
|
97
|
+
// 2. No errors exist
|
|
98
|
+
const isValid = computed(() => {
|
|
99
|
+
// Check if all fields have been validated
|
|
100
|
+
const allFieldsValidated = Array.from(fields.keys()).every(uid => hasValidated[uid] === true)
|
|
101
|
+
|
|
102
|
+
// No errors and all fields validated
|
|
103
|
+
return allFieldsValidated && Object.keys(errors).length === 0
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const updateModelValue = () => {
|
|
107
|
+
nextTick(() => {
|
|
108
|
+
if (props.modelValue !== null) {
|
|
109
|
+
emit('update:modelValue', isValid.value)
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const handleSubmit = async () => {
|
|
115
|
+
const result = await validate()
|
|
116
|
+
emit('submit', result)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
provide('formContext', {
|
|
120
|
+
register,
|
|
121
|
+
unregister,
|
|
122
|
+
validate: report,
|
|
123
|
+
validateOn: computed(() => props.validateOn)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
defineExpose({
|
|
127
|
+
validate,
|
|
128
|
+
reset,
|
|
129
|
+
resetValidation,
|
|
130
|
+
isValid
|
|
131
|
+
})
|
|
132
|
+
</script>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import AppBar from './AppBar.vue'
|
|
2
|
+
import Button from './Button.vue'
|
|
3
|
+
import Card from './Card.vue'
|
|
4
|
+
import Chip from './buttons/Chip.vue'
|
|
5
|
+
import ChipGroup from './buttons/ChipGroup.vue'
|
|
6
|
+
import DataTable from './DataTable.vue'
|
|
7
|
+
import Dropdown from './Dropdown.vue'
|
|
8
|
+
import HeroIcon from './HeroIcon.vue'
|
|
9
|
+
import Select from './inputs/Select.vue'
|
|
10
|
+
import NavDrawer from './NavDrawer.vue'
|
|
11
|
+
import NavDrawerItem from './NavDrawerItem.vue'
|
|
12
|
+
import FileUpload from './inputs/FileUpload.vue'
|
|
13
|
+
import Form from './forms/Form.vue'
|
|
14
|
+
|
|
15
|
+
export { AppBar, Button, Card, Chip, ChipGroup, DataTable, Dropdown, HeroIcon, Select, NavDrawer, NavDrawerItem, FileUpload, Form }
|