637 lines
16 KiB
Vue
637 lines
16 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
|
|
const router = useRouter()
|
|
const searchQuery = ref('')
|
|
const showUserMenu = ref(false)
|
|
const showNotifications = ref(false)
|
|
|
|
const emit = defineEmits<{
|
|
'toggle-sidebar': []
|
|
'toggle-mobile-menu': []
|
|
}>()
|
|
|
|
const currentUser = {
|
|
name: 'KArtSell User',
|
|
email: 'user@kartsell.com',
|
|
avatar: '👤',
|
|
}
|
|
|
|
const notifications = [
|
|
{ id: 1, message: '모델 Hawkeye-Alpha 검증 완료', time: '5분 전' },
|
|
{ id: 2, message: 'Shadow Run Job #893 진행 중 (45%)', time: '방금 전' },
|
|
{ id: 3, message: '승인 요청 대기 중 (3개)', time: '1시간 전' },
|
|
]
|
|
|
|
const unreadCount = computed(() => notifications.length)
|
|
|
|
const handleSearch = () => {
|
|
if (searchQuery.value.trim()) {
|
|
router.push({ path: '/search', query: { q: searchQuery.value } })
|
|
}
|
|
}
|
|
|
|
const onQuickSelect = (event: Event) => {
|
|
const target = event.target as HTMLSelectElement
|
|
if (target.value) {
|
|
router.push(target.value)
|
|
target.value = ''
|
|
}
|
|
}
|
|
|
|
const handleLogout = () => {
|
|
console.log('로그아웃 처리')
|
|
showUserMenu.value = false
|
|
}
|
|
|
|
const currentFontScale = ref<string>(localStorage.getItem('font-scale') || 'compact')
|
|
|
|
const setFontScale = (scale: string) => {
|
|
currentFontScale.value = scale
|
|
document.documentElement.setAttribute('data-font-scale', scale)
|
|
localStorage.setItem('font-scale', scale)
|
|
}
|
|
|
|
// Initial application
|
|
if (typeof document !== 'undefined') {
|
|
document.documentElement.setAttribute('data-font-scale', currentFontScale.value)
|
|
}
|
|
|
|
const toggleTheme = () => {
|
|
const root = document.documentElement
|
|
const isDark = root.getAttribute('data-theme') === 'dark'
|
|
root.setAttribute('data-theme', isDark ? 'light' : 'dark')
|
|
localStorage.setItem('theme', isDark ? 'light' : 'dark')
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<header class="ks-header ks-global-header">
|
|
<div class="ks-header__content">
|
|
<!-- Logo and menu toggle -->
|
|
<div class="ks-header__start">
|
|
<button
|
|
type="button"
|
|
class="ks-header__menu-toggle"
|
|
aria-label="메뉴 열기"
|
|
@click="emit('toggle-sidebar')"
|
|
>
|
|
☰
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="ks-header__mobile-toggle"
|
|
aria-label="모바일 메뉴 열기"
|
|
@click="emit('toggle-mobile-menu')"
|
|
>
|
|
☰
|
|
</button>
|
|
<div class="ks-header__logo">
|
|
<span class="ks-header__logo-icon">📊</span>
|
|
<span class="ks-header__logo-text">K-ArtSell</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Search bar -->
|
|
<div class="ks-header__search">
|
|
<input
|
|
v-model="searchQuery"
|
|
type="text"
|
|
class="ks-header__search-input"
|
|
placeholder="검색..."
|
|
@keyup.enter="handleSearch"
|
|
/>
|
|
<button
|
|
type="button"
|
|
class="ks-header__search-btn"
|
|
aria-label="검색"
|
|
@click="handleSearch"
|
|
>
|
|
🔍
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Right side actions -->
|
|
<div class="ks-header__end">
|
|
<!-- User Age-Tier Font Scale Selector -->
|
|
<div class="ks-header__font-scale-selector" title="연령별 / 가독성 폰트 크기 선택">
|
|
<button
|
|
type="button"
|
|
:class="['ks-header__font-btn', { 'ks-header__font-btn--active': currentFontScale === 'compact' }]"
|
|
title="청년/청장년층 (초고밀도 11px)"
|
|
@click="setFontScale('compact')"
|
|
>
|
|
청년 (소)
|
|
</button>
|
|
<button
|
|
type="button"
|
|
:class="['ks-header__font-btn', { 'ks-header__font-btn--active': currentFontScale === 'standard' }]"
|
|
title="중장년층 50대 (표준 13px)"
|
|
@click="setFontScale('standard')"
|
|
>
|
|
중장년 (중)
|
|
</button>
|
|
<button
|
|
type="button"
|
|
:class="['ks-header__font-btn', { 'ks-header__font-btn--active': currentFontScale === 'senior' }]"
|
|
title="시니어 60대 이상 (대형가독 15px)"
|
|
@click="setFontScale('senior')"
|
|
>
|
|
시니어 (대)
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Theme toggle -->
|
|
<button
|
|
type="button"
|
|
class="ks-header__action-btn"
|
|
aria-label="테마 전환"
|
|
@click="toggleTheme"
|
|
>
|
|
🌙
|
|
</button>
|
|
|
|
<!-- Notifications -->
|
|
<div class="ks-header__notifications">
|
|
<button
|
|
type="button"
|
|
class="ks-header__action-btn ks-header__action-btn--with-badge"
|
|
aria-label="알림"
|
|
@click="showNotifications = !showNotifications"
|
|
>
|
|
🔔
|
|
<span v-if="unreadCount > 0" class="ks-header__badge">
|
|
{{ unreadCount }}
|
|
</span>
|
|
</button>
|
|
|
|
<!-- Notification dropdown -->
|
|
<div v-if="showNotifications" class="ks-header__dropdown ks-header__dropdown--notifications">
|
|
<div class="ks-header__dropdown-header">알림</div>
|
|
<div class="ks-header__notification-list">
|
|
<div
|
|
v-for="notif in notifications"
|
|
:key="notif.id"
|
|
class="ks-header__notification-item"
|
|
>
|
|
<div class="ks-header__notification-message">{{ notif.message }}</div>
|
|
<div class="ks-header__notification-time">{{ notif.time }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="ks-header__dropdown-footer">
|
|
<button type="button" class="ks-header__dropdown-link">모든 알림 보기</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- User menu -->
|
|
<div class="ks-header__user-menu">
|
|
<button
|
|
type="button"
|
|
class="ks-header__user-btn"
|
|
aria-label="사용자 메뉴"
|
|
@click="showUserMenu = !showUserMenu"
|
|
>
|
|
<span class="ks-header__avatar">{{ currentUser.avatar }}</span>
|
|
<span class="ks-header__user-name">{{ currentUser.name }}</span>
|
|
<span class="ks-header__user-arrow">▼</span>
|
|
</button>
|
|
|
|
<!-- User dropdown -->
|
|
<div v-if="showUserMenu" class="ks-header__dropdown ks-header__dropdown--user">
|
|
<div class="ks-header__dropdown-header">
|
|
<div class="ks-header__user-info">
|
|
<div class="ks-header__user-avatar-large">{{ currentUser.avatar }}</div>
|
|
<div>
|
|
<div class="ks-header__user-name-large">{{ currentUser.name }}</div>
|
|
<div class="ks-header__user-email">{{ currentUser.email }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="ks-header__dropdown-menu">
|
|
<button type="button" class="ks-header__dropdown-link">
|
|
⚙️ 설정
|
|
</button>
|
|
<button type="button" class="ks-header__dropdown-link">
|
|
👤 프로필
|
|
</button>
|
|
<button type="button" class="ks-header__dropdown-link">
|
|
📚 문서
|
|
</button>
|
|
<hr class="ks-header__dropdown-divider" />
|
|
<button
|
|
type="button"
|
|
class="ks-header__dropdown-link ks-header__dropdown-link--danger"
|
|
@click="handleLogout"
|
|
>
|
|
🚪 로그아웃
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.ks-header {
|
|
background-color: var(--color-background-secondary);
|
|
border-bottom: 1px solid var(--color-border-primary);
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 100;
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
|
|
.ks-header__content {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
height: 44px;
|
|
max-width: 100%;
|
|
margin: 0 auto;
|
|
padding: 0 var(--spacing-4);
|
|
gap: var(--spacing-4);
|
|
}
|
|
|
|
.ks-header__start {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-4);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.ks-header__menu-toggle {
|
|
display: none;
|
|
background-color: var(--color-background-secondary);
|
|
border: 1px solid var(--color-border-primary);
|
|
font-size: 24px;
|
|
cursor: pointer;
|
|
color: var(--color-text-primary);
|
|
padding: var(--spacing-2);
|
|
border-radius: var(--border-radius-base);
|
|
transition: all var(--transition-base);
|
|
}
|
|
|
|
.ks-header__menu-toggle:hover {
|
|
background-color: var(--color-background-hover);
|
|
border-color: var(--color-border-secondary);
|
|
}
|
|
|
|
.ks-header__mobile-toggle {
|
|
display: none;
|
|
}
|
|
|
|
.ks-header__logo {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-2);
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
|
|
.ks-header__logo-icon {
|
|
font-size: 24px;
|
|
}
|
|
|
|
.ks-header__logo-text {
|
|
font-size: var(--font-size-lg);
|
|
font-weight: var(--font-weight-bold);
|
|
color: var(--color-primary-500);
|
|
}
|
|
|
|
.ks-header__search {
|
|
display: flex;
|
|
align-items: center;
|
|
flex: 1;
|
|
max-width: 300px;
|
|
background-color: var(--color-background-primary);
|
|
border: 1px solid var(--color-border-primary);
|
|
border-radius: var(--border-radius-base);
|
|
padding: 0 var(--spacing-3);
|
|
gap: var(--spacing-2);
|
|
}
|
|
|
|
.ks-header__search-input {
|
|
flex: 1;
|
|
border: none;
|
|
background: transparent;
|
|
outline: none;
|
|
color: var(--color-text-primary);
|
|
font-size: var(--font-size-sm);
|
|
padding: var(--spacing-2) 0;
|
|
}
|
|
|
|
.ks-header__search-input::placeholder {
|
|
color: var(--color-text-tertiary);
|
|
}
|
|
|
|
.ks-header__search-btn {
|
|
background: transparent;
|
|
border: none;
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
color: var(--color-primary-500);
|
|
padding: var(--spacing-1);
|
|
transition: all var(--transition-base);
|
|
}
|
|
|
|
.ks-header__search-btn:hover {
|
|
color: var(--color-primary-600);
|
|
}
|
|
|
|
.ks-header__search-btn:active {
|
|
color: var(--color-primary-700);
|
|
}
|
|
|
|
.ks-header__end {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--ks-space-2);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.ks-header__font-scale-selector {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 2px;
|
|
background-color: var(--ks-color-canvas);
|
|
border: 1px solid var(--ks-color-border);
|
|
border-radius: var(--ks-radius-sm);
|
|
padding: 2px;
|
|
}
|
|
|
|
.ks-header__font-btn {
|
|
background: transparent;
|
|
border: none;
|
|
color: var(--ks-color-text-muted);
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
padding: 2px 6px;
|
|
height: 22px;
|
|
border-radius: 2px;
|
|
cursor: pointer;
|
|
white-space: nowrap;
|
|
transition: var(--ks-transition-fast);
|
|
}
|
|
|
|
.ks-header__font-btn:hover {
|
|
color: var(--ks-color-text);
|
|
background-color: var(--ks-color-neutral-200);
|
|
}
|
|
|
|
.ks-header__font-btn--active {
|
|
background-color: var(--ks-color-action);
|
|
color: #ffffff !important;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.ks-header__action-btn {
|
|
background-color: var(--color-background-secondary);
|
|
border: 1px solid var(--color-border-primary);
|
|
font-size: 20px;
|
|
cursor: pointer;
|
|
color: var(--color-text-primary);
|
|
padding: var(--spacing-2);
|
|
border-radius: var(--border-radius-base);
|
|
transition: all var(--transition-base);
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 40px;
|
|
min-height: 40px;
|
|
}
|
|
|
|
.ks-header__action-btn:hover {
|
|
background-color: var(--color-background-hover);
|
|
border-color: var(--color-border-secondary);
|
|
}
|
|
|
|
.ks-header__action-btn:active {
|
|
background-color: var(--color-background-active);
|
|
}
|
|
|
|
.ks-header__action-btn--with-badge {
|
|
position: relative;
|
|
}
|
|
|
|
.ks-header__badge {
|
|
position: absolute;
|
|
top: 0;
|
|
right: 0;
|
|
background-color: var(--color-danger-500);
|
|
color: white;
|
|
width: 20px;
|
|
height: 20px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 12px;
|
|
font-weight: var(--font-weight-bold);
|
|
}
|
|
|
|
.ks-header__user-menu {
|
|
position: relative;
|
|
}
|
|
|
|
.ks-header__user-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-2);
|
|
background-color: var(--color-background-primary);
|
|
border: 1px solid var(--color-border-primary);
|
|
border-radius: var(--border-radius-base);
|
|
padding: var(--spacing-1) var(--spacing-2);
|
|
cursor: pointer;
|
|
transition: all var(--transition-base);
|
|
}
|
|
|
|
.ks-header__user-btn:hover {
|
|
background-color: var(--color-background-hover);
|
|
}
|
|
|
|
.ks-header__avatar {
|
|
font-size: 20px;
|
|
}
|
|
|
|
.ks-header__user-name {
|
|
font-size: var(--font-size-sm);
|
|
color: var(--color-text-primary);
|
|
}
|
|
|
|
.ks-header__user-arrow {
|
|
font-size: 12px;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
/* Dropdown styles */
|
|
.ks-header__dropdown {
|
|
position: absolute;
|
|
top: calc(100% + var(--spacing-2));
|
|
right: 0;
|
|
background-color: var(--color-background-primary);
|
|
border: 1px solid var(--color-border-primary);
|
|
border-radius: var(--border-radius-lg);
|
|
box-shadow: var(--shadow-lg);
|
|
min-width: 280px;
|
|
z-index: 1000;
|
|
animation: slideDown var(--transition-base);
|
|
}
|
|
|
|
@keyframes slideDown {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-8px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.ks-header__dropdown--notifications {
|
|
min-width: 320px;
|
|
}
|
|
|
|
.ks-header__dropdown-header {
|
|
padding: var(--spacing-3);
|
|
border-bottom: 1px solid var(--color-border-primary);
|
|
font-weight: var(--font-weight-semibold);
|
|
font-size: var(--font-size-sm);
|
|
color: var(--color-text-primary);
|
|
}
|
|
|
|
.ks-header__user-info {
|
|
display: flex;
|
|
gap: var(--spacing-3);
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.ks-header__user-avatar-large {
|
|
font-size: 32px;
|
|
}
|
|
|
|
.ks-header__user-name-large {
|
|
font-weight: var(--font-weight-semibold);
|
|
color: var(--color-text-primary);
|
|
}
|
|
|
|
.ks-header__user-email {
|
|
font-size: var(--font-size-xs);
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.ks-header__dropdown-menu {
|
|
padding: var(--spacing-2) 0;
|
|
}
|
|
|
|
.ks-header__dropdown-link {
|
|
display: block;
|
|
width: 100%;
|
|
padding: var(--spacing-2) var(--spacing-3);
|
|
background: none;
|
|
border: none;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
color: var(--color-text-primary);
|
|
font-size: var(--font-size-sm);
|
|
transition: all var(--transition-fast);
|
|
}
|
|
|
|
.ks-header__dropdown-link:hover {
|
|
background-color: var(--color-background-secondary);
|
|
}
|
|
|
|
.ks-header__dropdown-link--danger {
|
|
color: var(--color-danger-500);
|
|
}
|
|
|
|
.ks-header__dropdown-link--danger:hover {
|
|
background-color: var(--color-danger-50);
|
|
}
|
|
|
|
.ks-header__dropdown-divider {
|
|
margin: var(--spacing-2) 0;
|
|
border: none;
|
|
border-top: 1px solid var(--color-border-primary);
|
|
}
|
|
|
|
.ks-header__dropdown-footer {
|
|
padding: var(--spacing-2) var(--spacing-3);
|
|
border-top: 1px solid var(--color-border-primary);
|
|
text-align: center;
|
|
}
|
|
|
|
.ks-header__notification-list {
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.ks-header__notification-item {
|
|
padding: var(--spacing-3);
|
|
border-bottom: 1px solid var(--color-border-primary);
|
|
cursor: pointer;
|
|
transition: all var(--transition-base);
|
|
}
|
|
|
|
.ks-header__notification-item:hover {
|
|
background-color: var(--color-background-secondary);
|
|
}
|
|
|
|
.ks-header__notification-message {
|
|
font-size: var(--font-size-sm);
|
|
color: var(--color-text-primary);
|
|
margin-bottom: var(--spacing-1);
|
|
}
|
|
|
|
.ks-header__notification-time {
|
|
font-size: var(--font-size-xs);
|
|
color: var(--color-text-tertiary);
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 768px) {
|
|
.ks-header__content {
|
|
height: 56px;
|
|
padding: 0 var(--spacing-3);
|
|
}
|
|
|
|
.ks-header__menu-toggle {
|
|
display: block;
|
|
}
|
|
|
|
.ks-header__logo-text {
|
|
display: none;
|
|
}
|
|
|
|
.ks-header__search {
|
|
display: none;
|
|
}
|
|
|
|
.ks-header__user-name {
|
|
display: none;
|
|
}
|
|
|
|
.ks-header__dropdown {
|
|
right: -var(--spacing-3);
|
|
}
|
|
}
|
|
|
|
/* Prefers reduced motion */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.ks-header__action-btn,
|
|
.ks-header__dropdown {
|
|
transition: none;
|
|
}
|
|
|
|
.ks-header__dropdown {
|
|
animation: none;
|
|
}
|
|
}
|
|
</style>
|