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

@assassin1717/aifelib

v1.5.0

Published

Private UI component library — React + TypeScript + Tailwind + Lucide

Readme

@assassin1717/aifelib

Private UI component library — React + TypeScript + Tailwind CSS + Lucide Icons.

Built to standardize frontend across all internal apps: consistent visuals, mobile-first, accessible, and easy for AI agents to use.


Install

npm install @assassin1717/aifelib

Peer dependencies (must be installed in the consuming app):

npm install react react-dom

Tailwind CSS must be configured in the consuming app. Add the library path to your content array so Tailwind scans its classes:

// tailwind.config.js / tailwind.config.ts
content: [
  "./src/**/*.{ts,tsx}",
  "./node_modules/@assassin1717/aifelib/dist/**/*.js",
]

Setup

Wrap your app with ToastProvider (required for useToast to work):

import { ToastProvider } from "@assassin1717/aifelib";

function App() {
  return (
    <ToastProvider>
      <YourApp />
    </ToastProvider>
  );
}

Dark Mode

All components support dark mode via Tailwind's class strategy. Add the dark class to your <html> element (or any wrapper) to activate it:

<!-- entire app in dark mode -->
<html class="dark">
// controlled dark mode (e.g. with a toggle)
<div className={isDark ? "dark" : ""}>
  <YourApp />
</div>

Make sure your Tailwind config also has darkMode: "class":

// tailwind.config.ts
export default {
  darkMode: "class",
  content: [
    "./src/**/*.{ts,tsx}",
    "./node_modules/@assassin1717/aifelib/dist/**/*.js",
  ],
};

Components

Phase 1 — Form

Button

import { Button } from "@assassin1717/aifelib";

<Button>Save</Button>
<Button variant="destructive" size="lg">Delete</Button>
<Button loading>Saving…</Button>
<Button fullWidth>Submit</Button>

Props:

  • variant: "primary" | "secondary" | "ghost" | "destructive" | "outline" — default "primary"
  • size: "sm" | "md" | "lg" — default "md"
  • loading: boolean — shows spinner, disables button
  • fullWidth: booleanw-full
  • All native <button> props

Input

import { Input } from "@assassin1717/aifelib";
import { Search } from "lucide-react";

<Input placeholder="Email" type="email" />
<Input error placeholder="Invalid value" />
<Input startIcon={<Search size={16} />} placeholder="Search…" />

Props:

  • error: boolean — red border + aria-invalid
  • startIcon: ReactNode — icon on the left
  • endIcon: ReactNode — icon on the right
  • All native <input> props

Textarea

import { Textarea } from "@assassin1717/aifelib";

<Textarea placeholder="Description" rows={4} />
<Textarea error />

Props:

  • error: boolean
  • All native <textarea> props

Select

import { Select } from "@assassin1717/aifelib";

<Select
  options={[
    { value: "admin", label: "Admin" },
    { value: "user", label: "User" },
  ]}
  placeholder="Choose role…"
/>

Props:

  • options: { value: string; label: string; disabled?: boolean }[]
  • placeholder: string — disabled first option
  • error: boolean
  • All native <select> props

Checkbox

import { Checkbox } from "@assassin1717/aifelib";

<Checkbox label="Accept terms" />
<Checkbox label="Required" error />

Props:

  • label: string — inline label (renders its own <label>)
  • error: boolean
  • All native <input type="checkbox"> props except type

Label

import { Label } from "@assassin1717/aifelib";

<Label htmlFor="email" required>Email</Label>

Props:

  • required: boolean — appends a red *
  • All native <label> props

FormField

Composes Label + any input child. Automatically injects id, error, and aria-describedby into the child.

import { FormField, Input } from "@assassin1717/aifelib";

<FormField label="Email" htmlFor="email" required error="Invalid email">
  <Input id="email" type="email" />
</FormField>

<FormField label="Bio" htmlFor="bio" hint="Max 200 characters">
  <Textarea id="bio" />
</FormField>

Props:

  • label: string
  • htmlFor: string — links label to input
  • required: boolean
  • hint: string — helper text shown below input (hidden when error is set)
  • error: string — error message shown below input with role="alert"

Phase 2 — Feedback & Overlays

Spinner

import { Spinner } from "@assassin1717/aifelib";

<Spinner />
<Spinner size="lg" label="Loading users…" />

Props:

  • size: "sm" | "md" | "lg" — default "md"
  • label: string — aria-label for screen readers — default "Loading…"

Badge

import { Badge } from "@assassin1717/aifelib";

<Badge>Default</Badge>
<Badge variant="success">Active</Badge>
<Badge variant="destructive">Error</Badge>

Props:

  • variant: "default" | "success" | "warning" | "destructive" | "info" | "outline" — default "default"
  • All native <span> props

Alert

import { Alert } from "@assassin1717/aifelib";

<Alert variant="success" title="Saved" onDismiss={() => {}}>
  Your changes have been saved.
</Alert>

Props:

  • variant: "info" | "success" | "warning" | "destructive" — default "info"
  • title: string
  • onDismiss: () => void — shows dismiss button
  • children: message body

Card

import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@assassin1717/aifelib";

<Card>
  <CardHeader>
    <CardTitle>Users</CardTitle>
    <CardDescription>Manage your team members.</CardDescription>
  </CardHeader>
  <CardContent>Content here</CardContent>
  <CardFooter>
    <Button>Save</Button>
  </CardFooter>
</Card>

Card props:

  • padding: "none" | "sm" | "md" | "lg" — default "md"

Modal

import { Modal, Button } from "@assassin1717/aifelib";

const [open, setOpen] = useState(false);

<Modal
  open={open}
  onClose={() => setOpen(false)}
  title="Edit user"
  description="Update the user details below."
  size="md"
  footer={
    <>
      <Button variant="ghost" onClick={() => setOpen(false)}>Cancel</Button>
      <Button onClick={handleSave}>Save</Button>
    </>
  }
>
  <FormField label="Name" htmlFor="name">
    <Input id="name" />
  </FormField>
</Modal>

Props:

  • open: boolean
  • onClose: () => void — called on backdrop click and ESC key
  • title: string
  • description: string
  • size: "sm" | "md" | "lg" | "xl" | "full" — default "md"
  • footer: ReactNode — action buttons slot (rendered inside the modal)
  • children: modal body

Behaviour: focus trap, ESC to close, body scroll lock, bottom sheet on mobile / centered dialog on sm+.

ConfirmDialog

import { ConfirmDialog } from "@assassin1717/aifelib";

<ConfirmDialog
  open={open}
  onClose={() => setOpen(false)}
  onConfirm={handleDelete}
  title="Delete user"
  description="This action cannot be undone."
  variant="destructive"
  confirmLabel="Delete"
  loading={isDeleting}
/>

Props:

  • open: boolean
  • onClose: () => void
  • onConfirm: () => void
  • title: string
  • description: string
  • variant: "primary" | "destructive" — default "primary"
  • confirmLabel: string — default "Confirm"
  • cancelLabel: string — default "Cancel"
  • loading: boolean

ToastMessage

import { useToast } from "@assassin1717/aifelib";

function MyComponent() {
  const { addToast } = useToast();

  return (
    <Button onClick={() => addToast({ type: "success", title: "Saved", description: "Changes saved." })}>
      Save
    </Button>
  );
}

addToast options:

  • type: "success" | "error" | "warning" | "info"
  • title: string
  • description: string
  • duration: number — ms before auto-dismiss, default 5000. Pass 0 to disable auto-dismiss.

Requires <ToastProvider> at the root of the app.


Phase 3 — Data Display

Table

Mobile: each row renders as a card with label: value pairs. Desktop: standard table with horizontal scroll.

import {
  Table, TableHeader, TableBody, TableRow,
  TableHead, TableCell, TableToolbar, TableEmptyState
} from "@assassin1717/aifelib";

<TableToolbar
  filters={<Input placeholder="Search…" />}
  actions={<Button>Add user</Button>}
/>

<Table>
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Status</TableHead>
      <TableHead align="right">Actions</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    {users.length === 0 ? (
      <TableEmptyState colSpan={3} title="No users found" description="Add your first user." />
    ) : (
      users.map(user => (
        <TableRow key={user.id}>
          <TableCell label="Name">{user.name}</TableCell>
          <TableCell label="Status"><Badge variant="success">Active</Badge></TableCell>
          <TableCell label="Actions" align="right"><Button size="sm">Edit</Button></TableCell>
        </TableRow>
      ))
    )}
  </TableBody>
</Table>

Important: Always pass label to TableCell — it's the column header shown on mobile cards.

TableHead / TableCell props:

  • align: "left" | "center" | "right" — default "left"

TableCell extra props:

  • label: string — column name shown on mobile

TableEmptyState props:

  • colSpan: number — must match number of columns
  • title: string
  • description: string
  • icon: ReactNode
  • action: ReactNode — action button

TableToolbar props:

  • filters: ReactNode — left slot (search, selects)
  • actions: ReactNode — right slot (buttons)

PageHeader

import { PageHeader, Button } from "@assassin1717/aifelib";

<PageHeader
  title="Users"
  description="Manage your team members."
  actions={
    <>
      <Button variant="outline">Export</Button>
      <Button>Add user</Button>
    </>
  }
/>

Props:

  • title: string
  • description: string
  • prefix: ReactNode — breadcrumb or back link
  • actions: ReactNode — right slot, stacks on mobile

EmptyState

import { EmptyState, Button } from "@assassin1717/aifelib";
import { Users } from "lucide-react";

<EmptyState
  icon={<Users size={48} />}
  title="No users yet"
  description="Add your first team member to get started."
  action={<Button>Add user</Button>}
  secondaryAction={<Button variant="ghost">Learn more</Button>}
/>

Props:

  • icon: ReactNode
  • title: string
  • description: string
  • action: ReactNode — primary button
  • secondaryAction: ReactNode — secondary button

Phase 4 — Navigation & Extras

Tabs

import { Tabs, TabPanel } from "@assassin1717/aifelib";

const [tab, setTab] = useState("overview");

<Tabs
  tabs={[
    { value: "overview", label: "Overview" },
    { value: "members", label: "Members" },
    { value: "settings", label: "Settings", disabled: true },
  ]}
  value={tab}
  onChange={setTab}
>
  <TabPanel value="overview" activeValue={tab}>Overview content</TabPanel>
  <TabPanel value="members" activeValue={tab}>Members content</TabPanel>
</Tabs>

Tabs props:

  • tabs: { value: string; label: string; disabled?: boolean }[]
  • value: string — controlled active tab
  • onChange: (value: string) => void

TabPanel props:

  • value: string — this panel's tab value
  • activeValue: string — currently active tab value

Behaviour: horizontal scroll when tabs overflow on mobile, arrow key navigation.

DropdownMenu

import { DropdownMenu, Button } from "@assassin1717/aifelib";
import { Edit, Trash2, MoreHorizontal } from "lucide-react";

<DropdownMenu
  trigger={<Button variant="ghost" size="sm"><MoreHorizontal size={16} /></Button>}
  align="right"
  items={[
    { label: "Edit", icon: <Edit size={14} />, onClick: handleEdit },
    { label: "Delete", icon: <Trash2 size={14} />, destructive: true, divider: true, onClick: handleDelete },
  ]}
/>

Props:

  • trigger: ReactNode — the button that opens the menu
  • items: DropdownMenuItem[]
  • align: "left" | "right" — default "right"

DropdownMenuItem:

  • label: string
  • onClick: () => void
  • icon: ReactNode
  • disabled: boolean
  • destructive: boolean — red styling
  • divider: boolean — visual separator above item

Behaviour: closes on outside click, ESC, or item selection. Focus returns to trigger on ESC.

Pagination

import { Pagination } from "@assassin1717/aifelib";

const [page, setPage] = useState(1);

<Pagination page={page} totalPages={20} onChange={setPage} />

Props:

  • page: number
  • totalPages: number
  • onChange: (page: number) => void
  • siblingCount: number — page buttons around current, default 1

Mobile: shows "X / Y" compact label instead of page buttons. Returns null when totalPages <= 1.

Drawer

import { Drawer, Button } from "@assassin1717/aifelib";

<Drawer
  open={open}
  onClose={() => setOpen(false)}
  title="Filters"
  side="right"
  footer={
    <>
      <Button variant="ghost" onClick={() => setOpen(false)}>Cancel</Button>
      <Button onClick={applyFilters}>Apply</Button>
    </>
  }
>
  <FormField label="Status" htmlFor="status">
    <Select id="status" options={statusOptions} />
  </FormField>
</Drawer>

Props:

  • open: boolean
  • onClose: () => void
  • title: string
  • description: string
  • side: "right" | "left" — default "right"
  • widthClass: string — Tailwind width class for desktop, default "sm:w-96"
  • footer: ReactNode — action buttons slot

Behaviour: same as Modal (focus trap, ESC, scroll lock). Bottom sheet on mobile, side panel on sm+.

Tooltip

import { Tooltip, Button } from "@assassin1717/aifelib";

<Tooltip content="Save your changes" side="top">
  <Button>Save</Button>
</Tooltip>

Props:

  • content: string
  • side: "top" | "bottom" | "left" | "right" — default "top"
  • children: single ReactElement — must accept onMouseEnter, onMouseLeave, onFocus, onBlur

Behaviour: visible on hover and keyboard focus.


Phase 5 — App Shell

AppShell

Full-page layout with fixed sidebar (desktop) + Drawer sidebar (mobile).

import { AppShell } from "@assassin1717/aifelib";
import { LayoutDashboard, Users, Settings } from "lucide-react";

<AppShell
  sidebar={{
    logo: <span className="font-bold text-blue-600">MyApp</span>,
    groups: [
      {
        items: [
          { label: "Dashboard", href: "/", icon: <LayoutDashboard size={18} />, active: true },
          { label: "Users", href: "/users", icon: <Users size={18} /> },
        ],
      },
      {
        title: "Config",
        items: [
          { label: "Settings", href: "/settings", icon: <Settings size={18} /> },
        ],
      },
    ],
    footer: <div className="text-sm text-gray-500">v1.0.0</div>,
  }}
  topbar={{ title: "Dashboard" }}
>
  <PageHeader title="Dashboard" />
  {/* page content */}
</AppShell>

AppShell props:

  • sidebar: SidebarProps — see Sidebar below
  • topbar: TopbarProps (without onMenuOpen) — omit to hide topbar entirely
  • children: page content rendered in the scrollable main area

Sidebar

Can be used standalone (e.g. inside a custom layout).

import { Sidebar } from "@assassin1717/aifelib";

<Sidebar
  logo={<img src="/logo.svg" alt="MyApp" className="h-8" />}
  groups={[
    {
      items: [
        { label: "Dashboard", href: "/", active: true },
        { label: "Users", href: "/users", badge: 3 },
        { label: "Archived", href: "/archived", disabled: true },
      ],
    },
  ]}
  footer={<Button variant="ghost" fullWidth>Logout</Button>}
/>

SidebarNavItem:

  • label: string
  • href: string — renders as <a>, omit to render as <button>
  • icon: ReactNode
  • active: boolean — highlights item, sets aria-current="page"
  • disabled: boolean
  • onClick: () => void
  • badge: string | number — shown on the right

SidebarNavGroup:

  • title: string — optional group heading
  • items: SidebarNavItem[]

Topbar

import { Topbar } from "@assassin1717/aifelib";

<Topbar
  onMenuOpen={() => setSidebarOpen(true)}
  title="Users"
  actions={<Avatar name="Tiago" />}
/>

Props:

  • onMenuOpen: () => void — hamburger button handler (button hidden on sm+)
  • title: ReactNode
  • actions: ReactNode — right slot
  • hideMenuButton: boolean — hides the hamburger

Phase 5 — Extras (continued)

StatsCard

Metric card with label, value and optional trend indicator.

import { StatsCard } from "@assassin1717/aifelib";
import { Users } from "lucide-react";

<StatsCard
  label="Total Users"
  value="1,284"
  trend={{ direction: "up", value: "+12%" }}
  icon={<Users size={20} />}
/>

Props:

  • label: string
  • value: string | number
  • trend: { direction: "up" | "down" | "neutral"; value: string } — optional trend badge
  • icon: ReactNode — optional icon in the top-right corner
  • size: "sm" | "md" — default "md"

Grid

Responsive CSS grid wrapper.

import { Grid } from "@assassin1717/aifelib";

<Grid cols={3} gap="md">
  <StatsCard label="Users" value="1,284" />
  <StatsCard label="Revenue" value="$42k" />
  <StatsCard label="Uptime" value="99.9%" />
</Grid>

Props:

  • cols: 1 | 2 | 3 | 4 | 6 | 12 — default 1
  • gap: "none" | "sm" | "md" | "lg" — default "md"
  • responsive: { sm?: GridCols; md?: GridCols; lg?: GridCols } — breakpoint overrides

New Components

Skeleton

Pulsing placeholder for loading states. Prevents layout shift while content loads.

import { Skeleton, SkeletonCard } from "@assassin1717/aifelib";

// custom shapes
<Skeleton className="h-4 w-48" />
<Skeleton className="h-32 w-full" />

// pre-built card preset
<SkeletonCard />
<SkeletonCard className="w-80" />

Skeleton props:

  • className — Tailwind width/height classes (required to give it dimensions)
  • animate: boolean — pulse animation, default true

SkeletonCard props:

  • className — additional Tailwind classes

SectionHeader

Title + description + actions slot for sections within a page. Distinct from PageHeader which is used at page level.

import { SectionHeader, Button } from "@assassin1717/aifelib";

<SectionHeader
  title="Recent Ideas"
  description="Ideas submitted in the last 7 days."
  actions={<Button size="sm">View all</Button>}
/>

Props:

  • title: string
  • description: string
  • actions: ReactNode — right slot
  • className: string

DateInput

Native <input type="date"> with consistent styling matching Input. Supports dark mode date picker icon via color-scheme.

import { DateInput } from "@assassin1717/aifelib";

const [date, setDate] = useState("");

<DateInput value={date} onChange={setDate} />
<DateInput value={date} onChange={setDate} error min="2024-01-01" max="2026-12-31" />

Props:

  • value: string — ISO date string (YYYY-MM-DD)
  • onChange: (value: string) => void
  • error: boolean — red border
  • min / max: string — ISO date bounds
  • All native <input type="date"> props except type and onChange

Compose with FormField for labels and error messages:

<FormField label="Retention date" htmlFor="ret" error={errors.date}>
  <DateInput id="ret" value={date} onChange={setDate} error={!!errors.date} />
</FormField>

FileUpload

Drag-and-drop file input with click-to-upload fallback.

import { FileUpload } from "@assassin1717/aifelib";

<FileUpload
  accept=".pdf,.jpg,.png"
  multiple
  onFiles={(files) => handleUpload(files)}
  hint="PDF, JPG or PNG up to 10MB"
  error={uploadError}
  loading={isUploading}
/>

Props:

  • onFiles: (files: File[]) => void — called with selected/dropped files
  • accept: string — MIME types or extensions (e.g. ".pdf,.jpg")
  • multiple: boolean — allow selecting multiple files
  • hint: string — helper text shown inside the drop zone
  • error: string — error message shown below the drop zone
  • loading: boolean — shows "Uploading…" state, disables interaction
  • disabled: boolean
  • className: string

Slider

Range input with styled track, thumb, and optional value display.

import { Slider } from "@assassin1717/aifelib";

const [volume, setVolume] = useState(40);

<Slider value={volume} onChange={setVolume} />

<Slider
  label="Price range"
  value={price}
  onChange={setPrice}
  min={0}
  max={1000}
  step={10}
  showValue
  formatValue={(v) => `$${v}`}
  hint="Set your maximum budget"
/>

Props:

  • value: number — controlled value
  • onChange: (value: number) => void
  • min: number — default 0
  • max: number — default 100
  • step: number — default 1
  • showValue: boolean — shows current value on the right of the label
  • formatValue: (value: number) => string — custom value formatter, default String
  • label: string
  • hint: string — helper text
  • error: string — error message
  • disabled: boolean

Utilities

cn

Merges Tailwind classes safely (clsx + tailwind-merge).

import { cn } from "@assassin1717/aifelib";

<div className={cn("px-4 py-2", isActive && "bg-blue-50", className)} />

Development

# Install dependencies
npm install

# Run Storybook (component explorer)
npm run dev

# Run tests
npm test
npm run test:watch

# Type check
npm run type-check

# Lint
npm run lint

# Build library
npm run build

Release process

  1. Work on a feature branch (dev/<name>)
  2. Merge to staging — pipeline runs lint + type-check + test + build
  3. Bump version in package.json (npm version patch|minor|major)
  4. Merge stagingmain — pipeline publishes to npm automatically

CI/CD

Bitbucket Pipelines (bitbucket-pipelines.yml):

| Branch | Steps | |---|---| | Any push | install → lint + type-check + test (parallel) → build | | staging | Same as above | | main | Above + publish to npm |

Required pipeline variable (set in Bitbucket repository Settings → Pipelines → Variables):

  • NPM_TOKEN — npm access token with read+write on @assassin1717 scope. Mark as Secured.

Design decisions

  • No CSS-in-JS — Tailwind only. No runtime style injection.
  • No headless UI library — all components are hand-rolled to keep the bundle small and predictable.
  • Composition over configurationFormField injects props into children via cloneElement. Modal/Drawer/ConfirmDialog define their own action buttons internally — callers pass callbacks, not buttons.
  • Mobile-first — touch targets minimum 44px, bottom sheets on mobile, horizontal scroll on tables replaced by card layout.
  • Accessibilityaria-invalid, aria-describedby, aria-current, role="alert", role="status", focus traps in overlays, keyboard navigation in Tabs and DropdownMenu.
  • exactOptionalPropertyTypes: true — optional props are spread conditionally, never passed as undefined explicitly.