PluginProbe
The Innovative Form Builder – IvyForms / 0.5
The Innovative Form Builder – IvyForms v0.5
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / frontend / src / views / admin / form-builder / fields / address / AddressField.vue

AddressField.vue in The Innovative Form Builder – IvyForms 0.5, at frontend/src/views/admin/form-builder/fields/address/AddressField.vue

493 lines 16.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <template>
2 <div
3 class="ivyforms-field__address ivyforms-width-100"
4 :class="[
5 { 'ivyforms-field__address--readonly': !!field?.readOnly },
6 `ivyforms-field__address--label-${effectiveLabelPosition}`,
7 ]"
8 >
9 <div
10 v-if="
11 !field?.hideLabel &&
12 (effectiveLabelPosition === 'top' || effectiveLabelPosition === 'default')
13 "
14 class="ivyforms-field__address-label ivyforms-mb-16 regular-16"
15 >
16 {{ field?.label || getLabel('address') }}
17 </div>
18 <div
19 class="ivyforms-field__address-container ivyforms-width-100"
20 :class="{
21 'ivyforms-field__address-container--flex-row':
22 effectiveLabelPosition === 'left' || effectiveLabelPosition === 'right',
23 'ivyforms-field__address-container--flex-column':
24 effectiveLabelPosition === 'top' || effectiveLabelPosition === 'default',
25 }"
26 >
27 <template v-if="effectiveLabelPosition === 'left'">
28 <div
29 v-if="!field?.hideLabel"
30 class="ivyforms-field__address-label ivyforms-field__address-label--left regular-16 ivyforms-mr-16 ivyforms-flex ivyforms-align-items-center"
31 >
32 {{ field?.label || getLabel('address') }}
33 </div>
34 <div class="ivyforms-field__address-fields ivyforms-width-100">
35 <!-- First 3 fields: streetAddress, addressLine2, city -->
36 <div
37 v-for="fieldData in addressFields.slice(0, 3)"
38 :key="fieldData.type"
39 class="ivyforms-field__address-row ivyforms-field__address-row--full ivyforms-flex ivyforms-flex-direction-row ivyforms-width-100"
40 >
41 <div
42 :class="[
43 'ivyforms-field__address-field ivyforms-flex-1',
44 fieldData.type.toLowerCase() + '-address',
45 ]"
46 >
47 <IvyFormItem
48 :label="fieldData.hideLabel ? '' : fieldData.label"
49 :required="fieldData.required"
50 >
51 <IvyTextInput
52 :id="fieldData.id"
53 :aria-label="fieldData.label"
54 readonly
55 :model-value="fieldData.value"
56 type="text"
57 :placeholder="fieldData.placeholder"
58 />
59 <div
60 v-if="fieldData.description"
61 class="ivyforms-field__address-description regular-14"
62 >
63 {{ fieldData.description }}
64 </div>
65 </IvyFormItem>
66 </div>
67 </div>
68 <!-- Last 3 fields: state, zip, country -->
69 <div
70 class="ivyforms-field__address-row ivyforms-field__address-row--three-cols ivyforms-flex ivyforms-flex-direction-row ivyforms-gap-16 ivyforms-width-100"
71 >
72 <div
73 v-for="fieldData in addressFields.slice(3, 6)"
74 :key="fieldData.type"
75 :class="[
76 'ivyforms-field__address-field ivyforms-flex-1',
77 fieldData.type.toLowerCase() + '-address',
78 ]"
79 >
80 <IvyFormItem
81 :label="fieldData.hideLabel ? '' : fieldData.label"
82 :required="fieldData.required"
83 >
84 <template v-if="fieldData.type === 'country'">
85 <IvySelectInput
86 :id="fieldData.id"
87 v-model="countryValue"
88 :placeholder="getLabel('select_country')"
89 disabled
90 readonly
91 >
92 <IvySelectOption value="">{{ getLabel('select_country') }}</IvySelectOption>
93 <IvySelectOption
94 v-for="country in countryOptions"
95 :key="country"
96 :value="country"
97 >{{ country }}</IvySelectOption
98 >
99 </IvySelectInput>
100 </template>
101 <template v-else>
102 <IvyTextInput
103 :id="fieldData.id"
104 :aria-label="fieldData.label"
105 readonly
106 :model-value="fieldData.value"
107 type="text"
108 :placeholder="fieldData.placeholder"
109 />
110 </template>
111 <div
112 v-if="fieldData.description"
113 class="ivyforms-field__address-description regular-14"
114 >
115 {{ fieldData.description }}
116 </div>
117 </IvyFormItem>
118 </div>
119 </div>
120 </div>
121 </template>
122 <template v-else-if="effectiveLabelPosition === 'right'">
123 <div class="ivyforms-field__address-fields ivyforms-width-100">
124 <!-- First 3 fields: streetAddress, addressLine2, city -->
125 <div
126 v-for="fieldData in addressFields.slice(0, 3)"
127 :key="fieldData.type"
128 class="ivyforms-field__address-row ivyforms-field__address-row--full ivyforms-flex ivyforms-flex-direction-row ivyforms-width-100"
129 >
130 <div
131 :class="[
132 'ivyforms-field__address-field ivyforms-flex-1',
133 fieldData.type.toLowerCase() + '-address',
134 ]"
135 >
136 <IvyFormItem
137 :label="fieldData.hideLabel ? '' : fieldData.label"
138 :required="fieldData.required"
139 >
140 <IvyTextInput
141 :id="fieldData.id"
142 :aria-label="fieldData.label"
143 readonly
144 :model-value="fieldData.value"
145 type="text"
146 :placeholder="fieldData.placeholder"
147 />
148 <div
149 v-if="fieldData.description"
150 class="ivyforms-field__address-description regular-14"
151 >
152 {{ fieldData.description }}
153 </div>
154 </IvyFormItem>
155 </div>
156 </div>
157 <!-- Last 3 fields: state, zip, country -->
158 <div
159 class="ivyforms-field__address-row ivyforms-field__address-row--three-cols ivyforms-flex ivyforms-flex-direction-row ivyforms-gap-16 ivyforms-width-100"
160 >
161 <div
162 v-for="fieldData in addressFields.slice(3, 6)"
163 :key="fieldData.type"
164 :class="[
165 'ivyforms-field__address-field ivyforms-flex-1',
166 fieldData.type.toLowerCase() + '-address',
167 ]"
168 >
169 <IvyFormItem
170 :label="fieldData.hideLabel ? '' : fieldData.label"
171 :required="fieldData.required"
172 >
173 <template v-if="fieldData.type === 'country'">
174 <IvySelectInput
175 :id="fieldData.id"
176 v-model="countryValue"
177 :placeholder="getLabel('select_country')"
178 disabled
179 readonly
180 >
181 <IvySelectOption value="">{{ getLabel('select_country') }}</IvySelectOption>
182 <IvySelectOption
183 v-for="country in countryOptions"
184 :key="country"
185 :value="country"
186 >{{ country }}</IvySelectOption
187 >
188 </IvySelectInput>
189 </template>
190 <template v-else>
191 <IvyTextInput
192 :id="fieldData.id"
193 :aria-label="fieldData.label"
194 readonly
195 :model-value="fieldData.value"
196 type="text"
197 :placeholder="fieldData.placeholder"
198 />
199 </template>
200 <div
201 v-if="fieldData.description"
202 class="ivyforms-field__address-description regular-14"
203 >
204 {{ fieldData.description }}
205 </div>
206 </IvyFormItem>
207 </div>
208 </div>
209 </div>
210 <div
211 v-if="!field?.hideLabel"
212 class="ivyforms-field__address-label ivyforms-field__address-label--right regular-16 ivyforms-ml-16 ivyforms-flex ivyforms-align-items-center"
213 >
214 {{ field?.label || getLabel('address') }}
215 </div>
216 </template>
217 <template v-else>
218 <div class="ivyforms-field__address-fields ivyforms-width-100">
219 <!-- First 3 fields: streetAddress, addressLine2, city -->
220 <div
221 v-for="fieldData in addressFields.slice(0, 3)"
222 :key="fieldData.type"
223 class="ivyforms-field__address-row ivyforms-field__address-row--full ivyforms-flex ivyforms-flex-direction-row ivyforms-width-100"
224 >
225 <div
226 :class="[
227 'ivyforms-field__address-field ivyforms-flex-1',
228 fieldData.type.toLowerCase() + '-address',
229 ]"
230 >
231 <IvyFormItem
232 :label="fieldData.hideLabel ? '' : fieldData.label"
233 :required="fieldData.required"
234 >
235 <IvyTextInput
236 :id="fieldData.id"
237 :aria-label="fieldData.label"
238 readonly
239 :model-value="fieldData.value"
240 type="text"
241 :placeholder="fieldData.placeholder"
242 />
243 <div
244 v-if="fieldData.description"
245 class="ivyforms-field__address-description regular-14"
246 >
247 {{ fieldData.description }}
248 </div>
249 </IvyFormItem>
250 </div>
251 </div>
252 <!-- Last 3 fields: state, zip, country -->
253 <div
254 class="ivyforms-field__address-row ivyforms-field__address-row--three-cols ivyforms-flex ivyforms-flex-direction-row ivyforms-gap-16 ivyforms-width-100"
255 >
256 <div
257 v-for="fieldData in addressFields.slice(3, 6)"
258 :key="fieldData.type"
259 :class="[
260 'ivyforms-field__address-field ivyforms-flex-1',
261 fieldData.type.toLowerCase() + '-address',
262 ]"
263 >
264 <IvyFormItem
265 :label="fieldData.hideLabel ? '' : fieldData.label"
266 :required="fieldData.required"
267 >
268 <template v-if="fieldData.type === 'country'">
269 <IvySelectInput
270 :id="fieldData.id"
271 v-model="countryValue"
272 :placeholder="getLabel('select_country')"
273 disabled
274 readonly
275 >
276 <IvySelectOption value="">{{ getLabel('select_country') }}</IvySelectOption>
277 <IvySelectOption
278 v-for="country in countryOptions"
279 :key="country"
280 :value="country"
281 >{{ country }}</IvySelectOption
282 >
283 </IvySelectInput>
284 </template>
285 <template v-else>
286 <IvyTextInput
287 :id="fieldData.id"
288 :aria-label="fieldData.label"
289 readonly
290 :model-value="fieldData.value"
291 type="text"
292 :placeholder="fieldData.placeholder"
293 />
294 </template>
295 <div
296 v-if="fieldData.description"
297 class="ivyforms-field__address-description regular-14"
298 >
299 {{ fieldData.description }}
300 </div>
301 </IvyFormItem>
302 </div>
303 </div>
304 </div>
305 </template>
306 </div>
307 </div>
308 </template>
309
310 <script setup lang="ts">
311 import { computed } from 'vue'
312 import { useFormBuilder } from '@/stores/useFormBuilder'
313 import { useLabels } from '@/composables/useLabels'
314 import { countryList } from '@/constants/countries'
315 import type { AddressSubField } from '@/types/field.d.ts'
316
317 const { getLabel } = useLabels()
318
319 const props = defineProps<{ fieldIndex: number }>()
320 const formBuilderStore = useFormBuilder()
321
322 const field = computed(() => formBuilderStore.fields.find((f) => f.fieldIndex === props.fieldIndex))
323
324 const addressFields = computed<AddressSubField[]>(() =>
325 (field.value?.addressFields || []).filter((f: AddressSubField) => f.visible !== false),
326 )
327
328 const countryOptions = countryList.map((c) => c.name)
329
330 const countryValue = computed({
331 get: () => addressFields.value.find((f) => f.type === 'country')?.value || '',
332 set: () => {}, // No-op, readonly in builder
333 })
334
335 const effectiveLabelPosition = computed(() => {
336 const position = field.value?.labelPosition
337 if (position === 'default' || !position) {
338 return 'top'
339 }
340 return position
341 })
342 </script>
343
344 <style lang="scss" scoped>
345 .ivyforms-field__address {
346 cursor: move;
347 .ivyforms-field__address-label {
348 color: var(--map-base-text-0);
349 }
350 &--readonly {
351 opacity: 0.6;
352 }
353
354 &--label-left,
355 &--label-right {
356 .ivyforms-field__address-container {
357 @extend .ivyforms-flex;
358 @extend .ivyforms-flex-direction-row;
359 @extend .ivyforms-align-items-start;
360 }
361 .ivyforms-field__address-label {
362 &--left {
363 @extend .ivyforms-flex;
364 @extend .ivyforms-align-items-center;
365 @extend .ivyforms-mr-16;
366 min-width: fit-content;
367 }
368 &--right {
369 @extend .ivyforms-flex;
370 @extend .ivyforms-align-items-center;
371 @extend .ivyforms-ml-16;
372 min-width: fit-content;
373 }
374 }
375 }
376
377 &--label-top,
378 &--label-default {
379 .ivyforms-field__address {
380 &-container {
381 @extend .ivyforms-flex;
382 @extend .ivyforms-flex-direction-column;
383 }
384 &-label {
385 margin-bottom: 16px;
386 }
387 }
388 }
389
390 &-row {
391 @extend .ivyforms-flex;
392 @extend .ivyforms-flex-direction-row;
393 width: 100%;
394 &--full {
395 width: 100%;
396 }
397 &--three-cols {
398 gap: 16px;
399 width: 100%;
400 }
401 }
402
403 &-description {
404 font-size: 14px;
405 margin-top: 4px;
406 }
407
408 .ivyforms-form-item {
409 margin-bottom: 0;
410 cursor: move;
411
412 :deep(.ivyforms-form-item__label) {
413 display: flex;
414 align-items: center;
415 color: var(--map-base-text-0);
416 font-size: 14px;
417 font-style: normal;
418 font-weight: 500;
419 margin-bottom: 6px;
420 height: 20px !important;
421 cursor: move;
422 }
423
424 :deep(.el-form-item__label) {
425 cursor: move;
426 }
427
428 // Text inputs styling to match TextField behavior
429 .ivyforms-input.el-input {
430 :deep(.el-input__wrapper:hover),
431 :deep(.el-input__wrapper.is-focus),
432 :deep(.el-input__wrapper) {
433 border-radius: var(--Radius-radius-md, 8px);
434 border: 1px solid var(--map-base-dusk-stroke--2);
435 background: var(--map-base-dusk-o05);
436 box-shadow: none;
437 padding: 0 12px;
438 transition: none;
439 cursor: move;
440 }
441 :deep(input) {
442 border: none;
443 background: transparent;
444 cursor: move;
445 box-shadow: none;
446
447 &:focus {
448 outline: none;
449 border: 1px solid transparent;
450 background: none;
451 box-shadow: none;
452 }
453 }
454 }
455
456 // Select inputs styling to prevent focus
457 .ivyforms-select.el-select {
458 cursor: move;
459 :deep(.el-select__wrapper:hover),
460 :deep(.el-select__wrapper.is-focus),
461 :deep(.el-select__wrapper) {
462 border-radius: var(--Radius-radius-md, 8px);
463 border: 1px solid var(--map-base-dusk-stroke--2);
464 background: var(--map-base-dusk-o05);
465 box-shadow: none;
466 padding: 0 12px;
467 transition: none;
468 cursor: move;
469 }
470 :deep(.el-input) {
471 cursor: move;
472 }
473 :deep(.el-input__wrapper) {
474 cursor: move;
475 box-shadow: none;
476 }
477 :deep(.el-input__inner) {
478 cursor: move;
479 &:focus {
480 outline: none;
481 border: 1px solid transparent;
482 background: none;
483 box-shadow: none;
484 }
485 }
486 :deep(.el-select__caret) {
487 cursor: move;
488 }
489 }
490 }
491 }
492 </style>
493