mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-16 22:31:38 +00:00
* feat(ios): align brand color tokens with design guide (PR1) * fix(ios): preserve brand contrast * fix(ios): cover tinted palette surfaces * chore(ios): sync native i18n inventory * chore(ios): sync native i18n inventory * chore(ios): sync native i18n inventory --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
101 lines
4.0 KiB
Swift
101 lines
4.0 KiB
Swift
import Testing
|
|
import UIKit
|
|
@testable import OpenClaw
|
|
|
|
struct OpenClawBrandTests {
|
|
@Test func `brand colors meet text contrast in both appearances`() {
|
|
let foregroundColors = [
|
|
("accent", OpenClawBrand.uiAccentForeground),
|
|
("accentHot", OpenClawBrand.uiAccentHotForeground),
|
|
("ok", OpenClawBrand.uiOK),
|
|
("warn", OpenClawBrand.uiWarn),
|
|
("danger", OpenClawBrand.uiDanger),
|
|
("info", OpenClawBrand.uiInfo),
|
|
]
|
|
let backgrounds = [UIColor.systemBackground, UIColor.secondarySystemBackground]
|
|
|
|
for style in [UIUserInterfaceStyle.light, .dark] {
|
|
let traits = UITraitCollection(userInterfaceStyle: style)
|
|
for (name, color) in foregroundColors {
|
|
for background in backgrounds {
|
|
#expect(
|
|
Self.contrastRatio(color, background, traits: traits) >= 4.5,
|
|
"\(name) on system background in \(style)")
|
|
}
|
|
|
|
let tintedBackground = Self.composite(
|
|
color,
|
|
alpha: 0.10,
|
|
over: .secondarySystemGroupedBackground,
|
|
traits: traits)
|
|
#expect(
|
|
Self.contrastRatio(color, tintedBackground, traits: traits) >= 4.5,
|
|
"\(name) on tinted background in \(style)")
|
|
}
|
|
|
|
let pillBackground = Self.composite(
|
|
OpenClawBrand.uiAccentForeground,
|
|
alpha: style == .dark ? 0.12 : 0.08,
|
|
over: .secondarySystemGroupedBackground,
|
|
traits: traits)
|
|
#expect(Self.contrastRatio(OpenClawBrand.uiAccentForeground, pillBackground, traits: traits) >= 4.5)
|
|
#expect(Self.contrastRatio(OpenClawBrand.uiAccent, .white, traits: traits) >= 4.5)
|
|
}
|
|
}
|
|
|
|
private static func composite(
|
|
_ foreground: UIColor,
|
|
alpha: CGFloat,
|
|
over background: UIColor,
|
|
traits: UITraitCollection) -> UIColor
|
|
{
|
|
let foregroundComponents = Self.rgbComponents(foreground, traits: traits)
|
|
let backgroundComponents = Self.rgbComponents(background, traits: traits)
|
|
return UIColor(
|
|
red: foregroundComponents.red * alpha + backgroundComponents.red * (1 - alpha),
|
|
green: foregroundComponents.green * alpha + backgroundComponents.green * (1 - alpha),
|
|
blue: foregroundComponents.blue * alpha + backgroundComponents.blue * (1 - alpha),
|
|
alpha: 1)
|
|
}
|
|
|
|
private static func contrastRatio(
|
|
_ foreground: UIColor,
|
|
_ background: UIColor,
|
|
traits: UITraitCollection) -> CGFloat
|
|
{
|
|
let foregroundLuminance = Self.relativeLuminance(foreground, traits: traits)
|
|
let backgroundLuminance = Self.relativeLuminance(background, traits: traits)
|
|
let lighter = max(foregroundLuminance, backgroundLuminance)
|
|
let darker = min(foregroundLuminance, backgroundLuminance)
|
|
return (lighter + 0.05) / (darker + 0.05)
|
|
}
|
|
|
|
private static func relativeLuminance(_ color: UIColor, traits: UITraitCollection) -> CGFloat {
|
|
let components = Self.rgbComponents(color, traits: traits)
|
|
|
|
func linearize(_ component: CGFloat) -> CGFloat {
|
|
component <= 0.04045
|
|
? component / 12.92
|
|
: pow((component + 0.055) / 1.055, 2.4)
|
|
}
|
|
|
|
return 0.2126 * linearize(components.red) + 0.7152 * linearize(components.green) +
|
|
0.0722 * linearize(components.blue)
|
|
}
|
|
|
|
private static func rgbComponents(
|
|
_ color: UIColor,
|
|
traits: UITraitCollection) -> (red: CGFloat, green: CGFloat, blue: CGFloat)
|
|
{
|
|
let resolved = color.resolvedColor(with: traits)
|
|
var red: CGFloat = 0
|
|
var green: CGFloat = 0
|
|
var blue: CGFloat = 0
|
|
var alpha: CGFloat = 0
|
|
guard resolved.getRed(&red, green: &green, blue: &blue, alpha: &alpha) else {
|
|
return (0, 0, 0)
|
|
}
|
|
return (red, green, blue)
|
|
}
|
|
}
|