stories/transitions/Bootstrap.js (142 lines of code) (raw):
import { css } from 'astroturf';
import React, { useEffect, useRef } from 'react';
import style from 'dom-helpers/css';
import Transition, {
EXITED,
ENTERED,
ENTERING,
EXITING,
} from '../../src/Transition';
const styles = css`
.fade {
opacity: 0;
transition: opacity 0.15s linear;
}
.fade.in {
opacity: 1;
}
.collapse {
display: none;
}
.collapse.in {
display: block;
}
.collapsing {
position: relative;
height: 0;
overflow: hidden;
transition: 0.35s ease;
transition-property: height, visibility;
}
`;
const fadeStyles = {
[ENTERING]: styles.in,
[ENTERED]: styles.in,
};
export function Fade(props) {
const nodeRef = useRef();
return (
<Transition
{...props}
nodeRef={nodeRef}
className={styles.fade}
timeout={150}
>
{(status) => (
<div
ref={nodeRef}
className={`${styles.fade} ${fadeStyles[status] || ''}`}
>
{props.children}
</div>
)}
</Transition>
);
}
function getHeight(elem) {
let value = elem.offsetHeight;
let margins = ['marginTop', 'marginBottom'];
return (
value +
parseInt(style(elem, margins[0]), 10) +
parseInt(style(elem, margins[1]), 10)
);
}
const collapseStyles = {
[EXITED]: styles.collapse,
[EXITING]: styles.collapsing,
[ENTERING]: styles.collapsing,
[ENTERED]: `${styles.collapse} ${styles.in}`,
};
export class Collapse extends React.Component {
nodeRef = React.createRef();
/* -- Expanding -- */
handleEnter = () => {
this.nodeRef.current.style.height = '0';
};
handleEntering = () => {
this.nodeRef.current.style.height = `${this.nodeRef.current.scrollHeight}px`;
};
handleEntered = () => {
this.nodeRef.current.style.height = null;
};
/* -- Collapsing -- */
handleExit = () => {
this.nodeRef.current.style.height = getHeight(this.nodeRef.current) + 'px';
this.nodeRef.current.offsetHeight; // eslint-disable-line no-unused-expressions
};
handleExiting = () => {
this.nodeRef.current.style.height = '0';
};
render() {
const { children, ...rest } = this.props;
return (
<Transition
{...rest}
nodeRef={this.nodeRef}
timeout={350}
onEnter={this.handleEnter}
onEntering={this.handleEntering}
onEntered={this.handleEntered}
onExit={this.handleExit}
onExiting={this.handleExiting}
>
{(state, props) => (
<div ref={this.nodeRef} className={collapseStyles[state]} {...props}>
{children}
</div>
)}
</Transition>
);
}
}
export function FadeInnerRef(props) {
const nodeRef = useMergedRef(props.innerRef);
return (
<Transition
{...props}
nodeRef={nodeRef}
className={styles.fade}
timeout={150}
>
{(status) => (
<div
ref={nodeRef}
className={`${styles.fade} ${fadeStyles[status] || ''}`}
>
{props.children}
</div>
)}
</Transition>
);
}
export const FadeForwardRef = React.forwardRef((props, ref) => {
return <FadeInnerRef {...props} innerRef={ref} />;
});
/**
* Compose multiple refs, there may be different implementations
* This one is derived from
* e.g. https://github.com/react-restart/hooks/blob/ed37bf3dfc8fc1d9234a6d8fe0af94d69fad3b74/src/useMergedRefs.ts
* Also here are good discussion about this
* https://github.com/facebook/react/issues/13029
* @param ref
* @returns {React.MutableRefObject<undefined>}
*/
function useMergedRef(ref) {
const nodeRef = React.useRef();
useEffect(function () {
if (ref) {
if (typeof ref === 'function') {
ref(nodeRef.current);
} else {
ref.current = nodeRef.current;
}
}
});
return nodeRef;
}