mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-08 00:26:37 +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,133 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import {type SpringOptions, useMotionValue, useSpring} from 'motion/react';
|
||||
|
||||
import {useIsInView, type UseIsInViewOptions,} from '@/hooks/use-is-in-view';
|
||||
|
||||
type CountingNumberProps = Omit<React.ComponentProps<'span'>, 'children'> & {
|
||||
number: number;
|
||||
fromNumber?: number;
|
||||
padStart?: boolean;
|
||||
decimalSeparator?: string;
|
||||
decimalPlaces?: number;
|
||||
transition?: SpringOptions;
|
||||
delay?: number;
|
||||
initiallyStable?: boolean;
|
||||
thousandSeparator?: string;
|
||||
} & UseIsInViewOptions;
|
||||
|
||||
function CountingNumber({
|
||||
ref,
|
||||
number,
|
||||
fromNumber = 0,
|
||||
padStart = false,
|
||||
inView = false,
|
||||
inViewMargin = '0px',
|
||||
inViewOnce = true,
|
||||
decimalSeparator = '.',
|
||||
transition = { stiffness: 200, damping: 30 },
|
||||
decimalPlaces = 0,
|
||||
delay = 0,
|
||||
initiallyStable = false,
|
||||
thousandSeparator = '',
|
||||
...props
|
||||
}: CountingNumberProps) {
|
||||
const { ref: localRef, isInView } = useIsInView(
|
||||
ref as React.Ref<HTMLElement>,
|
||||
{
|
||||
inView,
|
||||
inViewOnce,
|
||||
inViewMargin,
|
||||
},
|
||||
);
|
||||
|
||||
const numberStr = number.toString();
|
||||
const decimals =
|
||||
typeof decimalPlaces === 'number'
|
||||
? decimalPlaces
|
||||
: numberStr.includes('.')
|
||||
? (numberStr.split('.')[1]?.length ?? 0)
|
||||
: 0;
|
||||
|
||||
const motionVal = useMotionValue(initiallyStable ? number : fromNumber);
|
||||
const springVal = useSpring(motionVal, transition);
|
||||
|
||||
React.useEffect(() => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (isInView) motionVal.set(number);
|
||||
}, delay);
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [isInView, number, motionVal, delay]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const unsubscribe = springVal.on('change', (latest) => {
|
||||
if (localRef.current) {
|
||||
let formatted =
|
||||
decimals > 0
|
||||
? latest.toFixed(decimals)
|
||||
: Math.round(latest).toString();
|
||||
|
||||
if (decimals > 0) {
|
||||
formatted = formatted.replace('.', decimalSeparator);
|
||||
}
|
||||
|
||||
if (padStart) {
|
||||
const finalIntLength = Math.floor(Math.abs(number)).toString().length;
|
||||
const [intPart, fracPart] = formatted.split(decimalSeparator);
|
||||
const paddedInt = intPart?.padStart(finalIntLength, '0') ?? '';
|
||||
formatted = fracPart
|
||||
? `${ paddedInt }${ decimalSeparator }${ fracPart }`
|
||||
: paddedInt;
|
||||
}
|
||||
|
||||
if (thousandSeparator) {
|
||||
const [intPart, fracPart] = formatted.split(decimalSeparator);
|
||||
const regex = /\B(?=(\d{3})+(?!\d))/g;
|
||||
const separatedInt = intPart.replace(regex, thousandSeparator);
|
||||
formatted = fracPart
|
||||
? `${ separatedInt }${ decimalSeparator }${ fracPart }`
|
||||
: separatedInt;
|
||||
}
|
||||
|
||||
localRef.current.textContent = formatted;
|
||||
}
|
||||
});
|
||||
return () => unsubscribe();
|
||||
}, [springVal, decimals, padStart, number, decimalSeparator, localRef, thousandSeparator]);
|
||||
|
||||
const finalIntLength = Math.floor(Math.abs(number)).toString().length;
|
||||
|
||||
const formatValue = (val: number) => {
|
||||
let out = decimals > 0 ? val.toFixed(decimals) : Math.round(val).toString();
|
||||
if (decimals > 0) out = out.replace('.', decimalSeparator);
|
||||
if (padStart) {
|
||||
const [intPart, fracPart] = out.split(decimalSeparator);
|
||||
const paddedInt = (intPart ?? '').padStart(finalIntLength, '0');
|
||||
out = fracPart ? `${ paddedInt }${ decimalSeparator }${ fracPart }` : paddedInt;
|
||||
}
|
||||
if (thousandSeparator) {
|
||||
const [intPart, fracPart] = out.split(decimalSeparator);
|
||||
const regex = /\B(?=(\d{3})+(?!\d))/g;
|
||||
const separatedInt = intPart.replace(regex, thousandSeparator);
|
||||
out = fracPart ? `${ separatedInt }${ decimalSeparator }${ fracPart }` : separatedInt;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const zeroText = padStart
|
||||
? '0'.padStart(finalIntLength, '0') +
|
||||
(decimals > 0 ? decimalSeparator + '0'.repeat(decimals) : '')
|
||||
: '0' + (decimals > 0 ? decimalSeparator + '0'.repeat(decimals) : '');
|
||||
|
||||
const initialText = initiallyStable ? formatValue(number) : zeroText;
|
||||
|
||||
return (
|
||||
<span ref={localRef} data-slot="counting-number" {...props}>
|
||||
{initialText}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export { CountingNumber, type CountingNumberProps };
|
||||
Reference in New Issue
Block a user