Merge branch 'develop' into feature/CSR-267

This commit is contained in:
Max 2022-01-20 13:21:31 -05:00
commit d3e88c6d10
24 changed files with 1089 additions and 291 deletions

View file

@ -12,6 +12,7 @@ module.exports = {
"!src/router/**/*.js", "!src/router/**/*.js",
"!src/helpers/unit-test-helper.js", "!src/helpers/unit-test-helper.js",
"!src/layouts/component-test/component-test.vue", "!src/layouts/component-test/component-test.vue",
"!src/layouts/form-test/form-test.vue",
"!src/layouts/address-poc/address-poc.vue" "!src/layouts/address-poc/address-poc.vue"
], //! means exclude from coverage. ], //! means exclude from coverage.
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"], testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],

120
package-lock.json generated
View file

@ -15,11 +15,13 @@
"core-js": "^3.6.5", "core-js": "^3.6.5",
"http-status-codes": "^2.1.4", "http-status-codes": "^2.1.4",
"jest-junit": "^13.0.0", "jest-junit": "^13.0.0",
"vee-validate": "^4.5.7",
"vue": "^3.0.0", "vue": "^3.0.0",
"vue-plugin-load-script": "^2.1.0", "vue-plugin-load-script": "^2.1.0",
"vue-router": "^4.0.11", "vue-router": "^4.0.11",
"vuex": "^4.0.2", "vuex": "^4.0.2",
"vuex-persistedstate": "^4.1.0" "vuex-persistedstate": "^4.1.0",
"yup": "^0.32.11"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0", "@vue/cli-plugin-babel": "~4.5.0",
@ -1632,7 +1634,6 @@
"version": "7.16.7", "version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz",
"integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==", "integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==",
"dev": true,
"dependencies": { "dependencies": {
"regenerator-runtime": "^0.13.4" "regenerator-runtime": "^0.13.4"
}, },
@ -4166,6 +4167,11 @@
"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
"dev": true "dev": true
}, },
"node_modules/@types/lodash": {
"version": "4.14.178",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz",
"integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw=="
},
"node_modules/@types/mime": { "node_modules/@types/mime": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
@ -16453,8 +16459,12 @@
"node_modules/lodash": { "node_modules/lodash": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
"dev": true },
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
}, },
"node_modules/lodash.debounce": { "node_modules/lodash.debounce": {
"version": "4.0.8", "version": "4.0.8",
@ -16974,6 +16984,11 @@
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"node_modules/nanoclone": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz",
"integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA=="
},
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.1.30", "version": "3.1.30",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
@ -18974,6 +18989,11 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/property-expr": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz",
"integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA=="
},
"node_modules/proto-list": { "node_modules/proto-list": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
@ -19352,8 +19372,7 @@
"node_modules/regenerator-runtime": { "node_modules/regenerator-runtime": {
"version": "0.13.9", "version": "0.13.9",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
"dev": true
}, },
"node_modules/regenerator-transform": { "node_modules/regenerator-transform": {
"version": "0.14.5", "version": "0.14.5",
@ -22340,6 +22359,17 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/vee-validate": {
"version": "4.5.7",
"resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.5.7.tgz",
"integrity": "sha512-EcpvOikBP5LlRIKwULfwRDeyIaERuOJztZUPWRmSIeWpeQWD6hs9qQ8A3CmVVteqEohbUM07gV/ea8lPXCfCJQ==",
"dependencies": {
"@vue/devtools-api": "^6.0.0-beta.15"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/vendors": { "node_modules/vendors": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz",
@ -24319,6 +24349,28 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
"dev": true "dev": true
},
"node_modules/yup": {
"version": "0.32.11",
"resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz",
"integrity": "sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==",
"dependencies": {
"@babel/runtime": "^7.15.4",
"@types/lodash": "^4.14.175",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"nanoclone": "^0.2.1",
"property-expr": "^2.0.4",
"toposort": "^2.0.2"
},
"engines": {
"node": ">=10"
}
},
"node_modules/yup/node_modules/toposort": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
"integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA="
} }
}, },
"dependencies": { "dependencies": {
@ -25421,7 +25473,6 @@
"version": "7.16.7", "version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz",
"integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==", "integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==",
"dev": true,
"requires": { "requires": {
"regenerator-runtime": "^0.13.4" "regenerator-runtime": "^0.13.4"
} }
@ -27496,6 +27547,11 @@
"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
"dev": true "dev": true
}, },
"@types/lodash": {
"version": "4.14.178",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz",
"integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw=="
},
"@types/mime": { "@types/mime": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
@ -37445,8 +37501,12 @@
"lodash": { "lodash": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
"dev": true },
"lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
}, },
"lodash.debounce": { "lodash.debounce": {
"version": "4.0.8", "version": "4.0.8",
@ -37888,6 +37948,11 @@
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"nanoclone": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz",
"integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA=="
},
"nanoid": { "nanoid": {
"version": "3.1.30", "version": "3.1.30",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
@ -39538,6 +39603,11 @@
"sisteransi": "^1.0.5" "sisteransi": "^1.0.5"
} }
}, },
"property-expr": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz",
"integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA=="
},
"proto-list": { "proto-list": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
@ -39856,8 +39926,7 @@
"regenerator-runtime": { "regenerator-runtime": {
"version": "0.13.9", "version": "0.13.9",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
"dev": true
}, },
"regenerator-transform": { "regenerator-transform": {
"version": "0.14.5", "version": "0.14.5",
@ -42300,6 +42369,14 @@
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
"dev": true "dev": true
}, },
"vee-validate": {
"version": "4.5.7",
"resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.5.7.tgz",
"integrity": "sha512-EcpvOikBP5LlRIKwULfwRDeyIaERuOJztZUPWRmSIeWpeQWD6hs9qQ8A3CmVVteqEohbUM07gV/ea8lPXCfCJQ==",
"requires": {
"@vue/devtools-api": "^6.0.0-beta.15"
}
},
"vendors": { "vendors": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz",
@ -43914,6 +43991,27 @@
"dev": true "dev": true
} }
} }
},
"yup": {
"version": "0.32.11",
"resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz",
"integrity": "sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==",
"requires": {
"@babel/runtime": "^7.15.4",
"@types/lodash": "^4.14.175",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"nanoclone": "^0.2.1",
"property-expr": "^2.0.4",
"toposort": "^2.0.2"
},
"dependencies": {
"toposort": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
"integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA="
}
}
} }
} }
} }

View file

@ -16,11 +16,13 @@
"core-js": "^3.6.5", "core-js": "^3.6.5",
"http-status-codes": "^2.1.4", "http-status-codes": "^2.1.4",
"jest-junit": "^13.0.0", "jest-junit": "^13.0.0",
"vee-validate": "^4.5.7",
"vue": "^3.0.0", "vue": "^3.0.0",
"vue-plugin-load-script": "^2.1.0", "vue-plugin-load-script": "^2.1.0",
"vue-router": "^4.0.11", "vue-router": "^4.0.11",
"vuex": "^4.0.2", "vuex": "^4.0.2",
"vuex-persistedstate": "^4.1.0" "vuex-persistedstate": "^4.1.0",
"yup": "^0.32.11"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0", "@vue/cli-plugin-babel": "~4.5.0",

View file

@ -7,7 +7,7 @@
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap" rel="stylesheet">
<title><%= htmlWebpackPlugin.options.title %></title> <title><%= htmlWebpackPlugin.options.title %></title>
</head> </head>
<body> <body>

View file

@ -1,3 +1,11 @@
<template> <template>
<router-view></router-view> <router-view></router-view>
</template> </template>
<style lang="scss">
@import "./node_modules/bootstrap/scss/bootstrap";
@import "@/styles/common-styles.scss";
@import "@/styles/common-button-styles.scss";
@import "@/styles/common-list-styles.scss";
@import "@/styles/common-typography-styles.scss";
</style>

View file

@ -1,28 +1,38 @@
<template> <template>
<div class="button_question"> <div :class="isVehicleQuestion ? 'button_question' : ''">
<div class="mt-6 mb-4 d-flex"> <div v-if="questionText" class="mt-6 mb-4 d-flex">
<span class="text-center fs-6 fw-bold w-100">{{ <span class="text-center fs-6 fw-bold w-100">{{
questionText questionText
}}</span> }}</span>
</div> </div>
<div class="w-100 d-flex justify-content-center"> <div class="w-100 d-flex justify-content-center">
<fieldset class="overflow-scroll position-absolute container-fluid w-100 pt-1 px-5 py-0" role="radiogroup"> <fieldset class="w-100" :class="isVehicleQuestion ? vehicleQuestionClasses : ''" role="radiogroup" :aria-labelledby="groupName ? groupName + '-radio-group' : ''">
<legend class="sr-only">{{groupName}}</legend> <legend class="sr-only">{{groupName}}</legend>
<listButton v-for="answer in answers" :key="answer" <div :class="isRow ? 'row g-2' : isColumn ? columnClasses : 'w-100'">
:buttonID="answer" <component :is="buttonType" v-for="answer in answers" :key="answer"
@mouseup="chooseAnswer(answer)" :buttonID="answer.Name ? answer.Name : answer"
@keyup.space="chooseAnswer(answer)" @mouseup="chooseAnswer(answer.Name ? answer.Name : answer)"
loaderColor="blue" @keyup.space="chooseAnswer(answer.Name ? answer.Name : answer)"
loaderPosition="right" :loaderEnabled="loaderEnabled"
sizeInRem="1.5" :loaderColor="loaderColor"
data-test="button" :loaderPosition="loaderPosition"
:groupName="groupName" :sizeInRem="sizeInRem"
textPosition="text-start" data-test="button"
:isRequired="true" :groupName="groupName"
:value="modelValue" :textPosition="textPosition"
screenReaderOnlyText="(opens new window)" screenReaderOnlyText="(opens new window)"
:isMultiSelect="isMultiSelect" :isMultiSelect="isMultiSelect"
/> :isHorizontal="isHorizontal"
:ariaLabelBy="ariaLabelBy"
:isRequired="isRequired"
:buttonImage="answer.AnswerImageUrl"
:buttonImageId="answer.ImageId"
:buttonLabel="answer.Text ? answer.Text : answer"
:altText="altText"
:buttonLabelSubCopy="answer.SubText"
:value="modelValue"
/>
</div>
</fieldset> </fieldset>
</div> </div>
</div> </div>
@ -30,14 +40,55 @@
<script> <script>
import listButton from "@/ux-components/list-button/list-button"; 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";
export default { export default {
name: "buttonQuestion", name: "buttonQuestion",
props: { props: {
buttonType: {
type: String,
default: 'listButton'
},
isMultiSelect: Boolean, isMultiSelect: Boolean,
questionText: String, questionText: String,
answers: Array, answers: Array,
textPosition: {
type: String,
default: 'text-center'
},
loaderEnabled: Boolean,
loaderColor: {
type: String,
default: 'blue'
},
loaderPosition: {
type: String,
default: 'right'
},
sizeInRem: {
type: [String, Number],
default: 1.5
},
modelValue: String, modelValue: String,
groupName: String groupName: String,
ariaLabelBy: String,
isRequired: Boolean,
isVehicleQuestion: Boolean,
isHorizontal: Boolean,
isColumn: Boolean,
isRow: Boolean,
buttonImage: String,
buttonImageId: String,
buttonLabel: String,
altText: String,
buttonLabelSubCopy: String,
},
data() {
return {
vehicleQuestionClasses: 'container-fluid overflow-scroll position-absolute px-5 pt-1 py-0',
columnClasses: 'd-flex flex-row p-0'
};
}, },
methods: { methods: {
chooseAnswer(answer) { chooseAnswer(answer) {
@ -46,12 +97,15 @@ export default {
}, },
components: { components: {
listButton, listButton,
listButtonHorizontal,
listCard
}, },
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.button_question { .button_question {
color: $black;
height: calc(100vh - 280px); height: calc(100vh - 280px);
.overflow-scroll { .overflow-scroll {

View file

@ -0,0 +1,38 @@
import { shallowMount } from "@vue/test-utils";
import textInput from "./text-input";
describe("text-input.vue", () => {
it("Should render a text input", async () => {
// Act
const wrapper = shallowMount(textInput, {
propsData: {
name: "test",
label: "unit test label",
},
});
// Assert
const input = wrapper.find("input");
expect(input.exists()).toBe(true);
});
it("Should return aria-required state", async () => {
// Act
const wrapper = shallowMount(textInput, {
propsData: {
name: "test",
label: "unit test label",
isRequired: true
},
});
// Assert
const input = wrapper.find("input");
expect(input.attributes()["aria-required"]).toEqual("true");
});
});

View file

@ -0,0 +1,71 @@
<!-- Simple implementation of an input field -->
<template>
<div class="d-flex w-50 mb-2" :class="{ 'has-error': !!errorMessage }">
<input
type="text"
:name="name"
:value="inputValue"
:id="name"
:aria-required="isRequired"
@input="handleChange"
@blur="handleBlur"
:data-focus-target="name"
/>
<label
:for="name"
:aria-labelledby="name"
class="d-flex justify-content-center py-3 px-4"
>
<span class="m-0">{{label}}</span>
</label>
</div>
<div v-show="errorMessage" class="row px-3 form-test-error">
{{ errorMessage }}
</div>
</template>
<script>
import { useField } from 'vee-validate';
export default {
name: "textInput",
props: {
type: {
type: String,
default: "text",
},
value: {
type: String,
default: "",
},
name: {
type: String,
required: true,
},
label: {
type: String,
required: true,
},
isRequired: Boolean,
},
setup(props) {
const {
value: inputValue,
errorMessage,
handleBlur,
handleChange,
meta,
} = useField(props.name, undefined, {
initialValue: props.value,
});
return {
handleChange,
handleBlur,
errorMessage,
inputValue,
meta,
};
},
};
</script>

View file

@ -53,39 +53,33 @@
<fieldset> <fieldset>
<legend class="sr-only">Functioning as Checkbox</legend> <legend class="sr-only">Functioning as Checkbox</legend>
<div class="row g-2"> <div class="row g-2">
<div class="col-4"> <listCard
<listCard isMultiSelect
isMultiSelect buttonImage="windshield-damage.svg"
buttonImage="windshield-damage.svg" buttonLabel="Windshield"
buttonLabel="Windshield" altText=""
altText="" buttonID="List Card Checkbox 1"
buttonID="List Card Checkbox 1" groupID="checkbox-demo-1"
groupID="checkbox-demo-1" buttonLabelSubCopy="Description"
buttonLabelSubCopy="Description" />
/> <listCard
</div> isMultiSelect
<div class="col-4"> buttonImage="side-window-damage-right-all.svg"
<listCard buttonLabel="Windshield"
isMultiSelect altText=""
buttonImage="side-window-damage-right-all.svg" buttonID="List Card Checkbox 2"
buttonLabel="Windshield" groupID="checkbox-demo-1"
altText="" buttonLabelSubCopy="Description with more than one line"
buttonID="List Card Checkbox 2" />
groupID="checkbox-demo-1" <listCard
buttonLabelSubCopy="Description with more than one line" isMultiSelect
/> buttonImage="windshield-damage.svg"
</div> buttonLabel="Windshield"
<div class="col-4"> altText=""
<listCard buttonID="List Card Checkbox 3"
isMultiSelect groupID="checkbox-demo-1"
buttonImage="windshield-damage.svg" buttonLabelSubCopy=""
buttonLabel="Windshield" />
altText=""
buttonID="List Card Checkbox 3"
groupID="checkbox-demo-1"
buttonLabelSubCopy=""
/>
</div>
</div> </div>
</fieldset> </fieldset>
<div class="row my-4"> <div class="row my-4">
@ -167,7 +161,8 @@
<fieldset> <fieldset>
<legend class="sr-only">Horizontal Checkbox</legend> <legend class="sr-only">Horizontal Checkbox</legend>
<listCard <listCard
isMultiSelectHorizontal isMultiSelect
isHorizontal
buttonImage="windshield-damage.svg" buttonImage="windshield-damage.svg"
buttonLabel="Windshield" buttonLabel="Windshield"
altText="" altText=""
@ -184,7 +179,8 @@
<fieldset> <fieldset>
<legend class="sr-only">Checkbox no Description</legend> <legend class="sr-only">Checkbox no Description</legend>
<listCard <listCard
isMultiSelectHorizontal isMultiSelect
isHorizontal
buttonImage="side-window-damage-right-all.svg" buttonImage="side-window-damage-right-all.svg"
buttonLabel="Side Window" buttonLabel="Side Window"
altText="" altText=""
@ -201,7 +197,7 @@
<fieldset> <fieldset>
<legend class="sr-only">Horizontal Radio Button</legend> <legend class="sr-only">Horizontal Radio Button</legend>
<listCard <listCard
isRadioHorizontal isHorizontal
buttonImage="back-glass-damage.svg" buttonImage="back-glass-damage.svg"
buttonLabel="Rear Window" buttonLabel="Rear Window"
altText="" altText=""
@ -218,7 +214,7 @@
<fieldset> <fieldset>
<legend class="sr-only">Radio Button no Description</legend> <legend class="sr-only">Radio Button no Description</legend>
<listCard <listCard
isRadioHorizontal isHorizontal
buttonImage="windshield-damage.svg" buttonImage="windshield-damage.svg"
buttonLabel="Windshield" buttonLabel="Windshield"
altText="" altText=""
@ -247,6 +243,7 @@
groupName="demo-1" groupName="demo-1"
ariaLabelBy="vehicle-year" ariaLabelBy="vehicle-year"
buttonID="2021" buttonID="2021"
buttonLabel="2021"
isRequired=true isRequired=true
textPosition="text-start" textPosition="text-start"
loaderColor="blue" loaderColor="blue"
@ -259,6 +256,7 @@
groupName="demo-1" groupName="demo-1"
ariaLabelBy="vehicle-year" ariaLabelBy="vehicle-year"
buttonID="2020" buttonID="2020"
buttonLabel="2020"
isRequired=true isRequired=true
textPosition="text-start" textPosition="text-start"
loaderColor="blue" loaderColor="blue"
@ -271,6 +269,7 @@
groupName="demo-1" groupName="demo-1"
ariaLabelBy="vehicle-year" ariaLabelBy="vehicle-year"
buttonID="2019" buttonID="2019"
buttonLabel="2019"
isRequired=true isRequired=true
textPosition="text-start" textPosition="text-start"
loaderColor="blue" loaderColor="blue"
@ -290,6 +289,7 @@
groupName="demo-1-checkbox" groupName="demo-1-checkbox"
ariaLabelBy="vehicle-year" ariaLabelBy="vehicle-year"
buttonID="2018" buttonID="2018"
buttonLabel="2018"
isRequired=true isRequired=true
textPosition="text-start" textPosition="text-start"
loaderColor="blue" loaderColor="blue"
@ -301,6 +301,7 @@
groupName="demo-1-checkbox" groupName="demo-1-checkbox"
ariaLabelBy="vehicle-year" ariaLabelBy="vehicle-year"
buttonID="2017" buttonID="2017"
buttonLabel="2017"
isRequired=true isRequired=true
textPosition="text-start" textPosition="text-start"
loaderColor="blue" loaderColor="blue"
@ -312,6 +313,7 @@
groupName="demo-1-checkbox" groupName="demo-1-checkbox"
ariaLabelBy="vehicle-year" ariaLabelBy="vehicle-year"
buttonID="2016" buttonID="2016"
buttonLabel="2016"
isRequired=true isRequired=true
textPosition="text-start" textPosition="text-start"
loaderColor="blue" loaderColor="blue"
@ -337,6 +339,7 @@
groupName="demo-2" groupName="demo-2"
ariaLabelBy="vehicle-make" ariaLabelBy="vehicle-make"
buttonID="Chevrolet" buttonID="Chevrolet"
buttonLabel="Chevrolet"
buttonLabelSubCopy="Test sub-headline" buttonLabelSubCopy="Test sub-headline"
textPosition="text-start" textPosition="text-start"
loaderColor="blue" loaderColor="blue"
@ -349,6 +352,7 @@
groupName="demo-2" groupName="demo-2"
ariaLabelBy="vehicle-make" ariaLabelBy="vehicle-make"
buttonID="Dodge" buttonID="Dodge"
buttonLabel="Dodge"
buttonLabelSubCopy="Test sub-headline" buttonLabelSubCopy="Test sub-headline"
textPosition="text-start" textPosition="text-start"
loaderColor="blue" loaderColor="blue"
@ -361,6 +365,7 @@
groupName="demo-2" groupName="demo-2"
ariaLabelBy="vehicle-make" ariaLabelBy="vehicle-make"
buttonID="Ford" buttonID="Ford"
buttonLabel="Ford"
buttonLabelSubCopy="Test sub-headline" buttonLabelSubCopy="Test sub-headline"
textPosition="text-start" textPosition="text-start"
loaderColor="blue" loaderColor="blue"
@ -380,6 +385,7 @@
groupName="demo-2-checkbox" groupName="demo-2-checkbox"
ariaLabelBy="vehicle-make" ariaLabelBy="vehicle-make"
buttonID="Honda" buttonID="Honda"
buttonLabel="Honda"
buttonLabelSubCopy="Test sub-headline" buttonLabelSubCopy="Test sub-headline"
textPosition="text-start" textPosition="text-start"
loaderColor="blue" loaderColor="blue"
@ -391,6 +397,7 @@
groupName="demo-2-checkbox" groupName="demo-2-checkbox"
ariaLabelBy="vehicle-make" ariaLabelBy="vehicle-make"
buttonID="Acura" buttonID="Acura"
buttonLabel="Acura"
buttonLabelSubCopy="Test sub-headline" buttonLabelSubCopy="Test sub-headline"
textPosition="text-start" textPosition="text-start"
loaderColor="blue" loaderColor="blue"
@ -402,6 +409,7 @@
groupName="demo-2-checkbox" groupName="demo-2-checkbox"
ariaLabelBy="vehicle-make" ariaLabelBy="vehicle-make"
buttonID="Infiniti" buttonID="Infiniti"
buttonLabel="Infiniti"
buttonLabelSubCopy="Test sub-headline" buttonLabelSubCopy="Test sub-headline"
textPosition="text-start" textPosition="text-start"
loaderColor="blue" loaderColor="blue"
@ -427,6 +435,7 @@
groupName="demo-3" groupName="demo-3"
ariaLabelBy="vehicle-model" ariaLabelBy="vehicle-model"
buttonID="Corvette" buttonID="Corvette"
buttonLabel="Corvette"
buttonLabelSubCopy="Test sub-headline" buttonLabelSubCopy="Test sub-headline"
textPosition="text-center" textPosition="text-center"
loaderColor="blue" loaderColor="blue"
@ -439,6 +448,7 @@
groupName="demo-3" groupName="demo-3"
ariaLabelBy="vehicle-model" ariaLabelBy="vehicle-model"
buttonID="Testarosa" buttonID="Testarosa"
buttonLabel="Testarosa"
buttonLabelSubCopy="Test sub-headline" buttonLabelSubCopy="Test sub-headline"
textPosition="text-center" textPosition="text-center"
loaderColor="blue" loaderColor="blue"
@ -451,6 +461,7 @@
groupName="demo-3" groupName="demo-3"
ariaLabelBy="vehicle-model" ariaLabelBy="vehicle-model"
buttonID="S600" buttonID="S600"
buttonLabel="S600"
buttonLabelSubCopy="Test sub-headline" buttonLabelSubCopy="Test sub-headline"
textPosition="text-center" textPosition="text-center"
loaderColor="blue" loaderColor="blue"
@ -470,6 +481,7 @@
groupName="demo-3-checkbox" groupName="demo-3-checkbox"
ariaLabelBy="vehicle-model" ariaLabelBy="vehicle-model"
buttonID="Accord" buttonID="Accord"
buttonLabel="Accord"
buttonLabelSubCopy="Test sub-headline" buttonLabelSubCopy="Test sub-headline"
textPosition="text-center" textPosition="text-center"
loaderColor="blue" loaderColor="blue"
@ -481,6 +493,7 @@
groupName="demo-3-checkbox" groupName="demo-3-checkbox"
ariaLabelBy="vehicle-model" ariaLabelBy="vehicle-model"
buttonID="Civic" buttonID="Civic"
buttonLabel="Civic"
buttonLabelSubCopy="Test sub-headline" buttonLabelSubCopy="Test sub-headline"
textPosition="text-center" textPosition="text-center"
loaderColor="blue" loaderColor="blue"
@ -492,6 +505,7 @@
groupName="demo-3-checkbox" groupName="demo-3-checkbox"
ariaLabelBy="vehicle-model" ariaLabelBy="vehicle-model"
buttonID="Ridgeline" buttonID="Ridgeline"
buttonLabel="Ridgeline"
buttonLabelSubCopy="Test sub-headline" buttonLabelSubCopy="Test sub-headline"
textPosition="text-center" textPosition="text-center"
loaderColor="blue" loaderColor="blue"
@ -519,6 +533,7 @@
groupName="demo-4" groupName="demo-4"
ariaLabelBy="vehicle-model" ariaLabelBy="vehicle-model"
buttonID="1" buttonID="1"
buttonLabel="1"
textPosition="text-center" textPosition="text-center"
loaderColor="blue" loaderColor="blue"
loaderPosition="right" loaderPosition="right"
@ -532,6 +547,7 @@
groupName="demo-4" groupName="demo-4"
ariaLabelBy="vehicle-model" ariaLabelBy="vehicle-model"
buttonID="2" buttonID="2"
buttonLabel="2"
textPosition="text-center" textPosition="text-center"
loaderColor="blue" loaderColor="blue"
loaderPosition="right" loaderPosition="right"
@ -545,6 +561,7 @@
groupName="demo-4" groupName="demo-4"
ariaLabelBy="vehicle-model" ariaLabelBy="vehicle-model"
buttonID="3" buttonID="3"
buttonLabel="3"
textPosition="text-center" textPosition="text-center"
loaderColor="blue" loaderColor="blue"
loaderPosition="right" loaderPosition="right"
@ -567,6 +584,7 @@
groupName="demo-4-checkbox" groupName="demo-4-checkbox"
ariaLabelBy="vehicle-model" ariaLabelBy="vehicle-model"
buttonID="4" buttonID="4"
buttonLabel="4"
textPosition="text-center" textPosition="text-center"
loaderEnabled=true loaderEnabled=true
loaderColor="blue" loaderColor="blue"
@ -580,6 +598,7 @@
groupName="demo-4-checkbox" groupName="demo-4-checkbox"
ariaLabelBy="vehicle-model" ariaLabelBy="vehicle-model"
buttonID="5" buttonID="5"
buttonLabel="5"
textPosition="text-center" textPosition="text-center"
loaderEnabled=true loaderEnabled=true
loaderColor="blue" loaderColor="blue"
@ -593,6 +612,7 @@
groupName="demo-4-checkbox" groupName="demo-4-checkbox"
ariaLabelBy="vehicle-model" ariaLabelBy="vehicle-model"
buttonID="6" buttonID="6"
buttonLabel="6"
textPosition="text-center" textPosition="text-center"
loaderEnabled=true loaderEnabled=true
loaderColor="blue" loaderColor="blue"
@ -606,12 +626,18 @@
</div> </div>
<div class="row my-4"> <div class="row my-4">
<div class="col"> <div class="col">
<h4 class="m-0 p-2 bg-light rounded">Text Link</h4> <h4 class="m-0 p-2 bg-light rounded">Links</h4>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col my-3 d-flex align-items-center"> <div class="col my-3">
<a href="#">Text Link</a> <a href="#">Text Link</a> <span>(default text link behavior)</span>
<br><br>
<a class="small" href="#">Text Link Small</a> <span>(.small)</span>
<br><br>
<a class="footer-link" href="#">Footer Link</a> <span>(.footer-link)</span>
<br><br>
<a class="navigation-link" href="#">Navigation Link</a> <span>(.navigation-link)</span>
</div> </div>
</div> </div>
<div class="row my-4"> <div class="row my-4">
@ -754,6 +780,100 @@
/> />
</div> </div>
</div> </div>
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">Button Question</h4>
</div>
</div>
<buttonQuestion
:answers="checkboxAnswers"
:isMultiSelect="true"
ariaLabelBy="checkbox"
groupName="checkbox-list"
questionText="List Button as checkbox"
/>
<buttonQuestion
:answers="radioAnswers"
ariaLabelBy="radio"
groupName="radio-list"
questionText="List Button as radio"
/>
<buttonQuestion
:answers="horizontalCheckboxAnswers"
buttonType="listButtonHorizontal"
:isMultiSelect="true"
isColumn
ariaLabelBy="checkbox"
groupName="checkbox-list-horizontal"
questionText="List Button Horizontal as checkbox"
/>
<buttonQuestion
:answers="horizontalRadioAnswers"
buttonType="listButtonHorizontal"
isColumn
ariaLabelBy="radio"
groupName="radio-list-horizontal"
questionText="List Button Horizontal as radio"
sizeInRem="1"
/>
<buttonQuestion
:answers="listCardCheckBox"
buttonType="listCard"
isMultiSelect
ariaLabelBy="check_card"
groupName="check-list-card"
questionText="List Card as checkbox"
/>
<buttonQuestion
:answers="listCardRadio"
buttonType="listCard"
ariaLabelBy="radio_card"
groupName="radio-card"
questionText="List Card as radio"
/>
<buttonQuestion
isMultiSelect
isHorizontal
:answers="listCardCheckBoxHorizontal"
buttonType="listCard"
ariaLabelBy="horizontal_check_card"
groupName="horizontal_check-card"
questionText="List Card Horizontal as checkbox"
/>
<buttonQuestion
isMultiSelect
isHorizontal
:answers="listCardCheckBoxHorizontalSubText"
buttonType="listCard"
ariaLabelBy="horizontal_check_card-st"
groupName="horizontal_check-card-st"
questionText="With Subtext"
/>
<buttonQuestion
isHorizontal
:answers="listRadioHorizontal"
buttonType="listCard"
ariaLabelBy="horizontal_radio_card"
groupName="horizontal_cradio-card"
questionText="List Card Horizontal as radio"
/>
<buttonQuestion
isHorizontal
:answers="listRadioHorizontalSubText"
buttonType="listCard"
ariaLabelBy="horizontal_radio_card-st"
groupName="horizontal_cradio-card-st"
questionText="With Subtext"
/>
<buttonQuestion
isMultiSelect
isRow
:answers="listCardGroup"
buttonType="listCard"
ariaLabelBy="horizontal_radio_card-group"
groupName="horizontal_cradio-card-group"
questionText="In column layout"
/>
</div> </div>
</template> </template>
@ -768,6 +888,7 @@ import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
import funnelHeader from "@/common-components/funnel-header/funnel-header"; import funnelHeader from "@/common-components/funnel-header/funnel-header";
import listButtonHorizontal from "@/ux-components/list-button-horizontal/list-button-horizontal"; import listButtonHorizontal from "@/ux-components/list-button-horizontal/list-button-horizontal";
import checkbox from "@/ux-components/checkbox/checkbox"; import checkbox from "@/ux-components/checkbox/checkbox";
import buttonQuestion from "@/common-components/button-question/button-question"
export default { export default {
name: "App", name: "App",
components: { components: {
@ -781,10 +902,75 @@ export default {
listButtonHorizontal, listButtonHorizontal,
checkbox, checkbox,
funnelHeader, funnelHeader,
buttonQuestion
}, },
data() { data() {
return { return {
years: [2023, 2022, 2021, 2020], years: [2023, 2022, 2021, 2020],
checkboxAnswers: ['Checkbox Answer 1', 'Checkbox Answer 2', 'Checkbox Answer 3'],
radioAnswers: ['Radio Answer 1', 'Radio Answer 2', 'Radio Answer 3'],
horizontalCheckboxAnswers: ['HCB Answer 1', 'HCB Answer 2', 'HCB Answer 3'],
horizontalRadioAnswers: ['HR Answer 1', 'HR Answer 2', 'HR Answer 3'],
listCardCheckBox: [{
Name: 'List-Card-CB',
Text: "List Card CB",
SubText: "checkbox",
ImageId: '53343ce4-5b6a-46aa-a80a-f948c1723955',
AnswerImageUrl: "side-window-damage-right-all.svg"
}],
listCardRadio: [{
Name: 'List-Card-R',
Text: "List Card R",
SubText: "Radio",
ImageId: '53343ce4-5b6a-46aa-a80a-f948c1723955',
AnswerImageUrl: "side-window-damage-right-all.svg"
}],
listCardCheckBoxHorizontalSubText: [{
Name: 'List-Card-CBHst',
Text: "List Card CBHst",
SubText: "With Subtext",
ImageId: '53343ce4-5b6a-46aa-a80a-f948c1723955',
AnswerImageUrl: "side-window-damage-right-all.svg"
}],
listCardCheckBoxHorizontal: [{
Name: 'List-Card-CBH',
Text: "List Card CBH",
ImageId: '53343ce4-5b6a-46aa-a80a-f948c1723955',
AnswerImageUrl: "side-window-damage-right-all.svg"
}],
listRadioHorizontalSubText: [{
Name: 'List-Card-RHst',
Text: "List Card RHst",
SubText: "With Subtext",
ImageId: '53343ce4-5b6a-46aa-a80a-f948c1723955',
AnswerImageUrl: "side-window-damage-right-all.svg"
}],
listRadioHorizontal: [{
Name: 'List-Card-RH',
Text: "List Card RH",
ImageId: '53343ce4-5b6a-46aa-a80a-f948c1723955',
AnswerImageUrl: "side-window-damage-right-all.svg"
}],
listCardGroup: [{
Name: 'Side-Window-1',
Text: "Side Window",
SubText: "With Subtext",
ImageId: '53343ce4-5b6a-46aa-a80a-f948c1723955',
AnswerImageUrl: "side-window-damage-right-all.svg"
},
{
Name: 'Side-Window-2',
Text: "Side Window",
SubText: "With Subtext that is more than one line",
ImageId: '53343ce4-5b6a-46aa-a80a-f948c1723955',
AnswerImageUrl: "side-window-damage-right-all.svg"
},
{
Name: 'Side-Window-3',
Text: "Side Window",
ImageId: '53343ce4-5b6a-46aa-a80a-f948c1723955',
AnswerImageUrl: "side-window-damage-right-all.svg"
}],
}; };
}, },
}; };

View file

@ -0,0 +1,358 @@
<template>
<Form
:validation-schema="schema"
@submit="onSubmit"
@invalid-submit="onInvalidSubmit"
v-slot="{ values, meta }"
class="w-50"
>
<div class="row g-2 mt-6 mx-4">
<h2 class="mx-2 mt-4 mb-0">FORM VALIDATION TEST</h2>
<p class="mx-2">NOTE: This page is testing functionality only; styling will not match Figma mocks.</p>
</div>
<div class="row g-2 mt-6 mx-4">
<h4 class="mx-2 mt-4 mb-0">How many chips are we repairing? (Required)</h4>
<div
role="radiogroup"
aria-labelledby="demo-group"
class="d-flex flex-row p-0"
>
<h4 class="sr-only" id="demo-group">
How many chips are we repairing?
</h4>
<listButtonHorizontal
groupName="demo"
aria-required="true"
buttonID="1"
buttonLabel="1"
value="1"
buttonLabelSubCopy=""
textPosition="text-center"
sizeInRem="1"
:totalInGroup="3"
:positionInGroup="1"
screenReaderOnlyText=" opens new window"
/>
<listButtonHorizontal
groupName="demo"
aria-required="true"
buttonID="2"
buttonLabel="2"
value="2"
buttonLabelSubCopy=""
textPosition="text-center"
sizeInRem="1"
:totalInGroup="3"
:positionInGroup="2"
screenReaderOnlyText=" opens new window"
/>
<listButtonHorizontal
groupName="demo"
aria-required="true"
buttonID="3"
buttonLabel="3"
value="3"
buttonLabelSubCopy=""
textPosition="text-center"
sizeInRem="1"
:totalInGroup="3"
:positionInGroup="3"
screenReaderOnlyText=" opens new window"
/>
</div>
<div class="row px-3 form-test-error">
<error-message name="demo"></error-message>
</div>
</div>
<div class="row g-2 mt-6 mx-4">
<div class="col">
<h4 class="mx-2 mt-4 mb-0">Which windshield part needs fixed? (Required)</h4>
<fieldset>
<legend class="sr-only">Which windshield part needs fixed?</legend>
<listButton
isMultiSelect
buttonID="Single Windshield"
buttonLabel="Single Windshield"
value="Single Windshield"
groupName="demo2"
/>
<listButton
isMultiSelect
buttonID="Driver Side"
buttonLabel="Driver Side"
value="Driver Side"
groupName="demo2"
/>
<listButton
isMultiSelect
buttonID="Passenger Side"
buttonLabel="Passenger Side"
value="Passenger Side"
groupName="demo2"
/>
</fieldset>
</div>
<div class="row px-3 form-test-error">
<error-message name="demo2"></error-message>
</div>
</div>
<div class="row g-2 mt-6 mx-4">
<div class="col">
<h4 class="mx-2 mt-4 mb-0">demo3: Enter your VIN (Required)</h4>
<textInput
name="demo3"
type="text"
label="VIN (Required)"
:isRequired="true"
/>
</div>
</div>
<!-- <div class="row g-2 mt-6 mx-4">
<div class="col">
<h4 class="mx-2 mt-4 mb-0">demo4: Enter some numbers (optional)</h4>
<textInput
name="demo4"
type="text"
label="Numbers Only"
:isRequired="true"
/>
</div>
</div> -->
<div class="row g-2 mt-6 mx-4">
<h4 class="mx-2 mt-4 mb-0">demo5: Where's your damage? (Required)</h4>
<div
aria-labelledby="demo5-group"
class="d-flex flex-row p-0"
>
<h4 class="sr-only" id="demo5-group">
Where's your damage?
</h4>
<listCard
isMultiSelect
buttonImage="windshield-damage.svg"
buttonLabel="Windshield"
value="Windshield"
altText=""
buttonID="List Card Checkbox a"
groupName="demo5"
/>
<listCard
isMultiSelect
buttonImage="side-window-damage-left-all.svg"
buttonLabel="Side Door"
value="Side Door"
altText=""
buttonID="List Card Checkbox b"
groupName="demo5"
/>
<listCard
isMultiSelect
buttonImage="back-glass-damage.svg"
buttonLabel="Rear Window"
value="Rear Window"
altText=""
buttonID="List Card Checkbox c"
groupName="demo5"
/>
</div>
</div>
<div class="row px-3 form-test-error">
<error-message name="demo5"></error-message>
</div>
<div class="row g-2 mt-6 mx-4">
<h4 class="mx-2 mt-4 mb-0">demo6: What's your windshield damage? (Required if demo5 "Windshield" is selected)</h4>
<div
role="radiogroup"
aria-labelledby="demo6-group"
class="d-flex flex-row p-0"
>
<h4 class="sr-only" id="demo6-group">
What's your windshield damage?
</h4>
<listCard
isRadio
buttonImage="windshield-damage.svg"
buttonLabel="Crack"
value="Crack"
buttonLabelSubCopy="My damage is larger than six inches."
altText=""
buttonID="Crack Checkbox a"
groupName="demo6"
/>
<listCard
isRadio
buttonImage="windshield-damage.svg"
buttonLabel="Chip(s)"
value="Chip"
buttonLabelSubCopy="I have three or fewer chips smaller than six inches."
altText=""
buttonID="Chip Checkbox b"
groupName="demo6"
/>
</div>
</div>
<div class="row px-3 form-test-error">
<error-message name="demo6"></error-message>
</div>
<!-- <div class="row g-2 mt-6 mx-4">
<div class="col">
<h4 class="mx-2 mt-4 mb-0">Password (Required, enter any string)</h4>
<textInput
name="password"
type="text"
label="Password (Required)"
:isRequired="true"
/>
</div>
</div>
<div class="row g-2 mt-6 mx-4">
<div class="col">
<h4 class="mx-2 mt-4 mb-0">Confirm Password (Required, must match above Password field)</h4>
<textInput
name="passwordConfirmation"
type="text"
label="Confirm Password (Required)"
:isRequired="true"
/>
</div>
</div> -->
<div class="row px-3 form-test-error" data-focus-target="demoX">
<error-message name="demoX"></error-message>
</div>
<div class="g-2 mt-6 mx-4">
<button class="mb-6" :class="!meta.valid && 'form-test-invalid'">Get your estimate</button>
<p>Current Form Values:</p>
<pre>{{ values }}</pre>
<p>Is Form Valid? (Meta.valid):</p>
<pre>{{ meta.valid }}</pre>
</div>
</Form>
</template>
<script>
import { Form, ErrorMessage } from 'vee-validate';
import * as Yup from "yup";
import listCard from "@/ux-components/list-card/list-card";
import textInput from "@/common-components/text-input/text-input";
import listButton from "@/ux-components/list-button/list-button";
import listButtonHorizontal from "@/ux-components/list-button-horizontal/list-button-horizontal";
export default {
name: "App",
data() {
const errorMessages = {
demo: 'Please select chip(s)',
demo2: 'Please select a windshield part',
demo3: 'You entered an invalid VIN. Please check to make sure that you inserted a 17 digit, alpha-numeric number. VINs do not contain the letters I, O or Q.',
demo4: 'Invalid Entry. Must enter only numbers, no letters.',
demo5: 'Please select damage location',
};
return {
schema: Yup.object().shape({
demo: Yup.string().required(errorMessages.demo),
demo2: Yup.array().required(errorMessages.demo2).min(1, errorMessages.demo2),
demo3: Yup.string()
.uppercase()
.required('Please enter a VIN.')
.min(17, errorMessages.demo3)
.max(17, errorMessages.demo3)
.matches('^[^IOQ]+$', errorMessages.demo3),
// demo4: Yup.string().matches('^[0-9]*$', errorMessages.demo4),
demo5: Yup.array().required(errorMessages.demo5).min(1, errorMessages.demo5),
demo6: Yup.string()
.when('demo5', function (demo5, schema, value) {
if (demo5) {
const demo5String = demo5.toString();
if (demo5String.includes("Windshield")) {
// make demo6 required
return schema.required('You must pick an option for Windshield Damage.')
}
return schema;
}
return schema;
}),
demoX: Yup.string().notRequired(),
// PASSWORD EXAMPLE, NOT BEING USED NOW
// password: Yup.string().min(6).required(),
// passwordConfirmation: Yup.string()
// .required()
// .oneOf([Yup.ref("password")], "Passwords do not match"),
}).test('repair-replace-conflict',
null, // need to pass null as error message so it won't get added to list of errors
function(value) {
// whole form test
console.log('ALL FORM VALUES: ', value);
// test problem scenario... return new error if conditions are true, allow submit if not
if (value.demo5 && value.demo6) {
const demo5String = value.demo5.toString();
console.log('demo5String: ', demo5String);
console.log('demo6: ', value.demo6);
if (demo5String.includes("Windshield") && value.demo5.length > 1 && value.demo6 == "Chip") {
return this.createError({ name: 'demoX', message: "Sorry, at this time we can't schedule a repair and replace at the same time. Please schedule these appointments separately as they are different technicians. Thanks.", path: 'demoX' });
}
return true;
}
return true;
}),
}
},
components: {
listCard,
listButton,
listButtonHorizontal,
textInput,
Form,
ErrorMessage,
},
methods: {
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 errorEls and order by alpha
const errorEls = Object.keys(errors).sort();
const firstErrorEl = errorEls[0];
console.log('firstErrorEl: ', firstErrorEl);
if (firstErrorEl) {
const qsString = "[data-focus-target='" + firstErrorEl + "']";
document.querySelector(qsString).focus();
}
}
},
};
</script>
<style lang="scss">
.form-test-error {
color: red;
font-weight: bold;
}
.form-test-invalid {
opacity: 0.5;
}
.has-error {
border: 1px solid red;
}
</style>

View file

@ -3,6 +3,9 @@
:questionText="questionText" :questionText="questionText"
:answers="makes" :answers="makes"
groupName="Choose Vehicle Make" groupName="Choose Vehicle Make"
textPosition="text-start"
:isVehicleQuestion="true"
:loaderEnabled="true"
v-model="modelValue" v-model="modelValue"
/> />
</template> </template>

View file

@ -3,6 +3,9 @@
:questionText="questionText" :questionText="questionText"
:answers="models" :answers="models"
groupName="Choose Vehicle Model" groupName="Choose Vehicle Model"
textPosition="text-start"
:isVehicleQuestion="true"
:loaderEnabled="true"
v-model="modelValue" v-model="modelValue"
/> />
</template> </template>

View file

@ -3,6 +3,9 @@
:questionText="questionText" :questionText="questionText"
:answers="styles" :answers="styles"
groupName="Choose Vehicle Style" groupName="Choose Vehicle Style"
textPosition="text-start"
:isVehicleQuestion="true"
:loaderEnabled="true"
v-model="modelValue" v-model="modelValue"
/> />
</template> </template>

View file

@ -3,6 +3,9 @@
:questionText="questionText" :questionText="questionText"
:answers="years" :answers="years"
groupName="Choose Vehicle Year" groupName="Choose Vehicle Year"
:isVehicleQuestion="true"
textPosition="text-start"
:loaderEnabled="true"
v-model="modelValue" v-model="modelValue"
/> />
</template> </template>

View file

@ -3,9 +3,8 @@ import { storeActions } from "@/constants/store-actions.js";
import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js"; import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js";
import { routingTable } from "@/router/router-constants/routing-table.js"; import { routingTable } from "@/router/router-constants/routing-table.js";
import ComponentTest from "@/layouts/component-test/component-test.vue"; import ComponentTest from "@/layouts/component-test/component-test.vue";
import FormTest from "@/layouts/form-test/form-test.vue";
import AddressPOC from "@/layouts/address-poc/address-poc.vue"; import AddressPOC from "@/layouts/address-poc/address-poc.vue";
import NotFound from "@/layouts/not-found/not-found.vue"; import NotFound from "@/layouts/not-found/not-found.vue";
import store from "@/store"; import store from "@/store";
@ -20,6 +19,11 @@ const routes = [
name: "ComponentTest", name: "ComponentTest",
component: ComponentTest, component: ComponentTest,
}, },
{
path: "/form-test", // This is a temporary route for testing.
name: "FormTest",
component: FormTest,
},
{ {
path: "/address-poc", // This is a temporary route for testing. path: "/address-poc", // This is a temporary route for testing.
name: "AddressPOC", name: "AddressPOC",

View file

@ -12,6 +12,8 @@
box-shadow: 0 0 0 2px $blue; box-shadow: 0 0 0 2px $blue;
} }
&:checked + label { &:checked + label {
color: $black;
font-weight: 500;
background: $blue-100; background: $blue-100;
box-shadow: 0 0 0 1px $blue; box-shadow: 0 0 0 1px $blue;
} }
@ -20,6 +22,7 @@
} }
} }
label { label {
color: $gray-600;
position: relative; position: relative;
background: $white; background: $white;
transition: all 150ms linear; transition: all 150ms linear;

View file

@ -14,8 +14,35 @@ body {
color: $blue; color: $blue;
text-decoration: none; text-decoration: none;
border-bottom: 1px solid $blue; border-bottom: 1px solid $blue;
line-height: 26px;
padding: 0 0 4px 0;
font-weight: 500;
&:hover { &:hover {
color: $blue-400; color: $blue-700;
}
&.small {
line-height: 24px;
&:hover {
color: $blue-700;
}
}
&.navigation-link {
color: $black;
border-bottom: 1px solid $black;
line-height: 26px;
}
&.footer-link {
color: $gray-500;
text-decoration: none;
border-bottom: 1px solid transparent;
line-height: 20px;
padding: 0 0 2px 0;
font-weight: 400;
font-size: .75rem;
&:hover {
border-bottom: 1px solid $gray-500;
padding: 0 0 2px 0;
}
} }
} }
.pointer { .pointer {

View file

@ -183,37 +183,4 @@ describe("list-button-horizontal.vue", () => {
}); });
it("Should return first item if first item in group", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
positionInGroup: 1
},
});
// Assert
const label = wrapper.find("label");
expect(label.attributes('class')).toContain("first-item");
});
it("Should return last item if last item in group", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
positionInGroup: 3,
totalInGroup: 3
},
});
// Assert
const label = wrapper.find("label");
expect(label.attributes('class')).toContain("last-item");
});
}); });

View file

@ -1,41 +1,39 @@
<!-- See the component-test.vue page for example implementation -->
<template> <template>
<!-- IMPORTANT: Refrain from using more than 4 horizontal buttons on desktop, 3 on mobile. --> <div class="list-group list-button-horizontal d-flex flex-column w-100 mb-2">
<div v-if="isMultiSelect" class="list-group list-button-horizontal d-flex flex-column w-100 mb-2"> <input :type="isMultiSelect ? 'checkbox' : 'radio'" :id="buttonID" :name="groupName" :value="buttonID" :aria-required="isRequired" @click="handleClick(value)" :data-focus-target="groupName" />
<input type="checkbox" :id="buttonID" :name="groupName" :value="buttonID" :aria-required="isRequired"> <label tabindex="-1" :for="buttonID" :aria-labelledby="buttonID" class="d-flex flex-column justify-content-center py-3 px-4">
<label tabindex="-1" aria-checked="false" :for="buttonID" :aria-labelledby="buttonID" class="d-flex flex-column justify-content-center py-3 px-4" :class="isFirstOrLastButton"> <span class="m-0" :class="textPosition">{{buttonLabel}}</span>
<span class="m-0" :class="[this.textPosition]">{{buttonID}}</span> <span v-if="buttonLabelSubCopy" class="m-0 small" :class="textPosition">{{buttonLabelSubCopy}}</span>
<span v-if="screenReaderOnlyText" class="sr-only">{{screenReaderOnlyText}}</span> <span v-if="screenReaderOnlyText" class="sr-only">{{screenReaderOnlyText}}</span>
</label> <loader v-if="isLoaderDisplayed && !isMultiSelect" :style="{width: `${sizeInRem}rem`, height: `${sizeInRem}rem`}" :class="[loaderColor, loaderPosition]" />
</div>
<div v-else class="col list-group list-button-horizontal d-flex flex-column mb-2">
<input type="radio" :id="buttonID" :name="groupName" :value="buttonID" aria-required="true" @keyup.space="handleClick()" @click="handleClick()" />
<label tabindex="-1" aria-checked="false" :for="buttonID" :aria-labelledby="buttonID" class="d-flex flex-column justify-content-center py-3 px-4" :class="isFirstOrLastButton">
<span class="m-0" :class="textPosition">{{buttonID}}</span>
<span v-if="screenReaderOnlyText" class="sr-only">{{screenReaderOnlyText}}</span>
<loader v-if="isLoaderDisplayed" :style="{width: `${sizeInRem}rem`, height: `${sizeInRem}rem`}" :class="[loaderColor, loaderPosition]" />
</label> </label>
</div> </div>
</template> </template>
<script> <script>
import { toRefs } from 'vue';
import { useField } from 'vee-validate';
import loader from "@/ux-components/loader/loader"; import loader from "@/ux-components/loader/loader";
export default { export default {
name: "listButtonHorizontal", name: "listButtonHorizontal",
props: { props: {
isMultiSelect: Boolean, /* Defines use as checkbox */ isMultiSelect: Boolean,
groupName: String, /* Required, unique for each button GROUP */ groupName: String,
buttonID: String, /* Required, unique for each button. Used for button id, label and <label for> */ buttonID: String,
screenReaderOnlyText: String, /* Optional, copy to be read by screenreader */ buttonLabel: String,
textPosition: String, /* Optional, use Bootstrap classes: text-start, text-center, text-end. Default (empty) is text-start */ buttonLabelSubCopy: String,
loaderEnabled: Boolean, /* Optional, need to include this for loader to be used at all */ screenReaderOnlyText: String,
loaderColor: String, /* Specify color of loader/spinner. Options are blue, red, green, white, black. Default is blue */ textPosition: String,
loaderPosition: String, /* Specify horizontal position of loader/spinner. Options are center, right, left */ loaderEnabled: Boolean,
sizeInRem: Number, /* Specify size of loader/spinner in rem. Example: 1.5 (equals 24px (16x1.5)) */ loaderColor: String,
totalInGroup: Number, /* Required, total number of buttons in group. Used to tell first and last in group to apply border radius. */ loaderPosition: String,
positionInGroup: Number, /* Required, position of button in group. Example, 1,2,3 */ sizeInRem: [Number, String],
isRequired: Boolean, isRequired: Boolean,
value: { // Field initial value
type: String,
default: ""
}
}, },
data() { data() {
return { return {
@ -46,24 +44,30 @@ export default {
displayLoader() { displayLoader() {
this.isLoaderDisplayed = true; this.isLoaderDisplayed = true;
}, },
handleClick() { handleClick(value) {
this.loaderEnabled && this.displayLoader(); if(this.loaderEnabled){
this.displayLoader();
}
this.handleChange(value);
} }
}, },
components: { components: {
loader, loader,
}, },
computed: { setup(props) {
isFirstOrLastButton() { const { groupName, value } = toRefs(props);
let className = ""; const inputType = props.isMultiSelect ? "checkbox" : "radio";
if (this.positionInGroup == this.totalInGroup) { const { checked, handleChange, errorMessage } = useField(groupName, undefined, {
className = "last-item"; type: inputType,
} else if (this.positionInGroup == 1) { checkedValue: value
className = "first-item"; });
} return {
return className; checked,
}, handleChange,
errorMessage,
};
}, },
}; };
</script> </script>
@ -72,8 +76,8 @@ export default {
input[type="radio"], input[type="radio"],
input[type="checkbox"] { input[type="checkbox"] {
opacity: 0; opacity: 0;
position: fixed;
width: 0; width: 0;
height: 0;
&:focus-visible + label { &:focus-visible + label {
box-shadow: 0 0 0 2px $blue; box-shadow: 0 0 0 2px $blue;
} }
@ -102,11 +106,15 @@ export default {
+ p { + p {
display: none; display: none;
} }
&.first-item { }
&:first-of-type {
label {
border-bottom-left-radius: 0.5rem; border-bottom-left-radius: 0.5rem;
border-top-left-radius: 0.5rem; border-top-left-radius: 0.5rem;
} }
&.last-item { }
&:last-of-type {
label {
border-bottom-right-radius: 0.5rem; border-bottom-right-radius: 0.5rem;
border-top-right-radius: 0.5rem; border-top-right-radius: 0.5rem;
} }

View file

@ -1,40 +1,38 @@
<template> <template>
<!-- See the component-test.vue page for example implementation --> <div class="list-group list-button d-flex flex-column w-100 mb-2">
<div v-if="isMultiSelect" class="list-group list-button d-flex flex-column w-100 mb-2"> <input :type="isMultiSelect ? 'checkbox' : 'radio'" :id="buttonID" :name="groupName" :value="buttonID" :aria-required="isRequired" @click="handleClick(value)" :data-focus-target="groupName">
<input type="checkbox" :id="buttonID" :name="groupName" :value="buttonID" :aria-required="isRequired"> <label tabindex="-1" :for="buttonID" :aria-labelledby="buttonID" class="d-flex flex-column justify-content-center py-3 px-4">
<label tabindex="-1" aria-checked="false" :for="buttonID" :aria-labelledby="buttonID" class="d-flex flex-column justify-content-center py-3 px-4"> <span class="m-0" :class="textPosition">{{ buttonLabel }}</span>
<span class="m-0" :class="[this.textPosition]">{{buttonID}}</span> <span v-if="buttonLabelSubCopy" class="m-0 small" :class="textPosition">{{buttonLabelSubCopy}}</span>
<span v-if="buttonLabelSubCopy" class="m-0 small" :class="[this.textPosition]">{{buttonLabelSubCopy}}</span> <span v-if="screenReaderOnlyText" class="sr-only">{{ screenReaderOnlyText }}</span>
<span v-if="screenReaderOnlyText" class="sr-only">{{screenReaderOnlyText}}</span> <loader v-if="isLoaderDisplayed && !isMultiSelect" :style="{width: `${sizeInRem}rem`, height: `${sizeInRem}rem`}" :class="[this.loaderColor, this.loaderPosition]" />
</label> </label>
</div> </div>
<div v-else class="list-group list-button d-flex flex-column w-100 mb-2">
<input type="radio" :id="buttonID" :name="groupName" :value="buttonID" :aria-required="isRequired" @keyup.space="displayLoader()">
<label tabindex="-1" aria-checked="false" :for="buttonID" :aria-labelledby="buttonID" class="d-flex flex-column justify-content-center py-3 px-4" @click="displayLoader()">
<span class="m-0" :class="[this.textPosition]">{{buttonID}}</span>
<span v-if="buttonLabelSubCopy" class="m-0 small" :class="[this.textPosition]">{{buttonLabelSubCopy}}</span>
<span v-if="screenReaderOnlyText" class="sr-only">{{screenReaderOnlyText}}</span>
<loader v-if="isLoaderDisplayed" :style="{width: `${sizeInRem}rem`, height: `${sizeInRem}rem`}" :class="[this.loaderColor, this.loaderPosition]" />
</label>
</div>
</template> </template>
<script> <script>
import { toRefs } from 'vue';
import { useField } from 'vee-validate';
import loader from "@/ux-components/loader/loader"; import loader from "@/ux-components/loader/loader";
export default { export default {
name: "listButton", name: "listButton",
props: { props: {
isMultiSelect: Boolean, /* Defines use as checkbox */ isMultiSelect: Boolean,
groupName: String, /* Required, unique for each button GROUP */ groupName: String,
buttonID: [Number,String], /* Required, unique for each button. Used for button id, label and <label for> */ buttonLabel: [Number,String],
isRequired: Boolean, /* Optional, default is false */ buttonID: [Number,String],
screenReaderOnlyText: String, /* Optional, copy to be read by screenreader */ isRequired: Boolean,
buttonLabelSubCopy: String, /* Optional, used for multi-line buttons */ textPosition: String,
textPosition: String, /* Optional, use Bootstrap classes: text-start, text-center, text-end. Default (empty) is text-start */ buttonLabelSubCopy: String,
loaderEnabled: Boolean, /* Optional, need to include this for loader to be used at all */ screenReaderOnlyText: String,
loaderColor: String, /* Specify color of loader/spinner. Options are blue, red, green, white, black. Default is blue */ loaderEnabled: Boolean,
loaderPosition: String, /* Specify horizontal position of loader/spinner. Options are center, right, left */ loaderColor: String,
sizeInRem: [Number,String], /* Specify size of loader/spinner in rem. Example: 1.5 (equals 24px (16x1.5)) */ loaderPosition: String,
sizeInRem: [Number,String],
value: { // Field initial value
type: String,
default: ""
}
}, },
data() { data() {
return { return {
@ -45,12 +43,40 @@ export default {
displayLoader() { displayLoader() {
this.isLoaderDisplayed = true; this.isLoaderDisplayed = true;
}, },
handleClick() { handleClick(value) {
this.loaderEnabled && this.displayLoader(); if(this.loaderEnabled){
this.displayLoader();
}
this.handleChange(value);
} }
}, },
components: { components: {
loader, loader,
} },
setup(props) {
const { groupName, value } = toRefs(props);
const inputType = props.isMultiSelect ? "checkbox" : "radio";
const { checked, handleChange, errorMessage } = useField(groupName, undefined, {
type: inputType,
checkedValue: value
});
return {
checked,
handleChange,
errorMessage,
};
},
}; };
</script> </script>
<style lang="scss">
.list-group {
&.list-button {
input[type="radio"],
input[type="checkbox"] {
position: static; //override bootstrap
height: 0;
}
}
}
</style>

View file

@ -22,65 +22,6 @@ describe("list-card.vue", () => {
expect(input.attributes().type).toEqual("checkbox"); expect(input.attributes().type).toEqual("checkbox");
}); });
it("Should return input type radio if isRadio is true", async () => {
// Act
const wrapper = shallowMount(listCard, {
propsData: {
isRadio: true,
buttonLabel: "Windshield",
buttonID: "List Card Checkbox",
groupID: "radio-demo-1",
groupName: "radio 1",
buttonImage: "windshield-damage.svg",
},
});
// Assert
const input = wrapper.find("input");
expect(input.attributes().type).toEqual("radio");
});
it("Should return input type checkbox if isMultiSelectHorizontal is true", async () => {
// Act
const wrapper = shallowMount(listCard, {
propsData: {
isMultiSelectHorizontal: true,
buttonLabel: "Windshield",
buttonID: "List Card Checkbox",
groupID: "radio-demo-1",
groupName: "radio 1",
buttonImage: "windshield-damage.svg"
},
});
// Assert
const input = wrapper.find("input");
expect(input.attributes().type).toEqual("checkbox");
});
it("Should return input type radio if isRadioHorizontal is true", async () => {
// Act
const wrapper = shallowMount(listCard, {
propsData: {
isRadioHorizontal: true,
buttonLabel: "Windshield",
buttonID: "List Card Checkbox",
groupID: "radio-demo-1",
groupName: "radio 1",
buttonImage: "windshield-damage.svg"
},
});
// Assert
const input = wrapper.find("input");
expect(input.attributes().type).toEqual("radio");
});
it("Should return primary label text", async () => { it("Should return primary label text", async () => {
// Act // Act
const wrapper = shallowMount(listCard, { const wrapper = shallowMount(listCard, {

View file

@ -1,61 +1,63 @@
<template> <template>
<!-- Heavily documented below --> <!-- Heavily documented below -->
<div v-if="isMultiSelect" class="list-card w-100 rounded-3 d-flex align-items-center h-100"> <div class="col">
<input type="checkbox" :id="buttonID" :name="groupName" :value="buttonLabel" :aria-required="isRequired" /> <div class="list-card w-100 rounded-3 d-flex align-items-center h-100" :class="isHorizontal ? 'horizontal' : ''">
<label :for="buttonID" class="d-flex flex-column w-100 align-items-center pt-4 pb-2 px-2 h-100" tabindex="1"> <input :type="isMultiSelect ? 'checkbox' : 'radio'" :id="buttonID" :name="groupName" :value="buttonID" :aria-required="isRequired" @click="handleChange(value)" :data-focus-target="groupName" />
<img class="order-1" v-bind:src="require(`@/assets/img/icons/${buttonImage}`)" v-bind:alt="altText" /> <label :for="buttonID" class="d-flex w-100 align-items-center px-2 h-100" :class="[isHorizontal ? rowClasses : columnClasses,isHorizontal && buttonLabelSubCopy ? 'checkboxTop' : '']" tabindex="-1">
<p class="small m-0 order-3">{{buttonLabel}}</p> <img :id="buttonImageId" :class="!isHorizontal ? 'order-1' : 'ms-auto order-3'" v-bind:src="require(`@/assets/img/icons/${buttonImage}`)" :alt="altText" />
<p v-if="buttonLabelSubCopy" class="fs-7 m-0 order-4">{{buttonLabelSubCopy}}</p> <p v-if="!isHorizontal" class="small order-3" :class="isMultiSelect ? 'm-0' : 'mt-2 mb-0'">{{buttonLabel}}</p>
</label> <p v-if="buttonLabelSubCopy && !isHorizontal" class="fs-7 m-0 order-4">{{buttonLabelSubCopy}}</p>
</div> <div v-if="isHorizontal" class="order-2">
<div v-else-if="isRadio" class="list-card w-100 rounded-3 d-flex align-items-center h-100"> <p class="m-0 small">{{buttonLabel}}</p>
<input type="radio" :id="buttonID" :name="groupName" :value="buttonID" :aria-required="isRequired" /> <p v-if="buttonLabelSubCopy" class="m-0 fs-7">{{buttonLabelSubCopy}}</p>
<label :for="buttonID" class="d-flex flex-column w-100 align-items-center pt-4 pb-2 px-2 h-100" tabindex="1"> </div>
<img class="order-1" v-bind:src="require(`@/assets/img/icons/${buttonImage}`)" v-bind:alt="altText" /> </label>
<p class="small mt-2 mb-0 order-2">{{buttonLabel}}</p> </div>
<p v-if="buttonLabelSubCopy" class="fs-7 m-0 order-3">{{buttonLabelSubCopy}}</p>
</label>
</div>
<div v-else-if="isMultiSelectHorizontal" class="list-card horizontal w-100 rounded-3 d-flex align-items-center h-100">
<input type="checkbox" :id="buttonID" :name="groupName" :value="buttonLabel" :aria-required="isRequired" />
<label :for="buttonID" class="d-flex flex-row w-100 align-items-center py-r ps-3 pe-8 px-2 h-100" tabindex="1" :class="{'checkboxTop': buttonLabelSubCopy}">
<img class="ms-auto order-3" v-bind:src="require(`@/assets/img/icons/${buttonImage}`)" v-bind:alt="altText" />
<div class="order-2">
<p class="m-0 small">{{buttonLabel}}</p>
<p v-if="buttonLabelSubCopy" class="m-0 fs-7">{{buttonLabelSubCopy}}</p>
</div>
</label>
</div>
<div v-else-if="isRadioHorizontal" class="list-card horizontal w-100 rounded-3 d-flex align-items-center h-100">
<input type="radio" :id="buttonID" :name="groupName" :value="buttonLabel" :aria-required="isRequired" />
<label :for="buttonID" class="d-flex flex-row w-100 align-items-center py-r ps-3 pe-8 px-2 h-100" tabindex="1" :class="{'checkboxTop': buttonLabelSubCopy}">
<img class="ms-auto order-3" v-bind:src="require(`@/assets/img/icons/${buttonImage}`)" v-bind:alt="altText" />
<div class="order-2">
<p class="m-0 small">{{buttonLabel}}</p>
<p v-if="buttonLabelSubCopy" class="m-0 fs-7">{{buttonLabelSubCopy}}</p>
</div>
</label>
</div> </div>
</template> </template>
<script> <script>
import { toRefs } from 'vue';
import { useField } from 'vee-validate';
export default { export default {
name: "listCard", name: "listCard",
props: { props: {
//Must choose one of the following four options //Must choose one of the following four options
isMultiSelect: Boolean, //Defines use as checkbox isMultiSelect: Boolean, //Defines use as checkbox
isRadio: Boolean, //Defines use as radio button isHorizontal: Boolean,
isMultiSelectHorizontal: Boolean, //Defines use as horizontal checkbox
isRadioHorizontal: Boolean, //Defines use as horizontal radio button
//end must choose //end must choose
buttonImage: String,//Required: File name of image buttonImage: String,//Required: File name of image
buttonImageId: String,
buttonLabel: String,//Required: Label text buttonLabel: String,//Required: Label text
isRequired: Boolean, //Required: is aria-required required or not? 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. altText: String,//Leave empty. Screen readers read the buttonLabel text. If alt has content, it will repeat unnecessarily.
buttonID: String,//Required: Unique buttonID: String,//Required: Unique
groupName: String,//Required: Unique groupName: String,//Rquired: Unique
buttonLabelSubCopy: String,//Optional: sub tex buttonLabelSubCopy: String,//Optional: sub text
} value: { // Field initial value
type: String,
default: ""
}
},
data(){
return {
columnClasses: "flex-column pt-4 pb-2",
rowClasses: "flex-row py-r ps-3 pe-8"
}
},
setup(props) {
const { groupName, value } = toRefs(props);
const inputType = props.isMultiSelect ? "checkbox" : "radio";
const { checked, handleChange } = useField(groupName, undefined, {
type: inputType,
checkedValue: value,
});
return {
checked,
handleChange,
};
},
}; };
</script> </script>
@ -77,8 +79,8 @@ export default {
input[type='checkbox'], input[type='checkbox'],
input[type="radio"] { input[type="radio"] {
opacity: 0; opacity: 0;
position: fixed;
width: 0; width: 0;
height: 0;
+ label { + label {
display: block; display: block;
position: relative; position: relative;

View file

@ -8,17 +8,13 @@ module.exports = {
loaderOptions: { loaderOptions: {
sass: { sass: {
// Load Order Matters!!! // Load Order Matters!!!
// Note: only include functions, variables and mixins here
prependData: ` prependData: `
@import "./node_modules/bootstrap/scss/functions"; @import "./node_modules/bootstrap/scss/functions";
@import "@/styles/ux-variables.scss"; @import "@/styles/ux-variables.scss";
@import "./node_modules/bootstrap/scss/variables"; @import "./node_modules/bootstrap/scss/variables";
@import "./node_modules/bootstrap/scss/mixins"; @import "./node_modules/bootstrap/scss/mixins";
@import "@/styles/mixins/customMixins"; @import "@/styles/mixins/customMixins";
@import "./node_modules/bootstrap/scss/bootstrap";
@import "@/styles/common-styles.scss";
@import "@/styles/common-button-styles.scss";
@import "@/styles/common-list-styles.scss";
@import "@/styles/common-typography-styles.scss";
`, `,
}, },
}, },

View file

@ -7,17 +7,13 @@ module.exports = {
loaderOptions: { loaderOptions: {
sass: { sass: {
// Load Order Matters!!! // Load Order Matters!!!
// Note: only include functions, variables and mixins here
prependData: ` prependData: `
@import "./node_modules/bootstrap/scss/functions"; @import "./node_modules/bootstrap/scss/functions";
@import "@/styles/ux-variables.scss"; @import "@/styles/ux-variables.scss";
@import "./node_modules/bootstrap/scss/variables"; @import "./node_modules/bootstrap/scss/variables";
@import "./node_modules/bootstrap/scss/mixins"; @import "./node_modules/bootstrap/scss/mixins";
@import "@/styles/mixins/customMixins"; @import "@/styles/mixins/customMixins";
@import "./node_modules/bootstrap/scss/bootstrap";
@import "@/styles/common-styles.scss";
@import "@/styles/common-button-styles.scss";
@import "@/styles/common-list-styles.scss";
@import "@/styles/common-typography-styles.scss";
`, `,
}, },
}, },