v0.0.53: 공유 모달·헤더 사용자 메뉴·회원가입·로그인 화면
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -22,7 +22,7 @@ const { data: navigation } = await useFetch('/api/navigation', {
|
||||
|
||||
<template>
|
||||
<aside
|
||||
class="left-sidebar site-sidebar hidden overflow-hidden transition-[width,opacity,border-color] duration-300 ease-out lg:sticky lg:top-[57px] lg:z-10 lg:h-[calc(100vh-57px)] lg:max-h-[calc(100vh-57px)] lg:self-start lg:flex lg:flex-col"
|
||||
class="left-sidebar site-sidebar hidden overflow-hidden border-r border-[var(--site-line)] transition-[width,opacity,border-color] duration-300 ease-out lg:sticky lg:top-[57px] lg:z-10 lg:h-[calc(100vh-57px)] lg:max-h-[calc(100vh-57px)] lg:self-start lg:flex lg:flex-col"
|
||||
:class="menuOpen ? 'w-[287px] opacity-100' : 'w-0 opacity-0 border-transparent'"
|
||||
>
|
||||
<div class="left-sidebar__scroll site-sidebar-scroll min-h-0 flex-1">
|
||||
@@ -87,7 +87,7 @@ const { data: navigation } = await useFetch('/api/navigation', {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="left-sidebar__footer flex shrink-0 items-center justify-between border-t border-[var(--site-line)] px-1 py-4 text-xs">
|
||||
<footer class="left-sidebar__footer flex shrink-0 items-center justify-between px-1 py-4 text-xs">
|
||||
<nav class="left-sidebar__footer-nav flex gap-4">
|
||||
<NuxtLink
|
||||
v-for="item in navigation.footer"
|
||||
|
||||
@@ -179,7 +179,7 @@ const { data: siteSettings } = await useFetch('/api/site-settings', {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="right-sidebar__footer shrink-0 border-t border-[var(--site-line)] py-4 pl-5 pr-0 text-xs site-muted">
|
||||
<footer class="right-sidebar__footer shrink-0 py-4 pl-5 pr-0 text-xs site-muted">
|
||||
{{ siteSettings.copyrightText }}
|
||||
</footer>
|
||||
</aside>
|
||||
|
||||
@@ -1,12 +1,58 @@
|
||||
<script setup>
|
||||
const { menuOpen, toggleMenu } = useMenuState()
|
||||
const { isDarkMode, toggleTheme } = useThemeMode()
|
||||
const menuUserOpen = ref(false)
|
||||
const userMenuRef = ref(null)
|
||||
const userMenuToggleRef = ref(null)
|
||||
|
||||
const { data: siteSettings } = await useFetch('/api/site-settings', {
|
||||
default: () => ({
|
||||
title: 'sori.studio'
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* 사용자 메뉴를 닫는다.
|
||||
* @returns {void}
|
||||
*/
|
||||
const closeUserMenu = () => {
|
||||
menuUserOpen.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 메뉴를 토글한다.
|
||||
* @returns {void}
|
||||
*/
|
||||
const toggleUserMenu = () => {
|
||||
menuUserOpen.value = !menuUserOpen.value
|
||||
}
|
||||
|
||||
/**
|
||||
* 문서 클릭 시 사용자 메뉴 외부 영역이면 메뉴를 닫는다.
|
||||
* @param {MouseEvent} event - 클릭 이벤트
|
||||
* @returns {void}
|
||||
*/
|
||||
const onDocumentClick = (event) => {
|
||||
const target = /** @type {Node | null} */ (event.target instanceof Node ? event.target : null)
|
||||
if (!target) {
|
||||
closeUserMenu()
|
||||
return
|
||||
}
|
||||
|
||||
const isInsideMenu = userMenuRef.value instanceof HTMLElement && userMenuRef.value.contains(target)
|
||||
const isToggleButton = userMenuToggleRef.value instanceof HTMLElement && userMenuToggleRef.value.contains(target)
|
||||
|
||||
if (!isInsideMenu && !isToggleButton) {
|
||||
closeUserMenu()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', onDocumentClick)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('click', onDocumentClick)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -54,19 +100,64 @@ const { data: siteSettings } = await useFetch('/api/site-settings', {
|
||||
<NuxtLink class="site-header__buy site-accent-button rounded-lg px-4 py-2 font-semibold" to="/pages/about">
|
||||
Subscribe
|
||||
</NuxtLink>
|
||||
<NuxtLink class="site-header__nav-link site-interactive rounded-md px-2 py-1" to="/pages/about">
|
||||
Account
|
||||
</NuxtLink>
|
||||
<button
|
||||
class="site-header__theme-toggle site-panel-hover site-interactive grid h-8 w-8 place-items-center rounded-full border border-[var(--site-line)]"
|
||||
type="button"
|
||||
:aria-label="isDarkMode ? '라이트 모드로 전환' : '다크 모드로 전환'"
|
||||
:title="isDarkMode ? '라이트 모드' : '다크 모드'"
|
||||
@click="toggleTheme"
|
||||
>
|
||||
<span v-if="isDarkMode">☀</span>
|
||||
<span v-else>☾</span>
|
||||
</button>
|
||||
<div class="site-header__user-menu relative">
|
||||
<button
|
||||
ref="userMenuToggleRef"
|
||||
class="site-header__user-toggle relative flex h-7 w-7 items-center justify-center rounded-full transition-opacity duration-200 hover:opacity-75 md:h-8 md:w-8"
|
||||
type="button"
|
||||
aria-label="Toggle user menu"
|
||||
:aria-expanded="menuUserOpen.toString()"
|
||||
@click="toggleUserMenu"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" class="h-6 w-6 stroke-current stroke-[1.75] md:h-7 md:w-7 md:stroke-[1.5]">
|
||||
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
|
||||
<path d="M12 10m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" />
|
||||
<path d="M6.168 18.849a4 4 0 0 1 3.832 -2.849h4a4 4 0 0 1 3.834 2.855" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<Transition
|
||||
enter-active-class="transition-[transform,opacity,visibility] duration-200 ease-out"
|
||||
enter-from-class="-translate-y-2 scale-95 opacity-0"
|
||||
enter-to-class="translate-y-0 scale-100 opacity-100"
|
||||
leave-active-class="transition-[transform,opacity,visibility] duration-150 ease-in"
|
||||
leave-from-class="translate-y-0 scale-100 opacity-100"
|
||||
leave-to-class="-translate-y-2 scale-95 opacity-0"
|
||||
>
|
||||
<div
|
||||
v-if="menuUserOpen"
|
||||
ref="userMenuRef"
|
||||
class="site-header__user-dropdown absolute top-12 right-0 z-30 flex min-w-[200px] max-w-xs flex-col overflow-hidden rounded-[10px] border border-[var(--site-line)] bg-[var(--site-bg)] p-3 pb-2 text-sm font-medium shadow-[0_12px_30px_rgba(0,0,0,0.12)]"
|
||||
>
|
||||
<div class="mb-2 flex items-center gap-2 border-b border-[var(--site-line)] pb-3">
|
||||
<div class="site-header__avatar-wrap flex h-8 w-8 items-center justify-center overflow-hidden rounded-full bg-[var(--site-panel)] md:h-10 md:w-10">
|
||||
<span class="text-base font-normal uppercase md:text-lg">@</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<div class="max-w-xs truncate leading-[1.15]">Anonymous</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NuxtLink class="site-header__user-link flex items-center gap-1.5 rounded-[8px] px-2.5 py-1.5 transition-colors duration-150 hover:bg-[var(--site-panel)]" to="/signup/" @click="closeUserMenu">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="h-5 w-5">
|
||||
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0" />
|
||||
<path d="M15 9l-6 6" />
|
||||
<path d="M15 15v-6h-6" />
|
||||
</svg>
|
||||
<span>Sign up</span>
|
||||
</NuxtLink>
|
||||
|
||||
<NuxtLink class="site-header__user-link flex items-center gap-1.5 rounded-[8px] px-2.5 py-1.5 transition-colors duration-150 hover:bg-[var(--site-panel)]" to="/signin/" @click="closeUserMenu">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="h-5 w-5">
|
||||
<path d="M15 8v-2a2 2 0 0 0 -2 -2h-7a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h7a2 2 0 0 0 2 -2v-2" />
|
||||
<path d="M21 12h-13l3 -3" />
|
||||
<path d="M11 15l-3 -3" />
|
||||
</svg>
|
||||
<span>Sign in</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
Reference in New Issue
Block a user