@designcrowd/library.brand-page 6.0.21 → 6.0.22
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-C52IyXZu.js → CreatedOnBrandCrowd-de-de-BS8dQhaZ.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-DhTy_dEV.js → CreatedOnBrandCrowd-en-us-45ZyXSxn.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-BJelIAQ4.js → CreatedOnBrandCrowd-es-es-B1K3Ojw-.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-DwJohy0T.js → CreatedOnBrandCrowd-fr-ca-4v9c20Q9.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-DwJohy0T.js → CreatedOnBrandCrowd-fr-fr-4v9c20Q9.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-CMzKbxdG.js → CreatedOnBrandCrowd-pt-br-BdDmm1GE.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-CMzKbxdG.js → CreatedOnBrandCrowd-pt-pt-BdDmm1GE.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-DAy1GHrw.js → CreatedOnDcom-de-de-Dc8rOxAE.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-CgBDAkJN.js → CreatedOnDcom-en-us-Bq6rusW6.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-Ds9d5rem.js → CreatedOnDcom-es-es-C5gNEuoi.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-CZL0BQ-u.js → CreatedOnDcom-fr-ca-NL2KoADg.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-BVYh5Wvo.js → CreatedOnDcom-fr-fr-DZaLN0j-.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-1hsWNXfq.js → CreatedOnDcom-pt-br-DXZvo45o.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-DvM0N-3M.js → CreatedOnDcom-pt-pt-GUJ4aTM2.js} +1 -1
- package/lib/src/brand-page/components/collapsible/Collapsible.mixin.d.ts +11 -0
- package/lib/src/brand-page/components/collapsible/Collapsible.mixin.js +13 -1
- package/lib/src/brand-page/components/contact-form/ContactForm.mixin.d.ts +1 -0
- package/lib/src/brand-page/components/contact-form/ContactForm.mixin.js +12 -3
- package/lib/src/brand-page/components/mailing-list/MailingList.mixin.js +5 -0
- package/lib/src/brand-page/components/payment/Payment.mixin.js +5 -0
- package/lib/src/brand-page/models/index.d.ts +1 -0
- package/lib/src/brand-page/models/payment.model.d.ts +2 -0
- package/lib/src/brand-page/utils/button.util.d.ts +5 -0
- package/lib/src/brand-page/utils/button.util.js +13 -0
- package/lib/src/brand-page/utils/migration/migration-19.d.ts +2 -0
- package/lib/src/brand-page/utils/migration/migration-19.js +41 -0
- package/lib/src/brand-page/utils/migration.util.js +11 -0
- package/lib/src/brand-page/utils/view-model.util.js +1 -0
- package/lib/src/brand-page-maker/assessors/contact-form-visibility.assessor.d.ts +1 -0
- package/lib/src/brand-page-maker/assessors/contact-form-visibility.assessor.js +16 -7
- package/lib/src/brand-page-maker/constants/config-settings-constant.js +40 -3
- package/lib/src/brand-page-maker/default-getters/contact-form-card-color.default-getter.d.ts +8 -0
- package/lib/src/brand-page-maker/default-getters/contact-form-card-color.default-getter.js +16 -0
- package/lib/src/brand-page-maker/default-getters/index.d.ts +1 -0
- package/lib/src/brand-page-maker/default-getters/index.js +1 -0
- package/lib/src/brand-page-maker/models/button-styles-config.js +15 -3
- package/lib/src/brand-page-maker/models/content-settings-configs.d.ts +2 -1
- package/lib/src/brand-page-maker/models/content-settings.js +15 -3
- package/lib/src/css/library.brand-page-brandCrowd.min.css +96 -68
- package/lib/src/css/library.brand-page-brandPage.min.css +96 -68
- package/lib/src/css/library.brand-page-designCom.min.css +96 -68
- package/lib/src/css/library.brand-page-preview.min.css +96 -68
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-CK2FPBv5.js → lib-Dh4vrmod.js} +18439 -18090
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.js +5 -0
- package/lib/src/shared/content-settings/models.d.ts +1 -0
- package/lib/src/shared/content-settings/store/content-settings-store.js +3 -0
- package/package.json +2 -2
- package/src/brand-page/components/button/ButtonStyleWrapper.vue +112 -44
- package/src/brand-page/components/button/ButtonStyleWrapper.vue.test.ts +167 -0
- package/src/brand-page/components/collapsible/Collapsible.mixin.ts +14 -1
- package/src/brand-page/components/contact-form/ContactForm.mixin.test.ts +18 -0
- package/src/brand-page/components/contact-form/ContactForm.mixin.ts +15 -3
- package/src/brand-page/components/contact-form/ContactForm.vue +97 -36
- package/src/brand-page/components/mailing-list/MailingList.mixin.ts +6 -0
- package/src/brand-page/components/mailing-list/MailingList.vue +18 -10
- package/src/brand-page/components/payment/Payment.mixin.ts +6 -0
- package/src/brand-page/components/payment/Payment.vue +43 -24
- package/src/brand-page/components/payment/RequestPaymentStripe.vue +26 -22
- package/src/brand-page/components/shop/Shop.vue +15 -6
- package/src/brand-page/models/index.ts +1 -0
- package/src/brand-page/models/payment.model.ts +2 -0
- package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +3 -3
- package/src/brand-page/utils/button.util.ts +17 -0
- package/src/brand-page/utils/migration/migration-19.test.ts +172 -0
- package/src/brand-page/utils/migration/migration-19.ts +65 -0
- package/src/brand-page/utils/migration.util.ts +12 -0
- package/src/brand-page/utils/view-model.util.ts +1 -0
- package/src/brand-page-maker/assessors/contact-form-visibility.assessor.ts +34 -26
- package/src/brand-page-maker/constants/config-settings-constant.ts +43 -2
- package/src/brand-page-maker/default-getters/contact-form-card-color.default-getter.ts +23 -0
- package/src/brand-page-maker/default-getters/index.ts +1 -0
- package/src/brand-page-maker/models/button-styles-config.ts +15 -3
- package/src/brand-page-maker/models/content-settings-configs.ts +4 -1
- package/src/brand-page-maker/models/content-settings.ts +16 -3
- package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.ts +6 -0
- package/src/shared/content-settings/models.ts +3 -0
- package/src/shared/content-settings/store/content-settings-store.ts +3 -0
- package/src/shared/style-settings/components/StyleSettings.vue +1 -0
- package/lib/src/metadata-builder/builders/reorder-pages-builder.d.ts +0 -13
- package/lib/src/metadata-builder/builders/reorder-pages-builder.js +0 -49
- package/src/metadata-builder/builders/reorder-pages-builder.ts +0 -80
|
@@ -171,6 +171,11 @@ export default defineComponent({
|
|
|
171
171
|
return this.field.showToggle;
|
|
172
172
|
},
|
|
173
173
|
disableTransparent() {
|
|
174
|
+
// Supports a function of brandPageType (like variant/min/maxLength) so a
|
|
175
|
+
// field can disable transparent only on some website types.
|
|
176
|
+
if (typeof this.field.disableTransparent === 'function') {
|
|
177
|
+
return this.field.disableTransparent(this.brandPageType);
|
|
178
|
+
}
|
|
174
179
|
return this.field.disableTransparent || false;
|
|
175
180
|
},
|
|
176
181
|
plainTextValue() {
|
|
@@ -71,6 +71,7 @@ export type FieldGroup = ContentSettingsFieldGroup & {
|
|
|
71
71
|
export interface CreateInitialContentOptions {
|
|
72
72
|
userState: UserState;
|
|
73
73
|
brandPageType: BrandPageType;
|
|
74
|
+
buttonFillColor?: string;
|
|
74
75
|
}
|
|
75
76
|
export interface AddContentOption<TContent> {
|
|
76
77
|
type: ContentType;
|
|
@@ -5,6 +5,7 @@ import { UI_EVENT_SOURCES } from '../../models';
|
|
|
5
5
|
import { CONTENT_SETTINGS_DISPLAY_MODES, ITEM_CATEGORIES, ITEM_CATEGORY_NAMES, } from '@brand-page-maker/models';
|
|
6
6
|
import { useUiStore, useBrandPageMakerUserStore, useBrandPageMakerStore, useVideoStore, } from '@brand-page-maker/stores';
|
|
7
7
|
import { useHistoryStore } from '@brand-page-maker/stores/history/history-store';
|
|
8
|
+
import { useUiStyleSettingsStore } from '@shared/style-settings/store';
|
|
8
9
|
import { useUiSideBarStore } from '../../side-bar/store';
|
|
9
10
|
import { createDefaultContentItemGroupState, createDefaultContentItemState, } from '../../navigation-style-settings/store';
|
|
10
11
|
import { adminValidatorsDict, dummyValidator, validatorDict, } from '@brand-page-maker/validators';
|
|
@@ -358,6 +359,7 @@ export const useUiContentSettingsStore = defineStore('uiContentSettings', {
|
|
|
358
359
|
addItemByType({ type, index, }) {
|
|
359
360
|
const userStore = useBrandPageMakerUserStore();
|
|
360
361
|
const brandPageMakerStore = useBrandPageMakerStore();
|
|
362
|
+
const styleSettingsStore = useUiStyleSettingsStore();
|
|
361
363
|
const option = this.addContentOptions.find((o) => o.type === type);
|
|
362
364
|
if (!option) {
|
|
363
365
|
console.error(`Unable to locate add content option type for '${type}'`);
|
|
@@ -366,6 +368,7 @@ export const useUiContentSettingsStore = defineStore('uiContentSettings', {
|
|
|
366
368
|
const content = option.createInitialContent({
|
|
367
369
|
userState: userStore.$state,
|
|
368
370
|
brandPageType: brandPageMakerStore.brandPageType,
|
|
371
|
+
buttonFillColor: styleSettingsStore.buttonFillColor,
|
|
369
372
|
});
|
|
370
373
|
const settingsConfig = CONTENT_SETTINGS_CONFIGS()[content.type];
|
|
371
374
|
const fields = Object.keys(content)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@designcrowd/library.brand-page",
|
|
3
3
|
"description": "A collection of Maker/Designer applications",
|
|
4
|
-
"version": "6.0.
|
|
4
|
+
"version": "6.0.22",
|
|
5
5
|
"author": "Design.com Eng",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"type": "module",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"@ckpack/vue-color": "1.6.0",
|
|
74
|
-
"@designcrowd/fe-shared-lib": "2.0.
|
|
74
|
+
"@designcrowd/fe-shared-lib": "2.0.7",
|
|
75
75
|
"@pinia/nuxt": "0.11.3",
|
|
76
76
|
"@storybook/react": "9.0.4",
|
|
77
77
|
"@tiptap/extension-bubble-menu": "^2.14.0",
|
|
@@ -3,8 +3,14 @@
|
|
|
3
3
|
class="bp-style-wrapper"
|
|
4
4
|
:class="{
|
|
5
5
|
'bp-button-arrow': isArrowIcon,
|
|
6
|
-
'tw-flex tw-justify-center
|
|
7
|
-
'
|
|
6
|
+
'tw-flex tw-justify-center': isBrushStrokeIcon,
|
|
7
|
+
'bp-button-shape': isPaperIcon,
|
|
8
|
+
'tw-mb-6': isPaperIcon && !noMargin,
|
|
9
|
+
'bp-style-wrapper--no-margin': isDecorative && noMargin,
|
|
10
|
+
'tw-mt-4': isDecorative && noMargin,
|
|
11
|
+
'tw-opacity-50': disabled,
|
|
12
|
+
'tw-w-full': isDecorative && fullWidth,
|
|
13
|
+
'tw-w-fit': isDecorative && !fullWidth,
|
|
8
14
|
}"
|
|
9
15
|
>
|
|
10
16
|
<template v-if="isBrushStrokeIcon">
|
|
@@ -25,21 +31,20 @@
|
|
|
25
31
|
/>
|
|
26
32
|
</template>
|
|
27
33
|
<template v-else-if="isPaperIcon">
|
|
34
|
+
<!-- Torn-paper: the fe-shared button-paper icon, stretched to the button box
|
|
35
|
+
(preserveAspectRatio none), sits behind the transparent button as its
|
|
36
|
+
shape. Inert (pointer-events:none) inset layer, so it fills exactly,
|
|
37
|
+
doesn't inflate the button's size, and doesn't capture clicks
|
|
38
|
+
(BP-5882/BP-5883). -->
|
|
28
39
|
<IconV2
|
|
29
|
-
name="button-paper
|
|
30
|
-
view-box="
|
|
31
|
-
size="
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
name="button-paper"
|
|
41
|
+
view-box="0 0 94 28"
|
|
42
|
+
size="full"
|
|
43
|
+
preserve-aspect-ratio="none"
|
|
44
|
+
class="bp-icon-shape"
|
|
45
|
+
:style="{ color: iconColor }"
|
|
34
46
|
/>
|
|
35
47
|
<slot :inner-class="'tw-px-5'" />
|
|
36
|
-
<IconV2
|
|
37
|
-
name="button-paper-bottom"
|
|
38
|
-
view-box="41 0 120 16"
|
|
39
|
-
size="auto"
|
|
40
|
-
class="tw-absolute tw-w-full bp-button-paper-bottom"
|
|
41
|
-
:style="paperIconStyle"
|
|
42
|
-
/>
|
|
43
48
|
</template>
|
|
44
49
|
<template v-else>
|
|
45
50
|
<slot :inner-class="'tw-px-5'" />
|
|
@@ -50,25 +55,54 @@
|
|
|
50
55
|
<script setup lang="ts">
|
|
51
56
|
import { computed } from 'vue';
|
|
52
57
|
import { Icon as IconV2 } from '@designcrowd/fe-shared-lib';
|
|
58
|
+
import {
|
|
59
|
+
BUTTON_STYLE_ARROW,
|
|
60
|
+
BUTTON_STYLE_BRUSHSTROKE,
|
|
61
|
+
BUTTON_STYLE_PAPER,
|
|
62
|
+
isDecorativeButtonStyle,
|
|
63
|
+
} from '../../utils/button.util';
|
|
53
64
|
|
|
54
|
-
const props =
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
65
|
+
const props = withDefaults(
|
|
66
|
+
defineProps<{
|
|
67
|
+
selectedButtonStyleId?: string;
|
|
68
|
+
iconColor?: string;
|
|
69
|
+
// Mirror the host button's width so the decoration matches its box.
|
|
70
|
+
// Defaults to true to preserve the original (full-width link) behaviour.
|
|
71
|
+
fullWidth?: boolean;
|
|
72
|
+
// Set by submit buttons (contact form, payment, mailing list, shop). Makes a
|
|
73
|
+
// decorative button sit flush at the container's bottom edge: drops the paper
|
|
74
|
+
// style's bottom margin, zeroes the host button's own margins (which would
|
|
75
|
+
// otherwise shift it out of sync with its wrapper-anchored decoration), and
|
|
76
|
+
// moves the top gap onto the wrapper. Standalone buttons keep it off (BP-5800).
|
|
77
|
+
noMargin?: boolean;
|
|
78
|
+
// Dims the whole wrapper to 50% when the host button is disabled — for every
|
|
79
|
+
// style, so disabled reads consistently. (Decorative styles were previously
|
|
80
|
+
// exempt to avoid a muddy tone with the old absolute-overlay rendering; the
|
|
81
|
+
// torn-paper mask removed that rendering, so decorative buttons now fade like
|
|
82
|
+
// the rest. BP-5882/BP-5883.)
|
|
83
|
+
disabled?: boolean;
|
|
84
|
+
}>(),
|
|
85
|
+
{
|
|
86
|
+
selectedButtonStyleId: undefined,
|
|
87
|
+
iconColor: undefined,
|
|
88
|
+
fullWidth: true,
|
|
89
|
+
noMargin: false,
|
|
90
|
+
disabled: false,
|
|
91
|
+
},
|
|
92
|
+
);
|
|
58
93
|
|
|
59
94
|
const isPaperIcon = computed(
|
|
60
|
-
() => props.selectedButtonStyleId ===
|
|
95
|
+
() => props.selectedButtonStyleId === BUTTON_STYLE_PAPER,
|
|
61
96
|
);
|
|
62
97
|
const isArrowIcon = computed(
|
|
63
|
-
() => props.selectedButtonStyleId ===
|
|
98
|
+
() => props.selectedButtonStyleId === BUTTON_STYLE_ARROW,
|
|
64
99
|
);
|
|
65
100
|
const isBrushStrokeIcon = computed(
|
|
66
|
-
() => props.selectedButtonStyleId ===
|
|
101
|
+
() => props.selectedButtonStyleId === BUTTON_STYLE_BRUSHSTROKE,
|
|
102
|
+
);
|
|
103
|
+
const isDecorative = computed(() =>
|
|
104
|
+
isDecorativeButtonStyle(props.selectedButtonStyleId),
|
|
67
105
|
);
|
|
68
|
-
|
|
69
|
-
const paperIconStyle = computed(() => ({
|
|
70
|
-
'--icon-color': props.iconColor,
|
|
71
|
-
}));
|
|
72
106
|
|
|
73
107
|
const brushLeftStyle = computed(() => ({
|
|
74
108
|
'--icon-color': props.iconColor,
|
|
@@ -86,37 +120,71 @@ const brushRightStyle = computed(() => ({
|
|
|
86
120
|
</script>
|
|
87
121
|
|
|
88
122
|
<style scoped>
|
|
89
|
-
.
|
|
90
|
-
|
|
123
|
+
/* Decorative styles on submit buttons render flush at the bottom. Zero the host
|
|
124
|
+
button's own margins: its decoration is positioned relative to the WRAPPER, so a
|
|
125
|
+
margin on the button itself shifts it out of alignment with the decoration. The
|
|
126
|
+
top gap is instead applied to the wrapper (tw-mt-4 above) so the whole decorated
|
|
127
|
+
unit moves together and stays flush at the container's bottom edge (BP-5800). */
|
|
128
|
+
.bp-style-wrapper--no-margin :deep(button) {
|
|
129
|
+
margin-top: 0;
|
|
130
|
+
margin-bottom: 0;
|
|
91
131
|
}
|
|
92
132
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
.bp-button-paper-bottom :deep(svg) {
|
|
99
|
-
width: 100%;
|
|
100
|
-
height: auto;
|
|
133
|
+
/* Brushstroke: flanking brush-end icons are transformed over the button's
|
|
134
|
+
left/right edges, so they must not capture clicks (BP-5882). */
|
|
135
|
+
.bp-icon-brush-left,
|
|
136
|
+
.bp-icon-brush-right {
|
|
137
|
+
pointer-events: none;
|
|
101
138
|
}
|
|
102
|
-
|
|
103
139
|
.bp-icon-brush-left :deep(svg) {
|
|
104
140
|
transform: translateX(-99%);
|
|
105
141
|
width: auto;
|
|
106
142
|
}
|
|
107
|
-
|
|
108
143
|
.bp-icon-brush-right :deep(svg) {
|
|
109
144
|
width: auto;
|
|
110
145
|
}
|
|
111
146
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
147
|
+
/* Torn-paper: the button-paper icon sits BEHIND the button as its shape. The
|
|
148
|
+
wrapper is the positioning context; the icon is an inert inset layer that fills
|
|
149
|
+
the button box exactly (inset:0 + preserveAspectRatio none) without inflating
|
|
150
|
+
its size or capturing clicks (BP-5882/BP-5883). The button's own fill is made
|
|
151
|
+
transparent so the icon's colour shows through. */
|
|
152
|
+
.bp-button-shape {
|
|
153
|
+
position: relative;
|
|
154
|
+
}
|
|
155
|
+
.bp-icon-shape {
|
|
156
|
+
position: absolute;
|
|
157
|
+
inset: 0;
|
|
158
|
+
width: 100%;
|
|
159
|
+
height: 100%;
|
|
160
|
+
pointer-events: none;
|
|
161
|
+
}
|
|
162
|
+
.bp-button-shape :deep(button),
|
|
163
|
+
.bp-button-shape :deep(a) {
|
|
164
|
+
position: relative; /* sit above the inert background */
|
|
165
|
+
/* !important to override the host button's inline background-color. */
|
|
166
|
+
background-color: transparent !important;
|
|
167
|
+
}
|
|
168
|
+
/* button-paper ships hardcoded fill="black"; recolour to the button's fill. CSS
|
|
169
|
+
fill overrides the presentation attribute; clip-path rects are geometric so
|
|
170
|
+
recolouring them is harmless. */
|
|
171
|
+
.bp-icon-shape :deep(path),
|
|
172
|
+
.bp-icon-shape :deep(rect) {
|
|
173
|
+
fill: currentColor;
|
|
115
174
|
}
|
|
116
175
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
176
|
+
.bp-button-arrow,
|
|
177
|
+
.device-wrapper--mobile .bp-button-arrow {
|
|
178
|
+
/* Fixed-px arrowhead (not %) so the chevron is equally sharp at any button
|
|
179
|
+
width — a %-based point collapses on narrow buttons (e.g. the 120px contact
|
|
180
|
+
form submit) and only reads as an arrow on wide ones. */
|
|
181
|
+
clip-path: polygon(
|
|
182
|
+
12px 100%,
|
|
183
|
+
0 50%,
|
|
184
|
+
12px 0,
|
|
185
|
+
calc(100% - 12px) 0,
|
|
186
|
+
100% 50%,
|
|
187
|
+
calc(100% - 12px) 100%
|
|
188
|
+
);
|
|
121
189
|
}
|
|
122
190
|
</style>
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import ButtonStyleWrapper from './ButtonStyleWrapper.vue';
|
|
3
|
+
|
|
4
|
+
// The three decorative styles that ONLY render via this wrapper (BP-5546).
|
|
5
|
+
const BRUSHSTROKE = 'BUTTON_STYLE_19';
|
|
6
|
+
const ARROW = 'BUTTON_STYLE_20';
|
|
7
|
+
const PAPER = 'BUTTON_STYLE_21';
|
|
8
|
+
|
|
9
|
+
const HOST_SLOT = '<button class="host-button">Send</button>';
|
|
10
|
+
|
|
11
|
+
const mountWrapper = (props: Record<string, unknown> = {}) =>
|
|
12
|
+
shallowMount(ButtonStyleWrapper, {
|
|
13
|
+
props,
|
|
14
|
+
slots: { default: HOST_SLOT },
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('ButtonStyleWrapper', () => {
|
|
18
|
+
describe('renders the host button (slot) for every style', () => {
|
|
19
|
+
test.each([
|
|
20
|
+
['no style', undefined],
|
|
21
|
+
['plain css style', 'BUTTON_STYLE_1'],
|
|
22
|
+
['brushstroke', BRUSHSTROKE],
|
|
23
|
+
['arrow', ARROW],
|
|
24
|
+
['paper', PAPER],
|
|
25
|
+
])('%s', (_label, selectedButtonStyleId) => {
|
|
26
|
+
const wrapper = mountWrapper({ selectedButtonStyleId });
|
|
27
|
+
|
|
28
|
+
expect(wrapper.find('button.host-button').exists()).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('decorative styles draw their SVG decoration', () => {
|
|
33
|
+
test('brushstroke (19) renders flanking brush ends', () => {
|
|
34
|
+
const wrapper = mountWrapper({ selectedButtonStyleId: BRUSHSTROKE });
|
|
35
|
+
|
|
36
|
+
expect(wrapper.classes()).toEqual(
|
|
37
|
+
expect.arrayContaining(['tw-flex', 'tw-justify-center']),
|
|
38
|
+
);
|
|
39
|
+
expect(wrapper.find('.bp-icon-brush-left').exists()).toBe(true);
|
|
40
|
+
expect(wrapper.find('.bp-icon-brush-right').exists()).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('arrow (20) clips the button into a chevron', () => {
|
|
44
|
+
const wrapper = mountWrapper({ selectedButtonStyleId: ARROW });
|
|
45
|
+
|
|
46
|
+
expect(wrapper.classes()).toContain('bp-button-arrow');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('paper (21) renders the torn button-paper icon behind the button', () => {
|
|
50
|
+
const wrapper = mountWrapper({ selectedButtonStyleId: PAPER });
|
|
51
|
+
|
|
52
|
+
// Icon shape behind the button — not absolute overlay elements (BP-5882/5883).
|
|
53
|
+
expect(wrapper.classes()).toEqual(
|
|
54
|
+
expect.arrayContaining(['bp-button-shape', 'tw-mb-6']),
|
|
55
|
+
);
|
|
56
|
+
expect(wrapper.find('.bp-button-paper-top').exists()).toBe(false);
|
|
57
|
+
expect(wrapper.find('.bp-button-paper-bottom').exists()).toBe(false);
|
|
58
|
+
// The torn shape is the fe-shared button-paper icon, stretched to the button.
|
|
59
|
+
const shape = wrapper.find('.bp-icon-shape');
|
|
60
|
+
|
|
61
|
+
expect(shape.exists()).toBe(true);
|
|
62
|
+
expect(shape.attributes('name')).toBe('button-paper');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('submit buttons render decorative styles flush (BP-5800)', () => {
|
|
67
|
+
test.each([BRUSHSTROKE, ARROW, PAPER])(
|
|
68
|
+
'%s gets the no-margin class + wrapper top margin when noMargin is set',
|
|
69
|
+
(selectedButtonStyleId) => {
|
|
70
|
+
const wrapper = mountWrapper({ selectedButtonStyleId, noMargin: true });
|
|
71
|
+
|
|
72
|
+
// The top gap moves onto the wrapper so the decoration stays synced;
|
|
73
|
+
// the CSS zeroes the host button's own margins.
|
|
74
|
+
expect(wrapper.classes()).toContain('bp-style-wrapper--no-margin');
|
|
75
|
+
expect(wrapper.classes()).toContain('tw-mt-4');
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
test('paper drops its bottom margin when noMargin is set', () => {
|
|
80
|
+
const wrapper = mountWrapper({
|
|
81
|
+
selectedButtonStyleId: PAPER,
|
|
82
|
+
noMargin: true,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(wrapper.classes()).not.toContain('tw-mb-6');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('non-decorative styles never get the no-margin class', () => {
|
|
89
|
+
const wrapper = mountWrapper({
|
|
90
|
+
selectedButtonStyleId: 'BUTTON_STYLE_1',
|
|
91
|
+
noMargin: true,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(wrapper.classes()).not.toContain('bp-style-wrapper--no-margin');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('disabled dimming (BP-5800)', () => {
|
|
99
|
+
test('dims non-decorative buttons when disabled', () => {
|
|
100
|
+
const wrapper = mountWrapper({
|
|
101
|
+
selectedButtonStyleId: 'BUTTON_STYLE_1',
|
|
102
|
+
disabled: true,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(wrapper.classes()).toContain('tw-opacity-50');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test.each([BRUSHSTROKE, ARROW, PAPER])(
|
|
109
|
+
'dims decorative style %s when disabled (consistent with other styles)',
|
|
110
|
+
(selectedButtonStyleId) => {
|
|
111
|
+
const wrapper = mountWrapper({ selectedButtonStyleId, disabled: true });
|
|
112
|
+
|
|
113
|
+
expect(wrapper.classes()).toContain('tw-opacity-50');
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
test('is not dimmed by default', () => {
|
|
118
|
+
const wrapper = mountWrapper({ selectedButtonStyleId: 'BUTTON_STYLE_1' });
|
|
119
|
+
|
|
120
|
+
expect(wrapper.classes()).not.toContain('tw-opacity-50');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('non-decorative styles are a layout no-op', () => {
|
|
125
|
+
test.each([
|
|
126
|
+
['no style', undefined],
|
|
127
|
+
['css style', 'BUTTON_STYLE_1'],
|
|
128
|
+
])(
|
|
129
|
+
'%s adds no decoration and no width class',
|
|
130
|
+
(_label, selectedButtonStyleId) => {
|
|
131
|
+
const wrapper = mountWrapper({ selectedButtonStyleId });
|
|
132
|
+
|
|
133
|
+
expect(wrapper.classes()).not.toContain('bp-button-arrow');
|
|
134
|
+
expect(wrapper.find('.bp-icon-brush-left').exists()).toBe(false);
|
|
135
|
+
expect(wrapper.find('.bp-button-paper-top').exists()).toBe(false);
|
|
136
|
+
// Width-neutral: must not force the host's width either way.
|
|
137
|
+
expect(wrapper.classes()).not.toContain('tw-w-full');
|
|
138
|
+
expect(wrapper.classes()).not.toContain('tw-w-fit');
|
|
139
|
+
},
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('width is preserved via the fullWidth prop', () => {
|
|
144
|
+
test.each([BRUSHSTROKE, ARROW, PAPER])(
|
|
145
|
+
'%s stretches full-width when fullWidth (default)',
|
|
146
|
+
(selectedButtonStyleId) => {
|
|
147
|
+
const wrapper = mountWrapper({ selectedButtonStyleId });
|
|
148
|
+
|
|
149
|
+
expect(wrapper.classes()).toContain('tw-w-full');
|
|
150
|
+
expect(wrapper.classes()).not.toContain('tw-w-fit');
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
test.each([BRUSHSTROKE, ARROW, PAPER])(
|
|
155
|
+
'%s shrinks to the host box when fullWidth is false',
|
|
156
|
+
(selectedButtonStyleId) => {
|
|
157
|
+
const wrapper = mountWrapper({
|
|
158
|
+
selectedButtonStyleId,
|
|
159
|
+
fullWidth: false,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(wrapper.classes()).toContain('tw-w-fit');
|
|
163
|
+
expect(wrapper.classes()).not.toContain('tw-w-full');
|
|
164
|
+
},
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -28,6 +28,11 @@ export default defineComponent({
|
|
|
28
28
|
required: false,
|
|
29
29
|
default: undefined,
|
|
30
30
|
},
|
|
31
|
+
backgroundStyle: {
|
|
32
|
+
type: Object as PropType<BrandPageCssStyle>,
|
|
33
|
+
required: false,
|
|
34
|
+
default: undefined,
|
|
35
|
+
},
|
|
31
36
|
context: {
|
|
32
37
|
type: Object as PropType<BrandPageContext>,
|
|
33
38
|
required: true,
|
|
@@ -45,10 +50,18 @@ export default defineComponent({
|
|
|
45
50
|
containerStyle.borderRadius = '26px';
|
|
46
51
|
}
|
|
47
52
|
|
|
48
|
-
|
|
53
|
+
// Guarded on backgroundColor so an empty {} never blanks the container
|
|
54
|
+
// (BP/BC contact-form card colour defaults to the button fill).
|
|
55
|
+
if (this.cardStyle?.backgroundColor) {
|
|
49
56
|
containerStyle.backgroundColor = this.cardStyle.backgroundColor;
|
|
50
57
|
}
|
|
51
58
|
|
|
59
|
+
// Own background colour decouples the container from the button fill (BP-5800).
|
|
60
|
+
// Guarded on backgroundColor so an empty {} never blanks the container.
|
|
61
|
+
if (this.backgroundStyle?.backgroundColor) {
|
|
62
|
+
containerStyle.backgroundColor = this.backgroundStyle.backgroundColor;
|
|
63
|
+
}
|
|
64
|
+
|
|
52
65
|
return containerStyle;
|
|
53
66
|
},
|
|
54
67
|
arrowStyle(): BrandPageCssStyle {
|
|
@@ -25,6 +25,24 @@ describe('ContactForm disclosureColor', () => {
|
|
|
25
25
|
expect(color).not.toBe('#ff0000');
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
+
test('brand.page / brand.contact anchor to the card colour when set', () => {
|
|
29
|
+
const color = disclosureColor({
|
|
30
|
+
context: { isBrandSite: false },
|
|
31
|
+
hasCard: false,
|
|
32
|
+
isDividedContent: false,
|
|
33
|
+
hasBgColor: false,
|
|
34
|
+
viewModel: {
|
|
35
|
+
cardStyle: { backgroundColor: '#101010' }, // dark card colour
|
|
36
|
+
buttonStyles: { style: { backgroundColor: '#f5f0e6' } }, // light fill
|
|
37
|
+
titleStyle: { color: '#ff0000' },
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Anchored to the dark card colour → a lighter colour, not the title red.
|
|
42
|
+
expect(color).toBeDefined();
|
|
43
|
+
expect(color).not.toBe('#ff0000');
|
|
44
|
+
});
|
|
45
|
+
|
|
28
46
|
test('brandSite card layout anchors to the card background', () => {
|
|
29
47
|
const color = disclosureColor({
|
|
30
48
|
context: { isBrandSite: true },
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
getNameValidationError,
|
|
14
14
|
getPhoneValidationError,
|
|
15
15
|
} from '@brand-page/utils';
|
|
16
|
+
import { isDecorativeButtonStyle } from '../../utils/button.util';
|
|
16
17
|
import brandPageClient from '../../clients/brand-page.client';
|
|
17
18
|
import { isEmail } from '../../../utils/validator.util';
|
|
18
19
|
import {
|
|
@@ -78,6 +79,15 @@ export default defineComponent({
|
|
|
78
79
|
submitError: undefined as string | undefined,
|
|
79
80
|
}),
|
|
80
81
|
computed: {
|
|
82
|
+
// Decorative styles (brushstroke/arrow/paper) render a shape that needs width
|
|
83
|
+
// to read — at pure w-fit the arrow chevron collapses. Give the submit button a
|
|
84
|
+
// ~200px min-width so the decoration looks intentional without going full-width.
|
|
85
|
+
isDecorativeSubmitStyle(): boolean {
|
|
86
|
+
return isDecorativeButtonStyle(
|
|
87
|
+
this.viewModel.buttonStyles?.selectedButtonStyleId,
|
|
88
|
+
);
|
|
89
|
+
},
|
|
90
|
+
|
|
81
91
|
submitButtonStyle(): BrandPageCssStyle {
|
|
82
92
|
const buttonStyle = { ...this.viewModel.buttonStyles?.style };
|
|
83
93
|
const buttonTextStyle = {
|
|
@@ -214,8 +224,8 @@ export default defineComponent({
|
|
|
214
224
|
// differs by website type:
|
|
215
225
|
// - brandSite: the card style (card + divided layouts) or the section
|
|
216
226
|
// background (other layouts).
|
|
217
|
-
// - brand.page / brand.contact:
|
|
218
|
-
//
|
|
227
|
+
// - brand.page / brand.contact: the container is the collapsible, whose
|
|
228
|
+
// colour is the card colour (defaulting to the global button fill).
|
|
219
229
|
// Falls back to the title colour when there's no solid colour to anchor to.
|
|
220
230
|
const style = this.context.isBrandSite
|
|
221
231
|
? this.hasCard || this.isDividedContent
|
|
@@ -223,7 +233,9 @@ export default defineComponent({
|
|
|
223
233
|
: this.hasBgColor
|
|
224
234
|
? this.viewModel.backgroundStyle
|
|
225
235
|
: undefined
|
|
226
|
-
: this.viewModel.
|
|
236
|
+
: this.viewModel.cardStyle?.backgroundColor
|
|
237
|
+
? this.viewModel.cardStyle
|
|
238
|
+
: this.viewModel.buttonStyles?.style;
|
|
227
239
|
|
|
228
240
|
return (
|
|
229
241
|
mutedTextForBackground(backgroundColorFromStyle(style)) ??
|