feat: MNG 모바일 반응형 Phase 1 - 사이드바 오버레이 및 햄버거 메뉴
- 모바일 사이드바 오버레이 구현 (슬라이드 인/아웃) - 헤더에 햄버거 메뉴 버튼 추가 - 모바일 백드롭 오버레이 추가 - ESC 키 및 메뉴 클릭 시 사이드바 자동 닫힘
This commit is contained in:
@@ -52,6 +52,12 @@
|
||||
</head>
|
||||
<body class="bg-gray-100">
|
||||
<div class="flex h-screen overflow-hidden" id="app-container">
|
||||
<!-- 모바일 사이드바 백드롭 (lg 미만에서만 동작) -->
|
||||
<div id="sidebar-backdrop"
|
||||
class="fixed inset-0 bg-black/50 z-40 hidden lg:hidden"
|
||||
onclick="closeMobileSidebar()">
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
@include('partials.sidebar')
|
||||
|
||||
|
||||
@@ -1,15 +1,31 @@
|
||||
<!-- Header -->
|
||||
<header class="bg-white shadow-sm h-16 flex items-center justify-between px-6 border-b border-gray-200">
|
||||
<!-- Tenant Selector (좌측) -->
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<header class="bg-white shadow-sm h-16 flex items-center justify-between px-4 lg:px-6 border-b border-gray-200">
|
||||
<!-- 좌측: 모바일 햄버거 + 테넌트 셀렉터 -->
|
||||
<div class="flex items-center gap-2 lg:gap-4">
|
||||
<!-- 모바일 햄버거 버튼 (lg 미만에서만 표시) -->
|
||||
<button
|
||||
type="button"
|
||||
onclick="openMobileSidebar()"
|
||||
class="p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors lg:hidden"
|
||||
title="메뉴 열기"
|
||||
>
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- 모바일 로고 (lg 미만에서만 표시) -->
|
||||
<span class="text-lg font-bold text-gray-900 lg:hidden">{{ config('app.name') }}</span>
|
||||
|
||||
<!-- 테넌트 셀렉터 (데스크톱: 전체 표시, 모바일: 축소) -->
|
||||
<div class="hidden lg:flex items-center gap-2">
|
||||
<svg class="w-5 h-5 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
|
||||
</svg>
|
||||
<label for="tenant-select" class="text-sm font-medium text-gray-700">테넌트 선택:</label>
|
||||
</div>
|
||||
|
||||
<form action="{{ route('tenant.switch') }}" method="POST" id="tenant-switch-form">
|
||||
<form action="{{ route('tenant.switch') }}" method="POST" id="tenant-switch-form" class="hidden lg:block">
|
||||
@csrf
|
||||
<select
|
||||
name="tenant_id"
|
||||
@@ -31,13 +47,13 @@ class="border-gray-300 rounded-lg text-sm focus:ring-primary focus:border-primar
|
||||
</select>
|
||||
</form>
|
||||
|
||||
<!-- 현재 테넌트 정보 -->
|
||||
<!-- 현재 테넌트 정보 (데스크톱에서만 표시) -->
|
||||
@if(session('selected_tenant_id'))
|
||||
@php
|
||||
$currentTenant = $globalTenants->firstWhere('id', session('selected_tenant_id'));
|
||||
@endphp
|
||||
@if($currentTenant)
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-primary/10 text-primary cursor-pointer hover:bg-primary/20"
|
||||
<span class="hidden lg:inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-primary/10 text-primary cursor-pointer hover:bg-primary/20"
|
||||
data-context-menu="tenant"
|
||||
data-entity-id="{{ $currentTenant->id }}"
|
||||
data-entity-name="{{ $currentTenant->company_name }}"
|
||||
@@ -49,7 +65,7 @@ class="border-gray-300 rounded-lg text-sm focus:ring-primary focus:border-primar
|
||||
</span>
|
||||
@endif
|
||||
@else
|
||||
<span class="text-xs text-gray-500">전체</span>
|
||||
<span class="hidden lg:inline text-xs text-gray-500">전체</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<!-- Sidebar (Dynamic Menu from DB + Static Tools/Labs) -->
|
||||
<aside id="sidebar" class="sidebar bg-white shadow-lg flex-shrink-0 transition-all duration-300 ease-in-out w-64">
|
||||
<!-- 모바일: fixed + 슬라이드 인/아웃, 데스크톱(lg+): static + 기존 동작 -->
|
||||
<aside id="sidebar" class="sidebar bg-white shadow-lg flex-shrink-0 transition-all duration-300 ease-in-out w-64
|
||||
fixed inset-y-0 left-0 z-50 transform -translate-x-full
|
||||
lg:relative lg:translate-x-0 lg:z-auto">
|
||||
<div class="flex flex-col h-full">
|
||||
<!-- Logo / Brand -->
|
||||
<div class="flex items-center h-16 border-b border-gray-200 px-3">
|
||||
@@ -325,9 +328,125 @@ class="sidebar-collapsed-only hidden w-full p-2 text-xl font-bold text-gray-900
|
||||
padding-top: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
/* ========== 모바일 반응형 스타일 ========== */
|
||||
|
||||
/* 모바일에서 사이드바 열림 상태 */
|
||||
.sidebar.sidebar-mobile-open {
|
||||
transform: translateX(0) !important;
|
||||
}
|
||||
|
||||
/* 모바일에서 데스크톱 토글 버튼 숨김 (lg 미만) */
|
||||
@media (max-width: 1023px) {
|
||||
.sidebar .sidebar-expanded-only button[onclick="toggleSidebar()"],
|
||||
.sidebar .sidebar-collapsed-only[onclick="toggleSidebar()"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 모바일에서 사이드바 헤더 로고만 표시 */
|
||||
.sidebar .sidebar-expanded-only {
|
||||
justify-content: center !important;
|
||||
}
|
||||
|
||||
/* 모바일에서 접힌 상태 클래스 무시 (항상 펼쳐진 상태로 표시) */
|
||||
.sidebar.sidebar-collapsed {
|
||||
width: 16rem !important;
|
||||
}
|
||||
|
||||
html.sidebar-is-collapsed #sidebar .sidebar-text,
|
||||
.sidebar.sidebar-collapsed .sidebar-text {
|
||||
display: inline !important;
|
||||
}
|
||||
|
||||
html.sidebar-is-collapsed #sidebar .sidebar-expanded-only,
|
||||
.sidebar.sidebar-collapsed .sidebar-expanded-only {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
html.sidebar-is-collapsed #sidebar .sidebar-collapsed-only,
|
||||
.sidebar.sidebar-collapsed .sidebar-collapsed-only {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
html.sidebar-is-collapsed #sidebar .sidebar-nav,
|
||||
.sidebar.sidebar-collapsed .sidebar-nav {
|
||||
padding: 1rem !important;
|
||||
}
|
||||
|
||||
html.sidebar-is-collapsed #sidebar .sidebar-nav a,
|
||||
html.sidebar-is-collapsed #sidebar .sidebar-nav span,
|
||||
.sidebar.sidebar-collapsed .sidebar-nav a,
|
||||
.sidebar.sidebar-collapsed .sidebar-nav span {
|
||||
justify-content: flex-start !important;
|
||||
padding-left: 0.75rem !important;
|
||||
padding-right: 0.75rem !important;
|
||||
}
|
||||
|
||||
html.sidebar-is-collapsed #sidebar .sidebar-nav ul ul,
|
||||
.sidebar.sidebar-collapsed .sidebar-nav ul ul {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
html.sidebar-is-collapsed #sidebar .sidebar-group-header,
|
||||
.sidebar.sidebar-collapsed .sidebar-group-header {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 데스크톱(lg+)에서 모바일 클래스 무시 */
|
||||
@media (min-width: 1024px) {
|
||||
.sidebar.sidebar-mobile-open {
|
||||
transform: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// ========== 모바일 사이드바 함수 ==========
|
||||
|
||||
// 모바일 사이드바 열기
|
||||
function openMobileSidebar() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const backdrop = document.getElementById('sidebar-backdrop');
|
||||
|
||||
sidebar.classList.add('sidebar-mobile-open');
|
||||
backdrop?.classList.remove('hidden');
|
||||
document.body.classList.add('overflow-hidden');
|
||||
}
|
||||
|
||||
// 모바일 사이드바 닫기
|
||||
function closeMobileSidebar() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const backdrop = document.getElementById('sidebar-backdrop');
|
||||
|
||||
sidebar.classList.remove('sidebar-mobile-open');
|
||||
backdrop?.classList.add('hidden');
|
||||
document.body.classList.remove('overflow-hidden');
|
||||
}
|
||||
|
||||
// ESC 키로 모바일 사이드바 닫기
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
closeMobileSidebar();
|
||||
}
|
||||
});
|
||||
|
||||
// 모바일에서 메뉴 링크 클릭 시 사이드바 닫기
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
if (sidebar) {
|
||||
sidebar.addEventListener('click', function(e) {
|
||||
const link = e.target.closest('a[href]');
|
||||
if (link && window.innerWidth < 1024) {
|
||||
// 약간의 지연 후 닫기 (시각적 피드백)
|
||||
setTimeout(closeMobileSidebar, 150);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ========== 데스크톱 사이드바 토글 ==========
|
||||
|
||||
// 사이드바 토글 (로컬스토리지 연동)
|
||||
function toggleSidebar() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
|
||||
Reference in New Issue
Block a user