Skip to content

Latest commit

 

History

History
367 lines (286 loc) · 8.3 KB

File metadata and controls

367 lines (286 loc) · 8.3 KB

React Sample App - Widget Integration Guide

Purpose

Demonstrates how to integrate Contact Center widgets into a React application. Use this as a reference when adding new widgets.

Design System

  • 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, .fieldset structure

Critical Integration Pattern

Follow this exact pattern for ALL new widgets:

Step 1: Import the Widget

import {NewWidget} from '@webex/cc-widgets';

Step 2: Add to defaultWidgets

const defaultWidgets = {
  stationLogin: true,
  userState: true,
  // ... existing widgets
  newWidget: false, // ← Add here (false by default for user opt-in)
};

Step 3: Add Checkbox for Widget Selection

<Checkbox
  onChange={handleCheckboxChange}
  name="newWidget"
  checked={selectedWidgets.newWidget}
  htmlId="newWidget-checkbox"
  aria-label="new widget checkbox"
>
  <Text>New Widget</Text>
</Checkbox>

Step 4: Conditional Rendering with Standard Layout

{
  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>
  );
}

Step 5: Add callbacks (if needed)

const handleNewWidgetCallback = (data) => {
  console.log('New widget callback:', data);
  // Handle callback logic
};

Layout Structure Rules

Container Hierarchy (ALWAYS use this)

<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>

Why this structure?

  • box - Consistent spacing and background
  • section-box - Momentum Design padding
  • fieldset - Semantic grouping
  • legend-box - Styled title
  • Result: Visual consistency across all widgets

Styling Rules

CSS Variables (MUST USE)

// 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)

❌ NEVER Do This

<div style={{color: '#FF0000'}}>  // Hardcoded color
<div style={{padding: '10px'}}>   // Hardcoded spacing

✅ ALWAYS Do This

<div style={{
  color: 'var(--mds-color-theme-text-primary-normal)',
  padding: 'var(--mds-spacing-3)'
}}>

Event Handling Pattern

Standard onError Handler

const onError = (widgetName: string, error: Error) => {
  console.error(`${widgetName} error:`, error);
  // Optional: Show toast notification
  setToast({type: 'error'});
};

EVERY widget MUST have onError callback.

Widget-Specific Events

// 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 Integration

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-ui library manages the theme through ThemeProvider
  • Theme storage: store.currentTheme stores 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.

State Management

When to Use store Directly

// 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

When to Use Local State

// UI-only state (no widget dependency)
const [showPopup, setShowPopup] = useState(false);
const [selectedOption, setSelectedOption] = useState('');

Complete Example: Adding a New Widget

// 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>
  );
}

Common Mistakes to AVOID

❌ Breaking CSS class structure

// WRONG
<div>
  <NewWidget />
</div>

✅ Correct

<div className="box">
  <section className="section-box">
    <fieldset className="fieldset">
      <legend className="legend-box">Widget Name</legend>
      <NewWidget />
    </fieldset>
  </section>
</div>

❌ Forgetting defaultWidgets entry

// WRONG - Widget renders immediately, user can't disable
{
  selectedWidgets.newWidget && <NewWidget />;
}
// But newWidget not in defaultWidgets!

✅ Correct

// In defaultWidgets
const defaultWidgets = {
  newWidget: false, // ← MUST ADD HERE
};

// Then render
{
  selectedWidgets.newWidget && <NewWidget />;
}

❌ Missing error handler

// WRONG
<NewWidget onEvent={handleEvent} />

✅ Correct

<NewWidget onEvent={handleEvent} onError={(error) => onError('NewWidget', error)} />

❌ Hardcoding colors

// WRONG
<div style={{backgroundColor: '#1a1a1a'}}>

✅ Correct

<div style={{backgroundColor: 'var(--mds-color-theme-background-primary-normal)'}}>

Testing Checklist

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

File Locations

  • Main App: src/App.tsx
  • Styles: src/App.scss

Additional Resources