react-layout-sdk 1.1.5 → 1.1.7
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 +72 -0
- package/bin/init.js +4 -4
- package/dist/index.d.mts +16 -9
- package/dist/index.d.ts +16 -9
- package/dist/index.js +6 -6
- package/dist/index.mjs +5 -5
- package/package.json +2 -2
- package/src/Placeholder.tsx +1 -1
- package/src/index.ts +19 -12
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# React Layout SDK (JD Layout System)
|
|
2
|
+
|
|
3
|
+
The **React Layout SDK** is an enterprise-grade Frontend SDK designed to consume layout definitions from Strapi via the **Strapi Layout Plugin**. Inspired by robust headless ecosystems like Sitecore JSS, this SDK provides dynamic placeholder routing and a scaffold generator to stream-line Developer Experience (DX).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install the package using your favorite package manager:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install react-layout-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start (Project Scaffolding)
|
|
14
|
+
|
|
15
|
+
To quickly set up your Next.js project to work with the JD Layout System, run the init command at the root of your project:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx react-layout-sdk init
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**What this does:**
|
|
22
|
+
1. Creates a `src/components/factory.tsx` file for component mapping.
|
|
23
|
+
2. Generates base boilerplate components like `Header.tsx` and `Footer.tsx`.
|
|
24
|
+
3. Auto-configures your Next.js App Router (`app/[[...slug]]/page.tsx`) or Pages Router to catch all layout routes and fetch them dynamically using `fetchJDLayout`.
|
|
25
|
+
|
|
26
|
+
## Component Scaffolding
|
|
27
|
+
|
|
28
|
+
You can scaffold new React components and automatically map them to their corresponding Strapi components in `factory.tsx` using the generate command:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx react-layout-sdk generate component <strapi-component-uid>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Example:**
|
|
35
|
+
```bash
|
|
36
|
+
npx react-layout-sdk generate component blocks.hero-banner
|
|
37
|
+
```
|
|
38
|
+
*This command will:*
|
|
39
|
+
- Create `src/components/HeroBanner.tsx`.
|
|
40
|
+
- Automatically inject `import HeroBanner` and map `'blocks.hero-banner': HeroBanner` inside your `factory.tsx`.
|
|
41
|
+
|
|
42
|
+
## API Reference
|
|
43
|
+
|
|
44
|
+
### 1. `fetchJDLayout(apiUrl, slug, locale)`
|
|
45
|
+
Fetches the layout structure and content from the Strapi backend.
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { fetchJDLayout } from 'react-layout-sdk';
|
|
49
|
+
|
|
50
|
+
const layoutData = await fetchJDLayout('http://localhost:1337', '/home', 'en');
|
|
51
|
+
const { route } = layoutData.strapi;
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 2. `<Placeholder />`
|
|
55
|
+
Renders a specific dynamic zone (like `header`, `main`, or `footer`) by matching the JSON data against your React components.
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { Placeholder } from 'react-layout-sdk';
|
|
59
|
+
import { componentMap } from '@/components/factory';
|
|
60
|
+
|
|
61
|
+
<main>
|
|
62
|
+
<Placeholder
|
|
63
|
+
name="main"
|
|
64
|
+
rendering={route.placeholders.main || []}
|
|
65
|
+
componentMap={componentMap}
|
|
66
|
+
/>
|
|
67
|
+
</main>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## TypeScript Support
|
|
71
|
+
|
|
72
|
+
This package comes with full TypeScript definitions, including `JDLayoutResponse`, `JDRoute`, and `JDContext`.
|
package/bin/init.js
CHANGED
|
@@ -128,7 +128,7 @@ if (command === 'init') {
|
|
|
128
128
|
|
|
129
129
|
// 2. Create Header.tsx
|
|
130
130
|
const headerPath = path.join(componentsDir, 'Header.tsx');
|
|
131
|
-
const headerContent = `import React from 'react';\n\nexport default function Header(props: any) {\n const title = props?.title || '
|
|
131
|
+
const headerContent = `import React from 'react';\n\nexport default function Header(props: any) {\n const title = props?.title || 'JD Header';\n const logoUrl = props?.logoUrl;\n\n return (\n <header style={{ padding: '20px', background: '#f5f5f5', borderBottom: '1px solid #ddd' }}>\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>\n <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>\n {logoUrl && <img src={logoUrl} alt="Logo" width="40" />}\n <h1 style={{ margin: 0, fontSize: '1.5rem' }}>{title}</h1>\n </div>\n </div>\n </header>\n );\n}\n`;
|
|
132
132
|
if (!fs.existsSync(headerPath)) {
|
|
133
133
|
fs.writeFileSync(headerPath, headerContent);
|
|
134
134
|
console.log('✅ Created components/Header.tsx');
|
|
@@ -136,7 +136,7 @@ if (command === 'init') {
|
|
|
136
136
|
|
|
137
137
|
// 3. Create Footer.tsx
|
|
138
138
|
const footerPath = path.join(componentsDir, 'Footer.tsx');
|
|
139
|
-
const footerContent = `import React from 'react';\n\nexport default function Footer(props: any) {\n const text = props?.text || '© 2026
|
|
139
|
+
const footerContent = `import React from 'react';\n\nexport default function Footer(props: any) {\n const text = props?.text || '© 2026 JD Layout';\n return (\n <footer style={{ padding: '20px', background: '#333', color: '#fff', textAlign: 'center', marginTop: '40px' }}>\n <p style={{ margin: 0 }}>{text}</p>\n </footer>\n );\n}\n`;
|
|
140
140
|
if (!fs.existsSync(footerPath)) {
|
|
141
141
|
fs.writeFileSync(footerPath, footerContent);
|
|
142
142
|
console.log('✅ Created components/Footer.tsx');
|
|
@@ -157,7 +157,7 @@ if (command === 'init') {
|
|
|
157
157
|
ensureDirSync(pageDir);
|
|
158
158
|
|
|
159
159
|
const appPagePath = path.join(pageDir, 'page.tsx');
|
|
160
|
-
const appPageContent = `import React from 'react';\nimport {
|
|
160
|
+
const appPageContent = `import React from 'react';\nimport { fetchJDLayout, Placeholder } from 'react-layout-sdk';\nimport { componentMap } from '@/components/factory';\n\nexport default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) {\n const resolvedParams = await params;\n const slugArray = resolvedParams?.slug || [];\n const path = slugArray.join('/') || '/';\n const STRAPI_URL = process.env.NEXT_PUBLIC_STRAPI_URL || 'http://localhost:1337';\n \n try {\n const layoutData = await fetchJDLayout(STRAPI_URL, path, 'en');\n if (!layoutData || !layoutData.strapi) return <h1>404 - Not Found</h1>;\n\n const { route } = layoutData.strapi;\n\n return (\n <div className="layout-wrapper">\n <Placeholder name="header" rendering={route.placeholders.header || []} componentMap={componentMap} />\n <main style={{ minHeight: '60vh', padding: '20px' }}>\n <Placeholder name="main" rendering={route.placeholders.main || []} componentMap={componentMap} />\n </main>\n <Placeholder name="footer" rendering={route.placeholders.footer || []} componentMap={componentMap} />\n </div>\n );\n } catch (error) {\n return <h1>Error Loading Layout</h1>;\n }\n}\n`;
|
|
161
161
|
if (!fs.existsSync(appPagePath)) {
|
|
162
162
|
fs.writeFileSync(appPagePath, appPageContent);
|
|
163
163
|
console.log('✅ Created app/[[...slug]]/page.tsx');
|
|
@@ -173,7 +173,7 @@ if (command === 'init') {
|
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
const pagePath = path.join(basePath, 'pages', '[[...slug]].tsx');
|
|
176
|
-
const pagesContent = `import React from 'react';\nimport {
|
|
176
|
+
const pagesContent = `import React from 'react';\nimport { fetchJDLayout, Placeholder } from 'react-layout-sdk';\nimport { componentMap } from '@/components/factory';\n\nexport default function LayoutPage({ layoutData, error }: any) {\n if (error || !layoutData?.strapi) return <h1>404 - Layout Not Found</h1>;\n\n const { route } = layoutData.strapi;\n return (\n <div className="layout-wrapper">\n <Placeholder name="header" rendering={route.placeholders.header || []} componentMap={componentMap} />\n <main style={{ minHeight: '60vh', padding: '20px' }}>\n <Placeholder name="main" rendering={route.placeholders.main || []} componentMap={componentMap} />\n </main>\n <Placeholder name="footer" rendering={route.placeholders.footer || []} componentMap={componentMap} />\n </div>\n );\n}\n\nexport async function getServerSideProps(context: any) {\n const slugArray = context.params?.slug || [];\n const path = slugArray.join('/') || '/';\n const STRAPI_URL = process.env.NEXT_PUBLIC_STRAPI_URL || 'http://localhost:1337';\n\n try {\n const layoutData = await fetchJDLayout(STRAPI_URL, path, 'en');\n if (!layoutData) return { notFound: true };\n\n return { props: { layoutData } };\n } catch (error) {\n return { props: { error: true } };\n }\n}\n`;
|
|
177
177
|
if (!fs.existsSync(pagePath)) {
|
|
178
178
|
fs.writeFileSync(pagePath, pagesContent);
|
|
179
179
|
console.log('✅ Created pages/[[...slug]].tsx');
|
package/dist/index.d.mts
CHANGED
|
@@ -16,27 +16,34 @@ interface PlaceholderProps {
|
|
|
16
16
|
}
|
|
17
17
|
declare const Placeholder: React.FC<PlaceholderProps>;
|
|
18
18
|
|
|
19
|
-
interface
|
|
19
|
+
interface JDPlaceholderData {
|
|
20
20
|
__component: string;
|
|
21
21
|
[key: string]: any;
|
|
22
22
|
}
|
|
23
|
-
interface
|
|
23
|
+
interface JDRoute {
|
|
24
24
|
name: string;
|
|
25
25
|
displayName: string;
|
|
26
|
-
|
|
26
|
+
fields?: Record<string, any>;
|
|
27
|
+
seo?: {
|
|
28
|
+
metaTitle?: string;
|
|
29
|
+
metaDescription?: string;
|
|
30
|
+
metaImage?: any;
|
|
31
|
+
[key: string]: any;
|
|
32
|
+
};
|
|
33
|
+
placeholders: Record<string, JDPlaceholderData[]>;
|
|
27
34
|
}
|
|
28
|
-
interface
|
|
35
|
+
interface JDContext {
|
|
29
36
|
pageEditing: boolean;
|
|
30
37
|
site: Record<string, any>;
|
|
31
38
|
language: string;
|
|
32
39
|
locales: any[];
|
|
33
40
|
}
|
|
34
|
-
interface
|
|
41
|
+
interface JDLayoutResponse {
|
|
35
42
|
strapi: {
|
|
36
|
-
context:
|
|
37
|
-
route:
|
|
43
|
+
context: JDContext;
|
|
44
|
+
route: JDRoute;
|
|
38
45
|
};
|
|
39
46
|
}
|
|
40
|
-
declare const
|
|
47
|
+
declare const fetchJDLayout: (apiUrl: string, slug: string, locale?: string, options?: RequestInit) => Promise<JDLayoutResponse | null>;
|
|
41
48
|
|
|
42
|
-
export { ComponentFactory, type ComponentFactoryProps,
|
|
49
|
+
export { ComponentFactory, type ComponentFactoryProps, type JDContext, type JDLayoutResponse, type JDPlaceholderData, type JDRoute, Placeholder, type PlaceholderProps, fetchJDLayout };
|
package/dist/index.d.ts
CHANGED
|
@@ -16,27 +16,34 @@ interface PlaceholderProps {
|
|
|
16
16
|
}
|
|
17
17
|
declare const Placeholder: React.FC<PlaceholderProps>;
|
|
18
18
|
|
|
19
|
-
interface
|
|
19
|
+
interface JDPlaceholderData {
|
|
20
20
|
__component: string;
|
|
21
21
|
[key: string]: any;
|
|
22
22
|
}
|
|
23
|
-
interface
|
|
23
|
+
interface JDRoute {
|
|
24
24
|
name: string;
|
|
25
25
|
displayName: string;
|
|
26
|
-
|
|
26
|
+
fields?: Record<string, any>;
|
|
27
|
+
seo?: {
|
|
28
|
+
metaTitle?: string;
|
|
29
|
+
metaDescription?: string;
|
|
30
|
+
metaImage?: any;
|
|
31
|
+
[key: string]: any;
|
|
32
|
+
};
|
|
33
|
+
placeholders: Record<string, JDPlaceholderData[]>;
|
|
27
34
|
}
|
|
28
|
-
interface
|
|
35
|
+
interface JDContext {
|
|
29
36
|
pageEditing: boolean;
|
|
30
37
|
site: Record<string, any>;
|
|
31
38
|
language: string;
|
|
32
39
|
locales: any[];
|
|
33
40
|
}
|
|
34
|
-
interface
|
|
41
|
+
interface JDLayoutResponse {
|
|
35
42
|
strapi: {
|
|
36
|
-
context:
|
|
37
|
-
route:
|
|
43
|
+
context: JDContext;
|
|
44
|
+
route: JDRoute;
|
|
38
45
|
};
|
|
39
46
|
}
|
|
40
|
-
declare const
|
|
47
|
+
declare const fetchJDLayout: (apiUrl: string, slug: string, locale?: string, options?: RequestInit) => Promise<JDLayoutResponse | null>;
|
|
41
48
|
|
|
42
|
-
export { ComponentFactory, type ComponentFactoryProps,
|
|
49
|
+
export { ComponentFactory, type ComponentFactoryProps, type JDContext, type JDLayoutResponse, type JDPlaceholderData, type JDRoute, Placeholder, type PlaceholderProps, fetchJDLayout };
|
package/dist/index.js
CHANGED
|
@@ -32,7 +32,7 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
ComponentFactory: () => ComponentFactory,
|
|
34
34
|
Placeholder: () => Placeholder,
|
|
35
|
-
|
|
35
|
+
fetchJDLayout: () => fetchJDLayout
|
|
36
36
|
});
|
|
37
37
|
module.exports = __toCommonJS(index_exports);
|
|
38
38
|
|
|
@@ -47,7 +47,7 @@ var ComponentFactory = ({
|
|
|
47
47
|
}) => {
|
|
48
48
|
const Component = componentMap[componentName];
|
|
49
49
|
if (!Component) {
|
|
50
|
-
console.warn(`[
|
|
50
|
+
console.warn(`[JD SDK] Component not found for: ${componentName}`);
|
|
51
51
|
return /* @__PURE__ */ import_react.default.createElement("div", { style: {
|
|
52
52
|
padding: "2rem",
|
|
53
53
|
margin: "1rem",
|
|
@@ -87,19 +87,19 @@ var Placeholder = ({ name, rendering, customProps, componentMap }) => {
|
|
|
87
87
|
};
|
|
88
88
|
|
|
89
89
|
// src/index.ts
|
|
90
|
-
var
|
|
90
|
+
var fetchJDLayout = async (apiUrl, slug, locale = "en", options) => {
|
|
91
91
|
try {
|
|
92
92
|
const querySymbol = slug.includes("?") ? "&" : "?";
|
|
93
93
|
const res = await fetch(`${apiUrl}/api/layout/${slug}${querySymbol}locale=${locale}`, {
|
|
94
94
|
...options
|
|
95
95
|
});
|
|
96
96
|
if (!res.ok) {
|
|
97
|
-
console.error(`[
|
|
97
|
+
console.error(`[JD SDK] Failed to fetch layout: ${res.statusText}`);
|
|
98
98
|
return null;
|
|
99
99
|
}
|
|
100
100
|
return await res.json();
|
|
101
101
|
} catch (error) {
|
|
102
|
-
console.error(`[
|
|
102
|
+
console.error(`[JD SDK] Network error fetching layout`, error);
|
|
103
103
|
return null;
|
|
104
104
|
}
|
|
105
105
|
};
|
|
@@ -107,5 +107,5 @@ var fetchVeloxLayout = async (apiUrl, slug, locale = "en", options) => {
|
|
|
107
107
|
0 && (module.exports = {
|
|
108
108
|
ComponentFactory,
|
|
109
109
|
Placeholder,
|
|
110
|
-
|
|
110
|
+
fetchJDLayout
|
|
111
111
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -9,7 +9,7 @@ var ComponentFactory = ({
|
|
|
9
9
|
}) => {
|
|
10
10
|
const Component = componentMap[componentName];
|
|
11
11
|
if (!Component) {
|
|
12
|
-
console.warn(`[
|
|
12
|
+
console.warn(`[JD SDK] Component not found for: ${componentName}`);
|
|
13
13
|
return /* @__PURE__ */ React.createElement("div", { style: {
|
|
14
14
|
padding: "2rem",
|
|
15
15
|
margin: "1rem",
|
|
@@ -49,24 +49,24 @@ var Placeholder = ({ name, rendering, customProps, componentMap }) => {
|
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
// src/index.ts
|
|
52
|
-
var
|
|
52
|
+
var fetchJDLayout = async (apiUrl, slug, locale = "en", options) => {
|
|
53
53
|
try {
|
|
54
54
|
const querySymbol = slug.includes("?") ? "&" : "?";
|
|
55
55
|
const res = await fetch(`${apiUrl}/api/layout/${slug}${querySymbol}locale=${locale}`, {
|
|
56
56
|
...options
|
|
57
57
|
});
|
|
58
58
|
if (!res.ok) {
|
|
59
|
-
console.error(`[
|
|
59
|
+
console.error(`[JD SDK] Failed to fetch layout: ${res.statusText}`);
|
|
60
60
|
return null;
|
|
61
61
|
}
|
|
62
62
|
return await res.json();
|
|
63
63
|
} catch (error) {
|
|
64
|
-
console.error(`[
|
|
64
|
+
console.error(`[JD SDK] Network error fetching layout`, error);
|
|
65
65
|
return null;
|
|
66
66
|
}
|
|
67
67
|
};
|
|
68
68
|
export {
|
|
69
69
|
ComponentFactory,
|
|
70
70
|
Placeholder,
|
|
71
|
-
|
|
71
|
+
fetchJDLayout
|
|
72
72
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-layout-sdk",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "React components for
|
|
3
|
+
"version": "1.1.7",
|
|
4
|
+
"description": "React components for JD SDK (Sitecore-like routing)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
package/src/Placeholder.tsx
CHANGED
|
@@ -18,7 +18,7 @@ export const ComponentFactory: React.FC<ComponentFactoryProps> = ({
|
|
|
18
18
|
const Component = componentMap[componentName];
|
|
19
19
|
|
|
20
20
|
if (!Component) {
|
|
21
|
-
console.warn(`[
|
|
21
|
+
console.warn(`[JD SDK] Component not found for: ${componentName}`);
|
|
22
22
|
return (
|
|
23
23
|
<div style={{
|
|
24
24
|
padding: '2rem',
|
package/src/index.ts
CHANGED
|
@@ -2,38 +2,45 @@ export { Placeholder, ComponentFactory } from './Placeholder';
|
|
|
2
2
|
export type { PlaceholderProps, ComponentFactoryProps } from './Placeholder';
|
|
3
3
|
|
|
4
4
|
// Useful type definitions for the API response
|
|
5
|
-
export interface
|
|
5
|
+
export interface JDPlaceholderData {
|
|
6
6
|
__component: string;
|
|
7
7
|
[key: string]: any;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export interface
|
|
10
|
+
export interface JDRoute {
|
|
11
11
|
name: string;
|
|
12
12
|
displayName: string;
|
|
13
|
-
|
|
13
|
+
fields?: Record<string, any>;
|
|
14
|
+
seo?: {
|
|
15
|
+
metaTitle?: string;
|
|
16
|
+
metaDescription?: string;
|
|
17
|
+
metaImage?: any;
|
|
18
|
+
[key: string]: any;
|
|
19
|
+
};
|
|
20
|
+
placeholders: Record<string, JDPlaceholderData[]>;
|
|
14
21
|
}
|
|
15
22
|
|
|
16
|
-
export interface
|
|
23
|
+
export interface JDContext {
|
|
17
24
|
pageEditing: boolean;
|
|
18
25
|
site: Record<string, any>;
|
|
19
26
|
language: string;
|
|
20
27
|
locales: any[];
|
|
21
28
|
}
|
|
22
29
|
|
|
23
|
-
export interface
|
|
30
|
+
export interface JDLayoutResponse {
|
|
24
31
|
strapi: {
|
|
25
|
-
context:
|
|
26
|
-
route:
|
|
32
|
+
context: JDContext;
|
|
33
|
+
route: JDRoute;
|
|
27
34
|
}
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
// Hook / Utility function to fetch layout
|
|
31
|
-
export const
|
|
38
|
+
export const fetchJDLayout = async (
|
|
32
39
|
apiUrl: string,
|
|
33
40
|
slug: string,
|
|
34
41
|
locale: string = 'en',
|
|
35
42
|
options?: RequestInit
|
|
36
|
-
): Promise<
|
|
43
|
+
): Promise<JDLayoutResponse | null> => {
|
|
37
44
|
try {
|
|
38
45
|
const querySymbol = slug.includes('?') ? '&' : '?';
|
|
39
46
|
const res = await fetch(`${apiUrl}/api/layout/${slug}${querySymbol}locale=${locale}`, {
|
|
@@ -41,13 +48,13 @@ export const fetchVeloxLayout = async (
|
|
|
41
48
|
});
|
|
42
49
|
|
|
43
50
|
if (!res.ok) {
|
|
44
|
-
console.error(`[
|
|
51
|
+
console.error(`[JD SDK] Failed to fetch layout: ${res.statusText}`);
|
|
45
52
|
return null;
|
|
46
53
|
}
|
|
47
54
|
|
|
48
|
-
return await res.json() as
|
|
55
|
+
return await res.json() as JDLayoutResponse;
|
|
49
56
|
} catch (error) {
|
|
50
|
-
console.error(`[
|
|
57
|
+
console.error(`[JD SDK] Network error fetching layout`, error);
|
|
51
58
|
return null;
|
|
52
59
|
}
|
|
53
60
|
}
|