Merge branch 'develop' into feature/CSR-254
This commit is contained in:
commit
b23c483823
9 changed files with 285 additions and 81 deletions
|
|
@ -7,9 +7,9 @@
|
|||
<fieldset class="w-100" :class="getFieldSetClasses" role="radiogroup" :aria-labelledby="groupName ? groupName + '-radio-group' : ''">
|
||||
<legend class="sr-only">{{groupName}}</legend>
|
||||
<div :class="getComponentWrapperClasses">
|
||||
<component
|
||||
:is="buttonType"
|
||||
v-for="answer in answers"
|
||||
<component
|
||||
:is="buttonType"
|
||||
v-for="answer in answers"
|
||||
:key="answer.Name ? answer.Name : answer"
|
||||
@isCheckedChanged="handleCheckedChanged"
|
||||
:buttonID="answer.Name ? answer.Name : answer"
|
||||
|
|
@ -48,6 +48,7 @@ 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 { ErrorMessage } from 'vee-validate';
|
||||
import radio from "@/ux-components/radio/radio";
|
||||
|
||||
export default {
|
||||
name: "buttonQuestion",
|
||||
|
|
@ -104,6 +105,9 @@ export default {
|
|||
case 'listCard':
|
||||
classes = 'row justify-content-center g-2'
|
||||
break;
|
||||
case 'radio':
|
||||
classes = 'ui-radio d-flex'
|
||||
break;
|
||||
}
|
||||
return classes;
|
||||
},
|
||||
|
|
@ -143,6 +147,7 @@ export default {
|
|||
listButtonHorizontal,
|
||||
listCard,
|
||||
ErrorMessage,
|
||||
radio,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,12 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="col-12 link-col py-1">
|
||||
<textLink linkType="navigation" :text="backLink" @click-event="linkClick"/>
|
||||
<textLink
|
||||
linkType="navigation"
|
||||
:text="backLink"
|
||||
@click-event="linkClick"
|
||||
href="javascript:void(0)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -73,10 +78,10 @@ export default {
|
|||
this.buttonText = cmsContent.ForwardButtonText;
|
||||
},
|
||||
buttonClick() {
|
||||
this.$emit("forwardClicked");
|
||||
this.$emit("ForwardClicked");
|
||||
},
|
||||
linkClick() {
|
||||
this.$emit("backClicked");
|
||||
this.$emit("BackClicked");
|
||||
},
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<div class="container-fluid container-shadow p-2 rounded-3">
|
||||
<div class="row my-4">
|
||||
<div class="col">
|
||||
<h4 class="m-0 p-2 bg-light rounded">Inputs</h4>
|
||||
<h4 class="m-0 p-2 bg-light rounded">Checkbox</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
|
@ -15,6 +15,36 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-4">
|
||||
<div class="col">
|
||||
<h4 class="m-0 p-2 bg-light rounded">Radio Button</h4>
|
||||
<p class="m-0">Sample radio button group</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<radio
|
||||
groupName="radio-demo-2"
|
||||
buttonLabel="I'm a radio button!"
|
||||
buttonID="radio-button-a"
|
||||
isRequired
|
||||
value="I'm a radio button!"
|
||||
modelValue="Model Value 1"
|
||||
screenReaderOnlyText="Model Value 1"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<radio
|
||||
groupName="radio-demo-2"
|
||||
buttonLabel="I'm also a radio button!"
|
||||
buttonID="radio-button-b"
|
||||
isRequired
|
||||
value="I'm also a radio button!"
|
||||
modelValue="Model Value 2"
|
||||
screenReaderOnlyText="Model Value 2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-4">
|
||||
<div class="col">
|
||||
<h4 class="m-0 p-2 bg-light rounded">Buttons</h4>
|
||||
|
|
@ -50,14 +80,17 @@
|
|||
<textLink
|
||||
linkType=navigation
|
||||
text="Navigation Link"
|
||||
href="#!"
|
||||
/><br><br>
|
||||
<textLink
|
||||
linkType="text"
|
||||
text="Default Link"
|
||||
href="#!"
|
||||
/><br><br>
|
||||
<textLink
|
||||
linkType=textSmall
|
||||
text="Small Link"
|
||||
href="#!"
|
||||
/><br><br>
|
||||
<textLink
|
||||
linkType=footer
|
||||
|
|
@ -926,6 +959,7 @@ import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
|||
import funnelHeader from "@/common-components/funnel-header/funnel-header";
|
||||
import listButtonHorizontal from "@/ux-components/list-button-horizontal/list-button-horizontal";
|
||||
import checkbox from "@/ux-components/checkbox/checkbox";
|
||||
import radio from "@/ux-components/radio/radio";
|
||||
import textLink from "@/ux-components/text-link/text-link";
|
||||
export default {
|
||||
name: "App",
|
||||
|
|
@ -938,6 +972,7 @@ export default {
|
|||
vehicleBanner,
|
||||
listButtonHorizontal,
|
||||
checkbox,
|
||||
radio,
|
||||
funnelHeader,
|
||||
textLink
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,51 +3,9 @@
|
|||
<funnelHeader ref="funnelHeader" />
|
||||
<vehicleBanner ref="vehicleBanner" :displayGenericVehicleImage=false />
|
||||
<funnelSubHeader ref="funnelSubHeader" />
|
||||
<Form
|
||||
@submit="onSubmit"
|
||||
@invalid-submit="onInvalidSubmit"
|
||||
ref="theForm"
|
||||
v-slot="{ values, meta, errors }"
|
||||
>
|
||||
<damageLocationQuestion
|
||||
ref="damageLocation"
|
||||
v-model="selectedDamageLocations"
|
||||
groupName="DamageLocationQuestion"
|
||||
/>
|
||||
<windshieldOptions
|
||||
ref="windshieldOptions"
|
||||
:showWindshieldDamageTypeQuestion="values.DamageLocationQuestion && values.DamageLocationQuestion.toString().includes('-Windshield')"
|
||||
:suppressError="errors.windshieldDamageTypeQuestion && errors.windshieldDamageTypeQuestion.startsWith('checkForRepairAndReplace')"
|
||||
/>
|
||||
<alert
|
||||
v-if="errors.windshieldDamageTypeQuestion && errors.windshieldDamageTypeQuestion.startsWith('checkForRepairAndReplace')"
|
||||
alertClass="alert-danger"
|
||||
alertHeadline="You'll need to schedule separate appointments"
|
||||
alertCopy="Vehicle service requiring both glass repair and replacement must be scheduled separately, as they're performed by different technicians."
|
||||
:isDismissible="false"
|
||||
/>
|
||||
<replaceOptionsQuestion
|
||||
ref="driverSideOptions"
|
||||
isAvailable
|
||||
v-model="driverSideOptionsData"
|
||||
groupName="DriverSideReplaceOptionsQuestion"
|
||||
/>
|
||||
<funnel-footer
|
||||
ref="funnelFooter"
|
||||
:isDisabled="!meta.valid"
|
||||
/>
|
||||
|
||||
<!-- KEEP TEMPORARILY FOR TROUBLESHOOTING VALIDATION
|
||||
<div class="g-2 mt-6 mx-4 w-25 position-fixed fixed-top">
|
||||
<p>Current Form, values:</p>
|
||||
<pre>{{ values }}</pre>
|
||||
<p>Errors:</p>
|
||||
<pre>{{ errors }}</pre>
|
||||
<p>Is Form Valid? (Meta.valid):</p>
|
||||
<pre>{{ meta.valid }}</pre>
|
||||
</div> -->
|
||||
|
||||
</Form>
|
||||
<damageLocationQuestion ref="damageLocation" v-model="selectedDamageLocations" groupName="DamageLocationQuestion" />
|
||||
<replaceOptionsQuestion ref="driverSideOptions" isAvailable v-model="driverSideOptionsData" groupName="DriverSideReplaceOptionsQuestion" />
|
||||
<funnelFooter ref="funnelFooter" @back-clicked="backButtonAction" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -59,11 +17,6 @@ import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
|||
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
|
||||
import replaceOptionsQuestion from "@/layouts/vehicle-damage/replace-options-question/replace-options-question";
|
||||
import damageLocationQuestion from "@/layouts/vehicle-damage/damage-location-question/damage-location-question";
|
||||
import windshieldOptions from "@/layouts/vehicle-damage/windshield-options/windshield-options";
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
|
||||
import { Form } from 'vee-validate';
|
||||
|
||||
|
||||
// Supporting files
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
|
|
@ -127,15 +80,12 @@ export default {
|
|||
}
|
||||
},
|
||||
components: {
|
||||
Form,
|
||||
funnelHeader,
|
||||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
damageLocationQuestion,
|
||||
replaceOptionsQuestion,
|
||||
funnelFooter,
|
||||
windshieldOptions,
|
||||
alert,
|
||||
},
|
||||
methods: {
|
||||
arePagePrerequisitesValid() {
|
||||
|
|
@ -145,24 +95,21 @@ export default {
|
|||
// Invokes
|
||||
store.dispatch(storeActions.RESET_PARTS_AND_DEPS);
|
||||
},
|
||||
onSubmit(values) {
|
||||
window.alert('Success! Submitted values: ' + JSON.stringify(values, null ,2))
|
||||
console.log('submitted: ', JSON.stringify(values, null ,2));
|
||||
},
|
||||
onInvalidSubmit({ values, errors, results }) {
|
||||
// identify the first error field and put focus on it
|
||||
console.log("values: ", values);
|
||||
console.log("errors: ", errors);
|
||||
console.log("results: ", results);
|
||||
// get error names array
|
||||
const errorNames = errors ? Object.keys(errors) : [];
|
||||
const firstErrorEl = errorNames[0];
|
||||
if (firstErrorEl) {
|
||||
console.log('firstErrorEl: ', firstErrorEl);
|
||||
const qsString = "[data-focus-target='" + firstErrorEl + "']";
|
||||
document.querySelector(qsString).focus();
|
||||
}
|
||||
backButtonAction() {
|
||||
// route to move backwards
|
||||
this.$router.navigate(
|
||||
this.navigationScenarios.CLICKED_BACK,
|
||||
this.$route
|
||||
);
|
||||
},
|
||||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
replaceOptionsQuestion,
|
||||
damageLocationQuestion,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -50,6 +50,15 @@ const routingTable = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
|
||||
maps: [
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_BACK,
|
||||
destinationFmgPageValue: fmgPageValues.VEHICLE_STYLE,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export { routingTable };
|
||||
|
|
|
|||
|
|
@ -44,12 +44,15 @@ export default {
|
|||
+ label {
|
||||
display: block;
|
||||
position: relative;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
+ label::before {
|
||||
content: "";
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
margin-right: 8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: white;
|
||||
|
|
@ -71,7 +74,7 @@ export default {
|
|||
transform: rotate(-45deg);
|
||||
}
|
||||
&:hover + label::before {
|
||||
box-shadow: 0 0 0 4px $blue-100;
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
}
|
||||
&:focus + label::before {
|
||||
box-shadow: 0 0 0 2px $blue;
|
||||
|
|
|
|||
63
src/ux-components/radio/radio.spec.js
Normal file
63
src/ux-components/radio/radio.spec.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import radio from "./radio";
|
||||
import { nextTick } from "vue";
|
||||
|
||||
describe("radio.vue", () => {
|
||||
it("Should return group name", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(radio, {
|
||||
propsData: {
|
||||
groupName: "radio-button-test",
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const input = wrapper.find("input");
|
||||
|
||||
// Expect
|
||||
expect(input.attributes().name).toEqual("radio-button-test");
|
||||
});
|
||||
|
||||
it("Should return checkbox id", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(radio, {
|
||||
propsData: {
|
||||
buttonID: "Radio ID",
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const input = wrapper.find("input");
|
||||
|
||||
// Expect
|
||||
expect(input.attributes().id).toEqual("Radio ID");
|
||||
});
|
||||
|
||||
it("Should return label text", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(radio, {
|
||||
propsData: {
|
||||
buttonLabel: "label text",
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const paragraph = wrapper.find("p");
|
||||
|
||||
expect(paragraph.text()).toEqual("label text");
|
||||
});
|
||||
|
||||
it("Should return label text", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(radio, {
|
||||
propsData: {
|
||||
screenReaderOnlyText: "screenreader text",
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const paragraph = wrapper.find("span");
|
||||
|
||||
expect(paragraph.text()).toEqual("screenreader text");
|
||||
});
|
||||
});
|
||||
138
src/ux-components/radio/radio.vue
Normal file
138
src/ux-components/radio/radio.vue
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
<template>
|
||||
<!-- Checkbox groups MUST be wrapped in a <fieldset> and <legend> tag and must contain tabindex -->
|
||||
<div class="ui-radio d-flex">
|
||||
<input
|
||||
type="radio"
|
||||
aria-checked="false"
|
||||
:name="groupName"
|
||||
:id="buttonID"
|
||||
:aria-required="isRequired"
|
||||
:value="value"
|
||||
:v-model="checkValue"
|
||||
@change="handleCheckChanged"
|
||||
/>
|
||||
<label class="d-flex align-items-center" :for="buttonID">
|
||||
<p v-if="buttonLabel" class="m-0">{{ buttonLabel }}</p>
|
||||
<span v-if="screenReaderOnlyText" class="sr-only">{{
|
||||
screenReaderOnlyText
|
||||
}}</span>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { toRefs } from "vue";
|
||||
import { useField } from "vee-validate";
|
||||
export default {
|
||||
name: "radio",
|
||||
props: {
|
||||
groupName: String,
|
||||
buttonLabel: String,
|
||||
buttonID: String,
|
||||
isRequired: Boolean,
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: "",
|
||||
},
|
||||
screenReaderOnlyText: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
checkValue: Boolean,
|
||||
};
|
||||
},
|
||||
created(){
|
||||
if(this.selectedButtonIDs){
|
||||
this.checkValue = this.selectedButtonIDs[0];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick(value) {
|
||||
this.handleChange(value);
|
||||
},
|
||||
handleCheckChange(newValue, oldValue){
|
||||
const isInitialization = typeof(oldValue) === 'function';
|
||||
if (!isInitialization) {
|
||||
this.$emit('isCheckedChanged', { isChecked: this.checkValue, buttonId: this.buttonID.toString() });
|
||||
}
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const { groupName, value } = toRefs(props);
|
||||
const { checked, handleChange, errorMessage } = useField(
|
||||
groupName,
|
||||
undefined,
|
||||
{
|
||||
type: "radio",
|
||||
checkedValue: value,
|
||||
}
|
||||
);
|
||||
return {
|
||||
checked,
|
||||
handleChange,
|
||||
errorMessage,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.ui-radio {
|
||||
position: relative;
|
||||
input[type="radio"] {
|
||||
position: absolute !important;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
+ label {
|
||||
display: block;
|
||||
position: relative;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
+ label::before {
|
||||
content: "";
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: white;
|
||||
border: 1px solid $gray-500;
|
||||
border-radius: 50%;
|
||||
}
|
||||
&:checked + label::before {
|
||||
background: $white;
|
||||
border: 1px solid $blue;
|
||||
}
|
||||
&:checked + label::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 3px;
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
transform: rotate(-45deg);
|
||||
border: 5px solid $blue;
|
||||
border-radius: 50%;
|
||||
}
|
||||
&:hover + label::before {
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
}
|
||||
&:focus + label::before {
|
||||
box-shadow: 0 0 0 2px $blue;
|
||||
}
|
||||
&:focus:checked + label::before {
|
||||
box-shadow: 0 0 0 2px transparent;
|
||||
}
|
||||
&:disabled + label {
|
||||
color: $gray-200;
|
||||
}
|
||||
&:disabled + label::before {
|
||||
background: $gray-200;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -13,11 +13,10 @@ export default {
|
|||
text: String,
|
||||
href: {
|
||||
type: String,
|
||||
default: `javascript:void(0)`, //Prevents page jump when value is empty or #
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClick() {
|
||||
handleClick(event) {
|
||||
this.$emit("click-event");
|
||||
},
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue