헤더: 코드로 참여 입력칸 빼고 테마 버튼(🌙/☀️) 추가 — 누르면 밝게↔어둡게

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
EJClaw
2026-10-07 14:40:26 +09:00
parent 843b18aea1
commit 53fbbc1c9d
3 changed files with 40 additions and 46 deletions

View File

@@ -1,28 +1,12 @@
/** PC top menu with an always-available room-code box (docs/07-ui-ux.md §3.2). */ /** PC top menu (docs/07-ui-ux.md §3.2): 로고·메뉴, 오른쪽에 테마 버튼과 로그인/계정 메뉴. */
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { Link, NavLink, useNavigate } from 'react-router'; import { Link, NavLink, useNavigate } from 'react-router';
import { normalizeRoomCode } from '@bg/shared/lite';
import { api } from '../net/api'; import { api } from '../net/api';
import { useSession } from '../store/session'; import { useSession } from '../store/session';
import { toast } from '../store/toast';
import { Avatar, ConfirmSheet } from './ui'; import { Avatar, ConfirmSheet } from './ui';
export function SiteHeader() { export function SiteHeader() {
const { me, siteName, role } = useSession(); const { me, siteName, role } = useSession();
const [code, setCode] = useState('');
const nav = useNavigate();
const join = async (e: React.FormEvent) => {
e.preventDefault();
const c = normalizeRoomCode(code);
if (!c) return toast('방 코드 6자리를 넣어 주세요.', 'error');
try {
await api.room(c);
setCode('');
nav(`/r/${c}`);
} catch (ex) {
toast((ex as Error).message, 'error');
}
};
return ( return (
<header className="site-header"> <header className="site-header">
<div className="site-header-inner"> <div className="site-header-inner">
@@ -37,10 +21,7 @@ export function SiteHeader() {
<NavLink to="/games">게임 목록</NavLink> <NavLink to="/games">게임 목록</NavLink>
{role !== 'user' && <NavLink to="/admin">관리자</NavLink>} {role !== 'user' && <NavLink to="/admin">관리자</NavLink>}
</nav> </nav>
<form className="header-join" onSubmit={join} aria-label="코드로 참여"> <ThemeToggle />
<input value={code} onChange={(e) => setCode(e.target.value)} placeholder="방 코드 6자리" inputMode="numeric" maxLength={40} aria-label="방 코드" />
<button className="btn btn-primary btn-sm">참여</button>
</form>
{me ? ( {me ? (
<AccountMenu /> <AccountMenu />
) : ( ) : (
@@ -53,6 +34,28 @@ export function SiteHeader() {
); );
} }
const darkQuery = () => matchMedia('(prefers-color-scheme: dark)');
/** 누를 때마다 밝게 ↔ 어둡게. '자동'이면 지금 보이는 색의 반대로 바꿔요. 설정 페이지의 '화면 색'과 같은 값이에요. */
function ThemeToggle() {
const theme = useSession((s) => s.settings.theme);
const updateSettings = useSession((s) => s.updateSettings);
const [systemDark, setSystemDark] = useState(() => darkQuery().matches);
useEffect(() => {
const q = darkQuery();
const on = () => setSystemDark(q.matches);
q.addEventListener('change', on);
return () => q.removeEventListener('change', on);
}, []);
const dark = theme === 'dark' || (theme === 'auto' && systemDark);
const label = dark ? '밝은 화면으로 바꾸기' : '어두운 화면으로 바꾸기';
return (
<button type="button" className="theme-toggle" aria-label={label} title={label} onClick={() => updateSettings({ theme: dark ? 'light' : 'dark' })}>
<span aria-hidden>{dark ? '☀️' : '🌙'}</span>
</button>
);
}
/** 로그인한 뒤: 닉네임(디스코드 연결 시 디스코드 이름) → 누르면 [설정] [로그아웃] 메뉴. */ /** 로그인한 뒤: 닉네임(디스코드 연결 시 디스코드 이름) → 누르면 [설정] [로그아웃] 메뉴. */
function AccountMenu() { function AccountMenu() {
const me = useSession((s) => s.me)!; const me = useSession((s) => s.me)!;

View File

@@ -589,29 +589,23 @@ label.field {
color: var(--primary); color: var(--primary);
background: var(--primary-soft); background: var(--primary-soft);
} }
.header-join { .theme-toggle {
display: flex;
gap: 6px;
margin-left: auto; margin-left: auto;
} width: 42px;
.header-join input {
width: 150px;
height: 42px; height: 42px;
border-radius: 10px; flex: none;
border: 2px solid var(--border); border: 2px solid var(--border);
background: var(--bg); border-radius: 999px;
padding: 0 12px; background: transparent;
font-size: 1.05rem; font-size: 1.2rem;
font-weight: 700; line-height: 1;
letter-spacing: 0.08em; cursor: pointer;
font-variant-numeric: tabular-nums; display: inline-flex;
align-items: center;
justify-content: center;
} }
.header-join input:focus { .theme-toggle:hover {
outline: none; background: var(--surface-2);
border-color: var(--primary);
}
.header-join .btn {
min-height: 42px;
} }
.header-user { .header-user {
display: flex; display: flex;
@@ -693,9 +687,6 @@ button.header-user {
padding: 0 12px; padding: 0 12px;
gap: 8px; gap: 8px;
} }
.header-join {
display: none;
}
.site-logo .full { .site-logo .full {
display: none; display: none;
} }

View File

@@ -53,11 +53,11 @@
### 3.2 공통 상단 메뉴 (PC) ### 3.2 공통 상단 메뉴 (PC)
``` ```
┌──────────────────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────────────────┐
│ ● 같이 놀자 보드게임 홈 게임 목록 [코드 6자리 입력칸][참여] 👤 닉네임 ▾ │ 64px │ ● 같이 놀자 보드게임 홈 게임 목록 [🌙] 👤 닉네임 ▾ │ 64px
└──────────────────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────────────────┘
``` ```
- 어느 화면에서든 코드를 바로 넣어 참여할 수 있게 상단에 코드 입력칸을 둔다. - 오른쪽에 테마 버튼(🌙/☀️)을 둔다. 누를 때마다 밝은 화면 ↔ 어두운 화면으로 바뀌고, `/me`의 '화면 색' 설정과 같은 값을 쓴다('자동'이면 지금 보이는 색의 반대로). 코드로 참여는 홈의 [🔢 코드로 참여](→ `/j`)로 한다.
- 오른쪽 끝: 로그인 전에는 [로그인](→ `/login`: 큰 [디스코드로 로그인], 아래 [게스트로 시작하기] → `/login/guest`에서 계정 이름 정하기). 로그인 뒤에는 닉네임(디스코드로 가입했으면 디스코드 이름)을 보이고, 누르면 [⚙️ 설정](→ `/me`)·[🚪 로그아웃] 메뉴가 열린다. 화면·소리 설정은 `/me`에만 있다. - 오른쪽 끝: 로그인 전에는 [로그인](→ `/login`: 큰 [디스코드로 로그인], 아래 [게스트로 시작하기] → `/login/guest`에서 계정 이름 정하기). 로그인 뒤에는 닉네임(디스코드로 가입했으면 디스코드 이름)을 보이고, 누르면 [⚙️ 설정](→ `/me`)·[🚪 로그아웃] 메뉴가 열린다. 테마 버튼 말고 글자 크기·소리 같은 설정은 `/me`에 있다.
- 내용 폭은 최대 1280px, 가운데 정렬. 게임 화면만 화면 전체 폭을 쓴다. - 내용 폭은 최대 1280px, 가운데 정렬. 게임 화면만 화면 전체 폭을 쓴다.
### 3.3 홈 (PC) ### 3.3 홈 (PC)