@citizenplane/pimp 18.19.10 → 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 +5 -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 +539 -464
- package/dist/pimp.umd.js +19 -19
- 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 +8 -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
|
@@ -60,6 +60,8 @@ import CpSegmentedControl from './CpSegmentedControl.vue'
|
|
|
60
60
|
import CpSelect from './CpSelect.vue'
|
|
61
61
|
import CpSelectableButton from './CpSelectableButton.vue'
|
|
62
62
|
import CpSelectMenu from './CpSelectMenu.vue'
|
|
63
|
+
import CpStepper from './CpStepper.vue'
|
|
64
|
+
import CpStepperMobile from './CpStepperMobile.vue'
|
|
63
65
|
import CpSwitch from './CpSwitch.vue'
|
|
64
66
|
import CpTable from './CpTable.vue'
|
|
65
67
|
import CpTableColumnEditor from './CpTableColumnEditor.vue'
|
|
@@ -131,6 +133,8 @@ const Components = {
|
|
|
131
133
|
CpRadio,
|
|
132
134
|
CpRadioGroup,
|
|
133
135
|
CpSelectableButton,
|
|
136
|
+
CpStepper,
|
|
137
|
+
CpStepperMobile,
|
|
134
138
|
CpSwitch,
|
|
135
139
|
CpSegmentedControl,
|
|
136
140
|
CpTable,
|
|
@@ -191,6 +195,7 @@ const Pimp: Plugin = {
|
|
|
191
195
|
},
|
|
192
196
|
}
|
|
193
197
|
|
|
198
|
+
export type { CpStepperStep } from '@/constants/CpStepperTypes'
|
|
194
199
|
export type {
|
|
195
200
|
CpSeatMapConfig,
|
|
196
201
|
CpSeatMapSeatSelection,
|
|
@@ -214,6 +219,7 @@ export type {
|
|
|
214
219
|
|
|
215
220
|
export { CpAncillaryCategoryTypes, isSwitchCategory } from '@/constants/CpAncillaryCategoryTypes'
|
|
216
221
|
export { CpAncillaryItemTypes } from '@/constants/CpAncillaryItemTypes'
|
|
222
|
+
export { CpStepperTransitionDirections } from '@/constants/CpStepperTypes'
|
|
217
223
|
export {
|
|
218
224
|
CpSeatMapTransitionDirections,
|
|
219
225
|
DEFAULT_CP_SEAT_MAP_CONFIG,
|
|
@@ -272,6 +278,8 @@ export {
|
|
|
272
278
|
CpSelect,
|
|
273
279
|
CpSelectableButton,
|
|
274
280
|
CpSelectMenu,
|
|
281
|
+
CpStepper,
|
|
282
|
+
CpStepperMobile,
|
|
275
283
|
CpSwitch,
|
|
276
284
|
CpTable,
|
|
277
285
|
CpTableColumnEditor,
|
|
@@ -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
|
+
}
|