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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@steinshy/wealthhealth-modal

v0.2.3

Published

A modern, accessible modal component library for React—published to npm as a proof of concept for OpenClassroom project.

Readme

npm version npm downloads license TypeScript React WCAG 2.1 AA Vite Storybook ESLint Prettier Stylelint

A lightweight React modal component library using native <dialog> element. Includes pre-built SignupModal, LoginModal, and ConfirmModal with form validation and accessibility built in.

Live demo and Storybook

| Resource | URL / link | | -------------- | ----------------------------------------------------------- | | Language | English · Français | | Architecture | ARCHITECTURE.md | | ArchitectureFr | ARCHITECTURE.fr.md | | GitHub | https://github.com/Steinshy/OC-WealthHealth-modal | | Demo app | https://steinshy.github.io/OC-WealthHealth-modal/ | | Storybook | https://steinshy.github.io/OC-WealthHealth-modal/storybook/ |

On GitHub Pages, the demo and Storybook are produced in one CI step: the demo is at the site root and Storybook is deployed under storybook/.

Features

  • Native <dialog> element — No custom focus trapping hacks, just the browser API
  • WCAG 2.1 AA accessible — Proper labels, error messaging, focus management
  • TypeScript-first — Full type safety out of the box
  • React 18 & 19 — Works with both versions
  • CSS Modules — Scoped styles, no class name conflicts
  • Pre-built forms — SignupModal, LoginModal, ConfirmModal ready to use
  • Dark mode & reduced motion — Respects user preferences
  • useTheme hook — Built-in light/dark toggle with localStorage persistence

Prerequisites

  • Node.js 18 or newer (repository CI uses Node 22)
  • React 18 or 19

Install

npm install @steinshy/wealthhealth-modal

Quick Start

Basic Modal

import { Modal } from '@steinshy/wealthhealth-modal';
import { useState } from 'react';

export function App() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <button onClick={() => setIsOpen(true)}>Open Modal</button>
      <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} title="Welcome" status="success">
        <p>Operation completed successfully!</p>
      </Modal>
    </>
  );
}

Signup Form

import { SignupModal, type SignupFormData } from '@steinshy/wealthhealth-modal';
import { useState } from 'react';

export function AuthPage() {
  const [isOpen, setIsOpen] = useState(false);
  const [error, setError] = useState<string>();

  const handleSignup = async (data: SignupFormData) => {
    try {
      await fetch('/api/auth/signup', {
        method: 'POST',
        body: JSON.stringify(data),
      });
      // Modal auto-closes on success
    } catch (err) {
      setError('Signup failed');
    }
  };

  return (
    <>
      <button onClick={() => setIsOpen(true)}>Sign Up</button>
      <SignupModal isOpen={isOpen} onClose={() => setIsOpen(false)} onSubmit={handleSignup} error={error} />
    </>
  );
}

Components

Modal

Base modal component. Use for custom content.

| Prop | Type | Default | Description | | ------------------- | ---------------------------------------------------------- | ----------- | ------------------------------------------------------- | | isOpen | boolean | required | Show/hide modal | | onClose | () => void | required | Called once when the dialog closes | | title | string | optional | Modal title (header omitted if unset) | | children | ReactNode | required | Modal content | | status | 'success' \| 'error' \| 'info' \| 'warning' \| 'default' | 'default' | Visual state (top border color) | | size | 'sm' \| 'md' \| 'lg' | 'md' | Modal width | | autoCloseDuration | number | optional | Close automatically after N milliseconds | | showCloseButton | boolean | true | Show × button (when title and dismissible allow it) | | dismissible | boolean | true | Allow ESC, backdrop (if enabled), and close button | | closeOnBackdrop | boolean | true | Allow clicking outside to close | | icon | ReactNode | optional | Optional icon in the header | | footer | ReactNode | optional | Optional footer below content | | className | string | optional | Extra class on <dialog> |

SignupModal

Pre-built signup form with email, password, password confirmation.

| Prop | Type | Default | Description | | --------------- | ----------------------------------------- | -------- | ----------------------------------------------- | | isOpen | boolean | required | Show/hide | | onClose | () => void | required | Close callback | | onSubmit | (data: SignupFormData) => Promise<void> | required | Async submit; throw on failure | | isLoading | boolean | false | Spinner and disabled submit | | error | string | optional | Error banner at top | | initialData | SignupFormData | optional | Pre-filled fields | | showSuccess | boolean | optional | Show success UI without submitting (e.g. demos) | | initialErrors | Record<string, string> | optional | Field-level errors on open |

LoginModal

Pre-built login form with email and password.

| Prop | Type | Default | Description | | --------------- | ---------------------------------------- | -------- | ---------------------------------- | | isOpen | boolean | required | Show/hide | | onClose | () => void | required | Close callback | | onSubmit | (data: LoginFormData) => Promise<void> | required | Async submit; throw on failure | | isLoading | boolean | false | Spinner and disabled submit | | error | string | optional | Error banner at top | | initialData | LoginFormData | optional | Pre-filled fields | | showSuccess | boolean | optional | Show success UI without submitting | | initialErrors | Record<string, string> | optional | Field-level errors on open |

ConfirmModal

Simple yes/no confirmation dialog.

| Prop | Type | Default | Description | | -------------- | ---------------------------------------------------------- | ----------- | ----------------------------------------- | | isOpen | boolean | required | Show/hide | | onClose | () => void | required | Close callback | | onConfirm | () => void \| Promise<void> | required | Confirm handler — modal closes on resolve | | title | string | required | Dialog title | | children | ReactNode | required | Dialog content | | confirmLabel | string | 'Confirm' | Confirm button text | | cancelLabel | string | 'Cancel' | Cancel button text | | isLoading | boolean | false | Show spinner, disable buttons | | status | 'success' \| 'error' \| 'info' \| 'warning' \| 'default' | 'default' | Visual state |

Theme

useTheme()

A hook that manages light/dark mode. It sets data-theme on <html>, persists the choice under the localStorage key wh-theme, and defaults to the OS prefers-color-scheme on first visit.

import { useTheme } from '@steinshy/wealthhealth-modal';

export function App() {
  const { theme, toggleTheme, setTheme, isDark } = useTheme();

  return (
    <>
      <button onClick={toggleTheme}>{isDark ? '☀ Light mode' : '☾ Dark mode'}</button>
      {/* All modals respond automatically */}
    </>
  );
}

| Return value | Type | Description | | ------------- | -------------------- | ------------------------------- | | theme | 'light' \| 'dark' | Current active theme | | isDark | boolean | true when dark mode is active | | toggleTheme | () => void | Toggle between light and dark | | setTheme | (t: Theme) => void | Set a specific theme |

Exported types: Theme, UseThemeReturn (see package exports).

The hook writes data-theme="light" or data-theme="dark" to the <html> element. All library components pick this up automatically — no ThemeProvider or prop drilling required.

SSR note: useTheme reads localStorage and window.matchMedia on mount, so it is safe to render server-side (it defaults to 'light' on the server).

Styling

Components use CSS Modules for styling. Override with custom classes:

import { Modal } from '@steinshy/wealthhealth-modal';
import styles from './customStyles.module.css';

<Modal isOpen={true} onClose={() => {}} className={styles.custom}>
  Content
</Modal>;

Available CSS custom properties:

:root {
  --modal-bg: #ffffff;
  --modal-text: rgba(0, 0, 0, 0.87);
  --modal-text-light: rgba(0, 0, 0, 0.6);
  --modal-border-radius: 4px;
  --modal-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
  --modal-border-color-success: #4caf50;
  --modal-border-color-error: #f44336;
  --modal-border-color-info: #2196f3;
  --modal-border-color-warning: #ff9800;
  --modal-title-color-success: #2e7d32;
  --modal-title-color-error: #c62828;
  --modal-title-color-info: #1565c0;
  --modal-title-color-warning: #e65100;
}

Override dark mode values via data-theme (set by useTheme) or the media query:

/* via useTheme() hook */
[data-theme='dark'] {
  --modal-bg: #424242;
  --modal-text: rgba(255, 255, 255, 0.87);
}

/* or via OS preference */
@media (prefers-color-scheme: dark) {
  :root {
    --modal-bg: #424242;
    --modal-text: rgba(255, 255, 255, 0.87);
  }
}

Accessibility

  • Native <dialog> element (proper semantics)
  • Automatic focus trapping
  • ESC key closes modal
  • Explicit labels on all form inputs
  • Error messages linked to inputs via aria-describedby
  • Focus management (moves to first error field)
  • Color contrast 4.5:1 (AA standard)
  • Respects prefers-reduced-motion and prefers-color-scheme
  • 16px minimum font size on inputs (prevents iOS zoom)

Browser Support

| Browser | Version | | ------- | ------- | | Chrome | 37+ | | Firefox | 98+ | | Safari | 15.4+ | | Edge | 79+ |

Developing this repository

| Command | Description | | -------------------- | ------------------------------------------------------------------------------ | | npm run dev | Demo (http://localhost:5173) and Storybook (http://localhost:6006) together | | npm run dev:demo | Demo only | | npm run storybook | Storybook only | | npm run build | Library build to dist/ | | npm run build:demo | Demo + Storybook static output into dist-demo/ (same layout as GitHub Pages) | | npm run preview | Serve dist-demo locally after build:demo |

CI runs formatting, ESLint, Stylelint, type-check, and build:demo with the GitHub Pages base path, then checks that dist-demo/storybook/index.html exists.

For a deeper view of folders, builds, and workflows, see ARCHITECTURE.md or the French architecture doc.

License

MIT