PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.4.6
Booking for Appointments and Events Calendar – Amelia v2.4.6
2.4.8 2.4.7 2.4.6 2.4.5 2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / v3 / src / views / _components / collapse / AmCollapseItem.vue
ameliabooking / v3 / src / views / _components / collapse Last commit date
AmCollapse.vue 1 month ago AmCollapseItem.vue 1 month ago
AmCollapseItem.vue
508 lines
1 <template>
2 <div
3 class="am-collapse-item"
4 :class="[
5 { 'am-collapse-item-no-button': !partsVisibility || props.side },
6 { 'am-collapse-item-no-border': props.borderless },
7 { 'am-collapse-item__arrow': props.side && partsVisibility },
8 { 'am-collapse-item__open': contentVisibility },
9 ]"
10 :style="cssVars"
11 >
12 <div
13 :id="headingId"
14 class="am-collapse-item__heading"
15 :class="[
16 { 'am-collapse-item__heading-active': contentVisibility },
17 { 'am-collapse-item__heading-side': contentVisibility && props.side },
18 props.headingClass,
19 ]"
20 :role="headingInteractive ? 'button' : undefined"
21 :aria-expanded="headingInteractive ? contentVisibility : undefined"
22 :aria-controls="headingInteractive ? contentId : undefined"
23 :tabindex="headingInteractive ? 0 : -1"
24 @click="onHeadingClick"
25 @keydown.enter.prevent="onHeadingKeydown"
26 @keydown.space.prevent="onHeadingKeydown"
27 >
28 <div
29 v-if="props.side && partsVisibility && props.arrowPosition === 'left'"
30 class="am-collapse-item__trigger am-collapse-item__trigger-side am-collapse-item__trigger-side--left"
31 >
32 <slot name="icon-start" class="am-collapse-item__trigger-start"></slot>
33 <span class="am-icon-arrow-down" :class="{ 'am-rotate-180': contentVisibility }"></span>
34 <slot name="icon-end"></slot>
35 </div>
36 <slot name="heading"></slot>
37 <div
38 v-if="props.side && partsVisibility && props.arrowPosition !== 'left'"
39 class="am-collapse-item__trigger am-collapse-item__trigger-side"
40 >
41 <slot name="icon-start" class="am-collapse-item__trigger-start"></slot>
42 <span class="am-icon-arrow-down" :class="{ 'am-rotate-180': contentVisibility }"></span>
43 <slot name="icon-end"></slot>
44 </div>
45 <slot name="icon-below"></slot>
46 </div>
47
48 <template v-if="partsVisibility && !contentClosed">
49 <div
50 :id="contentId"
51 ref="collapseContent"
52 class="am-collapse-item__content"
53 role="region"
54 :aria-labelledby="headingId"
55 :class="[
56 { 'am-collapse-item__content-no-border': !props.side },
57 contentVisibility ? 'am-collapse-item__content-open' : 'am-collapse-item__content-close',
58 contentClosing ? 'am-collapse-item__content-closing' : '',
59 ]"
60 tabindex="-1"
61 >
62 <slot></slot>
63 </div>
64 </template>
65 <div
66 v-if="partsVisibility && !props.side"
67 class="am-collapse-item__trigger"
68 :role="triggerInteractive ? 'button' : undefined"
69 :aria-expanded="triggerInteractive ? contentVisibility : undefined"
70 :aria-controls="triggerInteractive ? contentId : undefined"
71 :tabindex="triggerInteractive ? 0 : -1"
72 @click="onTriggerClick"
73 @keydown.enter.prevent="onTriggerKeydown"
74 @keydown.space.prevent="onTriggerKeydown"
75 >
76 <span class="am-collapse-item__trigger-label">
77 <slot v-if="!contentVisibility && !props.buttonClosed.length" name="button-closed"></slot>
78 <template v-if="!contentVisibility && props.buttonClosed.length">
79 {{ props.buttonClosed }}
80 </template>
81 <slot v-if="contentVisibility && !props.buttonOpened.length" name="button-opened"></slot>
82 <template v-if="contentVisibility && props.buttonOpened.length">
83 {{ props.buttonOpened }}
84 </template>
85 </span>
86 <span class="am-icon-arrow-down" :class="{ 'am-rotate-180': contentVisibility }"></span>
87 </div>
88 </div>
89 </template>
90
91 <script>
92 let collapseItemInstanceCounter = 0
93 </script>
94
95 <script setup>
96 // * Import from Vue
97 import { inject, ref, reactive, computed, nextTick, useSlots } from 'vue'
98
99 // * Composables
100 import { useColorTransparency } from '../../../assets/js/common/colorManipulation'
101
102 /**
103 * Component Props
104 */
105 let props = defineProps({
106 buttonOpened: {
107 type: String,
108 default: '',
109 },
110 buttonClosed: {
111 type: String,
112 default: '',
113 },
114 side: {
115 type: Boolean,
116 default: false,
117 },
118 borderless: {
119 type: Boolean,
120 default: false,
121 },
122 type: {
123 type: String,
124 default: '',
125 },
126 delay: {
127 type: Number,
128 default: 500,
129 },
130 collapsable: {
131 type: Boolean,
132 default: true,
133 },
134 headingClass: {
135 type: String,
136 default: '',
137 },
138 arrowPosition: {
139 type: String,
140 default: 'right',
141 },
142 })
143
144 /**
145 * Component Emits
146 * @type {EmitFn<(string)[]>}
147 */
148 const emits = defineEmits(['collapseOpen', 'collapseClose', 'collapseClicked'])
149
150 // * Template slots
151 let slots = useSlots()
152
153 let partsVisibility = computed(() => {
154 return slots.default().length && slots.default()[0].props !== null
155 })
156
157 const instanceId = ++collapseItemInstanceCounter
158 const headingId = `am-collapse-item-heading-${instanceId}`
159 const contentId = `am-collapse-item-content-${instanceId}`
160
161 const headingInteractive = computed(() => {
162 return props.side && props.collapsable && partsVisibility.value
163 })
164
165 const triggerInteractive = computed(() => {
166 return !props.side && props.collapsable && partsVisibility.value
167 })
168
169 const keyboardToggleHandledKey = '__amCollapseKeyboardToggleHandled'
170
171 function eventHandledByKeyboard(event) {
172 return !!(event && event[keyboardToggleHandledKey])
173 }
174
175 function markEventHandledByKeyboard(event) {
176 if (event) {
177 event[keyboardToggleHandledKey] = true
178 }
179 }
180
181 // * Card Expand variable
182 let contentVisibility = ref(false)
183 let contentClosing = ref(false)
184 let contentClosed = ref(true)
185 let collapseContent = ref(null)
186 let collapseHeight = reactive({
187 h: '0px',
188 })
189
190 function closingCollapse() {
191 contentClosing.value = true
192 setTimeout(() => {
193 contentClosing.value = false
194 contentClosed.value = true
195 }, 480)
196 }
197
198 function toggleContentHeading() {
199 if (props.side && props.collapsable) {
200 toggleContent()
201 }
202 }
203
204 // * Card Expand Function
205 function toggleContentButton() {
206 if (!props.side && props.collapsable) {
207 toggleContent()
208 }
209 }
210
211 function onHeadingClick(event) {
212 if (eventHandledByKeyboard(event)) {
213 return
214 }
215
216 toggleContentHeading()
217 }
218
219 function onHeadingKeydown(event) {
220 markEventHandledByKeyboard(event)
221 toggleContentHeading()
222 }
223
224 function onTriggerClick(event) {
225 if (eventHandledByKeyboard(event)) {
226 return
227 }
228
229 toggleContentButton()
230 }
231
232 function onTriggerKeydown(event) {
233 markEventHandledByKeyboard(event)
234 toggleContentButton()
235 }
236
237 // * Card Expand Function
238 function toggleContent() {
239 emits('collapseClicked')
240 if (partsVisibility.value) {
241 setTimeout(() => {
242 if (collapseContent.value) {
243 collapseHeight.h = `${collapseContent.value.offsetHeight}px`
244 }
245 if (contentVisibility.value) closingCollapse()
246 contentVisibility.value = !contentVisibility.value
247 if (contentVisibility.value) {
248 emits('collapseOpen')
249 contentClosed.value = false
250 }
251 if (!contentVisibility.value) emits('collapseClose')
252 }, props.delay)
253 }
254 }
255
256 function closingFromParent() {
257 if (contentVisibility.value) closingCollapse()
258 contentVisibility.value = false
259 }
260
261 function openingFromParent() {
262 contentVisibility.value = true
263 contentClosed.value = false
264 }
265
266 function focusContent() {
267 nextTick(() => {
268 if (collapseContent.value) {
269 collapseContent.value.focus()
270 }
271 })
272 }
273
274 defineExpose({
275 closingFromParent,
276 openingFromParent,
277 focusContent,
278 contentVisibility,
279 })
280
281 // * Colors
282 let amColors = inject(
283 'amColors',
284 ref({
285 colorPrimary: '#1246D6',
286 colorSuccess: '#019719',
287 colorError: '#B4190F',
288 colorWarning: '#CCA20C',
289 colorMainBgr: '#FFFFFF',
290 colorMainHeadingText: '#33434C',
291 colorMainText: '#1A2C37',
292 colorSbBgr: '#17295A',
293 colorSbText: '#FFFFFF',
294 colorInpBgr: '#FFFFFF',
295 colorInpBorder: '#D1D5D7',
296 colorInpText: '#1A2C37',
297 colorInpPlaceHolder: '#1A2C37',
298 colorDropBgr: '#FFFFFF',
299 colorDropBorder: '#D1D5D7',
300 colorDropText: '#0E1920',
301 colorBtnPrim: '#265CF2',
302 colorBtnPrimText: '#FFFFFF',
303 colorBtnSec: '#1A2C37',
304 colorBtnSecText: '#FFFFFF',
305 }),
306 )
307 let cssVars = computed(() => {
308 return {
309 '--am-c-collapse-text-op80': useColorTransparency(amColors.value.colorMainText, 0.8),
310 '--am-c-collapse-text-op10': useColorTransparency(amColors.value.colorMainText, 0.1),
311 '--am-delay-collapse': `${props.delay}ms`,
312 }
313 })
314 </script>
315
316 <style lang="scss">
317 $collapseContentHeight: v-bind('collapseHeight.h');
318
319 @keyframes am-animation-collapse-open {
320 0% {
321 height: 0;
322 }
323 100% {
324 height: $collapseContentHeight;
325 }
326 }
327
328 @keyframes am-animation-collapse-close {
329 0% {
330 height: $collapseContentHeight;
331 }
332 100% {
333 height: 0;
334 }
335 }
336
337 @mixin am-collapse-item-block {
338 // Collapse card
339 // am -- amelia
340 // c - color
341 // v - visible
342 .am-collapse-item {
343 --am-c-collapse-border: var(--am-c-inp-border);
344 --am-c-collapse-text: var(--am-c-main-text);
345 --am-combo-collapse-border: 1px solid var(--am-c-inp-border);
346 --am-pointer-collapse: unset;
347 position: relative;
348 margin: 8px 0;
349 overflow: hidden;
350
351 &-no-button {
352 .am-collapse-item__heading {
353 border-radius: 8px;
354 &-side {
355 border-radius: 8px 8px 0 0;
356 border-bottom: 0;
357 }
358 }
359 }
360
361 &-no-border {
362 --am-combo-collapse-border: none;
363 }
364
365 &__arrow {
366 --am-pointer-collapse: pointer;
367 }
368
369 * {
370 font-family: var(--am-font-family);
371 box-sizing: border-box;
372 }
373
374 // Collapse header
375 &__heading {
376 display: flex;
377 justify-content: space-between;
378 align-items: center;
379 border-radius: 8px 8px 0 0;
380 padding: 16px;
381 border: var(--am-combo-collapse-border);
382 cursor: var(--am-pointer-collapse);
383 transition-delay: var(--am-delay-collapse);
384 outline: none;
385
386 &:focus-visible {
387 border-color: var(--am-c-collapse-text-op80);
388
389 & + .am-collapse-item__content {
390 border-color: var(--am-c-collapse-text-op80);
391 }
392 }
393
394 &-side {
395 transition-delay: 0ms;
396 }
397 }
398
399 // Collapse card content
400 &__content {
401 display: flex;
402 font-size: 14px;
403 font-weight: 400;
404 line-height: 1.4285;
405 color: var(--am-c-collapse-text-op80);
406 border: var(--am-combo-collapse-border);
407 border-radius: 0 0 8px 8px;
408 overflow: hidden;
409 position: absolute;
410 opacity: 0;
411 z-index: -1;
412
413 & > * {
414 padding: 16px;
415 }
416
417 &-no-border {
418 border-radius: 0;
419 border-top: 0;
420 border-bottom: 0;
421 }
422
423 &-open {
424 position: relative;
425 opacity: 1;
426 z-index: 100;
427 animation: 500ms ease-in-out am-animation-collapse-open;
428 }
429
430 &-closing {
431 position: relative;
432 opacity: 1;
433 z-index: 100;
434 }
435
436 &-close {
437 animation: 500ms ease-in-out am-animation-collapse-close;
438 }
439 }
440
441 // Collapse trigger button
442 &__trigger {
443 display: flex;
444 align-items: center;
445 justify-content: center;
446 font-size: 13px;
447 line-height: 1.5385;
448 color: var(--am-c-collapse-text);
449 padding: 8px 16px;
450 border: var(--am-combo-collapse-border);
451 border-top: none;
452 border-radius: 0 0 8px 8px;
453 cursor: pointer;
454 outline: none;
455
456 &:focus-visible {
457 background-color: var(--am-c-collapse-text-op10);
458
459 .am-collapse-item__trigger-label {
460 color: var(--am-c-collapse-text-op80);
461 }
462 }
463
464 &-side {
465 border: none;
466
467 &--left {
468 margin-right: 8px;
469 flex: 0 0 auto;
470 }
471 }
472
473 &-label {
474 font-size: 13px;
475 line-height: 1.5385;
476 color: var(--am-c-collapse-text);
477 }
478
479 .am-icon-arrow-down {
480 font-size: 16px;
481 line-height: 1.4;
482 color: var(--am-c-collapse-text);
483 transition: all 0.3s ease-in-out;
484
485 &.am-rotate-180 {
486 transform: rotate(180deg);
487 }
488 }
489 }
490 }
491 }
492
493 // public
494 .amelia-v2-booking #amelia-container {
495 @include am-collapse-item-block;
496 }
497
498 // admin
499 #amelia-app-backend-new {
500 @include am-collapse-item-block;
501 }
502
503 // modal popup
504 .am-dialog-popup {
505 @include am-collapse-item-block;
506 }
507 </style>
508