@developer_tribe/react-builder 1.1.0 → 1.2.0

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 (682) hide show
  1. package/README.md +6 -0
  2. package/dist/AttributesEditor.d.ts +3 -1
  3. package/dist/DeviceMockFrame.d.ts +2 -1
  4. package/dist/RenderPage.d.ts +5 -3
  5. package/dist/assets/samples/getSamples.d.ts +1 -0
  6. package/dist/attributes-editor/Field.d.ts +18 -0
  7. package/dist/attributes-editor/FieldInfoTooltip.d.ts +7 -0
  8. package/dist/attributes-editor/LayoutPreviewPicker.d.ts +12 -0
  9. package/dist/attributes-editor/SizeField.d.ts +9 -0
  10. package/dist/attributes-editor/SpecialCategorySection.d.ts +20 -0
  11. package/dist/attributes-editor/types.d.ts +14 -0
  12. package/dist/build-components/BIcon/BIcon.d.ts +5 -0
  13. package/dist/build-components/BIcon/BIconProps.generated.d.ts +58 -0
  14. package/dist/build-components/BackgroundImage/BackgroundImage.d.ts +5 -0
  15. package/dist/build-components/BackgroundImage/BackgroundImageProps.generated.d.ts +50 -0
  16. package/dist/build-components/Button/Button.d.ts +1 -1
  17. package/dist/build-components/Button/ButtonProps.generated.d.ts +39 -1
  18. package/dist/build-components/Carousel/CarouselProps.generated.d.ts +44 -1
  19. package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +38 -0
  20. package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +38 -0
  21. package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +40 -1
  22. package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +40 -1
  23. package/dist/build-components/Image/ImageProps.generated.d.ts +38 -3
  24. package/dist/build-components/Main/Main.d.ts +5 -0
  25. package/dist/build-components/Main/MainProps.generated.d.ts +48 -0
  26. package/dist/build-components/Onboard/OnboardProps.generated.d.ts +40 -1
  27. package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +38 -1
  28. package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +38 -0
  29. package/dist/build-components/OnboardDot/OnboardDot.d.ts +1 -1
  30. package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +36 -3
  31. package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +21 -7
  32. package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +38 -3
  33. package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +37 -3
  34. package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +40 -6
  35. package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +21 -7
  36. package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +21 -7
  37. package/dist/build-components/PaywallBackground/PaywallBackground.d.ts +5 -0
  38. package/dist/build-components/PaywallBackground/PaywallBackgroundProps.generated.d.ts +47 -0
  39. package/dist/build-components/PaywallCloseButton/PaywallCloseButton.d.ts +5 -0
  40. package/dist/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.d.ts +58 -0
  41. package/dist/build-components/PaywallOptions/PaywallOptionButton.d.ts +9 -0
  42. package/dist/build-components/PaywallOptions/PaywallOptions.d.ts +5 -0
  43. package/dist/build-components/PaywallOptions/PaywallOptionsProps.generated.d.ts +47 -0
  44. package/dist/build-components/PaywallOptions/usePaywallOptionParamsFactory.d.ts +10 -0
  45. package/dist/build-components/PaywallProvider/PaywallContext.d.ts +12 -0
  46. package/dist/build-components/PaywallProvider/PaywallProvider.d.ts +5 -0
  47. package/dist/build-components/PaywallProvider/PaywallProviderProps.generated.d.ts +47 -0
  48. package/dist/build-components/PaywallSubscribeButton/PaywallSubscribeButton.d.ts +5 -0
  49. package/dist/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.d.ts +51 -0
  50. package/dist/build-components/RadioButton/RadioButton.d.ts +11 -0
  51. package/dist/build-components/RadioButton/RadioButtonProps.generated.d.ts +50 -0
  52. package/dist/build-components/RenderNode.generated.d.ts +1 -1
  53. package/dist/build-components/Text/TextProps.generated.d.ts +21 -7
  54. package/dist/build-components/View/ViewProps.generated.d.ts +16 -4
  55. package/dist/build-components/index.d.ts +10 -1
  56. package/dist/build-components/patterns.generated.d.ts +10325 -203
  57. package/dist/components/AttributesEditorPanel.d.ts +4 -4
  58. package/dist/components/BottomBar.d.ts +12 -0
  59. package/dist/components/Breadcrumb.d.ts +3 -1
  60. package/dist/components/Builder.d.ts +3 -2
  61. package/dist/components/BuilderButton.d.ts +9 -0
  62. package/dist/components/BuilderProvider.d.ts +17 -0
  63. package/dist/components/Checkbox.d.ts +17 -0
  64. package/dist/components/DeviceButton.d.ts +8 -0
  65. package/dist/components/DeviceNavigationBar.d.ts +10 -0
  66. package/dist/components/DeviceStatusBar.d.ts +9 -0
  67. package/dist/components/EditorHeader.d.ts +3 -8
  68. package/dist/components/Icon.generated.d.ts +11 -0
  69. package/dist/components/JsonTextEditor.d.ts +9 -0
  70. package/dist/components/LoadingComponent.d.ts +1 -0
  71. package/dist/components/LocalizationParamsProvider.d.ts +11 -0
  72. package/dist/components/ParamsProvider.d.ts +19 -0
  73. package/dist/components/RenderErrorBoundary.d.ts +28 -0
  74. package/dist/hooks/useLocalizationParams.d.ts +1 -0
  75. package/dist/hooks/useLocalize.d.ts +2 -0
  76. package/dist/hooks/useParams.d.ts +1 -0
  77. package/dist/hooks/useProjectFonts.d.ts +13 -0
  78. package/dist/hooks/useSafeAreaViewStyle.d.ts +1 -0
  79. package/dist/hooks/useSyncHtmlThemeClass.d.ts +7 -0
  80. package/dist/index.cjs.js +5 -5
  81. package/dist/index.cjs.js.map +1 -1
  82. package/dist/index.d.ts +17 -2
  83. package/dist/index.esm.js +5 -5
  84. package/dist/index.esm.js.map +1 -1
  85. package/dist/index.native.cjs.js +29 -0
  86. package/dist/index.native.cjs.js.map +1 -0
  87. package/dist/index.native.d.ts +32 -0
  88. package/dist/index.native.esm.js +29 -0
  89. package/dist/index.native.esm.js.map +1 -0
  90. package/dist/migrations/migratePipe.d.ts +14 -0
  91. package/dist/migrations/migrations/1.1.0_normalize_style_attributes.d.ts +2 -0
  92. package/dist/migrations/semver.d.ts +8 -0
  93. package/dist/migrations/types.d.ts +8 -0
  94. package/dist/mockOS/components/MockLaunchScreenComponent.d.ts +6 -0
  95. package/dist/mockOS/components/MockOSRouter.d.ts +8 -0
  96. package/dist/mockOS/components/PermissionModal.d.ts +9 -0
  97. package/dist/mockOS/components/SubscriptionModal.d.ts +7 -0
  98. package/dist/mockOS/context/MockOSContext.d.ts +22 -0
  99. package/dist/mockOS/context/MockOSContextBase.d.ts +22 -0
  100. package/dist/mockOS/hooks/useMockIap.d.ts +3 -0
  101. package/dist/mockOS/hooks/useMockNavigation.d.ts +3 -0
  102. package/dist/mockOS/hooks/useMockPermission.d.ts +3 -0
  103. package/dist/mockOS/index.d.ts +14 -0
  104. package/dist/mockOS/managers/mockOSIapManager.d.ts +6 -0
  105. package/dist/mockOS/managers/mockPermissionManager.d.ts +10 -0
  106. package/dist/mockOS/managers/navigationManager.d.ts +17 -0
  107. package/dist/mockOS/managers/subscriptionManager.d.ts +10 -0
  108. package/dist/modals/AddComponentModal.d.ts +8 -0
  109. package/dist/modals/BenefitEditModal.d.ts +13 -0
  110. package/dist/modals/BenefitPresetsModal.d.ts +9 -0
  111. package/dist/modals/ColorModal.d.ts +11 -0
  112. package/dist/modals/DeviceSelectorModal.d.ts +9 -0
  113. package/dist/modals/IconPickerModal.d.ts +9 -0
  114. package/dist/modals/LocalicationModal.d.ts +8 -0
  115. package/dist/modals/MockableFeatureModal.d.ts +6 -0
  116. package/dist/modals/Modal.d.ts +12 -0
  117. package/dist/modals/ProductEditModal.d.ts +9 -0
  118. package/dist/modals/ProductPresetsModal.d.ts +9 -0
  119. package/dist/modals/ScreenColorsModal.d.ts +8 -0
  120. package/dist/modals/index.d.ts +12 -0
  121. package/dist/pages/ProjectDebug.d.ts +14 -0
  122. package/dist/pages/ProjectMigrationPage.d.ts +23 -0
  123. package/dist/pages/ProjectPage.d.ts +11 -4
  124. package/dist/pages/ProjectValidationPage.d.ts +15 -0
  125. package/dist/pages/tabs/BuilderPanel.d.ts +8 -0
  126. package/dist/pages/tabs/SideTool.d.ts +8 -0
  127. package/dist/paywall/hooks/index.d.ts +5 -0
  128. package/dist/paywall/hooks/useCalculateLocalizedPrice.d.ts +4 -0
  129. package/dist/paywall/hooks/useCarouselOptionsSeperator.d.ts +6 -0
  130. package/dist/paywall/hooks/useCloseStatusPaywall.d.ts +4 -0
  131. package/dist/paywall/hooks/useDiscountRate.d.ts +4 -0
  132. package/dist/paywall/hooks/usePaywallCounter.d.ts +4 -0
  133. package/dist/paywall/types/benefits.d.ts +14 -0
  134. package/dist/paywall/types/paywall-types.d.ts +43 -0
  135. package/dist/store.d.ts +57 -3
  136. package/dist/styles.css +1 -1
  137. package/dist/types/Device.d.ts +5 -0
  138. package/dist/types/Fonts.d.ts +12 -0
  139. package/dist/types/Icons.d.ts +2 -0
  140. package/dist/types/Project.d.ts +26 -0
  141. package/dist/utils/__special_exceptions.d.ts +7 -0
  142. package/dist/utils/analyseNode.d.ts +2 -4
  143. package/dist/utils/analyseNodeByPatterns.d.ts +16 -0
  144. package/dist/utils/analyseNodeStructural.d.ts +11 -0
  145. package/dist/utils/extractImageStyle.d.ts +2 -1
  146. package/dist/utils/extractTextStyle.d.ts +8 -1
  147. package/dist/utils/extractViewStyle.d.ts +7 -1
  148. package/dist/utils/findNodeByKeyNested.d.ts +2 -0
  149. package/dist/utils/fontWeight.d.ts +3 -0
  150. package/dist/utils/fontsDebug.d.ts +12 -0
  151. package/dist/utils/getImage.d.ts +23 -0
  152. package/dist/utils/loadFontFamily.d.ts +30 -0
  153. package/dist/utils/logger.d.ts +11 -0
  154. package/dist/utils/nodeGuards.d.ts +5 -0
  155. package/dist/utils/nodeTree.d.ts +5 -0
  156. package/dist/utils/novaToJson.d.ts +5 -0
  157. package/dist/utils/parseColor.d.ts +7 -0
  158. package/dist/utils/pasteNode.d.ts +15 -0
  159. package/dist/utils/patterns.d.ts +39 -2
  160. package/dist/utils/replaceLocalizationParams.d.ts +1 -0
  161. package/dist/utils/selection.d.ts +7 -0
  162. package/dist/utils/useLogRender.d.ts +1 -0
  163. package/dist/utils/useMergedStyle.d.ts +2 -0
  164. package/package.json +42 -7
  165. package/scripts/migrate-patterns-to-v2.mjs +131 -0
  166. package/scripts/migrate-samples-to-current.ts +79 -0
  167. package/scripts/prebuild/assets/icon.template +71 -0
  168. package/scripts/prebuild/build-components.js +5 -0
  169. package/scripts/prebuild/icon-generator.js +206 -0
  170. package/scripts/prebuild/prebuild.js +10 -1
  171. package/scripts/prebuild/utils/createComponentTsx.js +6 -3
  172. package/scripts/prebuild/utils/createGeneratedProps.js +25 -8
  173. package/scripts/prebuild/utils/createRenderNodeGenerated.js +43 -8
  174. package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +92 -29
  175. package/scripts/prebuild/utils/validatePatternJson.js +26 -15
  176. package/src/.DS_Store +0 -0
  177. package/src/AttributesEditor.tsx +903 -303
  178. package/src/DeviceMockFrame.tsx +50 -66
  179. package/src/RenderPage.tsx +120 -16
  180. package/src/assets/.DS_Store +0 -0
  181. package/src/assets/benefits.json +24 -0
  182. package/src/assets/devices.json +91 -0
  183. package/src/assets/iconset/activity-heart.svg +3 -0
  184. package/src/assets/iconset/activity.svg +3 -0
  185. package/src/assets/iconset/alert-circle.svg +3 -0
  186. package/src/assets/iconset/alert-triangle.svg +3 -0
  187. package/src/assets/iconset/anchor.svg +3 -0
  188. package/src/assets/iconset/archive.svg +3 -0
  189. package/src/assets/iconset/arrow-down.svg +3 -0
  190. package/src/assets/iconset/arrow-left.svg +3 -0
  191. package/src/assets/iconset/arrow-narrow-down-left.svg +3 -0
  192. package/src/assets/iconset/arrow-narrow-up-right.svg +3 -0
  193. package/src/assets/iconset/arrow-right-smooth.svg +4 -0
  194. package/src/assets/iconset/arrow-right.svg +7 -0
  195. package/src/assets/iconset/asterisk-01.svg +3 -0
  196. package/src/assets/iconset/asterisk-02.svg +3 -0
  197. package/src/assets/iconset/at-sign.svg +3 -0
  198. package/src/assets/iconset/award.svg +4 -0
  199. package/src/assets/iconset/battery-charging.svg +6 -0
  200. package/src/assets/iconset/bell-01.svg +5 -0
  201. package/src/assets/iconset/bell-02.svg +3 -0
  202. package/src/assets/iconset/bell-ringing-02.svg +3 -0
  203. package/src/assets/iconset/bookmark-add.svg +3 -0
  204. package/src/assets/iconset/bookmark-check.svg +3 -0
  205. package/src/assets/iconset/bookmark-minus.svg +3 -0
  206. package/src/assets/iconset/bookmark-x.svg +3 -0
  207. package/src/assets/iconset/bookmark.svg +3 -0
  208. package/src/assets/iconset/bubble.svg +5 -0
  209. package/src/assets/iconset/building-01.svg +3 -0
  210. package/src/assets/iconset/building-02.svg +3 -0
  211. package/src/assets/iconset/building-03.svg +3 -0
  212. package/src/assets/iconset/building-04.svg +3 -0
  213. package/src/assets/iconset/building-05.svg +3 -0
  214. package/src/assets/iconset/building-06.svg +3 -0
  215. package/src/assets/iconset/building-07.svg +3 -0
  216. package/src/assets/iconset/building-08.svg +3 -0
  217. package/src/assets/iconset/building-09.svg +3 -0
  218. package/src/assets/iconset/camera-01.svg +8 -0
  219. package/src/assets/iconset/camera-steel.svg +4 -0
  220. package/src/assets/iconset/camera.svg +4 -0
  221. package/src/assets/iconset/check-circle-bold.svg +3 -0
  222. package/src/assets/iconset/check-circle-broken.svg +3 -0
  223. package/src/assets/iconset/check-circle.svg +3 -0
  224. package/src/assets/iconset/check-done-01.svg +3 -0
  225. package/src/assets/iconset/check-done-02.svg +3 -0
  226. package/src/assets/iconset/check-heart.svg +3 -0
  227. package/src/assets/iconset/check-square-broken.svg +3 -0
  228. package/src/assets/iconset/check-square.svg +3 -0
  229. package/src/assets/iconset/check-verified-01.svg +3 -0
  230. package/src/assets/iconset/check-verified-02.svg +3 -0
  231. package/src/assets/iconset/check-verified-03.svg +3 -0
  232. package/src/assets/iconset/check.svg +3 -0
  233. package/src/assets/iconset/checkbox.svg +4 -0
  234. package/src/assets/iconset/checkv.svg +3 -0
  235. package/src/assets/iconset/chevron-down.svg +3 -0
  236. package/src/assets/iconset/chevron-down2.svg +3 -0
  237. package/src/assets/iconset/chevron-left-2.svg +3 -0
  238. package/src/assets/iconset/chevron-left.svg +3 -0
  239. package/src/assets/iconset/chevron-right-empty.svg +3 -0
  240. package/src/assets/iconset/chevron-right-smooth.svg +3 -0
  241. package/src/assets/iconset/chevron-right.svg +3 -0
  242. package/src/assets/iconset/chevron-up.svg +3 -0
  243. package/src/assets/iconset/circle.svg +32 -0
  244. package/src/assets/iconset/clock-fast-forward.svg +10 -0
  245. package/src/assets/iconset/clock.svg +3 -0
  246. package/src/assets/iconset/close-circle.svg +3 -0
  247. package/src/assets/iconset/close.svg +3 -0
  248. package/src/assets/iconset/cloud-01.svg +5 -0
  249. package/src/assets/iconset/cloud-blank-01.svg +3 -0
  250. package/src/assets/iconset/cloud-blank-02.svg +3 -0
  251. package/src/assets/iconset/coin.svg +5 -0
  252. package/src/assets/iconset/coins-02.svg +3 -0
  253. package/src/assets/iconset/colors.svg +3 -0
  254. package/src/assets/iconset/copy-01.svg +3 -0
  255. package/src/assets/iconset/copy-02.svg +3 -0
  256. package/src/assets/iconset/copy-03.svg +3 -0
  257. package/src/assets/iconset/copy-04.svg +3 -0
  258. package/src/assets/iconset/copy-05.svg +3 -0
  259. package/src/assets/iconset/copy-06.svg +3 -0
  260. package/src/assets/iconset/copy-07.svg +3 -0
  261. package/src/assets/iconset/corner-down-right.svg +3 -0
  262. package/src/assets/iconset/crypto-bold.svg +4 -0
  263. package/src/assets/iconset/delete-icon.svg +5 -0
  264. package/src/assets/iconset/diamond.svg +3 -0
  265. package/src/assets/iconset/dice-3.svg +3 -0
  266. package/src/assets/iconset/divide-01.svg +3 -0
  267. package/src/assets/iconset/divide-02.svg +3 -0
  268. package/src/assets/iconset/divide-03.svg +3 -0
  269. package/src/assets/iconset/document-check-bold.svg +4 -0
  270. package/src/assets/iconset/dots-circle.svg +10 -0
  271. package/src/assets/iconset/dots-grid.svg +11 -0
  272. package/src/assets/iconset/dots-horizontal.svg +5 -0
  273. package/src/assets/iconset/dots-vertical.svg +5 -0
  274. package/src/assets/iconset/download-01.svg +3 -0
  275. package/src/assets/iconset/download-02.svg +3 -0
  276. package/src/assets/iconset/download-03.svg +3 -0
  277. package/src/assets/iconset/edit-03.svg +3 -0
  278. package/src/assets/iconset/edit-04.svg +3 -0
  279. package/src/assets/iconset/edit-05.svg +3 -0
  280. package/src/assets/iconset/element-3.svg +6 -0
  281. package/src/assets/iconset/ellipse-127.svg +3 -0
  282. package/src/assets/iconset/exclaimation-circle.svg +8 -0
  283. package/src/assets/iconset/eye-off-line.svg +5 -0
  284. package/src/assets/iconset/face-smile.svg +5 -0
  285. package/src/assets/iconset/file-04.svg +3 -0
  286. package/src/assets/iconset/file-05.svg +3 -0
  287. package/src/assets/iconset/file-check-02.svg +3 -0
  288. package/src/assets/iconset/file-plus-01.svg +5 -0
  289. package/src/assets/iconset/file-shield-02.svg +5 -0
  290. package/src/assets/iconset/filter-funnel-01.svg +3 -0
  291. package/src/assets/iconset/flag-03.svg +3 -0
  292. package/src/assets/iconset/flash.svg +3 -0
  293. package/src/assets/iconset/folder-plus.svg +3 -0
  294. package/src/assets/iconset/folder.svg +3 -0
  295. package/src/assets/iconset/gallery.svg +3 -0
  296. package/src/assets/iconset/globe-01.svg +3 -0
  297. package/src/assets/iconset/globe-04.svg +5 -0
  298. package/src/assets/iconset/globe-bold.svg +4 -0
  299. package/src/assets/iconset/guard.svg +3 -0
  300. package/src/assets/iconset/headphones-01.svg +3 -0
  301. package/src/assets/iconset/headphones-02.svg +5 -0
  302. package/src/assets/iconset/headset-bold.svg +4 -0
  303. package/src/assets/iconset/heart-bold.svg +3 -0
  304. package/src/assets/iconset/heart.svg +3 -0
  305. package/src/assets/iconset/help-circle.svg +5 -0
  306. package/src/assets/iconset/home-2.svg +4 -0
  307. package/src/assets/iconset/home-line.svg +3 -0
  308. package/src/assets/iconset/hourglass-02.svg +5 -0
  309. package/src/assets/iconset/image-01.svg +5 -0
  310. package/src/assets/iconset/image-03.svg +3 -0
  311. package/src/assets/iconset/image.svg +4 -0
  312. package/src/assets/iconset/inbox-01.svg +3 -0
  313. package/src/assets/iconset/inbox-arrow-down.svg +3 -0
  314. package/src/assets/iconset/info-circle.svg +3 -0
  315. package/src/assets/iconset/keyboard-line.svg +9 -0
  316. package/src/assets/iconset/lamp-charge.svg +5 -0
  317. package/src/assets/iconset/layer.svg +3 -0
  318. package/src/assets/iconset/light.svg +6 -0
  319. package/src/assets/iconset/like-dislike.svg +6 -0
  320. package/src/assets/iconset/lock-03.svg +3 -0
  321. package/src/assets/iconset/logout.svg +3 -0
  322. package/src/assets/iconset/magicpen.svg +7 -0
  323. package/src/assets/iconset/mail-01.svg +5 -0
  324. package/src/assets/iconset/mail.svg +3 -0
  325. package/src/assets/iconset/marker.svg +3 -0
  326. package/src/assets/iconset/medal-star.svg +5 -0
  327. package/src/assets/iconset/menu-04.svg +3 -0
  328. package/src/assets/iconset/menu.svg +5 -0
  329. package/src/assets/iconset/message-circle-01.svg +5 -0
  330. package/src/assets/iconset/message-plus-circle.svg +3 -0
  331. package/src/assets/iconset/message-question-circle.svg +5 -0
  332. package/src/assets/iconset/message-text-circle-01.svg +5 -0
  333. package/src/assets/iconset/message-text-square-02.svg +3 -0
  334. package/src/assets/iconset/message-x-square.svg +3 -0
  335. package/src/assets/iconset/microphone-02.svg +3 -0
  336. package/src/assets/iconset/microphone-slash.svg +8 -0
  337. package/src/assets/iconset/mirror.svg +4 -0
  338. package/src/assets/iconset/moon-01.svg +3 -0
  339. package/src/assets/iconset/moon-bold.svg +3 -0
  340. package/src/assets/iconset/mouse-circle.svg +4 -0
  341. package/src/assets/iconset/move.svg +5 -0
  342. package/src/assets/iconset/notification-fill.svg +3 -0
  343. package/src/assets/iconset/notification-text.svg +3 -0
  344. package/src/assets/iconset/notification.svg +5 -0
  345. package/src/assets/iconset/pdf-01.svg +6 -0
  346. package/src/assets/iconset/pencil-01.svg +5 -0
  347. package/src/assets/iconset/phone-01.svg +3 -0
  348. package/src/assets/iconset/phone-arrow-down-left.svg +4 -0
  349. package/src/assets/iconset/phone-arrow-up-right.svg +8 -0
  350. package/src/assets/iconset/phone-hang-up.svg +5 -0
  351. package/src/assets/iconset/phone-hangup2.svg +8 -0
  352. package/src/assets/iconset/phone-incoming-01.svg +3 -0
  353. package/src/assets/iconset/phone-outgoing-01.svg +3 -0
  354. package/src/assets/iconset/phone-plus.svg +3 -0
  355. package/src/assets/iconset/phone-x.svg +3 -0
  356. package/src/assets/iconset/phone.svg +3 -0
  357. package/src/assets/iconset/plus-circle.svg +3 -0
  358. package/src/assets/iconset/plus.svg +4 -0
  359. package/src/assets/iconset/printer.svg +3 -0
  360. package/src/assets/iconset/question-mark-circle.svg +5 -0
  361. package/src/assets/iconset/refresh-ccw-01.svg +3 -0
  362. package/src/assets/iconset/refresh-cw-01.svg +3 -0
  363. package/src/assets/iconset/refresh-cw-04.svg +3 -0
  364. package/src/assets/iconset/refresh-right-square-bold.svg +3 -0
  365. package/src/assets/iconset/remove-circle.svg +12 -0
  366. package/src/assets/iconset/repeat-04.svg +3 -0
  367. package/src/assets/iconset/repeat-bold.svg +3 -0
  368. package/src/assets/iconset/ruler-pen.svg +4 -0
  369. package/src/assets/iconset/search-lg.svg +3 -0
  370. package/src/assets/iconset/search-md.svg +5 -0
  371. package/src/assets/iconset/search-refraction.svg +5 -0
  372. package/src/assets/iconset/search.svg +3 -0
  373. package/src/assets/iconset/send-01.svg +3 -0
  374. package/src/assets/iconset/send-02.svg +5 -0
  375. package/src/assets/iconset/send-diagonal.svg +3 -0
  376. package/src/assets/iconset/setting-2.svg +4 -0
  377. package/src/assets/iconset/settings-02.svg +4 -0
  378. package/src/assets/iconset/settings-04.svg +5 -0
  379. package/src/assets/iconset/settings-2.svg +4 -0
  380. package/src/assets/iconset/settings-cog.svg +3 -0
  381. package/src/assets/iconset/settings.svg +4 -0
  382. package/src/assets/iconset/share-01.svg +4 -0
  383. package/src/assets/iconset/share-03.svg +3 -0
  384. package/src/assets/iconset/share-04.svg +3 -0
  385. package/src/assets/iconset/share-05.svg +5 -0
  386. package/src/assets/iconset/share-06.svg +3 -0
  387. package/src/assets/iconset/share-bold.svg +3 -0
  388. package/src/assets/iconset/shield-01.svg +3 -0
  389. package/src/assets/iconset/shield-bold.svg +3 -0
  390. package/src/assets/iconset/solar-check.svg +3 -0
  391. package/src/assets/iconset/speaker-wave.svg +9 -0
  392. package/src/assets/iconset/speaker.svg +5 -0
  393. package/src/assets/iconset/speedometer-03.svg +3 -0
  394. package/src/assets/iconset/star-rounded.svg +3 -0
  395. package/src/assets/iconset/star.svg +3 -0
  396. package/src/assets/iconset/sun.svg +5 -0
  397. package/src/assets/iconset/target-03.svg +3 -0
  398. package/src/assets/iconset/text-input.svg +3 -0
  399. package/src/assets/iconset/translate.svg +7 -0
  400. package/src/assets/iconset/trash-02.svg +3 -0
  401. package/src/assets/iconset/trash-03.svg +5 -0
  402. package/src/assets/iconset/trash-04.svg +3 -0
  403. package/src/assets/iconset/trash.svg +7 -0
  404. package/src/assets/iconset/trush-square-bold.svg +3 -0
  405. package/src/assets/iconset/unlimited.svg +3 -0
  406. package/src/assets/iconset/user-circle.svg +3 -0
  407. package/src/assets/iconset/user-jogging.svg +3 -0
  408. package/src/assets/iconset/user-plus-01.svg +5 -0
  409. package/src/assets/iconset/user-square.svg +5 -0
  410. package/src/assets/iconset/user-x-01.svg +5 -0
  411. package/src/assets/iconset/user-x-02.svg +3 -0
  412. package/src/assets/iconset/user2.svg +3 -0
  413. package/src/assets/iconset/users-02.svg +5 -0
  414. package/src/assets/iconset/users-speaker.svg +7 -0
  415. package/src/assets/iconset/verify.svg +3 -0
  416. package/src/assets/iconset/voice-cricle.svg +8 -0
  417. package/src/assets/iconset/x-circle.svg +3 -0
  418. package/src/assets/iconset/x-close.svg +3 -0
  419. package/src/assets/iconset/x-sm.svg +3 -0
  420. package/src/assets/iconset/zap.svg +3 -0
  421. package/src/assets/images/android.svg +42 -42
  422. package/src/assets/images/apple.svg +15 -15
  423. package/src/assets/images/background.jpg +0 -0
  424. package/src/assets/loading_animation.json +1 -0
  425. package/src/assets/products.json +98 -0
  426. package/src/assets/samples/carousel-sample.json +142 -29
  427. package/src/assets/samples/getSamples.ts +48 -66
  428. package/src/assets/samples/paywall-1.json +268 -0
  429. package/src/assets/samples/simple-1.json +29 -16
  430. package/src/assets/samples/simple-2.json +158 -82
  431. package/src/assets/samples/unmigrated-builder1.json +42 -0
  432. package/src/assets/samples/unvalidated-builder1.json +49 -0
  433. package/src/assets/samples/unvalidated-crash1.json +19 -0
  434. package/src/assets/samples/unvalidated-crashcomponent1.json +16 -0
  435. package/src/assets/samples/vpn-onboard-1.json +357 -713
  436. package/src/assets/samples/vpn-onboard-2.json +360 -704
  437. package/src/assets/samples/vpn-onboard-3.json +325 -701
  438. package/src/assets/samples/vpn-onboard-4.json +325 -702
  439. package/src/assets/samples/vpn-onboard-5.json +484 -909
  440. package/src/assets/samples/vpn-onboard-6.json +333 -608
  441. package/src/attributes-editor/Field.tsx +575 -0
  442. package/src/attributes-editor/FieldInfoTooltip.tsx +49 -0
  443. package/src/attributes-editor/LayoutPreviewPicker.tsx +202 -0
  444. package/src/attributes-editor/SizeField.tsx +184 -0
  445. package/src/attributes-editor/SpecialCategorySection.tsx +292 -0
  446. package/src/attributes-editor/types.ts +30 -0
  447. package/src/build-components/BIcon/BIcon.tsx +56 -0
  448. package/src/build-components/BIcon/BIconProps.generated.ts +74 -0
  449. package/src/build-components/BIcon/pattern.json +50 -0
  450. package/src/build-components/BackgroundImage/BackgroundImage.tsx +79 -0
  451. package/src/build-components/BackgroundImage/BackgroundImageProps.generated.ts +66 -0
  452. package/src/build-components/BackgroundImage/pattern.json +54 -0
  453. package/src/build-components/Button/Button.tsx +73 -2
  454. package/src/build-components/Button/ButtonProps.generated.ts +50 -1
  455. package/src/build-components/Button/pattern.json +41 -5
  456. package/src/build-components/Carousel/Carousel.tsx +39 -2
  457. package/src/build-components/Carousel/CarouselProps.generated.ts +56 -1
  458. package/src/build-components/Carousel/pattern.json +26 -5
  459. package/src/build-components/CarouselButtons/CarouselButtons.tsx +23 -2
  460. package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +49 -0
  461. package/src/build-components/CarouselButtons/pattern.json +32 -4
  462. package/src/build-components/CarouselDots/CarouselDots.tsx +51 -8
  463. package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +49 -0
  464. package/src/build-components/CarouselDots/pattern.json +19 -3
  465. package/src/build-components/CarouselItem/CarouselItem.tsx +24 -3
  466. package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +52 -1
  467. package/src/build-components/CarouselItem/pattern.json +11 -3
  468. package/src/build-components/CarouselProvider/CarouselProvider.tsx +66 -5
  469. package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +52 -1
  470. package/src/build-components/CarouselProvider/pattern.json +8 -1
  471. package/src/build-components/Image/Image.tsx +33 -6
  472. package/src/build-components/Image/ImageProps.generated.ts +49 -3
  473. package/src/build-components/Image/pattern.json +48 -7
  474. package/src/build-components/Main/Main.tsx +61 -0
  475. package/src/build-components/Main/MainProps.generated.ts +64 -0
  476. package/src/build-components/Main/pattern.json +38 -0
  477. package/src/build-components/Onboard/Onboard.tsx +8 -1
  478. package/src/build-components/Onboard/OnboardProps.generated.ts +52 -1
  479. package/src/build-components/Onboard/pattern.json +18 -2
  480. package/src/build-components/OnboardButton/OnboardButton.tsx +53 -6
  481. package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +49 -1
  482. package/src/build-components/OnboardButton/pattern.json +74 -7
  483. package/src/build-components/OnboardButtons/OnboardButtons.tsx +35 -11
  484. package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +49 -0
  485. package/src/build-components/OnboardButtons/pattern.json +73 -6
  486. package/src/build-components/OnboardDot/OnboardDot.tsx +135 -4
  487. package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +36 -3
  488. package/src/build-components/OnboardDot/pattern.json +45 -7
  489. package/src/build-components/OnboardFooter/OnboardFooter.tsx +42 -11
  490. package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +21 -18
  491. package/src/build-components/OnboardFooter/pattern.json +64 -5
  492. package/src/build-components/OnboardImage/OnboardImage.tsx +51 -5
  493. package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +49 -3
  494. package/src/build-components/OnboardImage/pattern.json +26 -2
  495. package/src/build-components/OnboardItem/OnboardItem.tsx +20 -12
  496. package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +48 -3
  497. package/src/build-components/OnboardItem/pattern.json +54 -9
  498. package/src/build-components/OnboardProvider/OnboardProvider.tsx +50 -21
  499. package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +52 -6
  500. package/src/build-components/OnboardProvider/pattern.json +56 -12
  501. package/src/build-components/OnboardSubtitle/OnboardSubtitle.tsx +2 -0
  502. package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +21 -18
  503. package/src/build-components/OnboardSubtitle/pattern.json +15 -5
  504. package/src/build-components/OnboardTitle/OnboardTitle.tsx +2 -0
  505. package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +21 -18
  506. package/src/build-components/OnboardTitle/pattern.json +16 -6
  507. package/src/build-components/PaywallBackground/PaywallBackground.tsx +47 -0
  508. package/src/build-components/PaywallBackground/PaywallBackgroundProps.generated.ts +63 -0
  509. package/src/build-components/PaywallBackground/pattern.json +17 -0
  510. package/src/build-components/PaywallCloseButton/PaywallCloseButton.tsx +67 -0
  511. package/src/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.ts +74 -0
  512. package/src/build-components/PaywallCloseButton/pattern.json +26 -0
  513. package/src/build-components/PaywallOptions/PaywallOptionButton.tsx +63 -0
  514. package/src/build-components/PaywallOptions/PaywallOptions.tsx +93 -0
  515. package/src/build-components/PaywallOptions/PaywallOptionsProps.generated.ts +63 -0
  516. package/src/build-components/PaywallOptions/pattern.json +25 -0
  517. package/src/build-components/PaywallOptions/usePaywallOptionParamsFactory.ts +42 -0
  518. package/src/build-components/PaywallProvider/PaywallContext.ts +25 -0
  519. package/src/build-components/PaywallProvider/PaywallProvider.tsx +175 -0
  520. package/src/build-components/PaywallProvider/PaywallProviderProps.generated.ts +63 -0
  521. package/src/build-components/PaywallProvider/pattern.json +27 -0
  522. package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButton.tsx +67 -0
  523. package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.ts +78 -0
  524. package/src/build-components/PaywallSubscribeButton/pattern.json +30 -0
  525. package/src/build-components/RadioButton/RadioButton.tsx +123 -0
  526. package/src/build-components/RadioButton/RadioButtonProps.generated.ts +66 -0
  527. package/src/build-components/RadioButton/pattern.json +43 -0
  528. package/src/build-components/RenderNode.generated.tsx +134 -35
  529. package/src/build-components/Text/Text.tsx +135 -8
  530. package/src/build-components/Text/TextProps.generated.ts +21 -18
  531. package/src/build-components/Text/pattern.json +66 -19
  532. package/src/build-components/View/View.tsx +33 -6
  533. package/src/build-components/View/ViewProps.generated.ts +16 -4
  534. package/src/build-components/View/pattern.json +352 -25
  535. package/src/build-components/index.ts +54 -9
  536. package/src/build-components/other.tsx +15 -0
  537. package/src/build-components/patterns.generated.ts +10505 -280
  538. package/src/build-components/useNode.ts +22 -3
  539. package/src/components/AttributesEditorPanel.tsx +21 -64
  540. package/src/components/BottomBar.tsx +242 -0
  541. package/src/components/Breadcrumb.tsx +39 -5
  542. package/src/components/Builder.tsx +389 -128
  543. package/src/components/BuilderButton.tsx +161 -0
  544. package/src/components/BuilderProvider.tsx +65 -0
  545. package/src/components/Checkbox.tsx +81 -0
  546. package/src/components/DeviceButton.tsx +46 -0
  547. package/src/components/DeviceNavigationBar.tsx +200 -0
  548. package/src/components/DeviceStatusBar.tsx +85 -0
  549. package/src/components/EditorHeader.tsx +328 -103
  550. package/src/components/Icon.generated.tsx +540 -0
  551. package/src/components/JsonTextEditor.tsx +185 -0
  552. package/src/components/LoadingComponent.tsx +10 -0
  553. package/src/components/LocalizationParamsProvider.tsx +22 -0
  554. package/src/components/ParamsProvider.tsx +45 -0
  555. package/src/components/RenderErrorBoundary.tsx +200 -0
  556. package/src/hooks/useLocalizationParams.ts +5 -0
  557. package/src/hooks/useLocalize.ts +23 -0
  558. package/src/hooks/useParams.ts +12 -0
  559. package/src/hooks/useProjectFonts.ts +130 -0
  560. package/src/hooks/useSafeAreaViewStyle.ts +67 -0
  561. package/src/hooks/useSyncHtmlThemeClass.ts +19 -0
  562. package/src/index.native.ts +82 -0
  563. package/src/index.ts +41 -2
  564. package/src/migrations/migratePipe.ts +59 -0
  565. package/src/migrations/migrations/1.1.0_normalize_style_attributes.ts +80 -0
  566. package/src/migrations/semver.ts +24 -0
  567. package/src/migrations/types.ts +9 -0
  568. package/src/mockOS/components/MockLaunchScreenComponent.tsx +43 -0
  569. package/src/mockOS/components/MockOSRouter.tsx +124 -0
  570. package/src/mockOS/components/PermissionModal.tsx +271 -0
  571. package/src/mockOS/components/SubscriptionModal.tsx +400 -0
  572. package/src/mockOS/context/MockOSContext.tsx +201 -0
  573. package/src/mockOS/context/MockOSContextBase.ts +36 -0
  574. package/src/mockOS/hooks/useMockIap.ts +11 -0
  575. package/src/mockOS/hooks/useMockNavigation.ts +11 -0
  576. package/src/mockOS/hooks/useMockPermission.ts +11 -0
  577. package/src/mockOS/index.ts +34 -0
  578. package/src/mockOS/managers/mockOSIapManager.ts +10 -0
  579. package/src/mockOS/managers/mockPermissionManager.ts +50 -0
  580. package/src/mockOS/managers/navigationManager.ts +86 -0
  581. package/src/mockOS/managers/subscriptionManager.ts +36 -0
  582. package/src/modals/AddComponentModal.tsx +314 -0
  583. package/src/modals/BenefitEditModal.tsx +160 -0
  584. package/src/modals/BenefitPresetsModal.tsx +166 -0
  585. package/src/modals/ColorModal.tsx +503 -0
  586. package/src/modals/DeviceSelectorModal.tsx +57 -0
  587. package/src/modals/IconPickerModal.tsx +109 -0
  588. package/src/modals/LocalicationModal.tsx +53 -0
  589. package/src/modals/MockableFeatureModal.tsx +292 -0
  590. package/src/modals/Modal.tsx +64 -0
  591. package/src/modals/ProductEditModal.tsx +215 -0
  592. package/src/modals/ProductPresetsModal.tsx +151 -0
  593. package/src/modals/ScreenColorsModal.tsx +121 -0
  594. package/src/modals/index.ts +12 -0
  595. package/src/pages/ProjectDebug.tsx +331 -0
  596. package/src/pages/ProjectMigrationPage.tsx +92 -0
  597. package/src/pages/ProjectPage.tsx +478 -70
  598. package/src/pages/ProjectValidationPage.tsx +54 -0
  599. package/src/pages/tabs/BuilderPanel.tsx +37 -0
  600. package/src/pages/tabs/SideTool.tsx +253 -0
  601. package/src/paywall/hooks/index.ts +5 -0
  602. package/src/paywall/hooks/useCalculateLocalizedPrice.ts +6 -0
  603. package/src/paywall/hooks/useCarouselOptionsSeperator.ts +8 -0
  604. package/src/paywall/hooks/useCloseStatusPaywall.ts +6 -0
  605. package/src/paywall/hooks/useDiscountRate.ts +6 -0
  606. package/src/paywall/hooks/usePaywallCounter.ts +6 -0
  607. package/src/paywall/types/benefits.ts +44 -0
  608. package/src/paywall/types/paywall-types.ts +51 -0
  609. package/src/size-matters/index.ts +27 -5
  610. package/src/store.ts +222 -16
  611. package/src/styles/base/_global.scss +469 -0
  612. package/src/styles/components/_attributes-editor.scss +310 -0
  613. package/src/styles/components/_bottom-bar.scss +104 -0
  614. package/src/styles/components/_editor-shell.scss +331 -0
  615. package/src/styles/components/_mockos-router.scss +143 -0
  616. package/src/styles/components/_ui-components.scss +188 -0
  617. package/src/styles/foundation/_colors.scss +95 -0
  618. package/src/styles/foundation/_mixins.scss +25 -0
  619. package/src/styles/{_reset.scss → foundation/_reset.scss} +5 -2
  620. package/src/styles/foundation/_sizes.scss +41 -0
  621. package/src/styles/foundation/_typography.scss +4 -0
  622. package/src/styles/foundation/_variables.scss +3 -0
  623. package/src/styles/index.scss +29 -136
  624. package/src/styles/layout/_builder.scss +185 -0
  625. package/src/styles/layout/_pages.scss +3 -0
  626. package/src/styles/layout/_project-validation.scss +214 -0
  627. package/src/styles/modals/_add-component.scss +124 -0
  628. package/src/styles/modals/_benefit-edit-modal.scss +17 -0
  629. package/src/styles/modals/_benefit-presets-modal.scss +79 -0
  630. package/src/styles/modals/_color-modal.scss +190 -0
  631. package/src/styles/modals/_device-selector.scss +18 -0
  632. package/src/styles/modals/_localication-modal.scss +68 -0
  633. package/src/styles/modals/_mockable-feature-modal.scss +15 -0
  634. package/src/styles/modals/_modal-shell.scss +48 -0
  635. package/src/styles/modals/_product-edit-modal.scss +23 -0
  636. package/src/styles/modals/_product-presets-modal.scss +81 -0
  637. package/src/styles/utilities/_carousel.scss +126 -0
  638. package/src/types/Device.ts +5 -0
  639. package/src/types/Fonts.ts +16 -0
  640. package/src/types/Icons.ts +244 -0
  641. package/src/types/Project.ts +32 -0
  642. package/src/types/images.d.ts +8 -0
  643. package/src/types/jest-globals.d.ts +13 -0
  644. package/src/utils/__special_exceptions.ts +88 -0
  645. package/src/utils/analyseNode.ts +79 -62
  646. package/src/utils/analyseNodeByPatterns.ts +481 -0
  647. package/src/utils/analyseNodeStructural.ts +52 -0
  648. package/src/utils/extractImageStyle.ts +3 -6
  649. package/src/utils/extractTextStyle.ts +130 -81
  650. package/src/utils/extractViewStyle.ts +118 -17
  651. package/src/utils/findNodeByKeyNested.ts +32 -0
  652. package/src/utils/fontWeight.ts +29 -0
  653. package/src/utils/fontsDebug.ts +16 -0
  654. package/src/utils/getImage.ts +76 -0
  655. package/src/utils/isCarousel.ts +21 -5
  656. package/src/utils/loadFontFamily.ts +318 -0
  657. package/src/utils/logger.ts +76 -0
  658. package/src/utils/nodeGuards.ts +26 -0
  659. package/src/utils/nodeTree.ts +99 -0
  660. package/src/utils/novaToJson.ts +23 -10
  661. package/src/utils/parseColor.ts +43 -0
  662. package/src/utils/pasteNode.ts +172 -0
  663. package/src/utils/patterns.ts +100 -5
  664. package/src/utils/replaceLocalizationParams.ts +15 -0
  665. package/src/utils/selection.ts +24 -0
  666. package/src/utils/useLogRender.ts +13 -0
  667. package/src/utils/useMergedStyle.ts +16 -0
  668. package/dist/build-components/OnboardDot/OnboardExpandingDotProps.generated.d.ts +0 -10
  669. package/dist/pages/tabs/BuilderTab.d.ts +0 -9
  670. package/dist/pages/tabs/DebugTab.d.ts +0 -7
  671. package/dist/pages/tabs/PreviewTab.d.ts +0 -3
  672. package/src/build-components/OnboardDot/OnboardExpandingDotProps.generated.ts +0 -20
  673. package/src/pages/tabs/BuilderTab.tsx +0 -31
  674. package/src/pages/tabs/DebugTab.tsx +0 -21
  675. package/src/pages/tabs/PreviewTab.tsx +0 -192
  676. package/src/styles/_mixins.scss +0 -21
  677. package/src/styles/_variables.scss +0 -27
  678. package/src/styles/builder.scss +0 -60
  679. package/src/styles/components.scss +0 -88
  680. package/src/styles/editor.scss +0 -174
  681. package/src/styles/global.scss +0 -200
  682. package/src/styles/pages.scss +0 -2
@@ -1,347 +1,947 @@
1
- import React from 'react';
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
2
  import { Node, NodeData, NodeDefaultAttribute } from './types/Node';
3
+ import type { ProjectColorTokenMap, ProjectColors } from './types/Project';
3
4
  import { isNodeString } from './utils/analyseNode';
5
+ import { useLogRender } from './utils/useLogRender';
4
6
  import {
7
+ getAttributeMeta,
5
8
  getAttributeSchema,
6
- getTypeSchema,
7
- getArrayItemType,
8
- isPrimitiveType,
9
+ getPatternByType,
9
10
  } from './utils/patterns';
11
+ import { useRenderStore } from './store';
12
+ import { Field } from './attributes-editor/Field';
13
+ import { SpecialCategorySection } from './attributes-editor/SpecialCategorySection';
14
+ import { toPreferredScale } from './attributes-editor/SizeField';
15
+ import {
16
+ LayoutContext,
17
+ SchemaEntry,
18
+ isBooleanFieldType,
19
+ } from './attributes-editor/types';
20
+ import { FieldInfoTooltip } from './attributes-editor/FieldInfoTooltip';
21
+ import type { ViewPropsGenerated } from './build-components/View/ViewProps.generated';
22
+ import useNode from './build-components/useNode';
23
+ import { MockableFeatureModal } from './modals';
24
+ import { Icon } from './components/Icon.generated';
25
+ import { IconPickerModal } from './modals/IconPickerModal';
26
+ import type { IconsType } from './types/Icons';
27
+ import Modal from './modals/Modal';
28
+ import { loadFontFamily } from './utils/loadFontFamily';
29
+ import { fontsDebug } from './utils/fontsDebug';
10
30
 
11
31
  type AttributesEditorProps = {
12
32
  node: Node;
13
33
  onChange: (next: Node) => void;
34
+ projectColors?: ProjectColors;
14
35
  };
15
36
 
16
- function Field({
17
- name,
18
- type,
19
- value,
20
- onChange,
21
- componentType,
22
- }: {
23
- name: string;
24
- type: string | string[];
25
- value: any;
26
- onChange: (v: any) => void;
27
- // The current node's component type is needed to resolve custom type schemas
28
- componentType?: string;
29
- }) {
30
- // Render enum selector
31
- if (Array.isArray(type)) {
32
- return (
33
- <select
34
- value={value ?? ''}
35
- onChange={(e) => onChange(e.target.value)}
36
- className="input"
37
- >
38
- <option value="">(none)</option>
39
- {type.map((opt) => (
40
- <option key={opt} value={opt}>
41
- {opt}
42
- </option>
43
- ))}
44
- </select>
45
- );
37
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
38
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
39
+ }
40
+
41
+ type TabId = 'style' | 'container' | 'other';
42
+
43
+ type TabConfig = {
44
+ id: TabId;
45
+ label: string;
46
+ entries: SchemaEntry[];
47
+ };
48
+
49
+ type SpecialSection = {
50
+ key: string;
51
+ entries: SchemaEntry[];
52
+ meta?: {
53
+ label?: string;
54
+ description?: string;
55
+ sort?: number;
56
+ };
57
+ };
58
+
59
+ type TabContentInfo = Record<
60
+ TabId,
61
+ {
62
+ baseCount: number;
63
+ specialCount: number;
46
64
  }
65
+ >;
66
+
67
+ export function AttributesEditor({
68
+ node,
69
+ onChange,
70
+ projectColors,
71
+ }: AttributesEditorProps) {
72
+ useLogRender('AttributesEditor');
73
+ if (!node || isNodeString(node)) return null;
74
+
75
+ const baseData = node as NodeData<NodeDefaultAttribute>;
76
+ const data = useNode(baseData);
77
+ const {
78
+ appConfig,
79
+ fonts: projectFonts,
80
+ loadedFonts,
81
+ markFontLoaded,
82
+ addError,
83
+ } = useRenderStore((state) => ({
84
+ appConfig: state.appConfig,
85
+ fonts: state.fonts,
86
+ loadedFonts: state.loadedFonts,
87
+ markFontLoaded: state.markFontLoaded,
88
+ addError: state.addError,
89
+ }));
90
+ const [activeFontField, setActiveFontField] = useState<string | null>(null);
91
+ const [isFontLoading, setIsFontLoading] = useState(false);
92
+ const [fontLoadError, setFontLoadError] = useState<string | null>(null);
93
+ const projectColorsForPicker = useMemo<ProjectColors | undefined>(() => {
94
+ if (projectColors) {
95
+ return projectColors;
96
+ }
97
+
98
+ const addColor = (collection: Set<string>, value?: string) => {
99
+ if (typeof value !== 'string') return;
100
+ const trimmed = value.trim();
101
+ if (!trimmed) return;
102
+ collection.add(trimmed);
103
+ };
104
+
105
+ const fallback = new Set<string>();
106
+ const styles = [
107
+ appConfig?.screenStyle?.light,
108
+ appConfig?.screenStyle?.dark,
109
+ ].filter(Boolean) as Array<{
110
+ backgroundColor?: string;
111
+ color?: string;
112
+ seperatorColor?: string;
113
+ }>;
114
+ styles.forEach((style) => {
115
+ ['backgroundColor', 'color', 'seperatorColor'].forEach((key) => {
116
+ const value = style?.[key as keyof typeof style];
117
+ addColor(fallback, value);
118
+ });
119
+ });
120
+
121
+ if (fallback.size === 0) {
122
+ return undefined;
123
+ }
124
+
125
+ const fallbackRecord: ProjectColorTokenMap = {};
126
+ Array.from(fallback).forEach((color, index) => {
127
+ fallbackRecord[`FALLBACK_${index + 1}`] = color;
128
+ });
129
+
130
+ return { STATIC_COLORS: fallbackRecord };
131
+ }, [appConfig, projectColors]);
132
+
133
+ const schema = getAttributeSchema(data?.type) ?? {};
134
+ const attributeMeta = getAttributeMeta(data?.type);
135
+ const attributes = (data?.attributes ?? {}) as NodeDefaultAttribute;
136
+ const styleAttributeKeys = useMemo(() => {
137
+ // schemaVersion=2 stores style-like props under attributes.style; keep legacy support for flat attrs.
138
+ const viewSchema = getAttributeSchema('View') ?? {};
139
+ const textSchema = getAttributeSchema('Text') ?? {};
140
+ return new Set([...Object.keys(viewSchema), ...Object.keys(textSchema)]);
141
+ }, []);
142
+ const getAttributeValue = useCallback(
143
+ (name: string) => {
144
+ const direct = (attributes as Record<string, unknown>)?.[name];
145
+ if (direct !== undefined && direct !== null) return direct;
146
+ const styleBag = (attributes as any)?.style as
147
+ | Record<string, unknown>
148
+ | undefined;
149
+ return styleBag?.[name];
150
+ },
151
+ [attributes],
152
+ );
153
+ const viewAttributes = useMemo<
154
+ Partial<ViewPropsGenerated['attributes']> | undefined
155
+ >(
156
+ () =>
157
+ data?.type === 'View'
158
+ ? (attributes as ViewPropsGenerated['attributes'])
159
+ : undefined,
160
+ [attributes, data?.type],
161
+ );
162
+
163
+ const layoutContext = useMemo<LayoutContext>(
164
+ () => ({
165
+ flexDirection:
166
+ attributes?.flexDirection as LayoutContext['flexDirection'],
167
+ alignItems: attributes?.alignItems as LayoutContext['alignItems'],
168
+ justifyContent:
169
+ attributes?.justifyContent as LayoutContext['justifyContent'],
170
+ }),
171
+ [
172
+ attributes?.flexDirection,
173
+ attributes?.alignItems,
174
+ attributes?.justifyContent,
175
+ ],
176
+ );
177
+
178
+ const patternForType = useMemo(
179
+ () => (data?.type ? getPatternByType(data.type) : undefined),
180
+ [data?.type],
181
+ );
182
+
183
+ const componentMeta = patternForType?.meta;
184
+
185
+ const entries = useMemo(
186
+ () =>
187
+ Object.entries(schema).filter(([, type]) =>
188
+ typeof type === 'string' ? type !== 'never' : true,
189
+ ),
190
+ [schema],
191
+ );
192
+
193
+ const visibleEntries = useMemo(() => {
194
+ if (!componentMeta?.hideAllAttributes) return entries;
195
+ return entries.filter(([name]) => {
196
+ const meta = attributeMeta?.[name];
197
+ return meta?.forceVisible === true || meta?.override === true;
198
+ });
199
+ }, [attributeMeta, componentMeta?.hideAllAttributes, entries]);
200
+
201
+ const componentTitle = componentMeta?.label ?? data?.type ?? 'Component';
202
+ const componentDescription = componentMeta?.description;
203
+
204
+ const mockableFeatureKeys = useMemo(() => {
205
+ const mockable = componentMeta?.mockableFeatures;
206
+ if (!mockable || typeof mockable !== 'object') return [];
207
+ return Object.entries(mockable)
208
+ .filter(([, enabled]) => enabled === true)
209
+ .map(([key]) => key)
210
+ .filter((key) => typeof key === 'string' && key.trim().length > 0)
211
+ .sort((a, b) => a.localeCompare(b));
212
+ }, [componentMeta?.mockableFeatures]);
213
+
214
+ const [activeMockableFeature, setActiveMockableFeature] = useState<
215
+ string | null
216
+ >(null);
217
+
218
+ const [activeIconField, setActiveIconField] = useState<string | null>(null);
219
+
220
+ const headerSection = (
221
+ <div className="attributes-editor__component-meta">
222
+ <p className="attributes-editor__component-title">{componentTitle}</p>
223
+ {componentDescription ? (
224
+ <p className="attributes-editor__component-description">
225
+ {componentDescription}
226
+ </p>
227
+ ) : null}
228
+ </div>
229
+ );
230
+
231
+ const mockableSection =
232
+ mockableFeatureKeys.length > 0 ? (
233
+ <section className="attributes-editor__mockable">
234
+ <p className="attributes-editor__mockable-title">Mockable</p>
235
+ <table className="attributes-editor__mockable-table">
236
+ <tbody>
237
+ {mockableFeatureKeys.map((key) => (
238
+ <tr key={key} className="attributes-editor__mockable-row">
239
+ <td className="attributes-editor__mockable-name">{key}</td>
240
+ <td className="attributes-editor__mockable-action">
241
+ <button
242
+ type="button"
243
+ className="editor-button"
244
+ onClick={() => setActiveMockableFeature(key)}
245
+ >
246
+ {key}
247
+ </button>
248
+ </td>
249
+ </tr>
250
+ ))}
251
+ </tbody>
252
+ </table>
253
+ </section>
254
+ ) : null;
47
255
 
48
- // Arrays: detect X[] (including string[]/number[]/boolean[]/CustomType[])
49
- const itemType = typeof type === 'string' ? getArrayItemType(type) : null;
50
- if (itemType) {
51
- const arr: any[] = Array.isArray(value) ? value : [];
256
+ const { grouped, specialGroups } = useMemo(() => {
257
+ const groups: Record<TabId, SchemaEntry[]> = {
258
+ style: [],
259
+ container: [],
260
+ other: [],
261
+ };
262
+ const specials: Record<string, SchemaEntry[]> = {};
52
263
 
53
- // Primitive arrays with add/remove controls
54
- if (isPrimitiveType(itemType)) {
264
+ const getSortOrder = (name: string) =>
265
+ attributeMeta?.[name]?.sort ?? Number.MAX_SAFE_INTEGER;
266
+ const compareEntries = (a: SchemaEntry, b: SchemaEntry) => {
267
+ const order = getSortOrder(a.name) - getSortOrder(b.name);
268
+ return order !== 0 ? order : a.name.localeCompare(b.name);
269
+ };
270
+
271
+ visibleEntries.forEach(([name, type]) => {
272
+ const meta = attributeMeta?.[name];
273
+ const specialCategory = meta?.specialCategory;
274
+ if (typeof specialCategory === 'string') {
275
+ const normalizedSpecialCategory = specialCategory.trim();
276
+ if (normalizedSpecialCategory) {
277
+ if (!specials[normalizedSpecialCategory]) {
278
+ specials[normalizedSpecialCategory] = [];
279
+ }
280
+ specials[normalizedSpecialCategory].push({ name, type });
281
+ return;
282
+ }
283
+ }
284
+
285
+ const metaCategory = meta?.category;
286
+ const normalized =
287
+ metaCategory === 'style'
288
+ ? 'style'
289
+ : metaCategory === 'container'
290
+ ? 'container'
291
+ : 'other';
292
+ groups[normalized].push({ name, type });
293
+ });
294
+
295
+ Object.values(groups).forEach((list) => {
296
+ list.sort(compareEntries);
297
+ });
298
+
299
+ Object.values(specials).forEach((list) => {
300
+ list.sort(compareEntries);
301
+ });
302
+
303
+ return { grouped: groups, specialGroups: specials };
304
+ }, [attributeMeta, visibleEntries]);
305
+
306
+ const specialSectionsByTab = useMemo<Record<TabId, SpecialSection[]>>(() => {
307
+ const buckets: Record<TabId, SpecialSection[]> = {
308
+ style: [],
309
+ container: [],
310
+ other: [],
311
+ };
312
+
313
+ const compareSections = (a: SpecialSection, b: SpecialSection) => {
314
+ const aSort = a.meta?.sort ?? Number.MAX_SAFE_INTEGER;
315
+ const bSort = b.meta?.sort ?? Number.MAX_SAFE_INTEGER;
316
+ const order = aSort - bSort;
317
+ return order !== 0 ? order : a.key.localeCompare(b.key);
318
+ };
319
+
320
+ Object.entries(specialGroups).forEach(([categoryKey, categoryEntries]) => {
321
+ if (!categoryEntries.length) return;
322
+ const metaForCategory = componentMeta?.specialCategories?.[categoryKey];
323
+ const targetCategory = (
324
+ metaForCategory?.category === 'style'
325
+ ? 'style'
326
+ : metaForCategory?.category === 'container'
327
+ ? 'container'
328
+ : 'other'
329
+ ) as TabId;
330
+ buckets[targetCategory].push({
331
+ key: categoryKey,
332
+ entries: categoryEntries,
333
+ meta: metaForCategory,
334
+ });
335
+ });
336
+
337
+ (Object.keys(buckets) as TabId[]).forEach((tabId) => {
338
+ buckets[tabId].sort(compareSections);
339
+ });
340
+
341
+ return buckets;
342
+ }, [componentMeta?.specialCategories, specialGroups]);
343
+
344
+ const handleAttributeChange = useCallback(
345
+ (name: string, val: unknown) => {
346
+ const prevAttrs =
347
+ ((baseData?.attributes ?? {}) as Record<string, unknown>) ?? {};
348
+ const isStyleKey = styleAttributeKeys.has(name);
349
+ const nextAttrs: Record<string, unknown> = { ...prevAttrs };
350
+ if (isStyleKey) {
351
+ const prevStyle = (
352
+ isPlainObject(nextAttrs.style) ? nextAttrs.style : {}
353
+ ) as Record<string, unknown>;
354
+ nextAttrs.style = { ...prevStyle, [name]: val };
355
+ // Normalize away legacy flat style keys once edited.
356
+ if (name in nextAttrs) delete nextAttrs[name];
357
+ } else {
358
+ nextAttrs[name] = val;
359
+ }
360
+ const next: NodeData<NodeDefaultAttribute> = {
361
+ ...baseData,
362
+ attributes: nextAttrs as NodeDefaultAttribute,
363
+ };
364
+ onChange(next);
365
+ },
366
+ [baseData, onChange, styleAttributeKeys],
367
+ );
368
+
369
+ const renderIconTypeField = useCallback(
370
+ (name: string, currentValue: unknown) => {
371
+ const normalized =
372
+ typeof currentValue === 'string'
373
+ ? (currentValue as IconsType)
374
+ : undefined;
55
375
  return (
56
- <div style={{ display: 'grid', gap: 8 }}>
57
- {arr.map((item, idx) => (
58
- <div
59
- key={idx}
60
- style={{ display: 'flex', gap: 8, alignItems: 'center' }}
61
- >
62
- {itemType === 'number' ? (
63
- <input
64
- type="number"
65
- value={item ?? ''}
66
- onChange={(e) => {
67
- const next = [...arr];
68
- next[idx] =
69
- e.target.value === ''
70
- ? undefined
71
- : Number(e.target.value);
72
- onChange(next);
73
- }}
74
- className="input"
75
- style={{ flex: 1 }}
76
- />
77
- ) : itemType === 'boolean' ? (
78
- <input
79
- type="checkbox"
80
- checked={Boolean(item)}
81
- onChange={(e) => {
82
- const next = [...arr];
83
- next[idx] = e.target.checked;
84
- onChange(next);
85
- }}
86
- />
87
- ) : (
88
- <input
89
- type="text"
90
- value={item ?? ''}
91
- onChange={(e) => {
92
- const next = [...arr];
93
- next[idx] =
94
- e.target.value === '' ? undefined : e.target.value;
95
- onChange(next);
96
- }}
97
- className="input"
98
- style={{ flex: 1 }}
99
- />
100
- )}
101
- <button
102
- type="button"
103
- onClick={() => {
104
- const next = arr.filter((_, i) => i !== idx);
105
- onChange(next.length ? next : undefined);
106
- }}
107
- >
108
- remove
109
- </button>
110
- </div>
111
- ))}
112
- <div>
113
- <button
114
- type="button"
115
- onClick={() => {
116
- const next = [
117
- ...arr,
118
- itemType === 'boolean'
119
- ? false
120
- : itemType === 'number'
121
- ? 0
122
- : '',
123
- ];
124
- onChange(next);
376
+ <>
377
+ <button
378
+ type="button"
379
+ onClick={() => setActiveIconField(name)}
380
+ style={{
381
+ width: '100%',
382
+ display: 'flex',
383
+ alignItems: 'center',
384
+ justifyContent: 'space-between',
385
+ gap: 8,
386
+ borderRadius: 6,
387
+ border: '1px solid #ddd',
388
+ padding: '8px 10px',
389
+ background: 'hsl(var(--card, var(--rb-card, 0 0% 100%)))',
390
+ cursor: 'pointer',
391
+ }}
392
+ >
393
+ <span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
394
+ {normalized ? <Icon iconType={normalized} size={18} /> : null}
395
+ <span style={{ fontWeight: 500 }}>
396
+ {normalized ?? 'Select icon'}
397
+ </span>
398
+ </span>
399
+ <span style={{ fontSize: 12, color: '#666' }}>Open</span>
400
+ </button>
401
+
402
+ {activeIconField === name ? (
403
+ <IconPickerModal
404
+ value={normalized}
405
+ onSelect={(iconName) => {
406
+ handleAttributeChange(name, iconName);
407
+ setActiveIconField(null);
125
408
  }}
126
- >
127
- add
128
- </button>
129
- </div>
130
- </div>
409
+ onClose={() => setActiveIconField(null)}
410
+ onClear={() => {
411
+ handleAttributeChange(name, undefined);
412
+ setActiveIconField(null);
413
+ }}
414
+ />
415
+ ) : null}
416
+ </>
131
417
  );
132
- }
418
+ },
419
+ [activeIconField, handleAttributeChange],
420
+ );
133
421
 
134
- // Object arrays with nested editors
135
- const schema = getTypeSchema(componentType, itemType) ?? {};
136
- return (
137
- <div style={{ display: 'grid', gap: 8 }}>
138
- {arr.map((item, idx) => {
139
- const obj = (item ?? {}) as Record<string, unknown>;
140
- return (
141
- <div
142
- key={idx}
143
- style={{ border: '1px solid #ddd', borderRadius: 6, padding: 8 }}
144
- >
145
- <div
146
- style={{
147
- display: 'grid',
148
- gridTemplateColumns: '1fr 1fr',
149
- gap: 8,
150
- }}
151
- >
152
- {Object.entries(schema).map(([fieldName, fieldType]) => (
153
- <React.Fragment key={fieldName}>
154
- <div style={{ alignSelf: 'center' }}>{fieldName}</div>
155
- <Field
156
- name={fieldName}
157
- type={fieldType}
158
- value={obj?.[fieldName as keyof typeof obj]}
159
- onChange={(val) => {
160
- const next = [...arr];
161
- const nextObj = { ...(obj ?? {}), [fieldName]: val };
162
- next[idx] = nextObj;
163
- onChange(next);
164
- }}
165
- componentType={componentType}
166
- />
167
- </React.Fragment>
168
- ))}
169
- </div>
170
- <div style={{ marginTop: 8 }}>
171
- <button
172
- type="button"
173
- onClick={() => {
174
- const next = arr.filter((_, i) => i !== idx);
175
- onChange(next.length ? next : undefined);
176
- }}
177
- >
178
- remove
179
- </button>
180
- </div>
181
- </div>
182
- );
183
- })}
184
- <div>
422
+ const renderFontFamilyField = useCallback(
423
+ (name: string, currentValue: unknown) => {
424
+ const normalized =
425
+ typeof currentValue === 'string' && currentValue.trim().length > 0
426
+ ? currentValue.trim()
427
+ : undefined;
428
+ const fontsList = Array.isArray(projectFonts) ? projectFonts : [];
429
+ const loaded = Array.isArray(loadedFonts) ? loadedFonts : [];
430
+ const isOpen = activeFontField === name;
431
+ return (
432
+ <>
185
433
  <button
186
434
  type="button"
187
435
  onClick={() => {
188
- const empty: Record<string, unknown> = {};
189
- const next = [...arr, empty];
190
- onChange(next);
436
+ setFontLoadError(null);
437
+ setActiveFontField(name);
438
+ }}
439
+ style={{
440
+ width: '100%',
441
+ display: 'flex',
442
+ alignItems: 'center',
443
+ justifyContent: 'space-between',
444
+ gap: 8,
445
+ borderRadius: 6,
446
+ border: '1px solid #ddd',
447
+ padding: '8px 10px',
448
+ background: 'hsl(var(--card, var(--rb-card, 0 0% 100%)))',
449
+ cursor: 'pointer',
191
450
  }}
192
451
  >
193
- add
452
+ <span style={{ flex: 1, textAlign: 'left', fontWeight: 500 }}>
453
+ {normalized ?? 'Select font'}
454
+ </span>
455
+ <span style={{ fontSize: 12, color: '#666' }}>Open</span>
194
456
  </button>
195
- </div>
196
- </div>
197
- );
198
- }
199
457
 
200
- // Non-array complex object types defined under pattern `types`
201
- if (typeof type === 'string' && !isPrimitiveType(type)) {
202
- const schema = getTypeSchema(componentType, type);
203
- if (schema) {
204
- const obj = (value ?? {}) as Record<string, unknown>;
458
+ {isOpen ? (
459
+ <Modal
460
+ ariaLabelledBy="font-family-picker-title"
461
+ onClose={() => {
462
+ if (isFontLoading) return;
463
+ setActiveFontField(null);
464
+ }}
465
+ closeOnOverlayClick={!isFontLoading}
466
+ closeOnEsc={!isFontLoading}
467
+ >
468
+ <div style={{ display: 'grid', gap: 12 }}>
469
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
470
+ <p
471
+ id="font-family-picker-title"
472
+ style={{ margin: 0, fontWeight: 700 }}
473
+ >
474
+ Select Font
475
+ </p>
476
+ <div style={{ flex: 1 }} />
477
+ <button
478
+ type="button"
479
+ className="editor-button"
480
+ onClick={() => {
481
+ handleAttributeChange(name, undefined);
482
+ setFontLoadError(null);
483
+ setActiveFontField(null);
484
+ }}
485
+ disabled={isFontLoading}
486
+ >
487
+ Clear
488
+ </button>
489
+ <button
490
+ type="button"
491
+ className="editor-button"
492
+ onClick={() => setActiveFontField(null)}
493
+ disabled={isFontLoading}
494
+ >
495
+ Close
496
+ </button>
497
+ </div>
498
+
499
+ {isFontLoading ? (
500
+ <div style={{ fontSize: 12, color: '#666' }}>
501
+ Loading font…
502
+ </div>
503
+ ) : null}
504
+ {fontLoadError ? (
505
+ <div style={{ fontSize: 12, color: '#b00020' }}>
506
+ {fontLoadError}
507
+ </div>
508
+ ) : null}
509
+
510
+ <div style={{ fontSize: 12, color: '#666' }}>
511
+ {fontsList.length} fonts
512
+ </div>
513
+
514
+ <div
515
+ style={{
516
+ display: 'grid',
517
+ gridTemplateColumns:
518
+ 'repeat(auto-fill, minmax(180px, 1fr))',
519
+ gap: 8,
520
+ maxHeight: '60vh',
521
+ overflow: 'auto',
522
+ paddingRight: 4,
523
+ }}
524
+ >
525
+ {fontsList.map((font) => {
526
+ const fontName = font?.name;
527
+ if (typeof fontName !== 'string' || !fontName.trim()) {
528
+ return null;
529
+ }
530
+ const familyName = fontName.trim();
531
+ const isActive = normalized === familyName;
532
+ const isLoaded = loaded.includes(familyName);
533
+ return (
534
+ <button
535
+ key={familyName}
536
+ type="button"
537
+ disabled={isFontLoading}
538
+ onClick={async () => {
539
+ fontsDebug.info(
540
+ 'AttributesEditor: select fontFamily',
541
+ {
542
+ field: name,
543
+ familyName,
544
+ wasLoaded: isLoaded,
545
+ },
546
+ );
547
+ setFontLoadError(null);
548
+ handleAttributeChange(name, familyName);
549
+ if (isLoaded) return;
550
+ setIsFontLoading(true);
551
+ try {
552
+ fontsDebug.info(
553
+ 'AttributesEditor: loadFontFamily start',
554
+ { familyName },
555
+ );
556
+ await loadFontFamily(fontsList, familyName, {
557
+ forceFetch: true,
558
+ });
559
+ markFontLoaded(familyName);
560
+ fontsDebug.info(
561
+ 'AttributesEditor: loadFontFamily success',
562
+ { familyName },
563
+ );
564
+ } catch (e) {
565
+ const msg =
566
+ e instanceof Error ? e.message : String(e);
567
+ setFontLoadError(
568
+ `Failed to load "${familyName}": ${msg}`,
569
+ );
570
+ addError(
571
+ `Failed to load font "${familyName}": ${msg}`,
572
+ );
573
+ fontsDebug.compactError(
574
+ 'AttributesEditor: loadFontFamily failed',
575
+ e,
576
+ { familyName },
577
+ );
578
+ } finally {
579
+ setIsFontLoading(false);
580
+ }
581
+ }}
582
+ style={{
583
+ display: 'flex',
584
+ alignItems: 'center',
585
+ justifyContent: 'space-between',
586
+ gap: 8,
587
+ borderRadius: 8,
588
+ border: isActive
589
+ ? '2px solid #222'
590
+ : '1px solid #ddd',
591
+ padding: '8px 10px',
592
+ background:
593
+ 'hsl(var(--card, var(--rb-card, 0 0% 100%)))',
594
+ cursor: isFontLoading ? 'not-allowed' : 'pointer',
595
+ }}
596
+ aria-label={`Select font ${familyName}`}
597
+ >
598
+ <span
599
+ style={{
600
+ fontSize: 12,
601
+ fontWeight: isActive ? 700 : 500,
602
+ textAlign: 'left',
603
+ overflow: 'hidden',
604
+ textOverflow: 'ellipsis',
605
+ whiteSpace: 'nowrap',
606
+ }}
607
+ title={familyName}
608
+ >
609
+ {familyName}
610
+ </span>
611
+ <span style={{ fontSize: 11, color: '#666' }}>
612
+ {isLoaded ? 'Loaded' : 'Not loaded'}
613
+ </span>
614
+ </button>
615
+ );
616
+ })}
617
+ </div>
618
+ </div>
619
+ </Modal>
620
+ ) : null}
621
+ </>
622
+ );
623
+ },
624
+ [
625
+ activeFontField,
626
+ addError,
627
+ handleAttributeChange,
628
+ isFontLoading,
629
+ loadedFonts,
630
+ markFontLoaded,
631
+ projectFonts,
632
+ fontLoadError,
633
+ ],
634
+ );
635
+
636
+ const handleChildrenChange = useCallback(
637
+ (val: string) => {
638
+ const next: NodeData<NodeDefaultAttribute> = {
639
+ ...baseData,
640
+ children: val,
641
+ };
642
+ onChange(next);
643
+ },
644
+ [baseData, onChange],
645
+ );
646
+
647
+ const renderSpecialSection = useCallback(
648
+ ({ key, entries: sectionEntries, meta }: SpecialSection) => {
649
+ if (key === 'size') {
650
+ const normalizedTitle =
651
+ meta?.label && meta.label.trim().length > 0
652
+ ? meta.label
653
+ : key.charAt(0).toUpperCase() + key.slice(1);
654
+ const normalizedDescription = meta?.description;
655
+ const orderedEntries = [...sectionEntries].sort((a, b) => {
656
+ const preferredOrder = [
657
+ 'width',
658
+ 'height',
659
+ 'minWidth',
660
+ 'minHeight',
661
+ 'maxWidth',
662
+ 'maxHeight',
663
+ ];
664
+ const aIndex = preferredOrder.indexOf(a.name);
665
+ const bIndex = preferredOrder.indexOf(b.name);
666
+ const aRank = aIndex === -1 ? Number.MAX_SAFE_INTEGER : aIndex;
667
+ const bRank = bIndex === -1 ? Number.MAX_SAFE_INTEGER : bIndex;
668
+ return aRank !== bRank ? aRank - bRank : a.name.localeCompare(b.name);
669
+ });
670
+ return (
671
+ <section key={key} className="special-category-section">
672
+ <div className="special-category-section__header">
673
+ <p className="special-category-section__title">
674
+ {normalizedTitle}
675
+ </p>
676
+ </div>
677
+ {normalizedDescription ? (
678
+ <p className="special-category-section__description">
679
+ {normalizedDescription}
680
+ </p>
681
+ ) : null}
682
+ <div className="attributes-editor__size-grid">
683
+ {orderedEntries.map(({ name, type }) => {
684
+ const label = attributeMeta?.[name]?.label ?? name;
685
+ const description = attributeMeta?.[name]?.description;
686
+ const preferredScale = toPreferredScale(
687
+ attributeMeta?.[name]?.preferedScale,
688
+ );
689
+ const currentValue = getAttributeValue(name);
690
+ const isBoolean = isBooleanFieldType(type);
691
+ const wrapperClassNames = [
692
+ 'attributes-editor__field-wrapper',
693
+ isBoolean ? 'attributes-editor__field-wrapper--boolean' : '',
694
+ ]
695
+ .filter(Boolean)
696
+ .join(' ');
697
+ return (
698
+ <FieldInfoTooltip key={name} description={description}>
699
+ <div
700
+ className={`${wrapperClassNames} attributes-editor__size-grid-item`}
701
+ >
702
+ {!isBoolean ? (
703
+ <p className="attributes-editor__field-label">
704
+ {label}
705
+ </p>
706
+ ) : null}
707
+ {type === 'iconType' ? (
708
+ renderIconTypeField(name, currentValue)
709
+ ) : type === 'fontFamily' ? (
710
+ renderFontFamilyField(name, currentValue)
711
+ ) : (
712
+ <Field
713
+ name={name}
714
+ type={type}
715
+ value={currentValue}
716
+ onChange={(val) => handleAttributeChange(name, val)}
717
+ componentType={data?.type}
718
+ projectColors={projectColorsForPicker}
719
+ layoutContext={layoutContext}
720
+ viewAttributes={viewAttributes}
721
+ label={isBoolean ? label : undefined}
722
+ preferredScale={preferredScale}
723
+ />
724
+ )}
725
+ </div>
726
+ </FieldInfoTooltip>
727
+ );
728
+ })}
729
+ </div>
730
+ </section>
731
+ );
732
+ }
205
733
  return (
206
- <div
207
- style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}
208
- >
209
- {Object.entries(schema).map(([fieldName, fieldType]) => (
210
- <React.Fragment key={fieldName}>
211
- <div style={{ alignSelf: 'center' }}>{fieldName}</div>
212
- <Field
213
- name={fieldName}
214
- type={fieldType}
215
- value={obj?.[fieldName as keyof typeof obj]}
216
- onChange={(val) => {
217
- const nextObj = { ...(obj ?? {}), [fieldName]: val };
218
- onChange(nextObj);
219
- }}
220
- componentType={componentType}
221
- />
222
- </React.Fragment>
223
- ))}
224
- </div>
734
+ <SpecialCategorySection
735
+ key={key}
736
+ category={key}
737
+ entries={sectionEntries}
738
+ attributeMeta={attributeMeta}
739
+ attributes={attributes}
740
+ onAttributeChange={handleAttributeChange}
741
+ componentType={data?.type}
742
+ projectColors={projectColorsForPicker}
743
+ layoutContext={layoutContext}
744
+ viewAttributes={viewAttributes}
745
+ meta={meta}
746
+ />
225
747
  );
226
- }
227
- }
748
+ },
749
+ [
750
+ attributeMeta,
751
+ attributes,
752
+ data?.type,
753
+ handleAttributeChange,
754
+ layoutContext,
755
+ projectColorsForPicker,
756
+ renderIconTypeField,
757
+ viewAttributes,
758
+ ],
759
+ );
228
760
 
229
- if (type === 'number') {
230
- return (
231
- <input
232
- type="number"
233
- value={value ?? ''}
234
- onChange={(e) =>
235
- onChange(e.target.value === '' ? undefined : Number(e.target.value))
236
- }
237
- className="input"
238
- />
239
- );
240
- }
241
- if (type === 'boolean') {
242
- return (
761
+ const tabs = useMemo<TabConfig[]>(
762
+ () => [
763
+ { id: 'container', label: 'Container', entries: grouped.container },
764
+ { id: 'style', label: 'Styles', entries: grouped.style },
765
+ { id: 'other', label: 'Others', entries: grouped.other },
766
+ ],
767
+ [grouped],
768
+ );
769
+
770
+ const tabContentInfo = useMemo<TabContentInfo>(() => {
771
+ const info: TabContentInfo = {
772
+ style: { baseCount: 0, specialCount: 0 },
773
+ container: { baseCount: 0, specialCount: 0 },
774
+ other: { baseCount: 0, specialCount: 0 },
775
+ };
776
+
777
+ tabs.forEach((tab) => {
778
+ info[tab.id].baseCount = tab.entries.length;
779
+ });
780
+
781
+ (Object.keys(specialSectionsByTab) as TabId[]).forEach((tabId) => {
782
+ info[tabId].specialCount = specialSectionsByTab[tabId].reduce(
783
+ (sum, section) => sum + section.entries.length,
784
+ 0,
785
+ );
786
+ });
787
+
788
+ return info;
789
+ }, [specialSectionsByTab, tabs]);
790
+
791
+ const firstAvailableTab = useMemo<TabId>(
792
+ () =>
793
+ tabs.find((tab) => {
794
+ const counts = tabContentInfo[tab.id];
795
+ return counts.baseCount + counts.specialCount > 0;
796
+ })?.id ?? 'other',
797
+ [tabContentInfo, tabs],
798
+ );
799
+
800
+ const [activeTab, setActiveTab] = useState<TabId>(firstAvailableTab);
801
+
802
+ useEffect(() => {
803
+ setActiveTab((prev) => {
804
+ const counts = tabContentInfo[prev];
805
+ if (counts && counts.baseCount + counts.specialCount > 0) {
806
+ return prev;
807
+ }
808
+ return firstAvailableTab;
809
+ });
810
+ }, [firstAvailableTab, tabContentInfo]);
811
+
812
+ const activeEntries =
813
+ tabs.find((tab) => tab.id === activeTab)?.entries ??
814
+ tabs.find((tab) => tab.id === firstAvailableTab)?.entries ??
815
+ [];
816
+
817
+ const activeSpecialSections = specialSectionsByTab[activeTab] ?? [];
818
+
819
+ const hasStringChildren =
820
+ !!patternForType?.pattern &&
821
+ (patternForType.pattern as { children?: unknown }).children === 'string';
822
+
823
+ const childrenValue =
824
+ typeof (baseData.children as unknown) === 'string'
825
+ ? (baseData.children as string)
826
+ : '';
827
+
828
+ const childrenSection = hasStringChildren ? (
829
+ <div className="attributes-editor__field-wrapper attributes-editor__field-wrapper--children">
830
+ <p className="attributes-editor__field-label">Text</p>
243
831
  <input
244
- type="checkbox"
245
- checked={Boolean(value)}
246
- onChange={(e) => onChange(e.target.checked)}
832
+ type="text"
833
+ className="attributes-editor__text-input"
834
+ value={childrenValue}
835
+ onChange={(e) => handleChildrenChange(e.target.value)}
247
836
  />
248
- );
249
- }
250
- // Legacy support: string[]
251
- if (type === 'string[]') {
252
- const arr: string[] = Array.isArray(value) ? value : [];
253
- return (
254
- <div style={{ display: 'grid', gap: 8 }}>
255
- {arr.map((item, idx) => (
256
- <div
257
- key={idx}
258
- style={{ display: 'flex', gap: 8, alignItems: 'center' }}
259
- >
260
- <input
261
- type="text"
262
- value={item ?? ''}
263
- onChange={(e) => {
264
- const next = [...arr];
265
- next[idx] = e.target.value;
266
- onChange(next);
267
- }}
268
- className="input"
269
- style={{ flex: 1 }}
270
- />
271
- <button
272
- type="button"
273
- onClick={() => {
274
- const next = arr.filter((_, i) => i !== idx);
275
- onChange(next.length ? next : undefined);
276
- }}
277
- >
278
- remove
279
- </button>
280
- </div>
281
- ))}
282
- <div>
837
+ </div>
838
+ ) : null;
839
+
840
+ const tabsSection = (
841
+ <div className="attributes-editor__tabs">
842
+ {tabs.map((tab) => {
843
+ const isActive = tab.id === activeTab;
844
+ const counts = tabContentInfo[tab.id];
845
+ const totalCount = counts.baseCount + counts.specialCount;
846
+ const disabled = totalCount === 0;
847
+ const buttonClassNames = [
848
+ 'attributes-editor__tab-button',
849
+ isActive ? 'attributes-editor__tab-button--active' : '',
850
+ ]
851
+ .filter(Boolean)
852
+ .join(' ');
853
+ return (
283
854
  <button
855
+ key={tab.id}
284
856
  type="button"
285
- onClick={() => {
286
- const next = [...arr, ''];
287
- onChange(next);
288
- }}
857
+ onClick={() => !disabled && setActiveTab(tab.id)}
858
+ disabled={disabled}
859
+ className={buttonClassNames}
289
860
  >
290
- add
861
+ {tab.label}
862
+ {totalCount > 0 ? ` (${totalCount})` : ''}
291
863
  </button>
292
- </div>
293
- </div>
294
- );
295
- }
296
- return (
297
- <input
298
- type="text"
299
- value={value ?? ''}
300
- onChange={(e) =>
301
- onChange(e.target.value === '' ? undefined : e.target.value)
302
- }
303
- className="input"
304
- />
864
+ );
865
+ })}
866
+ </div>
305
867
  );
306
- }
307
868
 
308
- export function AttributesEditor({ node, onChange }: AttributesEditorProps) {
309
- if (!node || isNodeString(node)) return null;
310
- const data = node as NodeData<NodeDefaultAttribute>;
311
- const schema = getAttributeSchema(data?.type) ?? {};
312
- const attributes = (data?.attributes ?? {}) as NodeDefaultAttribute;
313
-
314
- const entries = Object.entries(schema);
315
- if (entries.length === 0) {
869
+ if (visibleEntries.length === 0) {
316
870
  return (
317
- <div style={{ padding: 8, opacity: 0.7 }}>No editable attributes</div>
871
+ <div className="attributes-editor">
872
+ {headerSection}
873
+ {mockableSection}
874
+ {tabsSection}
875
+ {childrenSection}
876
+ {activeSpecialSections.map(renderSpecialSection)}
877
+ <div className="attributes-editor__empty-state">
878
+ No editable attributes
879
+ </div>
880
+ {activeMockableFeature ? (
881
+ <MockableFeatureModal
882
+ featureKey={activeMockableFeature}
883
+ onClose={() => setActiveMockableFeature(null)}
884
+ />
885
+ ) : null}
886
+ </div>
318
887
  );
319
888
  }
320
889
 
321
890
  return (
322
- <div style={{}}>
323
- {entries.map(([name, type]) => (
324
- <React.Fragment key={name}>
325
- <p style={{ alignSelf: 'center', marginBottom: 4, fontWeight: 700 }}>
326
- {name}
327
- </p>
328
- <div style={{ marginBottom: 16 }}>
329
- <Field
330
- name={name}
331
- type={type}
332
- value={attributes?.[name]}
333
- onChange={(val) => {
334
- const next: NodeData<NodeDefaultAttribute> = {
335
- ...data,
336
- attributes: { ...(attributes ?? {}), [name]: val },
337
- };
338
- onChange(next);
339
- }}
340
- componentType={data?.type}
341
- />
342
- </div>
343
- </React.Fragment>
344
- ))}
891
+ <div className="attributes-editor">
892
+ {headerSection}
893
+ {mockableSection}
894
+ {tabsSection}
895
+ {childrenSection}
896
+ {activeSpecialSections.map(renderSpecialSection)}
897
+
898
+ {activeEntries.map(({ name, type }) => {
899
+ const label = attributeMeta?.[name]?.label ?? name;
900
+ const description = attributeMeta?.[name]?.description;
901
+ const preferredScale = toPreferredScale(
902
+ attributeMeta?.[name]?.preferedScale,
903
+ );
904
+ const isBoolean = isBooleanFieldType(type);
905
+ const wrapperClassNames = [
906
+ 'attributes-editor__field-wrapper',
907
+ isBoolean ? 'attributes-editor__field-wrapper--boolean' : '',
908
+ ]
909
+ .filter(Boolean)
910
+ .join(' ');
911
+ return (
912
+ <FieldInfoTooltip key={name} description={description}>
913
+ <div className={wrapperClassNames}>
914
+ {!isBoolean ? (
915
+ <p className="attributes-editor__field-label">{label}</p>
916
+ ) : null}
917
+ {type === 'iconType' ? (
918
+ renderIconTypeField(name, getAttributeValue(name))
919
+ ) : type === 'fontFamily' ? (
920
+ renderFontFamilyField(name, getAttributeValue(name))
921
+ ) : (
922
+ <Field
923
+ name={name}
924
+ type={type}
925
+ value={getAttributeValue(name)}
926
+ onChange={(val) => handleAttributeChange(name, val)}
927
+ componentType={data?.type}
928
+ projectColors={projectColorsForPicker}
929
+ layoutContext={layoutContext}
930
+ viewAttributes={viewAttributes}
931
+ label={isBoolean ? label : undefined}
932
+ preferredScale={preferredScale}
933
+ />
934
+ )}
935
+ </div>
936
+ </FieldInfoTooltip>
937
+ );
938
+ })}
939
+ {activeMockableFeature ? (
940
+ <MockableFeatureModal
941
+ featureKey={activeMockableFeature}
942
+ onClose={() => setActiveMockableFeature(null)}
943
+ />
944
+ ) : null}
345
945
  </div>
346
946
  );
347
947
  }