@se-studio/core-ui 1.0.45 → 1.0.47
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/ANALYTICS.md +136 -0
- package/CHANGELOG.md +466 -0
- package/CMS_INFRASTRUCTURE.md +335 -0
- package/CONSENT.md +121 -0
- package/README.md +13 -8
- package/dist/cmsRegistration.d.ts +152 -0
- package/dist/cmsRegistration.d.ts.map +1 -0
- package/dist/cmsRegistration.js +145 -0
- package/dist/cmsRegistration.js.map +1 -0
- package/dist/components/CmsCollection.d.ts +2 -1
- package/dist/components/CmsCollection.d.ts.map +1 -1
- package/dist/components/CmsCollection.js +1 -1
- package/dist/components/CmsCollection.js.map +1 -1
- package/dist/components/CmsComponent.d.ts +2 -1
- package/dist/components/CmsComponent.d.ts.map +1 -1
- package/dist/components/CmsComponent.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/showcase/components/AllViewPanel.d.ts +7 -0
- package/dist/showcase/components/AllViewPanel.d.ts.map +1 -0
- package/dist/showcase/components/AllViewPanel.js +35 -0
- package/dist/showcase/components/AllViewPanel.js.map +1 -0
- package/dist/showcase/components/Controls.d.ts +15 -0
- package/dist/showcase/components/Controls.d.ts.map +1 -0
- package/dist/showcase/components/Controls.js +74 -0
- package/dist/showcase/components/Controls.js.map +1 -0
- package/dist/showcase/components/ControlsWrapper.d.ts +13 -0
- package/dist/showcase/components/ControlsWrapper.d.ts.map +1 -0
- package/dist/showcase/components/ControlsWrapper.js +9 -0
- package/dist/showcase/components/ControlsWrapper.js.map +1 -0
- package/dist/showcase/components/PreviewPanel.d.ts +10 -0
- package/dist/showcase/components/PreviewPanel.d.ts.map +1 -0
- package/dist/showcase/components/PreviewPanel.js +42 -0
- package/dist/showcase/components/PreviewPanel.js.map +1 -0
- package/dist/showcase/components/ScaledIframe.d.ts +10 -0
- package/dist/showcase/components/ScaledIframe.d.ts.map +1 -0
- package/dist/showcase/components/ScaledIframe.js +16 -0
- package/dist/showcase/components/ScaledIframe.js.map +1 -0
- package/dist/showcase/components/ShowcaseAllRenderPage.d.ts +19 -0
- package/dist/showcase/components/ShowcaseAllRenderPage.d.ts.map +1 -0
- package/dist/showcase/components/ShowcaseAllRenderPage.js +46 -0
- package/dist/showcase/components/ShowcaseAllRenderPage.js.map +1 -0
- package/dist/showcase/components/ShowcasePage.d.ts +15 -0
- package/dist/showcase/components/ShowcasePage.d.ts.map +1 -0
- package/dist/showcase/components/ShowcasePage.js +16 -0
- package/dist/showcase/components/ShowcasePage.js.map +1 -0
- package/dist/showcase/components/ShowcaseRenderPage.d.ts +19 -0
- package/dist/showcase/components/ShowcaseRenderPage.d.ts.map +1 -0
- package/dist/showcase/components/ShowcaseRenderPage.js +51 -0
- package/dist/showcase/components/ShowcaseRenderPage.js.map +1 -0
- package/dist/showcase/components/TopBar.d.ts +11 -0
- package/dist/showcase/components/TopBar.d.ts.map +1 -0
- package/dist/showcase/components/TopBar.js +103 -0
- package/dist/showcase/components/TopBar.js.map +1 -0
- package/dist/showcase/index.d.ts +12 -0
- package/dist/showcase/index.d.ts.map +1 -0
- package/dist/showcase/index.js +12 -0
- package/dist/showcase/index.js.map +1 -0
- package/dist/showcase/mockFactory.d.ts +34 -0
- package/dist/showcase/mockFactory.d.ts.map +1 -0
- package/dist/showcase/mockFactory.js +352 -0
- package/dist/showcase/mockFactory.js.map +1 -0
- package/dist/showcase/types.d.ts +20 -0
- package/dist/showcase/types.d.ts.map +1 -0
- package/dist/showcase/types.js +18 -0
- package/dist/showcase/types.js.map +1 -0
- package/dist/utils/buildPageMetadata.d.ts +20 -1
- package/dist/utils/buildPageMetadata.d.ts.map +1 -1
- package/dist/utils/buildPageMetadata.js +37 -0
- package/dist/utils/buildPageMetadata.js.map +1 -1
- package/dist/utils/componentUtils.d.ts +39 -146
- package/dist/utils/componentUtils.d.ts.map +1 -1
- package/dist/utils/componentUtils.js +142 -101
- package/dist/utils/componentUtils.js.map +1 -1
- package/dist/utils/urlUtils.d.ts +5 -0
- package/dist/utils/urlUtils.d.ts.map +1 -1
- package/dist/utils/urlUtils.js +10 -0
- package/dist/utils/urlUtils.js.map +1 -1
- package/package.json +9 -6
- package/src/showcase/README.md +131 -0
- package/dist/__tests__/setup.d.ts +0 -2
- package/dist/__tests__/setup.d.ts.map +0 -1
- package/dist/__tests__/setup.js +0 -2
- package/dist/__tests__/setup.js.map +0 -1
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# CMS Component Mapping Infrastructure
|
|
2
|
+
|
|
3
|
+
This document describes the CMS component mapping infrastructure extracted from the Point.me marketing-site project into the core-ui package.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Overview](#overview)
|
|
8
|
+
- [Architecture](#architecture)
|
|
9
|
+
- [Core Concepts](#core-concepts)
|
|
10
|
+
- [File Structure](#file-structure)
|
|
11
|
+
- [Usage Guide](#usage-guide)
|
|
12
|
+
- [1. Create Your Renderer Configuration](#1-create-your-renderer-configuration)
|
|
13
|
+
- [2. Use CmsContent with Your Configuration](#2-use-cmscontent-with-your-configuration)
|
|
14
|
+
- [3. Use UnusedChecker for Development](#3-use-unusedchecker-for-development)
|
|
15
|
+
- [Configuration Composition](#configuration-composition)
|
|
16
|
+
- [Preview Mode Configuration](#preview-mode-configuration)
|
|
17
|
+
- [Environment-Specific Configurations](#environment-specific-configurations)
|
|
18
|
+
- [Embedded Context Configuration](#embedded-context-configuration)
|
|
19
|
+
- [Type System](#type-system)
|
|
20
|
+
- [BaseCmsConfig](#basecmsconfig)
|
|
21
|
+
- [CmsRendererConfig](#cmsrendererconfig)
|
|
22
|
+
- [Context Types](#context-types)
|
|
23
|
+
- [Development Features](#development-features)
|
|
24
|
+
- [Unknown Type Warnings](#unknown-type-warnings)
|
|
25
|
+
- [Generic Fallbacks](#generic-fallbacks)
|
|
26
|
+
- [Type Safety](#type-safety)
|
|
27
|
+
- [Benefits Over Ad-Hoc Rendering](#benefits-over-ad-hoc-rendering)
|
|
28
|
+
- [Migration from Marketing-Site](#migration-from-marketing-site)
|
|
29
|
+
- [Examples](#examples)
|
|
30
|
+
- [API Reference](#api-reference)
|
|
31
|
+
- [CmsContent Props](#cmscontent-props)
|
|
32
|
+
- [Future Enhancements](#future-enhancements)
|
|
33
|
+
|
|
34
|
+
## Overview
|
|
35
|
+
|
|
36
|
+
The infrastructure provides a type-safe, extensible system for mapping CMS content types to React components. It supports:
|
|
37
|
+
|
|
38
|
+
- **Component/Collection Mapping**: Registry pattern for routing content types to renderers
|
|
39
|
+
- **Embedding System**: Components can be rendered in embedded contexts (like rich text)
|
|
40
|
+
- **Generic Fallbacks**: Automatic fallback rendering for unmapped types
|
|
41
|
+
- **Development Utilities**: Warnings for unused props and rendering errors
|
|
42
|
+
|
|
43
|
+
## Architecture
|
|
44
|
+
|
|
45
|
+
### Core Concepts
|
|
46
|
+
|
|
47
|
+
1. **Content Routing**: `CmsContent` receives an array of mixed content types and routes each to the appropriate renderer
|
|
48
|
+
2. **Type Mapping**: `ComponentMap` and `CollectionMap` define which React components render which content types
|
|
49
|
+
3. **Embedded Rendering**: Components can be rendered in embedded contexts (like rich text)
|
|
50
|
+
|
|
51
|
+
### File Structure
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
packages/core-ui/src/
|
|
55
|
+
├── components/
|
|
56
|
+
│ ├── CmsComponent.tsx # Component type router
|
|
57
|
+
│ ├── CmsCollection.tsx # Collection type router
|
|
58
|
+
│ ├── CmsContent.tsx # Main content renderer
|
|
59
|
+
│ ├── GenericComponent.tsx # Fallback component renderer
|
|
60
|
+
│ └── GenericCollection.tsx # Fallback collection renderer
|
|
61
|
+
├── embeddable/
|
|
62
|
+
│ ├── EmbeddableContext.ts # Context types for embedded rendering
|
|
63
|
+
│ ├── BuildEmbed.tsx # Factory functions for embeddable components
|
|
64
|
+
│ └── EmbeddableMaps.ts # Type definitions for embeddable maps
|
|
65
|
+
└── utils/
|
|
66
|
+
├── componentUtils.ts # Related content fetching
|
|
67
|
+
└── UnusedChecker.tsx # Development warning utility
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Usage Guide
|
|
71
|
+
|
|
72
|
+
### 1. Create Your Renderer Configuration
|
|
73
|
+
|
|
74
|
+
The recommended approach is to create a single `CmsRendererConfig` object that bundles all your renderer maps and options:
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { createCmsRendererConfig } from "@se-studio/core-ui";
|
|
78
|
+
import type { MyProjectConfig } from "./types";
|
|
79
|
+
|
|
80
|
+
// Import your component renderers
|
|
81
|
+
import { HeroComponent } from "@/components/Hero";
|
|
82
|
+
import { CTAComponent } from "@/components/CTA";
|
|
83
|
+
import { ContentBlock } from "@/components/ContentBlock";
|
|
84
|
+
|
|
85
|
+
// Import your collection renderers
|
|
86
|
+
import { FAQCollection } from "@/components/FAQCollection";
|
|
87
|
+
import { RelatedArticles } from "@/components/RelatedArticles";
|
|
88
|
+
|
|
89
|
+
// Import special renderers (optional)
|
|
90
|
+
import { VisualRenderer } from "@/components/VisualRenderer";
|
|
91
|
+
import { InternalLinkRenderer } from "@/components/InternalLinkRenderer";
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Main CMS renderer configuration for the project.
|
|
95
|
+
* Create this once and reuse it throughout your app.
|
|
96
|
+
*/
|
|
97
|
+
export const rendererConfig = createCmsRendererConfig<MyProjectConfig>({
|
|
98
|
+
componentMap: {
|
|
99
|
+
"Hero": HeroComponent,
|
|
100
|
+
"CTA": CTAComponent,
|
|
101
|
+
"Content Block": ContentBlock,
|
|
102
|
+
},
|
|
103
|
+
collectionMap: {
|
|
104
|
+
"FAQs": FAQCollection,
|
|
105
|
+
"Related Articles": RelatedArticles,
|
|
106
|
+
},
|
|
107
|
+
// Optional: external components, visuals, links
|
|
108
|
+
externalComponentMap: {
|
|
109
|
+
"Contact Form": ContactFormRenderer,
|
|
110
|
+
},
|
|
111
|
+
visualComponentRenderer: VisualRenderer,
|
|
112
|
+
internalLinkRenderer: InternalLinkRenderer,
|
|
113
|
+
// Show errors in development mode
|
|
114
|
+
showUnknownTypeErrors: process.env.NODE_ENV === 'development',
|
|
115
|
+
showRenderErrors: process.env.NODE_ENV === 'development',
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 2. Use CmsContent with Your Configuration
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
import { CmsContent } from "@se-studio/core-ui";
|
|
123
|
+
import { rendererConfig } from "@/cms/rendererConfig";
|
|
124
|
+
|
|
125
|
+
export function PageRenderer({ page }) {
|
|
126
|
+
const pageContext = {
|
|
127
|
+
pageLink: { slug: page.slug, title: page.title },
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<CmsContent
|
|
132
|
+
rendererConfig={rendererConfig}
|
|
133
|
+
pageContext={pageContext}
|
|
134
|
+
contents={page.contents}
|
|
135
|
+
/>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Benefits of this approach:**
|
|
141
|
+
- **Clean API**: Only 3 props instead of 7+ individual props
|
|
142
|
+
- **Single source of truth**: All renderer configuration in one place
|
|
143
|
+
- **Easy to extend**: Add new renderer types without touching every CmsContent call
|
|
144
|
+
- **Type-safe**: Full TypeScript support with generics
|
|
145
|
+
- **Composable**: Create variants with `mergeCmsRendererConfig()`
|
|
146
|
+
|
|
147
|
+
### 3. Use UnusedChecker for Development
|
|
148
|
+
|
|
149
|
+
Catch unused CMS properties that aren't being rendered:
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
import { UnusedChecker } from "@se-studio/core-ui";
|
|
153
|
+
|
|
154
|
+
export const MyComponent = ({ information, contentContext }) => {
|
|
155
|
+
const { heading, body, links, ...unused } = information;
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<UnusedChecker
|
|
159
|
+
componentName="MyComponent"
|
|
160
|
+
unused={unused}
|
|
161
|
+
showWarnings={true}
|
|
162
|
+
>
|
|
163
|
+
<div>
|
|
164
|
+
<h2>{heading}</h2>
|
|
165
|
+
<p>{body}</p>
|
|
166
|
+
</div>
|
|
167
|
+
</UnusedChecker>
|
|
168
|
+
);
|
|
169
|
+
};
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Configuration Composition
|
|
173
|
+
|
|
174
|
+
The `mergeCmsRendererConfig()` utility allows you to create specialized configurations from a base config:
|
|
175
|
+
|
|
176
|
+
### Preview Mode Configuration
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
import { mergeCmsRendererConfig } from "@se-studio/core-ui";
|
|
180
|
+
|
|
181
|
+
const baseConfig = createCmsRendererConfig({ ... });
|
|
182
|
+
|
|
183
|
+
// Preview config with all errors displayed
|
|
184
|
+
export const previewConfig = mergeCmsRendererConfig(baseConfig, {
|
|
185
|
+
showUnknownTypeErrors: true,
|
|
186
|
+
showRenderErrors: true,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Use in preview routes
|
|
190
|
+
<CmsContent rendererConfig={previewConfig} ... />
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Environment-Specific Configurations
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
const productionConfig = createCmsRendererConfig({
|
|
197
|
+
componentMap: { ... },
|
|
198
|
+
collectionMap: { ... },
|
|
199
|
+
showUnknownTypeErrors: false,
|
|
200
|
+
showRenderErrors: false,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const developmentConfig = mergeCmsRendererConfig(productionConfig, {
|
|
204
|
+
showUnknownTypeErrors: true,
|
|
205
|
+
showRenderErrors: true,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
export const rendererConfig =
|
|
209
|
+
process.env.NODE_ENV === 'production'
|
|
210
|
+
? productionConfig
|
|
211
|
+
: developmentConfig;
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Embedded Context Configuration
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
// Different component map for embedded contexts
|
|
218
|
+
const embeddedConfig = mergeCmsRendererConfig(baseConfig, {
|
|
219
|
+
componentMap: {
|
|
220
|
+
...baseConfig.componentMap,
|
|
221
|
+
"Hero": EmbeddedHeroComponent, // Use simplified version
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Type System
|
|
227
|
+
|
|
228
|
+
### BaseCmsConfig
|
|
229
|
+
|
|
230
|
+
Projects must extend `BaseCmsConfig` to define their CMS-specific types:
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
import type { BaseCmsConfig } from "@se-studio/core-data-types";
|
|
234
|
+
|
|
235
|
+
export interface MyProjectConfig extends BaseCmsConfig {
|
|
236
|
+
ComponentType: "Hero" | "CTA" | "Content Block";
|
|
237
|
+
CollectionType: "FAQs" | "Related Articles";
|
|
238
|
+
ExternalComponentType: "ContactForm" | "Newsletter";
|
|
239
|
+
ColourName: "red" | "blue" | "green";
|
|
240
|
+
// ... other type overrides
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### CmsRendererConfig
|
|
245
|
+
|
|
246
|
+
The main configuration type that bundles all renderer maps:
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
interface CmsRendererConfig<TConfig extends BaseCmsConfig> {
|
|
250
|
+
componentMap: ComponentMap<TConfig>;
|
|
251
|
+
collectionMap: CollectionMap<TConfig>;
|
|
252
|
+
externalComponentMap?: ExternalComponentMap<TConfig>;
|
|
253
|
+
visualComponentRenderer?: VisualComponentRenderer<TConfig>;
|
|
254
|
+
internalLinkRenderer?: InternalLinkRenderer<TConfig>;
|
|
255
|
+
showUnknownTypeErrors?: boolean;
|
|
256
|
+
showRenderErrors?: boolean;
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Context Types
|
|
261
|
+
|
|
262
|
+
- `IPageContext<TConfig>`: Page-level context (article, page, tag links)
|
|
263
|
+
- `IContentContext<TConfig>`: Content-level context (current, previous, next content)
|
|
264
|
+
- `IEmbeddableComponentContext<TConfig>`: Context for embedded components
|
|
265
|
+
- `IEmbeddableCollectionContext<TConfig>`: Context for embedded collections
|
|
266
|
+
|
|
267
|
+
## Development Features
|
|
268
|
+
|
|
269
|
+
### Unknown Type Warnings
|
|
270
|
+
|
|
271
|
+
In development mode, the infrastructure displays visual warnings for:
|
|
272
|
+
- Unmapped component/collection types
|
|
273
|
+
- Rendering errors
|
|
274
|
+
- Unused CMS properties
|
|
275
|
+
|
|
276
|
+
### Generic Fallbacks
|
|
277
|
+
|
|
278
|
+
When a component/collection type isn't mapped:
|
|
279
|
+
1. A warning is logged to console
|
|
280
|
+
2. In dev mode, an error panel is displayed
|
|
281
|
+
3. A generic fallback renderer is used
|
|
282
|
+
4. The page continues to render (no crashes)
|
|
283
|
+
|
|
284
|
+
### Type Safety
|
|
285
|
+
|
|
286
|
+
- Full TypeScript support with generic types
|
|
287
|
+
- Development warnings catch unused properties
|
|
288
|
+
|
|
289
|
+
## Benefits Over Ad-Hoc Rendering
|
|
290
|
+
|
|
291
|
+
1. **Centralized Mapping**: All component mappings in one place
|
|
292
|
+
2. **Reusability**: Components work in multiple contexts (full-page, embedded)
|
|
293
|
+
3. **Maintainability**: Changes to component structure are caught at compile-time
|
|
294
|
+
4. **Development UX**: Clear warnings for mistakes
|
|
295
|
+
5. **Fallback Handling**: Pages never break from unmapped types
|
|
296
|
+
|
|
297
|
+
## Migration from Marketing-Site
|
|
298
|
+
|
|
299
|
+
To migrate an existing project:
|
|
300
|
+
|
|
301
|
+
1. Install core-ui: `pnpm add @se-studio/core-ui`
|
|
302
|
+
2. Create ComponentMap and CollectionMap
|
|
303
|
+
3. Replace direct component rendering with CmsContent
|
|
304
|
+
4. Test with `showUnknownTypeErrors={true}` to find missing mappings
|
|
305
|
+
|
|
306
|
+
## Examples
|
|
307
|
+
|
|
308
|
+
See the example apps in this monorepo for complete working implementations:
|
|
309
|
+
- `apps/example-pointme`: Full Point.me-style implementation
|
|
310
|
+
- `apps/example-hsd`: Alternative implementation pattern
|
|
311
|
+
|
|
312
|
+
## API Reference
|
|
313
|
+
|
|
314
|
+
### CmsContent Props
|
|
315
|
+
|
|
316
|
+
- `pageContext`: Page-level context information
|
|
317
|
+
- `contents`: Array of content to render
|
|
318
|
+
- `componentMap`: Map of component types to renderers
|
|
319
|
+
- `collectionMap`: Map of collection types to renderers
|
|
320
|
+
- `externalComponentMap`: Map of external component types to renderers (optional)
|
|
321
|
+
- `visualComponentRenderer`: Renderer for Visual content types (optional)
|
|
322
|
+
- `internalLinkRenderer`: Renderer for Internal Link content types (optional)
|
|
323
|
+
- `showUnknownTypeErrors`: Show error panels for unknown types (default: false)
|
|
324
|
+
- `showRenderErrors`: Show error panels for rendering errors (default: false)
|
|
325
|
+
|
|
326
|
+
## Future Enhancements
|
|
327
|
+
|
|
328
|
+
Potential improvements for future iterations:
|
|
329
|
+
|
|
330
|
+
- [ ] Async component loading for code splitting
|
|
331
|
+
- [ ] Component preview mode for CMS editors
|
|
332
|
+
- [ ] Performance monitoring hooks
|
|
333
|
+
- [ ] Server-side rendering optimizations
|
|
334
|
+
- [ ] Component analytics integration
|
|
335
|
+
- [ ] Rich text node renderer registration system
|
package/CONSENT.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Consent Management Guide
|
|
2
|
+
|
|
3
|
+
Core UI integrates with [Orejime](https://github.com/empreinte-digitale/orejime), an open-source consent management manager, to provide GDPR and CCPA compliance.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The consent system ensures that:
|
|
8
|
+
1. Users are presented with a consent banner
|
|
9
|
+
2. Cookies/Trackers are blocked until consent is given
|
|
10
|
+
3. Analytics adapters only fire when allowed
|
|
11
|
+
|
|
12
|
+
## Setup
|
|
13
|
+
|
|
14
|
+
### 1. Configure Consent Provider
|
|
15
|
+
|
|
16
|
+
Wrap your application with `ConsentProvider` and provide configuration.
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
import { ConsentProvider } from '@se-studio/core-ui';
|
|
20
|
+
|
|
21
|
+
const config = {
|
|
22
|
+
apps: [
|
|
23
|
+
{
|
|
24
|
+
name: 'google-tag-manager',
|
|
25
|
+
title: 'Analytics',
|
|
26
|
+
purposes: ['analytics'],
|
|
27
|
+
cookies: ['_ga', '_gid'],
|
|
28
|
+
required: false,
|
|
29
|
+
default: true,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'marketing-cookies',
|
|
33
|
+
title: 'Marketing',
|
|
34
|
+
purposes: ['marketing'],
|
|
35
|
+
required: false,
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
privacyPolicy: '/privacy-policy',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Styling theme (optional)
|
|
42
|
+
const theme = {
|
|
43
|
+
primaryColor: '#0070f3',
|
|
44
|
+
borderRadius: '8px',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export function AppProviders({ children }) {
|
|
48
|
+
return (
|
|
49
|
+
<ConsentProvider config={config} theme={theme}>
|
|
50
|
+
{children}
|
|
51
|
+
</ConsentProvider>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 2. Connect to Analytics
|
|
57
|
+
|
|
58
|
+
Use `ConsentAwareAdapter` to gate analytics based on consent.
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { ConsentAwareAdapter, GoogleTagManagerAdapter, useConsent } from '@se-studio/core-ui';
|
|
62
|
+
|
|
63
|
+
function AnalyticsSetup() {
|
|
64
|
+
const { hasConsent } = useConsent();
|
|
65
|
+
|
|
66
|
+
const adapter = useMemo(() => {
|
|
67
|
+
const base = new GoogleTagManagerAdapter({ containerId: '...' });
|
|
68
|
+
// 'google-tag-manager' matches the app name in config
|
|
69
|
+
return new ConsentAwareAdapter(base, 'google-tag-manager', hasConsent);
|
|
70
|
+
}, [hasConsent]);
|
|
71
|
+
|
|
72
|
+
return <AnalyticsProvider adapter={adapter}>...</AnalyticsProvider>;
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Styling
|
|
77
|
+
|
|
78
|
+
Orejime styles can be customized via CSS variables or the `theme` prop.
|
|
79
|
+
|
|
80
|
+
### Using Theme Prop
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
<ConsentProvider
|
|
84
|
+
theme={{
|
|
85
|
+
fontFamily: 'Inter, sans-serif',
|
|
86
|
+
primaryColor: '#ff0000',
|
|
87
|
+
backgroundColor: '#ffffff'
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Using CSS Variables
|
|
93
|
+
|
|
94
|
+
In your global CSS:
|
|
95
|
+
|
|
96
|
+
```css
|
|
97
|
+
:root {
|
|
98
|
+
--orejime-color-primary: #ff0000;
|
|
99
|
+
--orejime-border-radius: 4px;
|
|
100
|
+
--orejime-z-index: 50;
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Hook API
|
|
105
|
+
|
|
106
|
+
The `useConsent` hook provides access to consent state.
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
import { useConsent } from '@se-studio/core-ui';
|
|
110
|
+
|
|
111
|
+
function MyComponent() {
|
|
112
|
+
const { hasConsent, showConsentModal, resetConsent } = useConsent();
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<div>
|
|
116
|
+
{hasConsent('analytics') && <AnalyticsDashboard />}
|
|
117
|
+
<button onClick={showConsentModal}>Cookie Settings</button>
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
```
|
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ Shared React UI component library with Tailwind CSS v4 and CMS infrastructure fo
|
|
|
10
10
|
- **Tailwind CSS v4** - Modern utility-first CSS framework with custom breakpoints
|
|
11
11
|
- **Visual Components** - Optimized image, video, and animation components
|
|
12
12
|
- **Rich Text Rendering** - RTF (Rich Text Field) support with embedded content
|
|
13
|
+
- **CMS Showcase** - Shared developer tools for testing and previewing CMS components
|
|
13
14
|
- **Tree-shakeable** - Import only what you need
|
|
14
15
|
- **Dual exports** - ESM and CJS support
|
|
15
16
|
- **Fully tested** - Comprehensive test coverage with Vitest
|
|
@@ -237,6 +238,16 @@ import { UnusedChecker } from '@se-studio/core-ui';
|
|
|
237
238
|
/>
|
|
238
239
|
```
|
|
239
240
|
|
|
241
|
+
#### CMS Showcase
|
|
242
|
+
|
|
243
|
+
The library provides a set of components and utilities to build a CMS component showcase for your project.
|
|
244
|
+
|
|
245
|
+
- **`ShowcasePage`** - The main interface for selecting components and adjusting mock data.
|
|
246
|
+
- **`ShowcaseRenderPage`** - The renderer component for the preview iframe.
|
|
247
|
+
- **`mockFactory`** - Utilities for generating mock data from CMS registrations.
|
|
248
|
+
|
|
249
|
+
See the [CMS Showcase Infrastructure Guide](./src/showcase/README.md) for more details.
|
|
250
|
+
|
|
240
251
|
### Animation & Monitoring Components
|
|
241
252
|
|
|
242
253
|
#### ClientMonitor
|
|
@@ -397,17 +408,12 @@ const previewConfig = mergeCmsRendererConfig(
|
|
|
397
408
|
#### Utilities
|
|
398
409
|
- **`buildPageMetadata`** - Generates Next.js metadata object from CMS page model
|
|
399
410
|
- **`handleCmsError`** - Error handling utility for CMS operations
|
|
400
|
-
- **`
|
|
401
|
-
- **`
|
|
402
|
-
- **`extractPageContext`** - Extract specific page context fields
|
|
411
|
+
- **`getRelatedArticles`** - Utility to fetch related articles for collections
|
|
412
|
+
- **`getRelatedPeople`** - Utility to fetch related people for collections
|
|
403
413
|
- **`cn`** - Utility function for merging Tailwind CSS classes
|
|
404
414
|
|
|
405
415
|
#### Types
|
|
406
416
|
- **`CmsRendererConfig`** - Complete renderer configuration type
|
|
407
|
-
- **`ComponentRenderer<T>`** - Type for component renderer functions
|
|
408
|
-
- **`CollectionRenderer<T>`** - Type for collection renderer functions
|
|
409
|
-
- **`ComponentConfig<T>`** - Type-safe component configuration
|
|
410
|
-
- **`CollectionConfig<T>`** - Type-safe collection configuration
|
|
411
417
|
|
|
412
418
|
For detailed JSDoc documentation on all exports, see the TypeScript declaration files (`.d.ts`) in the package.
|
|
413
419
|
|
|
@@ -417,7 +423,6 @@ For advanced CMS infrastructure usage, including:
|
|
|
417
423
|
- Configuration composition patterns
|
|
418
424
|
- Embeddable components
|
|
419
425
|
- Context passing
|
|
420
|
-
- Type-safe prop extraction
|
|
421
426
|
- Preview mode setup
|
|
422
427
|
|
|
423
428
|
See the [CMS Infrastructure Guide](./CMS_INFRASTRUCTURE.md).
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CMS Registration System
|
|
3
|
+
*
|
|
4
|
+
* This module provides a unified registration pattern for CMS components and collections.
|
|
5
|
+
* Instead of updating multiple places when adding a new component, you can define a single
|
|
6
|
+
* registration object that contains all related pieces (renderer, embedded renderer, used fields).
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* // In your component file
|
|
11
|
+
* export const HeroRegistration = defineComponent({
|
|
12
|
+
* name: 'Hero',
|
|
13
|
+
* renderer: Hero,
|
|
14
|
+
* usedFields: USED_FIELDS,
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* // In cms.ts
|
|
18
|
+
* const componentRegistrations = [HeroRegistration, ...];
|
|
19
|
+
* const { componentMap, embeddedComponentMap, componentFieldsMap } =
|
|
20
|
+
* buildComponentMaps(componentRegistrations);
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import type { ITyped } from '@se-studio/core-data-types';
|
|
24
|
+
import type { CollectionRenderer, EmbeddedCollectionRenderer } from './components/CmsCollection';
|
|
25
|
+
import type { ComponentRenderer, EmbeddedComponentRenderer } from './components/CmsComponent';
|
|
26
|
+
import type { EmbeddedExternalComponentRenderer, ExternalComponentRenderer } from './components/CmsExternalComponent';
|
|
27
|
+
export interface MockData {
|
|
28
|
+
visual?: {
|
|
29
|
+
width: number;
|
|
30
|
+
height: number;
|
|
31
|
+
};
|
|
32
|
+
[key: string]: string | number | boolean | {
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
} | undefined;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Registration for a CMS component.
|
|
39
|
+
* Components can optionally have an embedded variant for use in RTF/collections.
|
|
40
|
+
*/
|
|
41
|
+
export interface ComponentRegistration<TName extends string = string, TComponent extends ITyped = ITyped> {
|
|
42
|
+
/** CMS component type name (e.g., 'Hero', 'Callout') */
|
|
43
|
+
name: TName;
|
|
44
|
+
/** Main renderer for page-level display */
|
|
45
|
+
renderer: ComponentRenderer<TComponent>;
|
|
46
|
+
/** Fields used by this component (for cms-showcase and unused field checking) */
|
|
47
|
+
usedFields: Set<string>;
|
|
48
|
+
/** Optional embedded renderer for RTF/collection contexts */
|
|
49
|
+
embeddedRenderer?: EmbeddedComponentRenderer<TComponent>;
|
|
50
|
+
/** Optional mock data configuration for the showcase */
|
|
51
|
+
mock?: MockData;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Registration for a CMS collection.
|
|
55
|
+
* Collections render arrays of content items.
|
|
56
|
+
*/
|
|
57
|
+
export interface CollectionRegistration<TName extends string = string, TCollection extends ITyped = ITyped> {
|
|
58
|
+
/** CMS collection type name (e.g., 'Card grid', 'Icon cards') */
|
|
59
|
+
name: TName;
|
|
60
|
+
/** Main renderer for page-level display */
|
|
61
|
+
renderer: CollectionRenderer<TCollection>;
|
|
62
|
+
/** Fields used by this collection */
|
|
63
|
+
usedFields: Set<string>;
|
|
64
|
+
/** Optional embedded renderer for nested contexts */
|
|
65
|
+
embeddedRenderer?: EmbeddedCollectionRenderer<TCollection>;
|
|
66
|
+
/** Optional: fields used by card items within the collection */
|
|
67
|
+
cardUsedFields?: Set<string>;
|
|
68
|
+
/** Optional mock data configuration for the showcase (collection fields) */
|
|
69
|
+
mock?: MockData;
|
|
70
|
+
/** Optional mock data configuration for the showcase (card fields) */
|
|
71
|
+
cardMock?: MockData;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Registration for an external component (forms, iframes, third-party widgets).
|
|
75
|
+
*/
|
|
76
|
+
export interface ExternalComponentRegistration<TName extends string = string, TExternal extends ITyped = ITyped> {
|
|
77
|
+
/** External component type name (e.g., 'iframe', 'Pardot form') */
|
|
78
|
+
name: TName;
|
|
79
|
+
/** Renderer for the external component */
|
|
80
|
+
renderer: ExternalComponentRenderer<TExternal>;
|
|
81
|
+
/** Optional embedded renderer */
|
|
82
|
+
embeddedRenderer?: EmbeddedExternalComponentRenderer<TExternal>;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Builds component maps from an array of registrations.
|
|
86
|
+
* Returns componentMap, embeddedComponentMap, and componentFieldsMap.
|
|
87
|
+
*/
|
|
88
|
+
export declare function buildComponentMaps<TComponent extends ITyped>(registrations: ComponentRegistration<string, TComponent>[]): {
|
|
89
|
+
componentMap: Record<string, ComponentRenderer<TComponent>>;
|
|
90
|
+
embeddedComponentMap: Record<string, EmbeddedComponentRenderer<TComponent>>;
|
|
91
|
+
componentFieldsMap: Record<string, Set<string>>;
|
|
92
|
+
componentMockMap: Record<string, MockData>;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Builds collection maps from an array of registrations.
|
|
96
|
+
*/
|
|
97
|
+
export declare function buildCollectionMaps<TCollection extends ITyped>(registrations: CollectionRegistration<string, TCollection>[]): {
|
|
98
|
+
collectionMap: Record<string, CollectionRenderer<TCollection>>;
|
|
99
|
+
embeddedCollectionMap: Record<string, EmbeddedCollectionRenderer<TCollection>>;
|
|
100
|
+
collectionFieldsMap: Record<string, Set<string>>;
|
|
101
|
+
collectionCardFieldsMap: Record<string, Set<string>>;
|
|
102
|
+
collectionMockMap: Record<string, MockData>;
|
|
103
|
+
collectionCardMockMap: Record<string, MockData>;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Builds external component maps from an array of registrations.
|
|
107
|
+
*/
|
|
108
|
+
export declare function buildExternalComponentMaps<TExternal extends ITyped>(registrations: ExternalComponentRegistration<string, TExternal>[]): {
|
|
109
|
+
externalComponentMap: Record<string, ExternalComponentRenderer<TExternal>>;
|
|
110
|
+
embeddedExternalComponentMap: Record<string, EmbeddedExternalComponentRenderer<TExternal>>;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Helper to create a component registration with type inference.
|
|
114
|
+
* Use this in component files to get proper type checking.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```tsx
|
|
118
|
+
* export const HeroRegistration = defineComponent({
|
|
119
|
+
* name: 'Hero',
|
|
120
|
+
* renderer: Hero,
|
|
121
|
+
* usedFields: USED_FIELDS,
|
|
122
|
+
* });
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export declare function defineComponent<TName extends string, TComponent extends ITyped>(registration: ComponentRegistration<TName, TComponent>): ComponentRegistration<TName, TComponent>;
|
|
126
|
+
/**
|
|
127
|
+
* Helper to create a collection registration with type inference.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```tsx
|
|
131
|
+
* export const CardGridRegistration = defineCollection({
|
|
132
|
+
* name: 'Card grid',
|
|
133
|
+
* renderer: CardGrid,
|
|
134
|
+
* usedFields: COLLECTION_USED_FIELDS,
|
|
135
|
+
* embeddedRenderer: EmbeddedCardGrid,
|
|
136
|
+
* });
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
export declare function defineCollection<TName extends string, TCollection extends ITyped>(registration: CollectionRegistration<TName, TCollection>): CollectionRegistration<TName, TCollection>;
|
|
140
|
+
/**
|
|
141
|
+
* Helper to create an external component registration with type inference.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```tsx
|
|
145
|
+
* export const IFrameRegistration = defineExternalComponent({
|
|
146
|
+
* name: 'iframe',
|
|
147
|
+
* renderer: ExternalIFrame,
|
|
148
|
+
* });
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
export declare function defineExternalComponent<TName extends string, TExternal extends ITyped>(registration: ExternalComponentRegistration<TName, TExternal>): ExternalComponentRegistration<TName, TExternal>;
|
|
152
|
+
//# sourceMappingURL=cmsRegistration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cmsRegistration.d.ts","sourceRoot":"","sources":["../src/cmsRegistration.ts"],"names":[],"mappings":"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"}
|