@citizenplane/pimp 18.23.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@citizenplane/pimp",
3
- "version": "18.23.0",
3
+ "version": "18.23.1",
4
4
  "scripts": {
5
5
  "dev": "storybook dev -p 8081",
6
6
  "build-storybook": "storybook build --output-dir ./docs",
@@ -1,24 +1,30 @@
1
1
  <template>
2
2
  <div class="cpStepper">
3
3
  <div class="cpStepper__wrapper">
4
- <div v-for="(step, stepIndex) in steps" :key="step.id" class="cpStepper__stepWrapper">
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="stepActiveClass(step)"
8
- :disabled="isStepDisabled(stepIndex)"
8
+ :class="stepItem.className"
9
+ :disabled="stepItem.isDisabled"
9
10
  type="button"
10
- @click="handleStepClick(step)"
11
+ @click="handleStepClick(stepItem.id)"
11
12
  >
12
- <span class="cpStepper__stepNumber">{{ stepIndex + 1 }}</span>
13
- <span class="cpStepper__stepTitle">{{ step.label }}</span>
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="isNotLastStep(stepIndex)" class="cpStepper__chevron" size="16" type="chevron-right" />
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 isNotLastStep = (stepIndex: number) => stepIndex < props.steps.length - 1
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 isStepDisabled = (stepIndex: number) => stepIndex > props.reachedStepIndex
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 stepActiveClass = (step: CpStepperStep) => (step.id === props.activeStepId ? 'cpStepper__step--isActive' : '')
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 = (step: CpStepperStep) => emit('select', step.id)
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
 
@@ -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 },