5d00e71275
頂層 D22 決策(leo 2026-07-03 拍板):推什麼由開發環境歸屬決定, Gitea private=除機敏值/build 產物/.github 外全 push。 解 T1.5 卡點:雲端工人 clone 拿得到 credential-store-migration.md,可就地改寫 SDD。 機敏掃描兩輪通過(新增 189 檔約 2.1MB,node_modules/dist/wasm 照舊排除)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
100 lines
3.5 KiB
TypeScript
100 lines
3.5 KiB
TypeScript
'use client';
|
||
|
||
// Mira 側邊欄(桌機左固定欄 / 手機底部 tab bar)
|
||
// SDD: polaris/mira/.agents/specs/mira-app/design.md §5.2 v2.1 + §3.7.4
|
||
// 對應 task: 10B0.1 / 10B0.2 / 10B0.4
|
||
// 現階段入口手寫;detector framework(#8)上線後改讀 detector view 規格動態生成
|
||
|
||
import { useState } from 'react';
|
||
import Link from 'next/link';
|
||
import { usePathname, useRouter } from 'next/navigation';
|
||
|
||
type NavItem = {
|
||
href: string;
|
||
icon: string;
|
||
label: string;
|
||
status: 'live' | 'planned';
|
||
};
|
||
|
||
// 側邊欄入口 = 各 detector 的 view(§3.7.4)。河道是固定輸入入口,其餘是 detector 產物頁。
|
||
const NAV_ITEMS: NavItem[] = [
|
||
{ href: '/mira/feed', icon: '🌊', label: '河道', status: 'live' },
|
||
{ href: '/mira/chat', icon: '💬', label: '對話', status: 'live' },
|
||
{ href: '/mira/wiki', icon: '📚', label: 'Wiki', status: 'live' },
|
||
{ href: '/mira/projects', icon: '📋', label: '專案', status: 'live' },
|
||
{ href: '/mira/dissent', icon: '⚔️', label: '異見牆', status: 'planned' },
|
||
];
|
||
|
||
function isActive(pathname: string | null, href: string): boolean {
|
||
if (!pathname) return false;
|
||
if (href === '/mira/feed') return pathname === '/mira/feed' || pathname === '/mira';
|
||
return pathname === href || pathname.startsWith(href + '/');
|
||
}
|
||
|
||
export default function MiraSidebar() {
|
||
const pathname = usePathname();
|
||
const router = useRouter();
|
||
const [q, setQ] = useState('');
|
||
|
||
const submitSearch = (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
const query = q.trim();
|
||
if (!query) return;
|
||
router.push(`/mira/search?q=${encodeURIComponent(query)}`);
|
||
};
|
||
|
||
return (
|
||
<nav className="mira-sidebar" aria-label="Mira 導覽">
|
||
<Link href="/mira/feed" className="mira-sidebar-logo">
|
||
<span className="mira-sidebar-logo-icon">🦔</span>
|
||
<span className="mira-sidebar-logo-text">Mira</span>
|
||
</Link>
|
||
{/* 桌機:側欄輸入框 */}
|
||
<form className="mira-sidebar-search" onSubmit={submitSearch}>
|
||
<input
|
||
value={q}
|
||
onChange={e => setQ(e.target.value)}
|
||
placeholder="🔍 搜尋 wiki…"
|
||
aria-label="搜尋"
|
||
/>
|
||
</form>
|
||
{/* 手機:底部 bar 已滿,搜尋收成一個放大鏡 icon → 去搜尋頁輸入 */}
|
||
<Link
|
||
href="/mira/search"
|
||
className={`mira-sidebar-search-icon${isActive(pathname, '/mira/search') ? ' is-active' : ''}`}
|
||
aria-label="搜尋"
|
||
title="搜尋"
|
||
>
|
||
🔍
|
||
</Link>
|
||
<ul className="mira-sidebar-list">
|
||
{NAV_ITEMS.map(item => {
|
||
const active = isActive(pathname, item.href);
|
||
const planned = item.status === 'planned';
|
||
const className = `mira-sidebar-item${active ? ' is-active' : ''}${planned ? ' is-planned' : ''}`;
|
||
const inner = (
|
||
<>
|
||
<span className="mira-sidebar-icon">{item.icon}</span>
|
||
<span className="mira-sidebar-label">{item.label}</span>
|
||
{planned && <span className="mira-sidebar-badge">即將</span>}
|
||
</>
|
||
);
|
||
return (
|
||
<li key={item.href}>
|
||
{planned ? (
|
||
<span className={className} aria-disabled="true" title="即將開放">
|
||
{inner}
|
||
</span>
|
||
) : (
|
||
<Link href={item.href} className={className} aria-current={active ? 'page' : undefined}>
|
||
{inner}
|
||
</Link>
|
||
)}
|
||
</li>
|
||
);
|
||
})}
|
||
</ul>
|
||
</nav>
|
||
);
|
||
}
|