@se-studio/skills 1.0.1

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/package.json +25 -0
  3. package/skills/contentful-cms-alt-text-audit/SKILL.md +60 -0
  4. package/skills/contentful-cms-cms-guidelines/README.md +166 -0
  5. package/skills/contentful-cms-cms-guidelines/colour-hint-prompt.md +77 -0
  6. package/skills/contentful-cms-cms-guidelines/evaluation-prompt.md +84 -0
  7. package/skills/contentful-cms-cms-guidelines/generate-component-guidelines.md +126 -0
  8. package/skills/contentful-cms-cms-guidelines/generation-prompt.md +231 -0
  9. package/skills/contentful-cms-cms-guidelines/html-component-authoring.md +401 -0
  10. package/skills/contentful-cms-cms-guidelines/validation-prompt.md +170 -0
  11. package/skills/contentful-cms-cms-guidelines/variant-loop.md +189 -0
  12. package/skills/contentful-cms-cms-guidelines/variant-proposal-prompt.md +131 -0
  13. package/skills/contentful-cms-core/SKILL.md +793 -0
  14. package/skills/contentful-cms-generate-all-guidelines/SKILL.md +313 -0
  15. package/skills/contentful-cms-generate-cms-guidelines/SKILL.md +313 -0
  16. package/skills/contentful-cms-image-guide/SKILL.md +240 -0
  17. package/skills/contentful-cms-navigation/SKILL.md +23 -0
  18. package/skills/contentful-cms-rich-text/SKILL.md +96 -0
  19. package/skills/contentful-cms-schema-org/SKILL.md +74 -0
  20. package/skills/contentful-cms-screenshots/SKILL.md +46 -0
  21. package/skills/contentful-cms-seo-descriptions/SKILL.md +54 -0
  22. package/skills/contentful-cms-templates/SKILL.md +21 -0
  23. package/skills/contentful-cms-update-cms-guidelines/SKILL.md +348 -0
  24. package/skills/performance-audit/SKILL.md +344 -0
  25. package/skills/se-marketing-sites-cms-routes-and-appshared/SKILL.md +99 -0
  26. package/skills/se-marketing-sites-create-collection/SKILL.md +295 -0
  27. package/skills/se-marketing-sites-create-component/SKILL.md +250 -0
  28. package/skills/se-marketing-sites-create-page/SKILL.md +183 -0
  29. package/skills/se-marketing-sites-curate-showcase-mocks/SKILL.md +344 -0
  30. package/skills/se-marketing-sites-handling-media/SKILL.md +195 -0
  31. package/skills/se-marketing-sites-lib-cms-structure/SKILL.md +83 -0
  32. package/skills/se-marketing-sites-register-cms-features/SKILL.md +95 -0
  33. package/skills/se-marketing-sites-styling-system/SKILL.md +122 -0
  34. package/skills/site-workflows-brand-context-builder/SKILL.md +98 -0
@@ -0,0 +1,99 @@
1
+ ---
2
+ name: cms-routes-and-appshared
3
+ description: Guide for the (cms-routes) route group and appShared pattern. Use when setting up or migrating CMS-driven routing, creating new route segments, or refactoring page structure.
4
+ license: Private
5
+ metadata:
6
+ author: se-core-product
7
+ version: "1.0.0"
8
+ ---
9
+
10
+ # CMS Routes and appShared Pattern
11
+
12
+ Reference apps: **example-se2026**, **example-brightline**, **example-om1**. (example-om1 uses `resources/`, `team/`, `topics/` instead of `articles/`, `people/`, `tags/`.)
13
+
14
+ ## Route Group: (cms-routes)
15
+
16
+ All CMS-driven pages live under `src/app/(cms-routes)/`. The layout enables dynamic params:
17
+
18
+ ```tsx
19
+ // layout.tsx
20
+ export const dynamicParams = true;
21
+
22
+ export default function CmsRoutesLayout({ children }: { children: React.ReactNode }) {
23
+ return children;
24
+ }
25
+ ```
26
+
27
+ ## Route Structure
28
+
29
+ | Path | File | Purpose |
30
+ |------|------|---------|
31
+ | `/` | `page.ts` | Home (slug: `index`) |
32
+ | `/{slug}/` | `[level1]/page.ts` | Single-level page |
33
+ | `/{slug1}/{slug2}/...` | `[level1]/[...slugs]/page.ts` | Multi-level pages |
34
+ | `/articles/` | `articles/page.ts` | Articles index (uses ARTICLES_BASE) |
35
+ | `/articles/{type}/` | `articles/[articleType]/page.ts` | Article type index |
36
+ | `/articles/{type}/{tag}/` | `articles/[articleType]/[tag]/page.ts` | Article type + tag index |
37
+ | `/articles/{type}/{tag}/{article}/` | `articles/[articleType]/[tag]/[...slugs]/page.ts` | Individual article |
38
+ | `/tags/`, `/tags/{tag}/` | `tags/` | Tag index (if enabled) |
39
+ | `/people/`, `/people/{person}/` | `people/` | People (if enabled) |
40
+
41
+ Base paths come from `@/lib/constants` (e.g. ARTICLES_BASE = `/learning-hub` or `/articles`).
42
+
43
+ ## appShared Modules
44
+
45
+ Route files are thin wrappers. Logic lives in `src/appShared/`:
46
+
47
+ | Module | Exports | Used by |
48
+ |--------|---------|---------|
49
+ | `pageShared.tsx` | `generatePage`, `generatePageMetadata` | `/`, `[level1]`, `[level1]/[...slugs]` |
50
+ | `articleShared.tsx` | `generateArticlePage`, `generateArticleMetadata` | Article detail |
51
+ | `articleTypeShared.tsx` | `generateArticleTypePage`, `generateArticleTypeMetadata` | Article type index |
52
+ | `articleTypesIndexShared.tsx` | `generateArticleTypesIndexPage`, `generateArticleTypesIndexMetadata` | Articles index |
53
+ | `articleTypeTagShared.tsx` | `generateArticleTypeTagPage`, `generateArticleTypeTagMetadata` | Article type + tag index |
54
+ | `customTypeShared.tsx` | `generateCustomTypePage`, `generateCustomTypeMetadata` | Custom types |
55
+ | `personShared.tsx` | `generatePersonPage`, `generatePersonMetadata` | Person page |
56
+ | `tagShared.tsx` | `generateTagPage`, `generateTagMetadata` | Tag index |
57
+ | `tagsIndexShared.tsx` | `generateTagsIndexPage`, `generateTagsIndexMetadata` | Tags index |
58
+ | `teamIndexShared.tsx` | `generateTeamIndexPage`, `generateTeamIndexMetadata` | People index |
59
+
60
+ ## Route Page Pattern
61
+
62
+ Each route page:
63
+
64
+ 1. Exports `generateStaticParams() { return []; }` for dynamic routes
65
+ 2. Defines `extractDetails(props)` to derive slug/path from params
66
+ 3. Exports `generateMetadata(props, parent)` calling the shared metadata function
67
+ 4. Exports default async function calling the shared page function
68
+
69
+ ```typescript
70
+ import type { ResolvingMetadata } from 'next';
71
+ import { generatePage, generatePageMetadata } from '@/appShared/pageShared';
72
+
73
+ export function generateStaticParams() {
74
+ return [];
75
+ }
76
+
77
+ async function extractDetails(props: PageProps<'/[level1]'>) {
78
+ const params = await props.params;
79
+ const { level1 } = params;
80
+ return { slug: level1, path: `/${level1}/` };
81
+ }
82
+
83
+ export async function generateMetadata(props: PageProps<'/[level1]'>, parent: ResolvingMetadata) {
84
+ const { slug, path } = await extractDetails(props);
85
+ return generatePageMetadata(slug, path, true, parent);
86
+ }
87
+
88
+ export default async function (props: PageProps<'/[level1]'>) {
89
+ const { slug, path } = await extractDetails(props);
90
+ return generatePage(slug, path, true);
91
+ }
92
+ ```
93
+
94
+ For article routes, use `converterContext.urlCalculators.article`, `articleType`, `articleTypeTag` to compute the path. Import `converterContext` from `@/lib/converter-context` when that module exists (so route files don't pull in cms-server); otherwise from `@/lib/cms-server`.
95
+
96
+ ## See Also
97
+
98
+ - **create-page** – Creating new pages and routes
99
+ - **lib-cms-structure** – lib directory layout
@@ -0,0 +1,295 @@
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 **&lt;p&gt;** 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.
@@ -0,0 +1,250 @@
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.