CASH-2378: create new radio button for multi location modal usage
This commit is contained in:
parent
664a7d289d
commit
0c32eecdcc
3 changed files with 433 additions and 0 deletions
|
|
@ -226,6 +226,7 @@ export default {
|
||||||
let classes;
|
let classes;
|
||||||
switch (this.buttonTypeString) {
|
switch (this.buttonTypeString) {
|
||||||
case "timeSlotModalListButton":
|
case "timeSlotModalListButton":
|
||||||
|
case "multiLocationRadioButton":
|
||||||
case "listButton":
|
case "listButton":
|
||||||
classes = "w-100";
|
classes = "w-100";
|
||||||
break;
|
break;
|
||||||
|
|
@ -265,6 +266,8 @@ export default {
|
||||||
classes += " radio-button-container";
|
classes += " radio-button-container";
|
||||||
} else if (this.buttonTypeString == "servicePackageRadio") {
|
} else if (this.buttonTypeString == "servicePackageRadio") {
|
||||||
classes = "package-wrapper col";
|
classes = "package-wrapper col";
|
||||||
|
} else if (this.buttonTypeString == "multiLocationRadioButton") {
|
||||||
|
classes = "multi-location-wrapper col";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.buttonTypeString == "listCard" && this.buttonsInfo.length > 2) {
|
if (this.buttonTypeString == "listCard" && this.buttonsInfo.length > 2) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
import { mount } from "@vue/test-utils";
|
||||||
|
import multiLocationRadio from "./multi-location-radio";
|
||||||
|
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||||
|
|
||||||
|
describe("multi-location-radio.vue", () => {
|
||||||
|
it("Should include buttonLabel in html", async () => {
|
||||||
|
// Arrange
|
||||||
|
let { wrapper } = setupMocks({
|
||||||
|
mountOptionsMockData: {
|
||||||
|
propsData: mockProps,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const outputHtml = wrapper.html();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabel"]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should include buttonLabelSubCopy in html", async () => {
|
||||||
|
// Arrange
|
||||||
|
let { wrapper } = setupMocks({
|
||||||
|
mountOptionsMockData: {
|
||||||
|
propsData: mockProps,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const outputHtml = wrapper.html();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabelSubCopy"]));
|
||||||
|
});
|
||||||
|
it("Should include buttonFooterCopy in html", async () => {
|
||||||
|
// Arrange
|
||||||
|
let { wrapper } = setupMocks({
|
||||||
|
mountOptionsMockData: {
|
||||||
|
propsData: mockProps,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const outputHtml = wrapper.html();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonFooterCopy"]));
|
||||||
|
});
|
||||||
|
it("Should get 5 strings from getArrayOfListItemsFromRawCmsCopy when provided with a <ul><li>...</li>(x5)</ul> buttonBodyCopy", async () => {
|
||||||
|
// Arrange
|
||||||
|
let { wrapper } = setupMocks({
|
||||||
|
mountOptionsMockData: {
|
||||||
|
propsData: mockProps,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// Act
|
||||||
|
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(results.length).toBe(5);
|
||||||
|
});
|
||||||
|
it("Should get strings from getArrayOfListItemsFromRawCmsCopy without <ul> or <li> tags when provided with a <ul><li>...</li></ul> buttonBodyCopy", async () => {
|
||||||
|
// Arrange
|
||||||
|
let { wrapper } = setupMocks({
|
||||||
|
mountOptionsMockData: {
|
||||||
|
propsData: mockProps,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const fileredResults = results.filter((result) => {
|
||||||
|
return (
|
||||||
|
result.includes("<ul>") ||
|
||||||
|
result.includes("</ul>") ||
|
||||||
|
result.includes("<li>") ||
|
||||||
|
result.includes("</li>")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
expect(fileredResults.length).toBe(0);
|
||||||
|
});
|
||||||
|
it("Should get 5 strings from getArrayOfListItemsFromRawCmsCopy when buttonBodyCopy has 6 total <li>...</li>, but one is empty ", async () => {
|
||||||
|
// Arrange
|
||||||
|
const moddedProps = mockProps;
|
||||||
|
moddedProps["buttonBodyCopy"] =
|
||||||
|
"<ul><li>buttonBodyCopy test copy</li><li>2</li><li>3</li><li>4</li><li>5</li><li></li></ul>";
|
||||||
|
let { wrapper } = setupMocks({
|
||||||
|
mountOptionsMockData: {
|
||||||
|
propsData: moddedProps,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(results.length).toBe(5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockProps = {
|
||||||
|
groupName: "group-name",
|
||||||
|
buttonLabel: "buttonLabel test copy",
|
||||||
|
buttonLabelAuxillaryCopy: "buttonLabelAuxillaryCopy test copy",
|
||||||
|
buttonLabelSubCopy: "buttonLabelSubCopy test copy",
|
||||||
|
buttonBodyCopy:
|
||||||
|
"<ul><li>buttonBodyCopy test copy</li><li>2</li><li>3</li><li>4</li><li>5</li></ul>",
|
||||||
|
buttonFooterCopy: "buttonFooterCopy test copy",
|
||||||
|
buttonAuxillaryCopy: "buttonAuxillaryCopy test copy",
|
||||||
|
};
|
||||||
|
|
||||||
|
function setupMocks({ mountOptionsMockData = {} }) {
|
||||||
|
const wrapper = mount(multiLocationRadio, {
|
||||||
|
...mountOptionsMockData,
|
||||||
|
mixins: [inputButtonWrapperMixin],
|
||||||
|
});
|
||||||
|
|
||||||
|
return { wrapper };
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,309 @@
|
||||||
|
<template>
|
||||||
|
<baseInputButton v-bind="$props" @buttonClicked="handleAnswerChange" v-model="selectedValue">
|
||||||
|
<div class="location-label" for="testradio">
|
||||||
|
<div class="location-specs">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col md-6">
|
||||||
|
<p class="m-0">
|
||||||
|
<span v-html="this.buttonLabel"></span>
|
||||||
|
</p>
|
||||||
|
<span v-html="this.buttonAuxillaryCopy"></span>
|
||||||
|
<p
|
||||||
|
class="sub-label m-0"
|
||||||
|
v-if="this.buttonLabelSubCopy"
|
||||||
|
v-html="this.buttonLabelSubCopy"></p>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="col md-6 location-info">
|
||||||
|
<div
|
||||||
|
class="location-info-label"
|
||||||
|
v-if="this.buttonBodyCopy"
|
||||||
|
v-html="this.buttonBodyCopy"></div>
|
||||||
|
</div>
|
||||||
|
<div class="time-info" v-html="this.buttonFooterCopy"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</baseInputButton>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import baseInputButton from "@/digital-components/base-input-button/base-input-button";
|
||||||
|
import textLink from "@/ux-components/text-link/text-link";
|
||||||
|
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||||
|
import {
|
||||||
|
getRouterLinkDisplayTextFromCopy,
|
||||||
|
getRouterLinkRouteFromCopy,
|
||||||
|
splitCopyOnCMSPlaceHolder,
|
||||||
|
doesCopyContainTextLink,
|
||||||
|
} from "@/helpers/cms-content-helper";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "multiLocationRadioButton",
|
||||||
|
mixins: [inputButtonWrapperMixin],
|
||||||
|
components: {
|
||||||
|
baseInputButton,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
arrayOfListItemsFromBodyText() {
|
||||||
|
return this.getArrayOfListItemsFromRawCmsCopy(this.buttonBodyCopy);
|
||||||
|
},
|
||||||
|
shouldDisplayStrikeThroughPrice() {
|
||||||
|
const displayPrice = this.buttonAuxillaryCopy?.substring(
|
||||||
|
this.buttonAuxillaryCopy.indexOf("$")
|
||||||
|
);
|
||||||
|
return displayPrice != this.additionalButtonData?.strikeThroughPrice;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getRouterLinkDisplayTextFromCopy,
|
||||||
|
getRouterLinkRouteFromCopy,
|
||||||
|
splitCopyOnCMSPlaceHolder,
|
||||||
|
doesCopyContainTextLink,
|
||||||
|
stripUlTagFromCopy(copy) {
|
||||||
|
const regex = /(?:<ul(?:.*?)>)|(?:<\/ul>)/g;
|
||||||
|
return copy?.replace(regex, "");
|
||||||
|
},
|
||||||
|
getArrayOfListItemsFromRawCmsCopy(copy) {
|
||||||
|
const withoutUlTags = this.stripUlTagFromCopy(copy);
|
||||||
|
return withoutUlTags
|
||||||
|
?.split(/(?:<li(?:.*?)>)|(?:<\/li>)/g)
|
||||||
|
?.filter((lineItem) => lineItem);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.multi-location-wrapper {
|
||||||
|
margin: 1rem 0;
|
||||||
|
&:first-child {
|
||||||
|
margin: 1rem 0 0 0;
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
margin: 1rem 0 0 0;
|
||||||
|
}
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
margin: 1rem 0.5rem 0 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
&:first-child {
|
||||||
|
margin: 1rem 0.5rem 0 0;
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
margin: 1rem 0 0 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"] {
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
left: -9999px;
|
||||||
|
|
||||||
|
+ .location-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid $gray-300;
|
||||||
|
box-shadow:
|
||||||
|
0px 4px 8px -4px rgba(0, 0, 0, 0.15),
|
||||||
|
0px 4px 24px -8px rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 52px;
|
||||||
|
max-height: 126px;
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
max-height: 500px;
|
||||||
|
padding-bottom: 3rem;
|
||||||
|
}
|
||||||
|
transition: max-height 0.25s ease-in;
|
||||||
|
|
||||||
|
&.has-subheader {
|
||||||
|
min-height: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
position: relative;
|
||||||
|
top: 5px;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid $gray-500;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
min-width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 15px;
|
||||||
|
top: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
min-width: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
+ .location-label {
|
||||||
|
&:before {
|
||||||
|
border: 1px solid #8e9292;
|
||||||
|
box-shadow:
|
||||||
|
0px 0px 0px 4px #9fcee6,
|
||||||
|
0px 1px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked {
|
||||||
|
+ .location-label {
|
||||||
|
.location-specs {
|
||||||
|
max-height: 1000px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+ .location-label {
|
||||||
|
.location-specs {
|
||||||
|
.hide-when-closed {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
p:first-child {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+ .location-label {
|
||||||
|
border: 1px solid $blue;
|
||||||
|
max-height: 500px;
|
||||||
|
background-color: $blue-100;
|
||||||
|
.special-save-box {
|
||||||
|
background-color: $green-200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+ .location-label {
|
||||||
|
&:before {
|
||||||
|
box-shadow: 0px 0px 0px 1px $blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+ .location-label {
|
||||||
|
&:after {
|
||||||
|
background: $blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
+ .location-label {
|
||||||
|
&:before {
|
||||||
|
border: 2px solid $blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-footer {
|
||||||
|
color: $red;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-specs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
max-height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-info .insurance-pricing span {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-family: UrbanistSemibold, Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-family: UrbanistSemibold, Arial, Helvetica, sans-serif;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
&:first-child {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
&.pricing-info {
|
||||||
|
color: $green;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-family: UrbanistSemibold, Arial, Helvetica, sans-serif;
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0.75rem;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
margin-left: 1.25rem;
|
||||||
|
}
|
||||||
|
span.strikethrough-price {
|
||||||
|
text-decoration: line-through;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 400;
|
||||||
|
color: $gray-550;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.sub-label {
|
||||||
|
color: $green;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 1rem 0 0 -5px;
|
||||||
|
padding: 0;
|
||||||
|
li {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.714;
|
||||||
|
color: $gray-550;
|
||||||
|
a {
|
||||||
|
line-height: 1.714;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideaway {
|
||||||
|
from {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(40px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide-when-closed {
|
||||||
|
display: none;
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in a new issue