채팅 UI 개편: 한 줄 티커 + 우측 사이드패널 채팅창
- 티커: 반투명 검은 가로 박스, 새 채팅을 한 줄씩 7초간 순차 표시, 길면 말줄임(…) 처리. 접속 이후 도착한 메시지만 흘림 - 💬 버튼 → 기존 메뉴와 같은 우측 사이드패널로 전체 채팅창 (히스토리 스크롤 + 말풍선 + 입력창, 내 메시지는 오른쪽 초록) - SSE 연결은 chatClient 싱글톤(useSyncExternalStore)으로 티커·패널 공유 - 1:1 친구 채팅 로드맵을 docs/GAME_DESIGN.md 12절에 기록 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
이 커밋은 다음에 포함됨:
@@ -6,6 +6,7 @@ import VillageView, { type PlacingState } from "@/components/game/VillageView";
|
||||
import DbStatus from "@/components/DbStatus";
|
||||
import NicknameModal from "@/components/game/NicknameModal";
|
||||
import ChatTicker from "@/components/game/ChatTicker";
|
||||
import ChatPanel from "@/components/game/ChatPanel";
|
||||
import {
|
||||
engine,
|
||||
effectiveRate,
|
||||
@@ -112,6 +113,7 @@ type Panel =
|
||||
| { type: "shop" }
|
||||
| { type: "cats" }
|
||||
| { type: "deco"; id: string }
|
||||
| { type: "chat" }
|
||||
| null;
|
||||
|
||||
export default function Game() {
|
||||
@@ -233,9 +235,13 @@ export default function Game() {
|
||||
}
|
||||
/>
|
||||
|
||||
{/* ── 실시간 마을 채팅 (닉네임 확정 후) ── */}
|
||||
{/* ── 실시간 마을 채팅 티커 (닉네임 확정 후) ── */}
|
||||
{state.nickname && state.syncToken && (
|
||||
<ChatTicker nickname={state.nickname} token={state.syncToken} />
|
||||
<ChatTicker
|
||||
onOpen={() =>
|
||||
setPanel(panel?.type === "chat" ? null : { type: "chat" })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* ── 닉네임 입력 (인사 후, 미입력 시 게임 차단) ── */}
|
||||
@@ -430,6 +436,11 @@ export default function Game() {
|
||||
</div>
|
||||
|
||||
{/* ── 우측 패널 ── */}
|
||||
{panel?.type === "chat" && (
|
||||
<SidePanel title="💬 마을 채팅" onClose={() => setPanel(null)}>
|
||||
<ChatPanel nickname={state.nickname ?? ""} token={state.syncToken ?? ""} />
|
||||
</SidePanel>
|
||||
)}
|
||||
{panel?.type === "hall" && (
|
||||
<SidePanel title={`🏛️ 마을 회관 Lv${state.hall.level}`} onClose={() => setPanel(null)}>
|
||||
<HallPanel
|
||||
|
||||
새 이슈에서 참조
사용자 차단