@citizenplane/pimp 18.14.4 → 18.16.1

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 (59) hide show
  1. package/dist/components/CpMultiselect.vue.d.ts.map +1 -1
  2. package/dist/components/CpTextMorph.vue.d.ts +11 -0
  3. package/dist/components/CpTextMorph.vue.d.ts.map +1 -0
  4. package/dist/components/CpTripDetails.vue.d.ts +14 -0
  5. package/dist/components/CpTripDetails.vue.d.ts.map +1 -0
  6. package/dist/components/CpTripLegRouteInformation.vue.d.ts +11 -0
  7. package/dist/components/CpTripLegRouteInformation.vue.d.ts.map +1 -0
  8. package/dist/components/CpTripLegRouteLayover.vue.d.ts +13 -0
  9. package/dist/components/CpTripLegRouteLayover.vue.d.ts.map +1 -0
  10. package/dist/components/CpTripLegRoutePlace.vue.d.ts +22 -0
  11. package/dist/components/CpTripLegRoutePlace.vue.d.ts.map +1 -0
  12. package/dist/components/CpTripLegRouteSegment.vue.d.ts +18 -0
  13. package/dist/components/CpTripLegRouteSegment.vue.d.ts.map +1 -0
  14. package/dist/components/index.d.ts +8 -1
  15. package/dist/components/index.d.ts.map +1 -1
  16. package/dist/constants/tripDetails/Airline.d.ts +5 -0
  17. package/dist/constants/tripDetails/Airline.d.ts.map +1 -0
  18. package/dist/constants/tripDetails/Airport.d.ts +7 -0
  19. package/dist/constants/tripDetails/Airport.d.ts.map +1 -0
  20. package/dist/constants/tripDetails/CpTripDetailsConfig.d.ts +16 -0
  21. package/dist/constants/tripDetails/CpTripDetailsConfig.d.ts.map +1 -0
  22. package/dist/constants/tripDetails/Flight.d.ts +5 -0
  23. package/dist/constants/tripDetails/Flight.d.ts.map +1 -0
  24. package/dist/constants/tripDetails/FlightSegment.d.ts +15 -0
  25. package/dist/constants/tripDetails/FlightSegment.d.ts.map +1 -0
  26. package/dist/constants/tripDetails/FlightStopTypes.d.ts +7 -0
  27. package/dist/constants/tripDetails/FlightStopTypes.d.ts.map +1 -0
  28. package/dist/constants/tripDetails/Terminal.d.ts +5 -0
  29. package/dist/constants/tripDetails/Terminal.d.ts.map +1 -0
  30. package/dist/constants/tripDetails/index.d.ts +9 -0
  31. package/dist/constants/tripDetails/index.d.ts.map +1 -0
  32. package/dist/helpers/tripDetails.d.ts +23 -0
  33. package/dist/helpers/tripDetails.d.ts.map +1 -0
  34. package/dist/pimp.es.js +5496 -4508
  35. package/dist/pimp.umd.js +67 -34
  36. package/dist/style.css +1 -1
  37. package/package.json +2 -1
  38. package/src/assets/images/dot.svg +3 -0
  39. package/src/components/CpMultiselect.vue +23 -1
  40. package/src/components/CpTextMorph.vue +17 -0
  41. package/src/components/CpTripDetails.vue +64 -0
  42. package/src/components/CpTripLegRouteInformation.vue +224 -0
  43. package/src/components/CpTripLegRouteLayover.vue +63 -0
  44. package/src/components/CpTripLegRoutePlace.vue +196 -0
  45. package/src/components/CpTripLegRouteSegment.vue +99 -0
  46. package/src/components/index.ts +26 -0
  47. package/src/constants/tripDetails/Airline.ts +4 -0
  48. package/src/constants/tripDetails/Airport.ts +6 -0
  49. package/src/constants/tripDetails/CpTripDetailsConfig.ts +29 -0
  50. package/src/constants/tripDetails/Flight.ts +5 -0
  51. package/src/constants/tripDetails/FlightSegment.ts +15 -0
  52. package/src/constants/tripDetails/FlightStopTypes.ts +6 -0
  53. package/src/constants/tripDetails/Terminal.ts +4 -0
  54. package/src/constants/tripDetails/index.ts +9 -0
  55. package/src/helpers/tripDetails.ts +61 -0
  56. package/src/stories/CpMultiselect.stories.ts +30 -0
  57. package/src/stories/CpTextMorph.stories.ts +236 -0
  58. package/src/stories/CpTrip.stories.ts +147 -0
  59. 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.1",
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>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="cpMultiselect">
2
+ <div class="cpMultiselect" :class="multiselectDynamicClasses">
3
3
  <base-input-label
4
4
  v-if="label"
5
5
  v-bind-once="{ for: multiselectId }"
@@ -216,6 +216,20 @@ const chevronDynamicClass = computed(() => {
216
216
 
217
217
  const hasPrefixSlot = computed(() => !!slots.prefix)
218
218
 
219
+ const hasValue = computed(() => {
220
+ if (props.multiple) return !!selectModel.value?.length
221
+ return !isEmpty(selectModel.value)
222
+ })
223
+
224
+ const hasError = computed(() => (props.isInvalid ?? false) && !!props.errorMessage)
225
+
226
+ const multiselectDynamicClasses = computed(() => ({
227
+ 'cpMultiselect--hasPrefix': hasPrefixSlot.value,
228
+ 'cpMultiselect--isMultiple': props.multiple ?? false,
229
+ 'cpMultiselect--hasValue': hasValue.value,
230
+ 'cpMultiselect--hasError': hasError.value,
231
+ }))
232
+
219
233
  const displayClearButton = computed(() => {
220
234
  if (props.multiple) return false
221
235
  return props.isClearable && !isEmpty(selectModel.value)
@@ -348,6 +362,14 @@ onMounted(() => overrideAlignOverlay())
348
362
  }
349
363
  }
350
364
 
365
+ &--hasPrefix#{&}--isMultiple#{&}--hasValue .cpMultiselect__select {
366
+ gap: var(--cp-spacing-sm);
367
+ }
368
+
369
+ &--hasError .cpMultiselect__select {
370
+ margin-bottom: var(--cp-spacing-md);
371
+ }
372
+
351
373
  &__select {
352
374
  position: relative;
353
375
  display: flex;
@@ -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>