Vue3 사이드바 CSS 리팩토링 및 모바일 UX 개선하기

gittidev·2025년 8월 14일
0

🎯 문제 상황

Vue3 프로젝트에서 사이드바를 구현하며 마주친 문제들:

  1. CSS 중복 문제: 여러 컴포넌트에서 동일한 사이드바 스타일이 반복 정의
  2. 스타일 우선순위 충돌: scoped와 global 스타일이 혼재되어 예상치 못한 스타일 적용
  3. 모바일 UX 문제: 메뉴 클릭 후 사이드바가 자동으로 닫히지 않아 불편함

🔍 현재 구조 분석

프로젝트의 사이드바는 다음과 같은 구조로 되어 있었습니다:

src/
├── App.vue (전역 사이드바 스타일)
├── components/
│   ├── layouts/
│   │   └── MainLayout.vue (레이아웃 관리)
│   └── common/
│       ├── SidebarMenu.vue (메뉴 아이템)
│       └── SidebarFooter.vue (하단 메뉴)

문제점 상세 분석

1. 동일한 스타일이 3곳에서 반복

/* App.vue, SidebarMenu.vue, SidebarFooter.vue 모두에 존재 */
.sidebar-menu-item {
  display: flex;
  align-items: center;
  padding: 12px 20px;
  color: #ecf0f1;
  /* ... 동일한 스타일 반복 ... */
}

2. 접힘 상태 스타일 충돌

/* MainLayout.vue */
.main-sidebar.collapsed .sidebar-menu .sidebar-menu-item { ... }

/* SidebarMenu.vue */
.main-sidebar.collapsed .sidebar-menu-item { ... }

💡 해결 방안

1. CSS 구조 재정리

Step 1: 공통 스타일을 별도 파일로 분리

/* src/sidebar.css */
/* 사이드바 관련 CSS 변수 */
:root {
  --sidebar-width-expanded: 250px;
  --sidebar-width-collapsed: 60px;
  --sidebar-bg-primary: #333333;
  --sidebar-bg-secondary: #2b2b2b;
  --sidebar-text-color: #ecf0f1;
  --sidebar-hover-bg: rgba(255, 255, 255, 0.1);
  --sidebar-active-bg: rgba(255, 255, 255, 0.15);
  --sidebar-active-border: #F36545;
  --sidebar-transition: all 0.3s ease;
}

/* 기본 메뉴 아이템 스타일 */
.sidebar-menu-item {
  display: flex;
  align-items: center;
  padding: 12px 20px;
  color: var(--sidebar-text-color);
  text-decoration: none;
  transition: var(--sidebar-transition);
  cursor: pointer;
  position: relative;
  overflow: hidden;
  white-space: nowrap;
}

.sidebar-menu-item:hover {
  background: var(--sidebar-hover-bg);
  padding-left: 25px;
}

.sidebar-menu-item.active {
  background: var(--sidebar-active-bg);
  border-left: 4px solid var(--sidebar-active-border);
}

/* 사이드바 접힘 상태 */
.main-sidebar.collapsed .sidebar-menu-item {
  justify-content: center;
  padding: 8px 0;
  width: var(--sidebar-width-collapsed);
  height: 36px;
}

.main-sidebar.collapsed .menu-text {
  opacity: 0;
  visibility: hidden;
  width: 0;
  margin-left: 0;
}

/* 모바일 반응형 */
@media (max-width: 767px) {
  .menu-text {
    opacity: 1 !important;
    visibility: visible !important;
    width: auto !important;
    margin-left: 10px !important;
  }
  
  .sidebar-menu-item {
    justify-content: flex-start !important;
    padding: 12px 20px !important;
    width: 100% !important;
  }
}

Step 2: 메인 스타일 파일에서 import

/* src/style.css */
@import './sidebar.css';  /* 맨 위에 추가 */

/* 기존 스타일들... */

Step 3: 각 컴포넌트에서 중복 스타일 제거

<!-- SidebarMenu.vue -->
<style scoped>
/* 중복 스타일 모두 제거하고 고유 스타일만 유지 */
.sidebar-wrapper {
  width: 100%;
}
</style>

<!-- SidebarFooter.vue -->
<style scoped>
.sidebar-footer-wrapper {
  width: 100%;
}
</style>

2. 모바일 UX 개선 - 라우팅 시 사이드바 자동 닫기

Vue Router의 Navigation Guard 활용

// App.vue
import { useRouter } from 'vue-router'

setup() {
  const router = useRouter()
  const mainLayoutRef = ref(null)
  
  // 라우터 네비게이션 후 모바일에서 사이드바 닫기
  router.afterEach(() => {
    mainLayoutRef.value?.closeSidebar()
  })
  
  return {
    mainLayoutRef
    // ... 기존 반환값
  }
}

MainLayout 컴포넌트에서 메서드 노출

// MainLayout.vue
setup(props, { emit, expose }) {
  // ... 기존 코드
  
  const closeSidebar = () => {
    if (isMobile.value) {
      isSidebarOpen.value = false
    }
  }
  
  // 외부에서 사용할 수 있도록 메서드 노출
  expose({
    closeSidebar,
    toggleSidebar,
    isMobile
  })
  
  // ... 나머지 코드
}

Template에 ref 추가

<!-- App.vue -->
<template>
  <MainLayout 
    ref="mainLayoutRef"
    :page-title="pageTitle"
    @sidebar-toggle="handleLayoutSidebarToggle">
    <!-- slots... -->
  </MainLayout>
</template>

🎉 개선 결과

1. 코드 중복 제거

  • 사이드바 관련 스타일이 한 곳에서 관리되어 유지보수 용이
  • CSS 변수 활용으로 일관된 디자인 시스템 구축

2. 스타일 우선순위 명확화

  • 공통 스타일과 컴포넌트별 고유 스타일 분리
  • 예측 가능한 스타일 적용

3. 향상된 모바일 UX

  • 메뉴 클릭 시 자동으로 사이드바 닫힘
  • 더 나은 모바일 사용자 경험 제공

🚀 추가 개선 아이디어

1. CSS Modules 또는 CSS-in-JS 도입 검토

// CSS Modules 예시
import styles from './Sidebar.module.css'

<div :class="styles.menuItem">

2. Composable 활용

// useSidebar.js
export function useSidebar() {
  const isOpen = ref(false)
  const isCollapsed = ref(false)
  
  const toggle = () => {
    // 로직
  }
  
  return {
    isOpen,
    isCollapsed,
    toggle
  }
}

3. 애니메이션 개선

.sidebar-menu-item {
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

/* 서브메뉴 슬라이드 애니메이션 */
.submenu-enter-active,
.submenu-leave-active {
  transition: all 0.3s ease;
}

.submenu-enter-from,
.submenu-leave-to {
  opacity: 0;
  transform: translateY(-10px);
}

📝 마무리

사이드바 같은 공통 컴포넌트는 프로젝트가 커질수록 관리가 어려워집니다. 초기에 잘 구조화하고, 지속적으로 리팩토링하는 것이 중요합니다.

이번 리팩토링을 통해 얻은 교훈:
1. DRY 원칙 준수: 중복 코드는 즉시 제거
2. 관심사 분리: 레이아웃, 스타일, 로직을 명확히 분리
3. 사용자 경험 우선: 특히 모바일 환경에서의 UX 고려

🔗 관련 자료


태그: #Vue3 #CSS #Refactoring #MobileUX #Sidebar #ComponentDesign

0개의 댓글