PluginProbe
The Innovative Form Builder – IvyForms / 0.1.2
The Innovative Form Builder – IvyForms v0.1.2
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 / _components / form / IvyFormItem.vue

IvyFormItem.vue in The Innovative Form Builder – IvyForms 0.1.2, at frontend/src/views/_components/form/IvyFormItem.vue

372 lines 9.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <template>
2 <div ref="formItem" class="ivyforms-form-item">
3 <ElFormItem
4 v-bind="{ ...$props, ...$attrs }"
5 :class="[
6 {
7 'has-value': hasValue,
8 'is-active': isFormItemActive,
9 'is-form-element-hovered': isContentHovered,
10 'is-disabled': isFormItemDisabled,
11 'is-warning': warning,
12 'is-secondary': secondary,
13 },
14 getFormItemTypeClass(),
15 ]"
16 >
17 <template #label>
18 <slot name="label" />
19 <div class="ivyforms-form-item__left-label">
20 <!-- Label-->
21 <div class="ivyforms-form-item__label">
22 <span>{{ props.label }}</span>
23 </div>
24
25 <!-- Tooltip-->
26 <IvyTooltip
27 v-if="tooltip"
28 :content="tooltip"
29 raw-content
30 placement="top"
31 class="ivyforms-form-item__tooltip"
32 >
33 <IvyIcon
34 name="question"
35 type="outline"
36 size="l"
37 outer-size="16px"
38 color="var(--map-base-dusk-symbol-2)"
39 @click.prevent
40 />
41 </IvyTooltip>
42 </div>
43
44 <!-- Right Label-->
45 <div v-if="rightLabel" class="ivyforms-form-item__right-label">
46 <!-- TODO This should be a link component with an icon-->
47 {{ props.rightLabel }}
48 </div>
49 </template>
50 <template #error="{ error }">
51 <slot name="error" />
52 <div class="el-form-item__error">
53 <IvyIcon
54 name="danger"
55 size="l"
56 type="fill-duo"
57 outer-size="16px"
58 color="var(--map-status-error-symbol-0)"
59 />
60 {{ error }}
61 </div>
62 </template>
63 <slot />
64 <div v-if="showInfo" class="ivyforms-form-item__info" :class="`ivyforms-${props.infoType}`">
65 <IvyIcon
66 :name="infoIcon()"
67 type="fill-duo"
68 size="l"
69 :color="infoIconColor()"
70 outer-size="16px"
71 />
72 <div
73 class="ivyforms-form-item__info-text"
74 :style="{ '--ivyforms-info-color': infoIconColor() }"
75 >
76 {{ props.infoText }}
77 </div>
78 </div>
79 </ElFormItem>
80 </div>
81 </template>
82
83 <script setup lang="ts">
84 import { ref, provide, nextTick } from 'vue'
85 import type { Editor } from '@tiptap/core'
86
87 interface Props {
88 tooltip?: string
89 label?: string
90 rightLabel?: string
91 inputRef?: { focus?: () => void; editor?: Editor; hasContent?: () => boolean }
92 showInfo?: boolean
93 infoType?: 'default' | 'success' | 'warning' | 'info' | 'error'
94 infoText?: string
95 staticLabel?: boolean
96 warning?: boolean
97 secondary?: boolean
98 }
99
100 const props = withDefaults(defineProps<Props>(), {
101 tooltip: '',
102 label: '',
103 rightLabel: '',
104 inputRef: () => ({}),
105 showInfo: false,
106 infoType: 'default',
107 infoText: '',
108 staticLabel: false,
109 warning: false,
110 secondary: false,
111 })
112 const formItem = ref<HTMLElement | null>(null)
113 const hasValue = ref(false)
114 const isFormItemActive = ref(false)
115 const isContentHovered = ref(false)
116 const isFormItemDisabled = ref(false)
117
118 const getFormItemType = () => {
119 if (formItem.value?.querySelector('.el-date-editor')) {
120 return 'datepicker'
121 } else if (formItem.value?.querySelector('.el-select')) {
122 return 'select'
123 } else if (formItem.value?.querySelector('.ivyforms-phone-input')) {
124 return 'phone'
125 } else if (formItem.value?.querySelector('.el-textarea')) {
126 return 'textarea'
127 } else if (formItem.value?.querySelector('.el-input')) {
128 return 'input'
129 } else if (formItem.value?.querySelector('.ivyforms-editor')) {
130 return 'editor'
131 } else {
132 return ''
133 }
134 }
135
136 const getFormItemTypeClass = () => {
137 return `ivyforms-form-item-${getFormItemType()}`
138 }
139
140 const infoIcon = () => {
141 switch (props.infoType) {
142 case 'success':
143 return 'check-circle'
144 default:
145 return 'info'
146 }
147 }
148
149 const infoIconColor = () => {
150 switch (props.infoType) {
151 case 'success':
152 return 'var(--map-status-success-symbol-0)'
153 case 'warning':
154 return 'var(--map-status-warning-symbol-0)'
155 case 'error':
156 return 'var(--map-status-error-symbol-0)'
157 case 'info':
158 return 'var(--map-base-text-0)'
159 default:
160 return 'var(--map-base-text-0)'
161 }
162 }
163
164 const updateHasValue = () => {
165 nextTick(() => {
166 const input = formItem.value?.querySelector('.el-input__inner') as HTMLInputElement | null
167 const textarea = formItem.value?.querySelector('.el-textarea__inner') as HTMLInputElement | null
168 const select = formItem.value?.querySelector('.el-select') as HTMLElement | null
169
170 if (input) {
171 hasValue.value = input.value !== ''
172 } else if (textarea) {
173 hasValue.value = textarea.value !== ''
174 } else if (select) {
175 checkSelectHasValue()
176 }
177 })
178 }
179
180 const checkSelectHasValue = () => {
181 const parent = formItem.value?.querySelector(
182 '.ivyforms-select, .ivyforms-time-select, .ivyforms-multi-select',
183 )
184
185 if (parent) {
186 hasValue.value = parent.classList.contains('has-value')
187 }
188 }
189 defineExpose({
190 updateHasValue,
191 })
192 provide('isSecondary', props.secondary)
193 </script>
194
195 <style lang="scss">
196 //Form Item
197 .ivyforms-form-item {
198 position: relative;
199
200 // Element Form Item
201 .el-form-item {
202 flex-direction: column;
203
204 // Content
205 .el-form-item__content {
206 display: flex;
207 flex-direction: column;
208 gap: var(--Spacing-xs, 6px);
209
210 // Info message
211 .el-form-item__error {
212 display: flex;
213 align-items: center;
214 padding: 0 var(--Spacing-4xs, 1px);
215 gap: var(--Spacing-2xs, 4px);
216 align-self: stretch;
217 font-size: 14px;
218 font-style: normal;
219 font-weight: 400;
220 line-height: 20px;
221 }
222 }
223
224 // Label
225 .el-form-item__label {
226 display: flex;
227 align-items: center;
228 justify-content: space-between;
229 padding: 0;
230 margin: 0;
231 height: auto;
232
233 // Mandatory astrix
234 &::before {
235 display: none !important;
236 }
237
238 // Left label
239 .ivyforms-form-item__left-label {
240 align-items: center;
241 display: flex;
242 gap: 4px;
243
244 // Label
245 .ivyforms-form-item__label {
246 cursor: text;
247 gap: 2px;
248 background-color: transparent;
249 color: var(--map-base-text-0);
250 font-size: 14px;
251 font-style: normal;
252 font-weight: 500;
253 line-height: 20px;
254 padding: 0;
255 order: 0;
256 height: auto;
257 }
258
259 // Tooltip
260 .ivyforms-form-item__tooltip {
261 display: flex;
262 align-items: center;
263 order: 2;
264
265 .el-tooltip__trigger {
266 height: 20px;
267 display: flex;
268 align-items: center;
269 }
270 }
271 }
272
273 // Right label
274 .ivyforms-form-item__right-label {
275 order: 3;
276 margin-left: auto;
277 color: var(--map-base-dusk-symbol-2);
278 text-align: center;
279 font-size: 14px;
280 font-style: normal;
281 font-weight: 500;
282 line-height: 20px;
283 text-decoration-line: underline;
284 text-decoration-style: solid;
285 text-decoration-skip-ink: auto;
286 text-decoration-color: var(--map-base-dusk-symbol-2);
287 text-decoration-thickness: 10%;
288 text-underline-offset: 15.5%;
289 text-underline-position: from-font;
290 }
291 }
292
293 // Custom mandatory astrix positioning
294 &.is-required {
295 .el-form-item__label {
296 .ivyforms-form-item__left-label {
297 &::after {
298 content: '*';
299 color: var(--map-status-error-fill-0);
300 order: 1;
301 }
302 }
303 }
304 }
305
306 // Icons
307 .ivyforms-icon:not(.ivyforms-input__icon-end):not(.ivyforms-input__icon-start):not(
308 .ivyforms-input__clear
309 ):not(.selector__country-flag) {
310 display: contents;
311
312 svg {
313 transform: scale(0.67);
314 }
315 }
316
317 // Disabled
318 &.is-disabled {
319 opacity: 0.5;
320 cursor: not-allowed;
321
322 // Label
323 .el-form-item__label {
324 cursor: not-allowed !important;
325 }
326 }
327
328 // Filled
329 &.is-filled {
330 .el-select__wrapper {
331 border: 1px solid var(--map-base-dusk-stroke--2);
332 background: var(--map-base-dusk-o05);
333
334 // Hover
335 &:hover {
336 border: 1px solid var(--map-base-dusk-stroke-0);
337 background: var(--map-base-dusk-o05);
338 }
339
340 // Active & Focus
341 &:active,
342 &.is-active,
343 &:focus,
344 &.is-focus,
345 &:focus-within {
346 border: 1px solid var(--map-base-brand-stroke-0);
347 background: var(--map-base-dusk-o05);
348 }
349 }
350 }
351
352 // Hint info message
353 .ivyforms-form-item__info {
354 display: flex;
355 align-items: center;
356 padding: 0 var(--Spacing-4xs, 1px);
357 gap: var(--Spacing-2xs, 4px);
358 align-self: stretch;
359 font-size: 14px;
360 font-style: normal;
361 font-weight: 400;
362 line-height: 20px;
363 height: 20px;
364
365 .ivyforms-form-item__info-text {
366 color: var(--ivyforms-info-color);
367 }
368 }
369 }
370 }
371 </style>
372