meno-core 1.0.0
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/bin/cli.ts +281 -0
- package/build-static.ts +298 -0
- package/bunfig.toml +39 -0
- package/entries/client-router.tsx +111 -0
- package/entries/server-router.tsx +71 -0
- package/lib/client/ClientInitializer.test.ts +9 -0
- package/lib/client/ClientInitializer.test.ts.skip +92 -0
- package/lib/client/ClientInitializer.ts +60 -0
- package/lib/client/ErrorBoundary.test.tsx +595 -0
- package/lib/client/ErrorBoundary.tsx +230 -0
- package/lib/client/componentRegistry.test.ts +165 -0
- package/lib/client/componentRegistry.ts +18 -0
- package/lib/client/contexts/ThemeContext.tsx +73 -0
- package/lib/client/core/ComponentBuilder.test.ts +677 -0
- package/lib/client/core/ComponentBuilder.ts +660 -0
- package/lib/client/core/ComponentRenderer.test.tsx +176 -0
- package/lib/client/core/ComponentRenderer.tsx +83 -0
- package/lib/client/core/cmsTemplateProcessor.ts +129 -0
- package/lib/client/elementRegistry.ts +81 -0
- package/lib/client/hmr/HMRManager.tsx +179 -0
- package/lib/client/hmr/index.ts +5 -0
- package/lib/client/hmrWebSocket.test.ts +9 -0
- package/lib/client/hmrWebSocket.ts +250 -0
- package/lib/client/hooks/useColorVariables.test.ts +166 -0
- package/lib/client/hooks/useColorVariables.ts +249 -0
- package/lib/client/hooks/usePropertyAutocomplete.test.ts +9 -0
- package/lib/client/hooks/usePropertyAutocomplete.ts +40 -0
- package/lib/client/hydration/HydrationUtils.test.ts +154 -0
- package/lib/client/hydration/HydrationUtils.ts +35 -0
- package/lib/client/i18nConfigService.test.ts +74 -0
- package/lib/client/i18nConfigService.ts +78 -0
- package/lib/client/index.ts +56 -0
- package/lib/client/navigation.test.ts +441 -0
- package/lib/client/navigation.ts +23 -0
- package/lib/client/responsiveStyleResolver.test.ts +491 -0
- package/lib/client/responsiveStyleResolver.ts +184 -0
- package/lib/client/routing/RouteLoader.test.ts +635 -0
- package/lib/client/routing/RouteLoader.ts +347 -0
- package/lib/client/routing/Router.tsx +382 -0
- package/lib/client/scripts/ScriptExecutor.test.ts +489 -0
- package/lib/client/scripts/ScriptExecutor.ts +171 -0
- package/lib/client/scripts/formHandler.ts +103 -0
- package/lib/client/styleProcessor.test.ts +126 -0
- package/lib/client/styleProcessor.ts +92 -0
- package/lib/client/styles/StyleInjector.test.ts +354 -0
- package/lib/client/styles/StyleInjector.ts +154 -0
- package/lib/client/templateEngine.test.ts +660 -0
- package/lib/client/templateEngine.ts +667 -0
- package/lib/client/theme.test.ts +173 -0
- package/lib/client/theme.ts +159 -0
- package/lib/client/utils/toast.ts +46 -0
- package/lib/server/createServer.ts +170 -0
- package/lib/server/cssGenerator.test.ts +172 -0
- package/lib/server/cssGenerator.ts +58 -0
- package/lib/server/fileWatcher.ts +134 -0
- package/lib/server/index.ts +55 -0
- package/lib/server/jsonLoader.test.ts +103 -0
- package/lib/server/jsonLoader.ts +350 -0
- package/lib/server/middleware/cors.test.ts +177 -0
- package/lib/server/middleware/cors.ts +69 -0
- package/lib/server/middleware/errorHandler.test.ts +208 -0
- package/lib/server/middleware/errorHandler.ts +63 -0
- package/lib/server/middleware/index.ts +9 -0
- package/lib/server/middleware/logger.test.ts +233 -0
- package/lib/server/middleware/logger.ts +99 -0
- package/lib/server/pageCache.test.ts +167 -0
- package/lib/server/pageCache.ts +97 -0
- package/lib/server/projectContext.ts +51 -0
- package/lib/server/providers/fileSystemCMSProvider.test.ts +292 -0
- package/lib/server/providers/fileSystemCMSProvider.ts +227 -0
- package/lib/server/providers/fileSystemPageProvider.ts +83 -0
- package/lib/server/routes/api/cms.test.ts +177 -0
- package/lib/server/routes/api/cms.ts +82 -0
- package/lib/server/routes/api/colors.ts +59 -0
- package/lib/server/routes/api/components.ts +70 -0
- package/lib/server/routes/api/config.test.ts +9 -0
- package/lib/server/routes/api/config.ts +28 -0
- package/lib/server/routes/api/core-routes.ts +182 -0
- package/lib/server/routes/api/functions.ts +170 -0
- package/lib/server/routes/api/index.ts +69 -0
- package/lib/server/routes/api/pages.ts +95 -0
- package/lib/server/routes/api/shared.test.ts +81 -0
- package/lib/server/routes/api/shared.ts +31 -0
- package/lib/server/routes/editor.test.ts +9 -0
- package/lib/server/routes/index.ts +104 -0
- package/lib/server/routes/pages.ts +161 -0
- package/lib/server/routes/static.ts +107 -0
- package/lib/server/services/ColorService.ts +193 -0
- package/lib/server/services/cmsService.test.ts +388 -0
- package/lib/server/services/cmsService.ts +296 -0
- package/lib/server/services/componentService.test.ts +276 -0
- package/lib/server/services/componentService.ts +346 -0
- package/lib/server/services/configService.ts +156 -0
- package/lib/server/services/fileWatcherService.ts +67 -0
- package/lib/server/services/index.ts +10 -0
- package/lib/server/services/pageService.test.ts +258 -0
- package/lib/server/services/pageService.ts +240 -0
- package/lib/server/ssrRenderer.test.ts +1005 -0
- package/lib/server/ssrRenderer.ts +878 -0
- package/lib/server/utilityClassGenerator.ts +11 -0
- package/lib/server/utils/index.ts +5 -0
- package/lib/server/utils/jsonLineMapper.test.ts +100 -0
- package/lib/server/utils/jsonLineMapper.ts +166 -0
- package/lib/server/validateStyleCoverage.test.ts +9 -0
- package/lib/server/validateStyleCoverage.ts +167 -0
- package/lib/server/websocketManager.test.ts +9 -0
- package/lib/server/websocketManager.ts +95 -0
- package/lib/shared/attributeNodeUtils.test.ts +152 -0
- package/lib/shared/attributeNodeUtils.ts +50 -0
- package/lib/shared/breakpoints.test.ts +166 -0
- package/lib/shared/breakpoints.ts +65 -0
- package/lib/shared/colorProperties.test.ts +111 -0
- package/lib/shared/colorProperties.ts +40 -0
- package/lib/shared/colorVariableUtils.test.ts +319 -0
- package/lib/shared/colorVariableUtils.ts +97 -0
- package/lib/shared/constants.test.ts +175 -0
- package/lib/shared/constants.ts +116 -0
- package/lib/shared/cssGeneration.ts +481 -0
- package/lib/shared/cssProperties.test.ts +252 -0
- package/lib/shared/cssProperties.ts +338 -0
- package/lib/shared/elementUtils.test.ts +245 -0
- package/lib/shared/elementUtils.ts +90 -0
- package/lib/shared/fontLoader.ts +97 -0
- package/lib/shared/i18n.test.ts +313 -0
- package/lib/shared/i18n.ts +286 -0
- package/lib/shared/index.ts +50 -0
- package/lib/shared/interfaces/contentProvider.test.ts +9 -0
- package/lib/shared/interfaces/contentProvider.ts +121 -0
- package/lib/shared/nodeUtils.test.ts +320 -0
- package/lib/shared/nodeUtils.ts +220 -0
- package/lib/shared/pathArrayUtils.test.ts +315 -0
- package/lib/shared/pathArrayUtils.ts +17 -0
- package/lib/shared/pathUtils.test.ts +260 -0
- package/lib/shared/pathUtils.ts +244 -0
- package/lib/shared/paths/Path.test.ts +74 -0
- package/lib/shared/paths/Path.ts +23 -0
- package/lib/shared/paths/PathConverter.test.ts +232 -0
- package/lib/shared/paths/PathConverter.ts +141 -0
- package/lib/shared/paths/PathUtils.ts +290 -0
- package/lib/shared/paths/PathValidator.test.ts +193 -0
- package/lib/shared/paths/PathValidator.ts +53 -0
- package/lib/shared/paths/index.ts +48 -0
- package/lib/shared/propResolver.test.ts +639 -0
- package/lib/shared/propResolver.ts +124 -0
- package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +190 -0
- package/lib/shared/registry/BaseNodeTypeRegistry.ts +200 -0
- package/lib/shared/registry/ClientNodeTypeRegistry.ts +34 -0
- package/lib/shared/registry/ClientRegistry.test.ts +26 -0
- package/lib/shared/registry/ClientRegistry.ts +15 -0
- package/lib/shared/registry/ComponentRegistry.test.ts +293 -0
- package/lib/shared/registry/ComponentRegistry.ts +100 -0
- package/lib/shared/registry/NodeTypeDefinition.ts +198 -0
- package/lib/shared/registry/NodeTypeManager.ts +94 -0
- package/lib/shared/registry/RegistryManager.test.ts +58 -0
- package/lib/shared/registry/RegistryManager.ts +60 -0
- package/lib/shared/registry/SSRNodeTypeRegistry.ts +33 -0
- package/lib/shared/registry/SSRRegistry.test.ts +26 -0
- package/lib/shared/registry/SSRRegistry.ts +15 -0
- package/lib/shared/registry/createNodeType.ts +175 -0
- package/lib/shared/registry/defineNodeType.ts +73 -0
- package/lib/shared/registry/fieldPresets.ts +109 -0
- package/lib/shared/registry/index.ts +50 -0
- package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +71 -0
- package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +61 -0
- package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +88 -0
- package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +66 -0
- package/lib/shared/registry/nodeTypes/ObjectLinkNodeType.ts +75 -0
- package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +49 -0
- package/lib/shared/registry/nodeTypes/TextNodeType.ts +52 -0
- package/lib/shared/registry/nodeTypes/index.ts +75 -0
- package/lib/shared/responsiveScaling.test.ts +268 -0
- package/lib/shared/responsiveScaling.ts +194 -0
- package/lib/shared/responsiveStyleUtils.test.ts +300 -0
- package/lib/shared/responsiveStyleUtils.ts +139 -0
- package/lib/shared/slugTranslator.test.ts +325 -0
- package/lib/shared/slugTranslator.ts +177 -0
- package/lib/shared/styleNodeUtils.test.ts +132 -0
- package/lib/shared/styleNodeUtils.ts +102 -0
- package/lib/shared/styleUtils.test.ts +238 -0
- package/lib/shared/styleUtils.ts +63 -0
- package/lib/shared/themeDefaults.test.ts +113 -0
- package/lib/shared/themeDefaults.ts +103 -0
- package/lib/shared/tree/PathBuilder.ts +383 -0
- package/lib/shared/treePathUtils.test.ts +539 -0
- package/lib/shared/treePathUtils.ts +339 -0
- package/lib/shared/types/api.ts +58 -0
- package/lib/shared/types/cms.ts +95 -0
- package/lib/shared/types/colors.ts +45 -0
- package/lib/shared/types/components.ts +121 -0
- package/lib/shared/types/errors.test.ts +103 -0
- package/lib/shared/types/errors.ts +69 -0
- package/lib/shared/types/index.ts +96 -0
- package/lib/shared/types/nodes.ts +20 -0
- package/lib/shared/types/rendering.ts +61 -0
- package/lib/shared/types/styles.ts +38 -0
- package/lib/shared/types.ts +11 -0
- package/lib/shared/utilityClassConfig.ts +287 -0
- package/lib/shared/utilityClassMapper.test.ts +140 -0
- package/lib/shared/utilityClassMapper.ts +229 -0
- package/lib/shared/utils/fileUtils.test.ts +99 -0
- package/lib/shared/utils/fileUtils.ts +56 -0
- package/lib/shared/utils.test.ts +261 -0
- package/lib/shared/utils.ts +84 -0
- package/lib/shared/validation/index.ts +7 -0
- package/lib/shared/validation/propValidator.test.ts +178 -0
- package/lib/shared/validation/propValidator.ts +238 -0
- package/lib/shared/validation/schemas.test.ts +177 -0
- package/lib/shared/validation/schemas.ts +401 -0
- package/lib/shared/validation/validators.test.ts +109 -0
- package/lib/shared/validation/validators.ts +304 -0
- package/lib/test-utils/dom-setup.ts +55 -0
- package/lib/test-utils/factories/ConsoleMockFactory.ts +200 -0
- package/lib/test-utils/factories/DomMockFactory.ts +487 -0
- package/lib/test-utils/factories/EventMockFactory.ts +244 -0
- package/lib/test-utils/factories/FetchMockFactory.ts +210 -0
- package/lib/test-utils/factories/ServerMockFactory.ts +223 -0
- package/lib/test-utils/factories/StoreMockFactory.ts +370 -0
- package/lib/test-utils/factories/index.ts +11 -0
- package/lib/test-utils/fixtures.ts +134 -0
- package/lib/test-utils/helpers/asyncHelpers.test.ts +112 -0
- package/lib/test-utils/helpers/asyncHelpers.ts +196 -0
- package/lib/test-utils/helpers/index.ts +6 -0
- package/lib/test-utils/helpers.test.ts +73 -0
- package/lib/test-utils/helpers.ts +90 -0
- package/lib/test-utils/index.ts +17 -0
- package/lib/test-utils/mockFactories.ts +92 -0
- package/lib/test-utils/mocks.ts +341 -0
- package/package.json +38 -0
- package/templates/index-router.html +34 -0
- package/tsconfig.json +14 -0
- package/vite.config.ts +43 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Boundary Component
|
|
3
|
+
* Catches and handles React rendering errors gracefully
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Component, createElement as h, ReactNode } from 'react';
|
|
7
|
+
|
|
8
|
+
interface ErrorBoundaryProps {
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
fallback?: (error: Error, errorInfo: any) => ReactNode;
|
|
11
|
+
onError?: (error: Error, errorInfo: any) => void;
|
|
12
|
+
componentName?: string;
|
|
13
|
+
level?: 'component' | 'page';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ErrorBoundaryState {
|
|
17
|
+
hasError: boolean;
|
|
18
|
+
error: Error | null;
|
|
19
|
+
errorInfo: any;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
23
|
+
constructor(props: ErrorBoundaryProps) {
|
|
24
|
+
super(props);
|
|
25
|
+
this.state = {
|
|
26
|
+
hasError: false,
|
|
27
|
+
error: null,
|
|
28
|
+
errorInfo: null,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
|
|
33
|
+
return {
|
|
34
|
+
hasError: true,
|
|
35
|
+
error,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
componentDidCatch(error: Error, errorInfo: any) {
|
|
40
|
+
const { onError, componentName, level = 'component' } = this.props;
|
|
41
|
+
|
|
42
|
+
// Log error to console
|
|
43
|
+
console.error(
|
|
44
|
+
`❌ [${level.toUpperCase()} ERROR]${componentName ? ` in ${componentName}` : ''}:`,
|
|
45
|
+
error,
|
|
46
|
+
errorInfo
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Call custom error handler if provided
|
|
50
|
+
if (onError) {
|
|
51
|
+
onError(error, errorInfo);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Update state with error info
|
|
55
|
+
this.setState({
|
|
56
|
+
errorInfo,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
render() {
|
|
61
|
+
const { hasError, error, errorInfo } = this.state;
|
|
62
|
+
const { children, fallback, componentName, level = 'component' } = this.props;
|
|
63
|
+
|
|
64
|
+
if (hasError && error) {
|
|
65
|
+
// Use custom fallback if provided
|
|
66
|
+
if (fallback) {
|
|
67
|
+
return fallback(error, errorInfo);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Default fallback UI
|
|
71
|
+
if (level === 'page') {
|
|
72
|
+
return h('div', {
|
|
73
|
+
style: {
|
|
74
|
+
padding: '40px',
|
|
75
|
+
maxWidth: '800px',
|
|
76
|
+
margin: '0 auto',
|
|
77
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
h('div', {
|
|
81
|
+
style: {
|
|
82
|
+
background: '#fee',
|
|
83
|
+
border: '2px solid #fcc',
|
|
84
|
+
borderRadius: '8px',
|
|
85
|
+
padding: '24px',
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
h('h2', {
|
|
89
|
+
style: {
|
|
90
|
+
margin: '0 0 16px 0',
|
|
91
|
+
color: '#c00',
|
|
92
|
+
fontSize: '24px',
|
|
93
|
+
fontWeight: '600',
|
|
94
|
+
}
|
|
95
|
+
}, '⚠️ Page Rendering Error'),
|
|
96
|
+
h('p', {
|
|
97
|
+
style: {
|
|
98
|
+
margin: '0 0 16px 0',
|
|
99
|
+
color: '#666',
|
|
100
|
+
fontSize: '16px',
|
|
101
|
+
lineHeight: '1.5',
|
|
102
|
+
}
|
|
103
|
+
}, 'An error occurred while rendering this page. Please check your JSON configuration.'),
|
|
104
|
+
h('div', {
|
|
105
|
+
style: {
|
|
106
|
+
background: '#fff',
|
|
107
|
+
border: '1px solid #ddd',
|
|
108
|
+
borderRadius: '4px',
|
|
109
|
+
padding: '16px',
|
|
110
|
+
marginBottom: '16px',
|
|
111
|
+
fontFamily: 'monospace',
|
|
112
|
+
fontSize: '14px',
|
|
113
|
+
color: '#c00',
|
|
114
|
+
overflowX: 'auto',
|
|
115
|
+
}
|
|
116
|
+
}, error.message),
|
|
117
|
+
process.env.NODE_ENV === 'development' && errorInfo?.componentStack && h('details', {
|
|
118
|
+
style: {
|
|
119
|
+
marginTop: '16px',
|
|
120
|
+
fontSize: '14px',
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
h('summary', {
|
|
124
|
+
style: {
|
|
125
|
+
cursor: 'pointer',
|
|
126
|
+
color: '#666',
|
|
127
|
+
fontWeight: '600',
|
|
128
|
+
marginBottom: '8px',
|
|
129
|
+
}
|
|
130
|
+
}, 'Stack Trace'),
|
|
131
|
+
h('pre', {
|
|
132
|
+
style: {
|
|
133
|
+
background: '#f5f5f5',
|
|
134
|
+
border: '1px solid #ddd',
|
|
135
|
+
borderRadius: '4px',
|
|
136
|
+
padding: '12px',
|
|
137
|
+
fontSize: '12px',
|
|
138
|
+
overflowX: 'auto',
|
|
139
|
+
color: '#333',
|
|
140
|
+
}
|
|
141
|
+
}, errorInfo.componentStack)
|
|
142
|
+
),
|
|
143
|
+
h('button', {
|
|
144
|
+
onClick: () => window.location.reload(),
|
|
145
|
+
style: {
|
|
146
|
+
marginTop: '16px',
|
|
147
|
+
padding: '10px 20px',
|
|
148
|
+
background: '#007acc',
|
|
149
|
+
color: '#fff',
|
|
150
|
+
border: 'none',
|
|
151
|
+
borderRadius: '4px',
|
|
152
|
+
fontSize: '14px',
|
|
153
|
+
fontWeight: '600',
|
|
154
|
+
cursor: 'pointer',
|
|
155
|
+
}
|
|
156
|
+
}, '🔄 Reload Page')
|
|
157
|
+
)
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Component-level error
|
|
162
|
+
return h('div', {
|
|
163
|
+
style: {
|
|
164
|
+
border: '2px dashed #fcc',
|
|
165
|
+
borderRadius: '4px',
|
|
166
|
+
padding: '12px',
|
|
167
|
+
margin: '8px 0',
|
|
168
|
+
background: '#fef5f5',
|
|
169
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
h('div', {
|
|
173
|
+
style: {
|
|
174
|
+
display: 'flex',
|
|
175
|
+
alignItems: 'center',
|
|
176
|
+
gap: '8px',
|
|
177
|
+
marginBottom: '8px',
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
h('span', {
|
|
181
|
+
style: {
|
|
182
|
+
fontSize: '18px',
|
|
183
|
+
}
|
|
184
|
+
}, '⚠️'),
|
|
185
|
+
h('strong', {
|
|
186
|
+
style: {
|
|
187
|
+
color: '#c00',
|
|
188
|
+
fontSize: '14px',
|
|
189
|
+
}
|
|
190
|
+
}, `Component Error${componentName ? `: ${componentName}` : ''}`)
|
|
191
|
+
),
|
|
192
|
+
h('div', {
|
|
193
|
+
style: {
|
|
194
|
+
fontSize: '12px',
|
|
195
|
+
color: '#666',
|
|
196
|
+
fontFamily: 'monospace',
|
|
197
|
+
background: '#fff',
|
|
198
|
+
padding: '8px',
|
|
199
|
+
borderRadius: '4px',
|
|
200
|
+
border: '1px solid #fcc',
|
|
201
|
+
}
|
|
202
|
+
}, error.message)
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return children;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Higher-order function to wrap component building with error boundary
|
|
212
|
+
*/
|
|
213
|
+
export function withErrorBoundary<T>(
|
|
214
|
+
renderFn: () => T,
|
|
215
|
+
options: {
|
|
216
|
+
componentName?: string;
|
|
217
|
+
level?: 'component' | 'page';
|
|
218
|
+
onError?: (error: Error, errorInfo: any) => void;
|
|
219
|
+
} = {}
|
|
220
|
+
): T | ReturnType<typeof h> {
|
|
221
|
+
try {
|
|
222
|
+
return renderFn();
|
|
223
|
+
} catch (error) {
|
|
224
|
+
return h(ErrorBoundary, {
|
|
225
|
+
...options,
|
|
226
|
+
children: null,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { test, expect, describe, beforeEach } from "bun:test";
|
|
2
|
+
import { ComponentRegistry } from "./componentRegistry";
|
|
3
|
+
|
|
4
|
+
describe("ComponentRegistry", () => {
|
|
5
|
+
let registry: ComponentRegistry;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
registry = new ComponentRegistry();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe("register and get", () => {
|
|
12
|
+
test("should register and retrieve a component", () => {
|
|
13
|
+
const buttonDef = {
|
|
14
|
+
type: "button",
|
|
15
|
+
props: { className: "btn" }
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
registry.register("Button", buttonDef);
|
|
19
|
+
const retrieved = registry.get("Button");
|
|
20
|
+
|
|
21
|
+
expect(retrieved).toEqual(buttonDef);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("should return undefined for non-existent component", () => {
|
|
25
|
+
const result = registry.get("NonExistent");
|
|
26
|
+
expect(result).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("should overwrite existing component with same name", () => {
|
|
30
|
+
registry.register("Button", { type: "button", props: { color: "red" } });
|
|
31
|
+
registry.register("Button", { type: "button", props: { color: "blue" } });
|
|
32
|
+
|
|
33
|
+
const result = registry.get("Button");
|
|
34
|
+
expect(result?.props.color).toBe("blue");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("has", () => {
|
|
39
|
+
test("should return true for registered component", () => {
|
|
40
|
+
registry.register("Card", { type: "div" });
|
|
41
|
+
expect(registry.has("Card")).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("should return false for non-existent component", () => {
|
|
45
|
+
expect(registry.has("NonExistent")).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("clear", () => {
|
|
50
|
+
test("should clear all components", () => {
|
|
51
|
+
registry.register("Button", { type: "button" });
|
|
52
|
+
registry.register("Card", { type: "div" });
|
|
53
|
+
|
|
54
|
+
expect(registry.getNames().length).toBe(2);
|
|
55
|
+
|
|
56
|
+
registry.clear();
|
|
57
|
+
|
|
58
|
+
expect(registry.getNames().length).toBe(0);
|
|
59
|
+
expect(registry.has("Button")).toBe(false);
|
|
60
|
+
expect(registry.has("Card")).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("merge", () => {
|
|
65
|
+
test("should merge multiple components", () => {
|
|
66
|
+
registry.register("Button", { type: "button" });
|
|
67
|
+
|
|
68
|
+
registry.merge({
|
|
69
|
+
Card: { type: "div" },
|
|
70
|
+
Link: { type: "a" }
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(registry.getNames().length).toBe(3);
|
|
74
|
+
expect(registry.has("Button")).toBe(true);
|
|
75
|
+
expect(registry.has("Card")).toBe(true);
|
|
76
|
+
expect(registry.has("Link")).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("should overwrite existing components when merging", () => {
|
|
80
|
+
registry.register("Button", { type: "button", props: { color: "red" } });
|
|
81
|
+
|
|
82
|
+
registry.merge({
|
|
83
|
+
Button: { type: "button", props: { color: "blue" } }
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const button = registry.get("Button");
|
|
87
|
+
expect(button?.props.color).toBe("blue");
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("getAll", () => {
|
|
92
|
+
test("should return all registered components", () => {
|
|
93
|
+
registry.register("Button", { type: "button" });
|
|
94
|
+
registry.register("Card", { type: "div" });
|
|
95
|
+
|
|
96
|
+
const all = registry.getAll();
|
|
97
|
+
|
|
98
|
+
expect(Object.keys(all).length).toBe(2);
|
|
99
|
+
expect(all.Button).toBeDefined();
|
|
100
|
+
expect(all.Card).toBeDefined();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("should return a copy, not the original registry", () => {
|
|
104
|
+
registry.register("Button", { type: "button" });
|
|
105
|
+
|
|
106
|
+
const all = registry.getAll();
|
|
107
|
+
all.NewComponent = { type: "span" };
|
|
108
|
+
|
|
109
|
+
expect(registry.has("NewComponent")).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe("getNames", () => {
|
|
114
|
+
test("should return list of component names", () => {
|
|
115
|
+
registry.register("Button", { type: "button" });
|
|
116
|
+
registry.register("Card", { type: "div" });
|
|
117
|
+
|
|
118
|
+
const names = registry.getNames();
|
|
119
|
+
|
|
120
|
+
expect(names).toEqual(["Button", "Card"]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("should return empty array when no components", () => {
|
|
124
|
+
const names = registry.getNames();
|
|
125
|
+
expect(names).toEqual([]);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("remove", () => {
|
|
130
|
+
test("should remove a component and return true", () => {
|
|
131
|
+
registry.register("Button", { type: "button" });
|
|
132
|
+
|
|
133
|
+
const result = registry.remove("Button");
|
|
134
|
+
|
|
135
|
+
expect(result).toBe(true);
|
|
136
|
+
expect(registry.has("Button")).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("should return false when removing non-existent component", () => {
|
|
140
|
+
const result = registry.remove("NonExistent");
|
|
141
|
+
expect(result).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe("component with variants", () => {
|
|
146
|
+
test("should handle components with variant definitions", () => {
|
|
147
|
+
const buttonDef = {
|
|
148
|
+
type: "button",
|
|
149
|
+
variants: {
|
|
150
|
+
primary: { backgroundColor: "blue", color: "white" },
|
|
151
|
+
secondary: { backgroundColor: "gray", color: "black" }
|
|
152
|
+
},
|
|
153
|
+
defaultVariant: "primary"
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
registry.register("Button", buttonDef);
|
|
157
|
+
const retrieved = registry.get("Button");
|
|
158
|
+
|
|
159
|
+
expect(retrieved?.variants?.primary).toBeDefined();
|
|
160
|
+
expect(retrieved?.variants?.primary?.backgroundColor).toBe("blue");
|
|
161
|
+
expect(retrieved?.defaultVariant).toBe("primary");
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component Registry
|
|
3
|
+
* Manages registration and retrieval of custom components
|
|
4
|
+
*
|
|
5
|
+
* This now extends the base registry for consistency across client and SSR
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { BaseComponentRegistry } from '../shared/registry/ComponentRegistry';
|
|
9
|
+
import type { ComponentDefinition } from '../shared/types';
|
|
10
|
+
|
|
11
|
+
export class ComponentRegistry extends BaseComponentRegistry {
|
|
12
|
+
// Client-specific methods can be added here
|
|
13
|
+
// For now, it uses all functionality from BaseComponentRegistry
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Export a singleton instance for convenience
|
|
17
|
+
export const globalComponentRegistry = new ComponentRegistry();
|
|
18
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Context
|
|
3
|
+
* Provides theme switching functionality to components
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { createContext, useContext, useState, useEffect } from 'react';
|
|
7
|
+
import { useThemes } from '../hooks/useColorVariables';
|
|
8
|
+
|
|
9
|
+
interface ThemeContextType {
|
|
10
|
+
currentTheme: string;
|
|
11
|
+
setTheme: (themeName: string) => void;
|
|
12
|
+
themes: string[];
|
|
13
|
+
loading: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Theme Provider Component
|
|
20
|
+
* Wraps the app and provides theme context to all children
|
|
21
|
+
*/
|
|
22
|
+
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|
23
|
+
const { themes, defaultTheme, loading } = useThemes();
|
|
24
|
+
const [currentTheme, setCurrentTheme] = useState<string>('dark');
|
|
25
|
+
|
|
26
|
+
// Initialize theme from defaultTheme or localStorage
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (defaultTheme) {
|
|
29
|
+
const savedTheme = localStorage.getItem('theme');
|
|
30
|
+
const themeToUse = savedTheme || defaultTheme;
|
|
31
|
+
setCurrentTheme(themeToUse);
|
|
32
|
+
applyTheme(themeToUse);
|
|
33
|
+
}
|
|
34
|
+
}, [defaultTheme]);
|
|
35
|
+
|
|
36
|
+
const setTheme = (themeName: string) => {
|
|
37
|
+
setCurrentTheme(themeName);
|
|
38
|
+
localStorage.setItem('theme', themeName);
|
|
39
|
+
applyTheme(themeName);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const applyTheme = (themeName: string) => {
|
|
43
|
+
// Set the theme attribute on the root element
|
|
44
|
+
const root = document.documentElement;
|
|
45
|
+
root.setAttribute('theme', themeName);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const themeNames = themes?.map(theme => theme.name) || [];
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<ThemeContext.Provider
|
|
52
|
+
value={{
|
|
53
|
+
currentTheme,
|
|
54
|
+
setTheme,
|
|
55
|
+
themes: themeNames,
|
|
56
|
+
loading,
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
{children}
|
|
60
|
+
</ThemeContext.Provider>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Hook for using the theme context
|
|
66
|
+
*/
|
|
67
|
+
export function useTheme() {
|
|
68
|
+
const context = useContext(ThemeContext);
|
|
69
|
+
if (context === undefined) {
|
|
70
|
+
throw new Error('useTheme must be used within a ThemeProvider');
|
|
71
|
+
}
|
|
72
|
+
return context;
|
|
73
|
+
}
|