mirror of
https://github.com/vercel-labs/json-render.git
synced 2026-10-21 06:30:33 +08:00
* external store adapter for state management Introduces a `StateStore` interface that lets users plug in their own state management (Redux, Zustand, XState, etc.) instead of being locked into the internal `useState`-based store. - Added `StateStore` interface and `createStateStore()` factory to `@json-render/core` - `StateProvider`, `JSONUIProvider`, and `createRenderer` now accept an optional `store` prop for controlled mode - When `store` is provided, it becomes the single source of truth (`initialState`/`onStateChange` are ignored) - When `store` is omitted, everything works exactly as before (fully backward compatible) - Applied across all platform packages: react, react-native, react-pdf * improvements * update docs * improvements * fixes * fix CI * add store adapters * fixes * fixes * fixes * fixes * e2e tests * improvements * fixes * fixes * fixes * fixes * update lockfile for widened react peer deps * fix dashboard build
181 lines
4.5 KiB
Markdown
181 lines
4.5 KiB
Markdown
# @json-render/react-pdf
|
|
|
|
React PDF renderer for `@json-render/core`. Generate PDF documents from JSON specs using `@react-pdf/renderer`.
|
|
|
|
## Install
|
|
|
|
```bash
|
|
npm install @json-render/core @json-render/react-pdf
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
### Render a spec to a PDF buffer
|
|
|
|
```typescript
|
|
import { renderToBuffer } from "@json-render/react-pdf";
|
|
import type { Spec } from "@json-render/core";
|
|
|
|
const spec: Spec = {
|
|
root: "doc",
|
|
elements: {
|
|
doc: { type: "Document", props: { title: "Invoice" }, children: ["page"] },
|
|
page: {
|
|
type: "Page",
|
|
props: { size: "A4" },
|
|
children: ["heading", "table"],
|
|
},
|
|
heading: {
|
|
type: "Heading",
|
|
props: { text: "Invoice #1234", level: "h1" },
|
|
children: [],
|
|
},
|
|
table: {
|
|
type: "Table",
|
|
props: {
|
|
columns: [
|
|
{ header: "Item", width: "60%" },
|
|
{ header: "Price", width: "40%", align: "right" },
|
|
],
|
|
rows: [
|
|
["Widget A", "$10.00"],
|
|
["Widget B", "$25.00"],
|
|
],
|
|
},
|
|
children: [],
|
|
},
|
|
},
|
|
};
|
|
|
|
const buffer = await renderToBuffer(spec);
|
|
```
|
|
|
|
### With a custom catalog
|
|
|
|
```typescript
|
|
import { defineCatalog } from "@json-render/core";
|
|
import { schema, defineRegistry, renderToBuffer } from "@json-render/react-pdf";
|
|
import { standardComponentDefinitions } from "@json-render/react-pdf/catalog";
|
|
import { z } from "zod";
|
|
|
|
const catalog = defineCatalog(schema, {
|
|
components: {
|
|
...standardComponentDefinitions,
|
|
Badge: {
|
|
props: z.object({
|
|
label: z.string(),
|
|
color: z.string().nullable(),
|
|
}),
|
|
slots: [],
|
|
description: "A colored badge label",
|
|
},
|
|
},
|
|
actions: {},
|
|
});
|
|
|
|
const { registry } = defineRegistry(catalog, {
|
|
components: {
|
|
Badge: ({ props }) => (
|
|
<View style={{ backgroundColor: props.color ?? "#e5e7eb", padding: 4, borderRadius: 4 }}>
|
|
<Text style={{ fontSize: 10 }}>{props.label}</Text>
|
|
</View>
|
|
),
|
|
},
|
|
});
|
|
|
|
const buffer = await renderToBuffer(spec, { registry });
|
|
```
|
|
|
|
## Standard Components
|
|
|
|
### Document Structure
|
|
|
|
| Component | Description |
|
|
|-----------|-------------|
|
|
| `Document` | Top-level PDF wrapper. Must be the root element. |
|
|
| `Page` | A page with size (A4, LETTER, etc.), orientation, and margins. |
|
|
|
|
### Layout
|
|
|
|
| Component | Description |
|
|
|-----------|-------------|
|
|
| `View` | Generic container with padding, margin, background, border. |
|
|
| `Row` | Horizontal flex layout with gap, align, justify. |
|
|
| `Column` | Vertical flex layout with gap, align, justify. |
|
|
|
|
### Content
|
|
|
|
| Component | Description |
|
|
|-----------|-------------|
|
|
| `Heading` | h1-h4 heading text. |
|
|
| `Text` | Body text with fontSize, color, weight, alignment. |
|
|
| `Image` | Image from URL or base64. |
|
|
| `Link` | Hyperlink with text and href. |
|
|
|
|
### Data
|
|
|
|
| Component | Description |
|
|
|-----------|-------------|
|
|
| `Table` | Data table with typed columns and string rows. |
|
|
| `List` | Ordered or unordered list. |
|
|
|
|
### Decorative
|
|
|
|
| Component | Description |
|
|
|-----------|-------------|
|
|
| `Divider` | Horizontal line separator. |
|
|
| `Spacer` | Empty vertical space. |
|
|
|
|
### Page-Level
|
|
|
|
| Component | Description |
|
|
|-----------|-------------|
|
|
| `PageNumber` | Renders current page number and total pages. |
|
|
|
|
## Server-Side APIs
|
|
|
|
```typescript
|
|
import { renderToBuffer, renderToStream, renderToFile } from "@json-render/react-pdf";
|
|
|
|
// Render to an in-memory Buffer
|
|
const buffer = await renderToBuffer(spec);
|
|
|
|
// Render to a readable stream (pipe to HTTP response)
|
|
const stream = await renderToStream(spec);
|
|
stream.pipe(res);
|
|
|
|
// Render directly to a file
|
|
await renderToFile(spec, "./output.pdf");
|
|
```
|
|
|
|
All render functions accept an optional second argument with:
|
|
|
|
- `registry` - Custom component registry (merged with standard components)
|
|
- `state` - Initial state for `$state` / `$cond` dynamic prop resolution
|
|
- `handlers` - Action handlers
|
|
|
|
## External Store (Controlled Mode)
|
|
|
|
For full control over state, pass a `StateStore` to `StateProvider`, `JSONUIProvider`, or `createRenderer`. When `store` is provided, `initialState` and `onStateChange` are ignored and the store is the single source of truth:
|
|
|
|
```tsx
|
|
import { createStateStore, type StateStore } from "@json-render/react-pdf";
|
|
|
|
const store = createStateStore({ invoice: { total: 100 } });
|
|
|
|
// Mutate from anywhere — components re-render automatically:
|
|
store.set("/invoice/total", 200);
|
|
```
|
|
|
|
## Server-Safe Import
|
|
|
|
Import schema and catalog definitions without pulling in React:
|
|
|
|
```typescript
|
|
import { schema, standardComponentDefinitions } from "@json-render/react-pdf/server";
|
|
```
|
|
|
|
## License
|
|
|
|
Apache-2.0
|