@designcrowd/library.brand-page 6.0.13 → 6.0.14-5804-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/src/{CreatedOnBrandCrowd-de-de-CBjFFeBP.js → CreatedOnBrandCrowd-de-de-BYABvnQX.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-NuNXF81K.js → CreatedOnBrandCrowd-en-us-CU6aE_W2.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-BJG8nfug.js → CreatedOnBrandCrowd-es-es-Pfuc4Nfl.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-uX-nL53e.js → CreatedOnBrandCrowd-fr-ca-DyQ1RyZN.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-uX-nL53e.js → CreatedOnBrandCrowd-fr-fr-DyQ1RyZN.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-DTrgFGQV.js → CreatedOnBrandCrowd-pt-br-DXMSa1wa.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-DTrgFGQV.js → CreatedOnBrandCrowd-pt-pt-DXMSa1wa.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-DSfpxioT.js → CreatedOnDcom-de-de-kGnTiAMq.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-C7G4gKem.js → CreatedOnDcom-en-us-CKrgHXUY.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-DVckCaji.js → CreatedOnDcom-es-es-CVxOQoOy.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-C5GDrm_C.js → CreatedOnDcom-fr-ca-BduB1A0z.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-v5eMm-j2.js → CreatedOnDcom-fr-fr-LGhd51py.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-Dq3-fPUc.js → CreatedOnDcom-pt-br-BLqHx-RQ.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-DFsncYax.js → CreatedOnDcom-pt-pt-52goGQ1s.js} +1 -1
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +1 -0
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.js +5 -0
- package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.js +4 -6
- package/lib/src/brand-page-maker/composables/useQuickLinkBarVisibility.d.ts +17 -0
- package/lib/src/brand-page-maker/composables/useQuickLinkBarVisibility.js +23 -0
- package/lib/src/css/library.brand-page-brandCrowd.min.css +29 -32
- package/lib/src/css/library.brand-page-brandPage.min.css +29 -32
- package/lib/src/css/library.brand-page-designCom.min.css +29 -32
- package/lib/src/css/library.brand-page-preview.min.css +29 -32
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-BG-pyXi9.js → lib-ClM5iHnZ.js} +11321 -11310
- package/package.json +1 -1
- package/src/brand-page/components/button/Button.vue +20 -0
- package/src/brand-page/components/button/Button.vue.test.ts +175 -0
- package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.ts +5 -0
- package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.vue +2 -2
- package/src/brand-page-maker/components/device/Device.vue +0 -1
- package/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.ts +4 -7
- package/src/brand-page-maker/composables/useQuickLinkBarVisibility.test.ts +81 -0
- package/src/brand-page-maker/composables/useQuickLinkBarVisibility.ts +25 -0
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
class="tw-no-underline"
|
|
13
13
|
:to="{ name: routes.name, params: routes.routeParams }"
|
|
14
14
|
:class="{ 'tw-w-full': isBrushStrokeIcon }"
|
|
15
|
+
@click="onPageLinkClick"
|
|
15
16
|
>
|
|
16
17
|
<div
|
|
17
18
|
:title="viewModel.title"
|
|
@@ -101,6 +102,7 @@
|
|
|
101
102
|
|
|
102
103
|
<script setup lang="ts">
|
|
103
104
|
import { computed, ref } from 'vue';
|
|
105
|
+
import { useRouter } from 'vue-router';
|
|
104
106
|
import { HashRouteModal } from '@designcrowd/fe-shared-lib';
|
|
105
107
|
import { isEmail, isTel, isURL } from '../../../utils/validator.util';
|
|
106
108
|
import type { BrandPageContext } from '@brand-page/models';
|
|
@@ -231,6 +233,24 @@ const buttonStateClass = computed(() => ({
|
|
|
231
233
|
'tw-w-full': isBrushStrokeIcon.value,
|
|
232
234
|
}));
|
|
233
235
|
|
|
236
|
+
const router = useRouter();
|
|
237
|
+
|
|
238
|
+
const onPageLinkClick = (): void => {
|
|
239
|
+
// A page-link pointing to its own page (e.g. a "back to top" button) resolves
|
|
240
|
+
// to the current route. Vue Router dedupes that as a redundant navigation and
|
|
241
|
+
// never runs the global guard that scrolls to top, so trigger it explicitly.
|
|
242
|
+
if (!router) return;
|
|
243
|
+
|
|
244
|
+
const target = router.resolve({
|
|
245
|
+
name: routes.value.name,
|
|
246
|
+
params: routes.value.routeParams,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (target.fullPath === router.currentRoute.value.fullPath) {
|
|
250
|
+
window.scrollTo(0, 0);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
234
254
|
const onClick = (): void => {
|
|
235
255
|
if (isModalUrl.value && isModalImageUrlValid.value) {
|
|
236
256
|
isModalVisible.value = true;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { vi } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import { createRouter, createMemoryHistory, RouterLink } from 'vue-router';
|
|
4
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
5
|
+
import Button from './Button.vue';
|
|
6
|
+
import {
|
|
7
|
+
BRAND_PAGE_TYPES,
|
|
8
|
+
MEDIA_SIZES,
|
|
9
|
+
type BrandPageContext,
|
|
10
|
+
} from '@brand-page/models';
|
|
11
|
+
import type {
|
|
12
|
+
ButtonViewModel,
|
|
13
|
+
NavItemViewModel,
|
|
14
|
+
} from '@brand-page/view-models';
|
|
15
|
+
|
|
16
|
+
global.ResizeObserver = vi.fn().mockImplementation(() => ({
|
|
17
|
+
observe: vi.fn(),
|
|
18
|
+
unobserve: vi.fn(),
|
|
19
|
+
disconnect: vi.fn(),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
const LINKED_PAGE_ID = 'page-2';
|
|
23
|
+
const LINKED_PAGE_PATH = '/my-brand/second-page';
|
|
24
|
+
const OTHER_PAGE_PATH = '/my-brand';
|
|
25
|
+
|
|
26
|
+
const createTestNavItem = (): NavItemViewModel => ({
|
|
27
|
+
pageId: LINKED_PAGE_ID,
|
|
28
|
+
slug: 'my-brand',
|
|
29
|
+
displayName: 'Second Page',
|
|
30
|
+
routeName: 'page-route',
|
|
31
|
+
routeParams: { slug: 'my-brand', pageSlug: 'second-page' },
|
|
32
|
+
isRoot: false,
|
|
33
|
+
isHidden: false,
|
|
34
|
+
relativePath: 'second-page',
|
|
35
|
+
nestedNavItems: [],
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const createTestBrandPageContext = (): BrandPageContext => ({
|
|
39
|
+
brandPageSlug: 'my-brand',
|
|
40
|
+
brandPageToken: '',
|
|
41
|
+
isMobile: false,
|
|
42
|
+
isPreview: true,
|
|
43
|
+
isProduction: false,
|
|
44
|
+
isServerRender: false,
|
|
45
|
+
options: {
|
|
46
|
+
mediaSizes: MEDIA_SIZES.sizeThumb,
|
|
47
|
+
},
|
|
48
|
+
isTemplate: false,
|
|
49
|
+
isDesignCom: false,
|
|
50
|
+
pageTitle: 'My Brand',
|
|
51
|
+
runtimeEnvironment: '',
|
|
52
|
+
stripePublishableKey: undefined,
|
|
53
|
+
customDomain: undefined,
|
|
54
|
+
brandPageType: BRAND_PAGE_TYPES.brandSite,
|
|
55
|
+
isBrandPage: false,
|
|
56
|
+
isBrandPageOrIsBrandContact: false,
|
|
57
|
+
isBrandSite: true,
|
|
58
|
+
isBrandContact: false,
|
|
59
|
+
pages: [],
|
|
60
|
+
isCapturingPreview: false,
|
|
61
|
+
recaptchaSiteKey: undefined,
|
|
62
|
+
nav: {
|
|
63
|
+
navItems: [createTestNavItem()],
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const textStyle = {
|
|
68
|
+
fontSize: '1rem',
|
|
69
|
+
lineHeight: '1.5rem',
|
|
70
|
+
fontFamily: 'Lato',
|
|
71
|
+
textAlign: 'center',
|
|
72
|
+
overflowWrap: 'anywhere',
|
|
73
|
+
wordBreak: 'normal',
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const createTestButtonViewModel = (
|
|
77
|
+
overrides: Partial<ButtonViewModel> = {},
|
|
78
|
+
): ButtonViewModel =>
|
|
79
|
+
({
|
|
80
|
+
id: 'button-1',
|
|
81
|
+
type: 'button',
|
|
82
|
+
role: 'banner-section',
|
|
83
|
+
title: 'Go to top',
|
|
84
|
+
url: '',
|
|
85
|
+
page: LINKED_PAGE_ID,
|
|
86
|
+
pageSlug: '',
|
|
87
|
+
buttonLinkType: 'page',
|
|
88
|
+
clickActionField: 'url',
|
|
89
|
+
iconUrl: '',
|
|
90
|
+
modalImageUrl: '',
|
|
91
|
+
titleStyle: textStyle,
|
|
92
|
+
styles: { selectedButtonStyleId: 'BUTTON_STYLE_1', style: {} },
|
|
93
|
+
onClicked: vi.fn(),
|
|
94
|
+
...overrides,
|
|
95
|
+
}) as ButtonViewModel;
|
|
96
|
+
|
|
97
|
+
const createTestRouter = () =>
|
|
98
|
+
createRouter({
|
|
99
|
+
history: createMemoryHistory(),
|
|
100
|
+
routes: [
|
|
101
|
+
{
|
|
102
|
+
path: '/:slug/:pageSlug',
|
|
103
|
+
name: 'page-route',
|
|
104
|
+
component: { template: '<div />' },
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
path: '/:slug',
|
|
108
|
+
name: 'home-route',
|
|
109
|
+
component: { template: '<div />' },
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const mountButton = async (currentPath: string) => {
|
|
115
|
+
const router = createTestRouter();
|
|
116
|
+
|
|
117
|
+
router.push(currentPath);
|
|
118
|
+
await router.isReady();
|
|
119
|
+
|
|
120
|
+
const wrapper = mount(Button, {
|
|
121
|
+
propsData: {
|
|
122
|
+
context: createTestBrandPageContext(),
|
|
123
|
+
viewModel: createTestButtonViewModel(),
|
|
124
|
+
},
|
|
125
|
+
global: {
|
|
126
|
+
plugins: [router],
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return { wrapper, router };
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
describe('Button - page link "back to top" behaviour (BP-5834)', () => {
|
|
134
|
+
let scrollToSpy: ReturnType<typeof vi.spyOn>;
|
|
135
|
+
|
|
136
|
+
beforeEach(() => {
|
|
137
|
+
setActivePinia(createPinia());
|
|
138
|
+
scrollToSpy = vi
|
|
139
|
+
.spyOn(window, 'scrollTo')
|
|
140
|
+
.mockImplementation(() => undefined);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
afterEach(() => {
|
|
144
|
+
scrollToSpy.mockRestore();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('scrolls to top when the page-link target is the current route (duplicated navigation is skipped by the router)', async () => {
|
|
148
|
+
// A banner "back to top" button points to the page it lives on. Vue Router
|
|
149
|
+
// dedupes the navigation to the current location and never runs the guard
|
|
150
|
+
// that scrolls to top, so the button must scroll explicitly.
|
|
151
|
+
const { wrapper } = await mountButton(LINKED_PAGE_PATH);
|
|
152
|
+
|
|
153
|
+
const link = wrapper.findComponent(RouterLink);
|
|
154
|
+
|
|
155
|
+
expect(link.exists()).toBe(true);
|
|
156
|
+
|
|
157
|
+
await link.get('a').trigger('click');
|
|
158
|
+
|
|
159
|
+
expect(scrollToSpy).toHaveBeenCalledWith(0, 0);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('does not scroll to top when the page-link target is a different page', async () => {
|
|
163
|
+
// A page-link to another page is a real navigation; the router guard owns
|
|
164
|
+
// scroll-to-top there, so the button must not scroll on its own.
|
|
165
|
+
const { wrapper } = await mountButton(OTHER_PAGE_PATH);
|
|
166
|
+
|
|
167
|
+
const link = wrapper.findComponent(RouterLink);
|
|
168
|
+
|
|
169
|
+
expect(link.exists()).toBe(true);
|
|
170
|
+
|
|
171
|
+
await link.get('a').trigger('click');
|
|
172
|
+
|
|
173
|
+
expect(scrollToSpy).not.toHaveBeenCalled();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
} from '@brand-page-maker/stores';
|
|
38
38
|
import { useHistoryStore } from '@brand-page-maker/stores/history/history-store';
|
|
39
39
|
import { useUndoRedoFlag } from '@brand-page-maker/composables/useUndoRedoFlag';
|
|
40
|
+
import { useQuickLinkBarVisibility } from '@brand-page-maker/composables/useQuickLinkBarVisibility';
|
|
40
41
|
import type { AdminPreviewLogoTemplateModel } from '@brand-page-maker/stores/admin-logo-settings';
|
|
41
42
|
import { useUiContentSettingsStore } from '@shared/content-settings/store';
|
|
42
43
|
import { useUiTemplateSettingsStore } from '@shared/template-settings/store';
|
|
@@ -137,6 +138,10 @@ export default defineComponent({
|
|
|
137
138
|
isSaving: computed(() => brandPageMakerStore.isSaving),
|
|
138
139
|
isPublishing: computed(() => brandPageMakerStore.isPublishing),
|
|
139
140
|
isPublished: computed(() => brandPageMakerStore.isPublished),
|
|
141
|
+
// Whether the QuickLinkBar renders — used to offset the preview (Device)
|
|
142
|
+
// and SideBar clear of the fixed bar on mobile. Must match the bar's own
|
|
143
|
+
// render condition, so it shares the same predicate.
|
|
144
|
+
shouldShowQuickLinkBar: useQuickLinkBarVisibility(),
|
|
140
145
|
isBrandPageOrIsBrandContact: computed(
|
|
141
146
|
() => brandPageMakerStore.isBrandPageOrIsBrandContact,
|
|
142
147
|
),
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
:is-mobile-device="isMobileDevice"
|
|
91
91
|
:is-design-com="isDesignCom"
|
|
92
92
|
:container="previewContainer"
|
|
93
|
-
:has-quick-links="
|
|
93
|
+
:has-quick-links="shouldShowQuickLinkBar"
|
|
94
94
|
>
|
|
95
95
|
<div ref="previewContent" class="tw-h-full">
|
|
96
96
|
<PreviewWithState :device-mode="deviceMode" />
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
<SideBar
|
|
128
128
|
:mobile-tab-name="SideBarTabNames.PREVIEW"
|
|
129
129
|
:is-mobile-screen-size="isMobileScreenSize"
|
|
130
|
-
:has-quick-links="
|
|
130
|
+
:has-quick-links="shouldShowQuickLinkBar"
|
|
131
131
|
>
|
|
132
132
|
<template #tab>
|
|
133
133
|
<component
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
useEventManagerStore,
|
|
6
6
|
} from '@brand-page-maker/stores';
|
|
7
7
|
import { useUndoRedoFlag } from '@brand-page-maker/composables/useUndoRedoFlag';
|
|
8
|
+
import { useQuickLinkBarVisibility } from '@brand-page-maker/composables/useQuickLinkBarVisibility';
|
|
8
9
|
import { quickLinkBarTr } from '@src/composables/useTranslate';
|
|
9
10
|
|
|
10
11
|
export default defineComponent({
|
|
@@ -23,6 +24,9 @@ export default defineComponent({
|
|
|
23
24
|
brandPageContainerStore: useBrandPageContainerStore(),
|
|
24
25
|
eventManagerStore: useEventManagerStore(),
|
|
25
26
|
undoRedoEnabled: useUndoRedoFlag(),
|
|
27
|
+
// Single source of truth for whether the bar renders — shared with the
|
|
28
|
+
// layout components (Device, SideBar) that offset content clear of it.
|
|
29
|
+
shouldShowBar: useQuickLinkBarVisibility(),
|
|
26
30
|
};
|
|
27
31
|
},
|
|
28
32
|
data() {
|
|
@@ -35,13 +39,6 @@ export default defineComponent({
|
|
|
35
39
|
return this.brandPageContainerStore.isPublished;
|
|
36
40
|
},
|
|
37
41
|
|
|
38
|
-
// Only render the bar when at least one inner cluster has something to
|
|
39
|
-
// show. Otherwise we'd render an empty white strip — e.g. on an
|
|
40
|
-
// unpublished page with the undo flag off.
|
|
41
|
-
shouldShowBar(): boolean {
|
|
42
|
-
return Boolean(this.undoRedoEnabled) || this.isPublished;
|
|
43
|
-
},
|
|
44
|
-
|
|
45
42
|
brandPageUrl(): string {
|
|
46
43
|
if (
|
|
47
44
|
this.brandPageContainerStore.isCustomDomainEnabled &&
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import { useQuickLinkBarVisibility } from './useQuickLinkBarVisibility';
|
|
3
|
+
|
|
4
|
+
// The QuickLinkBar renders when it has something to show — the undo/redo
|
|
5
|
+
// controls (flag on, non-admin) OR the published-URL cluster. This predicate
|
|
6
|
+
// is the single source of truth shared by the bar and the layout components
|
|
7
|
+
// (Device, SideBar) that offset content clear of the fixed bar on mobile.
|
|
8
|
+
//
|
|
9
|
+
// The regression it guards: an unpublished page with the undo/redo flag on
|
|
10
|
+
// still shows the bar, so the offset must treat it as visible — otherwise the
|
|
11
|
+
// fixed bar overlaps the mobile preview.
|
|
12
|
+
//
|
|
13
|
+
// The real useUndoRedoFlag is exercised (only its underlying stores are
|
|
14
|
+
// mocked) so admin gating is covered end-to-end.
|
|
15
|
+
|
|
16
|
+
const containerStoreMock = { isPublished: false };
|
|
17
|
+
|
|
18
|
+
vi.mock('@brand-page-maker/stores', () => ({
|
|
19
|
+
useBrandPageContainerStore: vi.fn(() => containerStoreMock),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
const userStoreMock = {
|
|
23
|
+
features: { canUseUndoRedo: false },
|
|
24
|
+
isAdmin: false,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
vi.mock('@brand-page-maker/stores/user', () => ({
|
|
28
|
+
useBrandPageMakerUserStore: vi.fn(() => userStoreMock),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
containerStoreMock.isPublished = false;
|
|
33
|
+
userStoreMock.features.canUseUndoRedo = false;
|
|
34
|
+
userStoreMock.isAdmin = false;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('useQuickLinkBarVisibility', () => {
|
|
38
|
+
it('is visible when unpublished but the undo/redo flag is on (the overlap regression)', () => {
|
|
39
|
+
containerStoreMock.isPublished = false;
|
|
40
|
+
userStoreMock.features.canUseUndoRedo = true;
|
|
41
|
+
|
|
42
|
+
expect(useQuickLinkBarVisibility().value).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('is hidden when unpublished and the undo/redo flag is off', () => {
|
|
46
|
+
containerStoreMock.isPublished = false;
|
|
47
|
+
userStoreMock.features.canUseUndoRedo = false;
|
|
48
|
+
|
|
49
|
+
expect(useQuickLinkBarVisibility().value).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('is visible when published even with the undo/redo flag off', () => {
|
|
53
|
+
containerStoreMock.isPublished = true;
|
|
54
|
+
userStoreMock.features.canUseUndoRedo = false;
|
|
55
|
+
|
|
56
|
+
expect(useQuickLinkBarVisibility().value).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('is visible when both published and the undo/redo flag is on', () => {
|
|
60
|
+
containerStoreMock.isPublished = true;
|
|
61
|
+
userStoreMock.features.canUseUndoRedo = true;
|
|
62
|
+
|
|
63
|
+
expect(useQuickLinkBarVisibility().value).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('gates the undo/redo reason for admins: unpublished admin stays hidden despite the flag', () => {
|
|
67
|
+
containerStoreMock.isPublished = false;
|
|
68
|
+
userStoreMock.features.canUseUndoRedo = true;
|
|
69
|
+
userStoreMock.isAdmin = true;
|
|
70
|
+
|
|
71
|
+
expect(useQuickLinkBarVisibility().value).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('stays visible for a published admin (published cluster is not admin-gated)', () => {
|
|
75
|
+
containerStoreMock.isPublished = true;
|
|
76
|
+
userStoreMock.features.canUseUndoRedo = true;
|
|
77
|
+
userStoreMock.isAdmin = true;
|
|
78
|
+
|
|
79
|
+
expect(useQuickLinkBarVisibility().value).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { computed, type ComputedRef } from 'vue';
|
|
2
|
+
import { useBrandPageContainerStore } from '@brand-page-maker/stores';
|
|
3
|
+
import { useUndoRedoFlag } from './useUndoRedoFlag';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Whether the QuickLinkBar is rendered in the maker.
|
|
7
|
+
*
|
|
8
|
+
* The bar shows when at least one of its inner clusters has something to
|
|
9
|
+
* display: the undo/redo controls (flag on, non-admin — see
|
|
10
|
+
* {@link useUndoRedoFlag}) OR the published-URL cluster (page published).
|
|
11
|
+
*
|
|
12
|
+
* This is the single source of truth shared by the bar itself
|
|
13
|
+
* (`QuickLinkBar`) and the layout components that offset their content to sit
|
|
14
|
+
* clear of the fixed bar on mobile (`Device`, `SideBar`). Keeping the offset
|
|
15
|
+
* bound to the same predicate the bar renders on prevents the two from
|
|
16
|
+
* drifting — a drift where the bar showed for the undo/redo reason but the
|
|
17
|
+
* offset only accounted for the published reason caused the bar to overlap
|
|
18
|
+
* the mobile preview.
|
|
19
|
+
*/
|
|
20
|
+
export function useQuickLinkBarVisibility(): ComputedRef<boolean> {
|
|
21
|
+
const containerStore = useBrandPageContainerStore();
|
|
22
|
+
const undoRedoEnabled = useUndoRedoFlag();
|
|
23
|
+
|
|
24
|
+
return computed(() => undoRedoEnabled.value || containerStore.isPublished);
|
|
25
|
+
}
|