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

@extedcoud/smart-components

v0.0.1

Published

React UI component library

Downloads

13

Readme

@extedcoud/smart-components

Headless React components with the AI wiring already done. You bring an LLM client and your own styles; the library handles the fiddly parts — debouncing, aborting stale requests, ghost-text positioning, keyboard handling.

📖 Docs + live demos · 🎮 Storybook

Install

pnpm add @extedcoud/smart-components react react-dom

No runtime dependencies beyond React. Every adapter is built on plain fetch.

Quick start

import { SmartProvider, SmartTextbox, SmartSuggestion } from '@extedcoud/smart-components';
import { createProxyClient } from '@extedcoud/smart-components/adapters/proxy';
import '@extedcoud/smart-components/style.css';

// In production, point this at your own backend so the API key stays server-side.
const client = createProxyClient({ url: '/api/smart' });

export function App() {
  const [v, setV] = useState('');
  return (
    <SmartProvider client={client} model="gpt-4o-mini">
      <SmartTextbox value={v} onChange={setV} context="user is writing a support reply" />
      <SmartSuggestion
        value={v}
        onChange={setV}
        onSelect={(s) => console.log('picked', s)}
        context="naming a new project"
      />
    </SmartProvider>
  );
}

Components

| Component | What it does | | --- | --- | | <SmartTextbox> | Single-line input with Copilot-style ghost completion. ArrowRight accepts (configurable), Esc dismisses. | | <SmartTextarea> | Multiline ghost completion via a mirror div, with multiline stops and optional auto-resize. | | <SmartSuggestion> | Combobox with an AI-generated dropdown. Arrow keys to move, Enter to pick. | | <SmartRewrite> | Render-prop rewrite primitive. Ships with Shorter / Formal / Casual / Fix grammar presets. |

Everything is headless: minimal default DOM, render slots (renderItem, renderGhost, render-props), and native input attributes pass straight through.

Styling the ghost text

The ghost defaults to opacity: 0.4 and inherits the input's color. To recolor it, pass ghostClassName or ghostStyle:

<SmartTextbox value={v} onChange={setV} ghostStyle={{ color: '#0066cc', fontStyle: 'italic' }} />

For richer markup (icons, badges) use the renderGhost render-prop. To style globally, target [data-testid="smart-textbox-ghost"] / [data-testid="smart-textarea-ghost"].

Mobile

Touch is a first-class target, not an afterthought. Things to know:

  • The accept key is configurable. It defaults to ArrowRight, which most soft keyboards lack — on mobile, remap acceptKey or trigger accept imperatively via the field ref.
  • Wrappers use touch-action: manipulation to drop the 300ms tap delay.
  • SmartSuggestion selection runs on pointer events (mouse, touch, pen). Give items min-height: 44px to hit the WCAG touch target.
  • Inputs below 16px font size make iOS Safari zoom on focus — keep SmartTextbox / SmartTextarea at 16px or larger.
  • Known limit: the SmartSuggestion dropdown can sit under the soft keyboard on short viewports. Portal the list or use a fullscreen picker there.

AI clients

SmartClient is a capability-based interface. Use an adapter or implement your own:

| Adapter | Import | When | | --- | --- | --- | | createProxyClient | /adapters/proxy | Production. POSTs to your backend; the key stays on the server. | | createOpenAIClient | /adapters/openai | Dev and demos only — never ship a key to the browser. | | createAnthropicClient | /adapters/anthropic | Anthropic Messages API (complete + stream). | | createMockClient | /adapters/mock | Tests and Storybook. |

Rolling your own is a few lines:

import { SMART_CLIENT_PROTOCOL_VERSION, type SmartClient } from '@extedcoud/smart-components';

const myClient: SmartClient = {
  protocolVersion: SMART_CLIENT_PROTOCOL_VERSION,
  id: 'my-backend',
  capabilities: new Set(['complete', 'stream']),
  async complete(req) { /* ... */ return text; },
  async *stream(req) { /* yield chunks */ },
};

Hooks

The components are thin wrappers over hooks. Reach for these to build your own on the same plumbing:

import {
  useGhostCompletion,
  useSuggestionList,
  useRewrite,
  useSmartState,
} from '@extedcoud/smart-components';

useSmartState — useState that an LLM can fill

A drop-in useState with one extra: ai.generate(context?). The shape is read from your initial value, so the model is constrained to matching JSON — no schema, no parsing.

const [user, setUser, ai] = useSmartState(
  { name: '', age: 0, bio: '' },
  'a fictitious cyberpunk character',
);

ai.generate();          // → { name: 'Kael-9', age: 28, bio: '…' }
setUser({ ... });       // still plain useState

When the seed is empty (null, []), pass options.shape so it knows what to ask for:

const [tags, , ai] = useSmartState<string[]>([], 'tags for a sci-fi blog post', {
  shape: { type: 'array', item: 'string' },
});

A few things worth knowing:

  • Calling setValue mid-generate cancels the in-flight request — the user's edit wins.
  • An empty inner array ({ tags: [] }) throws at mount; seed it or pass shape. Same for Date, Map, Set, functions — JSON-serializable values only.
  • Results are cached LRU(16) by (shape, context). Opt out with { cache: false }.
  • Generate is atomic (no streaming) and needs a client with the complete capability.

Full walkthrough — form autofill, text extraction, palettes — is in the docs.

Develop

A pnpm workspace: the root is the library, docs/ is the public site (Next.js + Fumadocs on GitHub Pages).

pnpm install
pnpm storybook    # :6006 — internal dev surface
pnpm test         # vitest watch
pnpm lint
pnpm typecheck
pnpm build        # lib → dist/

# Docs site (build the lib first — it consumes dist/)
pnpm --filter @extedcoud/smart-components build
pnpm --filter docs dev      # :3000

Docs deploy to GitHub Pages via .github/workflows/deploy-docs.yml on push to main.

Stack

Library: Vite (lib mode), TypeScript, CSS Modules, ESLint 9, Prettier, Vitest + RTL, Storybook. Docs: Next.js 15 (static export), Fumadocs UI, Tailwind v4, MDX.