@developer_tribe/react-builder 1.2.20 → 1.2.22

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 (151) hide show
  1. package/dist/attribute-analyser/style/web/useExtractTextStyle.d.ts +1 -1
  2. package/dist/build-components/BIcon/BIconProps.generated.d.ts +2 -0
  3. package/dist/build-components/BackgroundImage/BackgroundImageProps.generated.d.ts +2 -0
  4. package/dist/build-components/Button/ButtonProps.generated.d.ts +2 -0
  5. package/dist/build-components/Carousel/CarouselProps.generated.d.ts +2 -0
  6. package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +2 -0
  7. package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +2 -0
  8. package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +2 -0
  9. package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +2 -0
  10. package/dist/build-components/CountDown/CountDownProps.generated.d.ts +2 -0
  11. package/dist/build-components/Counter/CounterProps.generated.d.ts +2 -0
  12. package/dist/build-components/Image/ImageProps.generated.d.ts +2 -0
  13. package/dist/build-components/Main/MainProps.generated.d.ts +2 -0
  14. package/dist/build-components/Onboard/OnboardProps.generated.d.ts +2 -0
  15. package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +2 -0
  16. package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +2 -0
  17. package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +2 -0
  18. package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +2 -0
  19. package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +2 -0
  20. package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +2 -0
  21. package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +2 -0
  22. package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +2 -0
  23. package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +2 -0
  24. package/dist/build-components/PaywallBackground/PaywallBackgroundProps.generated.d.ts +2 -0
  25. package/dist/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.d.ts +2 -0
  26. package/dist/build-components/PaywallCounter/PaywallCounterProps.generated.d.ts +2 -0
  27. package/dist/build-components/PaywallOptions/PaywallOptionsProps.generated.d.ts +2 -0
  28. package/dist/build-components/PaywallProvider/PaywallProviderProps.generated.d.ts +2 -0
  29. package/dist/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.d.ts +2 -0
  30. package/dist/build-components/RadioButton/RadioButtonProps.generated.d.ts +2 -0
  31. package/dist/build-components/Text/TextProps.generated.d.ts +2 -0
  32. package/dist/build-components/View/View.d.ts +1 -1
  33. package/dist/build-components/View/ViewProps.generated.d.ts +2 -0
  34. package/dist/build-components/patterns.generated.d.ts +287 -2
  35. package/dist/components/BuilderButton.d.ts +3 -1
  36. package/dist/index.cjs.js +3 -3
  37. package/dist/index.cjs.js.map +1 -1
  38. package/dist/index.esm.js +4 -4
  39. package/dist/index.esm.js.map +1 -1
  40. package/dist/index.web.cjs.js +4 -4
  41. package/dist/index.web.cjs.js.map +1 -1
  42. package/dist/index.web.esm.js +4 -4
  43. package/dist/index.web.esm.js.map +1 -1
  44. package/dist/store.d.ts +2 -0
  45. package/dist/styles.css +1 -1
  46. package/dist/utils/extractTextStyle/extractTextStyle.d.ts +1 -0
  47. package/package.json +1 -1
  48. package/scripts/migrate-patterns-to-v2.mjs +13 -8
  49. package/scripts/prebuild/icon-generator.js +34 -37
  50. package/src/assets/loading_animation.json +2587 -1
  51. package/src/assets/meta.json +1 -1
  52. package/src/assets/samples/carousel-sample.json +279 -197
  53. package/src/assets/samples/getSamples.ts +16 -1
  54. package/src/assets/samples/paywall-1.json +16 -0
  55. package/src/assets/samples/paywall-2.json +2 -2
  56. package/src/assets/samples/paywall-app-delete-offer.json +353 -0
  57. package/src/assets/samples/paywall-app-open-offer.json +353 -0
  58. package/src/assets/samples/paywall-back-offer.json +353 -0
  59. package/src/assets/samples/paywall-notification-offer.json +353 -0
  60. package/src/assets/samples/vpn-onboard-1.json +23 -12
  61. package/src/assets/samples/vpn-onboard-2.json +23 -12
  62. package/src/assets/samples/vpn-onboard-3.json +23 -12
  63. package/src/assets/samples/vpn-onboard-4.json +23 -12
  64. package/src/assets/samples/vpn-onboard-5.json +23 -12
  65. package/src/assets/samples/vpn-onboard-6.json +23 -12
  66. package/src/attribute-analyser/style/web/useExtractTextStyle.ts +9 -2
  67. package/src/build-components/BIcon/BIconProps.generated.ts +2 -0
  68. package/src/build-components/BackgroundImage/BackgroundImageProps.generated.ts +2 -0
  69. package/src/build-components/Button/ButtonProps.generated.ts +2 -0
  70. package/src/build-components/Carousel/CarouselProps.generated.ts +2 -0
  71. package/src/build-components/Carousel/pattern.json +2 -8
  72. package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +2 -0
  73. package/src/build-components/CarouselButtons/pattern.json +2 -9
  74. package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +2 -0
  75. package/src/build-components/CarouselDots/pattern.json +1 -3
  76. package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +2 -0
  77. package/src/build-components/CarouselItem/pattern.json +1 -3
  78. package/src/build-components/CarouselProvider/CarouselProvider.tsx +5 -44
  79. package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +2 -0
  80. package/src/build-components/CarouselProvider/pattern.json +6 -0
  81. package/src/build-components/CountDown/CountDownProps.generated.ts +2 -0
  82. package/src/build-components/CountDown/pattern.json +0 -1
  83. package/src/build-components/Counter/CounterProps.generated.ts +2 -0
  84. package/src/build-components/Counter/pattern.json +0 -1
  85. package/src/build-components/Image/Image.tsx +1 -1
  86. package/src/build-components/Image/ImageProps.generated.ts +2 -0
  87. package/src/build-components/Main/MainProps.generated.ts +2 -0
  88. package/src/build-components/Main/pattern.json +1 -3
  89. package/src/build-components/Onboard/OnboardProps.generated.ts +2 -0
  90. package/src/build-components/Onboard/pattern.json +2 -6
  91. package/src/build-components/OnboardButton/OnboardButton.tsx +0 -4
  92. package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +2 -0
  93. package/src/build-components/OnboardButton/pattern.json +9 -14
  94. package/src/build-components/OnboardButtons/OnboardButtons.tsx +17 -20
  95. package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +2 -0
  96. package/src/build-components/OnboardButtons/pattern.json +15 -15
  97. package/src/build-components/OnboardDot/OnboardDot.tsx +0 -3
  98. package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +2 -0
  99. package/src/build-components/OnboardFooter/OnboardFooter.tsx +63 -51
  100. package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +2 -0
  101. package/src/build-components/OnboardFooter/pattern.json +6 -3
  102. package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +2 -0
  103. package/src/build-components/OnboardImage/pattern.json +1 -5
  104. package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +2 -0
  105. package/src/build-components/OnboardItem/pattern.json +3 -11
  106. package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +2 -0
  107. package/src/build-components/OnboardProvider/pattern.json +2 -8
  108. package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +2 -0
  109. package/src/build-components/OnboardSubtitle/pattern.json +1 -4
  110. package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +2 -0
  111. package/src/build-components/OnboardTitle/pattern.json +1 -4
  112. package/src/build-components/PaywallBackground/PaywallBackgroundProps.generated.ts +2 -0
  113. package/src/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.ts +2 -0
  114. package/src/build-components/PaywallCloseButton/pattern.json +1 -3
  115. package/src/build-components/PaywallCounter/PaywallCounterProps.generated.ts +2 -0
  116. package/src/build-components/PaywallCounter/pattern.json +0 -1
  117. package/src/build-components/PaywallOptions/PaywallOptions.tsx +1 -1
  118. package/src/build-components/PaywallOptions/PaywallOptionsProps.generated.ts +2 -0
  119. package/src/build-components/PaywallOptions/pattern.json +1 -3
  120. package/src/build-components/PaywallProvider/PaywallProviderProps.generated.ts +2 -0
  121. package/src/build-components/PaywallProvider/pattern.json +1 -3
  122. package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.ts +2 -0
  123. package/src/build-components/PaywallSubscribeButton/pattern.json +1 -3
  124. package/src/build-components/RadioButton/RadioButtonProps.generated.ts +2 -0
  125. package/src/build-components/RadioButton/pattern.json +1 -3
  126. package/src/build-components/RenderNode.generated.tsx +1 -1
  127. package/src/build-components/Text/TextProps.generated.ts +2 -0
  128. package/src/build-components/View/View.tsx +11 -7
  129. package/src/build-components/View/ViewProps.generated.ts +2 -0
  130. package/src/build-components/View/pattern.json +8 -0
  131. package/src/build-components/patterns.generated.ts +277 -2
  132. package/src/build-components/useNode.ts +2 -2
  133. package/src/components/Builder.tsx +98 -8
  134. package/src/components/BuilderButton.tsx +39 -7
  135. package/src/components/DeviceButton.tsx +5 -1
  136. package/src/pages/DebugJsonPage.tsx +30 -1
  137. package/src/pages/ProjectDebug.tsx +0 -1
  138. package/src/pages/ProjectPage.tsx +2 -2
  139. package/src/store.ts +8 -0
  140. package/src/styles/base/_global.scss +0 -5
  141. package/src/styles/components/_editor-shell.scss +18 -3
  142. package/src/styles/components/_onboard.scss +0 -17
  143. package/src/styles/foundation/_colors.scss +1 -4
  144. package/src/styles/foundation/_typography.scss +0 -1
  145. package/src/styles/layout/_builder.scss +20 -0
  146. package/src/styles/modals/_product-presets-modal.scss +0 -2
  147. package/src/utils/extractTextStyle/extractTextStyle.ts +47 -13
  148. package/src/utils/extractViewStyle/extractViewStyle.ts +118 -39
  149. package/src/utils/logRenderStore.ts +7 -9
  150. package/src/utils/logger.ts +1 -5
  151. package/src/utils/repairNodeKeys.ts +1 -4
@@ -20,10 +20,10 @@ export default function useNode<
20
20
  ...(defaultAttributes?.styles ?? {}),
21
21
  ...(defaultAttributes?.style ?? {}),
22
22
  };
23
- // Merge node style from both node.attributes.style and node.attributes.styles
23
+ // Merge node style from both node.attributes.style and node.attributes.styles (preferring styles)
24
24
  const nodeStyle = {
25
- ...(nodeAttributes?.styles ?? {}),
26
25
  ...(nodeAttributes?.style ?? {}),
26
+ ...(nodeAttributes?.styles ?? {}),
27
27
  };
28
28
  const mergedStyle = {
29
29
  ...defaultStyle,
@@ -13,6 +13,7 @@ import { AddComponentModal } from '../modals/AddComponentModal';
13
13
  import { BuilderButton } from './BuilderButton';
14
14
  import { generateRandomKeyForNode } from '../utils/generateRandomKeyForNode';
15
15
  import { collectNodeKeys } from '../utils/repairNodeKeys';
16
+ import { useRenderStore } from '../store';
16
17
 
17
18
  type BuilderEditorProps = {
18
19
  data: Node;
@@ -30,6 +31,68 @@ interface BuilderEditorComponentProps {
30
31
  onReorder?: (prev: Node[], next: Node[]) => void;
31
32
  onMoveChildUp?: (parent: Node, childIndex: number) => void;
32
33
  onMoveChildDown?: (parent: Node, childIndex: number) => void;
34
+ maxNestedDepth?: number;
35
+ }
36
+
37
+ type BuilderTreeNodeProps = {
38
+ node: Node;
39
+ depthRemaining: number;
40
+ onClick: (node: Node) => void;
41
+ onDelete?: (node: Node) => void;
42
+ onMoveUp?: () => void;
43
+ onMoveDown?: () => void;
44
+ };
45
+
46
+ function BuilderTreeNode({
47
+ node,
48
+ depthRemaining,
49
+ onClick,
50
+ onDelete,
51
+ onMoveUp,
52
+ onMoveDown,
53
+ }: BuilderTreeNodeProps) {
54
+ const children = getNodeChildren(node);
55
+ const hasChildren = children.length > 0;
56
+ const childrenCount =
57
+ hasChildren && depthRemaining <= 0 ? children.length : undefined;
58
+ const onMore =
59
+ hasChildren && depthRemaining <= 0 ? () => onClick(node) : undefined;
60
+ const button = (
61
+ <BuilderButton
62
+ onClick={() => {
63
+ onClick(node);
64
+ }}
65
+ node={node}
66
+ onDelete={onDelete}
67
+ onMoveUp={onMoveUp}
68
+ onMoveDown={onMoveDown}
69
+ childrenCount={childrenCount}
70
+ onMore={onMore}
71
+ />
72
+ );
73
+
74
+ if (!hasChildren) {
75
+ return button;
76
+ }
77
+
78
+ if (depthRemaining <= 0) {
79
+ return <div className="builder__children">{button}</div>;
80
+ }
81
+
82
+ return (
83
+ <div className="builder__children">
84
+ {button}
85
+ {children.map((child, index) => (
86
+ <BuilderTreeNode
87
+ key={index}
88
+ node={child}
89
+ depthRemaining={depthRemaining - 1}
90
+ onClick={onClick}
91
+ onDelete={onDelete}
92
+ />
93
+ ))}
94
+ </div>
95
+ );
33
96
  }
34
97
 
35
98
  function BuilderComponent({
@@ -40,6 +103,7 @@ function BuilderComponent({
40
103
  onReorder,
41
104
  onMoveChildUp,
42
105
  onMoveChildDown,
106
+ maxNestedDepth,
43
107
  }: BuilderEditorComponentProps) {
44
108
  if (isNodeString(node)) {
45
109
  return (
@@ -64,6 +128,12 @@ function BuilderComponent({
64
128
 
65
129
  if (isNodeArray(node)) {
66
130
  const list = node as Node[];
131
+ const depthRemaining = Math.max(
132
+ 0,
133
+ Number.isFinite(maxNestedDepth) && (maxNestedDepth ?? 0) > 0
134
+ ? Math.floor(maxNestedDepth ?? 1) - 1
135
+ : 4,
136
+ );
67
137
 
68
138
  const moveItem = (index: number, direction: -1 | 1) => {
69
139
  if (!onReorder) return;
@@ -79,11 +149,10 @@ function BuilderComponent({
79
149
  <div className="builder__list">
80
150
  {list.map((item, index) => (
81
151
  <div key={index} className="builder__list-item">
82
- <BuilderButton
83
- onClick={() => {
84
- onClick(item);
85
- }}
152
+ <BuilderTreeNode
86
153
  node={item}
154
+ depthRemaining={depthRemaining}
155
+ onClick={onClick}
87
156
  onDelete={onDelete}
88
157
  onMoveUp={
89
158
  onReorder && index > 0 ? () => moveItem(index, -1) : undefined
@@ -109,6 +178,12 @@ function BuilderComponent({
109
178
  ? (rawChildren as Node[])
110
179
  : [rawChildren]
111
180
  : null;
181
+ const depthRemaining = Math.max(
182
+ 0,
183
+ Number.isFinite(maxNestedDepth) && (maxNestedDepth ?? 0) > 0
184
+ ? Math.floor(maxNestedDepth ?? 1) - 1
185
+ : 4,
186
+ );
112
187
 
113
188
  return (
114
189
  <div className="builder__node">
@@ -122,12 +197,11 @@ function BuilderComponent({
122
197
  />
123
198
  {children &&
124
199
  children.map((child, index) => (
125
- <BuilderButton
126
- onClick={() => {
127
- onClick(child);
128
- }}
200
+ <BuilderTreeNode
129
201
  key={index}
130
202
  node={child}
203
+ depthRemaining={depthRemaining}
204
+ onClick={onClick}
131
205
  onDelete={onDelete}
132
206
  onMoveUp={
133
207
  onMoveChildUp && hasArrayChildren && index > 0
@@ -158,7 +232,12 @@ export function Builder({
158
232
  }: BuilderEditorProps) {
159
233
  useLogRender('Builder');
160
234
  const [isAddModalOpen, setIsAddModalOpen] = useState(false);
235
+ const listMaxNested = useRenderStore((state) => state.listMaxNested);
161
236
  const usedKeys = useMemo(() => collectNodeKeys(data), [data]);
237
+ const resolvedListMaxNested =
238
+ Number.isFinite(listMaxNested) && listMaxNested > 0
239
+ ? Math.floor(listMaxNested)
240
+ : 5;
162
241
  const breadcrumbPath = useMemo(() => {
163
242
  const path = findNodePath(data, current);
164
243
  if (path.length) return path;
@@ -482,6 +561,7 @@ export function Builder({
482
561
  onReorder={handleReorder}
483
562
  onMoveChildUp={handleMoveChildUp}
484
563
  onMoveChildDown={handleMoveChildDown}
564
+ maxNestedDepth={resolvedListMaxNested}
485
565
  node={current}
486
566
  />
487
567
  {isAddModalOpen && (
@@ -510,6 +590,16 @@ function appendChild(children: Node, childToAppend: Node): Node {
510
590
  return [children as Node, childToAppend];
511
591
  }
512
592
 
593
+ function getNodeChildren(node: Node): Node[] {
594
+ if (isNodeNullOrUndefined(node) || isNodeString(node) || isNodeArray(node)) {
595
+ return [];
596
+ }
597
+ const nodeData = node as NodeData<NodeDefaultAttribute>;
598
+ const children = nodeData.children;
599
+ if (!children) return [];
600
+ return Array.isArray(children) ? children : [children];
601
+ }
602
+
513
603
  function getNodeLabel(node: Node): string {
514
604
  if (isNodeNullOrUndefined(node)) return 'Empty';
515
605
  if (isNodeString(node)) return node as string;
@@ -10,6 +10,8 @@ export type BuilderButtonProps = {
10
10
  onDelete?: (node: Node) => void;
11
11
  onMoveUp?: () => void;
12
12
  onMoveDown?: () => void;
13
+ childrenCount?: number;
14
+ onMore?: () => void;
13
15
  };
14
16
 
15
17
  export function BuilderButton({
@@ -18,6 +20,8 @@ export function BuilderButton({
18
20
  onDelete,
19
21
  onMoveUp,
20
22
  onMoveDown,
23
+ childrenCount,
24
+ onMore,
21
25
  }: BuilderButtonProps) {
22
26
  // IMPORTANT: Hooks must be called unconditionally on every render.
23
27
  // (Early returns before hooks can trigger React internal invariants.)
@@ -83,7 +87,8 @@ export function BuilderButton({
83
87
  : '';
84
88
  const hasTitle = title.length > 0;
85
89
  const topLabel = hasTitle ? title : baseLabel;
86
- const shouldShowBottom = hasTitle || conditionLabel.length > 0;
90
+ const shouldShowBottom =
91
+ hasTitle || conditionLabel.length > 0 || typeof childrenCount === 'number';
87
92
 
88
93
  return (
89
94
  <div className="builder__button">
@@ -140,6 +145,22 @@ export function BuilderButton({
140
145
  role="menu"
141
146
  aria-label="Node actions"
142
147
  >
148
+ {onMore ? (
149
+ <li role="none">
150
+ <button
151
+ type="button"
152
+ className="builder__button-actions-item"
153
+ role="menuitem"
154
+ onClick={(event) => {
155
+ event.stopPropagation();
156
+ onMore();
157
+ setIsMenuOpen(false);
158
+ }}
159
+ >
160
+ More
161
+ </button>
162
+ </li>
163
+ ) : null}
143
164
  <li role="none">
144
165
  <button
145
166
  type="button"
@@ -163,14 +184,25 @@ export function BuilderButton({
163
184
  </div>
164
185
  {shouldShowBottom && (
165
186
  <span className="builder__button-condition">
166
- {hasTitle ? (
167
- <>
168
- <small>{baseLabel}</small>
169
- {conditionLabel ? ` · ${conditionLabel}` : ''}
170
- </>
187
+ {hasTitle || conditionLabel.length > 0 ? (
188
+ <span className="builder__button-condition-text">
189
+ {hasTitle ? (
190
+ <>
191
+ <small>{baseLabel}</small>
192
+ {conditionLabel ? ` · ${conditionLabel}` : ''}
193
+ </>
194
+ ) : (
195
+ conditionLabel
196
+ )}
197
+ </span>
171
198
  ) : (
172
- conditionLabel
199
+ <span className="builder__button-condition-text" />
173
200
  )}
201
+ {typeof childrenCount === 'number' ? (
202
+ <span className="builder__button-children-count">
203
+ {childrenCount} children
204
+ </span>
205
+ ) : null}
174
206
  </span>
175
207
  )}
176
208
  </div>
@@ -38,7 +38,11 @@ export function DeviceButton({
38
38
  >
39
39
  {device.name} <br />
40
40
  {device.width}×{device.height} ({aspect})
41
- {platformIcon && <img src={platformIcon} alt="" aria-hidden="true" />}
41
+ {platformIcon && (
42
+ <span>
43
+ <img src={platformIcon} alt="" aria-hidden="true" />
44
+ </span>
45
+ )}
42
46
  </button>
43
47
  );
44
48
  }
@@ -1,4 +1,4 @@
1
- import React, { useMemo } from 'react';
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
2
  import type { Node } from '../types/Node';
3
3
  import type { NodeData, NodeDefaultAttribute } from '../types/Node';
4
4
  import type { AppConfig } from '../types/PreviewConfig';
@@ -44,6 +44,11 @@ export function DebugJsonPage({
44
44
  logLabel,
45
45
  }: DebugJsonPageProps) {
46
46
  const setCurrent = useRenderStore((s) => s.setCurrent);
47
+ const listMaxNested = useRenderStore((s) => s.listMaxNested);
48
+ const setListMaxNested = useRenderStore((s) => s.setListMaxNested);
49
+ const [listMaxNestedInput, setListMaxNestedInput] = useState(
50
+ String(listMaxNested),
51
+ );
47
52
  const canTogglePreviewMode = typeof setPreviewMode === 'function';
48
53
  const canToggleTheme =
49
54
  typeof setAppConfig === 'function' && typeof appConfig?.theme === 'string';
@@ -117,6 +122,19 @@ export function DebugJsonPage({
117
122
  return checkNode(data);
118
123
  }, [data]);
119
124
 
125
+ useEffect(() => {
126
+ setListMaxNestedInput(String(listMaxNested));
127
+ }, [listMaxNested]);
128
+
129
+ useEffect(() => {
130
+ const handle = window.setTimeout(() => {
131
+ const next = Number.parseInt(listMaxNestedInput, 10);
132
+ if (!Number.isFinite(next) || next <= 0) return;
133
+ setListMaxNested(next);
134
+ }, 300);
135
+ return () => window.clearTimeout(handle);
136
+ }, [listMaxNestedInput, setListMaxNested]);
137
+
120
138
  const handleFixStyleToStyles = () => {
121
139
  if (!data) return;
122
140
  const migrated = migrateStyleToStyles(data);
@@ -213,6 +231,17 @@ export function DebugJsonPage({
213
231
  </div>
214
232
  ) : null}
215
233
 
234
+ <div className="localication-modal__controls">
235
+ <label>List max nested depth</label>
236
+ <input
237
+ type="number"
238
+ min={1}
239
+ className="input"
240
+ value={listMaxNestedInput}
241
+ onChange={(event) => setListMaxNestedInput(event.target.value)}
242
+ />
243
+ </div>
244
+
216
245
  <div className="localication-modal__editor">
217
246
  <JsonTextEditor
218
247
  rootName="node"
@@ -300,7 +300,6 @@ export function ProjectDebug({
300
300
  className="rb-project-debug__preview"
301
301
  aria-label="Preview"
302
302
  style={{
303
- // eslint-disable-next-line @typescript-eslint/naming-convention
304
303
  ['--rb-canvas-bg' as any]: canvasBg ?? 'none',
305
304
  }}
306
305
  >
@@ -265,7 +265,7 @@ export function ProjectPage({
265
265
  setEditorData(processed);
266
266
  setCurrent(processed);
267
267
  } catch (error) {
268
- console.error(error);
268
+ logger.error('ProjectPage', 'Failed to process node', error);
269
269
  setValidationError(
270
270
  error instanceof Error ? error.message : 'Node is not valid',
271
271
  );
@@ -601,7 +601,7 @@ export function ProjectPage({
601
601
  <div
602
602
  style={{
603
603
  // Set as a CSS variable so `.dark .split-right` can override it.
604
- // eslint-disable-next-line @typescript-eslint/naming-convention
604
+
605
605
  ['--rb-canvas-bg' as any]: `url(${getImage(
606
606
  TribeAssetName.Background,
607
607
  )})`,
package/src/store.ts CHANGED
@@ -76,6 +76,8 @@ type RenderStore = {
76
76
  // Cache loaded font families to avoid repeated loads
77
77
  loadedFonts: string[];
78
78
  markFontLoaded: (fontFamily: string) => void;
79
+ listMaxNested: number;
80
+ setListMaxNested: (depth: number) => void;
79
81
  };
80
82
 
81
83
  export const useRenderStore = createWithEqualityFn<RenderStore>()(
@@ -233,6 +235,11 @@ export const useRenderStore = createWithEqualityFn<RenderStore>()(
233
235
  if (prev.includes(family)) return state;
234
236
  return { loadedFonts: [...prev, family] };
235
237
  }),
238
+ listMaxNested: 3,
239
+ setListMaxNested: (depth) =>
240
+ set({
241
+ listMaxNested: Number.isFinite(depth) && depth > 0 ? depth : 5,
242
+ }),
236
243
  }),
237
244
  {
238
245
  name: 'render-store',
@@ -242,6 +249,7 @@ export const useRenderStore = createWithEqualityFn<RenderStore>()(
242
249
  logLevel: state.logLevel,
243
250
  products: state.products,
244
251
  benefits: state.benefits,
252
+ listMaxNested: state.listMaxNested,
245
253
  }),
246
254
  storage: createJSONStorage(() => localStorage),
247
255
  },
@@ -135,11 +135,6 @@ body,
135
135
  }
136
136
  }
137
137
 
138
- /* Builder preview selection helpers */
139
- .rb-node-selected {
140
- border: 1px solid colors.$accentColor;
141
- }
142
-
143
138
  /* Header */
144
139
  .app-header {
145
140
  position: sticky;
@@ -94,12 +94,27 @@
94
94
  }
95
95
  }
96
96
 
97
- .editor-device-button img {
97
+ .editor-device-button span {
98
98
  position: absolute;
99
99
  bottom: 4px;
100
100
  right: 4px;
101
- width: 16px;
102
- height: 16px;
101
+ background-color: colors.$muted;
102
+ border-radius: 50%;
103
+ display: block;
104
+ width: 22px;
105
+ height: 22px;
106
+ overflow: hidden;
107
+ }
108
+
109
+ .editor-device-button img {
110
+ position: absolute;
111
+ top: 50%;
112
+ left: 50%;
113
+ transform: translate(-50%, -50%);
114
+ max-width: 11px;
115
+ max-height: 11px;
116
+ width: auto;
117
+ height: auto;
103
118
  }
104
119
 
105
120
  .editor-header__actions {
@@ -4,20 +4,3 @@
4
4
  height: 1px;
5
5
  width: 100%;
6
6
  }
7
-
8
- .onboard__buttons {
9
- display: flex;
10
- height: 40px;
11
- gap: 12px;
12
- align-items: center;
13
- justify-content: center;
14
- margin: 12px 24px;
15
- }
16
-
17
- .onboard__buttons--row {
18
- flex-direction: row;
19
- }
20
-
21
- .onboard__buttons--column {
22
- flex-direction: column;
23
- }
@@ -78,10 +78,7 @@ $primaryForeground: hsl(
78
78
  );
79
79
  $destructive: hsl(var(--destructive, var(--rb-destructive, 0 84.2% 60.2%)));
80
80
  $destructiveForeground: hsl(
81
- var(
82
- --destructive-foreground,
83
- var(--rb-destructive-foreground, 0 0% 100%)
84
- )
81
+ var(--destructive-foreground, var(--rb-destructive-foreground, 0 0% 100%))
85
82
  );
86
83
  $border: hsl(var(--border, var(--rb-border, 220 13% 91%)));
87
84
 
@@ -1,4 +1,3 @@
1
1
  $bodyFont:
2
2
  -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
3
3
  'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', sans-serif;
4
-
@@ -147,11 +147,19 @@
147
147
  position: absolute;
148
148
  bottom: -10px;
149
149
  font-size: 8px;
150
+ left: 0;
151
+ right: 0;
152
+ display: flex;
153
+ align-items: center;
154
+ justify-content: space-between;
155
+ padding-left: sizes.$spaceComfy;
156
+ padding-right: sizes.$spaceTight;
150
157
  }
151
158
 
152
159
  .builder__node {
153
160
  @include card;
154
161
  padding: sizes.$spaceSnug;
162
+ box-shadow: none;
155
163
  }
156
164
 
157
165
  .builder__node-type {
@@ -178,6 +186,18 @@
178
186
  }
179
187
  }
180
188
 
189
+ .builder__button-condition-text {
190
+ color: colors.$mutedTextColor;
191
+ white-space: nowrap;
192
+ overflow: hidden;
193
+ text-overflow: ellipsis;
194
+ }
195
+
196
+ .builder__button-children-count {
197
+ color: colors.$mutedTextColor;
198
+ white-space: nowrap;
199
+ }
200
+
181
201
  .builder__text,
182
202
  .builder__placeholder {
183
203
  color: colors.$mutedTextColor;
@@ -77,5 +77,3 @@
77
77
  gap: sizes.$spaceCompact;
78
78
  margin-top: sizes.$spaceCompact;
79
79
  }
80
-
81
-
@@ -1,5 +1,8 @@
1
1
  import type { NodeData } from '../../types/Node';
2
- import type { TextPropsGenerated } from '../../build-components/Text/TextProps.generated';
2
+ import type {
3
+ TextPropsGenerated,
4
+ TextStyleGenerated,
5
+ } from '../../build-components/Text/TextProps.generated';
3
6
  import type { AppConfig } from '../../types/PreviewConfig';
4
7
  import { defaultAppConfig } from '../../types/PreviewConfig';
5
8
  import type { ProjectColors } from '../../types/Project';
@@ -86,6 +89,7 @@ export type ExtractTextStyleOptions = {
86
89
  fonts?: Fonts;
87
90
  onFontLoaded?: (fontFamily: string) => void;
88
91
  onError?: (error: string) => void;
92
+ directlyTextStyle?: boolean;
89
93
  };
90
94
 
91
95
  export function extractTextStyle<T extends TextPropsGenerated['attributes']>(
@@ -93,13 +97,13 @@ export function extractTextStyle<T extends TextPropsGenerated['attributes']>(
93
97
  options: ExtractTextStyleOptions = {},
94
98
  ) {
95
99
  const attributes = node.attributes;
96
- const styleBag = (attributes as any)?.style as
97
- | Record<string, unknown>
100
+ const styleBag = (attributes as Record<string, unknown>)?.style as
101
+ | TextStyleGenerated
98
102
  | undefined;
99
103
  const get = (key: string): unknown => {
100
- const direct = (attributes as any)?.[key];
104
+ const direct = (attributes as Record<string, unknown>)?.[key];
101
105
  if (direct !== undefined && direct !== null) return direct;
102
- return styleBag?.[key];
106
+ return styleBag?.[key as keyof TextStyleGenerated];
103
107
  };
104
108
  const resolvedAppConfig = options.appConfig ?? defaultAppConfig;
105
109
  const { screenStyle, theme } = resolvedAppConfig;
@@ -115,15 +119,15 @@ export function extractTextStyle<T extends TextPropsGenerated['attributes']>(
115
119
  }
116
120
 
117
121
  // Typography
118
- const fontSize = get('fontSize') as any;
122
+ const fontSize = get('fontSize') as string | number | undefined;
119
123
  if (fontSize !== undefined) {
120
124
  const parsed = parseSize(fontSize);
121
125
  style.fontSize = parsed as React.CSSProperties['fontSize'];
122
126
  } else {
123
127
  style.fontSize = fs(14);
124
128
  }
125
- const fontFamily = get('fontFamily') as any;
126
- const fontWeight = get('fontWeight') as any;
129
+ const fontFamily = get('fontFamily') as string | undefined;
130
+ const fontWeight = get('fontWeight') as string | number | undefined;
127
131
  const requestedWeight = weightToNumericKey(fontWeight);
128
132
  const normalizedFontFamily =
129
133
  typeof fontFamily === 'string' && fontFamily.trim().length > 0
@@ -163,18 +167,48 @@ export function extractTextStyle<T extends TextPropsGenerated['attributes']>(
163
167
  // If no fontFamily is set, keep previous behavior.
164
168
  if (!normalizedFontFamily && normalizedFontWeight)
165
169
  style.fontWeight = normalizedFontWeight;
166
- const resolvedTextColor = parseColor(get('color') as any, {
170
+ const resolvedTextColor = parseColor(get('color') as string | undefined, {
167
171
  projectColors: options.projectColors,
168
172
  theme,
169
173
  });
170
174
  style.color = resolvedTextColor ?? fallbackColor;
171
- const textAlign = get('textAlign');
172
- if (textAlign)
173
- style.textAlign = textAlign as React.CSSProperties['textAlign'];
175
+ const textAlign = get('textAlign') as
176
+ | React.CSSProperties['textAlign']
177
+ | undefined;
178
+ if (textAlign) {
179
+ style.textAlign = textAlign;
180
+ }
174
181
 
175
182
  const viewStyle = extractViewStyle(node, {
176
183
  projectColors: options.projectColors,
177
184
  theme,
178
185
  });
179
- return { ...viewStyle, ...style };
186
+ const fullStyle = { ...viewStyle, ...style };
187
+
188
+ // If directlyTextStyle is true, return only text-related styles
189
+ if (options.directlyTextStyle) {
190
+ const textStyleProperties = new Set<keyof React.CSSProperties>([
191
+ 'fontSize',
192
+ 'fontFamily',
193
+ 'fontWeight',
194
+ 'color',
195
+ 'textAlign',
196
+ 'lineHeight',
197
+ 'letterSpacing',
198
+ 'textDecoration',
199
+ 'textTransform',
200
+ ]);
201
+
202
+ const textStyle: React.CSSProperties = {};
203
+ for (const key in fullStyle) {
204
+ const typedKey = key as keyof React.CSSProperties;
205
+ if (textStyleProperties.has(typedKey)) {
206
+ (textStyle as Record<string, unknown>)[key] = fullStyle[typedKey];
207
+ }
208
+ }
209
+
210
+ return textStyle;
211
+ }
212
+
213
+ return fullStyle;
180
214
  }