@twenty1tools/whatif
v0.2.0
Published
What If? SDK for collecting contextual user insights
Maintainers
Readme
WhatIf Widget SDK
A lightweight, customizable SDK for collecting contextual user feedback through beautiful widgets.
Installation
npm install @twenty1tools/whatifBasic Usage
import { WhatIf } from '@twenty1tools/whatif';
// Initialize the SDK
const whatif = new WhatIf('your-project-id');
// Show a question
await whatif.ask('question-id-123');Theming & Customization
The SDK supports extensive theming options to match your website's design. All theme properties are optional and have sensible defaults.
Theme Configuration
import { WhatIf, type ThemeConfig } from '@twenty1tools/whatif';
const theme: ThemeConfig = {
// Dark mode configuration
darkMode: 'auto', // 'auto' | 'light' | 'dark' | 'disabled'
// Widget position
position: 'bottom-right', // 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
// Colors
colors: {
primary: '#f97316',
primaryHover: '#ea580c',
background: '#ffffff',
textPrimary: '#111827',
textSecondary: '#6b7280',
border: '#e5e7eb',
borderHover: '#f97316',
hoverBackground: '#f9fafb',
closeButton: '#6b7280',
closeButtonHover: '#111827',
ratingFilled: '#fbbf24',
ratingEmpty: '#d1d5db',
},
// Typography
typography: {
fontFamily: 'Inter, system-ui, sans-serif',
questionSize: '18px',
questionWeight: '600',
inputSize: '14px',
buttonSize: '14px',
buttonWeight: '500',
},
// Spacing
spacing: {
padding: '24px',
gap: '16px',
offset: '20px', // Distance from screen edge
},
// Borders
border: {
radius: '16px',
buttonRadius: '8px',
inputRadius: '6px',
width: '1px',
},
// Shadow
shadow: {
enabled: true,
container: '0 10px 40px rgba(0,0,0,0.15)',
},
// Animations
animation: {
enabled: true,
duration: 400, // milliseconds
easing: 'cubic-bezier(0.16, 1, 0.3, 1)',
},
// Custom CSS (injected into shadow DOM)
customCss: `
.widget-container {
border: 2px solid #f97316;
}
`,
};
const whatif = new WhatIf('your-project-id', '/api', theme);Dark Mode
The SDK supports three dark mode strategies:
Auto Mode (Recommended)
Automatically detects and respects the user's system preference:
const whatif = new WhatIf('your-project-id', '/api', {
darkMode: 'auto',
});Force Light Mode
const whatif = new WhatIf('your-project-id', '/api', {
darkMode: 'light',
});Force Dark Mode
const whatif = new WhatIf('your-project-id', '/api', {
darkMode: 'dark',
colors: {
// Dark mode colors are automatically applied
// but you can override them
primary: '#fb923c',
background: '#1f2937',
textPrimary: '#f9fafb',
},
});Disabled (Default)
const whatif = new WhatIf('your-project-id', '/api', {
darkMode: 'disabled', // or omit entirely
});Common Use Cases
Match Your Brand Colors
const whatif = new WhatIf('your-project-id', '/api', {
colors: {
primary: '#3b82f6', // Blue
primaryHover: '#2563eb',
},
});Minimal Design
const whatif = new WhatIf('your-project-id', '/api', {
shadow: {
enabled: false,
},
border: {
radius: '4px',
buttonRadius: '4px',
},
colors: {
primary: '#000000',
primaryHover: '#333333',
},
});Playful Design
const whatif = new WhatIf('your-project-id', '/api', {
border: {
radius: '24px',
buttonRadius: '24px',
},
colors: {
primary: '#ec4899', // Pink
primaryHover: '#db2777',
ratingFilled: '#f472b6',
},
animation: {
duration: 600,
easing: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)', // Bouncy
},
});Corporate/Professional
const whatif = new WhatIf('your-project-id', '/api', {
colors: {
primary: '#1e40af', // Navy blue
primaryHover: '#1e3a8a',
background: '#ffffff',
},
typography: {
fontFamily: '"Helvetica Neue", Arial, sans-serif',
},
border: {
radius: '8px',
},
shadow: {
container: '0 4px 12px rgba(0,0,0,0.08)',
},
});Dynamic Theming
You can update the theme at runtime:
const whatif = new WhatIf('your-project-id');
// Later, update theme
whatif.setTheme({
darkMode: 'dark',
colors: {
primary: '#10b981', // Green
},
});Widget Positioning
Control where the widget appears on screen:
// Bottom right (default)
const whatif = new WhatIf('your-project-id', '/api', {
position: 'bottom-right',
});
// Bottom left
const whatif = new WhatIf('your-project-id', '/api', {
position: 'bottom-left',
});
// Top right
const whatif = new WhatIf('your-project-id', '/api', {
position: 'top-right',
});
// Top left
const whatif = new WhatIf('your-project-id', '/api', {
position: 'top-left',
});Advanced: Custom CSS
For complete control, you can inject custom CSS into the shadow DOM:
const whatif = new WhatIf('your-project-id', '/api', {
customCss: `
/* Target widget container */
.widget-container {
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.9) !important;
}
/* Style buttons */
button[type="submit"] {
text-transform: uppercase;
letter-spacing: 1px;
}
/* Custom animations */
@keyframes customPulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.star-svg {
animation: customPulse 2s infinite;
}
`,
});TypeScript Support
The SDK is fully typed with TypeScript. All theme options include JSDoc comments for IntelliSense:
import { WhatIf, type ThemeConfig, type DarkMode, type ColorPalette } from '@twenty1tools/whatif';
const colors: ColorPalette = {
primary: '#3b82f6',
// TypeScript will autocomplete and validate
};
const darkMode: DarkMode = 'auto'; // Only accepts valid valuesAPI Reference
Constructor
new WhatIf(projectId: string, baseUrl?: string, theme?: ThemeConfig)projectId: Your project IDbaseUrl: API base URL (default:/api)theme: Theme configuration object
Methods
ask(questionId: string, options?: AskOptions): Promise<void>
Display a question widget.
await whatif.ask('question-123', {
context: { page: 'checkout' },
userFingerprint: 'user-abc',
});setTheme(theme: ThemeConfig): void
Update the theme configuration.
whatif.setTheme({ darkMode: 'dark' });remove(): void
Manually close the current widget.
whatif.remove();destroy(): void
Clean up all resources (useful when unmounting).
whatif.destroy();Examples
React Hook
import { useEffect, useRef } from 'react';
import { WhatIf } from '@twenty1tools/whatif';
function useWhatIf(projectId: string, theme?: ThemeConfig) {
const whatifRef = useRef<WhatIf>();
useEffect(() => {
whatifRef.current = new WhatIf(projectId, '/api', theme);
return () => {
whatifRef.current?.destroy();
};
}, [projectId]);
return whatifRef.current;
}
// Usage
function MyComponent() {
const whatif = useWhatIf('your-project-id', {
darkMode: 'auto',
colors: { primary: '#3b82f6' },
});
const handleFeedback = () => {
whatif?.ask('question-123');
};
return <button onClick={handleFeedback}>Give Feedback</button>;
}Next.js App Router
'use client';
import { useEffect } from 'react';
import { WhatIf } from '@twenty1tools/whatif';
export default function WhatIfProvider() {
useEffect(() => {
const whatif = new WhatIf('your-project-id', '/api', {
darkMode: 'auto',
});
// Trigger on specific events
window.addEventListener('whatif:show', (e: CustomEvent) => {
whatif.ask(e.detail.questionId);
});
return () => whatif.destroy();
}, []);
return null;
}Vanilla JavaScript
<!DOCTYPE html>
<html>
<head>
<script type="module">
import { WhatIf } from 'https://unpkg.com/@twenty1tools/whatif';
const whatif = new WhatIf('your-project-id', '/api', {
darkMode: 'auto',
colors: {
primary: '#10b981',
},
});
// Show widget on button click
document.getElementById('feedback-btn').addEventListener('click', () => {
whatif.ask('question-123');
});
</script>
</head>
<body>
<button id="feedback-btn">Give Feedback</button>
</body>
</html>Browser Support
- Chrome/Edge: Latest 2 versions
- Firefox: Latest 2 versions
- Safari: Latest 2 versions
- Mobile browsers: iOS Safari, Chrome Android
License
MIT
