@africode/core 5.0.6 → 5.0.7
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/components/breadcrumb.js +72 -0
- package/components/carousel.js +32 -0
- package/components/cart-drawer.js +28 -0
- package/components/code-block.js +20 -0
- package/components/contact-form.js +25 -0
- package/components/cookie-consent.js +23 -0
- package/components/cta-banner.js +38 -0
- package/components/dashboard-activity-list.js +103 -0
- package/components/dashboard-card.js +85 -0
- package/components/dashboard-metric.js +81 -0
- package/components/dashboard-shell.js +85 -0
- package/components/dashboard-topbar.js +84 -0
- package/components/faq-accordion.js +23 -0
- package/components/feature-grid.js +85 -0
- package/components/file-uploader.js +22 -0
- package/components/filter-bar.js +19 -0
- package/components/footer.js +113 -0
- package/components/gallery.js +22 -0
- package/components/index.js +42 -0
- package/components/newsletter-signup.js +24 -0
- package/components/pagination.js +31 -0
- package/components/pricing-card.js +100 -0
- package/components/product-card.js +41 -0
- package/components/search-box.js +24 -0
- package/components/simple-chart.js +22 -0
- package/components/stepper.js +28 -0
- package/components/team-grid.js +23 -0
- package/components/testimonial.js +94 -0
- package/components/ui-avatar.js +79 -0
- package/components/ui-chip.js +87 -0
- package/components/ui-popover.js +84 -0
- package/components/ui-progress-ring.js +60 -0
- package/components/ui-select.js +85 -0
- package/components/ui-stat.js +75 -0
- package/components/ui-table.js +69 -0
- package/components/ui-tabs.js +99 -0
- package/components/ui-tag.js +64 -0
- package/components/ui-textarea.js +84 -0
- package/components/ui-tooltip.js +66 -0
- package/components/video-player.js +22 -0
- package/dist/africode.js +1480 -549
- package/dist/africode.js.map +25 -4
- package/dist/build-info.json +3 -3
- package/dist/components.js +1506 -575
- package/dist/components.js.map +25 -4
- package/package.json +1 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Breadcrumb Component
|
|
3
|
+
*
|
|
4
|
+
* Simple breadcrumb trail for website navigation.
|
|
5
|
+
* @module components/breadcrumb
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
9
|
+
|
|
10
|
+
export class AfriBreadcrumb extends AfriCodeComponent {
|
|
11
|
+
static get observedAttributes() {
|
|
12
|
+
return ['separator'];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.render();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
attributeChangedCallback() {
|
|
21
|
+
this.render();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
render() {
|
|
25
|
+
const separator = this.getAttribute('separator') || '/';
|
|
26
|
+
|
|
27
|
+
this.shadowRoot.innerHTML = html`
|
|
28
|
+
<style>
|
|
29
|
+
:host {
|
|
30
|
+
display: block;
|
|
31
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
32
|
+
color: #475569;
|
|
33
|
+
font-size: 0.9rem;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
nav {
|
|
37
|
+
display: flex;
|
|
38
|
+
flex-wrap: wrap;
|
|
39
|
+
gap: 0.5rem;
|
|
40
|
+
align-items: center;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.crumb {
|
|
44
|
+
display: inline-flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
gap: 0.35rem;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.separator {
|
|
50
|
+
color: #cbd5e1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
::slotted(a) {
|
|
54
|
+
color: #0f172a;
|
|
55
|
+
text-decoration: none;
|
|
56
|
+
font-weight: 600;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
::slotted(a:hover) {
|
|
60
|
+
text-decoration: underline;
|
|
61
|
+
}
|
|
62
|
+
</style>
|
|
63
|
+
|
|
64
|
+
<nav aria-label="Breadcrumb">
|
|
65
|
+
<slot></slot>
|
|
66
|
+
</nav>
|
|
67
|
+
`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
registerComponent('af-breadcrumb', AfriBreadcrumb);
|
|
72
|
+
export default AfriBreadcrumb;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Carousel
|
|
3
|
+
*
|
|
4
|
+
* Minimal image/content carousel with navigation.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
8
|
+
|
|
9
|
+
export class AfriCarousel extends AfriCodeComponent {
|
|
10
|
+
constructor(){ super(); this._idx=0; this.render(); }
|
|
11
|
+
connectedCallback(){ this._bind(); }
|
|
12
|
+
_bind(){ this.shadowRoot.addEventListener('click', (e)=>{ const t=e.target; if(t.matches('.next')){this.next();} if(t.matches('.prev')){this.prev();}}); }
|
|
13
|
+
next(){ const items=this.shadowRoot.querySelectorAll('.slide'); if(items.length===0) return; items[this._idx].classList.remove('active'); this._idx=(this._idx+1)%items.length; items[this._idx].classList.add('active'); }
|
|
14
|
+
prev(){ const items=this.shadowRoot.querySelectorAll('.slide'); if(items.length===0) return; items[this._idx].classList.remove('active'); this._idx=(this._idx-1+items.length)%items.length; items[this._idx].classList.add('active'); }
|
|
15
|
+
render(){ this.shadowRoot.innerHTML = html`
|
|
16
|
+
<style>
|
|
17
|
+
:host{display:block;position:relative;overflow:hidden}
|
|
18
|
+
.slides{display:flex}
|
|
19
|
+
.slide{min-width:100%;transition:transform 300ms ease;display:none}
|
|
20
|
+
.slide.active{display:block}
|
|
21
|
+
.nav{position:absolute;top:50%;transform:translateY(-50%);width:100%;display:flex;justify-content:space-between;padding:0 1rem}
|
|
22
|
+
.btn{background:rgba(0,0,0,0.4);border:none;color:white;padding:0.5rem 0.7rem;border-radius:8px;cursor:pointer}
|
|
23
|
+
</style>
|
|
24
|
+
<div class="viewport">
|
|
25
|
+
<slot></slot>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="nav"><button class="btn prev">‹</button><button class="btn next">›</button></div>
|
|
28
|
+
`; }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
registerComponent('af-carousel', AfriCarousel);
|
|
32
|
+
export default AfriCarousel;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Cart Drawer
|
|
3
|
+
*
|
|
4
|
+
* A slide-over cart drawer for e-commerce flows.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
8
|
+
|
|
9
|
+
export class AfriCartDrawer extends AfriCodeComponent {
|
|
10
|
+
constructor(){ super(); this.open=false; this.render(); }
|
|
11
|
+
connectedCallback(){ this.shadowRoot.addEventListener('click',(e)=>{ if(e.target.matches('.close')) this.close() }) }
|
|
12
|
+
openDrawer(){ this.open=true; this.render(); }
|
|
13
|
+
close(){ this.open=false; this.render(); }
|
|
14
|
+
render(){ this.shadowRoot.innerHTML = html`
|
|
15
|
+
<style>
|
|
16
|
+
:host{display:block}
|
|
17
|
+
.backdrop{position:fixed;inset:0;background:rgba(2,6,23,0.5);display:${this.open?'block':'none'};z-index:1200}
|
|
18
|
+
.drawer{position:fixed;right:0;top:0;height:100vh;width:360px;background:#fff;box-shadow:-20px 0 40px rgba(2,6,23,0.2);transform:translateX(${this.open?'0':'100%'});transition:transform 300ms}
|
|
19
|
+
.header{padding:16px;display:flex;justify-content:space-between;align-items:center}
|
|
20
|
+
.items{padding:16px;display:grid;gap:12px;overflow:auto;height:calc(100vh - 160px)}
|
|
21
|
+
.footer{padding:16px;border-top:1px solid #eef2f7}
|
|
22
|
+
</style>
|
|
23
|
+
<div class="backdrop"><div class="drawer"><div class="header"><strong>Cart</strong><button class="close">✕</button></div><div class="items"><slot></slot></div><div class="footer"><button class="checkout">Checkout</button></div></div></div>
|
|
24
|
+
`; this.shadowRoot.querySelector('.checkout')?.addEventListener('click',()=>this.emit('checkout')) }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
registerComponent('af-cart-drawer', AfriCartDrawer);
|
|
28
|
+
export default AfriCartDrawer;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Code Block
|
|
3
|
+
*
|
|
4
|
+
* Syntax-highlighted code block wrapper (lightweight).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
8
|
+
|
|
9
|
+
export class AfriCodeBlock extends AfriCodeComponent {
|
|
10
|
+
constructor(){ super(); this.render(); }
|
|
11
|
+
render(){ this.shadowRoot.innerHTML = html`
|
|
12
|
+
<style>
|
|
13
|
+
pre{background:#0f172a;color:#e6eef8;padding:12px;border-radius:8px;overflow:auto}
|
|
14
|
+
</style>
|
|
15
|
+
<pre><code><slot></slot></code></pre>
|
|
16
|
+
` }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
registerComponent('af-code-block', AfriCodeBlock);
|
|
20
|
+
export default AfriCodeBlock;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Contact Form
|
|
3
|
+
*
|
|
4
|
+
* Ready-to-use contact form with basic validation.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
8
|
+
|
|
9
|
+
export class AfriContactForm extends AfriCodeComponent {
|
|
10
|
+
constructor(){ super(); this.render(); }
|
|
11
|
+
connectedCallback(){ this.shadowRoot.addEventListener('submit',(e)=>{ if(e.target.matches('form')) this._onSubmit(e) }, true); }
|
|
12
|
+
async _onSubmit(e){ e.preventDefault(); const data=new FormData(e.target); this.emit('submit', Object.fromEntries(data.entries())); }
|
|
13
|
+
render(){ this.shadowRoot.innerHTML = html`
|
|
14
|
+
<style>
|
|
15
|
+
:host{display:block}
|
|
16
|
+
form{display:grid;gap:8px}
|
|
17
|
+
input,textarea{padding:10px;border-radius:8px;border:1px solid #e5e7eb}
|
|
18
|
+
button{padding:10px;border-radius:8px;background:#1EB53A;color:white;border:none}
|
|
19
|
+
</style>
|
|
20
|
+
<form><input name="name" placeholder="Name" required/><input name="email" placeholder="Email" type="email" required/><textarea name="message" placeholder="Message" rows="4" required></textarea><button type="submit">Send</button></form>
|
|
21
|
+
`}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
registerComponent('af-contact-form', AfriContactForm);
|
|
25
|
+
export default AfriContactForm;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Cookie Consent
|
|
3
|
+
*
|
|
4
|
+
* Simple cookie consent bar.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
8
|
+
|
|
9
|
+
export class AfriCookieConsent extends AfriCodeComponent {
|
|
10
|
+
constructor(){ super(); this.visible=!localStorage.getItem('af_cookie'); this.render(); }
|
|
11
|
+
connectedCallback(){ this.shadowRoot.addEventListener('click',(e)=>{ if(e.target.matches('.accept')){ localStorage.setItem('af_cookie','1'); this.visible=false; this.render(); this.emit('consent', {accepted:true}) }}) }
|
|
12
|
+
render(){ this.shadowRoot.innerHTML = html`
|
|
13
|
+
<style>
|
|
14
|
+
:host{display:block}
|
|
15
|
+
.bar{position:fixed;left:16px;right:16px;bottom:16px;padding:12px;border-radius:12px;background:#111;color:#fff;display:${this.visible?'flex':'none'};justify-content:space-between;align-items:center;gap:12px}
|
|
16
|
+
button{background:#1EB53A;border:none;padding:8px 12px;border-radius:8px;color:#fff}
|
|
17
|
+
</style>
|
|
18
|
+
<div class="bar"><div>We use cookies to improve your experience.</div><div><button class="accept">Accept</button></div></div>
|
|
19
|
+
` }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
registerComponent('af-cookie-consent', AfriCookieConsent);
|
|
23
|
+
export default AfriCookieConsent;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode CTA Banner
|
|
3
|
+
*
|
|
4
|
+
* Simple call-to-action banner for marketing pages.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
8
|
+
|
|
9
|
+
export class AfriCTABanner extends AfriCodeComponent {
|
|
10
|
+
static get observedAttributes() { return ['title','subtitle','cta-text']; }
|
|
11
|
+
constructor(){ super(); this.render(); }
|
|
12
|
+
attributeChangedCallback(){ this.render(); }
|
|
13
|
+
render(){
|
|
14
|
+
const title = this.getAttribute('title') || 'Get started today';
|
|
15
|
+
const subtitle = this.getAttribute('subtitle') || '';
|
|
16
|
+
const cta = this.getAttribute('cta-text') || 'Try for free';
|
|
17
|
+
this.shadowRoot.innerHTML = html`
|
|
18
|
+
<style>
|
|
19
|
+
:host{display:block;font-family:Inter,system-ui,sans-serif}
|
|
20
|
+
.banner{display:flex;gap:1rem;align-items:center;justify-content:space-between;padding:1.25rem;border-radius:12px;background:linear-gradient(90deg,#1EB53A,#FCD116);color:#08111a}
|
|
21
|
+
.text{display:flex;flex-direction:column}
|
|
22
|
+
.title{font-weight:800;font-size:1.125rem}
|
|
23
|
+
.subtitle{font-size:0.95rem;color:rgba(8,17,26,0.8)}
|
|
24
|
+
.cta{background:rgba(255,255,255,0.12);border:none;padding:0.6rem 1rem;border-radius:8px;color:inherit;cursor:pointer}
|
|
25
|
+
</style>
|
|
26
|
+
<div class="banner">
|
|
27
|
+
<div class="text">
|
|
28
|
+
<div class="title">${title}</div>
|
|
29
|
+
${subtitle?html`<div class="subtitle">${subtitle}</div>`:''}
|
|
30
|
+
</div>
|
|
31
|
+
<button class="cta">${cta}</button>
|
|
32
|
+
</div>
|
|
33
|
+
`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
registerComponent('af-cta-banner', AfriCTABanner);
|
|
38
|
+
export default AfriCTABanner;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Dashboard Activity List
|
|
3
|
+
*
|
|
4
|
+
* Displays recent activity or event history in a dashboard card.
|
|
5
|
+
* @module components/dashboard-activity-list
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
9
|
+
|
|
10
|
+
export class AfriDashboardActivityList extends AfriCodeComponent {
|
|
11
|
+
static get observedAttributes() {
|
|
12
|
+
return ['title', 'empty-text'];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.render();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
attributeChangedCallback() {
|
|
21
|
+
this.render();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
render() {
|
|
25
|
+
const title = this.getAttribute('title') || 'Recent Activity';
|
|
26
|
+
const emptyText = this.getAttribute('empty-text') || 'No recent activity available.';
|
|
27
|
+
|
|
28
|
+
this.shadowRoot.innerHTML = html`
|
|
29
|
+
<style>
|
|
30
|
+
:host {
|
|
31
|
+
display: block;
|
|
32
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.activity-card {
|
|
36
|
+
display: grid;
|
|
37
|
+
gap: 1rem;
|
|
38
|
+
padding: 1.5rem;
|
|
39
|
+
border-radius: 24px;
|
|
40
|
+
background: #ffffff;
|
|
41
|
+
border: 1px solid rgba(15, 23, 42, 0.08);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.header {
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
justify-content: space-between;
|
|
48
|
+
gap: 0.75rem;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.title {
|
|
52
|
+
margin: 0;
|
|
53
|
+
font-size: 1rem;
|
|
54
|
+
font-weight: 700;
|
|
55
|
+
color: #0f172a;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.list {
|
|
59
|
+
display: grid;
|
|
60
|
+
gap: 0.5rem;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.item {
|
|
64
|
+
padding: 0.95rem 1rem;
|
|
65
|
+
border-radius: 18px;
|
|
66
|
+
background: #f8fafc;
|
|
67
|
+
border: 1px solid rgba(15, 23, 42, 0.04);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.item-title {
|
|
71
|
+
font-size: 0.95rem;
|
|
72
|
+
font-weight: 600;
|
|
73
|
+
color: #0f172a;
|
|
74
|
+
margin: 0 0 0.25rem;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.item-meta {
|
|
78
|
+
margin: 0;
|
|
79
|
+
color: #6b7280;
|
|
80
|
+
font-size: 0.85rem;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.empty {
|
|
84
|
+
color: #6b7280;
|
|
85
|
+
font-size: 0.95rem;
|
|
86
|
+
padding: 1rem 0;
|
|
87
|
+
}
|
|
88
|
+
</style>
|
|
89
|
+
|
|
90
|
+
<div class="activity-card">
|
|
91
|
+
<div class="header"><h3 class="title">${title}</h3></div>
|
|
92
|
+
<div class="list">
|
|
93
|
+
<slot>
|
|
94
|
+
<div class="empty">${emptyText}</div>
|
|
95
|
+
</slot>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
registerComponent('af-dashboard-activity-list', AfriDashboardActivityList);
|
|
103
|
+
export default AfriDashboardActivityList;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Dashboard Card
|
|
3
|
+
*
|
|
4
|
+
* A reusable dashboard surface for metrics, lists, and analytics.
|
|
5
|
+
* @module components/dashboard-card
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
9
|
+
|
|
10
|
+
export class AfriDashboardCard extends AfriCodeComponent {
|
|
11
|
+
static get observedAttributes() {
|
|
12
|
+
return ['title', 'variant'];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.render();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
attributeChangedCallback() {
|
|
21
|
+
this.render();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
render() {
|
|
25
|
+
const title = this.getAttribute('title') || '';
|
|
26
|
+
const variant = this.getAttribute('variant') || 'surface';
|
|
27
|
+
|
|
28
|
+
const styles = {
|
|
29
|
+
surface: {
|
|
30
|
+
background: '#ffffff',
|
|
31
|
+
border: '1px solid rgba(15, 23, 42, 0.08)',
|
|
32
|
+
shadow: '0 18px 36px rgba(15, 23, 42, 0.04)'
|
|
33
|
+
},
|
|
34
|
+
elevated: {
|
|
35
|
+
background: 'linear-gradient(180deg, #ffffff 0%, #f8fafc 100%)',
|
|
36
|
+
border: '1px solid rgba(15, 23, 42, 0.12)',
|
|
37
|
+
shadow: '0 24px 48px rgba(15, 23, 42, 0.08)'
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const cardStyle = styles[variant] || styles.surface;
|
|
42
|
+
|
|
43
|
+
this.shadowRoot.innerHTML = html`
|
|
44
|
+
<style>
|
|
45
|
+
:host {
|
|
46
|
+
display: block;
|
|
47
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.card {
|
|
51
|
+
background: ${cardStyle.background};
|
|
52
|
+
border: ${cardStyle.border};
|
|
53
|
+
border-radius: 24px;
|
|
54
|
+
box-shadow: ${cardStyle.shadow};
|
|
55
|
+
padding: 1.5rem;
|
|
56
|
+
min-height: 100px;
|
|
57
|
+
overflow: hidden;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.header {
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
justify-content: space-between;
|
|
64
|
+
gap: 0.75rem;
|
|
65
|
+
margin-bottom: 1rem;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.title {
|
|
69
|
+
margin: 0;
|
|
70
|
+
font-size: 1rem;
|
|
71
|
+
font-weight: 700;
|
|
72
|
+
color: #0f172a;
|
|
73
|
+
}
|
|
74
|
+
</style>
|
|
75
|
+
|
|
76
|
+
<div class="card">
|
|
77
|
+
${title ? html`<div class="header"><h3 class="title">${title}</h3><slot name="header"></slot></div>` : ''}
|
|
78
|
+
<div class="body"><slot></slot></div>
|
|
79
|
+
</div>
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
registerComponent('af-dashboard-card', AfriDashboardCard);
|
|
85
|
+
export default AfriDashboardCard;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Dashboard Metric
|
|
3
|
+
*
|
|
4
|
+
* Summary tile for key performance indicators.
|
|
5
|
+
* @module components/dashboard-metric
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
9
|
+
|
|
10
|
+
export class AfriDashboardMetric extends AfriCodeComponent {
|
|
11
|
+
static get observedAttributes() {
|
|
12
|
+
return ['label', 'value', 'delta', 'trend'];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.render();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
attributeChangedCallback() {
|
|
21
|
+
this.render();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
render() {
|
|
25
|
+
const label = this.getAttribute('label') || 'Metric';
|
|
26
|
+
const value = this.getAttribute('value') || '0';
|
|
27
|
+
const delta = this.getAttribute('delta') || '';
|
|
28
|
+
const trend = this.getAttribute('trend') || 'neutral';
|
|
29
|
+
const trendColor = trend === 'positive' ? '#16a34a' : trend === 'negative' ? '#dc2626' : '#475569';
|
|
30
|
+
|
|
31
|
+
this.shadowRoot.innerHTML = html`
|
|
32
|
+
<style>
|
|
33
|
+
:host {
|
|
34
|
+
display: block;
|
|
35
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.metric {
|
|
39
|
+
display: grid;
|
|
40
|
+
gap: 0.5rem;
|
|
41
|
+
padding: 1.25rem;
|
|
42
|
+
border-radius: 24px;
|
|
43
|
+
background: #ffffff;
|
|
44
|
+
border: 1px solid rgba(15, 23, 42, 0.08);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.label {
|
|
48
|
+
color: #6b7280;
|
|
49
|
+
text-transform: uppercase;
|
|
50
|
+
letter-spacing: 0.14em;
|
|
51
|
+
font-size: 0.75rem;
|
|
52
|
+
font-weight: 700;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.value {
|
|
56
|
+
font-size: 2rem;
|
|
57
|
+
font-weight: 800;
|
|
58
|
+
color: #0f172a;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.delta {
|
|
62
|
+
display: inline-flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
gap: 0.4rem;
|
|
65
|
+
font-weight: 700;
|
|
66
|
+
color: ${trendColor};
|
|
67
|
+
font-size: 0.92rem;
|
|
68
|
+
}
|
|
69
|
+
</style>
|
|
70
|
+
|
|
71
|
+
<div class="metric">
|
|
72
|
+
<div class="label">${label}</div>
|
|
73
|
+
<div class="value">${value}</div>
|
|
74
|
+
${delta ? html`<div class="delta">${delta}</div>` : ''}
|
|
75
|
+
</div>
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
registerComponent('af-dashboard-metric', AfriDashboardMetric);
|
|
81
|
+
export default AfriDashboardMetric;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Dashboard Shell
|
|
3
|
+
*
|
|
4
|
+
* A layout container for dashboard applications.
|
|
5
|
+
* Use slots for sidebar, topbar, and page content.
|
|
6
|
+
* @module components/dashboard-shell
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
10
|
+
|
|
11
|
+
export class AfriDashboardShell extends AfriCodeComponent {
|
|
12
|
+
static get observedAttributes() {
|
|
13
|
+
return ['sidebar-width', 'topbar-height'];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
super();
|
|
18
|
+
this.render();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
attributeChangedCallback() {
|
|
22
|
+
this.render();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
render() {
|
|
26
|
+
const sidebarWidth = this.getAttribute('sidebar-width') || '280px';
|
|
27
|
+
const topbarHeight = this.getAttribute('topbar-height') || '72px';
|
|
28
|
+
|
|
29
|
+
this.shadowRoot.innerHTML = html`
|
|
30
|
+
<style>
|
|
31
|
+
:host {
|
|
32
|
+
display: block;
|
|
33
|
+
min-height: 100vh;
|
|
34
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
35
|
+
color: var(--af-text, #0f172a);
|
|
36
|
+
background: var(--af-bg, #f8fafc);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.dashboard-shell {
|
|
40
|
+
display: grid;
|
|
41
|
+
grid-template-columns: ${sidebarWidth} minmax(0, 1fr);
|
|
42
|
+
grid-template-rows: ${topbarHeight} minmax(0, 1fr);
|
|
43
|
+
min-height: 100vh;
|
|
44
|
+
gap: 1rem;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.sidebar {
|
|
48
|
+
grid-row: 1 / span 2;
|
|
49
|
+
background: transparent;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.topbar {
|
|
53
|
+
grid-column: 2 / 3;
|
|
54
|
+
position: sticky;
|
|
55
|
+
top: 0;
|
|
56
|
+
z-index: 10;
|
|
57
|
+
background: var(--af-surface, #ffffff);
|
|
58
|
+
border-bottom: 1px solid rgba(15, 23, 42, 0.08);
|
|
59
|
+
box-shadow: 0 4px 18px rgba(15, 23, 42, 0.04);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.content {
|
|
63
|
+
grid-column: 2 / 3;
|
|
64
|
+
padding: 24px;
|
|
65
|
+
min-height: calc(100vh - ${topbarHeight});
|
|
66
|
+
background: transparent;
|
|
67
|
+
overflow: hidden;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
::slotted([slot="sidebar"]) {
|
|
71
|
+
height: 100%;
|
|
72
|
+
}
|
|
73
|
+
</style>
|
|
74
|
+
|
|
75
|
+
<div class="dashboard-shell">
|
|
76
|
+
<aside class="sidebar"><slot name="sidebar"></slot></aside>
|
|
77
|
+
<header class="topbar"><slot name="topbar"></slot></header>
|
|
78
|
+
<main class="content"><slot></slot></main>
|
|
79
|
+
</div>
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
registerComponent('af-dashboard-shell', AfriDashboardShell);
|
|
85
|
+
export default AfriDashboardShell;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Dashboard Topbar
|
|
3
|
+
*
|
|
4
|
+
* Top navigation bar for dashboard control and page context.
|
|
5
|
+
* @module components/dashboard-topbar
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
9
|
+
|
|
10
|
+
export class AfriDashboardTopbar extends AfriCodeComponent {
|
|
11
|
+
static get observedAttributes() {
|
|
12
|
+
return ['title', 'description'];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.render();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
attributeChangedCallback() {
|
|
21
|
+
this.render();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
render() {
|
|
25
|
+
const title = this.getAttribute('title') || 'Dashboard';
|
|
26
|
+
const description = this.getAttribute('description') || '';
|
|
27
|
+
|
|
28
|
+
this.shadowRoot.innerHTML = html`
|
|
29
|
+
<style>
|
|
30
|
+
:host {
|
|
31
|
+
display: block;
|
|
32
|
+
width: 100%;
|
|
33
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.topbar {
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
justify-content: space-between;
|
|
40
|
+
padding: 1rem 1.5rem;
|
|
41
|
+
gap: 1rem;
|
|
42
|
+
min-height: 72px;
|
|
43
|
+
box-sizing: border-box;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.heading {
|
|
47
|
+
display: grid;
|
|
48
|
+
gap: 0.25rem;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.title {
|
|
52
|
+
margin: 0;
|
|
53
|
+
font-size: 1.1rem;
|
|
54
|
+
font-weight: 700;
|
|
55
|
+
color: #0f172a;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.description {
|
|
59
|
+
margin: 0;
|
|
60
|
+
color: #475569;
|
|
61
|
+
font-size: 0.95rem;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.actions {
|
|
65
|
+
display: flex;
|
|
66
|
+
gap: 0.75rem;
|
|
67
|
+
align-items: center;
|
|
68
|
+
flex-wrap: wrap;
|
|
69
|
+
}
|
|
70
|
+
</style>
|
|
71
|
+
|
|
72
|
+
<div class="topbar">
|
|
73
|
+
<div class="heading">
|
|
74
|
+
<div class="title">${title}</div>
|
|
75
|
+
${description ? html`<div class="description">${description}</div>` : ''}
|
|
76
|
+
</div>
|
|
77
|
+
<div class="actions"><slot name="actions"></slot></div>
|
|
78
|
+
</div>
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
registerComponent('af-dashboard-topbar', AfriDashboardTopbar);
|
|
84
|
+
export default AfriDashboardTopbar;
|