@citizenplane/pimp 18.19.9 → 18.20.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/CpStepper.vue.d.ts +17 -0
- package/dist/components/CpStepper.vue.d.ts.map +1 -0
- package/dist/components/CpStepperMobile.vue.d.ts +17 -0
- package/dist/components/CpStepperMobile.vue.d.ts.map +1 -0
- package/dist/components/index.d.ts +6 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/constants/CpStepperTypes.d.ts +9 -0
- package/dist/constants/CpStepperTypes.d.ts.map +1 -0
- package/dist/pimp.es.js +2644 -2569
- package/dist/pimp.umd.js +47 -47
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/CpStepper.vue +136 -0
- package/src/components/CpStepperMobile.vue +164 -0
- package/src/components/index.ts +10 -0
- package/src/constants/CpStepperTypes.ts +9 -0
- package/src/stories/CpStepper.stories.ts +90 -0
- package/src/stories/CpStepperMobile.stories.ts +92 -0
package/package.json
CHANGED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="cpStepper">
|
|
3
|
+
<div class="cpStepper__wrapper">
|
|
4
|
+
<div v-for="(step, stepIndex) in steps" :key="step.id" class="cpStepper__stepWrapper">
|
|
5
|
+
<button
|
|
6
|
+
class="cpStepper__step"
|
|
7
|
+
:class="stepActiveClass(step)"
|
|
8
|
+
:disabled="isStepDisabled(stepIndex)"
|
|
9
|
+
type="button"
|
|
10
|
+
@click="handleStepClick(step)"
|
|
11
|
+
>
|
|
12
|
+
<span class="cpStepper__stepNumber">{{ stepIndex + 1 }}</span>
|
|
13
|
+
<span class="cpStepper__stepTitle">{{ step.label }}</span>
|
|
14
|
+
</button>
|
|
15
|
+
<cp-icon v-if="isNotLastStep(stepIndex)" class="cpStepper__chevron" size="16" type="chevron-right" />
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
import type { CpStepperStep } from '@/constants/CpStepperTypes'
|
|
23
|
+
|
|
24
|
+
import CpIcon from '@/components/CpIcon.vue'
|
|
25
|
+
|
|
26
|
+
interface Props {
|
|
27
|
+
activeStepId?: string
|
|
28
|
+
reachedStepIndex?: number
|
|
29
|
+
steps: CpStepperStep[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
33
|
+
activeStepId: undefined,
|
|
34
|
+
reachedStepIndex: 0,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const emit = defineEmits<{ select: [id: string] }>()
|
|
38
|
+
|
|
39
|
+
const isNotLastStep = (stepIndex: number) => stepIndex < props.steps.length - 1
|
|
40
|
+
|
|
41
|
+
const isStepDisabled = (stepIndex: number) => stepIndex > props.reachedStepIndex
|
|
42
|
+
|
|
43
|
+
const stepActiveClass = (step: CpStepperStep) => (step.id === props.activeStepId ? 'cpStepper__step--isActive' : '')
|
|
44
|
+
|
|
45
|
+
const handleStepClick = (step: CpStepperStep) => emit('select', step.id)
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<style lang="scss">
|
|
49
|
+
.cpStepper {
|
|
50
|
+
min-width: 0;
|
|
51
|
+
flex: 1;
|
|
52
|
+
container-type: inline-size;
|
|
53
|
+
|
|
54
|
+
&__wrapper,
|
|
55
|
+
&__stepWrapper {
|
|
56
|
+
display: flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
justify-content: center;
|
|
59
|
+
gap: var(--cp-spacing-md);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
&__stepWrapper {
|
|
63
|
+
@extend %u-text-ellipsis;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
&__step {
|
|
67
|
+
@extend %u-text-ellipsis;
|
|
68
|
+
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
padding: fn.px-to-rem(6);
|
|
72
|
+
border: fn.px-to-rem(1) solid var(--cp-border-soft);
|
|
73
|
+
border-radius: fn.px-to-rem(50);
|
|
74
|
+
color: var(--cp-text-secondary);
|
|
75
|
+
gap: var(--cp-spacing-sm);
|
|
76
|
+
outline-offset: fn.px-to-rem(-2);
|
|
77
|
+
|
|
78
|
+
@container (width < 60.5rem) {
|
|
79
|
+
padding: var(--cp-spacing-sm);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
&--isActive {
|
|
83
|
+
border-color: transparent;
|
|
84
|
+
background-color: var(--cp-background-accent-secondary);
|
|
85
|
+
color: var(--cp-text-accent-primary);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
&:not(:disabled) {
|
|
89
|
+
@extend %u-focus-outline;
|
|
90
|
+
|
|
91
|
+
border-radius: fn.px-to-rem(50);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
&:disabled {
|
|
95
|
+
cursor: not-allowed;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
&__stepNumber {
|
|
100
|
+
@include mx.square-sizing(20);
|
|
101
|
+
|
|
102
|
+
display: flex;
|
|
103
|
+
flex-shrink: 0;
|
|
104
|
+
align-items: center;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
border-radius: 50%;
|
|
107
|
+
background-color: var(--cp-background-secondary);
|
|
108
|
+
font-size: var(--cp-text-size-xs);
|
|
109
|
+
font-weight: 500;
|
|
110
|
+
line-height: var(--cp-text-xs-line-height);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
&__step--isActive &__stepNumber {
|
|
114
|
+
background-color: var(--cp-background-accent-solid);
|
|
115
|
+
color: var(--cp-text-white);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
&__stepTitle {
|
|
119
|
+
@extend %u-text-ellipsis;
|
|
120
|
+
|
|
121
|
+
font-size: var(--cp-text-size-sm);
|
|
122
|
+
font-weight: 500;
|
|
123
|
+
line-height: var(--cp-text-sm-line-height);
|
|
124
|
+
padding-inline: var(--cp-spacing-sm);
|
|
125
|
+
|
|
126
|
+
@container (width < 60.5rem) {
|
|
127
|
+
display: none;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
&__chevron {
|
|
132
|
+
flex-shrink: 0;
|
|
133
|
+
color: var(--cp-foreground-quaternary);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
</style>
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="cpStepperMobile">
|
|
3
|
+
<button v-if="hasPreviousButton" class="cpStepperMobile__back" type="button" @click="emit('previous')">
|
|
4
|
+
<cp-icon size="24" type="chevron-left" />
|
|
5
|
+
</button>
|
|
6
|
+
<div class="cpStepperMobile__wrapper">
|
|
7
|
+
<cp-heading class="cpStepperMobile__title" heading-level="h3" :size="600">
|
|
8
|
+
<transition mode="out-in" :name="titleTransitionName">
|
|
9
|
+
<span :key="activeStepLabel" class="cpStepperMobile__titleInner">{{ activeStepLabel }}</span>
|
|
10
|
+
</transition>
|
|
11
|
+
</cp-heading>
|
|
12
|
+
<div class="cpStepperMobile__steps">
|
|
13
|
+
<div v-for="step in steps" :key="step.id" class="cpStepperMobile__step" :class="stepActiveClass(step)" />
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script setup lang="ts">
|
|
20
|
+
import { computed, ref, watch } from 'vue'
|
|
21
|
+
|
|
22
|
+
import CpHeading from '@/components/CpHeading.vue'
|
|
23
|
+
import CpIcon from '@/components/CpIcon.vue'
|
|
24
|
+
|
|
25
|
+
import { type CpStepperStep, CpStepperTransitionDirections } from '@/constants/CpStepperTypes'
|
|
26
|
+
|
|
27
|
+
interface Props {
|
|
28
|
+
activeStepId?: string
|
|
29
|
+
hasPreviousButton?: boolean
|
|
30
|
+
steps: CpStepperStep[]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
34
|
+
activeStepId: undefined,
|
|
35
|
+
hasPreviousButton: false,
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const emit = defineEmits<{ previous: [] }>()
|
|
39
|
+
|
|
40
|
+
const activeStep = computed(() => props.steps.find((step) => step.id === props.activeStepId))
|
|
41
|
+
const activeStepLabel = computed(() => activeStep.value?.label)
|
|
42
|
+
|
|
43
|
+
const titleTransitionDirection = ref<CpStepperTransitionDirections>(CpStepperTransitionDirections.FORWARD)
|
|
44
|
+
const titleTransitionName = computed(() => `slide-${titleTransitionDirection.value}`)
|
|
45
|
+
|
|
46
|
+
const stepActiveClass = (step: CpStepperStep) =>
|
|
47
|
+
step.id === props.activeStepId ? 'cpStepperMobile__step--isActive' : ''
|
|
48
|
+
|
|
49
|
+
watch(
|
|
50
|
+
() => props.activeStepId,
|
|
51
|
+
(newStepId, oldStepId) => {
|
|
52
|
+
if (!newStepId || !oldStepId) return
|
|
53
|
+
|
|
54
|
+
const newIndex = props.steps.findIndex((step) => step.id === newStepId)
|
|
55
|
+
const oldIndex = props.steps.findIndex((step) => step.id === oldStepId)
|
|
56
|
+
|
|
57
|
+
titleTransitionDirection.value =
|
|
58
|
+
newIndex > oldIndex ? CpStepperTransitionDirections.BACKWARD : CpStepperTransitionDirections.FORWARD
|
|
59
|
+
},
|
|
60
|
+
)
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<style lang="scss">
|
|
64
|
+
.cpStepperMobile {
|
|
65
|
+
display: flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
justify-content: center;
|
|
68
|
+
|
|
69
|
+
&__back {
|
|
70
|
+
@extend %u-focus-outline;
|
|
71
|
+
|
|
72
|
+
position: relative;
|
|
73
|
+
z-index: 2;
|
|
74
|
+
display: flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
justify-content: center;
|
|
77
|
+
color: var(--cp-foreground-accent-secondary);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
&__wrapper {
|
|
81
|
+
display: flex;
|
|
82
|
+
flex: 1;
|
|
83
|
+
flex-direction: column;
|
|
84
|
+
align-items: center;
|
|
85
|
+
justify-content: center;
|
|
86
|
+
padding: var(--cp-spacing-sm) var(--cp-spacing-xl);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
&__title {
|
|
90
|
+
overflow: hidden;
|
|
91
|
+
font-size: var(--cp-text-size-lg);
|
|
92
|
+
font-weight: 600;
|
|
93
|
+
line-height: var(--cp-text-lg-line-height);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
&__titleInner {
|
|
97
|
+
display: flex;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
&__steps {
|
|
101
|
+
display: flex;
|
|
102
|
+
width: 100%;
|
|
103
|
+
max-width: fn.px-to-rem(192);
|
|
104
|
+
min-height: fn.px-to-rem(20);
|
|
105
|
+
align-items: center;
|
|
106
|
+
justify-content: center;
|
|
107
|
+
gap: var(--cp-spacing-md);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
&__step {
|
|
111
|
+
height: fn.px-to-rem(4);
|
|
112
|
+
flex: 0 0 fn.px-to-rem(24);
|
|
113
|
+
border-radius: fn.px-to-rem(4);
|
|
114
|
+
background-color: var(--cp-utility-accent-100);
|
|
115
|
+
transition:
|
|
116
|
+
flex-basis 500ms var(--cp-easing-elastic),
|
|
117
|
+
background-color 500ms ease;
|
|
118
|
+
|
|
119
|
+
&--isActive {
|
|
120
|
+
flex-basis: fn.px-to-rem(48);
|
|
121
|
+
background-color: var(--cp-background-accent-solid);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.slide-backward,
|
|
127
|
+
.slide-forward {
|
|
128
|
+
&-enter-active,
|
|
129
|
+
&-leave-active {
|
|
130
|
+
transition:
|
|
131
|
+
transform 200ms ease,
|
|
132
|
+
opacity 200ms ease;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
&-enter-from,
|
|
136
|
+
&-leave-to {
|
|
137
|
+
opacity: 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
&-enter-to {
|
|
141
|
+
transform: translate3d(0, 0, 0);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.slide-forward {
|
|
146
|
+
&-enter-from {
|
|
147
|
+
transform: translate3d(3vw, 0, 0);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
&-leave-to {
|
|
151
|
+
transform: translate3d(-3vw, 0, 0);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.slide-backward {
|
|
156
|
+
&-enter-from {
|
|
157
|
+
transform: translate3d(-3vw, 0, 0);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
&-leave-to {
|
|
161
|
+
transform: translate3d(3vw, 0, 0);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
</style>
|
package/src/components/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { App, Plugin } from 'vue'
|
|
|
12
12
|
import { BindOnceDirective } from 'vue-bind-once'
|
|
13
13
|
import VueTelInput from 'vue-tel-input'
|
|
14
14
|
|
|
15
|
+
import { useIsBreakpoint } from '../composables/useIsBreakpoint'
|
|
15
16
|
import { useReducedMotion } from '../composables/useReducedMotion'
|
|
16
17
|
import ClickOutside from '../directives/ClickOutside'
|
|
17
18
|
import CpCoreDatepicker from '../libs/CoreDatepicker.vue'
|
|
@@ -59,6 +60,8 @@ import CpSegmentedControl from './CpSegmentedControl.vue'
|
|
|
59
60
|
import CpSelect from './CpSelect.vue'
|
|
60
61
|
import CpSelectableButton from './CpSelectableButton.vue'
|
|
61
62
|
import CpSelectMenu from './CpSelectMenu.vue'
|
|
63
|
+
import CpStepper from './CpStepper.vue'
|
|
64
|
+
import CpStepperMobile from './CpStepperMobile.vue'
|
|
62
65
|
import CpSwitch from './CpSwitch.vue'
|
|
63
66
|
import CpTable from './CpTable.vue'
|
|
64
67
|
import CpTableColumnEditor from './CpTableColumnEditor.vue'
|
|
@@ -130,6 +133,8 @@ const Components = {
|
|
|
130
133
|
CpRadio,
|
|
131
134
|
CpRadioGroup,
|
|
132
135
|
CpSelectableButton,
|
|
136
|
+
CpStepper,
|
|
137
|
+
CpStepperMobile,
|
|
133
138
|
CpSwitch,
|
|
134
139
|
CpSegmentedControl,
|
|
135
140
|
CpTable,
|
|
@@ -190,6 +195,7 @@ const Pimp: Plugin = {
|
|
|
190
195
|
},
|
|
191
196
|
}
|
|
192
197
|
|
|
198
|
+
export type { CpStepperStep } from '@/constants/CpStepperTypes'
|
|
193
199
|
export type {
|
|
194
200
|
CpSeatMapConfig,
|
|
195
201
|
CpSeatMapSeatSelection,
|
|
@@ -213,6 +219,7 @@ export type {
|
|
|
213
219
|
|
|
214
220
|
export { CpAncillaryCategoryTypes, isSwitchCategory } from '@/constants/CpAncillaryCategoryTypes'
|
|
215
221
|
export { CpAncillaryItemTypes } from '@/constants/CpAncillaryItemTypes'
|
|
222
|
+
export { CpStepperTransitionDirections } from '@/constants/CpStepperTypes'
|
|
216
223
|
export {
|
|
217
224
|
CpSeatMapTransitionDirections,
|
|
218
225
|
DEFAULT_CP_SEAT_MAP_CONFIG,
|
|
@@ -271,6 +278,8 @@ export {
|
|
|
271
278
|
CpSelect,
|
|
272
279
|
CpSelectableButton,
|
|
273
280
|
CpSelectMenu,
|
|
281
|
+
CpStepper,
|
|
282
|
+
CpStepperMobile,
|
|
274
283
|
CpSwitch,
|
|
275
284
|
CpTable,
|
|
276
285
|
CpTableColumnEditor,
|
|
@@ -303,6 +312,7 @@ export {
|
|
|
303
312
|
IconSupplier,
|
|
304
313
|
IconThirdParty,
|
|
305
314
|
IconTooltip,
|
|
315
|
+
useIsBreakpoint,
|
|
306
316
|
useReducedMotion,
|
|
307
317
|
}
|
|
308
318
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
import type { CpStepperStep } from '@/constants/CpStepperTypes'
|
|
5
|
+
|
|
6
|
+
import CpStepper from '@/components/CpStepper.vue'
|
|
7
|
+
|
|
8
|
+
import { docLabelStyle } from '@/stories/documentationStyles'
|
|
9
|
+
|
|
10
|
+
const galleryRowStyle = 'display: flex; flex-direction: column; gap: 10px; width: 100%;'
|
|
11
|
+
|
|
12
|
+
const steps: CpStepperStep[] = [
|
|
13
|
+
{ id: 'passengers', label: 'Passengers' },
|
|
14
|
+
{ id: 'safetyChecks', label: 'Safety checks' },
|
|
15
|
+
{ id: 'seats', label: 'Seats' },
|
|
16
|
+
{ id: 'extras', label: 'Extras' },
|
|
17
|
+
{ id: 'confirmation', label: 'Confirmation' },
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
const meta = {
|
|
21
|
+
title: 'Business/CpStepper',
|
|
22
|
+
component: CpStepper,
|
|
23
|
+
argTypes: {
|
|
24
|
+
activeStepId: {
|
|
25
|
+
control: 'select',
|
|
26
|
+
options: steps.map((step) => step.id),
|
|
27
|
+
description: 'id of the step to render as active',
|
|
28
|
+
},
|
|
29
|
+
reachedStepIndex: {
|
|
30
|
+
control: 'number',
|
|
31
|
+
description: 'Highest reached step index; steps beyond it render disabled',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
} satisfies Meta<typeof CpStepper>
|
|
35
|
+
|
|
36
|
+
export default meta
|
|
37
|
+
type Story = StoryObj<typeof meta>
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Clicking an enabled step updates the active step locally, the way a
|
|
41
|
+
* consumer would react to `select` by navigating.
|
|
42
|
+
*/
|
|
43
|
+
export const Default: Story = {
|
|
44
|
+
args: {
|
|
45
|
+
steps,
|
|
46
|
+
activeStepId: 'seats',
|
|
47
|
+
reachedStepIndex: 2,
|
|
48
|
+
},
|
|
49
|
+
render: (args) => ({
|
|
50
|
+
components: { CpStepper },
|
|
51
|
+
setup() {
|
|
52
|
+
const activeStepId = ref(args.activeStepId)
|
|
53
|
+
|
|
54
|
+
const handleSelect = (id: string) => {
|
|
55
|
+
activeStepId.value = id
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { args, activeStepId, handleSelect }
|
|
59
|
+
},
|
|
60
|
+
template: `
|
|
61
|
+
<div style="width: 70rem;">
|
|
62
|
+
<CpStepper v-bind="args" :active-step-id="activeStepId" @select="handleSelect" />
|
|
63
|
+
</div>
|
|
64
|
+
`,
|
|
65
|
+
}),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Below the container width threshold (60.5rem, matching online-checkin's
|
|
70
|
+
* former `$breakpoint-large-tablet`), step titles collapse and only the
|
|
71
|
+
* numbered pills remain.
|
|
72
|
+
*/
|
|
73
|
+
export const Narrow: Story = {
|
|
74
|
+
args: { ...Default.args },
|
|
75
|
+
parameters: { controls: { disable: true } },
|
|
76
|
+
render: () => ({
|
|
77
|
+
components: { CpStepper },
|
|
78
|
+
setup() {
|
|
79
|
+
return { steps, galleryRowStyle, docLabelStyle }
|
|
80
|
+
},
|
|
81
|
+
template: `
|
|
82
|
+
<div :style="galleryRowStyle">
|
|
83
|
+
<span :style="docLabelStyle">Container width: 28rem</span>
|
|
84
|
+
<div style="width: 28rem;">
|
|
85
|
+
<CpStepper :steps="steps" active-step-id="seats" :reached-step-index="2" />
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
`,
|
|
89
|
+
}),
|
|
90
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
import type { CpStepperStep } from '@/constants/CpStepperTypes'
|
|
5
|
+
|
|
6
|
+
import CpButton from '@/components/CpButton.vue'
|
|
7
|
+
import CpStepperMobile from '@/components/CpStepperMobile.vue'
|
|
8
|
+
|
|
9
|
+
const steps: CpStepperStep[] = [
|
|
10
|
+
{ id: 'passengers', label: 'Passengers' },
|
|
11
|
+
{ id: 'safetyChecks', label: 'Safety checks' },
|
|
12
|
+
{ id: 'seats', label: 'Seats' },
|
|
13
|
+
{ id: 'extras', label: 'Extras' },
|
|
14
|
+
{ id: 'confirmation', label: 'Confirmation' },
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
const meta = {
|
|
18
|
+
title: 'Business/CpStepperMobile',
|
|
19
|
+
component: CpStepperMobile,
|
|
20
|
+
argTypes: {
|
|
21
|
+
activeStepId: {
|
|
22
|
+
control: 'select',
|
|
23
|
+
options: steps.map((step) => step.id),
|
|
24
|
+
description: 'id of the step to render as active',
|
|
25
|
+
},
|
|
26
|
+
hasPreviousButton: {
|
|
27
|
+
control: 'boolean',
|
|
28
|
+
description: 'Whether the back chevron button is rendered',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
} satisfies Meta<typeof CpStepperMobile>
|
|
32
|
+
|
|
33
|
+
export default meta
|
|
34
|
+
type Story = StoryObj<typeof meta>
|
|
35
|
+
|
|
36
|
+
export const Default: Story = {
|
|
37
|
+
args: {
|
|
38
|
+
steps,
|
|
39
|
+
activeStepId: 'seats',
|
|
40
|
+
hasPreviousButton: true,
|
|
41
|
+
},
|
|
42
|
+
render: (args) => ({
|
|
43
|
+
components: { CpStepperMobile },
|
|
44
|
+
setup() {
|
|
45
|
+
return { args }
|
|
46
|
+
},
|
|
47
|
+
template: `
|
|
48
|
+
<div style="width: 24rem;">
|
|
49
|
+
<CpStepperMobile v-bind="args" />
|
|
50
|
+
</div>
|
|
51
|
+
`,
|
|
52
|
+
}),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Previous/Next move the active step index so the title's
|
|
57
|
+
* slide-forward / slide-backward transition plays in each direction.
|
|
58
|
+
*/
|
|
59
|
+
export const Interactive: Story = {
|
|
60
|
+
args: { ...Default.args },
|
|
61
|
+
parameters: { controls: { disable: true } },
|
|
62
|
+
render: () => ({
|
|
63
|
+
components: { CpStepperMobile, CpButton },
|
|
64
|
+
setup() {
|
|
65
|
+
const activeIndex = ref(2)
|
|
66
|
+
const activeStepId = computed(() => steps[activeIndex.value].id)
|
|
67
|
+
const hasPreviousButton = computed(() => activeIndex.value > 0)
|
|
68
|
+
const isLastStep = computed(() => activeIndex.value === steps.length - 1)
|
|
69
|
+
|
|
70
|
+
const handlePrevious = () => {
|
|
71
|
+
activeIndex.value -= 1
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const handleNext = () => {
|
|
75
|
+
activeIndex.value += 1
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return { steps, activeStepId, hasPreviousButton, isLastStep, handlePrevious, handleNext }
|
|
79
|
+
},
|
|
80
|
+
template: `
|
|
81
|
+
<div style="display: flex; flex-direction: column; gap: 16px; width: 24rem;">
|
|
82
|
+
<CpStepperMobile
|
|
83
|
+
:steps="steps"
|
|
84
|
+
:active-step-id="activeStepId"
|
|
85
|
+
:has-previous-button="hasPreviousButton"
|
|
86
|
+
@previous="handlePrevious"
|
|
87
|
+
/>
|
|
88
|
+
<CpButton :disabled="isLastStep" @click="handleNext">Next</CpButton>
|
|
89
|
+
</div>
|
|
90
|
+
`,
|
|
91
|
+
}),
|
|
92
|
+
}
|