@qwickapps/react-framework 1.3.5 → 1.4.1

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 (320) hide show
  1. package/README.md +1691 -2
  2. package/dist/__tests__/schemas/transformers/MockSerializableComponent.d.ts +66 -0
  3. package/dist/__tests__/schemas/transformers/MockSerializableComponent.d.ts.map +1 -0
  4. package/dist/components/ErrorBoundary.d.ts +7 -0
  5. package/dist/components/ErrorBoundary.d.ts.map +1 -1
  6. package/dist/components/Html.d.ts +28 -18
  7. package/dist/components/Html.d.ts.map +1 -1
  8. package/dist/components/Logo.d.ts +12 -35
  9. package/dist/components/Logo.d.ts.map +1 -1
  10. package/dist/components/Markdown.d.ts +18 -13
  11. package/dist/components/Markdown.d.ts.map +1 -1
  12. package/dist/components/QwickApp.d.ts +16 -3
  13. package/dist/components/QwickApp.d.ts.map +1 -1
  14. package/dist/components/QwickIcon.d.ts +23 -0
  15. package/dist/components/QwickIcon.d.ts.map +1 -0
  16. package/dist/components/SafeSpan.d.ts +12 -5
  17. package/dist/components/SafeSpan.d.ts.map +1 -1
  18. package/dist/components/Scaffold.d.ts.map +1 -1
  19. package/dist/components/base/ModelView.d.ts +101 -0
  20. package/dist/components/base/ModelView.d.ts.map +1 -0
  21. package/dist/components/base/index.d.ts +11 -0
  22. package/dist/components/base/index.d.ts.map +1 -0
  23. package/dist/components/blocks/Article.d.ts +12 -2
  24. package/dist/components/blocks/Article.d.ts.map +1 -1
  25. package/dist/components/blocks/Code.d.ts +13 -2
  26. package/dist/components/blocks/Code.d.ts.map +1 -1
  27. package/dist/components/blocks/CoverImageHeader.d.ts.map +1 -1
  28. package/dist/components/blocks/FeatureCard.d.ts.map +1 -1
  29. package/dist/components/blocks/FeatureGrid.d.ts.map +1 -1
  30. package/dist/components/blocks/Footer.d.ts.map +1 -1
  31. package/dist/components/blocks/HeroBlock.d.ts +27 -13
  32. package/dist/components/blocks/HeroBlock.d.ts.map +1 -1
  33. package/dist/components/blocks/Image.d.ts +41 -0
  34. package/dist/components/blocks/Image.d.ts.map +1 -0
  35. package/dist/components/blocks/PageBannerHeader.d.ts.map +1 -1
  36. package/dist/components/blocks/Section.d.ts +16 -2
  37. package/dist/components/blocks/Section.d.ts.map +1 -1
  38. package/dist/components/blocks/Text.d.ts +41 -0
  39. package/dist/components/blocks/Text.d.ts.map +1 -0
  40. package/dist/components/blocks/index.d.ts +4 -0
  41. package/dist/components/blocks/index.d.ts.map +1 -1
  42. package/dist/components/buttons/Button.d.ts +23 -7
  43. package/dist/components/buttons/Button.d.ts.map +1 -1
  44. package/dist/components/forms/FormBlock.d.ts +19 -13
  45. package/dist/components/forms/FormBlock.d.ts.map +1 -1
  46. package/dist/components/index.d.ts +4 -0
  47. package/dist/components/index.d.ts.map +1 -1
  48. package/dist/components/input/ChoiceInputField.d.ts +17 -11
  49. package/dist/components/input/ChoiceInputField.d.ts.map +1 -1
  50. package/dist/components/input/HtmlInputField.d.ts +17 -11
  51. package/dist/components/input/HtmlInputField.d.ts.map +1 -1
  52. package/dist/components/input/SelectInputField.d.ts +16 -10
  53. package/dist/components/input/SelectInputField.d.ts.map +1 -1
  54. package/dist/components/input/SwitchInputField.d.ts +16 -10
  55. package/dist/components/input/SwitchInputField.d.ts.map +1 -1
  56. package/dist/components/input/TextField.d.ts.map +1 -1
  57. package/dist/components/input/TextInputField.d.ts +16 -11
  58. package/dist/components/input/TextInputField.d.ts.map +1 -1
  59. package/dist/components/layout/GridCell.d.ts +23 -6
  60. package/dist/components/layout/GridCell.d.ts.map +1 -1
  61. package/dist/components/layout/GridLayout.d.ts +24 -23
  62. package/dist/components/layout/GridLayout.d.ts.map +1 -1
  63. package/dist/components/pages/FormPage.d.ts.map +1 -1
  64. package/dist/components/pages/Page.d.ts +49 -87
  65. package/dist/components/pages/Page.d.ts.map +1 -1
  66. package/dist/components/pages/index.d.ts +2 -2
  67. package/dist/components/pages/index.d.ts.map +1 -1
  68. package/dist/config/AppConfig.d.ts +49 -0
  69. package/dist/config/AppConfig.d.ts.map +1 -0
  70. package/dist/config/AppConfigBuilder.d.ts +75 -0
  71. package/dist/config/AppConfigBuilder.d.ts.map +1 -0
  72. package/dist/config/index.d.ts +13 -0
  73. package/dist/config/index.d.ts.map +1 -0
  74. package/dist/config/types.d.ts +130 -0
  75. package/dist/config/types.d.ts.map +1 -0
  76. package/dist/config.d.ts +15 -0
  77. package/dist/config.d.ts.map +1 -0
  78. package/dist/config.esm.js +451 -0
  79. package/dist/config.js +455 -0
  80. package/dist/contexts/PrintModeContext.d.ts +27 -0
  81. package/dist/contexts/PrintModeContext.d.ts.map +1 -0
  82. package/dist/contexts/QwickAppContext.d.ts +2 -2
  83. package/dist/contexts/QwickAppContext.d.ts.map +1 -1
  84. package/dist/contexts/index.d.ts +2 -0
  85. package/dist/contexts/index.d.ts.map +1 -1
  86. package/dist/hooks/index.d.ts +2 -0
  87. package/dist/hooks/index.d.ts.map +1 -1
  88. package/dist/hooks/usePrintMode.d.ts +39 -0
  89. package/dist/hooks/usePrintMode.d.ts.map +1 -0
  90. package/dist/index.css +1 -1
  91. package/dist/index.d.ts +1 -0
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.esm.css +1 -1
  94. package/dist/index.esm.js +10951 -6238
  95. package/dist/index.js +11014 -6287
  96. package/dist/schemas/CodeSchema.d.ts +2 -1
  97. package/dist/schemas/CodeSchema.d.ts.map +1 -1
  98. package/dist/schemas/CollapsibleLayoutSchema.d.ts +2 -1
  99. package/dist/schemas/CollapsibleLayoutSchema.d.ts.map +1 -1
  100. package/dist/schemas/ContentSchema.d.ts +2 -1
  101. package/dist/schemas/ContentSchema.d.ts.map +1 -1
  102. package/dist/schemas/GridCellSchema.d.ts +25 -0
  103. package/dist/schemas/GridCellSchema.d.ts.map +1 -0
  104. package/dist/schemas/GridLayoutSchema.d.ts +23 -0
  105. package/dist/schemas/GridLayoutSchema.d.ts.map +1 -0
  106. package/dist/schemas/HtmlSchema.d.ts +14 -0
  107. package/dist/schemas/HtmlSchema.d.ts.map +1 -0
  108. package/dist/schemas/ImageSchema.d.ts +32 -0
  109. package/dist/schemas/ImageSchema.d.ts.map +1 -0
  110. package/dist/schemas/LogoSchema.d.ts +35 -0
  111. package/dist/schemas/LogoSchema.d.ts.map +1 -0
  112. package/dist/schemas/MarkdownSchema.d.ts +14 -0
  113. package/dist/schemas/MarkdownSchema.d.ts.map +1 -0
  114. package/dist/schemas/PageTemplateSchema.d.ts +31 -0
  115. package/dist/schemas/PageTemplateSchema.d.ts.map +1 -0
  116. package/dist/schemas/PrintConfigSchema.d.ts +31 -0
  117. package/dist/schemas/PrintConfigSchema.d.ts.map +1 -0
  118. package/dist/schemas/SectionSchema.d.ts +2 -1
  119. package/dist/schemas/SectionSchema.d.ts.map +1 -1
  120. package/dist/schemas/TextSchema.d.ts +37 -0
  121. package/dist/schemas/TextSchema.d.ts.map +1 -0
  122. package/dist/schemas/ViewModelSchema.d.ts +23 -0
  123. package/dist/schemas/ViewModelSchema.d.ts.map +1 -0
  124. package/dist/schemas/index.d.ts +15 -1
  125. package/dist/schemas/index.d.ts.map +1 -1
  126. package/dist/schemas/transformers/ComponentTransformer.d.ts +116 -0
  127. package/dist/schemas/transformers/ComponentTransformer.d.ts.map +1 -0
  128. package/dist/schemas/transformers/ReactNodeTransformer.d.ts +53 -0
  129. package/dist/schemas/transformers/ReactNodeTransformer.d.ts.map +1 -0
  130. package/dist/schemas/transformers/__tests__/MockSerializableComponent.d.ts +66 -0
  131. package/dist/schemas/transformers/__tests__/MockSerializableComponent.d.ts.map +1 -0
  132. package/dist/schemas/transformers/registry.d.ts +15 -0
  133. package/dist/schemas/transformers/registry.d.ts.map +1 -0
  134. package/dist/schemas/types/Serializable.d.ts +46 -0
  135. package/dist/schemas/types/Serializable.d.ts.map +1 -0
  136. package/dist/utils/htmlTransform.d.ts.map +1 -1
  137. package/dist/utils/reactUtils.d.ts +12 -3
  138. package/dist/utils/reactUtils.d.ts.map +1 -1
  139. package/package.json +17 -3
  140. package/src/{components/__tests__ → __tests__/components}/AccessibilityProvider.test.tsx +1 -1
  141. package/src/{components/__tests__ → __tests__/components}/Article.test.tsx +1 -1
  142. package/src/{components/__tests__ → __tests__/components}/Breadcrumbs.test.tsx +1 -1
  143. package/src/{components/__tests__ → __tests__/components}/Button.test.tsx +1 -1
  144. package/src/{components/__tests__ → __tests__/components}/CardListGrid.test.tsx +2 -2
  145. package/src/{components/__tests__ → __tests__/components}/ChoiceInputField.test.tsx +1 -1
  146. package/src/{components/__tests__ → __tests__/components}/Code.test.tsx +1 -1
  147. package/src/{components/__tests__ → __tests__/components}/Content.integration.test.tsx +1 -1
  148. package/src/{components/__tests__ → __tests__/components}/Content.test.tsx +1 -1
  149. package/src/{components/__tests__ → __tests__/components}/CoverImageHeader.test.tsx +2 -2
  150. package/src/{components/__tests__ → __tests__/components}/ErrorBoundary.test.tsx +1 -1
  151. package/src/{components/__tests__ → __tests__/components}/FeatureCard.integration.test.tsx +2 -2
  152. package/src/{components/__tests__ → __tests__/components}/FeatureGrid.integration.test.tsx +2 -2
  153. package/src/{components/__tests__ → __tests__/components}/FeatureGrid.test.tsx +2 -2
  154. package/src/{components/__tests__ → __tests__/components}/Footer.test.tsx +4 -4
  155. package/src/{components/__tests__ → __tests__/components}/FormBlock.test.tsx +1 -1
  156. package/src/{components/__tests__ → __tests__/components}/HeroBlock.integration.test.tsx +2 -2
  157. package/src/{components/__tests__ → __tests__/components}/HeroBlock.test.tsx +233 -7
  158. package/src/{components/__tests__ → __tests__/components}/Html.test.tsx +11 -2
  159. package/src/{components/__tests__ → __tests__/components}/HtmlInputField.test.tsx +3 -3
  160. package/src/__tests__/components/Logo.test.js +3 -3
  161. package/src/{components/__tests__ → __tests__/components}/Markdown.test.tsx +1 -1
  162. package/src/{components/__tests__ → __tests__/components}/PageBannerHeader.test.tsx +3 -3
  163. package/src/{components/__tests__ → __tests__/components}/PaletteSwitcher.test.tsx +3 -3
  164. package/src/{components/__tests__ → __tests__/components}/ProductCard.test.tsx +4 -4
  165. package/src/{components/__tests__ → __tests__/components}/SafeSpan.integration.test.tsx +2 -2
  166. package/src/{components/__tests__ → __tests__/components}/SafeSpan.simple.test.tsx +1 -1
  167. package/src/{components/__tests__ → __tests__/components}/SafeSpan.test.tsx +1 -1
  168. package/src/{components/__tests__ → __tests__/components}/Section.integration.test.tsx +1 -1
  169. package/src/{components/__tests__ → __tests__/components}/Section.test.tsx +1 -1
  170. package/src/{components/__tests__ → __tests__/components}/SelectInputField.test.tsx +1 -1
  171. package/src/{components/__tests__ → __tests__/components}/TextInputField.test.tsx +3 -3
  172. package/src/{components/__tests__ → __tests__/components}/ThemeSwitcher.test.tsx +3 -3
  173. package/src/__tests__/components/base/ModelView.test.tsx +220 -0
  174. package/src/__tests__/components/blocks/Code.performance.test.tsx +625 -0
  175. package/src/__tests__/components/blocks/Code.serialization.test.tsx +507 -0
  176. package/src/__tests__/components/blocks/HeroBlock.serialization.test.tsx +414 -0
  177. package/src/__tests__/components/blocks/Image.serialization.test.tsx +257 -0
  178. package/src/__tests__/components/blocks/Section.serialization.test.tsx +553 -0
  179. package/src/__tests__/components/blocks/Text.performance.test.tsx +442 -0
  180. package/src/__tests__/components/blocks/Text.serialization.test.tsx +491 -0
  181. package/src/__tests__/components/buttons/Button.serialization.test.tsx +443 -0
  182. package/src/__tests__/components/input/FormComponents.serialization.test.tsx +482 -0
  183. package/src/__tests__/components/input/SelectInputField.serialization.test.tsx +439 -0
  184. package/src/__tests__/components/input/TextInputField.serialization.test.tsx +359 -0
  185. package/src/{components/layout/CollapsibleLayout/__tests__ → __tests__/components/layout}/CollapsibleLayout.test.tsx +4 -4
  186. package/src/__tests__/components/layout/GridCell.serialization.test.tsx +403 -0
  187. package/src/__tests__/components/layout/GridLayout.serialization.test.tsx +311 -0
  188. package/src/__tests__/hooks/usePrintMode.test.ts +89 -0
  189. package/src/__tests__/schemas/PageTemplateSchema.test.ts +161 -0
  190. package/src/__tests__/schemas/PrintConfigSchema.test.ts +127 -0
  191. package/src/__tests__/schemas/ViewModelSchema.test.ts +80 -0
  192. package/src/__tests__/schemas/transformers/ComponentSerializationPatterns.test.tsx +602 -0
  193. package/src/__tests__/schemas/transformers/ComponentTransformer.htmlPatterns.test.ts +301 -0
  194. package/src/__tests__/schemas/transformers/ComponentTransformer.test.ts +521 -0
  195. package/src/__tests__/schemas/transformers/CrossBrowserCompatibility.test.ts +586 -0
  196. package/src/__tests__/schemas/transformers/MockSerializableComponent.ts +103 -0
  197. package/src/__tests__/schemas/transformers/RealWorldScenarios.test.tsx +1165 -0
  198. package/src/__tests__/schemas/transformers/SerializationErrorHandling.test.ts +602 -0
  199. package/src/__tests__/schemas/transformers/SerializationIntegration.test.tsx +691 -0
  200. package/src/__tests__/schemas/transformers/SerializationPerformance.test.ts +460 -0
  201. package/src/__tests__/schemas/transformers/TestAutomation.test.ts +597 -0
  202. package/src/{utils/__tests__ → __tests__/utils}/nested-dom-fix.test.tsx +1 -1
  203. package/src/components/ErrorBoundary.tsx +8 -8
  204. package/src/components/Html.tsx +147 -44
  205. package/src/components/Logo.tsx +198 -100
  206. package/src/components/Markdown.tsx +125 -16
  207. package/src/components/QwickApp.tsx +64 -31
  208. package/src/components/QwickIcon.tsx +59 -0
  209. package/src/components/SafeSpan.tsx +65 -10
  210. package/src/components/Scaffold.tsx +2 -8
  211. package/src/components/base/ModelView.tsx +199 -0
  212. package/src/components/base/index.ts +11 -0
  213. package/src/components/blocks/Article.tsx +57 -18
  214. package/src/components/blocks/Code.md +529 -0
  215. package/src/components/blocks/Code.tsx +102 -15
  216. package/src/components/blocks/CoverImageHeader.tsx +9 -4
  217. package/src/components/blocks/FeatureCard.tsx +1 -2
  218. package/src/components/blocks/FeatureGrid.tsx +19 -1
  219. package/src/components/blocks/Footer.tsx +13 -1
  220. package/src/components/blocks/HeroBlock.tsx +87 -20
  221. package/src/components/blocks/Image.tsx +395 -0
  222. package/src/components/blocks/PageBannerHeader.tsx +14 -12
  223. package/src/components/blocks/ProductCard.tsx +1 -1
  224. package/src/components/blocks/Section.tsx +113 -8
  225. package/src/components/blocks/Text.tsx +285 -0
  226. package/src/components/blocks/index.ts +4 -0
  227. package/src/components/buttons/Button.tsx +184 -15
  228. package/src/components/forms/FormBlock.tsx +70 -17
  229. package/src/components/index.ts +5 -0
  230. package/src/components/input/ChoiceInputField.tsx +48 -18
  231. package/src/components/input/HtmlInputField.tsx +48 -18
  232. package/src/components/input/SelectInputField.tsx +48 -16
  233. package/src/components/input/SwitchInputField.tsx +48 -17
  234. package/src/components/input/TextField.tsx +41 -1
  235. package/src/components/input/TextInputField.tsx +52 -18
  236. package/src/components/layout/GridCell.tsx +118 -9
  237. package/src/components/layout/GridLayout.tsx +125 -24
  238. package/src/components/pages/FormPage.tsx +0 -1
  239. package/src/components/pages/Page.css +304 -332
  240. package/src/components/pages/Page.tsx +307 -255
  241. package/src/components/pages/index.ts +2 -2
  242. package/src/config/AppConfig.ts +133 -0
  243. package/src/config/AppConfigBuilder.ts +421 -0
  244. package/src/config/__tests__/AppConfig.test.ts +385 -0
  245. package/src/config/__tests__/AppConfigBuilder.test.ts +432 -0
  246. package/src/config/index.ts +24 -0
  247. package/src/config/types.ts +170 -0
  248. package/src/config.ts +25 -0
  249. package/src/contexts/PrintModeContext.tsx +332 -0
  250. package/src/contexts/QwickAppContext.tsx +2 -2
  251. package/src/contexts/index.ts +2 -0
  252. package/src/hooks/index.ts +5 -1
  253. package/src/hooks/usePrintMode.ts +73 -0
  254. package/src/index.ts +3 -0
  255. package/src/schemas/CodeSchema.ts +3 -3
  256. package/src/schemas/CollapsibleLayoutSchema.ts +2 -1
  257. package/src/schemas/ContentSchema.ts +2 -1
  258. package/src/schemas/GridCellSchema.ts +164 -0
  259. package/src/schemas/GridLayoutSchema.ts +133 -0
  260. package/src/schemas/HtmlSchema.ts +47 -0
  261. package/src/schemas/ImageSchema.ts +235 -0
  262. package/src/schemas/LogoSchema.ts +241 -0
  263. package/src/schemas/MarkdownSchema.ts +47 -0
  264. package/src/schemas/PageTemplateSchema.ts +186 -0
  265. package/src/schemas/PrintConfigSchema.ts +207 -0
  266. package/src/schemas/README.md +661 -0
  267. package/src/schemas/SectionSchema.ts +2 -1
  268. package/src/schemas/TextSchema.ts +329 -0
  269. package/src/schemas/ViewModelSchema.ts +115 -0
  270. package/src/schemas/index.ts +21 -2
  271. package/src/schemas/transformers/ComponentTransformer.ts +403 -0
  272. package/src/schemas/transformers/ReactNodeTransformer.ts +236 -0
  273. package/src/schemas/transformers/registry.ts +72 -0
  274. package/src/schemas/types/Serializable.ts +51 -0
  275. package/src/stories/AccessibilityProvider.stories.tsx +253 -253
  276. package/src/stories/Article.stories.tsx +433 -433
  277. package/src/stories/Button.stories.tsx +1 -1
  278. package/src/stories/CardListGrid.stories.tsx +451 -451
  279. package/src/stories/ChoiceInputField.stories.tsx +503 -503
  280. package/src/stories/Code.stories.tsx +1 -1
  281. package/src/stories/CollapsibleLayout.stories.tsx +1414 -1414
  282. package/src/stories/Content.stories.tsx +393 -393
  283. package/src/stories/CoverImageHeader.stories.tsx +701 -701
  284. package/src/stories/DataBinding.advanced.stories.tsx +432 -432
  285. package/src/stories/DataProvider.stories.tsx +1192 -1192
  286. package/src/stories/FeatureCard.stories.tsx +557 -557
  287. package/src/stories/FeatureGrid.stories.tsx +594 -594
  288. package/src/stories/Footer.stories.tsx +640 -640
  289. package/src/stories/FormBlock.stories.tsx +760 -760
  290. package/src/stories/FormComponents.stories.tsx +349 -541
  291. package/src/stories/GridCell.stories.tsx +417 -0
  292. package/src/stories/GridLayout.stories.tsx +353 -0
  293. package/src/stories/HeroBlock.stories.tsx +862 -373
  294. package/src/stories/HtmlInputField.stories.tsx +474 -474
  295. package/src/stories/Image.stories.tsx +819 -0
  296. package/src/stories/Introduction.stories.tsx +667 -667
  297. package/src/stories/LayoutBlocks.stories.tsx +324 -324
  298. package/src/stories/Logo.stories.tsx +165 -6
  299. package/src/stories/Markdown.stories.tsx +137 -137
  300. package/src/stories/ModelView.stories.tsx +477 -0
  301. package/src/stories/Page.stories.tsx +688 -688
  302. package/src/stories/PageBannerHeader.stories.tsx +864 -864
  303. package/src/stories/PaletteSwitcher.stories.tsx +119 -119
  304. package/src/stories/ProductCard.stories.tsx +424 -424
  305. package/src/stories/QwickApp.stories.tsx +368 -368
  306. package/src/stories/ResponsiveMenu.stories.tsx +249 -249
  307. package/src/stories/SafeSpan.stories.tsx +531 -531
  308. package/src/stories/Section.stories.tsx +90 -2
  309. package/src/stories/SelectInputField.stories.tsx +524 -524
  310. package/src/stories/Text.stories.tsx +560 -0
  311. package/src/stories/TextInputField.stories.tsx +443 -443
  312. package/src/stories/ThemeSwitcher.stories.tsx +123 -123
  313. package/src/utils/htmlTransform.tsx +74 -53
  314. package/src/utils/reactUtils.tsx +57 -6
  315. package/dist/index.bundled.css +0 -12
  316. /package/src/{hooks/__tests__ → __tests__/hooks}/useDataBinding.test.tsx.disabled +0 -0
  317. /package/src/{schemas/__tests__ → __tests__/schemas}/builders.test.ts +0 -0
  318. /package/src/{utils/__tests__ → __tests__/utils}/createDataDrivenComponent.test.tsx.disabled +0 -0
  319. /package/src/{utils/__tests__ → __tests__/utils}/htmlTransform.test.tsx +0 -0
  320. /package/src/{utils/__tests__ → __tests__/utils}/optional-logging.test.ts +0 -0
@@ -424,10 +424,15 @@ function CoverImageHeader(props: CoverImageHeaderProps) {
424
424
 
425
425
  // Convert HeaderActionModel[] to HeaderAction[] if actions exist
426
426
  const { actions: modelActions, ...viewProps } = coverImageHeaderProps;
427
- const convertedActions: HeaderAction[] = modelActions ? modelActions.map(action => ({
428
- ...action,
429
- onClick: () => console.log(`Action clicked: ${action.id}`) // Default handler for data-driven actions
430
- })) : [];
427
+ const convertedActions: HeaderAction[] = modelActions
428
+ ? modelActions
429
+ .filter(action => typeof action.id === 'string' && !!action.id)
430
+ .map(action => ({
431
+ ...action,
432
+ id: action.id as string,
433
+ onClick: () => console.log(`Action clicked: ${action.id}`) // Default handler for data-driven actions
434
+ }))
435
+ : [];
431
436
 
432
437
  return <CoverImageHeaderView {...viewProps} actions={convertedActions} />;
433
438
  }
@@ -237,10 +237,9 @@ function FeatureCardView({
237
237
  <Button
238
238
  key={action.id}
239
239
  variant={action.variant || 'contained'}
240
- color={action.color || 'primary'}
240
+ // color={action.color || 'primary'}
241
241
  disabled={action.disabled}
242
242
  onClick={action.onClick}
243
- size="small"
244
243
  >
245
244
  {action.label}
246
245
  </Button>
@@ -140,7 +140,25 @@ function FeatureGrid(props: FeatureGridProps) {
140
140
  return null;
141
141
  }
142
142
 
143
- return <FeatureGridView {...featureGridProps} />;
143
+ // Ensure features have a non-optional 'id'
144
+ const safeFeatures =
145
+ Array.isArray(featureGridProps.features)
146
+ ? featureGridProps.features.map((f, idx) => ({
147
+ ...f,
148
+ id: f.id ?? `feature-${idx}`,
149
+ title: f.title ?? '',
150
+ description: f.description ?? '',
151
+ }))
152
+ : featureGridProps.features;
153
+
154
+ // Ensure columns is one of the allowed values
155
+ const allowedColumns = [1, 2, 3, 4, 5, 6];
156
+ const columns =
157
+ allowedColumns.includes(featureGridProps.columns as number)
158
+ ? (featureGridProps.columns as 1 | 2 | 3 | 4 | 5 | 6)
159
+ : undefined;
160
+
161
+ return <FeatureGridView {...featureGridProps} features={safeFeatures} columns={columns} />;
144
162
  }
145
163
 
146
164
  export default FeatureGrid;
@@ -337,7 +337,19 @@ function Footer(props: FooterProps) {
337
337
  return null;
338
338
  }
339
339
 
340
- return <FooterView {...footerProps} />;
340
+ // Map FooterSectionModel[] to FooterSection[] and ensure id is a string
341
+ const mappedSections =
342
+ footerProps.sections?.map(section => ({
343
+ ...section,
344
+ id: section.id ?? '', // fallback to empty string if id is undefined
345
+ items: section.items?.map(item => ({
346
+ ...item,
347
+ id: item.id ?? '', // fallback to empty string if id is undefined
348
+ label: item.label ?? '', // ensure label is always a string
349
+ })) ?? [],
350
+ })) ?? [];
351
+
352
+ return <FooterView {...footerProps} sections={mappedSections} />;
341
353
  }
342
354
 
343
355
  export default Footer;
@@ -1,40 +1,44 @@
1
1
  /**
2
- * HeroBlock Component - Full-width hero section with data binding support
3
- *
4
- * Enhanced with data binding support through dataSource prop.
5
- *
6
- * Usage:
7
- * - Traditional: <HeroBlock title="Welcome" subtitle="Get started" actions={[...]} />
8
- * - Data-driven: <HeroBlock dataSource="pages.home.hero" />
2
+ * HeroBlock Component - Full-width hero section with serialization support
9
3
  *
10
4
  * Features:
11
5
  * - Responsive headline, subtitle, and actions
12
6
  * - Supports background images, gradients, and theme colors
13
7
  * - Overlay for image backgrounds
14
8
  * - Customizable height, alignment, and overlay opacity
9
+ * - Full serialization support via ModelView
10
+ * - Nested Button component serialization support
11
+ *
12
+ * Usage:
13
+ * - Traditional: <HeroBlock title="Welcome" subtitle="Get started" actions={[...]} />
14
+ * - Data-driven: <HeroBlock dataSource="pages.home.hero" />
15
+ * - Serializable: ComponentTransformer.serialize(<HeroBlock ... />)
15
16
  *
16
17
  * Copyright (c) 2025 QwickApps.com. All rights reserved.
17
18
  */
18
19
 
19
20
  import { Box, Container, Stack, Typography, useTheme } from '@mui/material';
20
- import { WithDataBinding, ModelProps } from '@qwickapps/schema';
21
- import React from 'react';
22
- import { QWICKAPP_COMPONENT, useBaseProps, useDataBinding, WithBaseProps } from '../../hooks';
21
+ import type { ModelProps, WithDataBinding } from '@qwickapps/schema';
22
+ import React, { ReactElement, ReactNode } from 'react';
23
+ import { QWICKAPP_COMPONENT, useBaseProps, useDataBinding } from '../../hooks';
23
24
  import HeroBlockModel from '../../schemas/HeroBlockSchema';
25
+ import { ModelView } from '../base/ModelView';
24
26
  import { Button, ButtonProps } from '../buttons/Button';
25
27
 
26
- type HeroBlockViewProps = Partial<ModelProps<HeroBlockModel>> & WithBaseProps & {
28
+ type HeroBlockViewProps = ModelProps<HeroBlockModel> & {
27
29
  /** Action buttons (data-driven) */
28
30
  actions?: ButtonProps[];
29
31
  /** Additional content below the text */
30
- children?: React.ReactNode;
32
+ children?: ReactNode;
33
+ /** Click handler for the hero section */
34
+ onClick?: (event: React.MouseEvent<HTMLElement>) => void;
35
+ /** Additional inline styles */
36
+ style?: React.CSSProperties;
31
37
  };
32
38
 
33
39
  export interface HeroBlockProps extends HeroBlockViewProps, WithDataBinding {}
34
40
 
35
- /**
36
- * Core HeroBlock View component - handles hero section rendering
37
- */
41
+ // View component - handles the actual rendering
38
42
  function HeroBlockView({
39
43
  title = '',
40
44
  subtitle,
@@ -230,17 +234,77 @@ function HeroBlockView({
230
234
  );
231
235
  }
232
236
 
233
- function HeroBlock(props: HeroBlockProps) {
234
- const { dataSource, bindingOptions, ...restProps } = props;
237
+ // Main component with data binding support and serialization capability
238
+ export class HeroBlock extends ModelView<HeroBlockProps, HeroBlockModel> {
239
+ // Component self-declaration for serialization
240
+ static readonly tagName = 'HeroBlock';
241
+ static readonly version = '1.0.0';
242
+
243
+ // Deserialization: JSON data → React element
244
+ static fromJson(jsonData: any): ReactElement {
245
+ return <HeroBlock {...jsonData} />;
246
+ }
247
+
248
+ // Component-specific serialization properties
249
+ protected getComponentSpecificProps(): any {
250
+ return {
251
+ title: this.props.title,
252
+ subtitle: this.props.subtitle,
253
+ backgroundImage: this.props.backgroundImage,
254
+ backgroundGradient: this.props.backgroundGradient,
255
+ backgroundColor: this.props.backgroundColor,
256
+ textAlign: this.props.textAlign,
257
+ blockHeight: this.props.blockHeight,
258
+ overlayOpacity: this.props.overlayOpacity,
259
+ // Serialize actions array (ButtonProps)
260
+ actions: this.props.actions
261
+ };
262
+ }
263
+
264
+ // Override serializeChildren to handle nested Button components
265
+ protected serializeChildren(children: ReactNode): any {
266
+ if (typeof children === 'string') {
267
+ return children;
268
+ }
269
+
270
+ // HeroBlock can contain complex nested content, but not necessarily other serializable components
271
+ // Handle the children as-is for now, since HeroBlock typically contains MUI Typography and Box elements
272
+ // The actions prop is handled separately in getComponentSpecificProps
273
+ if (children) {
274
+ // For HeroBlock, children are typically additional content (Typography, Box)
275
+ // These don't need component serialization, just text extraction
276
+ const { extractTextFromReactNode } = require('../../utils/reactUtils');
277
+ return extractTextFromReactNode(children);
278
+ }
279
+
280
+ return undefined;
281
+ }
235
282
 
236
- // If no dataSource, use traditional props
237
- if (!dataSource) {
283
+ // Indicate that HeroBlock doesn't contain nested serializable components in children
284
+ // (actions are handled separately via props)
285
+ protected hasNestedComponents(children: ReactNode): boolean {
286
+ return false;
287
+ }
288
+
289
+ // HeroBlock component renders traditional props view
290
+ protected renderView(): React.ReactElement {
291
+ const { dataSource, bindingOptions, ...restProps } = this.props;
238
292
  return <HeroBlockView {...restProps} />;
239
293
  }
240
294
 
295
+ // HeroBlock component renders data-bound view
296
+ protected renderWithDataBinding(): React.ReactElement {
297
+ return <HeroBlockWithDataBinding {...this.props} />;
298
+ }
299
+ }
300
+
301
+ // Helper component to handle data binding with hooks (since we can't use hooks in class components)
302
+ function HeroBlockWithDataBinding(props: HeroBlockProps) {
303
+ const { dataSource, bindingOptions, ...restProps } = props;
304
+
241
305
  // Use data binding
242
306
  const { dataSource: _source, loading, error, cached, ...heroProps } = useDataBinding<HeroBlockModel>(
243
- dataSource,
307
+ dataSource!,
244
308
  restProps as Partial<HeroBlockModel>,
245
309
  HeroBlockModel.getSchema(),
246
310
  { cache: true, cacheTTL: 300000, strict: false, ...bindingOptions }
@@ -277,4 +341,7 @@ function HeroBlock(props: HeroBlockProps) {
277
341
  return <HeroBlockView {...heroProps} />;
278
342
  }
279
343
 
344
+ // Mark as QwickApp component
345
+ (HeroBlock as any)[QWICKAPP_COMPONENT] = true;
346
+
280
347
  export default HeroBlock;
@@ -0,0 +1,395 @@
1
+ /**
2
+ * Image - Comprehensive image display component with serialization support
3
+ *
4
+ * Features:
5
+ * - Responsive image handling with srcSet and sizes
6
+ * - Multiple fit modes and positioning options
7
+ * - Loading states and error handling
8
+ * - Accessibility support with proper alt text
9
+ * - Lazy loading support
10
+ * - Fallback image handling
11
+ * - Full serialization support via ModelView
12
+ *
13
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
14
+ */
15
+
16
+ import React, { ReactElement, useState, useCallback } from 'react';
17
+ import { Box, Skeleton, Typography, useTheme } from '@mui/material';
18
+ import { BrokenImage as BrokenImageIcon } from '@mui/icons-material';
19
+ import type { WithDataBinding, ModelProps } from '@qwickapps/schema';
20
+ import { QWICKAPP_COMPONENT, useBaseProps, useDataBinding } from '../../hooks';
21
+ import ImageModel, { ImageFit, ImageLoading, ImagePosition } from '../../schemas/ImageSchema';
22
+ import { ModelView } from '../base/ModelView';
23
+
24
+ type ImageViewProps = ModelProps<ImageModel> & {
25
+ /** Click handler for the image */
26
+ onClick?: (event: React.MouseEvent<HTMLElement>) => void;
27
+ /** Additional inline styles */
28
+ style?: React.CSSProperties;
29
+ /** Additional CSS class names */
30
+ className?: string;
31
+ };
32
+
33
+ export interface ImageProps extends ImageViewProps, WithDataBinding {}
34
+
35
+ // View component - handles the actual rendering
36
+ function ImageView({
37
+ src,
38
+ alt = '',
39
+ width,
40
+ height,
41
+ objectFit = 'cover',
42
+ objectPosition = 'center',
43
+ loading = 'lazy',
44
+ title,
45
+ draggable = false,
46
+ borderRadius,
47
+ showLoading = false,
48
+ showError = false,
49
+ fallbackSrc,
50
+ sizes,
51
+ srcSet,
52
+ loadingPlaceholder,
53
+ errorPlaceholder,
54
+ onClick,
55
+ style,
56
+ className,
57
+ ...restProps
58
+ }: ImageViewProps) {
59
+ const { styleProps, htmlProps, restProps: otherProps } = useBaseProps(restProps);
60
+ const theme = useTheme();
61
+
62
+ // Mark as QwickApp component
63
+ (ImageView as any)[QWICKAPP_COMPONENT] = true;
64
+
65
+ const [imageState, setImageState] = useState<'loading' | 'loaded' | 'error'>('loading');
66
+ const [currentSrc, setCurrentSrc] = useState(src);
67
+
68
+ const handleLoad = useCallback(() => {
69
+ setImageState('loaded');
70
+ }, []);
71
+
72
+ const handleError = useCallback(() => {
73
+ // Try fallback source if available and not already using it
74
+ if (fallbackSrc && currentSrc !== fallbackSrc) {
75
+ setCurrentSrc(fallbackSrc);
76
+ return;
77
+ }
78
+ setImageState('error');
79
+ }, [fallbackSrc, currentSrc]);
80
+
81
+ // Early return if no src provided
82
+ if (!src) {
83
+ if (showError) {
84
+ return (
85
+ <Box
86
+ {...htmlProps}
87
+ {...styleProps}
88
+ {...otherProps}
89
+ className={`image-error ${className || ''}`.trim()}
90
+ style={{
91
+ display: 'flex',
92
+ alignItems: 'center',
93
+ justifyContent: 'center',
94
+ backgroundColor: theme.palette.grey[100],
95
+ color: theme.palette.text.secondary,
96
+ width: width || 200,
97
+ height: height || 150,
98
+ borderRadius,
99
+ ...style
100
+ }}
101
+ >
102
+ {errorPlaceholder || (
103
+ <Box sx={{ textAlign: 'center' }}>
104
+ <BrokenImageIcon sx={{ fontSize: 48, mb: 1, opacity: 0.5 }} />
105
+ <Typography variant="body2" color="text.secondary">
106
+ No image source
107
+ </Typography>
108
+ </Box>
109
+ )}
110
+ </Box>
111
+ );
112
+ }
113
+ return null;
114
+ }
115
+
116
+ // Loading state
117
+ if (imageState === 'loading' && showLoading) {
118
+ return (
119
+ <Box
120
+ {...htmlProps}
121
+ {...styleProps}
122
+ {...otherProps}
123
+ className={`image-loading ${className || ''}`.trim()}
124
+ style={{
125
+ width: width || '100%',
126
+ height: height || 200,
127
+ borderRadius,
128
+ ...style
129
+ }}
130
+ >
131
+ {loadingPlaceholder || (
132
+ <Skeleton
133
+ variant="rectangular"
134
+ width="100%"
135
+ height="100%"
136
+ sx={{ borderRadius }}
137
+ />
138
+ )}
139
+ </Box>
140
+ );
141
+ }
142
+
143
+ // Error state
144
+ if (imageState === 'error' && showError) {
145
+ return (
146
+ <Box
147
+ {...htmlProps}
148
+ {...styleProps}
149
+ {...otherProps}
150
+ className={`image-error ${className || ''}`.trim()}
151
+ style={{
152
+ display: 'flex',
153
+ alignItems: 'center',
154
+ justifyContent: 'center',
155
+ backgroundColor: theme.palette.grey[100],
156
+ color: theme.palette.text.secondary,
157
+ width: width || '100%',
158
+ height: height || 200,
159
+ borderRadius,
160
+ ...style
161
+ }}
162
+ >
163
+ {errorPlaceholder || (
164
+ <Box sx={{ textAlign: 'center', p: 2 }}>
165
+ <BrokenImageIcon sx={{ fontSize: 48, mb: 1, opacity: 0.5 }} />
166
+ <Typography variant="body2" color="text.secondary">
167
+ Failed to load image
168
+ </Typography>
169
+ {fallbackSrc && (
170
+ <Typography variant="caption" color="text.secondary">
171
+ Fallback image also failed
172
+ </Typography>
173
+ )}
174
+ </Box>
175
+ )}
176
+ </Box>
177
+ );
178
+ }
179
+
180
+ const imageStyles: React.CSSProperties = {
181
+ display: 'block',
182
+ maxWidth: '100%',
183
+ height: 'auto',
184
+ objectFit,
185
+ objectPosition,
186
+ borderRadius,
187
+ cursor: onClick ? 'pointer' : 'default',
188
+ ...style
189
+ };
190
+
191
+ // Apply dimensions if provided
192
+ if (width) imageStyles.width = width;
193
+ if (height) imageStyles.height = height;
194
+
195
+ return (
196
+ <img
197
+ {...htmlProps}
198
+ {...styleProps}
199
+ {...otherProps}
200
+ src={currentSrc}
201
+ alt={alt}
202
+ width={width}
203
+ height={height}
204
+ loading={loading}
205
+ title={title}
206
+ draggable={draggable}
207
+ sizes={sizes}
208
+ srcSet={srcSet}
209
+ className={`image ${className || ''}`.trim()}
210
+ style={imageStyles}
211
+ onClick={onClick}
212
+ onLoad={handleLoad}
213
+ onError={handleError}
214
+ />
215
+ );
216
+ }
217
+
218
+ // Main component with data binding support and serialization capability
219
+ export class Image extends ModelView<ImageProps, ImageModel> {
220
+ // Component self-declaration for serialization
221
+ static readonly tagName = 'Image';
222
+ static readonly version = '1.0.0';
223
+
224
+ // Deserialization: JSON data → React element
225
+ static fromJson(jsonData: any): ReactElement {
226
+ return <Image {...jsonData} />;
227
+ }
228
+
229
+ // Component-specific serialization properties
230
+ protected getComponentSpecificProps(): any {
231
+ return {
232
+ src: this.props.src,
233
+ alt: this.props.alt,
234
+ width: this.props.width,
235
+ height: this.props.height,
236
+ objectFit: this.props.objectFit,
237
+ objectPosition: this.props.objectPosition,
238
+ loading: this.props.loading,
239
+ title: this.props.title,
240
+ draggable: this.props.draggable,
241
+ borderRadius: this.props.borderRadius,
242
+ showLoading: this.props.showLoading,
243
+ showError: this.props.showError,
244
+ fallbackSrc: this.props.fallbackSrc,
245
+ sizes: this.props.sizes,
246
+ srcSet: this.props.srcSet,
247
+ // Note: loadingPlaceholder and errorPlaceholder are ReactNodes
248
+ // and will be handled by base serialization if needed
249
+ };
250
+ }
251
+
252
+ // Image component renders traditional props view
253
+ protected renderView(): React.ReactElement {
254
+ const { dataSource, bindingOptions, ...restProps } = this.props;
255
+ return <ImageView {...restProps} />;
256
+ }
257
+
258
+ // Image component renders data-bound view
259
+ protected renderWithDataBinding(): React.ReactElement {
260
+ return <ImageWithDataBinding {...this.props} />;
261
+ }
262
+
263
+ // Register HTML patterns that Image component can handle
264
+ static registerPatternHandlers(registry: any): void {
265
+ // Register img elements
266
+ if (!registry.hasPattern('img')) {
267
+ registry.registerPattern('img', Image.transformImage);
268
+ }
269
+
270
+ // Register figure elements with img
271
+ if (!registry.hasPattern('figure img')) {
272
+ registry.registerPattern('figure img', Image.transformFigureImage);
273
+ }
274
+ }
275
+
276
+ // Transform img elements to Image component
277
+ private static transformImage(element: Element): any {
278
+ const src = element.getAttribute('src') || '';
279
+ const alt = element.getAttribute('alt') || '';
280
+ const width = element.getAttribute('width');
281
+ const height = element.getAttribute('height');
282
+ const loading = element.getAttribute('loading') as 'lazy' | 'eager' | undefined;
283
+
284
+ return {
285
+ tagName: 'Image',
286
+ props: {
287
+ src,
288
+ alt,
289
+ width: width ? parseInt(width) : undefined,
290
+ height: height ? parseInt(height) : undefined,
291
+ loading: loading || 'lazy'
292
+ }
293
+ };
294
+ }
295
+
296
+ // Transform figure > img elements to Image component with caption support
297
+ private static transformFigureImage(element: Element): any {
298
+ const figure = element.closest('figure');
299
+ const figcaption = figure?.querySelector('figcaption');
300
+ const caption = figcaption?.textContent || '';
301
+
302
+ const src = element.getAttribute('src') || '';
303
+ const alt = element.getAttribute('alt') || caption;
304
+ const width = element.getAttribute('width');
305
+ const height = element.getAttribute('height');
306
+
307
+ return {
308
+ tagName: 'Image',
309
+ props: {
310
+ src,
311
+ alt,
312
+ caption: caption || undefined,
313
+ width: width ? parseInt(width) : undefined,
314
+ height: height ? parseInt(height) : undefined,
315
+ loading: 'lazy'
316
+ }
317
+ };
318
+ }
319
+ }
320
+
321
+ // Helper component to handle data binding with hooks (since we can't use hooks in class components)
322
+ function ImageWithDataBinding(props: ImageProps) {
323
+ const { dataSource, bindingOptions, ...restProps } = props;
324
+
325
+ // Use data binding
326
+ const { dataSource: _source, loading, error, cached, ...rawImageProps } = useDataBinding<ImageModel>(
327
+ dataSource!,
328
+ restProps as Partial<ImageModel>,
329
+ ImageModel.getSchema(),
330
+ { cache: true, cacheTTL: 300000, strict: false, ...bindingOptions }
331
+ );
332
+
333
+ // Use props directly since new serialization system handles component-level transformation
334
+ const imageProps = rawImageProps;
335
+
336
+ // Show loading state
337
+ if (loading) {
338
+ return (
339
+ <Box
340
+ sx={{
341
+ display: 'flex',
342
+ alignItems: 'center',
343
+ justifyContent: 'center',
344
+ backgroundColor: 'grey.100',
345
+ width: imageProps.width || 200,
346
+ height: imageProps.height || 150,
347
+ borderRadius: imageProps.borderRadius
348
+ }}
349
+ >
350
+ <Skeleton
351
+ variant="rectangular"
352
+ width="100%"
353
+ height="100%"
354
+ sx={{ borderRadius: imageProps.borderRadius }}
355
+ />
356
+ </Box>
357
+ );
358
+ }
359
+
360
+ if (error) {
361
+ console.error('Error loading image:', error);
362
+ if (process.env.NODE_ENV !== 'production') {
363
+ return (
364
+ <Box
365
+ sx={{
366
+ display: 'flex',
367
+ flexDirection: 'column',
368
+ alignItems: 'center',
369
+ justifyContent: 'center',
370
+ backgroundColor: 'error.light',
371
+ color: 'error.contrastText',
372
+ p: 2,
373
+ borderRadius: imageProps.borderRadius,
374
+ width: imageProps.width || 200,
375
+ height: imageProps.height || 150
376
+ }}
377
+ >
378
+ <BrokenImageIcon sx={{ fontSize: 48, mb: 1 }} />
379
+ <Typography variant="body2" align="center">
380
+ Error Loading Image
381
+ </Typography>
382
+ <Typography variant="caption" align="center">
383
+ {error.message}
384
+ </Typography>
385
+ </Box>
386
+ );
387
+ }
388
+ return null;
389
+ }
390
+
391
+ console.log('Resolved props for Image:', imageProps);
392
+ return <ImageView {...imageProps} />;
393
+ }
394
+
395
+ export default Image;
@@ -397,18 +397,20 @@ function PageBannerHeader(props: PageBannerHeaderProps) {
397
397
  );
398
398
 
399
399
  // Convert HeaderActionModel[] to HeaderAction[] by adding default onClick handlers
400
- const convertedActions: HeaderAction[] | undefined = modelProps.actions?.map(action => ({
401
- id: action.id,
402
- label: action.label,
403
- icon: action.icon as React.ReactNode,
404
- disabled: action.disabled,
405
- destructive: action.destructive,
406
- priority: action.priority,
407
- onClick: () => {
408
- console.log(`Action clicked: ${action.id} - ${action.label}`);
409
- // In a real app, this would dispatch events or call configured handlers
410
- }
411
- }));
400
+ const convertedActions: HeaderAction[] | undefined = modelProps.actions
401
+ ?.filter((action): action is typeof action & { id: string } => typeof action.id === 'string')
402
+ .map(action => ({
403
+ id: action.id,
404
+ label: action.label,
405
+ icon: action.icon as React.ReactNode,
406
+ disabled: action.disabled,
407
+ destructive: action.destructive,
408
+ priority: action.priority,
409
+ onClick: () => {
410
+ console.log(`Action clicked: ${action.id} - ${action.label}`);
411
+ // In a real app, this would dispatch events or call configured handlers
412
+ }
413
+ }));
412
414
 
413
415
  const pageBannerHeaderProps: PageBannerHeaderViewProps = {
414
416
  ...modelProps,
@@ -380,7 +380,7 @@ function ProductCardView({
380
380
  <Button
381
381
  key={action.id}
382
382
  variant={action.variant || 'contained'}
383
- color={action.color || 'primary'}
383
+ // color={action.color || 'primary'}
384
384
  disabled={action.disabled}
385
385
  onClick={action.onClick}
386
386
  {...(variant === 'compact' && { fullWidth: true })}