@designcrowd/library.brand-page 5.18.1 → 5.18.2-ast-padding-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-DFpjIAUv.js → CreatedOnBrandCrowd-de-de-GFYl8DtL.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-BapIT2xO.js → CreatedOnBrandCrowd-en-us-D5CcXf2I.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-DaSf3Dt6.js → CreatedOnBrandCrowd-es-es-Cr7J3ooe.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-Cvgei7yn.js → CreatedOnBrandCrowd-fr-ca-BC1NFFIW.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-Cvgei7yn.js → CreatedOnBrandCrowd-fr-fr-BC1NFFIW.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-Duxd2NmR.js → CreatedOnBrandCrowd-pt-br-BdiCcpwi.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-Duxd2NmR.js → CreatedOnBrandCrowd-pt-pt-BdiCcpwi.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-DXwNbruv.js → CreatedOnDcom-de-de-d4TmC_lo.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-CkNGwN-p.js → CreatedOnDcom-en-us-DlvLEF-R.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-Csyf5yhI.js → CreatedOnDcom-es-es-BulpVp3k.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-GOIUmIdy.js → CreatedOnDcom-fr-ca-M4xqunZe.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-DF63cL2a.js → CreatedOnDcom-fr-fr-BFqUXgzb.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-Ct6FWj7D.js → CreatedOnDcom-pt-br-CUVcVkV2.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-D22yb8Ci.js → CreatedOnDcom-pt-pt-Dc4CZ4xW.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/page/Page.mixin.d.ts +1 -1
  22. package/lib/src/brand-page/components/page/PageComponent.mixin.d.ts +1 -1
  23. package/lib/src/brand-page/components/payment/request-payment/RequestPayment.mixin.d.ts +4 -0
  24. package/lib/src/brand-page/components/payment/request-payment/RequestPayment.mixin.js +4 -0
  25. package/lib/src/brand-page/components/payment/support-me/SupportMe.mixin.d.ts +4 -0
  26. package/lib/src/brand-page/components/payment/support-me/SupportMe.mixin.js +4 -1
  27. package/lib/src/brand-page/components/video/Video.mixin.d.ts +4 -0
  28. package/lib/src/brand-page/components/video/Video.mixin.js +4 -0
  29. package/lib/src/brand-page/constants/misc.constants.d.ts +17 -0
  30. package/lib/src/brand-page/constants/misc.constants.js +25 -0
  31. package/lib/src/brand-page/models/content.model.d.ts +2 -0
  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 +37 -0
  38. package/lib/src/brand-page/utils/migration.util.js +6 -0
  39. package/lib/src/brand-page-maker/components/admin-logo-settings/AdminLogoSettings.mixin.d.ts +2 -0
  40. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +12 -0
  41. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +15 -1
  42. package/lib/src/brand-page-maker/constants/config-settings-constant.js +34 -0
  43. package/lib/src/brand-page-maker/convertors/banner-align-display-style.convertor.js +56 -2
  44. package/lib/src/brand-page-maker/convertors/contact-layout-style.convertor.js +6 -1
  45. package/lib/src/brand-page-maker/models/content-settings.js +30 -2
  46. package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +12 -0
  47. package/lib/src/{bundled-translations-CnareLWC.js → bundled-translations-DYd5oFZv.js} +1 -1
  48. package/lib/src/bundles/bundled-translations.json +2 -0
  49. package/lib/src/css/library.brand-page-brandCrowd.min.css +152 -140
  50. package/lib/src/css/library.brand-page-brandPage.min.css +153 -140
  51. package/lib/src/css/library.brand-page-designCom.min.css +152 -140
  52. package/lib/src/css/library.brand-page-preview.min.css +152 -140
  53. package/lib/src/index.mjs +1 -1
  54. package/lib/src/{lib-_iwoSD1Z.js → lib-CRihSxY0.js} +20195 -19572
  55. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +14 -0
  56. package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +14 -0
  57. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +12 -0
  58. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +12 -0
  59. package/lib/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.d.ts +12 -0
  60. package/lib/src/shared/content-settings/components/slider-field/SliderField.mixin.js +9 -0
  61. package/lib/src/shared/content-settings/store/content-settings-store.d.ts +12 -0
  62. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +16 -0
  63. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +14 -0
  64. package/lib/src/utils/minify-brand-page-metadata.js +11 -1
  65. package/package.json +2 -2
  66. package/src/brand-page/components/banner-section/BannerSection.vue +360 -12
  67. package/src/brand-page/components/banner-section/BannerSection.vue.test.ts +201 -0
  68. package/src/brand-page/components/booking-section/BookingSection.vue +11 -3
  69. package/src/brand-page/components/contact-form/ContactForm.mixin.ts +12 -6
  70. package/src/brand-page/components/contact-form/ContactForm.vue +115 -118
  71. package/src/brand-page/components/image-gallery/ImageGallery.mixin.ts +5 -0
  72. package/src/brand-page/components/image-gallery/ImageGallery.vue +1 -1
  73. package/src/brand-page/components/mailing-list/MailingList.mixin.ts +8 -1
  74. package/src/brand-page/components/mailing-list/MailingList.vue +1 -1
  75. package/src/brand-page/components/map/Map.vue +5 -1
  76. package/src/brand-page/components/payment/request-payment/RequestPayment.mixin.ts +5 -0
  77. package/src/brand-page/components/payment/request-payment/RequestPayment.vue +1 -0
  78. package/src/brand-page/components/payment/support-me/SupportMe.mixin.ts +8 -1
  79. package/src/brand-page/components/payment/support-me/SupportMe.vue +1 -0
  80. package/src/brand-page/components/placeholder/Placeholder.vue +39 -100
  81. package/src/brand-page/components/social-links/SocialLinks.vue +7 -3
  82. package/src/brand-page/components/text/Text.vue +12 -5
  83. package/src/brand-page/components/text-media-section/TextMediaSection.vue +11 -3
  84. package/src/brand-page/components/video/Video.mixin.ts +5 -0
  85. package/src/brand-page/components/video/Video.vue +1 -1
  86. package/src/brand-page/constants/misc.constants.ts +26 -0
  87. package/src/brand-page/models/content.model.ts +2 -0
  88. package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +7 -3
  89. package/src/brand-page/utils/content-padding.util.test.ts +29 -0
  90. package/src/brand-page/utils/content-padding.util.ts +13 -0
  91. package/src/brand-page/utils/index.ts +1 -0
  92. package/src/brand-page/utils/migration/migration-16.ts +51 -0
  93. package/src/brand-page/utils/migration.util.test.ts +64 -0
  94. package/src/brand-page/utils/migration.util.ts +7 -0
  95. package/src/brand-page-maker/constants/config-settings-constant.ts +40 -0
  96. package/src/brand-page-maker/convertors/banner-align-display-style.convertor.ts +61 -1
  97. package/src/brand-page-maker/convertors/contact-layout-style.convertor.test.ts +50 -0
  98. package/src/brand-page-maker/convertors/contact-layout-style.convertor.ts +8 -0
  99. package/src/brand-page-maker/models/content-settings.ts +32 -2
  100. package/src/bundles/bundled-translations.json +2 -0
  101. package/src/shared/content-settings/components/content-settings/i18n/content-settings.json +2 -0
  102. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.vue +1 -1
  103. package/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.vue +2 -2
  104. package/src/shared/content-settings/components/slider-field/SliderField.mixin.ts +9 -0
  105. package/src/utils/minify-brand-page-metadata.ts +11 -1
  106. package/lib/src/brand-page/components/banner-section/BannerSection.mixin.d.ts +0 -103
  107. package/lib/src/brand-page/components/banner-section/BannerSection.mixin.js +0 -178
  108. package/src/brand-page/components/banner-section/BannerSection.mixin.ts +0 -213
@@ -2,7 +2,7 @@
2
2
  <div>
3
3
  <div
4
4
  v-if="!context.isBrandSite"
5
- :style="containerStyle"
5
+ :style="[containerStyle, contentPaddingStyle]"
6
6
  :class="{
7
7
  [containerClasses]: true,
8
8
  }"
@@ -15,9 +15,9 @@
15
15
  data-test-tag="text-content-text"
16
16
  />
17
17
  </div>
18
- <div v-else :style="containerStyle">
18
+ <div v-else :style="[containerStyle, contentPaddingStyle]">
19
19
  <div
20
- class="c-text-container tw-py-3 tw-px-6 grid-container tw-gap-4"
20
+ class="c-text-container tw-px-6 grid-container tw-gap-4"
21
21
  :class="{
22
22
  [containerClasses]: true,
23
23
  [gridClass]: context.isBrandSite,
@@ -41,7 +41,10 @@
41
41
  import TextWrapper from '../text-wrapper/TextWrapper.vue';
42
42
  import { computed } from 'vue';
43
43
  import type { TextViewModel } from '@brand-page/view-models';
44
- import { getRenderText } from '@brand-page/utils';
44
+ import {
45
+ getRenderText,
46
+ contentPaddingStyle as toContentPaddingStyle,
47
+ } from '@brand-page/utils';
45
48
  import type { BrandPageContext } from '@brand-page/models';
46
49
 
47
50
  interface Props {
@@ -51,6 +54,10 @@ interface Props {
51
54
 
52
55
  const props = defineProps<Props>();
53
56
 
57
+ const contentPaddingStyle = computed(() =>
58
+ toContentPaddingStyle(props.viewModel),
59
+ );
60
+
54
61
  const containerStyle = computed<any>(() => {
55
62
  const containerStyles = {} as BrandPageCssStyle;
56
63
 
@@ -83,7 +90,7 @@ const containerClasses = computed(() => {
83
90
  return '';
84
91
  }
85
92
 
86
- return 'tw-py-3 tw-px-6';
93
+ return 'tw-px-6';
87
94
  });
88
95
 
89
96
  const renderText = computed(() => {
@@ -1,12 +1,12 @@
1
1
  <template>
2
2
  <section data-component="text-media-section">
3
3
  <div
4
- class="tw-relative tw-w-full tw-box-border tw-py-8"
4
+ class="tw-relative tw-w-full tw-box-border"
5
5
  :class="{
6
6
  'tw-pl-0 tw-pr-0': hasNoPadding || !hasBrandSitePadding,
7
7
  'tw-rounded-md': hasRoundedCorners,
8
8
  }"
9
- :style="viewModel.backgroundStyle"
9
+ :style="[viewModel.backgroundStyle, contentPaddingStyle]"
10
10
  >
11
11
  <div
12
12
  :class="[
@@ -148,7 +148,11 @@ import {
148
148
  MEDIA_ONLY,
149
149
  } from '../../constants';
150
150
  import { isHexTransparent } from '@brand-page/utils/color.util';
151
- import { escapeContentText, getRenderText } from '@brand-page/utils';
151
+ import {
152
+ escapeContentText,
153
+ getRenderText,
154
+ contentPaddingStyle as toContentPaddingStyle,
155
+ } from '@brand-page/utils';
152
156
 
153
157
  const props = defineProps({
154
158
  viewModel: {
@@ -171,6 +175,10 @@ const props = defineProps({
171
175
  },
172
176
  });
173
177
 
178
+ const contentPaddingStyle = computed(() =>
179
+ toContentPaddingStyle(props.viewModel),
180
+ );
181
+
174
182
  const items = computed(() => props.viewModel.items);
175
183
 
176
184
  const shouldDisplayText = computed(
@@ -1,6 +1,7 @@
1
1
  import { defineComponent } from 'vue';
2
2
  import type { PropType } from 'vue';
3
3
  import type { VideoViewModel } from '@brand-page/view-models';
4
+ import { contentPaddingStyle as toContentPaddingStyle } from '@brand-page/utils';
4
5
  import ComponentCommonMixin from '../mixins/component-common.mixin';
5
6
  import {
6
7
  DISPLAY_STYLE_PREVIEW,
@@ -22,6 +23,10 @@ export default defineComponent({
22
23
  title: 'Video',
23
24
  }),
24
25
  computed: {
26
+ contentPaddingStyle(): { paddingTop: string; paddingBottom: string } {
27
+ return toContentPaddingStyle(this.viewModel);
28
+ },
29
+
25
30
  arrowStyle(): BrandPageCssStyle {
26
31
  return {
27
32
  color: this.viewModel.buttonStyles.style?.color,
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="c-video">
2
+ <div class="c-video" :style="contentPaddingStyle">
3
3
  <div v-if="viewModel.videoUrl">
4
4
  <!-- Start -->
5
5
  <div
@@ -17,6 +17,12 @@ export const ALIGN_LEFT_BIG_IMAGE = 'left-aligned-big-image';
17
17
  export const ALIGN_CARD_CENTER_IMAGE = 'center-card-aligned-image';
18
18
  export const ALIGN_CARD_CENTER = 'center-card-aligned';
19
19
  export const ALIGN_CARD_LEFT_IMAGE = 'left-card-aligned-image';
20
+ export const ALIGN_TOP_CARD_IMAGE = 'top-card-aligned-image';
21
+ export const ALIGN_BOTTOM_CARD_IMAGE = 'bottom-card-aligned-image';
22
+ export const ALIGN_MIDDLE_CARD_IMAGE = 'middle-card-aligned-image';
23
+ export const ALIGN_TOP_FULL_IMAGE = 'top-aligned-full-image';
24
+ export const ALIGN_BOTTOM_FULL_IMAGE = 'bottom-aligned-full-image';
25
+ export const ALIGN_MIDDLE_FULL_IMAGE = 'middle-aligned-full-image';
20
26
 
21
27
  export const MAX_IMAGES_SECTION_ALIGNMENT_ITEMS_LENGTH = 3;
22
28
 
@@ -32,6 +38,26 @@ export const ALIGN_LEFT_FULL_IMAGE = 'left-aligned-full-image';
32
38
  export const ALIGN_RIGHT_FULL_IMAGE = 'right-aligned-full-image';
33
39
  export const ALTERNATE_IMAGE = 'alternate-image';
34
40
 
41
+ /**
42
+ * Padding Control (BP-5699): the top/bottom padding each Contact Form layout
43
+ * had hard-coded before the sliders existed. Single source for new-block
44
+ * defaults (content-settings) and the migration backfill for existing pages.
45
+ * The image layouts (full-image divided and card-over-image) are 0: their
46
+ * padding sits on the section root and reveals the background colour around
47
+ * the image block (same as the banner), while the spacing inside/over the
48
+ * image stays fixed — existing pages rendered the image flush to the section
49
+ * edges.
50
+ */
51
+ export const CONTACT_FORM_LAYOUT_DEFAULT_PADDING: Record<string, number> = {
52
+ '': 0, // collapsible (default on Brand Page / Brand Contact)
53
+ [ALIGN_CENTERED]: 24,
54
+ [ALIGN_CARD_CENTER]: 32,
55
+ [ALIGN_CARD_CENTER_IMAGE]: 0,
56
+ [TEXT_ONLY]: 32,
57
+ [ALIGN_LEFT_FULL_IMAGE]: 0,
58
+ [ALIGN_RIGHT_FULL_IMAGE]: 0,
59
+ };
60
+
35
61
  export const BIG_IMAGE = 'big-image';
36
62
  export const MEDIA_ONLY = 'media-only';
37
63
  export const COLUMNS = 'columns';
@@ -51,6 +51,8 @@ export interface Content {
51
51
  id: string;
52
52
  type: ContentType;
53
53
  personalisationRole?: PersonalisationRole;
54
+ topPadding?: number;
55
+ bottomPadding?: number;
54
56
 
55
57
  // Overrides for personalisation
56
58
  imageUrlOverride?: string;
@@ -454,7 +454,7 @@ exports[`migration.util > Banner Contents - text Align and Font Family should no
454
454
  },
455
455
  },
456
456
  },
457
- "v": 15,
457
+ "v": 16,
458
458
  },
459
459
  "useOldLogoRenderSize": true,
460
460
  }
@@ -751,6 +751,7 @@ exports[`migration.util > migrateBrandPageModel should return brand page model w
751
751
  {
752
752
  "contents": [
753
753
  {
754
+ "bottomPadding": 12,
754
755
  "id": "bec5aca6-e3c5-4d4c-93fa-2894816a8040",
755
756
  "text": "",
756
757
  "textColor": {},
@@ -774,6 +775,7 @@ exports[`migration.util > migrateBrandPageModel should return brand page model w
774
775
  "textAlign": "center",
775
776
  "textStyleType": "paragraphs",
776
777
  },
778
+ "topPadding": 12,
777
779
  "type": "text",
778
780
  },
779
781
  ],
@@ -897,7 +899,7 @@ exports[`migration.util > migrateBrandPageModel should return brand page model w
897
899
  },
898
900
  },
899
901
  },
900
- "v": 15,
902
+ "v": 16,
901
903
  },
902
904
  "useOldLogoRenderSize": true,
903
905
  }
@@ -1194,6 +1196,7 @@ exports[`migration.util > migrateBrandPageModel should return brand page model w
1194
1196
  {
1195
1197
  "contents": [
1196
1198
  {
1199
+ "bottomPadding": 12,
1197
1200
  "id": "bec5aca6-e3c5-4d4c-93fa-2894816a8040",
1198
1201
  "text": "",
1199
1202
  "textColor": {},
@@ -1224,6 +1227,7 @@ exports[`migration.util > migrateBrandPageModel should return brand page model w
1224
1227
  "textAlign": "center",
1225
1228
  "textStyleType": "paragraphs",
1226
1229
  },
1230
+ "topPadding": 12,
1227
1231
  "type": "text",
1228
1232
  },
1229
1233
  ],
@@ -1347,7 +1351,7 @@ exports[`migration.util > migrateBrandPageModel should return brand page model w
1347
1351
  },
1348
1352
  },
1349
1353
  },
1350
- "v": 15,
1354
+ "v": 16,
1351
1355
  },
1352
1356
  "useOldLogoRenderSize": true,
1353
1357
  }
@@ -0,0 +1,29 @@
1
+ import { describe, test, expect } from 'vitest';
2
+ import { contentPaddingStyle } from './content-padding.util';
3
+
4
+ describe('content-padding.util', () => {
5
+ test('contentPaddingStyle should map stored padding to px values', () => {
6
+ expect(contentPaddingStyle({ topPadding: 53, bottomPadding: 24 })).toEqual({
7
+ paddingTop: '53px',
8
+ paddingBottom: '24px',
9
+ });
10
+ });
11
+
12
+ test('contentPaddingStyle should default missing values to 0 for content created before the field existed', () => {
13
+ expect(contentPaddingStyle({})).toEqual({
14
+ paddingTop: '0px',
15
+ paddingBottom: '0px',
16
+ });
17
+ expect(contentPaddingStyle({ topPadding: 16 })).toEqual({
18
+ paddingTop: '16px',
19
+ paddingBottom: '0px',
20
+ });
21
+ });
22
+
23
+ test('contentPaddingStyle should keep an explicit 0 rather than treating it as unset', () => {
24
+ expect(contentPaddingStyle({ topPadding: 0, bottomPadding: 0 })).toEqual({
25
+ paddingTop: '0px',
26
+ paddingBottom: '0px',
27
+ });
28
+ });
29
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Block top/bottom padding (BP-5687) as a bindable CSS style object. Defaults
3
+ * live in the data — `content-settings` for new content, a migration for
4
+ * existing — so this just reads the stored value (`?? 0` covers content from
5
+ * before the field existed).
6
+ */
7
+ export const contentPaddingStyle = (content: {
8
+ topPadding?: number;
9
+ bottomPadding?: number;
10
+ }): { paddingTop: string; paddingBottom: string } => ({
11
+ paddingTop: `${content.topPadding ?? 0}px`,
12
+ paddingBottom: `${content.bottomPadding ?? 0}px`,
13
+ });
@@ -1,4 +1,5 @@
1
1
  export * from './style.util';
2
+ export * from './content-padding.util';
2
3
  export * from './view-model.util';
3
4
  export * from './text.util';
4
5
  export * from './model.util';
@@ -0,0 +1,51 @@
1
+ import type { MigrationData } from '../migration.util';
2
+ import type { ContactFormContent } from '../../models';
3
+ import { CONTACT_FORM_LAYOUT_DEFAULT_PADDING } from '../../constants';
4
+
5
+ /**
6
+ * Tier 2 Padding Control (BP-5699): blocks that already had hard-coded
7
+ * top/bottom spacing. Backfill that spacing as the block's explicit
8
+ * topPadding/bottomPadding so existing pages render unchanged once the spacing
9
+ * moves to the Padding Control sliders. Type-scoped and idempotent — only the
10
+ * listed types, and only when the value isn't already set.
11
+ */
12
+ const TIER2_DEFAULT_PADDING: Partial<Record<string, number>> = {
13
+ text: 12,
14
+ 'images-section': 32,
15
+ 'social-links': 32,
16
+ 'shapes-section': 32,
17
+ 'booking-section': 32,
18
+ };
19
+
20
+ export const migration16 = async (
21
+ migrationResult: MigrationData,
22
+ ): Promise<MigrationData> => {
23
+ const { brandPageModel } = migrationResult;
24
+
25
+ brandPageModel.pages.forEach((page) => {
26
+ page.contents.forEach((content) => {
27
+ // Contact Form's previous hard-coded spacing varies by layout, so it's
28
+ // keyed by layoutStyle rather than a single per-type value.
29
+ const value =
30
+ content.type === 'contact-form'
31
+ ? CONTACT_FORM_LAYOUT_DEFAULT_PADDING[
32
+ (content as ContactFormContent).layoutStyle ?? ''
33
+ ]
34
+ : TIER2_DEFAULT_PADDING[content.type];
35
+
36
+ if (value === undefined) {
37
+ return;
38
+ }
39
+
40
+ if (content.topPadding === undefined) {
41
+ content.topPadding = value;
42
+ }
43
+
44
+ if (content.bottomPadding === undefined) {
45
+ content.bottomPadding = value;
46
+ }
47
+ });
48
+ });
49
+
50
+ return migrationResult;
51
+ };
@@ -24,6 +24,9 @@ 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,
27
30
  DEFAULT_LETTER_SPACING,
28
31
  LINE_HEIGHT_BODY,
29
32
  LINE_HEIGHT_HEADINGS,
@@ -1464,6 +1467,67 @@ describe('migration.util', () => {
1464
1467
  expect(content.layoutStyle).toBe(TEXT_ONLY);
1465
1468
  });
1466
1469
 
1470
+ test('migration16 should backfill contact form padding from its layout style', async () => {
1471
+ const model = await migrateBrandPageModel(getBsModel(), {
1472
+ isUserDraft: false,
1473
+ isServerRender: false,
1474
+ });
1475
+
1476
+ const collapsible = model.brandPageModel.pages[3]
1477
+ .contents[0] as ContactFormContent;
1478
+ const textOnly = model.brandPageModel.pages[3]
1479
+ .contents[1] as ContactFormContent;
1480
+
1481
+ expect(collapsible.topPadding).toBe(0);
1482
+ expect(collapsible.bottomPadding).toBe(0);
1483
+ expect(textOnly.topPadding).toBe(32);
1484
+ expect(textOnly.bottomPadding).toBe(32);
1485
+ });
1486
+
1487
+ test.each([
1488
+ [ALIGN_LEFT_FULL_IMAGE],
1489
+ [ALIGN_RIGHT_FULL_IMAGE],
1490
+ [ALIGN_CARD_CENTER_IMAGE],
1491
+ ])(
1492
+ 'migration16 should backfill zero padding for the %s contact form layout',
1493
+ async (layoutStyle) => {
1494
+ const brandPageModel = getBsModel();
1495
+
1496
+ (
1497
+ brandPageModel.pages[3].contents[1] as ContactFormContent
1498
+ ).layoutStyle = layoutStyle;
1499
+
1500
+ const model = await migrateBrandPageModel(brandPageModel, {
1501
+ isUserDraft: false,
1502
+ isServerRender: false,
1503
+ });
1504
+
1505
+ const content = model.brandPageModel.pages[3]
1506
+ .contents[1] as ContactFormContent;
1507
+
1508
+ expect(content.topPadding).toBe(0);
1509
+ expect(content.bottomPadding).toBe(0);
1510
+ },
1511
+ );
1512
+
1513
+ test('migration16 should not overwrite contact form padding that is already set', async () => {
1514
+ const brandPageModel = getBsModel();
1515
+
1516
+ (brandPageModel.pages[3].contents[1] as ContactFormContent).topPadding =
1517
+ 10;
1518
+
1519
+ const model = await migrateBrandPageModel(brandPageModel, {
1520
+ isUserDraft: false,
1521
+ isServerRender: false,
1522
+ });
1523
+
1524
+ const content = model.brandPageModel.pages[3]
1525
+ .contents[1] as ContactFormContent;
1526
+
1527
+ expect(content.topPadding).toBe(10);
1528
+ expect(content.bottomPadding).toBe(32);
1529
+ });
1530
+
1467
1531
  test('migrateNewLogoLogic should migrate logoDraftToken and logoVariation', () => {
1468
1532
  const oldTemplate: BrandPageData = {
1469
1533
  pages: [
@@ -18,6 +18,7 @@ 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';
21
22
 
22
23
  export interface MigrationData {
23
24
  brandPageModel: BrandPageModel;
@@ -141,6 +142,12 @@ const MIGRATION_TABLE = [
141
142
  migrateFn: migration15,
142
143
  description: 'Adding default mediaOverlayStyle to background styles',
143
144
  },
145
+ {
146
+ version: 16,
147
+ migrateFn: migration16,
148
+ description:
149
+ 'Tier 2 Padding Control (BP-5699): backfill existing top/bottom spacing as explicit padding for text (12px), images, social links, shapes and booking (32px), and contact form (per layout)',
150
+ },
144
151
  ] as MigrationTableItem[];
145
152
 
146
153
  export const GetLatestModelVersion = () =>
@@ -201,6 +201,34 @@ const SETTINGS_STYLE_FIElD_GROUPS = (): ContentSettingsFieldGroup[] => {
201
201
  ];
202
202
  };
203
203
 
204
+ /**
205
+ * Shared Top/Bottom padding sliders (BP-5687). Reuses the same `slider` control
206
+ * as the Divider block (track + editable number box), range 0–100, continuous.
207
+ * Spread into a block's `fields` to expose the Padding Control on that block.
208
+ * Pass the Style field-group id for blocks that use grouped panels.
209
+ */
210
+ const PADDING_CONTROL_FIELDS = (
211
+ groupId?: string,
212
+ ): {
213
+ topPadding: ContentSettingsField<Content>;
214
+ bottomPadding: ContentSettingsField<Content>;
215
+ } => ({
216
+ topPadding: {
217
+ type: 'slider',
218
+ title: contentSettingsTr(`topPaddingTitle`),
219
+ minLength: 0,
220
+ maxLength: 100,
221
+ groupId,
222
+ },
223
+ bottomPadding: {
224
+ type: 'slider',
225
+ title: contentSettingsTr(`bottomPaddingTitle`),
226
+ minLength: 0,
227
+ maxLength: 100,
228
+ groupId,
229
+ },
230
+ });
231
+
204
232
  const DEFAULT_TITLE_STYLE_SETTINGS_FILED =
205
233
  (): ContentSettingsField<Content> => ({
206
234
  type: 'text-style',
@@ -1097,6 +1125,7 @@ export const IMAGE_GALLERY_CONTENT_SETTINGS_CONFIG = (): ContentSettingsConfig<
1097
1125
  defaultValue: false,
1098
1126
  visibilityAssessor: imageGalleryFieldVisibilityAssessor,
1099
1127
  },
1128
+ ...PADDING_CONTROL_FIELDS(),
1100
1129
  },
1101
1130
  isEnabled: true,
1102
1131
  isLocked: (features) => !features.canAddImageContents,
@@ -1136,6 +1165,7 @@ export const VIDEO_CONTENT_SETTINGS_CONFIG = (): ContentSettingsConfig<
1136
1165
  label: contentSettingsTr(`muteLabel`),
1137
1166
  valueConvertor: videoContentStateConvertor,
1138
1167
  },
1168
+ ...PADDING_CONTROL_FIELDS(),
1139
1169
  },
1140
1170
  isEnabled: true,
1141
1171
  isLocked: (features) => !features.canAddVideoContents,
@@ -1287,6 +1317,7 @@ export const TEXT_CONTENT_SETTINGS_CONFIG = (): ContentSettingsConfig<
1287
1317
  valueConvertor: backgroundColorConvertor,
1288
1318
  groupId: SETTINGS_STYLE_FIELD_GROUP_IDS.style,
1289
1319
  },
1320
+ ...PADDING_CONTROL_FIELDS(SETTINGS_STYLE_FIELD_GROUP_IDS.style),
1290
1321
  },
1291
1322
  isEnabled: true,
1292
1323
  isLocked: false,
@@ -1736,6 +1767,7 @@ export const MAP_CONTENT_SETTINGS_CONFIG =
1736
1767
  type: 'address',
1737
1768
  title: contentSettingsTr(`addressTitle`),
1738
1769
  },
1770
+ ...PADDING_CONTROL_FIELDS(),
1739
1771
  },
1740
1772
  isEnabled: true,
1741
1773
  isLocked: (features) => !features.canAddMapContents,
@@ -1787,6 +1819,7 @@ export const MAILING_LIST_CONTENT_SETTINGS_CONFIG =
1787
1819
  },
1788
1820
  titleStyle: DEFAULT_TITLE_STYLE_SETTINGS_FILED_WITH_TITLE(),
1789
1821
  buttonTextStyle: DEFAULT_BUTTON_TEXT_STYLE_FIELD(),
1822
+ ...PADDING_CONTROL_FIELDS(SETTINGS_STYLE_FIELD_GROUP_IDS.style),
1790
1823
  },
1791
1824
  isEnabled: true,
1792
1825
  isLocked: false,
@@ -1971,6 +2004,7 @@ export const REQUEST_PAYMENT_CONTENT_SETTINGS_CONFIG =
1971
2004
  defaultValue: {},
1972
2005
  label: contentSettingsTr(`buttonTextStyleLabel`),
1973
2006
  },
2007
+ ...PADDING_CONTROL_FIELDS(REQUEST_PAYMENT_GROUPS.style),
1974
2008
  },
1975
2009
  isEnabled: (features) => features.canAddRequestPaymentContents,
1976
2010
  isLocked: false,
@@ -2038,6 +2072,7 @@ export const SUPPORT_ME_CONTENT_SETTINGS_CONFIG =
2038
2072
  label: contentSettingsTr(`itemStyleLabel`),
2039
2073
  },
2040
2074
  buttonTextStyle: DEFAULT_BUTTON_TEXT_STYLE_FIELD(),
2075
+ ...PADDING_CONTROL_FIELDS(SETTINGS_STYLE_FIELD_GROUP_IDS.style),
2041
2076
  },
2042
2077
  isEnabled: (features) => features.canAddRequestPaymentContents,
2043
2078
  isLocked: false,
@@ -2193,6 +2228,7 @@ export const CONTACT_FORM_SETTINGS_CONFIG =
2193
2228
  title: contentSettingsTr(`backgroundColorTitle`),
2194
2229
  valueConvertor: backgroundColorConvertor,
2195
2230
  },
2231
+ ...PADDING_CONTROL_FIELDS(CONTACT_FORM_GROUPS.style),
2196
2232
  },
2197
2233
  isEnabled: true,
2198
2234
  isLocked: (features) => !features.canAddContactFormContents,
@@ -2656,6 +2692,7 @@ export const IMAGES_SECTION_SETTINGS_CONFIG =
2656
2692
  ? MAX_BS_MEDIA_AND_IMAGE_ITEMS
2657
2693
  : MAX_BP_MEDIA_AND_IMAGE_ITEMS,
2658
2694
  },
2695
+ ...PADDING_CONTROL_FIELDS(SETTINGS_STYLE_FIELD_GROUP_IDS.style),
2659
2696
  },
2660
2697
  isEnabled: true,
2661
2698
  isLocked: false,
@@ -2829,6 +2866,7 @@ export const SOCIAL_LINKS_SETTINGS_CONFIG =
2829
2866
  title: contentSettingsTr(`backgroundColorTitle`),
2830
2867
  valueConvertor: backgroundColorConvertor,
2831
2868
  },
2869
+ ...PADDING_CONTROL_FIELDS(SOCIAL_LINKS_GROUPS.style),
2832
2870
  },
2833
2871
  isEnabled: true,
2834
2872
  isLocked: false,
@@ -2942,6 +2980,7 @@ export const SHAPES_SECTION_SETTINGS_CONFIG =
2942
2980
  ? MAX_BS_MEDIA_AND_IMAGE_ITEMS
2943
2981
  : MAX_BP_MEDIA_AND_IMAGE_ITEMS,
2944
2982
  },
2983
+ ...PADDING_CONTROL_FIELDS(SETTINGS_STYLE_FIELD_GROUP_IDS.style),
2945
2984
  },
2946
2985
  isEnabled: true,
2947
2986
  isLocked: false,
@@ -3202,6 +3241,7 @@ export const BOOKING_SECTION_SETTINGS_CONFIG =
3202
3241
  validator: urlValidator,
3203
3242
  defaultValue: '',
3204
3243
  },
3244
+ ...PADDING_CONTROL_FIELDS(SETTINGS_STYLE_FIELD_GROUP_IDS.style),
3205
3245
  },
3206
3246
  isEnabled: true,
3207
3247
  isLocked: false,
@@ -9,6 +9,12 @@ import {
9
9
  ALIGN_LEFT_BIG_IMAGE,
10
10
  ALIGN_CARD_CENTER_IMAGE,
11
11
  ALIGN_CARD_LEFT_IMAGE,
12
+ ALIGN_TOP_CARD_IMAGE,
13
+ ALIGN_BOTTOM_CARD_IMAGE,
14
+ ALIGN_MIDDLE_CARD_IMAGE,
15
+ ALIGN_TOP_FULL_IMAGE,
16
+ ALIGN_BOTTOM_FULL_IMAGE,
17
+ ALIGN_MIDDLE_FULL_IMAGE,
12
18
  } from '@brand-page/constants';
13
19
  import type { BannerSectionContent } from '@brand-page';
14
20
 
@@ -46,6 +52,54 @@ export const bannerAlignDisplayStyleConvertor: ValueConvertor<
46
52
  iconViewBox: '0 0 110 64',
47
53
  dataAttribute: 'data-test-btn-text-only',
48
54
  },
55
+ {
56
+ title: '',
57
+ value: ALIGN_TOP_CARD_IMAGE,
58
+ isSelected: value === ALIGN_TOP_CARD_IMAGE,
59
+ iconName: 'display-style-banner-10',
60
+ iconViewBox: '0 0 110 64',
61
+ dataAttribute: 'data-test-btn-top-card-image',
62
+ },
63
+ {
64
+ title: '',
65
+ value: ALIGN_BOTTOM_CARD_IMAGE,
66
+ isSelected: value === ALIGN_BOTTOM_CARD_IMAGE,
67
+ iconName: 'display-style-banner-11',
68
+ iconViewBox: '0 0 110 64',
69
+ dataAttribute: 'data-test-btn-bottom-card-image',
70
+ },
71
+ {
72
+ title: '',
73
+ value: ALIGN_MIDDLE_CARD_IMAGE,
74
+ isSelected: value === ALIGN_MIDDLE_CARD_IMAGE,
75
+ iconName: 'display-style-banner-12',
76
+ iconViewBox: '0 0 110 64',
77
+ dataAttribute: 'data-test-btn-middle-card-image',
78
+ },
79
+ {
80
+ title: '',
81
+ value: ALIGN_TOP_FULL_IMAGE,
82
+ isSelected: value === ALIGN_TOP_FULL_IMAGE,
83
+ iconName: 'display-style-banner-13',
84
+ iconViewBox: '0 0 110 64',
85
+ dataAttribute: 'data-test-btn-top-full-image',
86
+ },
87
+ {
88
+ title: '',
89
+ value: ALIGN_BOTTOM_FULL_IMAGE,
90
+ isSelected: value === ALIGN_BOTTOM_FULL_IMAGE,
91
+ iconName: 'display-style-banner-14',
92
+ iconViewBox: '0 0 110 64',
93
+ dataAttribute: 'data-test-btn-bottom-full-image',
94
+ },
95
+ {
96
+ title: '',
97
+ value: ALIGN_MIDDLE_FULL_IMAGE,
98
+ isSelected: value === ALIGN_MIDDLE_FULL_IMAGE,
99
+ iconName: 'display-style-banner-15',
100
+ iconViewBox: '0 0 110 64',
101
+ dataAttribute: 'data-test-btn-middle-full-image',
102
+ },
49
103
  {
50
104
  title: '',
51
105
  value: BIG_IMAGE,
@@ -107,7 +161,13 @@ export const bannerAlignDisplayStyleConvertor: ValueConvertor<
107
161
  !selectedOption ||
108
162
  selectedOption.value === ALIGN_CENTERED ||
109
163
  selectedOption.value === BIG_IMAGE ||
110
- selectedOption.value === ALIGN_CARD_CENTER_IMAGE
164
+ selectedOption.value === ALIGN_CARD_CENTER_IMAGE ||
165
+ selectedOption.value === ALIGN_TOP_CARD_IMAGE ||
166
+ selectedOption.value === ALIGN_BOTTOM_CARD_IMAGE ||
167
+ selectedOption.value === ALIGN_MIDDLE_CARD_IMAGE ||
168
+ selectedOption.value === ALIGN_TOP_FULL_IMAGE ||
169
+ selectedOption.value === ALIGN_BOTTOM_FULL_IMAGE ||
170
+ selectedOption.value === ALIGN_MIDDLE_FULL_IMAGE
111
171
  ) {
112
172
  textAlign = 'center';
113
173
  }