@uniweb/templates 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +32 -0
- package/src/index.js +120 -0
- package/src/processor.js +184 -0
- package/src/validator.js +206 -0
- package/templates/marketing/template/README.md.hbs +56 -0
- package/templates/marketing/template/foundation/package.json.hbs +34 -0
- package/templates/marketing/template/foundation/postcss.config.js +6 -0
- package/templates/marketing/template/foundation/src/components/CTA/index.jsx +75 -0
- package/templates/marketing/template/foundation/src/components/CTA/meta.js +46 -0
- package/templates/marketing/template/foundation/src/components/Features/index.jsx +81 -0
- package/templates/marketing/template/foundation/src/components/Features/meta.js +46 -0
- package/templates/marketing/template/foundation/src/components/Hero/index.jsx +73 -0
- package/templates/marketing/template/foundation/src/components/Hero/meta.js +46 -0
- package/templates/marketing/template/foundation/src/components/Pricing/index.jsx +108 -0
- package/templates/marketing/template/foundation/src/components/Pricing/meta.js +36 -0
- package/templates/marketing/template/foundation/src/components/Testimonials/index.jsx +96 -0
- package/templates/marketing/template/foundation/src/components/Testimonials/meta.js +44 -0
- package/templates/marketing/template/foundation/src/entry-runtime.js +3 -0
- package/templates/marketing/template/foundation/src/index.js +37 -0
- package/templates/marketing/template/foundation/src/meta.js.hbs +28 -0
- package/templates/marketing/template/foundation/src/styles.css +3 -0
- package/templates/marketing/template/foundation/tailwind.config.js +17 -0
- package/templates/marketing/template/foundation/vite.config.js +23 -0
- package/templates/marketing/template/package.json.hbs +13 -0
- package/templates/marketing/template/pnpm-workspace.yaml +3 -0
- package/templates/marketing/template/site/index.html.hbs +18 -0
- package/templates/marketing/template/site/package.json.hbs +27 -0
- package/templates/marketing/template/site/pages/home/1-hero.md +12 -0
- package/templates/marketing/template/site/pages/home/2-features.md +33 -0
- package/templates/marketing/template/site/pages/home/3-pricing.md +43 -0
- package/templates/marketing/template/site/pages/home/4-testimonials.md +27 -0
- package/templates/marketing/template/site/pages/home/5-cta.md +12 -0
- package/templates/marketing/template/site/pages/home/page.yml +2 -0
- package/templates/marketing/template/site/postcss.config.js +6 -0
- package/templates/marketing/template/site/site.yml.hbs +5 -0
- package/templates/marketing/template/site/src/main.jsx +19 -0
- package/templates/marketing/template/site/tailwind.config.js +24 -0
- package/templates/marketing/template/site/vite.config.js +42 -0
- package/templates/marketing/template.json +8 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
export function CTA({ content, params }) {
|
|
4
|
+
const { title } = content.main?.header || {}
|
|
5
|
+
const { paragraphs = [], links = [] } = content.main?.body || {}
|
|
6
|
+
const { theme = 'primary', alignment = 'center' } = params || {}
|
|
7
|
+
|
|
8
|
+
const cta = links[0]
|
|
9
|
+
const secondaryCta = links[1]
|
|
10
|
+
|
|
11
|
+
const themeStyles = {
|
|
12
|
+
primary: 'bg-primary text-white',
|
|
13
|
+
gradient: 'bg-gradient-to-r from-primary to-blue-700 text-white',
|
|
14
|
+
dark: 'bg-gray-900 text-white',
|
|
15
|
+
light: 'bg-gray-100 text-gray-900',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const buttonStyles = {
|
|
19
|
+
primary: 'bg-white text-primary hover:bg-blue-50',
|
|
20
|
+
gradient: 'bg-white text-primary hover:bg-blue-50',
|
|
21
|
+
dark: 'bg-white text-gray-900 hover:bg-gray-100',
|
|
22
|
+
light: 'bg-primary text-white hover:bg-blue-700',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const secondaryStyles = {
|
|
26
|
+
primary: 'border-2 border-white text-white hover:bg-white/10',
|
|
27
|
+
gradient: 'border-2 border-white text-white hover:bg-white/10',
|
|
28
|
+
dark: 'border-2 border-gray-600 text-white hover:bg-gray-800',
|
|
29
|
+
light: 'border-2 border-gray-300 text-gray-700 hover:bg-gray-200',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const descStyles = {
|
|
33
|
+
primary: 'text-blue-100',
|
|
34
|
+
gradient: 'text-blue-100',
|
|
35
|
+
dark: 'text-gray-400',
|
|
36
|
+
light: 'text-gray-600',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<section className={`py-20 px-6 ${themeStyles[theme]}`}>
|
|
41
|
+
<div className={`max-w-4xl mx-auto ${alignment === 'center' ? 'text-center' : ''}`}>
|
|
42
|
+
{title && (
|
|
43
|
+
<h2 className="text-3xl md:text-4xl font-bold mb-4">{title}</h2>
|
|
44
|
+
)}
|
|
45
|
+
{paragraphs[0] && (
|
|
46
|
+
<p className={`text-lg mb-8 max-w-2xl ${alignment === 'center' ? 'mx-auto' : ''} ${descStyles[theme]}`}>
|
|
47
|
+
{paragraphs[0]}
|
|
48
|
+
</p>
|
|
49
|
+
)}
|
|
50
|
+
{(cta || secondaryCta) && (
|
|
51
|
+
<div className={`flex gap-4 ${alignment === 'center' ? 'justify-center' : ''} flex-wrap`}>
|
|
52
|
+
{cta && (
|
|
53
|
+
<a
|
|
54
|
+
href={cta.url}
|
|
55
|
+
className={`inline-block px-8 py-4 font-semibold rounded-lg transition-colors ${buttonStyles[theme]}`}
|
|
56
|
+
>
|
|
57
|
+
{cta.text}
|
|
58
|
+
</a>
|
|
59
|
+
)}
|
|
60
|
+
{secondaryCta && (
|
|
61
|
+
<a
|
|
62
|
+
href={secondaryCta.url}
|
|
63
|
+
className={`inline-block px-8 py-4 font-semibold rounded-lg transition-colors ${secondaryStyles[theme]}`}
|
|
64
|
+
>
|
|
65
|
+
{secondaryCta.text}
|
|
66
|
+
</a>
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
</section>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export default CTA
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CTA Component Metadata
|
|
3
|
+
*/
|
|
4
|
+
export default {
|
|
5
|
+
title: 'Call to Action',
|
|
6
|
+
description: 'A conversion-focused section with headline and action buttons',
|
|
7
|
+
category: 'Conversion',
|
|
8
|
+
|
|
9
|
+
elements: {
|
|
10
|
+
title: {
|
|
11
|
+
label: 'Headline',
|
|
12
|
+
description: 'From H2 in markdown',
|
|
13
|
+
required: true,
|
|
14
|
+
},
|
|
15
|
+
paragraphs: {
|
|
16
|
+
label: 'Description',
|
|
17
|
+
},
|
|
18
|
+
links: {
|
|
19
|
+
label: 'Action Buttons',
|
|
20
|
+
description: 'Primary and secondary CTAs',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
properties: {
|
|
25
|
+
theme: {
|
|
26
|
+
type: 'select',
|
|
27
|
+
label: 'Theme',
|
|
28
|
+
options: [
|
|
29
|
+
{ value: 'primary', label: 'Primary' },
|
|
30
|
+
{ value: 'gradient', label: 'Gradient' },
|
|
31
|
+
{ value: 'dark', label: 'Dark' },
|
|
32
|
+
{ value: 'light', label: 'Light' },
|
|
33
|
+
],
|
|
34
|
+
default: 'primary',
|
|
35
|
+
},
|
|
36
|
+
alignment: {
|
|
37
|
+
type: 'select',
|
|
38
|
+
label: 'Alignment',
|
|
39
|
+
options: [
|
|
40
|
+
{ value: 'center', label: 'Center' },
|
|
41
|
+
{ value: 'left', label: 'Left' },
|
|
42
|
+
],
|
|
43
|
+
default: 'center',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
export function Features({ content, params }) {
|
|
4
|
+
const { title } = content.main?.header || {}
|
|
5
|
+
const { paragraphs = [] } = content.main?.body || {}
|
|
6
|
+
const { columns = 3, theme = 'light' } = params || {}
|
|
7
|
+
|
|
8
|
+
// Extract features from subsections
|
|
9
|
+
const features = content.subsections || []
|
|
10
|
+
|
|
11
|
+
const themeStyles = {
|
|
12
|
+
light: 'bg-white',
|
|
13
|
+
gray: 'bg-gray-50',
|
|
14
|
+
dark: 'bg-gray-900 text-white',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const cardStyles = {
|
|
18
|
+
light: 'bg-gray-50',
|
|
19
|
+
gray: 'bg-white shadow-sm',
|
|
20
|
+
dark: 'bg-gray-800',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const descStyles = {
|
|
24
|
+
light: 'text-gray-600',
|
|
25
|
+
gray: 'text-gray-600',
|
|
26
|
+
dark: 'text-gray-400',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const gridCols = {
|
|
30
|
+
2: 'md:grid-cols-2',
|
|
31
|
+
3: 'md:grid-cols-3',
|
|
32
|
+
4: 'md:grid-cols-2 lg:grid-cols-4',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<section className={`py-20 px-6 ${themeStyles[theme]}`}>
|
|
37
|
+
<div className="max-w-6xl mx-auto">
|
|
38
|
+
{(title || paragraphs[0]) && (
|
|
39
|
+
<div className="text-center mb-16">
|
|
40
|
+
{title && (
|
|
41
|
+
<h2 className="text-3xl md:text-4xl font-bold mb-4">{title}</h2>
|
|
42
|
+
)}
|
|
43
|
+
{paragraphs[0] && (
|
|
44
|
+
<p className={`text-lg max-w-2xl mx-auto ${descStyles[theme]}`}>
|
|
45
|
+
{paragraphs[0]}
|
|
46
|
+
</p>
|
|
47
|
+
)}
|
|
48
|
+
</div>
|
|
49
|
+
)}
|
|
50
|
+
|
|
51
|
+
<div className={`grid gap-8 ${gridCols[columns] || 'md:grid-cols-3'}`}>
|
|
52
|
+
{features.map((feature, index) => {
|
|
53
|
+
const featureTitle = feature.header?.title
|
|
54
|
+
const featureDesc = feature.body?.paragraphs?.[0]
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div
|
|
58
|
+
key={index}
|
|
59
|
+
className={`p-6 rounded-xl ${cardStyles[theme]}`}
|
|
60
|
+
>
|
|
61
|
+
<div className="w-12 h-12 bg-primary/10 rounded-lg flex items-center justify-center mb-4">
|
|
62
|
+
<svg className="w-6 h-6 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
63
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
64
|
+
</svg>
|
|
65
|
+
</div>
|
|
66
|
+
{featureTitle && (
|
|
67
|
+
<h3 className="text-xl font-semibold mb-2">{featureTitle}</h3>
|
|
68
|
+
)}
|
|
69
|
+
{featureDesc && (
|
|
70
|
+
<p className={descStyles[theme]}>{featureDesc}</p>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
)
|
|
74
|
+
})}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</section>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export default Features
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Features Component Metadata
|
|
3
|
+
*/
|
|
4
|
+
export default {
|
|
5
|
+
title: 'Features Grid',
|
|
6
|
+
description: 'Showcase product features in a responsive grid layout',
|
|
7
|
+
category: 'Content',
|
|
8
|
+
|
|
9
|
+
elements: {
|
|
10
|
+
title: {
|
|
11
|
+
label: 'Section Title',
|
|
12
|
+
description: 'From H2 in markdown',
|
|
13
|
+
},
|
|
14
|
+
paragraphs: {
|
|
15
|
+
label: 'Section Description',
|
|
16
|
+
description: 'Introductory text for the features section',
|
|
17
|
+
},
|
|
18
|
+
subsections: {
|
|
19
|
+
label: 'Features',
|
|
20
|
+
description: 'Each H3 with its content becomes a feature card',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
properties: {
|
|
25
|
+
columns: {
|
|
26
|
+
type: 'select',
|
|
27
|
+
label: 'Columns',
|
|
28
|
+
options: [
|
|
29
|
+
{ value: 2, label: '2 Columns' },
|
|
30
|
+
{ value: 3, label: '3 Columns' },
|
|
31
|
+
{ value: 4, label: '4 Columns' },
|
|
32
|
+
],
|
|
33
|
+
default: 3,
|
|
34
|
+
},
|
|
35
|
+
theme: {
|
|
36
|
+
type: 'select',
|
|
37
|
+
label: 'Theme',
|
|
38
|
+
options: [
|
|
39
|
+
{ value: 'light', label: 'Light' },
|
|
40
|
+
{ value: 'gray', label: 'Gray' },
|
|
41
|
+
{ value: 'dark', label: 'Dark' },
|
|
42
|
+
],
|
|
43
|
+
default: 'light',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
export function Hero({ content, params }) {
|
|
4
|
+
const { title } = content.main?.header || {}
|
|
5
|
+
const { paragraphs = [], links = [] } = content.main?.body || {}
|
|
6
|
+
const { theme = 'gradient', alignment = 'center' } = params || {}
|
|
7
|
+
|
|
8
|
+
const cta = links[0]
|
|
9
|
+
const secondaryCta = links[1]
|
|
10
|
+
|
|
11
|
+
const themeStyles = {
|
|
12
|
+
gradient: 'bg-gradient-to-br from-primary to-blue-700 text-white',
|
|
13
|
+
light: 'bg-gray-50 text-gray-900',
|
|
14
|
+
dark: 'bg-gray-900 text-white',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const buttonStyles = {
|
|
18
|
+
gradient: 'bg-white text-primary hover:bg-blue-50',
|
|
19
|
+
light: 'bg-primary text-white hover:bg-blue-700',
|
|
20
|
+
dark: 'bg-primary text-white hover:bg-blue-700',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const secondaryButtonStyles = {
|
|
24
|
+
gradient: 'border-2 border-white text-white hover:bg-white/10',
|
|
25
|
+
light: 'border-2 border-gray-300 text-gray-700 hover:bg-gray-100',
|
|
26
|
+
dark: 'border-2 border-gray-600 text-gray-300 hover:bg-gray-800',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const descStyles = {
|
|
30
|
+
gradient: 'text-blue-100',
|
|
31
|
+
light: 'text-gray-600',
|
|
32
|
+
dark: 'text-gray-400',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<section className={`py-24 px-6 ${themeStyles[theme]}`}>
|
|
37
|
+
<div className={`max-w-4xl mx-auto ${alignment === 'center' ? 'text-center' : 'text-left'}`}>
|
|
38
|
+
{title && (
|
|
39
|
+
<h1 className="text-4xl md:text-6xl font-bold mb-6 leading-tight">
|
|
40
|
+
{title}
|
|
41
|
+
</h1>
|
|
42
|
+
)}
|
|
43
|
+
{paragraphs[0] && (
|
|
44
|
+
<p className={`text-xl md:text-2xl mb-10 max-w-2xl ${alignment === 'center' ? 'mx-auto' : ''} ${descStyles[theme]}`}>
|
|
45
|
+
{paragraphs[0]}
|
|
46
|
+
</p>
|
|
47
|
+
)}
|
|
48
|
+
{(cta || secondaryCta) && (
|
|
49
|
+
<div className={`flex gap-4 ${alignment === 'center' ? 'justify-center' : ''} flex-wrap`}>
|
|
50
|
+
{cta && (
|
|
51
|
+
<a
|
|
52
|
+
href={cta.url}
|
|
53
|
+
className={`inline-block px-8 py-4 font-semibold rounded-lg transition-colors ${buttonStyles[theme]}`}
|
|
54
|
+
>
|
|
55
|
+
{cta.text}
|
|
56
|
+
</a>
|
|
57
|
+
)}
|
|
58
|
+
{secondaryCta && (
|
|
59
|
+
<a
|
|
60
|
+
href={secondaryCta.url}
|
|
61
|
+
className={`inline-block px-8 py-4 font-semibold rounded-lg transition-colors ${secondaryButtonStyles[theme]}`}
|
|
62
|
+
>
|
|
63
|
+
{secondaryCta.text}
|
|
64
|
+
</a>
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
</section>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export default Hero
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hero Component Metadata
|
|
3
|
+
*/
|
|
4
|
+
export default {
|
|
5
|
+
title: 'Hero Banner',
|
|
6
|
+
description: 'A bold hero section with headline, description, and call-to-action buttons',
|
|
7
|
+
category: 'Headers',
|
|
8
|
+
|
|
9
|
+
elements: {
|
|
10
|
+
title: {
|
|
11
|
+
label: 'Headline',
|
|
12
|
+
description: 'From H1 in markdown',
|
|
13
|
+
required: true,
|
|
14
|
+
},
|
|
15
|
+
paragraphs: {
|
|
16
|
+
label: 'Description',
|
|
17
|
+
description: 'From paragraphs in markdown',
|
|
18
|
+
},
|
|
19
|
+
links: {
|
|
20
|
+
label: 'Call to Action',
|
|
21
|
+
description: 'Primary and secondary buttons from links',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
properties: {
|
|
26
|
+
theme: {
|
|
27
|
+
type: 'select',
|
|
28
|
+
label: 'Theme',
|
|
29
|
+
options: [
|
|
30
|
+
{ value: 'gradient', label: 'Gradient' },
|
|
31
|
+
{ value: 'light', label: 'Light' },
|
|
32
|
+
{ value: 'dark', label: 'Dark' },
|
|
33
|
+
],
|
|
34
|
+
default: 'gradient',
|
|
35
|
+
},
|
|
36
|
+
alignment: {
|
|
37
|
+
type: 'select',
|
|
38
|
+
label: 'Alignment',
|
|
39
|
+
options: [
|
|
40
|
+
{ value: 'center', label: 'Center' },
|
|
41
|
+
{ value: 'left', label: 'Left' },
|
|
42
|
+
],
|
|
43
|
+
default: 'center',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
export function Pricing({ content, params }) {
|
|
4
|
+
const { title } = content.main?.header || {}
|
|
5
|
+
const { paragraphs = [] } = content.main?.body || {}
|
|
6
|
+
const { theme = 'light' } = params || {}
|
|
7
|
+
|
|
8
|
+
// Extract pricing tiers from subsections
|
|
9
|
+
const tiers = content.subsections || []
|
|
10
|
+
|
|
11
|
+
const themeStyles = {
|
|
12
|
+
light: 'bg-gray-50',
|
|
13
|
+
white: 'bg-white',
|
|
14
|
+
dark: 'bg-gray-900 text-white',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const cardStyles = {
|
|
18
|
+
light: 'bg-white shadow-lg',
|
|
19
|
+
white: 'bg-gray-50',
|
|
20
|
+
dark: 'bg-gray-800',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const descStyles = {
|
|
24
|
+
light: 'text-gray-600',
|
|
25
|
+
white: 'text-gray-600',
|
|
26
|
+
dark: 'text-gray-400',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<section className={`py-20 px-6 ${themeStyles[theme]}`}>
|
|
31
|
+
<div className="max-w-6xl mx-auto">
|
|
32
|
+
{(title || paragraphs[0]) && (
|
|
33
|
+
<div className="text-center mb-16">
|
|
34
|
+
{title && (
|
|
35
|
+
<h2 className="text-3xl md:text-4xl font-bold mb-4">{title}</h2>
|
|
36
|
+
)}
|
|
37
|
+
{paragraphs[0] && (
|
|
38
|
+
<p className={`text-lg max-w-2xl mx-auto ${descStyles[theme]}`}>
|
|
39
|
+
{paragraphs[0]}
|
|
40
|
+
</p>
|
|
41
|
+
)}
|
|
42
|
+
</div>
|
|
43
|
+
)}
|
|
44
|
+
|
|
45
|
+
<div className="grid md:grid-cols-3 gap-8">
|
|
46
|
+
{tiers.map((tier, index) => {
|
|
47
|
+
const tierTitle = tier.header?.title
|
|
48
|
+
const tierDesc = tier.body?.paragraphs?.[0]
|
|
49
|
+
const features = tier.body?.lists?.[0] || []
|
|
50
|
+
const link = tier.body?.links?.[0]
|
|
51
|
+
const isPopular = index === 1 // Middle tier is popular
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
key={index}
|
|
56
|
+
className={`rounded-2xl p-8 ${cardStyles[theme]} ${
|
|
57
|
+
isPopular ? 'ring-2 ring-primary scale-105' : ''
|
|
58
|
+
}`}
|
|
59
|
+
>
|
|
60
|
+
{isPopular && (
|
|
61
|
+
<div className="text-center mb-4">
|
|
62
|
+
<span className="bg-primary text-white text-sm font-semibold px-3 py-1 rounded-full">
|
|
63
|
+
Most Popular
|
|
64
|
+
</span>
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
{tierTitle && (
|
|
68
|
+
<h3 className="text-2xl font-bold text-center mb-2">{tierTitle}</h3>
|
|
69
|
+
)}
|
|
70
|
+
{tierDesc && (
|
|
71
|
+
<p className={`text-center mb-6 ${descStyles[theme]}`}>{tierDesc}</p>
|
|
72
|
+
)}
|
|
73
|
+
|
|
74
|
+
{features.length > 0 && (
|
|
75
|
+
<ul className="space-y-3 mb-8">
|
|
76
|
+
{features.map((feature, i) => (
|
|
77
|
+
<li key={i} className="flex items-start gap-3">
|
|
78
|
+
<svg className="w-5 h-5 text-green-500 mt-0.5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
79
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
80
|
+
</svg>
|
|
81
|
+
<span className={descStyles[theme]}>{feature}</span>
|
|
82
|
+
</li>
|
|
83
|
+
))}
|
|
84
|
+
</ul>
|
|
85
|
+
)}
|
|
86
|
+
|
|
87
|
+
{link && (
|
|
88
|
+
<a
|
|
89
|
+
href={link.url}
|
|
90
|
+
className={`block w-full py-3 text-center font-semibold rounded-lg transition-colors ${
|
|
91
|
+
isPopular
|
|
92
|
+
? 'bg-primary text-white hover:bg-blue-700'
|
|
93
|
+
: 'bg-gray-100 text-gray-900 hover:bg-gray-200'
|
|
94
|
+
}`}
|
|
95
|
+
>
|
|
96
|
+
{link.text}
|
|
97
|
+
</a>
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
)
|
|
101
|
+
})}
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</section>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export default Pricing
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pricing Component Metadata
|
|
3
|
+
*/
|
|
4
|
+
export default {
|
|
5
|
+
title: 'Pricing Table',
|
|
6
|
+
description: 'Display pricing tiers with features and call-to-action',
|
|
7
|
+
category: 'Commerce',
|
|
8
|
+
|
|
9
|
+
elements: {
|
|
10
|
+
title: {
|
|
11
|
+
label: 'Section Title',
|
|
12
|
+
description: 'From H2 in markdown',
|
|
13
|
+
},
|
|
14
|
+
paragraphs: {
|
|
15
|
+
label: 'Section Description',
|
|
16
|
+
description: 'Introductory text',
|
|
17
|
+
},
|
|
18
|
+
subsections: {
|
|
19
|
+
label: 'Pricing Tiers',
|
|
20
|
+
description: 'Each H3 becomes a pricing card. Use lists for features.',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
properties: {
|
|
25
|
+
theme: {
|
|
26
|
+
type: 'select',
|
|
27
|
+
label: 'Theme',
|
|
28
|
+
options: [
|
|
29
|
+
{ value: 'light', label: 'Light' },
|
|
30
|
+
{ value: 'white', label: 'White' },
|
|
31
|
+
{ value: 'dark', label: 'Dark' },
|
|
32
|
+
],
|
|
33
|
+
default: 'light',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
export function Testimonials({ content, params }) {
|
|
4
|
+
const { title } = content.main?.header || {}
|
|
5
|
+
const { paragraphs = [] } = content.main?.body || {}
|
|
6
|
+
const { theme = 'light', columns = 3 } = params || {}
|
|
7
|
+
|
|
8
|
+
// Extract testimonials from subsections
|
|
9
|
+
const testimonials = content.subsections || []
|
|
10
|
+
|
|
11
|
+
const themeStyles = {
|
|
12
|
+
light: 'bg-white',
|
|
13
|
+
gray: 'bg-gray-50',
|
|
14
|
+
dark: 'bg-gray-900 text-white',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const cardStyles = {
|
|
18
|
+
light: 'bg-gray-50',
|
|
19
|
+
gray: 'bg-white shadow-sm',
|
|
20
|
+
dark: 'bg-gray-800',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const quoteStyles = {
|
|
24
|
+
light: 'text-gray-700',
|
|
25
|
+
gray: 'text-gray-700',
|
|
26
|
+
dark: 'text-gray-300',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const nameStyles = {
|
|
30
|
+
light: 'text-gray-900',
|
|
31
|
+
gray: 'text-gray-900',
|
|
32
|
+
dark: 'text-white',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const roleStyles = {
|
|
36
|
+
light: 'text-gray-500',
|
|
37
|
+
gray: 'text-gray-500',
|
|
38
|
+
dark: 'text-gray-400',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const gridCols = {
|
|
42
|
+
2: 'md:grid-cols-2',
|
|
43
|
+
3: 'md:grid-cols-3',
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<section className={`py-20 px-6 ${themeStyles[theme]}`}>
|
|
48
|
+
<div className="max-w-6xl mx-auto">
|
|
49
|
+
{(title || paragraphs[0]) && (
|
|
50
|
+
<div className="text-center mb-16">
|
|
51
|
+
{title && (
|
|
52
|
+
<h2 className="text-3xl md:text-4xl font-bold mb-4">{title}</h2>
|
|
53
|
+
)}
|
|
54
|
+
{paragraphs[0] && (
|
|
55
|
+
<p className={`text-lg max-w-2xl mx-auto ${roleStyles[theme]}`}>
|
|
56
|
+
{paragraphs[0]}
|
|
57
|
+
</p>
|
|
58
|
+
)}
|
|
59
|
+
</div>
|
|
60
|
+
)}
|
|
61
|
+
|
|
62
|
+
<div className={`grid gap-8 ${gridCols[columns] || 'md:grid-cols-3'}`}>
|
|
63
|
+
{testimonials.map((testimonial, index) => {
|
|
64
|
+
const name = testimonial.header?.title
|
|
65
|
+
const quote = testimonial.body?.paragraphs?.[0]
|
|
66
|
+
const role = testimonial.body?.paragraphs?.[1]
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div
|
|
70
|
+
key={index}
|
|
71
|
+
className={`p-6 rounded-xl ${cardStyles[theme]}`}
|
|
72
|
+
>
|
|
73
|
+
<svg className="w-8 h-8 text-primary/30 mb-4" fill="currentColor" viewBox="0 0 24 24">
|
|
74
|
+
<path d="M14.017 21v-7.391c0-5.704 3.731-9.57 8.983-10.609l.995 2.151c-2.432.917-3.995 3.638-3.995 5.849h4v10h-9.983zm-14.017 0v-7.391c0-5.704 3.748-9.57 9-10.609l.996 2.151c-2.433.917-3.996 3.638-3.996 5.849h3.983v10h-9.983z" />
|
|
75
|
+
</svg>
|
|
76
|
+
{quote && (
|
|
77
|
+
<p className={`text-lg mb-4 ${quoteStyles[theme]}`}>"{quote}"</p>
|
|
78
|
+
)}
|
|
79
|
+
<div>
|
|
80
|
+
{name && (
|
|
81
|
+
<p className={`font-semibold ${nameStyles[theme]}`}>{name}</p>
|
|
82
|
+
)}
|
|
83
|
+
{role && (
|
|
84
|
+
<p className={`text-sm ${roleStyles[theme]}`}>{role}</p>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
)
|
|
89
|
+
})}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</section>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export default Testimonials
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Testimonials Component Metadata
|
|
3
|
+
*/
|
|
4
|
+
export default {
|
|
5
|
+
title: 'Testimonials',
|
|
6
|
+
description: 'Display customer quotes and social proof',
|
|
7
|
+
category: 'Social Proof',
|
|
8
|
+
|
|
9
|
+
elements: {
|
|
10
|
+
title: {
|
|
11
|
+
label: 'Section Title',
|
|
12
|
+
description: 'From H2 in markdown',
|
|
13
|
+
},
|
|
14
|
+
paragraphs: {
|
|
15
|
+
label: 'Section Description',
|
|
16
|
+
},
|
|
17
|
+
subsections: {
|
|
18
|
+
label: 'Testimonials',
|
|
19
|
+
description: 'Each H3 (name) with paragraphs (quote, role) becomes a testimonial',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
properties: {
|
|
24
|
+
theme: {
|
|
25
|
+
type: 'select',
|
|
26
|
+
label: 'Theme',
|
|
27
|
+
options: [
|
|
28
|
+
{ value: 'light', label: 'Light' },
|
|
29
|
+
{ value: 'gray', label: 'Gray' },
|
|
30
|
+
{ value: 'dark', label: 'Dark' },
|
|
31
|
+
],
|
|
32
|
+
default: 'light',
|
|
33
|
+
},
|
|
34
|
+
columns: {
|
|
35
|
+
type: 'select',
|
|
36
|
+
label: 'Columns',
|
|
37
|
+
options: [
|
|
38
|
+
{ value: 2, label: '2 Columns' },
|
|
39
|
+
{ value: 3, label: '3 Columns' },
|
|
40
|
+
],
|
|
41
|
+
default: 3,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
}
|