@raclettejs/workbench 0.0.1

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 (89) hide show
  1. package/README.md +1 -0
  2. package/config/compositions.js +282 -0
  3. package/config/interactionLinks.js +164 -0
  4. package/i18n/de-DE.json +245 -0
  5. package/i18n/en-EU.json +245 -0
  6. package/i18n/sk-SK.json +245 -0
  7. package/package.json +63 -0
  8. package/packages.json +33 -0
  9. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/CompositionConfiguration.vue +140 -0
  10. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/CompositionIntegration.vue +3 -0
  11. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/CompositionWidgetsLayout.vue +129 -0
  12. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetContextMenu.vue +53 -0
  13. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetIcon.vue +99 -0
  14. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetList.vue +122 -0
  15. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetRenderer.vue +65 -0
  16. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetSettings.vue +96 -0
  17. package/plugins/pacifico__compositions/frontend/widgets/CompositionCreateWidget.vue +51 -0
  18. package/plugins/pacifico__compositions/frontend/widgets/CompositionEditWidget.vue +66 -0
  19. package/plugins/pacifico__compositions/frontend/widgets/CompositionListWidget.vue +132 -0
  20. package/plugins/pacifico__compositions/raclette.plugin.ts +8 -0
  21. package/plugins/pacifico__core/backend/index.ts +25 -0
  22. package/plugins/pacifico__core/backend/routes.ts +220 -0
  23. package/plugins/pacifico__core/raclette.plugin.ts +9 -0
  24. package/plugins/pacifico__interactionLinks/frontend/components/InteractionLinkConfiguration.vue +145 -0
  25. package/plugins/pacifico__interactionLinks/frontend/widgets/InteractionLinkCreateWidget.vue +48 -0
  26. package/plugins/pacifico__interactionLinks/frontend/widgets/InteractionLinkEditWidget.vue +66 -0
  27. package/plugins/pacifico__interactionLinks/frontend/widgets/InteractionLinkListWidget.vue +126 -0
  28. package/plugins/pacifico__interactionLinks/raclette.plugin.ts +8 -0
  29. package/plugins/pacifico__plugins/frontend/index.ts +42 -0
  30. package/plugins/pacifico__plugins/frontend/widgets/PluginDetailWidget.vue +26 -0
  31. package/plugins/pacifico__plugins/frontend/widgets/PluginListWidget.vue +59 -0
  32. package/plugins/pacifico__plugins/raclette.plugin.ts +8 -0
  33. package/plugins/pacifico__tags/frontend/components/TagConfiguration.vue +99 -0
  34. package/plugins/pacifico__tags/frontend/widgets/TagCreateWidget.vue +43 -0
  35. package/plugins/pacifico__tags/frontend/widgets/TagEditWidget.vue +64 -0
  36. package/plugins/pacifico__tags/frontend/widgets/TagListWidget.vue +71 -0
  37. package/plugins/pacifico__tags/raclette.plugin.ts +8 -0
  38. package/plugins/pacifico__users/frontend/components/UserConfiguration.vue +110 -0
  39. package/plugins/pacifico__users/frontend/widgets/UserCreateWidget.vue +48 -0
  40. package/plugins/pacifico__users/frontend/widgets/UserEditWidget.vue +64 -0
  41. package/plugins/pacifico__users/frontend/widgets/UserListWidget.vue +86 -0
  42. package/plugins/pacifico__users/raclette.plugin.ts +8 -0
  43. package/raclette.config.js +150 -0
  44. package/services/frontend/.DS_Store +0 -0
  45. package/services/frontend/public/favicon.png +0 -0
  46. package/services/frontend/public/home-icon.png +0 -0
  47. package/services/frontend/public/logo.png +0 -0
  48. package/services/frontend/src/app/.DS_Store +0 -0
  49. package/services/frontend/src/app/components/BaseDataTable.vue +284 -0
  50. package/services/frontend/src/app/components/dynamicForm/DynamicForm.vue +207 -0
  51. package/services/frontend/src/app/components/dynamicForm/DynamicFormTypes.ts +63 -0
  52. package/services/frontend/src/app/components/dynamicForm/DynamicInput.vue +344 -0
  53. package/services/frontend/src/app/components/dynamicForm/TextDivider.vue +13 -0
  54. package/services/frontend/src/app/components/dynamicForm/testing/DynamicFormInputTesting.vue +477 -0
  55. package/services/frontend/src/app/components/dynamicForm/typedInputs/CheckboxInput.vue +16 -0
  56. package/services/frontend/src/app/components/dynamicForm/typedInputs/ColorInput.vue +52 -0
  57. package/services/frontend/src/app/components/dynamicForm/typedInputs/CompositionInput.vue +32 -0
  58. package/services/frontend/src/app/components/dynamicForm/typedInputs/IconInput.vue +40 -0
  59. package/services/frontend/src/app/components/dynamicForm/typedInputs/ListInput.vue +51 -0
  60. package/services/frontend/src/app/components/dynamicForm/typedInputs/LocalizedInput.vue +51 -0
  61. package/services/frontend/src/app/components/dynamicForm/typedInputs/PasswordInput.vue +23 -0
  62. package/services/frontend/src/app/components/dynamicForm/typedInputs/RecordInput.vue +91 -0
  63. package/services/frontend/src/app/components/dynamicForm/typedInputs/SwitchInput.vue +17 -0
  64. package/services/frontend/src/app/components/dynamicForm/typedInputs/TagsInput.vue +58 -0
  65. package/services/frontend/src/app/components/dynamicForm/typedInputs/TriggerInput.vue +159 -0
  66. package/services/frontend/src/app/components/dynamicForm/typedInputs/UserInput.vue +63 -0
  67. package/services/frontend/src/app/components/dynamicForm/typedInputs/mdi-icons.ts +7002 -0
  68. package/services/frontend/src/app/components/dynamicForm/validators.ts +19 -0
  69. package/services/frontend/src/app/components/stepNavigator/StepNavigator.vue +331 -0
  70. package/services/frontend/src/app/components/stepNavigator/StepNavigatorTypes.ts +12 -0
  71. package/services/frontend/src/app/composables/useUserHelper.ts +254 -0
  72. package/services/frontend/src/app/composables/useVantaFog.ts +37 -0
  73. package/services/frontend/src/app/composables/useWorkbenchPlugins.ts +44 -0
  74. package/services/frontend/src/orchestrator/.DS_Store +0 -0
  75. package/services/frontend/src/orchestrator/LoginApp.vue +62 -0
  76. package/services/frontend/src/orchestrator/WelcomeScreen.vue +39 -0
  77. package/services/frontend/src/orchestrator/assets/styles/themes/dark.ts +39 -0
  78. package/services/frontend/src/orchestrator/assets/styles/themes/highContrast.ts +39 -0
  79. package/services/frontend/src/orchestrator/assets/styles/themes/light.ts +39 -0
  80. package/services/frontend/src/orchestrator/components/InitProjectScreen.vue +298 -0
  81. package/services/frontend/src/orchestrator/components/LoginScreen.vue +73 -0
  82. package/services/frontend/src/orchestrator/components/RacletteAdCorner.vue +313 -0
  83. package/services/frontend/src/orchestrator/components/layout/AppButton.vue +109 -0
  84. package/services/frontend/src/orchestrator/components/layout/AppHeadline.vue +46 -0
  85. package/services/frontend/src/orchestrator/components/layout/AppLabelledInput.vue +94 -0
  86. package/services/frontend/src/orchestrator/components/layout/AppModal.vue +28 -0
  87. package/services/frontend/src/orchestrator/components/layout/SvgButtonArrow.vue +14 -0
  88. package/services/frontend/src/orchestrator/composables/useWelcomeScreen.ts +19 -0
  89. package/yarn.lock +3435 -0
@@ -0,0 +1,284 @@
1
+ <template>
2
+ <div>
3
+ <!-- Search and Actions Bar -->
4
+ <div class="tw:flex tw:gap-2 tw:items-center tw:mb-6">
5
+ <v-text-field
6
+ v-model="search"
7
+ variant="outlined"
8
+ hide-details
9
+ prepend-inner-icon="mdi-magnify"
10
+ clearable
11
+ :placeholder="
12
+ $t('workbench.baseDataTable.searchPlaceholder', {
13
+ dataName,
14
+ })
15
+ "
16
+ density="compact"
17
+ />
18
+
19
+ <AppButton
20
+ v-if="showCreateButton"
21
+ @click="handleCreate"
22
+ variant="secondary"
23
+ class="!tw:h-10"
24
+ >
25
+ <template #prepend-icon>
26
+ <v-icon icon="mdi-plus-circle-outline" />
27
+ </template>
28
+ {{
29
+ $t("workbench.baseDataTable.createDataButton", {
30
+ dataName,
31
+ })
32
+ }}
33
+ </AppButton>
34
+
35
+ <!-- Additional action buttons slot -->
36
+ <slot name="actions" />
37
+ </div>
38
+
39
+ <!-- Data Table -->
40
+ <v-data-table
41
+ v-model:search="search"
42
+ density="comfortable"
43
+ :items-per-page="itemsPerPage"
44
+ sticky
45
+ fixed-header
46
+ :headers="computedHeaders"
47
+ :items="items"
48
+ :loading="loading"
49
+ @click:row="handleRowClick"
50
+ >
51
+ <!-- Built-in tags column -->
52
+ <template #item.tags="{ item }" v-if="!customSlots.includes('item.tags')">
53
+ <div class="tw:flex tw:gap-2" v-if="item.tags?.length">
54
+ <v-chip
55
+ v-for="tag in item.tags"
56
+ :key="tag._id || tag.id || tag"
57
+ :color="tag.color"
58
+ label
59
+ variant="outlined"
60
+ size="small"
61
+ >
62
+ {{ tag.title || tag.name || tag }}
63
+ </v-chip>
64
+ </div>
65
+ </template>
66
+
67
+ <!-- Dynamic slots for custom column rendering -->
68
+ <template v-for="slot in customSlots" :key="slot" #[slot]="slotProps">
69
+ <slot :name="slot" v-bind="slotProps" />
70
+ </template>
71
+
72
+ <!-- Actions column template -->
73
+ <template #item.actions="{ item }" v-if="showActionsColumn">
74
+ <slot name="row-actions" :item="item">
75
+ <!-- Default delete action -->
76
+ <v-icon
77
+ v-if="showDeleteAction && !isDeleteDisabled(item)"
78
+ color="red"
79
+ @click.stop.prevent="handleDelete(item)"
80
+ class="mr-2"
81
+ :title="
82
+ $t('workbench.baseDataTable.deleteDataTitle', {
83
+ dataName,
84
+ })
85
+ "
86
+ icon="mdi-delete"
87
+ />
88
+ </slot>
89
+ </template>
90
+
91
+ <!-- Loading template -->
92
+ <template #loader>
93
+ <v-skeleton-loader
94
+ class="mx-auto border"
95
+ :height="500"
96
+ width="100%"
97
+ type="list-item"
98
+ :loading="true"
99
+ />
100
+ </template>
101
+
102
+ <!-- Additional table slots (this caused the table to be empty) -->
103
+ <!-- <slot name="table-slots" /> -->
104
+ </v-data-table>
105
+ </div>
106
+
107
+ <!-- Delete Confirmation Dialog -->
108
+ <v-dialog
109
+ v-model="deleteDialog"
110
+ max-width="400"
111
+ v-if="showDeleteAction && itemToDelete"
112
+ >
113
+ <v-card>
114
+ <v-card-title class="text-h6">
115
+ {{
116
+ $t("workbench.baseDataTable.deleteDataTitle", {
117
+ dataName,
118
+ })
119
+ }}
120
+ </v-card-title>
121
+
122
+ <v-card-text
123
+ v-html="
124
+ $t('workbench.baseDataTable.deleteDataConfirmMessage', {
125
+ article: dataArticle,
126
+ dataName,
127
+ value:
128
+ itemToDelete.email ||
129
+ itemToDelete.title ||
130
+ itemToDelete.name ||
131
+ itemToDelete._id,
132
+ })
133
+ "
134
+ />
135
+ <v-card-actions>
136
+ <v-spacer />
137
+ <v-btn variant="text" @click="deleteDialog = false">
138
+ {{ $t("workbench.baseDataTable.deleteDataConfirmCancel") }}
139
+ </v-btn>
140
+ <v-btn color="red" variant="flat" @click="confirmDelete">
141
+ {{ $t("workbench.baseDataTable.deleteDataConfirmButton") }}
142
+ </v-btn>
143
+ </v-card-actions>
144
+ </v-card>
145
+ </v-dialog>
146
+
147
+ <!-- Additional dialogs slot -->
148
+ <slot name="dialogs" />
149
+ </template>
150
+
151
+ <script setup lang="ts" generic="T extends Record<string, any>">
152
+ import { computed, ref, useSlots } from "vue"
153
+ import { useRouteState } from "@raclettejs/core/orchestrator/composables"
154
+ import AppButton from "../../orchestrator/components/layout/AppButton.vue"
155
+
156
+ export interface BaseDataTableProps<T> {
157
+ uuid?: string
158
+ // Data props
159
+ items?: T[]
160
+ loading?: boolean
161
+
162
+ // Table configuration
163
+ headers: Array<{
164
+ title: string
165
+ key: keyof T | string
166
+ align?: "start" | "center" | "end"
167
+ sortable?: boolean
168
+ width?: string | number
169
+ }>
170
+ itemsPerPage?: number
171
+
172
+ // Create button
173
+ showCreateButton?: boolean
174
+ createInteractionLinkId?: string
175
+
176
+ // Row actions
177
+ showActionsColumn?: boolean
178
+ showDeleteAction?: boolean
179
+
180
+ // Navigation
181
+ editInteractionLinkId?: string
182
+
183
+ // Item processing
184
+ deleteValidator?: (item: T) => boolean
185
+ rowClickHandler?: (item: T) => void
186
+
187
+ dataName: string
188
+ dataArticle: string
189
+ }
190
+
191
+ const props = withDefaults(defineProps<BaseDataTableProps<T>>(), {
192
+ uuid: undefined,
193
+ items: () => [],
194
+ itemsPerPage: 50,
195
+ showCreateButton: true,
196
+ showActionsColumn: true,
197
+ showDeleteAction: true,
198
+ })
199
+
200
+ const emit = defineEmits<{
201
+ create: []
202
+ edit: [item: T]
203
+ delete: [item: T]
204
+ rowClick: [item: T]
205
+ }>()
206
+
207
+ const slots = useSlots()
208
+ const { triggerInteractionLinkById } = useRouteState()
209
+
210
+ // Reactive data
211
+ const search = ref("")
212
+ const deleteDialog = ref(false)
213
+ const itemToDelete = ref<T | null>(null)
214
+
215
+ const computedHeaders = computed(() => {
216
+ if (!props.headers || !props.headers?.length) {
217
+ return null
218
+ }
219
+
220
+ const headers = [...props.headers]
221
+
222
+ // Add actions column if needed
223
+ if (props.showActionsColumn) {
224
+ headers.push({
225
+ title: "Actions",
226
+ key: "actions",
227
+ align: "end" as const,
228
+ sortable: false,
229
+ })
230
+ }
231
+
232
+ return headers
233
+ })
234
+
235
+ const customSlots = computed(() => {
236
+ return Object.keys(slots).filter(
237
+ (slot) => slot.startsWith("item.") && slot !== "item.actions",
238
+ )
239
+ })
240
+
241
+ // Methods
242
+ const handleCreate = () => {
243
+ if (props.createInteractionLinkId) {
244
+ triggerInteractionLinkById(props.createInteractionLinkId)
245
+ }
246
+ emit("create")
247
+ }
248
+
249
+ const handleRowClick = (_: PointerEvent, row: { item: T }) => {
250
+ if (props.rowClickHandler) {
251
+ props.rowClickHandler(row.item)
252
+ return
253
+ }
254
+
255
+ if (props.editInteractionLinkId) {
256
+ const item = row.item as any
257
+
258
+ triggerInteractionLinkById(props.editInteractionLinkId, {
259
+ id: item._id,
260
+ })
261
+ }
262
+
263
+ emit("edit", row.item)
264
+ emit("rowClick", row.item)
265
+ }
266
+
267
+ const handleDelete = (item: T) => {
268
+ itemToDelete.value = item
269
+ deleteDialog.value = true
270
+ }
271
+
272
+ const confirmDelete = () => {
273
+ if (itemToDelete.value) {
274
+ emit("delete", itemToDelete.value)
275
+ }
276
+
277
+ itemToDelete.value = null
278
+ deleteDialog.value = false
279
+ }
280
+
281
+ const isDeleteDisabled = (item: T): boolean => {
282
+ return props.deleteValidator ? !props.deleteValidator(item) : false
283
+ }
284
+ </script>
@@ -0,0 +1,207 @@
1
+ <template>
2
+ <div class="tw:p-6" :class="{ 'tw:max-w-6xl': !fluid }">
3
+ <div v-if="title" class="tw:mb-6 tw:flex tw:gap-1 tw:items-center">
4
+ <v-btn
5
+ v-if="routeBackInteractionLinkId"
6
+ @click="navigateBack"
7
+ density="comfortable"
8
+ icon="mdi-chevron-left"
9
+ flat
10
+ />
11
+ <h1 v-if="title" class="tw:text-2xl tw:font-bold">
12
+ {{ title }}
13
+ </h1>
14
+ </div>
15
+
16
+ <!-- body -->
17
+ <div class="tw:grid tw:gap-6">
18
+ <template v-for="(value, key) in props.data" :key="key">
19
+ <TextDivider v-if="'divider' in value" :label="value.divider" />
20
+
21
+ <DynamicInput
22
+ v-else
23
+ v-model="modelProxy[value.field]"
24
+ :input-type="value.inputType"
25
+ :value="value"
26
+ :disabled="!!disabled"
27
+ :error-message="getValidationError(value)"
28
+ @validate="validateField(value)"
29
+ />
30
+ </template>
31
+ </div>
32
+
33
+ <!-- button area -->
34
+ <slot name="button-area-override">
35
+ <div
36
+ class="tw:mt-8 tw:flex tw:gap-4 tw:justify-end"
37
+ v-if="$slots?.['button-area']"
38
+ >
39
+ <slot name="button-area" v-bind="{ isValid, validateAllFields }" />
40
+ <!-- <v-btn color="primary" variant="outlined"> Validate Form </v-btn>
41
+ <v-btn color="secondary" variant="outlined"> Reset Form </v-btn>
42
+ <v-btn color="info" variant="outlined"> Show Form Data </v-btn> -->
43
+ </div>
44
+ </slot>
45
+ </div>
46
+ </template>
47
+
48
+ <script setup lang="ts" generic="T">
49
+ import { computed, ref, watch, watchEffect } from "vue"
50
+ import DynamicInput from "./DynamicInput.vue"
51
+ import TextDivider from "./TextDivider.vue"
52
+ import { DynamicFormItem, DynamicFormField } from "./DynamicFormTypes"
53
+ import { useI18n } from "vue-i18n"
54
+ import { useRouteState } from "@raclettejs/core/orchestrator/composables"
55
+
56
+ const props = defineProps<{
57
+ title?: string
58
+ data: DynamicFormItem<T>[]
59
+ modelValue: T
60
+ disabled?: Boolean
61
+ validateOnChange?: boolean
62
+ fluid?: boolean
63
+ routeBackInteractionLinkId?: string
64
+ }>()
65
+
66
+ const emit = defineEmits<{
67
+ (e: "update:modelValue", value: T): void
68
+ (e: "validation-change", isValid: boolean): void
69
+ }>()
70
+
71
+ const { t } = useI18n()
72
+ const { triggerInteractionLinkById } = useRouteState()
73
+
74
+ const validationErrors = ref<Record<string, string>>({})
75
+
76
+ const isValid = computed(() => Object.keys(validationErrors.value).length === 0)
77
+
78
+ const modelProxy = computed({
79
+ get: () => props.modelValue,
80
+ set: (value: T) => emit("update:modelValue", value),
81
+ })
82
+
83
+ const validateValue = (
84
+ field: DynamicFormField<T>,
85
+ value: any,
86
+ ): string | null => {
87
+ const validation = field.validation
88
+
89
+ if (!validation) {
90
+ return null
91
+ }
92
+
93
+ const { required, minLength, maxLength, pattern, customValidator } =
94
+ validation
95
+
96
+ // Required validation
97
+ if (required) {
98
+ if (
99
+ value === null ||
100
+ value === undefined ||
101
+ value === "" ||
102
+ (Array.isArray(value) && value.length === 0) ||
103
+ (typeof value === "object" && Object.keys(value).length === 0)
104
+ ) {
105
+ return t("workbench.dynamicInputs.validation.required", {
106
+ field: field.title,
107
+ })
108
+ }
109
+ }
110
+
111
+ // Skip other validations if value is empty and not required
112
+ if (!required && (!value || value === "")) {
113
+ return null
114
+ }
115
+
116
+ // Min length validation
117
+ if (minLength && typeof value === "string" && value.length < minLength) {
118
+ return t("workbench.dynamicInputs.validation.minLength", {
119
+ field: field.title,
120
+ minLength,
121
+ })
122
+ }
123
+
124
+ // Max length validation
125
+ if (maxLength && typeof value === "string" && value.length > maxLength) {
126
+ return t("workbench.dynamicInputs.validation.maxLength", {
127
+ field: field.title,
128
+ maxLength,
129
+ })
130
+ }
131
+
132
+ // Pattern validation
133
+ if (pattern && typeof value === "string" && !pattern.test(value)) {
134
+ return t("workbench.dynamicInputs.validation.invalidFormat", {
135
+ field: field.title,
136
+ })
137
+ }
138
+
139
+ if (required && field.inputType === "localized") {
140
+ if (!value.default) {
141
+ return t("workbench.dynamicInputs.validation.defaultValueRequired")
142
+ }
143
+ }
144
+
145
+ // Custom validator
146
+ if (customValidator) {
147
+ return t(customValidator(value) || "")
148
+ }
149
+
150
+ return null
151
+ }
152
+
153
+ // Validate a single field
154
+ const validateField = (field: DynamicFormField<T>) => {
155
+ const value = props.modelValue[field.field]
156
+ const error = validateValue(field, value)
157
+
158
+ if (error) {
159
+ validationErrors.value[field.field as string] = error
160
+ } else {
161
+ delete validationErrors.value[field.field as string]
162
+ }
163
+
164
+ // Emit validation status
165
+ emit("validation-change", Object.keys(validationErrors.value).length === 0)
166
+ }
167
+
168
+ // Get validation error for a field
169
+ const getValidationError = (field: DynamicFormField<T>): string | undefined => {
170
+ return validationErrors.value[field.field as string]
171
+ }
172
+
173
+ // Validate all fields
174
+ const validateAllFields = () => {
175
+ props.data.forEach((item) => {
176
+ if ("field" in item) {
177
+ validateField(item)
178
+ }
179
+ })
180
+
181
+ return isValid.value
182
+ }
183
+
184
+ const navigateBack = () => {
185
+ if (props.routeBackInteractionLinkId) {
186
+ triggerInteractionLinkById(props.routeBackInteractionLinkId)
187
+ }
188
+ }
189
+
190
+ watch(
191
+ () => props.modelValue,
192
+ () => {
193
+ if (props.validateOnChange) {
194
+ validateAllFields()
195
+ }
196
+ },
197
+ { deep: true },
198
+ )
199
+
200
+ watchEffect(() => emit("validation-change", isValid.value))
201
+
202
+ defineExpose({
203
+ validateAllFields,
204
+ isValid,
205
+ errors: computed(() => validationErrors.value),
206
+ })
207
+ </script>
@@ -0,0 +1,63 @@
1
+ import type { MdiIcon } from "./typedInputs/mdi-icons"
2
+
3
+ export type DynamicInputType =
4
+ | "permissions"
5
+ | "tags"
6
+ | "localized"
7
+ | "composition"
8
+ | "triggers"
9
+ | "record"
10
+ | "list"
11
+ | "text"
12
+ | "textarea"
13
+ | "number"
14
+ | "checkbox"
15
+ | "switch"
16
+ | "select"
17
+ | "multiselect"
18
+ | "password"
19
+ | "color"
20
+ | "user"
21
+
22
+ interface DynamicFormItemBase<T> {
23
+ title: string
24
+ description?: string
25
+ inputType: DynamicInputType
26
+ field: keyof T
27
+ }
28
+
29
+ export interface InputAttributes {
30
+ prependIcon?: MdiIcon
31
+ prependInnerIcon?: MdiIcon
32
+ appendIcon?: MdiIcon
33
+ appendInnerIcon?: MdiIcon
34
+ prefix?: string
35
+ suffix?: string
36
+ // click handlers
37
+ onClickPrepend?: (event: PointerEvent) => void
38
+ onClickPrependInner?: (event: PointerEvent) => void
39
+ onClickAppend?: (event: PointerEvent) => void
40
+ onClickAppendInner?: (event: PointerEvent) => void
41
+ }
42
+
43
+ interface SelectionProps {
44
+ selectItems?: string[]
45
+ }
46
+
47
+ export interface ValidationProps {
48
+ required?: boolean
49
+ minLength?: number
50
+ maxLength?: number
51
+ pattern?: RegExp
52
+ customValidator?: (value: any) => string | null // return error message or null if valid
53
+ }
54
+
55
+ export interface DynamicFormDivider {
56
+ divider: string
57
+ }
58
+
59
+ export type DynamicFormField<T> = DynamicFormItemBase<T> &
60
+ InputAttributes &
61
+ SelectionProps & { validation?: ValidationProps }
62
+
63
+ export type DynamicFormItem<T> = DynamicFormField<T> | DynamicFormDivider