Skip to content

Commit

Permalink
feat: BottomSheet handler drag μΆ”κ°€
Browse files Browse the repository at this point in the history
  • Loading branch information
Todari committed Aug 4, 2024
1 parent 98b2a44 commit 9876517
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 36 deletions.
15 changes: 10 additions & 5 deletions HDesign/src/components/BottomSheet/BottomSheet.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {Theme} from '@theme/theme.type';
export const display = (visible: boolean) =>
css({
visibility: visible ? 'visible' : 'hidden',
transition: 'visibility 0.2s ease-in-out',
});

export const dimmedLayerStyle = (theme: Theme, isOpened: boolean) =>
Expand All @@ -23,7 +22,7 @@ export const dimmedLayerStyle = (theme: Theme, isOpened: boolean) =>
transitionTimingFunction: 'cubic-bezier(0.7, 0.62, 0.62, 1.16)',
});

export const bottomSheetContainerStyle = (theme: Theme, isOpened: boolean) =>
export const bottomSheetContainerStyle = (theme: Theme, isOpened: boolean, isDragging: boolean, translateY: number) =>
css({
position: 'fixed',
display: 'flex',
Expand All @@ -37,14 +36,20 @@ export const bottomSheetContainerStyle = (theme: Theme, isOpened: boolean) =>
borderRadius: '1.5rem 1.5rem 0 0',
backgroundColor: theme.colors.white,

transform: isOpened ? 'translateY(0)' : 'translateY(100%)',
transition: 'transform 0.2s ease-in-out',
transform: isOpened ? `translateY(${translateY}px)` : 'translateY(100%)',
transition: isDragging ? 'none' : 'transform 0.2s ease-in-out',
});

export const indicatorContainerStyle = css({
display: 'flex',
justifyContent: 'center',
padding: '0.5rem 0',
width: '100%',
});

export const indicatorStyle = (theme: Theme) =>
css({
display: 'flex',
margin: '0.5rem 0',
width: '5rem',
height: '0.25rem',
borderRadius: '0.125rem',
Expand Down
52 changes: 34 additions & 18 deletions HDesign/src/components/BottomSheet/BottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,52 @@
/** @jsxImportSource @emotion/react */
import {createPortal} from 'react-dom';
import {useEffect, useState} from 'react';
import {useEffect, useRef, useState} from 'react';

import {BottomSheetProps} from '@components/BottomSheet/BottomSheet.type';
import FixedButton from '@components/FixedButton/FixedButton';

import {useTheme} from '@theme/HDesignProvider';

import {useBottomSheet} from './useBottomSheet';
import {bottomSheetContainerStyle, dimmedLayerStyle, display, indicatorStyle} from './BottomSheet.style';

const BottomSheet: React.FC<BottomSheetProps> = ({isOpened = false, children, ...props}: BottomSheetProps) => {
import {
bottomSheetContainerStyle,
dimmedLayerStyle,
display,
indicatorContainerStyle,
indicatorStyle,
} from './BottomSheet.style';

const BottomSheet: React.FC<BottomSheetProps> = ({
isOpened = false,
children,
onChangeClose,
onChangeOpen,
...props
}: BottomSheetProps) => {
const {theme} = useTheme();
const {opened, handleClose} = useBottomSheet({isOpened, ...props});
const [visible, setVisible] = useState(isOpened);

useEffect(() => {
if (opened) {
setVisible(true);
} else {
const timer = setTimeout(() => setVisible(false), 200);

return () => clearTimeout(timer);
}
}, [opened]);
const {opened, visible, handleClose, handleDragStart, handleDrag, handleDragEnd, isDragging, translateY} =
useBottomSheet({
isOpened,
onChangeClose,
onChangeOpen,
});

// TODO: (@todari) : children 길이 κΈΈ λ•Œ overflow button에 μ•ˆκ°€λ¦¬λŠ” μ˜μ—­ 처리
return createPortal(
<div css={display(visible)}>
<div css={dimmedLayerStyle(theme, opened)} onClick={handleClose} />
<div css={bottomSheetContainerStyle(theme, opened)}>
<div css={indicatorStyle(theme)} />
<div css={bottomSheetContainerStyle(theme, opened, isDragging, translateY)}>
<div
css={indicatorContainerStyle}
onMouseDown={handleDragStart}
onMouseMove={handleDrag}
onMouseUp={handleDragEnd}
onTouchStart={handleDragStart}
onTouchMove={handleDrag}
onTouchEnd={handleDragEnd}
>
<div css={indicatorStyle(theme)} />
</div>
{children}
</div>
</div>,
Expand Down
59 changes: 46 additions & 13 deletions HDesign/src/components/BottomSheet/useBottomSheet.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useCallback, useEffect, useState} from 'react';
import {useCallback, useEffect, useRef, useState} from 'react';

interface UseBottomSheetProps {
isOpened: boolean;
Expand All @@ -7,7 +7,22 @@ interface UseBottomSheetProps {
}

export const useBottomSheet = ({isOpened, onChangeClose, onChangeOpen}: UseBottomSheetProps) => {
const [opened, setOpened] = useState(false);
const [opened, setOpened] = useState(isOpened);
const [visible, setVisible] = useState(isOpened);

const [isDragging, setIsDragging] = useState(false);
const [translateY, setTranslateY] = useState(0);
const startY = useRef(0);

useEffect(() => {
if (opened) {
setVisible(true);
} else {
const timer = setTimeout(() => setVisible(false), 200);

return () => clearTimeout(timer);
}
}, [opened]);

useEffect(() => {
setOpened(isOpened);
Expand All @@ -16,37 +31,55 @@ export const useBottomSheet = ({isOpened, onChangeClose, onChangeOpen}: UseBotto
} else {
handleOpen();
}

document.body.style.overflow = 'hidden';
document.body.addEventListener('keydown', handleKeyDownEsc);

return () => {
document.body.style.overflow = 'scroll';
document.body.removeEventListener('keydown', handleKeyDownEsc);
};
}, [isOpened]);

const handleClose = useCallback(() => {
setOpened(false);
if (onChangeClose) {
onChangeClose();
}
}, []);
}, [onChangeClose]);

const handleOpen = useCallback(() => {
setOpened(true);
if (onChangeOpen) {
onChangeOpen();
}
}, []);
}, [onChangeOpen]);

useEffect(() => {
document.body.style.overflow = 'hidden';
document.body.addEventListener('keydown', handleKeyDownEsc);
const handleDragStart = (e: React.TouchEvent | React.MouseEvent) => {
setIsDragging(true);
startY.current = 'touches' in e ? e.touches[0].clientY : e.clientY;
};

return () => {
document.body.style.overflow = 'scroll';
document.body.removeEventListener('keydown', handleKeyDownEsc);
};
}, [isOpened]);
const handleDrag = (e: React.TouchEvent | React.MouseEvent) => {
if (!isDragging) return;
const currentY = 'touches' in e ? e.touches[0].clientY : e.clientY;
const deltaY = currentY - startY.current;
setTranslateY(Math.max(deltaY, 0));
};

const handleDragEnd = () => {
setIsDragging(false);
if (translateY > window.screen.height / 10) {
handleClose();
}
setTranslateY(0);
};

const handleKeyDownEsc = (e: KeyboardEvent) => {
if (e.key === 'Escape' && isOpened) {
handleClose();
}
};

return {opened, handleClose};
return {opened, visible, handleClose, handleDragStart, handleDrag, handleDragEnd, isDragging, translateY};
};

0 comments on commit 9876517

Please sign in to comment.