@developer_tribe/react-builder 1.2.7 → 1.2.9

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 (204) hide show
  1. package/dist/AttributesEditor.d.ts +2 -11
  2. package/dist/attribute-analyser/style/native/useExtractImageStyle.d.ts +10 -0
  3. package/dist/attribute-analyser/style/native/useExtractTextStyle.d.ts +9 -0
  4. package/dist/attribute-analyser/style/native/useExtractViewStyle.d.ts +8 -0
  5. package/dist/attribute-analyser/style/web/useExtractImageStyle.d.ts +4 -0
  6. package/dist/attribute-analyser/style/web/useExtractTextStyle.d.ts +4 -0
  7. package/dist/attribute-analyser/style/web/useExtractViewStyle.d.ts +4 -0
  8. package/dist/attributes-editor/AttributesEditorFields.d.ts +18 -0
  9. package/dist/attributes-editor/AttributesEditorView.d.ts +4 -0
  10. package/dist/attributes-editor/attributesEditorModelTypes.d.ts +67 -0
  11. package/dist/attributes-editor/attributesEditorUtils.d.ts +19 -0
  12. package/dist/attributes-editor/useAttributesEditorModel.d.ts +2 -0
  13. package/dist/build-components/BIcon/BIconProps.generated.d.ts +41 -38
  14. package/dist/build-components/BackgroundImage/BackgroundImageProps.generated.d.ts +37 -34
  15. package/dist/build-components/Button/ButtonProps.generated.d.ts +39 -36
  16. package/dist/build-components/Carousel/CarouselProps.generated.d.ts +37 -34
  17. package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +37 -34
  18. package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +37 -34
  19. package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +36 -33
  20. package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +36 -33
  21. package/dist/build-components/Image/ImageProps.generated.d.ts +38 -33
  22. package/dist/build-components/Main/MainProps.generated.d.ts +36 -33
  23. package/dist/build-components/Onboard/OnboardProps.generated.d.ts +36 -33
  24. package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +38 -34
  25. package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +39 -36
  26. package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +43 -34
  27. package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +41 -38
  28. package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +36 -31
  29. package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +39 -33
  30. package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +38 -34
  31. package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +41 -38
  32. package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +41 -38
  33. package/dist/build-components/PaywallBackground/PaywallBackgroundProps.generated.d.ts +36 -33
  34. package/dist/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.d.ts +41 -38
  35. package/dist/build-components/PaywallOptions/PaywallOptionsProps.generated.d.ts +36 -33
  36. package/dist/build-components/PaywallProvider/PaywallProviderProps.generated.d.ts +36 -33
  37. package/dist/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.d.ts +39 -36
  38. package/dist/build-components/RadioButton/RadioButtonProps.generated.d.ts +36 -33
  39. package/dist/build-components/Text/TextProps.generated.d.ts +41 -38
  40. package/dist/build-components/View/ViewProps.generated.d.ts +36 -33
  41. package/dist/build-components/patterns.generated.d.ts +2673 -5787
  42. package/dist/components/BuilderProvider.d.ts +6 -0
  43. package/dist/index.cjs.js +5 -5
  44. package/dist/index.cjs.js.map +1 -1
  45. package/dist/index.d.ts +5 -26
  46. package/dist/index.esm.js +5 -5
  47. package/dist/index.esm.js.map +1 -1
  48. package/dist/index.native.cjs.js +6 -4
  49. package/dist/index.native.cjs.js.map +1 -1
  50. package/dist/index.native.d.ts +6 -3
  51. package/dist/index.native.esm.js +6 -4
  52. package/dist/index.native.esm.js.map +1 -1
  53. package/dist/migrations/migratePipe.d.ts +1 -1
  54. package/dist/migrations/migrations/1.1.2_extract_component_attributes_from_style.d.ts +2 -0
  55. package/dist/mockOS/components/PermissionModal.d.ts +1 -2
  56. package/dist/styles.css +1 -1
  57. package/dist/types/PreviewConfig.d.ts +1 -5
  58. package/dist/utils/extractImageStyle.d.ts +3 -0
  59. package/dist/utils/extractTextStyle/extractTextStyleNative.d.ts +17 -0
  60. package/dist/utils/extractTextStyle.d.ts +2 -0
  61. package/dist/utils/extractViewStyle/extractViewStyleNative.d.ts +12 -0
  62. package/dist/utils/extractViewStyle.d.ts +2 -0
  63. package/dist/utils/getMeta.d.ts +5 -0
  64. package/dist/utils/patterns.d.ts +14 -1
  65. package/package.json +2 -1
  66. package/scripts/prebuild/prebuild.js +14 -0
  67. package/scripts/prebuild/utils/createGeneratedProps.js +51 -3
  68. package/scripts/prebuild/utils/index.js +1 -0
  69. package/scripts/prebuild/utils/updateMetaJson.js +66 -0
  70. package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +37 -3
  71. package/scripts/prebuild/utils/validatePatternJson.js +27 -2
  72. package/scripts/public/scripts/build/index.js +20 -3
  73. package/scripts/public/scripts/build/info.json +6 -0
  74. package/scripts/public/scripts/build/utils/createComponentsIndex.js +9 -3
  75. package/scripts/public/scripts/build/utils/createRenderNodeGenerated.js +66 -8
  76. package/src/AttributesEditor.tsx +8 -944
  77. package/src/assets/meta.json +4 -0
  78. package/src/assets/samples/carousel-sample.json +1 -1
  79. package/src/assets/samples/getSamples.ts +2 -0
  80. package/src/assets/samples/paywall-1.json +11 -7
  81. package/src/assets/samples/simple-1.json +3 -3
  82. package/src/assets/samples/simple-2.json +3 -3
  83. package/src/assets/samples/unmigrated-builder-1.1.1.json +87 -0
  84. package/src/assets/samples/unmigrated-builder1.json +1 -1
  85. package/src/assets/samples/unvalidated-builder1.json +3 -3
  86. package/src/assets/samples/unvalidated-crash1.json +1 -1
  87. package/src/assets/samples/unvalidated-crashcomponent1.json +1 -1
  88. package/src/assets/samples/vpn-onboard-1.json +1 -1
  89. package/src/assets/samples/vpn-onboard-2.json +1 -1
  90. package/src/assets/samples/vpn-onboard-3.json +1 -1
  91. package/src/assets/samples/vpn-onboard-4.json +1 -1
  92. package/src/assets/samples/vpn-onboard-5.json +1 -1
  93. package/src/assets/samples/vpn-onboard-6.json +1 -1
  94. package/src/attribute-analyser/style/native/useExtractImageStyle.ts +46 -0
  95. package/src/attribute-analyser/style/native/useExtractTextStyle.ts +50 -0
  96. package/src/attribute-analyser/style/native/useExtractViewStyle.ts +32 -0
  97. package/src/attribute-analyser/style/web/useExtractImageStyle.ts +20 -0
  98. package/src/{hooks → attribute-analyser/style/web}/useExtractTextStyle.ts +7 -6
  99. package/src/{hooks → attribute-analyser/style/web}/useExtractViewStyle.ts +7 -6
  100. package/src/attributes-editor/AttributesEditorFields.tsx +248 -0
  101. package/src/attributes-editor/AttributesEditorView.tsx +360 -0
  102. package/src/attributes-editor/LayoutPreviewPicker.tsx +4 -3
  103. package/src/attributes-editor/attributesEditorModelTypes.ts +86 -0
  104. package/src/attributes-editor/attributesEditorUtils.ts +102 -0
  105. package/src/attributes-editor/useAttributesEditorModel.ts +477 -0
  106. package/src/build-components/BIcon/BIcon.tsx +4 -3
  107. package/src/build-components/BIcon/BIconProps.generated.ts +42 -38
  108. package/src/build-components/BIcon/pattern.json +5 -6
  109. package/src/build-components/BackgroundImage/BackgroundImage.tsx +7 -4
  110. package/src/build-components/BackgroundImage/BackgroundImageProps.generated.ts +38 -34
  111. package/src/build-components/BackgroundImage/pattern.json +9 -17
  112. package/src/build-components/Button/Button.tsx +7 -6
  113. package/src/build-components/Button/ButtonProps.generated.ts +40 -36
  114. package/src/build-components/Button/pattern.json +17 -15
  115. package/src/build-components/Carousel/Carousel.tsx +1 -1
  116. package/src/build-components/Carousel/CarouselProps.generated.ts +38 -34
  117. package/src/build-components/CarouselButtons/CarouselButtons.tsx +4 -6
  118. package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +41 -37
  119. package/src/build-components/CarouselButtons/pattern.json +2 -1
  120. package/src/build-components/CarouselDots/CarouselDots.tsx +2 -2
  121. package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +44 -40
  122. package/src/build-components/CarouselItem/CarouselItem.tsx +1 -1
  123. package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +37 -33
  124. package/src/build-components/CarouselProvider/CarouselProvider.tsx +1 -1
  125. package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +37 -33
  126. package/src/build-components/Image/Image.tsx +4 -3
  127. package/src/build-components/Image/ImageProps.generated.ts +39 -33
  128. package/src/build-components/Image/pattern.json +5 -11
  129. package/src/build-components/Main/Main.tsx +1 -1
  130. package/src/build-components/Main/MainProps.generated.ts +37 -33
  131. package/src/build-components/Main/pattern.json +2 -1
  132. package/src/build-components/Onboard/OnboardProps.generated.ts +37 -33
  133. package/src/build-components/OnboardButton/OnboardButton.tsx +8 -6
  134. package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +44 -39
  135. package/src/build-components/OnboardButton/pattern.json +9 -7
  136. package/src/build-components/OnboardButtons/OnboardButtons.tsx +31 -31
  137. package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +43 -39
  138. package/src/build-components/OnboardButtons/pattern.json +9 -7
  139. package/src/build-components/OnboardDot/OnboardDot.tsx +7 -5
  140. package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +55 -34
  141. package/src/build-components/OnboardFooter/OnboardFooter.tsx +19 -23
  142. package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +42 -38
  143. package/src/build-components/OnboardFooter/pattern.json +16 -14
  144. package/src/build-components/OnboardImage/OnboardImage.tsx +8 -7
  145. package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +37 -31
  146. package/src/build-components/OnboardImage/pattern.json +2 -1
  147. package/src/build-components/OnboardItem/OnboardItem.tsx +1 -1
  148. package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +40 -33
  149. package/src/build-components/OnboardItem/pattern.json +2 -1
  150. package/src/build-components/OnboardProvider/OnboardProvider.tsx +1 -1
  151. package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +39 -34
  152. package/src/build-components/OnboardProvider/pattern.json +2 -1
  153. package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +42 -38
  154. package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +42 -38
  155. package/src/build-components/PaywallBackground/PaywallBackground.tsx +1 -1
  156. package/src/build-components/PaywallBackground/PaywallBackgroundProps.generated.ts +37 -33
  157. package/src/build-components/PaywallCloseButton/PaywallCloseButton.tsx +6 -5
  158. package/src/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.ts +42 -38
  159. package/src/build-components/PaywallOptions/PaywallOptionButton.tsx +1 -1
  160. package/src/build-components/PaywallOptions/PaywallOptionsProps.generated.ts +37 -33
  161. package/src/build-components/PaywallProvider/PaywallProvider.tsx +1 -1
  162. package/src/build-components/PaywallProvider/PaywallProviderProps.generated.ts +37 -33
  163. package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButton.tsx +1 -1
  164. package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.ts +40 -36
  165. package/src/build-components/RadioButton/RadioButton.tsx +5 -4
  166. package/src/build-components/RadioButton/RadioButtonProps.generated.ts +37 -33
  167. package/src/build-components/RadioButton/pattern.json +9 -7
  168. package/src/build-components/Text/Text.tsx +6 -8
  169. package/src/build-components/Text/TextProps.generated.ts +42 -38
  170. package/src/build-components/Text/pattern.json +15 -11
  171. package/src/build-components/View/View.tsx +1 -1
  172. package/src/build-components/View/ViewProps.generated.ts +37 -33
  173. package/src/build-components/View/pattern.json +71 -66
  174. package/src/build-components/patterns.generated.ts +3022 -5971
  175. package/src/components/AttributesEditorPanel.tsx +2 -2
  176. package/src/components/BuilderProvider.tsx +15 -1
  177. package/src/index.native.ts +7 -4
  178. package/src/index.ts +6 -77
  179. package/src/migrations/migratePipe.ts +7 -3
  180. package/src/migrations/migrations/1.1.2_extract_component_attributes_from_style.ts +211 -0
  181. package/src/mockOS/components/MockOSRouter.tsx +3 -1
  182. package/src/mockOS/components/PermissionModal.tsx +20 -160
  183. package/src/mockOS/components/SubscriptionModal.tsx +41 -278
  184. package/src/pages/ProjectPage.tsx +12 -6
  185. package/src/styles/components/_attributes-editor.scss +122 -0
  186. package/src/styles/components/_mockos-router.scss +388 -0
  187. package/src/styles/components/_onboard.scss +23 -0
  188. package/src/styles/index.scss +1 -0
  189. package/src/types/PreviewConfig.ts +1 -5
  190. package/src/utils/analyseNodeByPatterns.ts +39 -4
  191. package/src/utils/extractImageStyle.ts +34 -5
  192. package/src/utils/extractTextStyle/extractTextStyle.ts +7 -6
  193. package/src/utils/extractTextStyle/extractTextStyleNative.ts +106 -0
  194. package/src/utils/extractTextStyle.ts +2 -0
  195. package/src/utils/extractViewStyle/extractViewStyle.ts +2 -4
  196. package/src/utils/extractViewStyle/extractViewStyleNative.ts +111 -0
  197. package/src/utils/extractViewStyle.ts +2 -0
  198. package/src/utils/getMeta.ts +15 -0
  199. package/src/utils/patterns.ts +100 -3
  200. package/dist/hooks/useExtractImageStyle.d.ts +0 -3
  201. package/dist/hooks/useExtractTextStyle.d.ts +0 -3
  202. package/dist/hooks/useExtractViewStyle.d.ts +0 -3
  203. package/src/hooks/useExtractImageStyle.ts +0 -19
  204. package/src/migrations/migrations/1.1.0_normalize_style_attributes.ts +0 -80
@@ -0,0 +1,248 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import type { Fonts } from '../types/Fonts';
3
+ import { Icon } from '../components/Icon.generated';
4
+ import { IconPickerModal } from '../modals/IconPickerModal';
5
+ import Modal from '../modals/Modal';
6
+ import { loadFontFamily } from '../utils/loadFontFamily';
7
+ import { fontsDebug } from '../utils/fontsDebug';
8
+ import type { IconsType } from '../types/Icons';
9
+
10
+ type IconTypePickerFieldProps = {
11
+ name: string;
12
+ value: unknown;
13
+ onChange: (next: unknown) => void;
14
+ };
15
+
16
+ export function IconTypePickerField({
17
+ name,
18
+ value,
19
+ onChange,
20
+ }: IconTypePickerFieldProps) {
21
+ const [isOpen, setIsOpen] = useState(false);
22
+ const normalized =
23
+ typeof value === 'string' ? (value as IconsType) : undefined;
24
+
25
+ return (
26
+ <>
27
+ <button
28
+ type="button"
29
+ onClick={() => setIsOpen(true)}
30
+ className="attributes-editor__picker-button"
31
+ >
32
+ <span className="attributes-editor__picker-button-left">
33
+ {normalized ? <Icon iconType={normalized} size={18} /> : null}
34
+ <span className="attributes-editor__picker-button-label">
35
+ {normalized ?? 'Select icon'}
36
+ </span>
37
+ </span>
38
+ <span className="attributes-editor__picker-button-open">Open</span>
39
+ </button>
40
+
41
+ {isOpen ? (
42
+ <IconPickerModal
43
+ value={normalized}
44
+ onSelect={(iconName) => {
45
+ onChange(iconName);
46
+ setIsOpen(false);
47
+ }}
48
+ onClose={() => setIsOpen(false)}
49
+ onClear={() => {
50
+ onChange(undefined);
51
+ setIsOpen(false);
52
+ }}
53
+ />
54
+ ) : null}
55
+ </>
56
+ );
57
+ }
58
+
59
+ type FontFamilyPickerFieldProps = {
60
+ name: string;
61
+ value: unknown;
62
+ onChange: (next: unknown) => void;
63
+ fonts: Fonts;
64
+ loadedFonts: string[] | undefined;
65
+ markFontLoaded: (fontFamily: string) => void;
66
+ addError: (message: string) => void;
67
+ };
68
+
69
+ export function FontFamilyPickerField({
70
+ name,
71
+ value,
72
+ onChange,
73
+ fonts,
74
+ loadedFonts,
75
+ markFontLoaded,
76
+ addError,
77
+ }: FontFamilyPickerFieldProps) {
78
+ const [isOpen, setIsOpen] = useState(false);
79
+ const [isFontLoading, setIsFontLoading] = useState(false);
80
+ const [fontLoadError, setFontLoadError] = useState<string | null>(null);
81
+
82
+ const normalized =
83
+ typeof value === 'string' && value.trim().length > 0
84
+ ? value.trim()
85
+ : undefined;
86
+
87
+ const fontsList = Array.isArray(fonts) ? fonts : [];
88
+ const loaded = Array.isArray(loadedFonts) ? loadedFonts : [];
89
+
90
+ const fontCountLabel = useMemo(
91
+ () => `${fontsList.length} fonts`,
92
+ [fontsList.length],
93
+ );
94
+
95
+ return (
96
+ <>
97
+ <button
98
+ type="button"
99
+ onClick={() => {
100
+ setFontLoadError(null);
101
+ setIsOpen(true);
102
+ }}
103
+ className="attributes-editor__picker-button"
104
+ >
105
+ <span className="attributes-editor__picker-button-label attributes-editor__picker-button-label--fill">
106
+ {normalized ?? 'Select font'}
107
+ </span>
108
+ <span className="attributes-editor__picker-button-open">Open</span>
109
+ </button>
110
+
111
+ {isOpen ? (
112
+ <Modal
113
+ ariaLabelledBy="font-family-picker-title"
114
+ onClose={() => {
115
+ if (isFontLoading) return;
116
+ setIsOpen(false);
117
+ }}
118
+ closeOnOverlayClick={!isFontLoading}
119
+ closeOnEsc={!isFontLoading}
120
+ >
121
+ <div className="attributes-editor__font-modal">
122
+ <div className="attributes-editor__font-modal-header">
123
+ <p
124
+ id="font-family-picker-title"
125
+ className="attributes-editor__font-modal-title"
126
+ >
127
+ Select Font
128
+ </p>
129
+ <div className="attributes-editor__font-modal-spacer" />
130
+ <button
131
+ type="button"
132
+ className="editor-button"
133
+ onClick={() => {
134
+ onChange(undefined);
135
+ setFontLoadError(null);
136
+ setIsOpen(false);
137
+ }}
138
+ disabled={isFontLoading}
139
+ >
140
+ Clear
141
+ </button>
142
+ <button
143
+ type="button"
144
+ className="editor-button"
145
+ onClick={() => setIsOpen(false)}
146
+ disabled={isFontLoading}
147
+ >
148
+ Close
149
+ </button>
150
+ </div>
151
+
152
+ {isFontLoading ? (
153
+ <div className="attributes-editor__font-modal-note">
154
+ Loading font…
155
+ </div>
156
+ ) : null}
157
+ {fontLoadError ? (
158
+ <div className="attributes-editor__font-modal-error">
159
+ {fontLoadError}
160
+ </div>
161
+ ) : null}
162
+
163
+ <div className="attributes-editor__font-modal-note">
164
+ {fontCountLabel}
165
+ </div>
166
+
167
+ <div className="attributes-editor__font-grid">
168
+ {fontsList.map((font) => {
169
+ const fontName = font?.name;
170
+ if (typeof fontName !== 'string' || !fontName.trim()) {
171
+ return null;
172
+ }
173
+ const familyName = fontName.trim();
174
+ const isActive = normalized === familyName;
175
+ const isLoaded = loaded.includes(familyName);
176
+ const optionClassName = [
177
+ 'attributes-editor__font-option',
178
+ isActive ? 'attributes-editor__font-option--active' : '',
179
+ ]
180
+ .filter(Boolean)
181
+ .join(' ');
182
+
183
+ return (
184
+ <button
185
+ key={familyName}
186
+ type="button"
187
+ disabled={isFontLoading}
188
+ onClick={async () => {
189
+ fontsDebug.info('AttributesEditor: select fontFamily', {
190
+ field: name,
191
+ familyName,
192
+ wasLoaded: isLoaded,
193
+ });
194
+ setFontLoadError(null);
195
+ onChange(familyName);
196
+ if (isLoaded) return;
197
+
198
+ setIsFontLoading(true);
199
+ try {
200
+ fontsDebug.info(
201
+ 'AttributesEditor: loadFontFamily start',
202
+ { familyName },
203
+ );
204
+ await loadFontFamily(fontsList, familyName, {
205
+ forceFetch: true,
206
+ });
207
+ markFontLoaded(familyName);
208
+ fontsDebug.info(
209
+ 'AttributesEditor: loadFontFamily success',
210
+ { familyName },
211
+ );
212
+ } catch (e) {
213
+ const msg = e instanceof Error ? e.message : String(e);
214
+ setFontLoadError(
215
+ `Failed to load "${familyName}": ${msg}`,
216
+ );
217
+ addError(`Failed to load font "${familyName}": ${msg}`);
218
+ fontsDebug.compactError(
219
+ 'AttributesEditor: loadFontFamily failed',
220
+ e,
221
+ { familyName },
222
+ );
223
+ } finally {
224
+ setIsFontLoading(false);
225
+ }
226
+ }}
227
+ className={optionClassName}
228
+ aria-label={`Select font ${familyName}`}
229
+ >
230
+ <span
231
+ className="attributes-editor__font-option-name"
232
+ title={familyName}
233
+ >
234
+ {familyName}
235
+ </span>
236
+ <span className="attributes-editor__font-option-status">
237
+ {isLoaded ? 'Loaded' : 'Not loaded'}
238
+ </span>
239
+ </button>
240
+ );
241
+ })}
242
+ </div>
243
+ </div>
244
+ </Modal>
245
+ ) : null}
246
+ </>
247
+ );
248
+ }
@@ -0,0 +1,360 @@
1
+ import React, { useMemo } from 'react';
2
+ import { Field } from './Field';
3
+ import { SpecialCategorySection } from './SpecialCategorySection';
4
+ import { FieldInfoTooltip } from './FieldInfoTooltip';
5
+ import { toPreferredScale } from './SizeField';
6
+ import { isBooleanFieldType, type SchemaEntry } from './types';
7
+ import { MockableFeatureModal } from '../modals';
8
+ import {
9
+ FontFamilyPickerField,
10
+ IconTypePickerField,
11
+ } from './AttributesEditorFields';
12
+ import type {
13
+ AttributesEditorModel,
14
+ AttributesEditorSpecialSection,
15
+ } from './attributesEditorModelTypes';
16
+ import type { NodeDefaultAttribute } from '../types/Node';
17
+
18
+ type AttributesEditorViewProps = AttributesEditorModel;
19
+
20
+ function getPreferredOrderedSizeEntries(entries: SchemaEntry[]): SchemaEntry[] {
21
+ const preferredOrder = [
22
+ 'width',
23
+ 'height',
24
+ 'minWidth',
25
+ 'minHeight',
26
+ 'maxWidth',
27
+ 'maxHeight',
28
+ ];
29
+ return [...entries].sort((a, b) => {
30
+ const aIndex = preferredOrder.indexOf(a.name);
31
+ const bIndex = preferredOrder.indexOf(b.name);
32
+ const aRank = aIndex === -1 ? Number.MAX_SAFE_INTEGER : aIndex;
33
+ const bRank = bIndex === -1 ? Number.MAX_SAFE_INTEGER : bIndex;
34
+ return aRank !== bRank ? aRank - bRank : a.name.localeCompare(b.name);
35
+ });
36
+ }
37
+
38
+ function getSpecialSectionTitle(section: AttributesEditorSpecialSection): {
39
+ title: string;
40
+ description?: string;
41
+ } {
42
+ const key = section.key;
43
+ const meta = section.meta;
44
+ const title =
45
+ meta?.label && meta.label.trim().length > 0
46
+ ? meta.label
47
+ : key.charAt(0).toUpperCase() + key.slice(1);
48
+ return { title, description: meta?.description };
49
+ }
50
+
51
+ export function AttributesEditorView(props: AttributesEditorViewProps) {
52
+ const {
53
+ isInvalidNode,
54
+ componentTitle,
55
+ componentDescription,
56
+ mockableFeatureKeys,
57
+ activeMockableFeature,
58
+ setActiveMockableFeature,
59
+ tabs,
60
+ tabContentInfo,
61
+ activeTab,
62
+ setActiveTab,
63
+ firstAvailableTab,
64
+ activeEntries,
65
+ activeSpecialSections,
66
+ hasStringChildren,
67
+ childrenValue,
68
+ handleChildrenChange,
69
+ attributeMeta,
70
+ data,
71
+ getAttributeValue,
72
+ handleAttributeChange,
73
+ projectColorsForPicker,
74
+ layoutContext,
75
+ viewAttributes,
76
+ styleBag,
77
+ attributes,
78
+ projectFonts,
79
+ loadedFonts,
80
+ markFontLoaded,
81
+ addError,
82
+ } = props;
83
+
84
+ const headerSection = (
85
+ <div className="attributes-editor__component-meta">
86
+ <p className="attributes-editor__component-title">{componentTitle}</p>
87
+ {componentDescription ? (
88
+ <p className="attributes-editor__component-description">
89
+ {componentDescription}
90
+ </p>
91
+ ) : null}
92
+ </div>
93
+ );
94
+
95
+ const mockableSection =
96
+ mockableFeatureKeys.length > 0 ? (
97
+ <section className="attributes-editor__mockable">
98
+ <p className="attributes-editor__mockable-title">Mockable</p>
99
+ <table className="attributes-editor__mockable-table">
100
+ <tbody>
101
+ {mockableFeatureKeys.map((key) => (
102
+ <tr key={key} className="attributes-editor__mockable-row">
103
+ <td className="attributes-editor__mockable-name">{key}</td>
104
+ <td className="attributes-editor__mockable-action">
105
+ <button
106
+ type="button"
107
+ className="editor-button"
108
+ onClick={() => setActiveMockableFeature(key)}
109
+ >
110
+ {key}
111
+ </button>
112
+ </td>
113
+ </tr>
114
+ ))}
115
+ </tbody>
116
+ </table>
117
+ </section>
118
+ ) : null;
119
+
120
+ const tabsSection = (
121
+ <div className="attributes-editor__tabs">
122
+ {tabs.map((tab) => {
123
+ const isActive = tab.id === activeTab;
124
+ const counts = tabContentInfo[tab.id];
125
+ const totalCount = counts.baseCount + counts.specialCount;
126
+ const disabled = totalCount === 0;
127
+ const buttonClassNames = [
128
+ 'attributes-editor__tab-button',
129
+ isActive ? 'attributes-editor__tab-button--active' : '',
130
+ ]
131
+ .filter(Boolean)
132
+ .join(' ');
133
+
134
+ return (
135
+ <button
136
+ key={tab.id}
137
+ type="button"
138
+ onClick={() => !disabled && setActiveTab(tab.id)}
139
+ disabled={disabled}
140
+ className={buttonClassNames}
141
+ >
142
+ {tab.label}
143
+ {totalCount > 0 ? ` (${totalCount})` : ''}
144
+ </button>
145
+ );
146
+ })}
147
+ </div>
148
+ );
149
+
150
+ const childrenSection = hasStringChildren ? (
151
+ <div className="attributes-editor__field-wrapper attributes-editor__field-wrapper--children">
152
+ <p className="attributes-editor__field-label">Text</p>
153
+ <input
154
+ type="text"
155
+ className="attributes-editor__text-input"
156
+ value={childrenValue}
157
+ onChange={(e) => handleChildrenChange(e.target.value)}
158
+ />
159
+ </div>
160
+ ) : null;
161
+
162
+ const hasAnyContent = useMemo(() => {
163
+ const counts = tabContentInfo[firstAvailableTab];
164
+ return (
165
+ (counts?.baseCount ?? 0) + (counts?.specialCount ?? 0) > 0 ||
166
+ (hasStringChildren && childrenValue !== undefined)
167
+ );
168
+ }, [childrenValue, firstAvailableTab, hasStringChildren, tabContentInfo]);
169
+
170
+ function renderEntry(entry: SchemaEntry) {
171
+ const name = entry.name;
172
+ const type = entry.type;
173
+ const label = attributeMeta?.[name]?.label ?? name;
174
+ const description = attributeMeta?.[name]?.description;
175
+ const preferredScale = toPreferredScale(
176
+ attributeMeta?.[name]?.preferedScale,
177
+ );
178
+ const isBoolean = isBooleanFieldType(type);
179
+ const wrapperClassNames = [
180
+ 'attributes-editor__field-wrapper',
181
+ isBoolean ? 'attributes-editor__field-wrapper--boolean' : '',
182
+ ]
183
+ .filter(Boolean)
184
+ .join(' ');
185
+
186
+ const value = getAttributeValue(name);
187
+
188
+ return (
189
+ <FieldInfoTooltip key={name} description={description}>
190
+ <div className={wrapperClassNames}>
191
+ {!isBoolean ? (
192
+ <p className="attributes-editor__field-label">{label}</p>
193
+ ) : null}
194
+
195
+ {type === 'iconType' ? (
196
+ <IconTypePickerField
197
+ name={name}
198
+ value={value}
199
+ onChange={(next) => handleAttributeChange(name, next)}
200
+ />
201
+ ) : type === 'fontFamily' ? (
202
+ <FontFamilyPickerField
203
+ name={name}
204
+ value={value}
205
+ onChange={(next) => handleAttributeChange(name, next)}
206
+ fonts={projectFonts}
207
+ loadedFonts={loadedFonts}
208
+ markFontLoaded={markFontLoaded}
209
+ addError={addError}
210
+ />
211
+ ) : (
212
+ <Field
213
+ name={name}
214
+ type={type}
215
+ value={value}
216
+ onChange={(val) => handleAttributeChange(name, val)}
217
+ componentType={data?.type}
218
+ projectColors={projectColorsForPicker}
219
+ layoutContext={layoutContext}
220
+ viewAttributes={viewAttributes}
221
+ label={isBoolean ? label : undefined}
222
+ preferredScale={preferredScale}
223
+ />
224
+ )}
225
+ </div>
226
+ </FieldInfoTooltip>
227
+ );
228
+ }
229
+
230
+ function renderSpecialSection(section: AttributesEditorSpecialSection) {
231
+ if (section.key === 'size') {
232
+ const { title, description } = getSpecialSectionTitle(section);
233
+ const orderedEntries = getPreferredOrderedSizeEntries(section.entries);
234
+ return (
235
+ <section key={section.key} className="special-category-section">
236
+ <div className="special-category-section__header">
237
+ <p className="special-category-section__title">{title}</p>
238
+ </div>
239
+ {description ? (
240
+ <p className="special-category-section__description">
241
+ {description}
242
+ </p>
243
+ ) : null}
244
+ <div className="attributes-editor__size-grid">
245
+ {orderedEntries.map((entry) => {
246
+ const label = attributeMeta?.[entry.name]?.label ?? entry.name;
247
+ const description = attributeMeta?.[entry.name]?.description;
248
+ const preferredScale = toPreferredScale(
249
+ attributeMeta?.[entry.name]?.preferedScale,
250
+ );
251
+ const currentValue = getAttributeValue(entry.name);
252
+ const isBoolean = isBooleanFieldType(entry.type);
253
+ const wrapperClassNames = [
254
+ 'attributes-editor__field-wrapper',
255
+ isBoolean ? 'attributes-editor__field-wrapper--boolean' : '',
256
+ ]
257
+ .filter(Boolean)
258
+ .join(' ');
259
+ return (
260
+ <FieldInfoTooltip key={entry.name} description={description}>
261
+ <div
262
+ className={`${wrapperClassNames} attributes-editor__size-grid-item`}
263
+ >
264
+ {!isBoolean ? (
265
+ <p className="attributes-editor__field-label">{label}</p>
266
+ ) : null}
267
+ {entry.type === 'iconType' ? (
268
+ <IconTypePickerField
269
+ name={entry.name}
270
+ value={currentValue}
271
+ onChange={(next) =>
272
+ handleAttributeChange(entry.name, next)
273
+ }
274
+ />
275
+ ) : entry.type === 'fontFamily' ? (
276
+ <FontFamilyPickerField
277
+ name={entry.name}
278
+ value={currentValue}
279
+ onChange={(next) =>
280
+ handleAttributeChange(entry.name, next)
281
+ }
282
+ fonts={projectFonts}
283
+ loadedFonts={loadedFonts}
284
+ markFontLoaded={markFontLoaded}
285
+ addError={addError}
286
+ />
287
+ ) : (
288
+ <Field
289
+ name={entry.name}
290
+ type={entry.type}
291
+ value={currentValue}
292
+ onChange={(val) =>
293
+ handleAttributeChange(entry.name, val)
294
+ }
295
+ componentType={data?.type}
296
+ projectColors={projectColorsForPicker}
297
+ layoutContext={layoutContext}
298
+ viewAttributes={viewAttributes}
299
+ label={isBoolean ? label : undefined}
300
+ preferredScale={preferredScale}
301
+ />
302
+ )}
303
+ </div>
304
+ </FieldInfoTooltip>
305
+ );
306
+ })}
307
+ </div>
308
+ </section>
309
+ );
310
+ }
311
+
312
+ const sectionAttributes =
313
+ section.meta?.category === 'style'
314
+ ? ((styleBag ?? {}) as unknown as NodeDefaultAttribute)
315
+ : attributes;
316
+
317
+ return (
318
+ <SpecialCategorySection
319
+ key={section.key}
320
+ category={section.key}
321
+ entries={section.entries}
322
+ attributeMeta={attributeMeta}
323
+ attributes={sectionAttributes}
324
+ onAttributeChange={handleAttributeChange}
325
+ componentType={data?.type}
326
+ projectColors={projectColorsForPicker}
327
+ layoutContext={layoutContext}
328
+ viewAttributes={viewAttributes}
329
+ meta={section.meta}
330
+ />
331
+ );
332
+ }
333
+
334
+ if (isInvalidNode) return null;
335
+
336
+ return (
337
+ <div className="attributes-editor">
338
+ {headerSection}
339
+ {mockableSection}
340
+ {tabsSection}
341
+ {childrenSection}
342
+ {activeSpecialSections.map(renderSpecialSection)}
343
+
344
+ {activeEntries.map(renderEntry)}
345
+
346
+ {!hasAnyContent ? (
347
+ <div className="attributes-editor__empty-state">
348
+ No editable attributes
349
+ </div>
350
+ ) : null}
351
+
352
+ {activeMockableFeature ? (
353
+ <MockableFeatureModal
354
+ featureKey={activeMockableFeature}
355
+ onClose={() => setActiveMockableFeature(null)}
356
+ />
357
+ ) : null}
358
+ </div>
359
+ );
360
+ }
@@ -94,12 +94,13 @@ function LayoutPreview({
94
94
  const resolved = resolvePreviewContext(mode, option, layoutContext);
95
95
  const isColumn = resolved.flexDirection?.startsWith('column');
96
96
  const resolvedBackground =
97
- viewAttributes?.backgroundColor ??
97
+ (viewAttributes as any)?.style?.backgroundColor ??
98
98
  (isActive
99
99
  ? 'hsl(var(--muted, var(--rb-muted, 220 14.3% 95.9%)) / 0.55)'
100
100
  : 'hsl(var(--muted, var(--rb-muted, 220 14.3% 95.9%)))');
101
- const resolvedGap = parseNumeric(viewAttributes?.gap) ?? 3;
102
- const resolvedBorderRadius = parseNumeric(viewAttributes?.borderRadius) ?? 4;
101
+ const resolvedGap = parseNumeric((viewAttributes as any)?.style?.gap) ?? 3;
102
+ const resolvedBorderRadius =
103
+ parseNumeric((viewAttributes as any)?.style?.borderRadius) ?? 4;
103
104
 
104
105
  const stretchOverrides =
105
106
  resolved.alignItems === 'stretch'