@se-studio/project-build 1.0.51 → 1.0.53
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/dist/management/check-target.d.ts +2 -0
- package/dist/management/check-target.d.ts.map +1 -0
- package/dist/management/check-target.js +40 -0
- package/dist/management/check-target.js.map +1 -0
- package/dist/management/count-brightline.d.ts +2 -0
- package/dist/management/count-brightline.d.ts.map +1 -0
- package/dist/management/count-brightline.js +37 -0
- package/dist/management/count-brightline.js.map +1 -0
- package/dist/management/cross-check-migration.d.ts +2 -0
- package/dist/management/cross-check-migration.d.ts.map +1 -0
- package/dist/management/cross-check-migration.js +212 -0
- package/dist/management/cross-check-migration.js.map +1 -0
- package/dist/management/discovery-brightlife.d.ts +2 -0
- package/dist/management/discovery-brightlife.d.ts.map +1 -0
- package/dist/management/discovery-brightlife.js +78 -0
- package/dist/management/discovery-brightlife.js.map +1 -0
- package/dist/management/discovery-brightline.d.ts +2 -0
- package/dist/management/discovery-brightline.d.ts.map +1 -0
- package/dist/management/discovery-brightline.js +68 -0
- package/dist/management/discovery-brightline.js.map +1 -0
- package/dist/management/inspect-target.d.ts +2 -0
- package/dist/management/inspect-target.d.ts.map +1 -0
- package/dist/management/inspect-target.js +48 -0
- package/dist/management/inspect-target.js.map +1 -0
- package/dist/management/migrate-brightlife.d.ts +2 -0
- package/dist/management/migrate-brightlife.d.ts.map +1 -0
- package/dist/management/migrate-brightlife.js +475 -0
- package/dist/management/migrate-brightlife.js.map +1 -0
- package/dist/management/migrate-brightline.d.ts +2 -0
- package/dist/management/migrate-brightline.d.ts.map +1 -0
- package/dist/management/migrate-brightline.js +1085 -0
- package/dist/management/migrate-brightline.js.map +1 -0
- package/dist/management/sync-skills.d.ts +2 -0
- package/dist/management/sync-skills.d.ts.map +1 -0
- package/dist/management/sync-skills.js +46 -0
- package/dist/management/sync-skills.js.map +1 -0
- package/dist/seskills.d.ts +9 -0
- package/dist/seskills.d.ts.map +1 -0
- package/dist/seskills.js +25 -0
- package/dist/seskills.js.map +1 -0
- package/package.json +7 -6
- package/skills/se-marketing-sites/create-collection/SKILL.md +283 -0
- package/skills/se-marketing-sites/create-component/SKILL.md +245 -0
- package/skills/se-marketing-sites/create-page/SKILL.md +134 -0
- package/skills/se-marketing-sites/handling-media/SKILL.md +118 -0
- package/skills/se-marketing-sites/register-cms-features/SKILL.md +95 -0
- package/skills/se-marketing-sites/styling-system/SKILL.md +118 -0
|
@@ -0,0 +1,245 @@
|
|
|
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
|
+
RtfOrString,
|
|
76
|
+
UnusedChecker,
|
|
77
|
+
} from '@se-studio/core-ui';
|
|
78
|
+
import { SectionLinks } from '@/elements/SectionLinks';
|
|
79
|
+
import { Section } from '@/framework/Section';
|
|
80
|
+
import type { IComponent, IContent } from '@/lib/cms';
|
|
81
|
+
import { getPreviewFieldIdProps } 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
|
+
{...getPreviewFieldIdProps(id, 'heading')}
|
|
127
|
+
className={cn(sizingInformation.h1)}
|
|
128
|
+
>
|
|
129
|
+
{convertText(heading)}
|
|
130
|
+
</Element>
|
|
131
|
+
)}
|
|
132
|
+
{body && (
|
|
133
|
+
<RtfOrString
|
|
134
|
+
{...getPreviewFieldIdProps(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
|
+
trackingLocation={cmsLabel}
|
|
150
|
+
analyticsContext={contentContext?.analyticsContext}
|
|
151
|
+
/>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// 3. Layer 2: Wrapper Component (Section integration)
|
|
158
|
+
const MyNewComponent: React.FC<
|
|
159
|
+
Fields &
|
|
160
|
+
Pick<IComponent, 'componentType'> & {
|
|
161
|
+
contentContext: IContentContext;
|
|
162
|
+
information: IContent;
|
|
163
|
+
rendererConfig: CmsRendererConfig;
|
|
164
|
+
}
|
|
165
|
+
> = ({ information, componentType, ...rest }) => {
|
|
166
|
+
return (
|
|
167
|
+
<Section componentName={componentType} information={information}>
|
|
168
|
+
<CoreMyNewComponent
|
|
169
|
+
{...rest}
|
|
170
|
+
positionClassName="col-span-full laptop:col-start-2 laptop:col-span-10"
|
|
171
|
+
/>
|
|
172
|
+
</Section>
|
|
173
|
+
);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// 4. Layer 3: Renderer Export (CMS integration)
|
|
177
|
+
const MyNew: ComponentRenderer<IComponent> = ({
|
|
178
|
+
information,
|
|
179
|
+
contentContext,
|
|
180
|
+
rendererConfig,
|
|
181
|
+
}) => {
|
|
182
|
+
const { componentType, ...rest } = information;
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<UnusedChecker componentName={componentType} allFields={rest} usedFields={USED_FIELDS}>
|
|
186
|
+
<MyNewComponent
|
|
187
|
+
rendererConfig={rendererConfig}
|
|
188
|
+
contentContext={contentContext}
|
|
189
|
+
information={information}
|
|
190
|
+
componentType={componentType}
|
|
191
|
+
{...rest}
|
|
192
|
+
/>
|
|
193
|
+
</UnusedChecker>
|
|
194
|
+
);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
export default MyNew;
|
|
198
|
+
|
|
199
|
+
// 5. Layer 4: Registration
|
|
200
|
+
export const MyNewRegistration = defineComponent({
|
|
201
|
+
name: 'My New Component', // Must match CMS componentType
|
|
202
|
+
renderer: MyNew,
|
|
203
|
+
usedFields: USED_FIELDS,
|
|
204
|
+
mock: {
|
|
205
|
+
heading: 'Sample Heading',
|
|
206
|
+
body: 'This is sample body text for the showcase.',
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Implementation Checklist
|
|
212
|
+
|
|
213
|
+
1. **Create file**: `src/project/components/MyNewComponent.tsx`
|
|
214
|
+
2. **Define Fields**:
|
|
215
|
+
* Set `FIELD_KEYS` with all used fields.
|
|
216
|
+
* Create `Fields` type.
|
|
217
|
+
* Create `USED_FIELDS` set (don't forget `'componentType'`).
|
|
218
|
+
* Include standard fields: `id`, `index`, `anchor`, `cmsLabel`.
|
|
219
|
+
3. **Build Core Component**:
|
|
220
|
+
* **ENSURE SERVER COMPONENT**: Do not use `'use client'`.
|
|
221
|
+
* Export named `Core[Name]`.
|
|
222
|
+
* Accept `positionClassName`.
|
|
223
|
+
* Use `getSizingInformation(index)` for semantic headings.
|
|
224
|
+
* Check field existence (`field &&`).
|
|
225
|
+
* Use `convertText()` for strings.
|
|
226
|
+
* Use `<RtfOrString>` for rich text.
|
|
227
|
+
* Apply `getPreviewFieldIdProps()` for CMS preview.
|
|
228
|
+
4. **Handle Interactivity (If Needed)**:
|
|
229
|
+
* Create separate `[Name]Client.tsx` or `[Name]Animator.tsx`.
|
|
230
|
+
* Add `'use client'` to that file only.
|
|
231
|
+
* Import and use in Core component.
|
|
232
|
+
5. **Build Wrapper Component**:
|
|
233
|
+
* Wrap Core with `<Section>`.
|
|
234
|
+
* Pass `componentName={componentType}` and `information`.
|
|
235
|
+
* Set layout (e.g., `positionClassName="col-span-full"`).
|
|
236
|
+
6. **Build Renderer**:
|
|
237
|
+
* Default export (short name).
|
|
238
|
+
* Wrap with `<UnusedChecker>`.
|
|
239
|
+
7. **Define Registration**:
|
|
240
|
+
* Export named `[Name]Registration`.
|
|
241
|
+
* Use `defineComponent`.
|
|
242
|
+
* **CRITICAL**: `name` must match CMS `componentType` exactly.
|
|
243
|
+
* Provide mock data for showcase.
|
|
244
|
+
8. **Register Component**:
|
|
245
|
+
* Add to `componentRegistrations` array in `src/lib/cms.ts`.
|
|
@@ -0,0 +1,134 @@
|
|
|
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.
|
|
4
|
+
license: Private
|
|
5
|
+
metadata:
|
|
6
|
+
author: se-core-product
|
|
7
|
+
version: "1.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
|
+
## Page Architecture
|
|
15
|
+
|
|
16
|
+
### 1. Dynamic Catch-All Route (`[...slugs]`)
|
|
17
|
+
|
|
18
|
+
Most CMS pages are handled by a single catch-all route at `app/(pages)/[...slugs]/page.tsx`. This component:
|
|
19
|
+
1. Receives the URL slug.
|
|
20
|
+
2. Fetches the page data from Contentful.
|
|
21
|
+
3. Renders the `<CmsContent>` component.
|
|
22
|
+
|
|
23
|
+
### 2. Static Params Generation
|
|
24
|
+
|
|
25
|
+
To enable Static Site Generation (SSG), the page exports `generateStaticParams`:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { getAllPageLinks } from '@/lib/cms';
|
|
29
|
+
|
|
30
|
+
export async function generateStaticParams() {
|
|
31
|
+
const links = await getAllPageLinks();
|
|
32
|
+
return links
|
|
33
|
+
.filter(link => link.slug !== 'index')
|
|
34
|
+
.map(link => ({
|
|
35
|
+
slugs: link.href?.split('/').filter(Boolean)
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Creating a New Specialized Route
|
|
41
|
+
|
|
42
|
+
Sometimes you need a route that behaves differently from standard CMS pages (e.g., a search page, a dashboard, or a specialized landing page).
|
|
43
|
+
|
|
44
|
+
### Step 1: Create the Route Directory
|
|
45
|
+
|
|
46
|
+
Create a new directory in `src/app` matching your desired URL structure.
|
|
47
|
+
|
|
48
|
+
* Example: `src/app/search/page.tsx` -> `/search`
|
|
49
|
+
* Example: `src/app/team/[slug]/page.tsx` -> `/team/john-doe`
|
|
50
|
+
|
|
51
|
+
### Step 2: Implement the Page Component
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import type { Metadata } from 'next';
|
|
55
|
+
import { notFound } from 'next/navigation';
|
|
56
|
+
import { CmsContent } from '@se-studio/core-ui';
|
|
57
|
+
import { contentfulPageRest, getContentfulConfig } from '@/lib/cms';
|
|
58
|
+
import { projectRendererConfig } from '@/lib/cms';
|
|
59
|
+
|
|
60
|
+
interface PageProps {
|
|
61
|
+
params: { slug: string };
|
|
62
|
+
searchParams: { [key: string]: string | string[] | undefined };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default async function Page({ params, searchParams }: PageProps) {
|
|
66
|
+
const { slug } = params;
|
|
67
|
+
const isPreview = searchParams.preview === 'true';
|
|
68
|
+
|
|
69
|
+
// 1. Fetch content
|
|
70
|
+
const response = await contentfulPageRest(
|
|
71
|
+
getContentfulConfig(isPreview),
|
|
72
|
+
slug,
|
|
73
|
+
{ preview: isPreview }
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (!response.data) {
|
|
77
|
+
notFound();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 2. Render content with CmsContent
|
|
81
|
+
// This automatically handles the recursive rendering of components
|
|
82
|
+
return (
|
|
83
|
+
<main>
|
|
84
|
+
<CmsContent
|
|
85
|
+
content={response.data}
|
|
86
|
+
config={projectRendererConfig}
|
|
87
|
+
contentContext={{
|
|
88
|
+
// Pass any required context
|
|
89
|
+
analyticsContext: { ... }
|
|
90
|
+
}}
|
|
91
|
+
/>
|
|
92
|
+
</main>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Step 3: Add Metadata
|
|
98
|
+
|
|
99
|
+
Always export metadata for SEO:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
|
103
|
+
const response = await contentfulPageRest(/* ... */);
|
|
104
|
+
const page = response.data;
|
|
105
|
+
|
|
106
|
+
if (!page) return {};
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
title: page.seoTitle || page.title,
|
|
110
|
+
description: page.seoDescription,
|
|
111
|
+
// ... maps to Next.js metadata format
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Custom Layouts
|
|
117
|
+
|
|
118
|
+
If your new route needs a different layout (e.g., no header/footer), create a `layout.tsx` in your route directory.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
export default function CustomLayout({ children }: { children: React.ReactNode }) {
|
|
122
|
+
return (
|
|
123
|
+
<div className="custom-layout">
|
|
124
|
+
{children}
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Troubleshooting
|
|
131
|
+
|
|
132
|
+
* **404 on new route**: Ensure you've re-run the dev server or build if you added a new dynamic route segment.
|
|
133
|
+
* **Static params missing**: Check if your new content is published in Contentful and reachable via `getAllPageLinks` (or your custom fetcher).
|
|
134
|
+
* **Preview mode not working**: Ensure you are passing `preview: true` to the fetcher functions when `searchParams.preview` is set.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: handling-media
|
|
3
|
+
description: Guide for using images, videos, and animations in marketing sites using the ResponsiveVisual and VisualComponent systems.
|
|
4
|
+
license: Private
|
|
5
|
+
metadata:
|
|
6
|
+
author: se-core-product
|
|
7
|
+
version: "1.0.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Handling Media
|
|
11
|
+
|
|
12
|
+
This project uses a unified media system to handle images, videos, and animations efficiently.
|
|
13
|
+
|
|
14
|
+
## Core Components
|
|
15
|
+
|
|
16
|
+
### 1. `ResponsiveVisual` (or `VisualComponent`)
|
|
17
|
+
|
|
18
|
+
This is the main component for rendering media from Contentful. It handles:
|
|
19
|
+
* Responsive image sources (desktop/mobile).
|
|
20
|
+
* Format optimization (WebP/AVIF).
|
|
21
|
+
* Video auto-play/looping.
|
|
22
|
+
* Lottie animations.
|
|
23
|
+
* Lazy loading & Priority hints.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import VisualComponent from '@/framework/VisualComponent';
|
|
27
|
+
// or
|
|
28
|
+
import { Visual } from '@se-studio/core-ui';
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage Pattern
|
|
32
|
+
|
|
33
|
+
### Basic Usage
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
<VisualComponent
|
|
37
|
+
visual={field.visual}
|
|
38
|
+
className="w-full h-auto"
|
|
39
|
+
/>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Responsive Sizing (`visualSizes`)
|
|
43
|
+
|
|
44
|
+
You **MUST** provide `visualSizes` to ensure the browser loads the correct image size. This uses the `sizes` attribute.
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { calculateVisualSizes } from '@se-studio/core-ui';
|
|
48
|
+
|
|
49
|
+
// Example: Full width on mobile, 50% width on laptop
|
|
50
|
+
const sizes = calculateVisualSizes(1, { laptop: 0.5 });
|
|
51
|
+
|
|
52
|
+
<VisualComponent
|
|
53
|
+
visual={visual}
|
|
54
|
+
visualSizes={sizes}
|
|
55
|
+
/>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
* `1`: 100vw (Mobile default)
|
|
59
|
+
* `laptop: 0.5`: 50vw (Laptop breakpoint)
|
|
60
|
+
|
|
61
|
+
### LCP Optimization (`calculateImagePriority`)
|
|
62
|
+
|
|
63
|
+
For the Hero component (or whatever is at the top of the page), you must prioritize the image load to improve LCP (Largest Contentful Paint).
|
|
64
|
+
|
|
65
|
+
Use `calculateImagePriority(index)` where `index` is the component's position on the page.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { calculateImagePriority } from '@se-studio/core-ui';
|
|
69
|
+
|
|
70
|
+
<VisualComponent
|
|
71
|
+
visual={visual}
|
|
72
|
+
{...calculateImagePriority(index)}
|
|
73
|
+
/>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If `index` is `0`, this sets `priority={true}` (or `fetchPriority="high"`). If `index > 0`, it defaults to lazy loading.
|
|
77
|
+
|
|
78
|
+
### Analytics Tracking
|
|
79
|
+
|
|
80
|
+
Pass `componentLabel` (usually `cmsLabel`) and `analyticsContext` to enable click tracking on media elements (if they are linked).
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
<VisualComponent
|
|
84
|
+
visual={visual}
|
|
85
|
+
componentLabel={cmsLabel}
|
|
86
|
+
analyticsContext={contentContext.analyticsContext}
|
|
87
|
+
/>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Media Types
|
|
91
|
+
|
|
92
|
+
The `IResponsiveVisual` type (from `@se-studio/core-data-types`) supports:
|
|
93
|
+
|
|
94
|
+
1. **Image**: Standard Contentful image asset.
|
|
95
|
+
2. **Video**: Hosted video file (mp4/webm). The component handles `<video>` tag rendering with autoplay/loop/muted attributes suitable for background videos.
|
|
96
|
+
3. **Animation**: Lottie JSON file. Renders using a Lottie player.
|
|
97
|
+
|
|
98
|
+
The component automatically detects the type and renders the appropriate element.
|
|
99
|
+
|
|
100
|
+
## Mobile Specific Visuals
|
|
101
|
+
|
|
102
|
+
Contentful models often support a separate `mobileVisual` field. The `VisualComponent` handles switching between them using `<picture>` tags or CSS media queries internally.
|
|
103
|
+
|
|
104
|
+
You typically pass the combined object or handle it via props if the component exposes both:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// If your component merges them into one responsive object before rendering:
|
|
108
|
+
<VisualComponent visual={combinedVisual} />
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Or if using raw fields:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
<VisualComponent
|
|
115
|
+
visual={visual}
|
|
116
|
+
mobileVisual={mobileVisual}
|
|
117
|
+
/>
|
|
118
|
+
```
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: register-cms-features
|
|
3
|
+
description: Guide for registering new components, collections, and external integrations in the CMS configuration (src/lib/cms.ts).
|
|
4
|
+
license: Private
|
|
5
|
+
metadata:
|
|
6
|
+
author: se-core-product
|
|
7
|
+
version: "1.0.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Registering CMS Features
|
|
11
|
+
|
|
12
|
+
The file `src/lib/cms.ts` is the central registry for the application. It connects Contentful content types to their React implementations.
|
|
13
|
+
|
|
14
|
+
## How Registration Works
|
|
15
|
+
|
|
16
|
+
The system uses "Registration Objects" created via `defineComponent` or `defineCollection`. These objects contain:
|
|
17
|
+
1. **Name**: Matches the Contentful content type ID (or a mapped name).
|
|
18
|
+
2. **Renderer**: The React component to render.
|
|
19
|
+
3. **Used Fields**: Which fields from the content model are used.
|
|
20
|
+
4. **Mock Data**: Sample data for the styleguide/showcase.
|
|
21
|
+
|
|
22
|
+
## Registering a New Component
|
|
23
|
+
|
|
24
|
+
1. **Import the Registration**:
|
|
25
|
+
At the top of `src/lib/cms.ts`, import your component's registration export.
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { MyNewComponentRegistration } from '@/project/components/MyNewComponent';
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
2. **Add to `componentRegistrations`**:
|
|
32
|
+
Find the `componentRegistrations` array and add your component.
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
const componentRegistrations = [
|
|
36
|
+
HeroRegistration,
|
|
37
|
+
// ...
|
|
38
|
+
MyNewComponentRegistration, // Add this
|
|
39
|
+
];
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
*Order does not matter for functionality, but logical grouping is helpful.*
|
|
43
|
+
|
|
44
|
+
## Registering a New Collection
|
|
45
|
+
|
|
46
|
+
1. **Import the Registration**:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { MyCollectionRegistration } from '@/project/collections/MyCollection';
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
2. **Add to `collectionRegistrations`**:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
const collectionRegistrations = [
|
|
56
|
+
CardGridRegistration,
|
|
57
|
+
// ...
|
|
58
|
+
MyCollectionRegistration, // Add this
|
|
59
|
+
];
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Adding External Components
|
|
63
|
+
|
|
64
|
+
External components (like 3rd party forms or iframes) use a separate registry.
|
|
65
|
+
|
|
66
|
+
1. **Import**:
|
|
67
|
+
```typescript
|
|
68
|
+
import { MyExternalWidgetRegistration } from '@/project/externalComponents/MyExternalWidget';
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
2. **Add to `externalComponentRegistrations`**:
|
|
72
|
+
```typescript
|
|
73
|
+
const externalComponentRegistrations = [
|
|
74
|
+
ExternalIFrameRegistration,
|
|
75
|
+
MyExternalWidgetRegistration,
|
|
76
|
+
];
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Extending Type Definitions
|
|
80
|
+
|
|
81
|
+
If you introduce new Color names, Button variants, or other enumerations, update the type definitions in `src/lib/cms.ts`:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
export type CmsColourName = NonNullable<TypeComponentFields['backgroundColour']>['values'];
|
|
85
|
+
// Add or modify if types are not automatically generated from Contentful
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Note**: Most types are generated automatically into `@/generated/types` by the `type-gen` script. You typically don't need to manually edit types unless you are defining project-specific overrides.
|
|
89
|
+
|
|
90
|
+
## The Showcase
|
|
91
|
+
|
|
92
|
+
Registering a component automatically adds it to the CMS Showcase (usually available at `/cms-showcase` in development). This allows you to view the component in isolation using its mock data.
|
|
93
|
+
|
|
94
|
+
* Ensure your `mock` object in the registration is complete and realistic.
|
|
95
|
+
* The showcase helps verify that `usedFields` and `UnusedChecker` are working correctly.
|