UNPKG

@navikt/ds-react

Version:

React components from the Norwegian Labour and Welfare Administration.

180 lines 8.77 kB
var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; /* https://github.com/Stanko/react-animate-height/blob/v3/src/index.tsx */ import React, { useEffect, useRef, useState } from "react"; import { useRenameCSS } from "../theme/Theme.js"; import { useTimeout } from "../util/hooks/useTimeout.js"; // ------------------ Helpers const prefersReducedMotion = (globalThis === null || globalThis === void 0 ? void 0 : globalThis.matchMedia) ? globalThis.matchMedia("(prefers-reduced-motion: reduce)").matches : false; function isNumber(n) { const number = parseFloat(n); return !Number.isNaN(number) && Number.isFinite(number); } function isPercentage(height) { // Percentage height return (typeof height === "string" && height[height.length - 1] === "%" && isNumber(height.substring(0, height.length - 1))); } function hideContent(element, height) { // Check for element?.style is added cause this would fail in tests (react-test-renderer) // Read more here: https://github.com/Stanko/react-animate-height/issues/17 if (height === 0 && (element === null || element === void 0 ? void 0 : element.style)) { element.style.display = "none"; } } function showContent(element, height) { // Check for element?.style is added cause this would fail in tests (react-test-renderer) // Read more here: https://github.com/Stanko/react-animate-height/issues/17 if (height === 0 && (element === null || element === void 0 ? void 0 : element.style)) { element.style.display = ""; } } const AnimateHeight = (_a) => { var { children, className, innerClassName, duration: userDuration = 250, easing = "ease", height } = _a, props = __rest(_a, ["children", "className", "innerClassName", "duration", "easing", "height"]); const { cn } = useRenameCSS(); // ------------------ Initialization const prevHeight = useRef(height); const contentElement = useRef(null); const animationClassTimeout = useTimeout(); const animationTimeout = useTimeout(); const initialHeight = useRef(height); const initialOverflow = useRef("visible"); const duration = prefersReducedMotion ? 0 : userDuration; if (typeof initialHeight.current === "number") { // Reset negative height to 0 if (typeof height !== "string") { initialHeight.current = height < 0 ? 0 : height; } initialOverflow.current = "hidden"; } else if (isPercentage(initialHeight.current)) { // If value is string "0%" make sure we convert it to number 0 initialHeight.current = height === "0%" ? 0 : height; initialOverflow.current = "hidden"; } const [currentHeight, setCurrentHeight] = useState(initialHeight.current); const [overflow, setOverflow] = useState(initialOverflow.current); const [useTransitions, setUseTransitions] = useState(false); useEffect(() => { // Hide content if height is 0 (to prevent tabbing into it) hideContent(contentElement.current, initialHeight.current); }, []); // ------------------ Height update // biome-ignore lint/correctness/useExhaustiveDependencies: This should be explicitly run only on height change useEffect(() => { if (height !== prevHeight.current && contentElement.current) { showContent(contentElement.current, prevHeight.current); // Cache content height contentElement.current.style.overflow = "hidden"; const contentHeight = contentElement.current.offsetHeight; contentElement.current.style.overflow = ""; // set total animation time const totalDuration = duration; let newHeight; let timeoutHeight; let timeoutOverflow = "hidden"; let timeoutUseTransitions; const isCurrentHeightAuto = prevHeight.current === "auto"; if (typeof height === "number") { // Reset negative height to 0 newHeight = height < 0 ? 0 : height; timeoutHeight = newHeight; } else if (isPercentage(height)) { // If value is string "0%" make sure we convert it to number 0 newHeight = height === "0%" ? 0 : height; timeoutHeight = newHeight; } else { // If not, animate to content height // and then reset to auto newHeight = contentHeight; // TODO solve contentHeight = 0 timeoutHeight = "auto"; timeoutOverflow = undefined; } if (isCurrentHeightAuto) { // This is the height to be animated to timeoutHeight = newHeight; // If previous height was 'auto' // set starting height explicitly to be able to use transition newHeight = contentHeight; } // Set starting height and animating classes // When animating from 'auto' we first need to set fixed height // that change should be animated setCurrentHeight(newHeight); setOverflow("hidden"); setUseTransitions(!isCurrentHeightAuto); if (isCurrentHeightAuto) { // When animating from 'auto' we use a short timeout to start animation // after setting fixed height above timeoutUseTransitions = true; // Short timeout to allow rendering of the initial animation state first animationTimeout.start(50, () => { setCurrentHeight(timeoutHeight); setOverflow(timeoutOverflow); setUseTransitions(timeoutUseTransitions); }); // Set static classes and remove transitions when animation ends animationClassTimeout.start(totalDuration, () => { setUseTransitions(false); // ANIMATION ENDS // Hide content if height is 0 (to prevent tabbing into it) hideContent(contentElement.current, timeoutHeight); }); } else { // Set end height, classes and remove transitions when animation is complete animationTimeout.start(totalDuration, () => { setCurrentHeight(timeoutHeight); setOverflow(timeoutOverflow); setUseTransitions(false); // ANIMATION ENDS // If height is auto, don't hide the content // (case when element is empty, therefore height is 0) if (height !== "auto") { // Hide content if height is 0 (to prevent tabbing into it) hideContent(contentElement.current, newHeight); // TODO solve newHeight = 0 } }); } } prevHeight.current = height; /* We need to manually clear here since we cant guarantee the `.start()` getting called after `height` changes */ return () => { animationTimeout.clear(); animationClassTimeout.clear(); }; // This should be explicitly run only on height change // eslint-disable-next-line react-hooks/exhaustive-deps }, [height]); // ------------------ Render const componentStyle = { height: currentHeight, overflow, }; if (useTransitions) { componentStyle.transition = `height ${duration}ms ${easing} 0ms`; // Add webkit vendor prefix still used by opera, blackberry... componentStyle.WebkitTransition = componentStyle.transition; } // Check if user passed aria-hidden prop const hasAriaHiddenProp = typeof props["aria-hidden"] !== "undefined"; const ariaHidden = hasAriaHiddenProp ? props["aria-hidden"] : height === 0; return (React.createElement("div", Object.assign({}, props, { className: cn(className), style: componentStyle }), React.createElement("div", { "aria-hidden": ariaHidden, className: cn(innerClassName), ref: contentElement }, children))); }; export default AnimateHeight; //# sourceMappingURL=AnimateHeight.js.map