Mostly actually an update to questions-page-layout, question-chain, button-question and list-button-horizontal, since those are the actual bulk of what this page.
405 lines
13 KiB
Vue
405 lines
13 KiB
Vue
<!-- Documented in confluence https://safelite.atlassian.net/wiki/spaces/DC/pages/76644418/Button+Question+Component -->
|
|
<template>
|
|
<div
|
|
:class="
|
|
isOverflowScrollable
|
|
? 'button-question button-question-overflow'
|
|
: 'button-question'
|
|
">
|
|
<div
|
|
v-if="questionText && answers && answers.length > 0"
|
|
class="question-text d-flex"
|
|
:class="{
|
|
'small-question-text': isSmallQuestionText,
|
|
'small-question-label-text': isSmallQuestionLabelText,
|
|
}">
|
|
<span
|
|
class="w-100"
|
|
:class="questionTextClasses">{{ questionText }}</span>
|
|
</div>
|
|
<div class="w-100 d-flex justify-content-center">
|
|
<fieldset
|
|
class="w-100"
|
|
:aria-required="isRequired"
|
|
:role="isMultiSelect ? 'group' : 'radiogroup'"
|
|
:aria-labelledby="formatString(groupName)">
|
|
<legend
|
|
:id="formatString(groupName)"
|
|
class="sr-only"
|
|
:data-focus-target="formatString(groupName)"
|
|
tabindex="-1">
|
|
{{ questionText }}
|
|
{{
|
|
isMultiSelect && answers && answers.length > 1
|
|
? 'Select one or more options below.'
|
|
: 'Select an option below.'
|
|
}}
|
|
</legend>
|
|
<div :class="getComponentLoopWrapperClasses">
|
|
<div
|
|
v-for="answer in buttonsInfo"
|
|
:key="answer.value ?? answer"
|
|
:class="getComponentWrapperClasses">
|
|
<component
|
|
:is="buttonTypeString"
|
|
v-model="selectedValues"
|
|
:buttonLabel="answer.buttonLabel"
|
|
:buttonLabelSubCopy="answer.buttonLabelSubCopy"
|
|
:buttonBodyCopy="answer.buttonBodyCopy"
|
|
:buttonAuxiliaryCopy="answer.buttonAuxiliaryCopy"
|
|
:buttonFooterCopy="answer.buttonFooterCopy"
|
|
:buttonImage="answer.buttonImage"
|
|
:buttonImageId="answer.buttonImageId"
|
|
:groupName="answer.groupName"
|
|
:isMultiSelect="isMultiSelect"
|
|
:value="answer.value"
|
|
:selectingInitiatesLoad="selectingInitiatesLoad"
|
|
:isWide="isWide"
|
|
:validationRules="validationRules"
|
|
:textPosition="textPosition"
|
|
:additionalButtonData="additionalButtonData"
|
|
:additionalButtonStyling="additionalButtonStyling"
|
|
:lastValuePushedToGa="lastValuePushedToGa"
|
|
:setLastValuePushedToGa="setLastValuePushedToGa"
|
|
:suppressError="suppressError" />
|
|
<!-- For nested questions -->
|
|
<transition
|
|
name="fade"
|
|
mode="out-in">
|
|
<div
|
|
v-if="
|
|
typeof selectedValues == 'string' &&
|
|
selectedValues == answer.value
|
|
">
|
|
<slot></slot>
|
|
</div>
|
|
</transition>
|
|
</div>
|
|
</div>
|
|
</fieldset>
|
|
</div>
|
|
<div
|
|
v-if="!suppressError"
|
|
class="row form-test-error mt-1">
|
|
<error-message :name="formatString(groupName)"></error-message>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import listButton from '@/ux-components/list-button/list-button.vue';
|
|
import listButtonHorizontal from '@/ux-components/list-button-horizontal/list-button-horizontal.vue';
|
|
import listCard from '@/ux-components/list-card/list-card.vue';
|
|
import radio from '@/ux-components/radio/radio.vue';
|
|
import { useField, ErrorMessage } from 'vee-validate';
|
|
import providerPrefRadio from '@/layouts/provider-preference/provider-pref-radio/provider-pref-radio.vue';
|
|
|
|
export default {
|
|
name: 'button-question',
|
|
components: {
|
|
listButton,
|
|
listButtonHorizontal,
|
|
listCard,
|
|
ErrorMessage,
|
|
radio,
|
|
providerPrefRadio
|
|
},
|
|
props: {
|
|
buttonTypeString: {
|
|
type: String,
|
|
default: 'listButton'
|
|
},
|
|
buttonTypeObject: {
|
|
type: Object,
|
|
default: null
|
|
},
|
|
isMultiSelect: Boolean,
|
|
groupName: String,
|
|
questionText: String,
|
|
answers: Array,
|
|
questionTextClasses: Array,
|
|
textPosition: {
|
|
type: String,
|
|
default: 'text-center'
|
|
},
|
|
selectingInitiatesLoad: Boolean,
|
|
isRequired: Boolean,
|
|
isOverflowScrollable: Boolean,
|
|
isWide: Boolean,
|
|
isCashOrInsurance: Boolean,
|
|
modelValue: [Array, Number, String],
|
|
value: [Number, String],
|
|
validationRules: String,
|
|
suppressError: Boolean,
|
|
useTextForValue: Boolean,
|
|
valueToLogType: String,
|
|
additionalButtonData: Object,
|
|
additionalButtonStyling: String,
|
|
isSmallQuestionText: Boolean,
|
|
isSmallQuestionLabelText: Boolean
|
|
},
|
|
emits: ['update:modelValue'],
|
|
setup(props) {
|
|
const propsClone = { ...props };
|
|
const { modelValue } = propsClone;
|
|
|
|
const fieldOptions = {
|
|
value: modelValue,
|
|
initialValue: null
|
|
};
|
|
|
|
const {
|
|
errorMessage,
|
|
handleBlur,
|
|
handleChange,
|
|
meta,
|
|
validate,
|
|
errors,
|
|
resetField
|
|
} = useField(props.groupName, props.validationRules, fieldOptions);
|
|
|
|
return {
|
|
errorMessage,
|
|
handleBlur,
|
|
handleChange,
|
|
validate,
|
|
meta,
|
|
errors,
|
|
resetField
|
|
};
|
|
},
|
|
data() {
|
|
return {
|
|
lastValuePushedToGa: null
|
|
};
|
|
},
|
|
computed: {
|
|
getFieldSetClasses() {
|
|
// eslint-disable-next-line no-nested-ternary
|
|
const baseClasses = this.isOverflowScrollable
|
|
? 'container-fluid overflow-scroll position-absolute px-5 pt-1 py-0'
|
|
: this.buttonTypeString === 'listCard'
|
|
? 'w-100'
|
|
: '';
|
|
|
|
const SmallQuestionTextClass = this.isSmallQuestionText
|
|
? `${baseClasses}small-question-text`
|
|
: baseClasses;
|
|
|
|
const SmallQuestionLabelText = this.isSmallQuestionLabelText
|
|
? `${SmallQuestionTextClass}small-question-label-text`
|
|
: SmallQuestionTextClass;
|
|
return SmallQuestionLabelText;
|
|
},
|
|
getComponentLoopWrapperClasses() {
|
|
let classes;
|
|
switch (this.buttonTypeString) {
|
|
case 'listButton':
|
|
classes = 'w-100';
|
|
break;
|
|
case 'listButtonHorizontal':
|
|
classes = 'd-flex flex-row p-0';
|
|
break;
|
|
case 'listCard':
|
|
classes =
|
|
'row g-2 g-md-5 justify-content-center mb-1 flex-nowrap';
|
|
if (this.isWide) {
|
|
classes += ' flex-column';
|
|
}
|
|
break;
|
|
case 'radio':
|
|
classes = 'ui-radio d-flex';
|
|
break;
|
|
case 'servicePackageRadio':
|
|
classes = 'package-main';
|
|
break;
|
|
case 'providerPrefRadio':
|
|
classes = 'option-main';
|
|
break;
|
|
default:
|
|
}
|
|
return classes;
|
|
},
|
|
getComponentWrapperClasses() {
|
|
let classes = '';
|
|
|
|
classes += this.isWide ? 'col-12' : 'col';
|
|
|
|
switch (this.buttonTypeString) {
|
|
case 'radio':
|
|
classes += ' radio-button-container';
|
|
break;
|
|
case 'servicePackageRadio':
|
|
classes = 'package-wrapper';
|
|
break;
|
|
case 'providerPrefRadio':
|
|
classes = 'option-wrapper';
|
|
break;
|
|
default:
|
|
}
|
|
// Determine number of buttons and add class accordingly.
|
|
// This is to accommodate unique spacing per design specs.
|
|
if (
|
|
this.buttonTypeString === 'listCard'
|
|
&& this.buttonsInfo.length > 2
|
|
) {
|
|
classes += ' two-list-card-width';
|
|
}
|
|
|
|
if (
|
|
this.buttonTypeString === 'listCard'
|
|
&& this.buttonsInfo.length === 1
|
|
) {
|
|
classes += ' one-list-card-width';
|
|
}
|
|
|
|
return classes;
|
|
},
|
|
buttonsInfo() {
|
|
return (Array.isArray(this.answers) ? this.answers : [])?.map((answer) => ({
|
|
buttonLabel: answer.buttonLabel ?? answer.Text ?? answer,
|
|
altText:
|
|
answer.altText ?? (answer.Name ? answer.Name : answer),
|
|
buttonLabelSubCopy:
|
|
answer.buttonLabelSubCopy ?? answer.SubText,
|
|
buttonBodyCopy:
|
|
answer.buttonBodyCopy ?? answer.buttonBodyCopy,
|
|
buttonAuxiliaryCopy:
|
|
answer.buttonAuxiliaryCopy
|
|
?? answer.buttonAuxiliaryCopy,
|
|
buttonFooterCopy:
|
|
answer.buttonFooterCopy ?? answer.buttonFooterCopy,
|
|
buttonImage: answer.buttonImage ?? answer.AnswerImageUrl,
|
|
buttonImageId: answer.buttonImageId ?? answer.ImageId,
|
|
groupName: this.formatString(this.groupName),
|
|
value:
|
|
answer.value
|
|
?? (this.useTextForValue && answer.Text
|
|
? answer.Text
|
|
: answer.Name)
|
|
?? (typeof answer !== 'object' ? answer : null)
|
|
}));
|
|
},
|
|
selectedValues: {
|
|
get() {
|
|
return this.modelValue;
|
|
},
|
|
set(selectedAnswers) {
|
|
this.$emit('update:modelValue', selectedAnswers);
|
|
}
|
|
}
|
|
},
|
|
watch: {
|
|
modelValue(newValue) {
|
|
this.resetField({
|
|
value: newValue
|
|
});
|
|
},
|
|
answers() {
|
|
this.resetField({
|
|
value: this.modelValue
|
|
});
|
|
}
|
|
},
|
|
beforeMount() {
|
|
if (this.buttonTypeObject) {
|
|
this.$options.components[this.buttonTypeString] =
|
|
this.buttonTypeObject;
|
|
}
|
|
},
|
|
beforeUnmount() {
|
|
this.resetField({
|
|
value: ''
|
|
});
|
|
},
|
|
methods: {
|
|
formatString(str) {
|
|
return String(str).replaceAll(' ', '-');
|
|
},
|
|
setLastValuePushedToGa(lastValuePushedToGa) {
|
|
this.lastValuePushedToGa = lastValuePushedToGa;
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.button-question {
|
|
color: $black;
|
|
|
|
.radio-button-container {
|
|
&:not(:last-child) {
|
|
padding-bottom: map-get($spacers, 2);
|
|
}
|
|
}
|
|
|
|
.question-text {
|
|
margin-bottom: 1rem;
|
|
font-size: 1rem;
|
|
line-height: 1.625rem;
|
|
|
|
span {
|
|
&.select-all-glass-parts {
|
|
color: #525656;
|
|
}
|
|
&.button-question-text-left {
|
|
text-align: left;
|
|
}
|
|
}
|
|
}
|
|
|
|
&.provider-preference {
|
|
.question-text {
|
|
span {
|
|
text-align: left;
|
|
}
|
|
}
|
|
}
|
|
|
|
&:has(.list-button-horizontal) {
|
|
.question-text {
|
|
margin-bottom: .25rem;
|
|
font-weight: 600;
|
|
}
|
|
fieldset {
|
|
border-radius: $border-radius-list-button;
|
|
box-shadow: $box-shadow-input;
|
|
}
|
|
}
|
|
}
|
|
|
|
.small-question-text {
|
|
&.question-text {
|
|
span {
|
|
font-size: 0.875rem;
|
|
text-align: left;
|
|
margin: 0 0 0.5rem 0;
|
|
}
|
|
}
|
|
&.question-text {
|
|
margin: 0;
|
|
}
|
|
&fieldset {
|
|
.ui-radio {
|
|
margin: 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
.small-question-label-text {
|
|
&.question-text {
|
|
span {
|
|
text-align: left;
|
|
margin: 0 0 0.25rem 0;
|
|
}
|
|
}
|
|
&.question-text {
|
|
margin: 0;
|
|
}
|
|
&fieldset {
|
|
.ui-radio {
|
|
margin: 0;
|
|
}
|
|
}
|
|
}
|
|
</style>
|