From 3406ba64c4d2634c1d3f9ecfb02d52567c7c79d8 Mon Sep 17 00:00:00 2001 From: tkrmagid Date: Tue, 29 Sep 2026 14:11:06 +0900 Subject: [PATCH] =?UTF-8?q?feat(page):=20=EA=B3=A1=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=EB=A5=BC=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=96=89=EC=9C=BC=EB=A1=9C,=20=EB=8C=80=EA=B8=B0=EC=97=B4=20?= =?UTF-8?q?=EC=A0=91=EA=B8=B0/=ED=8E=B4=EA=B8=B0=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=E2=80=94=20=EC=9D=B5=EC=88=99=ED=95=9C=20=ED=98=95=ED=83=9C?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=95=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "많이 쓰는 음악 사이트처럼" 이라는 요구에 맞춰, 형태가 관행과 달랐던 두 곳을 바꿨다. 1) 검색 결과: 그리드 카드 -> 리스트 행 그리드(커버 중심)는 앨범·재생목록을 훑을 때 쓰는 형태이고, 개별 곡 검색 결과는 스포티파이·유튜브뮤직 모두 행(번호/썸네일/제목·아티스트/재생시간) 형식이다. 행 전체가 버튼이라 터치로도 바로 누를 수 있고, 마우스 환경에서는 번호 자리에 재생 아이콘이 겹치는 스포티파이 곡 목록 방식을 따랐다. 좁은 화면은 hover 가 없어 썸네일에 재생 아이콘을 항상 얹는다. 2) 대기열 접기/펴기 재생바 우측(스포티파이와 같은 자리)에 대기열 버튼을 두어 넓은 화면에서도 접을 수 있게 했다. 화면 크기별 기본값이 달라(좁은 화면=접힘, 넓은 화면=펼침) 상태를 둘로 나눴다. 창 크기를 읽어 한 상태로 합치면 SSR/하이드레이션이 어긋나고 setState-in-effect 경고도 난다. 검증: tsc 통과, lint 에러 0. 데스크톱(1440x900)·모바일(390x844)에서 검색 결과 행 렌더링과 대기열 접기/펴기 동작을 실제 화면으로 확인. README 5-3 비교표에 두 항목 추가. --- page/README.md | 2 + page/src/app/page.tsx | 13 +++-- page/src/components/player/MainContent.tsx | 57 ++++++++++++++-------- page/src/components/player/PlayerBar.tsx | 20 ++++++-- 4 files changed, 65 insertions(+), 27 deletions(-) diff --git a/page/README.md b/page/README.md index eff285b..e3e8ef1 100644 --- a/page/README.md +++ b/page/README.md @@ -124,6 +124,8 @@ site: "bot-site" 구독 → serverId 일치하는 것만 EventSource(SSE)로 브 | 관행 | 예전 | 현재 | |------|------|------| +| 곡 검색 결과 표시 | 그리드 카드 | 리스트 행(번호·썸네일·제목/아티스트·재생시간). 그리드는 앨범/재생목록처럼 커버가 주인공일 때 쓰는 형태이고, 개별 곡은 두 서비스 모두 행 형식이다 | +| 대기열 접기/펴기 | 넓은 화면에서 항상 고정 | 재생바 우측 대기열 버튼으로 접고 펼 수 있음(스포티파이와 같은 위치) | | 좁은 화면 대응 | 3열 고정이라 모바일에서 본문이 눌려 사용 불가 | 단일 컬럼 + 대기열 슬라이드 패널 | | 대기열 상단에 "지금 재생 중" | 없음(다음 곡만) | 현재 곡을 강조해 표시 | | 대기열 곡 클릭 → 그 곡부터 재생 | 없음 | 지원(앞 곡들은 빠짐 — 스포티파이 대기열과 동일) | diff --git a/page/src/app/page.tsx b/page/src/app/page.tsx index e548e0e..637adb3 100644 --- a/page/src/app/page.tsx +++ b/page/src/app/page.tsx @@ -18,8 +18,12 @@ export default function MusicPlayerLayout() { const [viewMode, setViewMode] = useState("SERVER_LIST"); const [selectedServer, setSelectedServer] = useState(null); const [searchQuery, setSearchQuery] = useState(""); - // 모바일에서 대기열은 상시 컬럼이 아니라 오른쪽에서 밀려나오는 패널로 띄운다. + // 대기열 표시 상태는 화면 크기별로 기본값이 달라서 두 개로 나눠 둔다. + // (창 크기를 읽어 한 상태로 합치면 SSR·하이드레이션이 어긋난다) + // · 좁은 화면: 기본 접힘 → 버튼으로 슬라이드 패널을 연다 + // · 넓은 화면: 기본 펼침 → 재생바의 대기열 버튼으로 접을 수 있다 const [isQueueOpen, setIsQueueOpen] = useState(false); + const [isQueueDocked, setIsQueueDocked] = useState(true); // 재생바가 이미 폴링하는 현재 곡을 대기열 패널과 공유한다(같은 API 를 두 번 부르지 않기 위함). const [nowPlaying, setNowPlaying] = useState(null); const handleNowPlayingChange = useCallback((t: Track | null) => setNowPlaying(t), []); @@ -95,8 +99,9 @@ export default function MusicPlayerLayout() {
setIsQueueOpen((v) => !v)} isQueueOpen={isQueueOpen} + onToggleDock={() => setIsQueueDocked((v) => !v)} + isQueueDocked={isQueueDocked} onNowPlayingChange={handleNowPlayingChange} /> )} diff --git a/page/src/components/player/MainContent.tsx b/page/src/components/player/MainContent.tsx index 3b056a5..43db7e7 100644 --- a/page/src/components/player/MainContent.tsx +++ b/page/src/components/player/MainContent.tsx @@ -197,34 +197,49 @@ export default function MainContent({ const hasAnyResults = searchResults.spotify.length > 0 || searchResults.youtubeMusic.length > 0 || searchResults.youtubeVideo.length > 0; - // 카드 전체가 눌리는 버튼이다. 예전에는 hover 시에만 나타나는 작은 아이콘만 눌러야 해서 - // 터치 기기(hover 없음)에서는 재생 방법을 찾기 어려웠다. - const renderTrackCard = (track: SearchTrack) => ( + /** + * 곡 하나는 '리스트 행'으로 보여준다. + * 그리드 카드는 앨범/재생목록처럼 커버가 주인공일 때 쓰는 형태고, + * 개별 곡 검색 결과는 스포티파이·유튜브뮤직 모두 행(제목·아티스트·재생시간) 형식이라 + * 사용자에게 익숙한 쪽으로 맞춘다. 행 전체가 버튼이라 터치로도 바로 누를 수 있다. + */ + const renderTrackRow = (track: SearchTrack, index: number) => ( ); @@ -360,8 +375,8 @@ export default function MainContent({

Spotify 결과

-
- {searchResults.spotify.map(renderTrackCard)} +
+ {searchResults.spotify.map(renderTrackRow)}
)} @@ -372,8 +387,8 @@ export default function MainContent({

YouTube Music

-
- {searchResults.youtubeMusic.map(renderTrackCard)} +
+ {searchResults.youtubeMusic.map(renderTrackRow)}
)} @@ -384,8 +399,8 @@ export default function MainContent({

YouTube 영상

-
- {searchResults.youtubeVideo.map(renderTrackCard)} +
+ {searchResults.youtubeVideo.map(renderTrackRow)}
)} diff --git a/page/src/components/player/PlayerBar.tsx b/page/src/components/player/PlayerBar.tsx index 2f3014a..1061527 100644 --- a/page/src/components/player/PlayerBar.tsx +++ b/page/src/components/player/PlayerBar.tsx @@ -8,11 +8,14 @@ interface PlayerBarProps { selectedServer: DiscordServer | null; onToggleQueue?: () => void; isQueueOpen?: boolean; + /** 넓은 화면에서 우측 대기열 컬럼을 접었다 펴는 토글 */ + onToggleDock?: () => void; + isQueueDocked?: boolean; /** 대기열 패널에도 "지금 재생 중"을 보여주기 위해 현재 곡을 위로 올려준다(중복 폴링 방지). */ onNowPlayingChange?: (track: Track | null) => void; } -export default function PlayerBar({ selectedServer, onToggleQueue, isQueueOpen, onNowPlayingChange }: PlayerBarProps) { +export default function PlayerBar({ selectedServer, onToggleQueue, isQueueOpen, onToggleDock, isQueueDocked, onNowPlayingChange }: PlayerBarProps) { const { data: session } = useSession(); // 재생 상태 관리 @@ -423,7 +426,7 @@ export default function PlayerBar({ selectedServer, onToggleQueue, isQueueOpen, - {/* 모바일 전용: 대기열 토글 */} + {/* 좁은 화면에서 대기열 열기(넓은 화면에는 우측 하단에 같은 버튼을 따로 둔다) */} {onToggleQueue && (
- {/* 3. 볼륨 (좁은 화면에서는 숨긴다 — 기기 자체 볼륨으로 조절 가능) */} + {/* 3. 우측 보조 조작: 대기열 + 볼륨 (스포티파이도 이 자리에 대기열 버튼을 둔다) */}
+ {onToggleDock && ( + + )}