project-booster-vue 9.24.1 → 9.25.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/package.json +1 -1
- package/src/components/mozaic/buttons/MButton.vue +26 -0
- package/src/components/mozaic/quantityselector/MQuantitySelector.stories.mdx +48 -0
- package/src/components/mozaic/quantityselector/MQuantitySelector.vue +188 -0
- package/src/components/mozaic/text-input/MTextInput.vue +59 -4
- package/src/components/question/incremental-amount-input/IncrementalAmount.ts +24 -0
- package/src/components/question/incremental-amount-input/PbIncrementalAmountInput.stories.mdx +50 -0
- package/src/components/question/incremental-amount-input/PbIncrementalAmountInput.vue +332 -0
- package/src/components/question/incremental-amount-input/__snapshots__/storyshots-puppeteer-test-puppeteer-ts-image-storyshots-project-booster-scenario-questions-pb-incremental-amount-input-/360/237/246/240-101-sandbox-1-snap.png +0 -0
- package/src/components/question/incremental-amount-input/default-payload.json +12 -0
- package/src/components/scenario/PbScenario.vue +4 -0
- package/src/components/warning-message/PbWarningMessage.vue +1 -1
package/package.json
CHANGED
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
class="mc-button"
|
|
36
36
|
:class="[...themeClasses, ...sizeClasses, ...widthClasses, $attrs.class]"
|
|
37
37
|
:aria-label="label"
|
|
38
|
+
:disabled="disabled"
|
|
38
39
|
>
|
|
39
40
|
<!-- Add wrapper to mitigate iOS 10 bug: https://github.com/philipwalton/flexbugs#9-some-html-elements-cant-be-flex-containers -->
|
|
40
41
|
<m-flex class="mc-button__content" direction="row" align-items="center" justify-content="center">
|
|
@@ -70,6 +71,8 @@ export const M_BUTTON_VALIDATOR = {
|
|
|
70
71
|
null,
|
|
71
72
|
'solid',
|
|
72
73
|
'bordered',
|
|
74
|
+
'bordered-left',
|
|
75
|
+
'bordered-right',
|
|
73
76
|
'bordered-neutral',
|
|
74
77
|
'solid-neutral',
|
|
75
78
|
'solid-primary-02',
|
|
@@ -94,6 +97,13 @@ export default defineComponent({
|
|
|
94
97
|
},
|
|
95
98
|
|
|
96
99
|
props: {
|
|
100
|
+
/**
|
|
101
|
+
* Disabled button
|
|
102
|
+
*/
|
|
103
|
+
disabled: {
|
|
104
|
+
type: Boolean,
|
|
105
|
+
default: false,
|
|
106
|
+
},
|
|
97
107
|
/**
|
|
98
108
|
* The button label
|
|
99
109
|
*/
|
|
@@ -531,6 +541,22 @@ $text-neutral: (
|
|
|
531
541
|
.mc-button--text-neutral {
|
|
532
542
|
@include set-button-theme($text-neutral);
|
|
533
543
|
}
|
|
544
|
+
|
|
545
|
+
.mc-button--bordered-left {
|
|
546
|
+
background: transparent;
|
|
547
|
+
border-color: $color-success-500;
|
|
548
|
+
border-radius: 0.25rem 0 0 0.25rem;
|
|
549
|
+
color: $color-success-500;
|
|
550
|
+
padding: 0.375rem;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.mc-button--bordered-right {
|
|
554
|
+
background: transparent;
|
|
555
|
+
border-color: $color-success-500;
|
|
556
|
+
border-radius: 0 0.25rem 0.25rem 0;
|
|
557
|
+
color: $color-success-500;
|
|
558
|
+
padding: 0.375rem;
|
|
559
|
+
}
|
|
534
560
|
</style>
|
|
535
561
|
|
|
536
562
|
<style lang="scss">
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Meta, Canvas, Story, ArgsTable } from '@storybook/addon-docs';
|
|
2
|
+
import MQuantitySelector, { M_NOTIFICATION_VALIDATOR } from './MQuantitySelector';
|
|
3
|
+
|
|
4
|
+
<Meta title="Mozaic/Components/MQuantitySelector 🧬" component={MQuantitySelector} argTypes={{}} />
|
|
5
|
+
|
|
6
|
+
# 🧬 `MQuantitySelector` - Component
|
|
7
|
+
|
|
8
|
+
[](http://mozaic.adeo.cloud/Components/Notification/)
|
|
9
|
+
[](https://www.figma.com/file/XqqYm6Trew66wygIrVdH6r/%5BLIBRAIRIE%5D-Composants-Project-Booster?node-id=389%3A0)
|
|
10
|
+
[](https://github.com/adeo/project-booster-vue/tree/master/src/components/mozaic/notifications)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
The `MQuantitySelector` Vue component is the implementation of the [mozaic design system **notification** component](http://mozaic.adeo.cloud/Components/Notification/).
|
|
15
|
+
|
|
16
|
+
# `MQuantitySelector` - Component props
|
|
17
|
+
|
|
18
|
+
export const TemplateSandbox = (args, { argTypes }) => ({
|
|
19
|
+
props: Object.keys(argTypes),
|
|
20
|
+
components: { MQuantitySelector },
|
|
21
|
+
setup() {
|
|
22
|
+
return { args };
|
|
23
|
+
},
|
|
24
|
+
template: `<m-quantity-selector
|
|
25
|
+
:value="4"
|
|
26
|
+
:valuemin="0"
|
|
27
|
+
:valuemax="50"
|
|
28
|
+
/>`,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
<Canvas>
|
|
32
|
+
<Story
|
|
33
|
+
name="101 Sandbox"
|
|
34
|
+
inline={false}
|
|
35
|
+
height="512px"
|
|
36
|
+
args={{
|
|
37
|
+
title: 'Notification title',
|
|
38
|
+
text: 'Notification text',
|
|
39
|
+
linkLabel: 'Notification link',
|
|
40
|
+
closable: true,
|
|
41
|
+
}}
|
|
42
|
+
parameters={{ storyshots: { disable: true } }}
|
|
43
|
+
>
|
|
44
|
+
{TemplateSandbox.bind({})}
|
|
45
|
+
</Story>
|
|
46
|
+
</Canvas>
|
|
47
|
+
|
|
48
|
+
<ArgsTable story="101 Sandbox" />
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="mc-quantity-selector">
|
|
3
|
+
<m-flex class="mc-quantity-selector__container" :class="cssFieldElementClass">
|
|
4
|
+
<m-button
|
|
5
|
+
class="mc-quantity-selector__button-left"
|
|
6
|
+
theme="bordered-left"
|
|
7
|
+
leftIcon="https://storage.googleapis.com/project-booster-media/mozaic-icons/svg/Navigation_Control_Less_32px.svg"
|
|
8
|
+
icon-position="left"
|
|
9
|
+
:aria-label="decrementAriaLabel"
|
|
10
|
+
:aria-controls="id"
|
|
11
|
+
:disabled="currentValue <= valuemin"
|
|
12
|
+
:size="small ? 's' : null"
|
|
13
|
+
tabindex="-1"
|
|
14
|
+
type="button"
|
|
15
|
+
aria-hidden="true"
|
|
16
|
+
@click="decrement()"
|
|
17
|
+
/>
|
|
18
|
+
|
|
19
|
+
<m-text-input
|
|
20
|
+
:id="id"
|
|
21
|
+
v-model="currentValue"
|
|
22
|
+
type="number"
|
|
23
|
+
class="mc-quantity-selector__input"
|
|
24
|
+
name="quantity-selector-input"
|
|
25
|
+
:aria-label="inputAriaLabel"
|
|
26
|
+
:aria-valuenow="currentValue"
|
|
27
|
+
:aria-valuemin="valuemin"
|
|
28
|
+
:aria-valuemax="valuemax"
|
|
29
|
+
:placeholder="placeholder"
|
|
30
|
+
:rounded="false"
|
|
31
|
+
:textCenter="true"
|
|
32
|
+
:blankField="true"
|
|
33
|
+
:size="small ? 's' : null"
|
|
34
|
+
role="spinbutton"
|
|
35
|
+
@input="handle($event.target.value)"
|
|
36
|
+
@keypress="integerOnly && formatValue($event)"
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
<m-button
|
|
40
|
+
class="mc-quantity-selector__button-right"
|
|
41
|
+
theme="bordered-right"
|
|
42
|
+
leftIcon="https://storage.googleapis.com/project-booster-media/mozaic-icons/svg/Navigation_Control_More_32px.svg"
|
|
43
|
+
icon-position="right"
|
|
44
|
+
:aria-label="incrementAriaLabel"
|
|
45
|
+
:aria-controls="id"
|
|
46
|
+
:disabled="currentValue === valuemax"
|
|
47
|
+
:size="small ? 's' : null"
|
|
48
|
+
tabindex="-1"
|
|
49
|
+
aria-hidden="true"
|
|
50
|
+
type="button"
|
|
51
|
+
@click="increment()"
|
|
52
|
+
/>
|
|
53
|
+
</m-flex>
|
|
54
|
+
<div v-if="!(error || errorMessage) && !info" class="m-text-input__error-placeholder mc-field__error-message">
|
|
55
|
+
|
|
56
|
+
</div>
|
|
57
|
+
<div v-else-if="error || errorMessage" class="m-text-input__error mc-field__error-message">
|
|
58
|
+
{{ error || errorMessage }}
|
|
59
|
+
</div>
|
|
60
|
+
<div v-else-if="info" class="m-text-input__info mc-field__error-message">
|
|
61
|
+
{{ info }}
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</template>
|
|
65
|
+
|
|
66
|
+
<script>
|
|
67
|
+
import MButton from '../buttons/MButton.vue';
|
|
68
|
+
import MTextInput from '../text-input/MTextInput.vue';
|
|
69
|
+
import MFlex from '../flex/MFlex.vue';
|
|
70
|
+
|
|
71
|
+
export default {
|
|
72
|
+
name: 'MQuantitySelector',
|
|
73
|
+
|
|
74
|
+
components: {
|
|
75
|
+
MButton,
|
|
76
|
+
MTextInput,
|
|
77
|
+
MFlex,
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
inject: {
|
|
81
|
+
cssFieldElementClass: {
|
|
82
|
+
default: '',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
props: {
|
|
87
|
+
id: {
|
|
88
|
+
type: String,
|
|
89
|
+
default: 'qty-selector',
|
|
90
|
+
},
|
|
91
|
+
value: {
|
|
92
|
+
type: Number,
|
|
93
|
+
default: 0,
|
|
94
|
+
},
|
|
95
|
+
inputAriaLabel: {
|
|
96
|
+
type: String,
|
|
97
|
+
default: 'Quantity Selector',
|
|
98
|
+
},
|
|
99
|
+
decrementAriaLabel: {
|
|
100
|
+
type: String,
|
|
101
|
+
default: 'Decrement',
|
|
102
|
+
},
|
|
103
|
+
incrementAriaLabel: {
|
|
104
|
+
type: String,
|
|
105
|
+
default: 'Increment',
|
|
106
|
+
},
|
|
107
|
+
valuemin: {
|
|
108
|
+
type: Number,
|
|
109
|
+
default: 1,
|
|
110
|
+
},
|
|
111
|
+
valuemax: {
|
|
112
|
+
type: Number,
|
|
113
|
+
default: 100,
|
|
114
|
+
},
|
|
115
|
+
placeholder: {
|
|
116
|
+
type: String,
|
|
117
|
+
default: null,
|
|
118
|
+
},
|
|
119
|
+
small: {
|
|
120
|
+
type: Boolean,
|
|
121
|
+
default: false,
|
|
122
|
+
},
|
|
123
|
+
integerOnly: {
|
|
124
|
+
type: Boolean,
|
|
125
|
+
default: false,
|
|
126
|
+
},
|
|
127
|
+
error: {
|
|
128
|
+
type: String,
|
|
129
|
+
default: null,
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
emits: ['input', 'increment', 'decrement'],
|
|
134
|
+
|
|
135
|
+
data() {
|
|
136
|
+
return {
|
|
137
|
+
currentValue: this.value || this.valuemin,
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
watch: {
|
|
142
|
+
currentValue(newValue, oldValue) {
|
|
143
|
+
this.handle(newValue);
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
methods: {
|
|
148
|
+
handle(value) {
|
|
149
|
+
this.currentValue = value;
|
|
150
|
+
if (this.currentValue > this.valuemax) {
|
|
151
|
+
this.currentValue = this.valuemax;
|
|
152
|
+
}
|
|
153
|
+
if (this.currentValue < this.valuemin) {
|
|
154
|
+
this.currentValue = this.valuemin;
|
|
155
|
+
}
|
|
156
|
+
this.$emit('input', this.currentValue);
|
|
157
|
+
},
|
|
158
|
+
increment() {
|
|
159
|
+
if (this.currentValue < this.valuemax) {
|
|
160
|
+
this.currentValue++;
|
|
161
|
+
this.$emit('increment', this.currentValue);
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
decrement() {
|
|
165
|
+
if (this.currentValue > this.valuemin) {
|
|
166
|
+
this.currentValue--;
|
|
167
|
+
this.$emit('decrement', this.currentValue);
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
formatValue(e) {
|
|
171
|
+
const INTEGER_ONLY_REGEX = /[0-9/]+/;
|
|
172
|
+
if (!INTEGER_ONLY_REGEX.test(e.key)) {
|
|
173
|
+
e.preventDefault();
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
</script>
|
|
179
|
+
|
|
180
|
+
<style lang="scss">
|
|
181
|
+
@import 'settings-tools/_all-settings';
|
|
182
|
+
@import 'components/_c.quantity-selector';
|
|
183
|
+
|
|
184
|
+
.mc-quantity-selector__button-left,
|
|
185
|
+
.mc-quantity-selector__button-right {
|
|
186
|
+
margin: 0.5rem 0;
|
|
187
|
+
}
|
|
188
|
+
</style>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="m-text-input mc-field">
|
|
2
|
+
<div class="m-text-input mc-field" :class="{ 'm-text-input__nomargin': blankField }">
|
|
3
3
|
<label v-if="label" class="m-text-input__label mc-field__label" :for="`input-${id}`">
|
|
4
4
|
{{ label }}
|
|
5
5
|
<span v-if="required" class="mc-field__requirement" aria-hidden="true"> obligatoire </span>
|
|
@@ -31,6 +31,8 @@
|
|
|
31
31
|
'is-valid': valid,
|
|
32
32
|
'is-invalid': invalid || errorMessage,
|
|
33
33
|
'mc-text-input--s': size === 's',
|
|
34
|
+
'unrounded': !rounded,
|
|
35
|
+
'text-center': textCenter,
|
|
34
36
|
}"
|
|
35
37
|
:name="name"
|
|
36
38
|
:disabled="disabled"
|
|
@@ -45,13 +47,25 @@
|
|
|
45
47
|
@blur="handleBlurEvent"
|
|
46
48
|
/>
|
|
47
49
|
</div>
|
|
48
|
-
<div
|
|
50
|
+
<div
|
|
51
|
+
v-if="!(error || errorMessage) && !info"
|
|
52
|
+
class="m-text-input__error-placeholder mc-field__error-message"
|
|
53
|
+
:class="{ 'm-text-input__blank': blankField }"
|
|
54
|
+
>
|
|
49
55
|
|
|
50
56
|
</div>
|
|
51
|
-
<div
|
|
57
|
+
<div
|
|
58
|
+
v-else-if="error || errorMessage"
|
|
59
|
+
class="m-text-input__error mc-field__error-message"
|
|
60
|
+
:class="{ 'm-text-input__blank': blankField }"
|
|
61
|
+
>
|
|
52
62
|
{{ error || errorMessage }}
|
|
53
63
|
</div>
|
|
54
|
-
<div
|
|
64
|
+
<div
|
|
65
|
+
v-else-if="info"
|
|
66
|
+
class="m-text-input__info mc-field__error-message"
|
|
67
|
+
:class="{ 'm-text-input__blank': blankField }"
|
|
68
|
+
>
|
|
55
69
|
{{ info }}
|
|
56
70
|
</div>
|
|
57
71
|
</div>
|
|
@@ -225,6 +239,27 @@ export default defineComponent({
|
|
|
225
239
|
type: Number,
|
|
226
240
|
default: -1,
|
|
227
241
|
},
|
|
242
|
+
/**
|
|
243
|
+
* Defines if this input has bordered
|
|
244
|
+
*/
|
|
245
|
+
rounded: {
|
|
246
|
+
type: Boolean,
|
|
247
|
+
default: true,
|
|
248
|
+
},
|
|
249
|
+
/**
|
|
250
|
+
* Defines text alignment inside input
|
|
251
|
+
*/
|
|
252
|
+
textCenter: {
|
|
253
|
+
type: Boolean,
|
|
254
|
+
default: false,
|
|
255
|
+
},
|
|
256
|
+
/**
|
|
257
|
+
* Definies if input has blank fields
|
|
258
|
+
*/
|
|
259
|
+
blankField: {
|
|
260
|
+
type: Boolean,
|
|
261
|
+
default: false,
|
|
262
|
+
},
|
|
228
263
|
},
|
|
229
264
|
|
|
230
265
|
setup(props, { emit, expose }) {
|
|
@@ -354,4 +389,24 @@ export default defineComponent({
|
|
|
354
389
|
@import 'components/c.text-input';
|
|
355
390
|
@import 'components/c.left-icon-input';
|
|
356
391
|
@import 'components/c.fields';
|
|
392
|
+
|
|
393
|
+
.unrounded {
|
|
394
|
+
border-left: 0;
|
|
395
|
+
border-radius: 0;
|
|
396
|
+
border-right: 0;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
.text-center {
|
|
400
|
+
text-align: center;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.m-text-input__blank {
|
|
404
|
+
display: none !important;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.m-text-input__nomargin {
|
|
408
|
+
input {
|
|
409
|
+
margin-top: 0 !important;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
357
412
|
</style>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface IncrementalAmountValidation {
|
|
2
|
+
minValue: number;
|
|
3
|
+
maxValue: number;
|
|
4
|
+
defaultValue: number;
|
|
5
|
+
requiredErrorMessage: string;
|
|
6
|
+
thresholdsIncluded: number;
|
|
7
|
+
minValueErrorMessage: string;
|
|
8
|
+
maxValueErrorMessage: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface IncrementalAmountViewModel {
|
|
12
|
+
backLabel: string;
|
|
13
|
+
label: string;
|
|
14
|
+
actionLabel: string;
|
|
15
|
+
validation: IncrementalAmountValidation;
|
|
16
|
+
minValue: number;
|
|
17
|
+
maxValue: number;
|
|
18
|
+
defaultValue: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface IncrementalAmountPayload {
|
|
22
|
+
viewModel: IncrementalAmountViewModel;
|
|
23
|
+
defaultDecoratorValue: string;
|
|
24
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { nestedAppDecorator } from '../../../../.storybook/nested-app-decorator';
|
|
2
|
+
import { ArgsTable, Canvas, Meta, Source, Story } from '@storybook/addon-docs';
|
|
3
|
+
import PbIncrementalAmountInput from './PbIncrementalAmountInput';
|
|
4
|
+
import DEFAULT_PAYLOAD from './default-payload.json';
|
|
5
|
+
|
|
6
|
+
<Meta
|
|
7
|
+
title="Project Booster/Scenario/Questions/PbIncrementalAmountInput 🦠"
|
|
8
|
+
component={PbIncrementalAmountInput}
|
|
9
|
+
decorators={[nestedAppDecorator({}, [])]}
|
|
10
|
+
parameters={{ layout: 'fullscreen' }}
|
|
11
|
+
/>
|
|
12
|
+
|
|
13
|
+
# 🦠 `PbFamilyAmountInput` - Components
|
|
14
|
+
|
|
15
|
+
[](https://github.com/adeo/project-booster-vue/tree/master/src/components/question)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
The `PbIncrementalAmountInput` Vue component allows select family amount persons. It
|
|
20
|
+
is customizable through the payload property. It has the same
|
|
21
|
+
behaviour as the `PbQuestion` component and is designed to work in scenarii.
|
|
22
|
+
|
|
23
|
+
It has a default provided payload, used in the Sandbox below.
|
|
24
|
+
|
|
25
|
+
The input field is automatically focused.
|
|
26
|
+
|
|
27
|
+
The back button will make `vue-router` trigger the history back feature from the browser. It can be hidden with the
|
|
28
|
+
`showBackButton` flag.
|
|
29
|
+
|
|
30
|
+
# `PbIncrementalAmountInput` - Component props
|
|
31
|
+
|
|
32
|
+
export const TemplateSandbox = (args, { argTypes }) => ({
|
|
33
|
+
props: Object.keys(argTypes),
|
|
34
|
+
components: { PbIncrementalAmountInput },
|
|
35
|
+
setup() {
|
|
36
|
+
return { args };
|
|
37
|
+
},
|
|
38
|
+
template: `<pb-incremental-amount-input
|
|
39
|
+
:payload="args.payload"
|
|
40
|
+
:show-back-button="args.showBackButton"
|
|
41
|
+
:completed-event-name="args.completedEventName"
|
|
42
|
+
@step-completed="args.onStepCompleted"
|
|
43
|
+
/>`,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
<Canvas>
|
|
47
|
+
<Story name="101 Sandbox" args={{ payload: DEFAULT_PAYLOAD }}>
|
|
48
|
+
{TemplateSandbox.bind({})}
|
|
49
|
+
</Story>
|
|
50
|
+
</Canvas>
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<m-flex class="pb-incremental-amount-input" direction="column" align-items="center">
|
|
3
|
+
<m-flex class="pb-incremental-amount-input__back-button-container" align-items="stretch">
|
|
4
|
+
<m-link
|
|
5
|
+
:label="computedPayload.viewModel.backLabel"
|
|
6
|
+
:left-icon="BACK_ICON"
|
|
7
|
+
:class="{
|
|
8
|
+
'pb-incremental-amount-input__back-button': true,
|
|
9
|
+
'pb-incremental-amount-input__back-button--hidden':
|
|
10
|
+
!showBackButton && !decorate(answers, runtimeOptions, payload.viewModel.forceBackButton),
|
|
11
|
+
}"
|
|
12
|
+
@click.once="$emit('go-back')"
|
|
13
|
+
/>
|
|
14
|
+
</m-flex>
|
|
15
|
+
|
|
16
|
+
<div class="pb-incremental-amount-input__title">
|
|
17
|
+
{{ computedPayload.viewModel.label }}
|
|
18
|
+
</div>
|
|
19
|
+
<div class="pb-incremental-amount-input__subtitle">
|
|
20
|
+
{{ computedPayload.viewModel.subtitle }}
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<form
|
|
24
|
+
class="pb-incremental-amount-input__form-container"
|
|
25
|
+
ref="pbDimensionsInputFormContainerObserver"
|
|
26
|
+
@submit.prevent="handleFormSubmit"
|
|
27
|
+
>
|
|
28
|
+
<m-flex class="pb-incremental-amount-input__form" direction="column" align-items="stretch">
|
|
29
|
+
<m-flex class="pb-incremental-amount-input__header" ref="PbIncrementalAmountInput" direction="column">
|
|
30
|
+
<m-flex
|
|
31
|
+
v-if="computedPayload.viewModel.image"
|
|
32
|
+
class="pb-incremental-amount-input__image"
|
|
33
|
+
direction="column"
|
|
34
|
+
align-items="center"
|
|
35
|
+
>
|
|
36
|
+
<img :src="computedPayload.viewModel.image" alt="Image décorative" />
|
|
37
|
+
</m-flex>
|
|
38
|
+
</m-flex>
|
|
39
|
+
</m-flex>
|
|
40
|
+
|
|
41
|
+
<m-flex class="pb-incremental-amount-input__body" justify-content="start" align-items="start">
|
|
42
|
+
<m-quantity-selector
|
|
43
|
+
:value="computedPayload.viewModel.defaultValue"
|
|
44
|
+
:valuemin="computedPayload.viewModel.minValue"
|
|
45
|
+
:valuemax="computedPayload.viewModel.maxValue"
|
|
46
|
+
@input="handleQuantitySelector"
|
|
47
|
+
:integerOnly="true"
|
|
48
|
+
/>
|
|
49
|
+
</m-flex>
|
|
50
|
+
|
|
51
|
+
<m-flex class="pb-incremental-amount-input__buttons-container" direction="column" align-items="center">
|
|
52
|
+
<m-button
|
|
53
|
+
class="pb-incremental-amount-input__button"
|
|
54
|
+
:label="computedPayload.viewModel.actionLabel"
|
|
55
|
+
width="full"
|
|
56
|
+
size="l"
|
|
57
|
+
type="submit"
|
|
58
|
+
/>
|
|
59
|
+
</m-flex>
|
|
60
|
+
</form>
|
|
61
|
+
</m-flex>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<script lang="ts">
|
|
65
|
+
import { defineComponent, computed, onMounted, ComputedRef, PropType, Ref } from 'vue';
|
|
66
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
67
|
+
import cloneDeep from 'lodash.clonedeep';
|
|
68
|
+
import merge from 'lodash.merge';
|
|
69
|
+
import MFlex from './../../mozaic/flex/MFlex.vue';
|
|
70
|
+
import DEFAULT_PAYLOAD from './default-payload.json';
|
|
71
|
+
import MLink from '../../mozaic/link/MLink.vue';
|
|
72
|
+
import MButton from '../../mozaic/buttons/MButton.vue';
|
|
73
|
+
import { ref } from 'vue';
|
|
74
|
+
import { ScenarioStepAnswer } from '@/types/pb/Scenario';
|
|
75
|
+
import { decorate } from '@/components/question/PbQuestion.vue';
|
|
76
|
+
import MQuantitySelector from '../../mozaic/quantityselector/MQuantitySelector.vue';
|
|
77
|
+
|
|
78
|
+
const BACK_ICON =
|
|
79
|
+
'https://storage.googleapis.com/project-booster-media/mozaic-icons/svg/Navigation_Arrow_Arrow--Left_16px.svg';
|
|
80
|
+
|
|
81
|
+
export default defineComponent({
|
|
82
|
+
name: 'PbIncrementalAmountInput',
|
|
83
|
+
components: {
|
|
84
|
+
MFlex,
|
|
85
|
+
MLink,
|
|
86
|
+
MButton,
|
|
87
|
+
MQuantitySelector,
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
props: {
|
|
91
|
+
/**
|
|
92
|
+
* The component view model and business data as an object. The provided prop
|
|
93
|
+
* is merged with the default payload value so only overriden values will change
|
|
94
|
+
* from the default ones.
|
|
95
|
+
*/
|
|
96
|
+
payload: {
|
|
97
|
+
type: Object,
|
|
98
|
+
default: () => ({}),
|
|
99
|
+
},
|
|
100
|
+
/**
|
|
101
|
+
* The options provided at runtime to customize component behaviour
|
|
102
|
+
*/
|
|
103
|
+
runtimeOptions: {
|
|
104
|
+
type: Object,
|
|
105
|
+
default: () => ({}),
|
|
106
|
+
},
|
|
107
|
+
/**
|
|
108
|
+
* Indicates whether the back button should be displayed
|
|
109
|
+
*/
|
|
110
|
+
showBackButton: {
|
|
111
|
+
type: Boolean,
|
|
112
|
+
default: true,
|
|
113
|
+
},
|
|
114
|
+
/**
|
|
115
|
+
* Name for the event to send when the step is questio is answered
|
|
116
|
+
*/
|
|
117
|
+
completedEventName: {
|
|
118
|
+
type: String,
|
|
119
|
+
default: 'step-completed',
|
|
120
|
+
},
|
|
121
|
+
/**
|
|
122
|
+
* The previous answers to inject
|
|
123
|
+
*/
|
|
124
|
+
answers: {
|
|
125
|
+
type: Object as PropType<Map<string, ScenarioStepAnswer[]>>,
|
|
126
|
+
default: () => new Map<string, ScenarioStepAnswer[]>(),
|
|
127
|
+
},
|
|
128
|
+
/**
|
|
129
|
+
* Define default value
|
|
130
|
+
*/
|
|
131
|
+
defaultValue: {
|
|
132
|
+
type: Number,
|
|
133
|
+
default: 1,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
setup(props, { emit }) {
|
|
137
|
+
const componentId = uuidv4();
|
|
138
|
+
const computedPayload = computed(() => {
|
|
139
|
+
const tempPayload = cloneDeep(DEFAULT_PAYLOAD);
|
|
140
|
+
return merge(tempPayload, props.payload);
|
|
141
|
+
});
|
|
142
|
+
const pbIncrementalAmountInput = ref<HTMLElement>();
|
|
143
|
+
let quantitySelector = props.payload.viewModel.defaultValue || props.defaultValue;
|
|
144
|
+
|
|
145
|
+
const handleFormSubmit = () => {
|
|
146
|
+
emit(props.completedEventName, {
|
|
147
|
+
answers: [
|
|
148
|
+
{
|
|
149
|
+
value: quantitySelector,
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const handleQuantitySelector = (value: number): void => {
|
|
156
|
+
quantitySelector = value;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
onMounted(() => {
|
|
160
|
+
setTimeout(() => {
|
|
161
|
+
pbIncrementalAmountInput.value?.focus();
|
|
162
|
+
}, 150);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
id: componentId,
|
|
167
|
+
handleFormSubmit,
|
|
168
|
+
BACK_ICON,
|
|
169
|
+
computedPayload,
|
|
170
|
+
pbIncrementalAmountInput,
|
|
171
|
+
quantitySelector,
|
|
172
|
+
handleQuantitySelector,
|
|
173
|
+
};
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
</script>
|
|
177
|
+
|
|
178
|
+
<style lang="scss" scoped>
|
|
179
|
+
@import 'pb-variables';
|
|
180
|
+
|
|
181
|
+
$small-responsive-breakpoint: 's-large';
|
|
182
|
+
$responsive-breakpoint: 'm';
|
|
183
|
+
|
|
184
|
+
.pb-incremental-amount-input {
|
|
185
|
+
@include set-font-face('regular');
|
|
186
|
+
|
|
187
|
+
flex-grow: 1;
|
|
188
|
+
margin: 0 auto;
|
|
189
|
+
width: calc(100% - #{$mu050} - #{$mu050});
|
|
190
|
+
|
|
191
|
+
&__back-button-container {
|
|
192
|
+
align-self: flex-start;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
&__back-button {
|
|
196
|
+
opacity: 1;
|
|
197
|
+
padding: $mu100 0 $mu100 $mu025;
|
|
198
|
+
transition: opacity 0.15s 0.5s;
|
|
199
|
+
|
|
200
|
+
&--hidden {
|
|
201
|
+
opacity: 0;
|
|
202
|
+
pointer-events: none;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
&__body {
|
|
207
|
+
flex-wrap: wrap;
|
|
208
|
+
margin-bottom: $mu250;
|
|
209
|
+
max-width: 100%;
|
|
210
|
+
|
|
211
|
+
@include set-from-screen($responsive-breakpoint) {
|
|
212
|
+
flex-wrap: nowrap;
|
|
213
|
+
width: 161px;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
&--input-container {
|
|
217
|
+
width: 100%;
|
|
218
|
+
|
|
219
|
+
&__validator {
|
|
220
|
+
width: 100%;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
&__center {
|
|
225
|
+
width: 65px;
|
|
226
|
+
|
|
227
|
+
input {
|
|
228
|
+
border-left: 0;
|
|
229
|
+
border-radius: 0;
|
|
230
|
+
border-right: 0;
|
|
231
|
+
margin-top: 0;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
&__reduce,
|
|
236
|
+
&__up {
|
|
237
|
+
margin-top: 0.5rem;
|
|
238
|
+
|
|
239
|
+
svg {
|
|
240
|
+
height: 1rem;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
&__form-container {
|
|
246
|
+
align-items: center;
|
|
247
|
+
display: flex;
|
|
248
|
+
flex-direction: column;
|
|
249
|
+
flex-grow: 1;
|
|
250
|
+
|
|
251
|
+
@include set-from-screen($responsive-breakpoint) {
|
|
252
|
+
//width: calc(100% - #{$mu050} - #{$mu050});
|
|
253
|
+
width: 314px;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
&__form {
|
|
258
|
+
flex-grow: 1;
|
|
259
|
+
max-width: 100%;
|
|
260
|
+
width: 384px;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
&__title {
|
|
264
|
+
@include set-font-face('semi-bold');
|
|
265
|
+
@include set-font-scale('07', 's');
|
|
266
|
+
|
|
267
|
+
color: $color-grey-700;
|
|
268
|
+
max-width: 100%;
|
|
269
|
+
padding: 0 0 0 0;
|
|
270
|
+
width: 384px;
|
|
271
|
+
|
|
272
|
+
@include set-from-screen($responsive-breakpoint) {
|
|
273
|
+
@include set-font-scale('08', 'm');
|
|
274
|
+
|
|
275
|
+
padding: $mu250 $mu100 $mu100 $mu100;
|
|
276
|
+
text-align: center;
|
|
277
|
+
width: auto;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
&__subtitle {
|
|
282
|
+
color: $color-grey-700;
|
|
283
|
+
max-width: 100%;
|
|
284
|
+
padding: 0 0 $mu250 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
&__image {
|
|
288
|
+
padding-bottom: $mu200;
|
|
289
|
+
|
|
290
|
+
img {
|
|
291
|
+
display: none;
|
|
292
|
+
width: 70%;
|
|
293
|
+
|
|
294
|
+
@include set-from-screen($small-responsive-breakpoint) {
|
|
295
|
+
display: block;
|
|
296
|
+
width: 70%;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
@include set-from-screen($responsive-breakpoint) {
|
|
300
|
+
width: 90%;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
&__text-input {
|
|
306
|
+
width: 100%;
|
|
307
|
+
z-index: 2;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
&__separator {
|
|
311
|
+
color: $color-grey-500;
|
|
312
|
+
display: none;
|
|
313
|
+
padding: $mu125 $mu100 0 $mu100;
|
|
314
|
+
|
|
315
|
+
@include set-from-screen($responsive-breakpoint) {
|
|
316
|
+
display: flex;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
&__buttons-container {
|
|
321
|
+
justify-content: flex-end !important;
|
|
322
|
+
margin-bottom: $mu100;
|
|
323
|
+
position: sticky;
|
|
324
|
+
width: 100%;
|
|
325
|
+
z-index: 1;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
&__link {
|
|
329
|
+
margin-top: $mu250;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"viewModel": {
|
|
3
|
+
"backLabel": "Question précédente",
|
|
4
|
+
"label": "Combien de personnes vivent dans votre foyer fiscal ?",
|
|
5
|
+
"subtitle": "Cette information est utile pour le calcul des aides",
|
|
6
|
+
"actionLabel": "Continuer",
|
|
7
|
+
"image": "https://storage.googleapis.com/project-booster-media/new-house/Estimation%20Construction%20Neuve%20-%20Localisation.png",
|
|
8
|
+
"minValue": 1,
|
|
9
|
+
"maxValue": 20,
|
|
10
|
+
"defaultValue": 1
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -103,6 +103,8 @@ import PbSpaceInput from '../question/space-input/PbSpaceInput.vue';
|
|
|
103
103
|
import PbUploadDocument from '../question/upload-document/PbUploadDocument.vue';
|
|
104
104
|
import PbConfigurationsImport from '../question/configurations-import/PbConfigurationsImport.vue';
|
|
105
105
|
import PbCitySearch from '../question/city-search/PbCitySearch.vue';
|
|
106
|
+
import PbWarningMessage from '../warning-message/PbWarningMessage.vue';
|
|
107
|
+
import PbIncrementalAmountInput from '../question/incremental-amount-input/PbIncrementalAmountInput.vue';
|
|
106
108
|
import { areConditionsValid } from '../../services/scenarioConditionals';
|
|
107
109
|
import {
|
|
108
110
|
Scenario,
|
|
@@ -138,6 +140,8 @@ export default defineComponent({
|
|
|
138
140
|
PbConfigurationsImport,
|
|
139
141
|
PbAppointmentForm,
|
|
140
142
|
PbCitySearch,
|
|
143
|
+
PbWarningMessage,
|
|
144
|
+
PbIncrementalAmountInput,
|
|
141
145
|
},
|
|
142
146
|
|
|
143
147
|
props: {
|