feat: generate Swift design tokens from tokens.json and add SstUI base

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 15:19:06 +02:00
co-authored by Claude Sonnet 5.5
parent bf731dab37
commit bd6bc3b890
10 changed files with 659 additions and 21 deletions
+132
View File
@@ -58,6 +58,138 @@ print(f"{'text':16} {'on':20} {'finish':6} ratio")
for fg, bg, theme, r, ok in rows:
print(f"{fg:16} {bg:20} {theme:6} {r:5.2f} {'ok' if ok else 'FAIL'}")
# --- swift ------------------------------------------------------------------
def camel(name):
parts = name.replace("-", ".").split(".")
return parts[0] + "".join(p[:1].upper() + p[1:] for p in parts[1:])
def num(v):
return repr(float(v)) if isinstance(v, float) else str(v)
def doc(use):
return " /// " + use.replace("\n", " ")
def swift_source(t):
o = []
add = o.append
add("// GENERATED by Design/build_tokens.py from Design/tokens.json. Do not edit by hand.")
add("import SwiftUI\n")
add("public enum Palette {")
for n, c in t["color"].items():
add(doc(c["use"]))
add(f" public static let {camel(n)} = Color.dynamic(light: 0x{c['light'][1:].upper()}, dark: 0x{c['dark'][1:].upper()})")
add("}\n")
add("public enum Opacity {")
for n, v in t["opacity"].items():
add(doc(v["use"]))
add(f" public static let {camel(n)}: Double = {num(v['value'])}")
add("}\n")
add("public enum FontFamily: String {")
for n, f in t["font"].items():
add(f" case {n.replace('family.', '')} = \"{f['value']}\"")
add("}\n")
add("public enum Typography {")
for n, s in t["type"].items():
add(doc(s["use"]))
add(f" public static let {camel(n)} = TextStyle(family: .{s['family']}, size: {num(s['size'])}, weight: {s['weight']}, "
f"tracking: {num(s['tracking'])}, line: {num(s['line'])}, caps: {'true' if s['caps'] else 'false'}, "
f"tabular: {'true' if s.get('tabular') else 'false'})")
add("}\n")
add("public enum Segment {")
for n, v in t["segment"].items():
add(doc(v["use"]))
add(f" public static let {camel(n)}: CGFloat = {num(v['value'])}")
add("}\n")
add("public enum Space {")
for n, v in t["space"].items():
add(f" public static let s{n}: CGFloat = {v}")
add("}\n")
for enum, group in (("Layout", "layout"), ("Size", "size"), ("Radius", "radius"), ("Border", "border")):
add(f"public enum {enum} {{")
for n, v in t[group].items():
add(doc(v["use"]))
add(f" public static let {camel(n)}: CGFloat = {num(v['value'])}")
add("}\n")
add("public enum Shadows {")
for n, s in t["shadow"].items():
add(doc(s["use"]))
add(f" public static let {camel(n)}: [ShadowLayer] = [")
for l in s["layers"]:
add(f" ShadowLayer(inset: {'true' if l['inset'] else 'false'}, x: {l['x']}, y: {l['y']}, blur: {l['blur']}, spread: {l['spread']}, "
f"hex: 0x{l['color'][1:].upper()}, alphaLight: {num(l['alpha']['light'])}, alphaDark: {num(l['alpha']['dark'])}),")
add(" ]")
if "translateY" in s:
add(f" public static let {camel(n)}Offset: CGFloat = {s['translateY']}")
add("}\n")
add("public enum Texture {")
for n, v in t["texture"].items():
add(f" public static let {camel(n)}GrainLight: Double = {num(v['grain']['light'])}")
add(f" public static let {camel(n)}GrainDark: Double = {num(v['grain']['dark'])}")
add("}\n")
add("public enum MeterTokens {")
for n, v in t["meter"].items():
val = v["value"]
if n == "vu.ticks":
add(doc(v["use"]))
add(f" public static let vuTicks: [Int] = [{', '.join(str(int(x)) for x in val.split())}]")
elif n == "led.segmentSize":
w, h = val.split("x")
add(doc(v["use"]))
add(f" public static let ledSegmentWidth: CGFloat = {w}")
add(f" public static let ledSegmentHeight: CGFloat = {h}")
elif n == "peak.holdMs":
add(doc(v["use"]))
add(f" public static let peakHoldSeconds: Double = {num(val / 1000)}")
elif n == "led.segments":
add(doc(v["use"]))
add(f" public static let ledSegments: Int = {val}")
else:
add(doc(v["use"]))
add(f" public static let {camel(n)}: Double = {num(val)}")
add("}\n")
add("public enum Motion {")
for n, m in t["motion"].items():
if n == "reducedMotion":
continue
add(doc(m["use"]))
if m.get("unit") == "ms":
add(f" public static let {camel(n)}: Double = {num(m['value'] / 1000)}")
elif m.get("unit") in ("s", "ratio", "Hz"):
add(f" public static let {camel(n)}: Double = {num(m['value'])}")
elif isinstance(m["value"], str) and m["value"].startswith("cubic-bezier"):
pts = [x.strip() for x in m["value"][m["value"].index("(") + 1:-1].split(",")]
add(f" public static let {camel(n)}: (Double, Double, Double, Double) = ({', '.join(pts)})")
add("}")
return "\n".join(o) + "\n"
swift_text = swift_source(tokens)
swift_dir = ROOT / "Sources" / "SstUI"
swift_path = swift_dir / "DesignTokens.swift"
if "--check" in sys.argv:
in_sync = swift_path.exists() and swift_path.read_text() == swift_text
print("DesignTokens.swift is " + ("in sync with tokens.json" if in_sync else "OUT OF DATE: run python3 Design/build_tokens.py"))
sys.exit(0 if (in_sync and not failed) else 1)
if swift_dir.exists():
swift_path.write_text(swift_text)
print("wrote Sources/SstUI/DesignTokens.swift")
# --- markdown ---------------------------------------------------------------
md = []
add = md.append