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>
This commit is contained in:
wangjia
2026-06-13 14:29:07 +08:00
parent 0b3ebbd569
commit 5a1e185b1a
102 changed files with 10323 additions and 0 deletions
+182
View File
@@ -0,0 +1,182 @@
// 已归档区(done / 取消,时间倒序分页)+ 归档详情模态
function ArchiveRow({ item, t, onOpen }) {
const { ComplexityBadge, StatusChip } = window.MaestroDesignSystem_a6a290;
const [hover, setHover] = React.useState(false);
return (
<div onClick={() => onOpen(item)} title={t.archiveTip}
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
style={{
display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer',
padding: '7px 12px', fontSize: 12.5,
borderBottom: '1px dashed var(--line-soft)',
background: hover ? 'var(--panel)' : 'transparent',
}}>
<span style={{ color: 'var(--muted)', fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{item.title}<span style={{ color: 'var(--faint)', fontSize: 10.5, marginLeft: 6 }}>{item.id}</span>
</span>
{item.subs ? <span style={{ flex: 'none', fontSize: 10.5, color: 'var(--faint)' }}>{t.archiveSubs.replace('{n}', item.subs)}</span> : null}
<span style={{ flex: 1, minWidth: 8 }}></span>
<ComplexityBadge complexity={item.cplx} />
<StatusChip status={item.status} label={t.status[item.status]} />
<span style={{ flex: 'none', fontSize: 11, color: 'var(--faint)' }} title={item.timeFull}>{item.time}</span>
</div>
);
}
function SizeSelect({ size, setSize, t }) {
const [open, setOpen] = React.useState(false);
const [hover, setHover] = React.useState(false);
const ref = React.useRef(null);
React.useEffect(() => {
if (!open) return;
const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
document.addEventListener('mousedown', onDoc);
return () => document.removeEventListener('mousedown', onDoc);
}, [open]);
return (
<span ref={ref} style={{ position: 'relative', display: 'inline-flex' }}>
<button onClick={() => setOpen(!open)}
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
style={{
fontFamily: 'var(--mono)', fontSize: 11, fontWeight: 600, letterSpacing: '.04em',
background: 'transparent', color: hover || open ? 'var(--green)' : 'var(--muted)',
border: '1px solid ' + (hover || open ? 'var(--green-dim)' : 'var(--line)'),
borderRadius: 'var(--radius-sm, 4px)', padding: '3px 9px', cursor: 'pointer',
display: 'inline-flex', alignItems: 'center', gap: 7, transition: 'color .12s, border-color .12s',
}}>
{t.perPage} <b style={{ color: 'var(--green)', fontSize: 11.5 }}>{size}</b>
<span style={{ fontSize: 8, color: 'var(--faint)', transform: open ? 'rotate(180deg)' : 'none', transition: 'transform .12s' }}></span>
</button>
{open ? (
<div style={{
position: 'absolute', bottom: 'calc(100% + 5px)', right: 0, zIndex: 60,
display: 'grid', minWidth: '100%',
background: 'var(--bg-deep)', border: '1px solid var(--line)', borderRadius: 'var(--radius-md, 6px)',
padding: 4, boxShadow: '0 10px 30px rgba(0,0,0,.65)', animation: 'maestro-rise .12s ease both',
}}>
{[10, 20, 50, 100].map((n) => {
const active = n === size;
return (
<button key={n} onClick={() => { setSize(n); setOpen(false); }} style={{
fontFamily: 'var(--mono)', fontSize: 11.5, fontWeight: active ? 700 : 400, textAlign: 'left',
background: active ? 'var(--panel-2)' : 'transparent',
color: active ? 'var(--green)' : 'var(--ink)',
border: 'none', borderRadius: 4, padding: '6px 12px 6px 8px', cursor: 'pointer',
display: 'flex', alignItems: 'center', gap: 7,
}}
onMouseEnter={(e) => { if (!active) e.currentTarget.style.background = 'var(--panel)'; }}
onMouseLeave={(e) => { if (!active) e.currentTarget.style.background = 'transparent'; }}>
<span style={{ width: 8, color: 'var(--green)', fontSize: 10 }}>{active ? '▍' : ''}</span>{n}
</button>
);
})}
</div>
) : null}
</span>
);
}
function ArchivePager({ page, pages, size, setPage, setSize, t }) {
const { Button } = window.MaestroDesignSystem_a6a290;
return (
<div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '10px 0', justifyContent: 'center', fontFamily: 'var(--mono)' }}>
{pages > 1 ? (
<React.Fragment>
<Button size="xs" disabled={page <= 1} onClick={() => setPage(page - 1)}>{t.pagePrev}</Button>
<span style={{ fontSize: 11, color: 'var(--muted)' }}>{t.pageOf.replace('{a}', page).replace('{b}', pages)}</span>
<Button size="xs" disabled={page >= pages} onClick={() => setPage(page + 1)}>{t.pageNext}</Button>
</React.Fragment>
) : null}
<SizeSelect size={size} setSize={setSize} t={t} />
</div>
);
}
function ArchiveSection({ items, t, onOpen }) {
const [page, setPage] = React.useState(1);
const [size, setSize] = React.useState(20);
if (!items.length) return null;
const pages = Math.max(1, Math.ceil(items.length / size));
const cur = Math.min(page, pages);
const slice = items.slice((cur - 1) * size, cur * size);
return (
<section style={{ marginTop: 28, opacity: .82 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 6, fontFamily: 'var(--mono)', fontSize: 11, fontWeight: 600, letterSpacing: '.22em', color: 'var(--muted)', textTransform: 'uppercase', padding: '18px 0 10px' }}>
<span style={{ color: 'var(--faint)' }}></span>{t.archive} · {items.length}
</div>
<div>{slice.map((it) => <ArchiveRow key={it.id} item={it} t={t} onOpen={onOpen} />)}</div>
<ArchivePager page={cur} pages={pages} size={size} setPage={setPage} setSize={(n) => { setSize(n); setPage(1); }} t={t} />
</section>
);
}
// 归档详情模态(只读:属性 / 产出 / 执行历史 / 结果 / 审批 / 时间线)
function ArchiveModal({ item, t, onClose }) {
const { ComplexityBadge, StatusChip, Timeline } = window.MaestroDesignSystem_a6a290;
React.useEffect(() => {
const onKey = (e) => { if (e.key === 'Escape') onClose(); };
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [onClose]);
if (!item) return null;
const d = item.detail || {};
const label = (text) => <div style={{ fontSize: 10.5, color: 'var(--muted)', letterSpacing: '.18em', margin: '14px 0 4px' }}>{text}</div>;
const doc = (text) => (
<pre style={{ background: 'var(--bg-deep)', border: '1px solid var(--line-soft)', borderLeft: '2px solid var(--green-dim)', borderRadius: 4, padding: '10px 12px', fontFamily: 'var(--mono)', fontSize: 12.5, lineHeight: 1.7, whiteSpace: 'pre-wrap', wordBreak: 'break-word', margin: 0, color: 'var(--ink)' }}>{text}</pre>
);
const kvGrid = { fontSize: 12, display: 'grid', gridTemplateColumns: 'auto 1fr', gap: '3px 12px', margin: 0 };
return (
<div style={{ position: 'fixed', inset: 0, zIndex: 1100, display: 'grid', placeItems: 'center' }}>
<div onClick={onClose} style={{ position: 'absolute', inset: 0, background: 'rgba(4,6,5,.78)', backdropFilter: 'blur(2px)' }}></div>
<div style={{ position: 'relative', display: 'flex', flexDirection: 'column', width: 'min(860px, 94vw)', maxHeight: '92vh', background: 'var(--panel)', border: '1px solid var(--line)', borderRadius: 'var(--radius-lg, 10px)', overflow: 'hidden', boxShadow: '0 24px 64px rgba(0,0,0,.6)', animation: 'maestro-rise .18s ease both' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap', padding: '12px 16px', borderBottom: '1px solid var(--line)', background: 'var(--panel-2)' }}>
<span style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: '.18em', color: 'var(--bg-deep)', background: 'var(--muted)', padding: '2px 8px', borderRadius: 3 }}>{t.archiveDetail}</span>
<span style={{ fontWeight: 700, fontSize: 15 }}>{item.title}</span>
<ComplexityBadge complexity={item.cplx} />
<StatusChip status={item.status} label={t.status[item.status]} />
<span style={{ flex: 1 }}></span>
<button onClick={onClose} title={t.closeTip} style={{ fontFamily: 'var(--mono)', fontSize: 13, background: 'transparent', color: 'var(--muted)', border: 'none', cursor: 'pointer', padding: '4px 8px' }}></button>
</div>
<div style={{ flex: 1, overflowY: 'auto', padding: '14px 20px 24px', fontFamily: 'var(--mono)' }}>
{d.attrs ? <dl style={kvGrid}>{d.attrs.map(([k, v], i) => (
<React.Fragment key={i}><dt style={{ color: 'var(--muted)' }}>{k}</dt><dd style={{ margin: 0, wordBreak: 'break-all' }}>{v}</dd></React.Fragment>
))}</dl> : null}
{d.spec ? <React.Fragment>{label('SPEC · 方案')}{doc(d.spec)}</React.Fragment> : null}
{d.ops ? <React.Fragment>{label('OPERATIONS · 操作')}{doc(d.ops)}</React.Fragment> : null}
{d.runs && d.runs.length ? <React.Fragment>
{label(t.runsLabel + '' + d.runs.length + '')}
<dl style={kvGrid}>{d.runs.map((r, i) => (
<React.Fragment key={i}>
<dt style={{ color: 'var(--muted)' }}>{r.kind} · {r.status}</dt>
<dd style={{ margin: 0 }}>{r.span}{r.ref ? <span style={{ display: 'block', color: 'var(--faint)', fontSize: 10.5 }}>{r.ref}</span> : null}</dd>
</React.Fragment>
))}</dl>
</React.Fragment> : null}
{d.result ? <React.Fragment>
{label(t.resultLabel)}
<dl style={kvGrid}>
<dt style={{ color: 'var(--muted)' }}>{t.branch}</dt><dd style={{ margin: 0 }}>{d.result.branch}</dd>
<dt style={{ color: 'var(--muted)' }}>commits</dt><dd style={{ margin: 0 }}>{d.result.commits.join(' · ')}</dd>
<dt style={{ color: 'var(--muted)' }}>diff</dt><dd style={{ margin: 0 }}>{d.result.diff}</dd>
</dl>
</React.Fragment> : null}
{d.approvals && d.approvals.length ? <React.Fragment>
{label(t.approvalsLabel)}
<dl style={kvGrid}>{d.approvals.map((a, i) => (
<React.Fragment key={i}>
<dt style={{ color: a.action === 'accept' ? 'var(--green)' : 'var(--red)', fontWeight: 700 }}>{a.gate} · {a.action === 'accept' ? t.accepted : t.rejected}</dt>
<dd style={{ margin: 0 }}>{t.approver} {a.actor} · {a.at}{a.reason ? <span style={{ display: 'block', color: 'var(--amber)' }}> {a.reason}</span> : null}</dd>
</React.Fragment>
))}</dl>
</React.Fragment> : null}
{d.timeline && d.timeline.length ? <React.Fragment>
{label(t.timelineLabel)}
<Timeline items={d.timeline} />
</React.Fragment> : null}
</div>
</div>
</div>
);
}
Object.assign(window, { MaestroKitArchiveSection: ArchiveSection, MaestroKitArchiveModal: ArchiveModal });