feat(page): 대시보드 디자인을 유튜브 뮤직(웹) 다크 테마로 전면 교체
- 상단 바: ☰ + 로고 + 반투명 검색창 + 프로필 메뉴(좁은 화면은 돋보기 → 전체 폭 검색) - 왼쪽 메뉴: 홈/서버 목록 + 봇이 있는 내 서버 목록, 넓은 화면 펼침(240)/접힘(72), 좁은 화면 서랍 - 홈: 서버를 원형 카드로, 서버 페이지: 앨범 페이지 형태(왼쪽 아이콘·정보, 오른쪽 재생 목록) - 검색: 필터 칩(전체/노래/동영상/Spotify) + 상위 검색결과 카드 + 구역별 목록(모두 표시) - 하단 재생바: 위쪽 빨간 진행선, 조작·시간 / 곡 정보 / hover 볼륨·재생 화면(⌃) - 재생 화면: 큰 앨범아트 + 다음 트랙 탭, 좁은 화면은 전체 화면 플레이어 - 재생/대기열/서버 로직을 hooks(usePlayer/useQueue/useServers)로 분리해 구독을 한 번만 연다 - 스낵바 알림, 파비콘, README 화면·버튼 문서 갱신 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
100
page/src/components/ui.tsx
Normal file
100
page/src/components/ui.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
"use client";
|
||||
import { Music, Server } from "lucide-react";
|
||||
import type { DiscordServer } from "@/types/music";
|
||||
import { serverIconUrl } from "@/hooks/useServers";
|
||||
|
||||
/** 유튜브 뮤직 로고 형태(빨간 원 + 흰 링 + 재생 삼각형) */
|
||||
export function Logo({ className = "" }: { className?: string }) {
|
||||
return (
|
||||
<span className={`flex items-center gap-1 ${className}`}>
|
||||
<svg viewBox="0 0 24 24" className="h-6 w-6 flex-shrink-0" aria-hidden>
|
||||
<circle cx="12" cy="12" r="12" fill="#ff0000" />
|
||||
<circle cx="12" cy="12" r="6.6" fill="none" stroke="#fff" strokeWidth="1.1" />
|
||||
<path d="M10 9.2v5.6l4.9-2.8z" fill="#fff" />
|
||||
</svg>
|
||||
<span className="text-[20px] font-bold tracking-[-0.04em] text-white">MusicBot</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/** 유튜브 기본 썸네일(hq/mq/sd/0.jpg)은 4:3 안에 16:9 영상이 위아래 검은 띠와 함께 들어 있다. */
|
||||
const LETTERBOXED_YT = /i\.ytimg\.com\/vi\/[^/]+\/(hqdefault|mqdefault|sddefault|0)\.jpg/;
|
||||
|
||||
/** 앨범아트/썸네일(이미지가 없으면 음표 아이콘) */
|
||||
export function Thumb({
|
||||
src,
|
||||
className = "",
|
||||
rounded = "rounded-sm",
|
||||
iconSize = 18,
|
||||
wide = false,
|
||||
}: {
|
||||
src?: string | null;
|
||||
className?: string;
|
||||
rounded?: string;
|
||||
iconSize?: number;
|
||||
/** 16:9 상자(동영상 행)면 true. 정사각형 상자에서만 검은 띠를 잘라낸다. */
|
||||
wide?: boolean;
|
||||
}) {
|
||||
// 정사각형 상자에 4:3 유튜브 썸네일을 넣으면 검은 띠가 보이므로 4/3 배 확대해 띠를 잘라낸다.
|
||||
const cropBars = !wide && !!src && LETTERBOXED_YT.test(src);
|
||||
return (
|
||||
<span className={`relative flex flex-shrink-0 items-center justify-center overflow-hidden bg-yt-raised ${rounded} ${className}`}>
|
||||
{src ? (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img src={src} alt="" className={`h-full w-full object-cover ${cropBars ? "scale-[1.34]" : ""}`} />
|
||||
) : (
|
||||
<Music size={iconSize} className="text-white/40" aria-hidden />
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/** 디스코드 서버 아이콘(원형, 없으면 서버 아이콘) */
|
||||
export function ServerAvatar({
|
||||
server,
|
||||
className = "",
|
||||
iconSize = 20,
|
||||
size = 128,
|
||||
rounded = "rounded-full",
|
||||
}: {
|
||||
server: DiscordServer;
|
||||
className?: string;
|
||||
iconSize?: number;
|
||||
size?: number;
|
||||
rounded?: string;
|
||||
}) {
|
||||
const url = serverIconUrl(server, size);
|
||||
return (
|
||||
<span className={`flex flex-shrink-0 items-center justify-center overflow-hidden bg-yt-raised ${rounded} ${className}`}>
|
||||
{url ? (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img src={url} alt="" className="h-full w-full object-cover" />
|
||||
) : (
|
||||
<Server size={iconSize} className="text-white/50" aria-hidden />
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/** 유튜브 뮤직 스타일 원형 로딩 표시 */
|
||||
export function Spinner({ size = 32, label = "불러오는 중" }: { size?: number; label?: string }) {
|
||||
return (
|
||||
<span
|
||||
role="status"
|
||||
aria-label={label}
|
||||
className="inline-block animate-spin rounded-full border-[3px] border-white/15 border-t-white"
|
||||
style={{ width: size, height: size }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/** 재생 중 이퀄라이저 표시 */
|
||||
export function Equalizer({ paused = false, className = "" }: { paused?: boolean; className?: string }) {
|
||||
return (
|
||||
<span className={`yt-eq flex items-end gap-[2px] ${paused ? "paused" : ""} ${className}`} aria-hidden>
|
||||
<span />
|
||||
<span />
|
||||
<span />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user