1
1
<script setup lang="ts">
2
- import { ref , computed , watch , provide } from ' vue' ;
2
+ import { ref , computed , provide } from ' vue' ;
3
3
import type { ComponentPublicInstance } from ' vue' ;
4
4
import type {
5
5
AnyControlNode ,
@@ -17,7 +17,7 @@ import ProgressBar from 'primevue/progressbar';
17
17
import Button from ' primevue/button' ;
18
18
19
19
const props = defineProps <{ nodes: readonly GeneralChildNode [] }>();
20
- const emit = defineEmits ([' endOfForm ' ]);
20
+ const emit = defineEmits ([' sendFormFromStepper ' ]);
21
21
22
22
const isGroupNode = (node : GeneralChildNode ): node is GroupNode => {
23
23
return node .nodeType === ' group' ;
@@ -59,44 +59,47 @@ const steps = computed(() =>
59
59
);
60
60
61
61
// Handle stepper state
62
- const currentStep = ref (0 );
63
- const isCurrentStepValidated = ref (true );
62
+ const firstStep = 0 ;
63
+ const finalStep = steps .value .length ;
64
+ const currentStep = ref (firstStep );
64
65
const submitPressed = ref (false );
65
66
provide (' submitPressed' , submitPressed );
66
67
67
- const validateStep = () => {
68
+ const allFieldsValid = () => {
68
69
// Manually trigger submitPressed to display error messages
69
70
submitPressed .value = true ;
70
71
71
72
const currentNode = steps .value [currentStep .value ];
73
+
74
+ // Check group error array
72
75
if (isGroupNode (currentNode ) && currentNode .validationState .violations .length > 0 ) {
73
- isCurrentStepValidated .value = false ;
76
+ return false ;
77
+
78
+ // Check question single error
74
79
} else if (currentNode .validationState .violation ) {
75
- isCurrentStepValidated .value = false ;
76
- } else {
77
- isCurrentStepValidated .value = true ;
80
+ return false ;
78
81
}
82
+
83
+ return true ;
79
84
}
80
85
const nextStep = () => {
81
- validateStep ();
86
+ if (! allFieldsValid ()) {
87
+ // There was an error validating
88
+ return false ;
89
+ }
82
90
83
- if (isCurrentStepValidated .value && currentStep .value < steps .value .length - 1 ) {
91
+ // Do not increment further if at end of form
92
+ if (currentStep .value < steps .value .length - 1 ) {
84
93
// Reset validation triggered later in the form
85
94
submitPressed .value = false ;
86
- // Also reset validation state of current node
87
- isCurrentStepValidated .value = true ;
88
95
currentStep .value ++ ;
89
96
}
90
97
};
91
98
const prevStep = () => {
92
- if (currentStep .value > 0 ) {
99
+ if (currentStep .value > firstStep ) {
93
100
currentStep .value -- ;
94
101
}
95
102
};
96
- const isLastStep = computed (() => currentStep .value === steps .value .length - 1 );
97
- watch (isLastStep , (newValue ) => {
98
- emit (' endOfForm' , newValue );
99
- });
100
103
101
104
// // Calculate stepper progress
102
105
// const totalNodes = computed(() =>
@@ -137,19 +140,55 @@ watch(isLastStep, (newValue) => {
137
140
<ExpectModelNode v-else :node =" step" />
138
141
</template >
139
142
</div >
140
-
141
- <div class =" navigation-buttons" >
142
- <Button label =" Previous" @click =" prevStep" :disabled =" currentStep === 0" />
143
- <Button label =" Next" @click =" nextStep" :disabled =" isCurrentStepValidated && currentStep === steps.length - 1" />
144
- </div >
145
143
</div >
144
+
145
+ <div class =" navigation-button-group" >
146
+ <!-- If swapping to arrows: 🡨 🡪 -->
147
+ <Button v-if =" currentStep > firstStep" class =" navigation-button" label =" Back" @click =" prevStep" rounded outlined />
148
+ <Button v-if =" currentStep === finalStep" class =" navigation-button" label =" Send" @click =" allFieldsValid ? emit('sendFormFromStepper') : null" rounded />
149
+ <!-- Note the button ordering is important here as we use a last-child selector for styling -->
150
+ <Button v-if =" currentStep < finalStep" class =" navigation-button" label =" Next" @click =" nextStep" rounded outlined />
151
+ </div >
146
152
</template >
147
153
148
154
<style scoped lang="scss">
149
- .navigation-buttons {
155
+ .stepper-container {
150
156
display : flex ;
151
- justify-content : space-between ;
157
+ flex-direction : column ;
158
+ flex-grow : 1 ;
159
+ overflow-y : auto ;
160
+ padding-bottom : 3rem ;
161
+
162
+ :deep (.p-panel ) {
163
+ box-shadow : none ;
164
+ }
165
+ }
166
+
167
+ .navigation-button-group {
168
+ display : flex ;
169
+ position : fixed ;
170
+ bottom : 0 ;
171
+ left : 0 ;
152
172
width : 100% ;
153
- margin-top : 1rem ;
173
+ background : white ;
174
+ padding : 1rem ;
175
+ justify-content : space-between ;
176
+ box-shadow : 0 -2px 5px rgba (0 , 0 , 0 , 0.1 );
177
+ }
178
+
179
+ /* Ensure Next button is on the right when Back is hidden */
180
+ .navigation-button-group .navigation-button :last-child {
181
+ margin-left : auto ;
182
+ }
183
+
184
+ /* If only one button is visible, align it to the right */
185
+ .navigation-button-group :has (.navigation-button :first-child:last-child ) {
186
+ justify-content : flex-end ;
187
+ }
188
+
189
+ .navigation-button {
190
+ padding-left : 3rem ;
191
+ padding-right : 3rem ;
192
+ font-size : 1rem ;
154
193
}
155
194
</style >
0 commit comments