프론트엔드/컴포넌트

드롭다운 메뉴

syleemomo 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