@designcrowd/library.brand-page 6.2.0 → 6.2.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-VQCQvlMg.js → CreatedOnBrandCrowd-de-de-CtvPtYF6.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-DSf3BjCG.js → CreatedOnBrandCrowd-en-us-eGF-EjAX.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-DGcdM9Ec.js → CreatedOnBrandCrowd-es-es-CSemsOtT.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-C9g1fc39.js → CreatedOnBrandCrowd-fr-ca-DzylX-AT.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-C9g1fc39.js → CreatedOnBrandCrowd-fr-fr-DzylX-AT.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-DSXr7Bxl.js → CreatedOnBrandCrowd-pt-br-QMUxXQsE.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-DSXr7Bxl.js → CreatedOnBrandCrowd-pt-pt-QMUxXQsE.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-BPdCL6wS.js → CreatedOnDcom-de-de-M7OoJt-1.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-CLkSTQRu.js → CreatedOnDcom-en-us-D7hMM0AI.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-CRknMAGv.js → CreatedOnDcom-es-es-CLw-BLp2.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-ClAY_dHI.js → CreatedOnDcom-fr-ca-BRu_kmeu.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-DQtv9rR-.js → CreatedOnDcom-fr-fr-IMnEv_yM.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-BbIxvmxv.js → CreatedOnDcom-pt-br-Bt6v1p27.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-BOfOesBj.js → CreatedOnDcom-pt-pt-BVsFJIJg.js} +1 -1
- package/lib/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.d.ts +16 -2
- package/lib/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.js +17 -0
- package/lib/src/brand-page/components/page/Page.mixin.d.ts +2 -1
- package/lib/src/brand-page/components/page/Page.mixin.js +12 -7
- package/lib/src/brand-page/components/page-header/HeaderLayout.stories.d.ts +1 -0
- package/lib/src/brand-page/components/page-header/HeaderLayout.stories.js +9 -0
- package/lib/src/brand-page/components/page-header/PageHeader.mixin.d.ts +3 -1
- package/lib/src/brand-page/components/page-header/PageHeader.mixin.js +10 -4
- package/lib/src/brand-page/constants/sections/header-display.constants.d.ts +3 -0
- package/lib/src/brand-page/constants/sections/header-display.constants.js +15 -0
- package/lib/src/brand-page/models/index.d.ts +12 -0
- package/lib/src/brand-page/models/index.js +4 -0
- package/lib/src/brand-page/utils/context.util.d.ts +1 -0
- package/lib/src/brand-page/utils/context.util.js +9 -0
- package/lib/src/brand-page/utils/route.util.d.ts +1 -0
- package/lib/src/brand-page/utils/route.util.js +9 -0
- package/lib/src/brand-page/utils/view-model.util.js +53 -1
- package/lib/src/brand-page/view-models.d.ts +3 -1
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +2 -1
- package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +2 -1
- package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +1626 -0
- package/lib/src/brand-page-maker/validators/section-button.validator.d.ts +5 -2
- package/lib/src/brand-page-maker/validators/section-button.validator.js +11 -15
- package/lib/src/{bundled-translations-B2q5GWTQ.js → bundled-translations-KY0aSmMp.js} +9 -9
- package/lib/src/{bundled-translations.de-DE-FhH2H_rd.js → bundled-translations.de-DE-BIFi4bYj.js} +9 -9
- package/lib/src/{bundled-translations.es-ES-_ZExxMi7.js → bundled-translations.es-ES-CHlKuPLO.js} +5 -5
- package/lib/src/{bundled-translations.fr-CA-Tg-ODIiC.js → bundled-translations.fr-CA-DR1fvm8e.js} +1 -1
- package/lib/src/{bundled-translations.fr-FR-C0sU51Mz.js → bundled-translations.fr-FR-DnpWVIQg.js} +9 -9
- package/lib/src/{bundled-translations.pt-BR-MYFIhA4u.js → bundled-translations.pt-BR-p8szTOiT.js} +17 -17
- package/lib/src/{bundled-translations.pt-PT-DeUBSzCB.js → bundled-translations.pt-PT-DEk39NhW.js} +22 -22
- package/lib/src/bundles/bundled-translations.de-DE.json +6 -1
- package/lib/src/bundles/bundled-translations.es-ES.json +6 -1
- package/lib/src/bundles/bundled-translations.fr-CA.json +6 -1
- package/lib/src/bundles/bundled-translations.fr-FR.json +6 -1
- package/lib/src/bundles/bundled-translations.json +6 -1
- package/lib/src/bundles/bundled-translations.pt-BR.json +6 -1
- package/lib/src/bundles/bundled-translations.pt-PT.json +6 -1
- package/lib/src/css/library.brand-page-brandCrowd.min.css +573 -135
- package/lib/src/css/library.brand-page-brandPage.min.css +573 -135
- package/lib/src/css/library.brand-page-designCom.min.css +573 -135
- package/lib/src/css/library.brand-page-preview.min.css +573 -135
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-BMVF4uNy.js → lib-B38VVCSw.js} +20304 -19843
- package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +2 -1
- package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/text-field/TextField.mixin.d.ts +14 -2
- package/lib/src/shared/content-settings/components/text-field/TextField.mixin.js +14 -0
- package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +1 -1
- package/lib/src/shared/page-navigation/store/page-navigation-store.d.ts +1 -0
- package/lib/src/shared/page-navigation/store/page-navigation-store.js +12 -0
- package/lib/src/shared/page-settings/components/delete-page-modal/DeletePageModal.mixin.d.ts +1 -1
- package/lib/src/shared/page-settings/components/new-page-modal/NewPageModal.mixin.d.ts +2 -1
- package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +190 -1
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +184 -2
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.js +34 -2
- package/lib/src/shared/page-settings/store/page-settings-store.d.ts +2 -2
- package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +3 -0
- package/package.json +2 -2
- package/src/brand-page/components/navigation/hamburger/HamburgerNavigation.vue +1 -0
- package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.ts +18 -1
- package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.vue +73 -3
- package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.vue.test.ts +181 -0
- package/src/brand-page/components/page/Page.mixin.ts +21 -11
- package/src/brand-page/components/page/Page.vue +2 -0
- package/src/brand-page/components/page-header/HeaderLayout.stories.ts +10 -0
- package/src/brand-page/components/page-header/HeaderLayout.vue +415 -71
- package/src/brand-page/components/page-header/PageHeader.mixin.ts +14 -10
- package/src/brand-page/components/page-header/PageHeader.vue +16 -1
- package/src/brand-page/components/page-header/PageHeader.vue.test.ts +339 -0
- package/src/brand-page/constants/sections/header-display.constants.test.ts +44 -0
- package/src/brand-page/constants/sections/header-display.constants.ts +35 -0
- package/src/brand-page/models/header-button.model.test.ts +85 -0
- package/src/brand-page/models/index.ts +19 -0
- package/src/brand-page/utils/context.util.ts +15 -0
- package/src/brand-page/utils/route.util.ts +13 -0
- package/src/brand-page/utils/view-model.util.test.ts +151 -1
- package/src/brand-page/utils/view-model.util.ts +73 -1
- package/src/brand-page/view-models.ts +3 -0
- package/src/brand-page-maker/validators/section-button.validator.ts +23 -19
- package/src/bundles/bundled-translations.de-DE.json +6 -1
- package/src/bundles/bundled-translations.es-ES.json +6 -1
- package/src/bundles/bundled-translations.fr-CA.json +6 -1
- package/src/bundles/bundled-translations.fr-FR.json +6 -1
- package/src/bundles/bundled-translations.json +6 -1
- package/src/bundles/bundled-translations.pt-BR.json +6 -1
- package/src/bundles/bundled-translations.pt-PT.json +6 -1
- package/src/shared/content-settings/components/text-field/TextField.mixin.ts +15 -0
- package/src/shared/content-settings/components/url-field/UrlField.vue +1 -0
- package/src/shared/page-navigation/store/page-navigation-store.ts +16 -0
- package/src/shared/page-settings/components/header-settings-panel/HeaderSettingsPanel.button-section.test.ts +131 -0
- package/src/shared/page-settings/components/header-settings-panel/HeaderSettingsPanel.vue +65 -10
- package/src/shared/page-settings/components/page-settings-section-styles/HeaderButtonSettings.test.ts +305 -0
- package/src/shared/page-settings/components/page-settings-section-styles/HeaderButtonSettings.vue +265 -0
- package/src/shared/page-settings/components/page-settings-section-styles/HeaderDisplayStyle.test.ts +354 -0
- package/src/shared/page-settings/components/page-settings-section-styles/HeaderDisplayStyle.vue +48 -9
- package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.header-button.test.ts +114 -0
- package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.ts +38 -1
- package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.de-DE.json +6 -1
- package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.es-ES.json +6 -1
- package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.fr-CA.json +6 -1
- package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.fr-FR.json +6 -1
- package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.json +6 -1
- package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.pt-BR.json +6 -1
- package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.pt-PT.json +6 -1
- package/src/shared/page-settings/store/page-settings-store.ts +2 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineComponent, computed } from 'vue';
|
|
2
2
|
import { PAGE_SECTION_STYLE_OPTIONS } from '@brand-page-maker/models/page-section-style.model';
|
|
3
3
|
import { PAGE_SECTIONS } from '../../models';
|
|
4
|
-
import { LOGO_SLIDER_DESKTOP_MAX, LOGO_SLIDER_DESKTOP_MIN, LOGO_SLIDER_MOBILE_MAX, LOGO_SLIDER_MOBILE_MIN, VIEWPORTS, getNavigationType, NAVIGATION_TYPE_DEFAULT_DISPLAY_TYPE, } from '@brand-page';
|
|
4
|
+
import { BUTTON_LINK_TYPE, LOGO_SLIDER_DESKTOP_MAX, LOGO_SLIDER_DESKTOP_MIN, LOGO_SLIDER_MOBILE_MAX, LOGO_SLIDER_MOBILE_MIN, VIEWPORTS, getNavigationType, NAVIGATION_TYPE_DEFAULT_DISPLAY_TYPE, } from '@brand-page';
|
|
5
5
|
import { UI_DEVICE_MODES, UI_EVENT_SOURCES } from '../../../models';
|
|
6
6
|
import { isTransparentColour } from '@brand-page/utils/color.util';
|
|
7
7
|
import { useUiPageSettingsStore } from '../../store';
|
|
@@ -12,7 +12,13 @@ import { useBrandPageContainerStore, useBrandPageMakerStore, useLogoStore, useUi
|
|
|
12
12
|
import { useUiEditableLogoStore, useUiLogoSettingsStore, } from '@shared/logo-settings/store';
|
|
13
13
|
import { FabricRenderClient } from '../../../../clients/fabric-render-client';
|
|
14
14
|
import { parseLogoThemeToCssStyles } from '@brand-page/utils';
|
|
15
|
-
import { commonTr } from '@src/composables/useTranslate';
|
|
15
|
+
import { commonTr, pageSettingsSectionTr } from '@src/composables/useTranslate';
|
|
16
|
+
// Starting values for a brand-new button; real "Contact Us" text so the label is never empty.
|
|
17
|
+
const newHeaderButton = () => ({
|
|
18
|
+
isVisible: false,
|
|
19
|
+
text: pageSettingsSectionTr('buttonLabelPlaceholder'),
|
|
20
|
+
buttonLinkType: BUTTON_LINK_TYPE.url,
|
|
21
|
+
});
|
|
16
22
|
const viewportsWithTr = () => [
|
|
17
23
|
{
|
|
18
24
|
key: VIEWPORTS.desktop,
|
|
@@ -100,6 +106,12 @@ export default defineComponent({
|
|
|
100
106
|
header() {
|
|
101
107
|
return this.sections[PAGE_SECTIONS.header][this.viewport];
|
|
102
108
|
},
|
|
109
|
+
headerButton() {
|
|
110
|
+
return this.header.headerButton;
|
|
111
|
+
},
|
|
112
|
+
isHeaderButtonVisible() {
|
|
113
|
+
return this.headerButton?.isVisible === true;
|
|
114
|
+
},
|
|
103
115
|
navigationType() {
|
|
104
116
|
return getNavigationType(this.navigation.displayType);
|
|
105
117
|
},
|
|
@@ -327,6 +339,26 @@ export default defineComponent({
|
|
|
327
339
|
viewport: VIEWPORTS.desktop,
|
|
328
340
|
});
|
|
329
341
|
},
|
|
342
|
+
onHeaderButtonChange(patch) {
|
|
343
|
+
const value = {
|
|
344
|
+
...newHeaderButton(),
|
|
345
|
+
...this.headerButton,
|
|
346
|
+
...patch,
|
|
347
|
+
};
|
|
348
|
+
// Write the whole button to BOTH viewports (own copy each) so they always match.
|
|
349
|
+
this.pageSettingsStore.setPageSettingsSectionValue({
|
|
350
|
+
sectionName: PAGE_SECTIONS.header,
|
|
351
|
+
propName: 'headerButton',
|
|
352
|
+
value: { ...value },
|
|
353
|
+
viewport: VIEWPORTS.mobile,
|
|
354
|
+
});
|
|
355
|
+
this.pageSettingsStore.setPageSettingsSectionValue({
|
|
356
|
+
sectionName: PAGE_SECTIONS.header,
|
|
357
|
+
propName: 'headerButton',
|
|
358
|
+
value: { ...value },
|
|
359
|
+
viewport: VIEWPORTS.desktop,
|
|
360
|
+
});
|
|
361
|
+
},
|
|
330
362
|
onItemListIntentImageReplace(name, { index }) {
|
|
331
363
|
this.contentSettingsStore.setSelectedItemAndFieldName({
|
|
332
364
|
id: this.sectionName,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ContentItem } from '../../content-settings/models';
|
|
2
2
|
import { PageHeaderDesktop, PageHeaderMobile, PageFooterDesktop, PageFooterMobile, VIEWPORTS } from '@brand-page';
|
|
3
3
|
import type { NavigationType } from '@brand-page';
|
|
4
|
-
import type { Content, ImageGalleryItem, LinksItem, LogoImageModel, ViewportsType, LogoTemplateImageModel, BrandPagePaymentConfig, BrandPagePaymentConfigCurrency } from '@brand-page';
|
|
4
|
+
import type { Content, HeaderButton, ImageGalleryItem, LinksItem, LogoImageModel, ViewportsType, LogoTemplateImageModel, BrandPagePaymentConfig, BrandPagePaymentConfigCurrency } from '@brand-page';
|
|
5
5
|
import type { ContentPage } from '../models';
|
|
6
6
|
import { PAGE_SECTIONS } from '../models';
|
|
7
7
|
export type PageSettingsStateSectionStyles = Partial<PageHeaderDesktop & PageHeaderMobile & PageFooterDesktop & PageFooterMobile>;
|
|
@@ -74,7 +74,7 @@ export declare const useUiPageSettingsStore: import("pinia").StoreDefinition<"ui
|
|
|
74
74
|
sectionName: PageSettingsSection;
|
|
75
75
|
propName: keyof PageSettingsStateSectionStyles;
|
|
76
76
|
index?: number;
|
|
77
|
-
value: string | boolean | LinksItem[] | BrandPageCssStyle | Partial<ImageGalleryItem> | number | null | undefined;
|
|
77
|
+
value: string | boolean | LinksItem[] | BrandPageCssStyle | Partial<ImageGalleryItem> | HeaderButton | number | null | undefined;
|
|
78
78
|
merge?: boolean;
|
|
79
79
|
viewport: ViewportsType;
|
|
80
80
|
}): void;
|
|
@@ -80,6 +80,7 @@ declare const _default: import("vue").DefineComponent<{}, {
|
|
|
80
80
|
paddingTop?: string | undefined;
|
|
81
81
|
position?: string | undefined;
|
|
82
82
|
textAlign?: string | undefined;
|
|
83
|
+
textAlignLast?: string | undefined;
|
|
83
84
|
textDecoration?: string | undefined;
|
|
84
85
|
top?: string | undefined;
|
|
85
86
|
transform?: string | undefined;
|
|
@@ -128,6 +129,7 @@ declare const _default: import("vue").DefineComponent<{}, {
|
|
|
128
129
|
paddingTop?: string | undefined;
|
|
129
130
|
position?: string | undefined;
|
|
130
131
|
textAlign?: string | undefined;
|
|
132
|
+
textAlignLast?: string | undefined;
|
|
131
133
|
textDecoration?: string | undefined;
|
|
132
134
|
top?: string | undefined;
|
|
133
135
|
transform?: string | undefined;
|
|
@@ -175,6 +177,7 @@ declare const _default: import("vue").DefineComponent<{}, {
|
|
|
175
177
|
paddingTop?: string | undefined;
|
|
176
178
|
position?: string | undefined;
|
|
177
179
|
textAlign?: string | undefined;
|
|
180
|
+
textAlignLast?: string | undefined;
|
|
178
181
|
textDecoration?: string | undefined;
|
|
179
182
|
top?: string | undefined;
|
|
180
183
|
transform?: string | undefined;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@designcrowd/library.brand-page",
|
|
3
3
|
"description": "A collection of Maker/Designer applications",
|
|
4
|
-
"version": "6.2.
|
|
4
|
+
"version": "6.2.1",
|
|
5
5
|
"author": "Design.com Eng",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"type": "module",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"@ckpack/vue-color": "1.6.0",
|
|
74
|
-
"@designcrowd/fe-shared-lib": "2.1.
|
|
74
|
+
"@designcrowd/fe-shared-lib": "2.1.2",
|
|
75
75
|
"@pinia/nuxt": "0.11.3",
|
|
76
76
|
"@storybook/react": "9.0.4",
|
|
77
77
|
"@tiptap/extension-bubble-menu": "3.28.0",
|
package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.ts
CHANGED
|
@@ -6,7 +6,8 @@ import type {
|
|
|
6
6
|
NavigationMobile,
|
|
7
7
|
} from '../../../models';
|
|
8
8
|
import { resolveSubNavItemBackgroundColor } from '../../../models';
|
|
9
|
-
import
|
|
9
|
+
import { guardPreviewClick } from '../../../utils/context.util';
|
|
10
|
+
import type { ButtonViewModel, NavItemViewModel } from '../../../view-models';
|
|
10
11
|
|
|
11
12
|
export default defineComponent({
|
|
12
13
|
props: {
|
|
@@ -26,6 +27,13 @@ export default defineComponent({
|
|
|
26
27
|
type: Object as PropType<NavigationDesktop | NavigationMobile>,
|
|
27
28
|
required: true,
|
|
28
29
|
},
|
|
30
|
+
// Provided only when the header row does not render the CTA (the caller
|
|
31
|
+
// decides via headerRowRendersCta) — absent means the side menu shows none.
|
|
32
|
+
headerButtonViewModel: {
|
|
33
|
+
type: Object as PropType<ButtonViewModel>,
|
|
34
|
+
required: false,
|
|
35
|
+
default: undefined,
|
|
36
|
+
},
|
|
29
37
|
},
|
|
30
38
|
emits: ['close-side-menu'],
|
|
31
39
|
data() {
|
|
@@ -74,6 +82,9 @@ export default defineComponent({
|
|
|
74
82
|
|
|
75
83
|
return rest;
|
|
76
84
|
},
|
|
85
|
+
showHeaderButton(): boolean {
|
|
86
|
+
return Boolean(this.headerButtonViewModel);
|
|
87
|
+
},
|
|
77
88
|
routes(): any[] {
|
|
78
89
|
return this.visibleNavItems.map((navItem, _i) => {
|
|
79
90
|
return {
|
|
@@ -114,6 +125,12 @@ export default defineComponent({
|
|
|
114
125
|
closeSideMenu(): void {
|
|
115
126
|
this.$emit('close-side-menu');
|
|
116
127
|
},
|
|
128
|
+
// Preview: inert; published: follow the link and close the side menu behind it.
|
|
129
|
+
onSideMenuCtaClick(event: MouseEvent): void {
|
|
130
|
+
if (!guardPreviewClick(event, this.context)) {
|
|
131
|
+
this.closeSideMenu();
|
|
132
|
+
}
|
|
133
|
+
},
|
|
117
134
|
toggleExpanded(pageId: string): void {
|
|
118
135
|
const isCurrentlyExpanded = this.expandedItems[pageId];
|
|
119
136
|
|
|
@@ -9,15 +9,18 @@
|
|
|
9
9
|
}"
|
|
10
10
|
@click="closeSideMenu"
|
|
11
11
|
/>
|
|
12
|
+
<!-- With a button, the nav list scrolls instead of the panel so the button stays put. -->
|
|
12
13
|
<div
|
|
13
14
|
class="c-sidebar"
|
|
14
15
|
:class="{
|
|
15
|
-
'c-sidebar--open
|
|
16
|
+
'c-sidebar--open': isSideMenuOpen,
|
|
16
17
|
'c-sidebar--closed': !isSideMenuOpen,
|
|
18
|
+
'tw-overflow-auto': isSideMenuOpen && !showHeaderButton,
|
|
19
|
+
'c-sidebar--has-cta': showHeaderButton,
|
|
17
20
|
}"
|
|
18
21
|
:style="defaultButtonStyle"
|
|
19
22
|
>
|
|
20
|
-
<div class="tw-w-full tw-flex tw-justify-end">
|
|
23
|
+
<div class="c-sidebar-header tw-w-full tw-flex tw-justify-end">
|
|
21
24
|
<button
|
|
22
25
|
class="tw-mt-4 tw-w-8 tw-h-8 tw-bg-transparent tw-border-none tw-cursor-pointer"
|
|
23
26
|
:style="closeButtonStyle"
|
|
@@ -27,7 +30,7 @@
|
|
|
27
30
|
<Icon name="close" size="sm" />
|
|
28
31
|
</button>
|
|
29
32
|
</div>
|
|
30
|
-
<div class="tw-flex tw-flex-col">
|
|
33
|
+
<div class="c-nav-list tw-flex tw-flex-col">
|
|
31
34
|
<template
|
|
32
35
|
v-for="(navItem, index) in visibleNavItems"
|
|
33
36
|
:key="`${navItem.relativePath}-${index}`"
|
|
@@ -91,16 +94,30 @@
|
|
|
91
94
|
</template>
|
|
92
95
|
</template>
|
|
93
96
|
</div>
|
|
97
|
+
<div
|
|
98
|
+
v-if="showHeaderButton"
|
|
99
|
+
data-component="navigation-side-menu-cta"
|
|
100
|
+
class="c-sidebar-cta"
|
|
101
|
+
>
|
|
102
|
+
<Button
|
|
103
|
+
data-test-header-cta-button="side-menu"
|
|
104
|
+
:view-model="headerButtonViewModel"
|
|
105
|
+
:context="context"
|
|
106
|
+
@click.capture="onSideMenuCtaClick"
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
94
109
|
</div>
|
|
95
110
|
</div>
|
|
96
111
|
</template>
|
|
97
112
|
<script>
|
|
98
113
|
import { Icon } from '@designcrowd/fe-shared-lib';
|
|
99
114
|
import NavigationSideMenuMixin from './NavigationSideMenu.mixin.ts';
|
|
115
|
+
import Button from '../../button/Button.vue';
|
|
100
116
|
|
|
101
117
|
export default {
|
|
102
118
|
components: {
|
|
103
119
|
Icon,
|
|
120
|
+
Button,
|
|
104
121
|
},
|
|
105
122
|
mixins: [NavigationSideMenuMixin],
|
|
106
123
|
};
|
|
@@ -146,4 +163,57 @@ export default {
|
|
|
146
163
|
.c-chevron-expanded {
|
|
147
164
|
transform: rotate(180deg);
|
|
148
165
|
}
|
|
166
|
+
|
|
167
|
+
/* Button pinned to the bottom of the side menu; rules only apply when the button is on. */
|
|
168
|
+
.c-sidebar--has-cta {
|
|
169
|
+
@apply tw-flex tw-flex-col;
|
|
170
|
+
overflow: hidden;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.c-sidebar--has-cta .c-sidebar-header {
|
|
174
|
+
flex-shrink: 0;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.c-sidebar--has-cta .c-nav-list {
|
|
178
|
+
/* Lets the link list scroll instead of the panel. */
|
|
179
|
+
min-height: 0;
|
|
180
|
+
overflow-y: auto;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.c-sidebar-cta {
|
|
184
|
+
@apply tw-relative tw-p-4;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* The generic Button ships section spacing; the side menu, as the caller, strips it
|
|
188
|
+
(same fit the header row applies in HeaderLayout.vue). */
|
|
189
|
+
.c-sidebar-cta :deep(.bp-button) {
|
|
190
|
+
margin-bottom: 0;
|
|
191
|
+
min-height: 0;
|
|
192
|
+
}
|
|
193
|
+
.c-sidebar-cta :deep(.c-button__no-icon) {
|
|
194
|
+
display: none;
|
|
195
|
+
}
|
|
196
|
+
/* Base label class, not a specific --offset-* variant, so a future offset swap can't slide it. */
|
|
197
|
+
.c-sidebar-cta :deep(.c-button__label) {
|
|
198
|
+
transform: none;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.c-sidebar--has-cta .c-sidebar-cta {
|
|
202
|
+
/* Keeps the button at the bottom whether links underfill or overflow. */
|
|
203
|
+
margin-top: auto;
|
|
204
|
+
flex-shrink: 0;
|
|
205
|
+
/* Opaque so scrolled links don't show through. */
|
|
206
|
+
background-color: inherit;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/* Thin separator line above the button, faded without dimming the button itself. */
|
|
210
|
+
.c-sidebar-cta::before {
|
|
211
|
+
content: '';
|
|
212
|
+
position: absolute;
|
|
213
|
+
top: 0;
|
|
214
|
+
left: 0;
|
|
215
|
+
right: 0;
|
|
216
|
+
border-top: 1px solid currentColor;
|
|
217
|
+
opacity: 0.2;
|
|
218
|
+
}
|
|
149
219
|
</style>
|
package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.vue.test.ts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { createRouter, createMemoryHistory } from 'vue-router';
|
|
3
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
4
|
+
import NavigationSideMenu from './NavigationSideMenu.vue';
|
|
5
|
+
import {
|
|
6
|
+
BRAND_PAGE_TYPES,
|
|
7
|
+
BUTTON_LINK_TYPE,
|
|
8
|
+
MEDIA_SIZES,
|
|
9
|
+
PageHeaderMobile,
|
|
10
|
+
PageStyles,
|
|
11
|
+
type BrandPageContext,
|
|
12
|
+
type HeaderButton,
|
|
13
|
+
} from '@brand-page/models';
|
|
14
|
+
import type { NavItemViewModel } from '@brand-page/view-models';
|
|
15
|
+
import { mapToPageHeaderViewModel } from '@brand-page/utils';
|
|
16
|
+
|
|
17
|
+
const CTA = '[data-test-header-cta-button]';
|
|
18
|
+
const SLUG = 'my-brand';
|
|
19
|
+
|
|
20
|
+
const createContext = (): BrandPageContext =>
|
|
21
|
+
({
|
|
22
|
+
brandPageSlug: SLUG,
|
|
23
|
+
brandPageToken: '',
|
|
24
|
+
isPreview: false,
|
|
25
|
+
isProduction: false,
|
|
26
|
+
isServerRender: false,
|
|
27
|
+
options: { mediaSizes: MEDIA_SIZES.sizeThumb },
|
|
28
|
+
isTemplate: false,
|
|
29
|
+
isDesignCom: false,
|
|
30
|
+
pageTitle: 'My Brand',
|
|
31
|
+
runtimeEnvironment: '',
|
|
32
|
+
stripePublishableKey: undefined,
|
|
33
|
+
recaptchaSiteKey: undefined,
|
|
34
|
+
customDomain: undefined,
|
|
35
|
+
brandPageType: BRAND_PAGE_TYPES.brandSite,
|
|
36
|
+
isBrandPage: false,
|
|
37
|
+
isBrandPageOrIsBrandContact: false,
|
|
38
|
+
isBrandSite: true,
|
|
39
|
+
isBrandContact: false,
|
|
40
|
+
pages: [],
|
|
41
|
+
isCapturingPreview: false,
|
|
42
|
+
currentPageRelativePath: '',
|
|
43
|
+
nav: {
|
|
44
|
+
navItems: [
|
|
45
|
+
{
|
|
46
|
+
pageId: 'home',
|
|
47
|
+
slug: SLUG,
|
|
48
|
+
displayName: 'Home',
|
|
49
|
+
routeName: 'home-route',
|
|
50
|
+
routeParams: { slug: SLUG },
|
|
51
|
+
isRoot: true,
|
|
52
|
+
isHidden: false,
|
|
53
|
+
relativePath: '',
|
|
54
|
+
nestedNavItems: [],
|
|
55
|
+
},
|
|
56
|
+
] as NavItemViewModel[],
|
|
57
|
+
},
|
|
58
|
+
}) as BrandPageContext;
|
|
59
|
+
|
|
60
|
+
const aHeaderButton = (): HeaderButton => ({
|
|
61
|
+
isVisible: true,
|
|
62
|
+
text: 'Book now',
|
|
63
|
+
buttonLinkType: BUTTON_LINK_TYPE.url,
|
|
64
|
+
url: 'https://example.com/book',
|
|
65
|
+
buttonTextStyle: { textStyleType: 'buttons' },
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const mountSideMenu = async (options: {
|
|
69
|
+
headerButton?: HeaderButton;
|
|
70
|
+
isPreview?: boolean;
|
|
71
|
+
}) => {
|
|
72
|
+
const router = createRouter({
|
|
73
|
+
history: createMemoryHistory(),
|
|
74
|
+
routes: [
|
|
75
|
+
{
|
|
76
|
+
path: '/:slug',
|
|
77
|
+
name: 'home-route',
|
|
78
|
+
component: { template: '<div />' },
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
router.push(`/${SLUG}`);
|
|
84
|
+
await router.isReady();
|
|
85
|
+
|
|
86
|
+
const headerViewModel = mapToPageHeaderViewModel(
|
|
87
|
+
new PageHeaderMobile({ headerButton: options.headerButton }),
|
|
88
|
+
new PageStyles(),
|
|
89
|
+
[],
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const context = createContext();
|
|
93
|
+
|
|
94
|
+
if (options.isPreview) {
|
|
95
|
+
context.isPreview = true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return mount(NavigationSideMenu, {
|
|
99
|
+
propsData: {
|
|
100
|
+
context,
|
|
101
|
+
isSideMenuOpen: true,
|
|
102
|
+
slug: SLUG,
|
|
103
|
+
styles: headerViewModel.navigation,
|
|
104
|
+
headerButtonViewModel: headerViewModel.headerButtonViewModel,
|
|
105
|
+
},
|
|
106
|
+
global: { plugins: [router] },
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
describe('NavigationSideMenu - header call-to-action button (BP-5852)', () => {
|
|
111
|
+
beforeEach(() => {
|
|
112
|
+
setActivePinia(createPinia());
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('renders the CTA in the mobile side menu', async () => {
|
|
116
|
+
const wrapper = await mountSideMenu({
|
|
117
|
+
headerButton: aHeaderButton(),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(
|
|
121
|
+
wrapper.find('[data-component="navigation-side-menu-cta"]').exists(),
|
|
122
|
+
).toBe(true);
|
|
123
|
+
expect(wrapper.get(CTA).text()).toContain('Book now');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('renders no CTA when the site has no header button', async () => {
|
|
127
|
+
const wrapper = await mountSideMenu({});
|
|
128
|
+
|
|
129
|
+
expect(wrapper.find(CTA).exists()).toBe(false);
|
|
130
|
+
expect(
|
|
131
|
+
wrapper.find('[data-component="navigation-side-menu-cta"]').exists(),
|
|
132
|
+
).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('with a CTA the panel hands scrolling to the nav list and pins the button last', async () => {
|
|
136
|
+
const wrapper = await mountSideMenu({
|
|
137
|
+
headerButton: aHeaderButton(),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const sidebar = wrapper.get('.c-sidebar');
|
|
141
|
+
|
|
142
|
+
expect(sidebar.classes()).toContain('c-sidebar--has-cta');
|
|
143
|
+
expect(sidebar.classes()).not.toContain('tw-overflow-auto');
|
|
144
|
+
expect(sidebar.element.lastElementChild).toBe(
|
|
145
|
+
wrapper.get('.c-sidebar-cta').element,
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('without a CTA the panel keeps its original scrolling and no layout class', async () => {
|
|
150
|
+
const wrapper = await mountSideMenu({});
|
|
151
|
+
|
|
152
|
+
const sidebar = wrapper.get('.c-sidebar');
|
|
153
|
+
|
|
154
|
+
expect(sidebar.classes()).toContain('tw-overflow-auto');
|
|
155
|
+
expect(sidebar.classes()).not.toContain('c-sidebar--has-cta');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('closes the side menu when the CTA is clicked', async () => {
|
|
159
|
+
const wrapper = await mountSideMenu({
|
|
160
|
+
headerButton: aHeaderButton(),
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
await wrapper.get(`${CTA} a`).trigger('click');
|
|
164
|
+
|
|
165
|
+
expect(wrapper.emitted('close-side-menu')).toHaveLength(1);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('a preview CTA click is inert: no navigation, side menu stays open', async () => {
|
|
169
|
+
const wrapper = await mountSideMenu({
|
|
170
|
+
headerButton: aHeaderButton(),
|
|
171
|
+
isPreview: true,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const event = new MouseEvent('click', { bubbles: true, cancelable: true });
|
|
175
|
+
|
|
176
|
+
wrapper.get(`${CTA} a`).element.dispatchEvent(event);
|
|
177
|
+
|
|
178
|
+
expect(event.defaultPrevented).toBe(true);
|
|
179
|
+
expect(wrapper.emitted('close-side-menu')).toBeUndefined();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -2,6 +2,7 @@ import type { PropType } from 'vue';
|
|
|
2
2
|
import { defineComponent } from 'vue';
|
|
3
3
|
import type {
|
|
4
4
|
BrandPageViewModel,
|
|
5
|
+
ButtonViewModel,
|
|
5
6
|
ContentViewModel,
|
|
6
7
|
PageViewModel,
|
|
7
8
|
} from '@brand-page/view-models';
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
DEFAULT_FONT_FAMILY_KEY,
|
|
15
16
|
FALLBACK_FONT_FAMILIES,
|
|
16
17
|
FONT_FAMILY_CONFIGS,
|
|
18
|
+
headerRowRendersCta,
|
|
17
19
|
} from '../../constants';
|
|
18
20
|
import {
|
|
19
21
|
getSlugFromContext,
|
|
@@ -23,7 +25,10 @@ import {
|
|
|
23
25
|
PAGE_SCOPE_ID_ATTRIBUTE,
|
|
24
26
|
resolveColorReference,
|
|
25
27
|
} from '@brand-page/utils';
|
|
26
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
findNavItemByRelativePath,
|
|
30
|
+
hasVisibleNavigation,
|
|
31
|
+
} from '@brand-page/utils/route.util';
|
|
27
32
|
import type { ComponentPublicInstance } from '@vue/runtime-core';
|
|
28
33
|
import { BRAND_PAGE_TYPES } from '@brand-page/models';
|
|
29
34
|
import { hasProfileContent as checkHasProfileContent } from '@brand-page/utils/vcard.util';
|
|
@@ -370,17 +375,22 @@ export default defineComponent({
|
|
|
370
375
|
);
|
|
371
376
|
},
|
|
372
377
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const
|
|
376
|
-
(item) => !item.isHidden,
|
|
377
|
-
);
|
|
378
|
+
// The button shows in the header row OR in the side menu, never both. This gives it to the side menu when the header doesn't show it; undefined = no button in the menu.
|
|
379
|
+
sideMenuHeaderButtonViewModel(): ButtonViewModel | undefined {
|
|
380
|
+
const header = this.viewModel.headerViewModelMobile;
|
|
378
381
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
(
|
|
382
|
-
|
|
383
|
-
|
|
382
|
+
if (
|
|
383
|
+
!header.headerButtonViewModel ||
|
|
384
|
+
headerRowRendersCta(header.displayStyle, this.shouldShowNavigation)
|
|
385
|
+
) {
|
|
386
|
+
return undefined;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return header.headerButtonViewModel;
|
|
390
|
+
},
|
|
391
|
+
|
|
392
|
+
shouldShowNavigation(): boolean {
|
|
393
|
+
return hasVisibleNavigation(this.context.nav.navItems);
|
|
384
394
|
},
|
|
385
395
|
|
|
386
396
|
showNavigationAfterLogoMobile(): boolean {
|
|
@@ -296,6 +296,7 @@
|
|
|
296
296
|
:page-navigation="viewModel.nav"
|
|
297
297
|
@close-side-menu="closeSideMenu"
|
|
298
298
|
/>
|
|
299
|
+
<!-- No header CTA: the desktop hamburger header row renders it. -->
|
|
299
300
|
</template>
|
|
300
301
|
<template #mobile>
|
|
301
302
|
<NavigationSideMenu
|
|
@@ -304,6 +305,7 @@
|
|
|
304
305
|
:slug="slug"
|
|
305
306
|
:styles="viewModel.headerViewModelMobile.navigation"
|
|
306
307
|
:page-navigation="viewModel.nav"
|
|
308
|
+
:header-button-view-model="sideMenuHeaderButtonViewModel"
|
|
307
309
|
@close-side-menu="closeSideMenu"
|
|
308
310
|
/>
|
|
309
311
|
</template>
|
|
@@ -43,6 +43,7 @@ const Template: StoryFn = (args) => ({
|
|
|
43
43
|
nav-id="nav-1"
|
|
44
44
|
:show-nav="args.showNav"
|
|
45
45
|
:display-style="args.displayStyle"
|
|
46
|
+
:has-cta="args.hasCta"
|
|
46
47
|
logo-width="100px"
|
|
47
48
|
>
|
|
48
49
|
<template v-slot:logo>
|
|
@@ -51,6 +52,9 @@ const Template: StoryFn = (args) => ({
|
|
|
51
52
|
<template v-slot:nav>
|
|
52
53
|
<div style="border: 1px solid red; display: inline-block;">Nav | Nav | Nav | Nav | Nav | Nav | Nav | Nav</div>
|
|
53
54
|
</template>
|
|
55
|
+
<template v-slot:cta>
|
|
56
|
+
<div style="border: 1px solid green; display: inline-block; padding: 8px 16px;">Contact Us</div>
|
|
57
|
+
</template>
|
|
54
58
|
</HeaderLayout>
|
|
55
59
|
</div>
|
|
56
60
|
`,
|
|
@@ -60,3 +64,9 @@ export const Sample = Template.bind({});
|
|
|
60
64
|
Sample.args = {
|
|
61
65
|
displayStyle: HEADER_DISPLAY.DESKTOP_TAB_5,
|
|
62
66
|
};
|
|
67
|
+
|
|
68
|
+
export const WithHeaderButton = Template.bind({});
|
|
69
|
+
WithHeaderButton.args = {
|
|
70
|
+
displayStyle: HEADER_DISPLAY.DESKTOP_TAB_1,
|
|
71
|
+
hasCta: true,
|
|
72
|
+
};
|