11import React from 'react'
22import clsx from 'clsx'
3+ import { theme } from 'antd'
34
45export interface OrbitingItem {
56 content : React . ReactNode
@@ -15,6 +16,8 @@ export interface OrbitingCirclesProps extends React.HTMLAttributes<HTMLDivElemen
1516 items ?: OrbitingItem [ ]
1617 showPath ?: boolean
1718 centerText ?: string
19+ centerTextStyle ?: React . CSSProperties
20+ centerTextClassName ?: string
1821}
1922
2023/**
@@ -29,20 +32,40 @@ export interface OrbitingCirclesProps extends React.HTMLAttributes<HTMLDivElemen
2932 * - iconSize: number (size of the item)
3033 * @param {boolean } showPath - Whether to show the orbital path
3134 * @param {string } centerText - Text to display in the center
35+ * @param {React.CSSProperties } centerTextStyle - Custom style for the center text
36+ * @param {string } centerTextClassName - Custom class name for the center text
3237 */
3338const OrbitingCircles = ( {
3439 className,
3540 items = [ ] ,
3641 showPath = true ,
3742 centerText = 'Orbit' ,
43+ centerTextStyle,
44+ centerTextClassName,
3845 ...props
3946} : OrbitingCirclesProps ) => {
47+ const { token } = theme . useToken ( )
48+
49+ const calculatedItems = React . useMemo (
50+ ( ) =>
51+ items . map ( ( item , index ) => ( {
52+ ...item ,
53+ radius : item . radius || 100 + index * 40 ,
54+ duration : item . duration || 20 ,
55+ reverse : item . reverse !== undefined ? item . reverse : index % 2 === 0 ,
56+ iconSize : item . iconSize || 30 ,
57+ delay : item . delay || 0 ,
58+ } ) ) ,
59+ [ items ]
60+ )
61+
4062 return (
4163 < div
4264 className = { clsx (
43- 'bg-background relative flex h-[500px] w-full flex-col items-center justify-center overflow-hidden rounded-lg' ,
65+ 'relative flex h-[500px] w-full flex-col items-center justify-center overflow-hidden rounded-lg' ,
4466 className
4567 ) }
68+ style = { { backgroundColor : token . colorBgLayout , ...props . style } }
4669 { ...props }
4770 >
4871 { /* Inject Keyframes locally to ensure they exist */ }
@@ -58,68 +81,75 @@ const OrbitingCircles = ({
5881 ` } </ style >
5982
6083 { /* Center Point (Optional, for visual reference) */ }
61- < span className = "pointer-events-none bg-gradient-to-b from-black to-gray-300/80 bg-clip-text text-center text-8xl leading-none font-semibold whitespace-pre-wrap text-transparent dark:from-white dark:to-slate-900/10" >
84+ < span
85+ className = { clsx (
86+ 'pointer-events-none bg-clip-text text-center text-4xl leading-none font-bold tracking-tighter whitespace-pre-wrap text-transparent md:text-6xl' ,
87+ centerTextClassName
88+ ) }
89+ style = { {
90+ backgroundImage : `linear-gradient(to bottom, ${ token . colorText } , ${ token . colorTextQuaternary } )` ,
91+ zIndex : 1 ,
92+ ...centerTextStyle ,
93+ } }
94+ >
6295 { centerText }
6396 </ span >
6497
65- { /* Render Orbits and Items */ }
66- < div className = "absolute inset-0 flex items-center justify-center" >
67- { items . map ( ( item , index ) => {
68- const radius = item . radius || 100 + index * 40 // Default radius increment if not provided
69- const duration = item . duration || 20
70- const reverse = item . reverse || index % 2 === 0 // Alternate direction by default
71- const iconSize = item . iconSize || 30
72- const delay = item . delay || 0
73-
74- return (
75- < React . Fragment key = { index } >
76- { /* Orbital Path */ }
77- { showPath && (
78- < svg
79- xmlns = "http://www.w3.org/2000/svg"
80- version = "1.1"
81- className = "pointer-events-none absolute inset-0 h-full w-full"
82- style = { {
83- zIndex : 0 ,
84- } }
85- >
86- < circle
87- className = "stroke-black/10 stroke-1 dark:stroke-white/10"
88- cx = "50%"
89- cy = "50%"
90- r = { radius }
91- fill = "none"
92- />
93- </ svg >
94- ) }
98+ { /* Render Orbital Paths (Single SVG for performance) */ }
99+ { showPath && (
100+ < svg
101+ xmlns = "http://www.w3.org/2000/svg"
102+ version = "1.1"
103+ className = "pointer-events-none absolute inset-0 h-full w-full"
104+ style = { { zIndex : 0 } }
105+ >
106+ { calculatedItems . map ( ( item , index ) => (
107+ < circle
108+ key = { index }
109+ className = "stroke-1"
110+ cx = "50%"
111+ cy = "50%"
112+ r = { item . radius }
113+ fill = "none"
114+ stroke = { token . colorBorder }
115+ style = { { opacity : 0.5 } }
116+ />
117+ ) ) }
118+ </ svg >
119+ ) }
95120
96- { /* Orbiting Item */ }
97- < div
98- style = {
99- {
100- '--radius' : radius ,
101- '--duration' : duration ,
102- '--icon-size' : `${ iconSize } px` ,
103- width : iconSize ,
104- height : iconSize ,
105- position : 'absolute' ,
106- top : '50%' ,
107- left : '50%' ,
108- marginTop : - iconSize / 2 ,
109- marginLeft : - iconSize / 2 ,
110- animation : `orbit ${ duration } s linear infinite` ,
111- animationDirection : reverse ? 'reverse' : 'normal' ,
112- animationDelay : `${ delay } s` ,
113- zIndex : 10 ,
114- } as React . CSSProperties
115- }
116- className = "flex items-center justify-center rounded-full bg-white/10 backdrop-blur-sm"
117- >
118- { item . content }
119- </ div >
120- </ React . Fragment >
121- )
122- } ) }
121+ { /* Render Orbiting Items */ }
122+ < div className = "absolute inset-0 flex items-center justify-center" >
123+ { calculatedItems . map ( ( item , index ) => (
124+ < div
125+ key = { index }
126+ style = {
127+ {
128+ '--radius' : item . radius ,
129+ '--duration' : item . duration ,
130+ '--icon-size' : `${ item . iconSize } px` ,
131+ width : item . iconSize ,
132+ height : item . iconSize ,
133+ position : 'absolute' ,
134+ top : '50%' ,
135+ left : '50%' ,
136+ marginTop : - item . iconSize / 2 ,
137+ marginLeft : - item . iconSize / 2 ,
138+ animation : `orbit ${ item . duration } s linear infinite` ,
139+ animationDirection : item . reverse ? 'reverse' : 'normal' ,
140+ animationDelay : `${ item . delay } s` ,
141+ zIndex : 10 ,
142+ backgroundColor : token . colorBgContainer ,
143+ // Set initial transform to prevent items from bunching in center before animation starts
144+ transform : `rotate(0deg) translateY(calc(var(--radius) * 1px)) rotate(0deg)` ,
145+ willChange : 'transform' ,
146+ } as React . CSSProperties
147+ }
148+ className = "flex items-center justify-center rounded-full shadow-sm backdrop-blur-sm"
149+ >
150+ { item . content }
151+ </ div >
152+ ) ) }
123153 </ div >
124154 </ div >
125155 )
0 commit comments