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