Initial commit
This commit is contained in:
104
src/components/bento/BentoOrbitingIcons.tsx
Normal file
104
src/components/bento/BentoOrbitingIcons.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { cls, shouldUseInvertedText } from "@/lib/utils";
|
||||
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { InvertedBackground } from "@/providers/themeProvider/config/constants";
|
||||
|
||||
export type OrbitingItem = {
|
||||
icon: LucideIcon;
|
||||
ring?: 1 | 2 | 3; // Which ring to orbit on (1=innermost, 3=outermost), defaults to 2
|
||||
duration?: number; // Animation duration in seconds, defaults to 10
|
||||
};
|
||||
|
||||
interface BentoOrbitingIconsProps {
|
||||
centerIcon: LucideIcon;
|
||||
items: OrbitingItem[];
|
||||
useInvertedBackground: InvertedBackground;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const BentoOrbitingIcons = ({
|
||||
centerIcon,
|
||||
items,
|
||||
useInvertedBackground,
|
||||
className = "",
|
||||
}: BentoOrbitingIconsProps) => {
|
||||
const theme = useTheme();
|
||||
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
|
||||
const CenterIcon = centerIcon;
|
||||
|
||||
const circleStyles = "secondary-button border border-background-accent! shadow rounded-full";
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cls("relative h-full flex flex-col overflow-hidden", className)}
|
||||
style={{
|
||||
perspective: "2000px",
|
||||
maskImage: "linear-gradient(to bottom, transparent 0%, black 10%, black 90%, transparent 100%), linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%)",
|
||||
WebkitMaskImage: "linear-gradient(to bottom, transparent 0%, black 10%, black 90%, transparent 100%), linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%)",
|
||||
maskComposite: "intersect",
|
||||
WebkitMaskComposite: "source-in"
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="flex-1 rounded-t-theme-capped gap-2 flex items-center justify-center w-full h-full inset-x-0 p-2 relative"
|
||||
style={{
|
||||
transform: "rotateY(20deg) rotateX(20deg) rotateZ(-20deg)"
|
||||
}}
|
||||
>
|
||||
{/* Background concentric circles */}
|
||||
<div className={cls("absolute! top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 shrink-0 h-[15rem] w-[15rem] z-[9] opacity-85", circleStyles)} />
|
||||
<div className={cls("absolute! top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 shrink-0 h-[20rem] w-[20rem] z-[8] opacity-65", circleStyles)} />
|
||||
<div className={cls("absolute! top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 shrink-0 h-[25rem] w-[25rem] z-[7] opacity-45", circleStyles)} />
|
||||
<div className={cls("absolute! top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 shrink-0 h-[30rem] w-[30rem] z-[6] opacity-25", circleStyles)} />
|
||||
|
||||
{/* Center circle with icon */}
|
||||
<div className={cls("absolute! inset-0 shrink-0 h-40 w-[10rem] z-10 m-auto flex items-center justify-center", circleStyles)}>
|
||||
|
||||
<div className="absolute! primary-button h-[5rem] w-[5rem] rounded-full flex items-center justify-center" >
|
||||
<CenterIcon className="absolute h-1/2 w-1/2 text-background" strokeWidth={1.25} />
|
||||
</div>
|
||||
|
||||
{/* Orbiting items */}
|
||||
{items.map((item, index) => {
|
||||
const Icon = item.icon;
|
||||
const ring = item.ring || 2;
|
||||
// Ring radii: 7.5rem=120px, 10rem=160px, 12.5rem=200px
|
||||
const radiusMap = { 1: 120, 2: 160, 3: 200 };
|
||||
const radius = radiusMap[ring];
|
||||
const duration = item.duration || 10;
|
||||
// Evenly distribute items around the circle
|
||||
const initialPosition = (360 / items.length) * index;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={cls("!absolute top-1/2 left-1/2 h-[2.5rem] w-[2.5rem] card shadow rounded-theme flex items-center justify-center")}
|
||||
style={{
|
||||
marginLeft: '-1.25rem',
|
||||
marginTop: '-1.25rem',
|
||||
animation: `orbit ${duration}s linear infinite`,
|
||||
"--initial-position": `${initialPosition}deg`,
|
||||
"--translate-position": `${radius}px`,
|
||||
"--orbit-duration": `${duration}s`,
|
||||
} as React.CSSProperties & {
|
||||
"--initial-position": string;
|
||||
"--translate-position": string;
|
||||
"--orbit-duration": string;
|
||||
}}
|
||||
>
|
||||
<Icon className={cls("h-4/10 w-4/10", shouldUseLightText ? "text-background" : "text-foreground")} strokeWidth={1.5} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
BentoOrbitingIcons.displayName = "BentoOrbitingIcons";
|
||||
|
||||
export default memo(BentoOrbitingIcons);
|
||||
Reference in New Issue
Block a user