@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
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
3
|
+
import { shallowMount } from '@vue/test-utils';
|
|
4
|
+
import { Toggle } from '@designcrowd/fe-shared-lib';
|
|
5
|
+
import type { LogoTheme } from '@brand-page';
|
|
6
|
+
import { VIEWPORTS } from '@brand-page';
|
|
7
|
+
import HeaderSettingsPanel from './HeaderSettingsPanel.vue';
|
|
8
|
+
import HeaderButtonSettings from '../page-settings-section-styles/HeaderButtonSettings.vue';
|
|
9
|
+
import { useUiPageSettingsStore } from '../../store';
|
|
10
|
+
import { FabricRenderClient } from '../../../../clients/fabric-render-client';
|
|
11
|
+
|
|
12
|
+
// Button section: brandsite-only, header toggle shows/hides the fields, writes sync both viewports.
|
|
13
|
+
describe('HeaderSettingsPanel — Button section', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
setActivePinia(createPinia());
|
|
16
|
+
// The Logo section renders alongside and needs the client singleton.
|
|
17
|
+
FabricRenderClient.setInstance({ baseUrl: 'http://localhost:3000' });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const CollapsiblePanelStub = {
|
|
21
|
+
template: '<div><slot name="header-actions" /><slot /></div>',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const mountPanel = (isBrandSite: boolean) =>
|
|
25
|
+
shallowMount(HeaderSettingsPanel, {
|
|
26
|
+
props: {
|
|
27
|
+
isAdminMaker: false,
|
|
28
|
+
isBrandSite,
|
|
29
|
+
logoTheme: {} as LogoTheme,
|
|
30
|
+
sectionName: 'header',
|
|
31
|
+
styleOptions: [],
|
|
32
|
+
},
|
|
33
|
+
global: {
|
|
34
|
+
// Render CollapsiblePanel slots so the toggle and body aren't swallowed.
|
|
35
|
+
stubs: { CollapsiblePanel: CollapsiblePanelStub },
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const buttonToggle = (wrapper: ReturnType<typeof mountPanel>) =>
|
|
40
|
+
wrapper
|
|
41
|
+
.findAllComponents(Toggle)
|
|
42
|
+
.find(
|
|
43
|
+
(t) => t.attributes('data-test-header-button-toggle') !== undefined,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
test('brandsite: the header toggle renders, off, and the fields stay hidden', () => {
|
|
47
|
+
const wrapper = mountPanel(true);
|
|
48
|
+
|
|
49
|
+
expect(buttonToggle(wrapper)).toBeDefined();
|
|
50
|
+
// the stubbed Toggle carries its props as attributes
|
|
51
|
+
expect(buttonToggle(wrapper)?.attributes('model-value')).toBe('false');
|
|
52
|
+
expect(wrapper.findComponent(HeaderButtonSettings).exists()).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('brand page / brand contact: no Button section at all', () => {
|
|
56
|
+
const wrapper = mountPanel(false);
|
|
57
|
+
|
|
58
|
+
expect(buttonToggle(wrapper)).toBeUndefined();
|
|
59
|
+
expect(wrapper.findComponent(HeaderButtonSettings).exists()).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('toggling on writes isVisible to BOTH viewports and reveals the fields', async () => {
|
|
63
|
+
const wrapper = mountPanel(true);
|
|
64
|
+
|
|
65
|
+
await buttonToggle(wrapper)?.vm.$emit('update:model-value', true);
|
|
66
|
+
await wrapper.vm.$nextTick();
|
|
67
|
+
|
|
68
|
+
const sections = useUiPageSettingsStore().sections.header;
|
|
69
|
+
|
|
70
|
+
expect(sections[VIEWPORTS.desktop].headerButton?.isVisible).toBe(true);
|
|
71
|
+
expect(sections[VIEWPORTS.mobile].headerButton?.isVisible).toBe(true);
|
|
72
|
+
expect(wrapper.findComponent(HeaderButtonSettings).exists()).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('expanding via the chevron while off switches the button on; later collapses freely', async () => {
|
|
76
|
+
const wrapper = mountPanel(true);
|
|
77
|
+
const sectionHeader = wrapper.get(
|
|
78
|
+
'[data-test-header-button-section-header]',
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// off + collapsed: expanding turns the button on and shows the fields
|
|
82
|
+
await sectionHeader.trigger('click');
|
|
83
|
+
|
|
84
|
+
const sections = useUiPageSettingsStore().sections.header;
|
|
85
|
+
|
|
86
|
+
expect(sections[VIEWPORTS.desktop].headerButton?.isVisible).toBe(true);
|
|
87
|
+
expect(sections[VIEWPORTS.mobile].headerButton?.isVisible).toBe(true);
|
|
88
|
+
expect(wrapper.findComponent(HeaderButtonSettings).exists()).toBe(true);
|
|
89
|
+
|
|
90
|
+
// on: the chevron only collapses/expands, never switches the button off
|
|
91
|
+
await sectionHeader.trigger('click');
|
|
92
|
+
|
|
93
|
+
expect(sections[VIEWPORTS.desktop].headerButton?.isVisible).toBe(true);
|
|
94
|
+
expect(wrapper.findComponent(HeaderButtonSettings).exists()).toBe(false);
|
|
95
|
+
|
|
96
|
+
await sectionHeader.trigger('click');
|
|
97
|
+
|
|
98
|
+
expect(wrapper.findComponent(HeaderButtonSettings).exists()).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('toggling off hides the fields but keeps the configuration for a re-enable', async () => {
|
|
102
|
+
const wrapper = mountPanel(true);
|
|
103
|
+
|
|
104
|
+
await buttonToggle(wrapper)?.vm.$emit('update:model-value', true);
|
|
105
|
+
// simulate a label edit, which the mixin dual-writes to both viewports
|
|
106
|
+
[VIEWPORTS.desktop, VIEWPORTS.mobile].forEach((viewport) => {
|
|
107
|
+
useUiPageSettingsStore().setPageSettingsSectionValue({
|
|
108
|
+
sectionName: 'header',
|
|
109
|
+
propName: 'headerButton',
|
|
110
|
+
value: { isVisible: true, text: 'Book now', buttonLinkType: 'url' },
|
|
111
|
+
viewport,
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
await buttonToggle(wrapper)?.vm.$emit('update:model-value', false);
|
|
115
|
+
await wrapper.vm.$nextTick();
|
|
116
|
+
|
|
117
|
+
const desktop = useUiPageSettingsStore().sections.header[VIEWPORTS.desktop];
|
|
118
|
+
|
|
119
|
+
expect(wrapper.findComponent(HeaderButtonSettings).exists()).toBe(false);
|
|
120
|
+
expect(desktop.headerButton?.isVisible).toBe(false);
|
|
121
|
+
expect(desktop.headerButton?.text).toBe('Book now');
|
|
122
|
+
|
|
123
|
+
// Toggling off also collapses, so one chevron click re-enables — no dead first click.
|
|
124
|
+
await wrapper
|
|
125
|
+
.get('[data-test-header-button-section-header]')
|
|
126
|
+
.trigger('click');
|
|
127
|
+
|
|
128
|
+
expect(desktop.headerButton?.isVisible).toBe(true);
|
|
129
|
+
expect(wrapper.findComponent(HeaderButtonSettings).exists()).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
:on-select="handleHeaderDisplayStyleChange"
|
|
65
65
|
:viewport="viewport"
|
|
66
66
|
:is-logo-visible="isLogoVisible"
|
|
67
|
+
:show-header-button="isHeaderButtonVisible"
|
|
67
68
|
/>
|
|
68
69
|
|
|
69
70
|
<NavigationStyleSettings
|
|
@@ -144,27 +145,63 @@
|
|
|
144
145
|
</div>
|
|
145
146
|
</CollapsiblePanel>
|
|
146
147
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
148
|
+
<div
|
|
149
|
+
v-if="isBrandSite"
|
|
150
|
+
data-test-header-button-section
|
|
151
|
+
class="tw-bg-white tw-border tw-border-grayscale-500 tw-border-solid tw-flex tw-flex-col tw-mb-4 tw-p-4 tw-rounded-md"
|
|
152
|
+
>
|
|
153
|
+
<div
|
|
154
|
+
data-test-header-button-section-header
|
|
155
|
+
class="tw-cursor-pointer tw-text-secondary-500 tw-flex tw-items-center tw-select-none"
|
|
156
|
+
:class="{
|
|
157
|
+
'tw-mb-2': !collapsedSections.button && isHeaderButtonVisible,
|
|
158
|
+
}"
|
|
159
|
+
@click="onButtonSectionChanged"
|
|
160
|
+
>
|
|
161
|
+
<div
|
|
162
|
+
class="tw-grow tw-text-md tw-font-sans tw-font-bold tw-text-secondary-800"
|
|
163
|
+
>
|
|
164
|
+
{{ pageSettingsTr('buttonSectionTitle') }}
|
|
165
|
+
</div>
|
|
166
|
+
<div class="tw-ml-2" @click.stop>
|
|
167
|
+
<Toggle
|
|
168
|
+
id="header-button-visible"
|
|
169
|
+
data-test-header-button-toggle
|
|
170
|
+
color="primary"
|
|
171
|
+
:model-value="isHeaderButtonVisible"
|
|
172
|
+
@update:model-value="onHeaderButtonToggle"
|
|
173
|
+
/>
|
|
174
|
+
</div>
|
|
175
|
+
<Icon
|
|
176
|
+
class="tw-ml-2 tw-text-grayscale-600"
|
|
177
|
+
:name="collapsedSections.button ? 'chevron-down' : 'chevron-up'"
|
|
178
|
+
size="sm"
|
|
179
|
+
/>
|
|
180
|
+
</div>
|
|
181
|
+
<HeaderButtonSettings
|
|
182
|
+
v-if="!collapsedSections.button && isHeaderButtonVisible"
|
|
183
|
+
:value="headerButton"
|
|
184
|
+
:is-admin-maker="isAdminMaker"
|
|
185
|
+
:logo-theme="logoTheme"
|
|
186
|
+
@on-change="onHeaderButtonChange"
|
|
187
|
+
/>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<!-- The Page dropdown's ADD PAGE opens this; every panel hosting a PageDropdownField mounts its own. -->
|
|
191
|
+
<NewPageModal />
|
|
157
192
|
</div>
|
|
158
193
|
</template>
|
|
159
194
|
<script>
|
|
160
195
|
import {
|
|
161
196
|
CollapsiblePanel,
|
|
162
197
|
HelloBar,
|
|
198
|
+
Icon,
|
|
163
199
|
Slider,
|
|
164
200
|
TabMenu,
|
|
165
201
|
Toggle,
|
|
166
202
|
} from '@designcrowd/fe-shared-lib';
|
|
167
203
|
import PageSettingsSectionStyleMixin from '../page-settings-section-styles/PageSettingsSectionStyles.mixin.ts';
|
|
204
|
+
import HeaderButtonSettings from '../page-settings-section-styles/HeaderButtonSettings.vue';
|
|
168
205
|
import HeaderDisplayStyle from '../page-settings-section-styles/HeaderDisplayStyle.vue';
|
|
169
206
|
import HeaderNavigationType from '../page-settings-section-styles/HeaderNavigationType.vue';
|
|
170
207
|
import HeaderNavigationStyle from '../page-settings-section-styles/HeaderNavigationStyle.vue';
|
|
@@ -173,16 +210,20 @@ import ColorField from '../../../content-settings/components/color-field/ColorFi
|
|
|
173
210
|
import LogoSelection from '@shared/content-settings/components/logo-selection-field/LogoSelection.vue';
|
|
174
211
|
import LogoSizeSlider from '@shared/content-settings/components/logo-size-field/LogoSizeSlider.vue';
|
|
175
212
|
import SideBarSettingsHeader from '../../../common/SideBarSettingsHeader.vue';
|
|
213
|
+
import NewPageModal from '../new-page-modal/NewPageModal.vue';
|
|
176
214
|
import { tr, commonTr, pageSettingsTr } from '@src/composables/useTranslate';
|
|
177
215
|
|
|
178
216
|
export default {
|
|
179
217
|
components: {
|
|
218
|
+
NewPageModal,
|
|
180
219
|
CollapsiblePanel,
|
|
181
220
|
ColorField,
|
|
182
221
|
HelloBar,
|
|
222
|
+
Icon,
|
|
183
223
|
Slider,
|
|
184
224
|
TabMenu,
|
|
185
225
|
Toggle,
|
|
226
|
+
HeaderButtonSettings,
|
|
186
227
|
HeaderDisplayStyle,
|
|
187
228
|
HeaderNavigationType,
|
|
188
229
|
HeaderNavigationStyle,
|
|
@@ -222,6 +263,20 @@ export default {
|
|
|
222
263
|
toggleSectionCollapsed(section) {
|
|
223
264
|
this.collapsedSections[section] = !this.collapsedSections[section];
|
|
224
265
|
},
|
|
266
|
+
onHeaderButtonToggle(isVisible) {
|
|
267
|
+
this.onHeaderButtonChange({ isVisible });
|
|
268
|
+
|
|
269
|
+
this.collapsedSections.button = !isVisible;
|
|
270
|
+
},
|
|
271
|
+
onButtonSectionChanged() {
|
|
272
|
+
const isExpanding = this.collapsedSections.button;
|
|
273
|
+
|
|
274
|
+
this.toggleSectionCollapsed('button');
|
|
275
|
+
|
|
276
|
+
if (isExpanding && !this.isHeaderButtonVisible) {
|
|
277
|
+
this.onHeaderButtonChange({ isVisible: true });
|
|
278
|
+
}
|
|
279
|
+
},
|
|
225
280
|
},
|
|
226
281
|
};
|
|
227
282
|
</script>
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
3
|
+
import { shallowMount } from '@vue/test-utils';
|
|
4
|
+
import type { VueWrapper } from '@vue/test-utils';
|
|
5
|
+
import type { HeaderButton, LogoTheme } from '@brand-page';
|
|
6
|
+
import HeaderButtonSettings from './HeaderButtonSettings.vue';
|
|
7
|
+
import { useUiPageSettingsStore } from '../../store';
|
|
8
|
+
import { useUiStyleSettingsStore } from '../../../style-settings/store/style-settings-store';
|
|
9
|
+
import { availableButtonStyleConfigs } from '@brand-page-maker/models';
|
|
10
|
+
import type { ContentPage } from '../../models';
|
|
11
|
+
|
|
12
|
+
// Explicit stub: the auto-stub misses mixin-declared props, so props('value') would be undefined.
|
|
13
|
+
const TextStyleFieldStub = {
|
|
14
|
+
name: 'TextStyleField',
|
|
15
|
+
props: ['value'],
|
|
16
|
+
template: '<div />',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const UrlFieldStub = {
|
|
20
|
+
name: 'UrlField',
|
|
21
|
+
props: ['settings'],
|
|
22
|
+
template: '<div />',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const TextFieldStub = {
|
|
26
|
+
name: 'TextField',
|
|
27
|
+
// showErrorWhenEmpty typed so the bare attribute casts to true, as on the real component.
|
|
28
|
+
props: { settings: Object, showErrorWhenEmpty: Boolean },
|
|
29
|
+
template: '<div />',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Fields are found by data-test hook (two of them are ColorFields).
|
|
33
|
+
const emitFrom = (
|
|
34
|
+
wrapper: VueWrapper,
|
|
35
|
+
hook: string,
|
|
36
|
+
payload: unknown,
|
|
37
|
+
): void => {
|
|
38
|
+
(wrapper.findComponent(hook) as unknown as VueWrapper).vm.$emit(
|
|
39
|
+
'update:modelValue',
|
|
40
|
+
payload,
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Every edit leaves through one on-change patch (the mixin writes it to both viewports).
|
|
45
|
+
describe('HeaderButtonSettings', () => {
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
setActivePinia(createPinia());
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const mountPanel = (value?: HeaderButton) =>
|
|
51
|
+
shallowMount(HeaderButtonSettings, {
|
|
52
|
+
props: {
|
|
53
|
+
value,
|
|
54
|
+
isAdminMaker: false,
|
|
55
|
+
logoTheme: {} as LogoTheme,
|
|
56
|
+
},
|
|
57
|
+
global: {
|
|
58
|
+
stubs: {
|
|
59
|
+
TextStyleField: TextStyleFieldStub,
|
|
60
|
+
UrlField: UrlFieldStub,
|
|
61
|
+
TextField: TextFieldStub,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const enabled: HeaderButton = {
|
|
67
|
+
isVisible: true,
|
|
68
|
+
text: '',
|
|
69
|
+
buttonLinkType: 'url',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const fieldHooks = [
|
|
73
|
+
'[data-test-header-button-text]',
|
|
74
|
+
'[data-test-header-button-link-type]',
|
|
75
|
+
'[data-test-header-button-text-style]',
|
|
76
|
+
'[data-test-header-button-fill-color]',
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
// The show/hide toggle lives in HeaderSettingsPanel, so every field here renders unconditionally.
|
|
80
|
+
test('every field renders', () => {
|
|
81
|
+
const wrapper = mountPanel(enabled);
|
|
82
|
+
|
|
83
|
+
fieldHooks.forEach((hook) => {
|
|
84
|
+
expect(wrapper.find(hook).exists()).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Border color only shows when the selected button style draws a border.
|
|
89
|
+
test('the border colour field is hidden while the selected button style draws no border', () => {
|
|
90
|
+
// default selectedButtonStyleId is BUTTON_STYLE_1, whose options carry no border
|
|
91
|
+
const wrapper = mountPanel(enabled);
|
|
92
|
+
|
|
93
|
+
expect(
|
|
94
|
+
wrapper.find('[data-test-header-button-border-color]').exists(),
|
|
95
|
+
).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('the border colour field renders when the selected button style has a border', () => {
|
|
99
|
+
useUiStyleSettingsStore().$patch(($state) => {
|
|
100
|
+
// availableButtonStyleConfigs starts empty until the store initialises
|
|
101
|
+
$state.availableButtonStyleConfigs = availableButtonStyleConfigs;
|
|
102
|
+
$state.buttonStyles.selectedButtonStyleId = 'BUTTON_STYLE_4';
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const wrapper = mountPanel(enabled);
|
|
106
|
+
|
|
107
|
+
expect(
|
|
108
|
+
wrapper.find('[data-test-header-button-border-color]').exists(),
|
|
109
|
+
).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('the link-value field follows the selected link kind', () => {
|
|
113
|
+
const kinds = {
|
|
114
|
+
url: '[data-test-header-button-url]',
|
|
115
|
+
page: '[data-test-header-button-page]',
|
|
116
|
+
mailTo: '[data-test-header-button-email]',
|
|
117
|
+
tel: '[data-test-header-button-tel]',
|
|
118
|
+
} as const;
|
|
119
|
+
|
|
120
|
+
Object.entries(kinds).forEach(([kind, hook]) => {
|
|
121
|
+
const wrapper = mountPanel({
|
|
122
|
+
...enabled,
|
|
123
|
+
buttonLinkType: kind as HeaderButton['buttonLinkType'],
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
Object.values(kinds).forEach((otherHook) => {
|
|
127
|
+
expect(wrapper.find(otherHook).exists()).toBe(otherHook === hook);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const linkTypeValues = (wrapper: VueWrapper): string[] => {
|
|
133
|
+
const group = wrapper.findComponent(
|
|
134
|
+
'[data-test-header-button-link-type]',
|
|
135
|
+
) as unknown as { props(name: string): unknown };
|
|
136
|
+
|
|
137
|
+
return (group.props('options') as { value: string }[]).map(
|
|
138
|
+
(option) => option.value,
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
test('the four link kinds are offered in a 2-column group', () => {
|
|
143
|
+
const wrapper = mountPanel(enabled);
|
|
144
|
+
|
|
145
|
+
expect(linkTypeValues(wrapper)).toEqual(['url', 'page', 'mailTo', 'tel']);
|
|
146
|
+
expect(
|
|
147
|
+
wrapper.find('[data-test-header-button-link-type]').attributes('columns'),
|
|
148
|
+
).toBe('2');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('a single-page site drops the Page kind (buttonLinkStyleConvertor)', () => {
|
|
152
|
+
useUiPageSettingsStore().setPages({
|
|
153
|
+
pages: [{ id: 'page-1', title: 'Home', contents: [] }] as ContentPage[],
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(linkTypeValues(mountPanel(enabled))).toEqual([
|
|
157
|
+
'url',
|
|
158
|
+
'mailTo',
|
|
159
|
+
'tel',
|
|
160
|
+
]);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('a label edit emits only the text patch', () => {
|
|
164
|
+
const wrapper = mountPanel(enabled);
|
|
165
|
+
|
|
166
|
+
emitFrom(wrapper, '[data-test-header-button-text]', {
|
|
167
|
+
id: 'headerButton',
|
|
168
|
+
name: 'text',
|
|
169
|
+
value: 'Book now',
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
expect(wrapper.emitted('on-change')).toEqual([[{ text: 'Book now' }]]);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Unset text style must show the renderer's Buttons default, not None/blank.
|
|
176
|
+
test('an unset text style presents the Buttons-linked default', () => {
|
|
177
|
+
const wrapper = mountPanel(enabled);
|
|
178
|
+
|
|
179
|
+
expect(
|
|
180
|
+
wrapper.findComponent({ name: 'TextStyleField' }).props('value'),
|
|
181
|
+
).toEqual({
|
|
182
|
+
textStyleType: 'buttons',
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('an authored text style is passed through untouched', () => {
|
|
187
|
+
const wrapper = mountPanel({
|
|
188
|
+
...enabled,
|
|
189
|
+
buttonTextStyle: { fontFamily: 'Lora', textAlign: 'center' },
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
expect(
|
|
193
|
+
wrapper.findComponent({ name: 'TextStyleField' }).props('value'),
|
|
194
|
+
).toEqual({
|
|
195
|
+
fontFamily: 'Lora',
|
|
196
|
+
textAlign: 'center',
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Stricter than the banner: the link is always required, whatever the label holds.
|
|
201
|
+
test('a labelled button with an empty link shows the error', () => {
|
|
202
|
+
const wrapper = mountPanel({ ...enabled, text: 'Contact Us' });
|
|
203
|
+
|
|
204
|
+
expect(
|
|
205
|
+
wrapper.findComponent({ name: 'UrlField' }).props('settings'),
|
|
206
|
+
).toMatchObject({ isValid: false });
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('an unlabelled button with an empty link also shows the error', () => {
|
|
210
|
+
const wrapper = mountPanel(enabled);
|
|
211
|
+
|
|
212
|
+
expect(
|
|
213
|
+
wrapper.findComponent({ name: 'UrlField' }).props('settings'),
|
|
214
|
+
).toMatchObject({ isValid: false });
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('a valid link is accepted while the label is still empty', () => {
|
|
218
|
+
const wrapper = mountPanel({ ...enabled, url: 'https://example.com' });
|
|
219
|
+
|
|
220
|
+
expect(
|
|
221
|
+
wrapper.findComponent({ name: 'UrlField' }).props('settings'),
|
|
222
|
+
).toMatchObject({ isValid: true });
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test('switching the link kind clears the previous value', () => {
|
|
226
|
+
const wrapper = mountPanel({ ...enabled, url: 'https://example.com' });
|
|
227
|
+
|
|
228
|
+
(
|
|
229
|
+
wrapper.findComponent(
|
|
230
|
+
'[data-test-header-button-link-type]',
|
|
231
|
+
) as unknown as VueWrapper
|
|
232
|
+
).vm.$emit('on-select', { value: 'mailTo' });
|
|
233
|
+
|
|
234
|
+
expect(wrapper.emitted('on-change')).toEqual([
|
|
235
|
+
[{ buttonLinkType: 'mailTo', url: '' }],
|
|
236
|
+
]);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// The label is required — an empty one hides the button on the site.
|
|
240
|
+
test.each([[''], [' ']])(
|
|
241
|
+
'a blank label shows the required error (%j)',
|
|
242
|
+
(text) => {
|
|
243
|
+
const wrapper = mountPanel({ ...enabled, text });
|
|
244
|
+
|
|
245
|
+
expect(
|
|
246
|
+
wrapper.findComponent({ name: 'TextField' }).props('settings'),
|
|
247
|
+
).toMatchObject({ isValid: false });
|
|
248
|
+
},
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
test('the label shows its error as soon as it is cleared', () => {
|
|
252
|
+
const wrapper = mountPanel(enabled);
|
|
253
|
+
|
|
254
|
+
expect(
|
|
255
|
+
wrapper.findComponent({ name: 'TextField' }).props('showErrorWhenEmpty'),
|
|
256
|
+
).toBe(true);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test('a filled label is valid', () => {
|
|
260
|
+
const wrapper = mountPanel({ ...enabled, text: 'Contact Us' });
|
|
261
|
+
|
|
262
|
+
expect(
|
|
263
|
+
wrapper.findComponent({ name: 'TextField' }).props('settings'),
|
|
264
|
+
).toMatchObject({ isValid: true });
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// A transparent fill or border would leave a ghost button, so neither offers the option.
|
|
268
|
+
test.each([
|
|
269
|
+
['fill', '[data-test-header-button-fill-color]', 'BUTTON_STYLE_1'],
|
|
270
|
+
['border', '[data-test-header-button-border-color]', 'BUTTON_STYLE_4'],
|
|
271
|
+
])(
|
|
272
|
+
'the %s colour field does not offer transparent',
|
|
273
|
+
(_name, hook, styleId) => {
|
|
274
|
+
useUiStyleSettingsStore().$patch(($state) => {
|
|
275
|
+
$state.availableButtonStyleConfigs = availableButtonStyleConfigs;
|
|
276
|
+
// BUTTON_STYLE_4 draws a border, so the border field renders.
|
|
277
|
+
$state.buttonStyles.selectedButtonStyleId = styleId;
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const wrapper = mountPanel(enabled);
|
|
281
|
+
|
|
282
|
+
expect(
|
|
283
|
+
(
|
|
284
|
+
wrapper.findComponent(hook) as unknown as {
|
|
285
|
+
props(name: string): unknown;
|
|
286
|
+
}
|
|
287
|
+
).props('disableTransparent'),
|
|
288
|
+
).toBe(true);
|
|
289
|
+
},
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
test('a fill-colour pick emits only the fillColor patch', () => {
|
|
293
|
+
const wrapper = mountPanel(enabled);
|
|
294
|
+
|
|
295
|
+
emitFrom(wrapper, '[data-test-header-button-fill-color]', {
|
|
296
|
+
id: 'fillColor',
|
|
297
|
+
name: 'fillColor',
|
|
298
|
+
value: 'var(--palette-2)',
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
expect(wrapper.emitted('on-change')).toEqual([
|
|
302
|
+
[{ fillColor: 'var(--palette-2)' }],
|
|
303
|
+
]);
|
|
304
|
+
});
|
|
305
|
+
});
|