@@ -71,7 +71,7 @@ const useImageLoader = (seqRef, onLoad, dependencies) => {
7171 } , dependencies ) ;
7272} ;
7373
74- const useAnimationLoop = ( trackRef , targetVelocity , seqWidth , isHovered , pauseOnHover ) => {
74+ const useAnimationLoop = ( trackRef , targetVelocity , seqWidth , seqHeight , isHovered , hoverSpeed , isVertical ) => {
7575 const rafRef = useRef ( null ) ;
7676 const lastTimestampRef = useRef ( null ) ;
7777 const offsetRef = useRef ( 0 ) ;
@@ -81,9 +81,14 @@ const useAnimationLoop = (trackRef, targetVelocity, seqWidth, isHovered, pauseOn
8181 const track = trackRef . current ;
8282 if ( ! track ) return ;
8383
84- if ( seqWidth > 0 ) {
85- offsetRef . current = ( ( offsetRef . current % seqWidth ) + seqWidth ) % seqWidth ;
86- track . style . transform = `translate3d(${ - offsetRef . current } px, 0, 0)` ;
84+ const seqSize = isVertical ? seqHeight : seqWidth ;
85+
86+ if ( seqSize > 0 ) {
87+ offsetRef . current = ( ( offsetRef . current % seqSize ) + seqSize ) % seqSize ;
88+ const transformValue = isVertical
89+ ? `translate3d(0, ${ - offsetRef . current } px, 0)`
90+ : `translate3d(${ - offsetRef . current } px, 0, 0)` ;
91+ track . style . transform = transformValue ;
8792 }
8893
8994 const animate = timestamp => {
@@ -94,18 +99,20 @@ const useAnimationLoop = (trackRef, targetVelocity, seqWidth, isHovered, pauseOn
9499 const deltaTime = Math . max ( 0 , timestamp - lastTimestampRef . current ) / 1000 ;
95100 lastTimestampRef . current = timestamp ;
96101
97- const target = pauseOnHover && isHovered ? 0 : targetVelocity ;
102+ const target = isHovered && hoverSpeed !== undefined ? hoverSpeed : targetVelocity ;
98103
99104 const easingFactor = 1 - Math . exp ( - deltaTime / ANIMATION_CONFIG . SMOOTH_TAU ) ;
100105 velocityRef . current += ( target - velocityRef . current ) * easingFactor ;
101106
102- if ( seqWidth > 0 ) {
107+ if ( seqSize > 0 ) {
103108 let nextOffset = offsetRef . current + velocityRef . current * deltaTime ;
104- nextOffset = ( ( nextOffset % seqWidth ) + seqWidth ) % seqWidth ;
109+ nextOffset = ( ( nextOffset % seqSize ) + seqSize ) % seqSize ;
105110 offsetRef . current = nextOffset ;
106111
107- const translateX = - offsetRef . current ;
108- track . style . transform = `translate3d(${ translateX } px, 0, 0)` ;
112+ const transformValue = isVertical
113+ ? `translate3d(0, ${ - offsetRef . current } px, 0)`
114+ : `translate3d(${ - offsetRef . current } px, 0, 0)` ;
115+ track . style . transform = transformValue ;
109116 }
110117
111118 rafRef . current = requestAnimationFrame ( animate ) ;
@@ -120,7 +127,7 @@ const useAnimationLoop = (trackRef, targetVelocity, seqWidth, isHovered, pauseOn
120127 }
121128 lastTimestampRef . current = null ;
122129 } ;
123- } , [ targetVelocity , seqWidth , isHovered , pauseOnHover , trackRef ] ) ;
130+ } , [ targetVelocity , seqWidth , seqHeight , isHovered , hoverSpeed , isVertical , trackRef ] ) ;
124131} ;
125132
126133export const LogoLoop = memo (
@@ -131,10 +138,12 @@ export const LogoLoop = memo(
131138 width = '100%' ,
132139 logoHeight = 28 ,
133140 gap = 32 ,
134- pauseOnHover = true ,
141+ pauseOnHover,
142+ hoverSpeed,
135143 fadeOut = false ,
136144 fadeOutColor,
137145 scaleOnHover = false ,
146+ renderItem,
138147 ariaLabel = 'Partner logos' ,
139148 className,
140149 style
@@ -144,32 +153,60 @@ export const LogoLoop = memo(
144153 const seqRef = useRef ( null ) ;
145154
146155 const [ seqWidth , setSeqWidth ] = useState ( 0 ) ;
156+ const [ seqHeight , setSeqHeight ] = useState ( 0 ) ;
147157 const [ copyCount , setCopyCount ] = useState ( ANIMATION_CONFIG . MIN_COPIES ) ;
148158 const [ isHovered , setIsHovered ] = useState ( false ) ;
149159
160+ // Determine the effective hover speed (support backward compatibility)
161+ const effectiveHoverSpeed = useMemo ( ( ) => {
162+ if ( hoverSpeed !== undefined ) return hoverSpeed ;
163+ if ( pauseOnHover === true ) return 0 ;
164+ if ( pauseOnHover === false ) return undefined ;
165+ // Default behavior: pause on hover
166+ return 0 ;
167+ } , [ hoverSpeed , pauseOnHover ] ) ;
168+
169+ const isVertical = direction === 'up' || direction === 'down' ;
170+
150171 const targetVelocity = useMemo ( ( ) => {
151172 const magnitude = Math . abs ( speed ) ;
152- const directionMultiplier = direction === 'left' ? 1 : - 1 ;
173+ let directionMultiplier ;
174+ if ( isVertical ) {
175+ directionMultiplier = direction === 'up' ? 1 : - 1 ;
176+ } else {
177+ directionMultiplier = direction === 'left' ? 1 : - 1 ;
178+ }
153179 const speedMultiplier = speed < 0 ? - 1 : 1 ;
154180 return magnitude * directionMultiplier * speedMultiplier ;
155- } , [ speed , direction ] ) ;
181+ } , [ speed , direction , isVertical ] ) ;
156182
157183 const updateDimensions = useCallback ( ( ) => {
158184 const containerWidth = containerRef . current ?. clientWidth ?? 0 ;
159- const sequenceWidth = seqRef . current ?. getBoundingClientRect ?. ( ) ?. width ?? 0 ;
160-
161- if ( sequenceWidth > 0 ) {
162- setSeqWidth ( Math . ceil ( sequenceWidth ) ) ;
163- const copiesNeeded = Math . ceil ( containerWidth / sequenceWidth ) + ANIMATION_CONFIG . COPY_HEADROOM ;
164- setCopyCount ( Math . max ( ANIMATION_CONFIG . MIN_COPIES , copiesNeeded ) ) ;
185+ const containerHeight = containerRef . current ?. clientHeight ?? 0 ;
186+ const sequenceRect = seqRef . current ?. getBoundingClientRect ?. ( ) ;
187+ const sequenceWidth = sequenceRect ?. width ?? 0 ;
188+ const sequenceHeight = sequenceRect ?. height ?? 0 ;
189+
190+ if ( isVertical ) {
191+ if ( sequenceHeight > 0 ) {
192+ setSeqHeight ( Math . ceil ( sequenceHeight ) ) ;
193+ const copiesNeeded = Math . ceil ( containerHeight / sequenceHeight ) + ANIMATION_CONFIG . COPY_HEADROOM ;
194+ setCopyCount ( Math . max ( ANIMATION_CONFIG . MIN_COPIES , copiesNeeded ) ) ;
195+ }
196+ } else {
197+ if ( sequenceWidth > 0 ) {
198+ setSeqWidth ( Math . ceil ( sequenceWidth ) ) ;
199+ const copiesNeeded = Math . ceil ( containerWidth / sequenceWidth ) + ANIMATION_CONFIG . COPY_HEADROOM ;
200+ setCopyCount ( Math . max ( ANIMATION_CONFIG . MIN_COPIES , copiesNeeded ) ) ;
201+ }
165202 }
166- } , [ ] ) ;
203+ } , [ isVertical ] ) ;
167204
168- useResizeObserver ( updateDimensions , [ containerRef , seqRef ] , [ logos , gap , logoHeight ] ) ;
205+ useResizeObserver ( updateDimensions , [ containerRef , seqRef ] , [ logos , gap , logoHeight , isVertical ] ) ;
169206
170- useImageLoader ( seqRef , updateDimensions , [ logos , gap , logoHeight ] ) ;
207+ useImageLoader ( seqRef , updateDimensions , [ logos , gap , logoHeight , isVertical ] ) ;
171208
172- useAnimationLoop ( trackRef , targetVelocity , seqWidth , isHovered , pauseOnHover ) ;
209+ useAnimationLoop ( trackRef , targetVelocity , seqWidth , seqHeight , isHovered , effectiveHoverSpeed , isVertical ) ;
173210
174211 const cssVariables = useMemo (
175212 ( ) => ( {
@@ -182,21 +219,37 @@ export const LogoLoop = memo(
182219
183220 const rootClassName = useMemo (
184221 ( ) =>
185- [ 'logoloop' , fadeOut && 'logoloop--fade' , scaleOnHover && 'logoloop--scale-hover' , className ]
222+ [
223+ 'logoloop' ,
224+ isVertical ? 'logoloop--vertical' : 'logoloop--horizontal' ,
225+ fadeOut && 'logoloop--fade' ,
226+ scaleOnHover && 'logoloop--scale-hover' ,
227+ className
228+ ]
186229 . filter ( Boolean )
187230 . join ( ' ' ) ,
188- [ fadeOut , scaleOnHover , className ]
231+ [ isVertical , fadeOut , scaleOnHover , className ]
189232 ) ;
190233
191234 const handleMouseEnter = useCallback ( ( ) => {
192- if ( pauseOnHover ) setIsHovered ( true ) ;
193- } , [ pauseOnHover ] ) ;
235+ if ( effectiveHoverSpeed !== undefined ) setIsHovered ( true ) ;
236+ } , [ effectiveHoverSpeed ] ) ;
194237
195238 const handleMouseLeave = useCallback ( ( ) => {
196- if ( pauseOnHover ) setIsHovered ( false ) ;
197- } , [ pauseOnHover ] ) ;
239+ if ( effectiveHoverSpeed !== undefined ) setIsHovered ( false ) ;
240+ } , [ effectiveHoverSpeed ] ) ;
198241
199242 const renderLogoItem = useCallback ( ( item , key ) => {
243+ // If renderItem prop is provided, use it
244+ if ( renderItem ) {
245+ return (
246+ < li className = "logoloop__item" key = { key } role = "listitem" >
247+ { renderItem ( item , key ) }
248+ </ li >
249+ ) ;
250+ }
251+
252+ // Default rendering logic
200253 const isNodeItem = 'node' in item ;
201254
202255 const content = isNodeItem ? (
@@ -239,7 +292,7 @@ export const LogoLoop = memo(
239292 { itemContent }
240293 </ li >
241294 ) ;
242- } , [ ] ) ;
295+ } , [ renderItem ] ) ;
243296
244297 const logoLists = useMemo (
245298 ( ) =>
0 commit comments