@developer_tribe/react-builder 1.0.5 → 1.0.6

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 (66) hide show
  1. package/dist/build-components/index.d.ts +1 -2
  2. package/dist/build-components/patterns.generated.d.ts +56 -439
  3. package/dist/components/AttributesEditorPanel.d.ts +2 -2
  4. package/dist/components/BottomBar.d.ts +8 -0
  5. package/dist/components/Checkbox.d.ts +1 -1
  6. package/dist/components/LoadingComponent.d.ts +1 -0
  7. package/dist/components/MobilePanelToggleButton.d.ts +8 -0
  8. package/dist/hooks/useMinimumDelay.d.ts +7 -0
  9. package/dist/hooks/useMobileEditorPanels.d.ts +12 -0
  10. package/dist/hooks/useSyncProjectPageStore.d.ts +15 -0
  11. package/dist/index.cjs.js +3 -3
  12. package/dist/index.cjs.js.map +1 -1
  13. package/dist/index.esm.js +3 -3
  14. package/dist/index.esm.js.map +1 -1
  15. package/dist/index.native.cjs.js +1 -1
  16. package/dist/index.native.cjs.js.map +1 -1
  17. package/dist/index.native.esm.js +4 -4
  18. package/dist/index.native.esm.js.map +1 -1
  19. package/dist/modals/ScreenColorsModal.d.ts +8 -0
  20. package/dist/modals/index.d.ts +1 -0
  21. package/dist/pages/tabs/BuilderPanel.d.ts +2 -2
  22. package/dist/store.d.ts +6 -0
  23. package/dist/styles.css +1 -1
  24. package/dist/utils/nodeTree.d.ts +5 -0
  25. package/package.json +1 -1
  26. package/src/RenderPage.tsx +4 -1
  27. package/src/assets/samples/carousel-sample.json +99 -81
  28. package/src/assets/samples/simple-1.json +8 -2
  29. package/src/assets/samples/simple-2.json +36 -9
  30. package/src/assets/samples/vpn-onboard-1.json +27 -23
  31. package/src/assets/samples/vpn-onboard-2.json +279 -275
  32. package/src/assets/samples/vpn-onboard-3.json +247 -246
  33. package/src/assets/samples/vpn-onboard-4.json +247 -246
  34. package/src/assets/samples/vpn-onboard-5.json +375 -369
  35. package/src/assets/samples/vpn-onboard-6.json +252 -248
  36. package/src/build-components/RenderNode.generated.tsx +0 -7
  37. package/src/build-components/View/pattern.json +2 -2
  38. package/src/build-components/index.ts +0 -5
  39. package/src/build-components/patterns.generated.ts +56 -455
  40. package/src/components/AttributesEditorPanel.tsx +12 -8
  41. package/src/components/BottomBar.tsx +236 -0
  42. package/src/components/EditorHeader.tsx +11 -4
  43. package/src/components/LoadingComponent.tsx +10 -0
  44. package/src/components/MobilePanelToggleButton.tsx +39 -0
  45. package/src/hooks/useMinimumDelay.ts +20 -0
  46. package/src/hooks/useMobileEditorPanels.ts +56 -0
  47. package/src/hooks/useSyncProjectPageStore.ts +40 -0
  48. package/src/modals/ScreenColorsModal.tsx +115 -0
  49. package/src/modals/index.ts +1 -0
  50. package/src/pages/ProjectPage.tsx +53 -243
  51. package/src/pages/tabs/BuilderPanel.tsx +14 -8
  52. package/src/store.ts +10 -6
  53. package/src/styles/base/_global.scss +12 -4
  54. package/src/styles/components/_attributes-editor.scss +9 -1
  55. package/src/styles/components/_bottom-bar.scss +113 -0
  56. package/src/styles/components/_editor-shell.scss +0 -19
  57. package/src/styles/index.scss +1 -0
  58. package/src/utils/analyseNodeByPatterns.ts +15 -0
  59. package/src/utils/nodeTree.ts +99 -0
  60. package/dist/build-components/PaywallSubscriButton/PaywallSubscriButton.d.ts +0 -5
  61. package/dist/build-components/PaywallSubscriButton/PaywallSubscriButtonProps.generated.d.ts +0 -50
  62. package/dist/pages/tabs/SideTool.d.ts +0 -8
  63. package/src/build-components/PaywallSubscriButton/PaywallSubscriButton.tsx +0 -10
  64. package/src/build-components/PaywallSubscriButton/PaywallSubscriButtonProps.generated.ts +0 -77
  65. package/src/build-components/PaywallSubscriButton/pattern.json +0 -27
  66. package/src/pages/tabs/SideTool.tsx +0 -253
@@ -187,22 +187,3 @@
187
187
  background: #eef2ff;
188
188
  color: #111827;
189
189
  }
190
-
191
- .side-tool-container {
192
- position: absolute;
193
- top: 0;
194
- left: 0;
195
- z-index: 5;
196
- }
197
-
198
- .side-tool select {
199
- width: 100%;
200
- font-size: 11px;
201
- padding: 4px 6px;
202
- }
203
-
204
- .side-tool .debug-button {
205
- border-radius: 0;
206
- width: 100%;
207
- justify-content: center;
208
- }
@@ -13,6 +13,7 @@
13
13
  @use './components/editor-shell';
14
14
  @use './components/attributes-editor';
15
15
  @use './components/mockos-router';
16
+ @use './components/bottom-bar';
16
17
 
17
18
  @use './modals/modal-shell';
18
19
  @use './modals/add-component';
@@ -334,11 +334,26 @@ function validateAttributesByPattern(
334
334
  if (attrs == null) return ok();
335
335
  if (!isPlainObject(attrs)) return fail(`attributes must be an object`, path);
336
336
 
337
+ const componentType = normalizeTypeOrFallback(pattern.pattern.type);
338
+
337
339
  const schema: AttributeSchema = (getAttributeSchema(pattern.pattern.type) ??
338
340
  pattern.pattern.attributes ??
339
341
  {}) as AttributeSchema;
340
342
 
341
343
  for (const [attrName, attrValue] of Object.entries(attrs)) {
344
+ // Legacy compatibility: older onboard samples/projects stored theme on the provider.
345
+ // Modern projects store theme under `appConfig.theme`, but we still accept this
346
+ // attribute to avoid breaking existing JSON.
347
+ if (componentType === 'OnboardProvider' && attrName === 'theme') {
348
+ if (typeof attrValue !== 'string') {
349
+ return fail(`Expected one of: light, dark`, joinPath(path, attrName));
350
+ }
351
+ if (attrValue !== 'light' && attrValue !== 'dark') {
352
+ return fail(`Expected one of: light, dark`, joinPath(path, attrName));
353
+ }
354
+ continue;
355
+ }
356
+
342
357
  const attrSpec = schema?.[attrName];
343
358
  if (!attrSpec) {
344
359
  if (pattern.allowUnknownAttributes) continue;
@@ -0,0 +1,99 @@
1
+ import type { Node, NodeData } from '../types/Node';
2
+
3
+ export function deleteNodeFromTree(root: Node, target: Node): Node {
4
+ if (root === null || root === undefined) return root;
5
+ if (typeof root === 'string') return root;
6
+
7
+ if (Array.isArray(root)) {
8
+ let changed = false;
9
+ const nextChildren: Node[] = [];
10
+ for (const child of root) {
11
+ if (child === target) {
12
+ changed = true;
13
+ continue;
14
+ }
15
+ const nextChild = deleteNodeFromTree(child, target);
16
+ if (nextChild !== child) changed = true;
17
+ nextChildren.push(nextChild);
18
+ }
19
+ return changed ? nextChildren : root;
20
+ }
21
+
22
+ const data = root as any;
23
+ if ('children' in data) {
24
+ const prev = data.children as Node;
25
+ if (!prev) return root;
26
+
27
+ if (Array.isArray(prev)) {
28
+ let changed = false;
29
+ const nextChildren: Node[] = [];
30
+ for (const child of prev) {
31
+ if (child === target) {
32
+ changed = true;
33
+ continue;
34
+ }
35
+ const nextChild = deleteNodeFromTree(child, target);
36
+ if (nextChild !== child) changed = true;
37
+ nextChildren.push(nextChild);
38
+ }
39
+ if (changed) {
40
+ return { ...data, children: nextChildren } as Node;
41
+ }
42
+ return root;
43
+ }
44
+
45
+ if (prev === target) {
46
+ return { ...data, children: '' } as Node;
47
+ }
48
+
49
+ const nextChild = deleteNodeFromTree(prev, target);
50
+ if (nextChild !== prev) {
51
+ return { ...data, children: nextChild } as Node;
52
+ }
53
+ }
54
+
55
+ return root;
56
+ }
57
+
58
+ export function isNodeRecord(node: Node): node is NodeData {
59
+ return (
60
+ node !== null &&
61
+ node !== undefined &&
62
+ typeof node === 'object' &&
63
+ !Array.isArray(node)
64
+ );
65
+ }
66
+
67
+ export function nodeHasChild(parent: NodeData, potentialChild: Node): boolean {
68
+ const { children } = parent;
69
+ if (!children) return false;
70
+ if (Array.isArray(children)) {
71
+ return children.some((child) => child === potentialChild);
72
+ }
73
+ return children === potentialChild;
74
+ }
75
+
76
+ export function findNodeByKey(root: Node, key?: string): Node | null {
77
+ if (!key) return null;
78
+ if (root === null || root === undefined) return null;
79
+ if (typeof root === 'string') return null;
80
+
81
+ if (Array.isArray(root)) {
82
+ for (const child of root) {
83
+ const found = findNodeByKey(child, key);
84
+ if (found) return found;
85
+ }
86
+ return null;
87
+ }
88
+
89
+ const nodeData = root as NodeData;
90
+ if (nodeData.key === key) {
91
+ return nodeData;
92
+ }
93
+
94
+ if (nodeData.children) {
95
+ return findNodeByKey(nodeData.children as Node, key);
96
+ }
97
+
98
+ return null;
99
+ }
@@ -1,5 +0,0 @@
1
- import React from 'react';
2
- import type { PaywallSubscriButtonComponentProps } from './PaywallSubscriButtonProps.generated';
3
- declare function PaywallSubscriButton({ node }: PaywallSubscriButtonComponentProps): null;
4
- declare const _default: React.MemoExoticComponent<typeof PaywallSubscriButton>;
5
- export default _default;
@@ -1,50 +0,0 @@
1
- import type { NodeData } from '../../types/Node';
2
- export type FontWeightOptionType = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
3
- export type FlexDirectionOptionType = 'row' | 'column';
4
- export type AlignItemsOptionType = 'flex-start' | 'center' | 'flex-end' | 'stretch' | 'baseline';
5
- export type JustifyContentOptionType = 'flex-start' | 'center' | 'flex-end' | 'space-between' | 'space-around' | 'space-evenly';
6
- export type PositionOptionType = 'relative' | 'absolute';
7
- export interface PaywallSubscriButtonPropsGenerated {
8
- child: string;
9
- attributes: {
10
- color?: string;
11
- fontSize?: string;
12
- fontWeight?: FontWeightOptionType;
13
- scrollable?: boolean;
14
- flexDirection?: FlexDirectionOptionType;
15
- alignItems?: AlignItemsOptionType;
16
- justifyContent?: JustifyContentOptionType;
17
- gap?: string;
18
- padding?: string;
19
- paddingHorizontal?: string;
20
- paddingVertical?: string;
21
- paddingTop?: string;
22
- paddingBottom?: string;
23
- paddingLeft?: string;
24
- paddingRight?: string;
25
- margin?: string;
26
- marginVertical?: string;
27
- marginTop?: string;
28
- marginBottom?: string;
29
- marginLeft?: string;
30
- marginRight?: string;
31
- backgroundColor?: string;
32
- borderRadius?: string;
33
- width?: string;
34
- minWidth?: string;
35
- maxWidth?: string;
36
- height?: string;
37
- minHeight?: string;
38
- maxHeight?: string;
39
- flex?: number;
40
- position?: PositionOptionType;
41
- top?: string;
42
- bottom?: string;
43
- left?: string;
44
- right?: string;
45
- zIndex?: number;
46
- };
47
- }
48
- export interface PaywallSubscriButtonComponentProps {
49
- node: NodeData<PaywallSubscriButtonPropsGenerated['attributes']>;
50
- }
@@ -1,8 +0,0 @@
1
- import React from 'react';
2
- import type { Node } from '../../types/Node';
3
- type SideToolProps = {
4
- data: Node;
5
- setData: React.Dispatch<React.SetStateAction<Node>>;
6
- };
7
- export declare function SideTool({ data, setData }: SideToolProps): import("react/jsx-runtime").JSX.Element;
8
- export {};
@@ -1,10 +0,0 @@
1
- import React from 'react';
2
- import type { PaywallSubscriButtonComponentProps } from './PaywallSubscriButtonProps.generated';
3
- import useNode from '../useNode';
4
-
5
- function PaywallSubscriButton({ node }: PaywallSubscriButtonComponentProps) {
6
- node = useNode(node);
7
- return null;
8
- }
9
-
10
- export default React.memo(PaywallSubscriButton);
@@ -1,77 +0,0 @@
1
- /* AUTO-GENERATED FILE - DO NOT EDIT */
2
-
3
- import type { NodeData } from '../../types/Node';
4
-
5
- export type FontWeightOptionType =
6
- | 'normal'
7
- | 'bold'
8
- | '100'
9
- | '200'
10
- | '300'
11
- | '400'
12
- | '500'
13
- | '600'
14
- | '700'
15
- | '800'
16
- | '900';
17
- export type FlexDirectionOptionType = 'row' | 'column';
18
- export type AlignItemsOptionType =
19
- | 'flex-start'
20
- | 'center'
21
- | 'flex-end'
22
- | 'stretch'
23
- | 'baseline';
24
- export type JustifyContentOptionType =
25
- | 'flex-start'
26
- | 'center'
27
- | 'flex-end'
28
- | 'space-between'
29
- | 'space-around'
30
- | 'space-evenly';
31
- export type PositionOptionType = 'relative' | 'absolute';
32
-
33
- export interface PaywallSubscriButtonPropsGenerated {
34
- child: string;
35
- attributes: {
36
- color?: string;
37
- fontSize?: string;
38
- fontWeight?: FontWeightOptionType;
39
- scrollable?: boolean;
40
- flexDirection?: FlexDirectionOptionType;
41
- alignItems?: AlignItemsOptionType;
42
- justifyContent?: JustifyContentOptionType;
43
- gap?: string;
44
- padding?: string;
45
- paddingHorizontal?: string;
46
- paddingVertical?: string;
47
- paddingTop?: string;
48
- paddingBottom?: string;
49
- paddingLeft?: string;
50
- paddingRight?: string;
51
- margin?: string;
52
- marginVertical?: string;
53
- marginTop?: string;
54
- marginBottom?: string;
55
- marginLeft?: string;
56
- marginRight?: string;
57
- backgroundColor?: string;
58
- borderRadius?: string;
59
- width?: string;
60
- minWidth?: string;
61
- maxWidth?: string;
62
- height?: string;
63
- minHeight?: string;
64
- maxHeight?: string;
65
- flex?: number;
66
- position?: PositionOptionType;
67
- top?: string;
68
- bottom?: string;
69
- left?: string;
70
- right?: string;
71
- zIndex?: number;
72
- };
73
- }
74
-
75
- export interface PaywallSubscriButtonComponentProps {
76
- node: NodeData<PaywallSubscriButtonPropsGenerated['attributes']>;
77
- }
@@ -1,27 +0,0 @@
1
- {
2
- "schemaVersion": 1,
3
- "allowUnknownAttributes": false,
4
- "pattern": {
5
- "type": "PaywallSubscriButton",
6
- "children": "string",
7
- "extends": "Button",
8
- "attributes": {}
9
- },
10
- "defaults": {
11
- "paddingHorizontal": "20@s",
12
- "paddingVertical": "12@vs",
13
- "borderRadius": "12@s",
14
- "backgroundColor": "#1C1C1E",
15
- "color": "cornflowerblue",
16
- "fontSize": "16@fs",
17
- "fontWeight": "700",
18
- "justifyContent": "center",
19
- "alignItems": "center"
20
- },
21
- "meta": {
22
- "desiredParent": [">PaywallProvider"],
23
- "label": "Paywall Subscribe Button",
24
- "description": "Paywall subscribe call-to-action button. Extends Button.",
25
- "attributes": {}
26
- }
27
- }
@@ -1,253 +0,0 @@
1
- import React, { useEffect, useState } from 'react';
2
- import { JsonTextEditor } from '../../components/JsonTextEditor';
3
- import { Modal } from '../../modals';
4
- import type { Node } from '../../types/Node';
5
- import type { Localication } from '../../types/PreviewConfig';
6
- import { useLogRender } from '../../utils/useLogRender';
7
- import { useRenderStore } from '../../store';
8
- import { Checkbox } from '../../components/Checkbox';
9
- import { LocalicationModal } from '../../modals/LocalicationModal';
10
- import { analyseAndProccess } from '../../utils/analyseNode';
11
-
12
- const screenStyleDefaults = {
13
- light: { backgroundColor: '#FDFDFD', color: '#161827' },
14
- dark: { backgroundColor: '#12131A', color: '#E9EBF9' },
15
- } as const;
16
-
17
- type ScreenMode = keyof typeof screenStyleDefaults;
18
- type ScreenColorKey = keyof (typeof screenStyleDefaults)['light'];
19
-
20
- type SideToolProps = {
21
- data: Node;
22
- setData: React.Dispatch<React.SetStateAction<Node>>;
23
- };
24
-
25
- const colorFields = [
26
- {
27
- id: 'light-bg',
28
- label: 'Light Background Color',
29
- mode: 'light' as ScreenMode,
30
- key: 'backgroundColor' as ScreenColorKey,
31
- },
32
- {
33
- id: 'light-color',
34
- label: 'Light Color',
35
- mode: 'light' as ScreenMode,
36
- key: 'color' as ScreenColorKey,
37
- },
38
- {
39
- id: 'dark-bg',
40
- label: 'Dark Background Color',
41
- mode: 'dark' as ScreenMode,
42
- key: 'backgroundColor' as ScreenColorKey,
43
- },
44
- {
45
- id: 'dark-color',
46
- label: 'Dark Color',
47
- mode: 'dark' as ScreenMode,
48
- key: 'color' as ScreenColorKey,
49
- },
50
- ];
51
-
52
- export function SideTool({ data, setData }: SideToolProps) {
53
- useLogRender('SideTool');
54
- const [isDebugModalOpen, setIsDebugModalOpen] = useState(false);
55
- const [isLocalicationModalOpen, setIsLocalicationModalOpen] = useState(false);
56
- const [isCompactMode, setIsCompactMode] = useState(() => {
57
- if (typeof window === 'undefined') {
58
- return false;
59
- }
60
- return window.innerWidth < 1000;
61
- });
62
- const [isCompactPanelVisible, setIsCompactPanelVisible] = useState(false);
63
- const { appConfig, setAppConfig, previewMode, setPreviewMode } =
64
- useRenderStore((s) => ({
65
- appConfig: s.appConfig,
66
- setAppConfig: s.setAppConfig,
67
- previewMode: s.previewMode,
68
- setPreviewMode: s.setPreviewMode,
69
- }));
70
-
71
- useEffect(() => {
72
- if (typeof window === 'undefined') {
73
- return;
74
- }
75
-
76
- const handleResize = () => {
77
- const compact = window.innerWidth < 1000;
78
- setIsCompactMode(compact);
79
- };
80
-
81
- handleResize();
82
- window.addEventListener('resize', handleResize);
83
- return () => window.removeEventListener('resize', handleResize);
84
- }, []);
85
-
86
- const getScreenColorValue = (mode: ScreenMode, key: ScreenColorKey) =>
87
- appConfig.screenStyle?.[mode]?.[key] ?? screenStyleDefaults[mode][key];
88
-
89
- const handleScreenStyleChange = (
90
- mode: ScreenMode,
91
- key: ScreenColorKey,
92
- value: string,
93
- ) => {
94
- setAppConfig({
95
- ...appConfig,
96
- screenStyle: {
97
- ...screenStyleDefaults,
98
- ...appConfig.screenStyle,
99
- [mode]: {
100
- ...screenStyleDefaults[mode],
101
- ...appConfig.screenStyle?.[mode],
102
- [key]: value,
103
- },
104
- },
105
- });
106
- };
107
-
108
- const handleLocalicationChange = (data: Localication) => {
109
- setAppConfig({ ...appConfig, localication: data });
110
- };
111
-
112
- return (
113
- <div className="side-tool-container">
114
- <button
115
- type="button"
116
- className="editor-button"
117
- onClick={() => setIsCompactPanelVisible((prev) => !prev)}
118
- aria-pressed={isCompactPanelVisible}
119
- >
120
- {isCompactPanelVisible ? 'Hide tools' : 'Show tools'}
121
- </button>
122
-
123
- {isCompactPanelVisible && (
124
- <div className="side-tool">
125
- <select
126
- value={appConfig.defaultLanguage ?? 'en'}
127
- onChange={(e) =>
128
- setAppConfig({ ...appConfig, defaultLanguage: e.target.value })
129
- }
130
- >
131
- {Object.keys(appConfig.localication ?? {}).map((language) => (
132
- <option key={language} value={language}>
133
- {language}
134
- </option>
135
- ))}
136
- </select>
137
-
138
- <Checkbox
139
- label="Dark Mode"
140
- checked={appConfig.theme === 'dark'}
141
- onChange={(checked) =>
142
- setAppConfig({ ...appConfig, theme: checked ? 'dark' : 'light' })
143
- }
144
- />
145
-
146
- <Checkbox
147
- label="Is RTL"
148
- checked={appConfig.isRtl ?? false}
149
- onChange={(checked) =>
150
- setAppConfig({ ...appConfig, isRtl: checked })
151
- }
152
- />
153
-
154
- <Checkbox
155
- label="Preview mode"
156
- checked={previewMode}
157
- onChange={setPreviewMode}
158
- />
159
-
160
- <div>
161
- <div
162
- style={{
163
- display: 'grid',
164
- gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
165
- gap: 12,
166
- }}
167
- >
168
- {colorFields.map(({ id, label, mode, key }) => (
169
- <React.Fragment key={id}>
170
- <div>{label}</div>
171
- <input
172
- id={id}
173
- type="color"
174
- className="input input--color"
175
- value={getScreenColorValue(mode, key)}
176
- onChange={(e) =>
177
- handleScreenStyleChange(mode, key, e.target.value)
178
- }
179
- />
180
- </React.Fragment>
181
- ))}
182
- </div>
183
- </div>
184
-
185
- <div
186
- style={{
187
- marginTop: 'auto',
188
- paddingTop: 16,
189
- display: 'flex',
190
- flexDirection: 'column',
191
- gap: 8,
192
- }}
193
- >
194
- <button
195
- type="button"
196
- className="editor-button"
197
- onClick={() => setIsLocalicationModalOpen(true)}
198
- >
199
- Open localization editor
200
- </button>
201
- <button
202
- type="button"
203
- className="editor-button debug-button"
204
- title="Inspect raw JSON data"
205
- onClick={() => setIsDebugModalOpen(true)}
206
- >
207
- Debug JSON
208
- </button>
209
- </div>
210
-
211
- {isDebugModalOpen && (
212
- <Modal
213
- onClose={() => setIsDebugModalOpen(false)}
214
- ariaLabelledBy="debug-json-editor-title"
215
- className="modal--large modal--scrollable"
216
- contentClassName="localication-modal__content"
217
- >
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>
239
- </Modal>
240
- )}
241
-
242
- {isLocalicationModalOpen && (
243
- <LocalicationModal
244
- data={appConfig.localication ?? {}}
245
- onChange={handleLocalicationChange}
246
- onClose={() => setIsLocalicationModalOpen(false)}
247
- />
248
- )}
249
- </div>
250
- )}
251
- </div>
252
- );
253
- }