PluginProbe
The Innovative Form Builder – IvyForms / 0.7
The Innovative Form Builder – IvyForms v0.7
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 / modal / IvyModal.vue

IvyModal.vue in The Innovative Form Builder – IvyForms 0.7, at frontend/src/views/_components/modal/IvyModal.vue

314 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <template>
2 <Teleport v-if="teleport" to="body">
3 <Transition name="ivyforms-modal">
4 <div
5 v-if="isVisible"
6 class="ivyforms-modal-overlay"
7 :class="{ 'ivyforms-modal-local': !teleport, 'ivyforms-modal-changelog': isChangelog }"
8 @click.self="handleOverlayClick"
9 >
10 <div
11 class="ivyforms-modal-container"
12 :class="{ 'ivyforms-modal-changelog-container': isChangelog }"
13 :style="modalStyles"
14 role="dialog"
15 aria-modal="true"
16 :aria-labelledby="title ? 'ivyforms-modal-title' : undefined"
17 >
18 <slot />
19 </div>
20 </div>
21 </Transition>
22 </Teleport>
23
24 <Transition v-else name="ivyforms-modal">
25 <div
26 v-if="isVisible"
27 class="ivyforms-modal-overlay ivyforms-modal-local"
28 :class="{ 'ivyforms-modal-changelog': isChangelog }"
29 @click.self="handleOverlayClick"
30 >
31 <div
32 class="ivyforms-modal-container"
33 :class="{ 'ivyforms-modal-changelog-container': isChangelog }"
34 :style="modalStyles"
35 role="dialog"
36 aria-modal="true"
37 :aria-labelledby="title ? 'ivyforms-modal-title' : undefined"
38 >
39 <slot />
40 </div>
41 </div>
42 </Transition>
43 </template>
44
45 <script setup lang="ts">
46 import { computed, onMounted, onUnmounted, watch } from 'vue'
47
48 interface Props {
49 visible: boolean
50 title?: string
51 width?: number | string
52 maxWidth?: number | string
53 showCloseButton?: boolean
54 showHeader?: boolean
55 closeOnOverlayClick?: boolean
56 closeOnEscape?: boolean
57 teleport?: boolean
58 isChangelog?: boolean
59 }
60
61 const props = withDefaults(defineProps<Props>(), {
62 title: '',
63 width: '',
64 maxWidth: '1300px',
65 showCloseButton: true,
66 showHeader: true,
67 closeOnOverlayClick: true,
68 closeOnEscape: true,
69 teleport: true,
70 isChangelog: false,
71 })
72
73 const emit = defineEmits<{
74 'update:visible': [value: boolean]
75 close: []
76 open: []
77 }>()
78
79 // Computed properties
80 const isVisible = computed({
81 get: () => props.visible,
82 set: (value) => emit('update:visible', value),
83 })
84
85 const modalStyles = computed(() => {
86 if (props.isChangelog) {
87 return {
88 width: typeof props.width === 'number' ? `${props.width}px` : props.width || '480px',
89 maxWidth: '90vw',
90 height: 'auto',
91 maxHeight: '85vh',
92 }
93 }
94
95 return {
96 width: typeof props.width === 'number' ? `${props.width}px` : props.width,
97 maxWidth: typeof props.maxWidth === 'number' ? `${props.maxWidth}px` : props.maxWidth,
98 }
99 })
100
101 // Methods
102 const handleClose = () => {
103 isVisible.value = false
104 emit('close')
105 }
106
107 const handleOverlayClick = () => {
108 if (props.closeOnOverlayClick) {
109 handleClose()
110 }
111 }
112
113 const handleEscapeKey = (event: KeyboardEvent) => {
114 if (event.key === 'Escape' && props.closeOnEscape && isVisible.value) {
115 handleClose()
116 }
117 }
118
119 const preventBodyScroll = () => {
120 document.body.style.overflow = 'hidden'
121 }
122
123 const restoreBodyScroll = () => {
124 document.body.style.overflow = ''
125 }
126
127 // Lifecycle hooks
128 onMounted(() => {
129 document.addEventListener('keydown', handleEscapeKey)
130 })
131
132 onUnmounted(() => {
133 document.removeEventListener('keydown', handleEscapeKey)
134 restoreBodyScroll()
135 })
136
137 // Watch for visibility changes
138 watch(isVisible, (newValue) => {
139 if (newValue) {
140 preventBodyScroll()
141 emit('open')
142 } else {
143 restoreBodyScroll()
144 }
145 })
146 </script>
147
148 <style scoped lang="scss">
149 .ivyforms-modal-overlay {
150 position: fixed;
151 inset: 0;
152 z-index: 9999;
153 background: var(--map-overlay);
154 display: flex;
155 justify-content: center;
156 align-items: flex-start;
157
158 &.ivyforms-modal-local {
159 @apply absolute;
160 position: absolute !important;
161 top: 0 !important;
162 left: 0 !important;
163 right: 0 !important;
164 bottom: 0 !important;
165 z-index: 100;
166 width: 100% !important;
167 height: 100% !important;
168 }
169
170 &.ivyforms-modal-changelog {
171 align-items: center;
172
173 .ivyforms-modal-container {
174 margin: 20px auto;
175 min-width: auto;
176 min-height: auto;
177 height: auto;
178 overflow: visible;
179
180 @media (max-width: 960px) {
181 width: calc(100vw - 2rem) !important;
182 max-width: calc(100vw - 2rem) !important;
183 margin: 20px auto;
184 }
185
186 @media (max-width: 600px) {
187 width: calc(100vw - 1rem) !important;
188 margin: 10px auto;
189 }
190 }
191 }
192
193 .ivyforms-modal-container {
194 width: 100%;
195 min-width: 800px;
196 height: calc(100vh - 80px);
197 max-height: none;
198 min-height: 600px;
199 padding: 0;
200 overflow-y: auto;
201 margin: 45px 20px 20px 180px;
202 border-radius: var(--Radius-radius-xl, 16px);
203 background: var(--map-ground-level-2-foreground);
204 box-shadow:
205 0 8px 12px 6px rgba(18, 26, 38, 0.15),
206 0 4px 4px 0 rgba(18, 26, 38, 0.3);
207 box-sizing: border-box;
208 position: relative;
209 z-index: 10000;
210
211 // Responsive adjustments
212 @media (max-width: 1200px) {
213 width: calc(100vw - 200px);
214 margin-left: 180px;
215 min-width: 700px;
216 height: calc(100vh - 60px);
217 min-height: 500px;
218 }
219
220 @media (max-width: 960px) {
221 width: calc(100vw - 2rem) !important;
222 max-width: calc(100vw - 2rem) !important;
223 min-width: auto !important;
224 height: calc(100vh - 40px) !important;
225 min-height: auto !important;
226 margin: 20px auto auto auto;
227 }
228
229 @media (max-width: 782px) {
230 width: calc(100vw - 76px);
231 margin-left: 56px;
232 min-width: auto;
233 height: calc(100vh - 60px);
234 min-height: 400px;
235 }
236
237 @media (max-width: 600px) {
238 width: calc(100vw - 1rem) !important;
239 margin: 10px auto auto auto;
240 min-width: auto !important;
241 height: calc(100vh - 20px) !important;
242 min-height: auto !important;
243 }
244
245 &.ivyforms-modal-changelog-container {
246 min-width: auto !important;
247 min-height: auto !important;
248 height: auto !important;
249 overflow: visible !important;
250 margin: 20px auto !important;
251
252 @media (max-width: 960px) {
253 width: calc(100vw - 2rem) !important;
254 max-width: calc(100vw - 2rem) !important;
255 }
256
257 @media (max-width: 600px) {
258 width: calc(100vw - 1rem) !important;
259 }
260 }
261 }
262 }
263 // Transitions
264 .ivyforms-modal-enter-active,
265 .ivyforms-modal-leave-active {
266 transition: opacity 0.3s ease;
267
268 .ivyforms-modal-container {
269 transition: transform 0.3s ease;
270 }
271 }
272
273 .ivyforms-modal-enter-from,
274 .ivyforms-modal-leave-to {
275 opacity: 0;
276
277 .ivyforms-modal-container {
278 transform: scale(0.9) translateY(-20px);
279 }
280 }
281 .ivyforms-fullscreen-mode {
282 .ivyforms-modal-overlay {
283 align-items: center;
284 padding: 20px;
285
286 .ivyforms-modal-container {
287 margin: 0 !important;
288 max-width: 1300px;
289
290 @media (max-width: 1200px) {
291 width: calc(100% - 40px);
292 margin: 0 !important;
293 }
294
295 @media (max-width: 960px) {
296 width: calc(100vw - 2rem) !important;
297 max-width: calc(100vw - 2rem) !important;
298 margin: 0 !important;
299 }
300
301 @media (max-width: 782px) {
302 width: calc(100vw - 2rem);
303 margin: 0 !important;
304 }
305
306 @media (max-width: 600px) {
307 width: calc(100vw - 1rem) !important;
308 margin: 0 !important;
309 }
310 }
311 }
312 }
313 </style>
314