mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-10 17:26:38 +08:00
refactor(repo): consolidate openflare-server to root and move subprojects to internal/apps
- Merge all files inside openflare-server to the repository root directory. - Relocate agent, relay, and flared subprojects from internal/ to internal/apps/. - Combine docker-compose files and update build context paths to root. - Update GitHub workflows and Dockerfiles to refer to new directories and package names. - Rewrite Go package imports across all files. - Resolve database renew test race condition and clean up docs.
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import {motion, type Variants} from 'motion/react';
|
||||
|
||||
import {getVariants, type IconProps, IconWrapper, useAnimateIconContext,} from '@/components/animate-ui/icons/icon';
|
||||
|
||||
type CheckProps = IconProps<keyof typeof animations>;
|
||||
|
||||
const animations = {
|
||||
default: {
|
||||
path: {
|
||||
initial: {
|
||||
pathLength: 1,
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
},
|
||||
animate: {
|
||||
pathLength: [0, 1],
|
||||
opacity: [0, 1],
|
||||
scale: [1, 1.1, 1],
|
||||
transition: {
|
||||
duration: 0.6,
|
||||
ease: 'easeInOut',
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies Record<string, Variants>,
|
||||
} as const;
|
||||
|
||||
function IconComponent({ size, ...props }: CheckProps) {
|
||||
const { controls } = useAnimateIconContext();
|
||||
const variants = getVariants(animations);
|
||||
|
||||
return (
|
||||
<motion.svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
initial="initial"
|
||||
animate={controls}
|
||||
{...props}
|
||||
>
|
||||
<motion.path
|
||||
d="m4 12 5 5L20 6"
|
||||
variants={variants.path}
|
||||
initial="initial"
|
||||
animate={controls}
|
||||
/>
|
||||
</motion.svg>
|
||||
);
|
||||
}
|
||||
|
||||
function Check(props: CheckProps) {
|
||||
return <IconWrapper icon={IconComponent} {...props} />;
|
||||
}
|
||||
|
||||
export {
|
||||
animations,
|
||||
Check,
|
||||
Check as CheckIcon,
|
||||
type CheckProps,
|
||||
type CheckProps as CheckIconProps,
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import {motion, type Variants} from 'motion/react';
|
||||
|
||||
import {getVariants, type IconProps, IconWrapper, useAnimateIconContext,} from '@/components/animate-ui/icons/icon';
|
||||
|
||||
type ChevronDownProps = IconProps<keyof typeof animations>;
|
||||
|
||||
const animations = {
|
||||
default: {
|
||||
path: {
|
||||
initial: {
|
||||
y: 0,
|
||||
transition: { duration: 0.3, ease: 'easeInOut' },
|
||||
},
|
||||
animate: {
|
||||
y: 4,
|
||||
transition: { duration: 0.3, ease: 'easeInOut' },
|
||||
},
|
||||
},
|
||||
} satisfies Record<string, Variants>,
|
||||
'default-loop': {
|
||||
path: {
|
||||
initial: {
|
||||
y: 0,
|
||||
},
|
||||
animate: {
|
||||
y: [0, 4, 0],
|
||||
transition: { duration: 0.6, ease: 'easeInOut' },
|
||||
},
|
||||
},
|
||||
} satisfies Record<string, Variants>,
|
||||
} as const;
|
||||
|
||||
function IconComponent({ size, ...props }: ChevronDownProps) {
|
||||
const { controls } = useAnimateIconContext();
|
||||
const variants = getVariants(animations);
|
||||
|
||||
return (
|
||||
<motion.svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
{...props}
|
||||
>
|
||||
<motion.path
|
||||
d="m6 9 6 6 6-6"
|
||||
variants={variants.path}
|
||||
initial="initial"
|
||||
animate={controls}
|
||||
/>
|
||||
</motion.svg>
|
||||
);
|
||||
}
|
||||
|
||||
function ChevronDown(props: ChevronDownProps) {
|
||||
return <IconWrapper icon={IconComponent} {...props} />;
|
||||
}
|
||||
|
||||
export {
|
||||
animations,
|
||||
ChevronDown,
|
||||
ChevronDown as ChevronDownIcon,
|
||||
type ChevronDownProps,
|
||||
type ChevronDownProps as ChevronDownIconProps,
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import {motion, type Variants} from 'motion/react';
|
||||
|
||||
import {getVariants, type IconProps, IconWrapper, useAnimateIconContext,} from '@/components/animate-ui/icons/icon';
|
||||
|
||||
type ChevronLeftProps = IconProps<keyof typeof animations>;
|
||||
|
||||
const animations = {
|
||||
default: {
|
||||
path: {
|
||||
initial: {
|
||||
x: 0,
|
||||
transition: { duration: 0.3, ease: 'easeInOut' },
|
||||
},
|
||||
animate: {
|
||||
x: -4,
|
||||
transition: { duration: 0.3, ease: 'easeInOut' },
|
||||
},
|
||||
},
|
||||
} satisfies Record<string, Variants>,
|
||||
'default-loop': {
|
||||
path: {
|
||||
initial: {
|
||||
x: 0,
|
||||
},
|
||||
animate: {
|
||||
x: [0, -4, 0],
|
||||
transition: { duration: 0.6, ease: 'easeInOut' },
|
||||
},
|
||||
},
|
||||
} satisfies Record<string, Variants>,
|
||||
} as const;
|
||||
|
||||
function IconComponent({ size, ...props }: ChevronLeftProps) {
|
||||
const { controls } = useAnimateIconContext();
|
||||
const variants = getVariants(animations);
|
||||
|
||||
return (
|
||||
<motion.svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
{...props}
|
||||
>
|
||||
<motion.path
|
||||
d="m15 18-6-6 6-6"
|
||||
variants={variants.path}
|
||||
initial="initial"
|
||||
animate={controls}
|
||||
/>
|
||||
</motion.svg>
|
||||
);
|
||||
}
|
||||
|
||||
function ChevronLeft(props: ChevronLeftProps) {
|
||||
return <IconWrapper icon={IconComponent} {...props} />;
|
||||
}
|
||||
|
||||
export {
|
||||
animations,
|
||||
ChevronLeft,
|
||||
ChevronLeft as ChevronLeftIcon,
|
||||
type ChevronLeftProps,
|
||||
type ChevronLeftProps as ChevronLeftIconProps,
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import {motion, type Variants} from 'motion/react';
|
||||
|
||||
import {getVariants, type IconProps, IconWrapper, useAnimateIconContext,} from '@/components/animate-ui/icons/icon';
|
||||
|
||||
type ChevronRightProps = IconProps<keyof typeof animations>;
|
||||
|
||||
const animations = {
|
||||
default: {
|
||||
path: {
|
||||
initial: {
|
||||
x: 0,
|
||||
transition: { duration: 0.3, ease: 'easeInOut' },
|
||||
},
|
||||
animate: {
|
||||
x: 4,
|
||||
transition: { duration: 0.3, ease: 'easeInOut' },
|
||||
},
|
||||
},
|
||||
} satisfies Record<string, Variants>,
|
||||
'default-loop': {
|
||||
path: {
|
||||
initial: {
|
||||
x: 0,
|
||||
},
|
||||
animate: {
|
||||
x: [0, 4, 0],
|
||||
transition: { duration: 0.6, ease: 'easeInOut' },
|
||||
},
|
||||
},
|
||||
} satisfies Record<string, Variants>,
|
||||
} as const;
|
||||
|
||||
function IconComponent({ size, ...props }: ChevronRightProps) {
|
||||
const { controls } = useAnimateIconContext();
|
||||
const variants = getVariants(animations);
|
||||
|
||||
return (
|
||||
<motion.svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
{...props}
|
||||
>
|
||||
<motion.path
|
||||
d="m9 18 6-6-6-6"
|
||||
variants={variants.path}
|
||||
initial="initial"
|
||||
animate={controls}
|
||||
/>
|
||||
</motion.svg>
|
||||
);
|
||||
}
|
||||
|
||||
function ChevronRight(props: ChevronRightProps) {
|
||||
return <IconWrapper icon={IconComponent} {...props} />;
|
||||
}
|
||||
|
||||
export {
|
||||
animations,
|
||||
ChevronRight,
|
||||
ChevronRight as ChevronRightIcon,
|
||||
type ChevronRightProps,
|
||||
type ChevronRightProps as ChevronRightIconProps,
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import {motion, type Variants} from 'motion/react';
|
||||
|
||||
import {getVariants, type IconProps, IconWrapper, useAnimateIconContext,} from '@/components/animate-ui/icons/icon';
|
||||
|
||||
type CircleCheckProps = IconProps<keyof typeof animations>;
|
||||
|
||||
const animations = {
|
||||
default: {
|
||||
circle: {},
|
||||
path: {
|
||||
initial: {
|
||||
pathLength: 1,
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
},
|
||||
animate: {
|
||||
pathLength: [0, 1],
|
||||
opacity: [0, 1],
|
||||
scale: [1, 1.1, 1],
|
||||
transition: {
|
||||
duration: 0.6,
|
||||
ease: 'easeInOut',
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies Record<string, Variants>,
|
||||
} as const;
|
||||
|
||||
function IconComponent({ size, ...props }: CircleCheckProps) {
|
||||
const { controls } = useAnimateIconContext();
|
||||
const variants = getVariants(animations);
|
||||
|
||||
return (
|
||||
<motion.svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
{...props}
|
||||
>
|
||||
<motion.circle
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
variants={variants.circle}
|
||||
initial="initial"
|
||||
animate={controls}
|
||||
/>
|
||||
<motion.path
|
||||
d="m9 12 2 2 4-4"
|
||||
variants={variants.path}
|
||||
initial="initial"
|
||||
animate={controls}
|
||||
/>
|
||||
</motion.svg>
|
||||
);
|
||||
}
|
||||
|
||||
function CircleCheck(props: CircleCheckProps) {
|
||||
return <IconWrapper icon={IconComponent} {...props} />;
|
||||
}
|
||||
|
||||
export {
|
||||
animations,
|
||||
CircleCheck,
|
||||
CircleCheck as CircleCheckIcon,
|
||||
type CircleCheckProps,
|
||||
type CircleCheckProps as CircleCheckIconProps,
|
||||
};
|
||||
@@ -0,0 +1,655 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import {
|
||||
type HTMLMotionProps,
|
||||
type LegacyAnimationControls,
|
||||
motion,
|
||||
type SVGMotionProps,
|
||||
useAnimation,
|
||||
type UseInViewOptions,
|
||||
type Variants,
|
||||
} from 'motion/react';
|
||||
|
||||
import {cn} from '@/lib/utils';
|
||||
import {useIsInView} from '@/hooks/use-is-in-view';
|
||||
import {Slot, type WithAsChild} from '@/components/animate-ui/primitives/animate/slot';
|
||||
|
||||
const staticAnimations = {
|
||||
path: {
|
||||
initial: { pathLength: 1 },
|
||||
animate: {
|
||||
pathLength: [0.05, 1],
|
||||
transition: {
|
||||
duration: 0.8,
|
||||
ease: 'easeInOut',
|
||||
},
|
||||
},
|
||||
} as Variants,
|
||||
'path-loop': {
|
||||
initial: { pathLength: 1 },
|
||||
animate: {
|
||||
pathLength: [1, 0.05, 1],
|
||||
transition: {
|
||||
duration: 1.6,
|
||||
ease: 'easeInOut',
|
||||
},
|
||||
},
|
||||
} as Variants,
|
||||
} as const;
|
||||
|
||||
type StaticAnimations = keyof typeof staticAnimations;
|
||||
type TriggerProp<T = string> = boolean | StaticAnimations | T;
|
||||
type Trigger = TriggerProp<string>;
|
||||
|
||||
type AnimateIconContextValue = {
|
||||
controls: LegacyAnimationControls | undefined;
|
||||
animation: StaticAnimations | string;
|
||||
loop: boolean;
|
||||
loopDelay: number;
|
||||
active: boolean;
|
||||
animate?: Trigger;
|
||||
initialOnAnimateEnd?: boolean;
|
||||
completeOnStop?: boolean;
|
||||
persistOnAnimateEnd?: boolean;
|
||||
delay?: number;
|
||||
};
|
||||
|
||||
type DefaultIconProps<T = string> = {
|
||||
animate?: TriggerProp<T>;
|
||||
animateOnHover?: TriggerProp<T>;
|
||||
animateOnTap?: TriggerProp<T>;
|
||||
animateOnView?: TriggerProp<T>;
|
||||
animateOnViewMargin?: UseInViewOptions['margin'];
|
||||
animateOnViewOnce?: boolean;
|
||||
animation?: T | StaticAnimations;
|
||||
loop?: boolean;
|
||||
loopDelay?: number;
|
||||
initialOnAnimateEnd?: boolean;
|
||||
completeOnStop?: boolean;
|
||||
persistOnAnimateEnd?: boolean;
|
||||
delay?: number;
|
||||
};
|
||||
|
||||
type AnimateIconProps<T = string> = WithAsChild<
|
||||
HTMLMotionProps<'span'> &
|
||||
DefaultIconProps<T> & {
|
||||
children: React.ReactNode;
|
||||
asChild?: boolean;
|
||||
}
|
||||
>;
|
||||
|
||||
type IconProps<T> = DefaultIconProps<T> &
|
||||
Omit<SVGMotionProps<SVGSVGElement>, 'animate'> & {
|
||||
size?: number;
|
||||
};
|
||||
|
||||
type IconWrapperProps<T> = IconProps<T> & {
|
||||
icon: React.ComponentType<IconProps<T>>;
|
||||
};
|
||||
|
||||
const AnimateIconContext = React.createContext<AnimateIconContextValue | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
function useAnimateIconContext() {
|
||||
const context = React.useContext(AnimateIconContext);
|
||||
if (!context)
|
||||
return {
|
||||
controls: undefined,
|
||||
animation: 'default',
|
||||
loop: undefined,
|
||||
loopDelay: undefined,
|
||||
active: undefined,
|
||||
animate: undefined,
|
||||
initialOnAnimateEnd: undefined,
|
||||
completeOnStop: undefined,
|
||||
persistOnAnimateEnd: undefined,
|
||||
delay: undefined,
|
||||
};
|
||||
return context;
|
||||
}
|
||||
|
||||
function composeEventHandlers<E extends React.SyntheticEvent<unknown>>(
|
||||
theirs?: (event: E) => void,
|
||||
ours?: (event: E) => void,
|
||||
) {
|
||||
return (event: E) => {
|
||||
theirs?.(event);
|
||||
ours?.(event);
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type AnyProps = Record<string, any>;
|
||||
|
||||
function AnimateIcon({
|
||||
asChild = false,
|
||||
animate = false,
|
||||
animateOnHover = false,
|
||||
animateOnTap = false,
|
||||
animateOnView = false,
|
||||
animateOnViewMargin = '0px',
|
||||
animateOnViewOnce = true,
|
||||
animation = 'default',
|
||||
loop = false,
|
||||
loopDelay = 0,
|
||||
initialOnAnimateEnd = false,
|
||||
completeOnStop = false,
|
||||
persistOnAnimateEnd = false,
|
||||
delay = 0,
|
||||
children,
|
||||
...props
|
||||
}: AnimateIconProps) {
|
||||
const controls = useAnimation();
|
||||
|
||||
const [localAnimate, setLocalAnimate] = React.useState<boolean>(() => {
|
||||
if (animate === undefined || animate === false) return false;
|
||||
return delay <= 0;
|
||||
});
|
||||
const [currentAnimation, setCurrentAnimation] = React.useState<
|
||||
string | StaticAnimations
|
||||
>(typeof animate === 'string' ? animate : animation);
|
||||
const [status, setStatus] = React.useState<'initial' | 'animate'>('initial');
|
||||
|
||||
const delayRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const loopDelayRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const isAnimateInProgressRef = React.useRef<boolean>(false);
|
||||
const animateEndPromiseRef = React.useRef<Promise<void> | null>(null);
|
||||
const resolveAnimateEndRef = React.useRef<(() => void) | null>(null);
|
||||
const activeRef = React.useRef<boolean>(localAnimate);
|
||||
|
||||
const runGenRef = React.useRef(0);
|
||||
const cancelledRef = React.useRef(false);
|
||||
|
||||
const bumpGeneration = React.useCallback(() => {
|
||||
runGenRef.current++;
|
||||
}, []);
|
||||
|
||||
const startAnimation = React.useCallback(
|
||||
(trigger: TriggerProp) => {
|
||||
const next = typeof trigger === 'string' ? trigger : animation;
|
||||
bumpGeneration();
|
||||
if (delayRef.current) {
|
||||
clearTimeout(delayRef.current);
|
||||
delayRef.current = null;
|
||||
}
|
||||
setCurrentAnimation(next);
|
||||
if (delay > 0) {
|
||||
setLocalAnimate(false);
|
||||
delayRef.current = setTimeout(() => {
|
||||
setLocalAnimate(true);
|
||||
}, delay);
|
||||
} else {
|
||||
setLocalAnimate(true);
|
||||
}
|
||||
},
|
||||
[animation, delay, bumpGeneration],
|
||||
);
|
||||
|
||||
const stopAnimation = React.useCallback(() => {
|
||||
bumpGeneration();
|
||||
if (delayRef.current) {
|
||||
clearTimeout(delayRef.current);
|
||||
delayRef.current = null;
|
||||
}
|
||||
if (loopDelayRef.current) {
|
||||
clearTimeout(loopDelayRef.current);
|
||||
loopDelayRef.current = null;
|
||||
}
|
||||
setLocalAnimate(false);
|
||||
}, [bumpGeneration]);
|
||||
|
||||
React.useEffect(() => {
|
||||
activeRef.current = localAnimate;
|
||||
}, [localAnimate]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (animate === undefined) return;
|
||||
setCurrentAnimation(typeof animate === 'string' ? animate : animation);
|
||||
if (animate) startAnimation(animate as TriggerProp);
|
||||
else stopAnimation();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [animate]);
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
if (delayRef.current) clearTimeout(delayRef.current);
|
||||
if (loopDelayRef.current) clearTimeout(loopDelayRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const viewOuterRef = React.useRef<HTMLElement>(null);
|
||||
const { ref: inViewRef, isInView } = useIsInView(viewOuterRef, {
|
||||
inView: !!animateOnView,
|
||||
inViewOnce: animateOnViewOnce,
|
||||
inViewMargin: animateOnViewMargin,
|
||||
});
|
||||
|
||||
const startAnim = React.useCallback(
|
||||
async (anim: 'initial' | 'animate', method: 'start' | 'set' = 'start') => {
|
||||
try {
|
||||
await controls[method](anim);
|
||||
setStatus(anim);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
},
|
||||
[controls],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!animateOnView) return;
|
||||
if (isInView) startAnimation(animateOnView);
|
||||
else stopAnimation();
|
||||
}, [isInView, animateOnView, startAnimation, stopAnimation]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const gen = ++runGenRef.current;
|
||||
cancelledRef.current = false;
|
||||
|
||||
async function run() {
|
||||
if (cancelledRef.current || gen !== runGenRef.current) {
|
||||
await startAnim('initial');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!localAnimate) {
|
||||
if (
|
||||
completeOnStop &&
|
||||
isAnimateInProgressRef.current &&
|
||||
animateEndPromiseRef.current
|
||||
) {
|
||||
try {
|
||||
await animateEndPromiseRef.current;
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
if (!persistOnAnimateEnd) {
|
||||
if (cancelledRef.current || gen !== runGenRef.current) {
|
||||
await startAnim('initial');
|
||||
return;
|
||||
}
|
||||
await startAnim('initial');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (loop) {
|
||||
if (cancelledRef.current || gen !== runGenRef.current) {
|
||||
await startAnim('initial');
|
||||
return;
|
||||
}
|
||||
await startAnim('initial', 'set');
|
||||
}
|
||||
|
||||
isAnimateInProgressRef.current = true;
|
||||
animateEndPromiseRef.current = new Promise<void>((resolve) => {
|
||||
resolveAnimateEndRef.current = resolve;
|
||||
});
|
||||
|
||||
if (cancelledRef.current || gen !== runGenRef.current) {
|
||||
isAnimateInProgressRef.current = false;
|
||||
resolveAnimateEndRef.current?.();
|
||||
resolveAnimateEndRef.current = null;
|
||||
animateEndPromiseRef.current = null;
|
||||
await startAnim('initial');
|
||||
return;
|
||||
}
|
||||
|
||||
await startAnim('animate');
|
||||
|
||||
if (cancelledRef.current || gen !== runGenRef.current) {
|
||||
isAnimateInProgressRef.current = false;
|
||||
resolveAnimateEndRef.current?.();
|
||||
resolveAnimateEndRef.current = null;
|
||||
animateEndPromiseRef.current = null;
|
||||
await startAnim('initial');
|
||||
return;
|
||||
}
|
||||
|
||||
isAnimateInProgressRef.current = false;
|
||||
resolveAnimateEndRef.current?.();
|
||||
resolveAnimateEndRef.current = null;
|
||||
animateEndPromiseRef.current = null;
|
||||
|
||||
if (initialOnAnimateEnd) {
|
||||
if (cancelledRef.current || gen !== runGenRef.current) {
|
||||
await startAnim('initial');
|
||||
return;
|
||||
}
|
||||
await startAnim('initial', 'set');
|
||||
}
|
||||
|
||||
if (loop) {
|
||||
if (loopDelay > 0) {
|
||||
await new Promise<void>((resolve) => {
|
||||
loopDelayRef.current = setTimeout(() => {
|
||||
loopDelayRef.current = null;
|
||||
resolve();
|
||||
}, loopDelay);
|
||||
});
|
||||
|
||||
if (cancelledRef.current || gen !== runGenRef.current) {
|
||||
await startAnim('initial');
|
||||
return;
|
||||
}
|
||||
if (!activeRef.current) {
|
||||
if (status !== 'initial' && !persistOnAnimateEnd)
|
||||
await startAnim('initial');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!activeRef.current) {
|
||||
if (status !== 'initial' && !persistOnAnimateEnd)
|
||||
await startAnim('initial');
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (cancelledRef.current || gen !== runGenRef.current) {
|
||||
await startAnim('initial');
|
||||
return;
|
||||
}
|
||||
await run();
|
||||
}
|
||||
}
|
||||
|
||||
void run();
|
||||
|
||||
return () => {
|
||||
cancelledRef.current = true;
|
||||
if (delayRef.current) {
|
||||
clearTimeout(delayRef.current);
|
||||
delayRef.current = null;
|
||||
}
|
||||
if (loopDelayRef.current) {
|
||||
clearTimeout(loopDelayRef.current);
|
||||
loopDelayRef.current = null;
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [localAnimate, controls]);
|
||||
|
||||
const childProps = (
|
||||
React.isValidElement(children) ? (children as React.ReactElement).props : {}
|
||||
) as AnyProps;
|
||||
|
||||
const handleMouseEnter = composeEventHandlers<React.MouseEvent<HTMLElement>>(
|
||||
childProps.onMouseEnter,
|
||||
() => {
|
||||
if (animateOnHover) startAnimation(animateOnHover);
|
||||
},
|
||||
);
|
||||
|
||||
const handleMouseLeave = composeEventHandlers<React.MouseEvent<HTMLElement>>(
|
||||
childProps.onMouseLeave,
|
||||
() => {
|
||||
if (animateOnHover || animateOnTap) stopAnimation();
|
||||
},
|
||||
);
|
||||
|
||||
const handlePointerDown = composeEventHandlers<
|
||||
React.PointerEvent<HTMLElement>
|
||||
>(childProps.onPointerDown, () => {
|
||||
if (animateOnTap) startAnimation(animateOnTap);
|
||||
});
|
||||
|
||||
const handlePointerUp = composeEventHandlers<React.PointerEvent<HTMLElement>>(
|
||||
childProps.onPointerUp,
|
||||
() => {
|
||||
if (animateOnTap) stopAnimation();
|
||||
},
|
||||
);
|
||||
|
||||
const content = asChild ? (
|
||||
<Slot
|
||||
ref={inViewRef}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerUp={handlePointerUp}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Slot>
|
||||
) : (
|
||||
<motion.span
|
||||
ref={inViewRef}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerUp={handlePointerUp}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</motion.span>
|
||||
);
|
||||
|
||||
return (
|
||||
<AnimateIconContext.Provider
|
||||
value={{
|
||||
controls,
|
||||
animation: currentAnimation,
|
||||
loop,
|
||||
loopDelay,
|
||||
active: localAnimate,
|
||||
animate,
|
||||
initialOnAnimateEnd,
|
||||
completeOnStop,
|
||||
delay,
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</AnimateIconContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const pathClassName =
|
||||
"[&_[stroke-dasharray='1px_1px']]:![stroke-dasharray:1px_0px]";
|
||||
|
||||
function IconWrapper<T extends string>({
|
||||
size = 28,
|
||||
animation: animationProp,
|
||||
animate,
|
||||
animateOnHover,
|
||||
animateOnTap,
|
||||
animateOnView,
|
||||
animateOnViewMargin,
|
||||
animateOnViewOnce,
|
||||
icon: IconComponent,
|
||||
loop,
|
||||
loopDelay,
|
||||
persistOnAnimateEnd,
|
||||
initialOnAnimateEnd,
|
||||
delay,
|
||||
completeOnStop,
|
||||
className,
|
||||
...props
|
||||
}: IconWrapperProps<T>) {
|
||||
const context = React.useContext(AnimateIconContext);
|
||||
|
||||
if (context) {
|
||||
const {
|
||||
controls,
|
||||
animation: parentAnimation,
|
||||
loop: parentLoop,
|
||||
loopDelay: parentLoopDelay,
|
||||
active: parentActive,
|
||||
animate: parentAnimate,
|
||||
persistOnAnimateEnd: parentPersistOnAnimateEnd,
|
||||
initialOnAnimateEnd: parentInitialOnAnimateEnd,
|
||||
delay: parentDelay,
|
||||
completeOnStop: parentCompleteOnStop,
|
||||
} = context;
|
||||
|
||||
const hasOverrides =
|
||||
animate !== undefined ||
|
||||
animateOnHover !== undefined ||
|
||||
animateOnTap !== undefined ||
|
||||
animateOnView !== undefined ||
|
||||
loop !== undefined ||
|
||||
loopDelay !== undefined ||
|
||||
initialOnAnimateEnd !== undefined ||
|
||||
persistOnAnimateEnd !== undefined ||
|
||||
delay !== undefined ||
|
||||
completeOnStop !== undefined;
|
||||
|
||||
if (hasOverrides) {
|
||||
const inheritedAnimate: Trigger = parentActive
|
||||
? (animationProp ?? parentAnimation ?? 'default')
|
||||
: false;
|
||||
|
||||
const finalAnimate: Trigger = (animate ??
|
||||
parentAnimate ??
|
||||
inheritedAnimate) as Trigger;
|
||||
|
||||
return (
|
||||
<AnimateIcon
|
||||
animate={finalAnimate}
|
||||
animateOnHover={animateOnHover}
|
||||
animateOnTap={animateOnTap}
|
||||
animateOnView={animateOnView}
|
||||
animateOnViewMargin={animateOnViewMargin}
|
||||
animateOnViewOnce={animateOnViewOnce}
|
||||
animation={animationProp ?? parentAnimation}
|
||||
loop={loop ?? parentLoop}
|
||||
loopDelay={loopDelay ?? parentLoopDelay}
|
||||
persistOnAnimateEnd={persistOnAnimateEnd ?? parentPersistOnAnimateEnd}
|
||||
initialOnAnimateEnd={initialOnAnimateEnd ?? parentInitialOnAnimateEnd}
|
||||
delay={delay ?? parentDelay}
|
||||
completeOnStop={completeOnStop ?? parentCompleteOnStop}
|
||||
asChild
|
||||
>
|
||||
<IconComponent
|
||||
size={size}
|
||||
className={cn(
|
||||
className,
|
||||
((animationProp ?? parentAnimation) === 'path' ||
|
||||
(animationProp ?? parentAnimation) === 'path-loop') &&
|
||||
pathClassName,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</AnimateIcon>
|
||||
);
|
||||
}
|
||||
|
||||
const animationToUse = animationProp ?? parentAnimation;
|
||||
const loopToUse = parentLoop;
|
||||
const loopDelayToUse = parentLoopDelay;
|
||||
|
||||
return (
|
||||
<AnimateIconContext.Provider
|
||||
value={{
|
||||
controls,
|
||||
animation: animationToUse,
|
||||
loop: loopToUse,
|
||||
loopDelay: loopDelayToUse,
|
||||
active: parentActive,
|
||||
animate: parentAnimate,
|
||||
initialOnAnimateEnd: parentInitialOnAnimateEnd,
|
||||
delay: parentDelay,
|
||||
completeOnStop: parentCompleteOnStop,
|
||||
}}
|
||||
>
|
||||
<IconComponent
|
||||
size={size}
|
||||
className={cn(
|
||||
className,
|
||||
(animationToUse === 'path' || animationToUse === 'path-loop') &&
|
||||
pathClassName,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</AnimateIconContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
animate !== undefined ||
|
||||
animateOnHover !== undefined ||
|
||||
animateOnTap !== undefined ||
|
||||
animateOnView !== undefined ||
|
||||
animationProp !== undefined
|
||||
) {
|
||||
return (
|
||||
<AnimateIcon
|
||||
animate={animate}
|
||||
animateOnHover={animateOnHover}
|
||||
animateOnTap={animateOnTap}
|
||||
animateOnView={animateOnView}
|
||||
animateOnViewMargin={animateOnViewMargin}
|
||||
animateOnViewOnce={animateOnViewOnce}
|
||||
animation={animationProp}
|
||||
loop={loop}
|
||||
loopDelay={loopDelay}
|
||||
delay={delay}
|
||||
completeOnStop={completeOnStop}
|
||||
asChild
|
||||
>
|
||||
<IconComponent
|
||||
size={size}
|
||||
className={cn(
|
||||
className,
|
||||
(animationProp === 'path' || animationProp === 'path-loop') &&
|
||||
pathClassName,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</AnimateIcon>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<IconComponent
|
||||
size={size}
|
||||
className={cn(
|
||||
className,
|
||||
(animationProp === 'path' || animationProp === 'path-loop') &&
|
||||
pathClassName,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function getVariants<
|
||||
V extends { default: T; [key: string]: T },
|
||||
T extends Record<string, Variants>,
|
||||
>(animations: V): T {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const { animation: animationType } = useAnimateIconContext();
|
||||
|
||||
let result: T;
|
||||
|
||||
if (animationType in staticAnimations) {
|
||||
const variant = staticAnimations[animationType as StaticAnimations];
|
||||
result = {} as T;
|
||||
for (const key in animations.default) {
|
||||
if (
|
||||
(animationType === 'path' || animationType === 'path-loop') &&
|
||||
key.includes('group')
|
||||
)
|
||||
continue;
|
||||
result[key] = variant as T[Extract<keyof T, string>];
|
||||
}
|
||||
} else {
|
||||
result = (animations[animationType as keyof V] as T) ?? animations.default;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export {
|
||||
pathClassName,
|
||||
staticAnimations,
|
||||
AnimateIcon,
|
||||
IconWrapper,
|
||||
useAnimateIconContext,
|
||||
getVariants,
|
||||
type IconProps,
|
||||
type IconWrapperProps,
|
||||
type AnimateIconProps,
|
||||
type AnimateIconContextValue,
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import {motion, type Variants} from 'motion/react';
|
||||
|
||||
import {getVariants, type IconProps, IconWrapper, useAnimateIconContext,} from '@/components/animate-ui/icons/icon';
|
||||
|
||||
type SendProps = IconProps<keyof typeof animations>;
|
||||
|
||||
const animations = {
|
||||
default: {
|
||||
group: {
|
||||
initial: {
|
||||
scale: 1,
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
animate: {
|
||||
scale: [1, 0.8, 1, 1, 1],
|
||||
x: [0, '-10%', '100%', '-125%', 0],
|
||||
y: [0, '10%', '-100%', '125%', 0],
|
||||
transition: {
|
||||
default: { ease: 'easeInOut', duration: 1.2 },
|
||||
x: {
|
||||
ease: 'easeInOut',
|
||||
duration: 1.2,
|
||||
times: [0, 0.25, 0.5, 0.5, 1],
|
||||
},
|
||||
y: {
|
||||
ease: 'easeInOut',
|
||||
duration: 1.2,
|
||||
times: [0, 0.25, 0.5, 0.5, 1],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
path1: {},
|
||||
path2: {},
|
||||
} satisfies Record<string, Variants>,
|
||||
} as const;
|
||||
|
||||
function IconComponent({ size, ...props }: SendProps) {
|
||||
const { controls } = useAnimateIconContext();
|
||||
const variants = getVariants(animations);
|
||||
|
||||
return (
|
||||
<motion.svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
{...props}
|
||||
>
|
||||
<motion.g variants={variants.group} initial="initial" animate={controls}>
|
||||
<motion.path
|
||||
d="M14.5,21.7c.1.3.4.4.7.3.1,0,.2-.2.3-.3L22,2.7c0-.3,0-.5-.3-.6-.1,0-.2,0-.3,0L2.3,8.5c-.3,0-.4.4-.3.6,0,.1.2.2.3.3l7.9,3.2c.5.2.9.6,1.1,1.1l3.2,7.9Z"
|
||||
variants={variants.path1}
|
||||
initial="initial"
|
||||
animate={controls}
|
||||
/>
|
||||
<motion.path
|
||||
d="M21.9,2.1l-10.9,10.9"
|
||||
variants={variants.path2}
|
||||
initial="initial"
|
||||
animate={controls}
|
||||
/>
|
||||
</motion.g>
|
||||
</motion.svg>
|
||||
);
|
||||
}
|
||||
|
||||
function Send(props: SendProps) {
|
||||
return <IconWrapper icon={IconComponent} {...props} />;
|
||||
}
|
||||
|
||||
export {
|
||||
animations,
|
||||
Send,
|
||||
Send as SendIcon,
|
||||
type SendProps,
|
||||
type SendProps as SendIconProps,
|
||||
};
|
||||
Reference in New Issue
Block a user