@djangocfg/ui-nextjs 2.1.319 → 2.1.321

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.
@@ -1,355 +0,0 @@
1
- 'use client';
2
-
3
- import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react';
4
-
5
- import { cn } from '@djangocfg/ui-core/lib';
6
-
7
- export type MouseFollowerVariant =
8
- | 'glow'
9
- | 'spotlight'
10
- | 'gradient-blob'
11
- | 'ring'
12
- | 'trail';
13
-
14
- interface MouseFollowerProps {
15
- /** Visual style of the follower */
16
- variant?: MouseFollowerVariant;
17
- /** Size of the effect in pixels */
18
- size?: number;
19
- /** Color - can be CSS color or 'primary' to use theme */
20
- color?: string;
21
- /** Smoothness of following (0.05 = very smooth, 0.3 = snappy) */
22
- smoothness?: number;
23
- /** Opacity of the effect (0-1) */
24
- opacity?: number;
25
- /** Blur amount for glow effects */
26
- blur?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl';
27
- /** Additional className for the container */
28
- className?: string;
29
- /** Whether to show on mobile (touch devices) */
30
- showOnMobile?: boolean;
31
- /** Disable the effect */
32
- disabled?: boolean;
33
- }
34
-
35
- interface Position {
36
- x: number;
37
- y: number;
38
- }
39
-
40
- const blurMap = {
41
- sm: 'blur-sm',
42
- md: 'blur-md',
43
- lg: 'blur-lg',
44
- xl: 'blur-xl',
45
- '2xl': 'blur-2xl',
46
- '3xl': 'blur-3xl',
47
- };
48
-
49
- export const MouseFollower: React.FC<MouseFollowerProps> = ({
50
- variant = 'glow',
51
- size = 300,
52
- color = 'primary',
53
- smoothness = 0.1,
54
- opacity = 0.3,
55
- blur = '2xl',
56
- className,
57
- showOnMobile = false,
58
- disabled = false,
59
- }) => {
60
- const [mounted, setMounted] = useState(false);
61
- const [isVisible, setIsVisible] = useState(false);
62
- const [isMobile, setIsMobile] = useState(false);
63
- const positionRef = useRef<Position>({ x: 0, y: 0 });
64
- const targetRef = useRef<Position>({ x: 0, y: 0 });
65
- const elementRef = useRef<HTMLDivElement>(null);
66
- const rafRef = useRef<number>(0);
67
-
68
- // Resolve color
69
- const resolvedColor = useMemo(() => {
70
- if (color === 'primary') return 'hsl(var(--primary))';
71
- if (color === 'secondary') return 'hsl(var(--secondary))';
72
- if (color === 'accent') return 'hsl(var(--accent))';
73
- return color;
74
- }, [color]);
75
-
76
- // Check if mobile
77
- useEffect(() => {
78
- setMounted(true);
79
- const checkMobile = () => {
80
- setIsMobile('ontouchstart' in window || navigator.maxTouchPoints > 0);
81
- };
82
- checkMobile();
83
- window.addEventListener('resize', checkMobile);
84
- return () => window.removeEventListener('resize', checkMobile);
85
- }, []);
86
-
87
- // Animation loop with lerp
88
- const animate = useCallback(() => {
89
- const dx = targetRef.current.x - positionRef.current.x;
90
- const dy = targetRef.current.y - positionRef.current.y;
91
-
92
- positionRef.current.x += dx * smoothness;
93
- positionRef.current.y += dy * smoothness;
94
-
95
- if (elementRef.current) {
96
- elementRef.current.style.transform = `translate(${positionRef.current.x - size / 2}px, ${positionRef.current.y - size / 2}px)`;
97
- }
98
-
99
- rafRef.current = requestAnimationFrame(animate);
100
- }, [smoothness, size]);
101
-
102
- // Mouse move handler
103
- useEffect(() => {
104
- if (disabled || !mounted) return;
105
- if (isMobile && !showOnMobile) return;
106
-
107
- const handleMouseMove = (e: MouseEvent) => {
108
- targetRef.current = { x: e.clientX, y: e.clientY };
109
- if (!isVisible) setIsVisible(true);
110
- };
111
-
112
- const handleMouseLeave = () => {
113
- setIsVisible(false);
114
- };
115
-
116
- const handleMouseEnter = () => {
117
- setIsVisible(true);
118
- };
119
-
120
- document.addEventListener('mousemove', handleMouseMove);
121
- document.addEventListener('mouseleave', handleMouseLeave);
122
- document.addEventListener('mouseenter', handleMouseEnter);
123
-
124
- rafRef.current = requestAnimationFrame(animate);
125
-
126
- return () => {
127
- document.removeEventListener('mousemove', handleMouseMove);
128
- document.removeEventListener('mouseleave', handleMouseLeave);
129
- document.removeEventListener('mouseenter', handleMouseEnter);
130
- cancelAnimationFrame(rafRef.current);
131
- };
132
- }, [disabled, mounted, isMobile, showOnMobile, animate, isVisible]);
133
-
134
- if (!mounted || disabled) return null;
135
- if (isMobile && !showOnMobile) return null;
136
-
137
- return (
138
- <div
139
- className={cn(
140
- 'fixed inset-0 pointer-events-none overflow-hidden z-0',
141
- className
142
- )}
143
- >
144
- <div
145
- ref={elementRef}
146
- className={cn(
147
- 'absolute top-0 left-0 transition-opacity duration-300',
148
- isVisible ? 'opacity-100' : 'opacity-0'
149
- )}
150
- style={{ width: size, height: size }}
151
- >
152
- {variant === 'glow' && (
153
- <GlowEffect
154
- size={size}
155
- color={resolvedColor}
156
- opacity={opacity}
157
- blur={blurMap[blur]}
158
- />
159
- )}
160
-
161
- {variant === 'spotlight' && (
162
- <SpotlightEffect
163
- size={size}
164
- color={resolvedColor}
165
- opacity={opacity}
166
- blur={blurMap[blur]}
167
- />
168
- )}
169
-
170
- {variant === 'gradient-blob' && (
171
- <GradientBlobEffect
172
- size={size}
173
- color={resolvedColor}
174
- opacity={opacity}
175
- blur={blurMap[blur]}
176
- />
177
- )}
178
-
179
- {variant === 'ring' && (
180
- <RingEffect
181
- size={size}
182
- color={resolvedColor}
183
- opacity={opacity}
184
- />
185
- )}
186
-
187
- {variant === 'trail' && (
188
- <TrailEffect
189
- size={size}
190
- color={resolvedColor}
191
- opacity={opacity}
192
- blur={blurMap[blur]}
193
- />
194
- )}
195
- </div>
196
- </div>
197
- );
198
- };
199
-
200
- // =============================================================================
201
- // Effect Variants
202
- // =============================================================================
203
-
204
- interface EffectProps {
205
- size: number;
206
- color: string;
207
- opacity: number;
208
- blur?: string;
209
- }
210
-
211
- const GlowEffect: React.FC<EffectProps> = ({ size: _size, color, opacity, blur }) => (
212
- <div
213
- className={cn('w-full h-full rounded-full', blur)}
214
- style={{
215
- background: `radial-gradient(circle, ${color} 0%, transparent 70%)`,
216
- opacity,
217
- }}
218
- />
219
- );
220
-
221
- const SpotlightEffect: React.FC<EffectProps> = ({ size: _size, color, opacity, blur }) => (
222
- <>
223
- {/* Main spotlight cone */}
224
- <div
225
- className={cn('absolute inset-0', blur)}
226
- style={{
227
- background: `conic-gradient(from 180deg at 50% 50%,
228
- transparent 0deg,
229
- ${color} 150deg,
230
- ${color} 210deg,
231
- transparent 360deg
232
- )`,
233
- opacity: opacity * 0.6,
234
- }}
235
- />
236
- {/* Center glow */}
237
- <div
238
- className={cn('absolute rounded-full', blur)}
239
- style={{
240
- width: '40%',
241
- height: '40%',
242
- top: '30%',
243
- left: '30%',
244
- background: `radial-gradient(circle, ${color} 0%, transparent 70%)`,
245
- opacity,
246
- }}
247
- />
248
- </>
249
- );
250
-
251
- const GradientBlobEffect: React.FC<EffectProps> = ({ size: _size, color, opacity, blur }) => (
252
- <>
253
- {/* Animated blob shape */}
254
- <div
255
- className={cn('w-full h-full', blur)}
256
- style={{
257
- background: `radial-gradient(ellipse 60% 40% at 50% 50%, ${color} 0%, transparent 70%)`,
258
- opacity,
259
- animation: 'blob-morph 8s ease-in-out infinite',
260
- }}
261
- />
262
- {/* Secondary blob */}
263
- <div
264
- className={cn('absolute inset-0', blur)}
265
- style={{
266
- background: `radial-gradient(ellipse 40% 60% at 50% 50%, ${color} 0%, transparent 60%)`,
267
- opacity: opacity * 0.5,
268
- animation: 'blob-morph 8s ease-in-out infinite reverse',
269
- animationDelay: '-4s',
270
- }}
271
- />
272
- <style>{`
273
- @keyframes blob-morph {
274
- 0%, 100% { transform: scale(1) rotate(0deg); border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%; }
275
- 25% { transform: scale(1.1) rotate(90deg); border-radius: 30% 60% 70% 40% / 50% 60% 30% 60%; }
276
- 50% { transform: scale(0.9) rotate(180deg); border-radius: 50% 60% 30% 60% / 30% 60% 70% 40%; }
277
- 75% { transform: scale(1.05) rotate(270deg); border-radius: 60% 40% 50% 40% / 70% 30% 50% 60%; }
278
- }
279
- `}</style>
280
- </>
281
- );
282
-
283
- const RingEffect: React.FC<EffectProps> = ({ size: _size, color, opacity }) => (
284
- <>
285
- {/* Outer ring */}
286
- <div
287
- className="absolute inset-0 rounded-full"
288
- style={{
289
- border: `2px solid ${color}`,
290
- opacity: opacity * 0.3,
291
- }}
292
- />
293
- {/* Middle ring */}
294
- <div
295
- className="absolute rounded-full"
296
- style={{
297
- inset: '15%',
298
- border: `2px solid ${color}`,
299
- opacity: opacity * 0.5,
300
- animation: 'ring-pulse 2s ease-in-out infinite',
301
- }}
302
- />
303
- {/* Inner ring */}
304
- <div
305
- className="absolute rounded-full"
306
- style={{
307
- inset: '30%',
308
- border: `2px solid ${color}`,
309
- opacity: opacity * 0.7,
310
- animation: 'ring-pulse 2s ease-in-out infinite',
311
- animationDelay: '-1s',
312
- }}
313
- />
314
- {/* Center dot */}
315
- <div
316
- className="absolute rounded-full"
317
- style={{
318
- width: '8px',
319
- height: '8px',
320
- top: '50%',
321
- left: '50%',
322
- transform: 'translate(-50%, -50%)',
323
- background: color,
324
- opacity,
325
- }}
326
- />
327
- <style>{`
328
- @keyframes ring-pulse {
329
- 0%, 100% { opacity: 0.3; transform: scale(1); }
330
- 50% { opacity: 0.7; transform: scale(1.05); }
331
- }
332
- `}</style>
333
- </>
334
- );
335
-
336
- const TrailEffect: React.FC<EffectProps> = ({ size: _size, color, opacity, blur }) => (
337
- <>
338
- {/* Multiple trailing circles */}
339
- {Array.from({ length: 5 }).map((_, i) => (
340
- <div
341
- key={i}
342
- className={cn('absolute rounded-full', blur)}
343
- style={{
344
- width: `${100 - i * 15}%`,
345
- height: `${100 - i * 15}%`,
346
- top: `${i * 7.5}%`,
347
- left: `${i * 7.5}%`,
348
- background: `radial-gradient(circle, ${color} 0%, transparent 70%)`,
349
- opacity: opacity * (1 - i * 0.15),
350
- transition: `transform ${0.1 + i * 0.05}s ease-out`,
351
- }}
352
- />
353
- ))}
354
- </>
355
- );
@@ -1,5 +0,0 @@
1
- export { AnimatedBackground } from './AnimatedBackground';
2
- export type { BackgroundVariant } from './AnimatedBackground';
3
-
4
- export { MouseFollower } from './MouseFollower';
5
- export type { MouseFollowerVariant } from './MouseFollower';
@@ -1,94 +0,0 @@
1
- import React from 'react';
2
- import moment from 'moment';
3
-
4
- import {
5
- Badge, ButtonLink, Card, CardContent, CardDescription, CardHeader, CardTitle
6
- } from '@djangocfg/ui-core/components';
7
-
8
- import { cn } from '@djangocfg/ui-core/lib';
9
-
10
- export type ArticleType = 'security' | 'release' | 'announcement' | 'feature';
11
-
12
- interface ArticleCardProps {
13
- title: string;
14
- description?: string;
15
- date: string;
16
- type: ArticleType;
17
- href: string;
18
- author?: string;
19
- tags?: string[];
20
- featured?: boolean;
21
- className?: string;
22
- }
23
-
24
- const typeConfig: Record<ArticleType, { label: string; variant: 'default' | 'secondary' | 'destructive' | 'outline'; icon: string }> = {
25
- security: { label: 'Security', variant: 'destructive', icon: '🛡️' },
26
- release: { label: 'Release', variant: 'default', icon: '📦' },
27
- announcement: { label: 'Announcement', variant: 'secondary', icon: '📢' },
28
- feature: { label: 'Feature', variant: 'outline', icon: '✨' },
29
- };
30
-
31
- export const ArticleCard: React.FC<ArticleCardProps> = ({
32
- title,
33
- description,
34
- date,
35
- type,
36
- href,
37
- author,
38
- tags,
39
- featured = false,
40
- className,
41
- }) => {
42
- const config = typeConfig[type];
43
- const formattedDate = moment(date).format('MMMM D, YYYY');
44
-
45
- return (
46
- <Card className={cn(
47
- 'group relative overflow-hidden transition-all duration-200 hover:shadow-lg',
48
- featured && 'border-primary/50 bg-primary/5',
49
- className
50
- )}>
51
- <CardHeader className="pb-3">
52
- <div className="flex items-center justify-between gap-2 mb-2">
53
- <div className="flex items-center gap-2">
54
- <span className="text-lg">{config.icon}</span>
55
- <Badge variant={config.variant}>{config.label}</Badge>
56
- </div>
57
- <time className="text-sm text-muted-foreground">{formattedDate}</time>
58
- </div>
59
- <CardTitle className="text-xl group-hover:text-primary transition-colors line-clamp-2">
60
- <a href={href} className="hover:underline">
61
- {title}
62
- </a>
63
- </CardTitle>
64
- {description && (
65
- <CardDescription className="line-clamp-2 mt-2">
66
- {description}
67
- </CardDescription>
68
- )}
69
- </CardHeader>
70
-
71
- <CardContent className="pt-0">
72
- <div className="flex items-center justify-between">
73
- <div className="flex items-center gap-2">
74
- {author && (
75
- <span className="text-sm text-muted-foreground">by {author}</span>
76
- )}
77
- {tags && tags.length > 0 && (
78
- <div className="flex gap-1">
79
- {tags.slice(0, 2).map((tag) => (
80
- <Badge key={tag} variant="outline" className="text-xs">
81
- {tag}
82
- </Badge>
83
- ))}
84
- </div>
85
- )}
86
- </div>
87
- <ButtonLink href={href} variant="ghost" size="sm">
88
- Read more →
89
- </ButtonLink>
90
- </div>
91
- </CardContent>
92
- </Card>
93
- );
94
- };
@@ -1,96 +0,0 @@
1
- import React, { useMemo } from 'react';
2
- import moment from 'moment';
3
-
4
- import { cn } from '@djangocfg/ui-core/lib';
5
-
6
- import { ArticleCard, ArticleType } from './ArticleCard';
7
-
8
- export interface Article {
9
- title: string;
10
- description?: string;
11
- date: string;
12
- type: ArticleType;
13
- href: string;
14
- author?: string;
15
- tags?: string[];
16
- featured?: boolean;
17
- }
18
-
19
- interface ArticleListProps {
20
- articles: Article[];
21
- title?: string;
22
- description?: string;
23
- showFeatured?: boolean;
24
- columns?: 1 | 2 | 3;
25
- className?: string;
26
- }
27
-
28
- export const ArticleList: React.FC<ArticleListProps> = ({
29
- articles,
30
- title,
31
- description,
32
- showFeatured = true,
33
- columns = 2,
34
- className,
35
- }) => {
36
- // Sort by date, newest first, and separate featured/regular articles
37
- const { featuredArticles, regularArticles } = useMemo(() => {
38
- const sorted = [...articles].sort(
39
- (a, b) => moment(b.date).valueOf() - moment(a.date).valueOf()
40
- );
41
- return {
42
- featuredArticles: showFeatured ? sorted.filter((a) => a.featured) : [],
43
- regularArticles: showFeatured ? sorted.filter((a) => !a.featured) : sorted,
44
- };
45
- }, [articles, showFeatured]);
46
-
47
- const gridCols = {
48
- 1: 'grid-cols-1',
49
- 2: 'grid-cols-1 md:grid-cols-2',
50
- 3: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
51
- };
52
-
53
- return (
54
- <section className={cn('py-8', className)}>
55
- {(title || description) && (
56
- <div className="mb-8">
57
- {title && (
58
- <h2 className="text-3xl font-bold tracking-tight">{title}</h2>
59
- )}
60
- {description && (
61
- <p className="mt-2 text-lg text-muted-foreground">{description}</p>
62
- )}
63
- </div>
64
- )}
65
-
66
- {/* Featured Articles */}
67
- {featuredArticles.length > 0 && (
68
- <div className="mb-8">
69
- <h3 className="text-sm font-semibold uppercase tracking-wider text-muted-foreground mb-4">
70
- Featured
71
- </h3>
72
- <div className="grid gap-4">
73
- {featuredArticles.map((article, index) => (
74
- <ArticleCard key={index} {...article} featured />
75
- ))}
76
- </div>
77
- </div>
78
- )}
79
-
80
- {/* Regular Articles */}
81
- {regularArticles.length > 0 && (
82
- <div className={cn('grid gap-4', gridCols[columns])}>
83
- {regularArticles.map((article, index) => (
84
- <ArticleCard key={index} {...article} />
85
- ))}
86
- </div>
87
- )}
88
-
89
- {articles.length === 0 && (
90
- <div className="text-center py-12 text-muted-foreground">
91
- No articles yet. Check back soon!
92
- </div>
93
- )}
94
- </section>
95
- );
96
- };
@@ -1,136 +0,0 @@
1
- "use client"
2
-
3
- import Link from 'next/link';
4
- import React from 'react';
5
-
6
- import { Button } from '@djangocfg/ui-core/components';
7
- import { cn } from '@djangocfg/ui-core/lib';
8
-
9
- interface CTAButton {
10
- label: string;
11
- href?: string;
12
- onClick?: () => void;
13
- variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
14
- size?: 'default' | 'sm' | 'lg' | 'icon';
15
- }
16
-
17
- interface CTASectionProps {
18
- title: string;
19
- subtitle?: string;
20
- primaryCTA?: CTAButton;
21
- secondaryCTA?: CTAButton;
22
- background?: 'default' | 'muted' | 'primary' | 'gradient';
23
- className?: string;
24
- children?: React.ReactNode;
25
- }
26
-
27
- export const CTASection: React.FC<CTASectionProps> = ({
28
- title,
29
- subtitle,
30
- primaryCTA,
31
- secondaryCTA,
32
- background = 'default',
33
- className,
34
- children
35
- }) => {
36
- // Simple Tailwind 4 classes - no custom utilities
37
- const backgroundClasses = {
38
- default: 'bg-background',
39
- muted: 'bg-muted/30',
40
- primary: 'bg-primary/5',
41
- gradient: 'bg-gradient-to-b from-background via-primary/5 to-background',
42
- };
43
-
44
- return (
45
- <section
46
- className={cn(
47
- 'relative py-16 sm:py-20 md:py-24 lg:py-32',
48
- backgroundClasses[background],
49
- className
50
- )}
51
- >
52
- {/* Simple decorative background - using only Tailwind classes */}
53
- {background === 'gradient' && (
54
- <div className="absolute inset-0 -z-10 overflow-hidden pointer-events-none" aria-hidden="true">
55
- <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-4xl aspect-square bg-primary/10 rounded-full blur-3xl" />
56
- </div>
57
- )}
58
-
59
- <div className="container max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
60
- <div className="text-center space-y-6 sm:space-y-8">
61
- {/* Title */}
62
- <h2 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight">
63
- {title}
64
- </h2>
65
-
66
- {/* Subtitle */}
67
- {subtitle && (
68
- <p className="text-base sm:text-lg md:text-xl text-muted-foreground max-w-2xl mx-auto leading-relaxed">
69
- {subtitle}
70
- </p>
71
- )}
72
-
73
- {/* CTA Buttons */}
74
- {(primaryCTA || secondaryCTA) && (
75
- <div className="flex flex-col sm:flex-row gap-3 sm:gap-4 justify-center items-center pt-4">
76
- {primaryCTA && (
77
- primaryCTA.onClick ? (
78
- <Button
79
- onClick={primaryCTA.onClick}
80
- variant={primaryCTA.variant || 'default'}
81
- size={primaryCTA.size || 'lg'}
82
- className="w-full sm:w-auto"
83
- >
84
- {primaryCTA.label}
85
- </Button>
86
- ) : (
87
- <Button
88
- asChild
89
- variant={primaryCTA.variant || 'default'}
90
- size={primaryCTA.size || 'lg'}
91
- className="w-full sm:w-auto"
92
- >
93
- <Link href={primaryCTA.href || '#'}>
94
- {primaryCTA.label}
95
- </Link>
96
- </Button>
97
- )
98
- )}
99
-
100
- {secondaryCTA && (
101
- secondaryCTA.onClick ? (
102
- <Button
103
- onClick={secondaryCTA.onClick}
104
- variant={secondaryCTA.variant || 'outline'}
105
- size={secondaryCTA.size || 'lg'}
106
- className="w-full sm:w-auto"
107
- >
108
- {secondaryCTA.label}
109
- </Button>
110
- ) : (
111
- <Button
112
- asChild
113
- variant={secondaryCTA.variant || 'outline'}
114
- size={secondaryCTA.size || 'lg'}
115
- className="w-full sm:w-auto"
116
- >
117
- <Link href={secondaryCTA.href || '#'}>
118
- {secondaryCTA.label}
119
- </Link>
120
- </Button>
121
- )
122
- )}
123
- </div>
124
- )}
125
-
126
- {/* Optional children content */}
127
- {children && (
128
- <div className="pt-8 sm:pt-12">
129
- {children}
130
- </div>
131
- )}
132
- </div>
133
- </div>
134
- </section>
135
- );
136
- };