PluginProbe
The Innovative Form Builder – IvyForms / 0.6
The Innovative Form Builder – IvyForms v0.6
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.6, at frontend/src/views/admin/form-builder/fields/address/AddressField.vue

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