Demonstrates how to integrate Contact Center widgets into a React application. Use this as a reference when adding new widgets.
- Framework: React 18.3.1 + TypeScript
- UI Library: Momentum Design System (
@momentum-design/components) - State: MobX store (
@webex/cc-store) - Theme: Dark/Light mode toggle (persisted in localStorage)
- Layout: CSS Grid with
.box,.section-box,.fieldsetstructure
Follow this exact pattern for ALL new widgets:
import {NewWidget} from '@webex/cc-widgets';const defaultWidgets = {
stationLogin: true,
userState: true,
// ... existing widgets
newWidget: false, // ← Add here (false by default for user opt-in)
};<Checkbox
onChange={handleCheckboxChange}
name="newWidget"
checked={selectedWidgets.newWidget}
htmlId="newWidget-checkbox"
aria-label="new widget checkbox"
>
<Text>New Widget</Text>
</Checkbox>{
selectedWidgets.newWidget && (
<div className="box">
<section className="section-box">
<fieldset className="fieldset">
<legend className="legend-box">New Widget</legend>
<NewWidget onEvent={(data) => handleNewWidgetEvent(data)} onError={(error) => onError('NewWidget', error)} />
</fieldset>
</section>
</div>
);
}const handleNewWidgetCallback = (data) => {
console.log('New widget callback:', data);
// Handle callback logic
};<div className="box">
{' '}
{/* Outer container with background */}
<section className="section-box">
{' '}
{/* Inner section with padding */}
<fieldset className="fieldset">
{' '}
{/* Fieldset for grouping */}
<legend className="legend-box">Title</legend> {/* Title/legend */}
<WidgetComponent /> {/* Actual widget */}
</fieldset>
</section>
</div>box- Consistent spacing and backgroundsection-box- Momentum Design paddingfieldset- Semantic groupinglegend-box- Styled title- Result: Visual consistency across all widgets
// Colors
var(--mds-color-theme-text-primary-normal)
var(--mds-color-theme-background-solid-primary-normal)
var(--mds-color-theme-background-primary-normal)
// Spacing
var(--mds-spacing-1) // 0.25rem (4px)
var(--mds-spacing-2) // 0.5rem (8px)
var(--mds-spacing-3) // 1rem (16px)
var(--mds-spacing-4) // 1.5rem (24px)
// Typography
var(--mds-font-size-body-small)
var(--mds-font-size-body-medium)<div style={{color: '#FF0000'}}> // Hardcoded color
<div style={{padding: '10px'}}> // Hardcoded spacing<div style={{
color: 'var(--mds-color-theme-text-primary-normal)',
padding: 'var(--mds-spacing-3)'
}}>const onError = (widgetName: string, error: Error) => {
console.error(`${widgetName} error:`, error);
// Optional: Show toast notification
setToast({type: 'error'});
};EVERY widget MUST have onError callback.
// IncomingTask
const onIncomingTaskCB = ({task}) => {
console.log('Incoming task:', task);
setIncomingTasks((prev) => [...prev, task]);
playNotificationSound(); // Custom logic
};
// UserState
const onAgentStateChangedCB = (newState: AgentState, oldState: AgentState) => {
console.log('State changed from', oldState, 'to', newState);
setSelectedState(newState);
};
// CallControl
const onRecordingToggleCB = ({isRecording, task}) => {
console.log('Recording:', isRecording, 'for task:', task.data.interactionId);
};Theme is controlled by @momentum-ui's ThemeProvider:
import {ThemeProvider} from '@momentum-design/components/dist/react';
<ThemeProvider
themeclass={store.currentTheme === 'LIGHT' ? 'mds-theme-stable-lightWebex' : 'mds-theme-stable-darkWebex'}
>
{/* Your widgets */}
</ThemeProvider>;- Theme provider:
@momentum-uilibrary manages the theme throughThemeProvider - Theme storage:
store.currentThemestores the theme as a string ('LIGHT' or 'DARK') - Theme classes: Theme class names (
mds-theme-stable-lightWebex/mds-theme-stable-darkWebex) are passed to ThemeProvider - Automatic updates: All widgets automatically respond to theme changes through the ThemeProvider context
User can toggle theme via UI checkbox - widgets update automatically through the provider.
// Access store for global state
import {store} from '@webex/cc-widgets';
// Examples:
store.currentTask; // Current active task
store.taskList; // All tasks
store.incomingTask; // Incoming task
store.agentState; // Current agent state// UI-only state (no widget dependency)
const [showPopup, setShowPopup] = useState(false);
const [selectedOption, setSelectedOption] = useState('');// 1. Import
import {NewAwesomeWidget} from '@webex/cc-widgets';
// 2. Add to defaultWidgets
const defaultWidgets = {
// ... existing
newAwesomeWidget: false,
};
// 3. Checkbox in widget selector
<Checkbox
onChange={handleCheckboxChange}
name="newAwesomeWidget"
checked={selectedWidgets.newAwesomeWidget}
htmlId="newAwesomeWidget-checkbox"
>
<Text>New Awesome Widget</Text>
</Checkbox>;
// 4. Setup callback handler (if needed)
const handleCallback = (data) => {
console.log('Callback:', data);
};
// 5. Render with standard layout
{
selectedWidgets.newAwesomeWidget && (
<div className="box">
<section className="section-box">
<fieldset className="fieldset">
<legend className="legend-box">New Awesome Widget</legend>
<NewAwesomeWidget
handleCallback={handleCallback}
onError={(error) => onError('NewAwesomeWidget', error)}
customProp={someValue}
/>
</fieldset>
</section>
</div>
);
}// WRONG
<div>
<NewWidget />
</div><div className="box">
<section className="section-box">
<fieldset className="fieldset">
<legend className="legend-box">Widget Name</legend>
<NewWidget />
</fieldset>
</section>
</div>// WRONG - Widget renders immediately, user can't disable
{
selectedWidgets.newWidget && <NewWidget />;
}
// But newWidget not in defaultWidgets!// In defaultWidgets
const defaultWidgets = {
newWidget: false, // ← MUST ADD HERE
};
// Then render
{
selectedWidgets.newWidget && <NewWidget />;
}// WRONG
<NewWidget onEvent={handleEvent} /><NewWidget onEvent={handleEvent} onError={(error) => onError('NewWidget', error)} />// WRONG
<div style={{backgroundColor: '#1a1a1a'}}><div style={{backgroundColor: 'var(--mds-color-theme-background-primary-normal)'}}>After adding a new widget:
- Widget imports without errors
- Appears in widget selector checkbox list
- Can be enabled/disabled via checkbox
- Selection persists in localStorage
- Renders with correct layout (box > section-box > fieldset)
- Has legend/title
- Uses Momentum CSS variables (no hardcoded colors)
- Callbacks are handled correctly
- onError handler present and logs errors
- Works in both light and dark themes
- No console errors when enabled/disabled
- No visual/layout breaking when rendered alongside other widgets
- Main App:
src/App.tsx - Styles:
src/App.scss