@se-studio/project-build 1.0.131 → 1.0.133
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/CHANGELOG.md +12 -0
- package/README.md +13 -14
- package/package.json +2 -4
- package/dist/management/sync-skills.d.ts +0 -4
- package/dist/management/sync-skills.d.ts.map +0 -1
- package/dist/management/sync-skills.js +0 -97
- package/dist/management/sync-skills.js.map +0 -1
- package/dist/seskills.d.ts +0 -9
- package/dist/seskills.d.ts.map +0 -1
- package/dist/seskills.js +0 -32
- package/dist/seskills.js.map +0 -1
- package/skills/contentful-cms/alt-text-audit/SKILL.md +0 -60
- package/skills/contentful-cms/cms-guidelines/README.md +0 -166
- package/skills/contentful-cms/cms-guidelines/colour-hint-prompt.md +0 -77
- package/skills/contentful-cms/cms-guidelines/evaluation-prompt.md +0 -84
- package/skills/contentful-cms/cms-guidelines/generate-component-guidelines.md +0 -126
- package/skills/contentful-cms/cms-guidelines/generation-prompt.md +0 -231
- package/skills/contentful-cms/cms-guidelines/html-component-authoring.md +0 -401
- package/skills/contentful-cms/cms-guidelines/validation-prompt.md +0 -170
- package/skills/contentful-cms/cms-guidelines/variant-loop.md +0 -189
- package/skills/contentful-cms/cms-guidelines/variant-proposal-prompt.md +0 -131
- package/skills/contentful-cms/core/SKILL.md +0 -793
- package/skills/contentful-cms/generate-all-guidelines/SKILL.md +0 -313
- package/skills/contentful-cms/generate-cms-guidelines/SKILL.md +0 -313
- package/skills/contentful-cms/image-guide/SKILL.md +0 -240
- package/skills/contentful-cms/manifest.json +0 -11
- package/skills/contentful-cms/navigation/SKILL.md +0 -23
- package/skills/contentful-cms/rich-text/SKILL.md +0 -96
- package/skills/contentful-cms/schema-org/SKILL.md +0 -74
- package/skills/contentful-cms/screenshots/SKILL.md +0 -46
- package/skills/contentful-cms/seo-descriptions/SKILL.md +0 -54
- package/skills/contentful-cms/templates/SKILL.md +0 -21
- package/skills/contentful-cms/update-cms-guidelines/SKILL.md +0 -348
- package/skills/se-marketing-sites/cms-routes-and-appshared/SKILL.md +0 -99
- package/skills/se-marketing-sites/create-collection/SKILL.md +0 -295
- package/skills/se-marketing-sites/create-component/SKILL.md +0 -250
- package/skills/se-marketing-sites/create-page/SKILL.md +0 -129
- package/skills/se-marketing-sites/curate-showcase-mocks/SKILL.md +0 -343
- package/skills/se-marketing-sites/handling-media/SKILL.md +0 -195
- package/skills/se-marketing-sites/lib-cms-structure/SKILL.md +0 -83
- package/skills/se-marketing-sites/register-cms-features/SKILL.md +0 -95
- package/skills/se-marketing-sites/styling-system/SKILL.md +0 -122
|
@@ -1,295 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: create-cms-collection
|
|
3
|
-
description: Guide for creating new CMS-driven collections (component containers) in the SE Core Product monorepo. Use this skill when asked to create a new collection or "grid" component.
|
|
4
|
-
license: Private
|
|
5
|
-
metadata:
|
|
6
|
-
author: se-core-product
|
|
7
|
-
version: "1.1.0"
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# Creating CMS Collections
|
|
11
|
-
|
|
12
|
-
Collections are components that contain other components (usually referred to as "Cards" in this context). They follow the same **four-layer architecture** as standard components but use specialized helpers.
|
|
13
|
-
|
|
14
|
-
## Architecture Overview
|
|
15
|
-
|
|
16
|
-
Like components, collections **MUST be Server Components** by default. Complex interactions (carousels, filters) must be isolated.
|
|
17
|
-
|
|
18
|
-
1. **Layer 1: Core Collection**
|
|
19
|
-
* Named export: `Core[CollectionName]`
|
|
20
|
-
* Iterates over `contents` array
|
|
21
|
-
* Renders children (Cards)
|
|
22
|
-
* Handles layout (grid, list) - **Server-side only**
|
|
23
|
-
* If a Carousel or Filter is needed, wraps children in a Client Component.
|
|
24
|
-
|
|
25
|
-
2. **Layer 2: Wrapper Collection**
|
|
26
|
-
* Named export: `[CollectionName]`
|
|
27
|
-
* Wraps Core with `<Section>`
|
|
28
|
-
* Handles outer spacing & background
|
|
29
|
-
|
|
30
|
-
3. **Layer 3: Renderer**
|
|
31
|
-
* Default export: `[CollectionName]`
|
|
32
|
-
* Implements `CollectionRenderer`
|
|
33
|
-
* Wraps with `UnusedChecker`
|
|
34
|
-
|
|
35
|
-
4. **Layer 4: Registration**
|
|
36
|
-
* Named export: `[CollectionName]Registration`
|
|
37
|
-
* Uses `defineCollection` (NOT `defineComponent`)
|
|
38
|
-
* Defines `usedFields` for the collection itself
|
|
39
|
-
* Defines `cardUsedFields` for the children components
|
|
40
|
-
|
|
41
|
-
## Client Component Strategy
|
|
42
|
-
|
|
43
|
-
**Rule: Isolate Client Logic (Carousels, Animations, Filters).**
|
|
44
|
-
|
|
45
|
-
Never make the whole collection a Client Component.
|
|
46
|
-
|
|
47
|
-
* **BAD**: `'use client'` on `MyCollection.tsx`.
|
|
48
|
-
* **GOOD**: Create `MyCollectionClient.tsx` (e.g., `<CarouselClient>`) that accepts children.
|
|
49
|
-
|
|
50
|
-
### Pattern: The Client Wrapper
|
|
51
|
-
|
|
52
|
-
For a carousel:
|
|
53
|
-
1. **Server Component** (`CoreMyCollection`): Renders the loop of cards.
|
|
54
|
-
2. **Client Component** (`CarouselClient`): Imports the carousel library (swiper, embla), manages state.
|
|
55
|
-
3. **Usage**: Pass the server-rendered cards as `children` to the client wrapper.
|
|
56
|
-
|
|
57
|
-
```tsx
|
|
58
|
-
// CoreMyCollection.tsx (Server)
|
|
59
|
-
<CarouselClient>
|
|
60
|
-
{contents.map(item => <Card content={item} />)}
|
|
61
|
-
</CarouselClient>
|
|
62
|
-
|
|
63
|
-
// CarouselClient.tsx (Client)
|
|
64
|
-
'use client';
|
|
65
|
-
export const CarouselClient = ({ children }) => {
|
|
66
|
-
return <Slider>{children}</Slider>; // Client logic here
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
This ensures the card content is SEO-ready and server-rendered, while only the slider mechanism is client-side.
|
|
71
|
-
|
|
72
|
-
## Quick Start Template
|
|
73
|
-
|
|
74
|
-
Use this template for new collections. Replace `MyCollection` with your collection name.
|
|
75
|
-
|
|
76
|
-
```typescript
|
|
77
|
-
import type { IContentContext } from '@se-studio/core-data-types';
|
|
78
|
-
import { isComponent } from '@se-studio/core-data-types';
|
|
79
|
-
import {
|
|
80
|
-
type CmsRendererConfig,
|
|
81
|
-
type CollectionRenderer,
|
|
82
|
-
cn,
|
|
83
|
-
defineCollection,
|
|
84
|
-
getPreviewFieldProps,
|
|
85
|
-
RtfOrString,
|
|
86
|
-
UnusedChecker,
|
|
87
|
-
} from '@se-studio/core-ui';
|
|
88
|
-
import { SectionLinks } from '@/elements/SectionLinks';
|
|
89
|
-
import { Section } from '@/framework/Section';
|
|
90
|
-
import type { ICollection, IComponent, IContent } from '@/lib/cms';
|
|
91
|
-
import { getSizingInformation } from '@/lib/SizingInformation';
|
|
92
|
-
// import MyCollectionClient from './MyCollectionClient'; // If needed
|
|
93
|
-
|
|
94
|
-
// 1. Define fields for the Collection itself
|
|
95
|
-
const FIELD_KEYS = [
|
|
96
|
-
'id',
|
|
97
|
-
'index',
|
|
98
|
-
'heading',
|
|
99
|
-
'body',
|
|
100
|
-
'contents', // The children
|
|
101
|
-
'links',
|
|
102
|
-
'anchor',
|
|
103
|
-
'cmsLabel',
|
|
104
|
-
] as const;
|
|
105
|
-
|
|
106
|
-
type Fields = Pick<ICollection, (typeof FIELD_KEYS)[number]>;
|
|
107
|
-
const USED_FIELDS = new Set([...FIELD_KEYS, 'collectionType']);
|
|
108
|
-
|
|
109
|
-
// 2. Define fields used by the Child Cards
|
|
110
|
-
// (If the children are generic components, this might just be 'id' and 'componentType')
|
|
111
|
-
const CARD_FIELD_KEYS = [
|
|
112
|
-
'id',
|
|
113
|
-
'heading',
|
|
114
|
-
'body',
|
|
115
|
-
'visual',
|
|
116
|
-
'links',
|
|
117
|
-
'cmsLabel',
|
|
118
|
-
] as const;
|
|
119
|
-
const CARD_USED_FIELDS = new Set([...CARD_FIELD_KEYS, 'componentType']);
|
|
120
|
-
|
|
121
|
-
// Helper to render individual cards (use ChildElement for card titles – items inside a collection)
|
|
122
|
-
const CardWrapper: React.FC<{
|
|
123
|
-
content: IComponent;
|
|
124
|
-
index: number;
|
|
125
|
-
contentContext: IContentContext;
|
|
126
|
-
rendererConfig: CmsRendererConfig;
|
|
127
|
-
}> = ({ content, index, contentContext, rendererConfig }) => {
|
|
128
|
-
const { ChildElement, sizingInformation } = getSizingInformation(1);
|
|
129
|
-
return (
|
|
130
|
-
<div className="p-6 bg-white rounded-lg shadow-sm">
|
|
131
|
-
{content.heading && (
|
|
132
|
-
<ChildElement className={cn(sizingInformation.h3, 'mb-2')}>
|
|
133
|
-
{content.heading}
|
|
134
|
-
</ChildElement>
|
|
135
|
-
)}
|
|
136
|
-
{/* ... */}
|
|
137
|
-
</div>
|
|
138
|
-
);
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
// 3. Layer 1: Core Collection (Server Component)
|
|
142
|
-
const CoreMyCollection: React.FC<
|
|
143
|
-
Fields & {
|
|
144
|
-
contentContext: IContentContext;
|
|
145
|
-
rendererConfig: CmsRendererConfig;
|
|
146
|
-
positionClassName?: string;
|
|
147
|
-
}
|
|
148
|
-
> = ({
|
|
149
|
-
id,
|
|
150
|
-
index,
|
|
151
|
-
heading,
|
|
152
|
-
body,
|
|
153
|
-
contents,
|
|
154
|
-
links,
|
|
155
|
-
cmsLabel,
|
|
156
|
-
contentContext,
|
|
157
|
-
rendererConfig,
|
|
158
|
-
positionClassName,
|
|
159
|
-
}) => {
|
|
160
|
-
const { Element, sizingInformation } = getSizingInformation(index);
|
|
161
|
-
|
|
162
|
-
return (
|
|
163
|
-
<div className={cn('space-y-8', positionClassName)}>
|
|
164
|
-
<div className="text-center space-y-4">
|
|
165
|
-
{heading && (
|
|
166
|
-
<Element
|
|
167
|
-
{...getPreviewFieldProps(rendererConfig, id, 'heading')}
|
|
168
|
-
className={cn(sizingInformation.h2)}
|
|
169
|
-
>
|
|
170
|
-
{heading}
|
|
171
|
-
</Element>
|
|
172
|
-
)}
|
|
173
|
-
{body && (
|
|
174
|
-
<RtfOrString
|
|
175
|
-
{...getPreviewFieldProps(rendererConfig, id, 'body')}
|
|
176
|
-
content={body}
|
|
177
|
-
className="rtf-standard"
|
|
178
|
-
rendererConfig={rendererConfig}
|
|
179
|
-
contentContext={contentContext}
|
|
180
|
-
/>
|
|
181
|
-
)}
|
|
182
|
-
</div>
|
|
183
|
-
|
|
184
|
-
{contents && contents.length > 0 && (
|
|
185
|
-
<ul className="grid grid-cols-1 laptop:grid-cols-3 gap-6">
|
|
186
|
-
{contents.filter(isComponent).map((item, i) => (
|
|
187
|
-
<li key={item.id}>
|
|
188
|
-
<CardWrapper
|
|
189
|
-
content={item}
|
|
190
|
-
index={i}
|
|
191
|
-
contentContext={contentContext}
|
|
192
|
-
rendererConfig={rendererConfig}
|
|
193
|
-
/>
|
|
194
|
-
</li>
|
|
195
|
-
))}
|
|
196
|
-
</ul>
|
|
197
|
-
)}
|
|
198
|
-
|
|
199
|
-
{links && (
|
|
200
|
-
<SectionLinks
|
|
201
|
-
links={links}
|
|
202
|
-
rendererConfig={rendererConfig}
|
|
203
|
-
trackingLocation={cmsLabel}
|
|
204
|
-
analyticsContext={contentContext.analyticsContext}
|
|
205
|
-
className="justify-center"
|
|
206
|
-
/>
|
|
207
|
-
)}
|
|
208
|
-
</div>
|
|
209
|
-
);
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
// 4. Layer 2: Wrapper Collection
|
|
213
|
-
const MyCollection: React.FC<
|
|
214
|
-
Fields & {
|
|
215
|
-
information: IContent;
|
|
216
|
-
contentContext: IContentContext;
|
|
217
|
-
rendererConfig: CmsRendererConfig;
|
|
218
|
-
}
|
|
219
|
-
> = ({ information, ...rest }) => {
|
|
220
|
-
return (
|
|
221
|
-
<Section
|
|
222
|
-
componentName={information.collectionType}
|
|
223
|
-
information={information}
|
|
224
|
-
previewHelpers={rest.rendererConfig?.previewHelpers}
|
|
225
|
-
>
|
|
226
|
-
<CoreMyCollection
|
|
227
|
-
{...rest}
|
|
228
|
-
positionClassName="col-span-full"
|
|
229
|
-
/>
|
|
230
|
-
</Section>
|
|
231
|
-
);
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
// 5. Layer 3: Renderer
|
|
235
|
-
const MyCollectionRenderer: CollectionRenderer<ICollection> = ({
|
|
236
|
-
information,
|
|
237
|
-
contentContext,
|
|
238
|
-
rendererConfig,
|
|
239
|
-
}) => {
|
|
240
|
-
const { collectionType, ...rest } = information;
|
|
241
|
-
|
|
242
|
-
return (
|
|
243
|
-
<UnusedChecker
|
|
244
|
-
componentName={collectionType}
|
|
245
|
-
allFields={rest}
|
|
246
|
-
usedFields={USED_FIELDS}
|
|
247
|
-
>
|
|
248
|
-
<MyCollection
|
|
249
|
-
information={information}
|
|
250
|
-
contentContext={contentContext}
|
|
251
|
-
rendererConfig={rendererConfig}
|
|
252
|
-
{...(rest as Fields)}
|
|
253
|
-
/>
|
|
254
|
-
</UnusedChecker>
|
|
255
|
-
);
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
export default MyCollectionRenderer;
|
|
259
|
-
|
|
260
|
-
// 6. Layer 4: Registration
|
|
261
|
-
export const MyCollectionRegistration = defineCollection({
|
|
262
|
-
name: 'My Collection', // Must match CMS collectionType
|
|
263
|
-
renderer: MyCollectionRenderer,
|
|
264
|
-
usedFields: USED_FIELDS,
|
|
265
|
-
cardUsedFields: CARD_USED_FIELDS,
|
|
266
|
-
mock: {
|
|
267
|
-
heading: 'Sample Collection',
|
|
268
|
-
body: 'Collection description text',
|
|
269
|
-
},
|
|
270
|
-
});
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
## Checklist for Collections
|
|
274
|
-
|
|
275
|
-
1. **Define Two Sets of Fields**:
|
|
276
|
-
* `USED_FIELDS` for the collection container (heading, body, background).
|
|
277
|
-
* `CARD_USED_FIELDS` for the items inside `contents`.
|
|
278
|
-
2. **Use `defineCollection`**:
|
|
279
|
-
* Instead of `defineComponent`.
|
|
280
|
-
* Pass both `usedFields` and `cardUsedFields`.
|
|
281
|
-
3. **Heading hierarchy**:
|
|
282
|
-
* Use **Element** from `getSizingInformation(index)` for the collection's section heading (and a class from `sizingInformation`). Use **ChildElement** from `getSizingInformation(1)` for item/card titles. Render preHeading/postHeading as **<p>** with a typography class, never as heading tags.
|
|
283
|
-
4. **Preview helpers**:
|
|
284
|
-
* Use `getPreviewFieldProps(rendererConfig, id, fieldId)` from `@se-studio/core-ui` for CMS preview (do not import from cms-server – avoids circular dependency). For responsive visuals use `getPreviewResponsiveVisualFieldProps(rendererConfig, id, 'visual', 'mobileVisual')`.
|
|
285
|
-
* For collections that fetch data: use `rendererConfig?.fetchHelpers?.getRelatedArticles?.(information, contentContext, count)` instead of importing from cms-server.
|
|
286
|
-
5. **Handle `contents`**:
|
|
287
|
-
* Check `if (contents && contents.length > 0)`.
|
|
288
|
-
* Filter with `contents.filter(isComponent)`.
|
|
289
|
-
* Map over the result to render items.
|
|
290
|
-
6. **Isolate Client Logic**:
|
|
291
|
-
* **ENSURE SERVER COMPONENT** for the main file.
|
|
292
|
-
* If a carousel/slider/filter is needed, move that logic to `[Name]Client.tsx`.
|
|
293
|
-
* Pass server-rendered cards as `children` to the client wrapper.
|
|
294
|
-
7. **Register**:
|
|
295
|
-
* Add to `collectionRegistrationsList` in `src/lib/registrations.ts`. The `name` is defined only in your `defineCollection(...)` call.
|
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: create-cms-component
|
|
3
|
-
description: Guide for creating new CMS-driven components in the SE Core Product monorepo. Use this skill when asked to create a new component, ensuring adherence to the 4-layer architecture.
|
|
4
|
-
license: Private
|
|
5
|
-
metadata:
|
|
6
|
-
author: se-core-product
|
|
7
|
-
version: "1.1.0"
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# Creating CMS Components
|
|
11
|
-
|
|
12
|
-
This guide explains how to build new CMS-driven components following the established architecture patterns in this codebase.
|
|
13
|
-
|
|
14
|
-
## Architecture Overview
|
|
15
|
-
|
|
16
|
-
All components follow a **four-layer architecture** and must remain **Server Components** by default.
|
|
17
|
-
|
|
18
|
-
1. **Layer 1: Core Component (Pure Presentation)**
|
|
19
|
-
* Named export: `Core[ComponentName]`
|
|
20
|
-
* CMS-agnostic rendering logic
|
|
21
|
-
* Accepts `positionClassName` prop
|
|
22
|
-
* **MUST be a Server Component** (no `'use client'`)
|
|
23
|
-
* If interactivity is needed, import a Client Component here.
|
|
24
|
-
|
|
25
|
-
2. **Layer 2: Wrapper Component (Section Integration)**
|
|
26
|
-
* Named export: `[ComponentName]`
|
|
27
|
-
* Wraps Core with `<Section>`
|
|
28
|
-
* Handles spacing & background
|
|
29
|
-
|
|
30
|
-
3. **Layer 3: Renderer (CMS Integration)**
|
|
31
|
-
* Default export: `[ComponentName]` (short name)
|
|
32
|
-
* Implements `ComponentRenderer`
|
|
33
|
-
* Wraps with `UnusedChecker` for CMS validation
|
|
34
|
-
|
|
35
|
-
4. **Layer 4: Registration (Showcase & CMS)**
|
|
36
|
-
* Named export: `[ComponentName]Registration`
|
|
37
|
-
* Uses `defineComponent` helper
|
|
38
|
-
* Provides metadata & mock data
|
|
39
|
-
|
|
40
|
-
## Client Component Strategy
|
|
41
|
-
|
|
42
|
-
**Rule: Isolate Client Logic.**
|
|
43
|
-
|
|
44
|
-
Never make the entire component a Client Component just for a small interactive part.
|
|
45
|
-
|
|
46
|
-
* **BAD**: Adding `'use client'` to `MyComponent.tsx` (Layers 1-3).
|
|
47
|
-
* **GOOD**: Creating `MyComponentClient.tsx` (or `*Animator.tsx`) with `'use client'` and importing it into `CoreMyComponent`.
|
|
48
|
-
|
|
49
|
-
### Why?
|
|
50
|
-
* **Performance**: Keeps the majority of the HTML generation on the server.
|
|
51
|
-
* **SEO**: Ensures content is fully rendered for crawlers.
|
|
52
|
-
* **Bundle Size**: Only sends necessary JS to the browser.
|
|
53
|
-
|
|
54
|
-
### Pattern: The Client Wrapper
|
|
55
|
-
|
|
56
|
-
If you need state (e.g., filters, toggles) or effects (e.g., animations), create a wrapper:
|
|
57
|
-
|
|
58
|
-
1. Create `MyComponentClient.tsx` (or `MyComponentAnimator.tsx`).
|
|
59
|
-
2. Add `'use client'` at the top.
|
|
60
|
-
3. Accept `children` or raw data props.
|
|
61
|
-
4. Import and use it in `CoreMyComponent`.
|
|
62
|
-
|
|
63
|
-
## Quick Start Template
|
|
64
|
-
|
|
65
|
-
Use this template for new components. Replace `MyNewComponent` with your component name.
|
|
66
|
-
|
|
67
|
-
```typescript
|
|
68
|
-
import type { IContentContext } from '@se-studio/core-data-types';
|
|
69
|
-
import {
|
|
70
|
-
type CmsRendererConfig,
|
|
71
|
-
type ComponentRenderer,
|
|
72
|
-
convertText,
|
|
73
|
-
cn,
|
|
74
|
-
defineComponent,
|
|
75
|
-
getPreviewFieldProps,
|
|
76
|
-
RtfOrString,
|
|
77
|
-
UnusedChecker,
|
|
78
|
-
} from '@se-studio/core-ui';
|
|
79
|
-
import { SectionLinks } from '@/elements/SectionLinks';
|
|
80
|
-
import { Section } from '@/framework/Section';
|
|
81
|
-
import type { IComponent, IContent } from '@/lib/cms';
|
|
82
|
-
import { getSizingInformation } from '@/lib/SizingInformation';
|
|
83
|
-
// If interactivity is needed:
|
|
84
|
-
// import MyNewComponentClient from './MyNewComponentClient';
|
|
85
|
-
|
|
86
|
-
// 1. Define which CMS fields this component uses
|
|
87
|
-
const FIELD_KEYS = [
|
|
88
|
-
'id',
|
|
89
|
-
'index',
|
|
90
|
-
'heading',
|
|
91
|
-
'body',
|
|
92
|
-
'links',
|
|
93
|
-
'anchor',
|
|
94
|
-
'cmsLabel',
|
|
95
|
-
] as const;
|
|
96
|
-
|
|
97
|
-
type Fields = Pick<IComponent, (typeof FIELD_KEYS)[number]>;
|
|
98
|
-
|
|
99
|
-
const USED_FIELDS = new Set([...FIELD_KEYS, 'componentType']);
|
|
100
|
-
|
|
101
|
-
// 2. Layer 1: Core Component (Pure presentation, Server Component)
|
|
102
|
-
export const CoreMyNewComponent: React.FC<
|
|
103
|
-
Fields & {
|
|
104
|
-
contentContext?: IContentContext;
|
|
105
|
-
rendererConfig?: CmsRendererConfig;
|
|
106
|
-
embedded?: boolean;
|
|
107
|
-
positionClassName?: string;
|
|
108
|
-
}
|
|
109
|
-
> = ({
|
|
110
|
-
id,
|
|
111
|
-
index,
|
|
112
|
-
heading,
|
|
113
|
-
body,
|
|
114
|
-
links,
|
|
115
|
-
cmsLabel,
|
|
116
|
-
contentContext,
|
|
117
|
-
rendererConfig,
|
|
118
|
-
positionClassName,
|
|
119
|
-
}) => {
|
|
120
|
-
const { Element, sizingInformation } = getSizingInformation(index);
|
|
121
|
-
|
|
122
|
-
return (
|
|
123
|
-
<div className={cn('space-y-6', positionClassName)}>
|
|
124
|
-
{heading && (
|
|
125
|
-
<Element
|
|
126
|
-
{...getPreviewFieldProps(rendererConfig, id, 'heading')}
|
|
127
|
-
className={cn(sizingInformation.h1)}
|
|
128
|
-
>
|
|
129
|
-
{convertText(heading)}
|
|
130
|
-
</Element>
|
|
131
|
-
)}
|
|
132
|
-
{body && (
|
|
133
|
-
<RtfOrString
|
|
134
|
-
{...getPreviewFieldProps(rendererConfig, id, 'body')}
|
|
135
|
-
content={body}
|
|
136
|
-
className={cn('rtf-standard')}
|
|
137
|
-
rendererConfig={rendererConfig}
|
|
138
|
-
contentContext={contentContext}
|
|
139
|
-
/>
|
|
140
|
-
)}
|
|
141
|
-
{/* Example of isolated client logic usage: */}
|
|
142
|
-
{/* <MyNewComponentClient>
|
|
143
|
-
<InteractivePart />
|
|
144
|
-
</MyNewComponentClient>
|
|
145
|
-
*/}
|
|
146
|
-
{links && (
|
|
147
|
-
<SectionLinks
|
|
148
|
-
links={links}
|
|
149
|
-
rendererConfig={rendererConfig}
|
|
150
|
-
trackingLocation={cmsLabel}
|
|
151
|
-
analyticsContext={contentContext?.analyticsContext}
|
|
152
|
-
/>
|
|
153
|
-
)}
|
|
154
|
-
</div>
|
|
155
|
-
);
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
// 3. Layer 2: Wrapper Component (Section integration)
|
|
159
|
-
const MyNewComponent: React.FC<
|
|
160
|
-
Fields &
|
|
161
|
-
Pick<IComponent, 'componentType'> & {
|
|
162
|
-
contentContext: IContentContext;
|
|
163
|
-
information: IContent;
|
|
164
|
-
rendererConfig: CmsRendererConfig;
|
|
165
|
-
}
|
|
166
|
-
> = ({ information, componentType, ...rest }) => {
|
|
167
|
-
return (
|
|
168
|
-
<Section
|
|
169
|
-
componentName={componentType}
|
|
170
|
-
information={information}
|
|
171
|
-
previewHelpers={rest.rendererConfig?.previewHelpers}
|
|
172
|
-
>
|
|
173
|
-
<CoreMyNewComponent
|
|
174
|
-
{...rest}
|
|
175
|
-
positionClassName="col-span-full laptop:col-start-2 laptop:col-span-10"
|
|
176
|
-
/>
|
|
177
|
-
</Section>
|
|
178
|
-
);
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
// 4. Layer 3: Renderer Export (CMS integration)
|
|
182
|
-
const MyNew: ComponentRenderer<IComponent> = ({
|
|
183
|
-
information,
|
|
184
|
-
contentContext,
|
|
185
|
-
rendererConfig,
|
|
186
|
-
}) => {
|
|
187
|
-
const { componentType, ...rest } = information;
|
|
188
|
-
|
|
189
|
-
return (
|
|
190
|
-
<UnusedChecker componentName={componentType} allFields={rest} usedFields={USED_FIELDS}>
|
|
191
|
-
<MyNewComponent
|
|
192
|
-
rendererConfig={rendererConfig}
|
|
193
|
-
contentContext={contentContext}
|
|
194
|
-
information={information}
|
|
195
|
-
componentType={componentType}
|
|
196
|
-
{...rest}
|
|
197
|
-
/>
|
|
198
|
-
</UnusedChecker>
|
|
199
|
-
);
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
export default MyNew;
|
|
203
|
-
|
|
204
|
-
// 5. Layer 4: Registration
|
|
205
|
-
export const MyNewRegistration = defineComponent({
|
|
206
|
-
name: 'My New Component', // Must match CMS componentType
|
|
207
|
-
renderer: MyNew,
|
|
208
|
-
usedFields: USED_FIELDS,
|
|
209
|
-
mock: {
|
|
210
|
-
heading: 'Sample Heading',
|
|
211
|
-
body: 'This is sample body text for the showcase.',
|
|
212
|
-
},
|
|
213
|
-
});
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
## Implementation Checklist
|
|
217
|
-
|
|
218
|
-
1. **Create file**: `src/project/components/MyNewComponent.tsx`
|
|
219
|
-
2. **Define Fields**:
|
|
220
|
-
* Set `FIELD_KEYS` with all used fields.
|
|
221
|
-
* Create `Fields` type.
|
|
222
|
-
* Create `USED_FIELDS` set (don't forget `'componentType'`).
|
|
223
|
-
* Include standard fields: `id`, `index`, `anchor`, `cmsLabel`.
|
|
224
|
-
3. **Build Core Component**:
|
|
225
|
-
* **ENSURE SERVER COMPONENT**: Do not use `'use client'`.
|
|
226
|
-
* Export named `Core[Name]`.
|
|
227
|
-
* Accept `positionClassName`.
|
|
228
|
-
* Use `getSizingInformation(index)` for semantic headings.
|
|
229
|
-
* Check field existence (`field &&`).
|
|
230
|
-
* Use `convertText()` for strings.
|
|
231
|
-
* Use `<RtfOrString>` for rich text.
|
|
232
|
-
* Use `getPreviewFieldProps(rendererConfig, id, fieldId)` from `@se-studio/core-ui` for CMS preview (do not import from cms-server – avoids circular dependency). For responsive visuals use `getPreviewResponsiveVisualFieldProps(rendererConfig, id, 'visual', 'mobileVisual')`.
|
|
233
|
-
4. **Handle Interactivity (If Needed)**:
|
|
234
|
-
* Create separate `[Name]Client.tsx` or `[Name]Animator.tsx`.
|
|
235
|
-
* Add `'use client'` to that file only.
|
|
236
|
-
* Import and use in Core component.
|
|
237
|
-
5. **Build Wrapper Component**:
|
|
238
|
-
* Wrap Core with `<Section>`.
|
|
239
|
-
* Pass `componentName={componentType}` and `information`.
|
|
240
|
-
* Set layout (e.g., `positionClassName="col-span-full"`).
|
|
241
|
-
6. **Build Renderer**:
|
|
242
|
-
* Default export (short name).
|
|
243
|
-
* Wrap with `<UnusedChecker>`.
|
|
244
|
-
7. **Define Registration**:
|
|
245
|
-
* Export named `[Name]Registration`.
|
|
246
|
-
* Use `defineComponent`.
|
|
247
|
-
* **CRITICAL**: `name` must match CMS `componentType` exactly.
|
|
248
|
-
* Provide mock data for showcase.
|
|
249
|
-
8. **Register Component**:
|
|
250
|
-
* Add to `componentRegistrationsList` in `src/lib/registrations.ts`. The `name` is defined only in your `defineComponent(...)` call; do not duplicate it as a Record key.
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: create-page-route
|
|
3
|
-
description: Guide for creating new Next.js App Router pages and dynamic routes in the SE Core Product framework. Use when creating CMS-driven pages, adding route segments, or setting up routing.
|
|
4
|
-
license: Private
|
|
5
|
-
metadata:
|
|
6
|
-
author: se-core-product
|
|
7
|
-
version: "2.0.0"
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# Creating Pages and Routes
|
|
11
|
-
|
|
12
|
-
This guide explains how to create new pages and handle routing in the SE Core Product Next.js framework. The system uses Next.js App Router with Contentful-driven dynamic routing.
|
|
13
|
-
|
|
14
|
-
Reference apps: **example-se2026**, **example-brightline**, **example-om1**.
|
|
15
|
-
|
|
16
|
-
## Page Architecture
|
|
17
|
-
|
|
18
|
-
### 1. (cms-routes) Route Group
|
|
19
|
-
|
|
20
|
-
All CMS-driven pages live under `src/app/(cms-routes)/`. The layout enables dynamic params:
|
|
21
|
-
|
|
22
|
-
```tsx
|
|
23
|
-
// layout.tsx
|
|
24
|
-
export const dynamicParams = true;
|
|
25
|
-
|
|
26
|
-
export default function CmsRoutesLayout({ children }: { children: React.ReactNode }) {
|
|
27
|
-
return children;
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### 2. Route Structure
|
|
32
|
-
|
|
33
|
-
| Path | File | Purpose |
|
|
34
|
-
|------|------|---------|
|
|
35
|
-
| `/` | `page.ts` | Home (slug: `index`) |
|
|
36
|
-
| `/{slug}/` | `[level1]/page.ts` | Single-level page |
|
|
37
|
-
| `/{slug1}/{slug2}/...` | `[level1]/[...slugs]/page.ts` | Multi-level pages |
|
|
38
|
-
| `/articles/` | `articles/page.ts` | Articles index (uses ARTICLES_BASE from constants) |
|
|
39
|
-
| `/articles/{type}/` | `articles/[articleType]/page.ts` | Article type index |
|
|
40
|
-
| `/articles/{type}/{tag}/` | `articles/[articleType]/[tag]/page.ts` | Article type + tag index |
|
|
41
|
-
| `/articles/{type}/{tag}/{article}/` | `articles/[articleType]/[tag]/[...slugs]/page.ts` | Individual article |
|
|
42
|
-
| `/tags/`, `/tags/{tag}/` | `tags/` | Tag index (if enabled) |
|
|
43
|
-
| `/people/`, `/people/{person}/` | `people/` | People (if enabled) |
|
|
44
|
-
|
|
45
|
-
Base paths (e.g. `/articles` vs `/learning-hub`) come from `@/lib/constants`.
|
|
46
|
-
|
|
47
|
-
### 3. appShared Pattern
|
|
48
|
-
|
|
49
|
-
Route files are thin wrappers. Data-fetching and rendering logic lives in `src/appShared/`:
|
|
50
|
-
|
|
51
|
-
- `pageShared.tsx` – `generatePage`, `generatePageMetadata`
|
|
52
|
-
- `articleShared.tsx` – articles
|
|
53
|
-
- `articleTypeShared.tsx`, `articleTypesIndexShared.tsx`, `articleTypeTagShared.tsx` – article indices
|
|
54
|
-
- `customTypeShared.tsx` – custom types
|
|
55
|
-
- `personShared.tsx`, `tagShared.tsx`, `tagsIndexShared.tsx`, `teamIndexShared.tsx`
|
|
56
|
-
|
|
57
|
-
## Route Page Template
|
|
58
|
-
|
|
59
|
-
Each route page extracts params and delegates to the appropriate appShared function:
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
import type { ResolvingMetadata } from 'next';
|
|
63
|
-
import { generatePage, generatePageMetadata } from '@/appShared/pageShared';
|
|
64
|
-
|
|
65
|
-
export function generateStaticParams() {
|
|
66
|
-
return [];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async function extractDetails(props: PageProps<'/[level1]'>) {
|
|
70
|
-
const params = await props.params;
|
|
71
|
-
const { level1 } = params;
|
|
72
|
-
return { slug: level1, path: `/${level1}/` };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export async function generateMetadata(props: PageProps<'/[level1]'>, parent: ResolvingMetadata) {
|
|
76
|
-
const { slug, path } = await extractDetails(props);
|
|
77
|
-
return generatePageMetadata(slug, path, true, parent);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export default async function (props: PageProps<'/[level1]'>) {
|
|
81
|
-
const { slug, path } = await extractDetails(props);
|
|
82
|
-
return generatePage(slug, path, true);
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
For the home page (`/`), use slug `'index'` and path `'/'` directly.
|
|
87
|
-
|
|
88
|
-
## CmsContent Usage
|
|
89
|
-
|
|
90
|
-
The appShared modules render content with:
|
|
91
|
-
|
|
92
|
-
```tsx
|
|
93
|
-
<CmsContent
|
|
94
|
-
contents={page.contents}
|
|
95
|
-
rendererConfig={projectRendererConfig}
|
|
96
|
-
pageContext={{ page }}
|
|
97
|
-
/>
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
- `contents` – array of page/article contents (not the full page object)
|
|
101
|
-
- `rendererConfig` – from `@/lib/cms-server`
|
|
102
|
-
- `pageContext` – provides page/article/customType to child components
|
|
103
|
-
|
|
104
|
-
For route files that only need `converterContext` or `buildInformation` (e.g. `generateStaticParams`, sitemap, path computation), import from `@/lib/converter-context` when that module exists, so you don't pull in cms-server.
|
|
105
|
-
|
|
106
|
-
## Creating a Specialized Route
|
|
107
|
-
|
|
108
|
-
For a route that behaves differently from standard CMS pages (e.g. search, dashboard):
|
|
109
|
-
|
|
110
|
-
1. Create the route under `(cms-routes)` or a separate route group.
|
|
111
|
-
2. Either create a new appShared module (if the pattern is reusable) or implement inline.
|
|
112
|
-
3. Use `getPageWithErrors`, `getArticleWithErrors`, etc. from `@/lib/cms-server`.
|
|
113
|
-
4. Use `projectRendererConfig` from `@/lib/cms-server`.
|
|
114
|
-
5. Render with `CmsContent` using `contents`, `rendererConfig`, and `pageContext`.
|
|
115
|
-
|
|
116
|
-
## Custom Layouts
|
|
117
|
-
|
|
118
|
-
If a route needs a different layout (e.g. no header/footer), create `layout.tsx` in that route directory.
|
|
119
|
-
|
|
120
|
-
## Troubleshooting
|
|
121
|
-
|
|
122
|
-
- **404 on new route**: Re-run the dev server or build after adding a dynamic route segment.
|
|
123
|
-
- **Static params missing**: Dynamic routes use `generateStaticParams() { return []; }` for on-demand rendering.
|
|
124
|
-
- **Preview mode**: Handled by `draftOnly` in `@/lib/server-config`; appShared modules use it automatically.
|
|
125
|
-
|
|
126
|
-
## See Also
|
|
127
|
-
|
|
128
|
-
- **cms-routes-and-appshared** – Full route structure and appShared pattern
|
|
129
|
-
- **lib-cms-structure** – lib directory layout and cms-server usage
|