Files
awoooi/apps/web/src/components/layout/header.tsx
Your Name a5324ef722
All checks were successful
CD Pipeline / tests (push) Successful in 1m22s
Code Review / ai-code-review (push) Successful in 13s
CD Pipeline / build-and-deploy (push) Successful in 3m40s
CD Pipeline / post-deploy-checks (push) Successful in 1m35s
feat(web): replace header logo with AwoooI pills mark
2026-06-05 00:30:30 +08:00

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>
)
}