191 lines
5.5 KiB
Vue
191 lines
5.5 KiB
Vue
<template>
|
|
<div
|
|
class="alert fade show text-center mb-0 py-2 px-4"
|
|
role="alert"
|
|
:class="[isDismissible ? 'alert-dismissible' : '', this.alertClass]"
|
|
>
|
|
<p class="m-0 fw-bold alert-heading">{{ alertHeadline }}</p>
|
|
<template v-for="paragraph in splitAlertCopyForParagraphTag" :key="paragraph">
|
|
<p class="m-0 text-body small" v-if="!doesCopyContainRouterLink(paragraph)" v-html="paragraph"></p>
|
|
<p class="m-0 text-body small" v-else>
|
|
<template v-for="copy in splitCopyForRouterLink(paragraph)" :key="copy">
|
|
<span v-if="!doesCopyContainRouterLink(copy)" v-html="copy"></span>
|
|
<span v-else>
|
|
<router-link :to="{query: {fmgPage: `${getRouterLinkRouteFromCopy(copy)}`}, name: 'root'}">{{ getRouterLinkDisplayTextFromCopy(copy) }}</router-link>
|
|
</span>
|
|
</template>
|
|
</p>
|
|
</template>
|
|
<button
|
|
type="button"
|
|
class="btn-close p-2"
|
|
data-bs-dismiss="alert"
|
|
aria-label="Close"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 23.7 23.7"
|
|
xml:space="preserve"
|
|
>
|
|
<path
|
|
d="m23.24 2.7-9.15 9.15L23.24 21a1.581 1.581 0 0 1-1.12 2.7c-.42 0-.82-.16-1.12-.46l-9.15-9.15-9.15 9.15c-.3.3-.7.46-1.12.46A1.581 1.581 0 0 1 .46 21l8.47-8.47.68-.68L.46 2.7c-.62-.62-.62-1.62 0-2.24.62-.62 1.62-.62 2.24 0l8.47 8.47.68.68L21 .46a1.57 1.57 0 0 1 2.23 0c.63.62.63 1.62.01 2.24z"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
|
|
export default {
|
|
name: "alert",
|
|
props: {
|
|
isDismissible: Boolean,
|
|
/*
|
|
alertClass class names:
|
|
alert-success (green)
|
|
alert-danger (red)
|
|
alert-warning (yellow)
|
|
alert-info (blue)
|
|
*/
|
|
alertClass: String,
|
|
cmsWidgetName: String,
|
|
manualHeadline: String,
|
|
manualCopy: String,
|
|
shouldScrollToOnMount: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
},
|
|
computed: {
|
|
alertHeadline(){
|
|
return this.cmsWidgetName ? this.getCmsContent(this.cmsWidgetName, 'HeadlineText') : this.manualHeadline;
|
|
},
|
|
alertCopy(){
|
|
return this.cmsWidgetName ? this.getCmsContent(this.cmsWidgetName, 'BodyText') : this.manualCopy;
|
|
},
|
|
splitAlertCopyForParagraphTag(){
|
|
// splits the alertCopy on <p ... > (with or without attributes) and </p>
|
|
// filter removes empty strings that are a result of string.split with regex
|
|
return this.alertCopy.split(/(?:<p(?:.*?)>)|(?:<\/p>)/g).filter(paragraph => paragraph !== "");
|
|
},
|
|
},
|
|
methods: {
|
|
doesCopyContainRouterLink(copy) {
|
|
return copy.includes('routerLink:');
|
|
},
|
|
splitCopyForRouterLink(copy){
|
|
// splits copy on { ... } such as {routerlink: ...}
|
|
return copy.split(/{(.*?)}/g);
|
|
},
|
|
getRouterLinkRouteFromCopy(copy){
|
|
// sample input: {routerLink:estimate,provide your VIN}
|
|
// first split would return 'estimate,provide your VIN'
|
|
// second split would return 'estimate'
|
|
return copy.split(':')[1].split(',')[0];
|
|
},
|
|
getRouterLinkDisplayTextFromCopy(copy){
|
|
// sample input: {routerLink:estimate,provide your VIN}
|
|
// first split would return 'estimate,provide your VIN'
|
|
// second split would return 'provide your VIN'
|
|
return copy.split(':')[1].split(',')[1];
|
|
},
|
|
ensureAlertIsInViewPort() {
|
|
if (this.shouldScrollToOnMount && this.$el.style.display != 'none') {
|
|
var footerHeight = this.getFooterInfoBoxHeight();
|
|
if (!this.isAlertInViewport(footerHeight)) {
|
|
this.scrollContainerToAlert(footerHeight);
|
|
}
|
|
}
|
|
},
|
|
isAlertInViewport(footerHeight) {
|
|
const rect = this.$el.getBoundingClientRect();
|
|
return (
|
|
rect.top >= 0 &&
|
|
// remove footerHeight from window height to avoid items being hidden behind footer
|
|
rect.bottom <= (window.innerHeight - footerHeight || document.documentElement.clientHeight - footerHeight)
|
|
);
|
|
},
|
|
scrollContainerToAlert(footerHeight) {
|
|
// alert position on page + height of alert + footer height
|
|
var scrollToHeight = this.$el.scrollHeight + this.$el.offsetHeight + footerHeight;
|
|
// find the div wrapped by the form element - this is the scrollable container
|
|
// should be a more future-proof selector in case of CSS class changes
|
|
var pageContainerScrollable = document.querySelector('form > div');
|
|
pageContainerScrollable.scrollTo(0, scrollToHeight);
|
|
},
|
|
},
|
|
mounted() {
|
|
this.ensureAlertIsInViewPort();
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.alert {
|
|
button {
|
|
display: none;
|
|
}
|
|
.btn-close {
|
|
background: none;
|
|
opacity: 1;
|
|
width: 0.75rem;
|
|
height: 0.75rem;
|
|
}
|
|
&.alert-dismissible {
|
|
button {
|
|
display: flex;
|
|
top: 2px;
|
|
right: 2px;
|
|
}
|
|
}
|
|
&.alert-info {
|
|
background-color: $blue-100;
|
|
.alert-heading {
|
|
color: $blue-700;
|
|
}
|
|
svg {
|
|
fill: $blue-700;
|
|
width: 1rem;
|
|
height: 1rem;
|
|
}
|
|
}
|
|
&.alert-danger {
|
|
background-color: $red-100;
|
|
.alert-heading {
|
|
color: $red-600;
|
|
}
|
|
svg {
|
|
fill: $red-600;
|
|
width: 1rem;
|
|
height: 1rem;
|
|
}
|
|
}
|
|
&.alert-warning {
|
|
background-color: $yellow-100;
|
|
.alert-heading {
|
|
color: $yellow-600;
|
|
}
|
|
svg {
|
|
fill: $yellow-600;
|
|
width: 1rem;
|
|
height: 1rem;
|
|
}
|
|
}
|
|
&.alert-success {
|
|
background-color: $green-100;
|
|
.alert-heading {
|
|
color: $green-700;
|
|
}
|
|
svg {
|
|
fill: $green-700;
|
|
width: 1rem;
|
|
height: 1rem;
|
|
}
|
|
}
|
|
& p {
|
|
font-size: .875rem;
|
|
margin-bottom: 0.25rem !important;
|
|
}
|
|
}
|
|
</style>
|