Skip to content

Commit df2c286

Browse files
committed
feat(banner): add pushDown option to push content down
Add optional pushDown config to banner plugin that allows top banners to smoothly push page content down (add margin-top) instead of overlaying. Usage: init({ banner: { position: 'top', pushDown: 'header' // CSS selector } }); - Opt-in feature (default behavior unchanged) - Smooth transition with CSS animations - Automatically removes margin when dismissed
1 parent c0b440c commit df2c286

File tree

2 files changed

+98
-1
lines changed

2 files changed

+98
-1
lines changed

.changeset/feat-banner-pushdown.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
'@prosdevlab/experience-sdk-plugins': patch
3+
---
4+
5+
feat(banner): add pushDown option to push content down instead of overlay
6+
7+
Add optional `pushDown` config to banner plugin that allows top banners to smoothly push page content down (add margin-top) instead of overlaying it.
8+
9+
**Usage:**
10+
```typescript
11+
init({
12+
banner: {
13+
position: 'top',
14+
pushDown: 'header' // CSS selector of element to push down
15+
}
16+
});
17+
```
18+
19+
**Benefits:**
20+
- Opt-in feature (default behavior unchanged)
21+
- Smooth transition with CSS animations
22+
- Improves UX for sticky navigation
23+
- Automatically removes margin when banner is dismissed
24+

packages/plugins/src/banner/banner.ts

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface BannerPluginConfig {
1414
position?: 'top' | 'bottom';
1515
dismissable?: boolean;
1616
zIndex?: number;
17+
pushDown?: string; // CSS selector of element to push down (add margin-top)
1718
};
1819
}
1920

@@ -33,7 +34,23 @@ export interface BannerPlugin {
3334
* import { createInstance } from '@prosdevlab/experience-sdk';
3435
* import { bannerPlugin } from '@prosdevlab/experience-sdk-plugins';
3536
*
36-
* const sdk = createInstance({ banner: { position: 'top', dismissable: true } });
37+
* // Basic usage (banner overlays at top)
38+
* const sdk = createInstance({
39+
* banner: {
40+
* position: 'top',
41+
* dismissable: true
42+
* }
43+
* });
44+
* sdk.use(bannerPlugin);
45+
*
46+
* // With pushDown (pushes navigation down instead of overlaying)
47+
* const sdk = createInstance({
48+
* banner: {
49+
* position: 'top',
50+
* dismissable: true,
51+
* pushDown: 'header' // CSS selector of element to push down
52+
* }
53+
* });
3754
* sdk.use(bannerPlugin);
3855
* ```
3956
*/
@@ -426,6 +443,49 @@ export const bannerPlugin: PluginFunction = (plugin, instance, config) => {
426443
return banner;
427444
}
428445

446+
/**
447+
* Apply pushDown margin to target element
448+
*/
449+
function applyPushDown(banner: HTMLElement, position: 'top' | 'bottom'): void {
450+
const pushDownSelector = config.get('banner.pushDown');
451+
452+
if (!pushDownSelector || position !== 'top') {
453+
return; // Only push down for top banners
454+
}
455+
456+
const targetElement = document.querySelector(pushDownSelector);
457+
if (!targetElement || !(targetElement instanceof HTMLElement)) {
458+
return;
459+
}
460+
461+
// Get banner height
462+
const height = banner.offsetHeight;
463+
464+
// Apply margin-top with transition
465+
targetElement.style.transition = 'margin-top 0.3s ease';
466+
targetElement.style.marginTop = `${height}px`;
467+
}
468+
469+
/**
470+
* Remove pushDown margin from target element
471+
*/
472+
function removePushDown(): void {
473+
const pushDownSelector = config.get('banner.pushDown');
474+
475+
if (!pushDownSelector) {
476+
return;
477+
}
478+
479+
const targetElement = document.querySelector(pushDownSelector);
480+
if (!targetElement || !(targetElement instanceof HTMLElement)) {
481+
return;
482+
}
483+
484+
// Remove margin-top with transition
485+
targetElement.style.transition = 'margin-top 0.3s ease';
486+
targetElement.style.marginTop = '0';
487+
}
488+
429489
/**
430490
* Show a banner experience
431491
*/
@@ -444,6 +504,11 @@ export const bannerPlugin: PluginFunction = (plugin, instance, config) => {
444504
document.body.appendChild(banner);
445505
activeBanners.set(experience.id, banner);
446506

507+
// Apply pushDown to target element if configured
508+
const content = experience.content as BannerContent;
509+
const position = content.position ?? config.get('banner.position') ?? 'top';
510+
applyPushDown(banner, position);
511+
447512
instance.emit('experiences:shown', {
448513
experienceId: experience.id,
449514
type: 'banner',
@@ -462,6 +527,11 @@ export const bannerPlugin: PluginFunction = (plugin, instance, config) => {
462527
banner.parentNode.removeChild(banner);
463528
}
464529
activeBanners.delete(experienceId);
530+
531+
// Remove pushDown if no more banners
532+
if (activeBanners.size === 0) {
533+
removePushDown();
534+
}
465535
} else {
466536
// Remove all banners
467537
for (const [id, banner] of activeBanners.entries()) {
@@ -470,6 +540,9 @@ export const bannerPlugin: PluginFunction = (plugin, instance, config) => {
470540
}
471541
activeBanners.delete(id);
472542
}
543+
544+
// Remove pushDown
545+
removePushDown();
473546
}
474547
}
475548

0 commit comments

Comments
 (0)