@developer_tribe/react-builder 1.2.12 → 1.2.13

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 (38) hide show
  1. package/dist/build-components/PaywallBackground/PaywallBackgroundProps.generated.d.ts +3 -0
  2. package/dist/build-components/patterns.generated.d.ts +5289 -1965
  3. package/dist/components/BottomBar.d.ts +3 -1
  4. package/dist/index.cjs.js +4 -4
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.esm.js +4 -4
  7. package/dist/index.esm.js.map +1 -1
  8. package/dist/index.web.cjs.js +5 -5
  9. package/dist/index.web.cjs.js.map +1 -1
  10. package/dist/index.web.d.ts +1 -0
  11. package/dist/index.web.esm.js +5 -5
  12. package/dist/index.web.esm.js.map +1 -1
  13. package/dist/pages/DebugJsonPage.d.ts +17 -0
  14. package/dist/pages/projectPageUtils.d.ts +7 -0
  15. package/dist/styles.css +1 -1
  16. package/dist/utils/logRenderStore.d.ts +6 -0
  17. package/package.json +2 -1
  18. package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +28 -11
  19. package/src/AttributesEditor.tsx +1 -0
  20. package/src/assets/meta.json +1 -1
  21. package/src/assets/samples/paywall-1.json +159 -150
  22. package/src/assets/samples/simple-1.json +3 -3
  23. package/src/build-components/BackgroundImage/BackgroundImage.tsx +1 -1
  24. package/src/build-components/BackgroundImage/pattern.json +12 -0
  25. package/src/build-components/PaywallBackground/PaywallBackground.tsx +3 -33
  26. package/src/build-components/PaywallBackground/PaywallBackgroundProps.generated.ts +3 -0
  27. package/src/build-components/PaywallBackground/pattern.json +15 -5
  28. package/src/build-components/Text/pattern.json +39 -38
  29. package/src/build-components/patterns.generated.ts +5289 -1951
  30. package/src/components/BottomBar.tsx +21 -60
  31. package/src/index.web.ts +1 -0
  32. package/src/pages/DebugJsonPage.tsx +135 -0
  33. package/src/pages/ProjectPage.tsx +17 -5
  34. package/src/pages/projectPageUtils.ts +14 -0
  35. package/src/pages/tabs/SideTool.tsx +8 -23
  36. package/src/styles/modals/_localication-modal.scss +6 -0
  37. package/src/utils/logRenderStore.ts +128 -0
  38. package/src/utils/patterns.ts +26 -3
@@ -2,23 +2,28 @@ import React, { useMemo, useState } from 'react';
2
2
  import { Icon } from './Icon.generated';
3
3
  import type { IconsType } from '../types/Icons';
4
4
  import { useRenderStore } from '../store';
5
- import { Checkbox } from './Checkbox';
6
5
  import type { Localication } from '../types/PreviewConfig';
7
6
  import { LocalicationModal, Modal, ScreenColorsModal } from '../modals';
8
- import { JsonTextEditor } from './JsonTextEditor';
9
7
  import type { Node } from '../types/Node';
10
- import { analyseAndProccess } from '../utils/analyseNode';
8
+ import type { Project } from '../types/Project';
9
+ import { DebugJsonPage } from '../pages/DebugJsonPage';
11
10
 
12
11
  type BottomBarProps = {
13
12
  className?: string;
14
13
  data: Node;
15
14
  setData: React.Dispatch<React.SetStateAction<Node>>;
15
+ project?: Project;
16
16
  };
17
17
 
18
18
  /**
19
19
  * Bottom tool bar (Figma-like).
20
20
  */
21
- export function BottomBar({ className, data, setData }: BottomBarProps) {
21
+ export function BottomBar({
22
+ className,
23
+ data,
24
+ setData,
25
+ project,
26
+ }: BottomBarProps) {
22
27
  const rtlIcon: IconsType = 'translate';
23
28
  const magicCursorIcon: IconsType = 'magicpen';
24
29
  const debugIcon: IconsType = 'speedometer-03';
@@ -179,62 +184,18 @@ export function BottomBar({ className, data, setData }: BottomBarProps) {
179
184
  className="modal--large modal--scrollable"
180
185
  contentClassName="localication-modal__content"
181
186
  >
182
- <div className="modal__header localication-modal__header">
183
- <div className="localication-modal__header-main">
184
- <h3 id="debug-json-editor-title" className="modal__title">
185
- Debug JSON
186
- </h3>
187
- <p className="localication-modal__description">
188
- Inspect and edit raw node JSON.
189
- </p>
190
- </div>
191
- <button
192
- type="button"
193
- className="editor-button"
194
- onClick={() => setIsDebugOpen(false)}
195
- >
196
- Close
197
- </button>
198
- </div>
199
- <div className="localication-modal__body">
200
- <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
201
- <Checkbox
202
- label="Preview mode"
203
- checked={previewMode}
204
- onChange={setPreviewMode}
205
- />
206
- <Checkbox
207
- label="Dark Mode"
208
- checked={appConfig.theme === 'dark'}
209
- onChange={(checked) =>
210
- setAppConfig({
211
- ...appConfig,
212
- theme: checked ? 'dark' : 'light',
213
- })
214
- }
215
- />
216
- <Checkbox
217
- label="Is RTL"
218
- checked={appConfig.isRtl ?? false}
219
- onChange={(checked) =>
220
- setAppConfig({ ...appConfig, isRtl: checked })
221
- }
222
- />
223
- </div>
224
- <div
225
- className="localication-modal__editor"
226
- style={{ marginTop: 12 }}
227
- >
228
- <JsonTextEditor
229
- rootName="node"
230
- value={data ?? {}}
231
- onChange={(next) =>
232
- setData(analyseAndProccess(next as Node) as Node)
233
- }
234
- className="localication-modal__json-editor"
235
- />
236
- </div>
237
- </div>
187
+ <DebugJsonPage
188
+ project={project}
189
+ data={data}
190
+ setData={setData}
191
+ onClose={() => setIsDebugOpen(false)}
192
+ description="Inspect and edit raw node JSON."
193
+ previewMode={previewMode}
194
+ setPreviewMode={setPreviewMode}
195
+ appConfig={appConfig}
196
+ setAppConfig={setAppConfig}
197
+ logLabel="BottomBar Debug JSON"
198
+ />
238
199
  </Modal>
239
200
  )}
240
201
  </>
package/src/index.web.ts CHANGED
@@ -2,6 +2,7 @@ import './styles/index.scss';
2
2
 
3
3
  // Web entrypoint.
4
4
  export { ProjectPage } from './pages/ProjectPage';
5
+ export { DebugJsonPage } from './pages/DebugJsonPage';
5
6
 
6
7
  // Re-export the RN-safe/root entry so `react-builder/web` can also access shared APIs.
7
8
  export * from './index';
@@ -0,0 +1,135 @@
1
+ import React from 'react';
2
+ import type { Node } from '../types/Node';
3
+ import type { AppConfig } from '../types/PreviewConfig';
4
+ import { Checkbox } from '../components/Checkbox';
5
+ import { JsonTextEditor } from '../components/JsonTextEditor';
6
+ import { analyseAndProccess } from '../utils/analyseNode';
7
+ import { logRenderStore } from '../utils/logRenderStore';
8
+
9
+ export type DebugJsonPageProps = {
10
+ data: Node | null | undefined;
11
+ setData: React.Dispatch<React.SetStateAction<Node>>;
12
+ project?: unknown;
13
+ onClose?: () => void;
14
+ title?: string;
15
+ description?: string;
16
+
17
+ previewMode?: boolean;
18
+ setPreviewMode?: (next: boolean) => void;
19
+
20
+ appConfig?: AppConfig;
21
+ setAppConfig?: (next: AppConfig) => void;
22
+
23
+ logLabel?: string;
24
+ };
25
+
26
+ export function DebugJsonPage({
27
+ data,
28
+ setData,
29
+ project,
30
+ onClose,
31
+ title = 'Debug JSON',
32
+ description,
33
+ previewMode,
34
+ setPreviewMode,
35
+ appConfig,
36
+ setAppConfig,
37
+ logLabel,
38
+ }: DebugJsonPageProps) {
39
+ const canTogglePreviewMode = typeof setPreviewMode === 'function';
40
+ const canToggleTheme =
41
+ typeof setAppConfig === 'function' && typeof appConfig?.theme === 'string';
42
+ const canToggleRtl =
43
+ typeof setAppConfig === 'function' &&
44
+ typeof (appConfig as any)?.isRtl !== 'undefined';
45
+
46
+ return (
47
+ <>
48
+ <div className="modal__header localication-modal__header">
49
+ <div className="localication-modal__header-main">
50
+ <h3 id="debug-json-editor-title" className="modal__title">
51
+ {title}
52
+ </h3>
53
+ {description ? (
54
+ <p className="localication-modal__description">{description}</p>
55
+ ) : null}
56
+ </div>
57
+
58
+ <button
59
+ type="button"
60
+ className="editor-button"
61
+ title="Log render store to console"
62
+ onClick={() =>
63
+ logRenderStore({
64
+ label: logLabel,
65
+ extra: {
66
+ project: project ?? null,
67
+ projectData: data ?? null,
68
+ },
69
+ })
70
+ }
71
+ >
72
+ Log store
73
+ </button>
74
+
75
+ {onClose ? (
76
+ <button
77
+ type="button"
78
+ className="editor-button"
79
+ onClick={() => onClose()}
80
+ >
81
+ Close
82
+ </button>
83
+ ) : null}
84
+ </div>
85
+
86
+ <div className="localication-modal__body">
87
+ {canTogglePreviewMode || canToggleTheme || canToggleRtl ? (
88
+ <div className="localication-modal__controls">
89
+ {canTogglePreviewMode ? (
90
+ <Checkbox
91
+ label="Preview mode"
92
+ checked={Boolean(previewMode)}
93
+ onChange={setPreviewMode!}
94
+ />
95
+ ) : null}
96
+
97
+ {canToggleTheme ? (
98
+ <Checkbox
99
+ label="Dark Mode"
100
+ checked={(appConfig?.theme ?? 'light') === 'dark'}
101
+ onChange={(checked) =>
102
+ setAppConfig!({
103
+ ...(appConfig as AppConfig),
104
+ theme: checked ? 'dark' : 'light',
105
+ })
106
+ }
107
+ />
108
+ ) : null}
109
+
110
+ {canToggleRtl ? (
111
+ <Checkbox
112
+ label="Is RTL"
113
+ checked={Boolean((appConfig as any)?.isRtl)}
114
+ onChange={(checked) =>
115
+ setAppConfig!({ ...(appConfig as AppConfig), isRtl: checked })
116
+ }
117
+ />
118
+ ) : null}
119
+ </div>
120
+ ) : null}
121
+
122
+ <div className="localication-modal__editor">
123
+ <JsonTextEditor
124
+ rootName="node"
125
+ value={data ?? ({} as any)}
126
+ onChange={(next) =>
127
+ setData(analyseAndProccess(next as Node) as Node)
128
+ }
129
+ className="localication-modal__json-editor"
130
+ />
131
+ </div>
132
+ </div>
133
+ </>
134
+ );
135
+ }
@@ -35,6 +35,7 @@ import {
35
35
  } from '../utils/nodeTree';
36
36
  import type { Fonts } from '../types/Fonts';
37
37
  import { useProjectFonts } from '../hooks/useProjectFonts';
38
+ import { resolveProjectForSave } from './projectPageUtils';
38
39
  export type ProjectPageProps = {
39
40
  project: Project;
40
41
  onSaveProject: (project: Project) => void;
@@ -101,6 +102,7 @@ export function ProjectPage({
101
102
  previewMode: s.previewMode,
102
103
  }));
103
104
  const resolvedAppConfig = appConfig ?? storeAppConfig ?? defaultAppConfig;
105
+ const [overrideProject, setOverrideProject] = useState<Project | null>(null);
104
106
  const [editorData, setEditorData] = useState<Node>(() => {
105
107
  if (!isEmptyProjectData) return null;
106
108
  // Empty project should start in a usable state (no loader / no error).
@@ -161,6 +163,7 @@ export function ProjectPage({
161
163
 
162
164
  useEffect(() => {
163
165
  logger.info('ProjectPage', 'mount', { projectName: project.name });
166
+ setOverrideProject(null);
164
167
  if (appConfig) {
165
168
  setAppConfig(appConfig);
166
169
  logger.verbose('ProjectPage', 'appConfig applied', appConfig);
@@ -290,10 +293,13 @@ export function ProjectPage({
290
293
  if (onSaveProjectColors && resolvedProjectColors) {
291
294
  onSaveProjectColors(resolvedProjectColors);
292
295
  }
293
- onSaveProject({
294
- ...project,
295
- data: editorData,
296
- });
296
+ onSaveProject(
297
+ resolveProjectForSave({
298
+ project,
299
+ overrideProject,
300
+ data: editorData,
301
+ }),
302
+ );
297
303
  toast.success('Saved');
298
304
  } catch (e) {
299
305
  logger.error('ProjectPage', 'save project failed', e);
@@ -302,6 +308,7 @@ export function ProjectPage({
302
308
  }}
303
309
  onRestoreProject={() => {
304
310
  logger.info('ProjectPage', 'restore project', { name: project.name });
311
+ setOverrideProject(null);
305
312
  setValidationError(null);
306
313
  setValidationErrorStack(null);
307
314
  setBypassValidation(false);
@@ -341,6 +348,7 @@ export function ProjectPage({
341
348
  const { project: migratedProject } =
342
349
  runProjectMigrations(project);
343
350
  onSaveProject(migratedProject);
351
+ setOverrideProject(migratedProject);
344
352
  setBypassValidation(true);
345
353
  setMigrationGate(null);
346
354
  setEditorData(migratedProject.data as unknown as Node);
@@ -494,7 +502,11 @@ export function ProjectPage({
494
502
  )}
495
503
  </div>
496
504
  {/* BOTOM BAR */}
497
- <BottomBar data={editorData} setData={setEditorData} />
505
+ <BottomBar
506
+ project={project}
507
+ data={editorData}
508
+ setData={setEditorData}
509
+ />
498
510
  <div
499
511
  id="split-attributes-panel"
500
512
  className={`split-third${attributesPanelIsOpen ? ' is-open' : ''}`}
@@ -0,0 +1,14 @@
1
+ import type { Project } from '../types/Project';
2
+ import type { Node } from '../types/Node';
3
+
4
+ export function resolveProjectForSave(args: {
5
+ project: Project;
6
+ overrideProject?: Project | null;
7
+ data: Node;
8
+ }): Project {
9
+ const base = args.overrideProject ?? args.project;
10
+ return {
11
+ ...base,
12
+ data: args.data,
13
+ };
14
+ }
@@ -1,5 +1,4 @@
1
1
  import React, { useEffect, useState } from 'react';
2
- import { JsonTextEditor } from '../../components/JsonTextEditor';
3
2
  import { Modal } from '../../modals';
4
3
  import type { Node } from '../../types/Node';
5
4
  import type { Localication } from '../../types/PreviewConfig';
@@ -7,7 +6,7 @@ import { useLogRender } from '../../utils/useLogRender';
7
6
  import { useRenderStore } from '../../store';
8
7
  import { Checkbox } from '../../components/Checkbox';
9
8
  import { LocalicationModal } from '../../modals/LocalicationModal';
10
- import { analyseAndProccess } from '../../utils/analyseNode';
9
+ import { DebugJsonPage } from '../DebugJsonPage';
11
10
 
12
11
  const screenStyleDefaults = {
13
12
  light: { backgroundColor: '#FDFDFD', color: '#161827' },
@@ -215,27 +214,13 @@ export function SideTool({ data, setData }: SideToolProps) {
215
214
  className="modal--large modal--scrollable"
216
215
  contentClassName="localication-modal__content"
217
216
  >
218
- <div className="modal__header localication-modal__header">
219
- <button
220
- type="button"
221
- className="editor-button"
222
- onClick={() => setIsDebugModalOpen(false)}
223
- >
224
- Close
225
- </button>
226
- </div>
227
- <div className="localication-modal__body">
228
- <div className="localication-modal__editor">
229
- <JsonTextEditor
230
- rootName="node"
231
- value={data ?? {}}
232
- onChange={(next) =>
233
- setData(analyseAndProccess(next as Node) as Node)
234
- }
235
- className="localication-modal__json-editor"
236
- />
237
- </div>
238
- </div>
217
+ <DebugJsonPage
218
+ data={data}
219
+ setData={setData}
220
+ onClose={() => setIsDebugModalOpen(false)}
221
+ description="Inspect and edit raw node JSON."
222
+ logLabel="SideTool Debug JSON"
223
+ />
239
224
  </Modal>
240
225
  )}
241
226
 
@@ -42,6 +42,12 @@
42
42
  overflow: auto;
43
43
  }
44
44
 
45
+ .localication-modal__controls {
46
+ display: flex;
47
+ flex-direction: column;
48
+ gap: 12px;
49
+ }
50
+
45
51
  .localication-modal__editor {
46
52
  flex: 1;
47
53
  min-height: 0;
@@ -0,0 +1,128 @@
1
+ import { useRenderStore } from '../store';
2
+
3
+ export type LogRenderStoreOptions = {
4
+ label?: string;
5
+ includeLocalStorage?: boolean;
6
+ extra?: Record<string, unknown>;
7
+ };
8
+
9
+ function isFn(value: unknown): value is (...args: unknown[]) => unknown {
10
+ return typeof value === 'function';
11
+ }
12
+
13
+ function safeStringify(value: unknown): string {
14
+ try {
15
+ const seen = new WeakSet<object>();
16
+ return JSON.stringify(
17
+ value,
18
+ (_key, v) => {
19
+ if (typeof v === 'function') {
20
+ const name = (v as Function).name;
21
+ return `[Function${name ? `: ${name}` : ''}]`;
22
+ }
23
+ if (typeof v === 'bigint') return v.toString();
24
+ if (v instanceof Error) {
25
+ return { name: v.name, message: v.message, stack: v.stack };
26
+ }
27
+ if (v instanceof Map) {
28
+ return { __type: 'Map', entries: Array.from(v.entries()) };
29
+ }
30
+ if (v instanceof Set) {
31
+ return { __type: 'Set', values: Array.from(v.values()) };
32
+ }
33
+ if (typeof v === 'object' && v !== null) {
34
+ if (seen.has(v as object)) return '[Circular]';
35
+ seen.add(v as object);
36
+ }
37
+ return v;
38
+ },
39
+ 2,
40
+ );
41
+ } catch (e) {
42
+ return `<< Unable to stringify value: ${String(e)} >>`;
43
+ }
44
+ }
45
+
46
+ function safeParseJson(text: unknown): unknown {
47
+ if (typeof text !== 'string' || !text.trim()) return null;
48
+ try {
49
+ return JSON.parse(text);
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+
55
+ function safeGetPersistedRenderStore(): unknown {
56
+ try {
57
+ if (typeof localStorage === 'undefined') return null;
58
+ return localStorage.getItem('render-store');
59
+ } catch (e) {
60
+ return { error: String(e) };
61
+ }
62
+ }
63
+
64
+ export function logRenderStore(options: LogRenderStoreOptions = {}): void {
65
+ const { label, includeLocalStorage = true, extra } = options;
66
+
67
+ const state = useRenderStore.getState();
68
+ const keys = Object.keys(state as Record<string, unknown>).sort();
69
+
70
+ const snapshot: Record<string, unknown> = {};
71
+ const actions: string[] = [];
72
+
73
+ for (const key of keys) {
74
+ const v = (state as any)[key];
75
+ if (isFn(v)) actions.push(key);
76
+ else snapshot[key] = v;
77
+ }
78
+
79
+ const title = `[RB Debug] Render store${label ? ` (${label})` : ''}`;
80
+ const groupCollapsed = (console as any).groupCollapsed as unknown;
81
+ const groupEnd = (console as any).groupEnd as unknown;
82
+
83
+ if (isFn(groupCollapsed)) groupCollapsed(title);
84
+ else {
85
+ // eslint-disable-next-line no-console
86
+ console.log(title);
87
+ }
88
+
89
+ const persistedRaw = includeLocalStorage
90
+ ? safeGetPersistedRenderStore()
91
+ : null;
92
+ const persistedParsed = includeLocalStorage
93
+ ? safeParseJson(persistedRaw)
94
+ : null;
95
+ const json = safeStringify({
96
+ snapshot,
97
+ actions,
98
+ extra: extra ?? null,
99
+ persisted: includeLocalStorage
100
+ ? { raw: persistedRaw, parsed: persistedParsed }
101
+ : null,
102
+ });
103
+
104
+ // NOTE: Logging objects in devtools can be confusing because they are "live" references.
105
+ // This JSON string is a stable snapshot of this click, and is copy/paste-friendly.
106
+ // eslint-disable-next-line no-console
107
+ console.log('json', json);
108
+
109
+ // eslint-disable-next-line no-console
110
+ console.log('state', snapshot);
111
+ // eslint-disable-next-line no-console
112
+ console.log('actions', actions);
113
+ // eslint-disable-next-line no-console
114
+ console.log('rawState (includes functions)', state);
115
+
116
+ if (includeLocalStorage) {
117
+ // eslint-disable-next-line no-console
118
+ console.log('localStorage.render-store (raw)', persistedRaw);
119
+ // eslint-disable-next-line no-console
120
+ console.log('localStorage.render-store (parsed)', persistedParsed);
121
+ }
122
+ if (extra) {
123
+ // eslint-disable-next-line no-console
124
+ console.log('extra', extra);
125
+ }
126
+
127
+ if (isFn(groupEnd)) groupEnd();
128
+ }
@@ -211,14 +211,37 @@ export function getAttributeMeta(
211
211
  const styles = p?.meta?.styles;
212
212
  const attributes = p?.meta?.attributes;
213
213
 
214
+ // Some patterns store style meta under `meta.attributes.styles` (nested) instead of `meta.styles`.
215
+ const nestedStyles =
216
+ attributes &&
217
+ typeof attributes === 'object' &&
218
+ (attributes as any).styles &&
219
+ typeof (attributes as any).styles === 'object'
220
+ ? ((attributes as any).styles as Record<string, AttributeMeta>)
221
+ : undefined;
222
+
223
+ // When `meta.attributes.styles` exists, treat `meta.attributes` (minus `styles`) as non-style meta.
224
+ const attributesWithoutNestedStyles =
225
+ attributes && typeof attributes === 'object'
226
+ ? Object.fromEntries(
227
+ Object.entries(attributes as Record<string, unknown>).filter(
228
+ ([k]) => k !== 'styles',
229
+ ),
230
+ )
231
+ : attributes;
232
+
214
233
  // schemaVersion=2 prefers `meta.styles` but some repos split UI meta into:
215
234
  // - meta.styles: style-tab fields
216
235
  // - meta.attributes: non-style fields (container/other)
217
236
  // Merge both to keep the editor + platform adjustments working.
218
237
  const merged =
219
- styles && attributes
220
- ? { ...attributes, ...styles }
221
- : (styles ?? attributes);
238
+ styles || attributesWithoutNestedStyles || nestedStyles
239
+ ? {
240
+ ...(attributesWithoutNestedStyles as Record<string, AttributeMeta>),
241
+ ...(styles as Record<string, AttributeMeta>),
242
+ ...(nestedStyles as Record<string, AttributeMeta>),
243
+ }
244
+ : undefined;
222
245
 
223
246
  return adjustMetaForPlatform(merged, platform);
224
247
  }