v0.0.51: 사이드바 열 높이 고정·발행일 YYYY.MM.DD 통일

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-08 10:18:43 +09:00
parent 3cb1290711
commit 4b1ab9e00e
12 changed files with 61 additions and 81 deletions

View File

@@ -22,7 +22,7 @@ const { data: navigation } = await useFetch('/api/navigation', {
<template>
<aside
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:max-h-[calc(100vh-57px)] lg:self-start lg:flex lg:flex-col"
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="menuOpen ? 'w-[287px] opacity-100' : 'w-0 opacity-0 border-transparent'"
>
<div class="left-sidebar__scroll site-sidebar-scroll min-h-0 flex-1">
@@ -99,7 +99,7 @@ const { data: navigation } = await useFetch('/api/navigation', {
</NuxtLink>
</nav>
<button
class="left-sidebar__theme-dot site-panel-hover site-interactive grid h-7 w-7 place-items-center rounded-full border border-[var(--site-line)]"
class="left-sidebar__theme-dot site-panel-hover site-interactive grid h-7 w-7 place-items-center rounded-full"
type="button"
:aria-label="isDarkMode ? '라이트 모드로 전환' : '다크 모드로 전환'"
:title="isDarkMode ? '라이트 모드' : '다크 모드'"

View File

@@ -19,7 +19,7 @@ const { data: siteSettings } = await useFetch('/api/site-settings', {
</script>
<template>
<aside class="right-sidebar site-sidebar hidden w-[287px] overflow-hidden border-l border-[var(--site-line)] lg:sticky lg:top-[57px] lg:z-10 lg:max-h-[calc(100vh-57px)] lg:self-start lg:flex lg:flex-col">
<aside class="right-sidebar site-sidebar hidden w-[287px] overflow-hidden border-l border-[var(--site-line)] 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">
<div class="right-sidebar__scroll site-sidebar-scroll min-h-0 flex-1">
<div class="right-sidebar__block site-sidebar-section py-5 pl-5 pr-0">
<div class="right-sidebar__profile flex items-center gap-3">

View File

@@ -0,0 +1,22 @@
/**
* 공개 화면용 게시 날짜를 YYYY.MM.DD 형식으로 변환한다.
* @param {string | null | undefined} value - ISO 8601 등 파싱 가능한 날짜 문자열
* @returns {string} 빈 문자열 또는 YYYY.MM.DD
*/
export function formatPostDate(value) {
if (!value) {
return ''
}
const date = new Date(value)
if (Number.isNaN(date.getTime())) {
return ''
}
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}.${month}.${day}`
}

View File

@@ -1,5 +1,11 @@
# 의사결정 이력
## 2026-05-08 v0.0.51
### 사이드바 고정 높이와 발행일 포맷
`lg+` 그리드에서 `items-start` 때문에 사이드바 박스 높이가 콘텐츠만큼만 잡히면 내부 `flex-1` 스크롤 영역이 늘어나지 않아 푸터가 상단 블록 바로 아래에 붙는다. 데스크톱에서 열 높이를 `h-[calc(100vh-57px)]`(및 동일 `max-h`)로 고정해 flex 컬럼 안에서 푸터를 열 하단에 두었다. 공개 피드·상세의 발행일은 `formatPostDate``YYYY.MM.DD`를 통일하고 `<time datetime>`에 원본 ISO를 넣어 접근성을 맞춘다.
## 2026-05-08 v0.0.50
### 문서 스크롤과 스티키 사이드바

View File

@@ -11,13 +11,19 @@
| layouts/admin.vue | 관리자 전체, 글 작성/수정 화면의 전체 화면 편집 모드와 문서 스크롤 잠금 |
| layouts/page.vue | 고정 페이지 전체 화면 |
## Composables
| 파일 | 용도 |
|------|------|
| composables/formatPostDate.js | 공개 화면 게시일 `YYYY.MM.DD` 포맷 |
## 사이트 컴포넌트
| 파일 | 화면 위치 |
|------|-----------|
| components/site/SiteHeader.vue | 모든 공개 페이지 상단 |
| components/site/LeftSidebar.vue | 왼쪽 사이드바, `sticky`+내부 무스크롤바 스크롤, 하단 푸터 고정 |
| components/site/RightSidebar.vue | 오른쪽 사이드바, 동일 패턴, 카피라이트 하단 고정 |
| components/site/LeftSidebar.vue | 왼쪽 사이드바, `sticky`+`h/max-h: calc(100vh-57px)`+내부 무스크롤바 스크롤, 하단 푸터 고정 |
| components/site/RightSidebar.vue | 오른쪽 사이드바, 동일 패턴(고정 열 높이), 카피라이트 하단 고정 |
| components/site/MainColumn.vue | 메인 화면 중앙 |
| components/site/PostCard.vue | 목록의 게시물 카드, 대표 이미지 썸네일, 카드 hover 인터랙션 |
| components/site/TagHeader.vue | 태그 페이지 헤더 |

View File

@@ -20,9 +20,9 @@
| Header | 높이 57px, `sticky top-0`, `shrink-0` |
| Shell | `min-height: 100vh`, `flex` 세로 컬럼 |
| 그리드(데스크톱 `lg+`) | `items-start`, 본문(중앙) 높이에 맞춰 행이 늘어남 — **문서(`html`/`body`) 스크롤**로 긴 본문 처리(스크롤바는 브라우저 오른쪽) |
| Left Aside | 너비 287px, `sticky top-[57px]`, `max-h-[calc(100vh-57px)]`, 내부 상단은 `.site-sidebar-scroll`(스크롤바 숨김), 하단 푸터 `shrink-0` |
| Left Aside | 너비 287px, `sticky top-[57px]`, `h-[calc(100vh-57px)]``max-h` 동일(뷰포트 기준 고정 높이), 내부 상단은 `.site-sidebar-scroll`(스크롤바 숨김), 하단 푸터 `shrink-0`·상단 보더로 스크롤 영역과 구분 |
| Main | 너비 720px, 별도 `overflow-y` 없음 — 뷰포트와 동일한 문서 스크롤에 포함 |
| Right Aside | Left와 동일 패턴(스티키·최대 높이·내부 무스크롤바 스크롤·하단 카피라이트) |
| Right Aside | Left와 동일 패턴(스티키·고정 높이·내부 무스크롤바 스크롤·하단 카피라이트) |
### 메뉴 토글
@@ -45,6 +45,12 @@
- Main 좌우 패딩: 24px → 20px
- 공개 게시물 본문은 콘텐츠 타입별 컴포넌트로 분리해 추후 스타일 변경이 쉽도록 구성
### 공개 목록·상세의 발행일 표시
- API의 ISO 8601 `publishedAt`를 공개 UI에서는 로컬 날짜 기준 `YYYY.MM.DD`로 표시한다.
- 변환은 `composables/formatPostDate.js``formatPostDate`를 사용한다.
- `<time>`에는 표시용 문자열과 함께 가능한 경우 원본 시각을 `datetime` 속성으로 둔다.
### Page 페이지
- About, Projects, Links, Contact, 서비스 소개 페이지 등 고정 콘텐츠에 사용

View File

@@ -1,5 +1,12 @@
# 업데이트 이력
## v0.0.51
- 좌·우 사이드바 데스크톱 열 높이를 `calc(100vh - 57px)`로 고정해 내부 스크롤·하단 푸터 배치가 뷰포트 기준으로 맞도록 수정.
- 사이드 푸터에 상단 보더 추가(스크롤 영역과 시각적 구분).
- 공개 피드·게시 상세·아카이브 발행일 `YYYY.MM.DD` 통일, `composables/formatPostDate.js` 사용·`<time datetime>` 보강.
- `pages/tag/[slug].vue` 동일 날짜·datetime 처리.
## v0.0.50
- 데스크톱(`lg+`)에서 긴 본문은 **문서 스크롤**(브라우저 오른쪽 스크롤바)로 처리하고, `main` 단독 스크롤은 제거.

View File

@@ -1,6 +1,6 @@
{
"name": "sori.studio",
"version": "0.0.50",
"version": "0.0.51",
"private": true,
"type": "module",
"scripts": {

View File

@@ -47,22 +47,6 @@ const onDocumentPointerDown = (event) => {
closePostFeedStyleMenu()
}
/**
* 날짜 표시 형식 변환
* @param {string | null} value - ISO 날짜 문자열
* @returns {string} 화면 표시 날짜
*/
const formatPostDate = (value) => {
if (!value) {
return ''
}
return new Intl.DateTimeFormat('en-US', {
month: 'short',
day: 'numeric'
}).format(new Date(value))
}
/**
* 태그 슬러그로 태그 정보 조회
* @param {string | undefined} slug - 태그 슬러그
@@ -94,6 +78,7 @@ const mapLatestPost = (post, index) => {
tagName: tagMeta.name,
tagColor: tagMeta.color,
publishedAt: formatPostDate(post.publishedAt),
publishedAtIso: post.publishedAt || '',
to: `/post/${post.slug}`,
isFeatured: index === 0
}
@@ -366,7 +351,7 @@ const scrollFeatured = (direction) => {
<h2 class="max-w-[90%] text-sm font-medium leading-tight">
<NuxtLink :to="post.to" class="transition-opacity duration-200 hover:opacity-75">
<span v-if="post.isFeatured" class="mr-1 inline-flex text-[var(--site-accent)] [&_svg]:-mt-0.5">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M13 3v7h6l-8 11v-7H5l8-11" />
</svg>
</span>
@@ -382,7 +367,7 @@ const scrollFeatured = (direction) => {
</p>
<div class="flex flex-wrap items-center gap-2 text-xs site-muted sm:gap-1.5">
<time>{{ post.publishedAt }}</time>
<time v-if="post.publishedAt" :datetime="post.publishedAtIso">{{ post.publishedAt }}</time>
<span class="text-[var(--site-line)]">/</span>
<span
class="rounded-sm px-1.5 py-px font-medium text-[var(--site-text)]"
@@ -391,7 +376,7 @@ const scrollFeatured = (direction) => {
{{ post.tagName }}
</span>
<span class="text-[var(--site-line)]">/</span>
<span class="flex items-center gap-0.75">
<span class="flex items-center gap-1">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="-mt-px">
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
</svg>

View File

@@ -21,23 +21,6 @@ if (!post.value) {
})
}
/**
* 게시물 날짜 표시 형식 변환 (Thred 참고)
* @param {string | null} value - ISO 날짜 문자열
* @returns {string} 화면 표시 날짜
*/
const formatPostDate = (value) => {
if (!value) {
return ''
}
return new Intl.DateTimeFormat('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
}).format(new Date(value))
}
const primaryTagSlug = computed(() => post.value.tags?.[0] || '')
const primaryTagMeta = computed(() => {
const matchedTag = tags.value.find((item) => item.slug === primaryTagSlug.value)

View File

@@ -3,24 +3,6 @@ const { data: posts } = await useFetch('/api/posts', {
default: () => []
})
/**
* 날짜 표시 형식 변환
* @param {string | null} value - ISO 날짜 문자열
* @returns {string} 화면 표시 날짜
*/
const formatPostDate = (value) => {
if (!value) {
return ''
}
const date = new Date(value)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}.${month}.${day}`
}
const postCards = computed(() => posts.value.map((post) => ({
title: post.title,
excerpt: post.excerpt,

View File

@@ -10,24 +10,6 @@ const { data: posts } = await useFetch('/api/posts', {
default: () => []
})
/**
* 날짜 표시 형식 변환
* @param {string | null} value - ISO 날짜 문자열
* @returns {string} 화면 표시 날짜
*/
const formatPostDate = (value) => {
if (!value) {
return ''
}
const date = new Date(value)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}.${month}.${day}`
}
const tag = computed(() => tags.value.find((item) => item.slug === slug.value))
const tagPosts = computed(() => posts.value
@@ -40,6 +22,7 @@ const tagPosts = computed(() => posts.value
tagColor: tags.value.find((item) => item.slug === (post.tags?.[0] || slug.value))?.color || '#4d4d4d',
isFeatured: index === 0,
publishedAt: formatPostDate(post.publishedAt),
publishedAtIso: post.publishedAt || '',
to: `/post/${post.slug}`
})))
</script>
@@ -102,7 +85,7 @@ const tagPosts = computed(() => posts.value
{{ post.excerpt }}
</p>
<div class="flex flex-wrap items-center gap-2 text-xs site-muted sm:gap-1.5">
<time>{{ post.publishedAt }}</time>
<time v-if="post.publishedAt" :datetime="post.publishedAtIso">{{ post.publishedAt }}</time>
<span class="text-[var(--site-line)]">/</span>
<span
class="rounded-sm px-1.5 py-px font-medium text-[var(--site-text)]"