@pramen/cms 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +168 -0
- package/package.json +51 -0
- package/src/index.ts +1589 -0
- package/src/react.ts +76 -0
package/src/react.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// @pramen/cms/react — the headless renderer. Like WollyCMS's BlockRenderer, it maps a
|
|
2
|
+
// block's `block_type` slug to a React component from a registry you provide; the CMS
|
|
3
|
+
// stays headless and never dictates markup. Pair with useLiveQuery from @pramen/react
|
|
4
|
+
// for a live-updating page:
|
|
5
|
+
//
|
|
6
|
+
// import { BlockRenderer, RegionRenderer } from "@pramen/cms/react";
|
|
7
|
+
// const components = { hero: Hero, rich_text: RichText, cta: Cta };
|
|
8
|
+
// const { data } = useLiveQuery(client, "getPage", { slug }); // AssembledPage
|
|
9
|
+
// return RegionRenderer({ regions: data.regions, name: "content", components });
|
|
10
|
+
//
|
|
11
|
+
// Written with `createElement` (not JSX) so the package needs no DOM lib / JSX build
|
|
12
|
+
// config and stays on the same tsconfig as the rest of pramen. In a JSX codebase you'd
|
|
13
|
+
// normally write `<BlockRenderer .../>` — it's an ordinary component either way.
|
|
14
|
+
|
|
15
|
+
import { createElement, Fragment } from "react";
|
|
16
|
+
import type { ComponentType, ReactElement } from "react";
|
|
17
|
+
import type { RenderedBlock, BlockTypeDef, BlockFieldsOf } from "./index";
|
|
18
|
+
|
|
19
|
+
/** Props a component for a specific typed block type receives — `fields` is inferred from
|
|
20
|
+
* the block type's schema via `BlockFieldsOf` (see `defineBlockType`). */
|
|
21
|
+
export interface TypedBlockProps<D extends BlockTypeDef> {
|
|
22
|
+
fields: BlockFieldsOf<D>;
|
|
23
|
+
block: RenderedBlock;
|
|
24
|
+
region?: string;
|
|
25
|
+
position: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A React component for a typed block type: `const Hero: TypedBlockComponent<typeof heroDef>`. */
|
|
29
|
+
export type TypedBlockComponent<D extends BlockTypeDef> = ComponentType<TypedBlockProps<D>>;
|
|
30
|
+
|
|
31
|
+
/** Props a block component receives. `fields` is the block's (override-merged) content. */
|
|
32
|
+
export interface BlockComponentProps {
|
|
33
|
+
fields: Record<string, unknown>;
|
|
34
|
+
block: RenderedBlock;
|
|
35
|
+
region?: string;
|
|
36
|
+
position: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type BlockComponents = Record<string, ComponentType<BlockComponentProps>>;
|
|
40
|
+
|
|
41
|
+
export interface BlockRendererProps {
|
|
42
|
+
blocks: RenderedBlock[];
|
|
43
|
+
region?: string;
|
|
44
|
+
components: BlockComponents;
|
|
45
|
+
/** Rendered when no component is registered for a block's type. Defaults to a text node. */
|
|
46
|
+
fallback?: ComponentType<{ block: RenderedBlock }>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Render an ordered list of blocks, each via its registered component. */
|
|
50
|
+
export function BlockRenderer({ blocks, region, components, fallback }: BlockRendererProps): ReactElement {
|
|
51
|
+
return createElement(
|
|
52
|
+
Fragment,
|
|
53
|
+
null,
|
|
54
|
+
blocks.map((block, index) => {
|
|
55
|
+
const Component = components[block.block_type];
|
|
56
|
+
if (!Component) {
|
|
57
|
+
return fallback
|
|
58
|
+
? createElement(fallback, { key: block.id, block })
|
|
59
|
+
: createElement(Fragment, { key: block.id }, `Unknown block type: ${block.block_type}`);
|
|
60
|
+
}
|
|
61
|
+
return createElement(Component, { key: block.id, fields: block.fields, block, region, position: index });
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface RegionRendererProps {
|
|
67
|
+
regions: Record<string, RenderedBlock[]>;
|
|
68
|
+
name: string;
|
|
69
|
+
components: BlockComponents;
|
|
70
|
+
fallback?: ComponentType<{ block: RenderedBlock }>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Render a single named region from an AssembledPage's `regions` map. */
|
|
74
|
+
export function RegionRenderer({ regions, name, components, fallback }: RegionRendererProps): ReactElement {
|
|
75
|
+
return BlockRenderer({ blocks: regions[name] ?? [], region: name, components, fallback });
|
|
76
|
+
}
|