@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.
Files changed (36) hide show
  1. package/dist/components/CpAncillaryItem.vue.d.ts +4 -0
  2. package/dist/components/CpAncillaryItem.vue.d.ts.map +1 -1
  3. package/dist/components/CpStepper.vue.d.ts.map +1 -1
  4. package/dist/pimp.es.js +1751 -1705
  5. package/dist/pimp.umd.js +40 -40
  6. package/dist/style.css +1 -1
  7. package/package.json +1 -1
  8. package/src/assets/images/ancillaries/airport-transfer.png +0 -0
  9. package/src/assets/images/ancillaries/bicycle.png +0 -0
  10. package/src/assets/images/ancillaries/cabin-bag.png +0 -0
  11. package/src/assets/images/ancillaries/checked-bag-size-1.png +0 -0
  12. package/src/assets/images/ancillaries/checked-bag-size-2.png +0 -0
  13. package/src/assets/images/ancillaries/checked-bag-size-3.png +0 -0
  14. package/src/assets/images/ancillaries/checked-bag.png +0 -0
  15. package/src/assets/images/ancillaries/connectivity.png +0 -0
  16. package/src/assets/images/ancillaries/diving-set.png +0 -0
  17. package/src/assets/images/ancillaries/extra-weight.png +0 -0
  18. package/src/assets/images/ancillaries/fast-track.png +0 -0
  19. package/src/assets/images/ancillaries/firearm.png +0 -0
  20. package/src/assets/images/ancillaries/fishing-rod.png +0 -0
  21. package/src/assets/images/ancillaries/golf.png +0 -0
  22. package/src/assets/images/ancillaries/hand-bag.png +0 -0
  23. package/src/assets/images/ancillaries/kite-surf.png +0 -0
  24. package/src/assets/images/ancillaries/lounge-access.png +0 -0
  25. package/src/assets/images/ancillaries/meet-and-greet.png +0 -0
  26. package/src/assets/images/ancillaries/music.png +0 -0
  27. package/src/assets/images/ancillaries/oversized.png +0 -0
  28. package/src/assets/images/ancillaries/pet-in-cabin.png +0 -0
  29. package/src/assets/images/ancillaries/pet-in-hold.png +0 -0
  30. package/src/assets/images/ancillaries/skis.png +0 -0
  31. package/src/assets/images/ancillaries/special-meal.png +0 -0
  32. package/src/assets/images/ancillaries/standard-meal.png +0 -0
  33. package/src/components/CpAncillaryItem.vue +165 -7
  34. package/src/components/CpStepper.vue +47 -11
  35. package/src/stories/CpAncillaryItem.stories.ts +212 -6
  36. package/src/stories/CpStepper.stories.ts +50 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@citizenplane/pimp",
3
- "version": "18.23.0",
3
+ "version": "18.23.2",
4
4
  "scripts": {
5
5
  "dev": "storybook dev -p 8081",
6
6
  "build-storybook": "storybook build --output-dir ./docs",
@@ -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="40" type="shopping-bag" />
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
- <p class="cpAncillaryItem__title">{{ title }}</p>
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="hasMaxQuantity" class="cpAncillaryItem__maxQuantity">{{ maxQuantityLabel }}</span>
38
+ <span v-if="hasMaxQuantityCaption" class="cpAncillaryItem__maxQuantity">{{ maxQuantityLabel }}</span>
24
39
  </div>
25
- <p v-if="formattedPrice" class="cpAncillaryItem__price">{{ formattedPrice }}</p>
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--isVertical': props.vertical,
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="(step, stepIndex) in steps" :key="step.id" class="cpStepper__stepWrapper">
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="stepActiveClass(step)"
8
- :disabled="isStepDisabled(stepIndex)"
8
+ :class="stepItem.className"
9
+ :disabled="stepItem.isDisabled"
9
10
  type="button"
10
- @click="handleStepClick(step)"
11
+ @click="handleStepClick(stepItem.id)"
11
12
  >
12
- <span class="cpStepper__stepNumber">{{ stepIndex + 1 }}</span>
13
- <span class="cpStepper__stepTitle">{{ step.label }}</span>
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="isNotLastStep(stepIndex)" class="cpStepper__chevron" size="16" type="chevron-right" />
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 isNotLastStep = (stepIndex: number) => stepIndex < props.steps.length - 1
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 isStepDisabled = (stepIndex: number) => stepIndex > props.reachedStepIndex
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 stepActiveClass = (step: CpStepperStep) => (step.id === props.activeStepId ? 'cpStepper__step--isActive' : '')
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 = (step: CpStepperStep) => emit('select', step.id)
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