@qwickapps/react-framework 1.5.6 → 1.5.8

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 (121) hide show
  1. package/dist/components/AccessibilityChecker.d.ts.map +1 -1
  2. package/dist/components/Html.d.ts +1 -1
  3. package/dist/components/Html.d.ts.map +1 -1
  4. package/dist/components/Logo.d.ts.map +1 -1
  5. package/dist/components/Markdown.d.ts +2 -2
  6. package/dist/components/Markdown.d.ts.map +1 -1
  7. package/dist/components/QwickApp.d.ts.map +1 -1
  8. package/dist/components/SafeSpan.d.ts +1 -1
  9. package/dist/components/SafeSpan.d.ts.map +1 -1
  10. package/dist/components/base/ModelView.d.ts +1 -1
  11. package/dist/components/base/ModelView.d.ts.map +1 -1
  12. package/dist/components/blocks/Article.d.ts +1 -1
  13. package/dist/components/blocks/Article.d.ts.map +1 -1
  14. package/dist/components/blocks/CardListGrid.d.ts.map +1 -1
  15. package/dist/components/blocks/Code.d.ts.map +1 -1
  16. package/dist/components/blocks/Content.d.ts.map +1 -1
  17. package/dist/components/blocks/CoverImageHeader.d.ts.map +1 -1
  18. package/dist/components/blocks/FeatureCard.d.ts.map +1 -1
  19. package/dist/components/blocks/FeatureGrid.d.ts.map +1 -1
  20. package/dist/components/blocks/Footer.d.ts.map +1 -1
  21. package/dist/components/blocks/Image.d.ts.map +1 -1
  22. package/dist/components/blocks/PageBannerHeader.d.ts.map +1 -1
  23. package/dist/components/blocks/ProductCard.d.ts.map +1 -1
  24. package/dist/components/blocks/Section.d.ts.map +1 -1
  25. package/dist/components/blocks/Text.d.ts +8 -1
  26. package/dist/components/blocks/Text.d.ts.map +1 -1
  27. package/dist/components/buttons/Button.d.ts.map +1 -1
  28. package/dist/components/buttons/PaletteSwitcher.d.ts.map +1 -1
  29. package/dist/components/buttons/ThemeSwitcher.d.ts.map +1 -1
  30. package/dist/components/forms/FormBlock.d.ts +1 -1
  31. package/dist/components/forms/FormBlock.d.ts.map +1 -1
  32. package/dist/components/forms/SchemaFormRenderer.d.ts +28 -0
  33. package/dist/components/forms/SchemaFormRenderer.d.ts.map +1 -0
  34. package/dist/components/forms/index.d.ts +2 -0
  35. package/dist/components/forms/index.d.ts.map +1 -1
  36. package/dist/components/index.d.ts +1 -0
  37. package/dist/components/index.d.ts.map +1 -1
  38. package/dist/components/input/ChoiceInputField.d.ts.map +1 -1
  39. package/dist/components/input/HtmlInputField.d.ts.map +1 -1
  40. package/dist/components/layout/CollapsibleLayout/CollapsibleLayout.d.ts.map +1 -1
  41. package/dist/components/layout/GridLayout.d.ts +5 -0
  42. package/dist/components/layout/GridLayout.d.ts.map +1 -1
  43. package/dist/components/plugins/DataTable.d.ts +57 -0
  44. package/dist/components/plugins/DataTable.d.ts.map +1 -0
  45. package/dist/components/plugins/StatCard.d.ts +44 -0
  46. package/dist/components/plugins/StatCard.d.ts.map +1 -0
  47. package/dist/components/plugins/index.d.ts +13 -0
  48. package/dist/components/plugins/index.d.ts.map +1 -0
  49. package/dist/components/shared/createSerializableView.d.ts.map +1 -1
  50. package/dist/contexts/NavigationContext.d.ts.map +1 -1
  51. package/dist/hooks/useBaseProps.d.ts +1 -1
  52. package/dist/hooks/useBaseProps.d.ts.map +1 -1
  53. package/dist/index.esm.js +5939 -5532
  54. package/dist/index.js +6028 -5618
  55. package/dist/palettes/manifest.json +19 -19
  56. package/dist/schemas/transformers/ReactNodeTransformer.d.ts.map +1 -1
  57. package/dist/utils/iconMap.d.ts +21 -8
  58. package/dist/utils/iconMap.d.ts.map +1 -1
  59. package/package.json +1 -2
  60. package/src/__tests__/utils/iconMap.test.tsx +197 -0
  61. package/src/components/AccessibilityChecker.tsx +10 -7
  62. package/src/components/ErrorBoundary.tsx +3 -3
  63. package/src/components/Html.tsx +17 -12
  64. package/src/components/Logo.tsx +1 -8
  65. package/src/components/Markdown.tsx +10 -10
  66. package/src/components/QwickApp.tsx +8 -1
  67. package/src/components/ResponsiveMenu.tsx +1 -1
  68. package/src/components/SafeSpan.tsx +9 -9
  69. package/src/components/Scaffold.tsx +4 -4
  70. package/src/components/base/ModelView.tsx +2 -2
  71. package/src/components/blocks/Article.tsx +7 -7
  72. package/src/components/blocks/CardListGrid.tsx +1 -3
  73. package/src/components/blocks/Code.tsx +10 -8
  74. package/src/components/blocks/Content.tsx +2 -4
  75. package/src/components/blocks/CoverImageHeader.tsx +3 -4
  76. package/src/components/blocks/FeatureCard.tsx +2 -4
  77. package/src/components/blocks/FeatureGrid.tsx +2 -4
  78. package/src/components/blocks/Footer.tsx +2 -4
  79. package/src/components/blocks/Image.tsx +8 -5
  80. package/src/components/blocks/PageBannerHeader.tsx +3 -4
  81. package/src/components/blocks/ProductCard.tsx +8 -5
  82. package/src/components/blocks/Section.tsx +6 -4
  83. package/src/components/blocks/Text.tsx +15 -7
  84. package/src/components/buttons/Button.tsx +8 -6
  85. package/src/components/buttons/PaletteSwitcher.tsx +6 -8
  86. package/src/components/buttons/ThemeSwitcher.tsx +8 -9
  87. package/src/components/forms/Captcha.tsx +1 -1
  88. package/src/components/forms/FormBlock.tsx +3 -5
  89. package/src/components/forms/FormCheckbox.tsx +1 -1
  90. package/src/components/forms/FormField.tsx +1 -1
  91. package/src/components/forms/FormSelect.tsx +1 -1
  92. package/src/components/forms/SchemaFormRenderer.tsx +268 -0
  93. package/src/components/forms/__tests__/SchemaFormRenderer.test.tsx +212 -0
  94. package/src/components/forms/index.ts +3 -0
  95. package/src/components/index.ts +1 -0
  96. package/src/components/input/ChoiceInputField.tsx +2 -1
  97. package/src/components/input/HtmlInputField.tsx +14 -9
  98. package/src/components/input/TextField.tsx +1 -1
  99. package/src/components/layout/CollapsibleLayout/CollapsibleLayout.tsx +6 -8
  100. package/src/components/layout/GridLayout.tsx +4 -0
  101. package/src/components/plugins/DataTable.tsx +259 -0
  102. package/src/components/plugins/StatCard.tsx +122 -0
  103. package/src/components/plugins/__tests__/DataTable.test.tsx +158 -0
  104. package/src/components/plugins/index.ts +14 -0
  105. package/src/components/shared/createSerializableView.tsx +8 -6
  106. package/src/contexts/NavigationContext.tsx +21 -15
  107. package/src/hooks/useBaseProps.ts +1 -1
  108. package/src/schemas/transformers/ReactNodeTransformer.ts +13 -10
  109. package/src/utils/iconMap.tsx +290 -174
  110. /package/dist/palettes/{palette-autumn.1.5.6.css → palette-autumn.1.5.8.css} +0 -0
  111. /package/dist/palettes/{palette-autumn.1.5.6.min.css → palette-autumn.1.5.8.min.css} +0 -0
  112. /package/dist/palettes/{palette-cosmic.1.5.6.css → palette-cosmic.1.5.8.css} +0 -0
  113. /package/dist/palettes/{palette-cosmic.1.5.6.min.css → palette-cosmic.1.5.8.min.css} +0 -0
  114. /package/dist/palettes/{palette-default.1.5.6.css → palette-default.1.5.8.css} +0 -0
  115. /package/dist/palettes/{palette-default.1.5.6.min.css → palette-default.1.5.8.min.css} +0 -0
  116. /package/dist/palettes/{palette-ocean.1.5.6.css → palette-ocean.1.5.8.css} +0 -0
  117. /package/dist/palettes/{palette-ocean.1.5.6.min.css → palette-ocean.1.5.8.min.css} +0 -0
  118. /package/dist/palettes/{palette-spring.1.5.6.css → palette-spring.1.5.8.css} +0 -0
  119. /package/dist/palettes/{palette-spring.1.5.6.min.css → palette-spring.1.5.8.min.css} +0 -0
  120. /package/dist/palettes/{palette-winter.1.5.6.css → palette-winter.1.5.8.css} +0 -0
  121. /package/dist/palettes/{palette-winter.1.5.6.min.css → palette-winter.1.5.8.min.css} +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "./manifest.schema.json",
3
- "version": "1.5.6",
3
+ "version": "1.5.8",
4
4
  "palettes": [
5
5
  {
6
6
  "id": "default",
@@ -8,11 +8,11 @@
8
8
  "description": "Classic blue and neutral color scheme - the original QwickApps palette",
9
9
  "author": "QwickApps",
10
10
  "license": "PolyForm-Shield-1.0.0",
11
- "version": "1.5.6",
12
- "file": "palette-default.1.5.6.css",
11
+ "version": "1.5.8",
12
+ "file": "palette-default.1.5.8.css",
13
13
  "primaryColor": "#007bff",
14
14
  "inlined": true,
15
- "fileMinified": "palette-default.1.5.6.min.css",
15
+ "fileMinified": "palette-default.1.5.8.min.css",
16
16
  "fileLatest": "palette-default.latest.css",
17
17
  "fileLatestMinified": "palette-default.latest.min.css"
18
18
  },
@@ -22,11 +22,11 @@
22
22
  "description": "Warm oranges, golden yellows, and earthy browns - inspired by fall foliage",
23
23
  "author": "QwickApps",
24
24
  "license": "PolyForm-Shield-1.0.0",
25
- "version": "1.5.6",
26
- "file": "palette-autumn.1.5.6.css",
25
+ "version": "1.5.8",
26
+ "file": "palette-autumn.1.5.8.css",
27
27
  "primaryColor": "#ea580c",
28
28
  "inlined": false,
29
- "fileMinified": "palette-autumn.1.5.6.min.css",
29
+ "fileMinified": "palette-autumn.1.5.8.min.css",
30
30
  "fileLatest": "palette-autumn.latest.css",
31
31
  "fileLatestMinified": "palette-autumn.latest.min.css"
32
32
  },
@@ -36,11 +36,11 @@
36
36
  "description": "Modern purple gradient for creative and tech brands - inspired by cosmic nebulae",
37
37
  "author": "QwickApps",
38
38
  "license": "PolyForm-Shield-1.0.0",
39
- "version": "1.5.6",
40
- "file": "palette-cosmic.1.5.6.css",
39
+ "version": "1.5.8",
40
+ "file": "palette-cosmic.1.5.8.css",
41
41
  "primaryColor": "#8b5cf6",
42
42
  "inlined": false,
43
- "fileMinified": "palette-cosmic.1.5.6.min.css",
43
+ "fileMinified": "palette-cosmic.1.5.8.min.css",
44
44
  "fileLatest": "palette-cosmic.latest.css",
45
45
  "fileLatestMinified": "palette-cosmic.latest.min.css"
46
46
  },
@@ -50,11 +50,11 @@
50
50
  "description": "Deep blues, aqua teals, and seafoam greens - inspired by ocean depths",
51
51
  "author": "QwickApps",
52
52
  "license": "PolyForm-Shield-1.0.0",
53
- "version": "1.5.6",
54
- "file": "palette-ocean.1.5.6.css",
53
+ "version": "1.5.8",
54
+ "file": "palette-ocean.1.5.8.css",
55
55
  "primaryColor": "#0891b2",
56
56
  "inlined": false,
57
- "fileMinified": "palette-ocean.1.5.6.min.css",
57
+ "fileMinified": "palette-ocean.1.5.8.min.css",
58
58
  "fileLatest": "palette-ocean.latest.css",
59
59
  "fileLatestMinified": "palette-ocean.latest.min.css"
60
60
  },
@@ -64,11 +64,11 @@
64
64
  "description": "Fresh greens, soft pinks, and bright yellows - inspired by spring blooms",
65
65
  "author": "QwickApps",
66
66
  "license": "PolyForm-Shield-1.0.0",
67
- "version": "1.5.6",
68
- "file": "palette-spring.1.5.6.css",
67
+ "version": "1.5.8",
68
+ "file": "palette-spring.1.5.8.css",
69
69
  "primaryColor": "#16a34a",
70
70
  "inlined": false,
71
- "fileMinified": "palette-spring.1.5.6.min.css",
71
+ "fileMinified": "palette-spring.1.5.8.min.css",
72
72
  "fileLatest": "palette-spring.latest.css",
73
73
  "fileLatestMinified": "palette-spring.latest.min.css"
74
74
  },
@@ -78,11 +78,11 @@
78
78
  "description": "Cool blues, icy whites, and frosty grays - inspired by winter landscapes",
79
79
  "author": "QwickApps",
80
80
  "license": "PolyForm-Shield-1.0.0",
81
- "version": "1.5.6",
82
- "file": "palette-winter.1.5.6.css",
81
+ "version": "1.5.8",
82
+ "file": "palette-winter.1.5.8.css",
83
83
  "primaryColor": "#0077be",
84
84
  "inlined": false,
85
- "fileMinified": "palette-winter.1.5.6.min.css",
85
+ "fileMinified": "palette-winter.1.5.8.min.css",
86
86
  "fileLatest": "palette-winter.latest.css",
87
87
  "fileLatestMinified": "palette-winter.latest.min.css"
88
88
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ReactNodeTransformer.d.ts","sourceRoot":"","sources":["../../../src/schemas/transformers/ReactNodeTransformer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAgB,SAAS,EAAiC,MAAM,OAAO,CAAC;AAG/E;;;GAGG;AACH,qBAAa,oBAAoB;IAC/B;;;;OAIG;IACH,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO;IAoE1C;;;;OAIG;IACH,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,SAAS;IA4C5C;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,cAAc;IAqB7B;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,uBAAuB;IAmBtC;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAkB/B;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;CAqBlC"}
1
+ {"version":3,"file":"ReactNodeTransformer.d.ts","sourceRoot":"","sources":["../../../src/schemas/transformers/ReactNodeTransformer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAgB,SAAS,EAAiC,MAAM,OAAO,CAAC;AAG/E;;;GAGG;AACH,qBAAa,oBAAoB;IAC/B;;;;OAIG;IACH,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO;IAoE1C;;;;OAIG;IACH,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,SAAS;IA4C5C;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,cAAc;IAqB7B;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,uBAAuB;IAoBtC;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAkB/B;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;CAuBlC"}
@@ -4,6 +4,11 @@
4
4
  * Provides centralized icon mapping for both Material-UI components and emoji representations.
5
5
  * Used across the framework for consistent icon rendering in buttons, navigation, admin UI, etc.
6
6
  *
7
+ * Features:
8
+ * - Static map for commonly used icons with emoji support
9
+ * - Fallback to HelpOutline icon for unmapped icons (with console warning)
10
+ * - Runtime icon registration via registerIcon() for app-specific icons
11
+ *
7
12
  * Copyright (c) 2025 QwickApps.com. All rights reserved.
8
13
  */
9
14
  import React from 'react';
@@ -15,21 +20,28 @@ export interface IconMapping {
15
20
  component: React.ComponentType;
16
21
  }
17
22
  /**
18
- * Centralized icon registry mapping icon names to their representations
19
- * Supports both Material-UI components and emoji for different contexts
23
+ * Centralized icon registry mapping icon names to their representations.
24
+ * Sorted alphabetically by category, then by key within each category.
25
+ *
26
+ * For icons not in this map, getIconComponent() will return a HelpOutline fallback
27
+ * and log a warning. Use registerIcon() to add app-specific icons at runtime.
20
28
  */
21
29
  export declare const iconMap: Record<string, IconMapping>;
22
30
  /**
23
31
  * Get emoji representation of an icon
24
- * @param iconName - Icon name (case-insensitive)
32
+ * @param iconName - Icon name (case-insensitive, supports snake_case)
25
33
  * @param fallback - Fallback emoji if icon not found (default: 🔗)
26
34
  * @returns Emoji string
27
35
  */
28
36
  export declare function getIconEmoji(iconName: string | undefined, fallback?: string): string;
29
37
  /**
30
- * Get Material-UI component representation of an icon
31
- * @param iconName - Icon name (case-insensitive)
32
- * @returns React element or null if not found
38
+ * Get Material-UI component representation of an icon.
39
+ *
40
+ * Uses the static iconMap for known icons. For unmapped icons,
41
+ * returns a HelpOutline fallback and logs a warning.
42
+ *
43
+ * @param iconName - Icon name (case-insensitive, supports snake_case)
44
+ * @returns React element (mapped icon or HelpOutline fallback), or null if no name provided
33
45
  */
34
46
  export declare function getIconComponent(iconName: string | undefined): React.ReactElement | null;
35
47
  /**
@@ -38,11 +50,12 @@ export declare function getIconComponent(iconName: string | undefined): React.Re
38
50
  */
39
51
  export declare function registerIcon(name: string, mapping: IconMapping): void;
40
52
  /**
41
- * Check if an icon is registered
53
+ * Check if an icon is registered in the static map
54
+ * If false, getIconComponent will return HelpOutline fallback
42
55
  */
43
56
  export declare function hasIcon(iconName: string): boolean;
44
57
  /**
45
- * Get all registered icon names
58
+ * Get all registered icon names from the static map
46
59
  */
47
60
  export declare function getRegisteredIcons(): string[];
48
61
  //# sourceMappingURL=iconMap.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"iconMap.d.ts","sourceRoot":"","sources":["../../src/utils/iconMap.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAuE1B;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC;CAChC;AAED;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAqG/C,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,GAAE,MAAa,GAAG,MAAM,CAI1F;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAWxF;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAErE;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAE7C"}
1
+ {"version":3,"file":"iconMap.d.ts","sourceRoot":"","sources":["../../src/utils/iconMap.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAyG1B;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC;CAChC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAmK/C,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,GAAE,MAAa,GAAG,MAAM,CAK1F;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAiBxF;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAErE;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAE7C"}
package/package.json CHANGED
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "name": "@qwickapps/react-framework",
3
- "version": "1.5.6",
4
- "type": "module",
3
+ "version": "1.5.8",
5
4
  "description": "Complete React framework with responsive navigation, flexible layouts, theming system, and reusable components for building modern applications.",
6
5
  "main": "dist/index.js",
7
6
  "module": "dist/index.esm.js",
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Icon Map Tests
3
+ *
4
+ * Tests for the icon mapping utility functions.
5
+ */
6
+
7
+ import {
8
+ getIconComponent,
9
+ getIconEmoji,
10
+ hasIcon,
11
+ registerIcon,
12
+ getRegisteredIcons,
13
+ iconMap,
14
+ } from '../../utils/iconMap';
15
+ import { Home, Star } from '@mui/icons-material';
16
+
17
+ describe('iconMap', () => {
18
+ describe('getIconComponent', () => {
19
+ it('returns null for undefined input', () => {
20
+ expect(getIconComponent(undefined)).toBeNull();
21
+ });
22
+
23
+ it('returns null for empty string', () => {
24
+ expect(getIconComponent('')).toBeNull();
25
+ });
26
+
27
+ it('returns correct component for mapped icon', () => {
28
+ const result = getIconComponent('home');
29
+ expect(result).not.toBeNull();
30
+ expect(result?.type).toBe(Home);
31
+ });
32
+
33
+ it('is case-insensitive', () => {
34
+ const lower = getIconComponent('home');
35
+ const upper = getIconComponent('HOME');
36
+ const mixed = getIconComponent('Home');
37
+
38
+ expect(lower?.type).toBe(upper?.type);
39
+ expect(lower?.type).toBe(mixed?.type);
40
+ });
41
+
42
+ it('supports snake_case icon names', () => {
43
+ const result = getIconComponent('manage_accounts');
44
+ expect(result).not.toBeNull();
45
+ });
46
+
47
+ it('returns HelpOutline fallback for unmapped icons', () => {
48
+ // Suppress console.warn for this test
49
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
50
+
51
+ const result = getIconComponent('nonexistent_icon_xyz');
52
+ expect(result).not.toBeNull();
53
+
54
+ warnSpy.mockRestore();
55
+ });
56
+
57
+ it('logs warning for unmapped icons in non-production', () => {
58
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
59
+
60
+ getIconComponent('nonexistent_icon_xyz');
61
+
62
+ expect(warnSpy).toHaveBeenCalledWith(
63
+ expect.stringContaining('nonexistent_icon_xyz')
64
+ );
65
+
66
+ warnSpy.mockRestore();
67
+ });
68
+ });
69
+
70
+ describe('getIconEmoji', () => {
71
+ it('returns fallback for undefined input', () => {
72
+ expect(getIconEmoji(undefined)).toBe('🔗');
73
+ });
74
+
75
+ it('returns custom fallback when provided', () => {
76
+ expect(getIconEmoji(undefined, '❓')).toBe('❓');
77
+ });
78
+
79
+ it('returns correct emoji for mapped icon', () => {
80
+ expect(getIconEmoji('home')).toBe('🏠');
81
+ expect(getIconEmoji('favorite')).toBe('❤️');
82
+ expect(getIconEmoji('star')).toBe('⭐');
83
+ });
84
+
85
+ it('is case-insensitive', () => {
86
+ expect(getIconEmoji('HOME')).toBe(getIconEmoji('home'));
87
+ });
88
+
89
+ it('returns fallback for unmapped icons', () => {
90
+ expect(getIconEmoji('nonexistent_icon')).toBe('🔗');
91
+ });
92
+ });
93
+
94
+ describe('hasIcon', () => {
95
+ it('returns true for mapped icons', () => {
96
+ expect(hasIcon('home')).toBe(true);
97
+ expect(hasIcon('settings')).toBe(true);
98
+ expect(hasIcon('people')).toBe(true);
99
+ });
100
+
101
+ it('returns false for unmapped icons', () => {
102
+ expect(hasIcon('nonexistent_icon')).toBe(false);
103
+ });
104
+
105
+ it('is case-insensitive', () => {
106
+ expect(hasIcon('HOME')).toBe(true);
107
+ expect(hasIcon('Home')).toBe(true);
108
+ });
109
+ });
110
+
111
+ describe('registerIcon', () => {
112
+ // Cleanup any test icons after each test to prevent pollution
113
+ afterEach(() => {
114
+ delete iconMap['custom_test_icon'];
115
+ delete iconMap['upper_case_icon'];
116
+ });
117
+
118
+ it('registers new icons at runtime', () => {
119
+ const customIconName = 'custom_test_icon';
120
+
121
+ // Should not exist initially
122
+ expect(hasIcon(customIconName)).toBe(false);
123
+
124
+ // Register it
125
+ registerIcon(customIconName, {
126
+ emoji: '🎯',
127
+ component: Star,
128
+ });
129
+
130
+ // Should exist now
131
+ expect(hasIcon(customIconName)).toBe(true);
132
+ expect(getIconEmoji(customIconName)).toBe('🎯');
133
+ });
134
+
135
+ it('is case-insensitive for registration', () => {
136
+ registerIcon('UPPER_CASE_ICON', {
137
+ emoji: '🔤',
138
+ component: Star,
139
+ });
140
+
141
+ expect(hasIcon('upper_case_icon')).toBe(true);
142
+ expect(hasIcon('UPPER_CASE_ICON')).toBe(true);
143
+ });
144
+ });
145
+
146
+ describe('getRegisteredIcons', () => {
147
+ it('returns an array of icon names', () => {
148
+ const icons = getRegisteredIcons();
149
+
150
+ expect(Array.isArray(icons)).toBe(true);
151
+ expect(icons.length).toBeGreaterThan(0);
152
+ });
153
+
154
+ it('includes common icons', () => {
155
+ const icons = getRegisteredIcons();
156
+
157
+ expect(icons).toContain('home');
158
+ expect(icons).toContain('settings');
159
+ expect(icons).toContain('dashboard');
160
+ expect(icons).toContain('people');
161
+ expect(icons).toContain('manage_accounts');
162
+ });
163
+ });
164
+
165
+ describe('iconMap coverage', () => {
166
+ it('has emoji and component for all entries', () => {
167
+ for (const [name, mapping] of Object.entries(iconMap)) {
168
+ expect(mapping.emoji).toBeDefined();
169
+ expect(typeof mapping.emoji).toBe('string');
170
+ expect(mapping.emoji.length).toBeGreaterThan(0);
171
+
172
+ expect(mapping.component).toBeDefined();
173
+ // Components can be functions or objects (React.memo, forwardRef, etc.)
174
+ expect(['function', 'object']).toContain(typeof mapping.component);
175
+ }
176
+ });
177
+
178
+ it('includes essential icons for navigation', () => {
179
+ const essentialIcons = [
180
+ 'home', 'dashboard', 'settings', 'menu',
181
+ 'people', 'person', 'help', 'info',
182
+ ];
183
+
184
+ for (const icon of essentialIcons) {
185
+ expect(hasIcon(icon)).toBe(true);
186
+ }
187
+ });
188
+
189
+ it('includes authentication icons', () => {
190
+ const authIcons = ['lock', 'security', 'key', 'login', 'logout'];
191
+
192
+ for (const icon of authIcons) {
193
+ expect(hasIcon(icon)).toBe(true);
194
+ }
195
+ });
196
+ });
197
+ });
@@ -23,13 +23,16 @@ import {
23
23
  IconButton,
24
24
  Divider,
25
25
  } from '@mui/material';
26
- import {
27
- Accessibility as AccessibilityIcon,
28
- CheckCircle as CheckIcon,
29
- Warning as WarningIcon,
30
- Error as ErrorIcon,
31
- Close as CloseIcon,
32
- } from '@mui/icons-material';
26
+ import Accessibility from '@mui/icons-material/Accessibility';
27
+ import CheckCircle from '@mui/icons-material/CheckCircle';
28
+ import Warning from '@mui/icons-material/Warning';
29
+ import Error from '@mui/icons-material/Error';
30
+ import Close from '@mui/icons-material/Close';
31
+ const AccessibilityIcon = Accessibility;
32
+ const CheckIcon = CheckCircle;
33
+ const WarningIcon = Warning;
34
+ const ErrorIcon = Error;
35
+ const CloseIcon = Close;
33
36
  import { getContrastRatio } from '@mui/material';
34
37
  import { getCurrentPalette } from '../utils/paletteUtils';
35
38
  import { getCurrentTheme } from '../utils/themeUtils';
@@ -68,9 +68,9 @@ export class ErrorBoundary extends Component<Props, State> {
68
68
 
69
69
  // Send error to logging service if available
70
70
  if (typeof window !== 'undefined') {
71
- // @ts-expect-error - Global error logging service may not be defined
72
- if (window.qwickapps?.logError) {
73
- window.qwickapps.logError(error, errorInfo);
71
+ const globalWindow = window as unknown as { qwickapps?: { logError?: (error: Error, errorInfo: React.ErrorInfo) => void } };
72
+ if (globalWindow.qwickapps?.logError) {
73
+ globalWindow.qwickapps.logError(error, errorInfo);
74
74
  }
75
75
  }
76
76
  }
@@ -58,12 +58,15 @@ function HtmlView({
58
58
  stripHeaders = false,
59
59
  placeholder,
60
60
  component = 'div',
61
+ transformConfig,
62
+ sanitize,
63
+ sanitizeOptions,
61
64
  ...restProps
62
65
  }: HtmlViewProps) {
63
66
  const { styleProps, htmlProps, restProps: otherProps } = useBaseProps(restProps);
64
67
 
65
68
  // Mark as QwickApp component
66
- (HtmlView as Record<string, unknown>)[QWICKAPP_COMPONENT] = true;
69
+ Object.assign(HtmlView, { [QWICKAPP_COMPONENT]: true });
67
70
 
68
71
  // Return placeholder if no HTML content
69
72
  if (!children || !children.trim()) {
@@ -177,7 +180,7 @@ function HtmlView({
177
180
  }
178
181
 
179
182
  // Main component with data binding support and serialization capability
180
- export class Html extends ModelView<HtmlProps, HtmlModel> {
183
+ export class Html extends ModelView<HtmlProps> {
181
184
  // Component self-declaration for serialization
182
185
  static readonly tagName = 'Html';
183
186
  static readonly version = '1.0.0';
@@ -191,9 +194,11 @@ export class Html extends ModelView<HtmlProps, HtmlModel> {
191
194
  if (version !== Html.version) {
192
195
  console.warn(`Version mismatch: Expected ${Html.version} but got ${version}`);
193
196
  }
194
-
195
- const { children, ...props } = data || {};
196
- return <Html {...props} >{ComponentTransformer.deserialize(children)}</Html>;
197
+
198
+ const typedData = (data as Record<string, unknown>) || {};
199
+ const { children, ...props } = typedData;
200
+ // Children should be a string (HTML content), not deserialized React nodes
201
+ return <Html {...props}>{children as string}</Html>;
197
202
  }
198
203
 
199
204
  // Component-specific serialization properties
@@ -218,14 +223,16 @@ export class Html extends ModelView<HtmlProps, HtmlModel> {
218
223
 
219
224
  // Register HTML patterns that Html component can handle
220
225
  static registerPatternHandlers(registry: Record<string, unknown>): void {
226
+ const typedRegistry = registry as { hasPattern?: (pattern: string) => boolean; registerPattern?: (pattern: string, handler: (element: Element) => Record<string, unknown>) => void };
227
+
221
228
  // Register div elements with specific classes for Html transformation
222
- if (!registry.hasPattern('div.html-content')) {
223
- registry.registerPattern('div.html-content', Html.transformHtmlDiv);
229
+ if (typedRegistry.hasPattern && !typedRegistry.hasPattern('div.html-content')) {
230
+ typedRegistry.registerPattern?.('div.html-content', Html.transformHtmlDiv);
224
231
  }
225
232
 
226
233
  // Register elements with data-html attribute
227
- if (!registry.hasPattern('[data-html]')) {
228
- registry.registerPattern('[data-html]', Html.transformDataHtml);
234
+ if (typedRegistry.hasPattern && !typedRegistry.hasPattern('[data-html]')) {
235
+ typedRegistry.registerPattern?.('[data-html]', Html.transformDataHtml);
229
236
  }
230
237
  }
231
238
 
@@ -268,9 +275,7 @@ function HtmlWithDataBinding(props: HtmlProps) {
268
275
  // Use data binding
269
276
  const { loading, error, ...htmlProps } = useDataBinding<HtmlModel>(
270
277
  dataSource!,
271
- restProps as Partial<HtmlModel>,
272
- HtmlModel.getSchema(),
273
- { cache: true, cacheTTL: 300000, strict: false, ...bindingOptions }
278
+ restProps as Partial<HtmlModel>
274
279
  );
275
280
 
276
281
  // Show loading state
@@ -430,14 +430,7 @@ function Logo(props: LogoProps) {
430
430
  // Always call hooks unconditionally
431
431
  const bindingResult = useDataBinding<LogoModel>(
432
432
  dataSource || '',
433
- {
434
- initialData: restProps,
435
- schema: LogoModel.getSchema(),
436
- cache: true,
437
- cacheTTL: 300000,
438
- strict: false,
439
- ...bindingOptions
440
- }
433
+ restProps as Partial<LogoModel>
441
434
  );
442
435
 
443
436
  // If no dataSource, use traditional props
@@ -35,7 +35,7 @@ type MarkdownViewProps = SchemaProps<MarkdownModel> & {
35
35
  /** Custom transformation configuration for HTML conversion */
36
36
  htmlTransformConfig?: TransformConfig;
37
37
  /** Custom sanitization options */
38
- sanitizeOptions?: unknown;
38
+ sanitizeOptions?: Record<string, unknown>;
39
39
  /** Container element type */
40
40
  component?: React.ElementType;
41
41
  /** Marked options for Markdown parsing */
@@ -78,7 +78,7 @@ function MarkdownView({
78
78
  const { styleProps, htmlProps, restProps: otherProps } = useBaseProps(restProps);
79
79
 
80
80
  // Mark as QwickApp component
81
- (MarkdownView as Record<string, unknown>)[QWICKAPP_COMPONENT] = true;
81
+ Object.assign(MarkdownView, { [QWICKAPP_COMPONENT]: true });
82
82
 
83
83
  // Return placeholder if no Markdown content
84
84
  if (!children || !children.trim()) {
@@ -186,7 +186,7 @@ function MarkdownView({
186
186
  }
187
187
 
188
188
  // Main component with data binding support and serialization capability
189
- export class Markdown extends ModelView<MarkdownProps, MarkdownModel> {
189
+ export class Markdown extends ModelView<MarkdownProps> {
190
190
  // Component self-declaration for serialization
191
191
  static readonly tagName = 'Markdown';
192
192
  static readonly version = '1.0.0';
@@ -213,14 +213,16 @@ export class Markdown extends ModelView<MarkdownProps, MarkdownModel> {
213
213
 
214
214
  // Register HTML patterns that Markdown component can handle
215
215
  static registerPatternHandlers(registry: unknown): void {
216
+ const typedRegistry = registry as { hasPattern?: (pattern: string) => boolean; registerPattern?: (pattern: string, handler: (element: Element) => Record<string, unknown>) => void };
217
+
216
218
  // Register div elements with specific classes for Markdown transformation
217
- if (!registry.hasPattern('div.markdown-content')) {
218
- registry.registerPattern('div.markdown-content', Markdown.transformMarkdownDiv);
219
+ if (typedRegistry.hasPattern && !typedRegistry.hasPattern('div.markdown-content')) {
220
+ typedRegistry.registerPattern?.('div.markdown-content', Markdown.transformMarkdownDiv);
219
221
  }
220
222
 
221
223
  // Register elements with data-markdown attribute
222
- if (!registry.hasPattern('[data-markdown]')) {
223
- registry.registerPattern('[data-markdown]', Markdown.transformDataMarkdown);
224
+ if (typedRegistry.hasPattern && !typedRegistry.hasPattern('[data-markdown]')) {
225
+ typedRegistry.registerPattern?.('[data-markdown]', Markdown.transformDataMarkdown);
224
226
  }
225
227
  }
226
228
 
@@ -263,9 +265,7 @@ function MarkdownWithDataBinding(props: MarkdownProps) {
263
265
  // Use data binding
264
266
  const { loading, error, ...markdownProps } = useDataBinding<MarkdownModel>(
265
267
  dataSource!,
266
- restProps as Partial<MarkdownModel>,
267
- MarkdownModel.getSchema(),
268
- { cache: true, cacheTTL: 300000, strict: false, ...bindingOptions }
268
+ restProps as Partial<MarkdownModel>
269
269
  );
270
270
 
271
271
  // Show loading state
@@ -34,7 +34,7 @@
34
34
  *
35
35
  * Copyright (c) 2025 QwickApps.com. All rights reserved.
36
36
  */
37
- import React, { cloneElement, useState } from 'react';
37
+ import React, { cloneElement, useState, useEffect } from 'react';
38
38
  import { DataProvider, ThemeProvider, PrintModeProvider, NavigationProvider, type ThemeMode } from '../contexts';
39
39
  import { QwickAppContext, type QwickAppContextValue, type QwickAppProps } from '../contexts/QwickAppContext';
40
40
  import { type TemplateResolverConfig } from '../types';
@@ -126,6 +126,13 @@ export const QwickApp: React.FC<QwickAppComponentProps> = ({
126
126
  setAppConfig(prev => ({ ...prev, ...updates } as typeof prev));
127
127
  };
128
128
 
129
+ // Sync logo prop changes with internal state (for dynamic logo updates)
130
+ useEffect(() => {
131
+ if (resolvedConfig.logo !== appConfig.logo) {
132
+ setAppConfig(prev => ({ ...prev, logo: resolvedConfig.logo }));
133
+ }
134
+ }, [resolvedConfig.logo]);
135
+
129
136
  const contextValue: QwickAppContextValue = {
130
137
  appName: resolvedConfig.appName!, // Safe to use ! since we validated above
131
138
  appId: resolvedConfig.appId,
@@ -57,7 +57,7 @@ const ResponsiveMenu: React.FC<ResponsiveMenuProps> = (props) => {
57
57
  } = restProps;
58
58
 
59
59
  // Mark as QwickApp component
60
- (ResponsiveMenu as Record<string, unknown>)[QWICKAPP_COMPONENT] = true;
60
+ Object.assign(ResponsiveMenu, { [QWICKAPP_COMPONENT]: true });
61
61
  const [screenSize, setScreenSize] = useState<ScreenSize>('desktop');
62
62
  const [isNavRailExpanded, setIsNavRailExpanded] = useState(false);
63
63
  const [isDrawerOpen, setIsDrawerOpen] = useState(false);
@@ -26,7 +26,7 @@ function SafeSpanView(props: SafeSpanViewProps) {
26
26
  const { styleProps, htmlProps } = useBaseProps(restProps);
27
27
 
28
28
  // Mark as QwickApp component
29
- (SafeSpanView as Record<string, unknown>)[QWICKAPP_COMPONENT] = true;
29
+ Object.assign(SafeSpanView, { [QWICKAPP_COMPONENT]: true });
30
30
 
31
31
  // Enhanced HTML sanitization with strict security configuration
32
32
  const sanitizeOptions = {
@@ -95,7 +95,7 @@ function SafeSpanView(props: SafeSpanViewProps) {
95
95
  }
96
96
 
97
97
  // Main component with data binding support and serialization capability
98
- export class SafeSpan extends ModelView<SafeSpanProps, SafeSpanModel> {
98
+ export class SafeSpan extends ModelView<SafeSpanProps> {
99
99
  // Component self-declaration for serialization
100
100
  static readonly tagName = 'SafeSpan';
101
101
  static readonly version = '1.0.0';
@@ -121,14 +121,16 @@ export class SafeSpan extends ModelView<SafeSpanProps, SafeSpanModel> {
121
121
 
122
122
  // Register HTML patterns that SafeSpan component can handle
123
123
  static registerPatternHandlers(registry: unknown): void {
124
+ const typedRegistry = registry as { hasPattern?: (pattern: string) => boolean; registerPattern?: (pattern: string, handler: (element: Element) => Record<string, unknown>) => void };
125
+
124
126
  // Register span elements with specific classes or attributes
125
- if (!registry.hasPattern('span.safe-content')) {
126
- registry.registerPattern('span.safe-content', SafeSpan.transformSafeSpan);
127
+ if (typedRegistry.hasPattern && !typedRegistry.hasPattern('span.safe-content')) {
128
+ typedRegistry.registerPattern?.('span.safe-content', SafeSpan.transformSafeSpan);
127
129
  }
128
130
 
129
131
  // Register span elements with data-safe attribute
130
- if (!registry.hasPattern('span[data-safe]')) {
131
- registry.registerPattern('span[data-safe]', SafeSpan.transformSafeSpan);
132
+ if (typedRegistry.hasPattern && !typedRegistry.hasPattern('span[data-safe]')) {
133
+ typedRegistry.registerPattern?.('span[data-safe]', SafeSpan.transformSafeSpan);
132
134
  }
133
135
  }
134
136
 
@@ -153,9 +155,7 @@ function SafeSpanWithDataBinding(props: SafeSpanProps) {
153
155
  // Use data binding
154
156
  const { loading, error, ...safeSpanProps } = useDataBinding<SafeSpanModel>(
155
157
  dataSource!,
156
- restProps as Partial<SafeSpanModel>,
157
- SafeSpanModel.getSchema(),
158
- { cache: true, cacheTTL: 300000, strict: false, ...bindingOptions }
158
+ restProps as Partial<SafeSpanModel>
159
159
  );
160
160
 
161
161
  // Show loading state