openpanel/documentation/src/refine-theme/changing-text-element.tsx
2024-02-05 10:23:04 +01:00

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
);
},
);