DigitalConsumer.FixMyGlass/src/digital-components/button-question/button-question.vue

393 lines
14 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 }">
<span class="fw-bold w-100" :class="[labelBold ? 'span-bold' : '']">
{{ questionText }}
</span>
</div>
<div class="w-100 d-flex justify-content-center">
<fieldset
class="w-100"
:aria-required="isRequired"
:class="getFieldSetClasses"
:role="isMultiSelect ? 'group' : 'radiogroup'"
:aria-labelledby="formatString(groupName)">
<legend
class="sr-only"
:data-focus-target="formatString(groupName)"
tabindex="-1"
:id="formatString(groupName)">
{{ questionText }}
{{
isMultiSelect && answers && answers.length > 1
? "Select one or more options below."
: "Select an option below."
}}
</legend>
<div :class="getComponentLoopWrapperClasses" class="button-inner-wrapper">
<div
:class="getComponentWrapperClasses"
v-for="answer in buttonsInfo"
:key="answer.value ? answer.value : answer">
<component
:is="buttonTypeString"
:buttonLabel="answer.buttonLabel"
:buttonLabelSubCopy="answer.buttonLabelSubCopy"
:buttonBodyCopy="answer.buttonBodyCopy"
:buttonAuxillaryCopy="answer.buttonAuxillaryCopy"
: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="answer.additionalButtonData"
:lastValuePushedToGa="lastValuePushedToGa"
:setLastValuePushedToGa="setLastValuePushedToGa"
:isInsuranceSelected="isInsuranceSelected"
:suppressError="suppressError"
:labelBold="labelBold"
@buttonEvent="handleButtonEvent"
v-model="selectedValues" />
<!-- 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
id="form-test-error"
class="row form-test-error"
:class="isErrorCentered ? 'text-center' : ''">
<error-message
role="comment"
aria-atomic="true"
aria-live="polite"
class="small mt-1"
:name="formatString(groupName)"
v-if="!suppressError"></error-message>
</div>
</div>
</template>
<script>
import listButton from "@/ux-components/list-button/list-button";
import listButtonHorizontal from "@/ux-components/list-button-horizontal/list-button-horizontal";
import listCard from "@/ux-components/list-card/list-card";
import radio from "@/ux-components/radio/radio";
import checkbox from "@/ux-components/checkbox/checkbox";
import { useField, ErrorMessage } from "vee-validate";
import { queryStrings } from "@/constants/query-strings";
export default {
name: "buttonQuestion",
props: {
buttonTypeString: {
type: String,
default: "listButton",
},
buttonTypeObject: {
type: Object,
default: null,
},
isMultiSelect: Boolean,
groupName: String,
questionText: String,
answers: Array,
textPosition: {
type: String,
default: "text-center",
},
selectingInitiatesLoad: Boolean,
loaderColor: {
type: String,
default: "blue",
},
loaderPosition: {
type: String,
default: "right",
},
isRequired: Boolean,
isOverflowScrollable: Boolean,
isWide: Boolean,
modelValue: [Array, Number, String],
value: [Number, String],
validationRules: String,
suppressError: Boolean,
useTextForValue: Boolean,
valueToLogType: String,
isSmallQuestionText: Boolean,
customButtonQuestionId: String,
logDisplayedValuesEvent: {
type: Boolean,
default: false,
},
isInsuranceSelected: Boolean,
labelBold: {
type: Boolean,
required: false,
default: false,
},
},
setup(props) {
const propsClone = Object.assign({}, props);
const modelValue = propsClone.modelValue;
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,
};
},
beforeMount() {
if (this.buttonTypeObject) {
this.$options.components[this.buttonTypeString] = this.buttonTypeObject;
}
},
data() {
return {
lastValuePushedToGa: null,
};
},
computed: {
getFieldSetClasses() {
const baseClasses = this.isOverflowScrollable
? "container-fluid overflow-scroll position-absolute px-5 pt-1 py-0"
: this.buttonTypeString == "listCard"
? "w-100"
: "";
const withSmallQuestionClass = this.isSmallQuestionText
? baseClasses + " small-question-text"
: baseClasses;
return withSmallQuestionClass;
},
getComponentLoopWrapperClasses() {
let classes;
switch (this.buttonTypeString) {
case "timeSlotModalListButton":
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 "addVapsButton":
classes = "row g-4 justify-content-center mb-2";
if (this.isWide) {
classes += " flex-column";
}
break;
case "radio":
classes = "ui-radio d-flex";
break;
case "checkbox":
classes = "ui-checkbox d-flex";
break;
case "servicePackageRadio":
classes = "package-main d-md-flex justify-content-md-center";
break;
}
return classes;
},
getComponentWrapperClasses() {
let classes = "";
classes += this.isWide ? "col-12" : "col";
if (this.buttonTypeString == "radio") {
classes += " radio-button-container";
} else if (this.buttonTypeString == "servicePackageRadio") {
classes = "package-wrapper col";
}
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;
},
isErrorCentered() {
if (this.buttonTypeString == "listCard" && this.buttonsInfo.length <= 2) {
return true;
}
return false;
},
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,
buttonAuxillaryCopy: answer.buttonAuxillaryCopy ?? answer.buttonAuxillaryCopy,
buttonFooterCopy: answer.buttonFooterCopy ?? answer.buttonFooterCopy,
buttonImage: answer.buttonImage ?? answer.AnswerImageUrl,
buttonImageId: answer.buttonImageId ?? answer.ImageId,
additionalButtonData: answer.additionalButtonData ?? answer.additionalButtonData,
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);
},
},
},
methods: {
formatString(str) {
return String(str).replaceAll(" ", "-");
},
setLastValuePushedToGa(lastValuePushedToGa) {
this.lastValuePushedToGa = lastValuePushedToGa;
},
handleButtonEvent(event) {
const eventName = event.eventName;
this.$emit(`buttonEvent.${eventName}`, event.args);
},
},
watch: {
modelValue(newValue, oldValue) {
this.resetField({
value: newValue,
});
},
answers() {
//once we get the answers to display from parent, see if we need a GA event to log what we showed
if (this.logDisplayedValuesEvent && this.answers.length > 0) {
var eventLabel = "";
//build comma separated list of all items in button list that we are going to display on page
this.answers.forEach((item) => {
if (item.Name) {
eventLabel += item.Name + ",";
}
if (item.buttonLabel) {
eventLabel += item.buttonLabel + ",";
}
});
eventLabel = eventLabel.slice(0, -1); //remove the last comma
this.pushEventToGA(
this.$route.query[queryStrings.FMG_PAGE],
this.GaActions.DISPLAYED,
eventLabel,
true
);
}
},
},
components: {
listButton,
listButtonHorizontal,
listCard,
ErrorMessage,
radio,
checkbox,
},
};
</script>
<style lang="scss">
.two-list-card-width {
width: 33.33333333%;
flex: 0 0 auto;
}
.button-question-overflow {
height: calc(100vh - 274px);
.overflow-scroll {
// Height will be determined by overall height of content above list
height: calc(100% - 314px);
overflow-x: hidden !important;
-webkit-overflow-scrolling: touch;
}
}
.button-question {
font-family: UrbanistSemibold;
color: $black;
.radio-button-container {
&:not(:last-child) {
padding-bottom: map-get($spacers, 2);
}
}
}
.question-text {
margin-top: 1.5rem;
margin-bottom: 1rem;
font-size: 1rem;
line-height: 1.625rem;
& > span {
text-align: center;
}
.span-bold {
font-weight: 600;
}
}
.small-question-text {
&.question-text {
span {
font-size: 0.875rem;
text-align: left;
margin: 1rem 0 0.5rem 0;
}
}
&.question-text {
margin: 0;
}
& fieldset {
.ui-radio {
margin: 0;
}
}
}
</style>