@djangocfg/ui-nextjs 2.1.42 → 2.1.44

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 (76) hide show
  1. package/package.json +4 -4
  2. package/src/animations/AnimatedBackground.tsx +1 -0
  3. package/src/blocks/ArticleCard.tsx +6 -7
  4. package/src/blocks/ArticleList.tsx +14 -13
  5. package/src/blocks/FeatureSection.tsx +87 -15
  6. package/src/blocks/SplitHero/SplitHero.tsx +95 -0
  7. package/src/blocks/SplitHero/SplitHeroContent.tsx +117 -0
  8. package/src/blocks/SplitHero/SplitHeroMedia.tsx +66 -0
  9. package/src/blocks/SplitHero/index.ts +13 -0
  10. package/src/blocks/SplitHero/types.ts +75 -0
  11. package/src/blocks/SuperHero.tsx +10 -4
  12. package/src/blocks/index.ts +1 -0
  13. package/src/components/breadcrumb-navigation.tsx +6 -9
  14. package/src/components/breadcrumb.tsx +6 -5
  15. package/src/components/button-download.tsx +7 -5
  16. package/src/components/dropdown-menu.tsx +6 -5
  17. package/src/components/markdown/MarkdownMessage.tsx +5 -2
  18. package/src/components/menubar.tsx +6 -5
  19. package/src/components/multi-select-pro/async.tsx +8 -18
  20. package/src/components/multi-select-pro/index.tsx +8 -18
  21. package/src/components/navigation-menu.tsx +7 -6
  22. package/src/components/otp/index.tsx +7 -9
  23. package/src/components/otp/use-otp-input.ts +2 -1
  24. package/src/components/pagination-static.tsx +7 -10
  25. package/src/components/pagination.tsx +6 -5
  26. package/src/components/phone-input.tsx +11 -10
  27. package/src/components/sidebar.tsx +11 -21
  28. package/src/components/sonner.tsx +2 -2
  29. package/src/components/ssr-pagination.tsx +6 -9
  30. package/src/hooks/useDeviceDetect.ts +1 -1
  31. package/src/hooks/useHotkey.ts +2 -1
  32. package/src/hooks/useLocalStorage.ts +1 -1
  33. package/src/hooks/useQueryParams.ts +1 -1
  34. package/src/theme/ForceTheme.tsx +1 -0
  35. package/src/theme/ThemeProvider.tsx +1 -0
  36. package/src/theme/ThemeToggle.tsx +4 -2
  37. package/src/tools/JsonForm/JsonSchemaForm.tsx +12 -21
  38. package/src/tools/JsonForm/examples/BotConfigExample.tsx +7 -3
  39. package/src/tools/JsonForm/examples/RealBotConfigExample.tsx +7 -3
  40. package/src/tools/JsonForm/templates/ArrayFieldItemTemplate.tsx +2 -1
  41. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +3 -2
  42. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +3 -2
  43. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +3 -2
  44. package/src/tools/JsonForm/templates/FieldTemplate.tsx +2 -1
  45. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +4 -7
  46. package/src/tools/JsonForm/utils.ts +2 -1
  47. package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +2 -1
  48. package/src/tools/JsonForm/widgets/ColorWidget.tsx +3 -2
  49. package/src/tools/JsonForm/widgets/NumberWidget.tsx +3 -2
  50. package/src/tools/JsonForm/widgets/SelectWidget.tsx +4 -7
  51. package/src/tools/JsonForm/widgets/SliderWidget.tsx +4 -3
  52. package/src/tools/JsonForm/widgets/SwitchWidget.tsx +2 -1
  53. package/src/tools/JsonForm/widgets/TextWidget.tsx +3 -2
  54. package/src/tools/JsonTree/index.tsx +2 -1
  55. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +1 -0
  56. package/src/tools/LottiePlayer/index.tsx +1 -0
  57. package/src/tools/LottiePlayer/useLottie.ts +2 -1
  58. package/src/tools/Mermaid/Mermaid.client.tsx +3 -2
  59. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +1 -0
  60. package/src/tools/Mermaid/hooks/useMermaidFullscreen.ts +1 -1
  61. package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +3 -2
  62. package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +22 -16
  63. package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +12 -4
  64. package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +4 -2
  65. package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +3 -1
  66. package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +9 -6
  67. package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +7 -7
  68. package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +8 -4
  69. package/src/tools/OpenapiViewer/components/VersionSelector.tsx +7 -3
  70. package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +9 -3
  71. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +3 -7
  72. package/src/tools/OpenapiViewer/index.tsx +3 -1
  73. package/src/tools/PrettyCode/PrettyCode.client.tsx +2 -0
  74. package/src/tools/PrettyCode/index.tsx +1 -0
  75. package/src/tools/VideoPlayer/VideoControls.tsx +4 -3
  76. package/src/tools/VideoPlayer/VideoPlayer.tsx +9 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-nextjs",
3
- "version": "2.1.42",
3
+ "version": "2.1.44",
4
4
  "description": "Next.js UI component library with Radix UI primitives, Tailwind CSS styling, charts, and form components",
5
5
  "keywords": [
6
6
  "ui-components",
@@ -58,8 +58,8 @@
58
58
  "check": "tsc --noEmit"
59
59
  },
60
60
  "peerDependencies": {
61
- "@djangocfg/api": "^2.1.42",
62
- "@djangocfg/ui-core": "^2.1.42",
61
+ "@djangocfg/api": "^2.1.44",
62
+ "@djangocfg/ui-core": "^2.1.44",
63
63
  "@types/react": "^19.1.0",
64
64
  "@types/react-dom": "^19.1.0",
65
65
  "consola": "^3.4.2",
@@ -106,7 +106,7 @@
106
106
  "vidstack": "next"
107
107
  },
108
108
  "devDependencies": {
109
- "@djangocfg/typescript-config": "^2.1.42",
109
+ "@djangocfg/typescript-config": "^2.1.44",
110
110
  "@types/node": "^24.7.2",
111
111
  "eslint": "^9.37.0",
112
112
  "tailwindcss-animate": "1.0.7",
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import React, { useMemo } from 'react';
4
+
4
5
  import { cn } from '@djangocfg/ui-core/lib';
5
6
 
6
7
  export type BackgroundVariant =
@@ -1,6 +1,10 @@
1
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';
2
7
  import { cn } from '@djangocfg/ui-core/lib';
3
- import { Badge, Card, CardContent, CardDescription, CardHeader, CardTitle, ButtonLink } from '@djangocfg/ui-core/components';
4
8
 
5
9
  export type ArticleType = 'security' | 'release' | 'announcement' | 'feature';
6
10
 
@@ -35,12 +39,7 @@ export const ArticleCard: React.FC<ArticleCardProps> = ({
35
39
  className,
36
40
  }) => {
37
41
  const config = typeConfig[type];
38
-
39
- const formattedDate = new Date(date).toLocaleDateString('en-US', {
40
- year: 'numeric',
41
- month: 'long',
42
- day: 'numeric',
43
- });
42
+ const formattedDate = moment(date).format('MMMM D, YYYY');
44
43
 
45
44
  return (
46
45
  <Card className={cn(
@@ -1,5 +1,8 @@
1
- import React from 'react';
1
+ import React, { useMemo } from 'react';
2
+ import moment from 'moment';
3
+
2
4
  import { cn } from '@djangocfg/ui-core/lib';
5
+
3
6
  import { ArticleCard, ArticleType } from './ArticleCard';
4
7
 
5
8
  export interface Article {
@@ -30,18 +33,16 @@ export const ArticleList: React.FC<ArticleListProps> = ({
30
33
  columns = 2,
31
34
  className,
32
35
  }) => {
33
- // Sort by date, newest first
34
- const sortedArticles = [...articles].sort(
35
- (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
36
- );
37
-
38
- // Separate featured and regular articles
39
- const featuredArticles = showFeatured
40
- ? sortedArticles.filter((a) => a.featured)
41
- : [];
42
- const regularArticles = showFeatured
43
- ? sortedArticles.filter((a) => !a.featured)
44
- : sortedArticles;
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]);
45
46
 
46
47
  const gridCols = {
47
48
  1: 'grid-cols-1',
@@ -1,6 +1,8 @@
1
1
  import React from 'react';
2
2
 
3
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@djangocfg/ui-core/components';
3
+ import {
4
+ Card, CardContent, CardDescription, CardHeader, CardTitle
5
+ } from '@djangocfg/ui-core/components';
4
6
  import { cn } from '@djangocfg/ui-core/lib';
5
7
 
6
8
  interface Feature {
@@ -11,12 +13,14 @@ interface Feature {
11
13
  }
12
14
 
13
15
  interface FeatureSectionProps {
14
- title: string;
16
+ title?: string;
15
17
  subtitle?: string;
16
18
  features: Feature[];
17
- columns?: 1 | 2 | 3 | 4;
19
+ columns?: 1 | 2 | 3 | 4 | 6;
18
20
  className?: string;
19
- background?: 'dark' | 'card' | 'gradient';
21
+ background?: 'dark' | 'card' | 'gradient' | 'none';
22
+ /** Compact variant: smaller icons, no cards, minimal spacing */
23
+ variant?: 'default' | 'compact';
20
24
  }
21
25
 
22
26
  export const FeatureSection: React.FC<FeatureSectionProps> = ({
@@ -25,9 +29,24 @@ export const FeatureSection: React.FC<FeatureSectionProps> = ({
25
29
  features,
26
30
  columns = 3,
27
31
  className,
28
- background = 'dark'
32
+ background = 'dark',
33
+ variant = 'default'
29
34
  }) => {
30
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
+ }
31
50
  switch (columns) {
32
51
  case 1:
33
52
  return 'grid-cols-1 max-w-2xl mx-auto';
@@ -37,6 +56,8 @@ export const FeatureSection: React.FC<FeatureSectionProps> = ({
37
56
  return 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3';
38
57
  case 4:
39
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';
40
61
  default:
41
62
  return 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3';
42
63
  }
@@ -50,24 +71,75 @@ export const FeatureSection: React.FC<FeatureSectionProps> = ({
50
71
  return 'bg-card';
51
72
  case 'gradient':
52
73
  return 'bg-gradient-to-b from-background to-muted/20';
74
+ case 'none':
75
+ return '';
53
76
  default:
54
77
  return 'bg-background';
55
78
  }
56
79
  };
57
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
58
126
  return (
59
127
  <section className={cn('py-12 sm:py-16 lg:py-24', getBackgroundClasses(), className)}>
60
128
  <div className="w-full px-4 sm:px-6 lg:px-8">
61
- <div className="text-center mb-12 sm:mb-16">
62
- <h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold text-foreground mb-4 sm:mb-6">
63
- {title}
64
- </h2>
65
- {subtitle && (
66
- <p className="text-base sm:text-lg md:text-xl text-muted-foreground max-w-3xl mx-auto px-2">
67
- {subtitle}
68
- </p>
69
- )}
70
- </div>
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
+ )}
71
143
 
72
144
  <div className={cn('grid gap-6 sm:gap-8', getGridClasses())}>
73
145
  {features.map((feature, index) => (
@@ -0,0 +1,95 @@
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
+ };
@@ -0,0 +1,117 @@
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
+ };
@@ -0,0 +1,66 @@
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 '../../tools/VideoPlayer';
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
+ url: media.url,
45
+ title: media.title,
46
+ poster: media.poster,
47
+ }}
48
+ theme="modern"
49
+ aspectRatio={16 / 9}
50
+ autoplay={media.autoplay}
51
+ muted={media.muted ?? media.autoplay}
52
+ />
53
+ </div>
54
+ );
55
+
56
+ case 'custom':
57
+ return (
58
+ <div className={containerClass}>
59
+ {media.content}
60
+ </div>
61
+ );
62
+
63
+ default:
64
+ return null;
65
+ }
66
+ };
@@ -0,0 +1,13 @@
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';
@@ -0,0 +1,75 @@
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
+ }
@@ -3,9 +3,12 @@
3
3
  import { ArrowRight, Sparkles, Wand2 } from 'lucide-react';
4
4
  import React from 'react';
5
5
 
6
+ import {
7
+ Button, ButtonLink, CopyButton, Sticky, Tooltip, TooltipContent, TooltipTrigger
8
+ } from '@djangocfg/ui-core/components';
6
9
  import { cn } from '@djangocfg/ui-core/lib';
7
- import { Button, ButtonLink, Sticky, Tooltip, TooltipTrigger, TooltipContent, CopyButton } from '@djangocfg/ui-core/components';
8
- import { AnimatedBackground, type BackgroundVariant } from '../animations';
10
+
11
+ import { AnimatedBackground, BackgroundVariant} from '../animations';
9
12
  import { ForceTheme } from '../theme';
10
13
 
11
14
  interface HeroFeature {
@@ -105,6 +108,9 @@ export const SuperHero: React.FC<SuperHeroProps> = ({
105
108
  if (codeCommand) return [codeCommand];
106
109
  return [];
107
110
  }, [codeCommand, codeCommands]);
111
+
112
+ // Pre-compute title lines to avoid repeated split in JSX
113
+ const titleLines = React.useMemo(() => title.split('\n'), [title]);
108
114
 
109
115
  // Show background switcher in development mode or if explicitly enabled
110
116
  const isDevelopment = process.env.NODE_ENV === 'development';
@@ -141,10 +147,10 @@ export const SuperHero: React.FC<SuperHeroProps> = ({
141
147
 
142
148
  {/* Enhanced Main Title */}
143
149
  <h1 className="text-4xl sm:text-5xl lg:text-6xl xl:text-7xl font-bold text-foreground mb-6 leading-tight">
144
- {title.split('\n').map((line, index) => (
150
+ {titleLines.map((line, index) => (
145
151
  <React.Fragment key={index}>
146
152
  {line}
147
- {index < title.split('\n').length - 1 && <br />}
153
+ {index < titleLines.length - 1 && <br />}
148
154
  </React.Fragment>
149
155
  ))}
150
156
  {titleGradient && (
@@ -4,6 +4,7 @@ export * from './CTASection';
4
4
  export * from './FeatureSection';
5
5
  export * from './Hero';
6
6
  export * from './NewsletterSection';
7
+ export * from './SplitHero';
7
8
  export * from './StatsSection';
8
9
  export * from './SuperHero';
9
10
  export * from './TestimonialSection';
@@ -1,15 +1,12 @@
1
- import React from 'react';
2
1
  import Link from 'next/link';
2
+ import React from 'react';
3
+
4
+ import { cn } from '@djangocfg/ui-core/lib';
5
+
3
6
  import {
4
- Breadcrumb,
5
- BreadcrumbList,
6
- BreadcrumbItem,
7
- BreadcrumbLink,
8
- BreadcrumbPage,
9
- BreadcrumbSeparator,
10
- BreadcrumbEllipsis,
7
+ Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage,
8
+ BreadcrumbSeparator
11
9
  } from './breadcrumb';
12
- import { cn } from '@djangocfg/ui-core/lib';
13
10
 
14
11
  export interface BreadcrumbItem {
15
12
  /** Display text for the breadcrumb */