@developer_tribe/react-builder 1.0.1 → 1.0.3

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/AttributesEditor.d.ts +3 -1
  2. package/dist/DeviceMockFrame.d.ts +2 -1
  3. package/dist/RenderPage.d.ts +5 -3
  4. package/dist/attributes-editor/Field.d.ts +17 -0
  5. package/dist/attributes-editor/FieldInfoTooltip.d.ts +7 -0
  6. package/dist/attributes-editor/LayoutPreviewPicker.d.ts +12 -0
  7. package/dist/attributes-editor/SpecialCategorySection.d.ts +20 -0
  8. package/dist/attributes-editor/types.d.ts +14 -0
  9. package/dist/background.jpg +0 -0
  10. package/dist/build-components/BackgroundImage/BackgroundImage.d.ts +5 -0
  11. package/dist/build-components/BackgroundImage/BackgroundImageProps.generated.d.ts +44 -0
  12. package/dist/build-components/Button/Button.d.ts +1 -1
  13. package/dist/build-components/Button/ButtonProps.generated.d.ts +33 -1
  14. package/dist/build-components/Carousel/CarouselProps.generated.d.ts +34 -1
  15. package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +32 -0
  16. package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +32 -0
  17. package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +34 -1
  18. package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +34 -1
  19. package/dist/build-components/Image/ImageProps.generated.d.ts +32 -3
  20. package/dist/build-components/Onboard/OnboardProps.generated.d.ts +34 -1
  21. package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +32 -0
  22. package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +32 -0
  23. package/dist/build-components/OnboardDot/OnboardDot.d.ts +1 -1
  24. package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +29 -0
  25. package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +11 -5
  26. package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +32 -3
  27. package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +31 -3
  28. package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +32 -5
  29. package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +11 -5
  30. package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +11 -5
  31. package/dist/build-components/Text/TextProps.generated.d.ts +11 -5
  32. package/dist/build-components/View/ViewProps.generated.d.ts +10 -4
  33. package/dist/build-components/index.d.ts +2 -1
  34. package/dist/build-components/patterns.generated.d.ts +6288 -136
  35. package/dist/components/AttributesEditorPanel.d.ts +3 -4
  36. package/dist/components/Breadcrumb.d.ts +3 -1
  37. package/dist/components/Builder.d.ts +2 -1
  38. package/dist/components/BuilderButton.d.ts +9 -0
  39. package/dist/components/Checkbox.d.ts +17 -0
  40. package/dist/components/DeviceButton.d.ts +8 -0
  41. package/dist/components/DeviceNavigationBar.d.ts +10 -0
  42. package/dist/components/DeviceStatusBar.d.ts +9 -0
  43. package/dist/components/EditorHeader.d.ts +3 -8
  44. package/dist/index.cjs.js +5 -5
  45. package/dist/index.cjs.js.map +1 -1
  46. package/dist/index.d.ts +2 -2
  47. package/dist/index.esm.js +5 -5
  48. package/dist/index.esm.js.map +1 -1
  49. package/dist/mockOS/components/MockLaunchScreenComponent.d.ts +6 -0
  50. package/dist/mockOS/components/MockOSRouter.d.ts +8 -0
  51. package/dist/mockOS/components/PermissionModal.d.ts +9 -0
  52. package/dist/mockOS/context/MockOSContext.d.ts +36 -0
  53. package/dist/mockOS/hooks/useMockNavigation.d.ts +3 -0
  54. package/dist/mockOS/hooks/useMockPermission.d.ts +3 -0
  55. package/dist/mockOS/index.d.ts +9 -0
  56. package/dist/mockOS/managers/mockPermissionManager.d.ts +10 -0
  57. package/dist/mockOS/managers/navigationManager.d.ts +17 -0
  58. package/dist/modals/AddComponentModal.d.ts +8 -0
  59. package/dist/modals/ColorModal.d.ts +11 -0
  60. package/dist/modals/DeviceSelectorModal.d.ts +9 -0
  61. package/dist/modals/LocalicationModal.d.ts +8 -0
  62. package/dist/modals/Modal.d.ts +12 -0
  63. package/dist/modals/index.d.ts +5 -0
  64. package/dist/pages/ProjectPage.d.ts +3 -3
  65. package/dist/pages/tabs/BuilderPanel.d.ts +8 -0
  66. package/dist/pages/tabs/{DebugTab.d.ts → SideTool.d.ts} +2 -2
  67. package/dist/store.d.ts +7 -3
  68. package/dist/styles.css +1 -1
  69. package/dist/types/Project.d.ts +11 -0
  70. package/dist/utils/analyseNode.d.ts +1 -0
  71. package/dist/utils/extractTextStyle.d.ts +8 -1
  72. package/dist/utils/extractViewStyle.d.ts +8 -1
  73. package/dist/utils/parseColor.d.ts +7 -0
  74. package/dist/utils/patterns.d.ts +24 -0
  75. package/package.json +2 -1
  76. package/scripts/prebuild/utils/createGeneratedProps.js +11 -3
  77. package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +45 -6
  78. package/scripts/prebuild/utils/validatePatternJson.js +13 -5
  79. package/src/AttributesEditor.tsx +493 -310
  80. package/src/DeviceMockFrame.tsx +21 -37
  81. package/src/RenderPage.tsx +86 -7
  82. package/src/assets/images/android.svg +42 -42
  83. package/src/assets/images/apple.svg +15 -15
  84. package/src/attributes-editor/Field.tsx +669 -0
  85. package/src/attributes-editor/FieldInfoTooltip.tsx +49 -0
  86. package/src/attributes-editor/LayoutPreviewPicker.tsx +199 -0
  87. package/src/attributes-editor/SpecialCategorySection.tsx +285 -0
  88. package/src/attributes-editor/types.ts +30 -0
  89. package/src/build-components/BackgroundImage/BackgroundImage.tsx +87 -0
  90. package/src/build-components/BackgroundImage/BackgroundImageProps.generated.ts +60 -0
  91. package/src/build-components/BackgroundImage/pattern.json +45 -0
  92. package/src/build-components/Button/Button.tsx +37 -2
  93. package/src/build-components/Button/ButtonProps.generated.ts +44 -1
  94. package/src/build-components/Button/pattern.json +31 -2
  95. package/src/build-components/Carousel/Carousel.tsx +39 -2
  96. package/src/build-components/Carousel/CarouselProps.generated.ts +46 -1
  97. package/src/build-components/Carousel/pattern.json +10 -0
  98. package/src/build-components/CarouselButtons/CarouselButtons.tsx +21 -2
  99. package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +43 -0
  100. package/src/build-components/CarouselButtons/pattern.json +22 -0
  101. package/src/build-components/CarouselDots/CarouselDots.tsx +49 -8
  102. package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +43 -0
  103. package/src/build-components/CarouselDots/pattern.json +15 -0
  104. package/src/build-components/CarouselItem/CarouselItem.tsx +21 -2
  105. package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +46 -1
  106. package/src/build-components/CarouselItem/pattern.json +7 -0
  107. package/src/build-components/CarouselProvider/CarouselProvider.tsx +21 -2
  108. package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +46 -1
  109. package/src/build-components/CarouselProvider/pattern.json +7 -0
  110. package/src/build-components/Image/Image.tsx +33 -2
  111. package/src/build-components/Image/ImageProps.generated.ts +43 -3
  112. package/src/build-components/Image/pattern.json +46 -3
  113. package/src/build-components/Onboard/Onboard.tsx +6 -1
  114. package/src/build-components/Onboard/OnboardProps.generated.ts +46 -1
  115. package/src/build-components/Onboard/pattern.json +11 -0
  116. package/src/build-components/OnboardButton/OnboardButton.tsx +54 -6
  117. package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +43 -0
  118. package/src/build-components/OnboardButton/pattern.json +71 -5
  119. package/src/build-components/OnboardButtons/OnboardButtons.tsx +33 -11
  120. package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +43 -0
  121. package/src/build-components/OnboardButtons/pattern.json +70 -4
  122. package/src/build-components/OnboardDot/OnboardDot.tsx +113 -4
  123. package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +29 -0
  124. package/src/build-components/OnboardDot/pattern.json +55 -2
  125. package/src/build-components/OnboardFooter/OnboardFooter.tsx +20 -4
  126. package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +11 -5
  127. package/src/build-components/OnboardFooter/pattern.json +58 -2
  128. package/src/build-components/OnboardImage/OnboardImage.tsx +49 -5
  129. package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +43 -3
  130. package/src/build-components/OnboardImage/pattern.json +21 -0
  131. package/src/build-components/OnboardItem/OnboardItem.tsx +17 -1
  132. package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +42 -3
  133. package/src/build-components/OnboardItem/pattern.json +38 -2
  134. package/src/build-components/OnboardProvider/OnboardProvider.tsx +52 -18
  135. package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +44 -5
  136. package/src/build-components/OnboardProvider/pattern.json +44 -5
  137. package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +11 -5
  138. package/src/build-components/OnboardSubtitle/pattern.json +7 -1
  139. package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +11 -5
  140. package/src/build-components/OnboardTitle/pattern.json +7 -1
  141. package/src/build-components/RenderNode.generated.tsx +3 -0
  142. package/src/build-components/Text/Text.tsx +34 -6
  143. package/src/build-components/Text/TextProps.generated.ts +11 -5
  144. package/src/build-components/Text/pattern.json +38 -2
  145. package/src/build-components/View/View.tsx +33 -6
  146. package/src/build-components/View/ViewProps.generated.ts +10 -4
  147. package/src/build-components/View/pattern.json +285 -19
  148. package/src/build-components/index.ts +5 -0
  149. package/src/build-components/patterns.generated.ts +6346 -143
  150. package/src/components/AttributesEditorPanel.tsx +17 -64
  151. package/src/components/Breadcrumb.tsx +37 -5
  152. package/src/components/Builder.tsx +311 -108
  153. package/src/components/BuilderButton.tsx +127 -0
  154. package/src/components/Checkbox.tsx +81 -0
  155. package/src/components/DeviceButton.tsx +39 -0
  156. package/src/components/DeviceNavigationBar.tsx +201 -0
  157. package/src/components/DeviceStatusBar.tsx +85 -0
  158. package/src/components/EditorHeader.tsx +26 -74
  159. package/src/index.ts +2 -2
  160. package/src/mockOS/components/MockLaunchScreenComponent.tsx +43 -0
  161. package/src/mockOS/components/MockOSRouter.tsx +123 -0
  162. package/src/mockOS/components/PermissionModal.tsx +270 -0
  163. package/src/mockOS/context/MockOSContext.tsx +179 -0
  164. package/src/mockOS/hooks/useMockNavigation.ts +11 -0
  165. package/src/mockOS/hooks/useMockPermission.ts +11 -0
  166. package/src/mockOS/index.ts +26 -0
  167. package/src/mockOS/managers/mockPermissionManager.ts +54 -0
  168. package/src/mockOS/managers/navigationManager.ts +91 -0
  169. package/src/modals/AddComponentModal.tsx +313 -0
  170. package/src/modals/ColorModal.tsx +425 -0
  171. package/src/modals/DeviceSelectorModal.tsx +57 -0
  172. package/src/modals/LocalicationModal.tsx +54 -0
  173. package/src/modals/Modal.tsx +57 -0
  174. package/src/modals/index.ts +5 -0
  175. package/src/pages/ProjectPage.tsx +307 -71
  176. package/src/pages/tabs/{BuilderTab.tsx → BuilderPanel.tsx} +13 -9
  177. package/src/pages/tabs/SideTool.tsx +259 -0
  178. package/src/size-matters/index.ts +27 -5
  179. package/src/store.ts +13 -5
  180. package/src/styles/base/_global.scss +404 -0
  181. package/src/styles/components/_attributes-editor.scss +273 -0
  182. package/src/styles/components/_editor-shell.scss +212 -0
  183. package/src/styles/components/_mockos-router.scss +140 -0
  184. package/src/styles/components/_ui-components.scss +183 -0
  185. package/src/styles/foundation/_colors.scss +8 -0
  186. package/src/styles/{_mixins.scss → foundation/_mixins.scss} +5 -4
  187. package/src/styles/{_reset.scss → foundation/_reset.scss} +5 -2
  188. package/src/styles/foundation/_sizes.scss +37 -0
  189. package/src/styles/foundation/_typography.scss +4 -0
  190. package/src/styles/foundation/_variables.scss +3 -0
  191. package/src/styles/index.scss +22 -136
  192. package/src/styles/layout/_builder.scss +124 -0
  193. package/src/styles/layout/_pages.scss +3 -0
  194. package/src/styles/modals/_add-component.scss +122 -0
  195. package/src/styles/modals/_color-modal.scss +159 -0
  196. package/src/styles/modals/_device-selector.scss +18 -0
  197. package/src/styles/modals/_localication-modal.scss +68 -0
  198. package/src/styles/modals/_modal-shell.scss +46 -0
  199. package/src/styles/utilities/_carousel.scss +125 -0
  200. package/src/types/Project.ts +14 -0
  201. package/src/types/images.d.ts +8 -0
  202. package/src/utils/analyseNode.ts +98 -0
  203. package/src/utils/extractTextStyle.ts +28 -10
  204. package/src/utils/extractViewStyle.ts +77 -9
  205. package/src/utils/parseColor.ts +43 -0
  206. package/src/utils/patterns.ts +33 -0
  207. package/dist/build-components/OnboardDot/OnboardExpandingDotProps.generated.d.ts +0 -10
  208. package/dist/pages/tabs/BuilderTab.d.ts +0 -9
  209. package/dist/pages/tabs/PreviewTab.d.ts +0 -3
  210. package/src/build-components/OnboardDot/OnboardExpandingDotProps.generated.ts +0 -20
  211. package/src/pages/tabs/DebugTab.tsx +0 -23
  212. package/src/pages/tabs/PreviewTab.tsx +0 -194
  213. package/src/styles/_variables.scss +0 -27
  214. package/src/styles/builder.scss +0 -60
  215. package/src/styles/components.scss +0 -88
  216. package/src/styles/editor.scss +0 -174
  217. package/src/styles/global.scss +0 -200
  218. package/src/styles/pages.scss +0 -2
@@ -1,35 +1,76 @@
1
- import { useEffect, useState } from 'react';
2
- import { Node, RenderPage, Project } from '..';
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { Node, NodeData, RenderPage, Project, ProjectColors } from '..';
3
3
  import { EditorHeader } from '../components/EditorHeader';
4
4
  import { AttributesEditorPanel } from '../components/AttributesEditorPanel';
5
- import { BuilderTab } from './tabs/BuilderTab';
6
- import { PreviewTab } from './tabs/PreviewTab';
7
- import { DebugTab } from './tabs/DebugTab';
5
+ import { BuilderPanel } from './tabs/BuilderPanel';
6
+ import { SideTool } from './tabs/SideTool';
8
7
  import { AppConfig, defaultAppConfig } from '../types/PreviewConfig';
9
8
  import { useRenderStore } from '../store';
10
9
  import { logger } from '../utils/logger';
11
10
  import { useLogRender } from '../utils/useLogRender';
12
11
  import type { LogLevel } from '../types/Project';
13
-
14
- export type Tab = 'builder' | 'preview' | 'debug';
15
-
12
+ import { analyseAndProccess } from '../utils/analyseNode';
13
+ import backgroundImage from '../assets/images/background.jpg';
16
14
  export type ProjectPageProps = {
17
15
  project: Project;
18
16
  onSaveProject: (project: Project) => void;
19
17
  appConfig?: AppConfig;
20
18
  logLevel?: LogLevel;
19
+ projectColors?: ProjectColors;
21
20
  };
22
21
 
22
+ const MOBILE_BREAKPOINT = 1000;
23
+
23
24
  export function ProjectPage({
24
25
  project,
25
26
  appConfig = defaultAppConfig,
26
27
  onSaveProject,
27
28
  logLevel,
29
+ projectColors,
28
30
  }: ProjectPageProps) {
29
31
  useLogRender('ProjectPage');
30
- const [current, setCurrent] = useState<Node>(project.data);
31
- const [editorData, setEditorData] = useState<Node>(project.data);
32
- const [tab, setTab] = useState<Tab>('builder');
32
+ const resolvedProjectColors = projectColors ?? project.projectColors;
33
+ const { current, setCurrent, setProjectColors } = useRenderStore((s) => ({
34
+ current: s.current,
35
+ setCurrent: s.setCurrent,
36
+ setProjectColors: s.setProjectColors,
37
+ }));
38
+ const [editorData, setEditorData] = useState<Node>(null);
39
+ const [mobilePanel, setMobilePanel] = useState<
40
+ 'builder' | 'attributes' | null
41
+ >(null);
42
+ const [isMobile, setIsMobile] = useState<boolean>(() => {
43
+ if (typeof window === 'undefined') return false;
44
+ return window.innerWidth <= MOBILE_BREAKPOINT;
45
+ });
46
+
47
+ const handleDeleteNode = useCallback(
48
+ (nodeToDelete: Node) => {
49
+ // Do not allow deleting the root node
50
+ if (nodeToDelete === editorData) {
51
+ return;
52
+ }
53
+ const updated: Node = deleteNodeFromTree(editorData, nodeToDelete);
54
+ //@ts-ignore
55
+ setEditorData(updated);
56
+
57
+ if (current === nodeToDelete) {
58
+ setCurrent(updated);
59
+ return;
60
+ }
61
+
62
+ if (isNodeRecord(current) && nodeHasChild(current, nodeToDelete)) {
63
+ const currentKey = current.key;
64
+ if (currentKey) {
65
+ const nextCurrent = findNodeByKey(updated, currentKey);
66
+ setCurrent(nextCurrent ?? updated);
67
+ } else {
68
+ setCurrent(updated);
69
+ }
70
+ }
71
+ },
72
+ [editorData, current],
73
+ );
33
74
 
34
75
  useEffect(() => {
35
76
  logger.info('ProjectPage', 'mount', { projectName: project.name });
@@ -40,11 +81,59 @@ export function ProjectPage({
40
81
  };
41
82
  }, [appConfig, project.name]);
42
83
 
84
+ useEffect(() => {
85
+ setProjectColors(resolvedProjectColors);
86
+ return () => setProjectColors(undefined);
87
+ }, [resolvedProjectColors, setProjectColors]);
88
+
43
89
  useEffect(() => {
44
90
  if (!logLevel) return;
45
91
  logger.setLevel(logLevel);
46
92
  }, [logLevel]);
47
93
 
94
+ useEffect(() => {
95
+ function handleResize() {
96
+ setIsMobile(window.innerWidth <= MOBILE_BREAKPOINT);
97
+ }
98
+
99
+ handleResize();
100
+ window.addEventListener('resize', handleResize);
101
+ return () => window.removeEventListener('resize', handleResize);
102
+ }, []);
103
+
104
+ useEffect(() => {
105
+ setMobilePanel(null);
106
+ }, [isMobile]);
107
+
108
+ const toggleMobilePanel = (panel: 'builder' | 'attributes') => {
109
+ setMobilePanel((prev) => (prev === panel ? null : panel));
110
+ };
111
+
112
+ const closeMobilePanels = () => {
113
+ setMobilePanel(null);
114
+ };
115
+
116
+ const leftPanelIsOpen = !isMobile || mobilePanel === 'builder';
117
+ const attributesPanelIsOpen = !isMobile || mobilePanel === 'attributes';
118
+
119
+ useEffect(() => {
120
+ try {
121
+ const processed = analyseAndProccess(project.data);
122
+ if (!processed) {
123
+ setEditorData(null);
124
+ setCurrent(null);
125
+ return;
126
+ }
127
+ setEditorData(processed);
128
+ setCurrent(processed);
129
+ } catch (error) {
130
+ // eslint-disable-next-line no-alert
131
+ alert('Duplicate node key detected. Please check your project data.');
132
+ setEditorData(null);
133
+ setCurrent(null);
134
+ }
135
+ }, [project.data]);
136
+
48
137
  return (
49
138
  <div className="container-full">
50
139
  <EditorHeader
@@ -55,77 +144,119 @@ export function ProjectPage({
55
144
  data: editorData,
56
145
  });
57
146
  }}
147
+ onRestoreProject={() => {
148
+ logger.info('ProjectPage', 'restore project', { name: project.name });
149
+ setEditorData(project.data);
150
+ setCurrent(project.data);
151
+ }}
58
152
  current={current}
59
153
  editorData={editorData}
60
154
  setEditorData={setEditorData}
61
155
  />
156
+ {isMobile && (
157
+ <div
158
+ className="mobile-panel-toggle"
159
+ role="group"
160
+ aria-label="Editor panels"
161
+ >
162
+ <button
163
+ type="button"
164
+ className={`mobile-panel-toggle__button${mobilePanel === 'builder' ? ' mobile-panel-toggle__button--active' : ''}`}
165
+ aria-label="Toggle builder panel"
166
+ aria-expanded={mobilePanel === 'builder'}
167
+ aria-controls="split-left-panel"
168
+ onClick={() => toggleMobilePanel('builder')}
169
+ >
170
+ <span className="mobile-panel-toggle__icon" aria-hidden="true">
171
+ <svg viewBox="0 0 16 12" role="presentation" focusable="false">
172
+ <path
173
+ d="M1 1h14M1 6h14M1 11h14"
174
+ stroke="currentColor"
175
+ strokeWidth="2"
176
+ strokeLinecap="round"
177
+ fill="none"
178
+ />
179
+ </svg>
180
+ </span>
181
+ <span className="mobile-panel-toggle__label">Builder</span>
182
+ </button>
183
+ <button
184
+ type="button"
185
+ className={`mobile-panel-toggle__button${mobilePanel === 'attributes' ? ' mobile-panel-toggle__button--active' : ''}`}
186
+ aria-label="Toggle attributes panel"
187
+ aria-expanded={mobilePanel === 'attributes'}
188
+ aria-controls="split-attributes-panel"
189
+ onClick={() => toggleMobilePanel('attributes')}
190
+ >
191
+ <span className="mobile-panel-toggle__icon" aria-hidden="true">
192
+ <svg viewBox="0 0 16 12" role="presentation" focusable="false">
193
+ <path
194
+ d="M1 1h14M1 6h14M1 11h14"
195
+ stroke="currentColor"
196
+ strokeWidth="2"
197
+ strokeLinecap="round"
198
+ fill="none"
199
+ />
200
+ </svg>
201
+ </span>
202
+ <span className="mobile-panel-toggle__label">Attributes</span>
203
+ </button>
204
+ </div>
205
+ )}
62
206
  <div className="editor-container">
63
- <div className="split-left">
64
- <div>
65
- <div
66
- className="editor-tabs"
67
- role="tablist"
68
- aria-label="Editor tabs"
207
+ <div
208
+ id="split-left-panel"
209
+ className={`split-left${leftPanelIsOpen ? ' is-open' : ''}`}
210
+ aria-hidden={isMobile && !leftPanelIsOpen}
211
+ >
212
+ {isMobile && (
213
+ <button
214
+ type="button"
215
+ className="split-panel__close"
216
+ aria-label="Close builder panel"
217
+ onClick={closeMobilePanels}
69
218
  >
70
- <button
71
- className={`editor-tab ${tab === 'builder' ? 'editor-tab--active' : ''}`}
72
- role="tab"
73
- aria-selected={tab === 'builder'}
74
- onClick={() => {
75
- setTab('builder');
76
- logger.info('ProjectPage', 'tab change', { to: 'builder' });
77
- }}
78
- >
79
- Builder
80
- </button>
81
- <button
82
- className={`editor-tab ${tab === 'preview' ? 'editor-tab--active' : ''}`}
83
- role="tab"
84
- aria-selected={tab === 'preview'}
85
- onClick={() => {
86
- setTab('preview');
87
- logger.info('ProjectPage', 'tab change', { to: 'preview' });
88
- }}
89
- >
90
- Preview Config
91
- </button>
92
- <button
93
- className={`editor-tab ${tab === 'debug' ? 'editor-tab--active' : ''}`}
94
- role="tab"
95
- aria-selected={tab === 'debug'}
96
- onClick={() => {
97
- setTab('debug');
98
- logger.info('ProjectPage', 'tab change', { to: 'debug' });
99
- }}
100
- >
101
- Debug
102
- </button>
103
- </div>
104
-
105
- {tab === 'builder' && (
106
- <BuilderTab
107
- data={editorData}
108
- setData={setEditorData}
109
- current={current}
110
- setCurrent={setCurrent}
111
- />
112
- )}
113
-
114
- {tab === 'preview' && <PreviewTab />}
115
-
116
- {tab === 'debug' && (
117
- <DebugTab data={editorData} setData={setEditorData} />
118
- )}
219
+ Close
220
+ </button>
221
+ )}
222
+ <div>
223
+ <BuilderPanel
224
+ data={editorData}
225
+ setData={setEditorData}
226
+ onDeleteNode={handleDeleteNode}
227
+ />
119
228
  </div>
120
229
  </div>
121
230
  <div className="split-right">
122
- <div className="split-right-background" />
123
- <RenderPage data={editorData} />
231
+ <div className="split-right__controls">
232
+ <SideTool data={editorData} setData={setEditorData} />
233
+ </div>
234
+ <div
235
+ className="split-right-background"
236
+ style={{
237
+ backgroundImage: `url(${backgroundImage})`,
238
+ }}
239
+ />
240
+ {editorData && <RenderPage data={editorData} name={project.name} />}
124
241
  </div>
125
- <div className="split-third">
242
+ <div
243
+ id="split-attributes-panel"
244
+ className={`split-third${attributesPanelIsOpen ? ' is-open' : ''}`}
245
+ aria-hidden={isMobile && !attributesPanelIsOpen}
246
+ >
247
+ {isMobile && (
248
+ <button
249
+ type="button"
250
+ className="split-panel__close"
251
+ aria-label="Close attributes panel"
252
+ onClick={closeMobilePanels}
253
+ >
254
+ Close
255
+ </button>
256
+ )}
126
257
  <AttributesEditorPanel
127
- current={current}
128
258
  attributes={editorData}
259
+ projectColors={resolvedProjectColors}
129
260
  onChange={(data) => {
130
261
  setEditorData(data);
131
262
  let nodeKey: string | undefined = undefined;
@@ -143,10 +274,115 @@ export function ProjectPage({
143
274
  nodeKey ? { nodeKey } : undefined,
144
275
  );
145
276
  }}
146
- setCurrent={setCurrent}
147
277
  />
148
278
  </div>
279
+ {isMobile && mobilePanel && (
280
+ <button
281
+ type="button"
282
+ className="editor-container__overlay"
283
+ aria-label="Close active panel"
284
+ onClick={closeMobilePanels}
285
+ />
286
+ )}
149
287
  </div>
150
288
  </div>
151
289
  );
152
290
  }
291
+
292
+ function deleteNodeFromTree(root: Node, target: Node): Node {
293
+ if (root === null || root === undefined) return root;
294
+ if (typeof root === 'string') return root;
295
+
296
+ if (Array.isArray(root)) {
297
+ let changed = false;
298
+ const nextChildren: Node[] = [];
299
+ for (const child of root) {
300
+ if (child === target) {
301
+ changed = true;
302
+ continue;
303
+ }
304
+ const nextChild = deleteNodeFromTree(child, target);
305
+ if (nextChild !== child) changed = true;
306
+ nextChildren.push(nextChild);
307
+ }
308
+ return changed ? nextChildren : root;
309
+ }
310
+
311
+ const data = root as any;
312
+ if ('children' in data) {
313
+ const prev = data.children as Node;
314
+ if (!prev) return root;
315
+
316
+ if (Array.isArray(prev)) {
317
+ let changed = false;
318
+ const nextChildren: Node[] = [];
319
+ for (const child of prev) {
320
+ if (child === target) {
321
+ changed = true;
322
+ continue;
323
+ }
324
+ const nextChild = deleteNodeFromTree(child, target);
325
+ if (nextChild !== child) changed = true;
326
+ nextChildren.push(nextChild);
327
+ }
328
+ if (changed) {
329
+ return { ...data, children: nextChildren } as Node;
330
+ }
331
+ return root;
332
+ }
333
+
334
+ if (prev === target) {
335
+ return { ...data, children: '' } as Node;
336
+ }
337
+
338
+ const nextChild = deleteNodeFromTree(prev, target);
339
+ if (nextChild !== prev) {
340
+ return { ...data, children: nextChild } as Node;
341
+ }
342
+ }
343
+
344
+ return root;
345
+ }
346
+
347
+ function isNodeRecord(node: Node): node is NodeData {
348
+ return (
349
+ node !== null &&
350
+ node !== undefined &&
351
+ typeof node === 'object' &&
352
+ !Array.isArray(node)
353
+ );
354
+ }
355
+
356
+ function nodeHasChild(parent: NodeData, potentialChild: Node): boolean {
357
+ const { children } = parent;
358
+ if (!children) return false;
359
+ if (Array.isArray(children)) {
360
+ return children.some((child) => child === potentialChild);
361
+ }
362
+ return children === potentialChild;
363
+ }
364
+
365
+ function findNodeByKey(root: Node, key?: string): Node | null {
366
+ if (!key) return null;
367
+ if (root === null || root === undefined) return null;
368
+ if (typeof root === 'string') return null;
369
+
370
+ if (Array.isArray(root)) {
371
+ for (const child of root) {
372
+ const found = findNodeByKey(child, key);
373
+ if (found) return found;
374
+ }
375
+ return null;
376
+ }
377
+
378
+ const nodeData = root as NodeData;
379
+ if (nodeData.key === key) {
380
+ return nodeData;
381
+ }
382
+
383
+ if (nodeData.children) {
384
+ return findNodeByKey(nodeData.children as Node, key);
385
+ }
386
+
387
+ return null;
388
+ }
@@ -1,24 +1,27 @@
1
1
  import { Node } from '../..';
2
2
  import { useLogRender } from '../../utils/useLogRender';
3
3
  import { Builder } from '../../components/Builder';
4
+ import { useRenderStore } from '../../store';
4
5
 
5
- type BuilderTabProps = {
6
+ type BuilderPanelProps = {
6
7
  data: Node;
7
8
  setData: (data: Node) => void;
8
- current: Node;
9
- setCurrent: (current: Node) => void;
9
+ onDeleteNode: (node: Node) => void;
10
10
  };
11
11
 
12
- export function BuilderTab({
12
+ export function BuilderPanel({
13
13
  data,
14
14
  setData,
15
- current,
16
- setCurrent,
17
- }: BuilderTabProps) {
18
- useLogRender('BuilderTab');
15
+ onDeleteNode,
16
+ }: BuilderPanelProps) {
17
+ useLogRender('BuilderPanel');
18
+ const { current, setCurrent } = useRenderStore((s) => ({
19
+ current: s.current,
20
+ setCurrent: s.setCurrent,
21
+ }));
19
22
  return (
20
23
  <div
21
- role="tabpanel"
24
+ role="region"
22
25
  className="editor-panel-builder editor-panel editor-panel--active"
23
26
  aria-hidden={false}
24
27
  >
@@ -27,6 +30,7 @@ export function BuilderTab({
27
30
  setData={setData}
28
31
  current={current}
29
32
  setCurrent={setCurrent}
33
+ onDeleteNode={onDeleteNode}
30
34
  />
31
35
  </div>
32
36
  );