@prosdevlab/experience-sdk-plugins 0.1.3 → 0.2.0

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.
@@ -14,6 +14,7 @@ export interface BannerPluginConfig {
14
14
  position?: 'top' | 'bottom';
15
15
  dismissable?: boolean;
16
16
  zIndex?: number;
17
+ pushDown?: string; // CSS selector of element to push down (add margin-top)
17
18
  };
18
19
  }
19
20
 
@@ -33,7 +34,23 @@ export interface BannerPlugin {
33
34
  * import { createInstance } from '@prosdevlab/experience-sdk';
34
35
  * import { bannerPlugin } from '@prosdevlab/experience-sdk-plugins';
35
36
  *
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
+ * });
37
54
  * sdk.use(bannerPlugin);
38
55
  * ```
39
56
  */
@@ -69,16 +86,12 @@ export const bannerPlugin: PluginFunction = (plugin, instance, config) => {
69
86
  left: 0;
70
87
  right: 0;
71
88
  width: 100%;
72
- padding: 16px 20px;
73
89
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
74
90
  font-size: 14px;
75
91
  line-height: 1.5;
76
- display: flex;
77
- align-items: center;
78
- justify-content: space-between;
79
92
  box-sizing: border-box;
80
93
  z-index: 10000;
81
- background: #f9fafb;
94
+ background: #ffffff;
82
95
  color: #111827;
83
96
  border-bottom: 1px solid #e5e7eb;
84
97
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.05);
@@ -98,33 +111,38 @@ export const bannerPlugin: PluginFunction = (plugin, instance, config) => {
98
111
  .xp-banner__container {
99
112
  display: flex;
100
113
  align-items: center;
101
- justify-content: space-between;
102
- gap: 20px;
103
- width: 100%;
114
+ gap: 16px;
115
+ max-width: 1280px;
116
+ margin: 0 auto;
117
+ padding: 14px 24px;
104
118
  }
105
119
 
106
120
  .xp-banner__content {
107
121
  flex: 1;
108
122
  min-width: 0;
123
+ display: flex;
124
+ flex-direction: column;
125
+ gap: 4px;
109
126
  }
110
127
 
111
128
  .xp-banner__title {
112
129
  font-weight: 600;
113
- margin-bottom: 4px;
114
- margin-top: 0;
115
- font-size: 14px;
130
+ margin: 0;
131
+ font-size: 15px;
132
+ line-height: 1.4;
116
133
  }
117
134
 
118
135
  .xp-banner__message {
119
136
  margin: 0;
120
137
  font-size: 14px;
138
+ line-height: 1.5;
139
+ color: #6b7280;
121
140
  }
122
141
 
123
142
  .xp-banner__buttons {
124
143
  display: flex;
125
144
  align-items: center;
126
- gap: 12px;
127
- flex-wrap: wrap;
145
+ gap: 8px;
128
146
  flex-shrink: 0;
129
147
  }
130
148
 
@@ -137,6 +155,10 @@ export const bannerPlugin: PluginFunction = (plugin, instance, config) => {
137
155
  cursor: pointer;
138
156
  transition: all 0.2s;
139
157
  text-decoration: none;
158
+ display: inline-flex;
159
+ align-items: center;
160
+ justify-content: center;
161
+ white-space: nowrap;
140
162
  }
141
163
 
142
164
  .xp-banner__button--primary {
@@ -149,71 +171,93 @@ export const bannerPlugin: PluginFunction = (plugin, instance, config) => {
149
171
  }
150
172
 
151
173
  .xp-banner__button--secondary {
152
- background: #ffffff;
174
+ background: #f3f4f6;
153
175
  color: #374151;
154
- border: 1px solid #d1d5db;
176
+ border: 1px solid #e5e7eb;
155
177
  }
156
178
 
157
179
  .xp-banner__button--secondary:hover {
158
- background: #f9fafb;
180
+ background: #e5e7eb;
159
181
  }
160
182
 
161
183
  .xp-banner__button--link {
162
184
  background: transparent;
163
185
  color: #2563eb;
164
- padding: 4px 8px;
186
+ padding: 6px 12px;
165
187
  font-weight: 400;
166
- text-decoration: underline;
167
188
  }
168
189
 
169
190
  .xp-banner__button--link:hover {
170
- background: rgba(0, 0, 0, 0.05);
191
+ background: #f3f4f6;
192
+ text-decoration: underline;
171
193
  }
172
194
 
173
195
  .xp-banner__close {
174
196
  background: transparent;
175
197
  border: none;
176
- color: #6b7280;
177
- font-size: 24px;
198
+ color: #9ca3af;
199
+ font-size: 20px;
178
200
  line-height: 1;
179
201
  cursor: pointer;
180
- padding: 0;
202
+ padding: 4px;
181
203
  margin: 0;
182
- opacity: 0.7;
183
- transition: opacity 0.2s;
204
+ transition: color 0.2s;
184
205
  flex-shrink: 0;
206
+ width: 28px;
207
+ height: 28px;
208
+ display: flex;
209
+ align-items: center;
210
+ justify-content: center;
211
+ border-radius: 4px;
185
212
  }
186
213
 
187
214
  .xp-banner__close:hover {
188
- opacity: 1;
215
+ color: #111827;
216
+ background: #f3f4f6;
189
217
  }
190
218
 
191
219
  @media (max-width: 640px) {
192
220
  .xp-banner__container {
193
- flex-direction: column;
194
- align-items: stretch;
221
+ flex-wrap: wrap;
222
+ padding: 14px 16px;
223
+ position: relative;
224
+ }
225
+
226
+ .xp-banner__content {
227
+ flex: 1 1 100%;
228
+ padding-right: 32px;
195
229
  }
196
230
 
197
231
  .xp-banner__buttons {
232
+ flex: 1 1 auto;
198
233
  width: 100%;
199
- flex-direction: column;
200
234
  }
201
235
 
202
236
  .xp-banner__button {
203
- width: 100%;
237
+ flex: 1;
238
+ }
239
+
240
+ .xp-banner__close {
241
+ position: absolute;
242
+ top: 12px;
243
+ right: 12px;
204
244
  }
205
245
  }
206
246
 
207
247
  /* Dark mode support */
208
248
  @media (prefers-color-scheme: dark) {
209
249
  .xp-banner {
210
- background: #1f2937;
211
- color: #f3f4f6;
212
- border-bottom-color: #374151;
250
+ background: #111827;
251
+ color: #f9fafb;
252
+ border-bottom-color: #1f2937;
213
253
  }
214
254
 
215
255
  .xp-banner--bottom {
216
- border-top-color: #374151;
256
+ border-top-color: #1f2937;
257
+ }
258
+
259
+ .xp-banner__message {
260
+ color: #9ca3af;
217
261
  }
218
262
 
219
263
  .xp-banner__button--primary {
@@ -225,21 +269,30 @@ export const bannerPlugin: PluginFunction = (plugin, instance, config) => {
225
269
  }
226
270
 
227
271
  .xp-banner__button--secondary {
228
- background: #374151;
229
- color: #f3f4f6;
230
- border-color: #4b5563;
272
+ background: #1f2937;
273
+ color: #f9fafb;
274
+ border-color: #374151;
231
275
  }
232
276
 
233
277
  .xp-banner__button--secondary:hover {
234
- background: #4b5563;
278
+ background: #374151;
235
279
  }
236
280
 
237
281
  .xp-banner__button--link {
238
- color: #93c5fd;
282
+ color: #60a5fa;
283
+ }
284
+
285
+ .xp-banner__button--link:hover {
286
+ background: #1f2937;
239
287
  }
240
288
 
241
289
  .xp-banner__close {
242
- color: #9ca3af;
290
+ color: #6b7280;
291
+ }
292
+
293
+ .xp-banner__close:hover {
294
+ color: #f9fafb;
295
+ background: #1f2937;
243
296
  }
244
297
  }
245
298
  `;
@@ -307,17 +360,6 @@ export const bannerPlugin: PluginFunction = (plugin, instance, config) => {
307
360
 
308
361
  container.appendChild(contentDiv);
309
362
 
310
- banner.appendChild(contentDiv);
311
-
312
- // Create button container for actions and/or dismiss
313
- const buttonContainer = document.createElement('div');
314
- buttonContainer.style.cssText = `
315
- display: flex;
316
- align-items: center;
317
- gap: 12px;
318
- flex-wrap: wrap;
319
- `;
320
-
321
363
  // Create buttons container
322
364
  const buttonsDiv = document.createElement('div');
323
365
  buttonsDiv.className = 'xp-banner__buttons';
@@ -401,6 +443,49 @@ export const bannerPlugin: PluginFunction = (plugin, instance, config) => {
401
443
  return banner;
402
444
  }
403
445
 
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
+
404
489
  /**
405
490
  * Show a banner experience
406
491
  */
@@ -419,6 +504,11 @@ export const bannerPlugin: PluginFunction = (plugin, instance, config) => {
419
504
  document.body.appendChild(banner);
420
505
  activeBanners.set(experience.id, banner);
421
506
 
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
+
422
512
  instance.emit('experiences:shown', {
423
513
  experienceId: experience.id,
424
514
  type: 'banner',
@@ -437,6 +527,11 @@ export const bannerPlugin: PluginFunction = (plugin, instance, config) => {
437
527
  banner.parentNode.removeChild(banner);
438
528
  }
439
529
  activeBanners.delete(experienceId);
530
+
531
+ // Remove pushDown if no more banners
532
+ if (activeBanners.size === 0) {
533
+ removePushDown();
534
+ }
440
535
  } else {
441
536
  // Remove all banners
442
537
  for (const [id, banner] of activeBanners.entries()) {
@@ -445,6 +540,9 @@ export const bannerPlugin: PluginFunction = (plugin, instance, config) => {
445
540
  }
446
541
  activeBanners.delete(id);
447
542
  }
543
+
544
+ // Remove pushDown
545
+ removePushDown();
448
546
  }
449
547
  }
450
548