@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,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode FAQ Accordion
|
|
3
|
+
*
|
|
4
|
+
* Simple accordion for FAQ sections.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
8
|
+
|
|
9
|
+
export class AfriFAQAccordion extends AfriCodeComponent {
|
|
10
|
+
constructor(){ super(); this.render(); }
|
|
11
|
+
connectedCallback(){ this.shadowRoot.addEventListener('click',(e)=>{ if(e.target.matches('.q')){ const p=e.target.nextElementSibling; p.hidden=!p.hidden}})}
|
|
12
|
+
render(){ this.shadowRoot.innerHTML = html`
|
|
13
|
+
<style>
|
|
14
|
+
:host{display:block}
|
|
15
|
+
.q{cursor:pointer;padding:12px;border-radius:8px;background:#f8fafc}
|
|
16
|
+
.a{padding:12px}
|
|
17
|
+
</style>
|
|
18
|
+
<slot></slot>
|
|
19
|
+
`}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
registerComponent('af-faq-accordion', AfriFAQAccordion);
|
|
23
|
+
export default AfriFAQAccordion;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Feature Grid Component
|
|
3
|
+
*
|
|
4
|
+
* Responsive feature tiles for website marketing sections.
|
|
5
|
+
* @module components/feature-grid
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
9
|
+
|
|
10
|
+
export class AfriFeatureGrid extends AfriCodeComponent {
|
|
11
|
+
static get observedAttributes() {
|
|
12
|
+
return ['columns'];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.render();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
attributeChangedCallback() {
|
|
21
|
+
this.render();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
render() {
|
|
25
|
+
const columns = this.getAttribute('columns') || '3';
|
|
26
|
+
|
|
27
|
+
this.shadowRoot.innerHTML = html`
|
|
28
|
+
<style>
|
|
29
|
+
:host {
|
|
30
|
+
display: block;
|
|
31
|
+
width: 100%;
|
|
32
|
+
box-sizing: border-box;
|
|
33
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.grid {
|
|
37
|
+
display: grid;
|
|
38
|
+
gap: 1.5rem;
|
|
39
|
+
grid-template-columns: repeat(${columns}, minmax(0, 1fr));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@media (max-width: 1024px) {
|
|
43
|
+
.grid {
|
|
44
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@media (max-width: 640px) {
|
|
49
|
+
.grid {
|
|
50
|
+
grid-template-columns: 1fr;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
::slotted(.feature-card) {
|
|
55
|
+
display: grid;
|
|
56
|
+
gap: 0.75rem;
|
|
57
|
+
padding: 1.5rem;
|
|
58
|
+
border-radius: 24px;
|
|
59
|
+
background: #ffffff;
|
|
60
|
+
border: 1px solid rgba(15, 23, 42, 0.08);
|
|
61
|
+
box-shadow: 0 12px 32px rgba(15, 23, 42, 0.04);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
::slotted(.feature-card h3) {
|
|
65
|
+
margin: 0;
|
|
66
|
+
font-size: 1.1rem;
|
|
67
|
+
color: #0f172a;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
::slotted(.feature-card p) {
|
|
71
|
+
margin: 0;
|
|
72
|
+
color: #475569;
|
|
73
|
+
line-height: 1.7;
|
|
74
|
+
}
|
|
75
|
+
</style>
|
|
76
|
+
|
|
77
|
+
<div class="grid">
|
|
78
|
+
<slot></slot>
|
|
79
|
+
</div>
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
registerComponent('af-feature-grid', AfriFeatureGrid);
|
|
85
|
+
export default AfriFeatureGrid;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode File Uploader
|
|
3
|
+
*
|
|
4
|
+
* Simple drag-and-drop file uploader component.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
8
|
+
|
|
9
|
+
export class AfriFileUploader extends AfriCodeComponent {
|
|
10
|
+
constructor(){ super(); this.render(); }
|
|
11
|
+
connectedCallback(){ const area=this.shadowRoot.getElementById('drop'); ['dragover','dragleave','drop'].forEach(ev=>area.addEventListener(ev,(e)=>{e.preventDefault();if(ev==='drop'){const files=Array.from(e.dataTransfer.files);this.emit('files',files)}})) }
|
|
12
|
+
render(){ this.shadowRoot.innerHTML = html`
|
|
13
|
+
<style>
|
|
14
|
+
:host{display:block}
|
|
15
|
+
.drop{padding:20px;border:2px dashed #e5e7eb;border-radius:12px;text-align:center}
|
|
16
|
+
</style>
|
|
17
|
+
<div id="drop" class="drop">Drop files here or click to select</div>
|
|
18
|
+
` }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
registerComponent('af-file-uploader', AfriFileUploader);
|
|
22
|
+
export default AfriFileUploader;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Filter Bar
|
|
3
|
+
*
|
|
4
|
+
* Simple filter controls for listing pages.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
8
|
+
|
|
9
|
+
export class AfriFilterBar extends AfriCodeComponent {
|
|
10
|
+
constructor(){ super(); this.render(); }
|
|
11
|
+
connectedCallback(){ this.shadowRoot.addEventListener('change', (e)=>{ this.emit('filter-change', {name: e.target.name, value: e.target.value}) }) }
|
|
12
|
+
render(){ this.shadowRoot.innerHTML = html`
|
|
13
|
+
<style> .wrap{display:flex;gap:8px;flex-wrap:wrap} select,input{padding:8px;border-radius:8px;border:1px solid #e5e7eb} </style>
|
|
14
|
+
<div class="wrap"><slot></slot></div>
|
|
15
|
+
` }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
registerComponent('af-filter-bar', AfriFilterBar);
|
|
19
|
+
export default AfriFilterBar;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Footer Component
|
|
3
|
+
*
|
|
4
|
+
* Flexible website footer with logo, links, and legal slot.
|
|
5
|
+
* @module components/footer
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
9
|
+
|
|
10
|
+
export class AfriFooter extends AfriCodeComponent {
|
|
11
|
+
static get observedAttributes() {
|
|
12
|
+
return ['company', 'year'];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.render();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
attributeChangedCallback() {
|
|
21
|
+
this.render();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
render() {
|
|
25
|
+
const company = this.getAttribute('company') || 'AfriCode';
|
|
26
|
+
const year = this.getAttribute('year') || new Date().getFullYear();
|
|
27
|
+
|
|
28
|
+
this.shadowRoot.innerHTML = html`
|
|
29
|
+
<style>
|
|
30
|
+
:host {
|
|
31
|
+
display: block;
|
|
32
|
+
width: 100%;
|
|
33
|
+
box-sizing: border-box;
|
|
34
|
+
background: #0f172a;
|
|
35
|
+
color: #e2e8f0;
|
|
36
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.footer {
|
|
40
|
+
padding: 48px 5%;
|
|
41
|
+
max-width: 1280px;
|
|
42
|
+
margin: 0 auto;
|
|
43
|
+
display: grid;
|
|
44
|
+
gap: 2rem;
|
|
45
|
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.brand {
|
|
49
|
+
display: grid;
|
|
50
|
+
gap: 0.75rem;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.brand h3 {
|
|
54
|
+
margin: 0;
|
|
55
|
+
font-size: 1.25rem;
|
|
56
|
+
color: #ffffff;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.links {
|
|
60
|
+
display: grid;
|
|
61
|
+
gap: 0.5rem;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.links slot {
|
|
65
|
+
display: grid;
|
|
66
|
+
gap: 0.5rem;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
a {
|
|
70
|
+
color: #94a3b8;
|
|
71
|
+
text-decoration: none;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
a:hover {
|
|
75
|
+
color: #ffffff;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.bottom {
|
|
79
|
+
grid-column: 1 / -1;
|
|
80
|
+
display: flex;
|
|
81
|
+
flex-wrap: wrap;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: space-between;
|
|
84
|
+
gap: 1rem;
|
|
85
|
+
padding-top: 1.5rem;
|
|
86
|
+
border-top: 1px solid rgba(226, 232, 240, 0.12);
|
|
87
|
+
color: #94a3b8;
|
|
88
|
+
font-size: 0.95rem;
|
|
89
|
+
}
|
|
90
|
+
</style>
|
|
91
|
+
|
|
92
|
+
<footer class="footer">
|
|
93
|
+
<div class="brand">
|
|
94
|
+
<h3>${company}</h3>
|
|
95
|
+
<slot name="description"></slot>
|
|
96
|
+
</div>
|
|
97
|
+
<div class="links">
|
|
98
|
+
<slot name="links"></slot>
|
|
99
|
+
</div>
|
|
100
|
+
<div class="links">
|
|
101
|
+
<slot name="resources"></slot>
|
|
102
|
+
</div>
|
|
103
|
+
<div class="bottom">
|
|
104
|
+
<span>© ${year} ${company}. All rights reserved.</span>
|
|
105
|
+
<slot name="legal"></slot>
|
|
106
|
+
</div>
|
|
107
|
+
</footer>
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
registerComponent('af-footer', AfriFooter);
|
|
113
|
+
export default AfriFooter;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Gallery
|
|
3
|
+
*
|
|
4
|
+
* Responsive image gallery grid with lightbox slot support.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
8
|
+
|
|
9
|
+
export class AfriGallery extends AfriCodeComponent {
|
|
10
|
+
constructor(){ super(); this.render(); }
|
|
11
|
+
render(){ this.shadowRoot.innerHTML = html`
|
|
12
|
+
<style>
|
|
13
|
+
:host{display:block}
|
|
14
|
+
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px}
|
|
15
|
+
::slotted(img){width:100%;height:160px;object-fit:cover;border-radius:12px}
|
|
16
|
+
</style>
|
|
17
|
+
<div class="grid"><slot></slot></div>
|
|
18
|
+
`}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
registerComponent('af-gallery', AfriGallery);
|
|
22
|
+
export default AfriGallery;
|
package/components/index.js
CHANGED
|
@@ -14,6 +14,27 @@ export { AfriUICard } from './ui-card.js';
|
|
|
14
14
|
export { AfriUIInput } from './ui-input.js';
|
|
15
15
|
export { AfriUIBadge } from './ui-badge.js';
|
|
16
16
|
export { AfriUISwitch } from './ui-switch.js';
|
|
17
|
+
export { AfriUISelect } from './ui-select.js';
|
|
18
|
+
export { AfriUITextarea } from './ui-textarea.js';
|
|
19
|
+
export { AfriUIChip } from './ui-chip.js';
|
|
20
|
+
export { AfriUITag } from './ui-tag.js';
|
|
21
|
+
export { AfriUIAvatar } from './ui-avatar.js';
|
|
22
|
+
export { AfriUIProgressRing } from './ui-progress-ring.js';
|
|
23
|
+
export { AfriUITooltip } from './ui-tooltip.js';
|
|
24
|
+
export { AfriUIPopover } from './ui-popover.js';
|
|
25
|
+
export { AfriUITabs } from './ui-tabs.js';
|
|
26
|
+
export { AfriUITable } from './ui-table.js';
|
|
27
|
+
export { AfriUIStat } from './ui-stat.js';
|
|
28
|
+
export { AfriFooter } from './footer.js';
|
|
29
|
+
export { AfriBreadcrumb } from './breadcrumb.js';
|
|
30
|
+
export { AfriFeatureGrid } from './feature-grid.js';
|
|
31
|
+
export { AfriPricingCard } from './pricing-card.js';
|
|
32
|
+
export { AfriTestimonial } from './testimonial.js';
|
|
33
|
+
export { AfriDashboardShell } from './dashboard-shell.js';
|
|
34
|
+
export { AfriDashboardTopbar } from './dashboard-topbar.js';
|
|
35
|
+
export { AfriDashboardCard } from './dashboard-card.js';
|
|
36
|
+
export { AfriDashboardMetric } from './dashboard-metric.js';
|
|
37
|
+
export { AfriDashboardActivityList } from './dashboard-activity-list.js';
|
|
17
38
|
|
|
18
39
|
// Map of Tag Name -> Dynamic Import
|
|
19
40
|
export const componentMap = {
|
|
@@ -33,6 +54,27 @@ export const componentMap = {
|
|
|
33
54
|
'af-ui-input': () => import('./ui-input.js'),
|
|
34
55
|
'af-ui-badge': () => import('./ui-badge.js'),
|
|
35
56
|
'af-ui-switch': () => import('./ui-switch.js'),
|
|
57
|
+
'af-ui-select': () => import('./ui-select.js'),
|
|
58
|
+
'af-ui-textarea': () => import('./ui-textarea.js'),
|
|
59
|
+
'af-ui-chip': () => import('./ui-chip.js'),
|
|
60
|
+
'af-ui-tag': () => import('./ui-tag.js'),
|
|
61
|
+
'af-ui-avatar': () => import('./ui-avatar.js'),
|
|
62
|
+
'af-ui-progress-ring': () => import('./ui-progress-ring.js'),
|
|
63
|
+
'af-ui-tooltip': () => import('./ui-tooltip.js'),
|
|
64
|
+
'af-ui-popover': () => import('./ui-popover.js'),
|
|
65
|
+
'af-ui-tabs': () => import('./ui-tabs.js'),
|
|
66
|
+
'af-ui-table': () => import('./ui-table.js'),
|
|
67
|
+
'af-ui-stat': () => import('./ui-stat.js'),
|
|
68
|
+
'af-footer': () => import('./footer.js'),
|
|
69
|
+
'af-breadcrumb': () => import('./breadcrumb.js'),
|
|
70
|
+
'af-feature-grid': () => import('./feature-grid.js'),
|
|
71
|
+
'af-pricing-card': () => import('./pricing-card.js'),
|
|
72
|
+
'af-testimonial': () => import('./testimonial.js'),
|
|
73
|
+
'af-dashboard-shell': () => import('./dashboard-shell.js'),
|
|
74
|
+
'af-dashboard-topbar': () => import('./dashboard-topbar.js'),
|
|
75
|
+
'af-dashboard-card': () => import('./dashboard-card.js'),
|
|
76
|
+
'af-dashboard-metric': () => import('./dashboard-metric.js'),
|
|
77
|
+
'af-dashboard-activity-list': () => import('./dashboard-activity-list.js'),
|
|
36
78
|
|
|
37
79
|
// Enterprise
|
|
38
80
|
'af-accordion': () => import('./accordion.js'),
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Newsletter Signup
|
|
3
|
+
*
|
|
4
|
+
* Simple email capture component.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
8
|
+
|
|
9
|
+
export class AfriNewsletterSignup extends AfriCodeComponent {
|
|
10
|
+
constructor(){ super(); this.render(); }
|
|
11
|
+
connectedCallback(){ this.shadowRoot.querySelector('form')?.addEventListener('submit',(e)=>{e.preventDefault();const f=new FormData(e.target);this.emit('subscribe',Object.fromEntries(f.entries()))}) }
|
|
12
|
+
render(){ this.shadowRoot.innerHTML = html`
|
|
13
|
+
<style>
|
|
14
|
+
:host{display:block}
|
|
15
|
+
form{display:flex;gap:8px}
|
|
16
|
+
input{flex:1;padding:10px;border-radius:8px;border:1px solid #e5e7eb}
|
|
17
|
+
button{padding:10px;border-radius:8px;background:#1EB53A;color:white;border:none}
|
|
18
|
+
</style>
|
|
19
|
+
<form><input name="email" type="email" placeholder="Your email" required/><button>Subscribe</button></form>
|
|
20
|
+
`}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
registerComponent('af-newsletter-signup', AfriNewsletterSignup);
|
|
24
|
+
export default AfriNewsletterSignup;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Pagination
|
|
3
|
+
*
|
|
4
|
+
* Simple pagination control for lists.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
8
|
+
|
|
9
|
+
export class AfriPagination extends AfriCodeComponent {
|
|
10
|
+
static get observedAttributes(){return ['page','pages']}
|
|
11
|
+
constructor(){super();this.render();}
|
|
12
|
+
attributeChangedCallback(){this.render();}
|
|
13
|
+
render(){const page=Number(this.getAttribute('page')||1);const pages=Number(this.getAttribute('pages')||1);
|
|
14
|
+
this.shadowRoot.innerHTML=html`
|
|
15
|
+
<style>
|
|
16
|
+
:host{display:block}
|
|
17
|
+
.wrap{display:flex;gap:8px}
|
|
18
|
+
button{padding:6px 10px;border-radius:8px;border:1px solid #e5e7eb;background:white}
|
|
19
|
+
</style>
|
|
20
|
+
<div class="wrap">
|
|
21
|
+
${page>1?html`<button data-action="prev">Prev</button>`:''}
|
|
22
|
+
<span>Page ${page} / ${pages}</span>
|
|
23
|
+
${page<pages?html`<button data-action="next">Next</button>`:''}
|
|
24
|
+
</div>
|
|
25
|
+
`;
|
|
26
|
+
this.shadowRoot.querySelectorAll('button').forEach(b=>b.addEventListener('click',e=>{const a=e.currentTarget.getAttribute('data-action');this.emit('navigate',{action:a})}))
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
registerComponent('af-pagination', AfriPagination);
|
|
31
|
+
export default AfriPagination;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Pricing Card Component
|
|
3
|
+
*
|
|
4
|
+
* Pricing plan card for SaaS and business websites.
|
|
5
|
+
* @module components/pricing-card
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
9
|
+
|
|
10
|
+
export class AfriPricingCard extends AfriCodeComponent {
|
|
11
|
+
static get observedAttributes() {
|
|
12
|
+
return ['title', 'price', 'frequency', 'highlight'];
|
|
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') || 'Standard';
|
|
26
|
+
const price = this.getAttribute('price') || '$49';
|
|
27
|
+
const frequency = this.getAttribute('frequency') || 'per month';
|
|
28
|
+
const highlight = this.hasAttribute('highlight');
|
|
29
|
+
|
|
30
|
+
this.shadowRoot.innerHTML = html`
|
|
31
|
+
<style>
|
|
32
|
+
:host {
|
|
33
|
+
display: block;
|
|
34
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.card {
|
|
38
|
+
display: grid;
|
|
39
|
+
gap: 1rem;
|
|
40
|
+
padding: 1.75rem;
|
|
41
|
+
border-radius: 28px;
|
|
42
|
+
background: ${highlight ? 'linear-gradient(180deg, #1EB53A, #0f172a)' : '#ffffff'};
|
|
43
|
+
color: ${highlight ? '#ffffff' : '#0f172a'};
|
|
44
|
+
border: 1px solid rgba(15, 23, 42, 0.08);
|
|
45
|
+
box-shadow: 0 20px 50px rgba(15, 23, 42, 0.08);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.title {
|
|
49
|
+
margin: 0;
|
|
50
|
+
font-size: 1.2rem;
|
|
51
|
+
font-weight: 700;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.price {
|
|
55
|
+
font-size: 2.75rem;
|
|
56
|
+
font-weight: 800;
|
|
57
|
+
line-height: 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.frequency {
|
|
61
|
+
color: ${highlight ? 'rgba(255,255,255,0.8)' : '#64748b'};
|
|
62
|
+
font-size: 0.95rem;
|
|
63
|
+
margin-top: -0.25rem;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.features {
|
|
67
|
+
display: grid;
|
|
68
|
+
gap: 0.75rem;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
::slotted(.feature) {
|
|
72
|
+
color: ${highlight ? '#e2e8f0' : '#475569'};
|
|
73
|
+
font-size: 0.95rem;
|
|
74
|
+
display: flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
gap: 0.5rem;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.footer {
|
|
80
|
+
margin-top: 1rem;
|
|
81
|
+
}
|
|
82
|
+
</style>
|
|
83
|
+
|
|
84
|
+
<div class="card">
|
|
85
|
+
<div>
|
|
86
|
+
<h3 class="title">${title}</h3>
|
|
87
|
+
<div class="price">${price}</div>
|
|
88
|
+
<div class="frequency">${frequency}</div>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="features">
|
|
91
|
+
<slot></slot>
|
|
92
|
+
</div>
|
|
93
|
+
<div class="footer"><slot name="action"></slot></div>
|
|
94
|
+
</div>
|
|
95
|
+
`;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
registerComponent('af-pricing-card', AfriPricingCard);
|
|
100
|
+
export default AfriPricingCard;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Product Card
|
|
3
|
+
*
|
|
4
|
+
* Product display card for e-commerce pages.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
8
|
+
|
|
9
|
+
export class AfriProductCard extends AfriCodeComponent {
|
|
10
|
+
static get observedAttributes() { return ['title','price','image','currency']; }
|
|
11
|
+
constructor(){ super(); this.render(); }
|
|
12
|
+
attributeChangedCallback(){ this.render(); }
|
|
13
|
+
render(){
|
|
14
|
+
const title = this.getAttribute('title') || 'Product';
|
|
15
|
+
const price = this.getAttribute('price') || '0';
|
|
16
|
+
const image = this.getAttribute('image') || '';
|
|
17
|
+
const currency = this.getAttribute('currency') || '$';
|
|
18
|
+
this.shadowRoot.innerHTML = html`
|
|
19
|
+
<style>
|
|
20
|
+
:host{display:block;font-family:Inter,system-ui,sans-serif}
|
|
21
|
+
.card{background:#fff;border-radius:12px;padding:12px;display:grid;gap:8px}
|
|
22
|
+
.media{height:180px;border-radius:8px;background:#f3f4f6;overflow:hidden}
|
|
23
|
+
.media img{width:100%;height:100%;object-fit:cover}
|
|
24
|
+
.meta{display:flex;justify-content:space-between;align-items:center}
|
|
25
|
+
.title{font-weight:700}
|
|
26
|
+
.price{font-weight:800}
|
|
27
|
+
.actions{display:flex;gap:8px}
|
|
28
|
+
button{padding:8px 12px;border-radius:8px;border:none;background:#1EB53A;color:white;cursor:pointer}
|
|
29
|
+
</style>
|
|
30
|
+
<div class="card">
|
|
31
|
+
<div class="media">${image?html`<img src="${image}" alt="${title}"/>` : ''}</div>
|
|
32
|
+
<div class="meta"><div class="title">${title}</div><div class="price">${currency}${price}</div></div>
|
|
33
|
+
<div class="actions"><button class="add">Add to cart</button><button class="view">View</button></div>
|
|
34
|
+
</div>
|
|
35
|
+
`;
|
|
36
|
+
this.shadowRoot.querySelector('.add')?.addEventListener('click',()=>this.emit('add-to-cart',{title,price,currency}))
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
registerComponent('af-product-card', AfriProductCard);
|
|
41
|
+
export default AfriProductCard;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Search Box
|
|
3
|
+
*
|
|
4
|
+
* Search input with debounced emit for live search.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
8
|
+
|
|
9
|
+
export class AfriSearchBox extends AfriCodeComponent {
|
|
10
|
+
constructor(){ super(); this._deb=null; this.render(); }
|
|
11
|
+
connectedCallback(){ this.shadowRoot.getElementById('q').addEventListener('input', (e)=>{ clearTimeout(this._deb); this._deb=setTimeout(()=>this.emit('search',{q:e.target.value}),300) }) }
|
|
12
|
+
render(){ this.shadowRoot.innerHTML = html`
|
|
13
|
+
<style>
|
|
14
|
+
:host{display:block}
|
|
15
|
+
.wrap{display:flex;gap:8px}
|
|
16
|
+
input{padding:10px;border-radius:8px;border:1px solid #e5e7eb;flex:1}
|
|
17
|
+
button{padding:10px;border-radius:8px;background:#0f172a;color:white;border:none}
|
|
18
|
+
</style>
|
|
19
|
+
<div class="wrap"><input id="q" placeholder="Search..."/><button aria-hidden>🔍</button></div>
|
|
20
|
+
`}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
registerComponent('af-search-box', AfriSearchBox);
|
|
24
|
+
export default AfriSearchBox;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Simple Chart
|
|
3
|
+
*
|
|
4
|
+
* Tiny chart wrapper for simple bar/line charts using Canvas.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
8
|
+
|
|
9
|
+
export class AfriSimpleChart extends AfriCodeComponent {
|
|
10
|
+
static get observedAttributes(){return ['type','data']}
|
|
11
|
+
constructor(){ super(); this.render(); }
|
|
12
|
+
attributeChangedCallback(){ this.render(); }
|
|
13
|
+
render(){ const type=this.getAttribute('type')||'bar'; const dataAttr=this.getAttribute('data')||'[]'; let data=[]; try{data=JSON.parse(dataAttr)}catch(e){}
|
|
14
|
+
this.shadowRoot.innerHTML = html`
|
|
15
|
+
<style>canvas{width:100%;height:200px}</style>
|
|
16
|
+
<canvas></canvas>
|
|
17
|
+
`; const canvas=this.shadowRoot.querySelector('canvas'); if(canvas){ const ctx=canvas.getContext('2d'); canvas.width=600; canvas.height=200; ctx.clearRect(0,0,canvas.width,canvas.height); if(type==='bar'){ const w=canvas.width/data.length; data.forEach((v,i)=>{ const h=(v/Math.max(...data))*150; ctx.fillStyle='#1EB53A'; ctx.fillRect(i*w+10,canvas.height-20-h,w-20,h) }) } }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
registerComponent('af-simple-chart', AfriSimpleChart);
|
|
22
|
+
export default AfriSimpleChart;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Stepper
|
|
3
|
+
*
|
|
4
|
+
* Multi-step wizard component.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
8
|
+
|
|
9
|
+
export class AfriStepper extends AfriCodeComponent {
|
|
10
|
+
constructor(){ super(); this._step=0; this.render(); }
|
|
11
|
+
next(){ this._step++; this.render(); this.emit('step-change',{step:this._step}) }
|
|
12
|
+
prev(){ this._step=Math.max(0,this._step-1); this.render(); this.emit('step-change',{step:this._step}) }
|
|
13
|
+
render(){ const steps=Array.from(this.querySelectorAll('[slot="step"]')); const labels=steps.map(s=>s.getAttribute('label')||'Step');
|
|
14
|
+
this.shadowRoot.innerHTML=html`
|
|
15
|
+
<style>
|
|
16
|
+
:host{display:block}
|
|
17
|
+
.nav{display:flex;gap:8px;margin-bottom:12px}
|
|
18
|
+
.panel{padding:12px;border-radius:8px;background:#fff}
|
|
19
|
+
button{padding:8px 10px;border-radius:8px;background:#1EB53A;border:none;color:white}
|
|
20
|
+
</style>
|
|
21
|
+
<div class="nav">${labels.map((l,i)=>`<div>${i+1}. ${l}</div>`).join('')}</div>
|
|
22
|
+
<div class="panel">${steps[this._step]?.innerHTML||''}</div>
|
|
23
|
+
<div style="margin-top:12px"><button class="prev">Prev</button><button class="next" style="margin-left:8px">Next</button></div>
|
|
24
|
+
`; this.shadowRoot.querySelector('.next')?.addEventListener('click',()=>this.next()); this.shadowRoot.querySelector('.prev')?.addEventListener('click',()=>this.prev()) }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
registerComponent('af-stepper', AfriStepper);
|
|
28
|
+
export default AfriStepper;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Team Grid
|
|
3
|
+
*
|
|
4
|
+
* Displays team members in a responsive grid.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AfriCodeComponent, registerComponent, html } from './base.js';
|
|
8
|
+
|
|
9
|
+
export class AfriTeamGrid extends AfriCodeComponent {
|
|
10
|
+
constructor(){ super(); this.render(); }
|
|
11
|
+
render(){ this.shadowRoot.innerHTML = html`
|
|
12
|
+
<style>
|
|
13
|
+
:host{display:block}
|
|
14
|
+
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px}
|
|
15
|
+
::slotted(.member){background:#fff;padding:1rem;border-radius:12px;display:flex;flex-direction:column;align-items:center;gap:8px}
|
|
16
|
+
::slotted(.avatar){width:72px;height:72px;border-radius:999px;object-fit:cover}
|
|
17
|
+
</style>
|
|
18
|
+
<div class="grid"><slot></slot></div>
|
|
19
|
+
`}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
registerComponent('af-team-grid', AfriTeamGrid);
|
|
23
|
+
export default AfriTeamGrid;
|