@djangocfg/ui-nextjs 1.4.45

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 (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +152 -0
  3. package/package.json +110 -0
  4. package/src/animations/AnimatedBackground.tsx +645 -0
  5. package/src/animations/index.ts +2 -0
  6. package/src/blocks/ArticleCard.tsx +94 -0
  7. package/src/blocks/ArticleList.tsx +95 -0
  8. package/src/blocks/CTASection.tsx +136 -0
  9. package/src/blocks/FeatureSection.tsx +104 -0
  10. package/src/blocks/Hero.tsx +102 -0
  11. package/src/blocks/NewsletterSection.tsx +119 -0
  12. package/src/blocks/StatsSection.tsx +103 -0
  13. package/src/blocks/SuperHero.tsx +328 -0
  14. package/src/blocks/TestimonialSection.tsx +122 -0
  15. package/src/blocks/index.ts +9 -0
  16. package/src/components/README.md +2018 -0
  17. package/src/components/breadcrumb-navigation.tsx +127 -0
  18. package/src/components/breadcrumb.tsx +132 -0
  19. package/src/components/button-download.tsx +275 -0
  20. package/src/components/dropdown-menu.tsx +219 -0
  21. package/src/components/index.ts +86 -0
  22. package/src/components/markdown/MarkdownMessage.tsx +338 -0
  23. package/src/components/markdown/index.ts +5 -0
  24. package/src/components/menubar.tsx +274 -0
  25. package/src/components/multi-select-pro/async.tsx +608 -0
  26. package/src/components/multi-select-pro/helpers.tsx +84 -0
  27. package/src/components/multi-select-pro/index.tsx +622 -0
  28. package/src/components/navigation-menu.tsx +153 -0
  29. package/src/components/pagination-static.tsx +348 -0
  30. package/src/components/pagination.tsx +138 -0
  31. package/src/components/phone-input.tsx +276 -0
  32. package/src/components/sidebar.tsx +866 -0
  33. package/src/components/sonner.tsx +31 -0
  34. package/src/components/ssr-pagination.tsx +237 -0
  35. package/src/hooks/index.ts +19 -0
  36. package/src/hooks/useCfgRouter.ts +153 -0
  37. package/src/hooks/useLocalStorage.ts +221 -0
  38. package/src/hooks/useQueryParams.ts +73 -0
  39. package/src/hooks/useSessionStorage.ts +188 -0
  40. package/src/hooks/useTheme.ts +57 -0
  41. package/src/index.ts +24 -0
  42. package/src/lib/index.ts +2 -0
  43. package/src/styles/index.css +2 -0
  44. package/src/theme/ForceTheme.tsx +115 -0
  45. package/src/theme/ThemeProvider.tsx +82 -0
  46. package/src/theme/ThemeToggle.tsx +52 -0
  47. package/src/theme/index.ts +3 -0
  48. package/src/tools/JsonForm/JsonSchemaForm.tsx +199 -0
  49. package/src/tools/JsonForm/examples/BotConfigExample.tsx +245 -0
  50. package/src/tools/JsonForm/examples/RealBotConfigExample.tsx +157 -0
  51. package/src/tools/JsonForm/index.ts +46 -0
  52. package/src/tools/JsonForm/templates/ArrayFieldItemTemplate.tsx +46 -0
  53. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +73 -0
  54. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +106 -0
  55. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +34 -0
  56. package/src/tools/JsonForm/templates/FieldTemplate.tsx +61 -0
  57. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +43 -0
  58. package/src/tools/JsonForm/templates/index.ts +12 -0
  59. package/src/tools/JsonForm/types.ts +83 -0
  60. package/src/tools/JsonForm/utils.ts +212 -0
  61. package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +36 -0
  62. package/src/tools/JsonForm/widgets/NumberWidget.tsx +88 -0
  63. package/src/tools/JsonForm/widgets/SelectWidget.tsx +100 -0
  64. package/src/tools/JsonForm/widgets/SwitchWidget.tsx +34 -0
  65. package/src/tools/JsonForm/widgets/TextWidget.tsx +95 -0
  66. package/src/tools/JsonForm/widgets/index.ts +12 -0
  67. package/src/tools/JsonTree/index.tsx +252 -0
  68. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +212 -0
  69. package/src/tools/LottiePlayer/index.tsx +54 -0
  70. package/src/tools/LottiePlayer/types.ts +108 -0
  71. package/src/tools/LottiePlayer/useLottie.ts +163 -0
  72. package/src/tools/Mermaid/Mermaid.client.tsx +341 -0
  73. package/src/tools/Mermaid/index.tsx +40 -0
  74. package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +144 -0
  75. package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +255 -0
  76. package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +123 -0
  77. package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +98 -0
  78. package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +164 -0
  79. package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +253 -0
  80. package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +169 -0
  81. package/src/tools/OpenapiViewer/components/VersionSelector.tsx +64 -0
  82. package/src/tools/OpenapiViewer/components/index.ts +14 -0
  83. package/src/tools/OpenapiViewer/constants.ts +39 -0
  84. package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +338 -0
  85. package/src/tools/OpenapiViewer/hooks/index.ts +8 -0
  86. package/src/tools/OpenapiViewer/hooks/useMobile.ts +10 -0
  87. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +203 -0
  88. package/src/tools/OpenapiViewer/index.tsx +36 -0
  89. package/src/tools/OpenapiViewer/types.ts +152 -0
  90. package/src/tools/OpenapiViewer/utils/apiKeyManager.ts +149 -0
  91. package/src/tools/OpenapiViewer/utils/formatters.ts +71 -0
  92. package/src/tools/OpenapiViewer/utils/index.ts +9 -0
  93. package/src/tools/OpenapiViewer/utils/versionManager.ts +161 -0
  94. package/src/tools/PrettyCode/PrettyCode.client.tsx +217 -0
  95. package/src/tools/PrettyCode/index.tsx +43 -0
  96. package/src/tools/VideoPlayer/README.md +239 -0
  97. package/src/tools/VideoPlayer/VideoControls.tsx +138 -0
  98. package/src/tools/VideoPlayer/VideoPlayer.tsx +230 -0
  99. package/src/tools/VideoPlayer/index.ts +9 -0
  100. package/src/tools/VideoPlayer/types.ts +62 -0
  101. package/src/tools/index.ts +43 -0
@@ -0,0 +1,103 @@
1
+ import React from 'react';
2
+
3
+ import { Card, CardContent } from '@djangocfg/ui-core/components';
4
+ import { cn } from '@djangocfg/ui-core/lib';
5
+
6
+ interface Stat {
7
+ number: string;
8
+ label: string;
9
+ description?: string;
10
+ icon?: React.ReactNode;
11
+ }
12
+
13
+ interface StatsSectionProps {
14
+ title: string;
15
+ subtitle?: string;
16
+ stats: Stat[];
17
+ columns?: 2 | 3 | 4;
18
+ className?: string;
19
+ background?: 'dark' | 'card' | 'gradient';
20
+ }
21
+
22
+ export const StatsSection: React.FC<StatsSectionProps> = ({
23
+ title,
24
+ subtitle,
25
+ stats,
26
+ columns = 4,
27
+ className,
28
+ background = 'dark'
29
+ }) => {
30
+ const getGridClasses = () => {
31
+ switch (columns) {
32
+ case 2:
33
+ return 'grid-cols-1 sm:grid-cols-2';
34
+ case 3:
35
+ return 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3';
36
+ case 4:
37
+ return 'grid-cols-2 sm:grid-cols-2 lg:grid-cols-4';
38
+ default:
39
+ return 'grid-cols-2 sm:grid-cols-2 lg:grid-cols-4';
40
+ }
41
+ };
42
+
43
+ const getBackgroundClasses = () => {
44
+ switch (background) {
45
+ case 'dark':
46
+ return 'bg-background';
47
+ case 'card':
48
+ return 'gradient-card';
49
+ case 'gradient':
50
+ return 'gradient-subtle';
51
+ default:
52
+ return 'bg-background';
53
+ }
54
+ };
55
+
56
+ return (
57
+ <section className={cn('py-12 sm:py-16 lg:py-24', getBackgroundClasses(), className)}>
58
+ <div className="w-full px-4 sm:px-6 lg:px-8">
59
+ <div className="text-center mb-12 sm:mb-16 animate-fade-in">
60
+ <h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold text-foreground mb-4 sm:mb-6">
61
+ {title}
62
+ </h2>
63
+ {subtitle && (
64
+ <p className="text-base sm:text-lg md:text-xl text-muted-foreground max-w-3xl mx-auto px-2">
65
+ {subtitle}
66
+ </p>
67
+ )}
68
+ </div>
69
+
70
+ <div className={cn('grid gap-6 sm:gap-8', getGridClasses())}>
71
+ {stats.map((stat, index) => (
72
+ <Card
73
+ key={index}
74
+ className="text-center hover:shadow-large transition-all duration-300 animate-scale-in backdrop-blur-sm border-border/50 hover:border-primary/30 group"
75
+ style={{ animationDelay: `${index * 0.1}s` }}
76
+ >
77
+ <CardContent className="p-6 sm:p-8">
78
+ {stat.icon && (
79
+ <div className="w-12 h-12 sm:w-16 sm:h-16 bg-primary/10 text-primary rounded-lg flex items-center justify-center mx-auto mb-4 sm:mb-6 group-hover:scale-110 group-hover:bg-primary/20 transition-all duration-300">
80
+ <div className="text-xl sm:text-2xl">
81
+ {stat.icon}
82
+ </div>
83
+ </div>
84
+ )}
85
+ <div className="text-3xl sm:text-4xl lg:text-5xl font-bold text-foreground mb-2 sm:mb-3 group-hover:text-primary transition-colors duration-300">
86
+ {stat.number}
87
+ </div>
88
+ <div className="text-sm sm:text-base text-muted-foreground font-medium">
89
+ {stat.label}
90
+ </div>
91
+ {stat.description && (
92
+ <p className="text-xs sm:text-sm text-muted-foreground mt-2">
93
+ {stat.description}
94
+ </p>
95
+ )}
96
+ </CardContent>
97
+ </Card>
98
+ ))}
99
+ </div>
100
+ </div>
101
+ </section>
102
+ );
103
+ };
@@ -0,0 +1,328 @@
1
+ "use client"
2
+
3
+ import { ArrowRight, Sparkles, Wand2, Copy, Check } from 'lucide-react';
4
+ import React from 'react';
5
+
6
+ import { cn } from '@djangocfg/ui-core/lib';
7
+ import { Button, ButtonLink, Sticky, Tooltip, TooltipTrigger, TooltipContent } from '@djangocfg/ui-core/components';
8
+ import { useCopy } from '@djangocfg/ui-core/hooks';
9
+ import { AnimatedBackground, type BackgroundVariant } from '../animations';
10
+ import { ForceTheme } from '../theme';
11
+
12
+ interface HeroFeature {
13
+ icon: React.ReactNode;
14
+ text: string;
15
+ }
16
+
17
+ interface StatItem {
18
+ number: string;
19
+ label: string;
20
+ icon?: React.ReactNode;
21
+ }
22
+
23
+ interface SuperHeroProps {
24
+ badge?: {
25
+ icon?: React.ReactNode;
26
+ text: string;
27
+ };
28
+ title: string;
29
+ titleGradient?: string;
30
+ subtitle: string;
31
+ features?: HeroFeature[];
32
+ primaryAction?: {
33
+ label: string;
34
+ href?: string;
35
+ onClick?: () => void;
36
+ };
37
+ secondaryAction?: {
38
+ label: string;
39
+ href?: string;
40
+ onClick?: () => void;
41
+ icon?: React.ReactNode;
42
+ };
43
+ stats?: StatItem[];
44
+ floatingElements?: React.ReactNode;
45
+ backgroundVariant?: BackgroundVariant;
46
+ backgroundIntensity?: 'subtle' | 'medium' | 'strong';
47
+ showBackgroundSwitcher?: boolean;
48
+ codeCommand?: string;
49
+ className?: string;
50
+ }
51
+
52
+ const BACKGROUND_VARIANTS: BackgroundVariant[] = [
53
+ 'aurora-borealis',
54
+ 'mesh-gradient',
55
+ 'floating-orbs',
56
+ 'geometric-flow',
57
+ 'liquid-gradient',
58
+ 'spotlight',
59
+ 'none'
60
+ ];
61
+
62
+ const VARIANT_LABELS: Partial<Record<BackgroundVariant, string>> = {
63
+ 'aurora-borealis': 'Aurora Borealis',
64
+ 'mesh-gradient': 'Mesh Gradient',
65
+ 'floating-orbs': 'Floating Orbs',
66
+ 'geometric-flow': 'Geometric Flow',
67
+ 'liquid-gradient': 'Liquid Gradient',
68
+ 'spotlight': 'Spotlight',
69
+ 'none': 'None',
70
+ // Legacy labels for backwards compatibility
71
+ 'gradient-mesh': 'Gradient Mesh',
72
+ 'dot-matrix': 'Dot Matrix',
73
+ 'grid-lines': 'Grid Lines',
74
+ 'aurora': 'Aurora',
75
+ 'particles': 'Particles',
76
+ 'waves': 'Waves',
77
+ };
78
+
79
+ export const SuperHero: React.FC<SuperHeroProps> = ({
80
+ badge,
81
+ title,
82
+ titleGradient,
83
+ subtitle,
84
+ features = [],
85
+ primaryAction,
86
+ secondaryAction,
87
+ stats = [],
88
+ floatingElements,
89
+ backgroundVariant = 'mesh-gradient',
90
+ backgroundIntensity = 'medium',
91
+ showBackgroundSwitcher = false,
92
+ codeCommand,
93
+ className
94
+ }) => {
95
+ const [currentVariant, setCurrentVariant] = React.useState<BackgroundVariant>(backgroundVariant);
96
+ const [isMenuOpen, setIsMenuOpen] = React.useState(false);
97
+ const [heroTheme, setHeroTheme] = React.useState<'light' | 'dark'>('dark');
98
+ const { copyToClipboard } = useCopy();
99
+
100
+ // Show background switcher in development mode or if explicitly enabled
101
+ const isDevelopment = process.env.NODE_ENV === 'development';
102
+ const shouldShowSwitcher = showBackgroundSwitcher || isDevelopment;
103
+
104
+ const cycleBackground = () => {
105
+ const currentIndex = BACKGROUND_VARIANTS.indexOf(currentVariant);
106
+ const nextIndex = (currentIndex + 1) % BACKGROUND_VARIANTS.length;
107
+ setCurrentVariant(BACKGROUND_VARIANTS[nextIndex]);
108
+ };
109
+
110
+ return (
111
+ <ForceTheme theme={heroTheme}>
112
+ <section
113
+ className={cn(
114
+ "relative overflow-hidden bg-background text-foreground z-0 isolate",
115
+ className
116
+ )}
117
+ >
118
+ {/* Animated Background */}
119
+ <div className="absolute inset-0 -z-10">
120
+ <AnimatedBackground variant={currentVariant} intensity={backgroundIntensity} />
121
+ </div>
122
+
123
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-20 sm:py-24 lg:py-32 relative z-10">
124
+ <div className="text-center">
125
+ {/* Enhanced Badge */}
126
+ {badge && (
127
+ <div className="inline-flex items-center gap-2 px-4 py-2 bg-primary/10 text-primary rounded-full text-sm font-medium mb-6 border border-primary/20 hover:bg-primary/20 transition-all duration-300">
128
+ {badge.icon || <Sparkles className="w-4 h-4" />}
129
+ {badge.text}
130
+ </div>
131
+ )}
132
+
133
+ {/* Enhanced Main Title */}
134
+ <h1 className="text-4xl sm:text-5xl lg:text-6xl xl:text-7xl font-bold text-foreground mb-6 leading-tight">
135
+ {title.split('\n').map((line, index) => (
136
+ <React.Fragment key={index}>
137
+ {line}
138
+ {index < title.split('\n').length - 1 && <br />}
139
+ </React.Fragment>
140
+ ))}
141
+ {titleGradient && (
142
+ <>
143
+ <br />
144
+ <span className="bg-gradient-to-r from-primary via-primary/80 to-secondary bg-clip-text text-transparent animate-pulse">
145
+ {titleGradient}
146
+ </span>
147
+ </>
148
+ )}
149
+ </h1>
150
+
151
+ {/* Enhanced Subtitle */}
152
+ <p className="text-xl sm:text-2xl lg:text-3xl text-muted-foreground mb-8 max-w-4xl mx-auto leading-relaxed">
153
+ {subtitle}
154
+ </p>
155
+
156
+ {/* Code Command */}
157
+ {codeCommand && (
158
+ <div className="flex justify-center mb-8">
159
+ <div className="inline-flex items-center gap-3 px-6 py-3 bg-background/50 backdrop-blur-md border border-primary/20 rounded-xl shadow-lg hover:border-primary/40 transition-colors group cursor-pointer"
160
+ onClick={() => copyToClipboard(codeCommand, 'Command copied!')}>
161
+ <code className="font-mono text-lg text-primary font-semibold">
162
+ {codeCommand}
163
+ </code>
164
+ <div className="p-1.5 rounded-md bg-primary/10 text-primary group-hover:bg-primary group-hover:text-primary-foreground transition-colors">
165
+ <Copy className="w-4 h-4" />
166
+ </div>
167
+ </div>
168
+ </div>
169
+ )}
170
+
171
+ {/* Enhanced Features */}
172
+ {features.length > 0 && (
173
+ <div className="flex flex-wrap justify-center gap-6 mb-8">
174
+ {features.map((feature, index) => (
175
+ <div
176
+ key={index}
177
+ className="flex items-center gap-2 text-sm text-muted-foreground bg-card/50 px-4 py-2 rounded-full border border-border/50 hover:border-primary/30 hover:bg-card transition-all duration-300"
178
+ >
179
+ {feature.icon}
180
+ <span>{feature.text}</span>
181
+ </div>
182
+ ))}
183
+ </div>
184
+ )}
185
+
186
+ {/* Enhanced CTA Buttons */}
187
+ {(primaryAction || secondaryAction) && (
188
+ <div className="flex flex-col sm:flex-row gap-4 justify-center mb-12">
189
+ {primaryAction && (
190
+ <ButtonLink
191
+ href={primaryAction.href || '#'}
192
+ onClick={primaryAction.onClick}
193
+ size="huge"
194
+ className="group"
195
+ >
196
+ {primaryAction.label}
197
+ <ArrowRight className="group-hover:translate-x-1 transition-transform duration-300" />
198
+ </ButtonLink>
199
+ )}
200
+ {secondaryAction && (
201
+ <ButtonLink
202
+ href={secondaryAction.href || '#'}
203
+ onClick={secondaryAction.onClick}
204
+ variant="outline"
205
+ size="huge"
206
+ className="group"
207
+ >
208
+ {secondaryAction.label}
209
+ {secondaryAction.icon && (
210
+ <span className="group-hover:rotate-12 transition-transform duration-300">
211
+ {secondaryAction.icon}
212
+ </span>
213
+ )}
214
+ </ButtonLink>
215
+ )}
216
+ </div>
217
+ )}
218
+
219
+ {/* Enhanced Stats Preview */}
220
+ {stats.length > 0 && (
221
+ <div className="flex flex-wrap justify-center gap-x-6 gap-y-3 md:gap-x-8 md:gap-y-4 max-w-3xl mx-auto">
222
+ {stats.map((stat, index) => (
223
+ <div
224
+ key={index}
225
+ className="text-center min-w-[80px] md:min-w-[100px] px-4 py-2 md:px-5 md:py-3 rounded-lg border border-foreground/8 bg-background/10 backdrop-blur-sm"
226
+ >
227
+ <div className="text-lg md:text-xl font-bold text-foreground mb-0.5 md:mb-1">
228
+ {stat.number}
229
+ </div>
230
+ <div className="text-[10px] md:text-xs text-foreground/50 uppercase tracking-wide">
231
+ {stat.label}
232
+ </div>
233
+ </div>
234
+ ))}
235
+ </div>
236
+ )}
237
+ </div>
238
+ </div>
239
+
240
+ {/* Floating Elements */}
241
+ {floatingElements}
242
+
243
+ {/* Sticky Background Switcher Bar */}
244
+ {shouldShowSwitcher && (
245
+ <Sticky
246
+ bottom
247
+ offsetBottom={32}
248
+ className="w-full flex justify-center pointer-events-none"
249
+ zIndex={50}
250
+ >
251
+ <div className="inline-flex items-center gap-2 px-4 py-2 bg-background/90 backdrop-blur-md border border-border rounded-full shadow-lg pointer-events-auto">
252
+ {/* Current Background Label */}
253
+ <span className="text-xs text-muted-foreground font-medium">
254
+ {VARIANT_LABELS[currentVariant] || currentVariant}
255
+ </span>
256
+
257
+ {/* Separator */}
258
+ <div className="w-px h-4 bg-border" />
259
+
260
+ {/* Cycle Background Button */}
261
+ <Tooltip>
262
+ <TooltipTrigger asChild>
263
+ <Button
264
+ size="sm"
265
+ variant="ghost"
266
+ onClick={cycleBackground}
267
+ className="h-7 w-7 p-0"
268
+ >
269
+ <Wand2 className="w-3.5 h-3.5" />
270
+ </Button>
271
+ </TooltipTrigger>
272
+ <TooltipContent>
273
+ <p>Cycle background</p>
274
+ </TooltipContent>
275
+ </Tooltip>
276
+
277
+ {/* Background Menu */}
278
+ <div className="relative">
279
+ {isMenuOpen && (
280
+ <div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 bg-card border border-border rounded-lg shadow-xl p-2 min-w-[180px]">
281
+ <div className="text-xs font-semibold text-muted-foreground mb-2 px-2">
282
+ Background Style
283
+ </div>
284
+ <div className="space-y-1">
285
+ {BACKGROUND_VARIANTS.map((variant) => (
286
+ <button
287
+ key={variant}
288
+ onClick={() => {
289
+ setCurrentVariant(variant);
290
+ setIsMenuOpen(false);
291
+ }}
292
+ className={cn(
293
+ "w-full text-left px-3 py-2 rounded-md text-sm transition-colors",
294
+ currentVariant === variant
295
+ ? "bg-primary text-primary-foreground"
296
+ : "hover:bg-accent hover:text-accent-foreground"
297
+ )}
298
+ >
299
+ {VARIANT_LABELS[variant] || variant}
300
+ </button>
301
+ ))}
302
+ </div>
303
+ </div>
304
+ )}
305
+
306
+ <Tooltip>
307
+ <TooltipTrigger asChild>
308
+ <Button
309
+ size="sm"
310
+ variant="ghost"
311
+ onClick={() => setIsMenuOpen(!isMenuOpen)}
312
+ className="h-7 w-7 p-0"
313
+ >
314
+ <Sparkles className="w-3.5 h-3.5" />
315
+ </Button>
316
+ </TooltipTrigger>
317
+ <TooltipContent>
318
+ <p>Choose background</p>
319
+ </TooltipContent>
320
+ </Tooltip>
321
+ </div>
322
+ </div>
323
+ </Sticky>
324
+ )}
325
+ </section>
326
+ </ForceTheme>
327
+ );
328
+ };
@@ -0,0 +1,122 @@
1
+ import React from 'react';
2
+
3
+ import { Card, CardContent } from '@djangocfg/ui-core/components';
4
+ import { cn } from '@djangocfg/ui-core/lib';
5
+
6
+ interface Testimonial {
7
+ content: string;
8
+ author: {
9
+ name: string;
10
+ title?: string;
11
+ company?: string;
12
+ avatar?: string;
13
+ };
14
+ rating?: number;
15
+ }
16
+
17
+ interface TestimonialSectionProps {
18
+ title?: string;
19
+ subtitle?: string;
20
+ testimonials: Testimonial[];
21
+ columns?: 1 | 2 | 3;
22
+ className?: string;
23
+ background?: 'dark' | 'card' | 'gradient';
24
+ }
25
+
26
+ export const TestimonialSection: React.FC<TestimonialSectionProps> = ({
27
+ title,
28
+ subtitle,
29
+ testimonials,
30
+ columns = 3,
31
+ className,
32
+ background = 'dark'
33
+ }) => {
34
+ // Simple Tailwind 4 classes only - no custom utilities
35
+ const gridClasses = {
36
+ 1: 'grid-cols-1 max-w-4xl mx-auto',
37
+ 2: 'grid-cols-1 md:grid-cols-2',
38
+ 3: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
39
+ };
40
+
41
+ const backgroundClasses = {
42
+ dark: 'bg-background',
43
+ card: 'bg-muted/30',
44
+ gradient: 'bg-gradient-to-b from-background via-muted/10 to-background',
45
+ };
46
+
47
+ const renderStars = (rating: number) => {
48
+ return Array.from({ length: 5 }, (_, i) => (
49
+ <span key={i} className={i < rating ? 'text-yellow-400' : 'text-neutral-400'}>
50
+
51
+ </span>
52
+ ));
53
+ };
54
+
55
+ return (
56
+ <section className={cn(
57
+ 'relative py-16 sm:py-20 md:py-24 lg:py-32',
58
+ backgroundClasses[background],
59
+ className
60
+ )}>
61
+ <div className="container max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
62
+ {(title || subtitle) && (
63
+ <div className="text-center mb-12 sm:mb-14 md:mb-16">
64
+ {title && (
65
+ <h2 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-bold text-foreground mb-4 sm:mb-6">
66
+ {title}
67
+ </h2>
68
+ )}
69
+ {subtitle && (
70
+ <p className="text-lg sm:text-xl text-muted-foreground max-w-3xl mx-auto">
71
+ {subtitle}
72
+ </p>
73
+ )}
74
+ </div>
75
+ )}
76
+
77
+ <div className={cn('grid gap-6 sm:gap-8', gridClasses[columns])}>
78
+ {testimonials.map((testimonial, index) => (
79
+ <Card
80
+ key={index}
81
+ className="h-full hover:shadow-lg transition-all duration-300 backdrop-blur-sm border-border/50 hover:border-primary/30 group"
82
+ >
83
+ <CardContent className="p-8">
84
+ {testimonial.rating && (
85
+ <div className="flex mb-6 text-lg">
86
+ {renderStars(testimonial.rating)}
87
+ </div>
88
+ )}
89
+
90
+ <blockquote className="text-foreground mb-8 italic text-lg leading-relaxed group-hover:text-primary transition-colors duration-300">
91
+ "{testimonial.content}"
92
+ </blockquote>
93
+
94
+ <div className="flex items-center">
95
+ {testimonial.author.avatar && (
96
+ <img
97
+ src={testimonial.author.avatar}
98
+ alt={testimonial.author.name}
99
+ className="w-14 h-14 rounded-full mr-4 ring-2 ring-primary/20 group-hover:ring-primary/40 transition-all duration-300"
100
+ />
101
+ )}
102
+
103
+ <div>
104
+ <div className="font-semibold text-foreground group-hover:text-primary transition-colors duration-300">
105
+ {testimonial.author.name}
106
+ </div>
107
+ {testimonial.author.title && (
108
+ <div className="text-sm text-muted-foreground">
109
+ {testimonial.author.title}
110
+ {testimonial.author.company && ` at ${testimonial.author.company}`}
111
+ </div>
112
+ )}
113
+ </div>
114
+ </div>
115
+ </CardContent>
116
+ </Card>
117
+ ))}
118
+ </div>
119
+ </div>
120
+ </section>
121
+ );
122
+ };
@@ -0,0 +1,9 @@
1
+ export * from './ArticleCard';
2
+ export * from './ArticleList';
3
+ export * from './CTASection';
4
+ export * from './FeatureSection';
5
+ export * from './Hero';
6
+ export * from './NewsletterSection';
7
+ export * from './StatsSection';
8
+ export * from './SuperHero';
9
+ export * from './TestimonialSection';