@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.
Files changed (46) hide show
  1. package/components/breadcrumb.js +72 -0
  2. package/components/carousel.js +32 -0
  3. package/components/cart-drawer.js +28 -0
  4. package/components/code-block.js +20 -0
  5. package/components/contact-form.js +25 -0
  6. package/components/cookie-consent.js +23 -0
  7. package/components/cta-banner.js +38 -0
  8. package/components/dashboard-activity-list.js +103 -0
  9. package/components/dashboard-card.js +85 -0
  10. package/components/dashboard-metric.js +81 -0
  11. package/components/dashboard-shell.js +85 -0
  12. package/components/dashboard-topbar.js +84 -0
  13. package/components/faq-accordion.js +23 -0
  14. package/components/feature-grid.js +85 -0
  15. package/components/file-uploader.js +22 -0
  16. package/components/filter-bar.js +19 -0
  17. package/components/footer.js +113 -0
  18. package/components/gallery.js +22 -0
  19. package/components/index.js +42 -0
  20. package/components/newsletter-signup.js +24 -0
  21. package/components/pagination.js +31 -0
  22. package/components/pricing-card.js +100 -0
  23. package/components/product-card.js +41 -0
  24. package/components/search-box.js +24 -0
  25. package/components/simple-chart.js +22 -0
  26. package/components/stepper.js +28 -0
  27. package/components/team-grid.js +23 -0
  28. package/components/testimonial.js +94 -0
  29. package/components/ui-avatar.js +79 -0
  30. package/components/ui-chip.js +87 -0
  31. package/components/ui-popover.js +84 -0
  32. package/components/ui-progress-ring.js +60 -0
  33. package/components/ui-select.js +85 -0
  34. package/components/ui-stat.js +75 -0
  35. package/components/ui-table.js +69 -0
  36. package/components/ui-tabs.js +99 -0
  37. package/components/ui-tag.js +64 -0
  38. package/components/ui-textarea.js +84 -0
  39. package/components/ui-tooltip.js +66 -0
  40. package/components/video-player.js +22 -0
  41. package/dist/africode.js +1480 -549
  42. package/dist/africode.js.map +25 -4
  43. package/dist/build-info.json +3 -3
  44. package/dist/components.js +1506 -575
  45. package/dist/components.js.map +25 -4
  46. package/package.json +1 -1
@@ -0,0 +1,94 @@
1
+ /**
2
+ * AfriCode Testimonial Component
3
+ *
4
+ * Card for customer quotes and endorsement sections.
5
+ * @module components/testimonial
6
+ */
7
+
8
+ import { AfriCodeComponent, registerComponent, html } from './base.js';
9
+
10
+ export class AfriTestimonial extends AfriCodeComponent {
11
+ static get observedAttributes() {
12
+ return ['author', 'role', 'company'];
13
+ }
14
+
15
+ constructor() {
16
+ super();
17
+ this.render();
18
+ }
19
+
20
+ attributeChangedCallback() {
21
+ this.render();
22
+ }
23
+
24
+ render() {
25
+ const author = this.getAttribute('author') || 'Customer';
26
+ const role = this.getAttribute('role') || 'Founder';
27
+ const company = this.getAttribute('company') || '';
28
+
29
+ this.shadowRoot.innerHTML = html`
30
+ <style>
31
+ :host {
32
+ display: block;
33
+ font-family: 'Inter', system-ui, sans-serif;
34
+ color: #0f172a;
35
+ }
36
+
37
+ .testimonial {
38
+ display: grid;
39
+ gap: 1rem;
40
+ padding: 1.75rem;
41
+ border-radius: 24px;
42
+ background: #ffffff;
43
+ border: 1px solid rgba(15, 23, 42, 0.08);
44
+ box-shadow: 0 16px 32px rgba(15, 23, 42, 0.06);
45
+ }
46
+
47
+ .quote {
48
+ font-size: 1rem;
49
+ line-height: 1.8;
50
+ color: #475569;
51
+ position: relative;
52
+ padding-left: 1rem;
53
+ }
54
+
55
+ .quote::before {
56
+ content: '“';
57
+ position: absolute;
58
+ left: 0;
59
+ top: 0;
60
+ font-size: 3rem;
61
+ color: #1EB53A;
62
+ line-height: 1;
63
+ }
64
+
65
+ .author {
66
+ display: flex;
67
+ flex-direction: column;
68
+ gap: 0.125rem;
69
+ color: #0f172a;
70
+ }
71
+
72
+ .name {
73
+ font-weight: 700;
74
+ }
75
+
76
+ .meta {
77
+ color: #64748b;
78
+ font-size: 0.9rem;
79
+ }
80
+ </style>
81
+
82
+ <article class="testimonial">
83
+ <div class="quote"><slot></slot></div>
84
+ <div class="author">
85
+ <span class="name">${author}</span>
86
+ <span class="meta">${role}${company ? ` · ${company}` : ''}</span>
87
+ </div>
88
+ </article>
89
+ `;
90
+ }
91
+ }
92
+
93
+ registerComponent('af-testimonial', AfriTestimonial);
94
+ export default AfriTestimonial;
@@ -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;