Skip to content

Commit bbdd9d4

Browse files
authored
Marquee (#2024)
1 parent 8f334d9 commit bbdd9d4

File tree

5 files changed

+423
-0
lines changed

5 files changed

+423
-0
lines changed

.changeset/real-nails-give.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@theguild/components": minor
3+
---
4+
5+
Add Marquee

packages/components/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export { Giscus } from './giscus';
3333
export * from './product-card';
3434
export * from './version-dropdown';
3535
export * from './dropdown';
36+
export * from './marquee';
3637
export { FrequentlyAskedQuestions } from './faq';
3738
export { ComparisonTable } from './comparison-table';
3839
export { HiveLayoutConfig } from './hive-layout-config';
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
'use client';
2+
3+
import React, { ReactElement, ReactNode } from 'react';
4+
import { cn } from '@theguild/components';
5+
import { useTweenPlaybackRate } from './use-tween-playback-rate';
6+
7+
const PresetSpeedToMs = {
8+
fast: 20_000,
9+
normal: 40_000,
10+
slow: 60_000,
11+
};
12+
13+
export interface MarqueeProps extends React.HTMLAttributes<HTMLDivElement> {
14+
direction?: 'left' | 'right';
15+
speed?: keyof typeof PresetSpeedToMs | number;
16+
pauseOnHover?: boolean;
17+
/**
18+
* Seconds to stop the animation
19+
*/
20+
pauseDurationSeconds?: number;
21+
children: ReactNode;
22+
}
23+
24+
export function Marquee({
25+
direction = 'left',
26+
speed = 'normal',
27+
pauseOnHover = false,
28+
className,
29+
children,
30+
pauseDurationSeconds = 1,
31+
...rest
32+
}: MarqueeProps) {
33+
const animationDuration =
34+
typeof speed === 'number' ? speed : PresetSpeedToMs[speed as keyof typeof PresetSpeedToMs];
35+
36+
const tweenPlaybackRate = useTweenPlaybackRate();
37+
38+
const STEP = 1 / (pauseDurationSeconds * 1000);
39+
40+
return (
41+
<div
42+
className={cn(
43+
'[mask-image:linear-gradient(to_right,transparent,white_20%,white_80%,transparent)]',
44+
className,
45+
)}
46+
{...rest}
47+
>
48+
<ul
49+
className={cn(
50+
'flex w-max animate-[marquee_var(--animation-duration)_var(--animation-direction)_linear_infinite] gap-2 py-1',
51+
)}
52+
style={
53+
{
54+
'--animation-duration': `${animationDuration}ms`,
55+
'--animation-direction': direction === 'left' ? 'forwards' : 'reverse',
56+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
57+
} as {}
58+
}
59+
onMouseEnter={
60+
pauseOnHover
61+
? event => {
62+
const animation = event.currentTarget.getAnimations()[0];
63+
if (animation) tweenPlaybackRate(animation, -STEP);
64+
}
65+
: undefined
66+
}
67+
onMouseLeave={
68+
pauseOnHover
69+
? event => {
70+
const animation = event.currentTarget.getAnimations()[0];
71+
if (animation) tweenPlaybackRate(animation, STEP);
72+
}
73+
: undefined
74+
}
75+
>
76+
{children}
77+
{children}
78+
</ul>
79+
<style href="Marquee-keyframes">
80+
{
81+
/* css */ `
82+
@keyframes marquee {
83+
to {
84+
translate: calc(-50% - .5rem);
85+
}
86+
}
87+
`
88+
}
89+
</style>
90+
</div>
91+
);
92+
}
93+
94+
export interface MarqueeRowsProps
95+
extends React.HTMLAttributes<HTMLElement>,
96+
Pick<MarqueeProps, 'pauseOnHover' | 'speed'> {
97+
rows: number;
98+
children: ReactElement[];
99+
}
100+
101+
export function MarqueeRows({
102+
children,
103+
rows,
104+
pauseOnHover,
105+
speed,
106+
className,
107+
...rest
108+
}: MarqueeRowsProps) {
109+
const chunkSize = Math.ceil(children.length / rows);
110+
const chunks: ReactElement[][] = [];
111+
112+
for (let i = 0; i < rows; i++) {
113+
chunks.push(children.slice(i * chunkSize, (i + 1) * chunkSize));
114+
}
115+
116+
return (
117+
<div className={cn('overflow-hidden', className)} {...rest}>
118+
{chunks.map((chunk, index) => (
119+
<Marquee
120+
key={index}
121+
direction={index % 2 ? 'left' : 'right'}
122+
pauseOnHover={pauseOnHover}
123+
speed={speed}
124+
>
125+
{chunk}
126+
{index === chunks.length - 1 && chunk}
127+
</Marquee>
128+
))}
129+
</div>
130+
);
131+
}
132+
133+
MarqueeRows.Rows = MarqueeRows;

0 commit comments

Comments
 (0)