Apply Emil Kowalski design across all screens

Near-monochrome palette (ink tokens), black primary buttons, spring
animations, 0.5pt hairline dividers, shadow cards, squircle corners.
Replaces all blue/coloured accents. Adds ButtonStyles component.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Robin Kutesa
2026-05-16 00:43:31 +03:00
parent 848c1da820
commit 04e7a77c1a
14 changed files with 889 additions and 625 deletions

View File

@@ -0,0 +1,63 @@
import SwiftUI
// MARK: Primary (black fill)
struct PrimaryButtonStyle: ButtonStyle {
var color: Color = AppTheme.ink
var height: CGFloat = 50
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.system(size: 15, weight: .medium))
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.frame(height: height)
.background(color)
.clipShape(RoundedRectangle(cornerRadius: AppTheme.radius, style: .continuous))
.scaleEffect(configuration.isPressed ? 0.97 : 1)
.animation(.spring(response: 0.25, dampingFraction: 0.7), value: configuration.isPressed)
}
}
// MARK: Ghost (outline)
struct GhostButtonStyle: ButtonStyle {
var height: CGFloat = 50
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.system(size: 15, weight: .medium))
.foregroundColor(AppTheme.ink)
.frame(maxWidth: .infinity)
.frame(height: height)
.overlay(
RoundedRectangle(cornerRadius: AppTheme.radius, style: .continuous)
.stroke(AppTheme.cardBorderColor, lineWidth: 1)
)
.scaleEffect(configuration.isPressed ? 0.97 : 1)
.animation(.spring(response: 0.25, dampingFraction: 0.7), value: configuration.isPressed)
}
}
// MARK: Scale (tap anywhere, just scales)
struct ScaleButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.scaleEffect(configuration.isPressed ? 0.97 : 1)
.animation(.spring(response: 0.25, dampingFraction: 0.7), value: configuration.isPressed)
}
}
// MARK: Pill (compact, rounded)
struct PillButtonStyle: ButtonStyle {
var color: Color = AppTheme.ink
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.system(size: 13, weight: .medium))
.foregroundColor(.white)
.padding(.horizontal, 14)
.padding(.vertical, 7)
.background(color)
.clipShape(Capsule())
.scaleEffect(configuration.isPressed ? 0.96 : 1)
.animation(.spring(response: 0.2, dampingFraction: 0.7), value: configuration.isPressed)
}
}

View File

@@ -8,31 +8,37 @@ struct FolderRow: View {
var body: some View {
Button(action: onTap) {
HStack(spacing: 12) {
Image(systemName: item.isDirectory ? "folder.fill" : "doc.fill")
.font(.system(size: 18))
.foregroundColor(isSelected ? .white : (item.isDirectory ? AppTheme.blue : AppTheme.textSecondary))
Image(systemName: item.isDirectory ? "folder" : "doc")
.font(.system(size: 15, weight: .regular))
.foregroundStyle(isSelected ? .white : AppTheme.inkSecondary)
.frame(width: 22)
Text(item.name)
.font(AppTheme.bodyFont)
.fontWeight(isSelected ? .semibold : .regular)
.foregroundColor(isSelected ? .white : AppTheme.textPrimary)
.font(.system(size: 15, weight: isSelected ? .medium : .regular))
.foregroundStyle(isSelected ? .white : AppTheme.ink)
.lineLimit(1)
Spacer()
if isSelected {
Image(systemName: "checkmark")
.font(.system(size: 13, weight: .bold))
.foregroundColor(.white)
.font(.system(size: 12, weight: .semibold))
.foregroundStyle(.white)
} else if item.isDirectory {
Image(systemName: "chevron.right")
.font(.system(size: 13, weight: .medium))
.foregroundColor(AppTheme.textTertiary)
.font(.system(size: 12, weight: .medium))
.foregroundStyle(AppTheme.inkQuaternary)
}
}
.padding(.horizontal, 16)
.padding(.vertical, 12)
.background(isSelected ? AppTheme.blue : Color.clear)
.background(
isSelected
? AppTheme.ink
: Color.clear
)
.animation(.spring(response: 0.2, dampingFraction: 0.8), value: isSelected)
}
.buttonStyle(.plain)
.buttonStyle(PlainButtonStyle())
}
}

View File

@@ -2,24 +2,21 @@ import SwiftUI
struct ProgressRing: View {
let progress: Double
var lineWidth: CGFloat = 14
var lineWidth: CGFloat = 8
var size: CGFloat = 200
var color: Color = AppTheme.blue
var backgroundColor: Color = Color(hex: "#E5E7EB")
var color: Color = AppTheme.ink
var backgroundColor: Color = Color(hex: "#EBEBEB")
var body: some View {
ZStack {
Circle()
.stroke(backgroundColor, lineWidth: lineWidth)
.stroke(backgroundColor, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round))
Circle()
.trim(from: 0, to: CGFloat(min(progress, 1.0)))
.stroke(
color,
style: StrokeStyle(lineWidth: lineWidth, lineCap: .round)
)
.stroke(color, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round))
.rotationEffect(.degrees(-90))
.animation(.easeInOut(duration: 0.4), value: progress)
.animation(.spring(response: 0.5, dampingFraction: 0.8), value: progress)
}
.frame(width: size, height: size)
}

View File

@@ -9,18 +9,18 @@ struct ToggleRow: View {
var body: some View {
HStack(spacing: 12) {
Image(systemName: icon)
.font(.system(size: 16, weight: .medium))
.foregroundColor(AppTheme.blue)
.frame(width: 24)
.font(.system(size: 14, weight: .medium))
.foregroundStyle(AppTheme.inkSecondary)
.frame(width: 20)
VStack(alignment: .leading, spacing: 2) {
VStack(alignment: .leading, spacing: 1) {
Text(title)
.font(AppTheme.bodyFont)
.foregroundColor(AppTheme.textPrimary)
.font(AppTheme.body())
.foregroundStyle(AppTheme.ink)
if let subtitle {
Text(subtitle)
.font(AppTheme.captionFont)
.foregroundColor(AppTheme.textSecondary)
.font(AppTheme.caption())
.foregroundStyle(AppTheme.inkTertiary)
}
}
@@ -28,8 +28,8 @@ struct ToggleRow: View {
Toggle("", isOn: $isOn)
.labelsHidden()
.tint(AppTheme.blue)
.tint(AppTheme.ink)
}
.padding(.vertical, 4)
.padding(.vertical, 2)
}
}