877e25eddf
**Layout Components:** - AppLayout.vue: Responsive grid layout (sidebar + main + footer) - Sidebar.vue: Collapsible navigation (256px → 64px), smooth animation - Header.vue: Top bar with theme toggle + user menu + notifications - Footer.vue: System status indicator + quick links + version info **Theme System Enhancements:** - tokens.css: Explicit dark mode via data-theme attribute - Dual-mode support: prefers-color-scheme + data-theme override - Smooth theme transitions (150ms cubic-bezier) - All semantic colors respond to theme changes **Mobile Responsiveness:** - Sidebar: Fixed overlay on mobile (<768px) - Header: Responsive user menu collapse - Footer: Multi-column → single column layout - Touch-friendly icon buttons (40px minimum) - Breadcrumb: Hidden on mobile to save space **Accessibility:** - ARIA labels on all interactive elements - Skip navigation link - Semantic HTML (nav, main, footer, header) - Keyboard navigation support (ESC to close menus) - Focus management in user dropdown **Testing:** - All 3 pages: ✅ 100% PASS (36/36 selectors) - Layout integration verified - Theme switching functional - Mobile layout tested **Commits:** Phase 1-3 now complete: 2681 LOC across 11 files Next Phase: Phase 4 — Accessibility & Performance (2-4h) - Complete ARIA + semantic HTML audit - Keyboard navigation for data grids - Performance optimization (lazy loading, code splitting) - E2E accessibility testing Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
82 lines
1.7 KiB
Vue
82 lines
1.7 KiB
Vue
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
import Sidebar from './Sidebar.vue'
|
|
import Header from './Header.vue'
|
|
import Footer from './Footer.vue'
|
|
|
|
const isSidebarOpen = ref(true)
|
|
const isDarkMode = ref(false)
|
|
|
|
const toggleSidebar = () => {
|
|
isSidebarOpen.value = !isSidebarOpen.value
|
|
}
|
|
|
|
const toggleDarkMode = () => {
|
|
isDarkMode.value = !isDarkMode.value
|
|
// Apply dark mode to root element
|
|
if (isDarkMode.value) {
|
|
document.documentElement.setAttribute('data-theme', 'dark')
|
|
} else {
|
|
document.documentElement.removeAttribute('data-theme')
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="app-layout" :class="{ 'dark-mode': isDarkMode }">
|
|
<!-- Sidebar -->
|
|
<Sidebar :isOpen="isSidebarOpen" @toggle="toggleSidebar" />
|
|
|
|
<!-- Main Content -->
|
|
<div class="app-main">
|
|
<!-- Header -->
|
|
<Header
|
|
@toggle-sidebar="toggleSidebar"
|
|
@toggle-dark-mode="toggleDarkMode"
|
|
:isDarkMode="isDarkMode"
|
|
/>
|
|
|
|
<!-- Page Content -->
|
|
<main class="app-content">
|
|
<slot></slot>
|
|
</main>
|
|
|
|
<!-- Footer -->
|
|
<Footer />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.app-layout {
|
|
display: grid;
|
|
grid-template-columns: auto 1fr;
|
|
height: 100vh;
|
|
background-color: var(--color-background-primary);
|
|
color: var(--color-text-primary);
|
|
}
|
|
|
|
.app-main {
|
|
display: grid;
|
|
grid-template-rows: auto 1fr auto;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.app-content {
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
background-color: var(--color-background-primary);
|
|
}
|
|
|
|
/* Smooth transitions */
|
|
:deep(*) {
|
|
transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.app-layout {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|