Merge branch 'develop' into feature/INSR-7754

This commit is contained in:
Katie Kroell 2026-01-08 10:19:21 -05:00
commit 3d64506791
28 changed files with 442 additions and 653 deletions

View file

@ -0,0 +1,8 @@
const buttonSizes = Object.freeze({
xs: 'xs',
sm: 'sm',
md: 'md',
lg: 'lg'
});
export default buttonSizes;

View file

@ -0,0 +1,9 @@
const buttonVariants = Object.freeze({
primary: 'primary',
secondary: 'secondary',
success: 'success',
link: 'link',
navigation: 'navigation'
});
export default buttonVariants;

View file

@ -81,7 +81,6 @@ describe('modal.vue', () => {
validate: mockValidate(true)
});
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, {
props: {
footerButtonText,
@ -89,7 +88,6 @@ describe('modal.vue', () => {
},
attachTo: document.body
});
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Act
const buttonMain = wrapper.findComponent({ ref: 'modalButtonMain' });
@ -112,8 +110,6 @@ describe('modal.vue', () => {
meta: mockMeta(fakeMeta),
validate: mockValidate(false)
});
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, {
props: {
footerButtonText,
@ -121,7 +117,6 @@ describe('modal.vue', () => {
},
attachTo: document.body
});
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Act
const buttonMain = wrapper.findComponent({ ref: 'modalButtonMain' });
@ -144,8 +139,6 @@ describe('modal.vue', () => {
meta: mockMeta(fakeMeta),
validate: mockValidate(true)
});
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, {
props: {
footerButtonText,
@ -153,7 +146,6 @@ describe('modal.vue', () => {
},
attachTo: document.body
});
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Assert
expect(wrapper.vm.isFooterButtonDisabled).toBe(true);
@ -172,8 +164,6 @@ describe('modal.vue', () => {
meta: mockMeta(fakeMeta),
validate: mockValidate(true)
});
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, {
props: {
footerButtonText,
@ -181,7 +171,6 @@ describe('modal.vue', () => {
},
attachTo: document.body
});
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Assert
expect(wrapper.vm.isFooterButtonDisabled).toBe(true);
@ -202,8 +191,6 @@ describe('modal.vue', () => {
});
const showMock = jest.spyOn(Modal.prototype, 'show');
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, {
props: {
footerButtonText,
@ -211,7 +198,6 @@ describe('modal.vue', () => {
},
attachTo: document.body
});
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Act
wrapper.vm.openModal();
@ -234,8 +220,6 @@ describe('modal.vue', () => {
validate: mockValidate(true)
});
const hideMock = jest.spyOn(Modal.prototype, 'hide');
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, {
props: {
footerButtonText,
@ -243,7 +227,6 @@ describe('modal.vue', () => {
},
attachTo: document.body
});
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Act
wrapper.vm.openModal();

View file

@ -11,7 +11,11 @@
'hidden.bs.modal': onModalClosed,
'shown.bs.modal': onModalOpened
}">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog"
:class="{
'modal-dialog-edge': modalPosition === 'edge',
'modal-dialog-centered': modalPosition === 'center'
}">
<div
class="modal-content"
role="dialog"
@ -34,30 +38,18 @@
<slot></slot>
</div>
<div class="modal-footer px-5 py-4">
<modalButtonMain
v-if="useDefaultButton"
<buttonMain
id="modalbtn"
ref="modalButtonMain"
isPrimary
:variant="buttonVariant"
class="w-100"
:buttonText="footerButtonText"
pushToGA
:class="[
(isButtonDisabled || isFooterButtonDisabled) &&
'form-test-invalid',
additionalClassesString
'form-test-invalid'
]"
@clickEvent="validateAndEmit" />
<button
v-if="!useDefaultButton"
class="w-100 btn d-flex align-items-center justify-content-center delay"
:class="[
(isButtonDisabled || isFooterButtonDisabled) &&
'form-test-invalid',
additionalClassesString
]"
@click="validateAndEmit">
<span class="m-0">{{ footerButtonText }}</span>
</button>
</div>
</div>
</div>
@ -65,7 +57,7 @@
</template>
<script>
import modalButtonMain from '@/ux-components/modal-button-main/modal-button-main.vue';
import ButtonMain from '@/ux-components/button-main/button-main.vue';
import { Modal } from 'bootstrap';
import { useForm } from 'vee-validate';
@ -73,7 +65,7 @@ export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'modal',
components: {
modalButtonMain
ButtonMain
},
props: {
modalId: String,
@ -86,13 +78,13 @@ export default {
type: Function
},
isButtonDisabled: Boolean,
useDefaultButton: {
type: Boolean,
default: true
},
additionalClassesString: {
buttonVariant: {
type: String,
default: null
default: 'success'
},
modalPosition: {
type: String,
default: 'edge'
}
},
emits: ['footer-button-event', 'isModalOpened'],
@ -131,8 +123,6 @@ export default {
const validationResult = await this.validate();
if (validationResult.valid) {
this.$emit('footer-button-event');
} else {
this.resetButtonStyle();
}
},
async onKeyDown(e) {
@ -141,14 +131,10 @@ export default {
this.$refs.modalButtonMain.clicked();
document.getElementById(this.modalId)?.focus();
},
resetButtonStyle() {
this.$refs.modalButtonMain?.resetButtonStyle();
},
onModalOpened() {
this.onModalOpenedCallback?.();
},
onModalClosed() {
this.resetButtonStyle();
this.onModalClosedCallback?.();
},
openModal() {
@ -214,24 +200,10 @@ export default {
}
&.modal-component {
.modal-dialog {
display: flex;
max-width: 47.9375rem;
margin: 0 auto;
@include media-breakpoint-up(md) {
width: 23.5rem;
position: absolute;
top: 0;
right: 0;
transform: translate(100%, 0);
}
.modal-content {
margin: 1.5rem auto 0 auto;
box-shadow: 0px 1rem 3rem -1rem rgba(0, 0, 0, 0.25);
border-radius: 1.5rem 1.5rem 0 0;
@include media-breakpoint-up(md) {
box-shadow: none;
border-radius: 0;
margin: 0 auto;
}
.modal-body {
.col {
max-width: 100%;
@ -257,11 +229,40 @@ export default {
}
}
}
&.modal-dialog-centered {
align-items: flex-end;
&.modal-dialog-edge {
min-height: 100%;
margin: 0 auto;
align-items: flex-end;
@include media-breakpoint-up(md) {
align-items: inherit;
width: 23.5rem;
position: absolute;
top: 0;
right: 0;
transform: translate(100%);
}
.modal-content {
box-shadow: 0px 1rem 3rem -1rem rgba(0, 0, 0, 0.25);
border-radius: 1.5rem 1.5rem 0 0;
@include media-breakpoint-up(md) {
box-shadow: none;
border-radius: 0;
margin: 0 auto;
}
}
}
&.modal-dialog-centered {
.modal-content {
box-shadow: 0px 1rem 3rem -1rem rgba(0, 0, 0, 0.25);
border-radius: 1.5rem;
overflow: auto;
}
}
}
&.show .modal-dialog {
&.modal-dialog-edge {
@include media-breakpoint-up(md) {
transform: translateY(0);
}
}
}
@ -273,9 +274,6 @@ export default {
}
}
}
&.show .modal-dialog {
transform: translateY(0);
}
@include media-breakpoint-up(md) {
&.fade {
transition: opacity 0.25s linear;

View file

@ -1,7 +1,10 @@
<template>
<div
class="textarea-question"
:class="(errors && errors.length) ? 'has-error' : ''">
:class="[
(errors && errors.length) ? 'has-error' : '',
(meta?.valid && meta?.touched && !(errors && errors.length)) ? 'has-success' : ''
]">
<label
:for="inputId"
class="form-label">
@ -84,7 +87,7 @@ export default {
initialValue
};
const { errorMessage, handleChange, handleBlur, validate, errors, resetField } =
const { errorMessage, handleChange, handleBlur, validate, errors, resetField, meta } =
useField(props.inputId, props.validationRules, fieldOptions);
return {
@ -93,7 +96,8 @@ export default {
handleBlur,
validate,
errors,
resetField
resetField,
meta
};
},
computed: {

View file

@ -1,7 +1,10 @@
<template>
<div
class="textbox-question"
:class="(errors && errors.length) || hasError ? 'has-error' : ''">
:class="[
((errors && errors.length) || hasError) ? 'has-error' : '',
(meta?.valid && meta?.touched && !((errors && errors.length) || hasError)) ? 'has-success' : ''
]">
<label
v-if="displayQuestionText"
:for="inputId"
@ -271,8 +274,8 @@ input::-webkit-date-and-time-value {
}
.form-test-error span {
color: #d4281c;
font-size: 0.875rem;
font-weight: 500;
font-size: 1rem;
font-weight: 400;
}
.input-wrapper {

View file

@ -5,8 +5,7 @@
class="modal fade modal-component"
tabindex="-1"
aria-labelledby="ModalComponentLabel"
aria-hidden="true"
v-on="{ 'hidden.bs.modal': resetButtonStyle }">
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
@ -34,8 +33,7 @@
<buttonMain
ref="buttonMain"
class="w-100"
isPrimary
suppressLoader
variant="success"
:buttonText="ModalSelectButtonText"
data-bs-dismiss="modal"
@clickEvent="buttonClick" />
@ -70,9 +68,6 @@ export default {
}
},
methods: {
resetButtonStyle() {
this.$refs.buttonMain.resetButtonStyle();
}
}
};
</script>

View file

@ -7,19 +7,19 @@
ref="continueModal"
:headerText="modalHeaderText"
:footerButtonText="modalFooterText"
buttonVariant="primary"
@isModalOpened="setModalStatus"
@footerButtonEvent="startNewReferral">
<p class="mb-2 subheader-text text-center">
{{ modalSubHeaderText }}
</p>
<button
<buttonMain
id="btnContinueReferral"
type="button"
class="btn btn-white-blue continue-referral
align-items-center justify-content-center d-inline-flex mt-2 py-3 px-4 delay w-100"
@click="continueReferral">
{{ modalBodyText }}
</button>
variant="success"
class="mt-2 w-100"
:buttonText="modalBodyText"
@click-event="continueReferral">
</buttonMain>
</modal>
</div>
</transition>
@ -28,10 +28,12 @@
<script>
import modal from '@/digital-components/modal/modal.vue';
import { getISSCookie } from '@/helpers/cookie-helper.js';
import ButtonMain from '@/ux-components/button-main/button-main.vue';
export default {
name: 'continue-modal',
components: {
ButtonMain,
modal
},
emits: ['continue-previous-referral', 'start-new-referral'],
@ -80,16 +82,5 @@ export default {
</script>
<style lang="scss" scoped>
.btn-white-blue {
border-radius: 0.5rem;
border-color: $blue;
color: $blue;
font-size: 1rem;
font-weight: $font-weight-bold;
line-height: 1.5rem;
background-color: #ffffff;
--bs-btn-focus-shadow-rgb: 218, 72, 62;
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}
</style>

View file

@ -16,7 +16,7 @@
<buttonMain
v-if="!isForwardButtonHidden"
ref="buttonMain"
isPrimary
variant="navigation"
:buttonText="buttonText"
:class="
(disableForwardAction || isForwardActionDisabled) &&
@ -136,7 +136,7 @@ export default {
width: auto;
justify-content: flex-end;
}
.btn-primary {
.btn-navigation {
width: auto;
}
a {
@ -149,7 +149,7 @@ export default {
overflow-x: visible; // needed to fix hidden footer on some iphones
}
div.flex-column {
.btn-primary {
.btn-navigation {
width: 100%;
justify-content: center;
}

View file

@ -8,20 +8,17 @@
ref="cancelClaimModal"
:headerText="modalHeaderText"
:footerButtonText="modalFooterText"
additionalClassesString="navigation-link"
:useDefaultButton="false"
buttonVariant="link"
@isModalOpened="setModalStatus"
@footerButtonEvent="cancelClaimConfirmation">
<div v-html="modalBodyText"></div>
<button
<buttonMain
id="btnContinueReferral"
type="button"
:aria-disabled="false"
class="btn btn-primary btn-override
align-items-center justify-content-center d-inline-flex mt-4 py-3 px-4 delay w-100"
@click="returnToClaim">
{{ modalBodyText2 }}
</button>
variant="success"
:buttonText="modalBodyText2"
class="mt-4 w-100"
@clickEvent="returnToClaim">
</buttonMain>
</modal>
</div>
</transition>
@ -29,10 +26,12 @@
<script>
import modal from '@/digital-components/modal/modal.vue';
import ButtonMain from '@/ux-components/button-main/button-main.vue';
export default {
name: 'cancel-claim-modal',
components: {
ButtonMain,
modal
},
emits: ['cancel-claim-confirmation', 'return-to-claim'],
@ -76,19 +75,5 @@ export default {
</script>
<style lang="scss" scoped>
.btn {
&.btn-primary {
position: relative;
background: linear-gradient(270deg, $blue 0%, $blue-800 100%);
border: none;
border-radius: $border-radius-lg;
color: $white;
justify-content: center;
font-weight: $font-weight-bold;
letter-spacing: inherit;
@media (hover: hover) {
background: linear-gradient(270deg, $blue 0%, $blue-800 100%);
}
}
}
</style>

View file

@ -2,7 +2,6 @@
<buttonQuestion
ref="policyVehiclesQuestion"
v-model="selectedVehicleVin"
:questionText="questionText"
groupName="policyVehiclesQuestionOption"
buttonTypeString="listButton"
isOverflowScrollable
@ -25,9 +24,6 @@ export default ({
},
emits: ['update:modelValue'],
computed: {
questionText() {
return this.getCmsContent('PolicyVehiclesQuestion', 'QuestionText');
},
answers() {
const policyVehiclesAnswerFromCMS = this.getCmsContent('PolicyVehiclesQuestion', 'Answers');
const combinedVehicles = [...this.vehicles, ...policyVehiclesAnswerFromCMS];

View file

@ -13,11 +13,20 @@
<div class="iss-heritage-container-width">
<div class="policy-vehicles-container iss-heritage-content-container-width">
<div class="select-car-form rounded text-center">
<siteSubHeader
cmsWidgetName="SiteSubHeaderWidget"
class="mt-4" />
<policyVehiclesQuestion
v-model="selectedVehicleVin"
cmsWidgetName="PolicyVehiclesQuestion"
:vehicles="VehiclesForQuestions"
:validationRules="rules.optionRequired" />
:validationRules="rules.optionRequired"
class="mb-2"/>
<buttonMain
:variant="buttonVariants.primary"
buttonText="Add another vehicle"
class="w-100"
@clickEvent="addAnotherVehicle" />
<alert
v-if="displayNoServiceAlert"
ref="AlertNoService"
@ -59,10 +68,15 @@ import {
} from '@/helpers/policy-vehicle-helper';
import coverageType from '@/constants/coverage-type';
import alert from '@/ux-components/alert/alert.vue';
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
import buttonMain from '@/ux-components/button-main/button-main.vue';
import buttonVariants from '@/constants/button-variants';
export default {
name: 'policy-vehicles',
components: {
buttonMain,
siteSubHeader,
siteHeader,
siteFooter,
policyVehiclesQuestion,
@ -90,7 +104,8 @@ export default {
rules: {
optionRequired: globalRules.OPTION_REQUIRED
},
displayNoServiceAlert: false
displayNoServiceAlert: false,
buttonVariants
};
},
computed: {
@ -274,6 +289,10 @@ export default {
data: responseError.data
};
}
},
async addAnotherVehicle() {
this.selectedVehicleVin = vehicleSelectionOptions.VEHICLE_NOT_LISTED;
await this.forwardButtonAction();
}
}
};

View file

@ -101,7 +101,6 @@ export default {
this.$refs[this.ModalName]?.closeModal();
this.$emit('buttonClick');
} else {
this.$refs[this.ModalName]?.resetButtonStyle();
this.showError = true;
}
}

View file

@ -260,9 +260,6 @@ export default {
this.displayInvalidZipAlert = false;
this.internalModel = deepClone(this.modelValue);
},
resetModalButtonStyle() {
this.modal.resetButtonStyle();
},
async setMobileLocation() {
if (
this.internalModel.addressQuestions.zipCode
@ -273,7 +270,6 @@ export default {
if (!zipCodeData.isValid) {
this.displayInvalidZipAlert = true;
this.resetModalButtonStyle();
} else {
// retrieve mobile fee part
const serviceZipCode = this.internalModel.addressQuestions.zipCode;

View file

@ -10,8 +10,7 @@ jest.mock('@/digital-components/textbox-question/textbox-question', () => ({
jest.mock('@/digital-components/modal/modal', () => ({
methods: {
closeModal: jest.fn(),
resetButtonStyle: jest.fn()
closeModal: jest.fn()
}
}));

View file

@ -15,6 +15,7 @@
:onModalOpenedCallback="onModalOpened"
:onModalClosedCallback="onModalClosed"
:footerButtonText="modalFooterText"
:modalPosition="'center'"
@footerButtonEvent="setZipCode">
<serviceZipQuestion
ref="serviceZipCodeQuestion"
@ -127,9 +128,6 @@ export default {
closeModal() {
this.$refs[this.modalName].closeModal();
},
resetModalButtonStyle() {
this.$refs[this.modalName].resetButtonStyle();
},
onInputIdAssigned(inputId) {
this.serviceZipCodeTextInputId = inputId;
},
@ -150,7 +148,6 @@ export default {
if (!zipCodeData.isValid) {
this.displayInvalidZipAlert = true;
this.focusOnZipInput();
this.resetModalButtonStyle();
} else {
this.internalModel.state = zipCodeData.state;
const serviceZipCode = this.internalModel.zipCode;

View file

@ -169,7 +169,7 @@ describe('tpa-submit', () => {
// Assert
expect(mainButton.exists()).toBeTruthy();
expect(mainButton.props().isPrimary).toBe(true);
expect(mainButton.props().variant).toBe('success');
expect(mainButton.classes()).toContain('w-100');
expect(mainButton.classes()).toContain('mb-5');
});

View file

@ -32,7 +32,7 @@
class="mb-4 small text-color--darker-gray" />
<buttonMain
ref="buttonMainOne"
isPrimary
variant="success"
:buttonText="forwardButtonText"
class="w-100 mb-5"
@clickEvent="forwardButtonAction" />

View file

@ -86,52 +86,14 @@
.btn-link {
color: $link;
}
.btn {
&.btn-override[aria-disabled="false"]:not(.form-test-invalid) {
&.btn-primary {
background: $button-color;
color: $button-text-color;
}
&:focus-visible {
background: $button-color;
color: $button-text-color;
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $button-color;
}
}
}
}
&.modal-open {
.modal {
.modal-body {
.btn {
&.btn-override[aria-disabled="false"]:not(.form-test-invalid) {
&.btn-primary {
background: $button-color;
color: $button-text-color;
}
&:focus-visible {
background: $button-color;
color: $button-text-color;
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $button-color;
}
}
}
}
.modal-footer {
.btn:not(.navigation-link) {
background: $button-color;
color: $button-text-color;
}
}
}
.btn {
&.btn-override {
@include heritage-btn-variant('success', $button-text-color, $button-color, transparent, true);
@include heritage-btn-variant('navigation', $button-text-color, $button-color, transparent, true);
}
}
}
// End Amica
@ -171,52 +133,14 @@
a.new-window-link {
color: $link;
}
.btn {
&.btn-override[aria-disabled="false"]:not(.form-test-invalid) {
&.btn-primary {
background: $button-color;
color: $button-text-color;
}
&:focus-visible {
background: $button-color;
color: $button-text-color;
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $button-color;
}
}
}
}
&.modal-open {
.modal {
.modal-body {
.btn {
&.btn-override[aria-disabled="false"]:not(.form-test-invalid) {
&.btn-primary {
background: $button-color;
color: $button-text-color;
}
&:focus-visible {
background: $button-color;
color: $button-text-color;
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $button-color;
}
}
}
}
.modal-footer {
.btn:not(.navigation-link) {
background: $button-color;
color: $button-text-color;
}
}
}
.btn {
&.btn-override {
@include heritage-btn-variant('success', $button-text-color, $button-color, transparent, true);
@include heritage-btn-variant('navigation', $button-text-color, $button-color, transparent, true);
}
}
}
// End Nationwide
@ -256,52 +180,14 @@
a.new-window-link {
color: $link;
}
.btn {
&.btn-override[aria-disabled="false"]:not(.form-test-invalid) {
&.btn-primary {
background: $button-color;
color: $button-text-color;
}
&:focus-visible {
background: $button-color;
color: $button-text-color;
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $button-color;
}
}
}
}
&.modal-open {
.modal {
.modal-body {
.btn {
&.btn-override[aria-disabled="false"]:not(.form-test-invalid) {
&.btn-primary {
background: $button-color;
color: $button-text-color;
}
&:focus-visible {
background: $button-color;
color: $button-text-color;
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $button-color;
}
}
}
}
.modal-footer {
.btn:not(.navigation-link) {
background: $button-color;
color: $button-text-color;
}
}
}
.btn {
&.btn-override {
@include heritage-btn-variant('success', $button-text-color, $button-color, transparent, true);
@include heritage-btn-variant('navigation', $button-text-color, $button-color, transparent, true);
}
}
}
// End Country Financial
@ -341,52 +227,14 @@
a.new-window-link {
color: $link;
}
.btn {
&.btn-override[aria-disabled="false"]:not(.form-test-invalid) {
&.btn-primary {
background: $button-color;
color: $button-text-color;
}
&:focus-visible {
background: $button-color;
color: $button-text-color;
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $button-color;
}
}
}
}
&.modal-open {
.modal {
.modal-body {
.btn {
&.btn-override[aria-disabled="false"]:not(.form-test-invalid) {
&.btn-primary {
background: $button-color;
color: $button-text-color;
}
&:focus-visible {
background: $button-color;
color: $button-text-color;
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $button-color;
}
}
}
}
.modal-footer {
.btn:not(.navigation-link) {
background: $button-color;
color: $button-text-color;
}
}
}
.btn {
&.btn-override {
@include heritage-btn-variant('success', $button-text-color, $button-color, transparent, true);
@include heritage-btn-variant('navigation', $button-text-color, $button-color, transparent, true);
}
}
}
// End Travelers

View file

@ -182,4 +182,50 @@ body {
:root {
--iss-loader-color: white;
}
//Button Styles
$heritage-btn-border-radius: 1.40625rem; // 22.5px
$heritage-btn-height: 2.8125rem; // 45px
$heritage-btn-width: 9.0625rem; // 145px
.btn {
position: relative;
--bs-btn-font-weight: 600;
min-height: $heritage-btn-height;
min-width: $heritage-btn-width;
letter-spacing: inherit;
line-height: .94;
@include heritage-btn-variant('primary', $white, $heritage-blue-primary, transparent, true);
@include heritage-btn-variant('success', $white, $green, transparent, true);
@include heritage-btn-variant('navigation', $white, $green, transparent, true);
@include heritage-btn-variant('secondary', $white, $heritage-blue-secondary, transparent, true);
@include heritage-btn-link('link', $blue, transparent, $heritage-blue-secondary);
@include heritage-btn-size('lg', 0.625rem, 1.25rem);
@include heritage-btn-size('md', 7px, 15px);
@include heritage-btn-size('sm', 3px, 6px);
@include heritage-btn-size('xs', 1px, 5px);
&.btn-link {
--bs-btn-padding-x: 0px;
text-decoration: none;
&:hover,
&:focus {
text-decoration: underline;
}
&.disabled,
&[disabled],
fieldset[disabled] {
text-decoration: none;
}
}
&.delay {
// fixes flicker while transitioning between states
transition: background 0s 0s ease-in-out;
}
}

View file

@ -8,3 +8,53 @@
@mixin box-shadow-hover($color) {
box-shadow: 0 0 0 4px $color;
}
@mixin heritage-btn-box-shadow($color) {
box-shadow:0 0 0 3px $white,0 0 0 5.5px $color
}
@mixin heritage-btn-variant($name, $color, $background, $border-color, $box-shadow) {
&.btn-#{$name} {
@include button-variant($background,
$border-color,
$color,
#{darken($background, 10%)},
$border-color,
$color,
$background,
$border-color,
$color,
$background,
$border-color,
$color
);
@if($box-shadow) {
&:focus, &:active, &:active:focus {
@include heritage-btn-box-shadow($background);
}
}
}
&.btn-outline-#{$name} {
@include button-outline-variant($background,
$color,
$background,
$color,
$background
);
}
}
@mixin heritage-btn-size($size, $y-padding, $x-padding) {
&.btn-#{$size} {
@include button-size($y-padding, $x-padding, $font-size-base, $heritage-btn-border-radius);
}
}
@mixin heritage-btn-link($name, $color, $background, $hover-color) {
&.btn-#{$name} {
--bs-btn-color: #{$color};
--bs-btn-bg: #{$background};
--bs-btn-hover-color: #{$hover-color};
--bs-btn-active-color: #{$hover-color};
}
}

View file

@ -5,7 +5,7 @@
&.list-button,
&.list-button-horizontal,
&.list-card {
&:not(.selected) {
&:not(.selected):not(.no-hover) {
position: relative;
z-index: 5;
@include box-shadow-hover($blue-300);

View file

@ -216,4 +216,8 @@ $enable-important-utilities: false;
$letter-spacing-primary: 0.03em;
// Borders
$border-input: 1px solid rgba(179, 180, 181);
$border-input-focus: 2.5px solid $blue;
$border-input-focus: 2.5px solid $blue;
//Heritage
$heritage-blue-primary: #0070D1;
$heritage-blue-secondary: #167cac;

View file

@ -1,6 +1,8 @@
import { shallowMount } from '@vue/test-utils';
import { getMountOptions } from '@/helpers/unit-test-helper.js';
import buttonMain from '@/ux-components/button-main/button-main.vue';
import buttonVariants from '@/constants/button-variants';
import buttonSizes from '@/constants/button-sizes';
/** @ignore */
function setupMocks(mountOptionsMockData = {}) {
@ -12,13 +14,75 @@ function setupMocks(mountOptionsMockData = {}) {
}
describe('buttonMain.vue', () => {
it('Should return btn-primary class', async () => {
describe.each([...Object.keys(buttonVariants)])('Button Variants', (variant) => {
test(`Should return btn-${variant} class`, () => {
// Act
const wrapper = shallowMount(
buttonMain,
setupMocks({
propsData: {
variant
}
})
);
// Assert
const button = wrapper.find('button');
// Expect
expect(button.attributes('class')).toContain(`btn-${variant}`);
});
});
describe.each([...Object.keys(buttonVariants)])('Button Outline Variants', (variant) => {
test(`Should return btn-outline-${variant} class`, () => {
// Act
const wrapper = shallowMount(
buttonMain,
setupMocks({
propsData: {
variant,
isOutline: true
}
})
);
// Assert
const button = wrapper.find('button');
// Expect
expect(button.attributes('class')).toContain(`btn-outline-${variant}`);
});
});
describe.each([...Object.keys(buttonSizes)])('Button Sizes', (size) => {
test(`Should return btn-${size} class`, () => {
// Act
const wrapper = shallowMount(
buttonMain,
setupMocks({
propsData: {
size
}
})
);
// Assert
const button = wrapper.find('button');
// Expect
expect(button.attributes('class')).toContain(`btn-${size}`);
});
});
it('Should return button text', async () => {
// Act
const buttonText = 'Test Text';
const wrapper = shallowMount(
buttonMain,
setupMocks({
propsData: {
isPrimary: true
buttonText
}
})
);
@ -27,10 +91,10 @@ describe('buttonMain.vue', () => {
const button = wrapper.find('button');
// Expect
expect(button.attributes('class')).toContain('btn-primary');
expect(button.text()).toContain(buttonText);
});
it('Should return aria-disabled state', async () => {
it('Should return disabled state', async () => {
// Act
const wrapper = shallowMount(
buttonMain,
@ -47,4 +111,85 @@ describe('buttonMain.vue', () => {
// Expect
expect(button.attributes()['aria-disabled']).toEqual('true');
});
it('Should emit click-event', async () => {
// Act
const wrapper = shallowMount(
buttonMain,
setupMocks({
propsData: {
isDisabled: false
}
})
);
// Assert
const button = wrapper.find('button');
await button.trigger('click');
// Expect
expect(wrapper.emitted('click-event').length).toBe(1);
});
it('Should not emit click-event when disabled', async () => {
// Act
const wrapper = shallowMount(
buttonMain,
setupMocks({
propsData: {
isDisabled: true
}
})
);
// Assert
const button = wrapper.find('button');
await button.trigger('click');
// Expect
expect(wrapper.emitted('click-event')).toBeUndefined();
});
it('Should emit GA event when clicked', async () => {
// Act
const wrapper = shallowMount(
buttonMain,
setupMocks({
propsData: {
pushToGA: true
}
})
);
const spy = jest.spyOn(wrapper.vm, 'pushEventToGA');
// Assert
const button = wrapper.find('button');
await button.trigger('click');
// Expect
expect(spy).toHaveBeenCalled();
});
it('Should not emit GA event when clicked', async () => {
// Act
const wrapper = shallowMount(
buttonMain,
setupMocks({
propsData: {
pushToGA: false
}
})
);
const spy = jest.spyOn(wrapper.vm, 'pushEventToGA');
// Assert
const button = wrapper.find('button');
await button.trigger('click');
// Expect
expect(spy).not.toHaveBeenCalled();
});
});

View file

@ -1,31 +1,50 @@
<template>
<button
:aria-disabled="isDisabled"
class="btn d-flex btn-override align-items-center justify-content-center py-3 px-4 delay"
:class="[
isPrimary ? 'btn-primary' : 'btn-secondary',
isFloat ? 'float-end' : '',
]"
class="btn d-flex align-items-center justify-content-center delay"
:class="[getVariant, `btn-${size}`, canOverride ? 'btn-override' : '']"
@click="clicked">
<span class="m-0">{{ buttonText }}</span>
</button>
</template>
<script>
import buttonVariants from '@/constants/button-variants';
import buttonSizes from '@/constants/button-sizes';
export default {
name: 'button-main',
props: {
isPrimary: Boolean,
variant: { type: String, default: 'primary', validator: (val) => Object.keys(buttonVariants).includes(val) },
size: { type: String, default: 'md', validator: (val) => Object.keys(buttonSizes).includes(val) },
buttonText: String,
isDisabled: Boolean,
loaderPosition: String,
isFloat: Boolean,
suppressLoader: Boolean
isOutline: Boolean,
pushToGA: Boolean,
canOverride: { type: Boolean, required: false, default: true }
},
emits: ['click-event'],
computed: {
getVariant() {
if (this.isOutline) {
return `btn-outline-${this.variant}`;
}
return `btn-${this.variant}`;
}
},
methods: {
clicked() {
if (!this.isDisabled) {
if (this.pushToGA) {
this.pushEventToGA(
this.$route.query[this.queryStrings.ISS_PAGE],
this.GaActions.CLICKED,
this.buttonText,
true
);
}
this.$emit('click-event');
}
}
@ -34,86 +53,5 @@ export default {
</script>
<style lang="scss" scoped>
$border-radius-heritage: 1.40625rem; // 22.5px
$button-height-heritage: 2.8125rem; // 45px
$button-width-heritage: 9.0625rem; // 145px
.btn {
&.btn-primary {
position: relative;
background: #0c7e47;
padding-left: 2rem;
padding-right: 2rem;
min-height: $button-height-heritage;
min-width: $button-width-heritage;
border: none;
border-radius: $border-radius-heritage;
color: $white;
justify-content: center;
font-weight: 600;
letter-spacing: inherit;
&:focus {
// Keyboard focus for accessibility
outline: none;
box-shadow: 0 0 0 3px #fff, 0 0 0 5.5px #0c7e47;
}
&:hover {
// Keyboard focus for accessibility
color: #fff;
background-color: #084f2d;
border-color: #042f1a;
}
&:active {
outline: none;
background-color: #0c7e47;
box-shadow: 0 0 0 3px #fff, 0 0 0 5.5px #0c7e47;
}
&:disabled {
background: red;
color: $gray-600 !important;
border: none;
border-radius: $border-radius-heritage;
cursor: pointer;
pointer-events: all;
}
&.delay {
// fixes flicker while transitioning between states
transition: background 0s 0s ease-in-out;
}
}
&.btn-secondary {
position: relative;
background: transparent;
border: 1px solid $blue;
border-radius: $border-radius-heritage;
color: $blue;
font-weight: 500;
transition: all 150ms linear;
&:hover {
color: $white;
@include blue-gradient;
}
&:focus, // Mouse, touch, stylus focus
&:focus-visible {
// Keyboard focus for accessibility
outline: none;
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $blue-700;
color: $white;
@include blue-gradient;
}
&:disabled {
background: transparent;
color: $gray-550 !important;
font-weight: 400;
border: 1px solid $gray-550;
border-radius: $border-radius-heritage;
cursor: pointer;
pointer-events: all;
}
&.delay {
// fixes flicker while transitioning between states
transition: background 0s 0s ease-in-out;
}
}
}
</style>

View file

@ -2,7 +2,7 @@
<baseInputButton
v-bind="$props"
v-model="selectedValue"
buttonWrapperClasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 mb-2">
buttonWrapperClasses="list-group base-input-button list-button no-hover d-flex flex-column w-100 mb-2">
<div
:aria-label="buttonLabel"
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4">
@ -67,6 +67,12 @@ export default {
</script>
<style lang="scss" scoped>
$heritage-border-radius: 60px;
$heritage-border-color: #CACBCC;
$heritage-box-shadow: 0 1px 5px rgba($black, 0.2);
$heritage-checked-background-color: #e7f1f6;
$heritage-checked-border-color: #0070d1;
.loader {
position: absolute;
}
@ -75,26 +81,11 @@ export default {
input[type="radio"],
input[type="checkbox"] {
position: static; //override bootstrap
&:focus-visible + .list-button-content {
box-shadow: 0 0 0 2.5px $blue;
}
&:focus + .list-button-content {
box-shadow: 0 0 0 2.5px $blue;
}
&:checked + .list-button-content {
color: $black;
font-weight: 500;
background: $blue-100;
background: $heritage-checked-background-color;
border-color: $heritage-checked-border-color;
box-shadow: 0 0 0 1px $blue;
}
&:checked:focus + .list-button-content {
box-shadow: 0 0 0 2.5px $blue;
}
&:checked + .list-button-content p,
&:checked + .list-button-content span {
font-weight: 500;
}
&:checked + .list-button-content span:nth-child(2) {
font-weight: 400;
color: $gray-600;
@ -102,19 +93,20 @@ export default {
}
}
.list-button-content {
color: $gray-600;
color: $black;
font-weight: $font-weight-semibold;
position: relative;
background: $white;
transition: all 150ms linear;
border-radius: $border-radius-lg;
border: 1px solid $gray-500;
border-radius: $heritage-border-radius;
border: 1px solid $heritage-border-color;
box-shadow: $heritage-box-shadow;
width: 100%;
outline: none;
span {
&.small {
font-size: 0.75rem;
color: $gray-550;
color: $darker-gray;
}
}
}

View file

@ -1,95 +0,0 @@
import { shallowMount } from '@vue/test-utils';
import { getMountOptions } from '@/helpers/unit-test-helper.js';
import { nextTick } from 'vue';
import modalButtonMain from '@/ux-components/modal-button-main/modal-button-main.vue';
/** @ignore */
function setupMocks(mountOptionsMockData = {}) {
const defaultMountOptions = { route: { query: { issPage: 'page-name' } } };
const baseMountOptions = getMountOptions(Object.assign(defaultMountOptions, mountOptionsMockData));
const allMountOptions = Object.assign(defaultMountOptions, baseMountOptions);
return allMountOptions;
}
describe('modal-button-main.vue', () => {
it('Should return btn-primary class', () => {
// Arrange/Act
const wrapper = shallowMount(
modalButtonMain,
setupMocks({
propsData: {
isPrimary: true
}
})
);
const button = wrapper.find('button');
// Assert
expect(button.attributes('class')).toContain('btn-primary');
});
it('Should return aria-disabled state', () => {
// Arrange/Act
const wrapper = shallowMount(
modalButtonMain,
setupMocks({
propsData: {
isDisabled: true
}
})
);
const button = wrapper.find('button');
// Assert
expect(button.attributes()['aria-disabled']).toEqual('true');
});
it("Should emit 'click-event' event when clicking if the button is enabled", async () => {
// Arrange
const wrapper = shallowMount(
modalButtonMain,
setupMocks({
propsData: {
loaderPosition: 'right',
loaderEnabled: true,
isDisabled: false
}
})
);
const buttonElement = wrapper.find('button');
// Act
buttonElement.trigger('click');
await nextTick();
// Assert
expect(wrapper.emitted('click-event')).toBeTruthy();
});
it("Should not emit 'click-event' event when clicking if the button is disabled", async () => {
// Arrange
const wrapper = shallowMount(
modalButtonMain,
setupMocks({
propsData: {
loaderPosition: 'right',
loaderEnabled: true,
isDisabled: true
}
})
);
const buttonElement = wrapper.find('button');
// Act
buttonElement.trigger('click');
await nextTick();
// Assert
expect(wrapper.emitted('click-event')).toBeFalsy();
});
});

View file

@ -1,121 +0,0 @@
<template>
<button
type="button"
:aria-disabled="isDisabled"
class="btn d-flex btn-override align-items-center justify-content-center py-3 px-4 delay"
:class="[
isPrimary ? 'btn-primary' : 'btn-secondary',
isFloat ? 'float-end' : '',
]"
@click="clicked">
<span class="m-0">{{ buttonText }}</span>
</button>
</template>
<script>
export default {
name: 'modal-button-main',
props: {
isPrimary: Boolean,
buttonText: String,
isDisabled: Boolean,
loaderColor: String,
loaderPosition: String,
isFloat: Boolean,
suppressLoader: Boolean
},
emits: ['click-event'],
methods: {
clicked() {
this.pushEventToGA(
this.$route.query[this.queryStrings.ISS_PAGE],
this.GaActions.CLICKED,
this.buttonText,
true
);
if (!this.isDisabled) {
this.$emit('click-event');
}
}
}
};
</script>
<style lang="scss" scoped>
.btn {
&.btn-primary {
position: relative;
background: linear-gradient(270deg, $blue 0%, $blue-800 100%);
border: none;
border-radius: $border-radius-lg;
color: $white;
justify-content: center;
font-weight: 500;
letter-spacing: inherit;
@media (hover: hover) {
background: linear-gradient(270deg, $blue 0%, $blue-800 100%);
}
// Mouse, touch, stylus focus
&:focus-visible {
// Keyboard focus for accessibility
outline: none;
box-shadow: 0 0 0 3px, 0 0 0 5.5px $blue-700;
color: $white;
background: linear-gradient(270deg, rgba(6, 87, 124, 1) 0%, rgba(6, 87, 124, 1) 100%);
}
&:disabled {
background: $gray-200 !important;
background: linear-gradient(270deg, $gray-200 0%, $gray-200 100%) !important;
color: $gray-600 !important;
font-weight: 400;
height: 48px;
border: none;
border-radius: $border-radius-lg;
cursor: pointer;
pointer-events: all;
}
&.delay {
// fixes flicker while transitioning between states
transition: background 0s 0s ease-in-out;
}
}
&.btn-secondary {
position: relative;
background: transparent;
border: 1px solid $blue;
border-radius: $border-radius-lg;
color: $blue;
font-weight: 500;
transition: all 150ms linear;
height: 3rem;
&:hover {
color: $white;
@include blue-gradient;
}
&:focus, // Mouse, touch, stylus focus
&:focus-visible {
// Keyboard focus for accessibility
outline: none;
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $blue-700;
color: $white;
@include blue-gradient;
}
&:disabled {
background: transparent;
color: $gray-550 !important;
font-weight: 400;
height: 48px;
border: 1px solid $gray-550;
border-radius: $border-radius-lg;
cursor: pointer;
pointer-events: all;
}
&.delay {
// fixes flicker while transitioning between states
transition: background 0s 0s ease-in-out;
}
}
}
</style>