1import { type ReactNode, useLayoutEffect, useRef, useState } from "react";
2
3import { observeResize } from "~/lib/observeResize";
4import { cn } from "~/lib/utils";
5
6export function AnimatedWidth({
7 children,
8 className,
9}: {
10 readonly children: ReactNode;
11 readonly className?: string;
12}) {
13 const contentRef = useRef<HTMLSpanElement>(null);
14 const [width, setWidth] = useState<number | null>(null);
15
16 useLayoutEffect(() => {
17 const element = contentRef.current;
18 if (!element) return;
19 return observeResize(element, (entries) => {
20 const nextWidth = Math.ceil(entries.at(-1)?.contentRect.width ?? 0);
21 if (nextWidth > 0) setWidth(nextWidth);
22 });
23 }, []);
24
25 return (
26 <span
27 data-slot="animated-width"
28 className={cn(
29 "inline-block overflow-hidden transition-[width] duration-150 ease-out motion-reduce:transition-none",
30 className,
31 )}
32 style={width === null ? undefined : { width }}
33 >
34 <span ref={contentRef} className="block w-max whitespace-nowrap">
35 {children}
36 </span>
37 </span>
38 );
39}