|
| 1 | +import { useCallback, useEffect, useRef, useState, type PropsWithChildren } from 'react'; |
| 2 | +import { cx } from '@styled-system/css'; |
| 3 | +import { motion, type PanInfo } from 'framer-motion'; |
| 4 | + |
| 5 | +import * as s from './style.css'; |
| 6 | + |
| 7 | +interface DrawerBodyProps extends PropsWithChildren { |
| 8 | + title: string; |
| 9 | + description?: string; |
| 10 | + close: () => void; |
| 11 | +} |
| 12 | + |
| 13 | +const DrawerBody = ({ children, title, description, close }: DrawerBodyProps) => { |
| 14 | + const contentRef = useRef<HTMLDivElement>(null); |
| 15 | + const [height, setHeight] = useState(0); |
| 16 | + |
| 17 | + useEffect(() => { |
| 18 | + // UI ํ์ธํ
์ดํ ๊ณ์ฐ |
| 19 | + if (contentRef.current) { |
| 20 | + setHeight(Math.min(contentRef.current.clientHeight, window.innerHeight)); |
| 21 | + } |
| 22 | + }, [children]); |
| 23 | + |
| 24 | + const onDragEnd = useCallback( |
| 25 | + (_: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => { |
| 26 | + const offsetThreshold = 150; |
| 27 | + const deltaThreshold = 5; |
| 28 | + |
| 29 | + const isOverOffsetThreshold = Math.abs(info.offset.y) > offsetThreshold; |
| 30 | + const isOverDeltaThreshold = Math.abs(info.delta.y) > deltaThreshold; |
| 31 | + |
| 32 | + const isOverThreshold = isOverOffsetThreshold || isOverDeltaThreshold; |
| 33 | + |
| 34 | + if (!isOverThreshold) return; |
| 35 | + |
| 36 | + if (info.offset.y >= 0) close(); |
| 37 | + }, |
| 38 | + [close], |
| 39 | + ); |
| 40 | + |
| 41 | + return ( |
| 42 | + <motion.div |
| 43 | + key="drawer_body" |
| 44 | + className={s.Container} |
| 45 | + drag={'y'} |
| 46 | + dragConstraints={{ top: 0, bottom: 0 }} |
| 47 | + transition={{ duration: 0.2 }} |
| 48 | + dragElastic={0.4} |
| 49 | + variants={{ |
| 50 | + opened: { top: `calc(100dvh - ${height}px)` }, |
| 51 | + closed: { top: '100dvh' }, |
| 52 | + }} |
| 53 | + initial={'closed'} |
| 54 | + animate={'opened'} |
| 55 | + exit={'closed'} |
| 56 | + onDragEnd={onDragEnd} |
| 57 | + > |
| 58 | + <header className={s.Header}> |
| 59 | + <div className={s.HeaderTitle}> |
| 60 | + <span className={s.Title}>{title}</span> |
| 61 | + {description && <span className={s.Description}>{description}</span>} |
| 62 | + </div> |
| 63 | + <button className={cx('mgc_close_line', s.CloseButton)} onClick={close} /> |
| 64 | + </header> |
| 65 | + <div ref={contentRef} className={s.Content}> |
| 66 | + {children} |
| 67 | + </div> |
| 68 | + </motion.div> |
| 69 | + ); |
| 70 | +}; |
| 71 | + |
| 72 | +export default DrawerBody; |
0 commit comments