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

@hadoku/task-ui-components

v4.23.35

Published

Reusable UI components with Bento grid and metallic gradient support

Readme

@wolffm/task-ui-components

Reusable UI components for task management applications. Production-ready and fully typed.

Installation

npm install @wolffm/task-ui-components

SSR Configuration (Astro/Next.js)

If using this package in an SSR environment (Astro, Next.js, etc.), add this one-time configuration:

// astro.config.mjs
export default defineConfig({
  vite: {
    ssr: {
      noExternal: ['@wolffm/task-ui-components']
    }
  }
})
// next.config.js
module.exports = {
  transpilePackages: ['@wolffm/task-ui-components']
}

This allows the package to bundle its CSS automatically. This is a one-time setup - all future component updates will work automatically without additional configuration.

Components

AppHeader (Overview)

The ecosystem-standard top bar for every hadoku.me app: title on the left, an action bar on the right holding the theme picker and the unified settings gear. Enforces the shared header contract so child apps stop drifting. See Unified app header below.

ConnectedSettings (Overview)

The unified per-user settings gear + popout (access tier, display name, global content-visibility level, and a "Switch key" link to the site's /auth page — only that page creates sessions). "Connected": it self-wires to the hadoku.me edge-router via same-origin fetches, so it needs zero props. Injected automatically by AppHeader; can also be used standalone.

A rename or avatar change fires NAME_EVENT (hadoku:name) / AVATAR_EVENT (hadoku:avatar) on window. The gear also starts relayIdentityChanges(), which carries those events over BroadcastChannel('hadoku:identity') to every other hadoku tab of the browser and re-fires them there as the same window event — so a room open in another tab hears a change made on a page with no room. It is one relay per window, even across duplicate bundled copies of this package; a page with no gear (a room chrome) may call relayIdentityChanges() (also on the React-free subpath @wolffm/task-ui-components/identity-relay) itself and joins the same one. Never write a second relay.

ThemePicker (Overview)

A gorgeous theme picker with light/dark pill design. Fully customizable to work with any theme system.

Modal (Overview)

Generic modal dialog with keyboard shortcuts, customizable actions, and optional input field.

ContextMenu (Overview)

Position-based context menu with customizable items and danger states.

LoadingSkeleton (Overview)

Flexible loading skeleton with multiple layouts (default, minimal, custom) and dark theme support.

SettingsModal (Overview)

Generic settings dialog with section-based organization and multiple field types (toggle, text, password, select, button, custom).

Toast & Toaster

Toast notification system with success, error, and info variants.

Native-control replacements

Themed stand-ins for the browser's default chrome. Each ships its own stylesheet; import them unlayered, next to @wolffm/themes/style.css. Live samples across every theme: pnpm run gallery:controls.

import '@wolffm/task-ui-components/button.css'
import '@wolffm/task-ui-components/select.css'
import '@wolffm/task-ui-components/tooltip.css'
import '@wolffm/task-ui-components/scrollbar.css'
import { Button, Select, NativeSelect, Tooltip } from '@wolffm/task-ui-components'
;<div className="task-app-container hdk-scroll">
  {' '}
  {/* themes every scroller inside */}
  <Tooltip content="Delete task">
    <Button variant="soft" family="danger" icon="trash" aria-label="Delete task" />
  </Tooltip>
  <Select
    aria-label="Status"
    value={status}
    onChange={setStatus}
    options={[
      { value: 'todo', label: 'To do', icon: 'circle' },
      { value: 'done', label: 'Done', icon: 'check' }
    ]}
  />
</div>
  • Scrollbars — CSS only. hdk-scroll styles its element and every scroller inside it, thumb in the theme's primary colour (resolved per element, so nested data-theme scopes get their own); modifiers --subtle, --thin, --rail, --autohide. Zero specificity (:where()) inside @layer hdk-fallbacks, so any scrollbar rule an app writes itself wins — see below.
  • Button — variant soft (default) | solid | gradient | outline | ghost × family primary | success | warning | danger | neutral; size, shape (rounded | pill | square), icon, iconEnd, loading, block. An icon with no children is a square icon button — give it aria-label.
  • Select — ARIA listbox with arrows, Home/End, Enter, Escape and typeahead; variant outline | filled | ghost | underline. NativeSelect is a real <select> with the same trigger; under the hadoku.me shell its list is the themed one too (see installSelectMenus).
  • NumberInput — a real <input type="number"> (all input props pass through, controlled or not) with a themed up/down stepper in place of the OS spinner; hold to repeat; size.
  • Tooltip — replaces title=. Hover and keyboard focus, Escape dismisses, hoverable bubble; variant card (default) | inverse | primary, placement with viewport flip.

Select and Tooltip portal their popups into the anchor's nearest [data-theme] scope (the app container, or a nested theme preview inside it), so a popup always paints in its anchor's theme and escapes overflow: hidden and transformed ancestors.

Page-wide fallbacks (hadoku_site installs these once, for every app)

These restyle what an app left to the browser, without touching its markup. An app does not install them itself; they are listed here so it knows what it gets and how to opt out.

  • controls.css + <html class="hdk-controls"> — focus ring, link colour, accent colour on checkboxes/radios/ranges/progress, selection, placeholder, <summary> marker, the file-picker button, no number spinners, and field/button chrome.
  • scrollbar.css + <html class="hdk-scroll"> — every scroller.
  • installTitleTooltips() (/title-tooltips) — every title= as the card tooltip. Opt out: data-native-title.
  • installSelectMenus() (/select-menus) — the themed list for every native <select>; touch keeps the phone's picker. Opt out: data-native-select.

Both sheets are zero-specificity and sit in @layer hdk-fallbacks, so ANY rule an app writes wins, including Tailwind utilities, which live in layers and would otherwise lose to an unlayered rule of any specificity. That only holds if the page declares the layer first: @layer hdk-fallbacks, base, theme, utilities; — hadoku_site's global.css does.

Usage

Unified app header (recommended for hadoku.me apps)

Every app under hadoku.me should render the same top bar. AppHeader gives you the enforced layout — title left, [ status? · ThemePicker · Settings ] right — and injects the hadoku-wired ConnectedSettings gear for you. Pass your own ConnectedThemePicker (theme state stays app-side):

import { AppHeader, ConnectedThemePicker } from '@wolffm/task-ui-components'
import { useTheme, THEME_FAMILIES, THEME_ICON_MAP } from '@wolffm/themes'
import '@wolffm/themes/style.css'
import '@wolffm/task-ui-components/theme-picker.css'
import '@wolffm/task-ui-components/app-header.css'

function App() {
  const { theme, setTheme } = useTheme()
  return (
    <>
      <AppHeader
        title="Print Tool"
        themePicker={
          <ConnectedThemePicker
            themeFamilies={THEME_FAMILIES}
            currentTheme={theme}
            onThemeChange={setTheme}
            getThemeIcon={t => {
              const Icon = THEME_ICON_MAP[t as keyof typeof THEME_ICON_MAP]
              return Icon ? <Icon /> : null
            }}
          />
        }
      />
      {/* app body — the app's own concern */}
    </>
  )
}

ConnectedSettings self-resolves the caller's tier/name via /session/whoami and hits the edge-router's /session/* + /prefs/api/v1/content-level routes. Off hadoku.me (e.g. Storybook) every call resolves to a safe default and the gear renders a read-only shell — it never throws. Pass settingsProps={{ userType, name }} when the app already knows the identity to skip the extra fetch, or showSettings={false} to omit the gear.

The theme picker trigger and the gear are both .app-header__icon-btn (32px round, one :focus-visible ring), defined once in icon-btn.css. A link in the actions row uses the same class and nothing else — it stays --color-text visited or not, with no underline, even under a host page's a:visited rule:

<a className="app-header__icon-btn" href="https://discord.gg/…" aria-label="Discord">
  <Icon name="chat" size={16} />
</a>

The title truncates with an ellipsis and the actions never shrink, so the bar holds a status and four icons at 320px (e2e/header-icon-btn.spec.ts).

Quick Start with @wolffm/themes (Recommended)

The easiest way to use the ThemePicker is with the @wolffm/themes package:

import { ConnectedThemePicker } from '@wolffm/task-ui-components'
import { useTheme, THEME_FAMILIES, THEME_ICON_MAP } from '@wolffm/themes'
import '@wolffm/themes/style.css'

function App() {
  const { theme, setTheme } = useTheme()

  return (
    <ConnectedThemePicker
      themeFamilies={THEME_FAMILIES}
      currentTheme={theme}
      onThemeChange={setTheme}
      getThemeIcon={theme => {
        const Icon = THEME_ICON_MAP[theme]
        return Icon ? <Icon /> : null
      }}
    />
  )
}

Note: CSS is automatically imported by components - no manual CSS imports needed!

Using ConnectedThemePicker (Stateful)

ConnectedThemePicker manages its own open/close state:

import { ConnectedThemePicker } from '@wolffm/task-ui-components'

function App() {
  const [theme, setTheme] = useState('light')

  return (
    <ConnectedThemePicker
      themeFamilies={THEME_FAMILIES}
      currentTheme={theme}
      onThemeChange={setTheme}
    />
  )
}

Using ThemePicker (Manual State)

For full control over the picker's state:

import { ThemePicker, SunIcon, MoonIcon } from '@wolffm/task-ui-components'

const THEME_FAMILIES = [
  {
    lightIcon: <SunIcon />,
    darkIcon: <MoonIcon />,
    lightTheme: 'light',
    darkTheme: 'dark',
    lightLabel: 'Light',
    darkLabel: 'Dark'
  }
  // ... more themes
]

function App() {
  const [theme, setTheme] = useState('light')
  const [showPicker, setShowPicker] = useState(false)

  return (
    <ThemePicker
      currentTheme={theme}
      isOpen={showPicker}
      themeFamilies={THEME_FAMILIES}
      onThemeChange={setTheme}
      onToggle={() => setShowPicker(!showPicker)}
      onSettingsClick={() => console.log('Settings clicked')}
    />
  )
}

With Automatic Fallback Icons (No Icons Needed!)

Perfect when using your own theme system:

import { ThemePicker } from '@wolffm/task-ui-components'
import './my-custom-style.css' // Your own theme CSS

const THEME_FAMILIES = [
  {
    // Icons will be automatically assigned (CircleIcon for first theme)
    lightTheme: 'ocean-light',
    darkTheme: 'ocean-dark',
    lightLabel: 'Ocean Light',
    darkLabel: 'Ocean Dark'
  },
  {
    // SquareIcon for second theme
    lightTheme: 'forest-light',
    darkTheme: 'forest-dark',
    lightLabel: 'Forest Light',
    darkLabel: 'Forest Dark'
  },
  {
    // TriangleIcon for third theme
    lightTheme: 'sunset-light',
    darkTheme: 'sunset-dark',
    lightLabel: 'Sunset Light',
    darkLabel: 'Sunset Dark'
  }
]

function App() {
  const [theme, setTheme] = useState('ocean-light')
  const [showPicker, setShowPicker] = useState(false)

  return (
    <ThemePicker
      currentTheme={theme}
      isOpen={showPicker}
      themeFamilies={THEME_FAMILIES}
      onThemeChange={setTheme}
      onToggle={() => setShowPicker(!showPicker)}
    />
  )
}

CSS Variables

The ThemePicker requires these CSS variables to be defined by your theme system:

:root {
  --color-text: #000000;
  --color-primary: #3b82f6;
  --color-primary-hover: rgba(59, 130, 246, 0.1);
  --color-primary-bg: rgba(59, 130, 246, 0.05);
  --color-bg-card: #ffffff;
  --color-text-secondary: #64748b;
  --shadow-focus-sm: 0 0 0 2px rgba(59, 130, 246, 0.25);
  --shadow-md: 0 2px 4px rgba(0, 0, 0, 0.1);
  --shadow-modal: 0 8px 24px rgba(0, 0, 0, 0.15);
  --transition-fast: 0.15s ease;
  --spacing-sm: 8px;
  --spacing-md: 12px;
  --spacing-lg: 16px;
  --font-size-lg: 18px;
  --font-weight-semibold: 600;
}

Icons

Theme-Specific Icons

Available themed icons for specific aesthetics:

  • SunIcon - Sun/light mode icon
  • MoonIcon - Moon/dark mode icon
  • WaveIcon - Ocean wave icon
  • ZapIcon - Lightning bolt icon (cyberpunk)
  • CoffeeIcon - Coffee cup icon
  • FlowerIcon - Flower/lavender icon
  • HeartIcon - Heart icon (pink theme)
  • LeafIcon - Leaf icon (nature theme)
  • SettingsIcon - Gear/settings icon
  • TagIcon - Tag/label icon
  • SpaIcon - Hot spring/spa icon (izakaya theme)

Generic/Fallback Icons

When you don't have specific icons for your themes, use generic shape icons for visual differentiation:

  • CircleIcon - Simple circle
  • SquareIcon - Rounded square
  • TriangleIcon - Triangle
  • DiamondIcon - Diamond shape
  • StarIcon - Star shape
  • HexagonIcon - Hexagon
  • PentagonIcon - Pentagon
  • OctagonIcon - Octagon

Automatic Fallback:

The ThemePicker component automatically uses fallback icons if you don't provide any:

import { ThemePicker } from '@wolffm/task-ui-components'

const THEME_FAMILIES = [
  {
    // No icons provided - will use CircleIcon automatically
    lightTheme: 'custom-light',
    darkTheme: 'custom-dark',
    lightLabel: 'Custom Light',
    darkLabel: 'Custom Dark'
  },
  {
    // No icons provided - will use SquareIcon automatically
    lightTheme: 'another-light',
    darkTheme: 'another-dark',
    lightLabel: 'Another Light',
    darkLabel: 'Another Dark'
  }
]

Manual Fallback Usage:

You can also manually use fallback icons:

import { getFallbackIcon, FALLBACK_ICONS } from '@wolffm/task-ui-components'

// Get icon by index (0-7, repeats after 8)
const icon = getFallbackIcon(0) // Returns CircleIcon
const icon2 = getFallbackIcon(5) // Returns HexagonIcon

// Or use the array directly
const CircleIconComponent = FALLBACK_ICONS[0]
const iconElement = <CircleIconComponent />

Fallback Icon Sequence:

When icons are omitted from ThemeFamily, they're assigned in this order:

  1. CircleIcon (index 0)
  2. SquareIcon (index 1)
  3. TriangleIcon (index 2)
  4. DiamondIcon (index 3)
  5. StarIcon (index 4)
  6. HexagonIcon (index 5)
  7. PentagonIcon (index 6)
  8. OctagonIcon (index 7)
  9. Pattern repeats from CircleIcon for 9+ themes

This ensures each theme gets a visually distinct icon while maintaining consistency across app reloads.

Logger

Production-safe logging utility that respects development mode and admin status.

Basic Usage

import { logger } from '@wolffm/task-ui-components'

// Informational logs (dev/admin only)
logger.info('[Component] Loading data', { userId: '123' })

// Debug logs (dev/admin only)
logger.debug('[Component] State updated', { isOpen: true, count: 5 })

// Warnings (always shown)
logger.warn('[Component] Deprecated API used', { feature: 'old-api' })

// Errors (always shown)
logger.error('[Component] Failed to save', {
  error: err instanceof Error ? err.message : String(err),
  taskId: 'task-123'
})

Advanced Features

Enable admin mode for production debugging:

// After user authentication
if (userIsAdmin) {
  logger.setAdminStatus(true)
}

Specialized log methods:

// Component lifecycle logging
logger.component('mount', 'ThemePicker', { props })
logger.component('update', 'TaskList', { taskCount: 42 })

// Theme changes
logger.theme('pink-dark', { source: 'user-preference' })
logger.theme('ocean-light', { source: 'system' })

Best Practices

Always use structured logging with a message and context object:

// ✅ Good - Structured with context
logger.info('[TaskList] Loading tasks', { boardId, filter })

// ❌ Bad - String concatenation
logger.info(`Loading tasks for board ${boardId}`)

// ✅ Good - Proper error extraction
logger.error('[API] Request failed', {
  error: err instanceof Error ? err.message : String(err),
  endpoint: '/api/tasks'
})

// ❌ Bad - Logging error object directly
logger.error('Request failed:', err)

Use component prefixes for easy filtering:

// Component-based prefixes
logger.info('[TaskList] Mounted')
logger.info('[ThemePicker] Theme changed')

// Feature-based prefixes
logger.info('[API] Request completed')
logger.info('[Auth] User logged in')

Behavior

| Mode | Development | Production (Admin) | Production (User) | | -------------------- | ----------- | ------------------ | ----------------- | | logger.info() | ✅ | ✅ | ❌ | | logger.debug() | ✅ | ✅ | ❌ | | logger.component() | ✅ | ✅ | ❌ | | logger.theme() | ✅ | ✅ | ❌ | | logger.warn() | ✅ | ✅ | ✅ | | logger.error() | ✅ | ✅ | ✅ |

Development Detection:

  • Automatically enabled on localhost or 127.0.0.1
  • Or when import.meta.env.DEV is true

Admin Mode:

  • Call logger.setAdminStatus(true) after authentication
  • Enables all logs in production for debugging
  • Useful for troubleshooting production issues

Props

ThemePicker

| Prop | Type | Required | Description | | ----------------- | ------------------------------ | -------- | ----------------------------------------------- | | currentTheme | string | ✅ | Currently active theme name | | isOpen | boolean | ✅ | Whether the picker dropdown is visible | | themeFamilies | ThemeFamily[] | ✅ | Array of theme families to display | | onThemeChange | (theme: string) => void | ✅ | Callback when theme is selected | | onToggle | () => void | ✅ | Callback to toggle picker visibility | | onSettingsClick | () => void | ❌ | Optional callback for settings button | | getThemeIcon | (theme: string) => ReactNode | ❌ | Optional function to get icon for current theme | | className | string | ❌ | Optional CSS class for container |

ThemeFamily

| Prop | Type | Required | Description | | ------------ | ----------- | -------- | ------------------------------------------------- | | lightIcon | ReactNode | ❌ | Icon for light variant (auto-fallback if omitted) | | darkIcon | ReactNode | ❌ | Icon for dark variant (auto-fallback if omitted) | | lightTheme | string | ✅ | Theme name for light variant | | darkTheme | string | ✅ | Theme name for dark variant | | lightLabel | string | ✅ | Label for light variant (tooltip) | | darkLabel | string | ✅ | Label for dark variant (tooltip) |

License

MIT


Additional Components Documentation

Modal (API Reference)

Generic modal dialog with keyboard shortcuts and customizable actions.

Features:

  • role="dialog" + aria-modal + aria-labelledby wired to the title, and focus moved into the dialog on open
  • Escape closes from anywhere in the dialog, with or without an input
  • Enter confirms from the optional input field
  • Configurable buttons (show/hide cancel/confirm), or replace the whole action row via actions for flows the fixed pair can't express
  • Danger variant for destructive actions
  • Custom class names for styling

Styles ship with the package — import them once:

import '@wolffm/task-ui-components/modal.css'

Usage:

import { Modal } from '@wolffm/task-ui-components'

function MyComponent() {
  const [isOpen, setIsOpen] = useState(false)
  const [value, setValue] = useState('')

  return (
    <Modal
      isOpen={isOpen}
      title="Create Item"
      onClose={() => setIsOpen(false)}
      onConfirm={async () => {
        await createItem(value)
        setIsOpen(false)
      }}
      inputValue={value}
      onInputChange={setValue}
      inputPlaceholder="Enter name..."
      confirmLabel="Create"
      confirmDisabled={!value.trim()}
    >
      <p>Enter a name for your new item.</p>
    </Modal>
  )
}

Props:

| Prop | Type | Required | Description | | -------------------- | ------------------------------- | -------- | -------------------------------------- | | `isOpen` | `boolean` | ✅ | Whether modal is visible | | `title` | `string` | ✅ | Modal title | | `onClose` | `() => void` | ✅ | Close callback | | `onConfirm` | `() => void | Promise` | ❌ | Confirm callback | | `children` | `ReactNode` | ❌ | Modal content | | `inputValue` | `string` | ❌ | Input field value | | `onInputChange` | `(value: string) => void` | ❌ | Input change callback | | `inputPlaceholder` | `string` | ❌ | Input placeholder | | `confirmLabel` | `string` | ❌ | Confirm button text (default: Confirm) | | `cancelLabel` | `string` | ❌ | Cancel button text (default: Cancel) | | `confirmDisabled` | `boolean` | ❌ | Disable confirm button | | `confirmDanger` | `boolean` | ❌ | Danger styling for confirm | | `showCancel` | `boolean` | ❌ | Show cancel button (default: true) | | `showConfirm` | `boolean` | ❌ | Show confirm button (default: true) | | `className` | `string` | ❌ | Custom CSS class | | `overlayClassName` | `string` | ❌ | Custom overlay CSS class | | `actions` | `ReactNode` | ❌ | Replaces the whole action row | | `lead` | `ReactNode` | ❌ | Rendered left of the title |

actions is checked against undefined, not with ?? — passing null renders no action row (a progress step, a viewer), while omitting it keeps the default Cancel/Confirm pair.


ContextMenu (API Reference)

Position-based context menu for right-click or contextual actions.

Features:

  • Position-based rendering (x, y coordinates)
  • Configurable menu items
  • Danger state for destructive items
  • Disabled state support
  • Fixed positioning with z-index

Usage:

import { ContextMenu, type ContextMenuItem } from '@wolffm/task-ui-components'

function MyComponent() {
  const [menu, setMenu] = useState<{ x: number; y: number } | null>(null)

  const items: ContextMenuItem[] = [
    {
      label: 'Edit',
      onClick: () => handleEdit()
    },
    {
      label: 'Delete',
      onClick: () => handleDelete(),
      isDanger: true
    }
  ]

  return (
    <div
      onContextMenu={e => {
        e.preventDefault()
        setMenu({ x: e.clientX, y: e.clientY })
      }}
    >
      <ContextMenu
        isOpen={!!menu}
        x={menu?.x || 0}
        y={menu?.y || 0}
        items={items}
        className="my-context-menu"
      />
    </div>
  )
}

Props:

| Prop | Type | Required | Description | | ------------- | --------------------- | -------- | ----------------------- | | `isOpen` | `boolean` | ✅ | Whether menu is visible | | `x` | `number` | ✅ | X position (pixels) | | `y` | `number` | ✅ | Y position (pixels) | | `items` | `ContextMenuItem[]` | ✅ | Menu items | | `className` | `string` | ❌ | Custom CSS class |

ContextMenuItem:

| Prop | Type | Required | Description | | ------------ | --------------- | --------- | -------------- | ------------- | | `label` | `string` | ✅ | Item label | | `onClick` | `() => void \ | Promise` | ✅ | Click handler | | `isDanger` | `boolean` | ❌ | Danger styling | | `disabled` | `boolean` | ❌ | Disable item |


LoadingSkeleton (API Reference)

Flexible loading skeleton with multiple layout options.

Features:

  • Multiple layout options (default, minimal, custom)
  • Dark theme support
  • Customizable content via `customContent` prop

Usage:

import { LoadingSkeleton } from '@wolffm/task-ui-components'

function MyComponent() {
  const [isLoading, setIsLoading] = useState(true)
  const isDark = useTheme() === 'dark'

  if (isLoading) {
    return <LoadingSkeleton isDarkTheme={isDark} layout="minimal" />
  }

  return <div>Content loaded!</div>
}

Props:

| Prop | Type | Required | Description | | ----------------- | -------------- | ------------ | ----------------------- | --- | -------------------------------- | | `isDarkTheme` | `boolean` | ❌ | Dark theme mode | | `className` | `string` | ❌ | Custom CSS class | | `layout` | `'default' \ | 'minimal' \ | 'custom'` | ❌ | Layout type (default: 'default') | | `customContent` | `ReactNode` | ❌ | Custom skeleton content |


SettingsModal (API Reference)

Generic settings dialog with section-based organization.

Features:

  • Section-based organization
  • Multiple field types: toggle, text, password, select, button, custom
  • Validation support
  • Description text for fields

Usage:

import { SettingsModal, type SettingsSection } from '@wolffm/task-ui-components'

function MyApp() {
  const [isOpen, setIsOpen] = useState(false)
  const [darkMode, setDarkMode] = useState(false)
  const [language, setLanguage] = useState('en')

  const sections: SettingsSection[] = [
    {
      id: 'display',
      title: 'Display Settings',
      fields: [
        {
          type: 'toggle',
          id: 'darkMode',
          label: 'Dark Mode',
          description: 'Use dark color scheme',
          value: darkMode,
          onChange: setDarkMode
        },
        {
          type: 'select',
          id: 'language',
          label: 'Language',
          value: language,
          onChange: setLanguage,
          options: [
            { value: 'en', label: 'English' },
            { value: 'es', label: 'Spanish' }
          ]
        }
      ]
    }
  ]

  return <SettingsModal isOpen={isOpen} sections={sections} onClose={() => setIsOpen(false)} />
}

Props:

| Prop | Type | Required | Description | | ------------------- | --------------------- | -------- | --------------------------------- | | `isOpen` | `boolean` | ✅ | Whether modal is visible | | `sections` | `SettingsSection[]` | ✅ | Settings sections | | `onClose` | `() => void` | ✅ | Close callback | | `title` | `string` | ❌ | Modal title (default: 'Settings') | | `className` | `string` | ❌ | Custom CSS class | | `showCloseButton` | `boolean` | ❌ | Show close button (default: true) |

Field Types:

// Toggle field
{
type: 'toggle',
id: string,
label: string,
description?: string,
value: boolean,
onChange: (value: boolean) => void,
disabled?: boolean
}

// Text input field
{
type: 'text',
id: string,
label: string,
description?: string,
value: string,
onChange: (value: string) => void,
placeholder?: string,
disabled?: boolean
}

// Password field
{
type: 'password',
id: string,
label: string,
description?: string,
value: string,
onChange: (value: string) => void,
placeholder?: string,
showButton?: boolean,
buttonLabel?: string,
onButtonClick?: () => void | Promise<void>,
buttonDisabled?: boolean,
error?: string | null,
autoComplete?: string,
disabled?: boolean
}

// Select dropdown
{
type: 'select',
id: string,
label: string,
description?: string,
value: string,
onChange: (value: string) => void,
options: Array<{ value: string; label: string }>,
disabled?: boolean
}

// Action button
{
type: 'button',
id: string,
label?: string,
description?: string,
buttonLabel: string,
onClick: () => void | Promise<void>,
variant?: 'primary' | 'danger' | 'default',
disabled?: boolean
}

// Custom component
{
type: 'custom',
id: string,
label?: string,
description?: string,
render: () => ReactNode,
disabled?: boolean
}