mirror of
https://github.com/Snigdha-OS/documentation.git
synced 2025-09-13 20:14:56 +02:00
157 lines
6.0 KiB
JavaScript
157 lines
6.0 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
import React, { useState, useEffect, useRef, useCallback, } from 'react';
|
|
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
|
import useIsomorphicLayoutEffect from '@docusaurus/useIsomorphicLayoutEffect';
|
|
import { prefersReducedMotion } from '../../utils/accessibilityUtils';
|
|
const DefaultAnimationEasing = 'ease-in-out';
|
|
/**
|
|
* This hook is a very thin wrapper around a `useState`.
|
|
*/
|
|
export function useCollapsible({ initialState, }) {
|
|
const [collapsed, setCollapsed] = useState(initialState ?? false);
|
|
const toggleCollapsed = useCallback(() => {
|
|
setCollapsed((expanded) => !expanded);
|
|
}, []);
|
|
return {
|
|
collapsed,
|
|
setCollapsed,
|
|
toggleCollapsed,
|
|
};
|
|
}
|
|
const CollapsedStyles = {
|
|
display: 'none',
|
|
overflow: 'hidden',
|
|
height: '0px',
|
|
};
|
|
const ExpandedStyles = {
|
|
display: 'block',
|
|
overflow: 'visible',
|
|
height: 'auto',
|
|
};
|
|
function applyCollapsedStyle(el, collapsed) {
|
|
const collapsedStyles = collapsed ? CollapsedStyles : ExpandedStyles;
|
|
el.style.display = collapsedStyles.display;
|
|
el.style.overflow = collapsedStyles.overflow;
|
|
el.style.height = collapsedStyles.height;
|
|
}
|
|
/*
|
|
Lex111: Dynamic transition duration is used in Material design, this technique
|
|
is good for a large number of items.
|
|
https://material.io/archive/guidelines/motion/duration-easing.html#duration-easing-dynamic-durations
|
|
https://github.com/mui-org/material-ui/blob/e724d98eba018e55e1a684236a2037e24bcf050c/packages/material-ui/src/styles/createTransitions.js#L40-L43
|
|
*/
|
|
function getAutoHeightDuration(height) {
|
|
if (prefersReducedMotion()) {
|
|
// Not using 0 because it prevents onTransitionEnd to fire and bubble up :/
|
|
// See https://github.com/facebook/docusaurus/pull/8906
|
|
return 1;
|
|
}
|
|
const constant = height / 36;
|
|
return Math.round((4 + 15 * constant ** 0.25 + constant / 5) * 10);
|
|
}
|
|
function useCollapseAnimation({ collapsibleRef, collapsed, animation, }) {
|
|
const mounted = useRef(false);
|
|
useEffect(() => {
|
|
const el = collapsibleRef.current;
|
|
function getTransitionStyles() {
|
|
const height = el.scrollHeight;
|
|
const duration = animation?.duration ?? getAutoHeightDuration(height);
|
|
const easing = animation?.easing ?? DefaultAnimationEasing;
|
|
return {
|
|
transition: `height ${duration}ms ${easing}`,
|
|
height: `${height}px`,
|
|
};
|
|
}
|
|
function applyTransitionStyles() {
|
|
const transitionStyles = getTransitionStyles();
|
|
el.style.transition = transitionStyles.transition;
|
|
el.style.height = transitionStyles.height;
|
|
}
|
|
// On mount, we just apply styles, no animated transition
|
|
if (!mounted.current) {
|
|
applyCollapsedStyle(el, collapsed);
|
|
mounted.current = true;
|
|
return undefined;
|
|
}
|
|
el.style.willChange = 'height';
|
|
function startAnimation() {
|
|
const animationFrame = requestAnimationFrame(() => {
|
|
// When collapsing
|
|
if (collapsed) {
|
|
applyTransitionStyles();
|
|
requestAnimationFrame(() => {
|
|
el.style.height = CollapsedStyles.height;
|
|
el.style.overflow = CollapsedStyles.overflow;
|
|
});
|
|
}
|
|
// When expanding
|
|
else {
|
|
el.style.display = 'block';
|
|
requestAnimationFrame(() => {
|
|
applyTransitionStyles();
|
|
});
|
|
}
|
|
});
|
|
return () => cancelAnimationFrame(animationFrame);
|
|
}
|
|
return startAnimation();
|
|
}, [collapsibleRef, collapsed, animation]);
|
|
}
|
|
/**
|
|
* Prevent hydration layout shift before animations are handled imperatively
|
|
* with JS
|
|
*/
|
|
function getSSRStyle(collapsed) {
|
|
if (ExecutionEnvironment.canUseDOM) {
|
|
return undefined;
|
|
}
|
|
return collapsed ? CollapsedStyles : ExpandedStyles;
|
|
}
|
|
function CollapsibleBase({ as: As = 'div', collapsed, children, animation, onCollapseTransitionEnd, className, disableSSRStyle, }) {
|
|
const collapsibleRef = useRef(null);
|
|
useCollapseAnimation({ collapsibleRef, collapsed, animation });
|
|
return (<As
|
|
// @ts-expect-error: the "too complicated type" is produced from
|
|
// "CollapsibleElementType" being a huge union
|
|
ref={collapsibleRef} // Refs are contravariant, which is not expressible in TS
|
|
style={disableSSRStyle ? undefined : getSSRStyle(collapsed)} onTransitionEnd={(e) => {
|
|
if (e.propertyName !== 'height') {
|
|
return;
|
|
}
|
|
applyCollapsedStyle(collapsibleRef.current, collapsed);
|
|
onCollapseTransitionEnd?.(collapsed);
|
|
}} className={className}>
|
|
{children}
|
|
</As>);
|
|
}
|
|
function CollapsibleLazy({ collapsed, ...props }) {
|
|
const [mounted, setMounted] = useState(!collapsed);
|
|
// Updated in effect so that first expansion transition can work
|
|
const [lazyCollapsed, setLazyCollapsed] = useState(collapsed);
|
|
useIsomorphicLayoutEffect(() => {
|
|
if (!collapsed) {
|
|
setMounted(true);
|
|
}
|
|
}, [collapsed]);
|
|
useIsomorphicLayoutEffect(() => {
|
|
if (mounted) {
|
|
setLazyCollapsed(collapsed);
|
|
}
|
|
}, [mounted, collapsed]);
|
|
return mounted ? (<CollapsibleBase {...props} collapsed={lazyCollapsed}/>) : null;
|
|
}
|
|
/**
|
|
* A headless component providing smooth and uniform collapsing behavior. The
|
|
* component will be invisible (zero height) when collapsed. Doesn't provide
|
|
* interactivity by itself: collapse state is toggled through props.
|
|
*/
|
|
export function Collapsible({ lazy, ...props }) {
|
|
const Comp = lazy ? CollapsibleLazy : CollapsibleBase;
|
|
return <Comp {...props}/>;
|
|
}
|
|
//# sourceMappingURL=index.js.map
|