@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/dist/components/CpAncillaryFlightSelection.vue.d.ts.map +1 -1
- package/dist/components/CpBasket.vue.d.ts +25 -0
- package/dist/components/CpBasket.vue.d.ts.map +1 -0
- package/dist/components/CpFlightTabs.vue.d.ts +17 -0
- package/dist/components/CpFlightTabs.vue.d.ts.map +1 -0
- package/dist/components/index.d.ts +5 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/constants/CpAncillaryFlightSelectionTypes.d.ts +1 -7
- package/dist/constants/CpAncillaryFlightSelectionTypes.d.ts.map +1 -1
- package/dist/constants/CpBasketTypes.d.ts +16 -0
- package/dist/constants/CpBasketTypes.d.ts.map +1 -0
- package/dist/constants/CpFlightTabsTypes.d.ts +9 -0
- package/dist/constants/CpFlightTabsTypes.d.ts.map +1 -0
- package/dist/pimp.es.js +1722 -1625
- package/dist/pimp.umd.js +40 -40
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/CpAncillaryFlightSelection.vue +10 -126
- package/src/components/CpBasket.vue +271 -0
- package/src/components/CpFlightTabs.vue +128 -0
- package/src/components/index.ts +8 -0
- package/src/constants/CpAncillaryFlightSelectionTypes.ts +1 -7
- package/src/constants/CpBasketTypes.ts +17 -0
- package/src/constants/CpFlightTabsTypes.ts +8 -0
- package/src/stories/CpBasket.stories.ts +323 -0
- package/src/stories/CpFlightTabs.stories.ts +142 -0
package/package.json
CHANGED
|
@@ -6,39 +6,7 @@
|
|
|
6
6
|
<cp-transition-expand>
|
|
7
7
|
<div v-if="!applyToAllFlights">
|
|
8
8
|
<div class="cpAncillaryFlightSelection__tabsWrapper">
|
|
9
|
-
<
|
|
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
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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>
|
package/src/components/index.ts
CHANGED
|
@@ -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
|
|
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
|
+
}
|