@bailierich/booking-components 2.0.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.
Files changed (83) hide show
  1. package/README.md +319 -0
  2. package/TENANT_DATA_INTEGRATION.md +402 -0
  3. package/TENANT_SETUP.md +316 -0
  4. package/components/BookingFlow/BookingFlow.tsx +790 -0
  5. package/components/BookingFlow/index.ts +5 -0
  6. package/components/BookingFlow/steps/AddonsSelection.tsx +118 -0
  7. package/components/BookingFlow/steps/Confirmation.tsx +185 -0
  8. package/components/BookingFlow/steps/ContactForm.tsx +292 -0
  9. package/components/BookingFlow/steps/CycleAwareDateSelection.tsx +277 -0
  10. package/components/BookingFlow/steps/DateSelection.tsx +473 -0
  11. package/components/BookingFlow/steps/ServiceSelection.tsx +315 -0
  12. package/components/BookingFlow/steps/TimeSelection.tsx +230 -0
  13. package/components/BookingFlow/steps/index.ts +10 -0
  14. package/components/BottomSheet/index.tsx +120 -0
  15. package/components/Forms/FormBlock.tsx +283 -0
  16. package/components/Forms/FormField.tsx +385 -0
  17. package/components/Forms/FormRenderer.tsx +216 -0
  18. package/components/Forms/FormValidation.ts +122 -0
  19. package/components/Forms/index.ts +4 -0
  20. package/components/HoldTimer/HoldTimer.tsx +266 -0
  21. package/components/HoldTimer/index.ts +2 -0
  22. package/components/SectionRenderer.tsx +558 -0
  23. package/components/Sections/About.tsx +145 -0
  24. package/components/Sections/BeforeAfter.tsx +81 -0
  25. package/components/Sections/BookingSection.tsx +76 -0
  26. package/components/Sections/Contact.tsx +103 -0
  27. package/components/Sections/FAQSection.tsx +239 -0
  28. package/components/Sections/FeatureContent.tsx +113 -0
  29. package/components/Sections/FeaturedLink.tsx +103 -0
  30. package/components/Sections/FixedInfoCard.tsx +189 -0
  31. package/components/Sections/Gallery.tsx +83 -0
  32. package/components/Sections/Header.tsx +78 -0
  33. package/components/Sections/Hero.tsx +178 -0
  34. package/components/Sections/ImageSection.tsx +147 -0
  35. package/components/Sections/InstagramFeed.tsx +38 -0
  36. package/components/Sections/LinkList.tsx +76 -0
  37. package/components/Sections/LocationMap.tsx +202 -0
  38. package/components/Sections/Logo.tsx +61 -0
  39. package/components/Sections/MinimalFooter.tsx +78 -0
  40. package/components/Sections/MinimalHeader.tsx +81 -0
  41. package/components/Sections/MinimalNavigation.tsx +63 -0
  42. package/components/Sections/Navbar.tsx +258 -0
  43. package/components/Sections/PricingTable.tsx +106 -0
  44. package/components/Sections/ScrollingTextDivider.tsx +138 -0
  45. package/components/Sections/ScrollingTextDivider.tsx.bak +138 -0
  46. package/components/Sections/ServicesPreview.tsx +129 -0
  47. package/components/Sections/SocialBar.tsx +177 -0
  48. package/components/Sections/Team.tsx +80 -0
  49. package/components/Sections/Testimonials.tsx +92 -0
  50. package/components/Sections/TextSection.tsx +116 -0
  51. package/components/Sections/VideoSection.tsx +178 -0
  52. package/components/Sections/index.ts +57 -0
  53. package/components/index.ts +21 -0
  54. package/dist/index-DAai7Glf.d.mts +474 -0
  55. package/dist/index-DAai7Glf.d.ts +474 -0
  56. package/dist/index.d.mts +1075 -0
  57. package/dist/index.d.ts +1075 -0
  58. package/dist/index.js +22 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/index.mjs +22 -0
  61. package/dist/index.mjs.map +1 -0
  62. package/dist/styles/index.d.mts +1 -0
  63. package/dist/styles/index.d.ts +1 -0
  64. package/dist/styles/index.js +2 -0
  65. package/dist/styles/index.js.map +1 -0
  66. package/dist/styles/index.mjs +2 -0
  67. package/dist/styles/index.mjs.map +1 -0
  68. package/docs/API.md +849 -0
  69. package/docs/CALLBACKS.md +760 -0
  70. package/docs/COMPLETE_SESSION_SUMMARY.md +404 -0
  71. package/docs/DATA_SHAPES.md +684 -0
  72. package/docs/MIGRATION.md +662 -0
  73. package/docs/PAYMENT_INTEGRATION.md +766 -0
  74. package/docs/SESSION_SUMMARY.md +185 -0
  75. package/docs/STYLING.md +735 -0
  76. package/index.ts +4 -0
  77. package/lib/storage.ts +239 -0
  78. package/package.json +59 -0
  79. package/styles/animations.ts +210 -0
  80. package/styles/index.ts +1 -0
  81. package/tsconfig.json +32 -0
  82. package/tsup.config.ts +13 -0
  83. package/types/index.ts +369 -0
@@ -0,0 +1,129 @@
1
+ 'use client';
2
+
3
+ // Local interface (not exported to avoid conflicts)
4
+ interface ServicePreview {
5
+ id: number;
6
+ name: string;
7
+ description: string;
8
+ price: number;
9
+ duration: number;
10
+ }
11
+
12
+ export interface ServicesPreviewProps {
13
+ title?: string;
14
+ services?: ServicePreview[];
15
+ layout?: 'grid' | 'list' | 'carousel';
16
+ columns?: 2 | 3 | 4;
17
+ showPrices?: boolean;
18
+ showDuration?: boolean;
19
+ colors: {
20
+ primary: string;
21
+ text: string;
22
+ };
23
+ typography: {
24
+ headingFont?: string;
25
+ };
26
+ enableInlineEditing?: boolean;
27
+ sectionId?: string;
28
+ }
29
+
30
+ export function ServicesPreview({
31
+ title,
32
+ services = [],
33
+ layout = 'grid',
34
+ columns = 3,
35
+ showPrices = true,
36
+ showDuration = true,
37
+ colors,
38
+ typography,
39
+ enableInlineEditing = false,
40
+ sectionId = ''
41
+ }: ServicesPreviewProps) {
42
+ const gridColsMap = {
43
+ 2: 'grid-cols-1 md:grid-cols-2',
44
+ 3: 'grid-cols-1 md:grid-cols-3',
45
+ 4: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4'
46
+ };
47
+
48
+ const mockServices = services.length > 0 ? services : [
49
+ { id: 1, name: 'Haircut & Style', description: 'Professional cut and styling', price: 75, duration: 60 },
50
+ { id: 2, name: 'Color Treatment', description: 'Full color or highlights', price: 150, duration: 120 },
51
+ { id: 3, name: 'Deep Conditioning', description: 'Restorative treatment', price: 50, duration: 45 }
52
+ ];
53
+
54
+ const getLayoutClasses = () => {
55
+ switch (layout) {
56
+ case 'grid':
57
+ return `grid ${gridColsMap[columns]} gap-8`;
58
+ case 'list':
59
+ return 'flex flex-col gap-4 max-w-3xl mx-auto';
60
+ case 'carousel':
61
+ return 'flex overflow-x-auto snap-x gap-6 pb-4';
62
+ default:
63
+ return `grid ${gridColsMap[columns]} gap-8`;
64
+ }
65
+ };
66
+
67
+ return (
68
+ <section className="py-16 px-6">
69
+ <div className="max-w-7xl mx-auto">
70
+ <h2
71
+ className="text-3xl md:text-4xl font-bold text-center mb-12"
72
+ style={{ fontFamily: typography.headingFont, color: colors.text }}
73
+ {...(enableInlineEditing && {
74
+ 'data-editable': true,
75
+ 'data-section-id': sectionId,
76
+ 'data-field-path': 'settings.title'
77
+ })}
78
+ >
79
+ {title || 'Our Services'}
80
+ </h2>
81
+ <div className={getLayoutClasses()}>
82
+ {mockServices.map((service) => (
83
+ <div
84
+ key={service.id}
85
+ className={`p-6 rounded-lg border hover:scale-105 transition-transform shadow-lg ${
86
+ layout === 'carousel' ? 'snap-center flex-shrink-0 w-80' : ''
87
+ } ${
88
+ layout === 'list' ? 'flex items-center gap-6' : ''
89
+ }`}
90
+ style={{
91
+ backgroundColor: 'rgba(255, 255, 255, 0.95)',
92
+ borderColor: `${colors.primary}20`
93
+ }}
94
+ >
95
+ {layout === 'list' && (
96
+ <div className="flex-shrink-0 w-24 h-24 rounded-lg" style={{ backgroundColor: colors.primary, opacity: 0.2 }} />
97
+ )}
98
+ <div className="flex-1">
99
+ <h3
100
+ className="text-xl font-semibold mb-3"
101
+ style={{ color: '#1a1a1a' }}
102
+ >
103
+ {service.name}
104
+ </h3>
105
+ <p className="text-sm opacity-80 mb-4" style={{ color: '#333333' }}>
106
+ {service.description}
107
+ </p>
108
+ <div className={`flex ${layout === 'list' ? 'gap-4' : 'justify-between'} items-center`}>
109
+ {showPrices && (
110
+ <p className="font-bold text-lg" style={{ color: colors.primary }}>
111
+ ${service.price}
112
+ </p>
113
+ )}
114
+ {showDuration && (
115
+ <p className="text-sm opacity-70" style={{ color: '#666666' }}>
116
+ {service.duration} min
117
+ </p>
118
+ )}
119
+ </div>
120
+ </div>
121
+ </div>
122
+ ))}
123
+ </div>
124
+ </div>
125
+ </section>
126
+ );
127
+ }
128
+
129
+ export default ServicesPreview;
@@ -0,0 +1,177 @@
1
+ 'use client';
2
+
3
+ import { Instagram, Facebook, Music, Twitter, Youtube, PinIcon as Pinterest } from 'lucide-react';
4
+
5
+ export interface SocialBarProps {
6
+ platforms: string[];
7
+ socialLinks?: Record<string, string>;
8
+ style?: 'filled' | 'outline' | 'ghost';
9
+ size?: 'small' | 'medium' | 'large';
10
+ position?: 'center' | 'left-vertical' | 'right-vertical';
11
+ invertIcons?: boolean;
12
+ colors: {
13
+ primary: string;
14
+ };
15
+ enableInlineEditing?: boolean;
16
+ sectionId?: string;
17
+ }
18
+
19
+ export function SocialBar({
20
+ platforms,
21
+ socialLinks = {},
22
+ style = 'filled',
23
+ size = 'large',
24
+ position = 'center',
25
+ invertIcons = false,
26
+ colors,
27
+ enableInlineEditing = false,
28
+ sectionId = ''
29
+ }: SocialBarProps) {
30
+ const getContrastColor = (hexColor: string) => {
31
+ const hex = hexColor.replace('#', '');
32
+ const r = parseInt(hex.substr(0, 2), 16);
33
+ const g = parseInt(hex.substr(2, 2), 16);
34
+ const b = parseInt(hex.substr(4, 2), 16);
35
+ const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
36
+ return luminance > 0.5 ? '#000000' : '#FFFFFF';
37
+ };
38
+
39
+ const getSocialIcon = (platform: string) => {
40
+ const iconSize = size === 'small' ? 20 : size === 'medium' ? 24 : 28;
41
+
42
+ switch (platform) {
43
+ case 'instagram':
44
+ return <Instagram size={iconSize} />;
45
+ case 'facebook':
46
+ return <Facebook size={iconSize} />;
47
+ case 'tiktok':
48
+ return <Music size={iconSize} />;
49
+ case 'twitter':
50
+ return <Twitter size={iconSize} />;
51
+ case 'youtube':
52
+ return <Youtube size={iconSize} />;
53
+ case 'pinterest':
54
+ return <Pinterest size={iconSize} />;
55
+ default:
56
+ return <Instagram size={iconSize} />;
57
+ }
58
+ };
59
+
60
+ const getSocialClassName = () => {
61
+ let sizeClasses = '';
62
+ switch (size) {
63
+ case 'small':
64
+ sizeClasses = 'w-10 h-10 text-lg';
65
+ break;
66
+ case 'medium':
67
+ sizeClasses = 'w-12 h-12 text-xl';
68
+ break;
69
+ case 'large':
70
+ sizeClasses = 'w-14 h-14 text-2xl';
71
+ break;
72
+ default:
73
+ sizeClasses = 'w-14 h-14 text-2xl';
74
+ }
75
+
76
+ let styleClasses = '';
77
+ switch (style) {
78
+ case 'filled':
79
+ styleClasses = 'rounded-full';
80
+ break;
81
+ case 'outline':
82
+ styleClasses = 'rounded-full border-2';
83
+ break;
84
+ case 'ghost':
85
+ styleClasses = 'rounded-full';
86
+ break;
87
+ default:
88
+ styleClasses = 'rounded-full';
89
+ }
90
+
91
+ return `${sizeClasses} ${styleClasses} flex items-center justify-center hover:scale-110 transition-transform`;
92
+ };
93
+
94
+ const getSocialStyle = () => {
95
+ switch (style) {
96
+ case 'filled':
97
+ return {
98
+ backgroundColor: colors.primary,
99
+ color: getContrastColor(colors.primary || '#BCB4FF')
100
+ };
101
+ case 'outline':
102
+ return {
103
+ backgroundColor: 'transparent',
104
+ borderColor: colors.primary,
105
+ color: colors.primary
106
+ };
107
+ case 'ghost':
108
+ return {
109
+ backgroundColor: 'rgba(0,0,0,0.05)',
110
+ color: colors.primary
111
+ };
112
+ default:
113
+ return {
114
+ backgroundColor: colors.primary,
115
+ color: getContrastColor(colors.primary || '#BCB4FF')
116
+ };
117
+ }
118
+ };
119
+
120
+ if (position === 'left-vertical' || position === 'right-vertical') {
121
+ return (
122
+ <div
123
+ className={`fixed top-6 z-40 flex flex-col gap-3 ${
124
+ position === 'left-vertical' ? 'left-4' : 'right-4'
125
+ }`}
126
+ >
127
+ {platforms.map((platform) => {
128
+ const socialUrl = socialLinks[platform] || `https://${platform}.com/`;
129
+ return (
130
+ <a
131
+ key={platform}
132
+ href={socialUrl}
133
+ target="_blank"
134
+ rel="noopener noreferrer"
135
+ className={getSocialClassName()}
136
+ style={{
137
+ ...getSocialStyle(),
138
+ filter: invertIcons ? 'invert(1)' : 'none'
139
+ }}
140
+ title={platform.charAt(0).toUpperCase() + platform.slice(1)}
141
+ >
142
+ {getSocialIcon(platform)}
143
+ </a>
144
+ );
145
+ })}
146
+ </div>
147
+ );
148
+ }
149
+
150
+ return (
151
+ <div className="container mx-auto px-4 py-8 text-center">
152
+ <div className="flex items-center justify-center gap-4">
153
+ {platforms.map((platform) => {
154
+ const socialUrl = socialLinks[platform] || `https://${platform}.com/`;
155
+ return (
156
+ <a
157
+ key={platform}
158
+ href={socialUrl}
159
+ target="_blank"
160
+ rel="noopener noreferrer"
161
+ className={getSocialClassName()}
162
+ style={{
163
+ ...getSocialStyle(),
164
+ filter: invertIcons ? 'invert(1)' : 'none'
165
+ }}
166
+ title={platform.charAt(0).toUpperCase() + platform.slice(1)}
167
+ >
168
+ {getSocialIcon(platform)}
169
+ </a>
170
+ );
171
+ })}
172
+ </div>
173
+ </div>
174
+ );
175
+ }
176
+
177
+ export default SocialBar;
@@ -0,0 +1,80 @@
1
+ 'use client';
2
+
3
+ export interface TeamMember {
4
+ id: string | number;
5
+ name: string;
6
+ role: string;
7
+ bio: string;
8
+ image?: string;
9
+ }
10
+
11
+ export interface TeamProps {
12
+ title?: string;
13
+ members?: TeamMember[];
14
+ colors: {
15
+ primary: string;
16
+ text: string;
17
+ };
18
+ typography: {
19
+ headingFont?: string;
20
+ };
21
+ enableInlineEditing?: boolean;
22
+ sectionId?: string;
23
+ }
24
+
25
+ export function Team({
26
+ title,
27
+ members = [],
28
+ colors,
29
+ typography,
30
+ enableInlineEditing = false,
31
+ sectionId = ''
32
+ }: TeamProps) {
33
+ const mockTeam = members.length > 0 ? members : [
34
+ { id: 1, name: 'Sarah Johnson', role: 'Master Stylist', bio: 'Expert in color and styling with 10+ years experience' },
35
+ { id: 2, name: 'Michael Chen', role: 'Senior Stylist', bio: 'Specializing in precision cuts and modern styles' },
36
+ { id: 3, name: 'Emma Davis', role: 'Stylist', bio: 'Passionate about creating beautiful transformations' }
37
+ ];
38
+
39
+ return (
40
+ <section className="py-16 px-6">
41
+ <div className="max-w-7xl mx-auto">
42
+ <h2
43
+ className="text-3xl md:text-4xl font-bold text-center mb-12"
44
+ style={{ fontFamily: typography.headingFont, color: colors.text }}
45
+ {...(enableInlineEditing && {
46
+ 'data-editable': true,
47
+ 'data-section-id': sectionId,
48
+ 'data-field-path': 'settings.title'
49
+ })}
50
+ >
51
+ {title || 'Meet Our Team'}
52
+ </h2>
53
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
54
+ {mockTeam.map((member) => (
55
+ <div key={member.id} className="text-center">
56
+ <div
57
+ className="w-32 h-32 rounded-full mx-auto mb-4"
58
+ style={{ backgroundColor: colors.primary, opacity: 0.2 }}
59
+ />
60
+ <h3
61
+ className="text-xl font-semibold mb-2"
62
+ style={{ color: colors.text }}
63
+ >
64
+ {member.name}
65
+ </h3>
66
+ <p className="text-sm mb-3" style={{ color: colors.primary }}>
67
+ {member.role}
68
+ </p>
69
+ <p className="text-sm opacity-80" style={{ color: colors.text }}>
70
+ {member.bio}
71
+ </p>
72
+ </div>
73
+ ))}
74
+ </div>
75
+ </div>
76
+ </section>
77
+ );
78
+ }
79
+
80
+ export default Team;
@@ -0,0 +1,92 @@
1
+ 'use client';
2
+
3
+ export interface Testimonial {
4
+ id: string | number;
5
+ text: string;
6
+ author: string;
7
+ rating: number;
8
+ }
9
+
10
+ export interface TestimonialsProps {
11
+ title?: string;
12
+ testimonials?: Testimonial[];
13
+ layout?: 'grid' | 'carousel';
14
+ colors: {
15
+ primary: string;
16
+ text: string;
17
+ };
18
+ typography: {
19
+ headingFont?: string;
20
+ };
21
+ enableInlineEditing?: boolean;
22
+ sectionId?: string;
23
+ }
24
+
25
+ export function Testimonials({
26
+ title,
27
+ testimonials = [],
28
+ layout = 'grid',
29
+ colors,
30
+ typography,
31
+ enableInlineEditing = false,
32
+ sectionId = ''
33
+ }: TestimonialsProps) {
34
+ const layoutClass = layout === 'carousel'
35
+ ? 'flex overflow-x-auto snap-x gap-6 pb-4'
36
+ : 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6';
37
+
38
+ const mockTestimonials = testimonials.length > 0 ? testimonials : [
39
+ { id: 1, text: 'Amazing service! I love my new look.', author: 'Sarah M.', rating: 5 },
40
+ { id: 2, text: 'Professional and friendly staff.', author: 'Jessica L.', rating: 5 },
41
+ { id: 3, text: 'Best salon in town!', author: 'Emily R.', rating: 5 }
42
+ ];
43
+
44
+ return (
45
+ <section className="py-16 px-6">
46
+ <div className="max-w-7xl mx-auto">
47
+ <h2
48
+ className="text-3xl md:text-4xl font-bold text-center mb-12"
49
+ style={{ fontFamily: typography.headingFont, color: colors.text }}
50
+ {...(enableInlineEditing && {
51
+ 'data-editable': true,
52
+ 'data-section-id': sectionId,
53
+ 'data-field-path': 'settings.title'
54
+ })}
55
+ >
56
+ {title || 'What Our Clients Say'}
57
+ </h2>
58
+ <div className={layoutClass}>
59
+ {mockTestimonials.map((testimonial) => (
60
+ <div
61
+ key={testimonial.id}
62
+ className={`p-6 rounded-lg bg-white/10 backdrop-blur-sm ${
63
+ layout === 'carousel' ? 'snap-center flex-shrink-0 w-80' : ''
64
+ }`}
65
+ >
66
+ <div className="flex gap-1 mb-4">
67
+ {[...Array(5)].map((_, i) => (
68
+ <svg
69
+ key={i}
70
+ className="w-5 h-5"
71
+ fill={i < testimonial.rating ? (colors.primary || '#8B5CF6') : '#E5E7EB'}
72
+ viewBox="0 0 20 20"
73
+ >
74
+ <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
75
+ </svg>
76
+ ))}
77
+ </div>
78
+ <p className="mb-4 italic" style={{ color: colors.text }}>
79
+ "{testimonial.text}"
80
+ </p>
81
+ <p className="font-semibold" style={{ color: colors.text }}>
82
+ {testimonial.author}
83
+ </p>
84
+ </div>
85
+ ))}
86
+ </div>
87
+ </div>
88
+ </section>
89
+ );
90
+ }
91
+
92
+ export default Testimonials;
@@ -0,0 +1,116 @@
1
+ 'use client';
2
+
3
+ export interface TextSectionProps {
4
+ title?: string;
5
+ content: string;
6
+ alignment?: 'left' | 'center' | 'right';
7
+ maxWidth?: 'full' | 'large' | 'medium' | 'small';
8
+ textSize?: 'small' | 'medium' | 'large';
9
+ backgroundColor?: string;
10
+ textColor?: string;
11
+ paddingY?: 'none' | 'small' | 'medium' | 'large';
12
+ colors: {
13
+ primary: string;
14
+ text: string;
15
+ };
16
+ typography?: {
17
+ headingFont?: string;
18
+ bodyFont?: string;
19
+ };
20
+ enableInlineEditing?: boolean;
21
+ sectionId?: string;
22
+ }
23
+
24
+ export function TextSection({
25
+ title,
26
+ content,
27
+ alignment = 'left',
28
+ maxWidth = 'medium',
29
+ textSize = 'medium',
30
+ backgroundColor,
31
+ textColor,
32
+ paddingY = 'medium',
33
+ colors,
34
+ typography,
35
+ enableInlineEditing = false,
36
+ sectionId = ''
37
+ }: TextSectionProps) {
38
+ // Max width classes
39
+ const maxWidthMap = {
40
+ full: 'max-w-full',
41
+ large: 'max-w-6xl',
42
+ medium: 'max-w-4xl',
43
+ small: 'max-w-2xl'
44
+ };
45
+
46
+ // Text alignment
47
+ const alignmentMap = {
48
+ left: 'text-left',
49
+ center: 'text-center',
50
+ right: 'text-right'
51
+ };
52
+
53
+ // Text sizes
54
+ const textSizeMap = {
55
+ small: 'text-sm',
56
+ medium: 'text-base',
57
+ large: 'text-lg'
58
+ };
59
+
60
+ // Padding
61
+ const paddingMap = {
62
+ none: 'py-0',
63
+ small: 'py-8',
64
+ medium: 'py-16',
65
+ large: 'py-24'
66
+ };
67
+
68
+ return (
69
+ <div
70
+ className={paddingMap[paddingY]}
71
+ style={{
72
+ backgroundColor: backgroundColor || 'transparent'
73
+ }}
74
+ >
75
+ <div className="container mx-auto px-4">
76
+ <div className={`${maxWidthMap[maxWidth]} mx-auto ${alignmentMap[alignment]}`}>
77
+ {/* Title */}
78
+ {title && (
79
+ <h2
80
+ className="text-3xl md:text-4xl font-bold mb-6"
81
+ style={{
82
+ color: textColor || colors.text,
83
+ fontFamily: typography?.headingFont || 'inherit'
84
+ }}
85
+ {...(enableInlineEditing && {
86
+ 'data-editable': true,
87
+ 'data-section-id': sectionId,
88
+ 'data-field-path': 'settings.title'
89
+ })}
90
+ >
91
+ {title}
92
+ </h2>
93
+ )}
94
+
95
+ {/* Content */}
96
+ <div
97
+ className={`${textSizeMap[textSize]} leading-relaxed whitespace-pre-wrap`}
98
+ style={{
99
+ color: textColor || colors.text,
100
+ fontFamily: typography?.bodyFont || 'inherit'
101
+ }}
102
+ {...(enableInlineEditing && {
103
+ 'data-editable': true,
104
+ 'data-section-id': sectionId,
105
+ 'data-field-path': 'settings.content'
106
+ })}
107
+ >
108
+ {content || 'Add your text content here...'}
109
+ </div>
110
+ </div>
111
+ </div>
112
+ </div>
113
+ );
114
+ }
115
+
116
+ export default TextSection;