@djangocfg/ui-nextjs 2.1.320 → 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,176 +0,0 @@
1
- import React from 'react';
2
-
3
- import {
4
- Card, CardContent, CardDescription, CardHeader, CardTitle
5
- } from '@djangocfg/ui-core/components';
6
- import { cn } from '@djangocfg/ui-core/lib';
7
-
8
- interface Feature {
9
- icon?: React.ReactNode;
10
- title: string;
11
- description: string;
12
- gradient?: string;
13
- }
14
-
15
- interface FeatureSectionProps {
16
- title?: string;
17
- subtitle?: string;
18
- features: Feature[];
19
- columns?: 1 | 2 | 3 | 4 | 6;
20
- className?: string;
21
- background?: 'dark' | 'card' | 'gradient' | 'none';
22
- /** Compact variant: smaller icons, no cards, minimal spacing */
23
- variant?: 'default' | 'compact';
24
- }
25
-
26
- export const FeatureSection: React.FC<FeatureSectionProps> = ({
27
- title,
28
- subtitle,
29
- features,
30
- columns = 3,
31
- className,
32
- background = 'dark',
33
- variant = 'default'
34
- }) => {
35
- const getGridClasses = () => {
36
- if (variant === 'compact') {
37
- switch (columns) {
38
- case 2:
39
- return 'grid-cols-2';
40
- case 3:
41
- return 'grid-cols-2 md:grid-cols-3';
42
- case 4:
43
- return 'grid-cols-2 md:grid-cols-4';
44
- case 6:
45
- return 'grid-cols-2 md:grid-cols-3 lg:grid-cols-6';
46
- default:
47
- return 'grid-cols-2 md:grid-cols-3';
48
- }
49
- }
50
- switch (columns) {
51
- case 1:
52
- return 'grid-cols-1 max-w-2xl mx-auto';
53
- case 2:
54
- return 'grid-cols-1 sm:grid-cols-2';
55
- case 3:
56
- return 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3';
57
- case 4:
58
- return 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4';
59
- case 6:
60
- return 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-6';
61
- default:
62
- return 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3';
63
- }
64
- };
65
-
66
- const getBackgroundClasses = () => {
67
- switch (background) {
68
- case 'dark':
69
- return 'bg-muted/30';
70
- case 'card':
71
- return 'bg-card';
72
- case 'gradient':
73
- return 'bg-gradient-to-b from-background to-muted/20';
74
- case 'none':
75
- return '';
76
- default:
77
- return 'bg-background';
78
- }
79
- };
80
-
81
- // Compact variant render
82
- if (variant === 'compact') {
83
- return (
84
- <section className={cn('py-12 md:py-16', getBackgroundClasses(), className)}>
85
- <div className="container max-w-5xl mx-auto px-4">
86
- {(title || subtitle) && (
87
- <div className="text-center mb-8">
88
- {title && (
89
- <h2 className="text-xl sm:text-2xl font-bold text-foreground mb-2">
90
- {title}
91
- </h2>
92
- )}
93
- {subtitle && (
94
- <p className="text-sm text-muted-foreground max-w-2xl mx-auto">
95
- {subtitle}
96
- </p>
97
- )}
98
- </div>
99
- )}
100
- <div className={cn('grid gap-4', getGridClasses())}>
101
- {features.map((feature, index) => (
102
- <div key={index} className="text-center p-4">
103
- {feature.icon && (
104
- <div
105
- className={cn(
106
- 'w-10 h-10 mx-auto mb-3 rounded-lg flex items-center justify-center',
107
- feature.gradient || 'bg-primary/10 text-primary'
108
- )}
109
- >
110
- <div className="[&>svg]:w-5 [&>svg]:h-5">
111
- {feature.icon}
112
- </div>
113
- </div>
114
- )}
115
- <h3 className="font-medium text-sm mb-1">{feature.title}</h3>
116
- <p className="text-xs text-muted-foreground">{feature.description}</p>
117
- </div>
118
- ))}
119
- </div>
120
- </div>
121
- </section>
122
- );
123
- }
124
-
125
- // Default variant render
126
- return (
127
- <section className={cn('py-12 sm:py-16 lg:py-24', getBackgroundClasses(), className)}>
128
- <div className="w-full px-4 sm:px-6 lg:px-8">
129
- {(title || subtitle) && (
130
- <div className="text-center mb-12 sm:mb-16">
131
- {title && (
132
- <h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold text-foreground mb-4 sm:mb-6">
133
- {title}
134
- </h2>
135
- )}
136
- {subtitle && (
137
- <p className="text-base sm:text-lg md:text-xl text-muted-foreground max-w-3xl mx-auto px-2">
138
- {subtitle}
139
- </p>
140
- )}
141
- </div>
142
- )}
143
-
144
- <div className={cn('grid gap-6 sm:gap-8', getGridClasses())}>
145
- {features.map((feature, index) => (
146
- <Card
147
- key={index}
148
- className="h-full hover:shadow-lg transition-all duration-300 backdrop-blur-sm border-border/50 hover:border-primary/30 group"
149
- >
150
- <CardHeader className="pb-4">
151
- {feature.icon && (
152
- <div className={cn(
153
- 'w-12 h-12 sm:w-16 sm:h-16 rounded-lg flex items-center justify-center mb-4 sm:mb-6 group-hover:scale-110 transition-all duration-300',
154
- feature.gradient || 'bg-primary/10 text-primary group-hover:bg-primary/20'
155
- )}>
156
- <div className="text-xl sm:text-2xl">
157
- {feature.icon}
158
- </div>
159
- </div>
160
- )}
161
- <CardTitle className="text-lg sm:text-xl text-foreground group-hover:text-primary transition-colors duration-300">
162
- {feature.title}
163
- </CardTitle>
164
- </CardHeader>
165
- <CardContent>
166
- <CardDescription className="text-sm sm:text-base text-muted-foreground leading-relaxed">
167
- {feature.description}
168
- </CardDescription>
169
- </CardContent>
170
- </Card>
171
- ))}
172
- </div>
173
- </div>
174
- </section>
175
- );
176
- };
@@ -1,102 +0,0 @@
1
- import React from 'react';
2
-
3
- import { ButtonLink } from '@djangocfg/ui-core/components';
4
- import { cn } from '@djangocfg/ui-core/lib';
5
-
6
- interface HeroProps {
7
- title: string;
8
- subtitle?: string;
9
- description?: string;
10
- primaryAction?: {
11
- label: string;
12
- href: string;
13
- variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
14
- };
15
- secondaryAction?: {
16
- label: string;
17
- href: string;
18
- variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
19
- };
20
- background?: 'gradient' | 'solid' | 'image' | 'dark';
21
- className?: string;
22
- children?: React.ReactNode;
23
- }
24
-
25
- export const Hero: React.FC<HeroProps> = ({
26
- title,
27
- subtitle,
28
- description,
29
- primaryAction,
30
- secondaryAction,
31
- background = 'dark',
32
- className,
33
- children
34
- }) => {
35
- // Simple Tailwind 4 classes only - no custom utilities
36
- const backgroundClasses = {
37
- gradient: 'bg-gradient-to-b from-primary/10 to-background',
38
- solid: 'bg-primary text-primary-foreground',
39
- image: 'bg-cover bg-center bg-no-repeat text-white',
40
- dark: 'bg-background text-foreground',
41
- };
42
-
43
- return (
44
- <section className={cn(
45
- 'relative py-16 sm:py-20 md:py-24 lg:py-32',
46
- backgroundClasses[background],
47
- className
48
- )}>
49
- <div className="container max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
50
- <div className="text-center max-w-4xl mx-auto">
51
- {subtitle && (
52
- <p className="text-base sm:text-lg md:text-xl mb-4 sm:mb-6 text-muted-foreground font-medium">
53
- {subtitle}
54
- </p>
55
- )}
56
-
57
- <h1 className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold mb-6 sm:mb-8 leading-tight">
58
- {title}
59
- </h1>
60
-
61
- {description && (
62
- <p className="text-lg sm:text-xl md:text-2xl mb-8 sm:mb-10 md:mb-12 text-muted-foreground leading-relaxed max-w-3xl mx-auto">
63
- {description}
64
- </p>
65
- )}
66
-
67
- {(primaryAction || secondaryAction) && (
68
- <div className="flex flex-col sm:flex-row gap-3 sm:gap-4 justify-center">
69
- {primaryAction && (
70
- <ButtonLink
71
- href={primaryAction.href}
72
- variant={primaryAction.variant || 'default'}
73
- size="huge"
74
- className="w-full sm:w-auto"
75
- >
76
- {primaryAction.label}
77
- </ButtonLink>
78
- )}
79
-
80
- {secondaryAction && (
81
- <ButtonLink
82
- href={secondaryAction.href}
83
- variant={secondaryAction.variant || 'outline'}
84
- size="huge"
85
- className="w-full sm:w-auto"
86
- >
87
- {secondaryAction.label}
88
- </ButtonLink>
89
- )}
90
- </div>
91
- )}
92
-
93
- {children && (
94
- <div className="mt-12 sm:mt-14 md:mt-16 flex justify-center">
95
- {children}
96
- </div>
97
- )}
98
- </div>
99
- </div>
100
- </section>
101
- );
102
- };
@@ -1,119 +0,0 @@
1
- "use client"
2
-
3
- import { Mail } from 'lucide-react';
4
- import React from 'react';
5
-
6
- import { Button, Input } from '@djangocfg/ui-core/components';
7
- import { cn } from '@djangocfg/ui-core/lib';
8
-
9
- interface NewsletterSectionProps {
10
- title?: string;
11
- description?: string;
12
- placeholder?: string;
13
- buttonText?: string;
14
- disclaimer?: string;
15
- background?: 'default' | 'muted' | 'primary' | 'gradient';
16
- className?: string;
17
- onSubmit?: (email: string) => void;
18
- }
19
-
20
- /**
21
- * NewsletterSection - Email subscription block
22
- * Fully responsive and theme-aware (light/dark mode)
23
- * Follows Tailwind CSS v4 canons - no custom utilities
24
- */
25
- export const NewsletterSection: React.FC<NewsletterSectionProps> = ({
26
- title = "Stay Updated with Our Newsletter",
27
- description = "Get the latest insights on AI, technology, and software development delivered to your inbox.",
28
- placeholder = "Enter your email address",
29
- buttonText = "Subscribe",
30
- disclaimer = "No spam, unsubscribe at any time",
31
- background = 'muted',
32
- className,
33
- onSubmit
34
- }) => {
35
- const [email, setEmail] = React.useState('');
36
-
37
- const handleSubmit = (e: React.FormEvent) => {
38
- e.preventDefault();
39
- if (email && onSubmit) {
40
- onSubmit(email);
41
- setEmail('');
42
- }
43
- };
44
-
45
- // Simple Tailwind 4 classes only - no custom utilities
46
- // All variants work in both light and dark themes
47
- const backgroundClasses = {
48
- default: 'bg-background',
49
- muted: 'bg-muted/50',
50
- primary: 'bg-primary/5',
51
- gradient: 'bg-gradient-to-b from-background via-primary/5 to-background',
52
- };
53
-
54
- return (
55
- <section className={cn(
56
- 'relative py-16 sm:py-20 md:py-24 lg:py-32',
57
- backgroundClasses[background],
58
- className
59
- )}>
60
- {/* Decorative background for gradient variant only */}
61
- {background === 'gradient' && (
62
- <div className="absolute inset-0 -z-10 overflow-hidden pointer-events-none" aria-hidden="true">
63
- <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" />
64
- </div>
65
- )}
66
-
67
- <div className="container max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
68
- <div className="text-center max-w-3xl mx-auto">
69
- {/* Title */}
70
- <h2 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight mb-4 sm:mb-6">
71
- {title}
72
- </h2>
73
-
74
- {/* Description */}
75
- {description && (
76
- <p className="text-base sm:text-lg md:text-xl text-muted-foreground leading-relaxed mb-8 sm:mb-10 md:mb-12">
77
- {description}
78
- </p>
79
- )}
80
-
81
- {/* Form */}
82
- <form onSubmit={handleSubmit} className="max-w-md mx-auto">
83
- <div className="flex flex-col sm:flex-row gap-3 sm:gap-4">
84
- {/* Email Input */}
85
- <div className="relative flex-1">
86
- <Mail className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" />
87
- <Input
88
- type="email"
89
- placeholder={placeholder}
90
- value={email}
91
- onChange={(e) => setEmail(e.target.value)}
92
- className="pl-10 h-12 w-full"
93
- required
94
- aria-label="Email address"
95
- />
96
- </div>
97
-
98
- {/* Submit Button */}
99
- <Button
100
- type="submit"
101
- size="lg"
102
- className="h-12 w-full sm:w-auto sm:min-w-[120px]"
103
- >
104
- {buttonText}
105
- </Button>
106
- </div>
107
-
108
- {/* Disclaimer */}
109
- {disclaimer && (
110
- <p className="text-xs sm:text-sm text-muted-foreground mt-3 text-center">
111
- {disclaimer}
112
- </p>
113
- )}
114
- </form>
115
- </div>
116
- </div>
117
- </section>
118
- );
119
- };
@@ -1,95 +0,0 @@
1
- 'use client';
2
-
3
- import React from 'react';
4
- import { cn } from '@djangocfg/ui-core/lib';
5
- import { SplitHeroContent } from './SplitHeroContent';
6
- import { SplitHeroMedia } from './SplitHeroMedia';
7
- import type { SplitHeroProps } from './types';
8
-
9
- const MAX_WIDTH_CLASSES = {
10
- sm: 'max-w-3xl',
11
- md: 'max-w-4xl',
12
- lg: 'max-w-5xl',
13
- xl: 'max-w-6xl',
14
- full: 'max-w-7xl',
15
- };
16
-
17
- const BACKGROUND_CLASSES = {
18
- none: '',
19
- muted: 'bg-muted/30',
20
- gradient: 'bg-gradient-to-br from-primary/5 via-background to-secondary/5',
21
- };
22
-
23
- export const SplitHero: React.FC<SplitHeroProps> = ({
24
- badge,
25
- title,
26
- titleGradient,
27
- subtitle,
28
- features,
29
- primaryAction,
30
- secondaryAction,
31
- media,
32
- layout = 'text-left',
33
- mobileOrder = 'text-first',
34
- align = 'center',
35
- maxWidth = 'xl',
36
- background = 'none',
37
- className,
38
- }) => {
39
- const isTextLeft = layout === 'text-left';
40
- const isMediaFirst = mobileOrder === 'media-first';
41
-
42
- return (
43
- <section
44
- className={cn(
45
- 'py-12 md:py-16 lg:py-20',
46
- BACKGROUND_CLASSES[background],
47
- className
48
- )}
49
- >
50
- <div className={cn('mx-auto px-4', MAX_WIDTH_CLASSES[maxWidth])}>
51
- <div
52
- className={cn(
53
- 'grid gap-8 lg:gap-12 items-center',
54
- media ? 'lg:grid-cols-2' : 'lg:grid-cols-1 max-w-3xl mx-auto'
55
- )}
56
- >
57
- {/* Content - order changes based on layout and mobile settings */}
58
- <div
59
- className={cn(
60
- // Desktop order based on layout
61
- isTextLeft ? 'lg:order-1' : 'lg:order-2',
62
- // Mobile order based on mobileOrder
63
- isMediaFirst ? 'order-2' : 'order-1'
64
- )}
65
- >
66
- <SplitHeroContent
67
- badge={badge}
68
- title={title}
69
- titleGradient={titleGradient}
70
- subtitle={subtitle}
71
- features={features}
72
- primaryAction={primaryAction}
73
- secondaryAction={secondaryAction}
74
- align={align}
75
- />
76
- </div>
77
-
78
- {/* Media */}
79
- {media && (
80
- <div
81
- className={cn(
82
- // Desktop order based on layout
83
- isTextLeft ? 'lg:order-2' : 'lg:order-1',
84
- // Mobile order based on mobileOrder
85
- isMediaFirst ? 'order-1' : 'order-2'
86
- )}
87
- >
88
- <SplitHeroMedia media={media} />
89
- </div>
90
- )}
91
- </div>
92
- </div>
93
- </section>
94
- );
95
- };
@@ -1,117 +0,0 @@
1
- 'use client';
2
-
3
- import React from 'react';
4
- import { ArrowRight, Sparkles } from 'lucide-react';
5
- import { ButtonLink } from '@djangocfg/ui-core/components';
6
- import { cn } from '@djangocfg/ui-core/lib';
7
- import type { SplitHeroBadge, SplitHeroAction, SplitHeroFeature } from './types';
8
-
9
- interface SplitHeroContentProps {
10
- badge?: SplitHeroBadge;
11
- title: string;
12
- titleGradient?: string;
13
- subtitle?: string;
14
- features?: SplitHeroFeature[];
15
- primaryAction?: SplitHeroAction;
16
- secondaryAction?: SplitHeroAction;
17
- align?: 'top' | 'center' | 'bottom';
18
- className?: string;
19
- }
20
-
21
- export const SplitHeroContent: React.FC<SplitHeroContentProps> = ({
22
- badge,
23
- title,
24
- titleGradient,
25
- subtitle,
26
- features = [],
27
- primaryAction,
28
- secondaryAction,
29
- align = 'center',
30
- className,
31
- }) => {
32
- const alignClasses = {
33
- top: 'justify-start',
34
- center: 'justify-center',
35
- bottom: 'justify-end',
36
- };
37
-
38
- return (
39
- <div className={cn('flex flex-col', alignClasses[align], className)}>
40
- {/* Badge */}
41
- {badge && (
42
- <div className="inline-flex items-center gap-2 px-3 py-1.5 bg-primary/10 text-primary rounded-full text-sm font-medium mb-4 w-fit border border-primary/20">
43
- {badge.icon || <Sparkles className="w-3.5 h-3.5" />}
44
- {badge.text}
45
- </div>
46
- )}
47
-
48
- {/* Title */}
49
- <h1 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-foreground mb-4 leading-tight">
50
- {title}
51
- {titleGradient && (
52
- <>
53
- {' '}
54
- <span className="bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
55
- {titleGradient}
56
- </span>
57
- </>
58
- )}
59
- </h1>
60
-
61
- {/* Subtitle */}
62
- {subtitle && (
63
- <p className="text-lg text-muted-foreground mb-6 leading-relaxed">
64
- {subtitle}
65
- </p>
66
- )}
67
-
68
- {/* Features */}
69
- {features.length > 0 && (
70
- <div className="flex flex-wrap gap-3 mb-6">
71
- {features.map((feature, index) => (
72
- <div
73
- key={index}
74
- className="flex items-center gap-2 text-sm text-muted-foreground"
75
- >
76
- {feature.icon && (
77
- <span className="text-primary">{feature.icon}</span>
78
- )}
79
- <span>{feature.text}</span>
80
- </div>
81
- ))}
82
- </div>
83
- )}
84
-
85
- {/* Actions */}
86
- {(primaryAction || secondaryAction) && (
87
- <div className="flex flex-wrap gap-3">
88
- {primaryAction && (
89
- <ButtonLink
90
- href={primaryAction.href || '#'}
91
- onClick={primaryAction.onClick}
92
- variant={primaryAction.variant || 'default'}
93
- size="lg"
94
- className="group"
95
- >
96
- {primaryAction.label}
97
- {primaryAction.icon || (
98
- <ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
99
- )}
100
- </ButtonLink>
101
- )}
102
- {secondaryAction && (
103
- <ButtonLink
104
- href={secondaryAction.href || '#'}
105
- onClick={secondaryAction.onClick}
106
- variant={secondaryAction.variant || 'outline'}
107
- size="lg"
108
- >
109
- {secondaryAction.label}
110
- {secondaryAction.icon}
111
- </ButtonLink>
112
- )}
113
- </div>
114
- )}
115
- </div>
116
- );
117
- };
@@ -1,67 +0,0 @@
1
- 'use client';
2
-
3
- import React from 'react';
4
- import Image from 'next/image';
5
- import { cn } from '@djangocfg/ui-core/lib';
6
- import { VideoPlayer } from '@djangocfg/ui-tools';
7
- import type { SplitHeroMedia as SplitHeroMediaType } from './types';
8
-
9
- interface SplitHeroMediaProps {
10
- media: SplitHeroMediaType;
11
- className?: string;
12
- }
13
-
14
- export const SplitHeroMedia: React.FC<SplitHeroMediaProps> = ({
15
- media,
16
- className,
17
- }) => {
18
- const containerClass = cn(
19
- 'relative w-full rounded-xl overflow-hidden',
20
- className
21
- );
22
-
23
- switch (media.type) {
24
- case 'image':
25
- return (
26
- <div className={containerClass}>
27
- <div className="relative aspect-[4/3] w-full">
28
- <Image
29
- src={media.src}
30
- alt={media.alt || ''}
31
- fill
32
- className="object-cover"
33
- sizes="(max-width: 768px) 100vw, 50vw"
34
- />
35
- </div>
36
- </div>
37
- );
38
-
39
- case 'video':
40
- return (
41
- <div className={containerClass}>
42
- <VideoPlayer
43
- source={{
44
- type: 'url',
45
- url: media.url,
46
- title: media.title,
47
- poster: media.poster,
48
- }}
49
- theme="modern"
50
- aspectRatio={16 / 9}
51
- autoPlay={media.autoplay}
52
- muted={media.muted ?? media.autoplay}
53
- />
54
- </div>
55
- );
56
-
57
- case 'custom':
58
- return (
59
- <div className={containerClass}>
60
- {media.content}
61
- </div>
62
- );
63
-
64
- default:
65
- return null;
66
- }
67
- };
@@ -1,13 +0,0 @@
1
- export { SplitHero } from './SplitHero';
2
- export { SplitHeroContent } from './SplitHeroContent';
3
- export { SplitHeroMedia } from './SplitHeroMedia';
4
- export type {
5
- SplitHeroProps,
6
- SplitHeroMedia as SplitHeroMediaType,
7
- SplitHeroMediaImage,
8
- SplitHeroMediaVideo,
9
- SplitHeroMediaCustom,
10
- SplitHeroBadge,
11
- SplitHeroAction,
12
- SplitHeroFeature,
13
- } from './types';