weg-shared-layout 0.0.11 → 0.0.12

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/docs/nextjs.md ADDED
@@ -0,0 +1,243 @@
1
+ # Next.js (App Router)
2
+
3
+ Guide for **Next.js 13+ App Router** (`app/` directory). For Vite/CRA-style client-only React, see **[React SPA](./react.md)**.
4
+
5
+ ## Why Next.js is different
6
+
7
+ | Topic | SPA (React 19) | Next.js App Router |
8
+ | --- | --- | --- |
9
+ | **Where `<weg-footer>` runs** | Client only | Must be in a **Client Component** (`"use client"`) |
10
+ | **Server Components** | N/A | Default in `app/` — cannot call `defineCustomElements()` or touch `window` |
11
+ | **SSR output** | N/A | Server HTML is often an **empty** `<weg-footer></weg-footer>` until the client bundle hydrates |
12
+ | **`layout={object}`** | Works on React 19+ | **Does not work reliably** — RSC/SSR serializes props; object props on custom elements become useless attributes |
13
+ | **Recommended `layout` value** | Object (React 19+) or string | **`JSON.stringify(layout)`** always |
14
+
15
+ Stencil custom elements need browser APIs. Registration and rendering belong in a small client wrapper; the root **Server** layout only imports that wrapper.
16
+
17
+ ## Requirements
18
+
19
+ | Requirement | Notes |
20
+ | --- | --- |
21
+ | **Next.js 13+** | App Router (`app/layout.tsx`) |
22
+ | **React 19** | Matches current Next defaults; string `layout` still required for custom elements |
23
+ | **`transpilePackages`** | Next must compile `weg-shared-layout` (see below) |
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ npm i weg-shared-layout
29
+ # or
30
+ pnpm add weg-shared-layout
31
+ ```
32
+
33
+ ## 1. Transpile the package
34
+
35
+ In `next.config.ts` / `next.config.js`:
36
+
37
+ ```ts
38
+ import type { NextConfig } from 'next';
39
+
40
+ const nextConfig: NextConfig = {
41
+ transpilePackages: ['weg-shared-layout'],
42
+ };
43
+
44
+ export default nextConfig;
45
+ ```
46
+
47
+ Without this, you may see build errors or stale/uncompiled Stencil output in the Next bundle.
48
+
49
+ ## 2. Client `Footer` component
50
+
51
+ Create a dedicated Client Component (pattern verified in [weg-payload](https://github.com/jobsac/weg-payload)):
52
+
53
+ ```tsx
54
+ // src/components/layout/Footer.tsx
55
+ 'use client';
56
+
57
+ import layout from 'weg-shared-layout/dummy-data.json';
58
+ import { defineCustomElements } from 'weg-shared-layout/loader';
59
+ import 'weg-shared-layout/weg-footer';
60
+
61
+ defineCustomElements();
62
+
63
+ export function Footer() {
64
+ return (
65
+ <weg-footer layout={JSON.stringify(layout)} suppressHydrationWarning />
66
+ );
67
+ }
68
+ ```
69
+
70
+ ### Why each line matters
71
+
72
+ | Line | Purpose |
73
+ | --- | --- |
74
+ | `'use client'` | Allows `defineCustomElements`, custom element upgrade, and DOM property updates |
75
+ | `defineCustomElements()` | Registers `<weg-footer>` in the browser (module runs once per client bundle) |
76
+ | `import 'weg-shared-layout/weg-footer'` | Ensures the component definition is bundled |
77
+ | `layout={JSON.stringify(layout)}` | Sets a string attribute/property Next/React can pass through SSR → client; component parses JSON |
78
+ | `suppressHydrationWarning` | Server renders empty tag; client fills content — avoids React hydration mismatch noise |
79
+
80
+ Do **not** pass `layout={layoutObject}` in Next — it will not hydrate correctly.
81
+
82
+ ## 3. Server layout: import the client footer
83
+
84
+ ```tsx
85
+ // app/layout.tsx (Server Component — no "use client")
86
+ import { Footer } from '@/components/layout/Footer';
87
+
88
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
89
+ return (
90
+ <html lang="en">
91
+ <body>
92
+ {children}
93
+ <Footer />
94
+ </body>
95
+ </html>
96
+ );
97
+ }
98
+ ```
99
+
100
+ Fetch layout on the server and pass it into `<Footer layout={...} />` when you wire production data (see below).
101
+
102
+ ## 4. Production: fetch on the server, stringify for the client
103
+
104
+ ```tsx
105
+ // app/layout.tsx
106
+ import { Footer } from '@/components/layout/Footer';
107
+
108
+ const LAYOUT_URL = 'https://weg-payload-test.vercel.app/api/layout';
109
+
110
+ async function getLayout() {
111
+ const res = await fetch(LAYOUT_URL, { next: { revalidate: 60 } });
112
+ if (!res.ok) throw new Error('Failed to load layout');
113
+ return res.json();
114
+ }
115
+
116
+ export default async function RootLayout({ children }: { children: React.ReactNode }) {
117
+ const layout = await getLayout();
118
+
119
+ return (
120
+ <html lang="en">
121
+ <body>
122
+ {children}
123
+ <Footer layout={layout} />
124
+ </body>
125
+ </html>
126
+ );
127
+ }
128
+ ```
129
+
130
+ ```tsx
131
+ // src/components/layout/Footer.tsx
132
+ 'use client';
133
+
134
+ import { defineCustomElements } from 'weg-shared-layout/loader';
135
+ import 'weg-shared-layout/weg-footer';
136
+
137
+ defineCustomElements();
138
+
139
+ type LayoutData = {
140
+ footer?: {
141
+ social?: { platform: string; href: string }[];
142
+ standardLinks?: { label: string; href: string }[];
143
+ credits?: string;
144
+ copyright?: string;
145
+ };
146
+ };
147
+
148
+ export function Footer({ layout }: { layout: LayoutData }) {
149
+ return (
150
+ <weg-footer layout={JSON.stringify(layout)} suppressHydrationWarning />
151
+ );
152
+ }
153
+ ```
154
+
155
+ **Rule:** whatever crosses the Server → Client boundary must be **serializable**. Pass the plain object into the Client Component, then **`JSON.stringify` inside the client** before setting it on `<weg-footer>`.
156
+
157
+ ## Passing `layout` — quick reference
158
+
159
+ | Approach | Server Component | Client Component | Works in Next? |
160
+ | --- | --- | --- | --- |
161
+ | `layout={object}` on `<weg-footer>` | No (don’t use CE directly) | Tried often | **No** |
162
+ | `layout={JSON.stringify(obj)}` on `<weg-footer>` | No | Yes | **Yes** (recommended) |
163
+ | Pass `layout` object to `<Footer />`, stringify inside | Fetch/await JSON | `JSON.stringify` in JSX | **Yes** (recommended for CMS) |
164
+ | `ref` + `el.layout = obj` in `useEffect` | No | Yes | Yes (escape hatch) |
165
+ | `prop:layout={obj}` | No | Unreliable | **Avoid** |
166
+
167
+ ## TypeScript
168
+
169
+ Use **module augmentation** (not triple-slash references in every file). Example `src/types/weg-shared-layout-jsx.d.ts`:
170
+
171
+ ```ts
172
+ import type { JSX as WegSharedLayoutJSX } from 'weg-shared-layout';
173
+ import type { DetailedHTMLProps, HTMLAttributes } from 'react';
174
+
175
+ declare module 'react' {
176
+ namespace JSX {
177
+ interface IntrinsicElements extends WegSharedLayoutJSX.IntrinsicElements {
178
+ 'weg-footer': WegSharedLayoutJSX.IntrinsicElements['weg-footer'] &
179
+ DetailedHTMLProps<HTMLAttributes<HTMLWegFooterElement>, HTMLWegFooterElement>;
180
+ }
181
+ }
182
+ }
183
+ ```
184
+
185
+ Enable `resolveJsonModule` if you import `weg-shared-layout/dummy-data.json` in the client footer.
186
+
187
+ ## Troubleshooting
188
+
189
+ | Symptom | Likely cause | Fix |
190
+ | --- | --- | --- |
191
+ | `document is not defined` | `defineCustomElements()` in a Server Component | Move registration to `"use client"` `Footer.tsx` only. |
192
+ | Empty `<weg-footer>` in Elements panel | SSR placeholder before hydration | Expected until client JS runs; confirm client bundle loads and `defineCustomElements()` ran. |
193
+ | Empty footer after hydration | `layout={object}` or missing stringify | Use `layout={JSON.stringify(layout)}` in the client component. |
194
+ | Hydration warning on `<weg-footer>` | Server HTML ≠ client DOM | Add `suppressHydrationWarning`; ensure `layout` JSON is identical server vs client props. |
195
+ | Build error importing package | Package not transpiled | Add `transpilePackages: ['weg-shared-layout']`. |
196
+ | `'weg-footer' is not a known element` (TS) | Missing JSX types | Add module augmentation file above. |
197
+ | Footer never upgrades | Loader/tag not imported on client | `import 'weg-shared-layout/weg-footer'` + `defineCustomElements()` in client file. |
198
+
199
+ ### DevTools tips
200
+
201
+ 1. **Elements:** Before hydration, `<weg-footer>` is often empty. After hydration, expand the shadow root — links should appear inside `#shadow-root`.
202
+ 2. **Console:** Log `document.querySelector('weg-footer')?.layout` in the browser — should be a **string** (JSON) or object, not `undefined`.
203
+ 3. **Network:** Confirm your layout API returns the same shape as [`dummy-data.json`](../src/assets/dummy-data.json).
204
+ 4. **React DevTools:** `Footer` should show under a Client Component boundary; parent `layout.tsx` stays a Server Component.
205
+
206
+ ### Ref fallback (if stringify still fails)
207
+
208
+ ```tsx
209
+ 'use client';
210
+
211
+ import { useEffect, useRef } from 'react';
212
+ import { defineCustomElements } from 'weg-shared-layout/loader';
213
+ import 'weg-shared-layout/weg-footer';
214
+
215
+ defineCustomElements();
216
+
217
+ export function Footer({ layout }: { layout: unknown }) {
218
+ const ref = useRef<HTMLWegFooterElement>(null);
219
+
220
+ useEffect(() => {
221
+ if (ref.current) ref.current.layout = layout as object;
222
+ }, [layout]);
223
+
224
+ return <weg-footer ref={ref} suppressHydrationWarning />;
225
+ }
226
+ ```
227
+
228
+ ## Integration checklist
229
+
230
+ - [ ] `weg-shared-layout` installed
231
+ - [ ] `transpilePackages: ['weg-shared-layout']` in `next.config`
232
+ - [ ] `Footer.tsx` with `'use client'`, loader, tag import, `JSON.stringify`, `suppressHydrationWarning`
233
+ - [ ] Root `app/layout.tsx` imports `<Footer />` (no `defineCustomElements` on server)
234
+ - [ ] Layout data fetched server-side (or static import) and passed as serializable props
235
+ - [ ] TypeScript module augmentation for `'weg-footer'`
236
+ - [ ] Verified in browser after hydration (not only view-source HTML)
237
+
238
+ ## See also
239
+
240
+ - **[React SPA](./react.md)** — object `layout={...}` on React 19 without SSR
241
+ - **[Angular](./angular.md)**
242
+ - **[Plain HTML / vanilla JS](./vanilla.md)**
243
+ - **[Package readme](../readme.md)**
package/docs/react.md CHANGED
@@ -1,8 +1,34 @@
1
- # React
1
+ # React (SPA)
2
2
 
3
- ## Register custom elements
3
+ Guide for **client-rendered** React apps (Vite, Create React App, etc.). If you use **Next.js App Router**, see **[Next.js](./nextjs.md)** — SSR and Server Components require a different integration.
4
4
 
5
- Run once (for example in `main.tsx` / your app entry):
5
+ ## What is `<weg-footer>`?
6
+
7
+ `<weg-footer>` is a **presentational** [Stencil](https://stenciljs.com/) Web Component shipped by `weg-shared-layout`. It renders the site footer (social links, standard links, credits, copyright) from a **layout payload** you provide.
8
+
9
+ The component **does not fetch data**. Your app loads JSON (from an API, CMS, or a static file) and passes it on the `layout` property.
10
+
11
+ Payload shape matches [`dummy-data.json`](../src/assets/dummy-data.json) (see also [readme](../readme.md#how-it-works)).
12
+
13
+ ## Requirements
14
+
15
+ | Requirement | Notes |
16
+ | --- | --- |
17
+ | **React 19+** | React 19 maps custom-element props to DOM **properties** when possible. Older React often sets `layout` as a string **attribute**, which breaks object payloads. |
18
+ | **Bundler** | Must resolve `node_modules` ESM/CJS from `weg-shared-layout`. |
19
+ | **TypeScript (optional)** | Module augmentation below; enable `resolveJsonModule` if you import `dummy-data.json`. |
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ npm i weg-shared-layout
25
+ # or
26
+ pnpm add weg-shared-layout
27
+ ```
28
+
29
+ ## 1. Register custom elements (once)
30
+
31
+ Call `defineCustomElements()` **once** before the first `<weg-footer>` render — typically in your app entry (`main.tsx`, `index.tsx`):
6
32
 
7
33
  ```ts
8
34
  import { defineCustomElements } from 'weg-shared-layout/loader';
@@ -10,9 +36,23 @@ import { defineCustomElements } from 'weg-shared-layout/loader';
10
36
  defineCustomElements();
11
37
  ```
12
38
 
13
- ## Pass layout data
39
+ Also side-effect import the tag so your bundler includes the component definition:
40
+
41
+ ```ts
42
+ import 'weg-shared-layout/weg-footer';
43
+ ```
44
+
45
+ **Alternative:** import only the footer bundle (no loader call):
46
+
47
+ ```ts
48
+ import 'weg-shared-layout/weg-footer';
49
+ ```
50
+
51
+ Use the loader when you may add more tags from this package later.
14
52
 
15
- Import the fixture (or your own object with the same shape) and pass it on **`layout`**:
53
+ ## 2. Render the footer
54
+
55
+ ### Recommended: pass `layout` as an object (React 19+)
16
56
 
17
57
  ```tsx
18
58
  import 'weg-shared-layout/weg-footer';
@@ -23,14 +63,139 @@ export function SiteFooter() {
23
63
  }
24
64
  ```
25
65
 
26
- Use **React 19 or newer** so `layout={...}` is applied as the custom element’s **`layout` property** (object), not a string attribute.
66
+ With React 19+, `layout={layoutObject}` is applied as the custom element’s **`layout` property** (object), which `<weg-footer>` expects.
67
+
68
+ ### Fallback: `JSON.stringify` (all React versions)
69
+
70
+ If you are on React 18 or see an empty footer despite correct data, pass a JSON **string**. The component parses string values the same as objects:
71
+
72
+ ```tsx
73
+ <weg-footer layout={JSON.stringify(layout)} />
74
+ ```
75
+
76
+ ### Plain HTML attribute
77
+
78
+ Outside React, you can set `layout='{"footer":{...}}'` on the element. Prefer the property in SPA code.
79
+
80
+ ## 3. Production: fetch layout from your API
81
+
82
+ Example using the public test API (same shape as `dummy-data.json`):
83
+
84
+ ```tsx
85
+ import { useEffect, useState } from 'react';
86
+ import 'weg-shared-layout/weg-footer';
87
+ import layoutFixture from 'weg-shared-layout/dummy-data.json';
88
+
89
+ type LayoutData = typeof layoutFixture;
90
+
91
+ const LAYOUT_URL = 'https://weg-payload-test.vercel.app/api/layout';
92
+
93
+ export function SiteFooter() {
94
+ const [layout, setLayout] = useState<LayoutData | null>(null);
95
+
96
+ useEffect(() => {
97
+ let cancelled = false;
98
+ fetch(LAYOUT_URL)
99
+ .then((res) => {
100
+ if (!res.ok) throw new Error(`layout fetch failed: ${res.status}`);
101
+ return res.json() as Promise<LayoutData>;
102
+ })
103
+ .then((data) => {
104
+ if (!cancelled) setLayout(data);
105
+ })
106
+ .catch(console.error);
107
+ return () => {
108
+ cancelled = true;
109
+ };
110
+ }, []);
111
+
112
+ if (!layout) return null; // or a skeleton
113
+
114
+ return <weg-footer layout={layout} />;
115
+ }
116
+ ```
117
+
118
+ Replace the URL with your own CMS/API. Keep the object shape aligned with `dummy-data.json`.
27
119
 
28
- **Next.js App Router:** keep registration and this JSX in a **Client Component** (`"use client"`).
120
+ ## TypeScript: module augmentation
29
121
 
30
- ## TypeScript
122
+ Do **not** rely on a triple-slash reference in every file. Augment React’s JSX namespace once (e.g. `src/types/weg-shared-layout-jsx.d.ts`):
31
123
 
32
124
  ```ts
33
- /// <reference types="weg-shared-layout/dist/types/components" />
125
+ import type { JSX as WegSharedLayoutJSX } from 'weg-shared-layout';
126
+ import type { DetailedHTMLProps, HTMLAttributes } from 'react';
127
+
128
+ declare module 'react' {
129
+ namespace JSX {
130
+ interface IntrinsicElements extends WegSharedLayoutJSX.IntrinsicElements {
131
+ 'weg-footer': WegSharedLayoutJSX.IntrinsicElements['weg-footer'] &
132
+ DetailedHTMLProps<HTMLAttributes<HTMLWegFooterElement>, HTMLWegFooterElement>;
133
+ }
134
+ }
135
+ }
136
+ ```
137
+
138
+ Ensure that file is included by your `tsconfig.json` `include`/`files`.
139
+
140
+ Enable in `tsconfig.json` when importing JSON fixtures:
141
+
142
+ ```json
143
+ {
144
+ "compilerOptions": {
145
+ "resolveJsonModule": true
146
+ }
147
+ }
34
148
  ```
35
149
 
36
- Enable `resolveJsonModule` in `tsconfig.json` if you import `dummy-data.json`.
150
+ ## `layout` prop vs attribute (why React version matters)
151
+
152
+ | How data is set | Works with object? |
153
+ | --- | --- |
154
+ | **Property** `el.layout = obj` / React 19 `layout={obj}` | Yes |
155
+ | **Attribute** `layout="[object Object]"` / React 18 `layout={obj}` | No — footer stays empty |
156
+ | **Attribute** `layout='{"footer":...}'` / `layout={JSON.stringify(obj)}` | Yes — component parses JSON |
157
+
158
+ `<weg-footer>` accepts `layout` as either an object or a JSON string (see `parseJsonProp` in the component source).
159
+
160
+ ## Note: `prop:layout` is unreliable in React
161
+
162
+ Some examples set Stencil props with a `prop:` prefix (e.g. `prop:layout={data}`). In React this is **not dependable** — behavior varies by version and reconciler. Prefer:
163
+
164
+ 1. **`layout={object}`** on React 19+, or
165
+ 2. **`layout={JSON.stringify(object)}`**, or
166
+ 3. **Ref fallback** after mount:
167
+
168
+ ```tsx
169
+ import { useEffect, useRef } from 'react';
170
+ import type layoutFixture from 'weg-shared-layout/dummy-data.json';
171
+
172
+ type LayoutData = typeof layoutFixture;
173
+
174
+ export function SiteFooter({ layout }: { layout: LayoutData }) {
175
+ const ref = useRef<HTMLWegFooterElement>(null);
176
+
177
+ useEffect(() => {
178
+ if (ref.current) ref.current.layout = layout;
179
+ }, [layout]);
180
+
181
+ return <weg-footer ref={ref} />;
182
+ }
183
+ ```
184
+
185
+ ## Troubleshooting
186
+
187
+ | Symptom | Likely cause | Fix |
188
+ | --- | --- | --- |
189
+ | Empty footer, no errors | `defineCustomElements()` not called, or missing `import 'weg-shared-layout/weg-footer'` | Register loader + import tag in entry before render. |
190
+ | Empty footer, data looks correct | React set `layout` as attribute (`[object Object]`) | Upgrade to React 19+, or use `layout={JSON.stringify(layout)}` or ref assignment. |
191
+ | TypeScript: `'weg-footer' does not exist on JSX.IntrinsicElements` | No module augmentation | Add `weg-shared-layout-jsx.d.ts` as above. |
192
+ | Cannot find module `weg-shared-layout/dummy-data.json` | `resolveJsonModule` off | Enable in `tsconfig.json`. |
193
+ | Footer flashes then disappears | Strict Mode double-mount + async layout | Ensure stable `layout` reference; avoid resetting state on remount. |
194
+ | Styles missing | Shadow DOM is encapsulated | Footer ships its own CSS; do not expect global site styles inside the shadow root. |
195
+
196
+ ## See also
197
+
198
+ - **[Next.js App Router](./nextjs.md)** — Server Components, `transpilePackages`, hydration
199
+ - **[Angular](./angular.md)**
200
+ - **[Plain HTML / vanilla JS](./vanilla.md)**
201
+ - **[Package readme](../readme.md)**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weg-shared-layout",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "description": "Shared layout Web Components built with Stencil",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.js",
package/readme.md CHANGED
@@ -29,7 +29,8 @@ The payload shape matches **`dummy-data.json`**:
29
29
  | Guide | Doc |
30
30
  | --- | --- |
31
31
  | Angular | [docs/angular.md](./docs/angular.md) |
32
- | React | [docs/react.md](./docs/react.md) |
32
+ | React (SPA, client-only) | [docs/react.md](./docs/react.md) |
33
+ | Next.js (App Router) | [docs/nextjs.md](./docs/nextjs.md) |
33
34
  | Plain HTML / vanilla JS | [docs/vanilla.md](./docs/vanilla.md) |
34
35
 
35
36
  ## Publishing (maintainers)