PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / trunk
Booking for Appointments and Events Calendar – Amelia vtrunk
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 / StepForm / common / StepCard.vue
ameliabooking / v3 / src / views / public / StepForm / common Last commit date
MoreInfoPopup.vue 1 week ago StepCard.vue 1 week ago StepCardLayout.vue 1 week ago
StepCard.vue
442 lines
1 <template>
2 <div
3 ref="initItemRef"
4 class="am-fs__init-item"
5 :class="[{ 'am--selected': props.selected }, { 'am--disabled': props.disabled }]"
6 :tabindex="props.disabled || props.keyboardSelectionDisabled ? -1 : 0"
7 @click="selectItem(props.item)"
8 @keydown.enter="selectItemWithKeyboard(props.item)"
9 >
10 <div v-if="componentWidth > 370" class="am-fs__init-item__img">
11 <img :src="usePictureLoad(baseUrls, item, props.isPerson)" :alt="props.itemName" />
12 </div>
13 <div
14 class="am-fs__init-item__content"
15 :class="[
16 responsiveClass,
17 {
18 'am-justify-center':
19 !props.infoItems.length && !props.infoBtnVisibility && !props.packagesBtnVisibility,
20 },
21 ]"
22 >
23 <!-- Heading -->
24 <div class="am-fs__init-item__heading" :class="responsiveClass">
25 <div v-if="componentWidth <= 370" class="am-fs__init-item__img" :class="responsiveClass">
26 <img :src="usePictureLoad(baseUrls, item, props.isPerson)" :alt="props.itemName" />
27 </div>
28 <div class="am-fs__init-item__name" :class="responsiveClass">
29 {{ props.itemName }}
30 </div>
31 <div v-if="props.priceVisibility" class="am-fs__init-item__cost" :class="responsiveClass">
32 <div class="am-fs__init-item__price">
33 {{ props.price }}
34 </div>
35 <div v-if="props.taxVisibility" class="am-fs__init-item__price am-fs__init-item__tax">
36 {{ props.taxExcluded ? `+${props.labels.total_tax_colon}` : props.labels.incl_tax }}
37 </div>
38 </div>
39 </div>
40 <!-- Heading -->
41
42 <!-- Info items -->
43 <div v-if="props.infoItems.length" class="am-fs__init-item__info">
44 <div
45 v-for="(infoItem, index) in props.infoItems"
46 :key="index"
47 class="am-fs__init-item__info-inner"
48 >
49 <IconComponent :icon="infoItem.icon" />
50 <a
51 v-if="'isLink' in infoItem && infoItem.isLink"
52 class="am-fs__init-item__info-name"
53 :href="`https://maps.google.com/?q=${infoItem.name}`"
54 target="_blank"
55 rel="noopener noreferrer"
56 tabindex="-1"
57 >
58 {{ infoItem.name }}
59 </a>
60 <span v-else class="am-fs__init-item__info-name">
61 {{ infoItem.name }}
62 </span>
63 </div>
64 </div>
65 <!-- Info items -->
66
67 <!-- Footer -->
68 <div
69 v-if="props.infoBtnVisibility || props.packagesBtnVisibility"
70 class="am-fs__init-item__footer"
71 >
72 <span
73 v-if="props.infoBtnVisibility"
74 class="am-fs__init-item__footer-actions"
75 :tabindex="props.keyboardSelectionDisabled ? -1 : 0"
76 @click.stop="triggerInfoPopup(props.item)"
77 @keydown.enter="triggerInfoPopup(props.item)"
78 >
79 {{ props.labels.learn_more }}
80 </span>
81 <span
82 v-if="props.packagesBtnVisibility"
83 class="am-fs__init-item__footer-actions"
84 :tabindex="props.keyboardSelectionDisabled ? -1 : 0"
85 @click.stop="triggerPackagesPopup(props.item)"
86 @keydown.enter="triggerPackagesPopup(props.item)"
87 >
88 {{ props.labels.view_in_package }}
89 </span>
90 </div>
91 <!-- Footer -->
92 </div>
93 </div>
94 </template>
95
96 <script setup>
97 // * Import from Vue
98 import { computed, inject, ref } from 'vue'
99
100 import { useElementSize } from '@vueuse/core'
101
102 // * Composables
103 import { usePictureLoad } from '../../../../assets/js/common/image'
104 import { useResponsiveClass } from '../../../../assets/js/common/responsive'
105
106 // * Components
107 import IconComponent from '../../../_components/icons/IconComponent.vue'
108
109 const props = defineProps({
110 item: {
111 type: Object,
112 required: true,
113 },
114 itemName: {
115 type: String,
116 required: true,
117 },
118 selected: {
119 type: Boolean,
120 required: true,
121 },
122 disabled: {
123 type: Boolean,
124 default: false,
125 },
126 parentWidth: {
127 type: Number,
128 default: 0,
129 },
130 isPerson: {
131 type: Boolean,
132 default: false,
133 },
134 priceVisibility: {
135 type: Boolean,
136 default: false,
137 },
138 price: {
139 type: String,
140 default: '',
141 },
142 taxVisibility: {
143 type: Boolean,
144 default: false,
145 },
146 taxExcluded: {
147 type: Boolean,
148 default: false,
149 },
150 infoItems: {
151 type: Array,
152 default: () => [],
153 },
154 infoBtnVisibility: {
155 type: Boolean,
156 default: false,
157 },
158 packagesBtnVisibility: {
159 type: Boolean,
160 default: false,
161 },
162 keyboardSelectionDisabled: {
163 type: Boolean,
164 default: false,
165 },
166 labels: {
167 type: Object,
168 required: true,
169 default: () => ({}),
170 },
171 })
172
173 // * Component emits
174 const emits = defineEmits(['selectItem', 'triggerInfoPopup', 'triggerPackagesPopup'])
175
176 const baseUrls = inject('baseUrls')
177
178 const initItemRef = ref(null)
179
180 // * Component width
181 const { width: itemWidth } = useElementSize(initItemRef)
182
183 let componentWidth = computed(() => {
184 return props.parentWidth ? props.parentWidth : itemWidth.value
185 })
186
187 let responsiveClass = computed(() => {
188 return useResponsiveClass(componentWidth.value)
189 })
190
191 // * Item selection
192 function selectItem(item) {
193 emits('selectItem', item)
194 }
195
196 function selectItemWithKeyboard(item) {
197 if (props.disabled || props.keyboardSelectionDisabled) {
198 return
199 }
200
201 selectItem(item)
202 }
203
204 function triggerInfoPopup(item) {
205 if (props.keyboardSelectionDisabled) {
206 return
207 }
208
209 emits('triggerInfoPopup', item)
210 }
211
212 function triggerPackagesPopup(item) {
213 if (props.keyboardSelectionDisabled) {
214 return
215 }
216
217 emits('triggerPackagesPopup', item)
218 }
219 </script>
220
221 <style lang="scss">
222 @import '../../../../../src/assets/scss/common/quill/_quill-mixin.scss';
223
224 .amelia-v2-booking #amelia-container {
225 .am-fs__init {
226 &-item {
227 width: 100%;
228 display: flex;
229 flex-wrap: wrap;
230 gap: 8px 12px;
231 padding: 12px;
232 border: 1px solid var(--am-c-inp-border);
233 border-radius: 6px;
234 cursor: pointer;
235
236 &:focus {
237 border-color: var(--am-c-primary);
238 }
239
240 &.am--selected {
241 border-color: var(--am-c-primary);
242 background-color: var(--am-c-primary-op05);
243 }
244
245 &.am--disabled {
246 cursor: not-allowed;
247 opacity: 0.5;
248
249 &:focus {
250 border-color: var(--am-c-inp-border);
251 }
252 }
253
254 // Card image
255 &__img {
256 width: 76px;
257 height: 76px;
258 overflow: hidden;
259 border-radius: 38px;
260 border: 1px solid var(--am-c-inp-border);
261
262 &.am-item-any {
263 width: 76px;
264 height: 48px;
265 display: flex;
266 align-items: center;
267 justify-content: center;
268 font-size: 26px;
269 color: var(--am-c-primary);
270 background-color: var(--am-c-primary-op05);
271 }
272
273 &.am-rw {
274 &-370 {
275 flex: 0 0 auto;
276 width: 42px;
277 height: 42px;
278 order: 0;
279 }
280 }
281
282 img {
283 width: 100%;
284 height: 100%;
285 object-fit: cover;
286 }
287 }
288
289 // Card content
290 &__content {
291 display: flex;
292 flex-direction: column;
293 flex-grow: 1;
294 gap: 2px 4px;
295 width: calc(100% - 88px);
296 justify-content: space-between;
297
298 &.am-justify-center {
299 justify-content: center;
300 }
301
302 &.am-rw {
303 &-370 {
304 width: 100%;
305 }
306 }
307 }
308
309 // Card Heading
310 &__heading {
311 display: flex;
312 width: 100%;
313 align-items: center;
314 justify-content: space-between;
315 gap: 2px 4px;
316
317 &.am-rw {
318 &-370 {
319 flex-wrap: wrap;
320 flex-direction: row;
321 align-items: center;
322 justify-content: space-between;
323 }
324 }
325 }
326
327 // Card name
328 &__name {
329 display: block;
330 font-weight: 500;
331 font-size: 15px;
332 line-height: 1.6;
333 color: var(--am-c-main-text);
334 white-space: nowrap;
335 overflow: hidden;
336 text-overflow: ellipsis;
337
338 &.am-rw {
339 &-370 {
340 display: flex;
341 width: 100%;
342 white-space: normal;
343 overflow: visible;
344 text-overflow: unset;
345 order: 2;
346 }
347 }
348 }
349
350 // Card cost
351 &__cost {
352 display: flex;
353 align-self: flex-start;
354 flex: 0 0 auto;
355 align-items: center;
356 gap: 4px;
357
358 &.am-rw {
359 &-370 {
360 order: 1;
361 align-self: center;
362 flex: 1;
363 flex-wrap: wrap;
364 justify-content: flex-end;
365 }
366 }
367 }
368
369 // Card price
370 &__price {
371 display: inline-flex;
372 align-items: center;
373 justify-content: center;
374 padding: 2px 8px;
375 font-weight: 500;
376 font-size: 14px;
377 line-height: 1.42857;
378 border-radius: 24px;
379 color: var(--am-c-primary);
380 background-color: var(--am-c-primary-op05);
381 }
382
383 // Card info
384 &__info {
385 display: flex;
386 flex-wrap: wrap;
387 gap: 2px 4px;
388 width: 100%;
389
390 &-inner {
391 display: flex;
392 align-items: center;
393 justify-content: flex-start;
394 }
395
396 [class^='am-icon-']^='am-icon-'] {
397 flex: 0 0 auto;
398 align-self: flex-start;
399 font-size: 24px;
400 line-height: 16px;
401 color: var(--am-c-primary);
402 }
403
404 &-name {
405 font-size: 13px;
406 font-weight: 400;
407 line-height: 1.38462;
408 color: var(--am-c-main-text-op80);
409 }
410 }
411
412 // Card footer
413 &__footer {
414 display: flex;
415 align-items: center;
416 justify-content: space-between;
417 width: 100%;
418 padding: 6px 0 0;
419 margin: 6px 0 0;
420 border-top: 1px solid var(--am-c-inp-border);
421
422 &-actions {
423 display: inline-flex;
424 align-items: center;
425 font-size: 11px;
426 font-weight: 500;
427 line-height: 1.81818;
428 color: var(--am-c-main-text-op80);
429 cursor: pointer;
430 transition: 0.3s ease-in-out;
431
432 &:hover,
433 &:focus {
434 color: var(--am-c-primary);
435 }
436 }
437 }
438 }
439 }
440 }
441 </style>
442