feat(design): design/ 真相源 + 防漂移静态闸(#25)
真相源整固: - design/index.html 登记簿:token 色板(var() 实时渲染)/ 组件登记卡 / 图标与跨端映射 / guidelines / ui_kits 单一入口,check-ds 强制登记 - design/icons.js 图标单源(Lucide 线条)+ icon-map.json 跨端映射表 (web/iOS/Android 三列,iOS 12 个 SF Symbol 全部收编登记) - components/core/Icon.jsx 单源图标渲染器;MicBar/RecognitionOverlay 接入 - tokens 新增 --brand-wechat(-press)(微信官方绿)与 --overlay-wave - fonts.css 弃 Google Fonts CDN,Outfit 可变字体自托管 (assets/outfit-latin.woff2,40KB;中国网络下 CDN 不可用会卡字体) - design/serve.mjs 零依赖预览服务器(登记簿与原型评审入口) codegen 强化(export-tokens.mjs): - 产物直写消费位置:ios/dudu/Shared/DuduTheme.swift、 android/.../design/DuduTheme.kt——删除 design-pipeline/generated/ 中间拷贝层(此前 app 内是手工拷贝,--check 守不到真实消费文件已漂移) - 新增 web/tokens.css 产物(custom props 透传 + Outfit data URI 内嵌) - tokens 文件遍历排序,产物确定性 存量收编: - desktop:三个窗口内联 SVG → Icon 组件;tray 裸 rgba → token+opacity - Android:LoginScreen 微信绿常量 → DuduPalette.brandWechat(Press), Color.White → textOnAccent - 官网 web/index.html:接入 tokens.css,34 处硬编码 hex 全部 var() 化; 深色板块(metrics/cta/footer)改挂 data-theme="dark" 复用 dark 令牌; 移除 Google Fonts CDN - design 包内 8 处裸色:微信绿 token 化、装饰色 ds-ignore 白名单、 同值色改 var() 引用 防漂移闸(零依赖 Node): - design-pipeline/check-ds.mjs:真相源自检 7 道(dark⊆light / var 链 / 裸色∈tokens 值集 / 组件登记 / icons 唯一 / icon-map 双向同集 / 禁 emoji) - design-pipeline/check-code.mjs:代码侧单源(desktop 禁字面色+内联 path / iOS 禁字面色+SF Symbol 同集 / Android 禁 Color(0x / web 禁字面色), ds-ignore 行级豁免制 - .githooks/pre-commit(启用:git config core.hooksPath .githooks) - CI design job 扩展为三道闸全绿 - 反向验证通过:注入裸色/孤儿 token/未登记组件/未登记 symbol 均被拦截 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* check-code.mjs — 代码侧颜色/图标单源闸(分端扫描,零依赖,Node >= 18)
|
||||
*
|
||||
* 各端规则(违规 exit 1;行内 `ds-ignore: 理由` 豁免):
|
||||
* desktop/src(jsx/css/js):禁字面色(hex/rgb/hsl),须 var(--*);
|
||||
* 禁内联 <path>(图标须经 @dudu/design 的 Icon/icons.js)
|
||||
* ios(swift) :禁 Color(red:/UIColor(/Color(0x/#colorLiteral,须 DuduTheme;
|
||||
* systemName 用的 SF Symbol 须 ∈ design/icon-map.json 的 ios 列
|
||||
* android(kt) :禁 Color(0x,须 DuduTheme(生成文件本身豁免)
|
||||
* web(html/css) :禁字面色(令牌由 GENERATED tokens.css 提供,页面只允许 var(--*))
|
||||
*
|
||||
* 用法:node design-pipeline/check-code.mjs
|
||||
*/
|
||||
|
||||
import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs';
|
||||
import { resolve, dirname, join, relative, basename } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dir = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = resolve(__dir, '..');
|
||||
const errors = [];
|
||||
const err = (file, line, msg) => errors.push(`${relative(ROOT, file)}:${line} ${msg}`);
|
||||
|
||||
function walk(dir, exts, out = []) {
|
||||
if (!existsSync(dir)) return out;
|
||||
for (const name of readdirSync(dir)) {
|
||||
if (name === 'node_modules' || name === 'dist' || name === 'target' || name.startsWith('.')) continue;
|
||||
const p = join(dir, name);
|
||||
if (statSync(p).isDirectory()) walk(p, exts, out);
|
||||
else if (exts.some((e) => name.endsWith(e))) out.push(p);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
const COLOR_RE = /#[0-9a-fA-F]{3,8}\b|(?:rgba?|hsla?)\(/g;
|
||||
|
||||
function scanColors(files, label) {
|
||||
for (const f of files) {
|
||||
readFileSync(f, 'utf8').split('\n').forEach((line, i) => {
|
||||
if (line.includes('ds-ignore:')) return;
|
||||
const stripped = line.replace(/var\(--[\w-]+\)/g, ''); // var() 里不含字面色,先剥掉便于报错定位
|
||||
const m = stripped.match(COLOR_RE);
|
||||
if (m) err(f, i + 1, `${label} 字面色 ${m[0]}(改用设计令牌,或加 ds-ignore: 理由)`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ─── desktop:字面色 + 内联 <path> ───────────────────────────────────────────
|
||||
|
||||
const desktopFiles = walk(join(ROOT, 'desktop/src'), ['.jsx', '.js', '.css', '.html']);
|
||||
scanColors(desktopFiles, 'desktop');
|
||||
for (const f of desktopFiles) {
|
||||
readFileSync(f, 'utf8').split('\n').forEach((line, i) => {
|
||||
if (line.includes('ds-ignore:')) return;
|
||||
if (/<path[\s>]/.test(line))
|
||||
err(f, i + 1, 'desktop 内联 <path>(图标须登记 design/icons.js 并经 Icon 组件渲染)');
|
||||
});
|
||||
}
|
||||
|
||||
// ─── iOS:字面色 + SF Symbol 同集 ────────────────────────────────────────────
|
||||
|
||||
const iconMap = JSON.parse(readFileSync(join(ROOT, 'design/icon-map.json'), 'utf8'));
|
||||
const iosSymbols = new Set(
|
||||
Object.entries(iconMap).filter(([k]) => k !== '$schema').map(([, v]) => v.ios).filter(Boolean)
|
||||
);
|
||||
|
||||
for (const f of walk(join(ROOT, 'ios'), ['.swift'])) {
|
||||
const isGenerated = basename(f) === 'DuduTheme.swift';
|
||||
readFileSync(f, 'utf8').split('\n').forEach((line, i) => {
|
||||
if (line.includes('ds-ignore:')) return;
|
||||
if (!isGenerated && /Color\(red:|UIColor\(red:|Color\(\s*0x|#colorLiteral/.test(line))
|
||||
err(f, i + 1, 'iOS 字面色(改用 DuduTheme.*,或加 ds-ignore: 理由)');
|
||||
for (const m of line.matchAll(/systemName:\s*"([^"]+)"/g)) {
|
||||
if (!iosSymbols.has(m[1]))
|
||||
err(f, i + 1, `iOS SF Symbol "${m[1]}" 未登记 design/icon-map.json(ios 列)`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Android:字面色 ─────────────────────────────────────────────────────────
|
||||
|
||||
for (const f of walk(join(ROOT, 'android/app/src'), ['.kt'])) {
|
||||
if (basename(f) === 'DuduTheme.kt') continue; // codegen 产物
|
||||
readFileSync(f, 'utf8').split('\n').forEach((line, i) => {
|
||||
if (line.includes('ds-ignore:')) return;
|
||||
if (/Color\(\s*0x/.test(line))
|
||||
err(f, i + 1, 'Android 字面色(改用 DuduTheme / DuduPalette,或加 ds-ignore: 理由)');
|
||||
});
|
||||
}
|
||||
|
||||
// ─── web:字面色(tokens.css 为 GENERATED 豁免)──────────────────────────────
|
||||
|
||||
scanColors(
|
||||
walk(join(ROOT, 'web'), ['.html', '.css']).filter((f) => basename(f) !== 'tokens.css'),
|
||||
'web'
|
||||
);
|
||||
|
||||
// ─── 汇总 ────────────────────────────────────────────────────────────────────
|
||||
|
||||
if (errors.length) {
|
||||
console.error(`❌ check-code:${errors.length} 处违规\n` + errors.map((e) => ' ' + e).join('\n'));
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('✅ check-code:desktop / iOS / Android / web 单源全绿');
|
||||
@@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* check-ds.mjs — 真相源自检闸(扫 design/ 自身,零依赖,Node >= 18)
|
||||
*
|
||||
* 七道检查(违规 exit 1,逐条打印文件:行):
|
||||
* ① tokens dark 键 ⊆ light 键(防孤儿 dark token)
|
||||
* ② token 值内 var(--x) 引用链可解析;styles.css @import 的文件存在
|
||||
* ③ design/ 内(guidelines/components/ui_kits/index.html/styles.css)裸色
|
||||
* 必须 ∈ tokens 值集,或行内带 `ds-ignore:` 豁免注释
|
||||
* ④ components/**\/*.jsx 导出的组件必须在登记簿 design/index.html 出现
|
||||
* ⑤ icons.js:key 唯一、path 为描边风格(禁 fill="#...")
|
||||
* ⑥ icon-map.json:web 列 ↔ icons.js key 双向同集;值为 string|null
|
||||
* ⑦ 组件/ui_kits 界面文案禁 emoji(键帽符号 ⌘⇧⌫↵⌥ 不在 emoji 区,天然放行)
|
||||
*
|
||||
* 用法:node design-pipeline/check-ds.mjs
|
||||
*/
|
||||
|
||||
import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
|
||||
import { resolve, dirname, join, relative } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dir = dirname(fileURLToPath(import.meta.url));
|
||||
const DESIGN = resolve(__dir, '../design');
|
||||
const errors = [];
|
||||
const err = (file, line, msg) =>
|
||||
errors.push(`${relative(resolve(__dir, '..'), file)}:${line} ${msg}`);
|
||||
|
||||
// ─── 工具 ────────────────────────────────────────────────────────────────────
|
||||
|
||||
function walk(dir, exts, out = []) {
|
||||
for (const name of readdirSync(dir)) {
|
||||
const p = join(dir, name);
|
||||
if (statSync(p).isDirectory()) walk(p, exts, out);
|
||||
else if (exts.some((e) => name.endsWith(e))) out.push(p);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function parseBlock(css, selector) {
|
||||
const out = {};
|
||||
const re = new RegExp(`${selector.replace(/[[\]"=]/g, (m) => '\\' + m)}\\s*\\{([\\s\\S]*?)\\}`, 'g');
|
||||
let m;
|
||||
while ((m = re.exec(css))) {
|
||||
const body = m[1].replace(/\/\*[\s\S]*?\*\//g, '');
|
||||
for (const line of body.split(';')) {
|
||||
const mm = line.match(/--([\w-]+)\s*:\s*(.+)/);
|
||||
if (mm) out[mm[1].trim()] = mm[2].trim();
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// ─── 解析 tokens ─────────────────────────────────────────────────────────────
|
||||
|
||||
const tokenFiles = readdirSync(join(DESIGN, 'tokens')).filter((f) => f.endsWith('.css')).sort();
|
||||
const light = {}, dark = {};
|
||||
for (const f of tokenFiles) {
|
||||
const css = readFileSync(join(DESIGN, 'tokens', f), 'utf8');
|
||||
Object.assign(light, parseBlock(css, ':root'));
|
||||
Object.assign(dark, parseBlock(css, '[data-theme="dark"]'));
|
||||
}
|
||||
|
||||
// tokens 合法值集(hex 大写 + rgba 去空格),供 ③ 比对
|
||||
const tokenValues = new Set();
|
||||
for (const v of [...Object.values(light), ...Object.values(dark)]) {
|
||||
for (const hex of v.matchAll(/#[0-9a-fA-F]{3,8}\b/g)) tokenValues.add(hex[0].toUpperCase());
|
||||
for (const fn of v.matchAll(/(?:rgba?|hsla?)\([^)]*\)/g)) tokenValues.add(fn[0].replace(/\s+/g, ''));
|
||||
}
|
||||
|
||||
// ① dark ⊆ light
|
||||
for (const k of Object.keys(dark)) {
|
||||
if (!(k in light)) err(join(DESIGN, 'tokens'), 0, `① 孤儿 dark token:--${k} 未在 :root 定义`);
|
||||
}
|
||||
|
||||
// ② var() 链可解析 + @import 存在
|
||||
for (const [scopeName, scope] of [['light', light], ['dark', dark]]) {
|
||||
for (const [k, v] of Object.entries(scope)) {
|
||||
for (const ref of v.matchAll(/var\(--([\w-]+)\)/g)) {
|
||||
if (!(ref[1] in light) && !(ref[1] in dark))
|
||||
err(join(DESIGN, 'tokens'), 0, `② --${k}(${scopeName})引用了不存在的 var(--${ref[1]})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
const styles = readFileSync(join(DESIGN, 'styles.css'), 'utf8');
|
||||
for (const im of styles.matchAll(/@import\s+(?:url\()?['"]([^'"]+)['"]/g)) {
|
||||
if (!existsSync(join(DESIGN, im[1]))) err(join(DESIGN, 'styles.css'), 0, `② @import 目标不存在:${im[1]}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ③ design/ 裸色须 ∈ tokens 值集(ds-ignore 豁免;tokens/ 与资产 svg 不扫)
|
||||
const scanFiles = [
|
||||
...walk(join(DESIGN, 'guidelines'), ['.html', '.css']),
|
||||
...walk(join(DESIGN, 'components'), ['.jsx', '.css', '.html']),
|
||||
...walk(join(DESIGN, 'ui_kits'), ['.jsx', '.css', '.html']),
|
||||
join(DESIGN, 'index.html'),
|
||||
join(DESIGN, 'styles.css'),
|
||||
];
|
||||
for (const f of scanFiles) {
|
||||
const lines = readFileSync(f, 'utf8').split('\n');
|
||||
lines.forEach((line, i) => {
|
||||
if (line.includes('ds-ignore:')) return;
|
||||
for (const m of line.matchAll(/#[0-9a-fA-F]{3,8}\b|(?:rgba?|hsla?)\([^)]*\)/g)) {
|
||||
const val = m[0].startsWith('#') ? m[0].toUpperCase() : m[0].replace(/\s+/g, '');
|
||||
// #fff/#FFF 简写归一到 6 位再比对
|
||||
const norm = /^#[0-9A-F]{3}$/.test(val)
|
||||
? '#' + [...val.slice(1)].map((c) => c + c).join('')
|
||||
: val;
|
||||
if (!tokenValues.has(norm))
|
||||
err(f, i + 1, `③ 裸色 ${m[0]} 不在 tokens 值集(改用 var(--*) 或加 ds-ignore: 理由)`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ④ 组件登记:components/**/*.jsx 导出名必须现于 index.html
|
||||
const registry = readFileSync(join(DESIGN, 'index.html'), 'utf8');
|
||||
for (const f of walk(join(DESIGN, 'components'), ['.jsx'])) {
|
||||
const src = readFileSync(f, 'utf8');
|
||||
for (const m of src.matchAll(/export\s+(?:function|const)\s+([A-Z]\w+)/g)) {
|
||||
if (!registry.includes(`<b>${m[1]}</b>`) && !registry.includes(`<b>${m[1]} `) && !new RegExp(`<b>[^<]*\\b${m[1]}\\b`).test(registry))
|
||||
err(f, 0, `④ 组件 ${m[1]} 未在登记簿 design/index.html 登记`);
|
||||
}
|
||||
}
|
||||
|
||||
// ⑤ icons.js:key 唯一 + 描边风格
|
||||
{
|
||||
const f = join(DESIGN, 'icons.js');
|
||||
const src = readFileSync(f, 'utf8');
|
||||
const keys = [...src.matchAll(/^\s{2}(\w+):/gm)].map((m) => m[1]);
|
||||
const seen = new Set();
|
||||
for (const k of keys) {
|
||||
if (seen.has(k)) err(f, 0, `⑤ icons.js 重复 key:${k}`);
|
||||
seen.add(k);
|
||||
}
|
||||
if (/fill="#/.test(src)) err(f, 0, '⑤ icons.js 含填充色(应为 stroke/currentColor 线条风格)');
|
||||
globalThis.__iconKeys = seen;
|
||||
}
|
||||
|
||||
// ⑥ icon-map.json ↔ icons.js 双向同集
|
||||
{
|
||||
const f = join(DESIGN, 'icon-map.json');
|
||||
const map = JSON.parse(readFileSync(f, 'utf8'));
|
||||
const webUsed = new Set();
|
||||
for (const [name, v] of Object.entries(map)) {
|
||||
if (name === '$schema') continue;
|
||||
for (const col of ['web', 'ios', 'android']) {
|
||||
if (!(col in v)) err(f, 0, `⑥ ${name} 缺 ${col} 列(未使用请置 null)`);
|
||||
else if (v[col] !== null && typeof v[col] !== 'string') err(f, 0, `⑥ ${name}.${col} 应为 string|null`);
|
||||
}
|
||||
if (v.web) {
|
||||
if (!globalThis.__iconKeys.has(v.web)) err(f, 0, `⑥ ${name}.web=${v.web} 不存在于 icons.js`);
|
||||
webUsed.add(v.web);
|
||||
}
|
||||
}
|
||||
for (const k of globalThis.__iconKeys) {
|
||||
if (!webUsed.has(k)) err(f, 0, `⑥ icons.js 的 ${k} 未登记进 icon-map.json(web 列)`);
|
||||
}
|
||||
}
|
||||
|
||||
// ⑦ 界面文案禁 emoji(components + ui_kits 的 jsx/card.html)
|
||||
const EMOJI = /[\u{1F000}-\u{1FAFF}\u{2600}-\u{27BF}\u{FE0F}]/u;
|
||||
for (const f of [...walk(join(DESIGN, 'components'), ['.jsx', '.html']), ...walk(join(DESIGN, 'ui_kits'), ['.jsx', '.html'])]) {
|
||||
readFileSync(f, 'utf8').split('\n').forEach((line, i) => {
|
||||
if (line.includes('ds-ignore:')) return;
|
||||
if (EMOJI.test(line)) err(f, i + 1, '⑦ 界面文案/原型含 emoji(品牌规范禁用;确属注释示例加 ds-ignore: 理由)');
|
||||
});
|
||||
}
|
||||
|
||||
// ─── 汇总 ────────────────────────────────────────────────────────────────────
|
||||
|
||||
if (errors.length) {
|
||||
console.error(`❌ check-ds:${errors.length} 处违规\n` + errors.map((e) => ' ' + e).join('\n'));
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('✅ check-ds:真相源自检 7 道全绿');
|
||||
@@ -3,9 +3,11 @@
|
||||
* export-tokens.mjs — 设计令牌导出管线(零依赖,Node >= 18)
|
||||
*
|
||||
* 解析 design/tokens/*.css 的 CSS 自定义属性(:root = light,[data-theme="dark"] = dark),
|
||||
* 生成移动端原生主题文件,保证三端令牌单一来源:
|
||||
* generated/ios/DuduTheme.swift — Color(light/dark 动态) + 尺寸/字号常量
|
||||
* generated/android/DuduTheme.kt — Compose Color/Dp/Sp 常量(Light/Dark 两组)
|
||||
* 生成各端主题产物并直写消费位置(无中间拷贝层,--check 守的即真实消费文件):
|
||||
* ios/dudu/Shared/DuduTheme.swift — Color(light/dark 动态) + 尺寸常量
|
||||
* android/app/src/main/java/app/dudu/design/DuduTheme.kt — Compose Color/Dp 常量(Light/Dark)
|
||||
* web/tokens.css — 官网令牌层(custom props 原样透传 +
|
||||
* Outfit 字体 data URI 内嵌,单文件自包含)
|
||||
*
|
||||
* 用法:node design-pipeline/export-tokens.mjs [--check]
|
||||
* --check CI 校验模式:重新生成并与已提交产物比对,不一致退出 1
|
||||
@@ -17,8 +19,10 @@ import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dir = dirname(fileURLToPath(import.meta.url));
|
||||
const TOKENS_DIR = resolve(__dir, '../design/tokens');
|
||||
const OUT_IOS = resolve(__dir, 'generated/ios/DuduTheme.swift');
|
||||
const OUT_ANDROID = resolve(__dir, 'generated/android/DuduTheme.kt');
|
||||
const OUT_IOS = resolve(__dir, '../ios/dudu/Shared/DuduTheme.swift');
|
||||
const OUT_ANDROID = resolve(__dir, '../android/app/src/main/java/app/dudu/design/DuduTheme.kt');
|
||||
const OUT_WEB = resolve(__dir, '../web/tokens.css');
|
||||
const FONT_WOFF2 = resolve(__dir, '../design/assets/outfit-latin.woff2');
|
||||
|
||||
// ─── 解析 ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -37,7 +41,7 @@ function parseBlock(css, selector) {
|
||||
return out;
|
||||
}
|
||||
|
||||
const files = readdirSync(TOKENS_DIR).filter((f) => f.endsWith('.css'));
|
||||
const files = readdirSync(TOKENS_DIR).filter((f) => f.endsWith('.css')).sort();
|
||||
let light = {}, dark = {};
|
||||
for (const f of files) {
|
||||
const css = readFileSync(resolve(TOKENS_DIR, f), 'utf8');
|
||||
@@ -166,11 +170,38 @@ ${dims}
|
||||
`;
|
||||
}
|
||||
|
||||
// ─── 生成 Web(官网令牌层)───────────────────────────────────────────────────
|
||||
|
||||
function web() {
|
||||
const props = (scope) =>
|
||||
Object.entries(scope).map(([k, v]) => ` --${k}: ${v};`).join('\n');
|
||||
const fontB64 = readFileSync(FONT_WOFF2).toString('base64');
|
||||
return `/* 自动生成 — 请勿手改。来源 design/tokens/*.css + assets/outfit-latin.woff2
|
||||
重新生成:node design-pipeline/export-tokens.mjs */
|
||||
@font-face {
|
||||
font-family: 'Outfit';
|
||||
font-style: normal;
|
||||
font-weight: 400 700;
|
||||
font-display: swap;
|
||||
src: url(data:font/woff2;base64,${fontB64}) format('woff2');
|
||||
}
|
||||
|
||||
:root {
|
||||
${props(light)}
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
${props(dark)}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
// ─── 输出 / 校验 ─────────────────────────────────────────────────────────────
|
||||
|
||||
const outputs = [
|
||||
[OUT_IOS, swift()],
|
||||
[OUT_ANDROID, kotlin()],
|
||||
[OUT_WEB, web()],
|
||||
];
|
||||
|
||||
const check = process.argv.includes('--check');
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
// 自动生成 — 请勿手改。来源 design/tokens/*.css
|
||||
// 重新生成:node design-pipeline/export-tokens.mjs
|
||||
|
||||
package app.dudu.design
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
object DuduTheme {
|
||||
object Light {
|
||||
val accent = Color(0xFF4F6EF7)
|
||||
val accentHover = Color(0xFF3D58DB)
|
||||
val accentPress = Color(0xFF2F44B3)
|
||||
val accentSoft = Color(0xFFEEF1FE)
|
||||
val accentSoft2 = Color(0xFFDFE5FD)
|
||||
val accentText = Color(0xFF3D58DB)
|
||||
val amber100 = Color(0xFFFCEFD7)
|
||||
val amber500 = Color(0xFFE8890C)
|
||||
val bgApp = Color(0xFFF6F7FA)
|
||||
val blue100 = Color(0xFFDFE5FD)
|
||||
val blue200 = Color(0xFFC4CFFB)
|
||||
val blue300 = Color(0xFF9FB1F9)
|
||||
val blue400 = Color(0xFF7590F8)
|
||||
val blue50 = Color(0xFFEEF1FE)
|
||||
val blue500 = Color(0xFF4F6EF7)
|
||||
val blue600 = Color(0xFF3D58DB)
|
||||
val blue700 = Color(0xFF2F44B3)
|
||||
val blue800 = Color(0xFF25368C)
|
||||
val blue900 = Color(0xFF1E2B6B)
|
||||
val border1 = Color(0xFFE4E6EB)
|
||||
val border2 = Color(0xFFD2D5DD)
|
||||
val danger = Color(0xFFDC2626)
|
||||
val dangerSoft = Color(0xFFFEE2E2)
|
||||
val gray0 = Color(0xFFFFFFFF)
|
||||
val gray100 = Color(0xFFF0F1F4)
|
||||
val gray200 = Color(0xFFE4E6EB)
|
||||
val gray25 = Color(0xFFFCFCFD)
|
||||
val gray300 = Color(0xFFD2D5DD)
|
||||
val gray400 = Color(0xFFA6ABB8)
|
||||
val gray50 = Color(0xFFF6F7FA)
|
||||
val gray500 = Color(0xFF7A8090)
|
||||
val gray600 = Color(0xFF5A6072)
|
||||
val gray700 = Color(0xFF434957)
|
||||
val gray800 = Color(0xFF2B2F3A)
|
||||
val gray900 = Color(0xFF1B1E26)
|
||||
val gray950 = Color(0xFF11131A)
|
||||
val green100 = Color(0xFFDCFCE7)
|
||||
val green500 = Color(0xFF16A34A)
|
||||
val overlayBg = Color(0xF2181A22)
|
||||
val overlayText = Color(0xFFF2F4FA)
|
||||
val overlayText2 = Color(0xFF9AA3BD)
|
||||
val overlayText3 = Color(0xFF646E8C)
|
||||
val positive = Color(0xFF16A34A)
|
||||
val positiveSoft = Color(0xFFDCFCE7)
|
||||
val red100 = Color(0xFFFEE2E2)
|
||||
val red500 = Color(0xFFDC2626)
|
||||
val surface2 = Color(0xFFF0F1F4)
|
||||
val surface3 = Color(0xFFE4E6EB)
|
||||
val surfaceCard = Color(0xFFFFFFFF)
|
||||
val text1 = Color(0xFF1B1E26)
|
||||
val text2 = Color(0xFF5A6072)
|
||||
val text3 = Color(0xFFA6ABB8)
|
||||
val textOnAccent = Color(0xFFFFFFFF)
|
||||
val warning = Color(0xFFE8890C)
|
||||
val warningSoft = Color(0xFFFCEFD7)
|
||||
}
|
||||
|
||||
object Dark {
|
||||
val accent = Color(0xFF5C77E8)
|
||||
val accentHover = Color(0xFF6E89F9)
|
||||
val accentPress = Color(0xFF4A63D6)
|
||||
val accentSoft = Color(0x295C77E8)
|
||||
val accentSoft2 = Color(0x425C77E8)
|
||||
val accentText = Color(0xFF7D95F7)
|
||||
val amber100 = Color(0xFFFCEFD7)
|
||||
val amber500 = Color(0xFFE8890C)
|
||||
val bgApp = Color(0xFF0F1116)
|
||||
val blue100 = Color(0xFFDFE5FD)
|
||||
val blue200 = Color(0xFFC4CFFB)
|
||||
val blue300 = Color(0xFF9FB1F9)
|
||||
val blue400 = Color(0xFF7590F8)
|
||||
val blue50 = Color(0xFFEEF1FE)
|
||||
val blue500 = Color(0xFF4F6EF7)
|
||||
val blue600 = Color(0xFF3D58DB)
|
||||
val blue700 = Color(0xFF2F44B3)
|
||||
val blue800 = Color(0xFF25368C)
|
||||
val blue900 = Color(0xFF1E2B6B)
|
||||
val border1 = Color(0xFF272C38)
|
||||
val border2 = Color(0xFF343A49)
|
||||
val danger = Color(0xFFF05B5B)
|
||||
val dangerSoft = Color(0x29F05B5B)
|
||||
val gray0 = Color(0xFFFFFFFF)
|
||||
val gray100 = Color(0xFFF0F1F4)
|
||||
val gray200 = Color(0xFFE4E6EB)
|
||||
val gray25 = Color(0xFFFCFCFD)
|
||||
val gray300 = Color(0xFFD2D5DD)
|
||||
val gray400 = Color(0xFFA6ABB8)
|
||||
val gray50 = Color(0xFFF6F7FA)
|
||||
val gray500 = Color(0xFF7A8090)
|
||||
val gray600 = Color(0xFF5A6072)
|
||||
val gray700 = Color(0xFF434957)
|
||||
val gray800 = Color(0xFF2B2F3A)
|
||||
val gray900 = Color(0xFF1B1E26)
|
||||
val gray950 = Color(0xFF11131A)
|
||||
val green100 = Color(0xFFDCFCE7)
|
||||
val green500 = Color(0xFF16A34A)
|
||||
val overlayBg = Color(0xF51E212B)
|
||||
val overlayText = Color(0xFFF2F4FA)
|
||||
val overlayText2 = Color(0xFF9AA3BD)
|
||||
val overlayText3 = Color(0xFF646E8C)
|
||||
val positive = Color(0xFF34C46A)
|
||||
val positiveSoft = Color(0x2934C46A)
|
||||
val red100 = Color(0xFFFEE2E2)
|
||||
val red500 = Color(0xFFDC2626)
|
||||
val surface2 = Color(0xFF1E222C)
|
||||
val surface3 = Color(0xFF262B37)
|
||||
val surfaceCard = Color(0xFF171A22)
|
||||
val text1 = Color(0xFFECEEF4)
|
||||
val text2 = Color(0xFF9AA1B2)
|
||||
val text3 = Color(0xFF5F6678)
|
||||
val textOnAccent = Color(0xFFFFFFFF)
|
||||
val warning = Color(0xFFF2A33C)
|
||||
val warningSoft = Color(0x29F2A33C)
|
||||
}
|
||||
|
||||
// Dimensions (dp)
|
||||
val controlLg = 44.dp
|
||||
val controlMd = 36.dp
|
||||
val controlSm = 28.dp
|
||||
val controlXl = 56.dp
|
||||
val radiusFull = 999.dp
|
||||
val radiusLg = 14.dp
|
||||
val radiusMd = 10.dp
|
||||
val radiusSm = 6.dp
|
||||
val radiusXl = 20.dp
|
||||
val radiusXs = 4.dp
|
||||
val space1 = 4.dp
|
||||
val space10 = 40.dp
|
||||
val space12 = 48.dp
|
||||
val space16 = 64.dp
|
||||
val space2 = 8.dp
|
||||
val space3 = 12.dp
|
||||
val space4 = 16.dp
|
||||
val space5 = 20.dp
|
||||
val space6 = 24.dp
|
||||
val space8 = 32.dp
|
||||
val text2xl = 24.dp
|
||||
val text3xl = 32.dp
|
||||
val text4xl = 44.dp
|
||||
val textBase = 14.dp
|
||||
val textLg = 17.dp
|
||||
val textMd = 15.dp
|
||||
val textSm = 13.dp
|
||||
val textXl = 20.dp
|
||||
val textXs = 12.dp
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
// 自动生成 — 请勿手改。来源 design/tokens/*.css
|
||||
// 重新生成:node design-pipeline/export-tokens.mjs
|
||||
|
||||
import SwiftUI
|
||||
|
||||
public enum DuduTheme {
|
||||
/// light/dark 动态色(跟随系统外观)
|
||||
static func dynamic(light: Color, dark: Color) -> Color {
|
||||
Color(UIColor { trait in
|
||||
trait.userInterfaceStyle == .dark ? UIColor(dark) : UIColor(light)
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: - Colors
|
||||
public static let accent = dynamic(
|
||||
light: Color(.sRGB, red: 0.3098, green: 0.4314, blue: 0.9686, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.3608, green: 0.4667, blue: 0.9098, opacity: 1.00))
|
||||
public static let accentHover = dynamic(
|
||||
light: Color(.sRGB, red: 0.2392, green: 0.3451, blue: 0.8588, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.4314, green: 0.5373, blue: 0.9765, opacity: 1.00))
|
||||
public static let accentPress = dynamic(
|
||||
light: Color(.sRGB, red: 0.1843, green: 0.2667, blue: 0.7020, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.2902, green: 0.3882, blue: 0.8392, opacity: 1.00))
|
||||
public static let accentSoft = dynamic(
|
||||
light: Color(.sRGB, red: 0.9333, green: 0.9451, blue: 0.9961, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.3608, green: 0.4667, blue: 0.9098, opacity: 0.16))
|
||||
public static let accentSoft2 = dynamic(
|
||||
light: Color(.sRGB, red: 0.8745, green: 0.8980, blue: 0.9922, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.3608, green: 0.4667, blue: 0.9098, opacity: 0.26))
|
||||
public static let accentText = dynamic(
|
||||
light: Color(.sRGB, red: 0.2392, green: 0.3451, blue: 0.8588, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.4902, green: 0.5843, blue: 0.9686, opacity: 1.00))
|
||||
public static let amber100 = dynamic(
|
||||
light: Color(.sRGB, red: 0.9882, green: 0.9373, blue: 0.8431, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.9882, green: 0.9373, blue: 0.8431, opacity: 1.00))
|
||||
public static let amber500 = dynamic(
|
||||
light: Color(.sRGB, red: 0.9098, green: 0.5373, blue: 0.0471, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.9098, green: 0.5373, blue: 0.0471, opacity: 1.00))
|
||||
public static let bgApp = dynamic(
|
||||
light: Color(.sRGB, red: 0.9647, green: 0.9686, blue: 0.9804, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.0588, green: 0.0667, blue: 0.0863, opacity: 1.00))
|
||||
public static let blue100 = dynamic(
|
||||
light: Color(.sRGB, red: 0.8745, green: 0.8980, blue: 0.9922, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.8745, green: 0.8980, blue: 0.9922, opacity: 1.00))
|
||||
public static let blue200 = dynamic(
|
||||
light: Color(.sRGB, red: 0.7686, green: 0.8118, blue: 0.9843, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.7686, green: 0.8118, blue: 0.9843, opacity: 1.00))
|
||||
public static let blue300 = dynamic(
|
||||
light: Color(.sRGB, red: 0.6235, green: 0.6941, blue: 0.9765, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.6235, green: 0.6941, blue: 0.9765, opacity: 1.00))
|
||||
public static let blue400 = dynamic(
|
||||
light: Color(.sRGB, red: 0.4588, green: 0.5647, blue: 0.9725, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.4588, green: 0.5647, blue: 0.9725, opacity: 1.00))
|
||||
public static let blue50 = dynamic(
|
||||
light: Color(.sRGB, red: 0.9333, green: 0.9451, blue: 0.9961, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.9333, green: 0.9451, blue: 0.9961, opacity: 1.00))
|
||||
public static let blue500 = dynamic(
|
||||
light: Color(.sRGB, red: 0.3098, green: 0.4314, blue: 0.9686, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.3098, green: 0.4314, blue: 0.9686, opacity: 1.00))
|
||||
public static let blue600 = dynamic(
|
||||
light: Color(.sRGB, red: 0.2392, green: 0.3451, blue: 0.8588, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.2392, green: 0.3451, blue: 0.8588, opacity: 1.00))
|
||||
public static let blue700 = dynamic(
|
||||
light: Color(.sRGB, red: 0.1843, green: 0.2667, blue: 0.7020, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.1843, green: 0.2667, blue: 0.7020, opacity: 1.00))
|
||||
public static let blue800 = dynamic(
|
||||
light: Color(.sRGB, red: 0.1451, green: 0.2118, blue: 0.5490, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.1451, green: 0.2118, blue: 0.5490, opacity: 1.00))
|
||||
public static let blue900 = dynamic(
|
||||
light: Color(.sRGB, red: 0.1176, green: 0.1686, blue: 0.4196, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.1176, green: 0.1686, blue: 0.4196, opacity: 1.00))
|
||||
public static let border1 = dynamic(
|
||||
light: Color(.sRGB, red: 0.8941, green: 0.9020, blue: 0.9216, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.1529, green: 0.1725, blue: 0.2196, opacity: 1.00))
|
||||
public static let border2 = dynamic(
|
||||
light: Color(.sRGB, red: 0.8235, green: 0.8353, blue: 0.8667, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.2039, green: 0.2275, blue: 0.2863, opacity: 1.00))
|
||||
public static let danger = dynamic(
|
||||
light: Color(.sRGB, red: 0.8627, green: 0.1490, blue: 0.1490, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.9412, green: 0.3569, blue: 0.3569, opacity: 1.00))
|
||||
public static let dangerSoft = dynamic(
|
||||
light: Color(.sRGB, red: 0.9961, green: 0.8863, blue: 0.8863, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.9412, green: 0.3569, blue: 0.3569, opacity: 0.16))
|
||||
public static let gray0 = dynamic(
|
||||
light: Color(.sRGB, red: 1.0000, green: 1.0000, blue: 1.0000, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 1.0000, green: 1.0000, blue: 1.0000, opacity: 1.00))
|
||||
public static let gray100 = dynamic(
|
||||
light: Color(.sRGB, red: 0.9412, green: 0.9451, blue: 0.9569, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.9412, green: 0.9451, blue: 0.9569, opacity: 1.00))
|
||||
public static let gray200 = dynamic(
|
||||
light: Color(.sRGB, red: 0.8941, green: 0.9020, blue: 0.9216, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.8941, green: 0.9020, blue: 0.9216, opacity: 1.00))
|
||||
public static let gray25 = dynamic(
|
||||
light: Color(.sRGB, red: 0.9882, green: 0.9882, blue: 0.9922, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.9882, green: 0.9882, blue: 0.9922, opacity: 1.00))
|
||||
public static let gray300 = dynamic(
|
||||
light: Color(.sRGB, red: 0.8235, green: 0.8353, blue: 0.8667, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.8235, green: 0.8353, blue: 0.8667, opacity: 1.00))
|
||||
public static let gray400 = dynamic(
|
||||
light: Color(.sRGB, red: 0.6510, green: 0.6706, blue: 0.7216, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.6510, green: 0.6706, blue: 0.7216, opacity: 1.00))
|
||||
public static let gray50 = dynamic(
|
||||
light: Color(.sRGB, red: 0.9647, green: 0.9686, blue: 0.9804, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.9647, green: 0.9686, blue: 0.9804, opacity: 1.00))
|
||||
public static let gray500 = dynamic(
|
||||
light: Color(.sRGB, red: 0.4784, green: 0.5020, blue: 0.5647, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.4784, green: 0.5020, blue: 0.5647, opacity: 1.00))
|
||||
public static let gray600 = dynamic(
|
||||
light: Color(.sRGB, red: 0.3529, green: 0.3765, blue: 0.4471, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.3529, green: 0.3765, blue: 0.4471, opacity: 1.00))
|
||||
public static let gray700 = dynamic(
|
||||
light: Color(.sRGB, red: 0.2627, green: 0.2863, blue: 0.3412, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.2627, green: 0.2863, blue: 0.3412, opacity: 1.00))
|
||||
public static let gray800 = dynamic(
|
||||
light: Color(.sRGB, red: 0.1686, green: 0.1843, blue: 0.2275, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.1686, green: 0.1843, blue: 0.2275, opacity: 1.00))
|
||||
public static let gray900 = dynamic(
|
||||
light: Color(.sRGB, red: 0.1059, green: 0.1176, blue: 0.1490, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.1059, green: 0.1176, blue: 0.1490, opacity: 1.00))
|
||||
public static let gray950 = dynamic(
|
||||
light: Color(.sRGB, red: 0.0667, green: 0.0745, blue: 0.1020, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.0667, green: 0.0745, blue: 0.1020, opacity: 1.00))
|
||||
public static let green100 = dynamic(
|
||||
light: Color(.sRGB, red: 0.8627, green: 0.9882, blue: 0.9059, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.8627, green: 0.9882, blue: 0.9059, opacity: 1.00))
|
||||
public static let green500 = dynamic(
|
||||
light: Color(.sRGB, red: 0.0863, green: 0.6392, blue: 0.2902, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.0863, green: 0.6392, blue: 0.2902, opacity: 1.00))
|
||||
public static let overlayBg = dynamic(
|
||||
light: Color(.sRGB, red: 0.0941, green: 0.1020, blue: 0.1333, opacity: 0.95),
|
||||
dark: Color(.sRGB, red: 0.1176, green: 0.1294, blue: 0.1686, opacity: 0.96))
|
||||
public static let overlayText = dynamic(
|
||||
light: Color(.sRGB, red: 0.9490, green: 0.9569, blue: 0.9804, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.9490, green: 0.9569, blue: 0.9804, opacity: 1.00))
|
||||
public static let overlayText2 = dynamic(
|
||||
light: Color(.sRGB, red: 0.6039, green: 0.6392, blue: 0.7412, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.6039, green: 0.6392, blue: 0.7412, opacity: 1.00))
|
||||
public static let overlayText3 = dynamic(
|
||||
light: Color(.sRGB, red: 0.3922, green: 0.4314, blue: 0.5490, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.3922, green: 0.4314, blue: 0.5490, opacity: 1.00))
|
||||
public static let positive = dynamic(
|
||||
light: Color(.sRGB, red: 0.0863, green: 0.6392, blue: 0.2902, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.2039, green: 0.7686, blue: 0.4157, opacity: 1.00))
|
||||
public static let positiveSoft = dynamic(
|
||||
light: Color(.sRGB, red: 0.8627, green: 0.9882, blue: 0.9059, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.2039, green: 0.7686, blue: 0.4157, opacity: 0.16))
|
||||
public static let red100 = dynamic(
|
||||
light: Color(.sRGB, red: 0.9961, green: 0.8863, blue: 0.8863, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.9961, green: 0.8863, blue: 0.8863, opacity: 1.00))
|
||||
public static let red500 = dynamic(
|
||||
light: Color(.sRGB, red: 0.8627, green: 0.1490, blue: 0.1490, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.8627, green: 0.1490, blue: 0.1490, opacity: 1.00))
|
||||
public static let surface2 = dynamic(
|
||||
light: Color(.sRGB, red: 0.9412, green: 0.9451, blue: 0.9569, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.1176, green: 0.1333, blue: 0.1725, opacity: 1.00))
|
||||
public static let surface3 = dynamic(
|
||||
light: Color(.sRGB, red: 0.8941, green: 0.9020, blue: 0.9216, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.1490, green: 0.1686, blue: 0.2157, opacity: 1.00))
|
||||
public static let surfaceCard = dynamic(
|
||||
light: Color(.sRGB, red: 1.0000, green: 1.0000, blue: 1.0000, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.0902, green: 0.1020, blue: 0.1333, opacity: 1.00))
|
||||
public static let text1 = dynamic(
|
||||
light: Color(.sRGB, red: 0.1059, green: 0.1176, blue: 0.1490, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.9255, green: 0.9333, blue: 0.9569, opacity: 1.00))
|
||||
public static let text2 = dynamic(
|
||||
light: Color(.sRGB, red: 0.3529, green: 0.3765, blue: 0.4471, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.6039, green: 0.6314, blue: 0.6980, opacity: 1.00))
|
||||
public static let text3 = dynamic(
|
||||
light: Color(.sRGB, red: 0.6510, green: 0.6706, blue: 0.7216, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.3725, green: 0.4000, blue: 0.4706, opacity: 1.00))
|
||||
public static let textOnAccent = dynamic(
|
||||
light: Color(.sRGB, red: 1.0000, green: 1.0000, blue: 1.0000, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 1.0000, green: 1.0000, blue: 1.0000, opacity: 1.00))
|
||||
public static let warning = dynamic(
|
||||
light: Color(.sRGB, red: 0.9098, green: 0.5373, blue: 0.0471, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.9490, green: 0.6392, blue: 0.2353, opacity: 1.00))
|
||||
public static let warningSoft = dynamic(
|
||||
light: Color(.sRGB, red: 0.9882, green: 0.9373, blue: 0.8431, opacity: 1.00),
|
||||
dark: Color(.sRGB, red: 0.9490, green: 0.6392, blue: 0.2353, opacity: 0.16))
|
||||
|
||||
// MARK: - Dimensions (pt)
|
||||
public static let controlLg: CGFloat = 44
|
||||
public static let controlMd: CGFloat = 36
|
||||
public static let controlSm: CGFloat = 28
|
||||
public static let controlXl: CGFloat = 56
|
||||
public static let radiusFull: CGFloat = 999
|
||||
public static let radiusLg: CGFloat = 14
|
||||
public static let radiusMd: CGFloat = 10
|
||||
public static let radiusSm: CGFloat = 6
|
||||
public static let radiusXl: CGFloat = 20
|
||||
public static let radiusXs: CGFloat = 4
|
||||
public static let space1: CGFloat = 4
|
||||
public static let space10: CGFloat = 40
|
||||
public static let space12: CGFloat = 48
|
||||
public static let space16: CGFloat = 64
|
||||
public static let space2: CGFloat = 8
|
||||
public static let space3: CGFloat = 12
|
||||
public static let space4: CGFloat = 16
|
||||
public static let space5: CGFloat = 20
|
||||
public static let space6: CGFloat = 24
|
||||
public static let space8: CGFloat = 32
|
||||
public static let text2xl: CGFloat = 24
|
||||
public static let text3xl: CGFloat = 32
|
||||
public static let text4xl: CGFloat = 44
|
||||
public static let textBase: CGFloat = 14
|
||||
public static let textLg: CGFloat = 17
|
||||
public static let textMd: CGFloat = 15
|
||||
public static let textSm: CGFloat = 13
|
||||
public static let textXl: CGFloat = 20
|
||||
public static let textXs: CGFloat = 12
|
||||
}
|
||||
Reference in New Issue
Block a user