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

@manyrows/appkit-react

v1.1.0

Published

React SDK for Manyrows AppKit. Provides authentication, real-time data, and image management for customer-facing apps.

Readme

@manyrows/appkit-react

React SDK for Manyrows AppKit. Provides authentication, real-time data, and image management for customer-facing apps.

Installation

npm install @manyrows/appkit-react

Quick Start

import { AppKit, AppKitAuthed, useUser, UserButton } from "@manyrows/appkit-react";

function MyApp() {
  const user = useUser();

  return (
    <div>
      <header style={{ display: "flex", justifyContent: "space-between", padding: 16 }}>
        <h1>My App</h1>
        <UserButton />
      </header>
      {user && <p>Welcome, {user.name || user.email}</p>}
    </div>
  );
}

export default function Page() {
  return (
    <AppKit workspace="acme" appId="your-app-id">
      <AppKitAuthed fallback={null}>
        <MyApp />
      </AppKitAuthed>
    </AppKit>
  );
}

Only workspace and appId are required. The runtime script loads automatically.

Convenience Hooks

Access common data without drilling into snapshot:

import {
  useUser,         // user's account (email, name)
  useRoles,        // string[] of roles
  usePermissions,  // string[] of permissions
  usePermission,   // check a single permission: usePermission("edit")
  useRole,         // check a single role: useRole("admin")
  useFeatureFlags, // all feature flags
  useFeatureFlag,  // check one flag: useFeatureFlag("dark-mode")
  useConfig,       // all config values
  useConfigValue,  // single config: useConfigValue("api-url", "default")
  useToken,        // JWT token for API calls
  useAuthFetch,    // fetch wrapper with automatic Bearer token
} from "@manyrows/appkit-react";

Auth Patterns

Option A (recommended): Use <AppKitAuthed> to gate authenticated content:

<AppKit workspace="acme" appId="your-app-id">
  <AppKitAuthed fallback={null}>
    <MyApp />
  </AppKitAuthed>
</AppKit>

Option B: Gate manually with useAppKit():

<AppKit workspace="acme" appId="your-app-id">
  {useAppKit().isAuthenticated ? <MyApp /> : null}
</AppKit>

Images

The SDK includes a full image management system — upload, browse, display, edit, and delete — backed by the manyrows-images service.

Setup

Images work automatically when the workspace has an imagesBaseURL configured. All image hooks and components read auth and config from the AppKit context, so they must be rendered inside <AppKitAuthed>.

useImages

Hook for listing, searching, editing, and deleting images.

import { useImages } from "@manyrows/appkit-react";

function Gallery() {
  const {
    images,       // ImageResource[]
    loading,
    error,
    page,
    pageCount,
    total,
    setPage,
    setQuery,     // search by title
    refetch,
    removeImage,  // (imageId) => Promise<void>
    updateImage,  // (imageId, { title, description }) => Promise<void>
    available,    // false if images service not configured
  } = useImages({ page: 0, pageSize: 20 });

  if (!available) return null;
  // ...
}

Options:

| Prop | Type | Default | Description | |------|------|---------|-------------| | page | number | 0 | Starting page (zero-indexed) | | pageSize | number | 50 | Items per page | | q | string | — | Search query | | enabled | boolean | true | Enable/disable fetching |

useImageUpload

Hook for uploading images with progress tracking.

import { useImageUpload } from "@manyrows/appkit-react";

function Uploader() {
  const { upload, cancel, progress, reset, available } = useImageUpload();

  const handleUpload = async (file: File) => {
    await upload({
      file,
      title: "My image",
      description: "Optional description",
      variants: "sq200,w400,w800",
    });
  };

  return (
    <div>
      <p>Status: {progress.status}</p>
      <p>Progress: {progress.progress}%</p>
      <button onClick={cancel}>Cancel</button>
    </div>
  );
}

Progress state:

| Field | Type | Description | |-------|------|-------------| | status | "idle" \| "uploading" \| "success" \| "error" | Current state | | progress | number | 0–100 percentage | | bytesUploaded | number | Bytes transferred | | bytesTotal | number | Total file size | | error | string | null | Error message |

MrImage

Responsive image component with automatic variant selection.

import { MrImage } from "@manyrows/appkit-react";

// Auto-selects best variant for dimensions + device DPR
<MrImage image={image} width={400} height={300} />

// Explicit variant
<MrImage image={image} variant="sq200" />

// Full-size original
<MrImage image={image} />

Props:

| Prop | Type | Default | Description | |------|------|---------|-------------| | image | ImageResource | required | Image to display | | width | number | — | Display width (used for variant selection) | | height | number | — | Display height | | variant | string | — | Explicit variant name (disables auto-selection) | | alt | string | image.title | Alt text | | className | string | — | CSS class | | style | CSSProperties | — | Inline styles | | loading | "lazy" \| "eager" | "lazy" | Browser loading strategy | | objectFit | CSSProperties["objectFit"] | "cover" | CSS object-fit | | sizes | string | — | Responsive sizes attribute | | onLoad | () => void | — | Load callback | | onError | () => void | — | Error callback |

Generates a srcSet from available width-based variants for responsive loading.

ImageUploader

Drop-in upload form with drag-and-drop, preview, variant selection, and progress.

import { ImageUploader } from "@manyrows/appkit-react";

<ImageUploader
  onUpload={() => console.log("Done!")}
  onError={(err) => console.error(err)}
  defaultTitle="Untitled"
/>

Props:

| Prop | Type | Default | Description | |------|------|---------|-------------| | onUpload | () => void | — | Called on successful upload | | onError | (error: string) => void | — | Called on upload error | | defaultTitle | string | — | Pre-fill title | | defaultDescription | string | "" | Pre-fill description | | variants | string | "sq200,w400,w800" | Variant names to generate | | showFields | boolean | true | Show title/description inputs | | showVariantPicker | boolean | false | Show advanced variant selector | | accept | string | "image/png,image/jpeg,..." | Accepted MIME types | | maxSize | number | 4194304 | Max file size in bytes | | label | string | "Drop an image here or click to select" | Drop zone text | | disabled | boolean | false | Disable all interactions | | className | string | — | CSS class | | style | CSSProperties | — | Inline styles |

Features:

  • Drag-and-drop or click to select
  • Image preview before upload
  • File size validation
  • Progress bar during upload
  • Success toast (auto-dismisses after 4s)
  • Auto-populates title from filename
  • Collapsible "Advanced" section for variant selection

ImagePicker

Image grid browser with search, pagination, and optional inline actions.

import { ImagePicker } from "@manyrows/appkit-react";

// Inline grid
<ImagePicker
  onSelect={(image) => setSelected(image)}
  pageSize={20}
  showSearch
  showActions
/>

// Modal overlay
<ImagePicker
  mode="modal"
  onSelect={handleSelect}
  onClose={() => setOpen(false)}
/>

// Refresh after upload
const [uploadCount, setUploadCount] = useState(0);
<ImageUploader onUpload={() => setUploadCount(c => c + 1)} />
<ImagePicker refreshSignal={uploadCount} />

Props:

| Prop | Type | Default | Description | |------|------|---------|-------------| | onSelect | (image: ImageResource) => void | — | Called when an image is clicked | | onClose | () => void | — | Called when modal is closed | | mode | "modal" \| "inline" | "inline" | Display mode | | pageSize | number | 20 | Images per page | | showSearch | boolean | true | Show search bar | | searchPlaceholder | string | "Search images..." | Placeholder text | | selectedImageId | string | — | Highlight selected image | | columns | number | 4 | Grid columns | | showActions | boolean | false | Show Info/Edit/Delete on hover | | refreshSignal | number | — | Increment to trigger refetch | | className | string | — | CSS class | | style | CSSProperties | — | Inline styles |

ImageDetails

Full-screen detail modal for a single image. Shows variant selector, metadata, copy-to-clipboard, and action buttons.

import { ImageDetails } from "@manyrows/appkit-react";

<ImageDetails
  image={selectedImage}
  onClose={() => setSelected(null)}
  onEdit={(img) => openEditor(img)}
  onDelete={(img) => confirmDelete(img)}
/>

Props:

| Prop | Type | Default | Description | |------|------|---------|-------------| | image | ImageResource | required | Image to display | | onClose | () => void | required | Close callback | | onEdit | (image: ImageResource) => void | — | Edit button callback | | onDelete | (image: ImageResource) => void | — | Delete button callback | | className | string | — | CSS class | | style | CSSProperties | — | Inline styles |

Displays:

  • Large image preview with variant selector tabs
  • File size, dimensions, format
  • Image ID and URL (both copyable)
  • Upload date and description

Types

interface ImageResource {
  imageId: string;
  title: string;
  description: string;
  originalName: string;
  uploadedAt: string;
  uploadedBy?: string;
  visibility: string;    // "private" | "shared"
  variants: ImageVariant[];
}

interface ImageVariant {
  id: string;
  variant: string;       // "sq200", "w800", "orig", etc.
  s3Key: string;
  content_type: string;
  format: string;
  size_bytes: number;
  sha256_hex: string;
  ext: string;
  width: number;
  height: number;
  etag: string;
  versionId: string;
  objectURL: string;     // presigned CloudFront URL
  expiresAt: string;
}

interface ImageListResponse {
  images: ImageResource[];
  page: number;
  pageSize: number;
  total: number;
  pageCount: number;
}

interface ImageUploadOptions {
  file: File;
  title: string;
  description?: string;
  variants?: string;     // comma-separated, e.g. "sq200,w400,w800"
  visibility?: "private" | "shared";  // default: "private"
}

interface ImageUpdateOptions {
  title: string;
  description?: string;
  visibility?: "private" | "shared";
}

interface UploadProgress {
  status: "idle" | "uploading" | "success" | "error";
  progress: number;
  bytesUploaded: number;
  bytesTotal: number;
  error: string | null;
}

Available Variants

| Name | Description | |------|-------------| | sq200 | Thumbnail (200x200 square crop) | | sq400 | Square (400x400) | | w400 | Small (400px wide) | | w800 | Medium (800px wide) | | w1200 | Large (1200px wide) | | w1600 | Retina (1600px wide) | | w1920 | Full HD (1920px wide) | | orig | Original upload (always created) |