Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a sample donation form accordion #303

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 33 additions & 61 deletions src/components/pages/DonationForm.vue
Original file line number Diff line number Diff line change
@@ -1,72 +1,49 @@
<template>
<div id="laika-donation">
<FeatureToggle default-template="campaigns.address_pages.legacy">
<template #campaigns.address_pages.legacy>
<keep-alive>
<PaymentPage
v-if="currentPageIndex === 0"
@next-page="currentPageIndex = 1"
:assets-path="assetsPath"
:payment-amounts="paymentAmounts"
:payment-intervals="paymentIntervals"
:payment-types="paymentTypes"
/>
<AddressPage
v-else
@previous-page="currentPageIndex = 0"
:assets-path="assetsPath"
:validate-address-url="validateAddressUrl"
:validate-email-url="validateEmailUrl"
:validate-bank-data-url="validateBankDataUrl"
:validate-legacy-bank-data-url="validateLegacyBankDataUrl"
:countries="countries"
:salutations="salutations"
:tracking-data="trackingData"
:campaign-values="campaignValues"
:address-validation-patterns="addressValidationPatterns"
/>
</keep-alive>
<h1 class="form-title" v-html="$t( 'donation_form_section_address_headline' )"/>
<FormAccordion :page-index="currentPageIndex" :start-payment-complete="paymentDataComplete">
<template #payment-title>Step 01. Payment Information</template>
<template #payment-content>
<PaymentPage
@next-page="currentPageIndex = 1"
:assets-path="assetsPath"
:payment-amounts="paymentAmounts"
:payment-intervals="paymentIntervals"
:payment-types="paymentTypes"
/>
</template>
<template #campaigns.address_pages.test_01>
<keep-alive>
<PaymentPage
v-if="currentPageIndex === 0"
@next-page="currentPageIndex = 1"
:assets-path="assetsPath"
:payment-amounts="paymentAmounts"
:payment-intervals="paymentIntervals"
:payment-types="paymentTypes"
/>
<AddressPageDonationReceipt
v-else
@previous-page="currentPageIndex = 0"
:assets-path="assetsPath"
:validate-address-url="validateAddressUrl"
:validate-email-url="validateEmailUrl"
:validate-bank-data-url="validateBankDataUrl"
:validate-legacy-bank-data-url="validateLegacyBankDataUrl"
:countries="countries"
:salutations="salutations"
:tracking-data="trackingData"
:campaign-values="campaignValues"
:address-validation-patterns="addressValidationPatterns"
/>
</keep-alive>

<template #address-title>Step 02. Address Information</template>
<template #address-content>
<AddressPage
@previous-page="currentPageIndex = 0"
:assets-path="assetsPath"
:validate-address-url="validateAddressUrl"
:validate-email-url="validateEmailUrl"
:validate-bank-data-url="validateBankDataUrl"
:validate-legacy-bank-data-url="validateLegacyBankDataUrl"
:countries="countries"
:salutations="salutations"
:tracking-data="trackingData"
:campaign-values="campaignValues"
:address-validation-patterns="addressValidationPatterns"
/>
</template>
</FeatureToggle>
</FormAccordion>

</div>
</template>

<script setup lang="ts">
import { ref, watch } from 'vue';
import { TrackingData } from '@src/view_models/TrackingData';
import PaymentPage from '@src/components/pages/donation_form/subpages/PaymentPage.vue';
import AddressPage from '@src/components/pages/donation_form/subpages/AddressPage.vue';
import { Country } from '@src/view_models/Country';
import { AddressValidation } from '@src/view_models/Validation';
import { Salutation } from '@src/view_models/Salutation';
import { CampaignValues } from '@src/view_models/CampaignValues';
import AddressPageDonationReceipt from '@src/components/pages/donation_form/subpages/AddressPageDonationReceipt.vue';
import FormAccordion from '@src/components/shared/FormAccordion.vue';
import { ref } from 'vue';

interface Props {
assetsPath: string;
Expand All @@ -82,15 +59,10 @@ interface Props {
trackingData: TrackingData;
campaignValues: CampaignValues;
addressValidationPatterns: AddressValidation;
startPageIndex: number;
paymentDataComplete: boolean;
}

const props = defineProps<Props>();

const currentPageIndex = ref<number>( props.startPageIndex );

watch( currentPageIndex, () => {
window.scrollTo( 0, 0 );
} );
const currentPageIndex = ref<number>( props.paymentDataComplete ? 1 : 0 );

</script>
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<template>
<div class="address-page">
<h1 class="form-title" v-html="$t( 'donation_form_section_address_headline' )"/>
<PaymentSummary
v-if="paymentWasInitialized"
:amount="paymentSummary.amount"
Expand Down
2 changes: 0 additions & 2 deletions src/components/pages/donation_form/subpages/PaymentPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
method="post"
@keydown.enter.prevent="next()"
>
<h1 class="form-title" v-html="$t( 'donation_form_section_address_headline' )"/>

<Payment
:payment-amounts="paymentAmounts"
:payment-intervals="paymentIntervals"
Expand Down
137 changes: 137 additions & 0 deletions src/components/shared/FormAccordion.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<template>

<div class="form-accordion" :style="{ '--transition-time' : `${ TRANSITION_TIME }ms` }">
<div class="form-accordion-item"
:class="{ 'form-accordion-item--open': paymentOpen, 'form-accordion-item--animating': paymentAnimating }">
<button class="form-accordion-item-button" @click="togglePayment"
:class="{ 'form-accordion-payment-button-complete' : paymentComplete }">
<span class="form-accordion-item-button-text">
<slot name="payment-title"/>
</span>
<span class="form-accordion-item-button-icon">
<SuccessIcon v-if="paymentComplete"/>
<WarningIcon v-else/>
</span>
</button>
<div class="form-accordion-item-content-wrapper" :style="{ '--height' : `${ paymentContentHeight }px` }">
<div class="form-accordion-item-content" ref="paymentContent">
<slot name="payment-content"/>
</div>
</div>
</div>
<div class="form-accordion-item" :class="{ 'form-accordion-item--open' : addressOpen, 'form-accordion-item--animating': addressAnimating }">
<button class="form-accordion-item-button" @click="toggleAddress">
<span class="form-accordion-item-button-text">
<slot name="address-title"/>
</span>
</button>
<div class="form-accordion-item-content-wrapper" :style="{ '--height' : `${ addressContentHeight }px` }">
<div class="form-accordion-item-content" ref="addressContent">
<slot name="address-content"/>
</div>
</div>
</div>
</div>

</template>

<script setup lang="ts">
import SuccessIcon from '@src/components/shared/icons/SuccessIcon.vue';
import WarningIcon from '@src/components/shared/icons/WarningIcon.vue';
import { useFormAccordionItem } from '@src/components/shared/useFormAccordionItem';
import { ref, watch } from 'vue';

const TRANSITION_TIME = 500;

interface Props {
pageIndex: 0 | 1;
startPaymentComplete: boolean;
}

const props = defineProps<Props>();
const paymentComplete = ref<boolean>( props.startPaymentComplete );

const {
itemOpen: paymentOpen,
itemAnimating: paymentAnimating,
itemContent: paymentContent,
itemContentHeight: paymentContentHeight,
open: openPayment,
close: closePayment,
toggleItem: togglePayment,
} = useFormAccordionItem( TRANSITION_TIME, props.pageIndex === 0 );

const {
itemOpen: addressOpen,
itemAnimating: addressAnimating,
itemContent: addressContent,
itemContentHeight: addressContentHeight,
open: openAddress,
close: closeAddress,
toggleItem: toggleAddress,
} = useFormAccordionItem( TRANSITION_TIME, props.pageIndex === 1 );

watch( () => props.pageIndex, ( newIndex: number ) => {
if ( newIndex === 0 ) {
openPayment();
closeAddress();
} else {
paymentComplete.value = true;
closePayment();
openAddress();
}
} );

</script>

<style lang="scss">
@use '@src/scss/settings/global';
@use '@src/scss/settings/units';
@use '@src/scss/settings/colors';
@use 'sass:map';

.form-accordion-item {
&-content-wrapper {
overflow: hidden;
height: 0;
transition: height var(--transition-time) global.$easing;
}

&-content {
padding: map.get(units.$spacing, 'small') 0;
}

&-button {
border: 0;
background: colors.$gray-light;
border-bottom: 2px solid colors.$gray-mid;
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: map.get(units.$spacing, 'xx-large');
padding: map.get(units.$spacing, 'small');
font-size: 16px;
font-weight: bold;
text-align: left;
cursor: pointer;

&-icon {
display: flex;
align-items: center;
}
}

&--animating {
.form-accordion-item-content-wrapper {
height: var(--height);
}
}

&--open {
.form-accordion-item-content-wrapper {
height: auto;
}
}
}
</style>
56 changes: 56 additions & 0 deletions src/components/shared/useFormAccordionItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Ref, ref } from 'vue';

interface ReturnType {
itemOpen: Ref<boolean>;
itemAnimating: Ref<boolean>;
itemContent: Ref<HTMLElement>;
itemContentHeight: Ref<number>;
open: () => void;
close: () => void;
toggleItem: () => void;
}

export function useFormAccordionItem( transitionTime: number, startOpen: boolean ): ReturnType {
const itemOpen = ref<boolean>( startOpen );
const itemAnimating = ref<boolean>( false );
const itemContent = ref<HTMLElement>( null );
const itemContentHeight = ref<number>( 0 );

const open = (): void => {
itemContentHeight.value = itemContent.value.offsetHeight;
itemAnimating.value = true;
setTimeout( () => {
itemAnimating.value = false;
itemOpen.value = true;
}, transitionTime );
};

const close = (): void => {
itemContentHeight.value = itemContent.value.offsetHeight;
itemOpen.value = false;
itemAnimating.value = true;
setTimeout( () => {
itemAnimating.value = false;
} );
};

const toggleItem = (): void => {
itemContentHeight.value = itemContent.value.offsetHeight;

if ( !itemOpen.value ) {
open();
} else {
close();
}
};

return {
itemOpen,
itemAnimating,
itemContent,
itemContentHeight,
open,
close,
toggleItem,
};
}
2 changes: 1 addition & 1 deletion src/pages/donation_form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ dataPersister.initialize( persistenceItems ).then( () => {
trackingData: pageData.applicationVars.tracking,
campaignValues: campaignParameters.getCampaignValues(),
addressValidationPatterns: pageData.applicationVars.addressValidationPatterns,
startPageIndex: paymentDataComplete ? 1 : 0,
paymentDataComplete,
},
} );
app.provide( 'cityAutocompleteResource', new ApiCityAutocompleteResource() );
Expand Down
1 change: 1 addition & 0 deletions src/scss/settings/_colors.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ $primary-hover: color.adjust( $primary, $lightness: -5% );
$primary-locale-active: #EAF3FF;
$error: #DD3333;
$error-light: #FDE1E1;
$success: #00AF89;

$footer: #C7D1D8;

Expand Down
Loading