Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .changeset/fix-banner-content-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
'@prosdevlab/experience-sdk': patch
'@prosdevlab/experience-sdk-plugins': patch
---

Fix BannerContent type definition, add CSS customization support, and implement HTML sanitization:

- Add `buttons` array property with variant and metadata support
- Add `position` property (top/bottom)
- Make `title` optional (message is the only required field)
- Add `className` and `style` props for banner and buttons
- Update banner plugin to use `.xp-*` CSS classes
- Provide minimal, functional default styles
- Add HTML sanitizer for XSS prevention in title and message fields
- Support safe HTML tags (strong, em, a, br, span, b, i, p)
- Block dangerous tags and event handlers
- Sanitize URLs to prevent javascript: and data: attacks
- Aligns core types with banner plugin implementation

This enables users to customize banners with Tailwind, design systems, or CSS frameworks while maintaining SDK's focus on targeting logic. HTML sanitization ensures safe rendering of user-provided content.

40 changes: 39 additions & 1 deletion docs/pages/demo/banner.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ experiences.register('welcome-banner', {
url: { contains: '/' }
},
content: {
title: '👋 Welcome!',
title: 'Welcome!',
message: 'Thanks for visiting our site',
buttons: [
{
Expand Down Expand Up @@ -115,6 +115,8 @@ const decisions = experiences.evaluateAll();
| `buttons` | `array` | Array of button configurations |
| `dismissable` | `boolean` | Can user dismiss? (default: `true`) |
| `position` | `'top' \| 'bottom'` | Banner position (default: `'top'`) |
| `className` | `string` | Custom CSS class for the banner |
| `style` | `Record<string, string>` | Inline styles for the banner |

### Button Options

Expand All @@ -125,6 +127,8 @@ buttons: [{
url?: string; // Navigate on click
variant?: 'primary' | 'secondary' | 'link'; // Visual style (default: 'primary')
metadata?: Record<string, any>; // Custom metadata
className?: string; // Custom CSS class
style?: Record<string, string>; // Inline styles
}]
```

Expand Down Expand Up @@ -159,6 +163,40 @@ targeting: {
}
```

## Customization

Customize banners with your own CSS using `className` or `style` props:

```typescript
// With Tailwind classes
experiences.register('promo', {
type: 'banner',
content: {
message: 'Flash Sale: 50% Off!',
className: 'bg-gradient-to-r from-blue-600 to-purple-600 text-white',
buttons: [{
text: 'Shop Now',
className: 'bg-white text-blue-600 hover:bg-gray-100'
}]
}
});

// With inline styles
experiences.register('custom', {
type: 'banner',
content: {
message: 'Custom styled banner',
style: {
background: 'linear-gradient(90deg, #667eea 0%, #764ba2 100%)',
color: 'white',
padding: '24px'
}
}
});
```

The banner plugin uses stable `.xp-*` CSS classes that you can target in your stylesheets. See the [Plugins documentation](/reference/plugins#customization) for complete customization guide.

## Events

Listen to banner interactions:
Expand Down
99 changes: 99 additions & 0 deletions docs/pages/reference/plugins.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,105 @@ experiences.on('experiences:dismissed', ({ experienceId }) => {

See [Banner Examples](/demo/banner) for complete usage examples.

#### Customization

The Experience SDK focuses on **targeting logic**, not visual design. The banner plugin provides minimal, functional default styles that you can customize using CSS.

**CSS Classes**

The banner plugin uses the `.xp-*` namespace for all CSS classes:

- `.xp-banner` - Main container
- `.xp-banner--top` - Top positioned banner
- `.xp-banner--bottom` - Bottom positioned banner
- `.xp-banner__container` - Inner wrapper
- `.xp-banner__content` - Content section
- `.xp-banner__title` - Optional title
- `.xp-banner__message` - Main message text
- `.xp-banner__buttons` - Buttons container
- `.xp-banner__button` - Individual button
- `.xp-banner__button--primary` - Primary button variant
- `.xp-banner__button--secondary` - Secondary button variant
- `.xp-banner__button--link` - Link button variant
- `.xp-banner__close` - Close button

**Use Case 1: User with Tailwind**

Add Tailwind classes via the `className` property:

```typescript
experiences.register('flash-sale', {
type: 'banner',
content: {
message: 'Flash Sale: 50% Off Everything!',
className: 'bg-gradient-to-r from-blue-600 to-purple-600 text-white',
buttons: [{
text: 'Shop Now',
url: '/shop',
variant: 'primary',
className: 'bg-white text-blue-600 hover:bg-gray-100'
}]
},
targeting: { url: { contains: '/shop' } }
});
```

**Use Case 2: User with Design System**

Build your own plugin using your design system components:

```typescript
import { MyBannerComponent } from '@your-org/design-system';

const myBannerPlugin: PluginFunction = (plugin, instance, config) => {
instance.on('experiences:evaluated', ({ decision, experience }) => {
if (decision.show && experience.type === 'banner') {
// Render using your React component
ReactDOM.render(
<MyBannerComponent {...experience.content} />,
document.getElementById('banner-root')
);
}
});
};

experiences.use(myBannerPlugin);
```

**Use Case 3: User with CSS Framework**

Add Bootstrap, Material UI, or other framework classes:

```typescript
experiences.register('alert', {
type: 'banner',
content: {
message: 'Important notice',
className: 'alert alert-warning',
buttons: [{
text: 'Learn More',
className: 'btn btn-primary'
}]
},
targeting: {}
});
```

**Inline Styles**

For quick overrides, use the `style` property:

```typescript
content: {
message: 'Flash Sale!',
style: {
background: 'linear-gradient(90deg, #667eea 0%, #764ba2 100%)',
color: 'white',
padding: '24px'
}
}
```

---

### Frequency Plugin
Expand Down
50 changes: 11 additions & 39 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,52 +78,24 @@ export interface FrequencyConfig {
per: 'session' | 'day' | 'week';
}

// Import plugin-specific content types from plugins package
// (Core depends on plugins, so plugins owns these definitions)
import type {
BannerContent,
ModalContent,
TooltipContent,
} from '@prosdevlab/experience-sdk-plugins';

/**
* Experience Content (type-specific)
*
* Union type for all possible experience content types.
* Content types are defined in the plugins package.
*/
export type ExperienceContent = BannerContent | ModalContent | TooltipContent;

/**
* Banner Content
*
* Content for banner-type experiences.
*/
export interface BannerContent {
/** Banner title/heading */
title: string;
/** Banner message/body text */
message: string;
/** Whether the banner can be dismissed */
dismissable?: boolean;
}

/**
* Modal Content
*
* Content for modal-type experiences.
*/
export interface ModalContent {
/** Modal title */
title: string;
/** Modal body content */
body: string;
/** Optional action buttons */
actions?: ModalAction[];
}

/**
* Tooltip Content
*
* Content for tooltip-type experiences.
*/
export interface TooltipContent {
/** Tooltip text */
text: string;
/** Position relative to target element */
position?: 'top' | 'bottom' | 'left' | 'right';
}
// Re-export plugin content types for convenience
export type { BannerContent, ModalContent, TooltipContent };

/**
* Modal Action Button
Expand Down
29 changes: 29 additions & 0 deletions packages/plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Renders banner experiences in the DOM with automatic positioning, theming, and r
- Automatic theme detection (light/dark mode)
- Top/bottom positioning
- Dismissable with close button
- **CSS customization** via `className` and `style` props
- Stable `.xp-*` CSS classes for styling

```typescript
import { createInstance, bannerPlugin } from '@prosdevlab/experience-sdk-plugins';
Expand All @@ -38,6 +40,33 @@ sdk.banner.show({
});
```

**Customization:**

The banner plugin uses `.xp-*` CSS classes and supports custom styling:

```typescript
// With Tailwind
content: {
message: 'Flash Sale!',
className: 'bg-gradient-to-r from-blue-600 to-purple-600 text-white',
buttons: [{
text: 'Shop Now',
className: 'bg-white text-blue-600 hover:bg-gray-100'
}]
}

// With inline styles
content: {
message: 'Flash Sale!',
style: {
background: 'linear-gradient(90deg, #667eea 0%, #764ba2 100%)',
color: 'white'
}
}
```

See the [Plugins documentation](https://prosdevlab.github.io/experience-sdk/reference/plugins#customization) for more customization examples.

### Frequency Plugin

Manages impression tracking and frequency capping with persistent storage.
Expand Down
Loading