@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,181 @@
1
+ /**
2
+ * AfriCode Data Table Component
3
+ *
4
+ * Sortable, paginated data table with cultural theming.
5
+ * Suitable for: Finance dashboards, Admin panels, Healthcare records, Inventory
6
+ *
7
+ * @module components/table
8
+ */
9
+
10
+ import { AfriCodeComponent, registerComponent } from './base.js';
11
+
12
+ export class AfriTable extends AfriCodeComponent {
13
+ static get observedAttributes() {
14
+ return ['theme', 'striped', 'hoverable'];
15
+ }
16
+
17
+ constructor() {
18
+ super();
19
+ this._columns = [];
20
+ this._data = [];
21
+ this._sortColumn = null;
22
+ this._sortDirection = 'asc';
23
+ this.render();
24
+ }
25
+
26
+ set columns(cols) {
27
+ this._columns = cols;
28
+ this.render();
29
+ }
30
+
31
+ set data(rows) {
32
+ this._data = rows;
33
+ this.render();
34
+ }
35
+
36
+ attributeChangedCallback() {
37
+ this.render();
38
+ }
39
+
40
+ _handleSort(column) {
41
+ if (this._sortColumn === column) {
42
+ this._sortDirection = this._sortDirection === 'asc' ? 'desc' : 'asc';
43
+ } else {
44
+ this._sortColumn = column;
45
+ this._sortDirection = 'asc';
46
+ }
47
+ this.emit('af-sort', { column, direction: this._sortDirection });
48
+ this.render();
49
+ }
50
+
51
+ _getSortedData() {
52
+ if (!this._sortColumn) {return this._data;}
53
+
54
+ return [...this._data].sort((a, b) => {
55
+ const aVal = a[this._sortColumn];
56
+ const bVal = b[this._sortColumn];
57
+ const modifier = this._sortDirection === 'asc' ? 1 : -1;
58
+
59
+ if (typeof aVal === 'number') {
60
+ return (aVal - bVal) * modifier;
61
+ }
62
+ return String(aVal).localeCompare(String(bVal)) * modifier;
63
+ });
64
+ }
65
+
66
+ render() {
67
+ const theme = this.getAttribute('theme') || 'tanzania';
68
+ const striped = this.hasAttribute('striped');
69
+ const hoverable = this.hasAttribute('hoverable');
70
+
71
+ const themes = {
72
+ tanzania: { header: '#1EB53A', text: '#FFFFFF', accent: '#FCD116' },
73
+ maasai: { header: '#8B0000', text: '#FFFFFF', accent: '#FF0000' },
74
+ ndebele: { header: '#4169E1', text: '#FFFFFF', accent: '#FFD700' }
75
+ };
76
+ const t = themes[theme] || themes.tanzania;
77
+ const sortedData = this._getSortedData();
78
+
79
+ this.shadowRoot.innerHTML = `
80
+ <style>
81
+ :host {
82
+ display: block;
83
+ font-family: 'Inter', system-ui, sans-serif;
84
+ }
85
+
86
+ .table-container {
87
+ overflow-x: auto;
88
+ border-radius: 8px;
89
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
90
+ }
91
+
92
+ table {
93
+ width: 100%;
94
+ border-collapse: collapse;
95
+ background: white;
96
+ }
97
+
98
+ th {
99
+ background: ${t.header};
100
+ color: ${t.text};
101
+ padding: 13px;
102
+ text-align: left;
103
+ font-weight: 600;
104
+ cursor: pointer;
105
+ user-select: none;
106
+ white-space: nowrap;
107
+ }
108
+
109
+ th:hover {
110
+ filter: brightness(1.1);
111
+ }
112
+
113
+ th .sort-icon {
114
+ margin-left: 5px;
115
+ opacity: 0.7;
116
+ }
117
+
118
+ td {
119
+ padding: 13px;
120
+ border-bottom: 1px solid #eee;
121
+ }
122
+
123
+ ${striped ? `
124
+ tbody tr:nth-child(even) {
125
+ background: #f8f9fa;
126
+ }
127
+ ` : ''}
128
+
129
+ ${hoverable ? `
130
+ tbody tr:hover {
131
+ background: ${t.accent}20;
132
+ }
133
+ ` : ''}
134
+
135
+ .empty-state {
136
+ text-align: center;
137
+ padding: 34px;
138
+ color: #888;
139
+ }
140
+ </style>
141
+
142
+ <div class="table-container">
143
+ <table>
144
+ <thead>
145
+ <tr>
146
+ ${this._columns.map(col => `
147
+ <th data-column="${col.key}">
148
+ ${col.label}
149
+ <span class="sort-icon">
150
+ ${this._sortColumn === col.key ? (this._sortDirection === 'asc' ? '↑' : '↓') : '↕'}
151
+ </span>
152
+ </th>
153
+ `).join('')}
154
+ </tr>
155
+ </thead>
156
+ <tbody>
157
+ ${sortedData.length ? sortedData.map(row => `
158
+ <tr>
159
+ ${this._columns.map(col => `<td>${row[col.key] ?? ''}</td>`).join('')}
160
+ </tr>
161
+ `).join('') : `
162
+ <tr>
163
+ <td colspan="${this._columns.length}" class="empty-state">No data available</td>
164
+ </tr>
165
+ `}
166
+ </tbody>
167
+ </table>
168
+ </div>
169
+ `;
170
+
171
+ // Add sort listeners
172
+ this.shadowRoot.querySelectorAll('th').forEach(th => {
173
+ th.addEventListener('click', () => {
174
+ this._handleSort(th.dataset.column);
175
+ });
176
+ });
177
+ }
178
+ }
179
+
180
+ registerComponent('af-table', AfriTable);
181
+ export default AfriTable;
@@ -0,0 +1,202 @@
1
+ /**
2
+ * AfriCode Tabs Component
3
+ *
4
+ * Tabbed navigation for organized content sections.
5
+ * Suitable for: Dashboards, Settings, Product details, Documentation
6
+ *
7
+ * @module components/tabs
8
+ */
9
+
10
+ import { AfriCodeComponent, registerComponent } from './base.js';
11
+
12
+ export class AfriTabs extends AfriCodeComponent {
13
+ static get observedAttributes() {
14
+ return ['theme'];
15
+ }
16
+
17
+ constructor() {
18
+ super();
19
+ this._activeTab = 0;
20
+ this.render();
21
+ }
22
+
23
+ connectedCallback() {
24
+ this._setupTabs();
25
+ this.addEventListener('keydown', this._handleKeydown.bind(this));
26
+ }
27
+
28
+ _handleKeydown(e) {
29
+ const tabSlot = this.querySelector('[slot="tabs"]');
30
+ const tabItems = tabSlot ? Array.from(tabSlot.children) : [];
31
+
32
+ if (tabItems.length === 0) {return;}
33
+
34
+ switch (e.key) {
35
+ case 'ArrowLeft':
36
+ e.preventDefault();
37
+ this._activeTab = this._activeTab > 0 ? this._activeTab - 1 : tabItems.length - 1;
38
+ this.render();
39
+ this._setupTabs();
40
+ this._focusActiveTab();
41
+ break;
42
+ case 'ArrowRight':
43
+ e.preventDefault();
44
+ this._activeTab = this._activeTab < tabItems.length - 1 ? this._activeTab + 1 : 0;
45
+ this.render();
46
+ this._setupTabs();
47
+ this._focusActiveTab();
48
+ break;
49
+ case 'Home':
50
+ e.preventDefault();
51
+ this._activeTab = 0;
52
+ this.render();
53
+ this._setupTabs();
54
+ this._focusActiveTab();
55
+ break;
56
+ case 'End':
57
+ e.preventDefault();
58
+ this._activeTab = tabItems.length - 1;
59
+ this.render();
60
+ this._setupTabs();
61
+ this._focusActiveTab();
62
+ break;
63
+ }
64
+ }
65
+
66
+ _focusActiveTab() {
67
+ const tabs = this.shadowRoot.querySelectorAll('.tab-button');
68
+ if (tabs[this._activeTab]) {
69
+ tabs[this._activeTab].focus();
70
+ }
71
+ }
72
+
73
+ _setupTabs() {
74
+ const tabs = this.shadowRoot.querySelectorAll('.tab-button');
75
+ tabs.forEach((tab, index) => {
76
+ tab.addEventListener('click', () => {
77
+ this._activeTab = index;
78
+ this.render();
79
+ this._setupTabs();
80
+ this.emit('af-tab-change', { index, tab: tab.textContent });
81
+ });
82
+ });
83
+ }
84
+
85
+ attributeChangedCallback() {
86
+ this.render();
87
+ this._setupTabs();
88
+ }
89
+
90
+ render() {
91
+ const theme = this.getAttribute('theme') || 'tanzania';
92
+ const tabSlot = this.querySelector('[slot="tabs"]');
93
+ const tabItems = tabSlot ? Array.from(tabSlot.children) : [];
94
+
95
+ const themes = {
96
+ tanzania: { active: '#1EB53A', text: '#FFFFFF' },
97
+ maasai: { active: '#FF0000', text: '#FFFFFF' },
98
+ ndebele: { active: '#4169E1', text: '#FFFFFF' }
99
+ };
100
+ const t = themes[theme] || themes.tanzania;
101
+
102
+ this.shadowRoot.innerHTML = `
103
+ <style>
104
+ :host {
105
+ display: block;
106
+ font-family: 'Inter', system-ui, sans-serif;
107
+ }
108
+
109
+ .tabs-container {
110
+ border-radius: 8px;
111
+ overflow: hidden;
112
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
113
+ }
114
+
115
+ .tab-list {
116
+ display: flex;
117
+ background: #f5f5f5;
118
+ border-bottom: 2px solid #eee;
119
+ overflow-x: auto;
120
+ }
121
+
122
+ .tab-button {
123
+ padding: 13px 21px;
124
+ background: none;
125
+ border: none;
126
+ font-family: inherit;
127
+ font-size: 14px;
128
+ font-weight: 500;
129
+ color: #666;
130
+ cursor: pointer;
131
+ white-space: nowrap;
132
+ transition: all 200ms ease;
133
+ border-bottom: 2px solid transparent;
134
+ margin-bottom: -2px;
135
+ }
136
+
137
+ .tab-button:hover {
138
+ color: ${t.active};
139
+ background: rgba(0,0,0,0.03);
140
+ }
141
+
142
+ .tab-button.active {
143
+ color: ${t.active};
144
+ border-bottom-color: ${t.active};
145
+ background: white;
146
+ }
147
+
148
+ .tab-panels {
149
+ background: white;
150
+ padding: 21px;
151
+ }
152
+
153
+ .tab-panel {
154
+ display: none;
155
+ }
156
+
157
+ .tab-panel.active {
158
+ display: block;
159
+ animation: fadeIn 200ms ease-out;
160
+ }
161
+
162
+ @keyframes fadeIn {
163
+ from { opacity: 0; }
164
+ to { opacity: 1; }
165
+ }
166
+
167
+ ::slotted([slot="tabs"]) {
168
+ display: none;
169
+ }
170
+ </style>
171
+
172
+ <div class="tabs-container">
173
+ <div class="tab-list" role="tablist">
174
+ ${tabItems.map((tab, i) => `
175
+ <button class="tab-button ${i === this._activeTab ? 'active' : ''}"
176
+ role="tab"
177
+ aria-selected="${i === this._activeTab}"
178
+ aria-controls="panel-${i}"
179
+ tabindex="${i === this._activeTab ? '0' : '-1'}"
180
+ id="tab-${i}">
181
+ ${tab.getAttribute('label') || `Tab ${i + 1}`}
182
+ </button>
183
+ `).join('')}
184
+ </div>
185
+ <div class="tab-panels">
186
+ ${tabItems.map((tab, i) => `
187
+ <div class="tab-panel ${i === this._activeTab ? 'active' : ''}"
188
+ role="tabpanel"
189
+ id="panel-${i}"
190
+ aria-labelledby="tab-${i}">
191
+ ${tab.innerHTML}
192
+ </div>
193
+ `).join('')}
194
+ </div>
195
+ </div>
196
+ <slot name="tabs"></slot>
197
+ `;
198
+ }
199
+ }
200
+
201
+ registerComponent('af-tabs', AfriTabs);
202
+ export default AfriTabs;
@@ -0,0 +1,82 @@
1
+ import { AfriCodeComponent, registerComponent, html } from './base.js';
2
+
3
+ class ThemeToggle extends AfriCodeComponent {
4
+ constructor() {
5
+ super();
6
+ this.currentTheme = 'dark';
7
+ }
8
+
9
+ connectedCallback() {
10
+ this.loadStyles();
11
+
12
+ // Check initial theme from document element or localStorage
13
+ const storedTheme = localStorage.getItem('africode-theme');
14
+ if (storedTheme) {
15
+ this.currentTheme = storedTheme;
16
+ document.documentElement.setAttribute('data-theme', storedTheme);
17
+ } else {
18
+ const hasLightMode = window.matchMedia('(prefers-color-scheme: light)').matches;
19
+ this.currentTheme = hasLightMode ? 'light' : 'dark';
20
+ document.documentElement.setAttribute('data-theme', this.currentTheme);
21
+ }
22
+
23
+ this.render();
24
+ }
25
+
26
+ toggleTheme() {
27
+ this.currentTheme = this.currentTheme === 'dark' ? 'light' : 'dark';
28
+ document.documentElement.setAttribute('data-theme', this.currentTheme);
29
+ localStorage.setItem('africode-theme', this.currentTheme);
30
+
31
+ // Dispatch event for other components that might need JS-level awareness
32
+ window.dispatchEvent(new CustomEvent('theme-changed', {
33
+ detail: { theme: this.currentTheme }
34
+ }));
35
+
36
+ this.render();
37
+ }
38
+
39
+ render() {
40
+ const isDark = this.currentTheme === 'dark';
41
+
42
+ this.shadowRoot.innerHTML = html`
43
+ <style>
44
+ .toggle-btn {
45
+ background: var(--bg-surface-elevated, var(--afri-obsidian));
46
+ border: 1px solid var(--border-color);
47
+ color: var(--text-main);
48
+ padding: var(--space-8) var(--space-13);
49
+ border-radius: var(--radius-lg);
50
+ cursor: pointer;
51
+ display: flex;
52
+ align-items: center;
53
+ gap: 8px;
54
+ font-family: var(--font-body);
55
+ font-size: 0.9rem;
56
+ transition: all 0.3s var(--ease-snap);
57
+ }
58
+ .toggle-btn:hover {
59
+ border-color: var(--brand-green);
60
+ transform: translateY(-2px);
61
+ }
62
+ .icon {
63
+ font-size: 1.1rem;
64
+ }
65
+ </style>
66
+
67
+ <button class="toggle-btn" @click="\${() => this.toggleTheme()}">
68
+ <span class="icon">\${isDark ? '🌙' : '☀️'}</span>
69
+ <span>\${isDark ? 'Dark Mode' : 'Light Mode'}</span>
70
+ </button>
71
+ `;
72
+
73
+ // Attach event listener natively since simple html tagged template doesn't bind events directly in this framework yet
74
+ const btn = this.shadowRoot.querySelector('.toggle-btn');
75
+ if (btn) {
76
+ btn.addEventListener('click', () => this.toggleTheme());
77
+ }
78
+ }
79
+ }
80
+
81
+ registerComponent('af-theme-toggle', ThemeToggle);
82
+ export default ThemeToggle;
@@ -0,0 +1,139 @@
1
+ /**
2
+ * AfriCode Toast Component
3
+ *
4
+ * Notification toasts with customizable types and auto-dismiss.
5
+ *
6
+ * @module components/toast
7
+ */
8
+
9
+ import { AfriCodeComponent, registerComponent } from './base.js';
10
+
11
+ export class AfriToast extends AfriCodeComponent {
12
+ static get observedAttributes() {
13
+ return ['type', 'message', 'duration'];
14
+ }
15
+
16
+ constructor() {
17
+ super();
18
+ this.render();
19
+ }
20
+
21
+ show(message, type = 'info', duration = 4000) {
22
+ this.setAttribute('message', message);
23
+ this.setAttribute('type', type);
24
+ this.render();
25
+
26
+ const toast = this.shadowRoot.querySelector('.toast');
27
+ toast.classList.add('show');
28
+
29
+ if (duration > 0) {
30
+ setTimeout(() => this.hide(), duration);
31
+ }
32
+ }
33
+
34
+ hide() {
35
+ const toast = this.shadowRoot.querySelector('.toast');
36
+ toast.classList.remove('show');
37
+ setTimeout(() => this.emit('af-toast-close'), 300);
38
+ }
39
+
40
+ render() {
41
+ const type = this.getAttribute('type') || 'info';
42
+ const message = this.getAttribute('message') || '';
43
+
44
+ const icons = {
45
+ success: '✓',
46
+ error: '✗',
47
+ warning: '⚠',
48
+ info: 'ℹ'
49
+ };
50
+
51
+ const colors = {
52
+ success: '#1EB53A',
53
+ error: '#EF3340',
54
+ warning: '#FCD116',
55
+ info: '#00A3DD'
56
+ };
57
+
58
+ this.shadowRoot.innerHTML = `
59
+ <style>
60
+ :host {
61
+ position: fixed;
62
+ bottom: 20px;
63
+ right: 20px;
64
+ z-index: 9999;
65
+ font-family: 'Inter', system-ui, sans-serif;
66
+ }
67
+
68
+ .toast {
69
+ display: flex;
70
+ align-items: center;
71
+ gap: 12px;
72
+ padding: 16px 20px;
73
+ background: #121212;
74
+ border: 1px solid #1e1e1e;
75
+ border-left: 4px solid ${colors[type]};
76
+ border-radius: 8px;
77
+ box-shadow: 0 10px 40px rgba(0,0,0,0.5);
78
+ transform: translateX(120%);
79
+ opacity: 0;
80
+ transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
81
+ }
82
+
83
+ .toast.show {
84
+ transform: translateX(0);
85
+ opacity: 1;
86
+ }
87
+
88
+ .toast-icon {
89
+ width: 28px;
90
+ height: 28px;
91
+ border-radius: 50%;
92
+ background: ${colors[type]}20;
93
+ color: ${colors[type]};
94
+ display: flex;
95
+ align-items: center;
96
+ justify-content: center;
97
+ font-size: 14px;
98
+ font-weight: bold;
99
+ }
100
+
101
+ .toast-content {
102
+ flex: 1;
103
+ }
104
+
105
+ .toast-message {
106
+ color: #ffffff;
107
+ font-size: 0.95rem;
108
+ }
109
+
110
+ .toast-close {
111
+ background: none;
112
+ border: none;
113
+ color: #606060;
114
+ cursor: pointer;
115
+ padding: 4px;
116
+ font-size: 18px;
117
+ line-height: 1;
118
+ }
119
+
120
+ .toast-close:hover {
121
+ color: #ffffff;
122
+ }
123
+ </style>
124
+
125
+ <div class="toast">
126
+ <span class="toast-icon">${icons[type]}</span>
127
+ <div class="toast-content">
128
+ <div class="toast-message">${message}</div>
129
+ </div>
130
+ <button class="toast-close" id="close">×</button>
131
+ </div>
132
+ `;
133
+
134
+ this.shadowRoot.getElementById('close')?.addEventListener('click', () => this.hide());
135
+ }
136
+ }
137
+
138
+ registerComponent('af-toast', AfriToast);
139
+ export default AfriToast;