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

wealthywidgets

v2.0.1

Published

A collection of ready-to-use, customizable React UI components for rapid prototyping and web app development

Downloads

211

Readme

🧩 wealthywidgets

A production-ready React component library with AI-powered generation built in.

CI npm version License: MIT TypeScript


✨ What's inside

| Component | Description | AI-powered | | ---------------- | -------------------------------------------------------------------- | :--------: | | Button | Interactive element with variants, sizes, icons, and loading state | | | Card | Flexible content container with image, header, body, and actions | | | Input | Text field with icon, validation, hint, and inline AI generation | ✦ | | Modal | Accessible dialog with backdrop, keyboard handling, and size presets | | | Dropdown | Single / multi-select with search and option descriptions | | | ProgressBar | Status-coloured progress indicator with label and striped mode | | | Tooltip | Lightweight hover hint with placement and variant options | | | ActivityWidget | Scrollable event timeline with live AI feed generation | ✦ | | WeatherWidget | Current conditions + 7-day forecast card with AI data generation | ✦ | | CodeWidget | Syntax-highlighted code block with editing and AI snippet generation | ✦ |

Every widget is:

  • Independent — import only what you need, zero forced bundle
  • Accessible — ARIA roles, keyboard nav, focus traps where needed
  • Fully typed — complete TypeScript definitions for props and slots
  • Tested — Jest + React Testing Library
  • Customisable — props + SCSS variables

📦 Installation

npm install wealthywidgets
# or
yarn add wealthywidgets
# or
pnpm add wealthywidgets

Add the styles once at the root of your app:

import 'wealthywidgets/dist/styles.css';

🚀 Quick Start

import { Button, Card, Input } from 'wealthywidgets';
import 'wealthywidgets/dist/styles.css';

export default function App() {
  return (
    <Card
      title="Welcome"
      description="Start building with WealthyWidgets."
      actions={<Button variant="primary">Get Started</Button>}
    />
  );
}

📖 Components

Button

import { Button } from 'wealthywidgets';

// Variants: primary | secondary | outline | ghost | danger | success
// Sizes:    xs | sm | md | lg | xl

<Button variant="primary" size="md" onClick={handleSave}>Save</Button>
<Button variant="outline" loading>Saving…</Button>
<Button variant="danger" rounded leftIcon={<TrashIcon />}>Delete</Button>

| Prop | Type | Default | Description | | ----------- | --------------- | ----------- | --------------------------------- | | variant | ButtonVariant | 'primary' | Visual style | | size | ButtonSize | 'md' | Size preset | | loading | boolean | false | Show spinner, disable interaction | | leftIcon | ReactNode | — | Icon before the label | | rightIcon | ReactNode | — | Icon after the label | | fullWidth | boolean | false | Stretch to container | | rounded | boolean | false | Pill shape |


Card

import { Card } from 'wealthywidgets';

<Card
  title="Article Title"
  subtitle="Technology"
  imageSrc="/cover.jpg"
  imageAlt="Cover photo"
  description="A brief summary of the article."
  variant="elevated"
  clickable
  onClick={() => navigate('/article')}
  actions={<Button size="sm">Read more</Button>}
/>;

| Prop | Type | Default | Description | | ------------------ | --------------------------------------- | ----------- | -------------------------- | | title | string | — | Card heading | | subtitle | string | — | Sub-heading | | imageSrc | string | — | Header image URL | | imageAspectRatio | string | '16/9' | CSS aspect ratio | | description | string | — | Body text | | actions | ReactNode | — | Footer slot (buttons etc.) | | headerExtra | ReactNode | — | Top-right header slot | | variant | 'default' \| 'elevated' \| 'outlined' | 'default' | Visual variant | | noPadding | boolean | false | Remove body padding | | clickable | boolean | false | Add hover/pointer styles |


Input ✦

The Input widget supports AI-powered content generation via onAiGenerate. When provided, a ✦ button appears inside the field. Clicking it calls your handler with the current value as a prompt.

import { Input } from 'wealthywidgets';

// Basic
<Input label="Email" type="email" placeholder="[email protected]" />

// Validation
<Input
  label="Username"
  status="error"
  message="Username already taken"
  value={username}
  onChange={e => setUsername(e.target.value)}
/>

// ✦ AI-powered autocomplete
<Input
  label="Product description"
  placeholder="Start typing or click ✦ to generate…"
  value={desc}
  onChange={e => setDesc(e.target.value)}
  onAiGenerate={async (prompt) => {
    const result = await myAI.complete(prompt);
    return result; // returned string replaces the field value
  }}
  aiLoading={loading}
/>

| Prop | Type | Default | Description | | -------------- | --------------------------------------------- | ----------- | ----------------------------------- | | label | string | — | Field label | | hint | string | — | Helper text below field | | message | string | — | Validation message | | status | InputStatus | 'default' | 'error' \| 'success' \| 'warning' | | leftIcon | ReactNode | — | Leading icon | | rightElement | ReactNode | — | Trailing element | | size | 'sm' \| 'md' \| 'lg' | 'md' | Size preset | | fullWidth | boolean | false | Stretch to container | | onAiGenerate | (prompt: string) => Promise<string \| void> | — | AI generate handler | | aiLoading | boolean | false | Loading state on AI button |


Modal

import { Modal, Button } from 'wealthywidgets';

<Modal
  isOpen={open}
  onClose={() => setOpen(false)}
  title="Confirm deletion"
  description="This action cannot be undone."
  size="sm"
  footer={
    <>
      <Button variant="ghost" onClick={() => setOpen(false)}>
        Cancel
      </Button>
      <Button variant="danger" onClick={handleDelete}>
        Delete
      </Button>
    </>
  }
>
  <p>Are you sure you want to delete this item?</p>
</Modal>;

| Prop | Type | Default | Description | | ---------------------- | ---------------------------------------- | ------- | ---------------------------- | | isOpen | boolean | — | Visibility toggle | | onClose | () => void | — | Close handler | | title | string | — | Dialog heading | | description | string | — | Sub-heading | | footer | ReactNode | — | Action buttons | | size | 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full' | 'md' | Width preset | | disableBackdropClose | boolean | false | Prevent backdrop-click close | | hideCloseButton | boolean | false | Hide × button |


Dropdown

import { Dropdown } from 'wealthywidgets';

const options = [
  { value: 'react', label: 'React', description: 'UI library' },
  { value: 'vue', label: 'Vue', description: 'Progressive framework' },
  { value: 'svelte', label: 'Svelte', icon: <SvelteIcon /> },
];

// Single select
<Dropdown
  label="Framework"
  options={options}
  value={selected}
  onChange={val => setSelected(val as string)}
  searchable
  placeholder="Choose framework…"
/>

// Multi-select
<Dropdown
  options={options}
  multiple
  value={selections}
  onChange={vals => setSelections(vals as string[])}
/>

| Prop | Type | Default | Description | | ------------- | -------------------- | ------- | ------------------------ | | options | DropdownOption[] | — | Available options | | value | string \| string[] | — | Selected value(s) | | onChange | (value) => void | — | Selection change handler | | multiple | boolean | false | Enable multi-select | | searchable | boolean | false | Show search field | | placeholder | string | — | Empty-state text | | label | string | — | Field label | | disabled | boolean | false | Disable control | | fullWidth | boolean | false | Stretch to container |


ProgressBar

import { ProgressBar } from 'wealthywidgets';

<ProgressBar value={65} />
<ProgressBar value={90} status="danger" showLabel title="Disk usage" />
<ProgressBar value={40} status="info" striped showLabel labelPosition="right" />

| Prop | Type | Default | Description | | --------------- | ------------------------------ | ----------- | ---------------------------------------------- | | value | number | — | Progress 0–100 | | max | number | 100 | Maximum value | | status | ProgressBarStatus | 'default' | 'success' \| 'warning' \| 'danger' \| 'info' | | size | 'xs' \| 'sm' \| 'md' \| 'lg' | 'md' | Bar thickness | | showLabel | boolean | false | Display percentage | | labelPosition | 'right' \| 'top' \| 'inside' | 'right' | Label placement | | title | string | — | Text above bar | | striped | boolean | false | Animated shimmer | | formatLabel | (value, max) => string | — | Custom label format |


Tooltip

import { Tooltip, Button } from 'wealthywidgets';

<Tooltip content="Save your work" placement="top">
  <Button>Save</Button>
</Tooltip>

<Tooltip content={<span><b>Shortcut:</b> ⌘S</span>} placement="right" variant="light">
  <InfoIcon />
</Tooltip>

| Prop | Type | Default | Description | | ----------- | ---------------------------------------- | -------- | ----------------------- | | content | ReactNode | — | Tooltip text or element | | placement | 'top' \| 'bottom' \| 'left' \| 'right' | 'top' | Preferred placement | | variant | 'dark' \| 'light' | 'dark' | Colour scheme | | showDelay | number | 300 | ms before show | | hideDelay | number | 100 | ms before hide | | disabled | boolean | false | Disable tooltip | | maxWidth | number \| string | 240 | Max bubble width |


ActivityWidget ✦

A scrollable timeline of activity events. Pass onAiGenerate to let users describe the feed they want and have it generated on the fly.

import { ActivityWidget } from 'wealthywidgets';

const [items, setItems] = useState([
  { id: 1, label: 'PR merged', description: 'feat: dark mode', status: 'success', timestamp: '3m ago' },
  { id: 2, label: 'Build failed', status: 'danger', timestamp: new Date() },
  { id: 3, label: 'Deploy started', status: 'info', timestamp: '12m ago' },
]);

// Static
<ActivityWidget title="Recent Activity" items={items} maxVisible={3} />

// ✦ AI-powered
<ActivityWidget
  title="Team Activity"
  items={items}
  onAiGenerate={async (prompt) => {
    const generated = await myAI.generateActivity(prompt);
    setItems(generated);
  }}
  bordered
/>

| Prop | Type | Default | Description | | -------------- | ----------------------------------- | ------------ | ------------------------ | | items | ActivityItem[] | — | Timeline entries | | title | string | 'Activity' | Widget heading | | compact | boolean | false | Reduced vertical spacing | | bordered | boolean | false | Left-border item style | | maxVisible | number | 5 | Items before "show more" | | onAiGenerate | (prompt: string) => Promise<void> | — | AI generate handler | | loading | boolean | false | Loading state |

ActivityItem shape:

interface ActivityItem {
  id: string | number;
  label: string;
  description?: string;
  timestamp?: string | Date; // Date auto-formatted as relative time
  icon?: ReactNode;
  status?: 'success' | 'warning' | 'danger' | 'info' | 'default';
}

WeatherWidget ✦

Current conditions card with a 7-day forecast strip. The condition drives a beautiful gradient background. Pass onAiGenerate to let users request weather for any city or scenario.

import { WeatherWidget } from 'wealthywidgets';

// Static
<WeatherWidget
  location="San Francisco, CA"
  temperature={18}
  unit="C"
  condition="partly-cloudy"
  feelsLike={15}
  humidity={72}
  windSpeed={14}
  showForecast
  forecast={[
    { day: 'Mon', high: 19, low: 12, condition: 'sunny' },
    { day: 'Tue', high: 16, low: 10, condition: 'rainy' },
    { day: 'Wed', high: 21, low: 14, condition: 'partly-cloudy' },
  ]}
/>

// ✦ AI-powered
<WeatherWidget
  location={city}
  temperature={temp}
  condition={cond}
  onAiGenerate={async (prompt) => {
    const data = await myAI.getWeather(prompt);
    applyWeather(data);
  }}
/>

| Prop | Type | Default | Description | | -------------- | ----------------------------------- | --------------- | --------------------- | | location | string | 'My Location' | City / place name | | temperature | number | — | Current temp | | unit | 'C' \| 'F' | 'C' | Temperature unit | | condition | WeatherCondition | 'sunny' | Current condition | | feelsLike | number | — | Perceived temperature | | humidity | number | — | Humidity % | | windSpeed | number | — | Wind speed | | windUnit | string | 'km/h' | Wind speed unit label | | visibility | number | — | Visibility km | | uvIndex | number | — | UV index | | forecast | WeatherForecastDay[] | [] | 7-day forecast data | | showForecast | boolean | false | Show forecast strip | | onAiGenerate | (prompt: string) => Promise<void> | — | AI generate handler | | loading | boolean | false | Loading state |

Available conditions: sunny · cloudy · partly-cloudy · rainy · stormy · snowy · windy · foggy · hail


CodeWidget ✦

A dark-themed code viewer with macOS-style toolbar, line numbers, copy button, and optional inline editing. The AI bar lets users describe snippets they want generated.

import { CodeWidget } from 'wealthywidgets';

const snippet = `import { useState } from 'react';

export function useCounter(initial = 0) {
  const [count, setCount] = useState(initial);
  return { count, inc: () => setCount(c => c + 1) };
}`;

// Read-only with copy
<CodeWidget
  title="useCounter.ts"
  language="typescript"
  code={snippet}
  showCopy
  showLineNumbers
  highlightLines={[4]}
/>

// Editable pad
<CodeWidget
  language="python"
  code={code}
  editable
  onChange={setCode}
  maxHeight="500px"
/>

// ✦ AI-powered snippet generator
<CodeWidget
  title="Generated snippet"
  language="typescript"
  code={snippet}
  showCopy
  onAiGenerate={async (prompt) => {
    const result = await myAI.generateCode(prompt);
    setSnippet(result);
  }}
/>

| Prop | Type | Default | Description | | ----------------- | ----------------------------------- | ------------- | ------------------------------ | | code | string | '' | Source code | | language | CodeLanguage | 'plaintext' | Language badge | | title | string | — | File name in toolbar | | showCopy | boolean | true | Show copy button | | showLineNumbers | boolean | false | Line number gutter | | maxHeight | string | '400px' | Scroll container height | | highlightLines | number[] | [] | Lines to highlight (1-indexed) | | editable | boolean | false | Inline editable textarea | | onChange | (code: string) => void | — | Edit change handler | | onAiGenerate | (prompt: string) => Promise<void> | — | AI generate handler | | loading | boolean | false | Loading state |

Supported languages: typescript · javascript · python · rust · go · bash · json · css · html · sql · plaintext


🎨 Customisation

SCSS variables

Override variables before importing the library:

// your-theme.scss
$color-primary: #7c3aed;
$color-primary-hover: #6d28d9;
$font-family-base: 'Geist', sans-serif;
$radius-md: 0.5rem;

@use 'wealthywidgets/src/styles/variables' as *;

CSS custom properties

All colour tokens are available as CSS variables in the distributed stylesheet:

:root {
  --ww-color-primary: #7c3aed;
  --ww-radius-md: 0.5rem;
}

🤖 AI Integration Pattern

All AI-powered widgets follow the same onAiGenerate interface — you supply the async handler, we supply the UI:

// 1. User types a description in the ✦ prompt bar
// 2. onAiGenerate is called with their prompt
// 3. Your handler fetches / generates data and updates state
// 4. The widget re-renders with the new data

async function generateActivity(prompt: string) {
  const res = await fetch('/api/ai/activity', {
    method: 'POST',
    body: JSON.stringify({ prompt }),
  });
  const data = await res.json();
  setItems(data.items);
}

<ActivityWidget items={items} onAiGenerate={generateActivity} />;

This pattern is provider-agnostic — use OpenAI, Anthropic, a local model, or any API.


🛠 Development

# Install dependencies
npm install

# Build the library
npm run build

# Run tests
npm test

# Watch mode
npm run test -- --watch

Project structure

src/
├── components/
│   ├── Button/
│   ├── Card/
│   ├── Dropdown/
│   ├── Input/
│   ├── Modal/
│   ├── ProgressBar/
│   ├── Tooltip/
│   ├── ActivityWidget/   ← new
│   ├── WeatherWidget/    ← new
│   └── CodeWidget/       ← new
├── styles/
│   └── _variables.scss
└── index.ts

Each component folder contains: Component.tsx · Component.types.ts · Component.scss · Component.test.tsx · index.ts


🤝 Contributing

See CONTRIBUTING.md for guidelines on adding new components, SCSS conventions, and the testing requirements.


📄 License

MIT © AceAnomDev