릴리스: v1.2.18 공통 56px 셸 헤더 정리
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
# 의사결정 이력
|
||||
|
||||
## 2026-03-30 v1.2.18
|
||||
- 피그마 기준 상단 구조는 페이지마다 다르게 보이면 안 되므로, 좌/중앙/우 컬럼 모두 `56px` 헤더를 고정으로 두고 내용이 없을 때도 빈 헤더 공간을 유지하는 편이 맞다고 정리했다.
|
||||
- 사이트 브랜드는 좌측 레일 안쪽 카드가 아니라 중앙 워크스페이스 상단의 고정 헤더에 두는 쪽이 시안과 더 가깝고, 페이지 이동 시에도 더 일관되게 읽힌다고 판단했다.
|
||||
- 에디터 화면 안에서 `.layout`이 다시 좌우 컬럼을 만들면 공통 3단 셸과 충돌하므로, 에디터 본문은 셸이 제공한 중앙 컬럼 안에서만 레이아웃을 잡아야 한다고 정리했다.
|
||||
|
||||
## 2026-03-30 v1.2.17
|
||||
- 공통 오른쪽 레일을 쓰는 화면에서는 로컬 패널이 다시 외곽 래퍼 카드로 감싸지면 “오른쪽 레일 안의 또 다른 사이드”처럼 읽히므로, 에디터 우측 패널은 섹션들만 공통 레일 루트에 직접 배치하는 쪽이 더 일관적이라고 정리했다.
|
||||
- 에디터/관리자 공통 오른쪽 컬럼은 컨테이너를 화면별로 따로 꾸미기보다, 셸의 `localRightRailRoot`가 기본 스택 문법을 제공하고 각 화면은 내부 section만 채우는 방식으로 맞추기로 했다.
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
## 공통 레이아웃
|
||||
- 앱 셸 파일: `frontend/src/App.vue`
|
||||
- 역할: 좌측 내비게이션, 중앙 워크스페이스, 우측 컨텍스트 패널로 구성된 공통 앱 셸 렌더링, 로그인 상태 반영, 사용자 메뉴, 관리자 메뉴 노출 제어, 일부 실제 SVG 에셋과 선형 SVG 아이콘이 혼합된 레일 UI, 전역 우측 상단 토스트 렌더링
|
||||
- 세부: 좌측 패널은 `248px`, 우측 패널은 `320px` 기준 폭을 사용하며, 상단 토글 버튼으로 우측 패널을 접고 펼칠 수 있다.
|
||||
- 세부: 좌측 패널은 `248px`, 우측 패널은 `320px` 기준 폭을 사용하며, 세 컬럼 모두 상단에 높이 `56px`의 헤더 블록을 유지한다. 중앙 헤더에는 고정 브랜드 `Tier Maker by zenn`이 표시되고, 상단 토글 버튼으로 우측 패널을 접고 펼칠 수 있다.
|
||||
|
||||
## 백엔드 진입점
|
||||
- 서버 엔트리: `backend/index.js`
|
||||
|
||||
@@ -186,6 +186,7 @@
|
||||
- 티어표에 썸네일을 직접 지정하지 않으면 저장 시 대표 아이템 이미지 하나를 기본 썸네일로 자동 선택한다.
|
||||
- 편집 화면 상단 헤더는 좌측 제목/설명, 우측 썸네일 카드 구조를 사용하며 모바일에서는 한 열로 접힌다.
|
||||
- 티어표 편집 화면의 우측 패널은 공통 `rightRail`의 `localRightRailRoot`에 직접 section들을 쌓는 구조이며, 별도 외곽 래퍼 카드 없이 공통 오른쪽 컬럼 문법을 그대로 따른다.
|
||||
- 공통 앱 셸은 좌측/중앙/우측 컬럼마다 높이 `56px`의 상단 헤더 블록을 유지하며, 중앙 헤더에는 고정 사이트 타이틀 `Tier Maker by zenn`을 표시한다.
|
||||
- 티어표 편집기의 아이콘 기본 크기는 `80px`이며, 사용자가 `48 / 60 / 80 / 100 / 120` 단계로 즉시 조절할 수 있다.
|
||||
- 공개 티어표 목록과 `내 티어표` 목록은 제목 옆에 작성자 아바타와 표시명을 함께 보여준다.
|
||||
- 작성자 아바타 이미지가 없을 경우 목록 썸네일 fallback은 닉네임이 아니라 계정명 기준 첫 글자를 사용한다.
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
- 에디터 로컬 우측 패널은 공통 토글과 연결됐지만, 아직 완전한 피그마 수준의 패널 애니메이션과 내부 카드 재배치는 더 다듬을 필요가 있다.
|
||||
- 에디터 우측 패널은 셸의 세 번째 컬럼으로 옮겼지만, 내부 카드 간격과 섹션 구분선은 아직 첨부 시안처럼 더 촘촘하게 정리할 필요가 있다.
|
||||
- 에디터 우측 패널 외곽 래퍼는 제거했으므로, 다음 단계는 공통 오른쪽 컬럼 안에서 입력/버튼/구분선 간격을 시안처럼 더 정교하게 다듬는 작업이다.
|
||||
- 공통 56px 셸 헤더는 반영했으므로, 다음 단계는 좌/중앙/우 헤더 안에 실제 아이콘/상태 요소를 시안 순서에 맞게 하나씩 채워 넣는 작업이다.
|
||||
- 공통 3단 셸 구조는 고정했지만, 관리자/에디터 우측 패널 내부에 아직 바디에 남아 있는 제어 요소를 더 옮겨야 한다.
|
||||
- 홈 화면 우측 사이드는 CTA 하나만 남긴 상태이므로, 이후 필요할 때도 임시 정보 카드 다수를 다시 넣기보다 실제 필요한 기능만 선별해 추가해야 한다.
|
||||
- 관리자 화면은 헤더 요약 통계와 카드 계층까지 정리됐지만, 아직 표준 SVG 아이콘 교체와 더 세밀한 상태 색상/선택 상태 표현은 남아 있다.
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# 업데이트 로그
|
||||
|
||||
## 2026-03-30 v1.2.18
|
||||
- **공통 56px 셸 헤더 도입**: 좌측 사이드, 중앙 워크스페이스, 우측 사이드 상단에 각각 높이 `56px`의 고정 헤더 블록을 두고, 사이트 타이틀 `Tier Maker by zenn`은 중앙 상단 헤더에만 표시되도록 셸 구조를 재정리
|
||||
- **에디터 메인 래퍼 단순화**: 티어표 편집 화면의 `.layout` 2열 그리드를 제거해 공통 3단 셸 바깥에 중복 컬럼이 생기지 않도록 정리
|
||||
- **아이템 라벨 overflow 수정**: 편집 화면 우측 아이템 풀에서 긴 아이템 이름이 화면 밖으로 밀려나지 않도록 `minmax(0, 1fr)`와 말줄임 처리 기준을 추가
|
||||
|
||||
## 2026-03-30 v1.2.17
|
||||
- **에디터 우측 패널 래퍼 제거**: 티어표 편집 화면의 `editorSidebar` 외곽 래퍼를 제거하고, 공통 오른쪽 레일 루트에 편집 섹션들이 직접 쌓이도록 구조를 단순화
|
||||
- **공통 우측 레일 정렬 통일**: `App.vue`의 `localRightRailRoot`에 섹션 스택 정렬을 부여해, 에디터/관리자 같은 로컬 패널 화면도 공통 레일 안에서 같은 방식으로 콘텐츠가 배치되도록 정리
|
||||
|
||||
@@ -210,16 +210,13 @@ async function logout() {
|
||||
</template>
|
||||
<template v-else>
|
||||
<aside class="leftRail">
|
||||
<div class="leftRail__top">
|
||||
<div class="leftRail__top railHeader">
|
||||
<button class="ghostIcon ghostIcon--iconOnly" type="button" aria-label="메뉴">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true"><path :d="railGlyph('menu')" /></svg>
|
||||
</button>
|
||||
<div class="brandBlock" @click="$router.push('/')">
|
||||
<div class="brandBlock__title">Tier Maker</div>
|
||||
<div class="brandBlock__sub">by zenn</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="leftRail__body">
|
||||
<div v-if="auth.user" class="appUserCard">
|
||||
<button v-if="auth.user" class="appUserCard__button" type="button" @click.stop="toggleMenu">
|
||||
<img v-if="avatarUrl" :src="avatarUrl" class="appUserCard__avatar" alt="avatar" />
|
||||
@@ -270,14 +267,15 @@ async function logout() {
|
||||
<RouterLink v-if="isAdmin" to="/admin" class="adminButton">관리자 메뉴</RouterLink>
|
||||
<RouterLink v-else-if="!auth.user" to="/login" class="adminButton">로그인</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="appMain">
|
||||
<section class="workspace" :class="{ 'workspace--localRail': usesLocalRightRail }">
|
||||
<header class="workspaceHead">
|
||||
<div>
|
||||
<div class="workspaceHead__title">{{ routeMeta.title }}</div>
|
||||
<div class="workspaceHead__subtitle">{{ routeMeta.subtitle }}</div>
|
||||
<header class="workspaceHead railHeader">
|
||||
<div class="workspaceHead__brand" @click="$router.push('/')">
|
||||
<span class="workspaceHead__brandTitle">Tier Maker</span>
|
||||
<span class="workspaceHead__brandSub">by zenn</span>
|
||||
</div>
|
||||
<div class="workspaceHead__actions">
|
||||
<button class="ghostIcon ghostIcon--workspace" type="button" :aria-pressed="rightRailOpen" @click="toggleRightRail">
|
||||
@@ -293,6 +291,8 @@ async function logout() {
|
||||
</main>
|
||||
|
||||
<aside class="rightRail" :class="{ 'rightRail--closed': !rightRailOpen }" :aria-hidden="!rightRailOpen">
|
||||
<div class="rightRail__top railHeader"></div>
|
||||
<div class="rightRail__body">
|
||||
<template v-if="!usesLocalRightRail">
|
||||
<section class="rightRailAction">
|
||||
<button class="rightRailAction__button" type="button" @click="routeMeta.action">
|
||||
@@ -301,6 +301,7 @@ async function logout() {
|
||||
</section>
|
||||
</template>
|
||||
<div id="local-right-rail-root" class="localRightRailRoot"></div>
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
@@ -335,11 +336,12 @@ async function logout() {
|
||||
.leftRail,
|
||||
.rightRail {
|
||||
min-height: 100vh;
|
||||
padding: 14px 12px;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(14, 14, 14, 0.92);
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.rightRail {
|
||||
@@ -366,12 +368,27 @@ async function logout() {
|
||||
border-left-color: transparent;
|
||||
}
|
||||
|
||||
.leftRail__top,
|
||||
.rightRail__top {
|
||||
.railHeader {
|
||||
height: 56px;
|
||||
min-height: 56px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.leftRail__top,
|
||||
.rightRail__top {
|
||||
gap: 12px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.leftRail__body,
|
||||
.rightRail__body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 14px 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ghostIcon {
|
||||
@@ -428,23 +445,6 @@ async function logout() {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.brandBlock {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.brandBlock__title {
|
||||
font-size: 21px;
|
||||
font-weight: 900;
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
|
||||
.brandBlock__sub {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.56);
|
||||
}
|
||||
|
||||
.appUserCard {
|
||||
position: relative;
|
||||
margin-bottom: 14px;
|
||||
@@ -629,14 +629,8 @@ async function logout() {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.leftRail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.appMain {
|
||||
min-width: 0;
|
||||
padding: 14px 18px 22px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@@ -646,20 +640,40 @@ async function logout() {
|
||||
|
||||
.workspace {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
gap: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.workspace--localRail {
|
||||
gap: 12px;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.workspaceHead {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.workspaceHead__brand {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.workspaceHead__brandTitle {
|
||||
font-size: 28px;
|
||||
font-weight: 900;
|
||||
letter-spacing: -0.05em;
|
||||
}
|
||||
|
||||
.workspaceHead__brandSub {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: rgba(255, 255, 255, 0.58);
|
||||
}
|
||||
|
||||
.workspaceHead__actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
@@ -667,40 +681,28 @@ async function logout() {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.workspaceHead__title {
|
||||
font-size: 30px;
|
||||
font-weight: 900;
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
|
||||
.workspaceHead__subtitle {
|
||||
margin-top: 6px;
|
||||
color: rgba(255, 255, 255, 0.58);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.workspaceBody {
|
||||
min-height: calc(100vh - 110px);
|
||||
padding: 20px;
|
||||
min-height: calc(100vh - 56px);
|
||||
padding: 18px;
|
||||
border-radius: 26px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: linear-gradient(180deg, #2d2d2d 0%, #2a2a2a 100%);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
margin: 18px;
|
||||
}
|
||||
|
||||
.workspaceBody--localRail {
|
||||
min-height: calc(100vh - 92px);
|
||||
min-height: calc(100vh - 56px);
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
margin: 18px;
|
||||
}
|
||||
|
||||
.rightRail {
|
||||
display: grid;
|
||||
align-content: start;
|
||||
gap: 18px;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.rightRailAction {
|
||||
@@ -719,7 +721,7 @@ async function logout() {
|
||||
}
|
||||
|
||||
.localRightRailRoot {
|
||||
min-height: calc(100vh - 40px);
|
||||
min-height: calc(100vh - 84px);
|
||||
display: grid;
|
||||
align-content: start;
|
||||
gap: 14px;
|
||||
@@ -815,15 +817,13 @@ async function logout() {
|
||||
.workspaceBody {
|
||||
padding: 14px;
|
||||
border-radius: 20px;
|
||||
margin: 14px;
|
||||
}
|
||||
|
||||
.workspaceBody--localRail {
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.workspaceHead__title {
|
||||
font-size: 26px;
|
||||
margin: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1040,14 +1040,8 @@ onUnmounted(() => {
|
||||
margin-top: 0;
|
||||
}
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 320px;
|
||||
gap: 18px;
|
||||
align-items: start;
|
||||
transition: grid-template-columns 220ms ease;
|
||||
}
|
||||
.layout--railClosed {
|
||||
grid-template-columns: minmax(0, 1fr) 0;
|
||||
display: block;
|
||||
min-width: 0;
|
||||
}
|
||||
.error {
|
||||
margin: 10px 0 14px;
|
||||
@@ -1531,7 +1525,7 @@ onUnmounted(() => {
|
||||
}
|
||||
.poolItem {
|
||||
display: grid;
|
||||
grid-template-columns: var(--thumb-size, 80px) 1fr;
|
||||
grid-template-columns: var(--thumb-size, 80px) minmax(0, 1fr);
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
@@ -1540,8 +1534,12 @@ onUnmounted(() => {
|
||||
background: rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
.poolItem__label {
|
||||
min-width: 0;
|
||||
font-weight: 800;
|
||||
opacity: 0.9;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
@@ -1560,9 +1558,6 @@ onUnmounted(() => {
|
||||
.heroCard {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.editorCanvas {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user