@citizenplane/pimp 18.21.2 → 18.23.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@citizenplane/pimp",
3
- "version": "18.21.2",
3
+ "version": "18.23.0",
4
4
  "scripts": {
5
5
  "dev": "storybook dev -p 8081",
6
6
  "build-storybook": "storybook build --output-dir ./docs",
@@ -6,39 +6,7 @@
6
6
  <cp-transition-expand>
7
7
  <div v-if="!applyToAllFlights">
8
8
  <div class="cpAncillaryFlightSelection__tabsWrapper">
9
- <div class="cpAncillaryFlightSelection__tabs" role="tablist">
10
- <button
11
- v-for="(flight, index) in flights"
12
- :key="flight.id"
13
- :aria-selected="isActiveTab(flight.id)"
14
- class="cpAncillaryFlightSelection__tab"
15
- :class="getTabDynamicClass(flight.id)"
16
- role="tab"
17
- type="button"
18
- @click="handleSelectFlight(flight)"
19
- >
20
- <span class="cpAncillaryFlightSelection__tabLabel">{{ getTabLabel(index) }}</span>
21
- <span class="cpAncillaryFlightSelection__tabRoute">
22
- <span
23
- class="cpAncillaryFlightSelection__tabRouteText cpAncillaryFlightSelection__tabRouteText--city"
24
- >{{ flight.originCity }}</span
25
- >
26
- <span
27
- class="cpAncillaryFlightSelection__tabRouteText cpAncillaryFlightSelection__tabRouteText--iata"
28
- >{{ flight.originIata }}</span
29
- >
30
- <cp-icon class="cpAncillaryFlightSelection__tabIcon" size="16" type="arrow-right" />
31
- <span
32
- class="cpAncillaryFlightSelection__tabRouteText cpAncillaryFlightSelection__tabRouteText--city"
33
- >{{ flight.destinationCity }}</span
34
- >
35
- <span
36
- class="cpAncillaryFlightSelection__tabRouteText cpAncillaryFlightSelection__tabRouteText--iata"
37
- >{{ flight.destinationIata }}</span
38
- >
39
- </span>
40
- </button>
41
- </div>
9
+ <cp-flight-tabs v-model:active-flight-id="activeFlightId" :flights="tabFlights" />
42
10
  </div>
43
11
  </div>
44
12
  </cp-transition-expand>
@@ -46,7 +14,10 @@
46
14
  </template>
47
15
 
48
16
  <script setup lang="ts">
17
+ import { computed } from 'vue'
18
+
49
19
  import type { CpAncillaryFlightSelectionFlight } from '@/constants/CpAncillaryFlightSelectionTypes'
20
+ import type { CpFlightTabsFlight } from '@/constants/CpFlightTabsTypes'
50
21
 
51
22
  interface Props {
52
23
  flights: CpAncillaryFlightSelectionFlight[]
@@ -64,16 +35,12 @@ const props = withDefaults(defineProps<Props>(), {
64
35
  const applyToAllFlights = defineModel<boolean>('applyToAllFlights', { default: false })
65
36
  const activeFlightId = defineModel<string | undefined>('activeFlightId')
66
37
 
67
- const isActiveTab = (id: string) => id === activeFlightId.value
68
-
69
- const getTabDynamicClass = (id: string): string =>
70
- isActiveTab(id) ? 'cpAncillaryFlightSelection__tab--isActive' : ''
71
-
72
- const getTabLabel = (index: number): string => (index === 0 ? props.outboundLabel : props.returnLabel)
73
-
74
- const handleSelectFlight = (flight: CpAncillaryFlightSelectionFlight) => {
75
- activeFlightId.value = flight.id
76
- }
38
+ const tabFlights = computed<CpFlightTabsFlight[]>(() =>
39
+ props.flights.map((flight, index) => ({
40
+ ...flight,
41
+ label: index === 0 ? props.outboundLabel : props.returnLabel,
42
+ })),
43
+ )
77
44
  </script>
78
45
 
79
46
  <style lang="scss">
@@ -84,7 +51,6 @@ const handleSelectFlight = (flight: CpAncillaryFlightSelectionFlight) => {
84
51
  padding: var(--cp-spacing-sm);
85
52
  border-radius: var(--cp-radius-xl);
86
53
  background: var(--cp-background-tertiary);
87
- container-type: inline-size;
88
54
  gap: var(--cp-spacing-sm);
89
55
 
90
56
  &__toggle {
@@ -106,87 +72,5 @@ const handleSelectFlight = (flight: CpAncillaryFlightSelectionFlight) => {
106
72
  padding: var(--cp-spacing-lg);
107
73
  gap: var(--cp-spacing-md);
108
74
  }
109
-
110
- &__tabs {
111
- display: flex;
112
- width: 100%;
113
- align-items: center;
114
- padding: var(--cp-spacing-sm);
115
- border-radius: var(--cp-radius-md);
116
- background: var(--cp-background-quaternary);
117
- gap: var(--cp-spacing-sm);
118
- }
119
-
120
- &__tab {
121
- @extend %u-focus-outline;
122
-
123
- display: flex;
124
- flex: 1;
125
- flex-direction: column;
126
- align-items: center;
127
- padding: var(--cp-spacing-lg) var(--cp-spacing-xl);
128
- border-radius: var(--cp-radius-sm);
129
- gap: var(--cp-spacing-sm-md);
130
-
131
- &--isActive {
132
- background: var(--cp-background-primary);
133
- box-shadow: var(--cp-shadows-xs);
134
- }
135
- }
136
-
137
- &__tabLabel {
138
- color: var(--cp-text-tertiary);
139
- font-size: var(--cp-text-size-xs);
140
- font-weight: 600;
141
- letter-spacing: 1px;
142
- line-height: var(--cp-text-xs-line-height);
143
- padding-inline: var(--cp-spacing-xs);
144
- text-transform: uppercase;
145
- }
146
-
147
- &__tabRoute {
148
- display: flex;
149
- align-items: center;
150
- gap: var(--cp-spacing-sm);
151
- }
152
-
153
- &__tabRouteText {
154
- color: var(--cp-text-secondary);
155
- font-size: var(--cp-text-size-md);
156
- font-weight: 500;
157
- line-height: var(--cp-text-md-line-height);
158
-
159
- &--iata {
160
- display: none;
161
- }
162
- }
163
-
164
- &__tabIcon {
165
- color: var(--cp-text-tertiary);
166
- }
167
-
168
- &__tab--isActive &__tabLabel {
169
- color: var(--cp-text-primary);
170
- }
171
-
172
- &__tab--isActive &__tabRouteText {
173
- color: var(--cp-text-accent-primary);
174
- }
175
-
176
- &__tab--isActive &__tabIcon {
177
- color: var(--cp-foreground-accent-secondary);
178
- }
179
-
180
- @container (width < 30rem) {
181
- .cpAncillaryFlightSelection__tabRouteText {
182
- &--city {
183
- display: none;
184
- }
185
-
186
- &--iata {
187
- display: inline;
188
- }
189
- }
190
- }
191
75
  }
192
76
  </style>
@@ -0,0 +1,271 @@
1
+ <template>
2
+ <div class="cpBasket">
3
+ <div v-if="hasLeading" class="cpBasket__trip">
4
+ <slot name="leading" />
5
+ </div>
6
+
7
+ <cp-transition-expand>
8
+ <div v-if="hasSections">
9
+ <div class="cpBasket__details">
10
+ <div v-for="category in visibleCategories" :key="category.id" class="cpBasket__category">
11
+ <div class="cpBasket__categoryTitle">
12
+ <p>{{ category.label }}</p>
13
+ <p>{{ category.subtotal }}</p>
14
+ </div>
15
+ <div v-if="category.hasGroups" class="cpBasket__groups">
16
+ <cp-transition-list-items>
17
+ <div v-for="group in category.groups" :key="group.id" class="cpBasket__group">
18
+ <p v-if="group.label" class="cpBasket__route">{{ group.label }}</p>
19
+ <cp-transition-list-items>
20
+ <div v-for="row in group.rows" :key="row.key" :class="group.rowClass">
21
+ <span>{{ row.label }}</span>
22
+ <span>{{ row.value }}</span>
23
+ </div>
24
+ </cp-transition-list-items>
25
+ </div>
26
+ </cp-transition-list-items>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </cp-transition-expand>
32
+
33
+ <div class="cpBasket__total">
34
+ <p>{{ totalLabel }}</p>
35
+ <p class="cpBasket__totalValue">{{ total }}</p>
36
+ </div>
37
+
38
+ <div v-if="hasTrailing" class="cpBasket__trailing">
39
+ <slot name="trailing" />
40
+ </div>
41
+ </div>
42
+ </template>
43
+
44
+ <script setup lang="ts">
45
+ import { computed, useSlots } from 'vue'
46
+
47
+ import type { CpBasketCategory, CpBasketRow } from '@/constants/CpBasketTypes'
48
+
49
+ import CpTransitionExpand from '@/components/CpTransitionExpand.vue'
50
+ import CpTransitionListItems from '@/components/CpTransitionListItems.vue'
51
+
52
+ interface Props {
53
+ hideDetailsOnCategories?: string[]
54
+ rows: CpBasketRow[]
55
+ total: string
56
+ totalLabel: string
57
+ }
58
+
59
+ interface ResolvedRow extends CpBasketRow {
60
+ key: string
61
+ }
62
+
63
+ interface ResolvedGroup {
64
+ id: string
65
+ label: string | null
66
+ rowClass: string
67
+ rows: ResolvedRow[]
68
+ }
69
+
70
+ interface ResolvedCategory {
71
+ groups: ResolvedGroup[]
72
+ hasGroups: boolean
73
+ id: string
74
+ label: string
75
+ subtotal: string
76
+ }
77
+
78
+ const props = withDefaults(defineProps<Props>(), {
79
+ hideDetailsOnCategories: () => [],
80
+ })
81
+
82
+ const UNGROUPED_ID = '__cpBasketUngrouped__'
83
+
84
+ const slots = useSlots()
85
+
86
+ const hasLeading = computed(() => !!slots.leading)
87
+
88
+ const hasTrailing = computed(() => !!slots.trailing)
89
+
90
+ const hideDetailsIds = computed(() => new Set(props.hideDetailsOnCategories))
91
+
92
+ const dedupeById = <T extends { id: string }>(items: T[]): T[] =>
93
+ items.filter((item, index) => items.findIndex((other) => other.id === item.id) === index)
94
+
95
+ const orderedCategories = computed<CpBasketCategory[]>(() => dedupeById(props.rows.map((row) => row.category)))
96
+
97
+ const withKeys = (rows: CpBasketRow[], categoryId: string): ResolvedRow[] =>
98
+ rows.map((row) => ({ ...row, key: `${categoryId}-${row.group?.id ?? 'ungrouped'}-${row.label}` }))
99
+
100
+ const buildResolvedGroup = (id: string, label: string | null, rows: CpBasketRow[], categoryId: string): ResolvedGroup => ({
101
+ id,
102
+ label,
103
+ rowClass: label ? 'cpBasket__row cpBasket__row--isIndented' : 'cpBasket__row',
104
+ rows: withKeys(rows, categoryId),
105
+ })
106
+
107
+ const buildResolvedCategory = (category: CpBasketCategory, categoryRows: CpBasketRow[]): ResolvedCategory => {
108
+ if (hideDetailsIds.value.has(category.id)) {
109
+ return { groups: [], hasGroups: false, id: category.id, label: category.label, subtotal: category.subtotal }
110
+ }
111
+
112
+ const groupsInCategory = categoryRows.flatMap((row) => (row.group ? [row.group] : []))
113
+ const uniqueGroups = dedupeById(groupsInCategory)
114
+ const labelledGroups = uniqueGroups.map((group) =>
115
+ buildResolvedGroup(
116
+ group.id,
117
+ group.label,
118
+ categoryRows.filter((row) => row.group?.id === group.id),
119
+ category.id,
120
+ ),
121
+ )
122
+
123
+ const ungroupedRows = categoryRows.filter((row) => row.group == null)
124
+ const ungroupedGroup: ResolvedGroup[] =
125
+ ungroupedRows.length === 0 ? [] : [buildResolvedGroup(UNGROUPED_ID, null, ungroupedRows, category.id)]
126
+
127
+ const groups = [...labelledGroups, ...ungroupedGroup]
128
+
129
+ return { groups, hasGroups: groups.length > 0, id: category.id, label: category.label, subtotal: category.subtotal }
130
+ }
131
+
132
+ const visibleCategories = computed<ResolvedCategory[]>(() =>
133
+ orderedCategories.value.map((category) =>
134
+ buildResolvedCategory(
135
+ category,
136
+ props.rows.filter((row) => row.category.id === category.id),
137
+ ),
138
+ ),
139
+ )
140
+
141
+ const hasSections = computed(() => visibleCategories.value.length > 0)
142
+ </script>
143
+
144
+ <style lang="scss">
145
+ .cpBasket {
146
+ display: flex;
147
+ flex-direction: column;
148
+ padding: var(--cp-spacing-sm);
149
+ border-radius: var(--cp-radius-xl);
150
+ background: var(--cp-background-tertiary);
151
+ gap: var(--cp-spacing-sm);
152
+
153
+ &__trip {
154
+ display: flex;
155
+ flex-direction: column;
156
+ padding: var(--cp-spacing-lg);
157
+ gap: var(--cp-spacing-md);
158
+ }
159
+
160
+ &__leg {
161
+ display: flex;
162
+ flex-direction: column;
163
+ gap: var(--cp-spacing-sm);
164
+
165
+ & + & {
166
+ padding-top: var(--cp-spacing-md);
167
+ border-top: fn.px-to-rem(1) solid var(--cp-border-soft);
168
+ }
169
+ }
170
+
171
+ &__legDate {
172
+ color: var(--cp-text-secondary);
173
+ font-size: var(--cp-text-size-xs);
174
+ font-weight: 600;
175
+ line-height: var(--cp-text-xs-line-height);
176
+ text-transform: uppercase;
177
+ }
178
+
179
+ &__details {
180
+ display: flex;
181
+ overflow: hidden;
182
+ flex-direction: column;
183
+ padding: var(--cp-spacing-md) var(--cp-spacing-lg);
184
+ border-radius: var(--cp-radius-lg);
185
+ background: var(--cp-background-secondary);
186
+ gap: var(--cp-spacing-sm-md);
187
+ }
188
+
189
+ &__category {
190
+ display: flex;
191
+ flex-direction: column;
192
+
193
+ & + & {
194
+ padding-top: var(--cp-spacing-sm-md);
195
+ border-top: fn.px-to-rem(1) solid var(--cp-border-soft);
196
+ }
197
+ }
198
+
199
+ &__categoryTitle {
200
+ display: flex;
201
+ align-items: center;
202
+ justify-content: space-between;
203
+ color: var(--cp-text-primary);
204
+ font-size: var(--cp-text-size-md);
205
+ font-weight: 600;
206
+ line-height: var(--cp-text-md-line-height);
207
+ padding-block: var(--cp-spacing-sm);
208
+
209
+ & > :last-child {
210
+ color: var(--cp-text-secondary);
211
+ }
212
+ }
213
+
214
+ &__groups {
215
+ display: flex;
216
+ flex-direction: column;
217
+ gap: var(--cp-spacing-md);
218
+ }
219
+
220
+ &__group {
221
+ position: relative;
222
+ display: flex;
223
+ flex-direction: column;
224
+ gap: var(--cp-spacing-sm);
225
+ }
226
+
227
+ &__route {
228
+ color: var(--cp-text-secondary);
229
+ font-size: var(--cp-text-size-sm);
230
+ font-weight: 500;
231
+ line-height: var(--cp-text-sm-line-height);
232
+ }
233
+
234
+ &__row {
235
+ display: flex;
236
+ width: 100%;
237
+ justify-content: space-between;
238
+ color: var(--cp-text-secondary);
239
+ font-size: var(--cp-text-size-sm);
240
+ gap: var(--cp-spacing-md);
241
+ line-height: var(--cp-text-sm-line-height);
242
+
243
+ &--isIndented {
244
+ padding-left: var(--cp-spacing-md);
245
+ }
246
+ }
247
+
248
+ &__total {
249
+ display: flex;
250
+ align-items: flex-end;
251
+ justify-content: space-between;
252
+ padding: var(--cp-spacing-lg);
253
+ color: var(--cp-text-primary);
254
+ font-size: var(--cp-text-size-lg);
255
+ font-weight: 600;
256
+ line-height: var(--cp-text-lg-line-height);
257
+ }
258
+
259
+ &__totalValue {
260
+ color: var(--cp-text-accent-primary);
261
+ font-variant-numeric: tabular-nums;
262
+ }
263
+
264
+ &__trailing {
265
+ display: flex;
266
+ flex-direction: column;
267
+ align-items: center;
268
+ gap: var(--cp-spacing-sm);
269
+ }
270
+ }
271
+ </style>
@@ -0,0 +1,128 @@
1
+ <template>
2
+ <div class="cpFlightTabs" role="tablist">
3
+ <button
4
+ v-for="flight in flights"
5
+ :key="flight.id"
6
+ :aria-selected="isActiveTab(flight.id)"
7
+ class="cpFlightTabs__tab"
8
+ :class="getTabDynamicClass(flight.id)"
9
+ role="tab"
10
+ type="button"
11
+ @click="handleSelectFlight(flight)"
12
+ >
13
+ <span v-if="flight.label" class="cpFlightTabs__tabLabel">{{ flight.label }}</span>
14
+ <span class="cpFlightTabs__tabRoute">
15
+ <span class="cpFlightTabs__tabRouteText cpFlightTabs__tabRouteText--city">{{ flight.originCity }}</span>
16
+ <span class="cpFlightTabs__tabRouteText cpFlightTabs__tabRouteText--iata">{{ flight.originIata }}</span>
17
+ <cp-icon class="cpFlightTabs__tabIcon" size="16" type="arrow-right" />
18
+ <span class="cpFlightTabs__tabRouteText cpFlightTabs__tabRouteText--city">{{ flight.destinationCity }}</span>
19
+ <span class="cpFlightTabs__tabRouteText cpFlightTabs__tabRouteText--iata">{{ flight.destinationIata }}</span>
20
+ </span>
21
+ </button>
22
+ </div>
23
+ </template>
24
+
25
+ <script setup lang="ts">
26
+ import type { CpFlightTabsFlight } from '@/constants/CpFlightTabsTypes'
27
+
28
+ interface Props {
29
+ flights: CpFlightTabsFlight[]
30
+ }
31
+
32
+ defineProps<Props>()
33
+
34
+ const activeFlightId = defineModel<string | undefined>('activeFlightId')
35
+
36
+ const isActiveTab = (id: string) => id === activeFlightId.value
37
+
38
+ const getTabDynamicClass = (id: string): string => (isActiveTab(id) ? 'cpFlightTabs__tab--isActive' : '')
39
+
40
+ const handleSelectFlight = (flight: CpFlightTabsFlight) => {
41
+ activeFlightId.value = flight.id
42
+ }
43
+ </script>
44
+
45
+ <style lang="scss">
46
+ .cpFlightTabs {
47
+ display: flex;
48
+ width: 100%;
49
+ align-items: center;
50
+ padding: var(--cp-spacing-sm);
51
+ border-radius: var(--cp-radius-md);
52
+ background: var(--cp-background-quaternary);
53
+ container-type: inline-size;
54
+ gap: var(--cp-spacing-sm);
55
+
56
+ &__tab {
57
+ @extend %u-focus-outline;
58
+
59
+ display: flex;
60
+ flex: 1;
61
+ flex-direction: column;
62
+ align-items: center;
63
+ padding: var(--cp-spacing-lg) var(--cp-spacing-xl);
64
+ border-radius: var(--cp-radius-sm);
65
+ gap: var(--cp-spacing-sm-md);
66
+
67
+ &--isActive {
68
+ background: var(--cp-background-primary);
69
+ box-shadow: var(--cp-shadows-xs);
70
+ }
71
+ }
72
+
73
+ &__tabLabel {
74
+ color: var(--cp-text-tertiary);
75
+ font-size: var(--cp-text-size-xs);
76
+ font-weight: 600;
77
+ letter-spacing: 1px;
78
+ line-height: var(--cp-text-xs-line-height);
79
+ padding-inline: var(--cp-spacing-xs);
80
+ text-transform: uppercase;
81
+ }
82
+
83
+ &__tabRoute {
84
+ display: flex;
85
+ align-items: center;
86
+ gap: var(--cp-spacing-sm);
87
+ }
88
+
89
+ &__tabRouteText {
90
+ color: var(--cp-text-secondary);
91
+ font-size: var(--cp-text-size-md);
92
+ font-weight: 500;
93
+ line-height: var(--cp-text-md-line-height);
94
+
95
+ &--iata {
96
+ display: none;
97
+ }
98
+ }
99
+
100
+ &__tabIcon {
101
+ color: var(--cp-text-tertiary);
102
+ }
103
+
104
+ &__tab--isActive &__tabLabel {
105
+ color: var(--cp-text-primary);
106
+ }
107
+
108
+ &__tab--isActive &__tabRouteText {
109
+ color: var(--cp-text-accent-primary);
110
+ }
111
+
112
+ &__tab--isActive &__tabIcon {
113
+ color: var(--cp-foreground-accent-secondary);
114
+ }
115
+
116
+ @container (width < 30rem) {
117
+ .cpFlightTabs__tabRouteText {
118
+ &--city {
119
+ display: none;
120
+ }
121
+
122
+ &--iata {
123
+ display: inline;
124
+ }
125
+ }
126
+ }
127
+ }
128
+ </style>
@@ -24,6 +24,7 @@ import CpAncillaryCategory from './CpAncillaryCategory.vue'
24
24
  import CpAncillaryFlightSelection from './CpAncillaryFlightSelection.vue'
25
25
  import CpAncillaryItem from './CpAncillaryItem.vue'
26
26
  import CpBadge from './CpBadge.vue'
27
+ import CpBasket from './CpBasket.vue'
27
28
  import CpBottomSheet from './CpBottomSheet.vue'
28
29
  import CpButton from './CpButton.vue'
29
30
  import CpButtonGroup from './CpButtonGroup.vue'
@@ -35,6 +36,7 @@ import CpDate from './CpDate.vue'
35
36
  import CpDatepicker from './CpDatepicker.vue'
36
37
  import CpDialog from './CpDialog.vue'
37
38
  import CpDynamicDialog from './CpDynamicDialog.vue'
39
+ import CpFlightTabs from './CpFlightTabs.vue'
38
40
  import CpHeading from './CpHeading.vue'
39
41
  import CpIcon from './CpIcon.vue'
40
42
  import CpInput from './CpInput.vue'
@@ -123,6 +125,8 @@ const Components = {
123
125
  CpAncillaryCategory,
124
126
  CpAncillaryFlightSelection,
125
127
  CpAncillaryItem,
128
+ CpBasket,
129
+ CpFlightTabs,
126
130
  CpLoader,
127
131
  CpInput,
128
132
  CpText,
@@ -198,6 +202,8 @@ const Pimp: Plugin = {
198
202
  }
199
203
 
200
204
  export type { CpAncillaryFlightSelectionFlight } from '@/constants/CpAncillaryFlightSelectionTypes'
205
+ export type { CpBasketCategory, CpBasketGroup, CpBasketRow } from '@/constants/CpBasketTypes'
206
+ export type { CpFlightTabsFlight } from '@/constants/CpFlightTabsTypes'
201
207
  export type { CpStepperStep } from '@/constants/CpStepperTypes'
202
208
  export type {
203
209
  CpSeatMapConfig,
@@ -243,6 +249,7 @@ export {
243
249
  CpAncillaryFlightSelection,
244
250
  CpAncillaryItem,
245
251
  CpBadge,
252
+ CpBasket,
246
253
  CpBottomSheet,
247
254
  CpButton,
248
255
  CpButtonGroup,
@@ -255,6 +262,7 @@ export {
255
262
  CpDatepicker,
256
263
  CpDialog,
257
264
  CpDynamicDialog,
265
+ CpFlightTabs,
258
266
  CpHeading,
259
267
  CpIcon,
260
268
  CpInput,
@@ -1,7 +1 @@
1
- export interface CpAncillaryFlightSelectionFlight {
2
- destinationCity?: string
3
- destinationIata?: string
4
- id: string
5
- originCity?: string
6
- originIata?: string
7
- }
1
+ export type { CpFlightTabsFlight as CpAncillaryFlightSelectionFlight } from '@/constants/CpFlightTabsTypes'
@@ -0,0 +1,17 @@
1
+ export interface CpBasketCategory {
2
+ id: string
3
+ label: string
4
+ subtotal: string
5
+ }
6
+
7
+ export interface CpBasketGroup {
8
+ id: string
9
+ label: string
10
+ }
11
+
12
+ export interface CpBasketRow {
13
+ category: CpBasketCategory
14
+ group?: CpBasketGroup | null
15
+ label: string
16
+ value: string
17
+ }
@@ -0,0 +1,8 @@
1
+ export interface CpFlightTabsFlight {
2
+ destinationCity?: string
3
+ destinationIata?: string
4
+ id: string
5
+ label?: string
6
+ originCity?: string
7
+ originIata?: string
8
+ }