@designcrowd/library.brand-page 5.18.1 → 5.18.2-ast-padding-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/src/{CreatedOnBrandCrowd-de-de-DFpjIAUv.js → CreatedOnBrandCrowd-de-de-GFYl8DtL.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-BapIT2xO.js → CreatedOnBrandCrowd-en-us-D5CcXf2I.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-DaSf3Dt6.js → CreatedOnBrandCrowd-es-es-Cr7J3ooe.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-Cvgei7yn.js → CreatedOnBrandCrowd-fr-ca-BC1NFFIW.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-Cvgei7yn.js → CreatedOnBrandCrowd-fr-fr-BC1NFFIW.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-Duxd2NmR.js → CreatedOnBrandCrowd-pt-br-BdiCcpwi.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-Duxd2NmR.js → CreatedOnBrandCrowd-pt-pt-BdiCcpwi.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-DXwNbruv.js → CreatedOnDcom-de-de-d4TmC_lo.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-CkNGwN-p.js → CreatedOnDcom-en-us-DlvLEF-R.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-Csyf5yhI.js → CreatedOnDcom-es-es-BulpVp3k.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-GOIUmIdy.js → CreatedOnDcom-fr-ca-M4xqunZe.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-DF63cL2a.js → CreatedOnDcom-fr-fr-BFqUXgzb.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-Ct6FWj7D.js → CreatedOnDcom-pt-br-CUVcVkV2.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-D22yb8Ci.js → CreatedOnDcom-pt-pt-Dc4CZ4xW.js} +1 -1
- package/lib/src/brand-page/components/contact-form/ContactForm.mixin.d.ts +4 -0
- package/lib/src/brand-page/components/contact-form/ContactForm.mixin.js +8 -4
- package/lib/src/brand-page/components/image-gallery/ImageGallery.mixin.d.ts +4 -0
- package/lib/src/brand-page/components/image-gallery/ImageGallery.mixin.js +4 -0
- package/lib/src/brand-page/components/mailing-list/MailingList.mixin.d.ts +4 -0
- package/lib/src/brand-page/components/mailing-list/MailingList.mixin.js +4 -1
- package/lib/src/brand-page/components/page/Page.mixin.d.ts +1 -1
- package/lib/src/brand-page/components/page/PageComponent.mixin.d.ts +1 -1
- package/lib/src/brand-page/components/payment/request-payment/RequestPayment.mixin.d.ts +4 -0
- package/lib/src/brand-page/components/payment/request-payment/RequestPayment.mixin.js +4 -0
- package/lib/src/brand-page/components/payment/support-me/SupportMe.mixin.d.ts +4 -0
- package/lib/src/brand-page/components/payment/support-me/SupportMe.mixin.js +4 -1
- package/lib/src/brand-page/components/video/Video.mixin.d.ts +4 -0
- package/lib/src/brand-page/components/video/Video.mixin.js +4 -0
- package/lib/src/brand-page/constants/misc.constants.d.ts +17 -0
- package/lib/src/brand-page/constants/misc.constants.js +25 -0
- package/lib/src/brand-page/models/content.model.d.ts +2 -0
- package/lib/src/brand-page/utils/content-padding.util.d.ts +13 -0
- package/lib/src/brand-page/utils/content-padding.util.js +10 -0
- package/lib/src/brand-page/utils/index.d.ts +1 -0
- package/lib/src/brand-page/utils/index.js +1 -0
- package/lib/src/brand-page/utils/migration/migration-16.d.ts +2 -0
- package/lib/src/brand-page/utils/migration/migration-16.js +37 -0
- package/lib/src/brand-page/utils/migration.util.js +6 -0
- package/lib/src/brand-page-maker/components/admin-logo-settings/AdminLogoSettings.mixin.d.ts +2 -0
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +12 -0
- package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +15 -1
- package/lib/src/brand-page-maker/constants/config-settings-constant.js +34 -0
- package/lib/src/brand-page-maker/convertors/banner-align-display-style.convertor.js +56 -2
- package/lib/src/brand-page-maker/convertors/contact-layout-style.convertor.js +6 -1
- package/lib/src/brand-page-maker/models/content-settings.js +30 -2
- package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +12 -0
- package/lib/src/{bundled-translations-CnareLWC.js → bundled-translations-DYd5oFZv.js} +1 -1
- package/lib/src/bundles/bundled-translations.json +2 -0
- package/lib/src/css/library.brand-page-brandCrowd.min.css +152 -140
- package/lib/src/css/library.brand-page-brandPage.min.css +153 -140
- package/lib/src/css/library.brand-page-designCom.min.css +152 -140
- package/lib/src/css/library.brand-page-preview.min.css +152 -140
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-_iwoSD1Z.js → lib-CRihSxY0.js} +20195 -19572
- package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +14 -0
- package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +14 -0
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +12 -0
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +12 -0
- package/lib/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.d.ts +12 -0
- package/lib/src/shared/content-settings/components/slider-field/SliderField.mixin.js +9 -0
- package/lib/src/shared/content-settings/store/content-settings-store.d.ts +12 -0
- package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +16 -0
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +14 -0
- package/lib/src/utils/minify-brand-page-metadata.js +11 -1
- package/package.json +2 -2
- package/src/brand-page/components/banner-section/BannerSection.vue +360 -12
- package/src/brand-page/components/banner-section/BannerSection.vue.test.ts +201 -0
- package/src/brand-page/components/booking-section/BookingSection.vue +11 -3
- package/src/brand-page/components/contact-form/ContactForm.mixin.ts +12 -6
- package/src/brand-page/components/contact-form/ContactForm.vue +115 -118
- package/src/brand-page/components/image-gallery/ImageGallery.mixin.ts +5 -0
- package/src/brand-page/components/image-gallery/ImageGallery.vue +1 -1
- package/src/brand-page/components/mailing-list/MailingList.mixin.ts +8 -1
- package/src/brand-page/components/mailing-list/MailingList.vue +1 -1
- package/src/brand-page/components/map/Map.vue +5 -1
- package/src/brand-page/components/payment/request-payment/RequestPayment.mixin.ts +5 -0
- package/src/brand-page/components/payment/request-payment/RequestPayment.vue +1 -0
- package/src/brand-page/components/payment/support-me/SupportMe.mixin.ts +8 -1
- package/src/brand-page/components/payment/support-me/SupportMe.vue +1 -0
- package/src/brand-page/components/placeholder/Placeholder.vue +39 -100
- package/src/brand-page/components/social-links/SocialLinks.vue +7 -3
- package/src/brand-page/components/text/Text.vue +12 -5
- package/src/brand-page/components/text-media-section/TextMediaSection.vue +11 -3
- package/src/brand-page/components/video/Video.mixin.ts +5 -0
- package/src/brand-page/components/video/Video.vue +1 -1
- package/src/brand-page/constants/misc.constants.ts +26 -0
- package/src/brand-page/models/content.model.ts +2 -0
- package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +7 -3
- package/src/brand-page/utils/content-padding.util.test.ts +29 -0
- package/src/brand-page/utils/content-padding.util.ts +13 -0
- package/src/brand-page/utils/index.ts +1 -0
- package/src/brand-page/utils/migration/migration-16.ts +51 -0
- package/src/brand-page/utils/migration.util.test.ts +64 -0
- package/src/brand-page/utils/migration.util.ts +7 -0
- package/src/brand-page-maker/constants/config-settings-constant.ts +40 -0
- package/src/brand-page-maker/convertors/banner-align-display-style.convertor.ts +61 -1
- package/src/brand-page-maker/convertors/contact-layout-style.convertor.test.ts +50 -0
- package/src/brand-page-maker/convertors/contact-layout-style.convertor.ts +8 -0
- package/src/brand-page-maker/models/content-settings.ts +32 -2
- package/src/bundles/bundled-translations.json +2 -0
- package/src/shared/content-settings/components/content-settings/i18n/content-settings.json +2 -0
- package/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.vue +1 -1
- package/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.vue +2 -2
- package/src/shared/content-settings/components/slider-field/SliderField.mixin.ts +9 -0
- package/src/utils/minify-brand-page-metadata.ts +11 -1
- package/lib/src/brand-page/components/banner-section/BannerSection.mixin.d.ts +0 -103
- package/lib/src/brand-page/components/banner-section/BannerSection.mixin.js +0 -178
- package/src/brand-page/components/banner-section/BannerSection.mixin.ts +0 -213
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
'c_banner--with-image-overlay tw-flex tw-flex-col tw-justify-center':
|
|
7
7
|
viewModel.isBigImageType,
|
|
8
8
|
'c_banner--with-image-overlay tw-flex tw-flex-col': isDividedContent,
|
|
9
|
-
'tw-px-6
|
|
9
|
+
'tw-px-6': !isDividedContent,
|
|
10
|
+
'tw-py-8': !isDividedContent && !isStackedFullWidth,
|
|
10
11
|
}"
|
|
11
12
|
:style="viewModel.backgroundStyle"
|
|
12
13
|
style="container-type: inline-size"
|
|
@@ -44,7 +45,85 @@
|
|
|
44
45
|
"
|
|
45
46
|
/>
|
|
46
47
|
<div
|
|
47
|
-
v-if="
|
|
48
|
+
v-if="isStackedImageLayout"
|
|
49
|
+
class="tw-flex tw-flex-col tw-gap-5 tw-items-center tw-w-full tw-h-full c_banner-container"
|
|
50
|
+
>
|
|
51
|
+
<div
|
|
52
|
+
v-if="stackedImagePosition !== 'top'"
|
|
53
|
+
class="tw-flex tw-flex-col tw-gap-5 tw-items-center tw-w-full !tw-text-center"
|
|
54
|
+
>
|
|
55
|
+
<TextWrapper
|
|
56
|
+
v-if="viewModel.title"
|
|
57
|
+
:text-style="viewModel.titleStyle"
|
|
58
|
+
:text="viewModel.title"
|
|
59
|
+
:slug="context.brandPageSlug"
|
|
60
|
+
element="h1"
|
|
61
|
+
/>
|
|
62
|
+
<template v-if="stackedImagePosition === 'bottom'">
|
|
63
|
+
<TextWrapper
|
|
64
|
+
v-if="hasDescriptionText"
|
|
65
|
+
:text-style="viewModel.descriptionStyle"
|
|
66
|
+
:text="renderText"
|
|
67
|
+
:slug="context.brandPageSlug"
|
|
68
|
+
element="div"
|
|
69
|
+
/>
|
|
70
|
+
<div class="tw-w-full tw-flex" :class="displayButtonClass">
|
|
71
|
+
<Button
|
|
72
|
+
v-if="shouldShowButton"
|
|
73
|
+
:view-model="buttonViewModel"
|
|
74
|
+
:context="context"
|
|
75
|
+
:content-index="contentIndex"
|
|
76
|
+
class="tw-w-fit"
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
</template>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<div :class="stackedImageWrapperClass">
|
|
83
|
+
<Media
|
|
84
|
+
:image-url="viewModel.imageUrl"
|
|
85
|
+
:context="context"
|
|
86
|
+
:color-replacements="viewModel.colorReplacements"
|
|
87
|
+
:media-type="viewModel.mediaType"
|
|
88
|
+
:size="viewModel.size"
|
|
89
|
+
:role="viewModel.role"
|
|
90
|
+
img-classes="tw-w-full tw-aspect-[3/1] tw-object-cover tw-block"
|
|
91
|
+
:content-index="contentIndex"
|
|
92
|
+
:media="viewModel.mediaViewModel"
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div
|
|
97
|
+
v-if="stackedImagePosition !== 'bottom'"
|
|
98
|
+
class="tw-flex tw-flex-col tw-gap-5 tw-items-center tw-w-full !tw-text-center"
|
|
99
|
+
>
|
|
100
|
+
<TextWrapper
|
|
101
|
+
v-if="stackedImagePosition === 'top' && viewModel.title"
|
|
102
|
+
:text-style="viewModel.titleStyle"
|
|
103
|
+
:text="viewModel.title"
|
|
104
|
+
:slug="context.brandPageSlug"
|
|
105
|
+
element="h1"
|
|
106
|
+
/>
|
|
107
|
+
<TextWrapper
|
|
108
|
+
v-if="hasDescriptionText"
|
|
109
|
+
:text-style="viewModel.descriptionStyle"
|
|
110
|
+
:text="renderText"
|
|
111
|
+
:slug="context.brandPageSlug"
|
|
112
|
+
element="div"
|
|
113
|
+
/>
|
|
114
|
+
<div class="tw-w-full tw-flex" :class="displayButtonClass">
|
|
115
|
+
<Button
|
|
116
|
+
v-if="shouldShowButton"
|
|
117
|
+
:view-model="buttonViewModel"
|
|
118
|
+
:context="context"
|
|
119
|
+
:content-index="contentIndex"
|
|
120
|
+
class="tw-w-fit"
|
|
121
|
+
/>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
<div
|
|
126
|
+
v-else-if="!isDividedContent"
|
|
48
127
|
:class="{
|
|
49
128
|
'c_banner-container--left-aligned-stacking':
|
|
50
129
|
viewModel.displayStyle === 'left-aligned',
|
|
@@ -101,7 +180,7 @@
|
|
|
101
180
|
<div class="tw-w-full tw-flex" :class="displayButtonClass">
|
|
102
181
|
<Button
|
|
103
182
|
v-if="shouldShowButton"
|
|
104
|
-
:view-model="
|
|
183
|
+
:view-model="buttonViewModel"
|
|
105
184
|
:context="context"
|
|
106
185
|
:content-index="contentIndex"
|
|
107
186
|
:class="{
|
|
@@ -112,7 +191,7 @@
|
|
|
112
191
|
</div>
|
|
113
192
|
</div>
|
|
114
193
|
</div>
|
|
115
|
-
<div v-if="isDividedContent">
|
|
194
|
+
<div v-else-if="isDividedContent">
|
|
116
195
|
<div
|
|
117
196
|
v-if="!shouldStackOnTop"
|
|
118
197
|
class="tw-flex tw-h-full tw-relative c_banner--divided-container"
|
|
@@ -142,11 +221,10 @@
|
|
|
142
221
|
<div class="tw-w-full tw-flex" :class="displayButtonClass">
|
|
143
222
|
<Button
|
|
144
223
|
v-if="shouldShowButton"
|
|
145
|
-
:view-model="
|
|
224
|
+
:view-model="buttonViewModel"
|
|
146
225
|
:context="context"
|
|
147
226
|
:content-index="contentIndex"
|
|
148
227
|
:class="{
|
|
149
|
-
'tw-w-fit': !isHorizontalAligned,
|
|
150
228
|
'tw-w-fit': isDividedContent,
|
|
151
229
|
'c_banner-button--stacking': isHorizontalAligned,
|
|
152
230
|
}"
|
|
@@ -227,7 +305,7 @@
|
|
|
227
305
|
<div class="tw-w-full tw-flex" :class="displayButtonClass">
|
|
228
306
|
<Button
|
|
229
307
|
v-if="shouldShowButton"
|
|
230
|
-
:view-model="
|
|
308
|
+
:view-model="buttonViewModel"
|
|
231
309
|
:context="context"
|
|
232
310
|
:content-index="contentIndex"
|
|
233
311
|
:class="{
|
|
@@ -242,16 +320,273 @@
|
|
|
242
320
|
</div>
|
|
243
321
|
</section>
|
|
244
322
|
</template>
|
|
245
|
-
<script>
|
|
246
|
-
import
|
|
323
|
+
<script setup lang="ts">
|
|
324
|
+
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue';
|
|
325
|
+
import type { PropType } from 'vue';
|
|
247
326
|
import Button from '../button/Button.vue';
|
|
248
327
|
import Media from '../media/Media.vue';
|
|
249
328
|
import TextWrapper from '../text-wrapper/TextWrapper.vue';
|
|
329
|
+
import type {
|
|
330
|
+
BannerSectionViewModel,
|
|
331
|
+
ButtonViewModel,
|
|
332
|
+
} from '@brand-page/view-models';
|
|
333
|
+
import type { BrandPageContext } from '@brand-page/models';
|
|
334
|
+
import { hasValidButton } from '../../../utils/validator.util';
|
|
335
|
+
import { getRenderText, escapeContentText } from '@brand-page/utils';
|
|
336
|
+
import { hasRTEContent } from '@brand-page-maker/utils';
|
|
337
|
+
import {
|
|
338
|
+
ALIGN_CENTERED,
|
|
339
|
+
ALIGN_LEFT_ALIGNED,
|
|
340
|
+
ALIGN_RIGHT_ALIGNED,
|
|
341
|
+
BIG_IMAGE,
|
|
342
|
+
ALIGN_LEFT_BIG_IMAGE,
|
|
343
|
+
ALIGN_RIGHT_FULL_IMAGE,
|
|
344
|
+
ALIGN_LEFT_FULL_IMAGE,
|
|
345
|
+
ALIGN_CARD_CENTER_IMAGE,
|
|
346
|
+
ALIGN_CARD_LEFT_IMAGE,
|
|
347
|
+
ALIGN_TOP_CARD_IMAGE,
|
|
348
|
+
ALIGN_BOTTOM_CARD_IMAGE,
|
|
349
|
+
ALIGN_MIDDLE_CARD_IMAGE,
|
|
350
|
+
ALIGN_TOP_FULL_IMAGE,
|
|
351
|
+
ALIGN_BOTTOM_FULL_IMAGE,
|
|
352
|
+
ALIGN_MIDDLE_FULL_IMAGE,
|
|
353
|
+
} from '../../constants';
|
|
354
|
+
|
|
355
|
+
type BannerLayout = 'inline' | 'divided' | 'stacked';
|
|
356
|
+
type StackedImagePosition = 'top' | 'middle' | 'bottom';
|
|
357
|
+
|
|
358
|
+
interface BannerLayoutConfig {
|
|
359
|
+
layout: BannerLayout;
|
|
360
|
+
/** Alignment/width classes for the text column (inline and divided-mobile layouts). */
|
|
361
|
+
alignClass: string;
|
|
362
|
+
/** Renders the 1/3 side image and the container-query stacking classes. */
|
|
363
|
+
hasSideImage?: boolean;
|
|
364
|
+
/** Text content rendered inside a card (cardStyle background, padding, rounding). */
|
|
365
|
+
isCard?: boolean;
|
|
366
|
+
isCardAlignedOnLeft?: boolean;
|
|
367
|
+
stackedImagePosition?: StackedImagePosition;
|
|
368
|
+
/** Stacked image bleeds edge-to-edge instead of staying inside the max-width container. */
|
|
369
|
+
isStackedFullWidth?: boolean;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const DEFAULT_LAYOUT: BannerLayoutConfig = { layout: 'inline', alignClass: '' };
|
|
250
373
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
374
|
+
const BANNER_LAYOUTS: Record<string, BannerLayoutConfig> = {
|
|
375
|
+
[ALIGN_CENTERED]: {
|
|
376
|
+
layout: 'inline',
|
|
377
|
+
alignClass: 'tw-items-center tw-w-full',
|
|
378
|
+
},
|
|
379
|
+
[BIG_IMAGE]: {
|
|
380
|
+
layout: 'inline',
|
|
381
|
+
alignClass: 'tw-items-center tw-w-full',
|
|
382
|
+
},
|
|
383
|
+
[ALIGN_LEFT_ALIGNED]: {
|
|
384
|
+
layout: 'inline',
|
|
385
|
+
alignClass: 'tw-items-start tw-w-2/3',
|
|
386
|
+
hasSideImage: true,
|
|
387
|
+
},
|
|
388
|
+
[ALIGN_RIGHT_ALIGNED]: {
|
|
389
|
+
layout: 'inline',
|
|
390
|
+
alignClass: 'tw-items-end tw-w-2/3',
|
|
391
|
+
hasSideImage: true,
|
|
392
|
+
},
|
|
393
|
+
[ALIGN_LEFT_BIG_IMAGE]: {
|
|
394
|
+
layout: 'inline',
|
|
395
|
+
alignClass: 'tw-items-start tw-w-2/3',
|
|
396
|
+
},
|
|
397
|
+
[ALIGN_CARD_CENTER_IMAGE]: {
|
|
398
|
+
layout: 'inline',
|
|
399
|
+
alignClass: 'tw-items-center tw-w-full',
|
|
400
|
+
isCard: true,
|
|
401
|
+
},
|
|
402
|
+
[ALIGN_CARD_LEFT_IMAGE]: {
|
|
403
|
+
layout: 'inline',
|
|
404
|
+
alignClass: 'tw-items-end',
|
|
405
|
+
isCard: true,
|
|
406
|
+
isCardAlignedOnLeft: true,
|
|
407
|
+
},
|
|
408
|
+
[ALIGN_LEFT_FULL_IMAGE]: {
|
|
409
|
+
layout: 'divided',
|
|
410
|
+
alignClass: '',
|
|
411
|
+
hasSideImage: true,
|
|
412
|
+
},
|
|
413
|
+
[ALIGN_RIGHT_FULL_IMAGE]: {
|
|
414
|
+
layout: 'divided',
|
|
415
|
+
alignClass: '',
|
|
416
|
+
hasSideImage: true,
|
|
417
|
+
},
|
|
418
|
+
[ALIGN_TOP_CARD_IMAGE]: {
|
|
419
|
+
layout: 'stacked',
|
|
420
|
+
alignClass: '',
|
|
421
|
+
stackedImagePosition: 'top',
|
|
422
|
+
},
|
|
423
|
+
[ALIGN_BOTTOM_CARD_IMAGE]: {
|
|
424
|
+
layout: 'stacked',
|
|
425
|
+
alignClass: '',
|
|
426
|
+
stackedImagePosition: 'bottom',
|
|
427
|
+
},
|
|
428
|
+
[ALIGN_MIDDLE_CARD_IMAGE]: {
|
|
429
|
+
layout: 'stacked',
|
|
430
|
+
alignClass: '',
|
|
431
|
+
stackedImagePosition: 'middle',
|
|
432
|
+
},
|
|
433
|
+
[ALIGN_TOP_FULL_IMAGE]: {
|
|
434
|
+
layout: 'stacked',
|
|
435
|
+
alignClass: '',
|
|
436
|
+
stackedImagePosition: 'top',
|
|
437
|
+
isStackedFullWidth: true,
|
|
438
|
+
},
|
|
439
|
+
[ALIGN_BOTTOM_FULL_IMAGE]: {
|
|
440
|
+
layout: 'stacked',
|
|
441
|
+
alignClass: '',
|
|
442
|
+
stackedImagePosition: 'bottom',
|
|
443
|
+
isStackedFullWidth: true,
|
|
444
|
+
},
|
|
445
|
+
[ALIGN_MIDDLE_FULL_IMAGE]: {
|
|
446
|
+
layout: 'stacked',
|
|
447
|
+
alignClass: '',
|
|
448
|
+
stackedImagePosition: 'middle',
|
|
449
|
+
isStackedFullWidth: true,
|
|
450
|
+
},
|
|
254
451
|
};
|
|
452
|
+
|
|
453
|
+
const props = defineProps({
|
|
454
|
+
viewModel: {
|
|
455
|
+
type: Object as PropType<BannerSectionViewModel & { pageSlug?: string }>,
|
|
456
|
+
required: true,
|
|
457
|
+
},
|
|
458
|
+
context: {
|
|
459
|
+
type: Object as PropType<BrandPageContext>,
|
|
460
|
+
required: true,
|
|
461
|
+
},
|
|
462
|
+
containerClientWidth: {
|
|
463
|
+
type: Number as PropType<number | undefined>,
|
|
464
|
+
required: false,
|
|
465
|
+
default: undefined,
|
|
466
|
+
},
|
|
467
|
+
containerClientHeight: {
|
|
468
|
+
type: Number as PropType<number | undefined>,
|
|
469
|
+
required: false,
|
|
470
|
+
default: undefined,
|
|
471
|
+
},
|
|
472
|
+
contentIndex: {
|
|
473
|
+
type: Number,
|
|
474
|
+
required: false,
|
|
475
|
+
default: undefined,
|
|
476
|
+
},
|
|
477
|
+
isPreview: {
|
|
478
|
+
type: Boolean,
|
|
479
|
+
required: false,
|
|
480
|
+
default: false,
|
|
481
|
+
},
|
|
482
|
+
isMobileDevice: {
|
|
483
|
+
type: Boolean,
|
|
484
|
+
required: false,
|
|
485
|
+
default: undefined,
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
const layout = computed<BannerLayoutConfig>(
|
|
490
|
+
() => BANNER_LAYOUTS[props.viewModel.displayStyle] ?? DEFAULT_LAYOUT,
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
const displayAlignClass = computed(() => layout.value.alignClass);
|
|
494
|
+
const isHorizontalAligned = computed(() => !!layout.value.hasSideImage);
|
|
495
|
+
const isDividedContent = computed(() => layout.value.layout === 'divided');
|
|
496
|
+
const isCardBannerContent = computed(() => !!layout.value.isCard);
|
|
497
|
+
const isCardAlignedOnLeft = computed(() => !!layout.value.isCardAlignedOnLeft);
|
|
498
|
+
const isStackedImageLayout = computed(() => layout.value.layout === 'stacked');
|
|
499
|
+
const stackedImagePosition = computed<StackedImagePosition>(
|
|
500
|
+
() => layout.value.stackedImagePosition ?? 'top',
|
|
501
|
+
);
|
|
502
|
+
const isStackedFullWidth = computed(() => !!layout.value.isStackedFullWidth);
|
|
503
|
+
|
|
504
|
+
const isRightImageAligned = computed(
|
|
505
|
+
() => props.viewModel.displayStyle === ALIGN_RIGHT_FULL_IMAGE,
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
const stackedImageWrapperClass = computed<Record<string, boolean>>(() => ({
|
|
509
|
+
'tw-w-full': !isStackedFullWidth.value,
|
|
510
|
+
'c_banner-stacked-image--full': isStackedFullWidth.value,
|
|
511
|
+
'tw-rounded-md tw-overflow-hidden': !isStackedFullWidth.value,
|
|
512
|
+
}));
|
|
513
|
+
|
|
514
|
+
const displayButtonClass = computed(() => {
|
|
515
|
+
const { buttonTextStyle, displayStyle } = props.viewModel;
|
|
516
|
+
|
|
517
|
+
if (buttonTextStyle) {
|
|
518
|
+
switch (buttonTextStyle.textAlign) {
|
|
519
|
+
case 'left':
|
|
520
|
+
return 'tw-justify-start';
|
|
521
|
+
case 'center':
|
|
522
|
+
return 'tw-justify-center';
|
|
523
|
+
case 'right':
|
|
524
|
+
return 'tw-justify-end';
|
|
525
|
+
default:
|
|
526
|
+
return '';
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return displayStyle === ALIGN_CARD_CENTER_IMAGE ||
|
|
531
|
+
displayStyle === ALIGN_CENTERED
|
|
532
|
+
? 'tw-justify-center'
|
|
533
|
+
: 'tw-justify-start';
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
const buttonViewModel = computed<ButtonViewModel>(() => {
|
|
537
|
+
const { buttonViewModel: model, buttonLinkType, pageSlug } = props.viewModel;
|
|
538
|
+
|
|
539
|
+
model.buttonLinkType = buttonLinkType;
|
|
540
|
+
model.pageSlug = pageSlug ?? '';
|
|
541
|
+
|
|
542
|
+
return model;
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
const shouldShowButton = computed(() =>
|
|
546
|
+
hasValidButton({
|
|
547
|
+
buttonText: props.viewModel.buttonText || undefined,
|
|
548
|
+
buttonViewModel: props.viewModel.buttonViewModel,
|
|
549
|
+
}),
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
const renderText = computed(() => {
|
|
553
|
+
if (!props.viewModel.description) {
|
|
554
|
+
return '';
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return hasRTEContent(props.viewModel.description)
|
|
558
|
+
? props.viewModel.description
|
|
559
|
+
: getRenderText(props.viewModel.description);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
const hasDescriptionText = computed(() => {
|
|
563
|
+
if (hasRTEContent(props.viewModel.description)) {
|
|
564
|
+
return escapeContentText(props.viewModel.description) !== '';
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return (props.viewModel?.description?.length ?? 0) > 0;
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
const backgroundOverlay = computed<BrandPageCssStyle>(() => ({
|
|
571
|
+
background: props.viewModel.overlayColor?.backgroundColor,
|
|
572
|
+
opacity: `${props.viewModel.overlayOpacity}%`,
|
|
573
|
+
}));
|
|
574
|
+
|
|
575
|
+
const shouldStackOnTop = ref(false);
|
|
576
|
+
|
|
577
|
+
const onResize = (): void => {
|
|
578
|
+
shouldStackOnTop.value = window.innerWidth < 640;
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
onMounted(() => {
|
|
582
|
+
nextTick(() => {
|
|
583
|
+
window.addEventListener('resize', onResize);
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
onUnmounted(() => {
|
|
588
|
+
window.removeEventListener('resize', onResize);
|
|
589
|
+
});
|
|
255
590
|
</script>
|
|
256
591
|
|
|
257
592
|
<style scoped>
|
|
@@ -273,6 +608,19 @@ export default {
|
|
|
273
608
|
margin: 0 auto;
|
|
274
609
|
}
|
|
275
610
|
|
|
611
|
+
/*
|
|
612
|
+
* Full-bleed stacked image. The wrapper lives inside .c_banner-container
|
|
613
|
+
* (capped at --brandsite-max-width and centered) and inside the outer
|
|
614
|
+
* tw-px-6 padding, so a plain width:100%/-mx-6 stops at the max-width edge.
|
|
615
|
+
* Using the outer container-query width (100cqw = content-box inline size),
|
|
616
|
+
* widen to the full banner and shift left so the image spans edge to edge
|
|
617
|
+
* (1.5rem = tw-px-6). Text blocks stay constrained to the max-width.
|
|
618
|
+
*/
|
|
619
|
+
.c_banner-stacked-image--full {
|
|
620
|
+
width: calc(100cqw + 3rem);
|
|
621
|
+
margin-inline: calc(50% - 50cqw - 1.5rem);
|
|
622
|
+
}
|
|
623
|
+
|
|
276
624
|
.c_banner--with-image-flip {
|
|
277
625
|
flex-direction: row-reverse;
|
|
278
626
|
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { vi } from 'vitest';
|
|
2
|
+
import { mount, RouterLinkStub } from '@vue/test-utils';
|
|
3
|
+
import BannerSection from './BannerSection.vue';
|
|
4
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
5
|
+
import {
|
|
6
|
+
BRAND_PAGE_TYPES,
|
|
7
|
+
MEDIA_SIZES,
|
|
8
|
+
type BrandPageContext,
|
|
9
|
+
} from '@brand-page/models';
|
|
10
|
+
import type {
|
|
11
|
+
BannerSectionViewModel,
|
|
12
|
+
ButtonViewModel,
|
|
13
|
+
NavItemViewModel,
|
|
14
|
+
} from '@brand-page/view-models';
|
|
15
|
+
|
|
16
|
+
global.ResizeObserver = vi.fn().mockImplementation(() => ({
|
|
17
|
+
observe: vi.fn(),
|
|
18
|
+
unobserve: vi.fn(),
|
|
19
|
+
disconnect: vi.fn(),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
const LINKED_PAGE_ID = 'page-2';
|
|
23
|
+
|
|
24
|
+
const createTestNavItem = (): NavItemViewModel => ({
|
|
25
|
+
pageId: LINKED_PAGE_ID,
|
|
26
|
+
slug: 'my-brand',
|
|
27
|
+
displayName: 'Second Page',
|
|
28
|
+
routeName: 'brand-page-sub-page',
|
|
29
|
+
routeParams: { slug: 'my-brand', pageSlug: 'second-page' },
|
|
30
|
+
isRoot: false,
|
|
31
|
+
isHidden: false,
|
|
32
|
+
relativePath: 'second-page',
|
|
33
|
+
nestedNavItems: [],
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const createTestBrandPageContext = (): BrandPageContext => ({
|
|
37
|
+
brandPageSlug: 'my-brand',
|
|
38
|
+
brandPageToken: '',
|
|
39
|
+
isMobile: false,
|
|
40
|
+
isPreview: true,
|
|
41
|
+
isProduction: false,
|
|
42
|
+
isServerRender: false,
|
|
43
|
+
options: {
|
|
44
|
+
mediaSizes: MEDIA_SIZES.sizeThumb,
|
|
45
|
+
},
|
|
46
|
+
isTemplate: false,
|
|
47
|
+
isDesignCom: false,
|
|
48
|
+
pageTitle: 'My Brand',
|
|
49
|
+
runtimeEnvironment: '',
|
|
50
|
+
stripePublishableKey: undefined,
|
|
51
|
+
customDomain: undefined,
|
|
52
|
+
brandPageType: BRAND_PAGE_TYPES.brandSite,
|
|
53
|
+
isBrandPage: false,
|
|
54
|
+
isBrandPageOrIsBrandContact: false,
|
|
55
|
+
isBrandSite: true,
|
|
56
|
+
isBrandContact: false,
|
|
57
|
+
pages: [],
|
|
58
|
+
isCapturingPreview: false,
|
|
59
|
+
recaptchaSiteKey: undefined,
|
|
60
|
+
nav: {
|
|
61
|
+
navItems: [createTestNavItem()],
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const textStyle = {
|
|
66
|
+
fontSize: '1rem',
|
|
67
|
+
lineHeight: '1.5rem',
|
|
68
|
+
fontFamily: 'Lato',
|
|
69
|
+
textAlign: 'center',
|
|
70
|
+
overflowWrap: 'anywhere',
|
|
71
|
+
wordBreak: 'normal',
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const createTestButtonViewModel = (
|
|
75
|
+
overrides: Partial<ButtonViewModel> = {},
|
|
76
|
+
): ButtonViewModel =>
|
|
77
|
+
({
|
|
78
|
+
id: 'button-1',
|
|
79
|
+
type: 'button',
|
|
80
|
+
role: 'banner-section',
|
|
81
|
+
title: 'Get Started',
|
|
82
|
+
url: '',
|
|
83
|
+
page: LINKED_PAGE_ID,
|
|
84
|
+
pageSlug: LINKED_PAGE_ID,
|
|
85
|
+
buttonLinkType: 'page',
|
|
86
|
+
clickActionField: 'url',
|
|
87
|
+
iconUrl: '',
|
|
88
|
+
modalImageUrl: '',
|
|
89
|
+
titleStyle: textStyle,
|
|
90
|
+
styles: { selectedButtonStyleId: 'BUTTON_STYLE_1', style: {} },
|
|
91
|
+
onClicked: vi.fn(),
|
|
92
|
+
...overrides,
|
|
93
|
+
}) as ButtonViewModel;
|
|
94
|
+
|
|
95
|
+
const createTestBannerViewModel = (
|
|
96
|
+
overrides: Partial<BannerSectionViewModel> = {},
|
|
97
|
+
): BannerSectionViewModel =>
|
|
98
|
+
({
|
|
99
|
+
id: 'banner-1',
|
|
100
|
+
type: 'banner-section',
|
|
101
|
+
role: 'banner-section',
|
|
102
|
+
imageUrl: '',
|
|
103
|
+
title: 'Welcome',
|
|
104
|
+
description: 'Your brand starts here.',
|
|
105
|
+
buttonText: 'Get Started',
|
|
106
|
+
url: '',
|
|
107
|
+
displayStyle: 'centered',
|
|
108
|
+
titleStyle: textStyle,
|
|
109
|
+
descriptionStyle: textStyle,
|
|
110
|
+
buttonTextStyle: textStyle,
|
|
111
|
+
backgroundStyle: {},
|
|
112
|
+
cardStyle: {},
|
|
113
|
+
colorReplacements: [],
|
|
114
|
+
buttonPageId: LINKED_PAGE_ID,
|
|
115
|
+
buttonLinkType: 'page',
|
|
116
|
+
overlayColor: {},
|
|
117
|
+
overlayOpacity: 0,
|
|
118
|
+
buttonViewModel: createTestButtonViewModel(),
|
|
119
|
+
isFullWidth: true,
|
|
120
|
+
isBigImageType: false,
|
|
121
|
+
...overrides,
|
|
122
|
+
}) as BannerSectionViewModel;
|
|
123
|
+
|
|
124
|
+
const mountBanner = (viewModel: BannerSectionViewModel) =>
|
|
125
|
+
mount(BannerSection, {
|
|
126
|
+
propsData: { context: createTestBrandPageContext(), viewModel },
|
|
127
|
+
global: {
|
|
128
|
+
stubs: { RouterLink: RouterLinkStub },
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('Banner Section', () => {
|
|
133
|
+
beforeEach(() => {
|
|
134
|
+
setActivePinia(createPinia());
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('page-link button renders a router-link resolved by page id when the banner view model has no pageSlug', () => {
|
|
138
|
+
// Real banner view models never carry pageSlug; the button must still
|
|
139
|
+
// render via the `page` fallback and route by buttonPageId.
|
|
140
|
+
const wrapper = mountBanner(createTestBannerViewModel());
|
|
141
|
+
|
|
142
|
+
const link = wrapper.findComponent(RouterLinkStub);
|
|
143
|
+
|
|
144
|
+
expect(link.exists()).toBe(true);
|
|
145
|
+
expect(link.props('to')).toEqual({
|
|
146
|
+
name: 'brand-page-sub-page',
|
|
147
|
+
params: { slug: 'my-brand', pageSlug: 'second-page' },
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('forwards buttonLinkType and an empty pageSlug to the button view model when absent on the banner', () => {
|
|
152
|
+
const viewModel = createTestBannerViewModel();
|
|
153
|
+
|
|
154
|
+
mountBanner(viewModel);
|
|
155
|
+
|
|
156
|
+
expect(viewModel.buttonViewModel.buttonLinkType).toBe('page');
|
|
157
|
+
expect(viewModel.buttonViewModel.pageSlug).toBe('');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('forwards the banner pageSlug to the button view model when present', () => {
|
|
161
|
+
const viewModel = createTestBannerViewModel({
|
|
162
|
+
pageSlug: 'second-page',
|
|
163
|
+
} as Partial<BannerSectionViewModel>);
|
|
164
|
+
const wrapper = mountBanner(viewModel);
|
|
165
|
+
|
|
166
|
+
expect(viewModel.buttonViewModel.pageSlug).toBe('second-page');
|
|
167
|
+
expect(wrapper.findComponent(RouterLinkStub).exists()).toBe(true);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('url button renders an anchor with the url as href', () => {
|
|
171
|
+
const wrapper = mountBanner(
|
|
172
|
+
createTestBannerViewModel({
|
|
173
|
+
buttonLinkType: 'url',
|
|
174
|
+
url: 'https://example.com',
|
|
175
|
+
buttonViewModel: createTestButtonViewModel({
|
|
176
|
+
buttonLinkType: 'url',
|
|
177
|
+
url: 'https://example.com',
|
|
178
|
+
page: '',
|
|
179
|
+
pageSlug: '',
|
|
180
|
+
}),
|
|
181
|
+
}),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
expect(wrapper.findComponent(RouterLinkStub).exists()).toBe(false);
|
|
185
|
+
const anchor = wrapper.find('a.c-button');
|
|
186
|
+
|
|
187
|
+
expect(anchor.exists()).toBe(true);
|
|
188
|
+
expect(anchor.attributes('href')).toBe('https://example.com');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('hides the button when there is no button text', () => {
|
|
192
|
+
const wrapper = mountBanner(
|
|
193
|
+
createTestBannerViewModel({
|
|
194
|
+
buttonText: false,
|
|
195
|
+
buttonViewModel: createTestButtonViewModel({ title: '' }),
|
|
196
|
+
}),
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
expect(wrapper.find('.bp-button').exists()).toBe(false);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<section data-component="booking-section">
|
|
3
3
|
<div
|
|
4
|
-
class="tw-relative tw-px-6
|
|
5
|
-
:style="viewModel.backgroundStyle"
|
|
4
|
+
class="tw-relative tw-px-6"
|
|
5
|
+
:style="[viewModel.backgroundStyle, contentPaddingStyle]"
|
|
6
6
|
style="container-type: inline-size"
|
|
7
7
|
>
|
|
8
8
|
<div
|
|
@@ -45,7 +45,11 @@ import type {
|
|
|
45
45
|
ButtonViewModel,
|
|
46
46
|
} from '@brand-page/view-models';
|
|
47
47
|
import type { BrandPageContext } from '@brand-page/models';
|
|
48
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
getRenderText,
|
|
50
|
+
escapeContentText,
|
|
51
|
+
contentPaddingStyle as toContentPaddingStyle,
|
|
52
|
+
} from '@brand-page/utils';
|
|
49
53
|
import { hasRTEContent } from '@brand-page-maker/utils';
|
|
50
54
|
import { hasValidButton as shouldShowButtonUtil } from '../../../utils/validator.util';
|
|
51
55
|
import Button from '../button/Button.vue';
|
|
@@ -71,6 +75,10 @@ const props = defineProps({
|
|
|
71
75
|
},
|
|
72
76
|
});
|
|
73
77
|
|
|
78
|
+
const contentPaddingStyle = computed(() =>
|
|
79
|
+
toContentPaddingStyle(props.viewModel),
|
|
80
|
+
);
|
|
81
|
+
|
|
74
82
|
const renderText = computed((): string => {
|
|
75
83
|
if (!props.viewModel.description) {
|
|
76
84
|
return '';
|
|
@@ -25,7 +25,10 @@ import {
|
|
|
25
25
|
RECAPTCHA_ACTION_CONTACT_FORM,
|
|
26
26
|
TEXT_ONLY,
|
|
27
27
|
} from '../../constants';
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
getRenderText,
|
|
30
|
+
contentPaddingStyle as toContentPaddingStyle,
|
|
31
|
+
} from '@brand-page/utils';
|
|
29
32
|
import { hasRTEContent } from '@brand-page-maker/utils';
|
|
30
33
|
import { useReCaptcha } from '../../composables/recaptcha.composable';
|
|
31
34
|
|
|
@@ -97,6 +100,10 @@ export default defineComponent({
|
|
|
97
100
|
return buttonTextStyle;
|
|
98
101
|
},
|
|
99
102
|
|
|
103
|
+
contentPaddingStyle(): { paddingTop: string; paddingBottom: string } {
|
|
104
|
+
return toContentPaddingStyle(this.viewModel);
|
|
105
|
+
},
|
|
106
|
+
|
|
100
107
|
hasCard(): boolean {
|
|
101
108
|
return (
|
|
102
109
|
this.viewModel.layoutStyle === ALIGN_CARD_CENTER ||
|
|
@@ -109,11 +116,10 @@ export default defineComponent({
|
|
|
109
116
|
},
|
|
110
117
|
|
|
111
118
|
hasBgColor(): boolean {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
);
|
|
119
|
+
// Every brandSite layout applies the background colour on the section
|
|
120
|
+
// root (banner-style) so the padding control reveals it; for the image
|
|
121
|
+
// layouts it only shows in the padding bands around the image block.
|
|
122
|
+
return Boolean(this.viewModel.layoutStyle);
|
|
117
123
|
},
|
|
118
124
|
|
|
119
125
|
isCenteredLayout(): boolean {
|