@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.
- package/AFRICODE_FRAMEWORK_GUIDE.md +707 -0
- package/LICENSE +623 -0
- package/README.md +442 -0
- package/bin/africode.js +73 -0
- package/bin/africode.js.1758507140 +343 -0
- package/bin/cli.ts +83 -0
- package/bin/create-africode.js +158 -0
- package/bin/scaffold.ts +219 -0
- package/components/accordion.js +183 -0
- package/components/alert.js +131 -0
- package/components/auth.js +172 -0
- package/components/avatar.js +117 -0
- package/components/badge.js +104 -0
- package/components/base.d.ts +139 -0
- package/components/base.js +184 -0
- package/components/button.js +164 -0
- package/components/card.js +137 -0
- package/components/cultural-card.js +243 -0
- package/components/divider.js +83 -0
- package/components/dropdown.js +171 -0
- package/components/error-boundary.js +155 -0
- package/components/form.js +131 -0
- package/components/grid.js +273 -0
- package/components/hero.js +138 -0
- package/components/icon.js +36 -0
- package/components/index.js +57 -0
- package/components/input.js +256 -0
- package/components/kanga-card.js +185 -0
- package/components/language-switcher.js +108 -0
- package/components/loader.js +80 -0
- package/components/modal.js +262 -0
- package/components/motion.js +84 -0
- package/components/navbar.js +236 -0
- package/components/pattern-showcase.js +225 -0
- package/components/progress.js +134 -0
- package/components/react.js +111 -0
- package/components/section.js +54 -0
- package/components/select.js +322 -0
- package/components/sidebar.js +180 -0
- package/components/skeleton.js +85 -0
- package/components/table.js +181 -0
- package/components/tabs.js +202 -0
- package/components/theme-toggle.js +82 -0
- package/components/toast.js +139 -0
- package/components/tooltip.js +167 -0
- package/core/a2ui-schema-manager.js +344 -0
- package/core/a2ui.js +431 -0
- package/core/bun-runtime.js +799 -0
- package/core/cli/commands/add.js +23 -0
- package/core/cli/commands/audit.js +58 -0
- package/core/cli/commands/build.js +137 -0
- package/core/cli/commands/create-plugin.js +241 -0
- package/core/cli/commands/dev.js +228 -0
- package/core/cli/commands/lint.js +23 -0
- package/core/cli/commands/test.js +34 -0
- package/core/cli/migrator.js +71 -0
- package/core/cli/ui.js +46 -0
- package/core/compliance.js +628 -0
- package/core/config.js +263 -0
- package/core/db-advanced.js +481 -0
- package/core/db.js +284 -0
- package/core/enhanced-hmr.js +404 -0
- package/core/errors.js +222 -0
- package/core/file-router.js +290 -0
- package/core/heartbeat.js +64 -0
- package/core/hmr-client.js +204 -0
- package/core/hmr.js +196 -0
- package/core/html.d.ts +116 -0
- package/core/html.js +160 -0
- package/core/hydration.js +52 -0
- package/core/lipa-namba-journey.js +572 -0
- package/core/motion.js +106 -0
- package/core/nida-cig-middleware.js +455 -0
- package/core/patterns.d.ts +124 -0
- package/core/patterns.js +833 -0
- package/core/plugins/index.js +312 -0
- package/core/router.js +387 -0
- package/core/sdk-client.js +62 -0
- package/core/sdk.d.ts +133 -0
- package/core/sdk.js +123 -0
- package/core/seo.js +76 -0
- package/core/server/auth-endpoints.js +339 -0
- package/core/server/auth.js +180 -0
- package/core/server/csrf.js +206 -0
- package/core/server/db.js +39 -0
- package/core/server/middleware.js +324 -0
- package/core/server/rate-limit.js +238 -0
- package/core/server/render.js +69 -0
- package/core/server/router.js +120 -0
- package/core/shim.js +28 -0
- package/core/state.d.ts +86 -0
- package/core/state.js +242 -0
- package/core/store.d.ts +122 -0
- package/core/store.js +61 -0
- package/core/validation.d.ts +233 -0
- package/core/validation.js +590 -0
- package/core/websocket.js +639 -0
- package/dist/africode.js +2905 -0
- package/dist/africode.js.map +61 -0
- package/dist/build-info.json +23 -0
- package/dist/components.js +2888 -0
- package/dist/components.js.map +58 -0
- package/dist/styles/africanity.css +322 -0
- package/dist/styles/typography.css +141 -0
- package/docs/IDE-Guide.md +50 -0
- package/package.json +110 -0
- package/src/index.ts +196 -0
- package/styles/africanity.css +322 -0
- package/styles/typography.css +141 -0
- package/templates/starter/.env.example +15 -0
- package/templates/starter/africode.config.js +40 -0
- package/templates/starter/package.json +14 -0
- package/templates/starter/src/pages/index.html +46 -0
- package/templates/starter/src/pages/index.js +32 -0
- package/templates/starter/src/styles/main.css +4 -0
- package/templates/starter-3d/.env.example +7 -0
- package/templates/starter-3d/africode.config.js +29 -0
- package/templates/starter-3d/components/af-model-viewer.js +125 -0
- package/templates/starter-3d/package.json +15 -0
- package/templates/starter-3d/src/pages/index.html +46 -0
- package/templates/starter-3d/src/pages/index.js +50 -0
- package/templates/starter-3d/src/styles/main.css +4 -0
- package/templates/starter-react/.env.example +15 -0
- package/templates/starter-react/africode.config.js +40 -0
- package/templates/starter-react/package.json +16 -0
- package/templates/starter-react/src/pages/index.html +46 -0
- package/templates/starter-react/src/pages/index.js +68 -0
- package/templates/starter-react/src/styles/main.css +4 -0
- package/templates/starter-tailwind/.env.example +15 -0
- package/templates/starter-tailwind/africode.config.js +40 -0
- package/templates/starter-tailwind/package.json +20 -0
- package/templates/starter-tailwind/src/pages/index.html +46 -0
- package/templates/starter-tailwind/src/pages/index.js +37 -0
- package/templates/starter-tailwind/src/styles/main.css +4 -0
- package/templates/starter-tailwind/src/styles/tailwind.css +1 -0
- package/templates/starter-tailwind/src/tailwind-loader.js +30 -0
package/bin/scaffold.ts
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* create-africode v5.0.0
|
|
4
|
+
*
|
|
5
|
+
* Scaffolds a new AfriCode project with autonomous lifecycle management
|
|
6
|
+
* usage: bunx create-africode my-app
|
|
7
|
+
*
|
|
8
|
+
* @module bin/scaffold
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { mkdir, writeFile, cp, readFile } from 'fs/promises';
|
|
12
|
+
import { existsSync } from 'fs';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
18
|
+
const FRAMEWORK_ROOT = path.resolve(__dirname, '..');
|
|
19
|
+
|
|
20
|
+
const colors = {
|
|
21
|
+
green: '\x1b[32m',
|
|
22
|
+
gold: '\x1b[33m',
|
|
23
|
+
blue: '\x1b[34m',
|
|
24
|
+
red: '\x1b[31m',
|
|
25
|
+
reset: '\x1b[0m',
|
|
26
|
+
bold: '\x1b[1m',
|
|
27
|
+
dim: '\x1b[2m'
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export async function createProject(targetName?: string): Promise<void> {
|
|
31
|
+
const projectName = targetName || 'africode-app';
|
|
32
|
+
|
|
33
|
+
console.log(`
|
|
34
|
+
${colors.gold}╔═══════════════════════════════════════╗
|
|
35
|
+
║ ${colors.green}Creating AfriCode v5.0.0 Project${colors.gold} ║
|
|
36
|
+
╚═══════════════════════════════════════╝${colors.reset}
|
|
37
|
+
`);
|
|
38
|
+
|
|
39
|
+
const projectDir = path.join(process.cwd(), projectName);
|
|
40
|
+
|
|
41
|
+
if (existsSync(projectDir)) {
|
|
42
|
+
console.log(`${colors.red}✗ Directory "${projectName}" already exists${colors.reset}`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(`${colors.dim} Creating ${projectName}...${colors.reset}\n`);
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// 1. Create Project Root
|
|
50
|
+
await mkdir(projectDir);
|
|
51
|
+
|
|
52
|
+
// 2. Create package.json
|
|
53
|
+
const packageJson = {
|
|
54
|
+
"name": projectName,
|
|
55
|
+
"version": "1.0.0",
|
|
56
|
+
"description": `AfriCode v5.0.0 application - ${projectName}`,
|
|
57
|
+
"type": "module",
|
|
58
|
+
"scripts": {
|
|
59
|
+
"dev": "bun run --hot src/server.ts",
|
|
60
|
+
"build": "bun build src/server.ts --outdir dist",
|
|
61
|
+
"start": "bun run dist/server.js",
|
|
62
|
+
"test": "bun test",
|
|
63
|
+
"lint": "eslint src --ext .ts,.tsx",
|
|
64
|
+
"generate": "africode generate a2ui"
|
|
65
|
+
},
|
|
66
|
+
"dependencies": {
|
|
67
|
+
"@africode/core": "^5.0.0"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@types/bun": "latest",
|
|
71
|
+
"eslint": "^9.0.0",
|
|
72
|
+
"typescript": "^5.0.0"
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
await writeFile(
|
|
77
|
+
path.join(projectDir, 'package.json'),
|
|
78
|
+
JSON.stringify(packageJson, null, 2)
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// 3. Create src directory structure
|
|
82
|
+
await mkdir(path.join(projectDir, 'src'), { recursive: true });
|
|
83
|
+
await mkdir(path.join(projectDir, 'src', 'pages'), { recursive: true });
|
|
84
|
+
await mkdir(path.join(projectDir, 'src', 'components'), { recursive: true });
|
|
85
|
+
await mkdir(path.join(projectDir, 'src', 'core'), { recursive: true });
|
|
86
|
+
await mkdir(path.join(projectDir, 'public'), { recursive: true });
|
|
87
|
+
|
|
88
|
+
// 4. Create server.ts
|
|
89
|
+
const serverContent = `#!/usr/bin/env bun
|
|
90
|
+
/**
|
|
91
|
+
* ${projectName} - AfriCode v5.0.0 Server
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
import { AfriCode } from '@africode/core';
|
|
95
|
+
|
|
96
|
+
const app = new AfriCode({
|
|
97
|
+
port: 3000,
|
|
98
|
+
pagesDir: 'src/pages',
|
|
99
|
+
publicDir: 'public'
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
console.log('🚀 Starting ${projectName}...');
|
|
103
|
+
app.start();
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
await writeFile(path.join(projectDir, 'src', 'server.ts'), serverContent);
|
|
107
|
+
|
|
108
|
+
// 5. Create index page
|
|
109
|
+
const indexPage = `/**
|
|
110
|
+
* Home Page - ${projectName}
|
|
111
|
+
*/
|
|
112
|
+
|
|
113
|
+
import { html } from '@africode/core';
|
|
114
|
+
|
|
115
|
+
export default function Home() {
|
|
116
|
+
return html\`
|
|
117
|
+
<af-app>
|
|
118
|
+
<af-navbar>
|
|
119
|
+
<h1 slot="brand">${projectName}</h1>
|
|
120
|
+
</af-navbar>
|
|
121
|
+
|
|
122
|
+
<main>
|
|
123
|
+
<af-hero>
|
|
124
|
+
<h1>Welcome to ${projectName}</h1>
|
|
125
|
+
<p>Built with AfriCode v5.0.0</p>
|
|
126
|
+
<af-button variant="primary" size="lg">Get Started</af-button>
|
|
127
|
+
</af-hero>
|
|
128
|
+
|
|
129
|
+
<af-section>
|
|
130
|
+
<af-card>
|
|
131
|
+
<h2>African-Centric Framework</h2>
|
|
132
|
+
<p>Real-time performance with cultural patterns</p>
|
|
133
|
+
</af-card>
|
|
134
|
+
</af-section>
|
|
135
|
+
</main>
|
|
136
|
+
</af-app>
|
|
137
|
+
\`;
|
|
138
|
+
}
|
|
139
|
+
`;
|
|
140
|
+
|
|
141
|
+
await writeFile(path.join(projectDir, 'src', 'pages', 'index.ts'), indexPage);
|
|
142
|
+
|
|
143
|
+
// 6. Create tsconfig.json
|
|
144
|
+
const tsconfig = {
|
|
145
|
+
"compilerOptions": {
|
|
146
|
+
"target": "ES2022",
|
|
147
|
+
"module": "ESNext",
|
|
148
|
+
"moduleResolution": "bundler",
|
|
149
|
+
"allowSyntheticDefaultImports": true,
|
|
150
|
+
"esModuleInterop": true,
|
|
151
|
+
"jsx": "react-jsx",
|
|
152
|
+
"jsxImportSource": "@africode/core",
|
|
153
|
+
"strict": true,
|
|
154
|
+
"skipLibCheck": true,
|
|
155
|
+
"forceConsistentCasingInFileNames": true,
|
|
156
|
+
"resolveJsonModule": true,
|
|
157
|
+
"isolatedModules": true,
|
|
158
|
+
"noEmit": true,
|
|
159
|
+
"types": ["bun-types"]
|
|
160
|
+
},
|
|
161
|
+
"include": ["src/**/*"],
|
|
162
|
+
"exclude": ["node_modules", "dist"]
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
await writeFile(
|
|
166
|
+
path.join(projectDir, 'tsconfig.json'),
|
|
167
|
+
JSON.stringify(tsconfig, null, 2)
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// 7. Create README
|
|
171
|
+
const readme = `# ${projectName}
|
|
172
|
+
|
|
173
|
+
Built with AfriCode v5.0.0 - African-centric full-stack framework.
|
|
174
|
+
|
|
175
|
+
## Getting Started
|
|
176
|
+
|
|
177
|
+
\`\`\`bash
|
|
178
|
+
cd ${projectName}
|
|
179
|
+
bun install
|
|
180
|
+
bun run dev
|
|
181
|
+
\`\`\`
|
|
182
|
+
|
|
183
|
+
## Features
|
|
184
|
+
|
|
185
|
+
- ⚡ Bun runtime with 5ms startup
|
|
186
|
+
- 🎨 African cultural patterns
|
|
187
|
+
- 🔐 Fintech compliance (NIDA, TIPS)
|
|
188
|
+
- 🤖 AI-safe component system
|
|
189
|
+
- 📱 Real-time WebSocket support
|
|
190
|
+
|
|
191
|
+
## Scripts
|
|
192
|
+
|
|
193
|
+
- \`bun run dev\` - Start development server with hot reload
|
|
194
|
+
- \`bun run build\` - Build for production
|
|
195
|
+
- \`bun run start\` - Start production server
|
|
196
|
+
- \`bun run test\` - Run tests
|
|
197
|
+
- \`bun run generate\` - Generate A2UI schema
|
|
198
|
+
`;
|
|
199
|
+
|
|
200
|
+
await writeFile(path.join(projectDir, 'README.md'), readme);
|
|
201
|
+
|
|
202
|
+
console.log(`${colors.green}✓ Project "${projectName}" created successfully!${colors.reset}`);
|
|
203
|
+
console.log(`\n${colors.blue}Next steps:${colors.reset}`);
|
|
204
|
+
console.log(` cd ${projectName}`);
|
|
205
|
+
console.log(` bun install`);
|
|
206
|
+
console.log(` bun run dev`);
|
|
207
|
+
console.log(`\n${colors.dim}Happy coding with AfriCode! 🇹🇿${colors.reset}`);
|
|
208
|
+
|
|
209
|
+
} catch (error) {
|
|
210
|
+
console.error(`${colors.red}✗ Failed to create project: ${error.message}${colors.reset}`);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// CLI runner
|
|
216
|
+
if (import.meta.main) {
|
|
217
|
+
const projectName = process.argv[2];
|
|
218
|
+
createProject(projectName);
|
|
219
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Accordion Component
|
|
3
|
+
*
|
|
4
|
+
* Collapsible content sections for FAQs and organized information.
|
|
5
|
+
* Suitable for: FAQs, Settings, Documentation, Course content
|
|
6
|
+
*
|
|
7
|
+
* @module components/accordion
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { AfriCodeComponent, registerComponent } from './base.js';
|
|
11
|
+
|
|
12
|
+
export class AfriAccordion extends AfriCodeComponent {
|
|
13
|
+
static get observedAttributes() {
|
|
14
|
+
return ['theme', 'multiple'];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
super();
|
|
19
|
+
this._openItems = new Set();
|
|
20
|
+
this._uniqueId = Math.random().toString(36).substr(2, 9);
|
|
21
|
+
this.render();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
connectedCallback() {
|
|
25
|
+
this._setupAccordion();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_setupAccordion() {
|
|
29
|
+
const headers = this.shadowRoot.querySelectorAll('.accordion-header');
|
|
30
|
+
headers.forEach((header, index) => {
|
|
31
|
+
header.addEventListener('click', () => {
|
|
32
|
+
this._toggleItem(index);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
_toggleItem(index) {
|
|
38
|
+
const multiple = this.hasAttribute('multiple');
|
|
39
|
+
|
|
40
|
+
if (this._openItems.has(index)) {
|
|
41
|
+
this._openItems.delete(index);
|
|
42
|
+
} else {
|
|
43
|
+
if (!multiple) {
|
|
44
|
+
this._openItems.clear();
|
|
45
|
+
}
|
|
46
|
+
this._openItems.add(index);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.render();
|
|
50
|
+
this._setupAccordion();
|
|
51
|
+
this.emit('af-toggle', { index, open: this._openItems.has(index) });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
attributeChangedCallback() {
|
|
55
|
+
this.render();
|
|
56
|
+
this._setupAccordion();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
render() {
|
|
60
|
+
const theme = this.getAttribute('theme') || 'tanzania';
|
|
61
|
+
const itemsSlot = this.querySelector('[slot="items"]');
|
|
62
|
+
const items = itemsSlot ? Array.from(itemsSlot.children) : [];
|
|
63
|
+
|
|
64
|
+
const themes = {
|
|
65
|
+
tanzania: { accent: '#1EB53A' },
|
|
66
|
+
maasai: { accent: '#FF0000' },
|
|
67
|
+
ndebele: { accent: '#4169E1' }
|
|
68
|
+
};
|
|
69
|
+
const t = themes[theme] || themes.tanzania;
|
|
70
|
+
|
|
71
|
+
this.shadowRoot.innerHTML = `
|
|
72
|
+
<style>
|
|
73
|
+
:host {
|
|
74
|
+
display: block;
|
|
75
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.accordion {
|
|
79
|
+
border-radius: 8px;
|
|
80
|
+
overflow: hidden;
|
|
81
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.accordion-item {
|
|
85
|
+
border-bottom: 1px solid #eee;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.accordion-item:last-child {
|
|
89
|
+
border-bottom: none;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.accordion-header {
|
|
93
|
+
width: 100%;
|
|
94
|
+
padding: 16px 21px;
|
|
95
|
+
background: white;
|
|
96
|
+
border: none;
|
|
97
|
+
text-align: left;
|
|
98
|
+
font-family: inherit;
|
|
99
|
+
font-size: 15px;
|
|
100
|
+
font-weight: 600;
|
|
101
|
+
color: #333;
|
|
102
|
+
cursor: pointer;
|
|
103
|
+
display: flex;
|
|
104
|
+
justify-content: space-between;
|
|
105
|
+
align-items: center;
|
|
106
|
+
transition: all 200ms ease;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.accordion-header:hover {
|
|
110
|
+
background: #f8f9fa;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.accordion-header.active {
|
|
114
|
+
color: ${t.accent};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.accordion-icon {
|
|
118
|
+
font-size: 12px;
|
|
119
|
+
transition: transform 300ms ease;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.accordion-header.active .accordion-icon {
|
|
123
|
+
transform: rotate(180deg);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.accordion-content {
|
|
127
|
+
max-height: 0;
|
|
128
|
+
overflow: hidden;
|
|
129
|
+
background: #fafafa;
|
|
130
|
+
transition: max-height 300ms ease-out;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.accordion-content.open {
|
|
134
|
+
max-height: 500px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.accordion-body {
|
|
138
|
+
padding: 16px 21px;
|
|
139
|
+
color: #555;
|
|
140
|
+
line-height: 1.6;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
::slotted([slot="items"]) {
|
|
144
|
+
display: none;
|
|
145
|
+
}
|
|
146
|
+
</style>
|
|
147
|
+
|
|
148
|
+
<div class="accordion">
|
|
149
|
+
${items.map((item, i) => {
|
|
150
|
+
const expanded = this._openItems.has(i);
|
|
151
|
+
const headerId = `accordion-header-${this._uniqueId}-${i}`;
|
|
152
|
+
const panelId = `accordion-panel-${this._uniqueId}-${i}`;
|
|
153
|
+
return `
|
|
154
|
+
<div class="accordion-item">
|
|
155
|
+
<button
|
|
156
|
+
class="accordion-header ${expanded ? 'active' : ''}"
|
|
157
|
+
aria-expanded="${expanded}"
|
|
158
|
+
aria-controls="${panelId}"
|
|
159
|
+
id="${headerId}"
|
|
160
|
+
>
|
|
161
|
+
<span>${item.getAttribute('title') || `Item ${i + 1}`}</span>
|
|
162
|
+
<span class="accordion-icon">▼</span>
|
|
163
|
+
</button>
|
|
164
|
+
<div
|
|
165
|
+
class="accordion-content ${expanded ? 'open' : ''}"
|
|
166
|
+
id="${panelId}"
|
|
167
|
+
role="region"
|
|
168
|
+
aria-labelledby="${headerId}"
|
|
169
|
+
>
|
|
170
|
+
<div class="accordion-body">
|
|
171
|
+
${item.innerHTML}
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
`}).join('')}
|
|
176
|
+
</div>
|
|
177
|
+
<slot name="items"></slot>
|
|
178
|
+
`;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
registerComponent('af-accordion', AfriAccordion);
|
|
183
|
+
export default AfriAccordion;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Alert/Notification Component
|
|
3
|
+
*
|
|
4
|
+
* Status messages, alerts, and notifications with cultural theming.
|
|
5
|
+
* Suitable for: Healthcare alerts, Financial notifications, System messages
|
|
6
|
+
*
|
|
7
|
+
* @module components/alert
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { AfriCodeComponent, registerComponent } from './base.js';
|
|
11
|
+
|
|
12
|
+
export class AfriAlert extends AfriCodeComponent {
|
|
13
|
+
static get observedAttributes() {
|
|
14
|
+
return ['type', 'title', 'dismissible'];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
super();
|
|
19
|
+
this.render();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
connectedCallback() {
|
|
23
|
+
this.shadowRoot.querySelector('.close-btn')?.addEventListener('click', () => {
|
|
24
|
+
this.remove();
|
|
25
|
+
this.emit('af-dismiss');
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
attributeChangedCallback() {
|
|
30
|
+
this.render();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
render() {
|
|
34
|
+
const type = this.getAttribute('type') || 'info';
|
|
35
|
+
const title = this.getAttribute('title') || '';
|
|
36
|
+
const dismissible = this.hasAttribute('dismissible');
|
|
37
|
+
|
|
38
|
+
const types = {
|
|
39
|
+
success: { bg: '#d4edda', border: '#1EB53A', text: '#155724', icon: '✓' },
|
|
40
|
+
warning: { bg: '#fff3cd', border: '#FCD116', text: '#856404', icon: '⚠' },
|
|
41
|
+
error: { bg: '#f8d7da', border: '#DC3545', text: '#721c24', icon: '✕' },
|
|
42
|
+
info: { bg: '#d1ecf1', border: '#00A3DD', text: '#0c5460', icon: 'ℹ' }
|
|
43
|
+
};
|
|
44
|
+
const t = types[type] || types.info;
|
|
45
|
+
|
|
46
|
+
this.shadowRoot.innerHTML = `
|
|
47
|
+
<style>
|
|
48
|
+
:host {
|
|
49
|
+
display: block;
|
|
50
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.alert {
|
|
54
|
+
padding: 13px 21px;
|
|
55
|
+
border-radius: 5px;
|
|
56
|
+
border-left: 4px solid ${t.border};
|
|
57
|
+
background: ${t.bg};
|
|
58
|
+
color: ${t.text};
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: flex-start;
|
|
61
|
+
gap: 13px;
|
|
62
|
+
animation: slideIn 300ms ease-out;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@keyframes slideIn {
|
|
66
|
+
from {
|
|
67
|
+
opacity: 0;
|
|
68
|
+
transform: translateY(-10px);
|
|
69
|
+
}
|
|
70
|
+
to {
|
|
71
|
+
opacity: 1;
|
|
72
|
+
transform: translateY(0);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.icon {
|
|
77
|
+
font-size: 18px;
|
|
78
|
+
flex-shrink: 0;
|
|
79
|
+
width: 24px;
|
|
80
|
+
height: 24px;
|
|
81
|
+
display: flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: center;
|
|
84
|
+
background: ${t.border};
|
|
85
|
+
color: white;
|
|
86
|
+
border-radius: 50%;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.content {
|
|
90
|
+
flex: 1;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.title {
|
|
94
|
+
font-weight: 600;
|
|
95
|
+
margin-bottom: 3px;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.close-btn {
|
|
99
|
+
background: none;
|
|
100
|
+
border: none;
|
|
101
|
+
font-size: 18px;
|
|
102
|
+
cursor: pointer;
|
|
103
|
+
color: ${t.text};
|
|
104
|
+
opacity: 0.7;
|
|
105
|
+
padding: 0;
|
|
106
|
+
line-height: 1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.close-btn:hover {
|
|
110
|
+
opacity: 1;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
::slotted(*) {
|
|
114
|
+
margin: 0;
|
|
115
|
+
}
|
|
116
|
+
</style>
|
|
117
|
+
|
|
118
|
+
<div class="alert" role="alert">
|
|
119
|
+
<span class="icon">${t.icon}</span>
|
|
120
|
+
<div class="content">
|
|
121
|
+
${title ? `<div class="title">${title}</div>` : ''}
|
|
122
|
+
<slot></slot>
|
|
123
|
+
</div>
|
|
124
|
+
${dismissible ? '<button class="close-btn" aria-label="Dismiss">×</button>' : ''}
|
|
125
|
+
</div>
|
|
126
|
+
`;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
registerComponent('af-alert', AfriAlert);
|
|
131
|
+
export default AfriAlert;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Auth Component
|
|
3
|
+
* A "Profound Dark" themed login/register form.
|
|
4
|
+
*
|
|
5
|
+
* Usage: <af-auth></af-auth>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AfriCodeComponent, registerComponent } from './base.js';
|
|
9
|
+
|
|
10
|
+
export class AfriAuth extends AfriCodeComponent {
|
|
11
|
+
constructor() {
|
|
12
|
+
super();
|
|
13
|
+
this.mode = 'login'; // login | register
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
connectedCallback() {
|
|
17
|
+
this.render();
|
|
18
|
+
this.setupEvents();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
setupEvents() {
|
|
22
|
+
const form = this.shadowRoot.querySelector('form');
|
|
23
|
+
const toggle = this.shadowRoot.querySelector('.toggle-mode');
|
|
24
|
+
|
|
25
|
+
if (toggle) {
|
|
26
|
+
toggle.addEventListener('click', (e) => {
|
|
27
|
+
e.preventDefault();
|
|
28
|
+
this.mode = this.mode === 'login' ? 'register' : 'login';
|
|
29
|
+
this.render();
|
|
30
|
+
this.setupEvents();
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (form) {
|
|
35
|
+
form.addEventListener('submit', async (e) => {
|
|
36
|
+
e.preventDefault();
|
|
37
|
+
const email = this.shadowRoot.querySelector('#email').value;
|
|
38
|
+
const password = this.shadowRoot.querySelector('#password').value;
|
|
39
|
+
const feedback = this.shadowRoot.querySelector('.feedback');
|
|
40
|
+
|
|
41
|
+
feedback.textContent = 'Processing...';
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const res = await fetch('/api/auth', {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: { 'Content-Type': 'application/json' },
|
|
47
|
+
body: JSON.stringify({ action: this.mode, email, password })
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const data = await res.json();
|
|
51
|
+
|
|
52
|
+
if (data.error) {
|
|
53
|
+
feedback.textContent = data.error;
|
|
54
|
+
feedback.style.color = '#ef4444';
|
|
55
|
+
} else {
|
|
56
|
+
feedback.textContent = 'Success! Redirecting...';
|
|
57
|
+
feedback.style.color = '#22c55e';
|
|
58
|
+
setTimeout(() => window.location.href = '/', 1000);
|
|
59
|
+
}
|
|
60
|
+
} catch (err) {
|
|
61
|
+
feedback.textContent = 'Network Error';
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
render() {
|
|
68
|
+
const isLogin = this.mode === 'login';
|
|
69
|
+
|
|
70
|
+
this.shadowRoot.innerHTML = `
|
|
71
|
+
<style>
|
|
72
|
+
:host {
|
|
73
|
+
display: block;
|
|
74
|
+
font-family: 'Inter', system-ui;
|
|
75
|
+
max-width: 400px;
|
|
76
|
+
margin: 0 auto;
|
|
77
|
+
}
|
|
78
|
+
.auth-card {
|
|
79
|
+
background: #1e293b;
|
|
80
|
+
border: 1px solid #334155;
|
|
81
|
+
border-radius: 12px;
|
|
82
|
+
padding: 32px;
|
|
83
|
+
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
|
|
84
|
+
}
|
|
85
|
+
h2 {
|
|
86
|
+
margin-top: 0;
|
|
87
|
+
color: white;
|
|
88
|
+
text-align: center;
|
|
89
|
+
font-weight: 800;
|
|
90
|
+
}
|
|
91
|
+
.input-group {
|
|
92
|
+
margin-bottom: 20px;
|
|
93
|
+
}
|
|
94
|
+
label {
|
|
95
|
+
display: block;
|
|
96
|
+
color: #94a3b8;
|
|
97
|
+
font-size: 0.9rem;
|
|
98
|
+
margin-bottom: 8px;
|
|
99
|
+
}
|
|
100
|
+
input {
|
|
101
|
+
width: 100%;
|
|
102
|
+
padding: 12px;
|
|
103
|
+
background: #0f172a;
|
|
104
|
+
border: 1px solid #334155;
|
|
105
|
+
border-radius: 6px;
|
|
106
|
+
color: white;
|
|
107
|
+
font-size: 1rem;
|
|
108
|
+
box-sizing: border-box;
|
|
109
|
+
}
|
|
110
|
+
input:focus {
|
|
111
|
+
border-color: #1EB53A;
|
|
112
|
+
outline: none;
|
|
113
|
+
}
|
|
114
|
+
button {
|
|
115
|
+
width: 100%;
|
|
116
|
+
padding: 12px;
|
|
117
|
+
background: #1EB53A;
|
|
118
|
+
color: white;
|
|
119
|
+
border: none;
|
|
120
|
+
border-radius: 6px;
|
|
121
|
+
font-weight: 700;
|
|
122
|
+
cursor: pointer;
|
|
123
|
+
font-size: 1rem;
|
|
124
|
+
transition: all 0.2s;
|
|
125
|
+
}
|
|
126
|
+
button:hover {
|
|
127
|
+
opacity: 0.9;
|
|
128
|
+
transform: translateY(-1px);
|
|
129
|
+
}
|
|
130
|
+
.footer {
|
|
131
|
+
margin-top: 20px;
|
|
132
|
+
text-align: center;
|
|
133
|
+
font-size: 0.9rem;
|
|
134
|
+
color: #94a3b8;
|
|
135
|
+
}
|
|
136
|
+
.toggle-mode {
|
|
137
|
+
color: #FCD116;
|
|
138
|
+
cursor: pointer;
|
|
139
|
+
text-decoration: underline;
|
|
140
|
+
}
|
|
141
|
+
.feedback {
|
|
142
|
+
text-align: center;
|
|
143
|
+
margin-bottom: 16px;
|
|
144
|
+
min-height: 20px;
|
|
145
|
+
font-size: 0.9rem;
|
|
146
|
+
}
|
|
147
|
+
</style>
|
|
148
|
+
|
|
149
|
+
<div class="auth-card">
|
|
150
|
+
<h2>${isLogin ? 'Welcome Back' : 'Create Account'}</h2>
|
|
151
|
+
<div class="feedback"></div>
|
|
152
|
+
<form>
|
|
153
|
+
<div class="input-group">
|
|
154
|
+
<label for="email">Email Address</label>
|
|
155
|
+
<input type="email" id="email" required placeholder="you@example.com">
|
|
156
|
+
</div>
|
|
157
|
+
<div class="input-group">
|
|
158
|
+
<label for="password">Password</label>
|
|
159
|
+
<input type="password" id="password" required placeholder="••••••••">
|
|
160
|
+
</div>
|
|
161
|
+
<button type="submit">${isLogin ? 'Sign In' : 'Sign Up'}</button>
|
|
162
|
+
</form>
|
|
163
|
+
<div class="footer">
|
|
164
|
+
${isLogin ? 'New here?' : 'Already have an account?'}
|
|
165
|
+
<span class="toggle-mode">${isLogin ? 'Create an account' : 'Sign in'}</span>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
`;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
registerComponent('af-auth', AfriAuth);
|