PluginProbe
The Innovative Form Builder – IvyForms / 0.2
The Innovative Form Builder – IvyForms v0.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.2, at frontend/src/views/_components/form/IvyFormItem.vue

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