feat: add Marquee component and its example component
This commit is contained in:
parent
3120b96d35
commit
a2f97802f2
81
src/components/magicui/example/marquee-example.tsx
Normal file
81
src/components/magicui/example/marquee-example.tsx
Normal file
@ -0,0 +1,81 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Marquee } from "@/components/magicui/marquee";
|
||||
|
||||
const reviews = [
|
||||
{
|
||||
name: "Jack",
|
||||
username: "@jack",
|
||||
body: "I've never seen anything like this before. It's amazing. I love it.",
|
||||
img: "https://avatar.vercel.sh/jack",
|
||||
},
|
||||
{
|
||||
name: "Jill",
|
||||
username: "@jill",
|
||||
body: "I don't know what to say. I'm speechless. This is amazing.",
|
||||
img: "https://avatar.vercel.sh/jill",
|
||||
},
|
||||
{
|
||||
name: "John",
|
||||
username: "@john",
|
||||
body: "I'm at a loss for words. This is amazing. I love it.",
|
||||
img: "https://avatar.vercel.sh/john",
|
||||
},
|
||||
];
|
||||
|
||||
const firstRow = reviews.slice(0, reviews.length / 2);
|
||||
const secondRow = reviews.slice(reviews.length / 2);
|
||||
|
||||
const ReviewCard = ({
|
||||
img,
|
||||
name,
|
||||
username,
|
||||
body,
|
||||
}: {
|
||||
img: string;
|
||||
name: string;
|
||||
username: string;
|
||||
body: string;
|
||||
}) => {
|
||||
return (
|
||||
<figure
|
||||
className={cn(
|
||||
"relative h-full w-fit sm:w-36 cursor-pointer overflow-hidden rounded-xl border p-4",
|
||||
// light styles
|
||||
"border-gray-950/[.1] bg-gray-950/[.01] hover:bg-gray-950/[.05]",
|
||||
// dark styles
|
||||
"dark:border-gray-50/[.1] dark:bg-gray-50/[.10] dark:hover:bg-gray-50/[.15]",
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<img className="rounded-full" width="32" height="32" alt="" src={img} />
|
||||
<div className="flex flex-col">
|
||||
<figcaption className="text-sm font-medium dark:text-white">
|
||||
{name}
|
||||
</figcaption>
|
||||
<p className="text-xs font-medium dark:text-white/40">{username}</p>
|
||||
</div>
|
||||
</div>
|
||||
<blockquote className="mt-2 text-sm">{body}</blockquote>
|
||||
</figure>
|
||||
);
|
||||
};
|
||||
|
||||
export function MarqueeDemoVertical() {
|
||||
return (
|
||||
<div className="relative flex h-[500px] w-full flex-row items-center justify-center overflow-hidden">
|
||||
<Marquee pauseOnHover vertical className="[--duration:20s]">
|
||||
{firstRow.map((review) => (
|
||||
<ReviewCard key={review.username} {...review} />
|
||||
))}
|
||||
</Marquee>
|
||||
<Marquee reverse pauseOnHover vertical className="[--duration:20s]">
|
||||
{secondRow.map((review) => (
|
||||
<ReviewCard key={review.username} {...review} />
|
||||
))}
|
||||
</Marquee>
|
||||
<div className="pointer-events-none absolute inset-x-0 top-0 h-1/4 bg-gradient-to-b from-background"></div>
|
||||
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-1/4 bg-gradient-to-t from-background"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
73
src/components/magicui/marquee.tsx
Normal file
73
src/components/magicui/marquee.tsx
Normal file
@ -0,0 +1,73 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
interface MarqueeProps extends ComponentPropsWithoutRef<"div"> {
|
||||
/**
|
||||
* Optional CSS class name to apply custom styles
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* Whether to reverse the animation direction
|
||||
* @default false
|
||||
*/
|
||||
reverse?: boolean;
|
||||
/**
|
||||
* Whether to pause the animation on hover
|
||||
* @default false
|
||||
*/
|
||||
pauseOnHover?: boolean;
|
||||
/**
|
||||
* Content to be displayed in the marquee
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Whether to animate vertically instead of horizontally
|
||||
* @default false
|
||||
*/
|
||||
vertical?: boolean;
|
||||
/**
|
||||
* Number of times to repeat the content
|
||||
* @default 4
|
||||
*/
|
||||
repeat?: number;
|
||||
}
|
||||
|
||||
export function Marquee({
|
||||
className,
|
||||
reverse = false,
|
||||
pauseOnHover = false,
|
||||
children,
|
||||
vertical = false,
|
||||
repeat = 4,
|
||||
...props
|
||||
}: MarqueeProps) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={cn(
|
||||
"group flex overflow-hidden p-2 [--duration:40s] [--gap:1rem] [gap:var(--gap)]",
|
||||
{
|
||||
"flex-row": !vertical,
|
||||
"flex-col": vertical,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{Array(repeat)
|
||||
.fill(0)
|
||||
.map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={cn("flex shrink-0 justify-around [gap:var(--gap)]", {
|
||||
"animate-marquee flex-row": !vertical,
|
||||
"animate-marquee-vertical flex-col": vertical,
|
||||
"group-hover:[animation-play-state:paused]": pauseOnHover,
|
||||
"[animation-direction:reverse]": reverse,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -67,9 +67,10 @@
|
||||
--animate-rainbow: rainbow var(--speed, 2s) infinite linear;
|
||||
--animate-accordion-down: accordion-down 0.2s ease-out;
|
||||
--animate-accordion-up: accordion-up 0.2s ease-out;
|
||||
--animate-marquee: marquee var(--duration) infinite linear;
|
||||
--animate-marquee-vertical: marquee-vertical var(--duration) linear infinite;
|
||||
|
||||
@keyframes shiny-text {
|
||||
|
||||
0%,
|
||||
90%,
|
||||
100% {
|
||||
@ -111,6 +112,24 @@
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes marquee {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
}
|
||||
to {
|
||||
transform: translateX(calc(-100% - var(--gap)));
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes marquee-vertical {
|
||||
from {
|
||||
transform: translateY(0);
|
||||
}
|
||||
to {
|
||||
transform: translateY(calc(-100% - var(--gap)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
@ -191,7 +210,6 @@
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
@ -265,4 +283,4 @@ body {
|
||||
--primary: var(--color-amber-500);
|
||||
--primary-foreground: var(--color-amber-50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user