From 5f8dc184b4b276d534c630df8e2c328d7467ade3 Mon Sep 17 00:00:00 2001 From: tkrmagid Date: Tue, 29 Sep 2026 13:47:26 +0900 Subject: [PATCH] =?UTF-8?q?feat(page):=20=EB=8C=80=EC=8B=9C=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=20UI/UX=20=EC=A0=84=EB=A9=B4=20=EA=B0=9C=EC=84=A0=20?= =?UTF-8?q?=E2=80=94=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20=EC=A7=80=EC=9B=90,?= =?UTF-8?q?=20=ED=84=B0=EC=B9=98=20=EC=A1=B0=EC=9E=91,=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 실제 화면을 캡처해 확인한 문제: - 모바일(390px)에서 3열 고정 레이아웃(사이드바 240 + 대기열 320)이 본문을 ~50px 로 눌러 "로그인이 필요합니다"가 한 글자씩 세로로 표시되는 등 사실상 사용 불가였다. - 대기열의 삭제/드래그가 hover 에서만 노출돼, hover 가 없는 터치 기기에서는 접근 자체가 불가능. 순서 변경도 HTML5 드래그앤드롭뿐이라 모바일에서 동작하지 않았다. - 조작 요소가 터치 기준(44px)에 한참 못 미쳤다(재생 32px, 삭제 ~24px, 진행바 높이 1px). - 로그아웃 상태에서도 사이드바·대기열·플레이어바가 자리를 차지했고, 본문은 "로그인이 필요합니다" 라고만 적어둔 채 정작 버튼은 우상단에만 있어 동선이 끊겼다. - 아이콘 버튼에 접근성 이름이 없고(title 만), 검색창 안내가 로그인 전에도 "서버를 먼저 선택"이었다. 변경: - 레이아웃: 반응형으로 재구성. 좁은 화면은 단일 컬럼 + 대기열을 슬라이드 패널(FAB/플레이어바에서 토글), 넓은 화면은 기존 3열 유지. 로그인 전·서버 미선택 시에는 재생/대기열 영역을 아예 렌더링하지 않는다. - 대기열: 조작 버튼 상시 노출, 좁은 화면에는 위/아래 이동 버튼 제공(드래그 대체), 넓은 화면은 드래그 유지. 번호·곡수 표시 추가, 실패 시 이전 순서로 롤백. - 플레이어바: 버튼을 44~48px 로 키우고 모바일은 진행바를 상단 전체 폭으로 분리. 볼륨은 좁은 화면에서 숨김. - 검색 결과/서버 카드: 카드 전체를 버튼으로 만들어 hover 없이도 누를 수 있게 하고 재생 아이콘 상시 노출. - 로그인 화면에 실제 로그인 버튼 배치, 서버 미선택/결과 없음 등 빈 상태 문구 정리. - 좌측 사이드바를 안내문 전용 죽은 공간에서 "조작 중인 서버 + 서버 전환 + 사용법"으로 교체. - 아이콘 버튼 aria-label, range input aria-label, 장식 이미지 alt="" 정리. 프로필 이미지 없을 때 이니셜 대체. 검증: tsc 통과, lint 에러 0. 실제 렌더링을 데스크톱(1440x900)·모바일(390x844)에서 캡처해 로그인 전/서버 목록/서버 상세/검색 결과/대기열 패널 화면을 모두 확인했다. --- page/src/app/page.tsx | 101 ++++++- page/src/components/layout/LeftSidebar.tsx | 63 ++++- page/src/components/layout/TopNav.tsx | 160 +++++++---- page/src/components/player/MainContent.tsx | 177 ++++++++---- page/src/components/player/PlayerBar.tsx | 289 +++++++++++--------- page/src/components/player/QueueSidebar.tsx | 206 ++++++++------ 6 files changed, 646 insertions(+), 350 deletions(-) diff --git a/page/src/app/page.tsx b/page/src/app/page.tsx index 201b0e4..5efd49f 100644 --- a/page/src/app/page.tsx +++ b/page/src/app/page.tsx @@ -1,6 +1,8 @@ "use client"; import { useState } from "react"; +import { useSession } from "next-auth/react"; +import { ListMusic, X } from "lucide-react"; import TopNav from "@/components/layout/TopNav"; import LeftSidebar from "@/components/layout/LeftSidebar"; import MainContent from "@/components/player/MainContent"; @@ -12,11 +14,17 @@ import type { DiscordServer } from "@/types/music"; export type ViewMode = "SERVER_LIST" | "SERVER_DETAIL" | "SEARCH_RESULT"; export default function MusicPlayerLayout() { + const { status } = useSession(); const [viewMode, setViewMode] = useState("SERVER_LIST"); const [selectedServer, setSelectedServer] = useState(null); const [searchQuery, setSearchQuery] = useState(""); + // 모바일에서 대기열은 상시 컬럼이 아니라 오른쪽에서 밀려나오는 패널로 띄운다. + const [isQueueOpen, setIsQueueOpen] = useState(false); + + const isAuthed = status === "authenticated"; + // 서버를 고르기 전에는 재생/대기열이 의미가 없어, 화면을 차지하지 않게 숨긴다. + const showPlayback = isAuthed && !!selectedServer; - // 홈 버튼 클릭 시: 서버 목록(또는 상세)으로 복귀 const handleHome = () => { if (selectedServer) { setViewMode("SERVER_DETAIL"); @@ -27,29 +35,38 @@ export default function MusicPlayerLayout() { } }; - // 검색 실행 시 const handleSearch = (query: string) => { setSearchQuery(query); setViewMode("SEARCH_RESULT"); }; - // 서버 선택 시 const handleSelectServer = (server: DiscordServer) => { setSelectedServer(server); setViewMode("SERVER_DETAIL"); }; + const handleBackToServers = () => { + setViewMode("SERVER_LIST"); + setSelectedServer(null); + setSearchQuery(""); + setIsQueueOpen(false); + }; + return ( -
- + -
- - + {/* 좌측 사이드바: 보조 정보라 넓은 화면에서만 노출한다(모바일 공간 절약). */} + {showPlayback && ( + + )} + + - + + {showPlayback && ( + <> + {/* 모바일에서 대기열이 열렸을 때 뒤쪽을 덮는 배경(탭하면 닫힘) */} + {isQueueOpen && ( +
- + {showPlayback && ( + setIsQueueOpen((v) => !v)} + isQueueOpen={isQueueOpen} + /> + )} + + {/* 모바일 전용 대기열 열기 버튼: 플레이어바에도 있지만, 재생 전에도 접근할 수 있게 둔다. */} + {showPlayback && !isQueueOpen && ( + + )} + + {/* 모바일 패널 닫기 버튼(패널 위에 떠 있음) */} + {showPlayback && isQueueOpen && ( + + )}
); -} \ No newline at end of file +} diff --git a/page/src/components/layout/LeftSidebar.tsx b/page/src/components/layout/LeftSidebar.tsx index 6d2703b..326d0f9 100644 --- a/page/src/components/layout/LeftSidebar.tsx +++ b/page/src/components/layout/LeftSidebar.tsx @@ -1,20 +1,61 @@ "use client"; -import { ListMusic } from "lucide-react"; +import { ChevronLeft, Info, Server } from "lucide-react"; +import type { DiscordServer } from "@/types/music"; -export default function LeftSidebar() { +interface LeftSidebarProps { + selectedServer: DiscordServer | null; + onBackToServers: () => void; +} + +/** + * 좌측 보조 패널. 예전에는 안내 문구만 있는 죽은 공간이었어서, + * "지금 어느 서버를 조작 중인지"와 서버 전환 동선을 넣어 실제로 쓰이게 했다. + * 좁은 화면에서는 숨긴다(같은 정보가 본문/플레이어에 이미 있다). + */ +export default function LeftSidebar({ selectedServer, onBackToServers }: LeftSidebarProps) { return ( -