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

@spaceinvoices/react-ui

v0.5.0

Published

Space Invoices UI components - copy-paste distribution with CLI support

Downloads

1,533

Readme

Space Invoices React UI

Pre-built React components for the Space Invoices API

70+ components for building invoicing applications with the Space Invoices API. Includes forms, tables, dashboard charts, and more - all designed to be copied into your project for full customization.

Philosophy

These components are designed to be copied, with an installer that helps you pull the files into your app:

  • Full ownership - Modify freely without breaking changes
  • Complete customization - Change behavior and structure
  • No version conflicts - You control when to update
  • Zero lock-in - Components work standalone in your codebase

Components are built on shadcn/ui-style primitives. The installer copies the exact primitives required by a selected feature; you can keep them or replace them with your own design system.

Quick Start

1. Recommended: Use the Installer

npx @spaceinvoices/react-ui@latest init
npx @spaceinvoices/react-ui@latest add customers/create-customer-form
npx @spaceinvoices/react-ui@latest add invoices/create-invoice-screen

init preserves an existing spaceinvoices.json and copied helper files by default, including with --yes. Use --force only when you explicitly want to replace the configuration and installed runtime helpers.

The published @spaceinvoices/react-ui package provides the installer/CLI. This monorepo contains the source components and registry metadata that feed that standalone package.

2. Install Dependencies

# Runtime closure for the full copy-paste tree. These versions mirror the
# canonical production dependencies in `packages/ui/package.json`.
npm install \
  @base-ui/react@^1.7.0 \
  @hookform/resolvers@^5.7.1 \
  @radix-ui/react-label@^2.1.15 \
  @radix-ui/react-slot@^1.3.3 \
  @spaceinvoices/[email protected] \
  @tanstack/react-query@^5.101.4 \
  class-variance-authority@^0.7.1 \
  clsx@^2.1.1 \
  cmdk@^1.1.1 \
  date-fns@^4.4.0 \
  lucide-react@^1.28.0 \
  markdown-it@^14.1.1 \
  next-themes@^0.4.6 \
  react@^19.2.8 \
  react-cookie@^8.1.2 \
  react-day-picker@^10.0.1 \
  react-hook-form@^7.84.0 \
  react-image-crop@^11.1.2 \
  react-dom@^19.2.8 \
  [email protected] \
  sonner@^2.0.7 \
  tailwind-merge@^3.6.0 \
  vaul@^1.1.2 \
  [email protected]

# TypeScript consumers need these declarations if they are not already in the
# host app (the focused type closure, not the UI package's full dev set).
npm install --save-dev \
  @types/markdown-it@^14.1.2 \
  @types/react@^19.2.18 \
  @types/react-dom@^19.2.4

# The copy recipe includes the exact UI primitives imported by the source tree.
# Keep them, or replace them deliberately with equivalent components from your
# own design system.

3. Manual Fallback: Copy Components From This Repository

Clone the repo, use a sparse checkout, or copy files directly from GitHub. The important part is that the code ends up in your app and becomes yours to edit.

4. Copy What You Need

# From the directory containing the cloned `react-ui` source, copy the shared
# closure and component tree into the documented consumer paths.
SOURCE_ROOT=react-ui
DEST_ROOT=your-project
mkdir -p "$DEST_ROOT/src/components" "$DEST_ROOT/src/lib" "$DEST_ROOT/src/providers" "$DEST_ROOT/src/hooks" "$DEST_ROOT/src/utils"
cp -R "$SOURCE_ROOT/src/lib/." "$DEST_ROOT/src/lib/"
cp -R "$SOURCE_ROOT/src/providers/." "$DEST_ROOT/src/providers/"
cp -R "$SOURCE_ROOT/src/hooks/." "$DEST_ROOT/src/hooks/"
cp -R "$SOURCE_ROOT/src/generated/." "$DEST_ROOT/src/generated/"
cp -R "$SOURCE_ROOT/src/common/." "$DEST_ROOT/src/common/"
cp -R "$SOURCE_ROOT/src/utils/." "$DEST_ROOT/src/utils/"
mkdir -p "$DEST_ROOT/src/components/space-invoices" "$DEST_ROOT/src/components/ui"
# Providers/hooks have relative imports into sibling feature folders, so copy
# the full components tree for a type-checkable consumer closure.
cp -R "$SOURCE_ROOT/src/components/." "$DEST_ROOT/src/components/space-invoices/"
cp -R "$SOURCE_ROOT/src/components/ui/." "$DEST_ROOT/src/components/ui/"
# Rewrite imports in the copied feature tree. This includes relative imports
# that crossed the original `src/components` boundary after relocation.
find "$DEST_ROOT/src/components/space-invoices" -type f \( -name "*.ts" -o -name "*.tsx" \) -print0 \
  | xargs -0 perl -pi -e 's#@/ui/components/ui/#@/components/ui/#g; s#@/ui/components/#@/components/space-invoices/#g; s#@/ui/common/#@/common/#g; s#@/ui/providers/#@/providers/#g; s#@/ui/lib/#@/lib/#g; s#@/ui/hooks/#@/hooks/#g; s#@/ui/generated/#@/generated/#g; s#@/ui/utils/#@/utils/#g; s#from "\.\./lib/#from "@/lib/#g; s#from "\.\./\.\./lib/#from "@/lib/#g; s#from "\.\./\.\./\.\./lib/#from "@/lib/#g; s#from "\.\./providers/#from "@/providers/#g; s#from "\.\./\.\./providers/#from "@/providers/#g; s#from "\.\./hooks/#from "@/hooks/#g; s#from "\.\./\.\./hooks/#from "@/hooks/#g; s#from "\.\./\.\./\.\./hooks/#from "@/hooks/#g; s#from "\.\./generated/#from "@/generated/#g; s#from "\.\./\.\./generated/#from "@/generated/#g'

# Rewrite aliases in the copied shared runtime files as well. This covers
# providers and helpers, not only the visible component entry point.
find "$DEST_ROOT/src/lib" "$DEST_ROOT/src/providers" "$DEST_ROOT/src/hooks" "$DEST_ROOT/src/generated" "$DEST_ROOT/src/common" "$DEST_ROOT/src/utils" -type f \( -name "*.ts" -o -name "*.tsx" \) -print0 \
  | xargs -0 perl -pi -e 's#@/ui/components/ui/#@/components/ui/#g; s#@/ui/components/#@/components/space-invoices/#g; s#@/ui/common/#@/common/#g; s#@/ui/providers/#@/providers/#g; s#@/ui/lib/#@/lib/#g; s#@/ui/hooks/#@/hooks/#g; s#@/ui/generated/#@/generated/#g; s#@/ui/utils/#@/utils/#g'

# The primitive folder is a separate destination, so rewrite it too.
find "$DEST_ROOT/src/components/ui" -type f \( -name "*.ts" -o -name "*.tsx" \) -print0 \
  | xargs -0 perl -pi -e 's#@/ui/components/ui/#@/components/ui/#g; s#@/ui/components/#@/components/space-invoices/#g; s#@/ui/common/#@/common/#g; s#@/ui/providers/#@/providers/#g; s#@/ui/lib/#@/lib/#g; s#@/ui/hooks/#@/hooks/#g; s#@/ui/generated/#@/generated/#g; s#@/ui/utils/#@/utils/#g'

5. Update Import Paths

# The rewrite in step 4 covers every copied .ts/.tsx file. Verify that no
# source-only aliases remain before running your app's typecheck:
if rg -n '@/ui/' "$DEST_ROOT/src/components/space-invoices" "$DEST_ROOT/src/components/ui" "$DEST_ROOT/src/lib" "$DEST_ROOT/src/providers" "$DEST_ROOT/src/hooks" "$DEST_ROOT/src/generated" "$DEST_ROOT/src/common" "$DEST_ROOT/src/utils" --glob '*.ts' --glob '*.tsx'; then
  echo 'Unrewritten @/ui/ imports remain' >&2
  exit 1
fi

6. Setup Providers

// app.tsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { SpaceInvoicesProvider } from "./providers/space-invoices-provider";
import { EntitiesProvider } from "./providers/entities-provider";

const queryClient = new QueryClient();
const activeAccountId = "acc_123"; // Required for multi-account user-token flows

export function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <SpaceInvoicesProvider
        accessToken={() => localStorage.getItem("si_token") ?? ""}
        accountId={activeAccountId}
        basePath="https://eu.spaceinvoices.com"
      >
        <EntitiesProvider>
          {/* Your app */}
        </EntitiesProvider>
      </SpaceInvoicesProvider>
    </QueryClientProvider>
  );
}

EntitiesProvider is optional. Prefer explicit entityId props for copy-paste components and add EntitiesProvider only when the host app wants shared active-entity state. For multi-account browser apps using a user token, pass accountId explicitly. API-key, embed, and single-account flows can leave it unset.

CreateInvoiceScreen renders its Save/Save as Draft footer when used without a host footer layout. If your app already uses FormFooterProvider, the screen registers with that existing provider and the host should render its one StickyFormFooter. CreateInvoiceForm by itself requires either that provider or its onFooterStateChange callback to be rendered by the host.

What's Included

Business Components (Copy These)

src/components/space-invoices/
├── invoices/           # Create, list, view invoices
├── customers/          # Customer management
├── items/              # Products/services catalog
├── payments/           # Payment tracking
├── taxes/              # Tax rate management
├── estimates/          # Estimates and proforma invoices
├── credit-notes/       # Credit note handling
├── advance-invoices/   # Advance invoice handling
├── dashboard/          # Analytics charts
├── entities/           # Company/organization settings
├── documents/          # Shared document components
├── form/               # Form primitives and sticky footer
├── overlays/           # Tooltip, popover, and alert-dialog wrappers
├── action-menu/        # Shared action-menu helpers
└── table/              # Generic data table infrastructure

UI Primitives

The manual recipe copies the components/ui/ primitives needed by the feature tree. If your app already has equivalent primitives, omit that copy and update the imports instead. Do not run a generator over the copied files unless you intend to replace and re-validate the complete primitive closure.

Required Utilities

src/
├── providers/
│   ├── space-invoices-provider.tsx # Runtime setup (recommended)
│   └── entities-provider.tsx       # Optional active entity context
├── hooks/
│   └── *.ts                   # Shared hooks
├── generated/                 # Generated Zod schemas for forms
├── common/                    # Shared autocomplete and form helpers
├── utils/                     # Shared string helpers
└── lib/
    ├── utils.ts               # cn() utility
    ├── locale.ts              # Locale normalization used by translations
    ├── dev-mode.ts            # Consumer-safe development-mode detection
    ├── markdown.ts            # Public-safe document markdown parser
    └── translation.ts         # i18n helper

Usage Examples

Customer List

import { CustomerListTable } from "@/components/space-invoices/customers/customer-list-table";

export function CustomersPage() {
  return (
    <CustomerListTable
      entityId="entity_123"
      onRowClick={(customer) => console.log("Selected:", customer)}
    />
  );
}

Create Invoice Form

import { CreateInvoiceForm } from "@/components/space-invoices/invoices/create";

export function NewInvoicePage() {
  return (
    <CreateInvoiceForm
      entityId="entity_123"
      onSuccess={(invoice) => {
        console.log("Created invoice:", invoice.number);
      }}
    />
  );
}

Dashboard Charts

import { RevenueTrendChart } from "@/components/space-invoices/dashboard/revenue-trend-chart";
import { InvoiceStatusChart } from "@/components/space-invoices/dashboard/invoice-status-chart";

export function DashboardPage() {
  return (
    <div className="grid grid-cols-2 gap-4">
      <RevenueTrendChart entityId="entity_123" />
      <InvoiceStatusChart entityId="entity_123" />
    </div>
  );
}

Customization

Since you own the code, customize freely:

Modify Styles

// In your copied component
<FormItem className="bg-gray-50 p-4 rounded-lg">
  <FormLabel className="text-blue-600 font-semibold">Customer Name</FormLabel>
  ...
</FormItem>

Add Fields

// Extend the schema
const createCustomerSchema = z.object({
  name: z.string().min(1),
  customField: z.string().optional(), // Your addition
});

// Add to the form
<FormField name="customField" ... />

Add Translations

Components in packages/ui are expected to be usable without any app-level i18n wiring:

  • If you do nothing, they render usable English copy by default.
  • If you pass locale, built-in component locale packs are used when available.
  • If you pass t, it acts as an override layer on top of the built-in copy.

That means a copied component should not depend on your app having translation keys just to avoid broken labels. Additional locales are optional overlays, not a requirement for basic use.

Recommended rule of thumb:

  • copy only the English/default component files if you want the smallest working install
  • add locale files later if your app needs built-in non-English copy
  • pass a custom t function only when you want your host app to override component wording
// components/space-invoices/customers/locales/fr.ts
export default {
  "customer.name": "Nom du client",
  "customer.email": "Adresse e-mail",
};

Architecture

SDK Integration

Components use TanStack Query hooks wrapping the SDK:

// customers.hooks.ts
export function useCreateCustomer() {
  return useMutation({
    mutationFn: (data) => customers.create({ data }),
  });
}

Type Safety

All types come from @spaceinvoices/js-sdk:

import type { Customer, Invoice } from "@spaceinvoices/js-sdk";

Dependencies

{
  "dependencies": {
    "@spaceinvoices/js-sdk": "^2.0.0",
    "@tanstack/react-query": "^5.0.0",
    "react": "^19.0.0",
    "react-hook-form": "^7.0.0",
    "@hookform/resolvers": "^5.0.0",
    "zod": "^3.0.0"
  }
}

Documentation

FAQ

Q: Why copy-paste instead of npm install? A: Full ownership. Modify components freely without version conflicts or breaking changes.

Q: Is there a public install CLI? A: Yes. The published @spaceinvoices/react-ui package provides a shadcn-style installer for pulling components into your app. This source repo still matters because it is the component and registry source of truth, and manual copy-paste remains a supported fallback.

Q: Can I use Material UI / Chakra / other libraries? A: Yes. Update the imports in copied components to use your UI library instead of shadcn/ui.

Q: Do I need the Space Invoices API? A: Yes. These components are built specifically for Space Invoices and won't work with other APIs.

Q: How do I get updates? A: Check this repo for changes and manually merge what you need into your customized versions.

License

MIT License - see LICENSE