spfx-notifications-hub
v1.1.1
Published
A powerful library for toast, snackbar, confirm popup, and dialog notifications following Fluent UI standards for SPFx and React applications. Works seamlessly with both Functional Components and Class Components.
Downloads
785
Maintainers
Readme
SPFx Notifications Hub
A powerful library for toast, snackbar, confirm popup, and dialog notifications following Fluent UI standards for SPFx and React applications. Works seamlessly with both Functional Components and Class Components.
✅ Compatibility
- React: 16.8+ to 19.x (supports all modern React versions)
- Node.js: 12.0+ (compatible with older Node versions)
- TypeScript: 4.0+ (optional, for type definitions)
✨ Features
- 🎯 Promise-based confirm() - Confirmation with async/await
- 🔔 notify.success() - Success notifications
- ⚠️ notify.warning() - Warning notifications
- ❌ notify.error() - Error notifications
- ℹ️ notify.info() - Info notifications
- 🌐 Global provider - No prop drilling needed
- 📐 Responsive placement - Control toast position per breakpoint or inline containers
- ⏱️ Auto dismiss - Automatically closes after specified duration with progress bar
- 📊 Server logging - Optional server logging
- 🎨 Fluent-inspired UI - Fluent look & feel with pure HTML/CSS/SVG (no Fluent UI dependency)
- 📦 Class Component Support - Works with both functional and class components
🗂️ Project structure
src/
├─ core/ # Provider, hooks, HOC, shared types
│ ├─ NotificationsProvider.tsx
│ ├─ hooks.ts / hoc.tsx
│ └─ utils/logging.ts
├─ components/ # UI building blocks (toast, dialog, confirm)
│ ├─ ConfirmDialog/
│ ├─ Dialog/
│ ├─ Toast/
│ ├─ ToastContainer/
│ └─ shared/SvgIcon.tsx
└─ styles.css # Aggregated CSS entry (import this from apps)📦 Installation
npm install spfx-notifications-hub🚀 Quick Start
1. Setup Provider (Required)
Wrap your app with NotificationsProvider:
import React from 'react';
import { NotificationsProvider } from 'spfx-notifications-hub';
import 'spfx-notifications-hub/styles';
// Functional root
export function App() {
return (
<NotificationsProvider
defaultDuration={5000}
loggingConfig={{
enabled: true,
logLevel: 'all',
onLog: (notification) => console.log('[Notification]', notification),
}}
>
<YourApp />
</NotificationsProvider>
);
}
// Class root
export class AppClass extends React.Component {
render() {
return (
<NotificationsProvider defaultDuration={4000}>
<YourApp />
</NotificationsProvider>
);
}
}2. Import CSS (Required)
// In your entry point (index.tsx or App.tsx)
import 'spfx-notifications-hub/styles';
// or
import 'spfx-notifications-hub/dist/index.css';🎯 Toast placement & responsive rules
You can control where toast notifications appear using the toastPlacement prop on NotificationsProvider.
Simple placement
<NotificationsProvider toastPlacement="bottom-left">
<YourApp />
</NotificationsProvider>Responsive placement
<NotificationsProvider
toastPlacement={{
default: 'top-right',
responsive: [
{ maxWidth: 1024, target: 'bottom-right' },
{ maxWidth: 640, target: 'bottom-center' },
],
}}
>
<YourApp />
</NotificationsProvider>Inline container (inside a card/panel)
import React, { useRef } from 'react';
import { NotificationsProvider } from 'spfx-notifications-hub';
const cardRef = useRef<HTMLDivElement>(null);
<NotificationsProvider
toastPlacement={{
default: {
position: 'top-right',
container: () => cardRef.current, // selector, element or function are supported
},
}}
>
<div ref={cardRef} className="card">
{/* Toasts for this provider render inside this element */}
</div>
</NotificationsProvider>container accepts:
- a CSS selector string (
'#toast-root') - an HTMLElement (
document.getElementById('toast-root')) - a function returning the element (
() => cardRef.current)
If no container is provided, toasts stick to the viewport (default top-right). Responsive rules use maxWidth to match the current viewport width.
📖 Usage Examples
Functional Components
Using Hooks (Recommended)
import React from 'react';
import { useNotify, useConfirm, useDialog } from 'spfx-notifications-hub';
function MyComponent() {
const notify = useNotify();
const confirm = useConfirm();
const dialog = useDialog();
const handleSuccess = () => {
notify.success('Operation successful!', {
title: 'Success',
duration: 3000,
});
};
const handleDelete = async () => {
const result = await confirm({
title: 'Confirm Delete',
message: 'Are you sure you want to delete this item?',
confirmText: 'Delete',
cancelText: 'Cancel',
});
if (result) {
notify.success('Item deleted!');
}
};
const handleShowDialog = () => {
dialog.show({
title: 'My Dialog',
content: <div>Dialog content here</div>,
size: 'medium',
});
};
return (
<div className="stack gap-8">
<button onClick={handleSuccess}>Success toast</button>
<button onClick={handleDelete}>Confirm delete</button>
<button onClick={handleShowDialog}>Show dialog</button>
</div>
);
}Using Global API
import React from 'react';
import { notify, confirm, dialog } from 'spfx-notifications-hub';
function MyComponent() {
const handleClick = () => {
notify.success('Success!');
};
const handleConfirm = async () => {
const result = await confirm({
message: 'Are you sure?',
});
if (result) {
notify.success('Confirmed!');
}
};
return (
<div className="stack gap-8">
<button onClick={handleClick}>Show Toast</button>
<button onClick={handleConfirm}>Confirm Action</button>
<button
onClick={() =>
dialog.show({
title: 'Dialog example',
content: <p>This dialog is opened via global API</p>,
})
}
>
Show Dialog
</button>
</div>
);
}Class Components
Using Global API (Simplest) ⭐
No HOC, no render props needed - just import and use!
import React from 'react';
import { notify, confirm, dialog } from 'spfx-notifications-hub';
class MyComponent extends React.Component {
private dialogId: string | null = null;
handleSuccess = () => {
notify.success('Operation completed!', {
title: 'Success',
duration: 3000,
});
};
handleWarning = () => {
notify.warning('Please check your input', {
title: 'Warning',
});
};
handleError = () => {
notify.error('Something went wrong!', {
title: 'Error',
});
};
handleConfirm = async () => {
const result = await confirm({
title: 'Confirm Action',
message: 'Are you sure you want to delete this item?',
confirmText: 'Delete',
cancelText: 'Cancel',
});
if (result) {
notify.success('Item deleted successfully!');
} else {
notify.info('Action cancelled');
}
};
handleShowDialog = () => {
this.dialogId = dialog.show({
title: 'My Dialog',
content: (
<div>
<p>This is dialog content from class component!</p>
</div>
),
size: 'medium',
footer: (
<div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
<button
onClick={() => {
if (this.dialogId) {
dialog.hide(this.dialogId);
this.dialogId = null;
notify.info('Dialog closed');
}
}}
>
Close
</button>
</div>
),
});
};
render() {
return (
<div className="stack gap-8">
<button onClick={this.handleSuccess}>Success</button>
<button onClick={this.handleWarning}>Warning</button>
<button onClick={this.handleError}>Error</button>
<button onClick={this.handleConfirm}>Confirm</button>
<button onClick={this.handleShowDialog}>Show Dialog</button>
</div>
);
}
}
export default MyComponent;Using HOC (Higher Order Component)
When you need better type safety:
import React from 'react';
import { withNotifications, WithNotificationsProps } from 'spfx-notifications-hub';
interface MyComponentProps {
title?: string;
}
class MyComponent extends React.Component<MyComponentProps & WithNotificationsProps> {
private dialogId: string | null = null;
handleSuccess = () => {
this.props.notify.success('Success!');
};
handleDialog = () => {
this.dialogId = this.props.dialog.show({
title: 'Dialog',
content: <p>Dialog content</p>,
});
};
render() {
return (
<div>
<h1>{this.props.title}</h1>
<button onClick={this.handleSuccess}>Success</button>
<button onClick={this.handleDialog}>Show Dialog</button>
</div>
);
}
}
export default withNotifications(MyComponent);Using Render Props
import React from 'react';
import { NotificationsConsumer } from 'spfx-notifications-hub';
class MyComponent extends React.Component {
render() {
return (
<NotificationsConsumer>
{({ notify, confirm, dialog }) => (
<div className="stack gap-8">
<button onClick={() => notify.success('Hello!')}>
Success
</button>
<button
onClick={async () => {
const result = await confirm({ message: 'Are you sure?' });
if (result) notify.success('Confirmed!');
}}
>
Confirm
</button>
</div>
)}
</NotificationsConsumer>
);
}
}
export default MyComponent;📚 API Reference
Global API (notify, confirm, dialog)
Works in both functional and class components:
import { notify, confirm, dialog } from 'spfx-notifications-hub';
// Toast notifications
notify.success(message: string, options?: {
title?: string;
duration?: number;
action?: { label: string; onClick: () => void };
});
notify.warning(message: string, options?);
notify.error(message: string, options?);
notify.info(message: string, options?);
// Confirm dialog
const result: Promise<boolean> = await confirm({
title?: string;
message: string;
confirmText?: string;
cancelText?: string;
});
// Dialog
const dialogId: string = dialog.show({
title?: string;
content: React.ReactNode; // Required
footer?: React.ReactNode;
size?: 'small' | 'medium' | 'large' | 'fullscreen';
closeOnOutsideClick?: boolean; // Default: true
closeOnEscape?: boolean; // Default: true
modalType?: 'modal' | 'non-modal' | 'alert';
backdrop?: 'none' | 'opaque' | 'transparent';
className?: string;
});
dialog.hide(dialogId: string);Hooks API (Functional Components only)
import { useNotify, useConfirm, useDialog } from 'spfx-notifications-hub';
function MyComponent() {
const notify = useNotify();
const confirm = useConfirm();
const dialog = useDialog();
// Use notify.success(), confirm(), dialog.show(), etc.
}HOC API (Class Components)
import { withNotifications, WithNotificationsProps } from 'spfx-notifications-hub';
interface MyProps extends WithNotificationsProps {
// Your custom props
}
class MyComponent extends React.Component<MyProps> {
// Access via this.props.notify, this.props.confirm, this.props.dialog
}
export default withNotifications(MyComponent);🎨 Toast Features
Auto-dismiss with Progress Bar
Toasts automatically dismiss after the specified duration with a visual progress bar:
notify.success('This will auto-dismiss in 5 seconds', {
duration: 5000, // Auto-dismiss after 5 seconds
});Action Button
Add an action button to toasts:
notify.success('Item deleted', {
action: {
label: 'Undo',
onClick: () => {
// Undo logic
},
},
});Pause on Hover
Progress bar automatically pauses when you hover over the toast.
💬 Dialog Examples
Basic Dialog
const dialogId = dialog.show({
title: 'My Dialog',
content: <div>Content here</div>,
size: 'medium',
});Dialog with Footer
const dialogId = dialog.show({
title: 'Confirm Action',
content: <p>Are you sure?</p>,
footer: (
<div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
<DefaultButton onClick={() => dialog.hide(dialogId)}>Cancel</DefaultButton>
<PrimaryButton onClick={() => {
// Save logic
dialog.hide(dialogId);
}}>Save</PrimaryButton>
</div>
),
});Blocking Dialog
const dialogId = dialog.show({
title: 'Blocking Dialog',
content: <p>Must use close button</p>,
closeOnOutsideClick: false, // Cannot close by clicking outside
footer: <PrimaryButton onClick={() => dialog.hide(dialogId)}>Close</PrimaryButton>,
});Form in Dialog (Class Component Example)
class UserForm extends React.Component {
state = { name: '', email: '' };
private dialogId: string | null = null;
handleOpenForm = () => {
this.dialogId = dialog.show({
title: 'Add New User',
content: (
<div style={{ padding: '16px 0' }}>
<TextField
label="Name"
value={this.state.name}
onChange={(e, newValue) => this.setState({ name: newValue || '' })}
/>
<TextField
label="Email"
value={this.state.email}
onChange={(e, newValue) => this.setState({ email: newValue || '' })}
/>
</div>
),
size: 'medium',
footer: (
<PrimaryButton
onClick={() => {
if (this.dialogId) {
// Save logic
dialog.hide(this.dialogId);
this.dialogId = null;
notify.success('User saved!');
}
}}
>
Save
</PrimaryButton>
),
});
};
render() {
return <PrimaryButton onClick={this.handleOpenForm}>Add User</PrimaryButton>;
}
}🔧 NotificationsProvider Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| defaultDuration | number | 5000 | Auto dismiss duration (ms), 0 = no auto dismiss |
| loggingConfig | LoggingConfig | - | Optional logging configuration |
LoggingConfig
{
enabled?: boolean;
endpoint?: string; // API endpoint for logging
logLevel?: 'error' | 'warning' | 'info' | 'all';
onLog?: (notification: Notification) => void | Promise<void>;
}🎨 Styling
The library uses Fluent UI v8 and can be customized through CSS variables:
--colorNeutralBackground1
--colorNeutralForeground1
--colorBrandForeground1All class names are prefixed with hh- to avoid conflicts:
.hh-notifications-toast.hh-notifications-toast-container.hh-dialog-*
📝 Comparison: Functional vs Class Components
| Feature | Functional Component | Class Component |
|---------|---------------------|-----------------|
| Hooks | ✅ useNotify(), useConfirm(), useDialog() | ❌ Not available |
| Global API | ✅ notify, confirm, dialog | ✅ notify, confirm, dialog |
| HOC | ❌ Not needed | ✅ withNotifications() |
| Render Props | ❌ Not needed | ✅ NotificationsConsumer |
| Type Safety | ✅ Full TypeScript support | ✅ Full TypeScript support |
Recommendation:
- Functional Components: Use hooks (
useNotify,useConfirm,useDialog) - Class Components: Use global API (
notify,confirm,dialog)
🏗️ Build as Library
To build as a library for other projects:
npm run buildOutput will be in the dist/ directory.
📄 License
MIT
📚 Additional Documentation
- Class Component Usage Guide - Detailed guide for class components
- Dialog in Class Components - Dialog usage examples for class components
