mirror of
https://github.com/stefanpejcic/openpanel
synced 2025-06-26 18:28:26 +00:00
119 lines
3.3 KiB
TypeScript
119 lines
3.3 KiB
TypeScript
import clsx from "clsx";
|
|
import React from "react";
|
|
|
|
type Props = {
|
|
first: string;
|
|
second: string;
|
|
onEnd?: () => void;
|
|
tick?: number;
|
|
className?: string;
|
|
prevClassName?: string;
|
|
nextClassName?: string;
|
|
activeClassName?: string;
|
|
};
|
|
|
|
export type ChangingTextElementRef = {
|
|
start: () => void;
|
|
reset: () => void;
|
|
};
|
|
|
|
export const ChangingTextElement = React.memo(
|
|
React.forwardRef<ChangingTextElementRef, Props>(function ChangingText(
|
|
{
|
|
first,
|
|
second,
|
|
onEnd,
|
|
tick = 75,
|
|
className,
|
|
prevClassName,
|
|
nextClassName,
|
|
activeClassName,
|
|
},
|
|
ref,
|
|
) {
|
|
const maxLen = Math.max(first.length, second.length);
|
|
const prev = first.padEnd(maxLen, " ");
|
|
const next = second.padEnd(maxLen, " ");
|
|
const [pos, setPos] = React.useState(-1);
|
|
const [status, setStatus] = React.useState<"idle" | "running" | "done">(
|
|
"idle",
|
|
);
|
|
|
|
React.useImperativeHandle(ref, () => ({
|
|
start: () => {
|
|
setPos(-1);
|
|
setStatus("running");
|
|
},
|
|
reset: () => {
|
|
setPos(-1);
|
|
setStatus("idle");
|
|
},
|
|
}));
|
|
|
|
React.useEffect(() => {
|
|
if (status === "running") {
|
|
setPos(-1);
|
|
}
|
|
}, [status]);
|
|
|
|
React.useEffect(() => {
|
|
if (status === "running") {
|
|
const timer = setInterval(() => {
|
|
setPos((prev) => {
|
|
if (prev === maxLen - 1) {
|
|
setStatus("done");
|
|
return prev;
|
|
}
|
|
|
|
return prev + 1;
|
|
});
|
|
}, tick);
|
|
|
|
return () => clearInterval(timer);
|
|
}
|
|
}, [status, tick, maxLen]);
|
|
|
|
React.useEffect(() => {
|
|
if (status === "done") {
|
|
onEnd?.();
|
|
}
|
|
}, [status]);
|
|
|
|
return (
|
|
<span className={clsx("will-change-contents", className)}>
|
|
<span className={clsx("will-change-contents", prevClassName)}>
|
|
{next
|
|
.split("")
|
|
.slice(0, Math.max(pos, 0))
|
|
|
|
.map((letter) => {
|
|
return letter;
|
|
})}
|
|
</span>
|
|
<span className={clsx("will-change-contents", activeClassName)}>
|
|
{next.split("")[pos] ?? ""}
|
|
</span>
|
|
<span className={clsx("will-change-contents", nextClassName)}>
|
|
{prev
|
|
.split("")
|
|
.slice(pos + 1)
|
|
.map((letter) => {
|
|
return letter;
|
|
})}
|
|
</span>
|
|
</span>
|
|
);
|
|
}),
|
|
(prev, next) => {
|
|
return (
|
|
prev.first === next.first &&
|
|
prev.second === next.second &&
|
|
prev.tick === next.tick &&
|
|
prev.className === next.className &&
|
|
prev.prevClassName === next.prevClassName &&
|
|
prev.nextClassName === next.nextClassName &&
|
|
prev.activeClassName === next.activeClassName
|
|
);
|
|
},
|
|
);
|