Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ struct ConsumerSample {
print("WCAG AA normal text: \(Accessibility.meets(.AA, foreground: white, background: black))")

// 3) Palettes
let theme = Palettes.defaultLight
let theme = defaultLight
if let primaryHex = theme.colors["primary"], let primaryRGBA = try? HexColorFormatter.parse(primaryHex) {
print("DefaultLight primary hex: \(primaryHex) -> rgba r=\(String(format: "%.3f", primaryRGBA.r))")
}
Expand Down
378 changes: 378 additions & 0 deletions Example/iOSAppDemo/ColorsKitDemo.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions Example/iOSAppDemo/ColorsKitDemo/App.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import SwiftUI

@main
struct ColorsKitDemoApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
184 changes: 184 additions & 0 deletions Example/iOSAppDemo/ColorsKitDemo/BlendingModesView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import SwiftUI
import ColorsKit

struct BlendingModesView: View {
@State private var baseColor = "#FF6B6B"
@State private var overlayColor = "#4ECDC4"
@State private var selectedBlendMode: ColorsKit.BlendMode = .normal
@State private var blendedResult: String = "#FF6B6B"

var body: some View {
ScrollView {
VStack(spacing: 24) {
Text("Advanced Color Blending")
.font(.title2)
.fontWeight(.bold)
.padding(.top)

// Color Input Section
VStack(spacing: 16) {
HStack {
VStack(alignment: .leading) {
Text("Base Color")
.font(.headline)
HStack {
TextField("Hex color", text: $baseColor)
.textFieldStyle(RoundedBorderTextFieldStyle())
Rectangle()
.fill(Color(hex: baseColor) ?? Color.gray)
.frame(width: 40, height: 40)
.cornerRadius(8)
}
}

VStack(alignment: .leading) {
Text("Overlay Color")
.font(.headline)
HStack {
TextField("Hex color", text: $overlayColor)
.textFieldStyle(RoundedBorderTextFieldStyle())
Rectangle()
.fill(Color(hex: overlayColor) ?? Color.gray)
.frame(width: 40, height: 40)
.cornerRadius(8)
}
}
}
.padding(.horizontal)

VStack(alignment: .leading) {
Text("Blend Mode")
.font(.headline)
.padding(.horizontal)

Picker("Blend Mode", selection: $selectedBlendMode) {
ForEach(ColorsKit.BlendMode.allCases, id: \.self) { mode in
Text(mode.rawValue.capitalized)
.tag(mode)
}
}
.pickerStyle(MenuPickerStyle())
.padding(.horizontal)
}

Button("Blend Colors") {
blendColors()
}
.buttonStyle(.borderedProminent)
}

// Blended Result
VStack(spacing: 16) {
Text("Blended Result")
.font(.headline)

HStack(spacing: 16) {
VStack {
Rectangle()
.fill(Color(hex: baseColor) ?? Color.gray)
.frame(width: 80, height: 80)
.cornerRadius(12)
Text("Base")
.font(.caption)
}

Text("+")
.font(.title2)
.fontWeight(.bold)

VStack {
Rectangle()
.fill(Color(hex: overlayColor) ?? Color.gray)
.frame(width: 80, height: 80)
.cornerRadius(12)
Text("Overlay")
.font(.caption)
}

Text("=")
.font(.title2)
.fontWeight(.bold)

VStack {
Rectangle()
.fill(Color(hex: blendedResult) ?? Color.gray)
.frame(width: 80, height: 80)
.cornerRadius(12)
Text(selectedBlendMode.rawValue.capitalized)
.font(.caption)
}
}

Text("Result: \(blendedResult)")
.font(.monospaced(.body)())
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
.padding(.horizontal)
}

// Blend Mode Gallery
VStack(alignment: .leading, spacing: 16) {
Text("Blend Mode Gallery")
.font(.headline)
.padding(.horizontal)

LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 3), spacing: 12) {
ForEach(ColorsKit.BlendMode.allCases, id: \.self) { mode in
VStack(spacing: 8) {
Rectangle()
.fill(Color(hex: getBlendedColor(mode: mode)) ?? Color.gray)
.frame(height: 60)
.cornerRadius(8)

Text(mode.rawValue.capitalized)
.font(.caption)
.fontWeight(.medium)
.multilineTextAlignment(.center)
}
.onTapGesture {
selectedBlendMode = mode
blendColors()
}
}
}
.padding(.horizontal)
}

Spacer()
}
}
.onAppear {
blendColors()
}
.onChange(of: baseColor) { _ in blendColors() }
.onChange(of: overlayColor) { _ in blendColors() }
.onChange(of: selectedBlendMode) { _ in blendColors() }
}

private func blendColors() {
guard let base = try? HexColorFormatter.parse(baseColor),
let overlay = try? HexColorFormatter.parse(overlayColor) else {
return
}

let blended = AdvancedBlending.blend(overlay, base, mode: selectedBlendMode)
blendedResult = HexColorFormatter.format(blended) ?? "#000000"
}

private func getBlendedColor(mode: ColorsKit.BlendMode) -> String {
guard let base = try? HexColorFormatter.parse(baseColor),
let overlay = try? HexColorFormatter.parse(overlayColor) else {
return "#000000"
}

let blended = AdvancedBlending.blend(overlay, base, mode: mode)
return HexColorFormatter.format(blended) ?? "#000000"
}
}



#Preview {
BlendingModesView()
}
134 changes: 134 additions & 0 deletions Example/iOSAppDemo/ColorsKitDemo/ColorHarmonyView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import SwiftUI
import ColorsKit

struct ColorHarmonyView: View {
@Binding var baseColor: String
@Binding var selectedHarmony: ColorHarmonyType
@State private var generatedColors: [String] = []

var body: some View {
ScrollView {
VStack(spacing: 20) {
Text("Color Harmony Generator")
.font(.title2)
.fontWeight(.bold)
.padding(.top)

// Base Color Input
VStack(alignment: .leading, spacing: 8) {
Text("Base Color")
.font(.headline)

HStack {
TextField("Enter hex color", text: $baseColor)
.textFieldStyle(RoundedBorderTextFieldStyle())
.onChange(of: baseColor) { _ in
generateHarmony()
}

Rectangle()
.fill(Color(hex: baseColor) ?? Color.gray)
.frame(width: 40, height: 40)
.cornerRadius(8)
}
}
.padding(.horizontal)

// Harmony Type Picker
VStack(alignment: .leading, spacing: 8) {
Text("Harmony Type")
.font(.headline)

Picker("Harmony Type", selection: $selectedHarmony) {
Text("Complementary").tag(ColorHarmonyType.complementary)
Text("Analogous").tag(ColorHarmonyType.analogous)
Text("Triadic").tag(ColorHarmonyType.triadic)
Text("Tetradic").tag(ColorHarmonyType.tetradic)
Text("Split Complementary").tag(ColorHarmonyType.splitComplementary)
Text("Monochromatic").tag(ColorHarmonyType.monochromatic)
}
.pickerStyle(SegmentedPickerStyle())
.onChange(of: selectedHarmony) { _ in
generateHarmony()
}
}
.padding(.horizontal)

// Generated Colors Display
if !generatedColors.isEmpty {
VStack(alignment: .leading, spacing: 12) {
Text("Generated Harmony")
.font(.headline)
.padding(.horizontal)

LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 3), spacing: 12) {
ForEach(generatedColors, id: \.self) { colorHex in
VStack(spacing: 4) {
Rectangle()
.fill(Color(hex: colorHex) ?? Color.gray)
.frame(height: 80)
.cornerRadius(8)

Text(colorHex)
.font(.caption)
.fontWeight(.medium)
}
}
}
.padding(.horizontal)

// Accessibility Information
VStack(alignment: .leading, spacing: 8) {
Text("Accessibility Analysis")
.font(.headline)
.padding(.horizontal)

ForEach(Array(generatedColors.enumerated()), id: \.offset) { index, colorHex in
HStack {
Rectangle()
.fill(Color(hex: colorHex) ?? Color.gray)
.frame(width: 20, height: 20)
.cornerRadius(4)

Text(colorHex)
.font(.caption)
.fontWeight(.medium)

Spacer()

if let contrastRatio = getContrastRatio(colorHex, "#FFFFFF") {
Text("AA: \(contrastRatio >= 4.5 ? "✓" : "✗")")
.font(.caption)
.foregroundColor(contrastRatio >= 4.5 ? .green : .red)
}
}
.padding(.horizontal)
}
}
}
}

Spacer()
}
}
.onAppear {
generateHarmony()
}
}

private func generateHarmony() {
generatedColors = ColorHarmonyGenerator.generateHarmony(from: baseColor, type: selectedHarmony)
}

private func getContrastRatio(_ foreground: String, _ background: String) -> Double? {
guard let fg = try? HexColorFormatter.parse(foreground),
let bg = try? HexColorFormatter.parse(background) else {
return nil
}
return ColorMath.contrastRatio(fg, bg)
}
}

#Preview {
ColorHarmonyView(baseColor: .constant("#0A84FF"), selectedHarmony: .constant(.complementary))
}
Loading