@developer_tribe/react-builder 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-components/index.d.ts +1 -2
- package/dist/build-components/patterns.generated.d.ts +0 -383
- package/dist/components/BottomBar.d.ts +12 -0
- package/dist/components/LoadingComponent.d.ts +1 -0
- package/dist/index.cjs.js +3 -3
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.esm.js +3 -3
- package/dist/index.esm.js.map +1 -1
- package/dist/index.native.cjs.js +1 -1
- package/dist/index.native.cjs.js.map +1 -1
- package/dist/index.native.esm.js +4 -4
- package/dist/index.native.esm.js.map +1 -1
- package/dist/modals/ScreenColorsModal.d.ts +8 -0
- package/dist/modals/index.d.ts +1 -0
- package/dist/pages/ProjectPage.d.ts +3 -2
- package/dist/styles.css +1 -1
- package/dist/utils/nodeTree.d.ts +5 -0
- package/package.json +5 -2
- package/src/build-components/RenderNode.generated.tsx +0 -7
- package/src/build-components/index.ts +0 -5
- package/src/build-components/patterns.generated.ts +0 -399
- package/src/components/BottomBar.tsx +242 -0
- package/src/components/LoadingComponent.tsx +10 -0
- package/src/index.ts +2 -1
- package/src/modals/ScreenColorsModal.tsx +121 -0
- package/src/modals/index.ts +1 -0
- package/src/pages/ProjectPage.tsx +72 -163
- package/src/styles/base/_global.scss +25 -20
- package/src/styles/components/_attributes-editor.scss +26 -24
- package/src/styles/components/_bottom-bar.scss +101 -0
- package/src/styles/components/_editor-shell.scss +19 -18
- package/src/styles/components/_mockos-router.scss +16 -14
- package/src/styles/components/_ui-components.scss +14 -15
- package/src/styles/foundation/_colors.scss +28 -8
- package/src/styles/foundation/_mixins.scss +1 -1
- package/src/styles/foundation/_sizes.scss +4 -2
- package/src/styles/index.scss +1 -0
- package/src/styles/layout/_builder.scss +1 -1
- package/src/styles/modals/_add-component.scss +2 -2
- package/src/styles/modals/_color-modal.scss +2 -2
- package/src/styles/modals/_modal-shell.scss +1 -1
- package/src/utils/nodeTree.ts +99 -0
- package/dist/build-components/PaywallSubscriButton/PaywallSubscriButton.d.ts +0 -5
- package/dist/build-components/PaywallSubscriButton/PaywallSubscriButtonProps.generated.d.ts +0 -50
- package/src/build-components/PaywallSubscriButton/PaywallSubscriButton.tsx +0 -10
- package/src/build-components/PaywallSubscriButton/PaywallSubscriButtonProps.generated.ts +0 -77
- package/src/build-components/PaywallSubscriButton/pattern.json +0 -27
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import type { AppConfig } from '../types/PreviewConfig';
|
|
3
|
+
import { defaultAppConfig } from '../types/PreviewConfig';
|
|
4
|
+
import Modal from './Modal';
|
|
5
|
+
|
|
6
|
+
type ScreenMode = 'light' | 'dark';
|
|
7
|
+
type ScreenColorKey = keyof AppConfig['screenStyle']['light'];
|
|
8
|
+
|
|
9
|
+
type ScreenColorsModalProps = {
|
|
10
|
+
appConfig: AppConfig;
|
|
11
|
+
onChange: (next: AppConfig) => void;
|
|
12
|
+
onClose: () => void;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type ColorField = {
|
|
16
|
+
id: string;
|
|
17
|
+
label: string;
|
|
18
|
+
mode: ScreenMode;
|
|
19
|
+
key: ScreenColorKey;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const colorFields: ColorField[] = [
|
|
23
|
+
{
|
|
24
|
+
id: 'light-bg',
|
|
25
|
+
label: 'Light Background',
|
|
26
|
+
mode: 'light',
|
|
27
|
+
key: 'backgroundColor',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'light-color',
|
|
31
|
+
label: 'Light Text',
|
|
32
|
+
mode: 'light',
|
|
33
|
+
key: 'color',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'dark-bg',
|
|
37
|
+
label: 'Dark Background',
|
|
38
|
+
mode: 'dark',
|
|
39
|
+
key: 'backgroundColor',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'dark-color',
|
|
43
|
+
label: 'Dark Text',
|
|
44
|
+
mode: 'dark',
|
|
45
|
+
key: 'color',
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
export function ScreenColorsModal({
|
|
50
|
+
appConfig,
|
|
51
|
+
onChange,
|
|
52
|
+
onClose,
|
|
53
|
+
}: ScreenColorsModalProps) {
|
|
54
|
+
const defaults = defaultAppConfig.screenStyle;
|
|
55
|
+
|
|
56
|
+
const getValue = (mode: ScreenMode, key: ScreenColorKey) =>
|
|
57
|
+
appConfig.screenStyle?.[mode]?.[key] ?? defaults[mode][key];
|
|
58
|
+
|
|
59
|
+
const handleChange = (mode: ScreenMode, key: ScreenColorKey, value: string) =>
|
|
60
|
+
onChange({
|
|
61
|
+
...appConfig,
|
|
62
|
+
screenStyle: {
|
|
63
|
+
...defaults,
|
|
64
|
+
...appConfig.screenStyle,
|
|
65
|
+
[mode]: {
|
|
66
|
+
...defaults[mode],
|
|
67
|
+
...appConfig.screenStyle?.[mode],
|
|
68
|
+
[key]: value,
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const headerDescription = useMemo(
|
|
74
|
+
() =>
|
|
75
|
+
'Edit light/dark screen background & text colors used in the preview.',
|
|
76
|
+
[],
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<Modal
|
|
81
|
+
onClose={onClose}
|
|
82
|
+
ariaLabelledBy="screen-colors-title"
|
|
83
|
+
contentClassName="localication-modal__content"
|
|
84
|
+
>
|
|
85
|
+
<div className="modal__header localication-modal__header">
|
|
86
|
+
<div className="localication-modal__header-main">
|
|
87
|
+
<h3 id="screen-colors-title" className="modal__title">
|
|
88
|
+
Screen Colors
|
|
89
|
+
</h3>
|
|
90
|
+
<p className="localication-modal__description">{headerDescription}</p>
|
|
91
|
+
</div>
|
|
92
|
+
<button type="button" className="editor-button" onClick={onClose}>
|
|
93
|
+
Close
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div className="localication-modal__body">
|
|
98
|
+
<div
|
|
99
|
+
style={{
|
|
100
|
+
display: 'grid',
|
|
101
|
+
gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
|
|
102
|
+
gap: 12,
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
{colorFields.map(({ id, label, mode, key }) => (
|
|
106
|
+
<React.Fragment key={id}>
|
|
107
|
+
<div style={{ alignSelf: 'center' }}>{label}</div>
|
|
108
|
+
<input
|
|
109
|
+
id={id}
|
|
110
|
+
type="color"
|
|
111
|
+
className="input input--color"
|
|
112
|
+
value={String(getValue(mode, key))}
|
|
113
|
+
onChange={(e) => handleChange(mode, key, e.target.value)}
|
|
114
|
+
/>
|
|
115
|
+
</React.Fragment>
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</Modal>
|
|
120
|
+
);
|
|
121
|
+
}
|
package/src/modals/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export { DeviceSelectorModal } from './DeviceSelectorModal';
|
|
|
4
4
|
export { ColorModal } from './ColorModal';
|
|
5
5
|
export { IconPickerModal } from './IconPickerModal';
|
|
6
6
|
export { LocalicationModal } from './LocalicationModal';
|
|
7
|
+
export { ScreenColorsModal } from './ScreenColorsModal';
|
|
7
8
|
export { MockableFeatureModal } from './MockableFeatureModal';
|
|
8
9
|
export { ProductEditModal } from './ProductEditModal';
|
|
9
10
|
export { ProductPresetsModal } from './ProductPresetsModal';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
-
import type { Node
|
|
2
|
+
import type { Node } from '../types/Node';
|
|
3
3
|
import type { Project, ProjectColors } from '../types/Project';
|
|
4
4
|
import { RenderPage } from '../RenderPage';
|
|
5
5
|
import { EditorHeader } from '../components/EditorHeader';
|
|
6
6
|
import { AttributesEditorPanel } from '../components/AttributesEditorPanel';
|
|
7
7
|
import { BuilderProvider } from '../components/BuilderProvider';
|
|
8
8
|
import { BuilderPanel } from './tabs/BuilderPanel';
|
|
9
|
-
import {
|
|
9
|
+
import { BottomBar } from '../components/BottomBar';
|
|
10
10
|
import { AppConfig, defaultAppConfig } from '../types/PreviewConfig';
|
|
11
11
|
import { useRenderStore } from '../store';
|
|
12
12
|
import { logger } from '../utils/logger';
|
|
@@ -15,15 +15,21 @@ import type { LogLevel } from '../types/Project';
|
|
|
15
15
|
import { analyseAndProccess } from '../utils/analyseNode';
|
|
16
16
|
import backgroundImage from '../assets/images/background.jpg';
|
|
17
17
|
import type { PaywallBenefits } from '../paywall/types/benefits';
|
|
18
|
-
import
|
|
19
|
-
import
|
|
18
|
+
import { LoadingComponent } from '../components/LoadingComponent';
|
|
19
|
+
import {
|
|
20
|
+
deleteNodeFromTree,
|
|
21
|
+
findNodeByKey,
|
|
22
|
+
isNodeRecord,
|
|
23
|
+
nodeHasChild,
|
|
24
|
+
} from '../utils/nodeTree';
|
|
20
25
|
export type ProjectPageProps = {
|
|
21
26
|
project: Project;
|
|
22
27
|
onSaveProject: (project: Project) => void;
|
|
23
28
|
appConfig?: AppConfig;
|
|
24
29
|
logLevel?: LogLevel;
|
|
25
30
|
projectColors?: ProjectColors;
|
|
26
|
-
|
|
31
|
+
onSaveProjectColors?: (colors: ProjectColors) => void;
|
|
32
|
+
name?: string;
|
|
27
33
|
};
|
|
28
34
|
|
|
29
35
|
const MOBILE_BREAKPOINT = 1000;
|
|
@@ -34,9 +40,11 @@ export function ProjectPage({
|
|
|
34
40
|
onSaveProject,
|
|
35
41
|
logLevel,
|
|
36
42
|
projectColors,
|
|
43
|
+
onSaveProjectColors,
|
|
37
44
|
name,
|
|
38
45
|
}: ProjectPageProps) {
|
|
39
46
|
useLogRender('ProjectPage');
|
|
47
|
+
const resolvedName = name ?? project.name;
|
|
40
48
|
const resolvedProjectColors = projectColors ?? project.projectColors;
|
|
41
49
|
const {
|
|
42
50
|
current,
|
|
@@ -45,7 +53,7 @@ export function ProjectPage({
|
|
|
45
53
|
setProjectName,
|
|
46
54
|
products,
|
|
47
55
|
benefits,
|
|
48
|
-
} = useRenderStore(
|
|
56
|
+
} = useRenderStore(s => ({
|
|
49
57
|
current: s.current,
|
|
50
58
|
setCurrent: s.setCurrent,
|
|
51
59
|
setProjectColors: s.setProjectColors,
|
|
@@ -69,7 +77,7 @@ export function ProjectPage({
|
|
|
69
77
|
// Extra warning for deleting the root node (editorData)
|
|
70
78
|
if (nodeToDelete === editorData) {
|
|
71
79
|
const shouldDeleteRoot = window.confirm(
|
|
72
|
-
'You are about to delete the root component. This will clear the entire screen. Continue?'
|
|
80
|
+
'You are about to delete the root component. This will clear the entire screen. Continue?'
|
|
73
81
|
);
|
|
74
82
|
if (!shouldDeleteRoot) return;
|
|
75
83
|
setEditorData(null);
|
|
@@ -95,7 +103,7 @@ export function ProjectPage({
|
|
|
95
103
|
}
|
|
96
104
|
}
|
|
97
105
|
},
|
|
98
|
-
[editorData, current]
|
|
106
|
+
[editorData, current]
|
|
99
107
|
);
|
|
100
108
|
|
|
101
109
|
useEffect(() => {
|
|
@@ -108,8 +116,8 @@ export function ProjectPage({
|
|
|
108
116
|
}, [appConfig, project.name]);
|
|
109
117
|
|
|
110
118
|
useEffect(() => {
|
|
111
|
-
setProjectName(
|
|
112
|
-
}, [
|
|
119
|
+
setProjectName(resolvedName);
|
|
120
|
+
}, [resolvedName, setProjectName]);
|
|
113
121
|
|
|
114
122
|
useEffect(() => {
|
|
115
123
|
setProjectColors(resolvedProjectColors);
|
|
@@ -136,7 +144,7 @@ export function ProjectPage({
|
|
|
136
144
|
}, [isMobile]);
|
|
137
145
|
|
|
138
146
|
const toggleMobilePanel = (panel: 'builder' | 'attributes') => {
|
|
139
|
-
setMobilePanel(
|
|
147
|
+
setMobilePanel(prev => (prev === panel ? null : panel));
|
|
140
148
|
};
|
|
141
149
|
|
|
142
150
|
const closeMobilePanels = () => {
|
|
@@ -176,10 +184,13 @@ export function ProjectPage({
|
|
|
176
184
|
const showLoading = editorData === null || !minLoadingDelayDone;
|
|
177
185
|
|
|
178
186
|
return (
|
|
179
|
-
<div className=
|
|
187
|
+
<div className='container-full'>
|
|
180
188
|
<EditorHeader
|
|
181
189
|
onSaveProject={() => {
|
|
182
190
|
logger.info('ProjectPage', 'save project', { name: project.name });
|
|
191
|
+
if (onSaveProjectColors && resolvedProjectColors) {
|
|
192
|
+
onSaveProjectColors(resolvedProjectColors);
|
|
193
|
+
}
|
|
183
194
|
onSaveProject({
|
|
184
195
|
...project,
|
|
185
196
|
data: editorData,
|
|
@@ -196,65 +207,65 @@ export function ProjectPage({
|
|
|
196
207
|
/>
|
|
197
208
|
{isMobile && (
|
|
198
209
|
<div
|
|
199
|
-
className=
|
|
200
|
-
role=
|
|
201
|
-
aria-label=
|
|
210
|
+
className='mobile-panel-toggle'
|
|
211
|
+
role='group'
|
|
212
|
+
aria-label='Editor panels'
|
|
202
213
|
>
|
|
203
214
|
<button
|
|
204
|
-
type=
|
|
215
|
+
type='button'
|
|
205
216
|
className={`mobile-panel-toggle__button${mobilePanel === 'builder' ? ' mobile-panel-toggle__button--active' : ''}`}
|
|
206
|
-
aria-label=
|
|
217
|
+
aria-label='Toggle builder panel'
|
|
207
218
|
aria-expanded={mobilePanel === 'builder'}
|
|
208
|
-
aria-controls=
|
|
219
|
+
aria-controls='split-left-panel'
|
|
209
220
|
onClick={() => toggleMobilePanel('builder')}
|
|
210
221
|
>
|
|
211
|
-
<span className=
|
|
212
|
-
<svg viewBox=
|
|
222
|
+
<span className='mobile-panel-toggle__icon' aria-hidden='true'>
|
|
223
|
+
<svg viewBox='0 0 16 12' role='presentation' focusable='false'>
|
|
213
224
|
<path
|
|
214
|
-
d=
|
|
215
|
-
stroke=
|
|
216
|
-
strokeWidth=
|
|
217
|
-
strokeLinecap=
|
|
218
|
-
fill=
|
|
225
|
+
d='M1 1h14M1 6h14M1 11h14'
|
|
226
|
+
stroke='currentColor'
|
|
227
|
+
strokeWidth='2'
|
|
228
|
+
strokeLinecap='round'
|
|
229
|
+
fill='none'
|
|
219
230
|
/>
|
|
220
231
|
</svg>
|
|
221
232
|
</span>
|
|
222
|
-
<span className=
|
|
233
|
+
<span className='mobile-panel-toggle__label'>Builder</span>
|
|
223
234
|
</button>
|
|
224
235
|
<button
|
|
225
|
-
type=
|
|
236
|
+
type='button'
|
|
226
237
|
className={`mobile-panel-toggle__button${mobilePanel === 'attributes' ? ' mobile-panel-toggle__button--active' : ''}`}
|
|
227
|
-
aria-label=
|
|
238
|
+
aria-label='Toggle attributes panel'
|
|
228
239
|
aria-expanded={mobilePanel === 'attributes'}
|
|
229
|
-
aria-controls=
|
|
240
|
+
aria-controls='split-attributes-panel'
|
|
230
241
|
onClick={() => toggleMobilePanel('attributes')}
|
|
231
242
|
>
|
|
232
|
-
<span className=
|
|
233
|
-
<svg viewBox=
|
|
243
|
+
<span className='mobile-panel-toggle__icon' aria-hidden='true'>
|
|
244
|
+
<svg viewBox='0 0 16 12' role='presentation' focusable='false'>
|
|
234
245
|
<path
|
|
235
|
-
d=
|
|
236
|
-
stroke=
|
|
237
|
-
strokeWidth=
|
|
238
|
-
strokeLinecap=
|
|
239
|
-
fill=
|
|
246
|
+
d='M1 1h14M1 6h14M1 11h14'
|
|
247
|
+
stroke='currentColor'
|
|
248
|
+
strokeWidth='2'
|
|
249
|
+
strokeLinecap='round'
|
|
250
|
+
fill='none'
|
|
240
251
|
/>
|
|
241
252
|
</svg>
|
|
242
253
|
</span>
|
|
243
|
-
<span className=
|
|
254
|
+
<span className='mobile-panel-toggle__label'>Attributes</span>
|
|
244
255
|
</button>
|
|
245
256
|
</div>
|
|
246
257
|
)}
|
|
247
|
-
<div className=
|
|
258
|
+
<div className='editor-container'>
|
|
248
259
|
<div
|
|
249
|
-
id=
|
|
260
|
+
id='split-left-panel'
|
|
250
261
|
className={`split-left${leftPanelIsOpen ? ' is-open' : ''}`}
|
|
251
262
|
aria-hidden={isMobile && !leftPanelIsOpen}
|
|
252
263
|
>
|
|
253
264
|
{isMobile && (
|
|
254
265
|
<button
|
|
255
|
-
type=
|
|
256
|
-
className=
|
|
257
|
-
aria-label=
|
|
266
|
+
type='button'
|
|
267
|
+
className='split-panel__close'
|
|
268
|
+
aria-label='Close builder panel'
|
|
258
269
|
onClick={closeMobilePanels}
|
|
259
270
|
>
|
|
260
271
|
Close
|
|
@@ -269,13 +280,15 @@ export function ProjectPage({
|
|
|
269
280
|
</div>
|
|
270
281
|
</div>
|
|
271
282
|
<div
|
|
272
|
-
style={{
|
|
273
|
-
|
|
283
|
+
style={{
|
|
284
|
+
// Set as a CSS variable so `.dark .split-right` can override it.
|
|
285
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
286
|
+
['--rb-canvas-bg' as any]: `url(${backgroundImage})`,
|
|
287
|
+
}}
|
|
288
|
+
className='split-right'
|
|
274
289
|
>
|
|
275
|
-
<SideTool data={editorData} setData={setEditorData} />
|
|
276
|
-
|
|
277
290
|
{showLoading && (
|
|
278
|
-
<div className=
|
|
291
|
+
<div className='rb-loading-overlay' aria-busy='true'>
|
|
279
292
|
<LoadingComponent />
|
|
280
293
|
</div>
|
|
281
294
|
)}
|
|
@@ -290,20 +303,22 @@ export function ProjectPage({
|
|
|
290
303
|
: {},
|
|
291
304
|
}}
|
|
292
305
|
>
|
|
293
|
-
<RenderPage data={editorData} name={
|
|
306
|
+
<RenderPage data={editorData} name={resolvedName} />
|
|
294
307
|
</BuilderProvider>
|
|
295
308
|
)}
|
|
296
309
|
</div>
|
|
310
|
+
{/* BOTOM BAR */}
|
|
311
|
+
<BottomBar data={editorData} setData={setEditorData} />
|
|
297
312
|
<div
|
|
298
|
-
id=
|
|
313
|
+
id='split-attributes-panel'
|
|
299
314
|
className={`split-third${attributesPanelIsOpen ? ' is-open' : ''}`}
|
|
300
315
|
aria-hidden={isMobile && !attributesPanelIsOpen}
|
|
301
316
|
>
|
|
302
317
|
{isMobile && (
|
|
303
318
|
<button
|
|
304
|
-
type=
|
|
305
|
-
className=
|
|
306
|
-
aria-label=
|
|
319
|
+
type='button'
|
|
320
|
+
className='split-panel__close'
|
|
321
|
+
aria-label='Close attributes panel'
|
|
307
322
|
onClick={closeMobilePanels}
|
|
308
323
|
>
|
|
309
324
|
Close
|
|
@@ -312,7 +327,7 @@ export function ProjectPage({
|
|
|
312
327
|
<AttributesEditorPanel
|
|
313
328
|
attributes={editorData}
|
|
314
329
|
projectColors={resolvedProjectColors}
|
|
315
|
-
onChange={
|
|
330
|
+
onChange={data => {
|
|
316
331
|
setEditorData(data);
|
|
317
332
|
let nodeKey: string | undefined = undefined;
|
|
318
333
|
if (
|
|
@@ -326,16 +341,16 @@ export function ProjectPage({
|
|
|
326
341
|
logger.verbose(
|
|
327
342
|
'ProjectPage',
|
|
328
343
|
'attributes change',
|
|
329
|
-
nodeKey ? { nodeKey } : undefined
|
|
344
|
+
nodeKey ? { nodeKey } : undefined
|
|
330
345
|
);
|
|
331
346
|
}}
|
|
332
347
|
/>
|
|
333
348
|
</div>
|
|
334
349
|
{isMobile && mobilePanel && (
|
|
335
350
|
<button
|
|
336
|
-
type=
|
|
337
|
-
className=
|
|
338
|
-
aria-label=
|
|
351
|
+
type='button'
|
|
352
|
+
className='editor-container__overlay'
|
|
353
|
+
aria-label='Close active panel'
|
|
339
354
|
onClick={closeMobilePanels}
|
|
340
355
|
/>
|
|
341
356
|
)}
|
|
@@ -343,109 +358,3 @@ export function ProjectPage({
|
|
|
343
358
|
</div>
|
|
344
359
|
);
|
|
345
360
|
}
|
|
346
|
-
|
|
347
|
-
function LoadingComponent() {
|
|
348
|
-
return (
|
|
349
|
-
<div className="rb-loading">
|
|
350
|
-
<Lottie animationData={loadingAnimation as any} loop autoplay />
|
|
351
|
-
</div>
|
|
352
|
-
);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function deleteNodeFromTree(root: Node, target: Node): Node {
|
|
356
|
-
if (root === null || root === undefined) return root;
|
|
357
|
-
if (typeof root === 'string') return root;
|
|
358
|
-
|
|
359
|
-
if (Array.isArray(root)) {
|
|
360
|
-
let changed = false;
|
|
361
|
-
const nextChildren: Node[] = [];
|
|
362
|
-
for (const child of root) {
|
|
363
|
-
if (child === target) {
|
|
364
|
-
changed = true;
|
|
365
|
-
continue;
|
|
366
|
-
}
|
|
367
|
-
const nextChild = deleteNodeFromTree(child, target);
|
|
368
|
-
if (nextChild !== child) changed = true;
|
|
369
|
-
nextChildren.push(nextChild);
|
|
370
|
-
}
|
|
371
|
-
return changed ? nextChildren : root;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
const data = root as any;
|
|
375
|
-
if ('children' in data) {
|
|
376
|
-
const prev = data.children as Node;
|
|
377
|
-
if (!prev) return root;
|
|
378
|
-
|
|
379
|
-
if (Array.isArray(prev)) {
|
|
380
|
-
let changed = false;
|
|
381
|
-
const nextChildren: Node[] = [];
|
|
382
|
-
for (const child of prev) {
|
|
383
|
-
if (child === target) {
|
|
384
|
-
changed = true;
|
|
385
|
-
continue;
|
|
386
|
-
}
|
|
387
|
-
const nextChild = deleteNodeFromTree(child, target);
|
|
388
|
-
if (nextChild !== child) changed = true;
|
|
389
|
-
nextChildren.push(nextChild);
|
|
390
|
-
}
|
|
391
|
-
if (changed) {
|
|
392
|
-
return { ...data, children: nextChildren } as Node;
|
|
393
|
-
}
|
|
394
|
-
return root;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (prev === target) {
|
|
398
|
-
return { ...data, children: '' } as Node;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
const nextChild = deleteNodeFromTree(prev, target);
|
|
402
|
-
if (nextChild !== prev) {
|
|
403
|
-
return { ...data, children: nextChild } as Node;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
return root;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
function isNodeRecord(node: Node): node is NodeData {
|
|
411
|
-
return (
|
|
412
|
-
node !== null &&
|
|
413
|
-
node !== undefined &&
|
|
414
|
-
typeof node === 'object' &&
|
|
415
|
-
!Array.isArray(node)
|
|
416
|
-
);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
function nodeHasChild(parent: NodeData, potentialChild: Node): boolean {
|
|
420
|
-
const { children } = parent;
|
|
421
|
-
if (!children) return false;
|
|
422
|
-
if (Array.isArray(children)) {
|
|
423
|
-
return children.some((child) => child === potentialChild);
|
|
424
|
-
}
|
|
425
|
-
return children === potentialChild;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
function findNodeByKey(root: Node, key?: string): Node | null {
|
|
429
|
-
if (!key) return null;
|
|
430
|
-
if (root === null || root === undefined) return null;
|
|
431
|
-
if (typeof root === 'string') return null;
|
|
432
|
-
|
|
433
|
-
if (Array.isArray(root)) {
|
|
434
|
-
for (const child of root) {
|
|
435
|
-
const found = findNodeByKey(child, key);
|
|
436
|
-
if (found) return found;
|
|
437
|
-
}
|
|
438
|
-
return null;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
const nodeData = root as NodeData;
|
|
442
|
-
if (nodeData.key === key) {
|
|
443
|
-
return nodeData;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
if (nodeData.children) {
|
|
447
|
-
return findNodeByKey(nodeData.children as Node, key);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
return null;
|
|
451
|
-
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
@use '../foundation/sizes' as sizes;
|
|
3
3
|
@use '../foundation/typography' as type;
|
|
4
4
|
@use '../foundation/mixins' as *;
|
|
5
|
-
@use 'sass:color';
|
|
6
5
|
|
|
7
6
|
/* Global utility classes */
|
|
8
7
|
html,
|
|
@@ -37,7 +36,7 @@ body,
|
|
|
37
36
|
height: 120px;
|
|
38
37
|
border: 2px dashed colors.$mutedTextColor;
|
|
39
38
|
border-radius: sizes.$radiusRounded;
|
|
40
|
-
background:
|
|
39
|
+
background: colors.$surfaceColor;
|
|
41
40
|
cursor: pointer;
|
|
42
41
|
font-weight: 600;
|
|
43
42
|
color: colors.$textColor;
|
|
@@ -45,12 +44,12 @@ body,
|
|
|
45
44
|
border-color 0.2s ease,
|
|
46
45
|
transform 0.1s ease,
|
|
47
46
|
box-shadow 0.2s ease;
|
|
48
|
-
box-shadow: 0 1px 2px
|
|
47
|
+
box-shadow: 0 1px 2px hsl(var(--foreground, 220.9 39.3% 11%) / 0.04);
|
|
49
48
|
|
|
50
49
|
&:hover {
|
|
51
|
-
border-color:
|
|
50
|
+
border-color: colors.$mutedTextColor;
|
|
52
51
|
transform: translateY(-1px);
|
|
53
|
-
box-shadow: 0 6px 16px
|
|
52
|
+
box-shadow: 0 6px 16px hsl(var(--foreground, 220.9 39.3% 11%) / 0.1);
|
|
54
53
|
}
|
|
55
54
|
}
|
|
56
55
|
|
|
@@ -72,6 +71,7 @@ body,
|
|
|
72
71
|
max-height: calc(100vh - 120px);
|
|
73
72
|
overflow: auto;
|
|
74
73
|
padding: sizes.$spaceComfy;
|
|
74
|
+
background-image: var(--rb-canvas-bg, none);
|
|
75
75
|
background-size: cover;
|
|
76
76
|
background-position: center;
|
|
77
77
|
background-repeat: repeat;
|
|
@@ -82,6 +82,11 @@ body,
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/* Hide the canvas grid background in dark theme (Tailwind/shadcn convention). */
|
|
86
|
+
.dark .split-right {
|
|
87
|
+
--rb-canvas-bg: none !important;
|
|
88
|
+
}
|
|
89
|
+
|
|
85
90
|
.split-third {
|
|
86
91
|
flex: 1 1 25%;
|
|
87
92
|
border-right: 1px solid colors.$borderColor;
|
|
@@ -94,10 +99,10 @@ body,
|
|
|
94
99
|
.stage {
|
|
95
100
|
@include card;
|
|
96
101
|
align-self: center;
|
|
97
|
-
border: 1px solid
|
|
102
|
+
border: 1px solid colors.$borderColor;
|
|
98
103
|
box-shadow:
|
|
99
|
-
0 18px 50px
|
|
100
|
-
0 6px 16px
|
|
104
|
+
0 18px 50px hsl(var(--foreground, 220.9 39.3% 11%) / 0.16),
|
|
105
|
+
0 6px 16px hsl(var(--foreground, 220.9 39.3% 11%) / 0.1);
|
|
101
106
|
overflow: hidden;
|
|
102
107
|
.scroll-container {
|
|
103
108
|
/* Mobile-like scrollbar styling */
|
|
@@ -111,19 +116,19 @@ body,
|
|
|
111
116
|
}
|
|
112
117
|
|
|
113
118
|
&::-webkit-scrollbar-thumb {
|
|
114
|
-
background:
|
|
119
|
+
background: hsl(var(--foreground, 220.9 39.3% 11%) / 0.25);
|
|
115
120
|
border-radius: 8px;
|
|
116
121
|
}
|
|
117
122
|
|
|
118
123
|
/* For Firefox */
|
|
119
124
|
scrollbar-width: thin;
|
|
120
|
-
scrollbar-color:
|
|
125
|
+
scrollbar-color: hsl(var(--foreground, 220.9 39.3% 11%) / 0.25) transparent;
|
|
121
126
|
}
|
|
122
127
|
}
|
|
123
128
|
|
|
124
129
|
/* Builder preview selection helpers */
|
|
125
130
|
.rb-node-selected {
|
|
126
|
-
border: 1px solid
|
|
131
|
+
border: 1px solid colors.$accentColor;
|
|
127
132
|
}
|
|
128
133
|
|
|
129
134
|
/* Header */
|
|
@@ -131,7 +136,7 @@ body,
|
|
|
131
136
|
position: sticky;
|
|
132
137
|
top: 0;
|
|
133
138
|
z-index: 10;
|
|
134
|
-
background:
|
|
139
|
+
background: colors.$surfaceColor;
|
|
135
140
|
border-bottom: 1px solid colors.$borderColor;
|
|
136
141
|
height: 60px;
|
|
137
142
|
padding: 0 sizes.$spaceComfy;
|
|
@@ -251,7 +256,7 @@ body,
|
|
|
251
256
|
padding: sizes.$spaceCozy;
|
|
252
257
|
border-radius: sizes.$radiusRounded;
|
|
253
258
|
border: 1px solid colors.$borderColor;
|
|
254
|
-
background:
|
|
259
|
+
background: colors.$surfaceColor;
|
|
255
260
|
color: colors.$textColor;
|
|
256
261
|
font-weight: 600;
|
|
257
262
|
cursor: pointer;
|
|
@@ -269,9 +274,9 @@ body,
|
|
|
269
274
|
|
|
270
275
|
.mobile-panel-toggle__button--active {
|
|
271
276
|
background: colors.$textColor;
|
|
272
|
-
color:
|
|
277
|
+
color: colors.$surfaceColor;
|
|
273
278
|
border-color: colors.$textColor;
|
|
274
|
-
box-shadow: 0 6px 16px
|
|
279
|
+
box-shadow: 0 6px 16px hsl(var(--foreground, 220.9 39.3% 11%) / 0.15);
|
|
275
280
|
}
|
|
276
281
|
|
|
277
282
|
.mobile-panel-toggle__icon {
|
|
@@ -296,8 +301,8 @@ body,
|
|
|
296
301
|
right: sizes.$spaceCozy;
|
|
297
302
|
border: none;
|
|
298
303
|
border-radius: sizes.$radiusRounded;
|
|
299
|
-
background:
|
|
300
|
-
color:
|
|
304
|
+
background: hsl(var(--foreground, 220.9 39.3% 11%) / 0.7);
|
|
305
|
+
color: colors.$surfaceColor;
|
|
301
306
|
padding: sizes.$spaceSnug sizes.$spaceComfy;
|
|
302
307
|
font-weight: 600;
|
|
303
308
|
cursor: pointer;
|
|
@@ -325,7 +330,7 @@ body,
|
|
|
325
330
|
width: 100%;
|
|
326
331
|
height: 100%;
|
|
327
332
|
background: colors.$canvasColor;
|
|
328
|
-
box-shadow: 0 25px 60px
|
|
333
|
+
box-shadow: 0 25px 60px hsl(var(--foreground, 220.9 39.3% 11%) / 0.15);
|
|
329
334
|
transition:
|
|
330
335
|
transform 0.3s ease,
|
|
331
336
|
opacity 0.3s ease;
|
|
@@ -370,7 +375,7 @@ body,
|
|
|
370
375
|
display: block;
|
|
371
376
|
position: absolute;
|
|
372
377
|
inset: 0;
|
|
373
|
-
background:
|
|
378
|
+
background: hsl(var(--foreground, 220.9 39.3% 11%) / 0.35);
|
|
374
379
|
border: none;
|
|
375
380
|
padding: 0;
|
|
376
381
|
margin: 0;
|
|
@@ -404,7 +409,7 @@ body,
|
|
|
404
409
|
align-items: center;
|
|
405
410
|
justify-content: center;
|
|
406
411
|
pointer-events: none;
|
|
407
|
-
background:
|
|
412
|
+
background: hsl(var(--card, 0 0% 100%) / 0.55);
|
|
408
413
|
backdrop-filter: blur(1px);
|
|
409
414
|
}
|
|
410
415
|
|