@designcrowd/library.brand-page 5.18.4 → 5.18.6-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.
Files changed (148) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-DQLtpdLe.js → CreatedOnBrandCrowd-de-de-CAtPZtwo.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-CvEhlADd.js → CreatedOnBrandCrowd-en-us-Dcx4yFkK.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-pYQeTTC0.js → CreatedOnBrandCrowd-es-es-DpodH0gD.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-CcE-iCEq.js → CreatedOnBrandCrowd-fr-ca-DZbMRJn-.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-CcE-iCEq.js → CreatedOnBrandCrowd-fr-fr-DZbMRJn-.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-OyhWWWyO.js → CreatedOnBrandCrowd-pt-br-C3gBveLk.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-OyhWWWyO.js → CreatedOnBrandCrowd-pt-pt-C3gBveLk.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-DH98FFLS.js → CreatedOnDcom-de-de-DHNyyTV9.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-DK67NN1r.js → CreatedOnDcom-en-us-BjqZ0Jmw.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-9-QU4BU6.js → CreatedOnDcom-es-es-tiNjDJmH.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-U9J9H9VB.js → CreatedOnDcom-fr-ca-COUSI6w1.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-DqdzB1DJ.js → CreatedOnDcom-fr-fr-c3UCfh9A.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-c4UiWtkB.js → CreatedOnDcom-pt-br-U1jbo_g5.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-Do2qJsYp.js → CreatedOnDcom-pt-pt-BOW_n3bo.js} +1 -1
  15. package/lib/src/brand-page/components/contact-form/ContactForm.mixin.d.ts +4 -0
  16. package/lib/src/brand-page/components/contact-form/ContactForm.mixin.js +8 -4
  17. package/lib/src/brand-page/components/image-gallery/ImageGallery.mixin.d.ts +4 -0
  18. package/lib/src/brand-page/components/image-gallery/ImageGallery.mixin.js +4 -0
  19. package/lib/src/brand-page/components/mailing-list/MailingList.mixin.d.ts +4 -0
  20. package/lib/src/brand-page/components/mailing-list/MailingList.mixin.js +4 -1
  21. package/lib/src/brand-page/components/payment/request-payment/RequestPayment.mixin.d.ts +4 -0
  22. package/lib/src/brand-page/components/payment/request-payment/RequestPayment.mixin.js +4 -0
  23. package/lib/src/brand-page/components/payment/support-me/SupportMe.mixin.d.ts +4 -0
  24. package/lib/src/brand-page/components/payment/support-me/SupportMe.mixin.js +4 -1
  25. package/lib/src/brand-page/components/video/Video.mixin.d.ts +4 -0
  26. package/lib/src/brand-page/components/video/Video.mixin.js +4 -0
  27. package/lib/src/brand-page/constants/misc.constants.d.ts +30 -0
  28. package/lib/src/brand-page/constants/misc.constants.js +51 -0
  29. package/lib/src/brand-page/models/content.model.d.ts +2 -0
  30. package/lib/src/brand-page/models/navigation-styles.model.d.ts +7 -0
  31. package/lib/src/brand-page/models/navigation-styles.model.js +25 -2
  32. package/lib/src/brand-page/utils/content-padding.util.d.ts +13 -0
  33. package/lib/src/brand-page/utils/content-padding.util.js +10 -0
  34. package/lib/src/brand-page/utils/index.d.ts +1 -0
  35. package/lib/src/brand-page/utils/index.js +1 -0
  36. package/lib/src/brand-page/utils/migration/migration-16.d.ts +2 -0
  37. package/lib/src/brand-page/utils/migration/migration-16.js +47 -0
  38. package/lib/src/brand-page/utils/migration/migration-17.d.ts +2 -0
  39. package/lib/src/brand-page/utils/migration/migration-17.js +50 -0
  40. package/lib/src/brand-page/utils/migration.util.js +17 -0
  41. package/lib/src/brand-page-maker/components/admin-logo-settings/AdminLogoSettings.mixin.d.ts +2 -0
  42. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +12 -0
  43. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +14 -0
  44. package/lib/src/brand-page-maker/constants/config-settings-constant.js +37 -0
  45. package/lib/src/brand-page-maker/convertors/about-display-style.convertor.js +9 -1
  46. package/lib/src/brand-page-maker/convertors/banner-align-display-style.convertor.js +7 -1
  47. package/lib/src/brand-page-maker/convertors/contact-layout-style.convertor.js +6 -1
  48. package/lib/src/brand-page-maker/models/content-settings.js +36 -2
  49. package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +12 -0
  50. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +1 -9
  51. package/lib/src/{bundled-translations-B5Fkt3qd.js → bundled-translations-Chw0nsFv.js} +9 -9
  52. package/lib/src/{bundled-translations.de-DE-DpdVePM9.js → bundled-translations.de-DE-cqBv7atZ.js} +1 -1
  53. package/lib/src/{bundled-translations.es-ES-BsIcrzZF.js → bundled-translations.es-ES-D9VXftl_.js} +9 -9
  54. package/lib/src/{bundled-translations.fr-CA-G1XCp2Qh.js → bundled-translations.fr-CA-BYstWXcO.js} +1 -1
  55. package/lib/src/{bundled-translations.fr-FR-BdwLXOnB.js → bundled-translations.fr-FR-D1H1hJD4.js} +1 -1
  56. package/lib/src/{bundled-translations.pt-BR-Bvn5ePe7.js → bundled-translations.pt-BR-DqNFuNHX.js} +1 -1
  57. package/lib/src/{bundled-translations.pt-PT-CDtkb2k1.js → bundled-translations.pt-PT-tOic0FfQ.js} +9 -9
  58. package/lib/src/bundles/bundled-translations.de-DE.json +2 -0
  59. package/lib/src/bundles/bundled-translations.es-ES.json +2 -0
  60. package/lib/src/bundles/bundled-translations.fr-CA.json +2 -0
  61. package/lib/src/bundles/bundled-translations.fr-FR.json +2 -0
  62. package/lib/src/bundles/bundled-translations.json +2 -0
  63. package/lib/src/bundles/bundled-translations.pt-BR.json +2 -0
  64. package/lib/src/bundles/bundled-translations.pt-PT.json +2 -0
  65. package/lib/src/css/library.brand-page-brandCrowd.min.css +150 -168
  66. package/lib/src/css/library.brand-page-brandPage.min.css +150 -168
  67. package/lib/src/css/library.brand-page-designCom.min.css +150 -168
  68. package/lib/src/css/library.brand-page-preview.min.css +150 -168
  69. package/lib/src/index.mjs +62 -60
  70. package/lib/src/{lib-BI3J4Mbk.js → lib-Bl7w5LQl.js} +19205 -18991
  71. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +14 -0
  72. package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +14 -0
  73. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +12 -0
  74. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +12 -0
  75. package/lib/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.d.ts +12 -0
  76. package/lib/src/shared/content-settings/components/slider-field/SliderField.mixin.js +9 -0
  77. package/lib/src/shared/content-settings/store/content-settings-store.d.ts +12 -0
  78. package/lib/src/shared/navigation-style-settings/components/navigation-style-settings/NavigationStyleSettings.mixin.js +10 -8
  79. package/lib/src/shared/navigation-style-settings/store/index.js +1 -12
  80. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +16 -0
  81. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +14 -0
  82. package/lib/src/utils/minify-brand-page-metadata.js +11 -1
  83. package/package.json +1 -1
  84. package/src/brand-page/components/about-section/AboutSection.vue +10 -4
  85. package/src/brand-page/components/banner-section/BannerSection.vue +11 -4
  86. package/src/brand-page/components/booking-section/BookingSection.vue +11 -3
  87. package/src/brand-page/components/button/Button.vue +2 -10
  88. package/src/brand-page/components/contact-form/ContactForm.mixin.ts +12 -6
  89. package/src/brand-page/components/contact-form/ContactForm.vue +117 -120
  90. package/src/brand-page/components/image-gallery/ImageGallery.mixin.ts +5 -0
  91. package/src/brand-page/components/image-gallery/ImageGallery.vue +1 -1
  92. package/src/brand-page/components/mailing-list/MailingList.mixin.ts +8 -1
  93. package/src/brand-page/components/mailing-list/MailingList.vue +1 -1
  94. package/src/brand-page/components/map/Map.vue +5 -1
  95. package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.vue +4 -1
  96. package/src/brand-page/components/payment/request-payment/RequestPayment.mixin.ts +5 -0
  97. package/src/brand-page/components/payment/request-payment/RequestPayment.vue +1 -0
  98. package/src/brand-page/components/payment/support-me/SupportMe.mixin.ts +8 -1
  99. package/src/brand-page/components/payment/support-me/SupportMe.vue +1 -0
  100. package/src/brand-page/components/shop/Shop.vue +7 -2
  101. package/src/brand-page/components/social-links/SocialLinks.vue +7 -3
  102. package/src/brand-page/components/text/Text.vue +12 -5
  103. package/src/brand-page/components/text-media-section/TextMediaSection.vue +11 -3
  104. package/src/brand-page/components/video/Video.mixin.ts +5 -0
  105. package/src/brand-page/components/video/Video.vue +1 -1
  106. package/src/brand-page/constants/misc.constants.ts +60 -0
  107. package/src/brand-page/models/content.model.ts +2 -0
  108. package/src/brand-page/models/navigation-styles.model.ts +39 -1
  109. package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +9 -3
  110. package/src/brand-page/utils/content-padding.util.test.ts +29 -0
  111. package/src/brand-page/utils/content-padding.util.ts +13 -0
  112. package/src/brand-page/utils/index.ts +1 -0
  113. package/src/brand-page/utils/migration/migration-16.test.ts +319 -0
  114. package/src/brand-page/utils/migration/migration-16.ts +75 -0
  115. package/src/brand-page/utils/migration/migration-17.ts +73 -0
  116. package/src/brand-page/utils/migration.util.test.ts +145 -0
  117. package/src/brand-page/utils/migration.util.ts +19 -0
  118. package/src/brand-page-maker/components/color-palette/BpColorPalette.vue +9 -7
  119. package/src/brand-page-maker/constants/config-settings-constant.ts +43 -0
  120. package/src/brand-page-maker/convertors/about-display-style.convertor.ts +10 -0
  121. package/src/brand-page-maker/convertors/banner-align-display-style.convertor.ts +8 -0
  122. package/src/brand-page-maker/convertors/contact-layout-style.convertor.test.ts +50 -0
  123. package/src/brand-page-maker/convertors/contact-layout-style.convertor.ts +8 -0
  124. package/src/brand-page-maker/models/content-settings.ts +38 -2
  125. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.test.ts +43 -0
  126. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +0 -14
  127. package/src/bundles/bundled-translations.de-DE.json +2 -0
  128. package/src/bundles/bundled-translations.es-ES.json +2 -0
  129. package/src/bundles/bundled-translations.fr-CA.json +2 -0
  130. package/src/bundles/bundled-translations.fr-FR.json +2 -0
  131. package/src/bundles/bundled-translations.json +2 -0
  132. package/src/bundles/bundled-translations.pt-BR.json +2 -0
  133. package/src/bundles/bundled-translations.pt-PT.json +2 -0
  134. package/src/shared/content-settings/components/color-field/ColorField.vue +10 -5
  135. package/src/shared/content-settings/components/content-settings/i18n/content-settings.de-DE.json +2 -0
  136. package/src/shared/content-settings/components/content-settings/i18n/content-settings.es-ES.json +2 -0
  137. package/src/shared/content-settings/components/content-settings/i18n/content-settings.fr-CA.json +2 -0
  138. package/src/shared/content-settings/components/content-settings/i18n/content-settings.fr-FR.json +2 -0
  139. package/src/shared/content-settings/components/content-settings/i18n/content-settings.json +2 -0
  140. package/src/shared/content-settings/components/content-settings/i18n/content-settings.pt-BR.json +2 -0
  141. package/src/shared/content-settings/components/content-settings/i18n/content-settings.pt-PT.json +2 -0
  142. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.vue +1 -1
  143. package/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.vue +2 -2
  144. package/src/shared/content-settings/components/slider-field/SliderField.mixin.ts +9 -0
  145. package/src/shared/navigation-style-settings/components/navigation-style-settings/NavigationStyleSettings.mixin.ts +17 -11
  146. package/src/shared/navigation-style-settings/components/navigation-style-settings/NavigationStyleSettings.vue +4 -1
  147. package/src/shared/navigation-style-settings/store/index.ts +1 -21
  148. package/src/utils/minify-brand-page-metadata.ts +11 -1
@@ -0,0 +1,319 @@
1
+ import { describe, test, expect } from 'vitest';
2
+ import { migration16 } from './migration-16';
3
+ import type { MigrationData, MigrationOptions } from '../migration.util';
4
+ import { STYLE_THEME_KEYS, NAVIGATION_DISPLAY } from '../../constants';
5
+ import { NavigationDesktop } from '../../models';
6
+
7
+ const createMigrationData = (brandPageModel: any): MigrationData => ({
8
+ brandPageModel,
9
+ useOldLogoRenderSize: false,
10
+ });
11
+
12
+ // The sub nav default is applied when content is first materialised — a template
13
+ // load, the create-draft flow, or the admin maker (isUserDraft === false). An
14
+ // existing user draft already carries its resolved value, so it is skipped.
15
+ const APPLY_DEFAULTS: MigrationOptions = {
16
+ isUserDraft: false,
17
+ isServerRender: false,
18
+ };
19
+ const EXISTING_DRAFT_SKIP: MigrationOptions = {
20
+ isUserDraft: true,
21
+ isServerRender: false,
22
+ };
23
+
24
+ const desktopNav = (displayType: string, overrides: any = {}) => ({
25
+ pageHeaderDesktop: {
26
+ navigation: {
27
+ displayType,
28
+ defaultButtonStyle: {},
29
+ hamburgerMenuStyle: { defaultButtonStyle: {} },
30
+ ...overrides,
31
+ },
32
+ },
33
+ });
34
+
35
+ describe('migration16', () => {
36
+ test('sets color matched sub nav bg for TAB_2 using the top-level styles', async () => {
37
+ const model = desktopNav(NAVIGATION_DISPLAY.TAB_2, {
38
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoTextColor },
39
+ });
40
+
41
+ const result = await migration16(
42
+ createMigrationData(model),
43
+ APPLY_DEFAULTS,
44
+ );
45
+
46
+ expect(
47
+ result.brandPageModel.pageHeaderDesktop.navigation.subNavBackgroundColor,
48
+ ).toBe(STYLE_THEME_KEYS.LogoBackgroundColor);
49
+ });
50
+
51
+ test('sets color matched sub nav bg on hamburgerMenuStyle when unset (vice versa: logo bg text -> logo text sub nav)', async () => {
52
+ const model = desktopNav(NAVIGATION_DISPLAY.HAMBURGER_1, {
53
+ hamburgerMenuStyle: {
54
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoBackgroundColor },
55
+ },
56
+ });
57
+
58
+ const result = await migration16(
59
+ createMigrationData(model),
60
+ APPLY_DEFAULTS,
61
+ );
62
+
63
+ const nav = result.brandPageModel.pageHeaderDesktop.navigation;
64
+
65
+ expect(nav.hamburgerMenuStyle.subNavBackgroundColor).toBe(
66
+ STYLE_THEME_KEYS.LogoTextColor,
67
+ );
68
+ // top-level value untouched for hamburger
69
+ expect(nav.subNavBackgroundColor).toBeUndefined();
70
+ });
71
+
72
+ test('preserves an explicit hex hamburger sub nav even when the text is color matched (admin choice)', async () => {
73
+ const model = desktopNav(NAVIGATION_DISPLAY.HAMBURGER_1, {
74
+ hamburgerMenuStyle: {
75
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoTextColor },
76
+ subNavBackgroundColor: 'rgb(10,20,30)',
77
+ },
78
+ });
79
+
80
+ const result = await migration16(
81
+ createMigrationData(model),
82
+ APPLY_DEFAULTS,
83
+ );
84
+
85
+ expect(
86
+ result.brandPageModel.pageHeaderDesktop.navigation.hamburgerMenuStyle
87
+ .subNavBackgroundColor,
88
+ ).toBe('rgb(10,20,30)');
89
+ });
90
+
91
+ test('color matches an empty-string hamburger sub nav when the text is color matched', async () => {
92
+ const model = desktopNav(NAVIGATION_DISPLAY.HAMBURGER_1, {
93
+ hamburgerMenuStyle: {
94
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoTextColor },
95
+ subNavBackgroundColor: '',
96
+ },
97
+ });
98
+
99
+ const result = await migration16(
100
+ createMigrationData(model),
101
+ APPLY_DEFAULTS,
102
+ );
103
+
104
+ expect(
105
+ result.brandPageModel.pageHeaderDesktop.navigation.hamburgerMenuStyle
106
+ .subNavBackgroundColor,
107
+ ).toBe(STYLE_THEME_KEYS.LogoBackgroundColor);
108
+ });
109
+
110
+ test('color matches a transparent hamburger sub nav when the text is color matched', async () => {
111
+ const model = desktopNav(NAVIGATION_DISPLAY.HAMBURGER_1, {
112
+ hamburgerMenuStyle: {
113
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoTextColor },
114
+ subNavBackgroundColor: 'transparent',
115
+ },
116
+ });
117
+
118
+ const result = await migration16(
119
+ createMigrationData(model),
120
+ APPLY_DEFAULTS,
121
+ );
122
+
123
+ expect(
124
+ result.brandPageModel.pageHeaderDesktop.navigation.hamburgerMenuStyle
125
+ .subNavBackgroundColor,
126
+ ).toBe(STYLE_THEME_KEYS.LogoBackgroundColor);
127
+ });
128
+
129
+ test('leaves an empty hamburger sub nav untouched when text color is not color matched', async () => {
130
+ const model = desktopNav(NAVIGATION_DISPLAY.HAMBURGER_1, {
131
+ hamburgerMenuStyle: {
132
+ defaultButtonStyle: { color: 'rgb(50,50,50)' },
133
+ subNavBackgroundColor: '',
134
+ },
135
+ });
136
+
137
+ const result = await migration16(
138
+ createMigrationData(model),
139
+ APPLY_DEFAULTS,
140
+ );
141
+
142
+ expect(
143
+ result.brandPageModel.pageHeaderDesktop.navigation.hamburgerMenuStyle
144
+ .subNavBackgroundColor,
145
+ ).toBe('');
146
+ });
147
+
148
+ test('leaves hamburger sub nav unset when default text color is not color matched', async () => {
149
+ const model = desktopNav(NAVIGATION_DISPLAY.HAMBURGER_2, {
150
+ hamburgerMenuStyle: {
151
+ defaultButtonStyle: { color: 'rgb(50,50,50)' },
152
+ },
153
+ });
154
+
155
+ const result = await migration16(
156
+ createMigrationData(model),
157
+ APPLY_DEFAULTS,
158
+ );
159
+
160
+ expect(
161
+ result.brandPageModel.pageHeaderDesktop.navigation.hamburgerMenuStyle
162
+ .subNavBackgroundColor,
163
+ ).toBeUndefined();
164
+ });
165
+
166
+ test('keeps an already color matched value', async () => {
167
+ const model = desktopNav(NAVIGATION_DISPLAY.HAMBURGER_1, {
168
+ hamburgerMenuStyle: {
169
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoTextColor },
170
+ subNavBackgroundColor: STYLE_THEME_KEYS.LogoBackgroundColor,
171
+ },
172
+ });
173
+
174
+ const result = await migration16(
175
+ createMigrationData(model),
176
+ APPLY_DEFAULTS,
177
+ );
178
+
179
+ expect(
180
+ result.brandPageModel.pageHeaderDesktop.navigation.hamburgerMenuStyle
181
+ .subNavBackgroundColor,
182
+ ).toBe(STYLE_THEME_KEYS.LogoBackgroundColor);
183
+ });
184
+
185
+ test('applies to all display types: TAB_1 color matches when unset', async () => {
186
+ const model = desktopNav(NAVIGATION_DISPLAY.TAB_1, {
187
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoTextColor },
188
+ });
189
+
190
+ const result = await migration16(
191
+ createMigrationData(model),
192
+ APPLY_DEFAULTS,
193
+ );
194
+
195
+ expect(
196
+ result.brandPageModel.pageHeaderDesktop.navigation.subNavBackgroundColor,
197
+ ).toBe(STYLE_THEME_KEYS.LogoBackgroundColor);
198
+ });
199
+
200
+ test('existing user draft is left untouched, even when the font is matched and the sub nav is unset', async () => {
201
+ const model = desktopNav(NAVIGATION_DISPLAY.HAMBURGER_1, {
202
+ hamburgerMenuStyle: {
203
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoTextColor },
204
+ },
205
+ });
206
+
207
+ const result = await migration16(
208
+ createMigrationData(model),
209
+ EXISTING_DRAFT_SKIP,
210
+ );
211
+
212
+ expect(
213
+ result.brandPageModel.pageHeaderDesktop.navigation.hamburgerMenuStyle
214
+ .subNavBackgroundColor,
215
+ ).toBeUndefined();
216
+ });
217
+
218
+ test('existing user draft keeps a deliberate transparent sub nav (not re-matched on load)', async () => {
219
+ const model = desktopNav(NAVIGATION_DISPLAY.HAMBURGER_1, {
220
+ hamburgerMenuStyle: {
221
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoTextColor },
222
+ subNavBackgroundColor: 'transparent',
223
+ },
224
+ });
225
+
226
+ const result = await migration16(
227
+ createMigrationData(model),
228
+ EXISTING_DRAFT_SKIP,
229
+ );
230
+
231
+ expect(
232
+ result.brandPageModel.pageHeaderDesktop.navigation.hamburgerMenuStyle
233
+ .subNavBackgroundColor,
234
+ ).toBe('transparent');
235
+ });
236
+
237
+ test('migrates mobile navigation as well', async () => {
238
+ const model = {
239
+ pageHeaderMobile: {
240
+ navigation: {
241
+ displayType: NAVIGATION_DISPLAY.HAMBURGER_3,
242
+ hamburgerMenuStyle: {
243
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoTextColor },
244
+ },
245
+ },
246
+ },
247
+ };
248
+
249
+ const result = await migration16(
250
+ createMigrationData(model),
251
+ APPLY_DEFAULTS,
252
+ );
253
+
254
+ expect(
255
+ result.brandPageModel.pageHeaderMobile.navigation.hamburgerMenuStyle
256
+ .subNavBackgroundColor,
257
+ ).toBe(STYLE_THEME_KEYS.LogoBackgroundColor);
258
+ });
259
+
260
+ test('handles missing navigation gracefully', async () => {
261
+ const result = await migration16(createMigrationData({}), APPLY_DEFAULTS);
262
+
263
+ expect(result.brandPageModel).toEqual({});
264
+ });
265
+
266
+ // Materialisation path (create-draft / server render): NavigationDesktop is
267
+ // constructed WITH a theme. The constructor must leave the sub nav unset, else the
268
+ // baked hex would defeat the migration when the default is applied.
269
+ const theme = {
270
+ primaryColor: 'rgb(0,0,0)',
271
+ secondaryColor: 'rgb(255,255,255)',
272
+ };
273
+
274
+ test('materialised hamburger content (colour-matched font, theme passed) colour-matches the sub nav', async () => {
275
+ const navigation = new NavigationDesktop(
276
+ {
277
+ displayType: NAVIGATION_DISPLAY.HAMBURGER_1,
278
+ hamburgerMenuStyle: {
279
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoTextColor },
280
+ },
281
+ },
282
+ theme,
283
+ );
284
+
285
+ expect(navigation.hamburgerMenuStyle.subNavBackgroundColor).toBeUndefined();
286
+
287
+ const result = await migration16(
288
+ createMigrationData({ pageHeaderDesktop: { navigation } }),
289
+ APPLY_DEFAULTS,
290
+ );
291
+
292
+ expect(
293
+ result.brandPageModel.pageHeaderDesktop.navigation.hamburgerMenuStyle
294
+ .subNavBackgroundColor,
295
+ ).toBe(STYLE_THEME_KEYS.LogoBackgroundColor);
296
+ });
297
+
298
+ test('leaves the sub nav untoggled when the default text color is not colour matched', async () => {
299
+ const navigation = new NavigationDesktop(
300
+ {
301
+ displayType: NAVIGATION_DISPLAY.HAMBURGER_1,
302
+ hamburgerMenuStyle: {
303
+ defaultButtonStyle: { color: 'rgb(50,50,50)' },
304
+ },
305
+ },
306
+ theme,
307
+ );
308
+
309
+ const result = await migration16(
310
+ createMigrationData({ pageHeaderDesktop: { navigation } }),
311
+ APPLY_DEFAULTS,
312
+ );
313
+
314
+ expect(
315
+ result.brandPageModel.pageHeaderDesktop.navigation.hamburgerMenuStyle
316
+ .subNavBackgroundColor,
317
+ ).toBeUndefined();
318
+ });
319
+ });
@@ -0,0 +1,75 @@
1
+ import type { MigrationData, MigrationOptions } from '../migration.util';
2
+ import {
3
+ getDefaultSubNavBackgroundColor,
4
+ isUnsetOrTransparentColor,
5
+ } from '../../models';
6
+ import type {
7
+ NavigationDesktop,
8
+ NavigationMobile,
9
+ NavigationMenuStyles,
10
+ } from '../../models';
11
+
12
+ /**
13
+ * Migration 16: default the sub nav background colour to "colour matched" (the opposite
14
+ * logo colour) when the menu font is itself colour matched and the sub nav has no
15
+ * deliberate value. The default is applied ONCE, when content is first materialised — a
16
+ * template load, the create-draft flow, or the admin maker (isUserDraft === false). An
17
+ * existing user draft already carries its resolved value (including a deliberate
18
+ * transparent/explicit choice), so it is left untouched.
19
+ */
20
+
21
+ // Hamburger keeps its styles on hamburgerMenuStyle; every other nav type uses the
22
+ // top-level navigation styles. (This only locates the styles - the rule itself is uniform.)
23
+ const isHamburger = (displayType: string): boolean =>
24
+ displayType.includes('hamburger');
25
+
26
+ const applyDefaultSubNavColorMatch = (
27
+ navigation: NavigationDesktop | NavigationMobile | undefined,
28
+ ): void => {
29
+ if (!navigation?.displayType) {
30
+ return;
31
+ }
32
+
33
+ let menuStyles: NavigationMenuStyles | NavigationDesktop | NavigationMobile;
34
+
35
+ if (isHamburger(navigation.displayType)) {
36
+ navigation.hamburgerMenuStyle =
37
+ navigation.hamburgerMenuStyle ?? ({} as NavigationMenuStyles);
38
+ menuStyles = navigation.hamburgerMenuStyle;
39
+ } else {
40
+ menuStyles = navigation;
41
+ }
42
+
43
+ // Leave a deliberately-set colour (an explicit hex or an already colour-matched key).
44
+ if (!isUnsetOrTransparentColor(menuStyles.subNavBackgroundColor)) {
45
+ return;
46
+ }
47
+
48
+ // Only when the menu font is itself colour matched: take the opposite logo colour
49
+ // (the helper returns undefined when the font is not colour matched).
50
+ const colorMatchedSubNav = getDefaultSubNavBackgroundColor(
51
+ menuStyles.defaultButtonStyle?.color,
52
+ );
53
+
54
+ if (colorMatchedSubNav) {
55
+ menuStyles.subNavBackgroundColor = colorMatchedSubNav;
56
+ }
57
+ };
58
+
59
+ export const migration16 = async (
60
+ migrationResult: MigrationData,
61
+ options: MigrationOptions,
62
+ ): Promise<MigrationData> => {
63
+ // Skip user drafts (isUserDraft === true) — they already have their value. Only
64
+ // templates (isUserDraft === false) get the sub nav default.
65
+ if (options.isUserDraft) {
66
+ return migrationResult;
67
+ }
68
+
69
+ const { brandPageModel } = migrationResult;
70
+
71
+ applyDefaultSubNavColorMatch(brandPageModel.pageHeaderDesktop?.navigation);
72
+ applyDefaultSubNavColorMatch(brandPageModel.pageHeaderMobile?.navigation);
73
+
74
+ return migrationResult;
75
+ };
@@ -0,0 +1,73 @@
1
+ import type { MigrationData } from '../migration.util';
2
+ import type {
3
+ AboutSectionContent,
4
+ BannerSectionContent,
5
+ ContactFormContent,
6
+ } from '../../models';
7
+ import {
8
+ CONTACT_FORM_LAYOUT_DEFAULT_PADDING,
9
+ aboutDisplayStyleDefaultPadding,
10
+ bannerDisplayStyleDefaultPadding,
11
+ } from '../../constants';
12
+
13
+ /**
14
+ * Padding Control backfill (BP-5699 Tier 2, BP-5700 Store): blocks that already
15
+ * had hard-coded top/bottom spacing. Backfill that spacing as the block's explicit
16
+ * topPadding/bottomPadding so existing pages render unchanged once the spacing
17
+ * moves to the Padding Control sliders. Type-scoped and idempotent — only the
18
+ * listed types, and only when the value isn't already set.
19
+ */
20
+ const BLOCK_DEFAULT_PADDING: Partial<Record<string, number>> = {
21
+ text: 12,
22
+ 'images-section': 32,
23
+ 'social-links': 32,
24
+ 'shapes-section': 32,
25
+ 'booking-section': 32,
26
+ 'shop-section': 16,
27
+ };
28
+
29
+ export const migration17 = async (
30
+ migrationResult: MigrationData,
31
+ ): Promise<MigrationData> => {
32
+ const { brandPageModel } = migrationResult;
33
+
34
+ brandPageModel.pages.forEach((page) => {
35
+ page.contents.forEach((content) => {
36
+ // Contact Form, Banner and About previously varied their hard-coded spacing by
37
+ // layout/display style (full-bleed image layouts had no outer padding), so they're
38
+ // keyed by that rather than a single per-type value.
39
+ let value: number | undefined;
40
+
41
+ if (content.type === 'contact-form') {
42
+ value =
43
+ CONTACT_FORM_LAYOUT_DEFAULT_PADDING[
44
+ (content as ContactFormContent).layoutStyle ?? ''
45
+ ];
46
+ } else if (content.type === 'banner-section') {
47
+ value = bannerDisplayStyleDefaultPadding(
48
+ (content as BannerSectionContent).displayStyle,
49
+ );
50
+ } else if (content.type === 'about-section') {
51
+ value = aboutDisplayStyleDefaultPadding(
52
+ (content as AboutSectionContent).displayStyle,
53
+ );
54
+ } else {
55
+ value = BLOCK_DEFAULT_PADDING[content.type];
56
+ }
57
+
58
+ if (value === undefined) {
59
+ return;
60
+ }
61
+
62
+ if (content.topPadding === undefined) {
63
+ content.topPadding = value;
64
+ }
65
+
66
+ if (content.bottomPadding === undefined) {
67
+ content.bottomPadding = value;
68
+ }
69
+ });
70
+ });
71
+
72
+ return migrationResult;
73
+ };
@@ -24,6 +24,12 @@ import {
24
24
  createInitialTestimonialSectionContent,
25
25
  } from '@brand-page-maker/models';
26
26
  import {
27
+ ALIGN_CARD_CENTER_IMAGE,
28
+ ALIGN_LEFT_FULL_IMAGE,
29
+ ALIGN_RIGHT_FULL_IMAGE,
30
+ ALIGN_TOP_FULL_IMAGE,
31
+ ALIGN_BOTTOM_FULL_IMAGE,
32
+ ALIGN_MIDDLE_FULL_IMAGE,
27
33
  DEFAULT_LETTER_SPACING,
28
34
  LINE_HEIGHT_BODY,
29
35
  LINE_HEIGHT_HEADINGS,
@@ -1464,6 +1470,145 @@ describe('migration.util', () => {
1464
1470
  expect(content.layoutStyle).toBe(TEXT_ONLY);
1465
1471
  });
1466
1472
 
1473
+ test('migration17 should backfill contact form padding from its layout style', async () => {
1474
+ const model = await migrateBrandPageModel(getBsModel(), {
1475
+ isUserDraft: false,
1476
+ isServerRender: false,
1477
+ });
1478
+
1479
+ const collapsible = model.brandPageModel.pages[3]
1480
+ .contents[0] as ContactFormContent;
1481
+ const textOnly = model.brandPageModel.pages[3]
1482
+ .contents[1] as ContactFormContent;
1483
+
1484
+ expect(collapsible.topPadding).toBe(0);
1485
+ expect(collapsible.bottomPadding).toBe(0);
1486
+ expect(textOnly.topPadding).toBe(32);
1487
+ expect(textOnly.bottomPadding).toBe(32);
1488
+ });
1489
+
1490
+ test.each([
1491
+ [ALIGN_LEFT_FULL_IMAGE],
1492
+ [ALIGN_RIGHT_FULL_IMAGE],
1493
+ [ALIGN_CARD_CENTER_IMAGE],
1494
+ ])(
1495
+ 'migration17 should backfill zero padding for the %s contact form layout',
1496
+ async (layoutStyle) => {
1497
+ const brandPageModel = getBsModel();
1498
+
1499
+ (
1500
+ brandPageModel.pages[3].contents[1] as ContactFormContent
1501
+ ).layoutStyle = layoutStyle;
1502
+
1503
+ const model = await migrateBrandPageModel(brandPageModel, {
1504
+ isUserDraft: false,
1505
+ isServerRender: false,
1506
+ });
1507
+
1508
+ const content = model.brandPageModel.pages[3]
1509
+ .contents[1] as ContactFormContent;
1510
+
1511
+ expect(content.topPadding).toBe(0);
1512
+ expect(content.bottomPadding).toBe(0);
1513
+ },
1514
+ );
1515
+
1516
+ test('migration17 should not overwrite contact form padding that is already set', async () => {
1517
+ const brandPageModel = getBsModel();
1518
+
1519
+ (brandPageModel.pages[3].contents[1] as ContactFormContent).topPadding =
1520
+ 10;
1521
+
1522
+ const model = await migrateBrandPageModel(brandPageModel, {
1523
+ isUserDraft: false,
1524
+ isServerRender: false,
1525
+ });
1526
+
1527
+ const content = model.brandPageModel.pages[3]
1528
+ .contents[1] as ContactFormContent;
1529
+
1530
+ expect(content.topPadding).toBe(10);
1531
+ expect(content.bottomPadding).toBe(32);
1532
+ });
1533
+
1534
+ test('migration17 should backfill 32 padding for standard banner display styles', async () => {
1535
+ const model = await migrateBrandPageModel(getBsModel(), {
1536
+ isUserDraft: false,
1537
+ isServerRender: false,
1538
+ });
1539
+
1540
+ // pages[0].contents[0] is a 'centered' banner (standard, not full-bleed).
1541
+ const banner = model.brandPageModel.pages[0]
1542
+ .contents[0] as BannerSectionContent;
1543
+
1544
+ expect(banner.topPadding).toBe(32);
1545
+ expect(banner.bottomPadding).toBe(32);
1546
+ });
1547
+
1548
+ test.each([
1549
+ [ALIGN_LEFT_FULL_IMAGE],
1550
+ [ALIGN_RIGHT_FULL_IMAGE],
1551
+ [ALIGN_TOP_FULL_IMAGE],
1552
+ [ALIGN_BOTTOM_FULL_IMAGE],
1553
+ [ALIGN_MIDDLE_FULL_IMAGE],
1554
+ ])(
1555
+ 'migration17 should backfill zero padding for the %s full-bleed banner style',
1556
+ async (displayStyle) => {
1557
+ const brandPageModel = getBsModel();
1558
+
1559
+ (
1560
+ brandPageModel.pages[0].contents[0] as BannerSectionContent
1561
+ ).displayStyle = displayStyle;
1562
+
1563
+ const model = await migrateBrandPageModel(brandPageModel, {
1564
+ isUserDraft: false,
1565
+ isServerRender: false,
1566
+ });
1567
+
1568
+ const banner = model.brandPageModel.pages[0]
1569
+ .contents[0] as BannerSectionContent;
1570
+
1571
+ expect(banner.topPadding).toBe(0);
1572
+ expect(banner.bottomPadding).toBe(0);
1573
+ },
1574
+ );
1575
+
1576
+ test('migration17 should backfill 32 padding for standard about display styles', async () => {
1577
+ const model = await migrateBrandPageModel(getBsModel(), {
1578
+ isUserDraft: false,
1579
+ isServerRender: false,
1580
+ });
1581
+
1582
+ // pages[0].contents[2] is a 'centered' about section (standard, not full-image).
1583
+ const about = model.brandPageModel.pages[0]
1584
+ .contents[2] as AboutSectionContent;
1585
+
1586
+ expect(about.topPadding).toBe(32);
1587
+ expect(about.bottomPadding).toBe(32);
1588
+ });
1589
+
1590
+ test.each([[ALIGN_LEFT_FULL_IMAGE], [ALIGN_RIGHT_FULL_IMAGE]])(
1591
+ 'migration17 should backfill zero padding for the %s full-image about style',
1592
+ async (displayStyle) => {
1593
+ const brandPageModel = getBsModel();
1594
+
1595
+ (
1596
+ brandPageModel.pages[0].contents[2] as AboutSectionContent
1597
+ ).displayStyle = displayStyle;
1598
+
1599
+ const model = await migrateBrandPageModel(brandPageModel, {
1600
+ isUserDraft: false,
1601
+ isServerRender: false,
1602
+ });
1603
+
1604
+ const about = model.brandPageModel.pages[0]
1605
+ .contents[2] as AboutSectionContent;
1606
+
1607
+ expect(about.topPadding).toBe(0);
1608
+ expect(about.bottomPadding).toBe(0);
1609
+ },
1610
+ );
1611
+
1467
1612
  test('migrateNewLogoLogic should migrate logoDraftToken and logoVariation', () => {
1468
1613
  const oldTemplate: BrandPageData = {
1469
1614
  pages: [
@@ -18,6 +18,8 @@ import { migration12_1 } from './migration/migration-12_1';
18
18
  import { migration13 } from './migration/migration-13';
19
19
  import { migration14 } from './migration/migration-14';
20
20
  import { migration15 } from './migration/migration-15';
21
+ import { migration16 } from './migration/migration-16';
22
+ import { migration17 } from './migration/migration-17';
21
23
 
22
24
  export interface MigrationData {
23
25
  brandPageModel: BrandPageModel;
@@ -141,6 +143,23 @@ const MIGRATION_TABLE = [
141
143
  migrateFn: migration15,
142
144
  description: 'Adding default mediaOverlayStyle to background styles',
143
145
  },
146
+ {
147
+ version: 16,
148
+ migrateFn: migration16,
149
+ runAlways: true,
150
+ description:
151
+ 'Default sub nav background color to color match on for TAB_2 and hamburger navs depending on font color only on first load flow',
152
+ },
153
+ {
154
+ version: 17,
155
+ migrateFn: migration17,
156
+ // runAlways: re-apply on every load (ticket stack) so already-migrated pages
157
+ // pick up newly-added block defaults (e.g. Store) without a new migration.
158
+ // Idempotent — only backfills when topPadding/bottomPadding aren't already set.
159
+ runAlways: true,
160
+ description:
161
+ 'Padding Control backfill (BP-5699 Tier 2 + BP-5700 Store): backfill existing top/bottom spacing as explicit padding for text (12px), images/social links/shapes/booking (32px), store (16px), contact form (per layout), banner and about (per display style)',
162
+ },
144
163
  ] as MigrationTableItem[];
145
164
 
146
165
  export const GetLatestModelVersion = () =>
@@ -27,8 +27,9 @@
27
27
  </div>
28
28
  <button
29
29
  v-else
30
- :style="generateStyle(color)"
30
+ :style="color === 'transparent' ? undefined : generateStyle(color)"
31
31
  class="c-color tw-bg-transparent tw-block tw-w-full tw-h-full tw-border-none tw-rounded tw-cursor-pointer tw-transition-all tw-duration-300 tw-ease-out"
32
+ :class="{ 'c-transparent': color === 'transparent' }"
32
33
  @click="onClick"
33
34
  />
34
35
  </div>
@@ -197,12 +198,13 @@ const clickOutside = (event: MouseEvent): void => {
197
198
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.3);
198
199
  transform: scale(1.025);
199
200
  }
200
- .c-color {
201
- box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
202
- }
203
- .c-color:hover {
204
- box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.3);
205
- transform: scale(1.025);
201
+ .c-transparent {
202
+ background: linear-gradient(
203
+ 135deg,
204
+ transparent calc(50% - 1.5px),
205
+ red 50%,
206
+ transparent calc(50% + 1.5px)
207
+ );
206
208
  }
207
209
  .maker-color-palette__popup {
208
210
  position: absolute;