project-booster-vue 9.24.0 → 9.26.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-booster-vue",
3
- "version": "9.24.0",
3
+ "version": "9.26.0",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "serve": "vue-cli-service serve",
@@ -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
+ [![alt text](https://storage.googleapis.com/project-booster-media/project-booster-vue/mozaic-documentation.svg 'Mozaic component documentation')](http://mozaic.adeo.cloud/Components/Notification/)
9
+ [![alt text](https://storage.googleapis.com/project-booster-media/project-booster-vue/project-booster-wireframes.svg 'Project booster component wireframes')](https://www.figma.com/file/XqqYm6Trew66wygIrVdH6r/%5BLIBRAIRIE%5D-Composants-Project-Booster?node-id=389%3A0)
10
+ [![alt text](https://storage.googleapis.com/project-booster-media/project-booster-vue/project-booster-sources.svg 'Project booster component source code')](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
+ &nbsp;
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 v-if="!(error || errorMessage) && !info" class="m-text-input__error-placeholder mc-field__error-message">
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
  &nbsp;
50
56
  </div>
51
- <div v-else-if="error || errorMessage" class="m-text-input__error mc-field__error-message">
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 v-else-if="info" class="m-text-input__info mc-field__error-message">
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>
@@ -136,9 +136,7 @@ export default defineComponent({
136
136
 
137
137
  async handleValidateClicked() {
138
138
  this.$emit('step-completed', {
139
- answers: this.payload.multiSelect.actions.VALIDATE.isAnswer
140
- ? [this.payload.multiSelect.actions.VALIDATE.code]
141
- : [],
139
+ answers: this.payload.multiSelect.actions.VALIDATE.isAnswer ? [this.payload.multiSelect.actions.VALIDATE] : [],
142
140
  nextStep: this.payload.multiSelect.actions.VALIDATE.nextStep,
143
141
  });
144
142
  },
@@ -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
+ [![alt text](https://storage.googleapis.com/project-booster-media/project-booster-vue/project-booster-sources.svg 'Project booster component source code')](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
+ }
@@ -247,3 +247,51 @@ export const customFooter = {
247
247
  {TemplateSandbox.bind({})}
248
248
  </Story>
249
249
  </Canvas>
250
+
251
+ ## With modal support
252
+
253
+ export const withModal = {
254
+ viewModel: {
255
+ validation: {
256
+ maxValue: 10001,
257
+ maxErrorMessage: 'La surface doit être comprise entre 100m² et 10 000m²',
258
+ minValue: 99,
259
+ minErrorMessage: 'La surface doit être comprise entre 100m² et 10 000m²',
260
+ },
261
+ backLabel: 'Question précédente',
262
+ label: 'Quelle est votre surface ?',
263
+ placeholder: 'Votre surface',
264
+ actionLabel: 'Continuer',
265
+ image: null,
266
+ },
267
+ helpArea: [
268
+ {
269
+ type: 'text',
270
+ label: 'Besoin d’aide pour calculer la surface ?',
271
+ },
272
+ {
273
+ type: 'link',
274
+ label: 'Voir le guide pour calculer sa surface',
275
+ action: {
276
+ type: 'MODAL',
277
+ dialogViewModel: {
278
+ headerTitle: 'Pour calculer la surface chauffée c’est facile.',
279
+ htmlContent:
280
+ '<p>Pour calculer la surface chauffée c’est facile.</p><p> Il suffit de déduire de <strong>la surface habitable</strong> de votre habitation, <strong>les surfaces des pièces non chauffées</strong> par la pompe à chaleur. </p><p> Les pièces non chauffées peuvent être des pièces annexes (buanderie, véranda, …) ou de pièces chauffées par un autre mode de chauffage (radiateurs électriques par exemple). </p><p> <strong>Attention :</strong> le garage et le grenier ne sont pas pris en compte dans la surface habitable de base, il ne faut donc pas les déduire </p>',
281
+ },
282
+ },
283
+ },
284
+ ],
285
+ };
286
+
287
+ <Source language="json" code={JSON.stringify(withModal, null, ' ')} />
288
+
289
+ <Canvas>
290
+ <Story
291
+ name="Showcase - Feature with modal"
292
+ parameters={{ controls: { disable: true } }}
293
+ args={{ payload: withModal }}
294
+ >
295
+ {TemplateSandbox.bind({})}
296
+ </Story>
297
+ </Canvas>
@@ -12,6 +12,7 @@
12
12
  @click.once="$emit('go-back')"
13
13
  />
14
14
  </m-flex>
15
+
15
16
  <form
16
17
  class="pb-space-input__form-container"
17
18
  ref="pbSpaceInputFormContainerObserver"
@@ -57,6 +58,30 @@
57
58
  </m-flexy-col>
58
59
  </m-flex>
59
60
 
61
+ <m-flex
62
+ class="pb-space-input__help"
63
+ v-if="computedPayload.helpArea"
64
+ direction="column"
65
+ align-items="center"
66
+ justify-content="center"
67
+ >
68
+ <div v-for="helpItem in computedPayload.helpArea" :key="helpItem.type">
69
+ <div v-if="helpItem.type === 'text'">
70
+ <p class="pb-space-input__help__text">{{ helpItem.label }}</p>
71
+ </div>
72
+ <div v-if="helpItem.type === 'link'">
73
+ <m-link
74
+ class="pb-space-input__help__link"
75
+ :label="helpItem.label"
76
+ width="full"
77
+ size="l"
78
+ theme="primary"
79
+ @click="handleShowModal(helpItem.action.dialogViewModel)"
80
+ />
81
+ </div>
82
+ </div>
83
+ </m-flex>
84
+
60
85
  <m-flex class="pb-space-input__buttons-container" direction="column" align-items="center">
61
86
  <m-button
62
87
  class="pb-space-input__button"
@@ -77,6 +102,30 @@
77
102
  </m-flex>
78
103
  </m-flex>
79
104
  </form>
105
+
106
+ <m-dialog
107
+ class="pb-space-input__dialog"
108
+ :show-dialog="showModal"
109
+ width="680px"
110
+ height="520px"
111
+ maxHeight="100vh"
112
+ @update:show-dialog="handleShowModal"
113
+ v-if="computedPayload.helpArea"
114
+ >
115
+ <template v-slot:header>
116
+ <div class="pb-space-input__dialog__title">
117
+ <h2>{{ contentModal.headerTitle }}</h2>
118
+ </div>
119
+ </template>
120
+ <template v-slot:body>
121
+ <div class="pb-space-input__dialog__body" v-html="contentModal.htmlContent"></div>
122
+ </template>
123
+ <template v-slot:footer>
124
+ <m-flex class="pb-space-input__dialog__footer" align-items="center" justify-content="center">
125
+ <m-button label="Fermer" theme="bordered-neutral" @click.prevent="handleShowModal"></m-button>
126
+ </m-flex>
127
+ </template>
128
+ </m-dialog>
80
129
  </m-flex>
81
130
  </template>
82
131
 
@@ -93,6 +142,7 @@ import MLink from '../../mozaic/link/MLink.vue';
93
142
  import MTextInput from '../../mozaic/text-input/MTextInput.vue';
94
143
  import MFlexyCol from '../../mozaic/grid/MFlexyCol.vue';
95
144
  import MIcon from '../../mozaic/icon/MIcon.vue';
145
+ import MDialog from '../../mozaic/dialog/MDialog.vue';
96
146
  import DEFAULT_PAYLOAD from './default-payload.json';
97
147
  import { SpaceInputPayload } from '@/components/question/space-input/SpaceInput';
98
148
  import { ScenarioStepAnswer } from '@/types/pb/Scenario';
@@ -145,6 +195,7 @@ export default defineComponent({
145
195
  MFlexyCol,
146
196
  MTextInput,
147
197
  MIcon,
198
+ MDialog,
148
199
  },
149
200
 
150
201
  props: {
@@ -194,9 +245,19 @@ export default defineComponent({
194
245
  return merge(tempPayload, props.payload);
195
246
  });
196
247
  const pbSpaceInputTextInput = ref<HTMLElement>();
248
+ const showModal = ref(false);
249
+ const contentModal = ref({});
197
250
 
198
251
  const space = computeDefaultValue(props.runtimeOptions, props.answers!, computedPayload);
199
252
 
253
+ const handleShowModal = ({ headerTitle, htmlContent }: { headerTitle: string; htmlContent: string }) => {
254
+ showModal.value = !showModal.value;
255
+
256
+ if (headerTitle && htmlContent) {
257
+ contentModal.value = { headerTitle, htmlContent };
258
+ }
259
+ };
260
+
200
261
  const validationSchema = initValidation(componentId, computedPayload);
201
262
 
202
263
  const { handleSubmit } = useForm({ validationSchema: validationSchema.value });
@@ -250,6 +311,9 @@ export default defineComponent({
250
311
  handleFormSubmit,
251
312
  isShowingFooter,
252
313
  skipQuestion,
314
+ showModal,
315
+ handleShowModal,
316
+ contentModal,
253
317
  };
254
318
  },
255
319
  });
@@ -382,5 +446,29 @@ $responsive-breakpoint: 'm';
382
446
  &__link {
383
447
  margin-top: $mu250;
384
448
  }
449
+
450
+ &__dialog {
451
+ &__title {
452
+ padding: $mu250 $mu250 0 $mu250;
453
+ }
454
+
455
+ &__body {
456
+ padding: 0 $mu250;
457
+ @include set-font-scale('05', 'l');
458
+ }
459
+
460
+ &__footer {
461
+ padding: $mu100 $mu250 $mu250 $mu250;
462
+ }
463
+ }
464
+
465
+ &__help {
466
+ margin-bottom: $mu250;
467
+
468
+ &__link,
469
+ &__text {
470
+ margin: 0;
471
+ }
472
+ }
385
473
  }
386
474
  </style>
@@ -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: {
@@ -123,7 +123,7 @@ export default defineComponent({
123
123
  if (element) {
124
124
  SVGInjector(element, {
125
125
  each: function (svg) {
126
- svg.setAttribute('fill', '#C65200');
126
+ (svg as any).setAttribute('fill', '#C65200');
127
127
  },
128
128
  });
129
129
  }