Dev_logroome

React Dock Menu 인터랙션 컴포넌트 구현 – RoomE #1

2025-03-31
ProjectReactInteraction
독메뉴 와이어프레임
  • 기본적으로 메뉴가 호버되면 툴팁이 나타나고 선택된 메뉴는 아이콘이 변경되며, 나머지 메뉴들은 #ffffff 으로 바뀐다. 메뉴가 선택되면 메뉴는 자동으로 닫히고 관련된 컴포넌트가 렌더링된다.
  • 이번에 가장 신경 쓴 부분은 UX와 부드러운 전환이다.

01. 배치 및 스타일링

독메뉴 와이어프레임2

메뉴의 초기 사이즈는 76px 였으나, 실제 화면에서 테스트해본 결과 크기가 너무 커 보였다. 그래서 일반적인 Dock-Menu는 어떤 사이즈일까 찾아보았다.

  • 모바일 : 48~64px

  • 태블릿/PC : 56~72px

  • macOs Dock : 48~64px

  • 결론적으로는 64px 로 변경하였다.

📍 Dock-Menu 구조

jsx
1
export default function DockMenu({ activeSettings, onSettingsChange }) {
  const [isOpen, setIsOpen] = useState(false);
  const menuRef = useRef(null);
  
  const getMainButtonBackground = () => {
    if (!hasSelectedSetting) {
      return 'bg-white';
    }
    if (!isOpen && activeSettings) {
      return 'bg-white';
    }
    return 'bg-transparent hover:bg-white/50';
  };
 
  return (
    <div
      ref={menuRef}
      className={`bottom-menu ... ${isOpen ? 'h-[202px]' : 'h-16'}`}
    >
      <div className="...">
        <button
          className={`bottom-menu-icon group absolute ${getMainButtonBackground()}`}
          onClick={() => setIsOpen(!isOpen)}
          aria-label="도구 메뉴 열기"
        >
          <img src={isOpen ? dockMenuNoselectIcon : dockMenuIcon} />
        </button>
        <div className={`bottom-menu-content transition-all duration-100 ${isOpen ? 'opacity-100' : 'opacity-0 pointer-events-none'}`}>
          {['preference', 'theme'].map((setting, index) => (
            <button
              key={setting}
              onClick={() => onSettingsChange(setting)}
              className={`bottom-menu-icon group absolute bottom-[${68 + index * 70}px] ${activeSettings === setting ? 'bg-white' : 'bg-transparent hover:bg-white/50'}`}
            >
              <img src={activeSettings === setting ? selectedIcons[setting] : unselectedIcons[setting]} />
              <span className="... opacity-0 group-hover:opacity-100 transition-opacity">
                {setting === 'preference' ? '취향 설정하기' : '테마 설정하기'}
              </span>
            ...
}

(1)ㅤ메뉴의 동작 및 상태에 따른 스타일링

  • 메뉴가 열리면 bottom-menu 의 높이가 확장되며, 닫히면 기본 상태로 돌아간다.
  • Dock-Menu 버튼 클릭시 setIsOpen(true) 로 메뉴를 열고 다시 클릭 시 setIsOpen(false) 로 닫는다.
  • pointer-events-none : 메뉴가 닫혀있을 때 사용자가 메뉴 항목을 클릭할 수 없도록 설정
  • getMainButtonBackground : 사용자가 특정 메뉴를 활성화하면 그 메뉴의 배경이 bg-white 로 변경되고 다른 메뉴는 bg-transparent 로 설정하여 어떤 메뉴가 활성화되어있는지 쉽게 알 수 있게 했다.

(2)ㅤ하위 메뉴 아이템

  • 각 설정 버튼 preferencethemeabsolute 로 위치가 고정되어 있고 메뉴가 열리면 버튼들이 각 위로 배치헸다.
  • transition-opacitygroup-hover:opacity-100 을 사용하여 hover 시 이미지의 투명도가 변경되도록 했다. 이로 인해 호버 시 좀 더 직관적인 구분이 가능하도록 했다.
  • hover 하면 해당 메뉴의 툴팁이 나타나도록 group-hover:opacity-100 transition-opacity 효과를 추가

02. 상태 관리 및 로직 구현

Dock-Menu의 열림 상태 및 선택된 설정을 관리하기 위해 zustand 활용

독메뉴 와이어프레임2
jsx
1
const [isOpen, setIsOpen] = useState<boolean>(false);
const [hasSelectedSetting, setHasSelectedSetting] = useState<boolean>(false);
 
jsx
1
const handleSettingClick = (setting: string) => {
  if (setting === activeSettings) {
    setIsOpen(false);
    setHasSelectedSetting(false);
    onSettingsChange(null);
  } else {
    setIsOpen(false);
    setHasSelectedSetting(true);
    onSettingsChange(setting);
  }
};
 
const handleMainButtonClick = () => {
  if (isOpen) {
    setIsOpen(false);
    onSettingsChange(null);
    setHasSelectedSetting(false);
  } else {
    setIsOpen(true);
  }
};
  • isOpen : Dock-Menu의 열림/닫힘 상태를 관리
  • activeSettings : 부모 컴포넌트에서 관리하는 상태로, preference 또는 theme 설정이 선택됨을 관리
  • hasSelectedSetting : 선택 여부 관리
  • handleSettingClick : 이미 선택된 설정을 다시 클릭하면 해당 설정을 비활성화, 다른 설정을 클릭하면 새로운 설정을 활성화
  • handleMainButtonClick : Dock-Menu가 열려있을 때, 클릭하면 닫고 설정을 초기화하고 닫혀있을 때 클릭하면 열리게 설정

03. 사용자 경험 개선

(1)ㅤ자동 닫힘 및 아이콘 변경

자동 닫힘 및 아이콘 변경
jsx
1
const handleSettingClick = (setting: string) => {
  //...
  setIsOpen(false);
};
 
const getCurrentIcon = () => {
  if (!hasSelectedSetting) return dockMenuIcon;
  if (isOpen) return dockMenuNoselectIcon;
  if (activeSettings === 'preference' && !isOpen) return preferenceIcon;
  if (activeSettings === 'theme' && !isOpen) return themeIcon;
  return dockMenuIcon;
};
  • 사용자가 preference 또는 theme 을 선택하면 Dock-Menu가 닫히도록 구현
  • handleSettingClick 함수에서 setIsOpen(false) 호출하여 선택후 자동으로 닫히도록 처리
  • 닫힌 후 기본 도구 메뉴 아이콘에서 getCurrentIcon 함수로 선택한 설정 메뉴 아이콘으로 변경되도록 설정

(2)ㅤDock-Menu 바깥 클릭 시 메뉴 닫기

Dock-Menu 바깥 클릭 시 메뉴 닫기
jsx
1
const menuRef = useRef<HTMLDivElement>(null);
 
useEffect(() => {
  const handleClickOutside = (event: MouseEvent) => {
    if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
      setIsOpen(false);
    }
  };
  document.addEventListener("mousedown", handleClickOutside);
  return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
  • useRef 를 활용해 Dock-Menu 컨테이너를 참조
  • mousedown 이벤트 리스너를 추가하여 바깥을 클릭하면 자동으로 닫히도록 구현
  • menuRef.current.contains(event.target as Node) 를 검사하여 바깥 클릭 감지 후 setIsOpen(false) 수행