@citizenplane/pimp 18.14.4 → 18.16.0

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 (56) hide show
  1. package/dist/components/CpTextMorph.vue.d.ts +11 -0
  2. package/dist/components/CpTextMorph.vue.d.ts.map +1 -0
  3. package/dist/components/CpTripDetails.vue.d.ts +14 -0
  4. package/dist/components/CpTripDetails.vue.d.ts.map +1 -0
  5. package/dist/components/CpTripLegRouteInformation.vue.d.ts +11 -0
  6. package/dist/components/CpTripLegRouteInformation.vue.d.ts.map +1 -0
  7. package/dist/components/CpTripLegRouteLayover.vue.d.ts +13 -0
  8. package/dist/components/CpTripLegRouteLayover.vue.d.ts.map +1 -0
  9. package/dist/components/CpTripLegRoutePlace.vue.d.ts +22 -0
  10. package/dist/components/CpTripLegRoutePlace.vue.d.ts.map +1 -0
  11. package/dist/components/CpTripLegRouteSegment.vue.d.ts +18 -0
  12. package/dist/components/CpTripLegRouteSegment.vue.d.ts.map +1 -0
  13. package/dist/components/index.d.ts +8 -1
  14. package/dist/components/index.d.ts.map +1 -1
  15. package/dist/constants/tripDetails/Airline.d.ts +5 -0
  16. package/dist/constants/tripDetails/Airline.d.ts.map +1 -0
  17. package/dist/constants/tripDetails/Airport.d.ts +7 -0
  18. package/dist/constants/tripDetails/Airport.d.ts.map +1 -0
  19. package/dist/constants/tripDetails/CpTripDetailsConfig.d.ts +16 -0
  20. package/dist/constants/tripDetails/CpTripDetailsConfig.d.ts.map +1 -0
  21. package/dist/constants/tripDetails/Flight.d.ts +5 -0
  22. package/dist/constants/tripDetails/Flight.d.ts.map +1 -0
  23. package/dist/constants/tripDetails/FlightSegment.d.ts +15 -0
  24. package/dist/constants/tripDetails/FlightSegment.d.ts.map +1 -0
  25. package/dist/constants/tripDetails/FlightStopTypes.d.ts +7 -0
  26. package/dist/constants/tripDetails/FlightStopTypes.d.ts.map +1 -0
  27. package/dist/constants/tripDetails/Terminal.d.ts +5 -0
  28. package/dist/constants/tripDetails/Terminal.d.ts.map +1 -0
  29. package/dist/constants/tripDetails/index.d.ts +9 -0
  30. package/dist/constants/tripDetails/index.d.ts.map +1 -0
  31. package/dist/helpers/tripDetails.d.ts +23 -0
  32. package/dist/helpers/tripDetails.d.ts.map +1 -0
  33. package/dist/pimp.es.js +5656 -4673
  34. package/dist/pimp.umd.js +66 -33
  35. package/dist/style.css +1 -1
  36. package/package.json +2 -1
  37. package/src/assets/images/dot.svg +3 -0
  38. package/src/components/CpTextMorph.vue +17 -0
  39. package/src/components/CpTripDetails.vue +64 -0
  40. package/src/components/CpTripLegRouteInformation.vue +224 -0
  41. package/src/components/CpTripLegRouteLayover.vue +63 -0
  42. package/src/components/CpTripLegRoutePlace.vue +196 -0
  43. package/src/components/CpTripLegRouteSegment.vue +99 -0
  44. package/src/components/index.ts +26 -0
  45. package/src/constants/tripDetails/Airline.ts +4 -0
  46. package/src/constants/tripDetails/Airport.ts +6 -0
  47. package/src/constants/tripDetails/CpTripDetailsConfig.ts +29 -0
  48. package/src/constants/tripDetails/Flight.ts +5 -0
  49. package/src/constants/tripDetails/FlightSegment.ts +15 -0
  50. package/src/constants/tripDetails/FlightStopTypes.ts +6 -0
  51. package/src/constants/tripDetails/Terminal.ts +4 -0
  52. package/src/constants/tripDetails/index.ts +9 -0
  53. package/src/helpers/tripDetails.ts +61 -0
  54. package/src/stories/CpTextMorph.stories.ts +236 -0
  55. package/src/stories/CpTrip.stories.ts +147 -0
  56. package/src/stories/CpTripDetails.stories.ts +134 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@citizenplane/pimp",
3
- "version": "18.14.4",
3
+ "version": "18.16.0",
4
4
  "scripts": {
5
5
  "dev": "storybook dev -p 8081",
6
6
  "build-storybook": "storybook build --output-dir ./docs",
@@ -54,6 +54,7 @@
54
54
  "maska": "3.2.0",
55
55
  "modern-normalize": "3.0.1",
56
56
  "primevue": "4.5.5",
57
+ "torph": "^0.1.0",
57
58
  "vue": "3.5.39",
58
59
  "vue-bind-once": "0.2.1",
59
60
  "vue-tel-input": "9.5.0"
@@ -0,0 +1,3 @@
1
+ <svg width="120" height="120" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M60 90C76.5685 90 90 76.5685 90 60C90 43.4315 76.5685 30 60 30C43.4315 30 30 43.4315 30 60C30 76.5685 43.4315 90 60 90Z" fill="#D8D8DE"/>
3
+ </svg>
@@ -0,0 +1,17 @@
1
+ <template>
2
+ <text-morph :text="text" :duration="duration" :disabled="disabled" :as="as" :class="props.class" />
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ import { TextMorph } from 'torph/vue'
7
+
8
+ interface Props {
9
+ as?: string
10
+ class?: string
11
+ disabled?: boolean
12
+ duration?: number
13
+ text: string
14
+ }
15
+
16
+ const props = defineProps<Props>()
17
+ </script>
@@ -0,0 +1,64 @@
1
+ <template>
2
+ <div class="cpTripDetails">
3
+ <cp-trip-leg-route-segment
4
+ v-for="(segment, segmentIndex) in flightSegments"
5
+ :key="`segment_${segmentIndex}`"
6
+ :config="config"
7
+ :locale="locale"
8
+ :previous-segment-arrival="getPreviousSegmentArrival(segmentIndex)"
9
+ :segment="segment"
10
+ :segment-count="segmentCount"
11
+ :segment-index="segmentIndex"
12
+ :stop-duration="getStopDuration(segmentIndex)"
13
+ />
14
+ </div>
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ import { DateTime } from 'luxon'
19
+ import { computed } from 'vue'
20
+
21
+ import type { CpTripDetailsConfig } from '@/constants/tripDetails/CpTripDetailsConfig'
22
+ import type { Flight } from '@/constants/tripDetails/Flight'
23
+
24
+ import { DEFAULT_CP_TRIP_DETAILS_CONFIG } from '@/constants/tripDetails/CpTripDetailsConfig'
25
+ import { getDuration } from '@/helpers/tripDetails'
26
+ import CpTripLegRouteSegment from './CpTripLegRouteSegment.vue'
27
+
28
+ interface Props {
29
+ config?: CpTripDetailsConfig
30
+ flight: Flight
31
+ locale?: string
32
+ }
33
+
34
+ const props = withDefaults(defineProps<Props>(), {
35
+ config: () => DEFAULT_CP_TRIP_DETAILS_CONFIG,
36
+ locale: 'en',
37
+ })
38
+
39
+ const flightSegments = computed(() => props.flight.segments)
40
+ const segmentCount = computed(() => flightSegments.value.length)
41
+
42
+ const getSegmentTime = (segmentIndex: number, timeKey: 'arrival_date' | 'departure_date') => {
43
+ return DateTime.fromISO(flightSegments.value[segmentIndex]?.[timeKey])
44
+ }
45
+
46
+ const getPreviousSegmentArrival = (segmentIndex: number) => {
47
+ return getSegmentTime(segmentIndex - 1, 'arrival_date')
48
+ }
49
+
50
+ const getStopDuration = (segmentIndex: number) => {
51
+ if (segmentCount.value < 2) return null
52
+
53
+ const departure = getSegmentTime(segmentIndex, 'departure_date')
54
+ const arrival = getPreviousSegmentArrival(segmentIndex)
55
+
56
+ return getDuration(departure, arrival)
57
+ }
58
+ </script>
59
+
60
+ <style lang="scss">
61
+ .cpTripDetails {
62
+ padding: var(--cp-spacing-xl) var(--cp-spacing-md);
63
+ }
64
+ </style>
@@ -0,0 +1,224 @@
1
+ <template>
2
+ <div class="cpTripLegRouteInformation">
3
+ <div class="cpTripLegRouteInformation__icon">
4
+ <cp-icon size="18" type="flight" />
5
+ </div>
6
+ <div class="cpTripLegRouteInformation__content" @click="toggleDetails">
7
+ <div class="cpTripLegRouteInformation__main">
8
+ <div class="cpTripLegRouteInformation__airline">
9
+ <cp-airline-logo class="cpTripLegRouteInformation__logo" :iata-code="airlineIata" />
10
+ </div>
11
+ <cp-badge class="cpTripLegRouteInformation__badge" size="sm">{{ airlineName }}</cp-badge>
12
+ <cp-badge class="cpTripLegRouteInformation__badge" color="accent" size="sm">{{ flightDuration }}</cp-badge>
13
+ <div class="cpTripLegRouteInformation__toggle">
14
+ <button class="toggle__button" type="button" @click.stop="toggleDetails">
15
+ <cp-icon class="toggle__icon" :class="toggleDynamicClass" size="16" type="chevron-down" />
16
+ </button>
17
+ </div>
18
+ </div>
19
+ <cp-transition-expand>
20
+ <div v-if="isToggled" class="cpTripLegRouteInformation__details">
21
+ <div class="details__wrapper">
22
+ <p class="details__title">{{ config.routeInformation.connectionInfo }}</p>
23
+ <span class="details__label">
24
+ <cp-airline-logo class="details__airlineLogo" :iata-code="airlineIata" :size="16" />
25
+ {{ config.routeInformation.airline }}
26
+ </span>
27
+ <span class="details__value">{{ airlineName }}</span>
28
+ <span class="details__label">
29
+ <cp-icon class="details__icon" size="16" type="info" />{{ config.routeInformation.flightNo }}
30
+ </span>
31
+ <span class="details__value">{{ flightNumber }}</span>
32
+ <template v-if="technicalStop">
33
+ <span class="details__label">
34
+ <cp-icon class="details__icon" size="16" type="route-one-stop" />
35
+ {{ config.routeInformation.technicalStop }}
36
+ </span>
37
+ <span class="details__value">{{ technicalStop }}</span>
38
+ </template>
39
+ </div>
40
+ </div>
41
+ </cp-transition-expand>
42
+ </div>
43
+ </div>
44
+ </template>
45
+
46
+ <script setup lang="ts">
47
+ import { computed, ref } from 'vue'
48
+
49
+ import type { CpTripDetailsConfig } from '@/constants/tripDetails/CpTripDetailsConfig'
50
+ import type { FlightSegment } from '@/constants/tripDetails/FlightSegment'
51
+
52
+ import CpAirlineLogo from './CpAirlineLogo.vue'
53
+ import CpBadge from './CpBadge.vue'
54
+ import CpIcon from './CpIcon.vue'
55
+ import CpTransitionExpand from './CpTransitionExpand.vue'
56
+
57
+ interface Props {
58
+ config: CpTripDetailsConfig
59
+ flightDuration: string
60
+ segment: FlightSegment
61
+ }
62
+
63
+ const props = defineProps<Props>()
64
+
65
+ const isToggled = ref(false)
66
+
67
+ const airlineIata = computed(() => props.segment.airline.iata_code)
68
+ const airlineName = computed(() => props.segment.airline.name || 'n/a')
69
+ const flightNumber = computed(() => props.segment.flight_number)
70
+
71
+ const technicalStop = computed(() => {
72
+ const viaAirport = props.segment.via_airport
73
+ return viaAirport ? `${viaAirport.city_name} (${viaAirport.iata_code})` : ''
74
+ })
75
+
76
+ const toggleDynamicClass = computed(() => ({ 'toggle__icon--isRotated': isToggled.value }))
77
+
78
+ const toggleDetails = () => (isToggled.value = !isToggled.value)
79
+ </script>
80
+
81
+ <style lang="scss">
82
+ .cpTripLegRouteInformation {
83
+ position: relative;
84
+ display: flex;
85
+ width: min(100%, #{fn.px-to-rem(450)});
86
+ align-items: flex-start;
87
+
88
+ &::before {
89
+ position: absolute;
90
+ width: fn.px-to-rem(24);
91
+ background-image: url('@/assets/images/dot.svg');
92
+ background-position: center center;
93
+ background-repeat: repeat-y;
94
+ background-size: var(--cp-spacing-sm);
95
+ content: '';
96
+ inset: 0;
97
+ }
98
+
99
+ &__icon {
100
+ position: relative;
101
+ display: flex;
102
+ width: fn.px-to-rem(18);
103
+ flex-shrink: 0;
104
+ align-items: center;
105
+ margin: fn.px-to-rem(22) var(--cp-spacing-md) 0 fn.px-to-rem(3); // 3px so it matches 24px width (18px + 2 * 3px)
106
+ background-color: var(--cp-background-primary);
107
+ color: var(--cp-text-secondary);
108
+ transform: rotate(180deg);
109
+ }
110
+
111
+ &__icon::before {
112
+ position: absolute;
113
+ z-index: -1;
114
+ top: calc(#{var(--cp-spacing-sm)} / 2 * -1);
115
+ width: 100%;
116
+ height: calc(100% + #{var(--cp-spacing-sm)});
117
+ background-color: var(--cp-background-primary);
118
+ content: '';
119
+ }
120
+
121
+ &__icon i {
122
+ width: 100%;
123
+ }
124
+
125
+ &__content {
126
+ min-width: 0;
127
+ flex: 1;
128
+ align-items: center;
129
+ cursor: pointer;
130
+ padding-block: fn.px-to-rem(20);
131
+ }
132
+
133
+ &__main {
134
+ display: flex;
135
+ flex-wrap: wrap;
136
+ align-items: center;
137
+ gap: var(--cp-spacing-md);
138
+ }
139
+
140
+ &__airline {
141
+ @include mx.square-sizing(24);
142
+
143
+ overflow: hidden;
144
+ border-radius: fn.px-to-rem(4);
145
+ }
146
+
147
+ &__logo {
148
+ width: 100% !important;
149
+ }
150
+
151
+ &__badge {
152
+ @extend %u-text-ellipsis;
153
+
154
+ min-width: 0;
155
+
156
+ .cpBadge__label {
157
+ text-transform: initial !important;
158
+ }
159
+ }
160
+
161
+ .details {
162
+ &__wrapper {
163
+ display: grid;
164
+ width: min(100%, #{fn.px-to-rem(300)});
165
+ padding: var(--cp-spacing-lg) 0 var(--cp-spacing-md);
166
+ font-size: fn.px-to-em(14);
167
+ gap: var(--cp-spacing-md);
168
+ grid-template-columns: auto 1fr;
169
+ }
170
+
171
+ &__title {
172
+ margin-bottom: var(--cp-spacing-xs);
173
+ font-weight: 600;
174
+ grid-column: 1 / -1;
175
+ }
176
+
177
+ &__label,
178
+ &__value {
179
+ color: var(--cp-text-secondary);
180
+ text-transform: capitalize;
181
+ }
182
+
183
+ &__label {
184
+ display: flex;
185
+ align-items: center;
186
+ }
187
+
188
+ &__icon,
189
+ &__airlineLogo {
190
+ margin-right: var(--cp-spacing-md);
191
+ }
192
+
193
+ &__value {
194
+ text-align: right;
195
+ }
196
+ }
197
+
198
+ &__toggle {
199
+ flex-shrink: 0;
200
+ }
201
+
202
+ .toggle {
203
+ &__button {
204
+ display: flex;
205
+ align-items: center;
206
+ padding: var(--cp-spacing-sm);
207
+ border-radius: 50%;
208
+ background-color: var(--cp-background-secondary);
209
+
210
+ &:focus-visible {
211
+ @extend %u-focus-outline;
212
+ }
213
+ }
214
+
215
+ &__icon {
216
+ transition: rotate 300ms ease;
217
+
218
+ &--isRotated {
219
+ rotate: 180deg;
220
+ }
221
+ }
222
+ }
223
+ }
224
+ </style>
@@ -0,0 +1,63 @@
1
+ <template>
2
+ <div class="cpTripLegRouteLayover">
3
+ <p class="cpTripLegRouteLayover__content">
4
+ <cp-icon class="cpTripLegRouteLayover__icon" type="clock" />
5
+ <time :datetime="isoDuration">{{ formattedDuration }}</time>
6
+ {{ config.routeLayover.layover }}
7
+ </p>
8
+ </div>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ import { computed } from 'vue'
13
+
14
+ import type { CpTripDetailsConfig } from '@/constants/tripDetails/CpTripDetailsConfig'
15
+ import type { TripDuration } from '@/helpers/tripDetails'
16
+
17
+ import { getFormattedDuration } from '@/helpers/tripDetails'
18
+ import CpIcon from './CpIcon.vue'
19
+
20
+ interface Props {
21
+ config: CpTripDetailsConfig
22
+ duration: TripDuration | null
23
+ locale?: string
24
+ }
25
+
26
+ const props = withDefaults(defineProps<Props>(), {
27
+ locale: 'en',
28
+ })
29
+
30
+ const isoDuration = computed(() => props.duration?.toISO() ?? '')
31
+ const formattedDuration = computed(() => getFormattedDuration(props.duration, props.locale))
32
+ </script>
33
+
34
+ <style lang="scss">
35
+ .cpTripLegRouteLayover {
36
+ position: relative;
37
+
38
+ &__content {
39
+ display: flex;
40
+ padding-left: calc(#{fn.px-to-rem(24)} + #{fn.px-to-rem(12)});
41
+ color: var(--cp-text-secondary);
42
+ padding-block: var(--cp-spacing-2xl);
43
+
44
+ time {
45
+ margin-right: var(--cp-spacing-sm);
46
+ }
47
+ }
48
+
49
+ &__icon {
50
+ @include mx.square-sizing(18);
51
+
52
+ margin-right: var(--cp-spacing-md);
53
+ }
54
+
55
+ &::before {
56
+ position: absolute;
57
+ width: fn.px-to-rem(2);
58
+ background-color: var(--cp-border-soft);
59
+ content: '';
60
+ inset: 0 fn.px-to-rem(11);
61
+ }
62
+ }
63
+ </style>
@@ -0,0 +1,196 @@
1
+ <template>
2
+ <div class="cpTripLegRoutePlace" :class="classes">
3
+ <div class="cpTripLegRoutePlace__pin">
4
+ <cp-icon v-if="displayPinIcon" type="map-pin" />
5
+ </div>
6
+ <div class="cpTripLegRoutePlace__details">
7
+ <cp-tooltip class="cpTripLegRoutePlace__timeTooltip" :content="dateFormatted">
8
+ <time class="cpTripLegRoutePlace__time" :datetime="isoTime">
9
+ <!-- eslint-disable-next-line -->
10
+ <span>{{ formattedTime }}</span
11
+ ><!-- remove unwanted white space --><sup v-if="plusOneDay">+{{ plusOneDay }}</sup>
12
+ </time>
13
+ </cp-tooltip>
14
+ <h4 class="cpTripLegRoutePlace__city">{{ city }}</h4>
15
+ <h5 class="cpTripLegRoutePlace__iata">{{ iata }}</h5>
16
+ <span v-if="terminal" class="cpTripLegRoutePlace__terminal">
17
+ <span class="cpTripLegRoutePlace__terminalIcon">T{{ terminal.terminal_code }}</span>
18
+ <span class="cpTripLegRoutePlace__terminalLabel">{{ terminalLabel }}</span>
19
+ </span>
20
+ </div>
21
+ </div>
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+ import { computed } from 'vue'
26
+
27
+ import type { CpTripDetailsConfig } from '@/constants/tripDetails/CpTripDetailsConfig'
28
+ import type { FlightTerminal } from '@/constants/tripDetails/Terminal'
29
+ import type { TripDateTime } from '@/helpers/tripDetails'
30
+
31
+ import { FlightStopTypes } from '@/constants/tripDetails/FlightStopTypes'
32
+ import CpIcon from './CpIcon.vue'
33
+ import CpTooltip from './CpTooltip.vue'
34
+
35
+ interface Props {
36
+ city: string
37
+ config: CpTripDetailsConfig
38
+ dayCountAfterArrival?: number
39
+ dayCountAfterDeparture?: number
40
+ iata: string
41
+ terminal?: FlightTerminal
42
+ time: TripDateTime
43
+ type: FlightStopTypes
44
+ }
45
+
46
+ const props = withDefaults(defineProps<Props>(), {
47
+ dayCountAfterArrival: 0,
48
+ dayCountAfterDeparture: 0,
49
+ terminal: undefined,
50
+ })
51
+
52
+ const displayPinIcon = computed(() => props.type === FlightStopTypes.DESTINATION)
53
+ const classes = computed(() => `cpTripLegRoutePlace--${props.type}`)
54
+ const dateFormatted = computed(() => props.time.toFormat('ccc, LLL d'))
55
+ const formattedTime = computed(() => props.time.toFormat('T'))
56
+ const terminalLabel = computed(() => props.terminal?.label || props.config.routePlace.terminalNotSpecified)
57
+ const plusOneDay = computed(() => props.dayCountAfterArrival || props.dayCountAfterDeparture)
58
+ const isoTime = computed(() => props.time.toISO() ?? '')
59
+ </script>
60
+
61
+ <style lang="scss">
62
+ .cpTripLegRoutePlace {
63
+ position: relative;
64
+ display: flex;
65
+
66
+ &::before {
67
+ position: absolute;
68
+ width: fn.px-to-rem(24);
69
+ background-image: url('@/assets/images/dot.svg');
70
+ background-position: center center;
71
+ background-repeat: repeat-y;
72
+ background-size: var(--cp-spacing-sm);
73
+ content: '';
74
+ inset: 0;
75
+ }
76
+
77
+ &--layover:last-child::before {
78
+ width: fn.px-to-rem(2);
79
+ background-color: var(--cp-border-soft);
80
+ inset: 0 fn.px-to-rem(11);
81
+ }
82
+
83
+ &--destination:last-child::before {
84
+ content: none;
85
+ }
86
+
87
+ &__pin {
88
+ position: relative;
89
+ z-index: 1;
90
+ display: flex;
91
+ width: fn.px-to-rem(24);
92
+ height: fn.px-to-rem(22); // Same value as __details line-height
93
+ flex-shrink: 0;
94
+ align-items: center;
95
+ justify-content: center;
96
+ margin-right: var(--cp-spacing-lg);
97
+ color: var(--cp-text-secondary); // Visual design, no design token needed
98
+
99
+ > i {
100
+ width: fn.px-to-rem(18);
101
+ height: fn.px-to-rem(22);
102
+ }
103
+
104
+ &::before,
105
+ &::after {
106
+ background-color: var(--cp-background-primary);
107
+ content: '';
108
+ }
109
+
110
+ &::before {
111
+ position: absolute;
112
+ z-index: -1;
113
+ top: calc(#{var(--cp-spacing-xs)} * -1);
114
+ left: 0;
115
+ width: 100%;
116
+ height: calc(100% + #{var(--cp-spacing-sm)});
117
+ }
118
+ }
119
+
120
+ &--origin &__pin::after {
121
+ width: fn.px-to-rem(10);
122
+ border: max(1.5px, 0.08em) solid var(--cp-border-soft); // Visual design, no design token needed
123
+ border-radius: fn.px-to-rem(1000);
124
+ aspect-ratio: 1 / 1;
125
+ }
126
+
127
+ &--layover &__pin::after {
128
+ width: fn.px-to-rem(4);
129
+ height: fn.px-to-rem(4);
130
+ border-radius: fn.px-to-rem(1000);
131
+ background-color: var(--cp-text-secondary); // Visual design, no design token needed
132
+ }
133
+
134
+ &__details {
135
+ line-height: fn.px-to-rem(22); // Same value as pin height
136
+ }
137
+
138
+ &__timeTooltip {
139
+ display: inline-block;
140
+ vertical-align: middle;
141
+ }
142
+
143
+ &__details > * {
144
+ display: inline-block;
145
+ vertical-align: middle;
146
+
147
+ &:not(:first-child) {
148
+ font-size: fn.px-to-em(14);
149
+ }
150
+
151
+ &:not(:last-child)::after {
152
+ display: inline-block;
153
+ margin: 0 var(--cp-spacing-md);
154
+ color: currentColor;
155
+ content: '•';
156
+ font-size: fn.px-to-em(10);
157
+ transform: translateY(-1px);
158
+ }
159
+ }
160
+
161
+ :is(&__time, &__city) {
162
+ font-weight: 500;
163
+ }
164
+
165
+ &__time {
166
+ font-variant: tabular-nums;
167
+ }
168
+
169
+ &__iata {
170
+ color: var(--cp-text-secondary);
171
+ font-weight: normal;
172
+ }
173
+
174
+ &__terminal {
175
+ color: var(--cp-text-secondary);
176
+ line-height: fn.px-to-rem(12);
177
+
178
+ > span {
179
+ vertical-align: middle;
180
+ }
181
+ }
182
+
183
+ &__terminalIcon {
184
+ display: inline-block;
185
+ min-width: fn.px-to-rem(16);
186
+ min-height: fn.px-to-rem(16);
187
+ padding: fn.px-to-rem(1) fn.px-to-rem(3);
188
+ border: fn.px-to-rem(1.5) solid var(--cp-border-soft);
189
+ border-radius: fn.px-to-rem(2);
190
+ margin-right: var(--cp-spacing-md);
191
+ font-size: fn.px-to-rem(9);
192
+ text-align: center;
193
+ vertical-align: middle;
194
+ }
195
+ }
196
+ </style>
@@ -0,0 +1,99 @@
1
+ <template>
2
+ <div class="cpTripLegRouteSegment">
3
+ <cp-trip-leg-route-layover
4
+ v-if="isLayoverSectionVisible"
5
+ :config="config"
6
+ :duration="stopDuration"
7
+ :locale="locale"
8
+ />
9
+ <cp-trip-leg-route-place
10
+ :city="origin.name"
11
+ class="cpTripLegRouteSegment__place"
12
+ :config="config"
13
+ :day-count-after-arrival="dayCountAfterArrival"
14
+ :iata="origin.iata_code"
15
+ :terminal="segment.origin_terminal"
16
+ :time="departureDate"
17
+ :type="firstAirportType"
18
+ />
19
+ <cp-trip-leg-route-information :config="config" :flight-duration="segmentDuration" :segment="segment" />
20
+ <cp-trip-leg-route-place
21
+ :city="destination.name"
22
+ class="cpTripLegRouteSegment__place"
23
+ :config="config"
24
+ :day-count-after-departure="dayCountAfterDeparture"
25
+ :iata="destination.iata_code"
26
+ :terminal="segment.destination_terminal"
27
+ :time="arrivalDate"
28
+ :type="secondAirportType"
29
+ />
30
+ </div>
31
+ </template>
32
+
33
+ <script setup lang="ts">
34
+ import { computed } from 'vue'
35
+
36
+ import type { CpTripDetailsConfig } from '@/constants/tripDetails/CpTripDetailsConfig'
37
+ import type { FlightSegment } from '@/constants/tripDetails/FlightSegment'
38
+ import type { TripDateTime, TripDuration } from '@/helpers/tripDetails'
39
+
40
+ import { FlightStopTypes } from '@/constants/tripDetails/FlightStopTypes'
41
+ import { fromGMTToLocal, getDayCountBetweenDates, getDuration, getFormattedDuration } from '@/helpers/tripDetails'
42
+ import CpTripLegRouteInformation from './CpTripLegRouteInformation.vue'
43
+ import CpTripLegRouteLayover from './CpTripLegRouteLayover.vue'
44
+ import CpTripLegRoutePlace from './CpTripLegRoutePlace.vue'
45
+
46
+ interface Props {
47
+ config: CpTripDetailsConfig
48
+ locale?: string
49
+ previousSegmentArrival: TripDateTime
50
+ segment: FlightSegment
51
+ segmentCount: number
52
+ segmentIndex: number
53
+ stopDuration: TripDuration | null
54
+ }
55
+
56
+ const props = withDefaults(defineProps<Props>(), {
57
+ locale: 'en',
58
+ })
59
+
60
+ const isFirstSegment = computed(() => props.segmentIndex === 0)
61
+ const isLayoverSectionVisible = computed(() => !isFirstSegment.value)
62
+ const origin = computed(() => props.segment.origin)
63
+ const destination = computed(() => props.segment.destination)
64
+ const isDirectFlight = computed(() => props.segmentCount === 1)
65
+ const isLastSegment = computed(() => props.segmentIndex === props.segmentCount - 1)
66
+
67
+ const firstAirportType = computed(() => {
68
+ if (isFirstSegment.value) return FlightStopTypes.ORIGIN
69
+ return FlightStopTypes.LAYOVER_ORIGIN
70
+ })
71
+
72
+ const secondAirportType = computed(() => {
73
+ if (isDirectFlight.value || isLastSegment.value) return FlightStopTypes.DESTINATION
74
+ return FlightStopTypes.LAYOVER_DESTINATION
75
+ })
76
+
77
+ const departureDate = computed(() =>
78
+ fromGMTToLocal(props.segment.departure_date, props.segment.origin.timezone, props.locale),
79
+ )
80
+ const arrivalDate = computed(() =>
81
+ fromGMTToLocal(props.segment.arrival_date, props.segment.destination.timezone, props.locale),
82
+ )
83
+
84
+ const dayCountAfterArrival = computed(() => {
85
+ return getDayCountBetweenDates({
86
+ departureDate: departureDate.value,
87
+ arrivalDate: props.previousSegmentArrival,
88
+ isArrival: true,
89
+ })
90
+ })
91
+
92
+ const dayCountAfterDeparture = computed(() => {
93
+ return getDayCountBetweenDates({ departureDate: departureDate.value, arrivalDate: arrivalDate.value })
94
+ })
95
+
96
+ const segmentDuration = computed(() =>
97
+ getFormattedDuration(getDuration(arrivalDate.value, departureDate.value), props.locale),
98
+ )
99
+ </script>