@citizenplane/pimp 18.23.0 → 18.23.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/CpAncillaryItem.vue.d.ts +4 -0
- package/dist/components/CpAncillaryItem.vue.d.ts.map +1 -1
- package/dist/components/CpStepper.vue.d.ts.map +1 -1
- package/dist/pimp.es.js +1751 -1705
- package/dist/pimp.umd.js +40 -40
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/assets/images/ancillaries/airport-transfer.png +0 -0
- package/src/assets/images/ancillaries/bicycle.png +0 -0
- package/src/assets/images/ancillaries/cabin-bag.png +0 -0
- package/src/assets/images/ancillaries/checked-bag-size-1.png +0 -0
- package/src/assets/images/ancillaries/checked-bag-size-2.png +0 -0
- package/src/assets/images/ancillaries/checked-bag-size-3.png +0 -0
- package/src/assets/images/ancillaries/checked-bag.png +0 -0
- package/src/assets/images/ancillaries/connectivity.png +0 -0
- package/src/assets/images/ancillaries/diving-set.png +0 -0
- package/src/assets/images/ancillaries/extra-weight.png +0 -0
- package/src/assets/images/ancillaries/fast-track.png +0 -0
- package/src/assets/images/ancillaries/firearm.png +0 -0
- package/src/assets/images/ancillaries/fishing-rod.png +0 -0
- package/src/assets/images/ancillaries/golf.png +0 -0
- package/src/assets/images/ancillaries/hand-bag.png +0 -0
- package/src/assets/images/ancillaries/kite-surf.png +0 -0
- package/src/assets/images/ancillaries/lounge-access.png +0 -0
- package/src/assets/images/ancillaries/meet-and-greet.png +0 -0
- package/src/assets/images/ancillaries/music.png +0 -0
- package/src/assets/images/ancillaries/oversized.png +0 -0
- package/src/assets/images/ancillaries/pet-in-cabin.png +0 -0
- package/src/assets/images/ancillaries/pet-in-hold.png +0 -0
- package/src/assets/images/ancillaries/skis.png +0 -0
- package/src/assets/images/ancillaries/special-meal.png +0 -0
- package/src/assets/images/ancillaries/standard-meal.png +0 -0
- package/src/components/CpAncillaryItem.vue +165 -7
- package/src/components/CpStepper.vue +47 -11
- package/src/stories/CpAncillaryItem.stories.ts +212 -6
- package/src/stories/CpStepper.stories.ts +50 -1
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -3,16 +3,31 @@
|
|
|
3
3
|
<div class="cpAncillaryItem__wrap">
|
|
4
4
|
<div class="cpAncillaryItem__visual">
|
|
5
5
|
<img v-if="ancillaryImage" alt="" class="cpAncillaryItem__image" :src="ancillaryImage" />
|
|
6
|
-
<cp-icon v-else class="cpAncillaryItem__fallbackIcon" size="
|
|
6
|
+
<cp-icon v-else class="cpAncillaryItem__fallbackIcon" :size="fallbackIconSize" type="shopping-bag" />
|
|
7
7
|
</div>
|
|
8
8
|
<div class="cpAncillaryItem__content">
|
|
9
9
|
<div class="cpAncillaryItem__name">
|
|
10
|
-
<
|
|
10
|
+
<div class="cpAncillaryItem__heading">
|
|
11
|
+
<p class="cpAncillaryItem__title">{{ title }}</p>
|
|
12
|
+
<p v-if="hasInlinePrice" class="cpAncillaryItem__bullet">•</p>
|
|
13
|
+
<p v-if="hasInlinePrice" class="cpAncillaryItem__inlinePrice">{{ formattedPrice }}</p>
|
|
14
|
+
</div>
|
|
11
15
|
<p class="cpAncillaryItem__description">{{ description }}</p>
|
|
12
16
|
<p v-if="subtitle" class="cpAncillaryItem__subtitle">{{ subtitle }}</p>
|
|
13
17
|
</div>
|
|
14
18
|
<div class="cpAncillaryItem__quantity">
|
|
19
|
+
<cp-button
|
|
20
|
+
v-if="isAddButtonVisible"
|
|
21
|
+
appearance="secondary"
|
|
22
|
+
class="cpAncillaryItem__addButton"
|
|
23
|
+
color="accent"
|
|
24
|
+
size="sm"
|
|
25
|
+
@click="selectFirstQuantity"
|
|
26
|
+
>
|
|
27
|
+
{{ addLabel }}
|
|
28
|
+
</cp-button>
|
|
15
29
|
<cp-quantity-counter
|
|
30
|
+
v-else
|
|
16
31
|
v-model="quantity"
|
|
17
32
|
class="cpAncillaryItem__counter"
|
|
18
33
|
:decrease-label="decreaseLabel"
|
|
@@ -20,9 +35,9 @@
|
|
|
20
35
|
:increase-label="increaseLabel"
|
|
21
36
|
:max="maxQuantity"
|
|
22
37
|
/>
|
|
23
|
-
<span v-if="
|
|
38
|
+
<span v-if="hasMaxQuantityCaption" class="cpAncillaryItem__maxQuantity">{{ maxQuantityLabel }}</span>
|
|
24
39
|
</div>
|
|
25
|
-
<p v-if="
|
|
40
|
+
<p v-if="hasStackedPrice" class="cpAncillaryItem__price">{{ formattedPrice }}</p>
|
|
26
41
|
</div>
|
|
27
42
|
</div>
|
|
28
43
|
</div>
|
|
@@ -31,6 +46,7 @@
|
|
|
31
46
|
<script setup lang="ts">
|
|
32
47
|
import { computed } from 'vue'
|
|
33
48
|
|
|
49
|
+
import CpButton from '@/components/CpButton.vue'
|
|
34
50
|
import CpIcon from '@/components/CpIcon.vue'
|
|
35
51
|
import CpQuantityCounter from '@/components/CpQuantityCounter.vue'
|
|
36
52
|
|
|
@@ -62,9 +78,11 @@ import StandardMealImage from '@/assets/images/ancillaries/standard-meal.png'
|
|
|
62
78
|
import { CpAncillaryItemTypes } from '@/constants/CpAncillaryItemTypes'
|
|
63
79
|
|
|
64
80
|
interface Props {
|
|
81
|
+
addLabel?: string
|
|
65
82
|
currency?: string
|
|
66
83
|
description: string
|
|
67
84
|
disableAnimation?: boolean
|
|
85
|
+
layout?: 'card' | 'list'
|
|
68
86
|
locale?: string
|
|
69
87
|
maxQuantity?: number | null
|
|
70
88
|
maxQuantityLabel?: string
|
|
@@ -76,6 +94,8 @@ interface Props {
|
|
|
76
94
|
}
|
|
77
95
|
|
|
78
96
|
const props = withDefaults(defineProps<Props>(), {
|
|
97
|
+
addLabel: 'Add',
|
|
98
|
+
layout: 'card',
|
|
79
99
|
locale: () => (typeof navigator === 'undefined' ? 'en' : navigator.language),
|
|
80
100
|
maxQuantity: null,
|
|
81
101
|
vertical: false,
|
|
@@ -116,16 +136,40 @@ const ancillaryImages: Partial<Record<CpAncillaryItemTypes, string>> = {
|
|
|
116
136
|
|
|
117
137
|
const ancillaryImage = computed(() => ancillaryImages[props.type] ?? null)
|
|
118
138
|
|
|
139
|
+
const isListLayout = computed(() => props.layout === 'list')
|
|
140
|
+
|
|
141
|
+
const fallbackIconSize = computed(() => {
|
|
142
|
+
if (isListLayout.value) return '28'
|
|
143
|
+
return '40'
|
|
144
|
+
})
|
|
145
|
+
|
|
119
146
|
const hasMaxQuantity = computed(() => props.maxQuantity !== null)
|
|
120
147
|
|
|
148
|
+
const hasMaxQuantityCaption = computed(() => hasMaxQuantity.value && !isListLayout.value)
|
|
149
|
+
|
|
150
|
+
const isAddButtonVisible = computed(() => quantity.value < 1)
|
|
151
|
+
|
|
152
|
+
const selectFirstQuantity = (): void => {
|
|
153
|
+
quantity.value = 1
|
|
154
|
+
}
|
|
155
|
+
|
|
121
156
|
const formattedPrice = computed(() => {
|
|
122
157
|
if (props.price === undefined || !props.currency) return null
|
|
123
158
|
return new Intl.NumberFormat(props.locale, { style: 'currency', currency: props.currency }).format(props.price / 100)
|
|
124
159
|
})
|
|
125
160
|
|
|
161
|
+
const hasPrice = computed(() => formattedPrice.value !== null)
|
|
162
|
+
|
|
163
|
+
const hasInlinePrice = computed(() => hasPrice.value && isListLayout.value)
|
|
164
|
+
|
|
165
|
+
const hasStackedPrice = computed(() => hasPrice.value && !isListLayout.value)
|
|
166
|
+
|
|
167
|
+
const isVerticalCard = computed(() => props.vertical && !isListLayout.value)
|
|
168
|
+
|
|
126
169
|
const isSelected = computed(() => quantity.value > 0)
|
|
127
170
|
const cpAncillaryItemDynamicClass = computed(() => ({
|
|
128
|
-
'cpAncillaryItem--
|
|
171
|
+
'cpAncillaryItem--isList': isListLayout.value,
|
|
172
|
+
'cpAncillaryItem--isVertical': isVerticalCard.value,
|
|
129
173
|
'cpAncillaryItem--isSelected': isSelected.value,
|
|
130
174
|
}))
|
|
131
175
|
</script>
|
|
@@ -256,7 +300,7 @@ const cpAncillaryItemDynamicClass = computed(() => ({
|
|
|
256
300
|
}
|
|
257
301
|
}
|
|
258
302
|
|
|
259
|
-
&--isSelected {
|
|
303
|
+
&--isSelected:not(#{&}--isList) {
|
|
260
304
|
border-color: var(--cp-border-accent-solid);
|
|
261
305
|
outline: fn.px-to-rem(1) solid var(--cp-border-accent-solid);
|
|
262
306
|
outline-offset: fn.px-to-rem(-2);
|
|
@@ -270,8 +314,122 @@ const cpAncillaryItemDynamicClass = computed(() => ({
|
|
|
270
314
|
}
|
|
271
315
|
}
|
|
272
316
|
|
|
317
|
+
&--isList {
|
|
318
|
+
padding: 0;
|
|
319
|
+
border: none;
|
|
320
|
+
border-radius: 0;
|
|
321
|
+
background: none;
|
|
322
|
+
container-type: inline-size;
|
|
323
|
+
|
|
324
|
+
.cpAncillaryItem__wrap {
|
|
325
|
+
align-items: center;
|
|
326
|
+
padding: var(--cp-spacing-lg) 0;
|
|
327
|
+
border-radius: 0;
|
|
328
|
+
border-bottom: fn.px-to-rem(1) solid var(--cp-border-soft);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.cpAncillaryItem__visual {
|
|
332
|
+
width: fn.px-to-rem(44);
|
|
333
|
+
height: fn.px-to-rem(44);
|
|
334
|
+
border-radius: 0;
|
|
335
|
+
background: none;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.cpAncillaryItem__image {
|
|
339
|
+
width: fn.px-to-rem(28);
|
|
340
|
+
height: fn.px-to-rem(28);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.cpAncillaryItem__content {
|
|
344
|
+
flex-direction: row;
|
|
345
|
+
align-items: center;
|
|
346
|
+
padding: 0;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.cpAncillaryItem__name {
|
|
350
|
+
flex: auto;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.cpAncillaryItem__heading {
|
|
354
|
+
display: flex;
|
|
355
|
+
flex-direction: column;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.cpAncillaryItem__title {
|
|
359
|
+
font-weight: 500;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.cpAncillaryItem__bullet,
|
|
363
|
+
.cpAncillaryItem__inlinePrice {
|
|
364
|
+
color: var(--cp-text-secondary);
|
|
365
|
+
font-size: var(--cp-text-size-md);
|
|
366
|
+
font-weight: 500;
|
|
367
|
+
line-height: var(--cp-text-md-line-height);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.cpAncillaryItem__bullet {
|
|
371
|
+
display: none;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.cpAncillaryItem__quantity {
|
|
375
|
+
flex: none;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
&:last-child .cpAncillaryItem__wrap {
|
|
379
|
+
border-bottom: none;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
@include mx.media-query-min(48rem) {
|
|
383
|
+
.cpAncillaryItem__heading {
|
|
384
|
+
flex-direction: row;
|
|
385
|
+
gap: var(--cp-spacing-sm);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.cpAncillaryItem__bullet {
|
|
389
|
+
display: block;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
@container (width < 22rem) {
|
|
394
|
+
.cpAncillaryItem__wrap {
|
|
395
|
+
gap: var(--cp-spacing-sm);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.cpAncillaryItem__visual {
|
|
399
|
+
width: fn.px-to-rem(40);
|
|
400
|
+
height: fn.px-to-rem(40);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.cpAncillaryItem__image {
|
|
404
|
+
width: fn.px-to-rem(24);
|
|
405
|
+
height: fn.px-to-rem(24);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.cpAncillaryItem__content {
|
|
409
|
+
flex-wrap: wrap;
|
|
410
|
+
align-items: center;
|
|
411
|
+
gap: var(--cp-spacing-md);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.cpAncillaryItem__heading {
|
|
415
|
+
flex-direction: column;
|
|
416
|
+
gap: 0;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.cpAncillaryItem__bullet {
|
|
420
|
+
display: none;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.cpAncillaryItem__description,
|
|
424
|
+
.cpAncillaryItem__subtitle {
|
|
425
|
+
font-size: var(--cp-text-size-xs);
|
|
426
|
+
line-height: var(--cp-text-xs-line-height);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
273
431
|
@media (hover: hover) {
|
|
274
|
-
&:hover .cpAncillaryItem__image {
|
|
432
|
+
&:not(#{&}--isList):hover .cpAncillaryItem__image {
|
|
275
433
|
transform: scale3d(1.1, 1.1, 1);
|
|
276
434
|
}
|
|
277
435
|
}
|
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="cpStepper">
|
|
3
3
|
<div class="cpStepper__wrapper">
|
|
4
|
-
<div v-for="
|
|
4
|
+
<div v-for="stepItem in stepItems" :key="stepItem.id" class="cpStepper__stepWrapper">
|
|
5
5
|
<button
|
|
6
|
+
:aria-label="stepItem.label"
|
|
6
7
|
class="cpStepper__step"
|
|
7
|
-
:class="
|
|
8
|
-
:disabled="
|
|
8
|
+
:class="stepItem.className"
|
|
9
|
+
:disabled="stepItem.isDisabled"
|
|
9
10
|
type="button"
|
|
10
|
-
@click="handleStepClick(
|
|
11
|
+
@click="handleStepClick(stepItem.id)"
|
|
11
12
|
>
|
|
12
|
-
<span class="cpStepper__stepNumber">
|
|
13
|
-
|
|
13
|
+
<span class="cpStepper__stepNumber">
|
|
14
|
+
<cp-icon v-if="stepItem.isCompleted" size="12" type="check" />
|
|
15
|
+
<template v-else>{{ stepItem.number }}</template>
|
|
16
|
+
</span>
|
|
17
|
+
<span class="cpStepper__stepTitle">{{ stepItem.label }}</span>
|
|
14
18
|
</button>
|
|
15
|
-
<cp-icon v-if="
|
|
19
|
+
<cp-icon v-if="stepItem.isNotLast" class="cpStepper__chevron" size="16" type="chevron-right" />
|
|
16
20
|
</div>
|
|
17
21
|
</div>
|
|
18
22
|
</div>
|
|
19
23
|
</template>
|
|
20
24
|
|
|
21
25
|
<script setup lang="ts">
|
|
26
|
+
import { computed } from 'vue'
|
|
27
|
+
|
|
22
28
|
import type { CpStepperStep } from '@/constants/CpStepperTypes'
|
|
23
29
|
|
|
24
30
|
import CpIcon from '@/components/CpIcon.vue'
|
|
@@ -36,13 +42,32 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
36
42
|
|
|
37
43
|
const emit = defineEmits<{ select: [id: string] }>()
|
|
38
44
|
|
|
39
|
-
const
|
|
45
|
+
const activeStepIndex = computed(() => props.steps.findIndex((step) => step.id === props.activeStepId))
|
|
46
|
+
|
|
47
|
+
const isStepCompleted = (stepIndex: number) => activeStepIndex.value > -1 && stepIndex < activeStepIndex.value
|
|
40
48
|
|
|
41
|
-
const
|
|
49
|
+
const stepClassName = (step: CpStepperStep, stepIndex: number) => {
|
|
50
|
+
return {
|
|
51
|
+
'cpStepper__step--isActive': step.id === props.activeStepId,
|
|
52
|
+
'cpStepper__step--isCompleted': isStepCompleted(stepIndex),
|
|
53
|
+
}
|
|
54
|
+
}
|
|
42
55
|
|
|
43
|
-
const
|
|
56
|
+
const stepItems = computed(() => {
|
|
57
|
+
return props.steps.map((step, stepIndex) => {
|
|
58
|
+
return {
|
|
59
|
+
className: stepClassName(step, stepIndex),
|
|
60
|
+
id: step.id,
|
|
61
|
+
isCompleted: isStepCompleted(stepIndex),
|
|
62
|
+
isDisabled: stepIndex > props.reachedStepIndex,
|
|
63
|
+
isNotLast: stepIndex < props.steps.length - 1,
|
|
64
|
+
label: step.label,
|
|
65
|
+
number: stepIndex + 1,
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
})
|
|
44
69
|
|
|
45
|
-
const handleStepClick = (
|
|
70
|
+
const handleStepClick = (stepId: string) => emit('select', stepId)
|
|
46
71
|
</script>
|
|
47
72
|
|
|
48
73
|
<style lang="scss">
|
|
@@ -85,6 +110,12 @@ const handleStepClick = (step: CpStepperStep) => emit('select', step.id)
|
|
|
85
110
|
color: var(--cp-text-accent-primary);
|
|
86
111
|
}
|
|
87
112
|
|
|
113
|
+
&--isCompleted {
|
|
114
|
+
border-color: transparent;
|
|
115
|
+
background-color: var(--cp-background-primary);
|
|
116
|
+
color: var(--cp-text-primary);
|
|
117
|
+
}
|
|
118
|
+
|
|
88
119
|
&:not(:disabled) {
|
|
89
120
|
@extend %u-focus-outline;
|
|
90
121
|
|
|
@@ -115,6 +146,11 @@ const handleStepClick = (step: CpStepperStep) => emit('select', step.id)
|
|
|
115
146
|
color: var(--cp-text-white);
|
|
116
147
|
}
|
|
117
148
|
|
|
149
|
+
&__step--isCompleted &__stepNumber {
|
|
150
|
+
background-color: var(--cp-background-success-primary);
|
|
151
|
+
color: var(--cp-foreground-success-primary);
|
|
152
|
+
}
|
|
153
|
+
|
|
118
154
|
&__stepTitle {
|
|
119
155
|
@extend %u-text-ellipsis;
|
|
120
156
|
|