218 lines
7.4 KiB
TypeScript
218 lines
7.4 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* Header - figma-v2 品牌導航列
|
|
* ==============================
|
|
* @updated 2026-04-03 Claude Code — 完整對齊 figma-v2 設計
|
|
* - brand-area: AwoooI Pills V2 logo + display name
|
|
* - page-title: Syne 26px 800
|
|
* - lang-btn: pill 樣式 (border-radius 20px)
|
|
* - avatar: 34px 圓形 #d97757
|
|
*
|
|
* Phase 19: 使用 Z_INDEX.HEADER (30)
|
|
* @see lib/constants/z-index.ts
|
|
* @updated 2026-06-05 Codex — 左上品牌改用 AwoooI Pills V2 logo-gallery 推薦稿
|
|
*/
|
|
|
|
import { useCallback } from 'react'
|
|
import { useTranslations } from 'next-intl'
|
|
import { usePathname } from 'next/navigation'
|
|
import { Z_INDEX } from '@/lib/constants/z-index'
|
|
|
|
// =============================================================================
|
|
// Types
|
|
// =============================================================================
|
|
|
|
interface HeaderProps {
|
|
locale: string
|
|
sidebarCollapsed?: boolean
|
|
compact?: boolean
|
|
className?: string
|
|
}
|
|
|
|
// =============================================================================
|
|
// Component
|
|
// =============================================================================
|
|
|
|
export function Header({
|
|
locale,
|
|
sidebarCollapsed = false,
|
|
compact = false,
|
|
className,
|
|
}: HeaderProps) {
|
|
const t = useTranslations('locale')
|
|
const tBrand = useTranslations('brand')
|
|
const tDashboard = useTranslations('dashboard')
|
|
const pathname = usePathname()
|
|
|
|
const switchLocale = useCallback((newLocale: string) => {
|
|
const newPath = pathname.replace(`/${locale}`, `/${newLocale}`)
|
|
window.location.href = newPath
|
|
}, [locale, pathname])
|
|
|
|
const brandWidth = compact ? 64 : sidebarCollapsed ? 64 : 224
|
|
|
|
return (
|
|
<header
|
|
className={className}
|
|
style={{
|
|
position: 'fixed',
|
|
top: 0,
|
|
left: 0,
|
|
width: '100%',
|
|
height: 68,
|
|
background: '#faf9f3',
|
|
borderBottom: '0.5px solid #e0ddd4',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
zIndex: Z_INDEX.HEADER,
|
|
}}
|
|
>
|
|
{/* Brand Area — 224px 固定寬,與 sidebar 對齊 */}
|
|
<div style={{
|
|
width: brandWidth,
|
|
height: 68,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
padding: '0 14px',
|
|
gap: 10,
|
|
flexShrink: 0,
|
|
borderRight: '0.5px solid #e0ddd4',
|
|
background: '#faf9f3',
|
|
overflow: 'hidden',
|
|
transition: 'width 0.3s ease',
|
|
}}>
|
|
{/* AwoooI Pills V2 logo — source: logo-gallery.html */}
|
|
<div style={{ width: 36, height: 36, flexShrink: 0 }}>
|
|
<svg width="36" height="36" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
<g style={{ mixBlendMode: 'multiply' }}>
|
|
<rect x="14" y="6" width="8" height="36" rx="4" transform="rotate(-25 18 24)" fill="#3B82F6" opacity="0.8" />
|
|
<rect x="26" y="6" width="8" height="36" rx="4" transform="rotate(25 30 24)" fill="#8B5CF6" opacity="0.8" />
|
|
<rect x="14" y="26" width="20" height="8" rx="4" fill="#06B6D4" opacity="0.8" />
|
|
</g>
|
|
</svg>
|
|
</div>
|
|
|
|
{/* Brand text: AwoooI logo-gallery display name */}
|
|
{!compact && !sidebarCollapsed && (
|
|
<span style={{
|
|
fontFamily: 'var(--font-heading), Syne, var(--font-body), sans-serif',
|
|
fontSize: 24,
|
|
fontWeight: 800,
|
|
color: '#334155',
|
|
letterSpacing: 0,
|
|
lineHeight: 1,
|
|
whiteSpace: 'nowrap',
|
|
}}>
|
|
{tBrand('displayName')}
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
{/* Header Right — page title + lang switcher + avatar */}
|
|
<div style={{
|
|
flex: 1,
|
|
minWidth: 0,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
padding: compact ? '0 10px' : '0 24px',
|
|
gap: compact ? 8 : 16,
|
|
background: '#faf9f3',
|
|
}}>
|
|
{/* Page title */}
|
|
<span style={{
|
|
fontFamily: 'var(--font-heading), Syne, sans-serif',
|
|
fontSize: compact ? 20 : 26,
|
|
fontWeight: 800,
|
|
color: '#141413',
|
|
flex: 1,
|
|
minWidth: 0,
|
|
overflow: 'hidden',
|
|
textOverflow: 'ellipsis',
|
|
whiteSpace: 'nowrap',
|
|
letterSpacing: 0,
|
|
}}>
|
|
{tDashboard('title')}
|
|
</span>
|
|
|
|
{/* ⌘K Command Palette 入口提示 */}
|
|
<button
|
|
onClick={() => {
|
|
const e = new KeyboardEvent('keydown', { key: 'k', metaKey: true, bubbles: true })
|
|
window.dispatchEvent(e)
|
|
}}
|
|
style={{
|
|
display: compact ? 'none' : 'flex', alignItems: 'center', gap: 6,
|
|
padding: '5px 12px',
|
|
background: '#fff', border: '0.5px solid #e0ddd4', borderRadius: 8,
|
|
cursor: 'pointer', fontSize: 11, color: '#87867f',
|
|
transition: 'all 0.15s',
|
|
}}
|
|
onMouseEnter={e => { (e.currentTarget as HTMLButtonElement).style.borderColor = '#4A90D9'; (e.currentTarget as HTMLButtonElement).style.color = '#4A90D9' }}
|
|
onMouseLeave={e => { (e.currentTarget as HTMLButtonElement).style.borderColor = '#e0ddd4'; (e.currentTarget as HTMLButtonElement).style.color = '#87867f' }}
|
|
>
|
|
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
|
|
<span style={{ fontFamily: 'var(--font-body), monospace' }}>{tDashboard('searchPlaceholderShort')}</span>
|
|
<kbd style={{ fontSize: 9, background: '#f0ede5', borderRadius: 3, padding: '1px 5px', fontFamily: 'monospace', color: '#87867f' }}>⌘K</kbd>
|
|
</button>
|
|
|
|
{/* Language switcher — pill style */}
|
|
<button
|
|
onClick={() => switchLocale('zh-TW')}
|
|
style={{
|
|
display: compact ? 'none' : 'block',
|
|
padding: '5px 12px',
|
|
fontFamily: 'var(--font-body), "DM Mono", monospace',
|
|
fontSize: 11,
|
|
border: '0.5px solid',
|
|
borderRadius: 20,
|
|
cursor: 'pointer',
|
|
transition: 'all 0.15s',
|
|
...(locale === 'zh-TW'
|
|
? { background: '#141413', color: '#fff', borderColor: '#141413' }
|
|
: { background: '#fff', color: '#87867f', borderColor: '#e0ddd4' }),
|
|
}}
|
|
>
|
|
{t('zhTW')}
|
|
</button>
|
|
<button
|
|
onClick={() => switchLocale('en')}
|
|
style={{
|
|
display: compact ? 'none' : 'block',
|
|
padding: '5px 12px',
|
|
fontFamily: 'var(--font-body), "DM Mono", monospace',
|
|
fontSize: 11,
|
|
border: '0.5px solid',
|
|
borderRadius: 20,
|
|
cursor: 'pointer',
|
|
transition: 'all 0.15s',
|
|
...(locale === 'en'
|
|
? { background: '#141413', color: '#fff', borderColor: '#141413' }
|
|
: { background: '#fff', color: '#87867f', borderColor: '#e0ddd4' }),
|
|
}}
|
|
>
|
|
{t('en')}
|
|
</button>
|
|
|
|
{/* Avatar — 34px 圓形 #d97757 */}
|
|
<div style={{
|
|
display: compact ? 'none' : 'flex',
|
|
width: 34,
|
|
height: 34,
|
|
borderRadius: '50%',
|
|
background: '#d97757',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
fontSize: 12,
|
|
color: '#fff',
|
|
fontWeight: 700,
|
|
flexShrink: 0,
|
|
fontFamily: 'var(--font-body), monospace',
|
|
}}>
|
|
OG
|
|
</div>
|
|
</div>
|
|
</header>
|
|
)
|
|
}
|