UNPKG

@chayns-components/core

Version:

A set of beautiful React components for developing your own applications with chayns.

176 lines (174 loc) • 7.3 kB
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { ClampPosition } from '../../types/truncation'; import { debounce } from '../../utils/debounce'; import { truncateElement } from '../../utils/truncation'; import { StyledMotionTruncationContent, StyledTruncation, StyledTruncationClamp, StyledTruncationClampWrapper, StyledTruncationPseudoContent } from './Truncation.styles'; const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect; const Truncation = _ref => { let { collapsedHeight = 150, clampPosition = ClampPosition.Right, isOpen, moreLabel = 'Mehr', lessLabel = 'Weniger', onChange, children } = _ref; const [internalIsOpen, setInternalIsOpen] = useState(false); const [showClamp, setShowClamp] = useState(true); const [newCollapsedHeight, setNewCollapsedHeight] = useState(collapsedHeight); const [originalHeight, setOriginalHeight] = useState(0); const [shouldShowCollapsedElement, setShouldShowCollapsedElement] = useState(true); const [hasSizeChanged, setHasSizeChanged] = useState(false); const [initialRender, setInitialRender] = useState(true); const [shouldSkipChangeCheck, setShouldSkipChangeCheck] = useState(false); const [originalSmallHeight, setOriginalSmallHeight] = useState(0); const [originalBigHeight, setOriginalBigHeight] = useState(0); useEffect(() => { setInitialRender(false); }, []); const parentRef = useRef(null); const pseudoChildrenRef = useRef(null); const childrenRef = useRef(null); const originalChildrenRef = useRef(null); const hasCollapsed = useRef(false); const isAnimating = useRef(false); const hasSizeRecentlyChanged = useRef(false); const canResetSizeChanged = useRef(true); useEffect(() => { if (typeof isOpen === 'boolean') { setInternalIsOpen(isOpen); setShowClamp(!isOpen); } }, [isOpen]); // Changes the state of the truncation const handleClampClick = useCallback(event => { setInternalIsOpen(current => { if (typeof onChange === 'function') { onChange(event, !current); } return !current; }); }, [onChange]); useEffect(() => { if (children) { setShouldSkipChangeCheck(true); window.setTimeout(() => { setShouldSkipChangeCheck(false); }, 200); } }, [children]); const handleAnimationEnd = useCallback(() => { hasCollapsed.current = true; isAnimating.current = false; if (canResetSizeChanged.current) { setHasSizeChanged(false); canResetSizeChanged.current = false; } window.setTimeout(() => { hasSizeRecentlyChanged.current = false; }, 10); setShouldShowCollapsedElement(!internalIsOpen); window.setTimeout(() => { hasCollapsed.current = false; }, 30); }, [internalIsOpen]); useEffect(() => { if (!pseudoChildrenRef.current) { return; } setOriginalHeight(pseudoChildrenRef.current.offsetHeight); setOriginalBigHeight(pseudoChildrenRef.current.offsetHeight); truncateElement(pseudoChildrenRef.current, collapsedHeight); setNewCollapsedHeight(pseudoChildrenRef.current.offsetHeight); setOriginalSmallHeight(pseudoChildrenRef.current.offsetHeight); }, [collapsedHeight, pseudoChildrenRef, children]); // Checks if the clamp should be shown useEffect(() => { if (pseudoChildrenRef.current && (!hasSizeChanged || shouldSkipChangeCheck) && !initialRender) { setShowClamp(originalHeight > newCollapsedHeight); } }, [shouldSkipChangeCheck, collapsedHeight, hasSizeChanged, initialRender, newCollapsedHeight, originalHeight, children]); useEffect(() => { if (childrenRef.current && pseudoChildrenRef.current && originalChildrenRef.current) { while (childrenRef.current.firstChild) { childrenRef.current.removeChild(childrenRef.current.firstChild); } childrenRef.current.appendChild(shouldShowCollapsedElement && !internalIsOpen ? pseudoChildrenRef.current : originalChildrenRef.current); parentRef.current?.appendChild(shouldShowCollapsedElement && !internalIsOpen ? originalChildrenRef.current : pseudoChildrenRef.current); childrenRef.current.children[0].style.visibility = 'visible'; } }, [children, internalIsOpen, shouldShowCollapsedElement]); useIsomorphicLayoutEffect(() => { if (originalChildrenRef.current) { const resizeObserver = new ResizeObserver(entries => { if (entries && entries[0]) { const observedHeight = entries[0].contentRect.height; setOriginalHeight(observedHeight < originalBigHeight ? originalBigHeight : observedHeight); if (!hasCollapsed.current && !isAnimating.current && !hasSizeRecentlyChanged.current) { void debounce(() => { canResetSizeChanged.current = true; }, 250)(); setHasSizeChanged(true); hasSizeRecentlyChanged.current = true; } } }); resizeObserver.observe(originalChildrenRef.current); return () => { resizeObserver.disconnect(); }; } return () => {}; }, [originalBigHeight, children]); useIsomorphicLayoutEffect(() => { if (pseudoChildrenRef.current) { const resizeObserver = new ResizeObserver(entries => { if (entries && entries[0]) { const observedHeight = entries[0].contentRect.height; setNewCollapsedHeight(observedHeight < originalSmallHeight ? originalSmallHeight : observedHeight); if (!hasCollapsed.current && !isAnimating.current && !hasSizeRecentlyChanged.current) { void debounce(() => { canResetSizeChanged.current = true; }, 250)(); setHasSizeChanged(true); hasSizeRecentlyChanged.current = true; } } }); resizeObserver.observe(pseudoChildrenRef.current); return () => { resizeObserver.disconnect(); }; } return () => {}; }, [originalSmallHeight, children]); return useMemo(() => /*#__PURE__*/React.createElement(StyledTruncation, { className: "beta-chayns-truncation", ref: parentRef }, /*#__PURE__*/React.createElement(StyledTruncationPseudoContent, { ref: pseudoChildrenRef }, children), /*#__PURE__*/React.createElement(StyledTruncationPseudoContent, { ref: originalChildrenRef }, children), /*#__PURE__*/React.createElement(StyledMotionTruncationContent, { animate: { height: internalIsOpen ? originalHeight : newCollapsedHeight }, initial: false, transition: { type: 'tween', duration: hasSizeChanged ? 0 : 0.2 }, onAnimationComplete: handleAnimationEnd, onAnimationStart: () => { isAnimating.current = true; }, ref: childrenRef }), showClamp && /*#__PURE__*/React.createElement(StyledTruncationClampWrapper, { $position: clampPosition }, /*#__PURE__*/React.createElement(StyledTruncationClamp, { onClick: handleClampClick }, internalIsOpen ? lessLabel : moreLabel))), [children, clampPosition, handleAnimationEnd, handleClampClick, hasSizeChanged, internalIsOpen, lessLabel, moreLabel, newCollapsedHeight, originalHeight, showClamp]); }; export default Truncation; //# sourceMappingURL=Truncation.js.map