Files
maestro/design/ui_kits/console_mobile/MobileChrome.jsx
T
wangjia 5a1e185b1a docs(design): 解压 Maestro Design System 到 design/(重构参照材料)
phosphor console v1.1 设计系统(从现网 web/style.css 演化):tokens(colors/effects/
typography/fonts) + components(core/forms/surfaces .jsx) + ui_kits(console + console_mobile)
+ assets/icons(15 双色 SVG) + guidelines + CLAUDE/SKILL/readme。供 B1–B6 重构任务对照实现。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 14:29:07 +08:00

94 lines
4.5 KiB
React

// 移动版 · 头部 + 底部 Tab 栏
const mIcon = { fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round', viewBox: '0 0 24 24' };
function MIconTask({ s = 22 }) {
return <svg {...mIcon} width={s} height={s}><rect x="4" y="4" width="16" height="16" rx="2.5" /><polyline points="8 12 11 15 16 9" /></svg>;
}
function MIconGate({ s = 22 }) {
return <svg {...mIcon} width={s} height={s}><path d="M12 3 L22 21 L2 21 Z" /><line x1="12" y1="10" x2="12" y2="14" /><line x1="12" y1="17.5" x2="12" y2="17.51" /></svg>;
}
function MIconEvent({ s = 22 }) {
return <svg {...mIcon} width={s} height={s}><polyline points="2 12 7 12 10 5 14 19 17 12 22 12" /></svg>;
}
function MIconGit({ s = 22 }) {
return <svg {...mIcon} width={s} height={s}><line x1="6" y1="3" x2="6" y2="15" /><circle cx="18" cy="6" r="3" /><circle cx="6" cy="18" r="3" /><path d="M18 9a9 9 0 0 1-9 9" /></svg>;
}
function MobileMark({ scale = 1.6 }) {
const ref = React.useRef(null);
React.useEffect(() => {
const MAP = ['.....LLLLL.....','...LLLLLLLLL...','..GGGGGGGGGGG..','..GGEEGGGEEGG..','..GGEEGGGEEGG..','..GGGGGGGGGGG..','...GGGGGGGGG...','...G..G.G..G...','...G..G.G..G...','..G...G.G...G..','..D...D.D...D..','.D...D...D...D.'];
const INK = { L: '#79ec94', G: '#5fdd7d', D: '#2e6b3d', E: '#070908' };
const ctx = ref.current.getContext('2d');
MAP.forEach((row, y) => [...row].forEach((ch, x) => {
if (INK[ch]) { ctx.fillStyle = INK[ch]; ctx.fillRect(x, y, 1, 1); }
}));
}, []);
return <canvas ref={ref} width="15" height="12"
style={{ width: 15 * scale, height: 12 * scale, imageRendering: 'pixelated', filter: 'drop-shadow(0 0 5px rgba(95,221,125,.4))' }} />;
}
function MobileHeader({ project, counts }) {
const { CountBadge } = window.MaestroDesignSystem_a6a290;
return (
<header style={{
display: 'flex', alignItems: 'center', gap: 10, padding: '12px 14px',
borderBottom: '1px solid var(--line)', background: 'var(--bg-deep)',
position: 'sticky', top: 0, zIndex: 20,
}}>
<MobileMark />
<div style={{ minWidth: 0 }}>
<div style={{ fontSize: 15, fontWeight: 700, letterSpacing: '.04em', whiteSpace: 'nowrap' }}>{project.name}</div>
<div style={{ fontSize: 10, color: 'var(--faint)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{project.branch} · {project.autonomy}</div>
</div>
<div style={{ marginLeft: 'auto', display: 'flex', gap: 6 }}>
<CountBadge kind="gate" count={counts.gate} />
<CountBadge kind="run" count={counts.run} />
</div>
</header>
);
}
function MobileTabBar({ tab, setTab, gateCount }) {
const tabs = [
{ id: 'tasks', label: '任务', icon: MIconTask },
{ id: 'gates', label: '审批', icon: MIconGate, badge: gateCount },
{ id: 'events', label: '事件', icon: MIconEvent },
{ id: 'projects', label: '项目', icon: MIconGit },
];
return (
<nav style={{
display: 'grid', gridTemplateColumns: 'repeat(4,1fr)',
borderTop: '1px solid var(--line)', background: 'var(--bg-deep)',
position: 'sticky', bottom: 0, zIndex: 20,
}}>
{tabs.map((t) => {
const active = tab === t.id;
const Icon = t.icon;
return (
<button key={t.id} onClick={() => setTab(t.id)} style={{
fontFamily: 'var(--mono)', background: 'transparent', border: 'none', cursor: 'pointer',
display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 3,
padding: '9px 0 10px', minHeight: 56, position: 'relative',
color: active ? 'var(--green)' : 'var(--faint)',
textShadow: active ? '0 0 10px rgba(95,221,125,.45)' : 'none',
}}>
<span style={{ position: 'relative', filter: active ? 'drop-shadow(0 0 6px rgba(95,221,125,.5))' : 'none' }}>
<Icon />
{t.badge ? (
<span style={{
position: 'absolute', top: -4, right: -8, minWidth: 15, height: 15,
fontSize: 9.5, fontWeight: 700, lineHeight: '15px', textAlign: 'center',
background: 'var(--violet)', color: 'var(--bg-deep)', borderRadius: 8, padding: '0 3px',
}}>{t.badge}</span>
) : null}
</span>
<span style={{ fontSize: 10, letterSpacing: '.12em' }}>{t.label}</span>
</button>
);
})}
</nav>
);
}
Object.assign(window, { MaestroMobHeader: MobileHeader, MaestroMobTabBar: MobileTabBar });