b12fc7411d
- Create useFormFieldNavigation composable for Tab-like Enter behavior - KsTextField: Enter -> next field - KsTextArea: Ctrl+Enter for newline, Enter -> next field - KsSelect: Enter -> next field after selection - Implements standard form navigation pattern across input components
167 lines
3.5 KiB
Vue
167 lines
3.5 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted, watch } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { useLayoutStore } from './layoutStore'
|
|
import KsHeader from './KsHeader.vue'
|
|
import KsSidebar from './KsSidebar.vue'
|
|
import KsFooter from './KsFooter.vue'
|
|
import KsNotifications from './KsNotifications.vue'
|
|
import KsTabs from './KsTabs.vue'
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const layoutStore = useLayoutStore()
|
|
|
|
const syncRouteTab = (to: { path: string; meta?: Record<string, unknown> }) => {
|
|
if (to.path !== '/' && to.path !== '/home') {
|
|
const title = (to.meta?.title as string) || to.path
|
|
const icon = (to.meta?.icon as string) || '📄'
|
|
const id = `tab-${to.path}`
|
|
|
|
layoutStore.addTab({
|
|
id,
|
|
title,
|
|
path: to.path,
|
|
icon,
|
|
})
|
|
}
|
|
}
|
|
|
|
watch(() => route.path, () => {
|
|
syncRouteTab(route)
|
|
}, { immediate: true })
|
|
|
|
onMounted(() => {
|
|
syncRouteTab(route)
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="ks-app-shell">
|
|
<!-- Skip to main content link (accessibility) -->
|
|
<a href="#ks-main" class="ks-skip ks-skip-link">본문으로 이동</a>
|
|
|
|
<!-- Header -->
|
|
<KsHeader
|
|
@toggle-sidebar="layoutStore.toggleSidebar()"
|
|
@toggle-mobile-menu="layoutStore.mobileMenuOpen = !layoutStore.mobileMenuOpen"
|
|
/>
|
|
|
|
<!-- Tabs (if any tabs are open) -->
|
|
<KsTabs v-if="layoutStore.hasOpenedTabs" />
|
|
|
|
<div class="ks-app-shell__container">
|
|
<!-- Sidebar -->
|
|
<KsSidebar
|
|
:collapsed="layoutStore.sidebarCollapsed"
|
|
:mobile-open="layoutStore.mobileMenuOpen"
|
|
@close-mobile="layoutStore.mobileMenuOpen = false"
|
|
@update:collapsed="layoutStore.sidebarCollapsed = $event"
|
|
/>
|
|
|
|
<!-- Mobile backdrop -->
|
|
<div
|
|
v-if="layoutStore.mobileMenuOpen"
|
|
class="ks-mobile-backdrop"
|
|
@click="layoutStore.mobileMenuOpen = false"
|
|
/>
|
|
|
|
<!-- Main content with page transition -->
|
|
<main
|
|
id="ks-main"
|
|
tabindex="-1"
|
|
:class="['ks-app-shell__main', { 'ks-app-shell__main--collapsed': layoutStore.sidebarCollapsed }]"
|
|
>
|
|
<slot />
|
|
</main>
|
|
</div>
|
|
|
|
<!-- Notifications (toasts) -->
|
|
<KsNotifications />
|
|
</div>
|
|
</template>
|
|
|
|
|
|
<style scoped>
|
|
.ks-app-shell {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100vh;
|
|
background-color: var(--color-background-primary);
|
|
color: var(--color-text-primary);
|
|
}
|
|
|
|
.ks-skip-link {
|
|
position: absolute;
|
|
top: -40px;
|
|
left: 0;
|
|
background-color: var(--color-primary-500);
|
|
color: white;
|
|
padding: var(--spacing-2) var(--spacing-3);
|
|
text-decoration: none;
|
|
z-index: 10000;
|
|
border-radius: 0 0 var(--border-radius-base) 0;
|
|
}
|
|
|
|
.ks-skip-link:focus {
|
|
top: 0;
|
|
}
|
|
|
|
.ks-app-shell__container {
|
|
display: flex;
|
|
flex: 1;
|
|
overflow: hidden;
|
|
position: relative;
|
|
min-height: 0;
|
|
}
|
|
|
|
.ks-app-shell__main {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
padding: var(--ks-space-3);
|
|
max-width: 100%;
|
|
min-height: 0;
|
|
}
|
|
|
|
.ks-app-shell__main--collapsed {
|
|
/* Sidebar is collapsed, main takes full width */
|
|
}
|
|
|
|
.ks-mobile-backdrop {
|
|
position: fixed;
|
|
inset: 0;
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
backdrop-filter: blur(4px);
|
|
z-index: 99;
|
|
animation: fadeIn var(--transition-base);
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
/* Responsive design */
|
|
@media (max-width: 768px) {
|
|
.ks-app-shell__main {
|
|
padding: var(--spacing-4);
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.ks-app-shell__main {
|
|
scroll-behavior: auto;
|
|
}
|
|
|
|
.ks-mobile-backdrop {
|
|
animation: none;
|
|
}
|
|
}
|
|
</style>
|