mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-12 03:36:59 +00:00
0cb5fae947
Install motion + animate-ui, overhaul design tokens with brand cyan accent, and replace CSS keyframe animations in Dialog, Tabs, Switch, and Tooltip with spring-physics and blur-fade transitions via animate-ui Radix primitives.
103 lines
2.6 KiB
TypeScript
103 lines
2.6 KiB
TypeScript
'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;
|
|
};
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, deps);
|
|
|
|
React.useLayoutEffect(() => {
|
|
if (height === 0) {
|
|
const next = measure();
|
|
if (next !== 0) setHeight(next);
|
|
}
|
|
}, [height, measure]);
|
|
|
|
return { ref, height } as const;
|
|
}
|