✨ feat(auth): Add Oauth configuration for Telegram, Facebook, Google, Github, and Apple
This commit is contained in:
@@ -60,10 +60,11 @@
|
||||
"cmdk": "1.0.4",
|
||||
"date-fns": "^4.1.0",
|
||||
"embla-carousel-react": "^8.5.2",
|
||||
"framer-motion": "^11.16.1",
|
||||
"framer-motion": "^11.18.1",
|
||||
"input-otp": "^1.4.2",
|
||||
"lucide-react": "^0.469.0",
|
||||
"lucide-react": "^0.473.0",
|
||||
"mathjs": "^14.0.1",
|
||||
"motion": "^11.18.1",
|
||||
"next-themes": "^0.4.4",
|
||||
"react-day-picker": "8.10.1",
|
||||
"react-hook-form": "^7.54.2",
|
||||
@@ -77,7 +78,7 @@
|
||||
"remark-math": "^6.0.0",
|
||||
"remark-toc": "^9.0.0",
|
||||
"rtl-detect": "^1.1.2",
|
||||
"sonner": "^1.7.1",
|
||||
"sonner": "^1.7.2",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vaul": "^1.1.2",
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
'use client';
|
||||
|
||||
import { cn } from '@workspace/ui/lib/utils';
|
||||
import { AnimatePresence, motion, MotionProps } from 'motion/react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
type CharacterSet = string[] | readonly string[];
|
||||
|
||||
interface HyperTextProps extends MotionProps {
|
||||
/** The text content to be animated */
|
||||
children: string;
|
||||
/** Optional className for styling */
|
||||
className?: string;
|
||||
/** Duration of the animation in milliseconds */
|
||||
duration?: number;
|
||||
/** Delay before animation starts in milliseconds */
|
||||
delay?: number;
|
||||
/** Component to render as - defaults to div */
|
||||
as?: React.ElementType;
|
||||
/** Whether to start animation when element comes into view */
|
||||
startOnView?: boolean;
|
||||
/** Whether to trigger animation on hover */
|
||||
animateOnHover?: boolean;
|
||||
/** Custom character set for scramble effect. Defaults to uppercase alphabet */
|
||||
characterSet?: CharacterSet;
|
||||
}
|
||||
|
||||
const DEFAULT_CHARACTER_SET = Object.freeze(
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
|
||||
) as readonly string[];
|
||||
|
||||
const getRandomInt = (max: number): number => Math.floor(Math.random() * max);
|
||||
|
||||
export default function HyperText({
|
||||
children,
|
||||
className,
|
||||
duration = 800,
|
||||
delay = 0,
|
||||
as: Component = 'div',
|
||||
startOnView = false,
|
||||
animateOnHover = true,
|
||||
characterSet = DEFAULT_CHARACTER_SET,
|
||||
...props
|
||||
}: HyperTextProps) {
|
||||
const MotionComponent = motion.create(Component, {
|
||||
forwardMotionProps: true,
|
||||
});
|
||||
|
||||
const [displayText, setDisplayText] = useState<string[]>(() => children.split(''));
|
||||
const [isAnimating, setIsAnimating] = useState(false);
|
||||
const iterationCount = useRef(0);
|
||||
const elementRef = useRef<HTMLElement>(null);
|
||||
|
||||
const handleAnimationTrigger = () => {
|
||||
if (animateOnHover && !isAnimating) {
|
||||
iterationCount.current = 0;
|
||||
setIsAnimating(true);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle animation start based on view or delay
|
||||
useEffect(() => {
|
||||
if (!startOnView) {
|
||||
const startTimeout = setTimeout(() => {
|
||||
setIsAnimating(true);
|
||||
}, delay);
|
||||
return () => clearTimeout(startTimeout);
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting) {
|
||||
setTimeout(() => {
|
||||
setIsAnimating(true);
|
||||
}, delay);
|
||||
observer.disconnect();
|
||||
}
|
||||
},
|
||||
{ threshold: 0.1, rootMargin: '-30% 0px -30% 0px' },
|
||||
);
|
||||
|
||||
if (elementRef.current) {
|
||||
observer.observe(elementRef.current);
|
||||
}
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, [delay, startOnView]);
|
||||
|
||||
// Handle scramble animation
|
||||
useEffect(() => {
|
||||
if (!isAnimating) return;
|
||||
|
||||
const intervalDuration = duration / (children.length * 10);
|
||||
const maxIterations = children.length;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (iterationCount.current < maxIterations) {
|
||||
setDisplayText((currentText) =>
|
||||
currentText.map((letter, index) =>
|
||||
letter === ' '
|
||||
? letter
|
||||
: index <= iterationCount.current
|
||||
? children[index]
|
||||
: characterSet[getRandomInt(characterSet.length)],
|
||||
),
|
||||
);
|
||||
iterationCount.current = iterationCount.current + 0.1;
|
||||
} else {
|
||||
setIsAnimating(false);
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, intervalDuration);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [children, duration, isAnimating, characterSet]);
|
||||
|
||||
return (
|
||||
<MotionComponent
|
||||
ref={elementRef}
|
||||
className={cn('overflow-hidden py-2 text-4xl font-bold', className)}
|
||||
onMouseEnter={handleAnimationTrigger}
|
||||
{...props}
|
||||
>
|
||||
<AnimatePresence>
|
||||
{displayText.map((letter, index) => (
|
||||
<motion.span key={index} className={cn('font-mono', letter === ' ' ? 'w-3' : '')}>
|
||||
{letter.toUpperCase()}
|
||||
</motion.span>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</MotionComponent>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { cn } from '@workspace/ui/lib/utils';
|
||||
import React from 'react';
|
||||
|
||||
export interface OrbitingCirclesProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
reverse?: boolean;
|
||||
duration?: number;
|
||||
delay?: number;
|
||||
radius?: number;
|
||||
path?: boolean;
|
||||
iconSize?: number;
|
||||
speed?: number;
|
||||
}
|
||||
|
||||
export function OrbitingCircles({
|
||||
className,
|
||||
children,
|
||||
reverse,
|
||||
duration = 20,
|
||||
radius = 160,
|
||||
path = true,
|
||||
iconSize = 30,
|
||||
speed = 1,
|
||||
...props
|
||||
}: OrbitingCirclesProps) {
|
||||
const calculatedDuration = duration / speed;
|
||||
return (
|
||||
<>
|
||||
{path && (
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
version='1.1'
|
||||
className='pointer-events-none absolute inset-0 size-full'
|
||||
>
|
||||
<circle
|
||||
className='stroke-black/10 stroke-1 dark:stroke-white/10'
|
||||
cx='50%'
|
||||
cy='50%'
|
||||
r={radius}
|
||||
fill='none'
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
{React.Children.map(children, (child, index) => {
|
||||
const angle = (360 / React.Children.count(children)) * index;
|
||||
return (
|
||||
<div
|
||||
style={
|
||||
{
|
||||
'--duration': calculatedDuration,
|
||||
'--radius': radius,
|
||||
'--angle': angle,
|
||||
'--icon-size': `${iconSize}px`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
className={cn(
|
||||
`animate-orbit absolute flex size-[var(--icon-size)] transform-gpu items-center justify-center rounded-full`,
|
||||
{ '[animation-direction:reverse]': reverse },
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{child}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -92,10 +92,21 @@ const config = {
|
||||
height: '0',
|
||||
},
|
||||
},
|
||||
'orbit': {
|
||||
'0%': {
|
||||
transform:
|
||||
'rotate(calc(var(--angle) * 1deg)) translateY(calc(var(--radius) * 1px)) rotate(calc(var(--angle) * -1deg))',
|
||||
},
|
||||
'100%': {
|
||||
transform:
|
||||
'rotate(calc(var(--angle) * 1deg + 360deg)) translateY(calc(var(--radius) * 1px)) rotate(calc((var(--angle) * -1deg) - 360deg))',
|
||||
},
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
'accordion-down': 'accordion-down 0.2s ease-out',
|
||||
'accordion-up': 'accordion-up 0.2s ease-out',
|
||||
'orbit': 'orbit calc(var(--duration)*1s) linear infinite',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user