헤더: 코드로 참여 입력칸 빼고 테마 버튼(🌙/☀️) 추가 — 누르면 밝게↔어둡게
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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)!;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user