Files
dudu/design/ui_kits/desktop/DesktopShared.jsx
T
wangjia 0ca1caa460
ci / server (push) Failing after 10s
ci / design-system (push) Failing after 12s
feat(design+desktop): 设置屏改版——卡片分组布局(12A 实测反馈,原型先行)
原布局「灰字组标题 + 通栏细线」平铺无体块感、行距不均、控件对齐参差。
改版(原型 SettingsTray 与实现 SettingsApp 同步):

- 分组卡片化:组标题在卡外(12px text-3 加字距),每组一张 radius-lg
  卡片(复用 Card 组件),卡内行以内嵌分隔线相隔,页面 bg-app 底上
  卡片体块清晰
- 行规格统一:SettingRow 复用,右侧控件统一右缘对齐
- 时长余额改大数字排版(text-xl bold + 单位小字基线对齐)
- 载入动效:四组 40ms 级差上浮淡入(对齐浮层动效语言,快入慢出)
- 顶条标题绝对居中(mac 原生习惯),与红绿灯永不重叠;原型 MacWindow
  同步改居中
- 原型补齐「帮助」组与实现对位

双主题截图验收通过;三道闸全绿;vite 构建通过。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 09:09:12 +08:00

130 lines
5.9 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// dudu 桌面端 UI kit · 共享元素:Lucide 图标子集、mac 窗口框、伪二维码
// 通过 window 共享给其余 text/babel 脚本
function DuduIcon({ d, size = 16, sw = 2, children, ...rest }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
strokeWidth={sw} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...rest}>
{children}
</svg>
);
}
const DuIcons = {
Mic: (p) => (
<DuduIcon {...p}>
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"></path>
<path d="M19 10v2a7 7 0 0 1-14 0v-2"></path>
<line x1="12" x2="12" y1="19" y2="22"></line>
</DuduIcon>
),
Settings: (p) => (
<DuduIcon {...p}>
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"></path>
<circle cx="12" cy="12" r="3"></circle>
</DuduIcon>
),
Check: (p) => <DuduIcon {...p}><path d="M20 6 9 17l-5-5"></path></DuduIcon>,
X: (p) => <DuduIcon {...p}><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></DuduIcon>,
ChevronDown: (p) => <DuduIcon {...p}><path d="m6 9 6 6 6-6"></path></DuduIcon>,
ChevronRight: (p) => <DuduIcon {...p}><path d="m9 18 6-6-6-6"></path></DuduIcon>,
Sun: (p) => (
<DuduIcon {...p}>
<circle cx="12" cy="12" r="4"></circle>
<path d="M12 2v2"></path><path d="M12 20v2"></path><path d="m4.93 4.93 1.41 1.41"></path>
<path d="m17.66 17.66 1.41 1.41"></path><path d="M2 12h2"></path><path d="M20 12h2"></path>
<path d="m6.34 17.66-1.41 1.41"></path><path d="m19.07 4.93-1.41 1.41"></path>
</DuduIcon>
),
Moon: (p) => <DuduIcon {...p}><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"></path></DuduIcon>,
AudioLines: (p) => (
<DuduIcon {...p}>
<path d="M2 10v3"></path><path d="M6 6v11"></path><path d="M10 3v18"></path>
<path d="M14 8v7"></path><path d="M18 5v13"></path><path d="M22 10v3"></path>
</DuduIcon>
),
User: (p) => (
<DuduIcon {...p}>
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</DuduIcon>
),
LogOut: (p) => (
<DuduIcon {...p}>
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
<polyline points="16 17 21 12 16 7"></polyline>
<line x1="21" x2="9" y1="12" y2="12"></line>
</DuduIcon>
),
};
// dudu 圆嘴 logocurrentColor 线条版)
function DuduLogo({ size = 20, sw = 3.5 }) {
return (
<svg width={size} height={size} viewBox="0 0 48 48" fill="none" stroke="currentColor"
strokeWidth={sw} strokeLinecap="round" aria-hidden="true">
<circle cx="24" cy="24" r="18"></circle>
<path d="M17 20.5v7"></path>
<path d="M24 16.5v15"></path>
<path d="M31 20.5v7"></path>
</svg>
);
}
// mac 风格窗口(红绿灯 + 标题)
function MacWindow({ title, width = 460, children, style }) {
return (
<div style={{
width, background: 'var(--surface-card)', borderRadius: 'var(--radius-md)',
border: '1px solid var(--border-1)', boxShadow: 'var(--shadow-window)',
overflow: 'hidden', fontFamily: 'var(--font-sans)', ...style,
}}>
<div style={{
position: 'relative', display: 'flex', alignItems: 'center', gap: 7, padding: '10px 14px',
background: 'var(--surface-2)', borderBottom: '1px solid var(--border-1)',
}}>
<span style={{ width: 11, height: 11, borderRadius: '50%', background: '#FF5F57' /* ds-ignore: mac 红绿灯窗饰,系统固定色 */ }}></span>
<span style={{ width: 11, height: 11, borderRadius: '50%', background: '#FEBC2E' /* ds-ignore: mac 红绿灯窗饰,系统固定色 */ }}></span>
<span style={{ width: 11, height: 11, borderRadius: '50%', background: '#28C840' /* ds-ignore: mac 红绿灯窗饰,系统固定色 */ }}></span>
{/* 标题绝对居中(mac 原生窗口习惯),永不与红绿灯重叠 */}
<span style={{
position: 'absolute', left: '50%', transform: 'translateX(-50%)',
fontSize: 'var(--text-xs)', color: 'var(--text-2)', whiteSpace: 'nowrap',
fontWeight: 'var(--weight-medium)',
}}>{title}</span>
</div>
{children}
</div>
);
}
// 伪二维码占位(确定性图案,仅作占位 — 接入后端 /v1/auth/qr 后替换)
function FakeQr({ size = 148, seed = 7 }) {
const n = 17;
const cells = [];
let s = seed;
const rnd = () => { s = (s * 16807) % 2147483647; return s / 2147483647; };
for (let y = 0; y < n; y++) {
for (let x = 0; x < n; x++) {
const corner = (x < 5 && y < 5) || (x > n - 6 && y < 5) || (x < 5 && y > n - 6);
let on;
if (corner) {
const cx = x > n - 6 ? x - (n - 5) : x, cy = y > n - 6 ? y - (n - 5) : y;
on = cx === 0 || cx === 4 || cy === 0 || cy === 4 || (cx > 1 && cx < 3 + 1 && cy > 1 && cy < 3 + 1);
} else {
on = rnd() > 0.52;
}
if (on) cells.push(<span key={x + '-' + y} style={{ gridColumn: x + 1, gridRow: y + 1, background: 'var(--gray-900)', borderRadius: 1 }}></span>);
}
}
return (
<div style={{
width: size, height: size, padding: 10, boxSizing: 'border-box', background: '#fff',
borderRadius: 'var(--radius-md)', border: '1px solid var(--border-1)',
display: 'grid', gridTemplateColumns: `repeat(${n}, 1fr)`, gridTemplateRows: `repeat(${n}, 1fr)`, gap: 1,
}} aria-label="二维码占位">{cells}</div>
);
}
Object.assign(window, { DuduIcon, DuIcons, DuduLogo, MacWindow, FakeQr });