@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-screeninit 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
fi6. 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 infrastructureUI 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 helperUsage 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
tfunction 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
- CONVENTIONS.md - Component patterns and architecture
- registry.json - Internal component registry metadata used for docs/tests and future automation
- Space Invoices API Docs
- shadcn/ui
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
