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:
ryan
2026-06-19 14:23:29 +08:00
parent 19d476ed7f
commit 63cd906cfc
1064 changed files with 366 additions and 1397 deletions
@@ -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,
};