stremio-web/src/components/BottomSheet/BottomSheet.tsx

93 lines
2.9 KiB
TypeScript

// Copyright (C) 2017-2024 Smart code 203358507
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import classNames from 'classnames';
import useBinaryState from 'stremio/common/useBinaryState';
import useOrientation from 'stremio/common/useOrientation';
import styles from './BottomSheet.less';
const CLOSE_THRESHOLD = 100;
type Props = {
children: JSX.Element,
title: string,
show: boolean,
onClose: () => void,
};
const BottomSheet = ({ children, title, show, onClose }: Props) => {
const containerRef = useRef<HTMLDivElement>(null);
const orientation = useOrientation();
const [startOffset, setStartOffset] = useState(0);
const [offset, setOffset] = useState(0);
const [opened, open, close] = useBinaryState();
const containerStyle = useMemo(() => ({
transform: `translateY(${offset}px)`
}), [offset]);
const containerHeight = () => containerRef.current?.offsetHeight ?? 0;
const onCloseRequest = () => setOffset(containerHeight());
const onTouchStart = ({ touches }: React.TouchEvent<HTMLDivElement>) => {
const { clientY } = touches[0];
setStartOffset(clientY);
};
const onTouchMove = useCallback(({ touches }: React.TouchEvent<HTMLDivElement>) => {
const { clientY } = touches[0];
setOffset(Math.max(0, clientY - startOffset));
}, [startOffset]);
const onTouchEnd = () => {
setOffset((offset) => offset > CLOSE_THRESHOLD ? containerHeight() : 0);
setStartOffset(0);
};
const onTransitionEnd = useCallback(() => {
(offset === containerHeight()) && close();
}, [offset]);
useEffect(() => {
setOffset(0);
show ? open() : close();
}, [show]);
useEffect(() => {
!opened && onClose();
}, [opened]);
useEffect(() => {
opened && close();
}, [orientation]);
return opened && createPortal((
<div className={styles['bottom-sheet']}>
<div className={styles['backdrop']} onClick={onCloseRequest} />
<div
ref={containerRef}
className={classNames(styles['container'], { [styles['dragging']]: startOffset }, 'animation-slide-up')}
style={containerStyle}
onTouchStart={onTouchStart}
onTouchMove={onTouchMove}
onTouchEnd={onTouchEnd}
onTransitionEnd={onTransitionEnd}
>
<div className={styles['heading']}>
<div className={styles['handle']} />
<div className={styles['title']}>
{title}
</div>
</div>
<div className={styles['content']} onClick={onCloseRequest}>
{children}
</div>
</div>
</div>
), document.body);
};
export default BottomSheet;