@developer_tribe/react-builder 0.1.32 → 1.0.2

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 (218) hide show
  1. package/dist/DeviceMockFrame.d.ts +2 -17
  2. package/dist/RenderPage.d.ts +4 -11
  3. package/dist/attributes-editor/Field.d.ts +16 -0
  4. package/dist/attributes-editor/FieldInfoTooltip.d.ts +7 -0
  5. package/dist/attributes-editor/LayoutPreviewPicker.d.ts +12 -0
  6. package/dist/attributes-editor/SpecialCategorySection.d.ts +19 -0
  7. package/dist/attributes-editor/types.d.ts +14 -0
  8. package/dist/background.jpg +0 -0
  9. package/dist/build-components/Button/Button.d.ts +1 -1
  10. package/dist/build-components/Button/ButtonProps.generated.d.ts +26 -1
  11. package/dist/build-components/Carousel/CarouselProps.generated.d.ts +27 -1
  12. package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +25 -0
  13. package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +25 -0
  14. package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +27 -1
  15. package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +27 -1
  16. package/dist/build-components/Image/ImageProps.generated.d.ts +25 -3
  17. package/dist/build-components/Onboard/OnboardProps.generated.d.ts +27 -1
  18. package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +25 -0
  19. package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +25 -0
  20. package/dist/build-components/OnboardDot/OnboardDot.d.ts +1 -1
  21. package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +22 -0
  22. package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +4 -5
  23. package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +25 -3
  24. package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +24 -3
  25. package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +25 -4
  26. package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +4 -5
  27. package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +4 -5
  28. package/dist/build-components/Text/TextProps.generated.d.ts +4 -5
  29. package/dist/build-components/View/ViewProps.generated.d.ts +3 -4
  30. package/dist/build-components/index.d.ts +1 -0
  31. package/dist/build-components/patterns.generated.d.ts +4855 -132
  32. package/dist/components/AttributesEditorPanel.d.ts +9 -0
  33. package/dist/components/Breadcrumb.d.ts +15 -0
  34. package/dist/components/Builder.d.ts +9 -0
  35. package/dist/components/Checkbox.d.ts +17 -0
  36. package/dist/components/DeviceButton.d.ts +8 -0
  37. package/dist/components/DeviceNavigationBar.d.ts +10 -0
  38. package/dist/components/DeviceStatusBar.d.ts +9 -0
  39. package/dist/components/EditorHeader.d.ts +10 -0
  40. package/dist/index.cjs.js +6 -5
  41. package/dist/index.cjs.js.map +1 -0
  42. package/dist/index.d.ts +8 -4
  43. package/dist/index.esm.js +6 -5
  44. package/dist/index.esm.js.map +1 -0
  45. package/dist/mockOS/components/MockLaunchScreenComponent.d.ts +6 -0
  46. package/dist/mockOS/components/MockOSRouter.d.ts +8 -0
  47. package/dist/mockOS/components/PermissionModal.d.ts +9 -0
  48. package/dist/mockOS/context/MockOSContext.d.ts +36 -0
  49. package/dist/mockOS/hooks/useMockNavigation.d.ts +3 -0
  50. package/dist/mockOS/hooks/useMockPermission.d.ts +3 -0
  51. package/dist/mockOS/index.d.ts +9 -0
  52. package/dist/mockOS/managers/mockPermissionManager.d.ts +10 -0
  53. package/dist/mockOS/managers/navigationManager.d.ts +17 -0
  54. package/dist/modals/AddComponentModal.d.ts +8 -0
  55. package/dist/modals/ColorModal.d.ts +9 -0
  56. package/dist/modals/DeviceSelectorModal.d.ts +9 -0
  57. package/dist/modals/LocalicationModal.d.ts +8 -0
  58. package/dist/modals/Modal.d.ts +12 -0
  59. package/dist/modals/index.d.ts +5 -0
  60. package/dist/pages/ProjectPage.d.ts +11 -0
  61. package/dist/pages/tabs/BuilderTab.d.ts +9 -0
  62. package/dist/pages/tabs/DebugTab.d.ts +7 -0
  63. package/dist/pages/tabs/PreviewTab.d.ts +3 -0
  64. package/dist/store.d.ts +15 -18
  65. package/dist/styles.css +1 -1
  66. package/dist/types/PreviewConfig.d.ts +6 -3
  67. package/dist/types/Project.d.ts +12 -2
  68. package/dist/utils/copyNode.d.ts +2 -0
  69. package/dist/utils/logger.d.ts +11 -0
  70. package/dist/utils/patterns.d.ts +24 -0
  71. package/dist/utils/useLogRender.d.ts +1 -0
  72. package/package.json +17 -9
  73. package/scripts/prebuild/utils/createBuildComponentsIndex.js +15 -1
  74. package/scripts/prebuild/utils/createGeneratedProps.js +11 -3
  75. package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +45 -6
  76. package/scripts/prebuild/utils/validatePatternJson.js +13 -5
  77. package/src/AttributesEditor.tsx +434 -311
  78. package/src/DeviceMockFrame.tsx +42 -67
  79. package/src/RenderPage.tsx +8 -44
  80. package/src/assets/images/android.svg +43 -0
  81. package/src/assets/images/apple.svg +16 -0
  82. package/src/assets/images/background.jpg +0 -0
  83. package/src/assets/samples/carousel-sample.json +2 -3
  84. package/src/assets/samples/getSamples.ts +49 -12
  85. package/src/assets/samples/simple-1.json +1 -2
  86. package/src/assets/samples/simple-2.json +1 -2
  87. package/src/assets/samples/vpn-onboard-1.json +1 -2
  88. package/src/assets/samples/vpn-onboard-2.json +1 -2
  89. package/src/assets/samples/vpn-onboard-3.json +1 -2
  90. package/src/assets/samples/vpn-onboard-4.json +1 -2
  91. package/src/assets/samples/vpn-onboard-5.json +1 -2
  92. package/src/assets/samples/vpn-onboard-6.json +1 -2
  93. package/src/attributes-editor/Field.tsx +662 -0
  94. package/src/attributes-editor/FieldInfoTooltip.tsx +49 -0
  95. package/src/attributes-editor/LayoutPreviewPicker.tsx +199 -0
  96. package/src/attributes-editor/SpecialCategorySection.tsx +284 -0
  97. package/src/attributes-editor/types.ts +30 -0
  98. package/src/build-components/Button/Button.tsx +12 -2
  99. package/src/build-components/Button/ButtonProps.generated.ts +37 -1
  100. package/src/build-components/Button/pattern.json +31 -2
  101. package/src/build-components/Carousel/Carousel.tsx +17 -2
  102. package/src/build-components/Carousel/CarouselProps.generated.ts +39 -1
  103. package/src/build-components/Carousel/pattern.json +10 -0
  104. package/src/build-components/CarouselButtons/CarouselButtons.tsx +8 -2
  105. package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +36 -0
  106. package/src/build-components/CarouselButtons/pattern.json +22 -0
  107. package/src/build-components/CarouselDots/CarouselDots.tsx +42 -8
  108. package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +36 -0
  109. package/src/build-components/CarouselDots/pattern.json +15 -0
  110. package/src/build-components/CarouselItem/CarouselItem.tsx +7 -2
  111. package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +39 -1
  112. package/src/build-components/CarouselItem/pattern.json +7 -0
  113. package/src/build-components/CarouselProvider/CarouselProvider.tsx +10 -2
  114. package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +39 -1
  115. package/src/build-components/CarouselProvider/pattern.json +7 -0
  116. package/src/build-components/Image/Image.tsx +10 -2
  117. package/src/build-components/Image/ImageProps.generated.ts +36 -3
  118. package/src/build-components/Image/pattern.json +46 -3
  119. package/src/build-components/Onboard/Onboard.tsx +8 -1
  120. package/src/build-components/Onboard/OnboardProps.generated.ts +39 -1
  121. package/src/build-components/Onboard/pattern.json +11 -0
  122. package/src/build-components/OnboardButton/OnboardButton.tsx +53 -9
  123. package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +36 -0
  124. package/src/build-components/OnboardButton/pattern.json +71 -5
  125. package/src/build-components/OnboardButtons/OnboardButtons.tsx +27 -17
  126. package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +36 -0
  127. package/src/build-components/OnboardButtons/pattern.json +70 -4
  128. package/src/build-components/OnboardDot/OnboardDot.tsx +106 -4
  129. package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +22 -0
  130. package/src/build-components/OnboardDot/pattern.json +54 -1
  131. package/src/build-components/OnboardFooter/OnboardFooter.tsx +14 -6
  132. package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +4 -5
  133. package/src/build-components/OnboardFooter/pattern.json +58 -2
  134. package/src/build-components/OnboardImage/OnboardImage.tsx +29 -5
  135. package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +36 -3
  136. package/src/build-components/OnboardImage/pattern.json +21 -0
  137. package/src/build-components/OnboardItem/OnboardItem.tsx +8 -1
  138. package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +35 -3
  139. package/src/build-components/OnboardItem/pattern.json +38 -2
  140. package/src/build-components/OnboardProvider/OnboardProvider.tsx +22 -8
  141. package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +37 -4
  142. package/src/build-components/OnboardProvider/pattern.json +51 -4
  143. package/src/build-components/OnboardSubtitle/OnboardSubtitle.tsx +2 -0
  144. package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +4 -5
  145. package/src/build-components/OnboardSubtitle/pattern.json +6 -0
  146. package/src/build-components/OnboardTitle/OnboardTitle.tsx +2 -0
  147. package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +4 -5
  148. package/src/build-components/OnboardTitle/pattern.json +6 -0
  149. package/src/build-components/Text/Text.tsx +12 -6
  150. package/src/build-components/Text/TextProps.generated.ts +4 -5
  151. package/src/build-components/Text/pattern.json +38 -2
  152. package/src/build-components/View/View.tsx +11 -6
  153. package/src/build-components/View/ViewProps.generated.ts +3 -4
  154. package/src/build-components/View/pattern.json +227 -19
  155. package/src/build-components/index.ts +22 -0
  156. package/src/build-components/patterns.generated.ts +4905 -139
  157. package/src/components/AttributesEditorPanel.tsx +58 -0
  158. package/src/components/Breadcrumb.tsx +80 -0
  159. package/src/components/Builder.tsx +375 -0
  160. package/src/components/Checkbox.tsx +81 -0
  161. package/src/components/DeviceButton.tsx +39 -0
  162. package/src/components/DeviceNavigationBar.tsx +201 -0
  163. package/src/components/DeviceStatusBar.tsx +85 -0
  164. package/src/components/EditorHeader.tsx +138 -0
  165. package/src/index.ts +8 -4
  166. package/src/mockOS/components/MockLaunchScreenComponent.tsx +43 -0
  167. package/src/mockOS/components/MockOSRouter.tsx +115 -0
  168. package/src/mockOS/components/PermissionModal.tsx +270 -0
  169. package/src/mockOS/context/MockOSContext.tsx +179 -0
  170. package/src/mockOS/hooks/useMockNavigation.ts +11 -0
  171. package/src/mockOS/hooks/useMockPermission.ts +11 -0
  172. package/src/mockOS/index.ts +26 -0
  173. package/src/mockOS/managers/mockPermissionManager.ts +54 -0
  174. package/src/mockOS/managers/navigationManager.ts +91 -0
  175. package/src/modals/AddComponentModal.tsx +313 -0
  176. package/src/modals/ColorModal.tsx +268 -0
  177. package/src/modals/DeviceSelectorModal.tsx +57 -0
  178. package/src/modals/LocalicationModal.tsx +54 -0
  179. package/src/modals/Modal.tsx +57 -0
  180. package/src/modals/index.ts +5 -0
  181. package/src/pages/ProjectPage.tsx +150 -0
  182. package/src/pages/tabs/BuilderTab.tsx +33 -0
  183. package/src/pages/tabs/DebugTab.tsx +64 -0
  184. package/src/pages/tabs/PreviewTab.tsx +206 -0
  185. package/src/size-matters/index.ts +25 -5
  186. package/src/store.ts +56 -38
  187. package/src/styles/base/_global.scss +253 -0
  188. package/src/styles/components/_attributes-editor.scss +261 -0
  189. package/src/styles/components/_editor-shell.scss +189 -0
  190. package/src/styles/components/_mockos-router.scss +140 -0
  191. package/src/styles/components/_ui-components.scss +183 -0
  192. package/src/styles/foundation/_colors.scss +8 -0
  193. package/src/styles/foundation/_mixins.scss +22 -0
  194. package/src/styles/{_reset.scss → foundation/_reset.scss} +5 -2
  195. package/src/styles/foundation/_sizes.scss +37 -0
  196. package/src/styles/foundation/_typography.scss +4 -0
  197. package/src/styles/foundation/_variables.scss +3 -0
  198. package/src/styles/index.scss +22 -129
  199. package/src/styles/layout/_builder.scss +68 -0
  200. package/src/styles/layout/_pages.scss +3 -0
  201. package/src/styles/modals/_add-component.scss +122 -0
  202. package/src/styles/modals/_color-modal.scss +130 -0
  203. package/src/styles/modals/_device-selector.scss +18 -0
  204. package/src/styles/modals/_localication-modal.scss +68 -0
  205. package/src/styles/modals/_modal-shell.scss +46 -0
  206. package/src/styles/utilities/_carousel.scss +125 -0
  207. package/src/types/PreviewConfig.ts +14 -5
  208. package/src/types/Project.ts +15 -2
  209. package/src/types/images.d.ts +8 -0
  210. package/src/utils/copyNode.ts +7 -0
  211. package/src/utils/extractTextStyle.ts +8 -4
  212. package/src/utils/extractViewStyle.ts +51 -7
  213. package/src/utils/getDevices.ts +1 -0
  214. package/src/utils/logger.ts +76 -0
  215. package/src/utils/patterns.ts +33 -0
  216. package/src/utils/useLogRender.ts +13 -0
  217. package/dist/build-components/OnboardDot/OnboardExpandingDotProps.generated.d.ts +0 -10
  218. package/src/build-components/OnboardDot/OnboardExpandingDotProps.generated.ts +0 -20
@@ -0,0 +1,58 @@
1
+ import { AttributesEditor, Node, NodeData } from '..';
2
+ import { useLogRender } from '../utils/useLogRender';
3
+
4
+ interface AttributesEditorPanelProps {
5
+ current: Node;
6
+ attributes: any;
7
+ onChange: (data: Node) => void;
8
+ setCurrent: (current: Node) => void;
9
+ }
10
+
11
+ export function AttributesEditorPanel({
12
+ current,
13
+ attributes,
14
+ onChange,
15
+ setCurrent,
16
+ }: AttributesEditorPanelProps) {
17
+ useLogRender('AttributesEditorPanel');
18
+ if (!current) return null;
19
+
20
+ function replaceNode(root: Node, target: Node, next: Node): Node {
21
+ if (root === target) return next;
22
+ if (root === null || root === undefined) return root;
23
+ if (typeof root === 'string') return root;
24
+ if (Array.isArray(root)) {
25
+ let changed = false;
26
+ const arr = root.map((item) => {
27
+ const r = replaceNode(item, target, next);
28
+ if (r !== item) changed = true;
29
+ return r;
30
+ });
31
+ return changed ? arr : root;
32
+ }
33
+ const data = root as any;
34
+ if ('children' in data) {
35
+ const prev = data.children;
36
+ const replaced = Array.isArray(prev)
37
+ ? prev.map((c: Node) => replaceNode(c, target, next))
38
+ : replaceNode(prev as Node, target, next);
39
+ if (replaced !== prev) {
40
+ data.children = replaced;
41
+ return { ...data, children: replaced } as Node;
42
+ }
43
+ }
44
+ return root;
45
+ }
46
+ const handleAttributesChange = (next: Node) => {
47
+ const root = attributes as Node;
48
+ const updated = replaceNode(root, current, next);
49
+ onChange(updated);
50
+ setCurrent(next);
51
+ };
52
+
53
+ return (
54
+ <div style={{ padding: 12 }}>
55
+ <AttributesEditor node={current} onChange={handleAttributesChange} />
56
+ </div>
57
+ );
58
+ }
@@ -0,0 +1,80 @@
1
+ import { ReactNode } from 'react';
2
+ import { useLogRender } from '../utils/useLogRender';
3
+ export type BreadcrumbItem = {
4
+ label: string;
5
+ to?: string;
6
+ onClick?: () => void;
7
+ };
8
+
9
+ type BreadcrumbProps = {
10
+ items: BreadcrumbItem[];
11
+ separator?: ReactNode;
12
+ ariaLabel?: string;
13
+ onBack?: () => void;
14
+ backLabel?: string;
15
+ };
16
+
17
+ export function Breadcrumb({
18
+ items,
19
+ separator = '/',
20
+ ariaLabel = 'Breadcrumb',
21
+ onBack,
22
+ backLabel = 'Back',
23
+ }: BreadcrumbProps) {
24
+ useLogRender('Breadcrumb');
25
+ return (
26
+ <nav className="breadcrumb" aria-label={ariaLabel}>
27
+ {onBack && (
28
+ <button
29
+ type="button"
30
+ className="breadcrumb__back"
31
+ onClick={onBack}
32
+ aria-label="Go back"
33
+ >
34
+ <span className="breadcrumb__back-icon" aria-hidden>
35
+
36
+ </span>
37
+ <span>{backLabel}</span>
38
+ </button>
39
+ )}
40
+ <ol className="breadcrumb__list">
41
+ {items.map((item, index) => {
42
+ const isLast = index === items.length - 1;
43
+ const isClickable = !isLast && (item.to || item.onClick);
44
+ return (
45
+ <li
46
+ className={`breadcrumb__item ${isClickable ? 'breadcrumb__item--clickable' : ''}`}
47
+ key={`${item.label}-${index}`}
48
+ onClick={isClickable ? item.onClick : undefined}
49
+ role={isClickable ? 'button' : undefined}
50
+ tabIndex={isClickable ? 0 : undefined}
51
+ onKeyDown={
52
+ isClickable && item.onClick
53
+ ? (e) => {
54
+ if (e.key === 'Enter' || e.key === ' ') {
55
+ e.preventDefault();
56
+ item.onClick?.();
57
+ }
58
+ }
59
+ : undefined
60
+ }
61
+ >
62
+ {index > 0 && (
63
+ <span className="breadcrumb__separator" aria-hidden>
64
+ {separator}
65
+ </span>
66
+ )}
67
+ {isLast || (!item.to && !item.onClick) ? (
68
+ <span className="breadcrumb__current" aria-current="page">
69
+ {item.label}
70
+ </span>
71
+ ) : (
72
+ <span className="breadcrumb__link">{item.label}</span>
73
+ )}
74
+ </li>
75
+ );
76
+ })}
77
+ </ol>
78
+ </nav>
79
+ );
80
+ }
@@ -0,0 +1,375 @@
1
+ import { useCallback, useMemo, useState } from 'react';
2
+ import {
3
+ isNodeArray,
4
+ isNodeNullOrUndefined,
5
+ isNodeString,
6
+ Node,
7
+ NodeData,
8
+ NodeDefaultAttribute,
9
+ allcomponentNames,
10
+ } from '..';
11
+ import { Breadcrumb } from './Breadcrumb';
12
+ import { useLogRender } from '../utils/useLogRender';
13
+ import { getDefaultsForType, getPatternByType } from '../utils/patterns';
14
+ import { AddComponentModal } from '../modals/AddComponentModal';
15
+
16
+ type BuilderEditorProps = {
17
+ data: Node;
18
+ setData: (data: Node) => void;
19
+ current: Node;
20
+ setCurrent: (current: Node) => void;
21
+ };
22
+
23
+ interface BuilderEditorComponentProps {
24
+ node: Node;
25
+ onClick: (node: Node) => void;
26
+ onAdd?: () => void;
27
+ }
28
+
29
+ function BuilderButton({ node, onClick }: { node: Node; onClick: () => void }) {
30
+ if (isNodeNullOrUndefined(node)) {
31
+ return <div className="builder__placeholder">Null or undefined</div>;
32
+ }
33
+ if (isNodeString(node)) {
34
+ return <div className="builder__text">{node as string}</div>;
35
+ }
36
+ const nodeData = node as NodeData<NodeDefaultAttribute>;
37
+
38
+ let extra = '';
39
+ if (nodeData.attributes?.condition) {
40
+ extra = ` (${nodeData.attributes.condition} ${nodeData.attributes.conditionVariable})`;
41
+ }
42
+ const patternLabel = getPatternByType(nodeData.type)?.meta?.label?.trim();
43
+ const baseLabel =
44
+ patternLabel && patternLabel.length > 0 ? patternLabel : nodeData.type;
45
+ const conditionLabel = extra.trim() ? extra : '';
46
+ const fullLabel = `${baseLabel}${conditionLabel}`.trim();
47
+ return (
48
+ <a onClick={onClick} className="builder__button">
49
+ <span className="builder__button-label">{baseLabel}</span> <br />
50
+ {conditionLabel && (
51
+ <span className="builder__button-condition">{conditionLabel}</span>
52
+ )}
53
+ </a>
54
+ );
55
+ }
56
+
57
+ function BuilderComponent({
58
+ node,
59
+ onClick,
60
+ onAdd,
61
+ }: BuilderEditorComponentProps) {
62
+ if (isNodeNullOrUndefined(node)) {
63
+ return <div className="builder__placeholder">Null or undefined</div>;
64
+ }
65
+ if (isNodeString(node)) {
66
+ return (
67
+ <div className="builder__text">
68
+ {node as string} (Please define a node)
69
+ </div>
70
+ );
71
+ }
72
+
73
+ const addButton = onAdd && (
74
+ <button
75
+ type="button"
76
+ className="editor-button builder__add-button"
77
+ onClick={onAdd}
78
+ >
79
+ <span className="builder__add-button-icon" aria-hidden="true">
80
+ +
81
+ </span>
82
+ <span>Add component</span>
83
+ </button>
84
+ );
85
+
86
+ if (isNodeArray(node)) {
87
+ return (
88
+ <div className="builder__list">
89
+ {(node as Node[]).map((item, index) => (
90
+ <BuilderButton
91
+ onClick={() => {
92
+ onClick(item);
93
+ }}
94
+ key={index}
95
+ node={item}
96
+ />
97
+ ))}
98
+ {addButton}
99
+ </div>
100
+ );
101
+ }
102
+
103
+ const nodeData = node as NodeData<NodeDefaultAttribute>;
104
+ const children = nodeData.children
105
+ ? isNodeArray(nodeData.children)
106
+ ? (nodeData.children as Node[])
107
+ : [nodeData.children]
108
+ : null;
109
+
110
+ return (
111
+ <div className="builder__node">
112
+ <div className="builder__children">
113
+ {children &&
114
+ children.map((child, index) => (
115
+ <BuilderButton
116
+ onClick={() => {
117
+ onClick(child);
118
+ }}
119
+ key={index}
120
+ node={child}
121
+ />
122
+ ))}
123
+ </div>
124
+ {addButton}
125
+ </div>
126
+ );
127
+ }
128
+
129
+ export function Builder({
130
+ data,
131
+ setData,
132
+ current,
133
+ setCurrent,
134
+ }: BuilderEditorProps) {
135
+ useLogRender('Builder');
136
+ const [isAddModalOpen, setIsAddModalOpen] = useState(false);
137
+ const breadcrumbPath = useMemo(() => {
138
+ const path = findNodePath(data, current);
139
+ if (path.length) return path;
140
+ if (!isNodeNullOrUndefined(current)) return [current];
141
+ if (!isNodeNullOrUndefined(data)) return [data];
142
+ return [];
143
+ }, [data, current]);
144
+
145
+ const handleNodeSelect = useCallback(
146
+ (node: Node) => {
147
+ setCurrent(node);
148
+ },
149
+ [setCurrent],
150
+ );
151
+
152
+ const breadcrumbItems = useMemo(
153
+ () =>
154
+ breadcrumbPath.map((node, index) => ({
155
+ label: getNodeLabel(node),
156
+ onClick:
157
+ index === breadcrumbPath.length - 1
158
+ ? undefined
159
+ : () => handleNodeSelect(node),
160
+ })),
161
+ [breadcrumbPath, handleNodeSelect],
162
+ );
163
+
164
+ const handleBackClick = useCallback(() => {
165
+ if (breadcrumbPath.length < 2) return;
166
+ handleNodeSelect(breadcrumbPath[breadcrumbPath.length - 2]);
167
+ }, [breadcrumbPath, handleNodeSelect]);
168
+
169
+ const handleAddChild = useCallback(
170
+ (type: string) => {
171
+ if (
172
+ isNodeNullOrUndefined(current) ||
173
+ isNodeString(current) ||
174
+ isNodeArray(current)
175
+ ) {
176
+ return;
177
+ }
178
+
179
+ const parent = current as NodeData<NodeDefaultAttribute>;
180
+ const nextChild = createDefaultNode(type);
181
+ const updatedParent: NodeData<NodeDefaultAttribute> = {
182
+ ...parent,
183
+ children: appendChild(parent.children, nextChild),
184
+ };
185
+ const updatedRoot = replaceNode(data, current, updatedParent);
186
+ setData(updatedRoot);
187
+ setCurrent(updatedParent);
188
+ },
189
+ [current, data, setData, setCurrent],
190
+ );
191
+
192
+ const allowedChildTypes = useMemo(
193
+ () => getAllowedChildTypes(current),
194
+ [current],
195
+ );
196
+ const parentType = useMemo(() => {
197
+ if (
198
+ isNodeNullOrUndefined(current) ||
199
+ isNodeString(current) ||
200
+ isNodeArray(current)
201
+ ) {
202
+ return null;
203
+ }
204
+ return (current as NodeData<NodeDefaultAttribute>).type ?? null;
205
+ }, [current]);
206
+ const canAddChild = allowedChildTypes.length > 0;
207
+
208
+ const handleOpenAddModal = useCallback(() => {
209
+ if (!canAddChild) return;
210
+ setIsAddModalOpen(true);
211
+ }, [canAddChild]);
212
+
213
+ const handleCloseAddModal = useCallback(() => {
214
+ setIsAddModalOpen(false);
215
+ }, []);
216
+
217
+ const handleAddChildFromModal = useCallback(
218
+ (type: string) => {
219
+ handleAddChild(type);
220
+ setIsAddModalOpen(false);
221
+ },
222
+ [handleAddChild],
223
+ );
224
+
225
+ function replaceNode(root: Node, target: Node, next: Node): Node {
226
+ if (root === target) return next;
227
+ if (root === null || root === undefined) return root;
228
+ if (typeof root === 'string') return root;
229
+ if (Array.isArray(root)) {
230
+ let changed = false;
231
+ const arr = root.map((item) => {
232
+ const r = replaceNode(item, target, next);
233
+ if (r !== item) changed = true;
234
+ return r;
235
+ });
236
+ return changed ? arr : root;
237
+ }
238
+ const data = root as any;
239
+ if ('children' in data) {
240
+ const prev = data.children;
241
+ const replaced = Array.isArray(prev)
242
+ ? prev.map((c: Node) => replaceNode(c, target, next))
243
+ : replaceNode(prev as Node, target, next);
244
+ if (replaced !== prev) {
245
+ data.children = replaced;
246
+ return { ...data, children: replaced } as Node;
247
+ }
248
+ }
249
+ return root;
250
+ }
251
+
252
+ function createDefaultNode(type: string): NodeData<NodeDefaultAttribute> {
253
+ const pattern = getPatternByType(type)?.pattern;
254
+ const defaults = getDefaultsForType(type) ?? {};
255
+ let children: Node = '';
256
+ const childrenSchema = pattern?.children as unknown;
257
+ if (childrenSchema === 'never') {
258
+ children = '';
259
+ } else if (childrenSchema === 'string') {
260
+ children = '';
261
+ } else if (
262
+ childrenSchema === 'node' ||
263
+ (Array.isArray(childrenSchema) && childrenSchema.includes('node'))
264
+ ) {
265
+ children = [];
266
+ } else if (typeof childrenSchema === 'string') {
267
+ // Specific child type like 'carouselItem' – initialize as empty array to allow multiple
268
+ children = [];
269
+ } else {
270
+ children = '';
271
+ }
272
+ return {
273
+ type,
274
+ children,
275
+ attributes: { ...defaults },
276
+ } as NodeData<NodeDefaultAttribute>;
277
+ }
278
+
279
+ function getAllowedChildTypes(parent: Node): string[] {
280
+ if (
281
+ isNodeNullOrUndefined(parent) ||
282
+ isNodeString(parent) ||
283
+ isNodeArray(parent)
284
+ )
285
+ return [];
286
+ const parentData = parent as NodeData;
287
+ const parentType = parentData.type;
288
+ // Special rule: limit OnboardButtons to OnboardButton only
289
+ if (parentType === 'OnboardButtons') return ['OnboardButton'];
290
+ const childrenSchema = getPatternByType(parentType)?.pattern
291
+ ?.children as unknown;
292
+ if (!childrenSchema) return [];
293
+ if (childrenSchema === 'never' || childrenSchema === 'string') return [];
294
+ if (
295
+ childrenSchema === 'node' ||
296
+ (Array.isArray(childrenSchema) && childrenSchema.includes('node'))
297
+ ) {
298
+ return [...allcomponentNames];
299
+ }
300
+ if (typeof childrenSchema === 'string') {
301
+ return [childrenSchema];
302
+ }
303
+ return [];
304
+ }
305
+
306
+ return (
307
+ <div className="builder">
308
+ <Breadcrumb
309
+ items={breadcrumbItems}
310
+ onBack={breadcrumbPath.length > 1 ? handleBackClick : undefined}
311
+ />
312
+ <BuilderComponent
313
+ onClick={handleNodeSelect}
314
+ onAdd={canAddChild ? handleOpenAddModal : undefined}
315
+ node={current}
316
+ />
317
+ {isAddModalOpen && (
318
+ <AddComponentModal
319
+ allowedChildTypes={allowedChildTypes}
320
+ parentType={parentType}
321
+ onSelect={handleAddChildFromModal}
322
+ onClose={handleCloseAddModal}
323
+ />
324
+ )}
325
+ </div>
326
+ );
327
+ }
328
+
329
+ function appendChild(children: Node, childToAppend: Node): Node {
330
+ if (Array.isArray(children)) {
331
+ return [...children, childToAppend];
332
+ }
333
+ if (
334
+ children === null ||
335
+ children === undefined ||
336
+ typeof children === 'string'
337
+ ) {
338
+ return [childToAppend];
339
+ }
340
+ return [children as Node, childToAppend];
341
+ }
342
+
343
+ function getNodeLabel(node: Node): string {
344
+ if (isNodeNullOrUndefined(node)) return 'Empty';
345
+ if (isNodeString(node)) return node as string;
346
+ if (isNodeArray(node)) return 'Collection';
347
+ return (node as NodeData<NodeDefaultAttribute>).type ?? 'Node';
348
+ }
349
+
350
+ function findNodePath(root: Node, target: Node): Node[] {
351
+ if (root === null || root === undefined) return [];
352
+ if (root === target) return [root];
353
+ if (typeof root === 'string') return [];
354
+ if (Array.isArray(root)) {
355
+ for (const child of root) {
356
+ const childPath = findNodePath(child, target);
357
+ if (childPath.length) {
358
+ return childPath;
359
+ }
360
+ }
361
+ return [];
362
+ }
363
+
364
+ const nodeData = root as NodeData<NodeDefaultAttribute>;
365
+ const children = nodeData.children;
366
+ if (!children) return [];
367
+ const childList = Array.isArray(children) ? children : [children];
368
+ for (const child of childList) {
369
+ const childPath = findNodePath(child, target);
370
+ if (childPath.length) {
371
+ return [root, ...childPath];
372
+ }
373
+ }
374
+ return [];
375
+ }
@@ -0,0 +1,81 @@
1
+ import React, { useId } from 'react';
2
+
3
+ type CheckboxChangeHandler = (
4
+ checked: boolean,
5
+ event: React.ChangeEvent<HTMLInputElement>,
6
+ ) => void;
7
+
8
+ export type CheckboxProps = Omit<
9
+ React.InputHTMLAttributes<HTMLInputElement>,
10
+ 'type' | 'onChange' | 'className'
11
+ > & {
12
+ label?: React.ReactNode;
13
+ helperText?: React.ReactNode;
14
+ className?: string;
15
+ inputClassName?: string;
16
+ onChange?: CheckboxChangeHandler;
17
+ };
18
+
19
+ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
20
+ (
21
+ {
22
+ label,
23
+ helperText,
24
+ className,
25
+ inputClassName,
26
+ onChange,
27
+ id,
28
+ disabled,
29
+ ...rest
30
+ },
31
+ ref,
32
+ ) => {
33
+ const autoId = useId();
34
+ const inputId = id ?? autoId;
35
+ const helperId = helperText ? `${inputId}-helper` : undefined;
36
+
37
+ const wrapperClassName = [
38
+ 'builder-checkbox',
39
+ disabled ? 'builder-checkbox--disabled' : undefined,
40
+ className,
41
+ ]
42
+ .filter(Boolean)
43
+ .join(' ');
44
+
45
+ const nativeClassName = ['builder-checkbox__native', inputClassName]
46
+ .filter(Boolean)
47
+ .join(' ');
48
+
49
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
50
+ onChange?.(event.target.checked, event);
51
+ };
52
+
53
+ return (
54
+ <div className={wrapperClassName}>
55
+ <label htmlFor={inputId} className="builder-checkbox__label">
56
+ <input
57
+ {...rest}
58
+ ref={ref}
59
+ id={inputId}
60
+ type="checkbox"
61
+ className={nativeClassName}
62
+ onChange={handleChange}
63
+ disabled={disabled}
64
+ aria-describedby={helperId}
65
+ />
66
+ <span className="builder-checkbox__control" aria-hidden="true" />
67
+ {label ? (
68
+ <span className="builder-checkbox__text">{label}</span>
69
+ ) : null}
70
+ </label>
71
+ {helperText ? (
72
+ <span id={helperId} className="builder-checkbox__helper">
73
+ {helperText}
74
+ </span>
75
+ ) : null}
76
+ </div>
77
+ );
78
+ },
79
+ );
80
+
81
+ Checkbox.displayName = 'Checkbox';
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import { Device } from '../types/Device';
3
+ import androidIcon from '../assets/images/android.svg';
4
+ import iosIcon from '../assets/images/apple.svg';
5
+
6
+ const platformIcons: Record<string, string> = {
7
+ android: androidIcon,
8
+ ios: iosIcon,
9
+ };
10
+
11
+ type DeviceButtonProps = {
12
+ device: Device;
13
+ selectedDevice: Device | null;
14
+ onSelect: (device: Device) => void;
15
+ };
16
+
17
+ export function DeviceButton({
18
+ device,
19
+ selectedDevice,
20
+ onSelect,
21
+ }: DeviceButtonProps) {
22
+ const platformIcon = platformIcons[device.platform];
23
+
24
+ return (
25
+ <button
26
+ type="button"
27
+ className={`editor-device-button${
28
+ selectedDevice === device ? ' editor-device-button--selected' : ''
29
+ }`}
30
+ onClick={() => onSelect(device)}
31
+ >
32
+ {device.name} <br />
33
+ {device.width}x{device.height}
34
+ {platformIcon && <img src={platformIcon} alt="" aria-hidden="true" />}
35
+ </button>
36
+ );
37
+ }
38
+
39
+ export default DeviceButton;