@africode/core 5.0.5 → 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/AGENTS.md +583 -0
- package/AGENT_INSTRUCTIONS.md +595 -0
- package/README.md +2 -0
- 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 +3 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode UI Avatar
|
|
3
|
+
*
|
|
4
|
+
* User avatar component with initials fallback and optional badge.
|
|
5
|
+
* @module components/ui-avatar
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
9
|
+
|
|
10
|
+
export class AfriUIAvatar extends AfriCodeComponent {
|
|
11
|
+
static get observedAttributes() {
|
|
12
|
+
return ['src', 'initials', 'size', 'badge'];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.render();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
attributeChangedCallback() {
|
|
21
|
+
this.render();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
render() {
|
|
25
|
+
const src = this.getAttribute('src');
|
|
26
|
+
const initials = this.getAttribute('initials') || 'AC';
|
|
27
|
+
const size = this.getAttribute('size') || 'md';
|
|
28
|
+
const dimensions = size === 'sm' ? '32px' : size === 'lg' ? '64px' : '48px';
|
|
29
|
+
const badge = this.getAttribute('badge');
|
|
30
|
+
|
|
31
|
+
this.shadowRoot.innerHTML = html`
|
|
32
|
+
<style>
|
|
33
|
+
:host { display: inline-flex; }
|
|
34
|
+
|
|
35
|
+
.avatar {
|
|
36
|
+
position: relative;
|
|
37
|
+
width: ${dimensions};
|
|
38
|
+
height: ${dimensions};
|
|
39
|
+
border-radius: 999px;
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
background: linear-gradient(135deg, #1eb53a, #fcd116);
|
|
42
|
+
color: #fff;
|
|
43
|
+
display: inline-flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
justify-content: center;
|
|
46
|
+
font-weight: 700;
|
|
47
|
+
font-size: ${size === 'sm' ? '0.85rem' : size === 'lg' ? '1.25rem' : '1rem'};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
img {
|
|
51
|
+
width: 100%;
|
|
52
|
+
height: 100%;
|
|
53
|
+
object-fit: cover;
|
|
54
|
+
display: block;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.badge {
|
|
58
|
+
position: absolute;
|
|
59
|
+
bottom: -2px;
|
|
60
|
+
right: -2px;
|
|
61
|
+
background: #2563eb;
|
|
62
|
+
color: #fff;
|
|
63
|
+
border-radius: 999px;
|
|
64
|
+
padding: 0.15rem 0.45rem;
|
|
65
|
+
font-size: 0.65rem;
|
|
66
|
+
font-weight: 700;
|
|
67
|
+
}
|
|
68
|
+
</style>
|
|
69
|
+
|
|
70
|
+
<div class="avatar">
|
|
71
|
+
${src ? html`<img src="${src}" alt="Avatar" />` : html`<span>${initials}</span>`}
|
|
72
|
+
${badge ? html`<span class="badge">${badge}</span>` : ''}
|
|
73
|
+
</div>
|
|
74
|
+
`;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
registerComponent('af-ui-avatar', AfriUIAvatar);
|
|
79
|
+
export default AfriUIAvatar;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode UI Chip
|
|
3
|
+
*
|
|
4
|
+
* Compact pill element for tags, status chips, and inline metadata.
|
|
5
|
+
* @module components/ui-chip
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
9
|
+
|
|
10
|
+
export class AfriUIChip extends AfriCodeComponent {
|
|
11
|
+
static get observedAttributes() {
|
|
12
|
+
return ['variant', 'size', 'dismissible'];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.render();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
connectedCallback() {
|
|
21
|
+
this.render();
|
|
22
|
+
this._attachListeners();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
attributeChangedCallback() {
|
|
26
|
+
this.render();
|
|
27
|
+
this._attachListeners();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_attachListeners() {
|
|
31
|
+
const button = this.shadowRoot.querySelector('.chip button');
|
|
32
|
+
if (!button) return;
|
|
33
|
+
button.addEventListener('click', (event) => {
|
|
34
|
+
event.stopPropagation();
|
|
35
|
+
this.emit('af-ui-chip-dismiss', {});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
render() {
|
|
40
|
+
const variant = this.getAttribute('variant') || 'neutral';
|
|
41
|
+
const size = this.getAttribute('size') || 'md';
|
|
42
|
+
const dismissible = this.hasAttribute('dismissible');
|
|
43
|
+
|
|
44
|
+
this.shadowRoot.innerHTML = html`
|
|
45
|
+
<style>
|
|
46
|
+
:host { display: inline-flex; }
|
|
47
|
+
|
|
48
|
+
.chip {
|
|
49
|
+
display: inline-flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
gap: 0.5rem;
|
|
52
|
+
border-radius: 999px;
|
|
53
|
+
padding: ${size === 'sm' ? '0.35rem 0.75rem' : '0.45rem 1rem'};
|
|
54
|
+
font-size: ${size === 'sm' ? '0.75rem' : '0.85rem'};
|
|
55
|
+
font-weight: 600;
|
|
56
|
+
background: #f8fafc;
|
|
57
|
+
color: #0f172a;
|
|
58
|
+
border: 1px solid rgba(15, 23, 42, 0.08);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.chip.primary { background: #eff6ff; color: #1d4ed8; }
|
|
62
|
+
.chip.success { background: #dcfce7; color: #166534; }
|
|
63
|
+
.chip.warning { background: #fef3c7; color: #92400e; }
|
|
64
|
+
.chip.danger { background: #fee2e2; color: #991b1b; }
|
|
65
|
+
.chip.neutral { background: #f8fafc; color: #0f172a; }
|
|
66
|
+
|
|
67
|
+
button {
|
|
68
|
+
border: none;
|
|
69
|
+
background: transparent;
|
|
70
|
+
color: inherit;
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
font-size: 0.95rem;
|
|
73
|
+
padding: 0;
|
|
74
|
+
line-height: 1;
|
|
75
|
+
}
|
|
76
|
+
</style>
|
|
77
|
+
|
|
78
|
+
<span class="chip ${variant}">
|
|
79
|
+
<slot></slot>
|
|
80
|
+
${dismissible ? html`<button aria-label="Remove">×</button>` : ''}
|
|
81
|
+
</span>
|
|
82
|
+
`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
registerComponent('af-ui-chip', AfriUIChip);
|
|
87
|
+
export default AfriUIChip;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode UI Popover
|
|
3
|
+
*
|
|
4
|
+
* A small popover panel for contextual actions and menus.
|
|
5
|
+
* @module components/ui-popover
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
9
|
+
|
|
10
|
+
export class AfriUIPopover extends AfriCodeComponent {
|
|
11
|
+
static get observedAttributes() {
|
|
12
|
+
return ['label', 'open'];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this._open = this.hasAttribute('open');
|
|
18
|
+
this.render();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
connectedCallback() {
|
|
22
|
+
this._attachListeners();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
attributeChangedCallback() {
|
|
26
|
+
this._open = this.hasAttribute('open');
|
|
27
|
+
this.render();
|
|
28
|
+
this._attachListeners();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_attachListeners() {
|
|
32
|
+
const trigger = this.shadowRoot.querySelector('.trigger');
|
|
33
|
+
if (!trigger) return;
|
|
34
|
+
trigger.addEventListener('click', () => {
|
|
35
|
+
this._open = !this._open;
|
|
36
|
+
if (this._open) this.setAttribute('open', '');
|
|
37
|
+
else this.removeAttribute('open');
|
|
38
|
+
this.render();
|
|
39
|
+
this._attachListeners();
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
render() {
|
|
44
|
+
const label = this.getAttribute('label') || 'Open';
|
|
45
|
+
const open = this._open;
|
|
46
|
+
|
|
47
|
+
this.shadowRoot.innerHTML = html`
|
|
48
|
+
<style>
|
|
49
|
+
:host { position: relative; display: inline-flex; }
|
|
50
|
+
.trigger {
|
|
51
|
+
border: 1px solid #cbd5e1;
|
|
52
|
+
border-radius: 0.75rem;
|
|
53
|
+
padding: 0.75rem 1rem;
|
|
54
|
+
background: #fff;
|
|
55
|
+
color: #111827;
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
font-family: 'Inter', sans-serif;
|
|
58
|
+
font-weight: 600;
|
|
59
|
+
}
|
|
60
|
+
.panel {
|
|
61
|
+
position: absolute;
|
|
62
|
+
top: calc(100% + 0.5rem);
|
|
63
|
+
right: 0;
|
|
64
|
+
min-width: 220px;
|
|
65
|
+
background: #ffffff;
|
|
66
|
+
border: 1px solid #e5e7eb;
|
|
67
|
+
box-shadow: 0 20px 40px rgba(15, 23, 42, 0.08);
|
|
68
|
+
border-radius: 1rem;
|
|
69
|
+
padding: 1rem;
|
|
70
|
+
opacity: ${open ? '1' : '0'};
|
|
71
|
+
visibility: ${open ? 'visible' : 'hidden'};
|
|
72
|
+
transform: ${open ? 'translateY(0)' : 'translateY(-10px)'};
|
|
73
|
+
transition: opacity 180ms ease, transform 180ms ease;
|
|
74
|
+
z-index: 10;
|
|
75
|
+
}
|
|
76
|
+
</style>
|
|
77
|
+
<button class="trigger" type="button">${label}</button>
|
|
78
|
+
<div class="panel"><slot></slot></div>
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
registerComponent('af-ui-popover', AfriUIPopover);
|
|
84
|
+
export default AfriUIPopover;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode UI Progress Ring
|
|
3
|
+
*
|
|
4
|
+
* Circular progress indicator that pairs well with dashboard metrics.
|
|
5
|
+
* @module components/ui-progress-ring
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
9
|
+
|
|
10
|
+
export class AfriUIProgressRing extends AfriCodeComponent {
|
|
11
|
+
static get observedAttributes() {
|
|
12
|
+
return ['value', 'size', 'stroke', 'label'];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.render();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
attributeChangedCallback() {
|
|
21
|
+
this.render();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
render() {
|
|
25
|
+
const value = Number(this.getAttribute('value') || 0);
|
|
26
|
+
const size = Number(this.getAttribute('size') || 96);
|
|
27
|
+
const stroke = Number(this.getAttribute('stroke') || 10);
|
|
28
|
+
const label = this.getAttribute('label') || '';
|
|
29
|
+
const radius = (size - stroke) / 2;
|
|
30
|
+
const circumference = 2 * Math.PI * radius;
|
|
31
|
+
const dashoffset = circumference - (value / 100) * circumference;
|
|
32
|
+
|
|
33
|
+
this.shadowRoot.innerHTML = html`
|
|
34
|
+
<style>
|
|
35
|
+
:host { display: inline-flex; align-items: center; justify-content: center; }
|
|
36
|
+
.ring { position: relative; width: ${size}px; height: ${size}px; }
|
|
37
|
+
svg { transform: rotate(-90deg); width: 100%; height: 100%; }
|
|
38
|
+
circle { fill: none; stroke-width: ${stroke}; stroke-linecap: round; }
|
|
39
|
+
.bg { stroke: rgba(15, 23, 42, 0.12); }
|
|
40
|
+
.progress { stroke: #2563eb; stroke-dasharray: ${circumference}; stroke-dashoffset: ${dashoffset}; transition: stroke-dashoffset 0.35s ease; }
|
|
41
|
+
.label { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #111827; font-family: 'Inter', sans-serif; }
|
|
42
|
+
.value { font-size: 1rem; font-weight: 700; }
|
|
43
|
+
.subtitle { font-size: 0.75rem; color: #6b7280; }
|
|
44
|
+
</style>
|
|
45
|
+
<div class="ring" role="progressbar" aria-valuenow="${value}" aria-valuemin="0" aria-valuemax="100">
|
|
46
|
+
<svg viewBox="0 0 ${size} ${size}">
|
|
47
|
+
<circle class="bg" cx="${size/2}" cy="${size/2}" r="${radius}" />
|
|
48
|
+
<circle class="progress" cx="${size/2}" cy="${size/2}" r="${radius}" />
|
|
49
|
+
</svg>
|
|
50
|
+
<div class="label">
|
|
51
|
+
<div class="value">${value}%</div>
|
|
52
|
+
${label ? html`<div class="subtitle">${label}</div>` : ''}
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
registerComponent('af-ui-progress-ring', AfriUIProgressRing);
|
|
60
|
+
export default AfriUIProgressRing;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode UI Select
|
|
3
|
+
*
|
|
4
|
+
* A styled select component with label support and custom appearance.
|
|
5
|
+
* @module components/ui-select
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
9
|
+
|
|
10
|
+
export class AfriUISelect extends AfriCodeComponent {
|
|
11
|
+
static get observedAttributes() {
|
|
12
|
+
return ['label', 'placeholder', 'disabled', 'error'];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.render();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
connectedCallback() {
|
|
21
|
+
this._attachListeners();
|
|
22
|
+
this.loadStyles();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
attributeChangedCallback() {
|
|
26
|
+
this.render();
|
|
27
|
+
this._attachListeners();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_attachListeners() {
|
|
31
|
+
const select = this.shadowRoot.querySelector('select');
|
|
32
|
+
if (!select) return;
|
|
33
|
+
select.addEventListener('change', () => {
|
|
34
|
+
this.emit('af-ui-select', { value: select.value });
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
render() {
|
|
39
|
+
const label = this.getAttribute('label') || '';
|
|
40
|
+
const placeholder = this.getAttribute('placeholder') || 'Select an option';
|
|
41
|
+
const disabled = this.hasAttribute('disabled');
|
|
42
|
+
const error = this.getAttribute('error') || '';
|
|
43
|
+
|
|
44
|
+
this.shadowRoot.innerHTML = html`
|
|
45
|
+
<style>
|
|
46
|
+
:host { display: block; font-family: 'Inter', system-ui, sans-serif; }
|
|
47
|
+
|
|
48
|
+
.field { display: grid; gap: 0.5rem; }
|
|
49
|
+
label { font-size: 0.85rem; font-weight: 600; color: #111827; }
|
|
50
|
+
select {
|
|
51
|
+
width: 100%;
|
|
52
|
+
border: 1px solid #d1d5db;
|
|
53
|
+
border-radius: 0.75rem;
|
|
54
|
+
padding: 0.9rem 1rem;
|
|
55
|
+
background: #fff;
|
|
56
|
+
color: #111827;
|
|
57
|
+
appearance: none;
|
|
58
|
+
font-size: 0.95rem;
|
|
59
|
+
outline: none;
|
|
60
|
+
transition: border-color 150ms ease, box-shadow 150ms ease;
|
|
61
|
+
}
|
|
62
|
+
select:focus {
|
|
63
|
+
border-color: #2563eb;
|
|
64
|
+
box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.12);
|
|
65
|
+
}
|
|
66
|
+
select:disabled { background: #f8fafc; cursor: not-allowed; }
|
|
67
|
+
|
|
68
|
+
.hint { font-size: 0.85rem; color: #6b7280; }
|
|
69
|
+
.error { color: #dc2626; font-size: 0.8rem; min-height: 1rem; }
|
|
70
|
+
</style>
|
|
71
|
+
|
|
72
|
+
<div class="field">
|
|
73
|
+
${label ? html`<label>${label}</label>` : ''}
|
|
74
|
+
<select ${disabled ? 'disabled' : ''}>
|
|
75
|
+
<option disabled selected hidden>${placeholder}</option>
|
|
76
|
+
<slot></slot>
|
|
77
|
+
</select>
|
|
78
|
+
<div class="error">${error}</div>
|
|
79
|
+
</div>
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
registerComponent('af-ui-select', AfriUISelect);
|
|
85
|
+
export default AfriUISelect;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode UI Stat
|
|
3
|
+
*
|
|
4
|
+
* Metric summary card for dashboard statistics.
|
|
5
|
+
* @module components/ui-stat
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
9
|
+
|
|
10
|
+
export class AfriUIStat 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
|
+
|
|
30
|
+
const trendColor = trend === 'positive' ? '#16a34a' : trend === 'negative' ? '#dc2626' : '#475569';
|
|
31
|
+
|
|
32
|
+
this.shadowRoot.innerHTML = html`
|
|
33
|
+
<style>
|
|
34
|
+
:host { display: block; font-family: 'Inter', sans-serif; }
|
|
35
|
+
.stat {
|
|
36
|
+
display: grid;
|
|
37
|
+
gap: 0.5rem;
|
|
38
|
+
padding: 1.25rem;
|
|
39
|
+
border-radius: 1rem;
|
|
40
|
+
background: #ffffff;
|
|
41
|
+
border: 1px solid #e5e7eb;
|
|
42
|
+
box-shadow: 0 12px 24px rgba(15, 23, 42, 0.06);
|
|
43
|
+
}
|
|
44
|
+
.label {
|
|
45
|
+
font-size: 0.85rem;
|
|
46
|
+
font-weight: 700;
|
|
47
|
+
color: #475569;
|
|
48
|
+
text-transform: uppercase;
|
|
49
|
+
letter-spacing: 0.08em;
|
|
50
|
+
}
|
|
51
|
+
.value {
|
|
52
|
+
font-size: 2.25rem;
|
|
53
|
+
font-weight: 800;
|
|
54
|
+
color: #0f172a;
|
|
55
|
+
}
|
|
56
|
+
.delta {
|
|
57
|
+
display: inline-flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
gap: 0.35rem;
|
|
60
|
+
color: ${trendColor};
|
|
61
|
+
font-size: 0.95rem;
|
|
62
|
+
font-weight: 700;
|
|
63
|
+
}
|
|
64
|
+
</style>
|
|
65
|
+
<div class="stat">
|
|
66
|
+
<div class="label">${label}</div>
|
|
67
|
+
<div class="value">${value}</div>
|
|
68
|
+
${delta ? html`<div class="delta">${delta}</div>` : ''}
|
|
69
|
+
</div>
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
registerComponent('af-ui-stat', AfriUIStat);
|
|
75
|
+
export default AfriUIStat;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode UI Table
|
|
3
|
+
*
|
|
4
|
+
* A styled table component for dashboard and data display.
|
|
5
|
+
* @module components/ui-table
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
9
|
+
|
|
10
|
+
export class AfriUITable extends AfriCodeComponent {
|
|
11
|
+
static get observedAttributes() {
|
|
12
|
+
return ['striped', 'bordered', 'compact'];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.render();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
attributeChangedCallback() {
|
|
21
|
+
this.render();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
render() {
|
|
25
|
+
const striped = this.hasAttribute('striped');
|
|
26
|
+
const bordered = this.hasAttribute('bordered');
|
|
27
|
+
const compact = this.hasAttribute('compact');
|
|
28
|
+
|
|
29
|
+
this.shadowRoot.innerHTML = html`
|
|
30
|
+
<style>
|
|
31
|
+
:host { display: block; overflow-x: auto; }
|
|
32
|
+
table {
|
|
33
|
+
width: 100%;
|
|
34
|
+
border-collapse: collapse;
|
|
35
|
+
min-width: 100%;
|
|
36
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
37
|
+
color: #0f172a;
|
|
38
|
+
}
|
|
39
|
+
th, td {
|
|
40
|
+
padding: ${compact ? '0.75rem' : '1rem'};
|
|
41
|
+
text-align: left;
|
|
42
|
+
border-bottom: 1px solid #e5e7eb;
|
|
43
|
+
}
|
|
44
|
+
th {
|
|
45
|
+
font-size: 0.85rem;
|
|
46
|
+
font-weight: 700;
|
|
47
|
+
color: #334155;
|
|
48
|
+
text-transform: uppercase;
|
|
49
|
+
letter-spacing: 0.04em;
|
|
50
|
+
}
|
|
51
|
+
tbody tr {
|
|
52
|
+
background: #fff;
|
|
53
|
+
}
|
|
54
|
+
tbody tr:nth-child(even) {
|
|
55
|
+
background: ${striped ? '#f8fafc' : '#fff'};
|
|
56
|
+
}
|
|
57
|
+
table.bordered th, table.bordered td {
|
|
58
|
+
border: 1px solid #e5e7eb;
|
|
59
|
+
}
|
|
60
|
+
</style>
|
|
61
|
+
<table class="${bordered ? 'bordered' : ''}">
|
|
62
|
+
<slot></slot>
|
|
63
|
+
</table>
|
|
64
|
+
`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
registerComponent('af-ui-table', AfriUITable);
|
|
69
|
+
export default AfriUITable;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode UI Tabs
|
|
3
|
+
*
|
|
4
|
+
* A simple tabbed interface for content panels.
|
|
5
|
+
* @module components/ui-tabs
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
9
|
+
|
|
10
|
+
export class AfriUITabs extends AfriCodeComponent {
|
|
11
|
+
static get observedAttributes() {
|
|
12
|
+
return ['active'];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this._active = this.getAttribute('active') || '0';
|
|
18
|
+
this.render();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
connectedCallback() {
|
|
22
|
+
this._attachListeners();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
attributeChangedCallback() {
|
|
26
|
+
this._active = this.getAttribute('active') || '0';
|
|
27
|
+
this.render();
|
|
28
|
+
this._attachListeners();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_attachListeners() {
|
|
32
|
+
const tabButtons = this.shadowRoot.querySelectorAll('[role="tab"]');
|
|
33
|
+
tabButtons.forEach((tab) => {
|
|
34
|
+
tab.addEventListener('click', () => {
|
|
35
|
+
const index = tab.getAttribute('data-index');
|
|
36
|
+
this.setAttribute('active', index);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
render() {
|
|
42
|
+
const active = this._active;
|
|
43
|
+
const tabs = Array.from(this.children).filter((child) => child.tagName.toLowerCase() === 'ui-tab-panel');
|
|
44
|
+
|
|
45
|
+
this.shadowRoot.innerHTML = html`
|
|
46
|
+
<style>
|
|
47
|
+
:host { display: block; font-family: 'Inter', system-ui, sans-serif; }
|
|
48
|
+
|
|
49
|
+
.tab-list {
|
|
50
|
+
display: flex;
|
|
51
|
+
gap: 0.5rem;
|
|
52
|
+
border-bottom: 1px solid #e5e7eb;
|
|
53
|
+
margin-bottom: 1rem;
|
|
54
|
+
}
|
|
55
|
+
button {
|
|
56
|
+
all: unset;
|
|
57
|
+
cursor: pointer;
|
|
58
|
+
padding: 0.75rem 1rem;
|
|
59
|
+
border-radius: 999px;
|
|
60
|
+
color: #334155;
|
|
61
|
+
font-weight: 600;
|
|
62
|
+
}
|
|
63
|
+
button.active {
|
|
64
|
+
background: #2563eb;
|
|
65
|
+
color: #fff;
|
|
66
|
+
}
|
|
67
|
+
.panel { display: none; }
|
|
68
|
+
.panel.active { display: block; }
|
|
69
|
+
</style>
|
|
70
|
+
<div class="tab-list" role="tablist">
|
|
71
|
+
${tabs.map((tab, index) => html`
|
|
72
|
+
<button
|
|
73
|
+
role="tab"
|
|
74
|
+
data-index="${index}"
|
|
75
|
+
class="${String(index) === String(active) ? 'active' : ''}"
|
|
76
|
+
aria-selected="${String(index) === String(active)}"
|
|
77
|
+
>
|
|
78
|
+
${tab.getAttribute('label') || `Tab ${index + 1}`}
|
|
79
|
+
</button>
|
|
80
|
+
`).join('')}
|
|
81
|
+
</div>
|
|
82
|
+
${tabs.map((tab, index) => html`
|
|
83
|
+
<div class="panel ${String(index) === String(active) ? 'active' : ''}" role="tabpanel">
|
|
84
|
+
${tab.innerHTML}
|
|
85
|
+
</div>
|
|
86
|
+
`).join('')}
|
|
87
|
+
`;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export class AfriUITabPanel extends AfriCodeComponent {
|
|
92
|
+
render() {
|
|
93
|
+
this.shadowRoot.innerHTML = html`<slot></slot>`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
registerComponent('af-ui-tabs', AfriUITabs);
|
|
98
|
+
registerComponent('ui-tab-panel', AfriUITabPanel);
|
|
99
|
+
export default AfriUITabs;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode UI Tag
|
|
3
|
+
*
|
|
4
|
+
* Simple inline tag for metadata, status, and category labels.
|
|
5
|
+
* @module components/ui-tag
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
9
|
+
|
|
10
|
+
export class AfriUITag extends AfriCodeComponent {
|
|
11
|
+
static get observedAttributes() {
|
|
12
|
+
return ['variant', 'size'];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.render();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
attributeChangedCallback() {
|
|
21
|
+
this.render();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
render() {
|
|
25
|
+
const variant = this.getAttribute('variant') || 'secondary';
|
|
26
|
+
const size = this.getAttribute('size') || 'md';
|
|
27
|
+
const sizes = {
|
|
28
|
+
sm: '0.25rem 0.6rem',
|
|
29
|
+
md: '0.35rem 0.8rem',
|
|
30
|
+
lg: '0.5rem 1rem'
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
this.shadowRoot.innerHTML = html`
|
|
34
|
+
<style>
|
|
35
|
+
:host { display: inline-block; font-family: 'Inter', sans-serif; }
|
|
36
|
+
|
|
37
|
+
.tag {
|
|
38
|
+
display: inline-flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
justify-content: center;
|
|
41
|
+
gap: 0.35rem;
|
|
42
|
+
padding: ${sizes[size]};
|
|
43
|
+
border-radius: 999px;
|
|
44
|
+
font-size: ${size === 'sm' ? '0.75rem' : size === 'lg' ? '0.95rem' : '0.85rem'};
|
|
45
|
+
font-weight: 600;
|
|
46
|
+
color: #111827;
|
|
47
|
+
background: #f8fafc;
|
|
48
|
+
border: 1px solid rgba(15, 23, 42, 0.08);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.tag.primary { background: #dbeafe; color: #1e40af; }
|
|
52
|
+
.tag.success { background: #d1fae5; color: #14532d; }
|
|
53
|
+
.tag.warning { background: #fde68a; color: #92400e; }
|
|
54
|
+
.tag.danger { background: #fecaca; color: #991b1b; }
|
|
55
|
+
.tag.secondary { background: #f3f4f6; color: #1f2937; }
|
|
56
|
+
</style>
|
|
57
|
+
|
|
58
|
+
<span class="tag ${variant}"><slot></slot></span>
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
registerComponent('af-ui-tag', AfriUITag);
|
|
64
|
+
export default AfriUITag;
|