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 / dialog / IvyDialog.vue

IvyDialog.vue in The Innovative Form Builder – IvyForms 0.2, at frontend/src/views/_components/dialog/IvyDialog.vue

294 lines 6.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <template>
2 <div class="ivyforms-dialog-wrapper">
3 <ElDialog
4 ref="dialogRef"
5 v-model="localModelValue"
6 v-bind="{ ...$attrs, ...$props }"
7 :show-close="false"
8 :style="{ bottom: dialogBottom + 'px' }"
9 class="ivyforms-dialog"
10 :class="[`type-${props.type}`]"
11 :width="props.width"
12 @open="onOpen()"
13 >
14 <template #header>
15 <div ref="dialogHeader" class="ivyforms-dialog__header">
16 <div class="ivyforms-dialog__header__drag-handle" />
17 <div v-if="$slots.header" class="ivyforms-dialog__header__title-wrapper">
18 <slot name="header" />
19 </div>
20 <div
21 v-if="props.title && !$slots.header"
22 class="ivyforms-dialog__header__title-wrapper ivyforms-flex ivyforms-flex-direction-column ivyforms-gap-8"
23 >
24 <div
25 class="ivyforms-dialog__header__title ivyforms-flex ivyforms-gap-8 ivyforms-justify-content-between ivyforms-align-items-center"
26 >
27 <span class="medium-18">{{ props.title }}</span>
28 <slot name="titleButton" />
29 </div>
30 <span v-if="props.subtitle" class="regular-16 ivyforms-text-secondary">
31 {{ props.subtitle }}
32 </span>
33 </div>
34 <div v-if="showClose" class="ivyforms-dialog__header__close">
35 <IvyIcon
36 priority="tertiary"
37 name="close"
38 type="outline"
39 size="s"
40 category="global"
41 color="var(--map-base-dusk-symbol-2)"
42 @click="localModelValue = false"
43 />
44 </div>
45 </div>
46 </template>
47 <div v-if="!!$slots.default" class="ivyforms-dialog__body">
48 <slot />
49 </div>
50 <template v-if="$slots.footer" #footer>
51 <slot name="footer" />
52 </template>
53 </ElDialog>
54 </div>
55 </template>
56
57 <script setup lang="ts">
58 import { useSwipe } from '@vueuse/core'
59 import { computed, ref } from 'vue'
60 interface Props {
61 modelValue: boolean
62 title?: string
63 subtitle?: string | null
64 showClose?: boolean
65 width?: string
66 type?: 'fill' | 'r-alinged' | 'option'
67 }
68
69 const props = withDefaults(defineProps<Props>(), {
70 modelValue: false,
71 title: '',
72 subtitle: '',
73 showClose: true,
74 width: '500',
75 type: 'fill',
76 })
77
78 const emit = defineEmits(['update:modelValue'])
79
80 const dialogHeader = ref<HTMLElement | null>(null)
81 const dialogBottom = ref(0)
82 const dialogHeight = ref(0)
83 const dialogRef = ref(null)
84
85 const localModelValue = computed({
86 get() {
87 return props.modelValue
88 },
89 set(value) {
90 emit('update:modelValue', value)
91 },
92 })
93
94 const onOpen = () => {
95 dialogBottom.value = 0
96 calculateDialogHeight()
97
98 const { direction, lengthY } = useSwipe(dialogHeader, {
99 threshold: 0,
100 onSwipe() {
101 if (direction.value === 'down') {
102 dialogBottom.value = Math.min(lengthY.value, window.innerHeight)
103 } else if (direction.value === 'up') {
104 dialogBottom.value = Math.max(dialogBottom.value - lengthY.value, 0)
105 }
106 },
107 onSwipeEnd() {
108 if (direction.value === 'down' && Math.abs(lengthY.value) > dialogHeight.value / 2) {
109 localModelValue.value = false
110 } else {
111 dialogBottom.value = 0
112 }
113 },
114 })
115
116 dialogRef.value.$el.nextSibling.querySelector('.el-overlay-dialog').scrollTo(0, 0)
117 }
118
119 const calculateDialogHeight = () => {
120 const dialogElement = document.querySelector('.ivyforms-dialog')
121 if (dialogElement) {
122 dialogHeight.value = dialogElement.clientHeight
123 }
124 }
125 </script>
126
127 <style lang="scss">
128 @use 'sass:list' as *;
129 @use 'sass:meta' as *;
130 // Dialog
131 .ivyforms-dialog {
132 display: flex;
133 max-width: 100%;
134 padding: 0;
135 flex-direction: column;
136 align-items: flex-start;
137 border-radius: 16px;
138 background-color: var(--theme-ground-level-2-foreground);
139 margin: 0 auto;
140 max-height: calc(100% - 78px);
141 box-shadow: var(--shadow-500);
142 position: absolute;
143 bottom: 0;
144 overflow: hidden;
145
146 // Tablet And Up
147 @include tablet-and-up {
148 max-height: unset;
149 border-radius: 12px;
150 position: relative;
151 min-width: unset;
152 }
153
154 // Element Dialog
155 &.el-dialog {
156 border-radius: 16px;
157 background-color: var(--theme-ground-level-2-foreground);
158 padding: 0;
159 // Element Header
160 .el-dialog__header {
161 padding: 24px 24px 8px 24px;
162 width: 100%;
163 }
164
165 .el-dialog__header {
166 padding: 0;
167 }
168 // Default Header
169 .ivyforms-dialog__header {
170 width: 100%;
171 display: flex;
172 justify-content: space-between;
173 padding: 24px 24px 8px 24px;
174 box-sizing: border-box;
175 flex-direction: column;
176 background-color: var(--theme-ground-level-2-foreground);
177 // Tablet And Up
178 @include tablet-and-up {
179 padding: 24px 24px 8px 24px;
180 }
181
182 // Drag Handle
183 &__drag-handle {
184 width: 32px;
185 height: 4px;
186 border-radius: 100px;
187 opacity: 0.4;
188 background: var(--theme-base-dusk-0);
189 margin: 16px auto 16px;
190
191 // Tablet And Up
192 @include tablet-and-up {
193 display: none;
194 }
195 }
196
197 // Title
198 &__title {
199 // Tablet And Up
200 @include tablet-and-up {
201 margin-right: 16px;
202 }
203 span {
204 color: var(--map-base-text-0);
205 }
206 }
207
208 // Close
209 &__close {
210 display: none;
211 position: absolute;
212 top: 8px;
213 right: 10px;
214 cursor: pointer;
215
216 // Tablet And Up
217 @include tablet-and-up {
218 display: flex;
219 }
220 }
221 .ivyforms-text-secondary {
222 color: var(--map-base-text--1);
223 }
224 }
225
226 // Element Body
227 .el-dialog__body {
228 display: flex;
229 width: 100%;
230 height: 100%;
231 box-sizing: border-box;
232 color: var(--map-base-text-0);
233 background-color: var(--theme-ground-level-2-foreground);
234 overflow: auto;
235
236 // Tablet And Up
237 @include tablet-and-up {
238 height: initial;
239 max-height: unset;
240 }
241
242 // Body
243 .ivyforms-dialog__body {
244 width: 100%;
245 // Tablet And Up
246 @include tablet-and-up {
247 padding: 24px;
248 }
249
250 .ivyforms-checkbox {
251 margin-top: 8px;
252 }
253 }
254 }
255
256 // Element Footer
257 .el-dialog__footer {
258 display: flex;
259 justify-content: flex-end;
260 gap: 12px;
261 width: 100%;
262 padding: 12px 24px 24px 24px;
263 border-top: 1px solid var(--map-base-dusk-stroke--2);
264 background-color: var(--theme-ground-level-2-foreground);
265 margin-top: auto;
266 position: sticky;
267 bottom: 0;
268 z-index: 2;
269
270 // Button
271 button {
272 flex: 1;
273
274 // Tablet And Up
275 @include tablet-and-up {
276 flex: 0 1 auto;
277 }
278 }
279 }
280 }
281 }
282
283 // Overlay
284 .el-overlay {
285 overflow: hidden;
286 background-color: var(--theme-overlay);
287
288 // Tablet And Up
289 @include tablet-and-up {
290 overflow: auto;
291 }
292 }
293 </style>
294