ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 드롭다운 메뉴
    프론트엔드/컴포넌트 2024. 5. 31. 09:53
    728x90
    import React, { useState, useEffect, useRef } from 'react'
    import './App.css'
    import Dropdown from './Dropdown'
    
    const dropdownMenu = {
        'Home': ['home-1', 'home-2'],
        'About': ['about-1', 'about-2', 'about-3'],
        'Contact': ['contact-1']
    }
    
    function App(){
        const [page, setPage] = useState('') // 현재 선택한 메뉴 저장
        const [layout, setLayout] = useState({}) // 현재 드롭다운 위치 저장
        const [target, setTarget] = useState(null) // 현재 선택한 타겟 저장 
        const [resize, setResize] = useState(false) // 브라우저 크기 조정중인지 판단 
    
        const menus = dropdownMenu[page] // 드롭다운 메뉴 
        const sx = {backgroundColor: 'orange', color: 'red' } // 드롭다운 스타일 설정
        const itemStyle = {backgroundColor: 'black', color: '#ccc'} // 드롭다운 메뉴 호버스타일 설정
        const dropdown = useRef(null)
        
        const openDropdown = (e) => {
            e.stopPropagation()
            const {x, bottom} = e.target.getBoundingClientRect()
            setPage(e.target.innerText)
            setLayout({x, y: bottom })
            setTarget(e.target)
        }
        const closeDropdown = () => {
            setPage('')
            setLayout({})
            setTarget(null)
        }
        const changePosition = () => {
            if(target){
                const {x, bottom} = target.getBoundingClientRect()
                setLayout({x, y: bottom })
                setResize(prevResize => !prevResize)
            }
        }
        const setDropdown = (e) => {
            if(!dropdown.current.contains(e.target)){ // 드롭다운 바깥 영역을 클릭한 경우
                closeDropdown()
            }
        }
    
        useEffect(() => { // 메뉴닫기
            window.addEventListener('click', setDropdown)
            return () => window.removeEventListener('click', setDropdown)
        }, [])
    
        useEffect(() => { // 드롭다운 위치조정 
            window.addEventListener('resize', changePosition)
            return () => window.removeEventListener('resize', changePosition)
        }, [target])
    
        return (
            <div className='App'>
            <Dropdown ref={dropdown} page={page} menus={menus} layout={layout} closeDropdown={closeDropdown} sx={sx} itemStyle={itemStyle} duration={300} resize={resize}/>
             <nav>
                <ul>
                    {Object.keys(dropdownMenu).map((menu, id) => <li key={id} onClick={openDropdown}>{menu}</li>)}
                </ul>
             </nav>
            </div>
        )
    }
    export default App

    App.js 파일을 위와 같이 작성한다. 

    .App{
        text-align: center;
    }
    ul{
        list-style: none;
    }
    nav > ul{
        display: flex;
        flex-wrap: wrap;
        justify-content: flex-end;
    }
    nav > ul > li{
        margin-right: 2rem;
        cursor: pointer;
    }
    nav > ul > li:hover{
        color: orange;
    }

    App.css 파일을 위와 같이 작성한다. 

    import React, { useState, useEffect, forwardRef } from 'react'
    import './Dropdown.css'
    
    const Dropdown = forwardRef(({ page, menus, layout, closeDropdown, sx, itemStyle, duration, resize }, ref) => {
        const [fade, setFade] = useState("") // 액티브 클래스 
        const [id, setId] = useState(null) // 현재 호버된 메뉴
        const [pos, setPos] = useState({x: 0, y: 0}) // 드롭다운 위치
        const [items, setItems] = useState(null) // 메뉴목록
    
        const handleClick = () =>{
            closeDropdown()
            setId(null)
        }
        
        useEffect(() => {
            let timer = null
            if(page){
                timer = setTimeout(() => {
                    setFade('active')
                    setPos({...layout}) // 드롭다운이 사라진후 위치변경
                    setItems(menus) // 드롭다운이 사라진후 메뉴변경
                }, duration) // 클린업과 동시에 실행되지 않도록 시간차를 둠
            }
            
            return () => {
                clearTimeout(timer)
                setFade("")
            }
        }, [page])
    
        
    
        useEffect(() => {
            setPos({...layout}) // 브라우저 크기 조정시 드롭다운 위치변경
        }, [resize])
    
        return (
            <div ref={ref} className={`dropdown-container ${fade}`} style={{...sx, left: `${pos.x}px`, top: `${pos.y}px`}}>
                <ul>
                    {Array.isArray(items) && items.length > 0 && items.map((menu, idx) => 
                    (<li key={idx} style={id == idx ? itemStyle: {}} id={idx} onClick={handleClick} onMouseEnter={(e) => setId(e.target.id)} onMouseLeave={() => setId(null)}>{menu}</li>))}
                </ul>
            </div>
        )
    })
    export default Dropdown

    Dropdown.js 파일을 위와 같이 작성한다. 

    .dropdown-container{
        padding: .5rem 0rem; 
        border: 1px solid #eee;
        border-radius: 5px;
        opacity: 0;
        position: absolute;
        transition: opacity .3s linear;
    }
    .dropdown-container.active{
        opacity: 1;
    }
    .dropdown-container > ul{
        list-style: none;
        margin: 0; padding: 0;
    }
    .dropdown-container > ul > li {
        cursor: pointer;
        padding: .3rem 1rem;
    }
    .dropdown-container > ul > li:hover{
        background-color: #eee;
    }

    Dropdown.css 파일을 위와 같이 작성한다. 

    728x90
Designed by Tistory.