@africode/core 5.0.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 (136) hide show
  1. package/AFRICODE_FRAMEWORK_GUIDE.md +707 -0
  2. package/LICENSE +623 -0
  3. package/README.md +442 -0
  4. package/bin/africode.js +73 -0
  5. package/bin/africode.js.1758507140 +343 -0
  6. package/bin/cli.ts +83 -0
  7. package/bin/create-africode.js +158 -0
  8. package/bin/scaffold.ts +219 -0
  9. package/components/accordion.js +183 -0
  10. package/components/alert.js +131 -0
  11. package/components/auth.js +172 -0
  12. package/components/avatar.js +117 -0
  13. package/components/badge.js +104 -0
  14. package/components/base.d.ts +139 -0
  15. package/components/base.js +184 -0
  16. package/components/button.js +164 -0
  17. package/components/card.js +137 -0
  18. package/components/cultural-card.js +243 -0
  19. package/components/divider.js +83 -0
  20. package/components/dropdown.js +171 -0
  21. package/components/error-boundary.js +155 -0
  22. package/components/form.js +131 -0
  23. package/components/grid.js +273 -0
  24. package/components/hero.js +138 -0
  25. package/components/icon.js +36 -0
  26. package/components/index.js +57 -0
  27. package/components/input.js +256 -0
  28. package/components/kanga-card.js +185 -0
  29. package/components/language-switcher.js +108 -0
  30. package/components/loader.js +80 -0
  31. package/components/modal.js +262 -0
  32. package/components/motion.js +84 -0
  33. package/components/navbar.js +236 -0
  34. package/components/pattern-showcase.js +225 -0
  35. package/components/progress.js +134 -0
  36. package/components/react.js +111 -0
  37. package/components/section.js +54 -0
  38. package/components/select.js +322 -0
  39. package/components/sidebar.js +180 -0
  40. package/components/skeleton.js +85 -0
  41. package/components/table.js +181 -0
  42. package/components/tabs.js +202 -0
  43. package/components/theme-toggle.js +82 -0
  44. package/components/toast.js +139 -0
  45. package/components/tooltip.js +167 -0
  46. package/core/a2ui-schema-manager.js +344 -0
  47. package/core/a2ui.js +431 -0
  48. package/core/bun-runtime.js +799 -0
  49. package/core/cli/commands/add.js +23 -0
  50. package/core/cli/commands/audit.js +58 -0
  51. package/core/cli/commands/build.js +137 -0
  52. package/core/cli/commands/create-plugin.js +241 -0
  53. package/core/cli/commands/dev.js +228 -0
  54. package/core/cli/commands/lint.js +23 -0
  55. package/core/cli/commands/test.js +34 -0
  56. package/core/cli/migrator.js +71 -0
  57. package/core/cli/ui.js +46 -0
  58. package/core/compliance.js +628 -0
  59. package/core/config.js +263 -0
  60. package/core/db-advanced.js +481 -0
  61. package/core/db.js +284 -0
  62. package/core/enhanced-hmr.js +404 -0
  63. package/core/errors.js +222 -0
  64. package/core/file-router.js +290 -0
  65. package/core/heartbeat.js +64 -0
  66. package/core/hmr-client.js +204 -0
  67. package/core/hmr.js +196 -0
  68. package/core/html.d.ts +116 -0
  69. package/core/html.js +160 -0
  70. package/core/hydration.js +52 -0
  71. package/core/lipa-namba-journey.js +572 -0
  72. package/core/motion.js +106 -0
  73. package/core/nida-cig-middleware.js +455 -0
  74. package/core/patterns.d.ts +124 -0
  75. package/core/patterns.js +833 -0
  76. package/core/plugins/index.js +312 -0
  77. package/core/router.js +387 -0
  78. package/core/sdk-client.js +62 -0
  79. package/core/sdk.d.ts +133 -0
  80. package/core/sdk.js +123 -0
  81. package/core/seo.js +76 -0
  82. package/core/server/auth-endpoints.js +339 -0
  83. package/core/server/auth.js +180 -0
  84. package/core/server/csrf.js +206 -0
  85. package/core/server/db.js +39 -0
  86. package/core/server/middleware.js +324 -0
  87. package/core/server/rate-limit.js +238 -0
  88. package/core/server/render.js +69 -0
  89. package/core/server/router.js +120 -0
  90. package/core/shim.js +28 -0
  91. package/core/state.d.ts +86 -0
  92. package/core/state.js +242 -0
  93. package/core/store.d.ts +122 -0
  94. package/core/store.js +61 -0
  95. package/core/validation.d.ts +233 -0
  96. package/core/validation.js +590 -0
  97. package/core/websocket.js +639 -0
  98. package/dist/africode.js +2905 -0
  99. package/dist/africode.js.map +61 -0
  100. package/dist/build-info.json +23 -0
  101. package/dist/components.js +2888 -0
  102. package/dist/components.js.map +58 -0
  103. package/dist/styles/africanity.css +322 -0
  104. package/dist/styles/typography.css +141 -0
  105. package/docs/IDE-Guide.md +50 -0
  106. package/package.json +110 -0
  107. package/src/index.ts +196 -0
  108. package/styles/africanity.css +322 -0
  109. package/styles/typography.css +141 -0
  110. package/templates/starter/.env.example +15 -0
  111. package/templates/starter/africode.config.js +40 -0
  112. package/templates/starter/package.json +14 -0
  113. package/templates/starter/src/pages/index.html +46 -0
  114. package/templates/starter/src/pages/index.js +32 -0
  115. package/templates/starter/src/styles/main.css +4 -0
  116. package/templates/starter-3d/.env.example +7 -0
  117. package/templates/starter-3d/africode.config.js +29 -0
  118. package/templates/starter-3d/components/af-model-viewer.js +125 -0
  119. package/templates/starter-3d/package.json +15 -0
  120. package/templates/starter-3d/src/pages/index.html +46 -0
  121. package/templates/starter-3d/src/pages/index.js +50 -0
  122. package/templates/starter-3d/src/styles/main.css +4 -0
  123. package/templates/starter-react/.env.example +15 -0
  124. package/templates/starter-react/africode.config.js +40 -0
  125. package/templates/starter-react/package.json +16 -0
  126. package/templates/starter-react/src/pages/index.html +46 -0
  127. package/templates/starter-react/src/pages/index.js +68 -0
  128. package/templates/starter-react/src/styles/main.css +4 -0
  129. package/templates/starter-tailwind/.env.example +15 -0
  130. package/templates/starter-tailwind/africode.config.js +40 -0
  131. package/templates/starter-tailwind/package.json +20 -0
  132. package/templates/starter-tailwind/src/pages/index.html +46 -0
  133. package/templates/starter-tailwind/src/pages/index.js +37 -0
  134. package/templates/starter-tailwind/src/styles/main.css +4 -0
  135. package/templates/starter-tailwind/src/styles/tailwind.css +1 -0
  136. package/templates/starter-tailwind/src/tailwind-loader.js +30 -0
@@ -0,0 +1,225 @@
1
+ /**
2
+ * AfriCode Pattern Showcase Component
3
+ *
4
+ * Interactive gallery displaying all African cultural patterns
5
+ * with hover effects and pattern information.
6
+ *
7
+ * @module components/pattern-showcase
8
+ */
9
+
10
+ import { AfriCodeComponent, registerComponent } from './base.js';
11
+ import * as patterns from '../core/patterns.js';
12
+
13
+ export class PatternShowcase extends AfriCodeComponent {
14
+ static get observedAttributes() {
15
+ return ['region', 'size'];
16
+ }
17
+
18
+ constructor() {
19
+ super();
20
+ this._allPatterns = [
21
+ // East Africa
22
+ { key: 'shuka', name: 'Maasai Shuka', region: 'east', country: 'Tanzania/Kenya', fn: patterns.generateShuka, meaning: 'Bravery, Unity, Identity' },
23
+ { key: 'hadzabe', name: 'Hadzabe', region: 'east', country: 'Tanzania', fn: patterns.generateHadzabe, meaning: 'Ancient traditions, Hunter-gatherer heritage' },
24
+ { key: 'kanga', name: 'Kanga', region: 'east', country: 'Tanzania/Kenya', fn: patterns.generateKangaBorder, meaning: 'Communication, Proverbs, Femininity' },
25
+ { key: 'kitenge', name: 'Kitenge', region: 'east', country: 'East Africa', fn: patterns.generateKitenge, meaning: 'Celebration, African identity' },
26
+ { key: 'tibeb', name: 'Tibeb', region: 'east', country: 'Ethiopia', fn: patterns.generateTibeb, meaning: 'Faith, Tradition, Celebration' },
27
+ // West Africa
28
+ { key: 'kente', name: 'Kente', region: 'west', country: 'Ghana', fn: patterns.generateKente, meaning: 'Royalty, Wisdom, Celebration' },
29
+ { key: 'adinkra', name: 'Adinkra', region: 'west', country: 'Ghana', fn: patterns.generateAdinkra, meaning: 'Philosophy, Proverbs, Wisdom' },
30
+ { key: 'bogolan', name: 'Bogolan', region: 'west', country: 'Mali', fn: patterns.generateBogolan, meaning: 'Earth energy, Protection, Healing' },
31
+ { key: 'asooke', name: 'Aso-Oke', region: 'west', country: 'Nigeria', fn: patterns.generateAsoOke, meaning: 'Prestige, Celebration, Heritage' },
32
+ { key: 'adire', name: 'Adire (Indigo)', region: 'west', country: 'Nigeria', fn: patterns.generateAdire, meaning: 'Creativity, Uniqueness, Depth' },
33
+ { key: 'ankara', name: 'Ankara', region: 'west', country: 'West Africa', fn: patterns.generateAnkara, meaning: 'Boldness, Pan-African pride' },
34
+ // Central Africa
35
+ { key: 'kuba', name: 'Kuba Shoowa', region: 'central', country: 'DRC', fn: patterns.generateKuba, meaning: 'Status, Mathematics, Eternity' },
36
+ { key: 'imigongo', name: 'Imigongo', region: 'central', country: 'Rwanda', fn: patterns.generateImigongo, meaning: 'Resilience, Strength, Artistry' },
37
+ // North Africa
38
+ { key: 'berber', name: 'Berber', region: 'north', country: 'North Africa', fn: patterns.generateBerber, meaning: 'Protection, Nature, Identity' },
39
+ // Southern Africa
40
+ { key: 'ndebele', name: 'Ndebele', region: 'south', country: 'South Africa', fn: patterns.generateNdebele, meaning: 'Artistry, Marriage, Status' },
41
+ { key: 'zulu', name: 'Zulu Beadwork', region: 'south', country: 'South Africa', fn: patterns.generateZulu, meaning: 'Love letters, Community' },
42
+ { key: 'swazi', name: 'Swazi', region: 'south', country: 'Eswatini', fn: patterns.generateSwazi, meaning: 'Monarchy, Warriors, Pride' },
43
+ { key: 'xhosa', name: 'Xhosa', region: 'south', country: 'South Africa', fn: patterns.generateXhosa, meaning: 'Dignity, Coming of age' }
44
+ ];
45
+ this.render();
46
+ }
47
+
48
+ connectedCallback() {
49
+ this._setupHovers();
50
+ }
51
+
52
+ _setupHovers() {
53
+ this.shadowRoot.querySelectorAll('.pattern-item').forEach(item => {
54
+ item.addEventListener('click', () => {
55
+ const key = item.dataset.key;
56
+ const pattern = this._allPatterns.find(p => p.key === key);
57
+ if (pattern) {
58
+ this.emit('af-pattern-select', { pattern });
59
+ }
60
+ });
61
+ });
62
+ }
63
+
64
+ attributeChangedCallback() {
65
+ this.render();
66
+ this._setupHovers();
67
+ }
68
+
69
+ render() {
70
+ const regionFilter = this.getAttribute('region') || 'all';
71
+ const size = this.getAttribute('size') || 'md';
72
+
73
+ const filtered = regionFilter === 'all'
74
+ ? this._allPatterns
75
+ : this._allPatterns.filter(p => p.region === regionFilter);
76
+
77
+ const sizes = { sm: 80, md: 120, lg: 160 };
78
+ const itemSize = sizes[size] || sizes.md;
79
+
80
+ this.shadowRoot.innerHTML = `
81
+ <style>
82
+ :host {
83
+ display: block;
84
+ font-family: 'Inter', system-ui, sans-serif;
85
+ }
86
+
87
+ .showcase-container {
88
+ padding: 10px;
89
+ }
90
+
91
+ .region-tabs {
92
+ display: flex;
93
+ gap: 8px;
94
+ margin-block-end: 20px;
95
+ flex-wrap: wrap;
96
+ }
97
+
98
+ .region-tab {
99
+ padding: 8px 16px;
100
+ border: none;
101
+ border-radius: 20px;
102
+ font-family: inherit;
103
+ font-size: 13px;
104
+ font-weight: 500;
105
+ cursor: pointer;
106
+ background: #e9ecef;
107
+ color: #495057;
108
+ transition: all 200ms ease;
109
+ }
110
+
111
+ .region-tab:hover {
112
+ background: #dee2e6;
113
+ }
114
+
115
+ .region-tab.active {
116
+ background: linear-gradient(135deg, #1EB53A 0%, #00A3DD 100%);
117
+ color: white;
118
+ }
119
+
120
+ .pattern-grid {
121
+ display: grid;
122
+ grid-template-columns: repeat(auto-fill, minmax(${itemSize + 40}px, 1fr));
123
+ gap: 16px;
124
+ }
125
+
126
+ .pattern-item {
127
+ background: white;
128
+ border-radius: 12px;
129
+ overflow: hidden;
130
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
131
+ cursor: pointer;
132
+ transition: all 300ms ease;
133
+ }
134
+
135
+ .pattern-item:hover {
136
+ transform: scale(1.05);
137
+ box-shadow: 0 8px 25px rgba(0,0,0,0.2);
138
+ }
139
+
140
+ .pattern-preview {
141
+ inline-size: 100%;
142
+ block-size: ${itemSize}px;
143
+ background-repeat: repeat;
144
+ transition: background-position 10s linear;
145
+ }
146
+
147
+ .pattern-item:hover .pattern-preview {
148
+ background-position: 50px 50px;
149
+ }
150
+
151
+ .pattern-info {
152
+ padding: 12px;
153
+ }
154
+
155
+ .pattern-name {
156
+ font-weight: 600;
157
+ font-size: 14px;
158
+ margin-block-end: 4px;
159
+ color: #333;
160
+ }
161
+
162
+ .pattern-country {
163
+ font-size: 11px;
164
+ color: #888;
165
+ margin-block-end: 6px;
166
+ }
167
+
168
+ .pattern-meaning {
169
+ font-size: 11px;
170
+ color: #666;
171
+ font-style: italic;
172
+ line-height: 1.4;
173
+ margin-block-end: 6px;
174
+ }
175
+
176
+ .region-label {
177
+ font-size: 12px;
178
+ padding: 3px 8px;
179
+ border-radius: 10px;
180
+ display: inline-block;
181
+ margin-block-end: 6px;
182
+ }
183
+
184
+ .region-east { background: #d4edda; color: #155724; }
185
+ .region-west { background: #fff3cd; color: #856404; }
186
+ .region-central { background: #e2e3ff; color: #3e2723; }
187
+ .region-south { background: #d1ecf1; color: #0c5460; }
188
+ </style>
189
+
190
+ <div class="showcase-container">
191
+ <div class="region-tabs">
192
+ <button class="region-tab ${regionFilter === 'all' ? 'active' : ''}" data-region="all">All Regions</button>
193
+ <button class="region-tab ${regionFilter === 'east' ? 'active' : ''}" data-region="east">East Africa</button>
194
+ <button class="region-tab ${regionFilter === 'west' ? 'active' : ''}" data-region="west">West Africa</button>
195
+ <button class="region-tab ${regionFilter === 'central' ? 'active' : ''}" data-region="central">Central Africa</button>
196
+ <button class="region-tab ${regionFilter === 'south' ? 'active' : ''}" data-region="south">Southern Africa</button>
197
+ </div>
198
+
199
+ <div class="pattern-grid">
200
+ ${filtered.map(p => `
201
+ <div class="pattern-item" data-key="${p.key}">
202
+ <div class="pattern-preview" style="background-image: url('${p.fn()}')"></div>
203
+ <div class="pattern-info">
204
+ <span class="region-label region-${p.region}">${p.region.charAt(0).toUpperCase() + p.region.slice(1)}</span>
205
+ <div class="pattern-name">${p.name}</div>
206
+ <div class="pattern-country">${p.country}</div>
207
+ <div class="pattern-meaning">${p.meaning}</div>
208
+ </div>
209
+ </div>
210
+ `).join('')}
211
+ </div>
212
+ </div>
213
+ `;
214
+
215
+ // Region filter tabs
216
+ this.shadowRoot.querySelectorAll('.region-tab').forEach(tab => {
217
+ tab.addEventListener('click', () => {
218
+ this.setAttribute('region', tab.dataset.region);
219
+ });
220
+ });
221
+ }
222
+ }
223
+
224
+ registerComponent('af-pattern-showcase', PatternShowcase);
225
+ export default PatternShowcase;
@@ -0,0 +1,134 @@
1
+ /**
2
+ * AfriCode Progress Bar Component
3
+ *
4
+ * Progress indicators for loading, completion, and metrics.
5
+ * Suitable for: File uploads, Course progress, Health goals, Downloads
6
+ *
7
+ * @module components/progress
8
+ */
9
+
10
+ import { AfriCodeComponent, registerComponent } from './base.js';
11
+
12
+ export class AfriProgress extends AfriCodeComponent {
13
+ static get observedAttributes() {
14
+ return ['value', 'max', 'theme', 'size', 'label', 'animated'];
15
+ }
16
+
17
+ constructor() {
18
+ super();
19
+ this.render();
20
+ }
21
+
22
+ attributeChangedCallback() {
23
+ this.render();
24
+ }
25
+
26
+ render() {
27
+ const value = parseFloat(this.getAttribute('value')) || 0;
28
+ const max = parseFloat(this.getAttribute('max')) || 100;
29
+ const theme = this.getAttribute('theme') || 'tanzania';
30
+ const size = this.getAttribute('size') || 'md';
31
+ const label = this.getAttribute('label');
32
+ const animated = this.hasAttribute('animated');
33
+
34
+ const percentage = Math.min(100, Math.max(0, (value / max) * 100));
35
+
36
+ const themes = {
37
+ tanzania: { bar: '#1EB53A', track: '#e9ecef' },
38
+ maasai: { bar: '#FF0000', track: '#e9ecef' },
39
+ ndebele: { bar: '#4169E1', track: '#e9ecef' },
40
+ gold: { bar: '#FCD116', track: '#e9ecef' },
41
+ gradient: { bar: 'linear-gradient(90deg, #1EB53A 0%, #FCD116 50%, #00A3DD 100%)', track: '#e9ecef' }
42
+ };
43
+ const t = themes[theme] || themes.tanzania;
44
+
45
+ const sizes = {
46
+ sm: '6px',
47
+ md: '10px',
48
+ lg: '16px'
49
+ };
50
+ const height = sizes[size] || sizes.md;
51
+
52
+ this.shadowRoot.innerHTML = `
53
+ <style>
54
+ :host {
55
+ display: block;
56
+ font-family: 'Inter', system-ui, sans-serif;
57
+ }
58
+
59
+ .progress-container {
60
+ width: 100%;
61
+ }
62
+
63
+ .progress-header {
64
+ display: flex;
65
+ justify-content: space-between;
66
+ margin-bottom: 5px;
67
+ font-size: 13px;
68
+ }
69
+
70
+ .progress-label {
71
+ color: #333;
72
+ font-weight: 500;
73
+ }
74
+
75
+ .progress-value {
76
+ color: #666;
77
+ }
78
+
79
+ .progress-track {
80
+ width: 100%;
81
+ height: ${height};
82
+ background: ${t.track};
83
+ border-radius: 100px;
84
+ overflow: hidden;
85
+ }
86
+
87
+ .progress-bar {
88
+ height: 100%;
89
+ width: ${percentage}%;
90
+ background: ${t.bar};
91
+ border-radius: 100px;
92
+ transition: width 500ms ease-out;
93
+ ${animated ? `
94
+ background-size: 30px 30px;
95
+ background-image: linear-gradient(
96
+ 135deg,
97
+ rgba(255,255,255,0.2) 25%,
98
+ transparent 25%,
99
+ transparent 50%,
100
+ rgba(255,255,255,0.2) 50%,
101
+ rgba(255,255,255,0.2) 75%,
102
+ transparent 75%,
103
+ transparent
104
+ );
105
+ animation: stripes 1s linear infinite;
106
+ ` : ''}
107
+ }
108
+
109
+ @keyframes stripes {
110
+ 0% { background-position: 0 0; }
111
+ 100% { background-position: 30px 0; }
112
+ }
113
+ </style>
114
+
115
+ <div class="progress-container">
116
+ ${label || true ? `
117
+ <div class="progress-header">
118
+ <span class="progress-label">${label || ''}</span>
119
+ <span class="progress-value">${percentage.toFixed(0)}%</span>
120
+ </div>
121
+ ` : ''}
122
+ <div class="progress-track">
123
+ <div class="progress-bar" role="progressbar"
124
+ aria-valuenow="${value}"
125
+ aria-valuemin="0"
126
+ aria-valuemax="${max}"></div>
127
+ </div>
128
+ </div>
129
+ `;
130
+ }
131
+ }
132
+
133
+ registerComponent('af-progress', AfriProgress);
134
+ export default AfriProgress;
@@ -0,0 +1,111 @@
1
+ /**
2
+ * AfriCode React Interoperability Wrapper
3
+ *
4
+ * Mounts a React component tree inside an AfriCode Web Component's Shadow DOM.
5
+ * This allows teams to embed React views within AfriCode pages without replacing
6
+ * the framework's routing, build, or HMR systems.
7
+ *
8
+ * Usage:
9
+ * import { AfriReact } from 'africode/components/react.js';
10
+ * // or use declaratively after registration:
11
+ * <af-react component="MyReactApp"></af-react>
12
+ *
13
+ * Programmatic mount:
14
+ * const wrapper = document.querySelector('af-react');
15
+ * wrapper.mount(MyReactComponent, { someProp: 'value' });
16
+ *
17
+ * @module components/react
18
+ */
19
+
20
+ import { AfriCodeComponent, registerComponent } from './base.js';
21
+
22
+ export class AfriReact extends AfriCodeComponent {
23
+ static get observedAttributes() {
24
+ return ['component'];
25
+ }
26
+
27
+ constructor() {
28
+ super();
29
+ this._root = null; // React root instance
30
+ this._mountPoint = null; // DOM node React renders into
31
+ this._component = null; // The React component function/class
32
+ this._props = {}; // Props passed to the React component
33
+ }
34
+
35
+ connectedCallback() {
36
+ super.connectedCallback();
37
+
38
+ // Create mount point inside Shadow DOM
39
+ this._mountPoint = document.createElement('div');
40
+ this._mountPoint.setAttribute('data-af-react-root', '');
41
+ this.shadowRoot.appendChild(this._mountPoint);
42
+ }
43
+
44
+ disconnectedCallback() {
45
+ // Clean unmount to prevent memory leaks
46
+ this._unmount();
47
+ }
48
+
49
+ /**
50
+ * Mount a React component into this wrapper.
51
+ * @param {Function|Object} Component - React component (function or class)
52
+ * @param {Object} [props={}] - Props to pass to the component
53
+ */
54
+ async mount(Component, props = {}) {
55
+ if (!this._mountPoint) {
56
+ console.error('[AfriCode/React] Cannot mount — component not connected to DOM.');
57
+ return;
58
+ }
59
+
60
+ this._component = Component;
61
+ this._props = props;
62
+
63
+ try {
64
+ const React = await import('react');
65
+ const ReactDOM = await import('react-dom/client');
66
+
67
+ // Unmount any previous tree
68
+ this._unmount();
69
+
70
+ this._root = ReactDOM.createRoot(this._mountPoint);
71
+ this._root.render(React.createElement(Component, props));
72
+ } catch (err) {
73
+ console.error('[AfriCode/React] Failed to mount React component:', err);
74
+ this._mountPoint.innerHTML = `
75
+ <div style="padding: 12px; border: 1px dashed crimson; border-radius: 6px; color: crimson; font-size: 0.85rem;">
76
+ React mount failed. Ensure <code>react</code> and <code>react-dom</code> are installed.
77
+ </div>
78
+ `;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Update props on the currently mounted React component.
84
+ * @param {Object} nextProps - New props to merge and re-render with
85
+ */
86
+ async update(nextProps) {
87
+ if (!this._root || !this._component) {
88
+ console.warn('[AfriCode/React] No component mounted — call mount() first.');
89
+ return;
90
+ }
91
+ this._props = { ...this._props, ...nextProps };
92
+
93
+ const React = await import('react');
94
+ this._root.render(React.createElement(this._component, this._props));
95
+ }
96
+
97
+ /** @private */
98
+ _unmount() {
99
+ if (this._root) {
100
+ try {
101
+ this._root.unmount();
102
+ } catch (e) {
103
+ // Already unmounted or root was invalid
104
+ }
105
+ this._root = null;
106
+ }
107
+ }
108
+ }
109
+
110
+ registerComponent('af-react', AfriReact);
111
+ export default AfriReact;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * AfriCode Content Section
3
+ * Standardized vertical spacing and containerization.
4
+ */
5
+ import { AfriCodeComponent, registerComponent, html } from './base.js';
6
+
7
+ export class AfriSection extends AfriCodeComponent {
8
+ render() {
9
+ const variant = this.getAttribute('variant') || 'default';
10
+ const title = this.getAttribute('title');
11
+
12
+ this.shadowRoot.innerHTML = html`
13
+ <style>
14
+ :host { display: block; width: 100%; }
15
+ .section {
16
+ padding: 100px 5%;
17
+ max-width: var(--page-max-width, 1280px);
18
+ margin: 0 auto;
19
+ }
20
+ .section-header {
21
+ text-align: center;
22
+ margin-bottom: 60px;
23
+ }
24
+ .section-header h2 {
25
+ font-size: 3rem;
26
+ font-family: 'Outfit', sans-serif;
27
+ margin: 0;
28
+ color: var(--text-primary);
29
+ }
30
+
31
+ :host([variant="muted"]) { background: var(--bg-surface, rgba(30, 41, 59, 0.3)); }
32
+ :host([variant="brand"]) {
33
+ background: linear-gradient(135deg, var(--bg-base) 0%, var(--bg-surface) 100%);
34
+ border-top: 1px solid var(--glass-border);
35
+ border-bottom: 1px solid var(--glass-border);
36
+ }
37
+ </style>
38
+ <section class="section">
39
+ ${title ? html`
40
+ <div class="section-header">
41
+ <h2>${title}</h2>
42
+ </div>
43
+ ` : ''}
44
+ <slot></slot>
45
+ </section>
46
+ `;
47
+ }
48
+
49
+ connectedCallback() {
50
+ this.render();
51
+ }
52
+ }
53
+
54
+ registerComponent('af-section', AfriSection);