@citizenplane/pimp 18.22.0 → 18.23.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.
- package/dist/components/CpAncillaryFlightSelection.vue.d.ts.map +1 -1
- package/dist/components/CpFlightTabs.vue.d.ts +17 -0
- package/dist/components/CpFlightTabs.vue.d.ts.map +1 -0
- package/dist/components/CpStepper.vue.d.ts.map +1 -1
- package/dist/components/index.d.ts +3 -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/CpFlightTabsTypes.d.ts +9 -0
- package/dist/constants/CpFlightTabsTypes.d.ts.map +1 -0
- package/dist/pimp.es.js +1675 -1630
- package/dist/pimp.umd.js +50 -50
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/CpAncillaryFlightSelection.vue +10 -126
- package/src/components/CpFlightTabs.vue +128 -0
- package/src/components/CpStepper.vue +47 -11
- package/src/components/index.ts +4 -0
- package/src/constants/CpAncillaryFlightSelectionTypes.ts +1 -7
- package/src/constants/CpFlightTabsTypes.ts +8 -0
- package/src/stories/CpFlightTabs.stories.ts +142 -0
- package/src/stories/CpStepper.stories.ts +50 -1
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,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>
|
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="cpStepper">
|
|
3
3
|
<div class="cpStepper__wrapper">
|
|
4
|
-
<div v-for="
|
|
4
|
+
<div v-for="stepItem in stepItems" :key="stepItem.id" class="cpStepper__stepWrapper">
|
|
5
5
|
<button
|
|
6
|
+
:aria-label="stepItem.label"
|
|
6
7
|
class="cpStepper__step"
|
|
7
|
-
:class="
|
|
8
|
-
:disabled="
|
|
8
|
+
:class="stepItem.className"
|
|
9
|
+
:disabled="stepItem.isDisabled"
|
|
9
10
|
type="button"
|
|
10
|
-
@click="handleStepClick(
|
|
11
|
+
@click="handleStepClick(stepItem.id)"
|
|
11
12
|
>
|
|
12
|
-
<span class="cpStepper__stepNumber">
|
|
13
|
-
|
|
13
|
+
<span class="cpStepper__stepNumber">
|
|
14
|
+
<cp-icon v-if="stepItem.isCompleted" size="12" type="check" />
|
|
15
|
+
<template v-else>{{ stepItem.number }}</template>
|
|
16
|
+
</span>
|
|
17
|
+
<span class="cpStepper__stepTitle">{{ stepItem.label }}</span>
|
|
14
18
|
</button>
|
|
15
|
-
<cp-icon v-if="
|
|
19
|
+
<cp-icon v-if="stepItem.isNotLast" class="cpStepper__chevron" size="16" type="chevron-right" />
|
|
16
20
|
</div>
|
|
17
21
|
</div>
|
|
18
22
|
</div>
|
|
19
23
|
</template>
|
|
20
24
|
|
|
21
25
|
<script setup lang="ts">
|
|
26
|
+
import { computed } from 'vue'
|
|
27
|
+
|
|
22
28
|
import type { CpStepperStep } from '@/constants/CpStepperTypes'
|
|
23
29
|
|
|
24
30
|
import CpIcon from '@/components/CpIcon.vue'
|
|
@@ -36,13 +42,32 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
36
42
|
|
|
37
43
|
const emit = defineEmits<{ select: [id: string] }>()
|
|
38
44
|
|
|
39
|
-
const
|
|
45
|
+
const activeStepIndex = computed(() => props.steps.findIndex((step) => step.id === props.activeStepId))
|
|
46
|
+
|
|
47
|
+
const isStepCompleted = (stepIndex: number) => activeStepIndex.value > -1 && stepIndex < activeStepIndex.value
|
|
40
48
|
|
|
41
|
-
const
|
|
49
|
+
const stepClassName = (step: CpStepperStep, stepIndex: number) => {
|
|
50
|
+
return {
|
|
51
|
+
'cpStepper__step--isActive': step.id === props.activeStepId,
|
|
52
|
+
'cpStepper__step--isCompleted': isStepCompleted(stepIndex),
|
|
53
|
+
}
|
|
54
|
+
}
|
|
42
55
|
|
|
43
|
-
const
|
|
56
|
+
const stepItems = computed(() => {
|
|
57
|
+
return props.steps.map((step, stepIndex) => {
|
|
58
|
+
return {
|
|
59
|
+
className: stepClassName(step, stepIndex),
|
|
60
|
+
id: step.id,
|
|
61
|
+
isCompleted: isStepCompleted(stepIndex),
|
|
62
|
+
isDisabled: stepIndex > props.reachedStepIndex,
|
|
63
|
+
isNotLast: stepIndex < props.steps.length - 1,
|
|
64
|
+
label: step.label,
|
|
65
|
+
number: stepIndex + 1,
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
})
|
|
44
69
|
|
|
45
|
-
const handleStepClick = (
|
|
70
|
+
const handleStepClick = (stepId: string) => emit('select', stepId)
|
|
46
71
|
</script>
|
|
47
72
|
|
|
48
73
|
<style lang="scss">
|
|
@@ -85,6 +110,12 @@ const handleStepClick = (step: CpStepperStep) => emit('select', step.id)
|
|
|
85
110
|
color: var(--cp-text-accent-primary);
|
|
86
111
|
}
|
|
87
112
|
|
|
113
|
+
&--isCompleted {
|
|
114
|
+
border-color: transparent;
|
|
115
|
+
background-color: var(--cp-background-primary);
|
|
116
|
+
color: var(--cp-text-primary);
|
|
117
|
+
}
|
|
118
|
+
|
|
88
119
|
&:not(:disabled) {
|
|
89
120
|
@extend %u-focus-outline;
|
|
90
121
|
|
|
@@ -115,6 +146,11 @@ const handleStepClick = (step: CpStepperStep) => emit('select', step.id)
|
|
|
115
146
|
color: var(--cp-text-white);
|
|
116
147
|
}
|
|
117
148
|
|
|
149
|
+
&__step--isCompleted &__stepNumber {
|
|
150
|
+
background-color: var(--cp-background-success-primary);
|
|
151
|
+
color: var(--cp-foreground-success-primary);
|
|
152
|
+
}
|
|
153
|
+
|
|
118
154
|
&__stepTitle {
|
|
119
155
|
@extend %u-text-ellipsis;
|
|
120
156
|
|
package/src/components/index.ts
CHANGED
|
@@ -36,6 +36,7 @@ import CpDate from './CpDate.vue'
|
|
|
36
36
|
import CpDatepicker from './CpDatepicker.vue'
|
|
37
37
|
import CpDialog from './CpDialog.vue'
|
|
38
38
|
import CpDynamicDialog from './CpDynamicDialog.vue'
|
|
39
|
+
import CpFlightTabs from './CpFlightTabs.vue'
|
|
39
40
|
import CpHeading from './CpHeading.vue'
|
|
40
41
|
import CpIcon from './CpIcon.vue'
|
|
41
42
|
import CpInput from './CpInput.vue'
|
|
@@ -125,6 +126,7 @@ const Components = {
|
|
|
125
126
|
CpAncillaryFlightSelection,
|
|
126
127
|
CpAncillaryItem,
|
|
127
128
|
CpBasket,
|
|
129
|
+
CpFlightTabs,
|
|
128
130
|
CpLoader,
|
|
129
131
|
CpInput,
|
|
130
132
|
CpText,
|
|
@@ -201,6 +203,7 @@ const Pimp: Plugin = {
|
|
|
201
203
|
|
|
202
204
|
export type { CpAncillaryFlightSelectionFlight } from '@/constants/CpAncillaryFlightSelectionTypes'
|
|
203
205
|
export type { CpBasketCategory, CpBasketGroup, CpBasketRow } from '@/constants/CpBasketTypes'
|
|
206
|
+
export type { CpFlightTabsFlight } from '@/constants/CpFlightTabsTypes'
|
|
204
207
|
export type { CpStepperStep } from '@/constants/CpStepperTypes'
|
|
205
208
|
export type {
|
|
206
209
|
CpSeatMapConfig,
|
|
@@ -259,6 +262,7 @@ export {
|
|
|
259
262
|
CpDatepicker,
|
|
260
263
|
CpDialog,
|
|
261
264
|
CpDynamicDialog,
|
|
265
|
+
CpFlightTabs,
|
|
262
266
|
CpHeading,
|
|
263
267
|
CpIcon,
|
|
264
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,142 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
import type { CpFlightTabsFlight } from '@/constants/CpFlightTabsTypes'
|
|
5
|
+
|
|
6
|
+
import CpFlightTabs from '@/components/CpFlightTabs.vue'
|
|
7
|
+
|
|
8
|
+
import { docLabelStyle } from '@/stories/documentationStyles'
|
|
9
|
+
|
|
10
|
+
const sampleFlights: CpFlightTabsFlight[] = [
|
|
11
|
+
{
|
|
12
|
+
id: 'outbound',
|
|
13
|
+
label: 'Outbound',
|
|
14
|
+
originCity: 'Paris',
|
|
15
|
+
originIata: 'CDG',
|
|
16
|
+
destinationCity: 'New York',
|
|
17
|
+
destinationIata: 'JFK',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'inbound',
|
|
21
|
+
label: 'Return',
|
|
22
|
+
originCity: 'New York',
|
|
23
|
+
originIata: 'JFK',
|
|
24
|
+
destinationCity: 'Paris',
|
|
25
|
+
destinationIata: 'CDG',
|
|
26
|
+
},
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
const noLabelFlights: CpFlightTabsFlight[] = sampleFlights.map(({ label: _label, ...flight }) => flight)
|
|
30
|
+
|
|
31
|
+
const threeFlights: CpFlightTabsFlight[] = [
|
|
32
|
+
...sampleFlights,
|
|
33
|
+
{
|
|
34
|
+
id: 'connecting',
|
|
35
|
+
label: 'Connecting',
|
|
36
|
+
originCity: 'Paris',
|
|
37
|
+
originIata: 'CDG',
|
|
38
|
+
destinationCity: 'Lyon',
|
|
39
|
+
destinationIata: 'LYS',
|
|
40
|
+
},
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
const meta = {
|
|
44
|
+
title: 'Business/CpFlightTabs',
|
|
45
|
+
component: CpFlightTabs,
|
|
46
|
+
argTypes: {
|
|
47
|
+
flights: {
|
|
48
|
+
control: 'object',
|
|
49
|
+
description: 'Flights shown as tabs',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
} satisfies Meta<typeof CpFlightTabs>
|
|
53
|
+
|
|
54
|
+
export default meta
|
|
55
|
+
type Story = StoryObj<typeof meta>
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Two flights, first one selected. The model is wired with a ref to
|
|
59
|
+
* demonstrate the round trip.
|
|
60
|
+
*/
|
|
61
|
+
export const Default: Story = {
|
|
62
|
+
args: {
|
|
63
|
+
flights: sampleFlights,
|
|
64
|
+
},
|
|
65
|
+
render: (args) => ({
|
|
66
|
+
components: { CpFlightTabs },
|
|
67
|
+
setup() {
|
|
68
|
+
const activeFlightId = ref(sampleFlights[0].id)
|
|
69
|
+
return { args, activeFlightId }
|
|
70
|
+
},
|
|
71
|
+
template: `
|
|
72
|
+
<div style="padding: 20px; width: 40rem;">
|
|
73
|
+
<CpFlightTabs v-bind="args" v-model:active-flight-id="activeFlightId" />
|
|
74
|
+
</div>
|
|
75
|
+
`,
|
|
76
|
+
}),
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Flights without a `label`: only the route line is rendered.
|
|
81
|
+
*/
|
|
82
|
+
export const NoLabels: Story = {
|
|
83
|
+
args: {
|
|
84
|
+
flights: noLabelFlights,
|
|
85
|
+
},
|
|
86
|
+
render: (args) => ({
|
|
87
|
+
components: { CpFlightTabs },
|
|
88
|
+
setup() {
|
|
89
|
+
const activeFlightId = ref(noLabelFlights[0].id)
|
|
90
|
+
return { args, activeFlightId }
|
|
91
|
+
},
|
|
92
|
+
template: `
|
|
93
|
+
<div style="padding: 20px; width: 40rem;">
|
|
94
|
+
<CpFlightTabs v-bind="args" v-model:active-flight-id="activeFlightId" />
|
|
95
|
+
</div>
|
|
96
|
+
`,
|
|
97
|
+
}),
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Three flights sharing the tab strip.
|
|
102
|
+
*/
|
|
103
|
+
export const ThreeFlights: Story = {
|
|
104
|
+
args: {
|
|
105
|
+
flights: threeFlights,
|
|
106
|
+
},
|
|
107
|
+
render: (args) => ({
|
|
108
|
+
components: { CpFlightTabs },
|
|
109
|
+
setup() {
|
|
110
|
+
const activeFlightId = ref(threeFlights[0].id)
|
|
111
|
+
return { args, activeFlightId }
|
|
112
|
+
},
|
|
113
|
+
template: `
|
|
114
|
+
<div style="padding: 20px; width: 40rem;">
|
|
115
|
+
<CpFlightTabs v-bind="args" v-model:active-flight-id="activeFlightId" />
|
|
116
|
+
</div>
|
|
117
|
+
`,
|
|
118
|
+
}),
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* At a 358px container width the tabs swap city names for IATA codes.
|
|
123
|
+
*/
|
|
124
|
+
export const Mobile: Story = {
|
|
125
|
+
args: {
|
|
126
|
+
flights: sampleFlights,
|
|
127
|
+
},
|
|
128
|
+
parameters: { controls: { disable: true } },
|
|
129
|
+
render: () => ({
|
|
130
|
+
components: { CpFlightTabs },
|
|
131
|
+
setup() {
|
|
132
|
+
const activeFlightId = ref(sampleFlights[0].id)
|
|
133
|
+
return { sampleFlights, activeFlightId, docLabelStyle }
|
|
134
|
+
},
|
|
135
|
+
template: `
|
|
136
|
+
<div style="display: flex; flex-direction: column; gap: 10px; width: 358px;">
|
|
137
|
+
<span :style="docLabelStyle">358px</span>
|
|
138
|
+
<CpFlightTabs :flights="sampleFlights" v-model:active-flight-id="activeFlightId" />
|
|
139
|
+
</div>
|
|
140
|
+
`,
|
|
141
|
+
}),
|
|
142
|
+
}
|
|
@@ -37,6 +37,10 @@ export default meta
|
|
|
37
37
|
type Story = StoryObj<typeof meta>
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
+
* Three visual states coexist: steps before the active one render as
|
|
41
|
+
* completed (white pill, green check badge, dark label), the active one keeps
|
|
42
|
+
* its accent pill, and the following ones stay upcoming (bordered, grey).
|
|
43
|
+
*
|
|
40
44
|
* Clicking an enabled step updates the active step locally, the way a
|
|
41
45
|
* consumer would react to `select` by navigating.
|
|
42
46
|
*/
|
|
@@ -65,10 +69,55 @@ export const Default: Story = {
|
|
|
65
69
|
}),
|
|
66
70
|
}
|
|
67
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Three steps already behind the active one: each renders completed, with the
|
|
74
|
+
* check badge replacing its number. Completed steps stay clickable.
|
|
75
|
+
*/
|
|
76
|
+
export const Completed: Story = {
|
|
77
|
+
args: {
|
|
78
|
+
steps,
|
|
79
|
+
activeStepId: 'extras',
|
|
80
|
+
reachedStepIndex: 3,
|
|
81
|
+
},
|
|
82
|
+
render: (args) => ({
|
|
83
|
+
components: { CpStepper },
|
|
84
|
+
setup() {
|
|
85
|
+
return { args }
|
|
86
|
+
},
|
|
87
|
+
template: `
|
|
88
|
+
<div style="width: 70rem;">
|
|
89
|
+
<CpStepper v-bind="args" />
|
|
90
|
+
</div>
|
|
91
|
+
`,
|
|
92
|
+
}),
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The funnel's last step is active, so every preceding step is completed.
|
|
97
|
+
*/
|
|
98
|
+
export const LastStep: Story = {
|
|
99
|
+
args: {
|
|
100
|
+
steps,
|
|
101
|
+
activeStepId: 'confirmation',
|
|
102
|
+
reachedStepIndex: 4,
|
|
103
|
+
},
|
|
104
|
+
render: (args) => ({
|
|
105
|
+
components: { CpStepper },
|
|
106
|
+
setup() {
|
|
107
|
+
return { args }
|
|
108
|
+
},
|
|
109
|
+
template: `
|
|
110
|
+
<div style="width: 70rem;">
|
|
111
|
+
<CpStepper v-bind="args" />
|
|
112
|
+
</div>
|
|
113
|
+
`,
|
|
114
|
+
}),
|
|
115
|
+
}
|
|
116
|
+
|
|
68
117
|
/**
|
|
69
118
|
* Below the container width threshold (60.5rem, matching online-checkin's
|
|
70
119
|
* former `$breakpoint-large-tablet`), step titles collapse and only the
|
|
71
|
-
* numbered pills remain.
|
|
120
|
+
* numbered pills remain, the completed ones showing their check badge.
|
|
72
121
|
*/
|
|
73
122
|
export const Narrow: Story = {
|
|
74
123
|
args: { ...Default.args },
|