@djangocfg/ui-nextjs 2.1.320 → 2.1.322
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/README.md +27 -262
- package/package.json +7 -17
- package/src/index.ts +5 -14
- package/src/animations/AnimatedBackground.tsx +0 -646
- package/src/animations/MouseFollower.tsx +0 -355
- package/src/animations/index.ts +0 -5
- package/src/blocks/ArticleCard.tsx +0 -94
- package/src/blocks/ArticleList.tsx +0 -96
- package/src/blocks/CTASection.tsx +0 -136
- package/src/blocks/FeatureSection.tsx +0 -176
- package/src/blocks/Hero.tsx +0 -102
- package/src/blocks/NewsletterSection.tsx +0 -119
- package/src/blocks/SplitHero/SplitHero.tsx +0 -95
- package/src/blocks/SplitHero/SplitHeroContent.tsx +0 -117
- package/src/blocks/SplitHero/SplitHeroMedia.tsx +0 -67
- package/src/blocks/SplitHero/index.ts +0 -13
- package/src/blocks/SplitHero/types.ts +0 -75
- package/src/blocks/StatsSection.tsx +0 -103
- package/src/blocks/SuperHero.tsx +0 -352
- package/src/blocks/TestimonialSection.tsx +0 -122
- package/src/blocks/index.ts +0 -10
- package/src/components/README.md +0 -2018
- package/src/components/breadcrumb-navigation.tsx +0 -130
- package/src/components/breadcrumb.tsx +0 -133
- package/src/components/dropdown-menu.tsx +0 -220
- package/src/components/index.ts +0 -52
- package/src/components/pagination-static.tsx +0 -344
- package/src/components/pagination.tsx +0 -138
- package/src/components/sidebar.tsx +0 -923
- package/src/components/ssr-pagination.tsx +0 -214
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
|
-
|
|
3
|
-
export interface SplitHeroBadge {
|
|
4
|
-
icon?: ReactNode;
|
|
5
|
-
text: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface SplitHeroAction {
|
|
9
|
-
label: string;
|
|
10
|
-
href?: string;
|
|
11
|
-
onClick?: () => void;
|
|
12
|
-
icon?: ReactNode;
|
|
13
|
-
variant?: 'default' | 'outline' | 'ghost' | 'secondary';
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface SplitHeroFeature {
|
|
17
|
-
icon?: ReactNode;
|
|
18
|
-
text: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** Image media type */
|
|
22
|
-
export interface SplitHeroMediaImage {
|
|
23
|
-
type: 'image';
|
|
24
|
-
src: string;
|
|
25
|
-
alt?: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Video media type - uses VideoPlayer */
|
|
29
|
-
export interface SplitHeroMediaVideo {
|
|
30
|
-
type: 'video';
|
|
31
|
-
url: string;
|
|
32
|
-
title?: string;
|
|
33
|
-
poster?: string;
|
|
34
|
-
autoplay?: boolean;
|
|
35
|
-
muted?: boolean;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Custom component media type */
|
|
39
|
-
export interface SplitHeroMediaCustom {
|
|
40
|
-
type: 'custom';
|
|
41
|
-
content: ReactNode;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export type SplitHeroMedia = SplitHeroMediaImage | SplitHeroMediaVideo | SplitHeroMediaCustom;
|
|
45
|
-
|
|
46
|
-
export interface SplitHeroProps {
|
|
47
|
-
/** Badge above title */
|
|
48
|
-
badge?: SplitHeroBadge;
|
|
49
|
-
/** Main title */
|
|
50
|
-
title: string;
|
|
51
|
-
/** Gradient part of title (optional) */
|
|
52
|
-
titleGradient?: string;
|
|
53
|
-
/** Subtitle text */
|
|
54
|
-
subtitle?: string;
|
|
55
|
-
/** Feature list */
|
|
56
|
-
features?: SplitHeroFeature[];
|
|
57
|
-
/** Primary CTA button */
|
|
58
|
-
primaryAction?: SplitHeroAction;
|
|
59
|
-
/** Secondary CTA button */
|
|
60
|
-
secondaryAction?: SplitHeroAction;
|
|
61
|
-
/** Media content - image, video, or custom component */
|
|
62
|
-
media?: SplitHeroMedia;
|
|
63
|
-
/** Layout direction */
|
|
64
|
-
layout?: 'text-left' | 'text-right';
|
|
65
|
-
/** Mobile stack order */
|
|
66
|
-
mobileOrder?: 'media-first' | 'text-first';
|
|
67
|
-
/** Content vertical alignment */
|
|
68
|
-
align?: 'top' | 'center' | 'bottom';
|
|
69
|
-
/** Container max width */
|
|
70
|
-
maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
71
|
-
/** Background style */
|
|
72
|
-
background?: 'none' | 'muted' | 'gradient';
|
|
73
|
-
/** Custom className */
|
|
74
|
-
className?: string;
|
|
75
|
-
}
|
|
@@ -1,103 +0,0 @@
|
|
|
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
|
-
};
|
package/src/blocks/SuperHero.tsx
DELETED
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import { isDev as isDevelopment } from "@djangocfg/ui-core/lib";
|
|
4
|
-
import { ArrowRight, Sparkles, Wand2 } from 'lucide-react';
|
|
5
|
-
import React from 'react';
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
Button, ButtonLink, CopyButton, Sticky, Tooltip, TooltipContent, TooltipTrigger
|
|
9
|
-
} from '@djangocfg/ui-core/components';
|
|
10
|
-
|
|
11
|
-
import { cn } from '@djangocfg/ui-core/lib';
|
|
12
|
-
|
|
13
|
-
import { AnimatedBackground, BackgroundVariant} from '../animations';
|
|
14
|
-
import { ForceTheme } from '../theme';
|
|
15
|
-
|
|
16
|
-
interface HeroFeature {
|
|
17
|
-
icon: React.ReactNode;
|
|
18
|
-
text: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface StatItem {
|
|
22
|
-
number: string;
|
|
23
|
-
label: string;
|
|
24
|
-
icon?: React.ReactNode;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface SuperHeroProps {
|
|
28
|
-
badge?: {
|
|
29
|
-
icon?: React.ReactNode;
|
|
30
|
-
text: string;
|
|
31
|
-
};
|
|
32
|
-
title: string;
|
|
33
|
-
titleGradient?: string;
|
|
34
|
-
subtitle: string;
|
|
35
|
-
features?: HeroFeature[];
|
|
36
|
-
primaryAction?: {
|
|
37
|
-
label: string;
|
|
38
|
-
href?: string;
|
|
39
|
-
onClick?: () => void;
|
|
40
|
-
};
|
|
41
|
-
secondaryAction?: {
|
|
42
|
-
label: string;
|
|
43
|
-
href?: string;
|
|
44
|
-
onClick?: () => void;
|
|
45
|
-
icon?: React.ReactNode;
|
|
46
|
-
};
|
|
47
|
-
stats?: StatItem[];
|
|
48
|
-
floatingElements?: React.ReactNode;
|
|
49
|
-
backgroundVariant?: BackgroundVariant;
|
|
50
|
-
backgroundIntensity?: 'subtle' | 'medium' | 'strong';
|
|
51
|
-
showBackgroundSwitcher?: boolean;
|
|
52
|
-
/** Single command (for backwards compatibility) */
|
|
53
|
-
codeCommand?: string;
|
|
54
|
-
/** Array of commands to display with copy buttons */
|
|
55
|
-
codeCommands?: string[];
|
|
56
|
-
className?: string;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const BACKGROUND_VARIANTS: BackgroundVariant[] = [
|
|
60
|
-
'aurora-borealis',
|
|
61
|
-
'mesh-gradient',
|
|
62
|
-
'floating-orbs',
|
|
63
|
-
'geometric-flow',
|
|
64
|
-
'liquid-gradient',
|
|
65
|
-
'spotlight',
|
|
66
|
-
'none'
|
|
67
|
-
];
|
|
68
|
-
|
|
69
|
-
const VARIANT_LABELS: Partial<Record<BackgroundVariant, string>> = {
|
|
70
|
-
'aurora-borealis': 'Aurora Borealis',
|
|
71
|
-
'mesh-gradient': 'Mesh Gradient',
|
|
72
|
-
'floating-orbs': 'Floating Orbs',
|
|
73
|
-
'geometric-flow': 'Geometric Flow',
|
|
74
|
-
'liquid-gradient': 'Liquid Gradient',
|
|
75
|
-
'spotlight': 'Spotlight',
|
|
76
|
-
'none': 'None',
|
|
77
|
-
// Legacy labels for backwards compatibility
|
|
78
|
-
'gradient-mesh': 'Gradient Mesh',
|
|
79
|
-
'dot-matrix': 'Dot Matrix',
|
|
80
|
-
'grid-lines': 'Grid Lines',
|
|
81
|
-
'aurora': 'Aurora',
|
|
82
|
-
'particles': 'Particles',
|
|
83
|
-
'waves': 'Waves',
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
export const SuperHero: React.FC<SuperHeroProps> = ({
|
|
87
|
-
badge,
|
|
88
|
-
title,
|
|
89
|
-
titleGradient,
|
|
90
|
-
subtitle,
|
|
91
|
-
features = [],
|
|
92
|
-
primaryAction,
|
|
93
|
-
secondaryAction,
|
|
94
|
-
stats = [],
|
|
95
|
-
floatingElements,
|
|
96
|
-
backgroundVariant = 'mesh-gradient',
|
|
97
|
-
backgroundIntensity = 'medium',
|
|
98
|
-
showBackgroundSwitcher = false,
|
|
99
|
-
codeCommand,
|
|
100
|
-
codeCommands,
|
|
101
|
-
className
|
|
102
|
-
}) => {
|
|
103
|
-
const [currentVariant, setCurrentVariant] = React.useState<BackgroundVariant>(backgroundVariant);
|
|
104
|
-
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
|
|
105
|
-
const [heroTheme, _setHeroTheme] = React.useState<'light' | 'dark'>('dark');
|
|
106
|
-
|
|
107
|
-
// Merge codeCommand (deprecated) with codeCommands for backwards compatibility
|
|
108
|
-
const commands = React.useMemo(() => {
|
|
109
|
-
if (codeCommands && codeCommands.length > 0) return codeCommands;
|
|
110
|
-
if (codeCommand) return [codeCommand];
|
|
111
|
-
return [];
|
|
112
|
-
}, [codeCommand, codeCommands]);
|
|
113
|
-
|
|
114
|
-
// Pre-compute title lines to avoid repeated split in JSX
|
|
115
|
-
const titleLines = React.useMemo(() => title.split('\n'), [title]);
|
|
116
|
-
|
|
117
|
-
// Show background switcher in development mode or if explicitly enabled
|
|
118
|
-
const shouldShowSwitcher = showBackgroundSwitcher || isDevelopment;
|
|
119
|
-
|
|
120
|
-
const cycleBackground = () => {
|
|
121
|
-
const currentIndex = BACKGROUND_VARIANTS.indexOf(currentVariant);
|
|
122
|
-
const nextIndex = (currentIndex + 1) % BACKGROUND_VARIANTS.length;
|
|
123
|
-
setCurrentVariant(BACKGROUND_VARIANTS[nextIndex]!);
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
return (
|
|
127
|
-
<ForceTheme theme={heroTheme}>
|
|
128
|
-
<section
|
|
129
|
-
className={cn(
|
|
130
|
-
"relative overflow-hidden bg-background text-foreground z-0 isolate",
|
|
131
|
-
className
|
|
132
|
-
)}
|
|
133
|
-
>
|
|
134
|
-
{/* Animated Background */}
|
|
135
|
-
<div className="absolute inset-0 -z-10">
|
|
136
|
-
<AnimatedBackground variant={currentVariant} intensity={backgroundIntensity} />
|
|
137
|
-
</div>
|
|
138
|
-
|
|
139
|
-
<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">
|
|
140
|
-
<div className="text-center">
|
|
141
|
-
{/* Enhanced Badge */}
|
|
142
|
-
{badge && (
|
|
143
|
-
<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">
|
|
144
|
-
{badge.icon || <Sparkles className="w-4 h-4" />}
|
|
145
|
-
{badge.text}
|
|
146
|
-
</div>
|
|
147
|
-
)}
|
|
148
|
-
|
|
149
|
-
{/* Enhanced Main Title */}
|
|
150
|
-
<h1 className="text-4xl sm:text-5xl lg:text-6xl xl:text-7xl font-bold text-foreground mb-6 leading-tight">
|
|
151
|
-
{titleLines.map((line, index) => (
|
|
152
|
-
<React.Fragment key={index}>
|
|
153
|
-
{line}
|
|
154
|
-
{index < titleLines.length - 1 && <br />}
|
|
155
|
-
</React.Fragment>
|
|
156
|
-
))}
|
|
157
|
-
{titleGradient && (
|
|
158
|
-
<>
|
|
159
|
-
<br />
|
|
160
|
-
<span className="bg-gradient-to-r from-primary via-primary/80 to-secondary bg-clip-text text-transparent animate-pulse">
|
|
161
|
-
{titleGradient}
|
|
162
|
-
</span>
|
|
163
|
-
</>
|
|
164
|
-
)}
|
|
165
|
-
</h1>
|
|
166
|
-
|
|
167
|
-
{/* Enhanced Subtitle */}
|
|
168
|
-
<p className="text-xl sm:text-2xl lg:text-3xl text-muted-foreground mb-8 max-w-4xl mx-auto leading-relaxed">
|
|
169
|
-
{subtitle}
|
|
170
|
-
</p>
|
|
171
|
-
|
|
172
|
-
{/* Code Commands */}
|
|
173
|
-
{commands.length > 0 && (
|
|
174
|
-
<div className="flex flex-col items-center gap-2 mb-8">
|
|
175
|
-
{commands.map((cmd, index) => (
|
|
176
|
-
<div
|
|
177
|
-
key={index}
|
|
178
|
-
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"
|
|
179
|
-
>
|
|
180
|
-
<span className="text-muted-foreground font-mono text-sm select-none">$</span>
|
|
181
|
-
<code className="font-mono text-lg text-primary font-semibold">
|
|
182
|
-
{cmd}
|
|
183
|
-
</code>
|
|
184
|
-
<CopyButton
|
|
185
|
-
value={cmd}
|
|
186
|
-
variant="ghost"
|
|
187
|
-
className="p-1.5 h-auto rounded-md bg-primary/10 text-primary hover:bg-primary hover:text-primary-foreground transition-colors"
|
|
188
|
-
iconClassName="w-4 h-4"
|
|
189
|
-
/>
|
|
190
|
-
</div>
|
|
191
|
-
))}
|
|
192
|
-
</div>
|
|
193
|
-
)}
|
|
194
|
-
|
|
195
|
-
{/* Enhanced Features */}
|
|
196
|
-
{features.length > 0 && (
|
|
197
|
-
<div className="flex flex-wrap justify-center gap-6 mb-8">
|
|
198
|
-
{features.map((feature, index) => (
|
|
199
|
-
<div
|
|
200
|
-
key={index}
|
|
201
|
-
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"
|
|
202
|
-
>
|
|
203
|
-
{feature.icon}
|
|
204
|
-
<span>{feature.text}</span>
|
|
205
|
-
</div>
|
|
206
|
-
))}
|
|
207
|
-
</div>
|
|
208
|
-
)}
|
|
209
|
-
|
|
210
|
-
{/* Enhanced CTA Buttons */}
|
|
211
|
-
{(primaryAction || secondaryAction) && (
|
|
212
|
-
<div className="flex flex-col sm:flex-row gap-4 justify-center mb-12">
|
|
213
|
-
{primaryAction && (
|
|
214
|
-
<ButtonLink
|
|
215
|
-
href={primaryAction.href || '#'}
|
|
216
|
-
onClick={primaryAction.onClick}
|
|
217
|
-
size="huge"
|
|
218
|
-
className="group"
|
|
219
|
-
>
|
|
220
|
-
{primaryAction.label}
|
|
221
|
-
<ArrowRight className="group-hover:translate-x-1 transition-transform duration-300" />
|
|
222
|
-
</ButtonLink>
|
|
223
|
-
)}
|
|
224
|
-
{secondaryAction && (
|
|
225
|
-
<ButtonLink
|
|
226
|
-
href={secondaryAction.href || '#'}
|
|
227
|
-
onClick={secondaryAction.onClick}
|
|
228
|
-
variant="outline"
|
|
229
|
-
size="huge"
|
|
230
|
-
className="group"
|
|
231
|
-
>
|
|
232
|
-
{secondaryAction.label}
|
|
233
|
-
{secondaryAction.icon && (
|
|
234
|
-
<span className="group-hover:rotate-12 transition-transform duration-300">
|
|
235
|
-
{secondaryAction.icon}
|
|
236
|
-
</span>
|
|
237
|
-
)}
|
|
238
|
-
</ButtonLink>
|
|
239
|
-
)}
|
|
240
|
-
</div>
|
|
241
|
-
)}
|
|
242
|
-
|
|
243
|
-
{/* Enhanced Stats Preview */}
|
|
244
|
-
{stats.length > 0 && (
|
|
245
|
-
<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">
|
|
246
|
-
{stats.map((stat, index) => (
|
|
247
|
-
<div
|
|
248
|
-
key={index}
|
|
249
|
-
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"
|
|
250
|
-
>
|
|
251
|
-
<div className="text-lg md:text-xl font-bold text-foreground mb-0.5 md:mb-1">
|
|
252
|
-
{stat.number}
|
|
253
|
-
</div>
|
|
254
|
-
<div className="text-[10px] md:text-xs text-foreground/50 uppercase tracking-wide">
|
|
255
|
-
{stat.label}
|
|
256
|
-
</div>
|
|
257
|
-
</div>
|
|
258
|
-
))}
|
|
259
|
-
</div>
|
|
260
|
-
)}
|
|
261
|
-
</div>
|
|
262
|
-
</div>
|
|
263
|
-
|
|
264
|
-
{/* Floating Elements */}
|
|
265
|
-
{floatingElements}
|
|
266
|
-
|
|
267
|
-
{/* Sticky Background Switcher Bar */}
|
|
268
|
-
{shouldShowSwitcher && (
|
|
269
|
-
<Sticky
|
|
270
|
-
bottom
|
|
271
|
-
offsetBottom={32}
|
|
272
|
-
className="w-full flex justify-center pointer-events-none"
|
|
273
|
-
zIndex={50}
|
|
274
|
-
>
|
|
275
|
-
<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">
|
|
276
|
-
{/* Current Background Label */}
|
|
277
|
-
<span className="text-xs text-muted-foreground font-medium">
|
|
278
|
-
{VARIANT_LABELS[currentVariant] || currentVariant}
|
|
279
|
-
</span>
|
|
280
|
-
|
|
281
|
-
{/* Separator */}
|
|
282
|
-
<div className="w-px h-4 bg-border" />
|
|
283
|
-
|
|
284
|
-
{/* Cycle Background Button */}
|
|
285
|
-
<Tooltip>
|
|
286
|
-
<TooltipTrigger asChild>
|
|
287
|
-
<Button
|
|
288
|
-
size="sm"
|
|
289
|
-
variant="ghost"
|
|
290
|
-
onClick={cycleBackground}
|
|
291
|
-
className="h-7 w-7 p-0"
|
|
292
|
-
>
|
|
293
|
-
<Wand2 className="w-3.5 h-3.5" />
|
|
294
|
-
</Button>
|
|
295
|
-
</TooltipTrigger>
|
|
296
|
-
<TooltipContent>
|
|
297
|
-
<p>Cycle background</p>
|
|
298
|
-
</TooltipContent>
|
|
299
|
-
</Tooltip>
|
|
300
|
-
|
|
301
|
-
{/* Background Menu */}
|
|
302
|
-
<div className="relative">
|
|
303
|
-
{isMenuOpen && (
|
|
304
|
-
<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]">
|
|
305
|
-
<div className="text-xs font-semibold text-muted-foreground mb-2 px-2">
|
|
306
|
-
Background Style
|
|
307
|
-
</div>
|
|
308
|
-
<div className="space-y-1">
|
|
309
|
-
{BACKGROUND_VARIANTS.map((variant) => (
|
|
310
|
-
<button
|
|
311
|
-
key={variant}
|
|
312
|
-
onClick={() => {
|
|
313
|
-
setCurrentVariant(variant);
|
|
314
|
-
setIsMenuOpen(false);
|
|
315
|
-
}}
|
|
316
|
-
className={cn(
|
|
317
|
-
"w-full text-left px-3 py-2 rounded-md text-sm transition-colors",
|
|
318
|
-
currentVariant === variant
|
|
319
|
-
? "bg-primary text-primary-foreground"
|
|
320
|
-
: "hover:bg-accent hover:text-accent-foreground"
|
|
321
|
-
)}
|
|
322
|
-
>
|
|
323
|
-
{VARIANT_LABELS[variant] || variant}
|
|
324
|
-
</button>
|
|
325
|
-
))}
|
|
326
|
-
</div>
|
|
327
|
-
</div>
|
|
328
|
-
)}
|
|
329
|
-
|
|
330
|
-
<Tooltip>
|
|
331
|
-
<TooltipTrigger asChild>
|
|
332
|
-
<Button
|
|
333
|
-
size="sm"
|
|
334
|
-
variant="ghost"
|
|
335
|
-
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
|
336
|
-
className="h-7 w-7 p-0"
|
|
337
|
-
>
|
|
338
|
-
<Sparkles className="w-3.5 h-3.5" />
|
|
339
|
-
</Button>
|
|
340
|
-
</TooltipTrigger>
|
|
341
|
-
<TooltipContent>
|
|
342
|
-
<p>Choose background</p>
|
|
343
|
-
</TooltipContent>
|
|
344
|
-
</Tooltip>
|
|
345
|
-
</div>
|
|
346
|
-
</div>
|
|
347
|
-
</Sticky>
|
|
348
|
-
)}
|
|
349
|
-
</section>
|
|
350
|
-
</ForceTheme>
|
|
351
|
-
);
|
|
352
|
-
};
|
|
@@ -1,122 +0,0 @@
|
|
|
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
|
-
};
|
package/src/blocks/index.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
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 './SplitHero';
|
|
8
|
-
export * from './StatsSection';
|
|
9
|
-
export * from './SuperHero';
|
|
10
|
-
export * from './TestimonialSection';
|