refactor: animate tabs

This commit is contained in:
zhom
2025-12-11 23:14:42 +04:00
parent 564c57fefc
commit 0b43c6776b
5 changed files with 1073 additions and 18 deletions
+101
View File
@@ -0,0 +1,101 @@
"use client";
import * as React from "react";
type AutoHeightOptions = {
includeParentBox?: boolean;
includeSelfBox?: boolean;
};
export function useAutoHeight<T extends HTMLElement = HTMLDivElement>(
deps: React.DependencyList = [],
options: AutoHeightOptions = {
includeParentBox: true,
includeSelfBox: false,
},
) {
const ref = React.useRef<T | null>(null);
const roRef = React.useRef<ResizeObserver | null>(null);
const [height, setHeight] = React.useState(0);
const measure = React.useCallback(() => {
const el = ref.current;
if (!el) return 0;
const base = el.getBoundingClientRect().height || 0;
let extra = 0;
if (options.includeParentBox && el.parentElement) {
const cs = getComputedStyle(el.parentElement);
const paddingY =
(parseFloat(cs.paddingTop || "0") || 0) +
(parseFloat(cs.paddingBottom || "0") || 0);
const borderY =
(parseFloat(cs.borderTopWidth || "0") || 0) +
(parseFloat(cs.borderBottomWidth || "0") || 0);
const isBorderBox = cs.boxSizing === "border-box";
if (isBorderBox) {
extra += paddingY + borderY;
}
}
if (options.includeSelfBox) {
const cs = getComputedStyle(el);
const paddingY =
(parseFloat(cs.paddingTop || "0") || 0) +
(parseFloat(cs.paddingBottom || "0") || 0);
const borderY =
(parseFloat(cs.borderTopWidth || "0") || 0) +
(parseFloat(cs.borderBottomWidth || "0") || 0);
const isBorderBox = cs.boxSizing === "border-box";
if (isBorderBox) {
extra += paddingY + borderY;
}
}
const dpr =
typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
const total = Math.ceil((base + extra) * dpr) / dpr;
return total;
}, [options.includeParentBox, options.includeSelfBox]);
React.useLayoutEffect(() => {
const el = ref.current;
if (!el) return;
setHeight(measure());
if (roRef.current) {
roRef.current.disconnect();
roRef.current = null;
}
const ro = new ResizeObserver(() => {
const next = measure();
requestAnimationFrame(() => setHeight(next));
});
ro.observe(el);
if (options.includeParentBox && el.parentElement) {
ro.observe(el.parentElement);
}
roRef.current = ro;
return () => {
ro.disconnect();
roRef.current = null;
};
}, [...deps, measure, options.includeParentBox]);
React.useLayoutEffect(() => {
if (height === 0) {
const next = measure();
if (next !== 0) setHeight(next);
}
}, [height, measure]);
return { ref, height } as const;
}