npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

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

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

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

Output will be in the dist/ directory.

📄 License

MIT

📚 Additional Documentation