feat: 앱 셸 모바일 레이아웃 — BottomNav 통합 + 사이드바 조건부 렌더링

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-23 14:38:49 +09:00
parent d53108f1c9
commit 0922261c74
4 changed files with 47 additions and 95 deletions

View File

@@ -62,6 +62,7 @@
@media (max-width: 768px) {
.site-main {
padding: 16px;
padding-bottom: calc(var(--bottom-nav-h, 64px) + var(--safe-area-bottom, 0px) + 16px);
}
}

View File

@@ -1,11 +1,15 @@
import React from 'react';
import { Outlet } from 'react-router-dom';
import Navbar from './components/Navbar';
import BottomNav from './components/BottomNav';
import PageHeader from './components/PageHeader';
import Loading from './components/Loading';
import { useIsMobile } from './hooks/useIsMobile';
import './App.css';
function App() {
const isMobile = useIsMobile();
return (
<div className="app-shell">
<Navbar />
@@ -17,6 +21,7 @@ function App() {
</React.Suspense>
</main>
</div>
{isMobile && <BottomNav />}
</div>
);
}

View File

@@ -334,26 +334,6 @@
@media (max-width: 768px) {
.sidebar {
transform: translateX(-100%);
}
.sidebar.is-open {
transform: translateX(0);
}
.sidebar-toggle {
display: flex;
}
}
/* ── 데스크톱: 토글 버튼 숨김 ────────────────────────────────────────── */
@media (min-width: 769px) {
.sidebar-toggle {
display: none;
}
.sidebar__overlay {
display: none;
}
}

View File

@@ -1,47 +1,18 @@
import React, { useEffect, useState } from 'react';
import React from 'react';
import { NavLink } from 'react-router-dom';
import { navLinks } from '../routes.jsx';
import { useIsMobile } from '../hooks/useIsMobile';
import mainLogo from '../assets/main_logo.png';
import './Navbar.css';
const Navbar = () => {
const [menuOpen, setMenuOpen] = useState(false);
const closeMenu = () => setMenuOpen(false);
const isMobile = useIsMobile();
useEffect(() => {
document.body.style.overflow = menuOpen ? 'hidden' : '';
return () => {
document.body.style.overflow = '';
};
}, [menuOpen]);
// 모바일에서는 BottomNav가 대체하므로 사이드바 미렌더링
if (isMobile) return null;
return (
<>
{/* 모바일 오버레이 */}
<div
className={`sidebar__overlay${menuOpen ? ' is-visible' : ''}`}
onClick={closeMenu}
aria-hidden="true"
/>
{/* 모바일 토글 버튼 */}
<button
type="button"
className="sidebar-toggle"
onClick={() => setMenuOpen((prev) => !prev)}
aria-label="메뉴 열기/닫기"
aria-expanded={menuOpen}
>
<span className={`sidebar-toggle__icon${menuOpen ? ' is-open' : ''}`}>
<span />
<span />
<span />
</span>
</button>
{/* 사이드바 본체 */}
<aside className={`sidebar${menuOpen ? ' is-open' : ''}`}>
{/* 브랜드 섹션 */}
<aside className="sidebar">
<div className="sidebar__brand">
<img src={mainLogo} alt="Logo" className="sidebar__logo" />
<div className="sidebar__brand-text">
@@ -50,17 +21,14 @@ const Navbar = () => {
</div>
</div>
{/* 구분선 */}
<div className="sidebar__divider" />
{/* 네비게이션 */}
<nav className="sidebar__nav">
<p className="sidebar__section-label">NAVIGATION</p>
{navLinks.map((link) => (
<NavLink
key={link.id}
to={link.path}
onClick={closeMenu}
className={({ isActive }) =>
`sidebar__item${isActive ? ' is-active' : ''}`
}
@@ -74,7 +42,6 @@ const Navbar = () => {
))}
</nav>
{/* 사이드바 푸터 */}
<div className="sidebar__footer">
<div className="sidebar__divider" />
<div className="sidebar__footer-content">
@@ -86,7 +53,6 @@ const Navbar = () => {
</div>
</div>
</aside>
</>
);
};