commit
This commit is contained in:
parent
59fab86ceb
commit
f20758ee5b
11 changed files with 502 additions and 373 deletions
170
src/common-components/base-input-button/base-input-button.vue
Normal file
170
src/common-components/base-input-button/base-input-button.vue
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
<template>
|
||||
<label
|
||||
:class="[buttonWrapperClasses, { 'has-error': errors.length > 0 }]"
|
||||
:for="buttonId"
|
||||
@focusin="handleFocus"
|
||||
@focusout="handleBlur"
|
||||
@mousedown.left="handleEventAction(eventTypes.CLICK, $event)">
|
||||
<input
|
||||
:type="inputType"
|
||||
:id="buttonId"
|
||||
:key="buttonId"
|
||||
:name="groupName"
|
||||
:class="inputClasses"
|
||||
:aria-required="isRequired"
|
||||
:value="value"
|
||||
:checked="isChecked"
|
||||
@keypress.space="handleEventAction(eventTypes.SPACE, $event)"
|
||||
@keypress.enter="handleEventAction(eventTypes.ENTER, $event)"
|
||||
@change="handleEventAction(eventTypes.CHANGE, $event)" />
|
||||
|
||||
<slot></slot>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useField } from "vee-validate";
|
||||
import { toRef } from "vue";
|
||||
import {
|
||||
handleButtonComponentFocus,
|
||||
handleInputComponentBlur,
|
||||
} from "@/helpers/button-question-focus-helper";
|
||||
import { inputButtonProps } from "@/common-components/base-input-button/button-functionality-props";
|
||||
|
||||
export default {
|
||||
name: "base-input-button",
|
||||
props: {
|
||||
...inputButtonProps,
|
||||
buttonWrapperClasses: [String, Array, Object],
|
||||
inputClasses: [String, Array, Object],
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
valueToEmit: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (this.isChecked) {
|
||||
this.handleChange(this.modelValue);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleEventAction(eventType, e) {
|
||||
if (this.isMultiSelect) {
|
||||
switch (eventType) {
|
||||
case this.eventTypes.CHANGE:
|
||||
this.handleClick(e);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (eventType) {
|
||||
case this.eventTypes.CLICK:
|
||||
case this.eventTypes.ENTER:
|
||||
case this.eventTypes.SPACE:
|
||||
this.handleClick(e);
|
||||
break;
|
||||
case this.eventTypes.CHANGE:
|
||||
this.selectingInitiatesLoad
|
||||
? this.handleSelectionChange(e)
|
||||
: this.handleClick(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
handleSelectionChange(e) {
|
||||
if (
|
||||
this.isMultiSelect &&
|
||||
(this.modelValue instanceof Array || this.modelValue == null)
|
||||
) {
|
||||
let newValue = this.modelValue ? [...this.modelValue] : [];
|
||||
if (!newValue.includes(this.value)) {
|
||||
newValue.push(this.value);
|
||||
} else {
|
||||
newValue.splice(newValue.indexOf(this.value), 1);
|
||||
}
|
||||
|
||||
this.valueToEmit = newValue;
|
||||
} else if (!this.isMultiSelect) {
|
||||
this.valueToEmit = this.value;
|
||||
}
|
||||
|
||||
this.handleChange(this.valueToEmit);
|
||||
},
|
||||
handleClick(e) {
|
||||
this.handleSelectionChange(e);
|
||||
this.$emit("update:modelValue", this.valueToEmit);
|
||||
},
|
||||
handleFocus() {
|
||||
handleButtonComponentFocus({
|
||||
groupName: this.groupName,
|
||||
});
|
||||
},
|
||||
handleBlur() {
|
||||
handleInputComponentBlur({
|
||||
groupName: this.groupName,
|
||||
onButtonQuestionLostFocusCallback: this.handlePushClickEventToGACheck,
|
||||
});
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isChecked() {
|
||||
if (this.isMultiSelect && this.modelValue instanceof Array) {
|
||||
return this.modelValue.includes(this.value);
|
||||
} else if (!this.isMultiSelect) {
|
||||
return this.modelValue == this.value;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
inputType() {
|
||||
return this.isMultiSelect ? "checkbox" : "radio";
|
||||
},
|
||||
buttonId() {
|
||||
return `${this.groupName?.replace(" ", "-")}-${this.value
|
||||
?.toString()
|
||||
?.replace(" ", "-")}`;
|
||||
},
|
||||
isValueSelectedOnClick() {
|
||||
return this.isMultiSelect || this.selectingInitiatesLoad;
|
||||
},
|
||||
eventTypes() {
|
||||
return {
|
||||
CHANGE: "change",
|
||||
ENTER: "enter",
|
||||
SPACE: "space",
|
||||
CLICK: "click",
|
||||
};
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const inputType = props.isMultiSelect ? "checkbox" : "radio";
|
||||
|
||||
const fieldOptions = {
|
||||
type: inputType,
|
||||
validateOnValueUpdate: false,
|
||||
validateOnMount: false,
|
||||
};
|
||||
|
||||
const { handleChange, meta, errors } = useField(
|
||||
toRef(props, "groupName"),
|
||||
toRef(props, "validationRules"),
|
||||
fieldOptions
|
||||
);
|
||||
|
||||
return {
|
||||
handleChange,
|
||||
errors,
|
||||
meta,
|
||||
fieldOptions, // only need to expose this for unit test purposes
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
input {
|
||||
opacity: 0;
|
||||
height: 0.1px; // NOTE: cannot be zero or Safari can't put focus on it
|
||||
width: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
export const inputButtonProps = {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
modelValue: {
|
||||
type: [Array, String, Number],
|
||||
required: true,
|
||||
},
|
||||
isMultiSelect: Boolean,
|
||||
groupName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
validationRules: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
valueToLogType: String,
|
||||
isRequired: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
lastValuePushedToGa: [String, Number],
|
||||
setLastValuePushedToGa: Function,
|
||||
selectingInitiatesLoad: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
};
|
||||
|
|
@ -25,14 +25,6 @@ export default {
|
|||
headers: headers,
|
||||
})
|
||||
.then((response) => {
|
||||
if (logApiCall) {
|
||||
analyticsMixIn.methods.pushEventToGA(
|
||||
GaCategories.API_RESPONSE,
|
||||
GaActions.RESULT,
|
||||
`${GaLabels.SUCCESS}_${endpoint}`,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
return resolve(response);
|
||||
},
|
||||
|
|
|
|||
38
src/helpers/button-question-focus-helper.js
Normal file
38
src/helpers/button-question-focus-helper.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Helper for GA click event. When the user mouse clicks on a `base-input-button`, we
|
||||
* push the click event. When the user tabs through a list of radio buttons via a
|
||||
* keyboard, we only want to push the GA click event if the selection was deliberate
|
||||
* (space/enter key) or if there is a selection and the user tabs off of the radio group.
|
||||
*/
|
||||
|
||||
let lastFocusedInputGroupName = "";
|
||||
let onButtonQuestionLostFocusCallback = null;
|
||||
|
||||
const handleAnyComponentFocus = (e) => {
|
||||
const targetType = e.target.type;
|
||||
if (targetType !== "radio" && targetType !== "checkbox") {
|
||||
invokeButtonQuestionLostFocusCallback();
|
||||
}
|
||||
};
|
||||
|
||||
const handleButtonComponentFocus = (e) => {
|
||||
if (e && lastFocusedInputGroupName !== e.groupName) {
|
||||
invokeButtonQuestionLostFocusCallback();
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputComponentBlur = (e) => {
|
||||
if (e) {
|
||||
lastFocusedInputGroupName = e.groupName;
|
||||
onButtonQuestionLostFocusCallback = e.onButtonQuestionLostFocusCallback;
|
||||
}
|
||||
};
|
||||
|
||||
const invokeButtonQuestionLostFocusCallback = () => {
|
||||
if (onButtonQuestionLostFocusCallback) {
|
||||
onButtonQuestionLostFocusCallback();
|
||||
}
|
||||
};
|
||||
|
||||
export { handleAnyComponentFocus, handleButtonComponentFocus, handleInputComponentBlur };
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ export default {
|
|||
? this.answersFromCms.filter((ans) => {
|
||||
const name = ans.Name.split("-");
|
||||
return (
|
||||
name[0].toUpperCase() === this.mainStore.getters.vehicle.category &&
|
||||
name[0].toUpperCase() === this.mainStore.vehicle.category &&
|
||||
this.damageOptionsMap[name[1]]
|
||||
);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ export default {
|
|||
? this.answersFromCms.filter((ans) => {
|
||||
const name = ans.Name.split("-");
|
||||
return this.filterByVehicleCategory
|
||||
? name[0].toUpperCase() === this.mainStore.getters.vehicle.category &&
|
||||
? name[0].toUpperCase() === this.mainStore.vehicle.category &&
|
||||
this.replaceOptions.includes(name[1])
|
||||
: this.replaceOptions.includes(ans.Name);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ export default {
|
|||
const filteredAnswers = Array.isArray(this.answersFromCms)
|
||||
? this.answersFromCms.filter((ans) => {
|
||||
const name = ans.Name.split("-");
|
||||
return name[0].toUpperCase() === this.mainStore.getters.vehicle.category;
|
||||
return name[0].toUpperCase() === this.mainStore.vehicle.category;
|
||||
})
|
||||
: [];
|
||||
|
||||
|
|
|
|||
40
src/mixins/input-button-wrapper-mixin.js
Normal file
40
src/mixins/input-button-wrapper-mixin.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import { inputButtonProps } from "@/common-components/base-input-button/button-functionality-props";
|
||||
|
||||
export default {
|
||||
model: {
|
||||
prop: "modelValue",
|
||||
event: "change",
|
||||
},
|
||||
props: {
|
||||
...inputButtonProps,
|
||||
buttonLabel: [Number, String],
|
||||
buttonLabelSubCopy: String,
|
||||
buttonBodyCopy: String,
|
||||
buttonAuxillaryCopy: String,
|
||||
buttonFooterCopy: String,
|
||||
buttonImage: String,
|
||||
buttonImageId: String,
|
||||
altText: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
textPosition: String,
|
||||
screenReaderOnlyText: String,
|
||||
isWide: Boolean,
|
||||
additionalButtonStyling: String,
|
||||
},
|
||||
computed: {
|
||||
selectedValue: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(e) {
|
||||
if (this.preHandleAnswerChange) {
|
||||
this.preHandleAnswerChange(e);
|
||||
}
|
||||
|
||||
this.$emit("update:modelValue", e);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -60,10 +60,10 @@ router.afterEach((to, from) => {
|
|||
store.updateLastPageVisited(to.name);
|
||||
|
||||
// Push page view to GA
|
||||
analyticsMixin.methods.pushPageViewToGA();
|
||||
//analyticsMixin.methods.pushPageViewToGA();
|
||||
|
||||
// Push experiments to Data Layer
|
||||
analyticsMixin.methods.pushExperimentsToDataLayer();
|
||||
//analyticsMixin.methods.pushExperimentsToDataLayer();
|
||||
});
|
||||
|
||||
// Get route information by page name.
|
||||
|
|
|
|||
|
|
@ -204,8 +204,8 @@ export const useMainStore = defineStore({
|
|||
return globalMethods.callHttpClient({
|
||||
methods: endpoints.GetDamageOptions.method,
|
||||
endpoint: `${endpoints.GetDamageOptions.url}/${carId}`,
|
||||
});
|
||||
payload: {},
|
||||
});
|
||||
},
|
||||
setVehicle() {
|
||||
return globalMethods
|
||||
|
|
@ -215,13 +215,17 @@ export const useMainStore = defineStore({
|
|||
payload: {},
|
||||
})
|
||||
.then((response) => {
|
||||
updateVehicle(response.vehicle);
|
||||
this.updateVehicle(response.data);
|
||||
return response;
|
||||
});
|
||||
},
|
||||
|
||||
updateVehicle(vehicle) {
|
||||
this.order.vehicle = { ...this.order.vehicle, ...vehicle };
|
||||
this.order.vehicle.carId = vehicle.carId;
|
||||
this.order.vehicle.category = vehicle.category;
|
||||
this.order.vehicle.imageUrl = vehicle.imageUrl;
|
||||
this.order.vehicle.imageVifNumber = vehicle.imageVifNumber;
|
||||
this.order.vehicle.imageColor = vehicle.imageColor;
|
||||
},
|
||||
|
||||
resetVehicleState()
|
||||
|
|
|
|||
|
|
@ -1,399 +1,254 @@
|
|||
<template>
|
||||
<div :class="{'h-100': !isWide}">
|
||||
<baseInputButton
|
||||
v-bind="$props"
|
||||
:buttonWrapperClasses="[
|
||||
'list-card w-100 rounded-3 d-flex align-items-center h-100',
|
||||
{ horizontal: isWide },
|
||||
]"
|
||||
v-model="selectedValue">
|
||||
<div
|
||||
class="list-card w-100 rounded-3 d-flex align-items-center"
|
||||
:class="[
|
||||
'h-100',
|
||||
isWide ? 'horizontal' : '',
|
||||
(errors.length > 0 || hasError) ? 'has-error' : '',
|
||||
]"
|
||||
@keyup.space="triggerButton"
|
||||
@keyup.up="handleKeyupArrow"
|
||||
@keyup.down="handleKeyupArrow"
|
||||
@keyup.left="handleKeyupArrow"
|
||||
@keyup.right="handleKeyupArrow"
|
||||
>
|
||||
<input
|
||||
:type="isMultiSelect ? 'checkbox' : 'radio'"
|
||||
:id="buttonID"
|
||||
:name="groupName"
|
||||
:value="value"
|
||||
:aria-required="isRequired"
|
||||
v-model="checkValue"
|
||||
:checked="checkValue"
|
||||
@change="handleInputChange"
|
||||
/>
|
||||
<label
|
||||
tabindex="-1"
|
||||
:for="buttonID"
|
||||
:aria-label="buttonLabel"
|
||||
class="d-flex w-100 align-items-center px-2 h-100"
|
||||
:class="getLabelClasses"
|
||||
@mouseup="triggerButton"
|
||||
>
|
||||
class="d-flex w-100 align-items-center px-2 h-100 list-card-content button-content rounded-3"
|
||||
:class="labelClasses">
|
||||
<img
|
||||
:id="buttonImageId"
|
||||
:class="!isWide ? 'order-1' : 'ms-auto order-3'"
|
||||
:src="buttonImage"
|
||||
:alt="altText"
|
||||
/>
|
||||
<p
|
||||
v-if="!isWide"
|
||||
class="small order-3"
|
||||
:class="isMultiSelect ? 'm-0' : 'mt-2 mb-0'"
|
||||
>
|
||||
{{ buttonLabel }}
|
||||
:id="buttonImageId"
|
||||
:class="!isWide ? 'order-1' : 'ms-auto order-3'"
|
||||
:src="buttonImage"
|
||||
:alt="altText" />
|
||||
<p v-if="!isWide" class="small order-3" :class="isMultiSelect ? 'm-0' : 'mt-2 mb-0'">
|
||||
{{ buttonLabel }}
|
||||
</p>
|
||||
<p
|
||||
v-if="buttonLabelSubCopy && !isWide"
|
||||
class="fs-7 m-0 order-4 sub-copy"
|
||||
>
|
||||
{{ buttonLabelSubCopy }}
|
||||
<p v-if="buttonLabelSubCopy && !isWide" class="fs-7 m-0 order-4 sub-copy">
|
||||
{{ buttonLabelSubCopy }}
|
||||
</p>
|
||||
<div v-if="isWide" class="order-2">
|
||||
<p class="m-0 small">{{ buttonLabel }}</p>
|
||||
<p v-if="buttonLabelSubCopy" class="m-0 fs-7 sub-copy">
|
||||
{{ buttonLabelSubCopy }}
|
||||
</p>
|
||||
<p class="m-0 small">{{ buttonLabel }}</p>
|
||||
<p v-if="buttonLabelSubCopy" class="m-0 fs-7 sub-copy">
|
||||
{{ buttonLabelSubCopy }}
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useField } from "vee-validate";
|
||||
import { toRef } from "vue";
|
||||
|
||||
export default {
|
||||
name: "listCard",
|
||||
props: {
|
||||
isMultiSelect: Boolean, //Defines use as checkbox
|
||||
isWide: Boolean,
|
||||
buttonImage: String, //Required: File name of image
|
||||
buttonImageId: String,
|
||||
buttonLabel: String, //Required: Label text
|
||||
isRequired: Boolean, //Required: is aria-required required or not?
|
||||
altText: String, //Leave empty. Screen readers read the buttonLabel text. If alt has content, it will repeat unnecessarily.
|
||||
buttonID: String, //Required: Unique
|
||||
groupName: String, //Rquired: Unique
|
||||
buttonLabelSubCopy: String, //Optional: sub text
|
||||
value: {
|
||||
// Field initial value
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
colLength: String,
|
||||
validationRules: String,
|
||||
selectedValues: [Array, String],
|
||||
hasError: Boolean,
|
||||
valueToLogType: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
checkValue: null,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (Array.isArray(this.validateValue)) {
|
||||
this.checkValue = this.isValueSelectedByArray(this.selectedValues);
|
||||
const isSelectedByValidator = this.isValueSelectedByArray(this.validateValue);
|
||||
|
||||
if (this.checkValue != isSelectedByValidator) {
|
||||
this.handleChange(this.value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.checkValue = this.selectedValues == this.value;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getLabelClasses() {
|
||||
if (this.isWide) {
|
||||
let classes = "flex-row py-2 ps-4 pe-4";
|
||||
if (this.buttonLabelSubCopy) {
|
||||
classes += " checkboxTop";
|
||||
</baseInputButton>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import baseInputButton from "@/common-components/base-input-button/base-input-button";
|
||||
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||
|
||||
export default {
|
||||
name: "listCard",
|
||||
mixins: [inputButtonWrapperMixin],
|
||||
components: {
|
||||
baseInputButton,
|
||||
},
|
||||
computed: {
|
||||
labelClasses() {
|
||||
if (this.isWide) {
|
||||
let classes = "flex-row py-2 ps-4 pe-4";
|
||||
if (this.buttonLabelSubCopy) {
|
||||
classes += " checkboxTop";
|
||||
}
|
||||
return classes;
|
||||
} else {
|
||||
return "flex-column pt-4 pb-3";
|
||||
}
|
||||
return classes;
|
||||
} else {
|
||||
return "flex-column pt-4 pb-3";
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isValueSelectedByArray(arr) {
|
||||
return this.isMultiSelect
|
||||
? arr.includes(this.value)
|
||||
: arr[0];
|
||||
},
|
||||
handleInputChange() {
|
||||
if(!this.selectingInitiatesLoad) {
|
||||
this.handleCheckChange();
|
||||
}
|
||||
},
|
||||
handleKeyupArrow() {
|
||||
if (this.isMultiSelect) {
|
||||
return; // Prevent arrow keys from doing anything if element is a checkbox
|
||||
}
|
||||
|
||||
if(!this.selectingInitiatesLoad) {
|
||||
this.handleCheckChange();
|
||||
}
|
||||
},
|
||||
triggerButton() {
|
||||
if(this.selectingInitiatesLoad) {
|
||||
this.displayLoader();
|
||||
this.handleCheckChange();
|
||||
}
|
||||
|
||||
},
|
||||
handleCheckChange() {
|
||||
const emitEvent = {
|
||||
checkValue: this.checkValue, // only read on checkboxes, on handleCheckedChanged on button-question
|
||||
value: this.value.toString(),
|
||||
buttonId: this.buttonID && this.buttonID.toString(),
|
||||
};
|
||||
|
||||
this.handleChange(this.value);
|
||||
this.$emit("isCheckedChanged", emitEvent);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// Changing this will impact pre-selection data loads on vehicle-parts.
|
||||
// If changed, please regression test that vehicle-parts data still loads correctly with previous selections.
|
||||
selectedValues(newVal) {
|
||||
if (typeof newVal === "string") {
|
||||
this.checkValue = newVal == this.value;
|
||||
}
|
||||
else if (newVal !== undefined) {
|
||||
this.checkValue = newVal.value;
|
||||
}
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const inputType = props.isMultiSelect ? "checkbox" : "radio";
|
||||
|
||||
const fieldOptions = {
|
||||
type: inputType,
|
||||
checkedValue: props.value, // EX: "Single" or "Passenger"
|
||||
potentialInitialValue: props.selectedValues,
|
||||
};
|
||||
|
||||
// Set initialValue for validation setup if pre-selected
|
||||
// NOTE: props.selectedValues could be an array of strings, or an array of integers...
|
||||
if (props.selectedValues && (props.selectedValues.includes(props.value) || props.selectedValues.includes(parseInt(props.value)))) {
|
||||
fieldOptions['initialValue'] = fieldOptions.potentialInitialValue;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@mixin list-card-focus($box-shadow-color) {
|
||||
&:focus-visible + .list-card-content {
|
||||
box-shadow: 0 0 0 2.5px $box-shadow-color;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
&:focus + .list-card-content {
|
||||
box-shadow: 0 0 0 2.5px $box-shadow-color;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
&:checked:focus + .list-card-content {
|
||||
box-shadow: 0 0 0 2.5px $box-shadow-color;
|
||||
}
|
||||
}
|
||||
.list-card {
|
||||
border: 1px solid $gray-500;
|
||||
|
||||
&.has-error {
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
@include list-card-focus($red);
|
||||
}
|
||||
|
||||
const {
|
||||
handleChange,
|
||||
errors,
|
||||
value
|
||||
} = useField(toRef(props, "groupName"), toRef(props, "validationRules"), fieldOptions);
|
||||
|
||||
// First land on the blank, unselected page, no handleChange
|
||||
// Land on page with initial values, handleChange
|
||||
const validateValue = value;
|
||||
return {
|
||||
handleChange,
|
||||
errors,
|
||||
validateValue,
|
||||
fieldOptions, // only need to expose this for unit test purposes
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.list-card {
|
||||
border: 1px solid $gray-500;
|
||||
|
||||
&.invalid {
|
||||
//Red border if invalid
|
||||
border: 1px solid $red;
|
||||
}
|
||||
|
||||
img {
|
||||
}
|
||||
|
||||
img {
|
||||
// svg's should be constructed on the same canvas size/viewbox to ensure they occupy the same space in the DOM. This will allow easy/proper alignment of elements. See exisitng svg's for examples.
|
||||
height: auto;
|
||||
width: 6.5rem;
|
||||
margin-bottom: 2.2rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@include media-breakpoint-up(sm) {
|
||||
box-shadow: 0px 0px 0px 4px $blue-300;
|
||||
border: 1px solid transparent;
|
||||
box-shadow: 0px 0px 0px 4px $blue-300;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0.1px; // NOTE: cannot be zero or safari can't put focus on it
|
||||
}
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
position: absolute;
|
||||
|
||||
+ label {
|
||||
outline: none;
|
||||
display: block;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
p {
|
||||
color: $gray-600;
|
||||
text-align: center;
|
||||
|
||||
&.sub-copy {
|
||||
color: $gray-550;
|
||||
|
||||
+ .list-card-content {
|
||||
outline: none;
|
||||
display: block;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
p {
|
||||
color: $gray-600;
|
||||
text-align: center;
|
||||
|
||||
&.sub-copy {
|
||||
color: $gray-550;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:checked + label {
|
||||
background: $blue-100;
|
||||
box-shadow: 0 0 0 1px $blue;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
&:checked + .list-card-content {
|
||||
background: $blue-100;
|
||||
box-shadow: 0 0 0 1px $blue;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
&:focus-visible + label {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
@include list-card-focus($blue);
|
||||
|
||||
&:checked + .list-card-content {
|
||||
p {
|
||||
color: $black;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.sub-copy {
|
||||
color: $gray-600;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
&:focus + label {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
&:checked:focus + label {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:checked + label {
|
||||
p {
|
||||
color: $black;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.sub-copy {
|
||||
|
||||
+ .list-card-content::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: flex;
|
||||
margin: 0 auto;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin: 3rem 0 0 0;
|
||||
background: white;
|
||||
border: 1px solid $gray-500;
|
||||
border-radius: 2px;
|
||||
order: 2;
|
||||
flex-shrink: 0;
|
||||
color: $gray-600;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
+ label::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: flex;
|
||||
margin: 0 auto;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin: 3rem 0 0 0;
|
||||
background: white;
|
||||
border: 1px solid $gray-500;
|
||||
border-radius: 2px;
|
||||
order: 2;
|
||||
flex-shrink: 0;
|
||||
color: $gray-600;
|
||||
|
||||
+ .list-card-content.checkboxTop::before {
|
||||
margin: -1.25rem 0.5rem 0 0 !important;
|
||||
}
|
||||
|
||||
+ label.checkboxTop::before {
|
||||
margin: -1.25rem 0.5rem 0 0 !important;
|
||||
|
||||
+ .list-card-content.checkboxTop::after {
|
||||
margin: -1.5rem 0.5rem 0 0 !important;
|
||||
}
|
||||
|
||||
+ label.checkboxTop::after {
|
||||
margin: -1.5rem 0.5rem 0 0 !important;
|
||||
|
||||
&:checked + .list-card-content::before {
|
||||
background: $blue;
|
||||
}
|
||||
|
||||
&:checked + label::before {
|
||||
background: $blue;
|
||||
|
||||
&:checked + .list-card-content::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
margin: 3.2rem 0 0 0;
|
||||
border-left: 2px solid $white;
|
||||
border-bottom: 2px solid $white;
|
||||
height: 6px;
|
||||
width: 11px;
|
||||
transform: rotate(-45deg);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&:checked + label::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
margin: 3.2rem 0 0 0;
|
||||
border-left: 2px solid $white;
|
||||
border-bottom: 2px solid $white;
|
||||
height: 6px;
|
||||
width: 11px;
|
||||
transform: rotate(-45deg);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
+ .list-card-content::before {
|
||||
content: "";
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
+ label::before {
|
||||
content: "";
|
||||
display: none;
|
||||
|
||||
+ .list-card-content::after {
|
||||
content: "";
|
||||
display: none;
|
||||
}
|
||||
|
||||
+ label::after {
|
||||
content: "";
|
||||
display: none;
|
||||
|
||||
+ .list-card-content {
|
||||
img {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
+ label {
|
||||
img {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.horizontal {
|
||||
}
|
||||
|
||||
&.horizontal {
|
||||
img {
|
||||
margin-bottom: 0;
|
||||
width: 5.5rem;
|
||||
margin-bottom: 0;
|
||||
width: 5.5rem;
|
||||
}
|
||||
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
+ label::before {
|
||||
content: "";
|
||||
position: relative;
|
||||
margin: 0 0.5rem 0 0;
|
||||
order: 1;
|
||||
}
|
||||
|
||||
&:checked + label::after {
|
||||
content: "";
|
||||
margin: -0.15rem 0 0 0;
|
||||
left: 1.175rem;
|
||||
}
|
||||
|
||||
&:checked + label {
|
||||
p {
|
||||
color: $black;
|
||||
font-weight: 500;
|
||||
+ .list-card-content::before {
|
||||
content: "";
|
||||
position: relative;
|
||||
margin: 0 0.5rem 0 0;
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.sub-copy {
|
||||
color: $gray-600;
|
||||
font-weight: 400;
|
||||
|
||||
&:checked + .list-card-content::after {
|
||||
content: "";
|
||||
margin: -0.15rem 0 0 0;
|
||||
left: 1.175rem;
|
||||
}
|
||||
}
|
||||
|
||||
+ label {
|
||||
outline: none;
|
||||
min-height: 48px;
|
||||
color: $gray-600;
|
||||
|
||||
img {
|
||||
margin-bottom: 0;
|
||||
|
||||
&:checked + .list-card-content {
|
||||
p {
|
||||
color: $black;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.sub-copy {
|
||||
color: $gray-600;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
color: $gray-600;
|
||||
text-align: left;
|
||||
|
||||
&.sub-copy {
|
||||
color: $gray-550;
|
||||
}
|
||||
|
||||
+ .list-card-content {
|
||||
outline: none;
|
||||
min-height: 48px;
|
||||
color: $gray-600;
|
||||
|
||||
img {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
color: $gray-600;
|
||||
text-align: left;
|
||||
|
||||
&.sub-copy {
|
||||
color: $gray-550;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in a new issue