PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.4.8
Booking for Appointments and Events Calendar – Amelia v2.4.8
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 / public / EventForm / Common / Parts / GalleryCarousel.vue
ameliabooking / v3 / src / views / public / EventForm / Common / Parts Last commit date
BringingAnyone.vue 1 month ago DescriptionItem.vue 1 month ago EmptyState.vue 1 month ago EventCard.vue 1 month ago EventEmployee.vue 1 month ago EventTicket.vue 1 month ago GalleryCarousel.vue 1 month ago
GalleryCarousel.vue
277 lines
1 <template>
2 <div
3 class="am-gc"
4 :style="cssVars"
5 role="region"
6 aria-roledescription="carousel"
7 :aria-label="carouselLabel"
8 tabindex="0"
9 @keydown.left.prevent="previousImage"
10 @keydown.right.prevent="nextImage"
11 >
12 <div v-if="gallery.length > 1" class="am-gc__arrows">
13 <button
14 type="button"
15 class="am-gc__arrow am-icon-arrow-left"
16 :aria-label="carouselPreviousLabel"
17 @click="previousImage"
18 ></button>
19 <button
20 type="button"
21 class="am-gc__arrow am-icon-arrow-right"
22 :aria-label="carouselNextLabel"
23 @click="nextImage"
24 ></button>
25 </div>
26
27 <div
28 v-for="(img, index) in gallery"
29 :key="index"
30 class="am-gc__display"
31 :style="{
32 display: index === activeImage ? 'flex' : 'none',
33 backgroundImage: `url(${img.pictureFullPath})`,
34 }"
35 role="group"
36 aria-roledescription="slide"
37 :aria-hidden="index !== activeImage"
38 :aria-label="getSlideLabel(index, img)"
39 ></div>
40
41 <div class="am-gc__bullets" role="group" :aria-label="carouselNavigationLabel">
42 <button
43 v-for="(img, index) in gallery"
44 :key="index"
45 class="am-gc__bullets-item"
46 :class="{ 'am-active': index === activeImage }"
47 type="button"
48 :aria-current="index === activeImage ? 'true' : 'false'"
49 :aria-label="getBulletLabel(index, img)"
50 @click="goToImage(index)"
51 ></button>
52 </div>
53
54 <span class="am-gc__sr-only" aria-live="polite" aria-atomic="true">{{
55 activeSlideAnnouncement
56 }}</span>
57 </div>
58 </template>
59
60 <script setup>
61 // * Import from Vue
62 import { ref, inject, computed } from 'vue'
63
64 // * Comosables
65 import { useColorTransparency } from '../../../../../assets/js/common/colorManipulation.js'
66
67 const props = defineProps({
68 gallery: {
69 type: Array,
70 required: true,
71 },
72 })
73
74 const activeImage = ref(0)
75
76 const labels = inject('labels', {})
77
78 const carouselLabel = computed(() => labels.event_image_gallery || 'Event image gallery')
79 const carouselPreviousLabel = computed(() => labels.previous_image || 'Previous image')
80 const carouselNextLabel = computed(() => labels.next_image || 'Next image')
81 const carouselNavigationLabel = computed(() => labels.image_navigation || 'Image navigation')
82
83 const normalizeIndex = (index) => {
84 if (!props.gallery.length) {
85 return 0
86 }
87
88 return (index + props.gallery.length) % props.gallery.length
89 }
90
91 const previousImage = () => {
92 activeImage.value = normalizeIndex(activeImage.value - 1)
93 }
94
95 const nextImage = () => {
96 activeImage.value = normalizeIndex(activeImage.value + 1)
97 }
98
99 const goToImage = (index) => {
100 activeImage.value = normalizeIndex(index)
101 }
102
103 const getImageText = (img, index) => {
104 return img?.title || img?.name || img?.alt || `${labels.image || 'Image'} ${index + 1}`
105 }
106
107 const getSlideLabel = (index, img) => {
108 return `${getImageText(img, index)} (${index + 1} ${labels.of || 'of'} ${props.gallery.length})`
109 }
110
111 const getBulletLabel = (index, img) => {
112 return `${labels.show || 'Show'} ${getSlideLabel(index, img)}`
113 }
114
115 const activeSlideAnnouncement = computed(() => {
116 if (!props.gallery.length) {
117 return ''
118 }
119
120 return getSlideLabel(activeImage.value, props.gallery[activeImage.value])
121 })
122
123 // * Colors
124 const amColors = inject('amColors')
125
126 // * Css Vars
127 const cssVars = computed(() => {
128 return {
129 '--am-c-gc-bgr': amColors.value.colorMainBgr,
130 '--am-c-gc-bgr-op70': useColorTransparency(amColors.value.colorMainBgr, 0.7),
131 '--am-c-gc-text': amColors.value.colorMainText,
132 '--am-c-gc-text-op20': useColorTransparency(amColors.value.colorMainText, 0.2),
133 '--am-c-gc-text-op10': useColorTransparency(amColors.value.colorMainText, 0.1),
134 }
135 })
136 </script>
137
138 <script>
139 export default {
140 name: 'GalleryCarousel',
141 }
142 </script>
143
144 <style lang="scss">
145 @mixin gallery-carousel {
146 // am - amelia
147 // gc - gallery carousel
148 .am-gc {
149 position: relative;
150
151 &:focus-visible {
152 outline: 2px solid var(--am-c-gc-text);
153 outline-offset: 2px;
154 }
155
156 &__arrows {
157 display: flex;
158 justify-content: space-between;
159 width: 100%;
160 height: 100%;
161 position: absolute;
162 top: 0;
163 left: 0;
164 z-index: 100;
165
166 button {
167 position: absolute;
168 width: 60px;
169 height: 100%;
170 font-size: 38px;
171 cursor: pointer;
172 border: none;
173 background: transparent;
174 padding: 0;
175
176 &[class*='am-icon']*='am-icon'] {
177 flex: 0 0 auto;
178 font-size: 28px;
179 }
180
181 &::before {
182 color: var(--am-c-gc-text);
183 background: var(--am-c-gc-bgr);
184 border: 1px solid var(--am-c-gc-text-op20);
185 box-shadow: 0px 1px 3px var(--am-c-gc-text-op10);
186 border-radius: 6px;
187 position: absolute;
188 top: 50%;
189 left: 50%;
190 transform: translate(-50%, -50%);
191 }
192
193 &:focus-visible::before {
194 outline: 2px solid var(--am-c-gc-text);
195 outline-offset: 2px;
196 }
197
198 &:first-child {
199 left: 0;
200 }
201
202 &:last-child {
203 right: 0;
204 }
205 }
206 }
207
208 &__display {
209 display: flex;
210 align-items: center;
211 justify-content: center;
212 height: 300px;
213 background-size: auto 100%;
214 background-repeat: no-repeat;
215 background-position: center;
216 border-radius: 6px;
217 }
218
219 &__bullets {
220 position: absolute;
221 left: 50%;
222 transform: translateX(-50%);
223 bottom: 16px;
224 display: flex;
225 align-items: center;
226 justify-content: space-between;
227 z-index: 100;
228
229 &-item {
230 display: inline-flex;
231 width: 8px;
232 height: 8px;
233 background-color: var(--am-c-gc-bgr-op70);
234 border-radius: 50%;
235 cursor: pointer;
236 margin: 4px;
237 border: none;
238 padding: 0;
239
240 &:focus-visible {
241 outline: 2px solid var(--am-c-gc-text);
242 outline-offset: 2px;
243 }
244
245 &.am-active {
246 width: 16px;
247 height: 16px;
248 background-color: var(--am-c-gc-bgr);
249 }
250 }
251 }
252
253 &__sr-only {
254 position: absolute;
255 width: 1px;
256 height: 1px;
257 padding: 0;
258 margin: -1px;
259 overflow: hidden;
260 clip: rect(0, 0, 0, 0);
261 white-space: nowrap;
262 border: 0;
263 }
264 }
265 }
266
267 // Public
268 .amelia-v2-booking #amelia-container {
269 @include gallery-carousel;
270 }
271
272 // Admin
273 #amelia-app-backend-new {
274 @include gallery-carousel;
275 }
276 </style>
277