Merge branch 'develop' into feature/humphries/INSR-7768

This commit is contained in:
Alex Humphries 2026-01-06 10:36:51 -05:00
commit a43480dd02
22 changed files with 350 additions and 596 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) validate: mockValidate(true)
}); });
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, { const wrapper = shallowMount(modal, {
props: { props: {
footerButtonText, footerButtonText,
@ -89,7 +88,6 @@ describe('modal.vue', () => {
}, },
attachTo: document.body attachTo: document.body
}); });
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Act // Act
const buttonMain = wrapper.findComponent({ ref: 'modalButtonMain' }); const buttonMain = wrapper.findComponent({ ref: 'modalButtonMain' });
@ -112,8 +110,6 @@ describe('modal.vue', () => {
meta: mockMeta(fakeMeta), meta: mockMeta(fakeMeta),
validate: mockValidate(false) validate: mockValidate(false)
}); });
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, { const wrapper = shallowMount(modal, {
props: { props: {
footerButtonText, footerButtonText,
@ -121,7 +117,6 @@ describe('modal.vue', () => {
}, },
attachTo: document.body attachTo: document.body
}); });
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Act // Act
const buttonMain = wrapper.findComponent({ ref: 'modalButtonMain' }); const buttonMain = wrapper.findComponent({ ref: 'modalButtonMain' });
@ -144,8 +139,6 @@ describe('modal.vue', () => {
meta: mockMeta(fakeMeta), meta: mockMeta(fakeMeta),
validate: mockValidate(true) validate: mockValidate(true)
}); });
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, { const wrapper = shallowMount(modal, {
props: { props: {
footerButtonText, footerButtonText,
@ -153,7 +146,6 @@ describe('modal.vue', () => {
}, },
attachTo: document.body attachTo: document.body
}); });
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Assert // Assert
expect(wrapper.vm.isFooterButtonDisabled).toBe(true); expect(wrapper.vm.isFooterButtonDisabled).toBe(true);
@ -172,8 +164,6 @@ describe('modal.vue', () => {
meta: mockMeta(fakeMeta), meta: mockMeta(fakeMeta),
validate: mockValidate(true) validate: mockValidate(true)
}); });
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, { const wrapper = shallowMount(modal, {
props: { props: {
footerButtonText, footerButtonText,
@ -181,7 +171,6 @@ describe('modal.vue', () => {
}, },
attachTo: document.body attachTo: document.body
}); });
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Assert // Assert
expect(wrapper.vm.isFooterButtonDisabled).toBe(true); expect(wrapper.vm.isFooterButtonDisabled).toBe(true);
@ -202,8 +191,6 @@ describe('modal.vue', () => {
}); });
const showMock = jest.spyOn(Modal.prototype, 'show'); const showMock = jest.spyOn(Modal.prototype, 'show');
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, { const wrapper = shallowMount(modal, {
props: { props: {
footerButtonText, footerButtonText,
@ -211,7 +198,6 @@ describe('modal.vue', () => {
}, },
attachTo: document.body attachTo: document.body
}); });
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Act // Act
wrapper.vm.openModal(); wrapper.vm.openModal();
@ -234,8 +220,6 @@ describe('modal.vue', () => {
validate: mockValidate(true) validate: mockValidate(true)
}); });
const hideMock = jest.spyOn(Modal.prototype, 'hide'); const hideMock = jest.spyOn(Modal.prototype, 'hide');
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, { const wrapper = shallowMount(modal, {
props: { props: {
footerButtonText, footerButtonText,
@ -243,7 +227,6 @@ describe('modal.vue', () => {
}, },
attachTo: document.body attachTo: document.body
}); });
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Act // Act
wrapper.vm.openModal(); wrapper.vm.openModal();

View file

@ -34,30 +34,18 @@
<slot></slot> <slot></slot>
</div> </div>
<div class="modal-footer px-5 py-4"> <div class="modal-footer px-5 py-4">
<modalButtonMain <buttonMain
v-if="useDefaultButton"
id="modalbtn" id="modalbtn"
ref="modalButtonMain" ref="modalButtonMain"
isPrimary :variant="buttonVariant"
class="w-100" class="w-100"
:buttonText="footerButtonText" :buttonText="footerButtonText"
pushToGA
:class="[ :class="[
(isButtonDisabled || isFooterButtonDisabled) && (isButtonDisabled || isFooterButtonDisabled) &&
'form-test-invalid', 'form-test-invalid'
additionalClassesString
]" ]"
@clickEvent="validateAndEmit" /> @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> </div>
</div> </div>
@ -65,7 +53,7 @@
</template> </template>
<script> <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 { Modal } from 'bootstrap';
import { useForm } from 'vee-validate'; import { useForm } from 'vee-validate';
@ -73,7 +61,7 @@ export default {
// eslint-disable-next-line vue/multi-word-component-names // eslint-disable-next-line vue/multi-word-component-names
name: 'modal', name: 'modal',
components: { components: {
modalButtonMain ButtonMain
}, },
props: { props: {
modalId: String, modalId: String,
@ -86,13 +74,9 @@ export default {
type: Function type: Function
}, },
isButtonDisabled: Boolean, isButtonDisabled: Boolean,
useDefaultButton: { buttonVariant: {
type: Boolean,
default: true
},
additionalClassesString: {
type: String, type: String,
default: null default: 'success'
} }
}, },
emits: ['footer-button-event', 'isModalOpened'], emits: ['footer-button-event', 'isModalOpened'],
@ -131,8 +115,6 @@ export default {
const validationResult = await this.validate(); const validationResult = await this.validate();
if (validationResult.valid) { if (validationResult.valid) {
this.$emit('footer-button-event'); this.$emit('footer-button-event');
} else {
this.resetButtonStyle();
} }
}, },
async onKeyDown(e) { async onKeyDown(e) {
@ -141,14 +123,10 @@ export default {
this.$refs.modalButtonMain.clicked(); this.$refs.modalButtonMain.clicked();
document.getElementById(this.modalId)?.focus(); document.getElementById(this.modalId)?.focus();
}, },
resetButtonStyle() {
this.$refs.modalButtonMain?.resetButtonStyle();
},
onModalOpened() { onModalOpened() {
this.onModalOpenedCallback?.(); this.onModalOpenedCallback?.();
}, },
onModalClosed() { onModalClosed() {
this.resetButtonStyle();
this.onModalClosedCallback?.(); this.onModalClosedCallback?.();
}, },
openModal() { openModal() {

View file

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

View file

@ -7,19 +7,19 @@
ref="continueModal" ref="continueModal"
:headerText="modalHeaderText" :headerText="modalHeaderText"
:footerButtonText="modalFooterText" :footerButtonText="modalFooterText"
buttonVariant="primary"
@isModalOpened="setModalStatus" @isModalOpened="setModalStatus"
@footerButtonEvent="startNewReferral"> @footerButtonEvent="startNewReferral">
<p class="mb-2 subheader-text text-center"> <p class="mb-2 subheader-text text-center">
{{ modalSubHeaderText }} {{ modalSubHeaderText }}
</p> </p>
<button <buttonMain
id="btnContinueReferral" id="btnContinueReferral"
type="button" variant="success"
class="btn btn-white-blue continue-referral class="mt-2 w-100"
align-items-center justify-content-center d-inline-flex mt-2 py-3 px-4 delay w-100" :buttonText="modalBodyText"
@click="continueReferral"> @click-event="continueReferral">
{{ modalBodyText }} </buttonMain>
</button>
</modal> </modal>
</div> </div>
</transition> </transition>
@ -28,10 +28,12 @@
<script> <script>
import modal from '@/digital-components/modal/modal.vue'; import modal from '@/digital-components/modal/modal.vue';
import { getISSCookie } from '@/helpers/cookie-helper.js'; import { getISSCookie } from '@/helpers/cookie-helper.js';
import ButtonMain from '@/ux-components/button-main/button-main.vue';
export default { export default {
name: 'continue-modal', name: 'continue-modal',
components: { components: {
ButtonMain,
modal modal
}, },
emits: ['continue-previous-referral', 'start-new-referral'], emits: ['continue-previous-referral', 'start-new-referral'],
@ -80,16 +82,5 @@ export default {
</script> </script>
<style lang="scss" scoped> <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> </style>

View file

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

View file

@ -8,20 +8,17 @@
ref="cancelClaimModal" ref="cancelClaimModal"
:headerText="modalHeaderText" :headerText="modalHeaderText"
:footerButtonText="modalFooterText" :footerButtonText="modalFooterText"
additionalClassesString="navigation-link" buttonVariant="link"
:useDefaultButton="false"
@isModalOpened="setModalStatus" @isModalOpened="setModalStatus"
@footerButtonEvent="cancelClaimConfirmation"> @footerButtonEvent="cancelClaimConfirmation">
<div v-html="modalBodyText"></div> <div v-html="modalBodyText"></div>
<button <buttonMain
id="btnContinueReferral" id="btnContinueReferral"
type="button" variant="success"
:aria-disabled="false" :buttonText="modalBodyText2"
class="btn btn-primary btn-override class="mt-4 w-100"
align-items-center justify-content-center d-inline-flex mt-4 py-3 px-4 delay w-100" @clickEvent="returnToClaim">
@click="returnToClaim"> </buttonMain>
{{ modalBodyText2 }}
</button>
</modal> </modal>
</div> </div>
</transition> </transition>
@ -29,10 +26,12 @@
<script> <script>
import modal from '@/digital-components/modal/modal.vue'; import modal from '@/digital-components/modal/modal.vue';
import ButtonMain from '@/ux-components/button-main/button-main.vue';
export default { export default {
name: 'cancel-claim-modal', name: 'cancel-claim-modal',
components: { components: {
ButtonMain,
modal modal
}, },
emits: ['cancel-claim-confirmation', 'return-to-claim'], emits: ['cancel-claim-confirmation', 'return-to-claim'],
@ -76,19 +75,5 @@ export default {
</script> </script>
<style lang="scss" scoped> <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> </style>

View file

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

View file

@ -260,9 +260,6 @@ export default {
this.displayInvalidZipAlert = false; this.displayInvalidZipAlert = false;
this.internalModel = deepClone(this.modelValue); this.internalModel = deepClone(this.modelValue);
}, },
resetModalButtonStyle() {
this.modal.resetButtonStyle();
},
async setMobileLocation() { async setMobileLocation() {
if ( if (
this.internalModel.addressQuestions.zipCode this.internalModel.addressQuestions.zipCode
@ -273,7 +270,6 @@ export default {
if (!zipCodeData.isValid) { if (!zipCodeData.isValid) {
this.displayInvalidZipAlert = true; this.displayInvalidZipAlert = true;
this.resetModalButtonStyle();
} else { } else {
// retrieve mobile fee part // retrieve mobile fee part
const serviceZipCode = this.internalModel.addressQuestions.zipCode; 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', () => ({ jest.mock('@/digital-components/modal/modal', () => ({
methods: { methods: {
closeModal: jest.fn(), closeModal: jest.fn()
resetButtonStyle: jest.fn()
} }
})); }));

View file

@ -127,9 +127,6 @@ export default {
closeModal() { closeModal() {
this.$refs[this.modalName].closeModal(); this.$refs[this.modalName].closeModal();
}, },
resetModalButtonStyle() {
this.$refs[this.modalName].resetButtonStyle();
},
onInputIdAssigned(inputId) { onInputIdAssigned(inputId) {
this.serviceZipCodeTextInputId = inputId; this.serviceZipCodeTextInputId = inputId;
}, },
@ -150,7 +147,6 @@ export default {
if (!zipCodeData.isValid) { if (!zipCodeData.isValid) {
this.displayInvalidZipAlert = true; this.displayInvalidZipAlert = true;
this.focusOnZipInput(); this.focusOnZipInput();
this.resetModalButtonStyle();
} else { } else {
this.internalModel.state = zipCodeData.state; this.internalModel.state = zipCodeData.state;
const serviceZipCode = this.internalModel.zipCode; const serviceZipCode = this.internalModel.zipCode;

View file

@ -169,7 +169,7 @@ describe('tpa-submit', () => {
// Assert // Assert
expect(mainButton.exists()).toBeTruthy(); 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('w-100');
expect(mainButton.classes()).toContain('mb-5'); expect(mainButton.classes()).toContain('mb-5');
}); });

View file

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

View file

@ -86,52 +86,14 @@
.btn-link { .btn-link {
color: $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 { .btn {
.modal { &.btn-override {
.modal-body { @include heritage-btn-variant('success', $button-text-color, $button-color, transparent, true);
.btn { @include heritage-btn-variant('navigation', $button-text-color, $button-color, transparent, true);
&.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;
}
}
}
} }
}
} }
// End Amica // End Amica
@ -171,52 +133,14 @@
a.new-window-link { a.new-window-link {
color: $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 { .btn {
.modal { &.btn-override {
.modal-body { @include heritage-btn-variant('success', $button-text-color, $button-color, transparent, true);
.btn { @include heritage-btn-variant('navigation', $button-text-color, $button-color, transparent, true);
&.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;
}
}
}
} }
}
} }
// End Nationwide // End Nationwide
@ -256,52 +180,14 @@
a.new-window-link { a.new-window-link {
color: $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 { .btn {
.modal { &.btn-override {
.modal-body { @include heritage-btn-variant('success', $button-text-color, $button-color, transparent, true);
.btn { @include heritage-btn-variant('navigation', $button-text-color, $button-color, transparent, true);
&.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;
}
}
}
} }
}
} }
// End Country Financial // End Country Financial
@ -341,52 +227,14 @@
a.new-window-link { a.new-window-link {
color: $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 { .btn {
.modal { &.btn-override {
.modal-body { @include heritage-btn-variant('success', $button-text-color, $button-color, transparent, true);
.btn { @include heritage-btn-variant('navigation', $button-text-color, $button-color, transparent, true);
&.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;
}
}
}
} }
}
} }
// End Travelers // End Travelers

View file

@ -183,3 +183,49 @@ body {
:root { :root {
--iss-loader-color: white; --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: #{$font-weight-bolder};
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, 20px, $heritage-btn-border-radius);
@include heritage-btn-size('md', 7px, 15px, 15px, $heritage-btn-border-radius);
@include heritage-btn-size('sm', 3px, 6px, 12px, $heritage-btn-border-radius);
@include heritage-btn-size('xs', 1px, 5px, 12px, $heritage-btn-border-radius);
&.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) { @mixin box-shadow-hover($color) {
box-shadow: 0 0 0 4px $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, $font-size, $border-radius) {
&.btn-#{$size} {
@include button-size($y-padding, $x-padding, $font-size, $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

@ -218,3 +218,7 @@ $letter-spacing-primary: 0.03em;
// Borders // Borders
$border-input: 1px solid rgba(179, 180, 181); $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 { shallowMount } from '@vue/test-utils';
import { getMountOptions } from '@/helpers/unit-test-helper.js'; import { getMountOptions } from '@/helpers/unit-test-helper.js';
import buttonMain from '@/ux-components/button-main/button-main.vue'; import buttonMain from '@/ux-components/button-main/button-main.vue';
import buttonVariants from '@/constants/button-variants';
import buttonSizes from '@/constants/button-sizes';
/** @ignore */ /** @ignore */
function setupMocks(mountOptionsMockData = {}) { function setupMocks(mountOptionsMockData = {}) {
@ -12,13 +14,75 @@ function setupMocks(mountOptionsMockData = {}) {
} }
describe('buttonMain.vue', () => { 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 // Act
const buttonText = 'Test Text';
const wrapper = shallowMount( const wrapper = shallowMount(
buttonMain, buttonMain,
setupMocks({ setupMocks({
propsData: { propsData: {
isPrimary: true buttonText
} }
}) })
); );
@ -27,10 +91,10 @@ describe('buttonMain.vue', () => {
const button = wrapper.find('button'); const button = wrapper.find('button');
// Expect // 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 // Act
const wrapper = shallowMount( const wrapper = shallowMount(
buttonMain, buttonMain,
@ -47,4 +111,85 @@ describe('buttonMain.vue', () => {
// Expect // Expect
expect(button.attributes()['aria-disabled']).toEqual('true'); 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> <template>
<button <button
:aria-disabled="isDisabled" :aria-disabled="isDisabled"
class="btn d-flex btn-override align-items-center justify-content-center py-3 px-4 delay" class="btn d-flex align-items-center justify-content-center delay"
:class="[ :class="[getVariant, `btn-${size}`, canOverride ? 'btn-override' : '']"
isPrimary ? 'btn-primary' : 'btn-secondary',
isFloat ? 'float-end' : '',
]"
@click="clicked"> @click="clicked">
<span class="m-0">{{ buttonText }}</span> <span class="m-0">{{ buttonText }}</span>
</button> </button>
</template> </template>
<script> <script>
import buttonVariants from '@/constants/button-variants';
import buttonSizes from '@/constants/button-sizes';
export default { export default {
name: 'button-main', name: 'button-main',
props: { 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, buttonText: String,
isDisabled: Boolean, isDisabled: Boolean,
loaderPosition: String, isOutline: Boolean,
isFloat: Boolean, pushToGA: Boolean,
suppressLoader: Boolean canOverride: { type: Boolean, required: false, default: true }
}, },
emits: ['click-event'], emits: ['click-event'],
computed: {
getVariant() {
if (this.isOutline) {
return `btn-outline-${this.variant}`;
}
return `btn-${this.variant}`;
}
},
methods: { methods: {
clicked() { clicked() {
if (!this.isDisabled) { 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'); this.$emit('click-event');
} }
} }
@ -34,86 +53,5 @@ export default {
</script> </script>
<style lang="scss" scoped> <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> </style>

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>