- Remove AppColors struct, migrate to iOS system colors throughout - Redesign ContractorFormSheet to use native SwiftUI Form components - Add color-coded icons to contractor form sections - Improve dark mode contrast for task cards - Add background colors to document detail fields - Fix text alignment issues in ContractorDetailView - Make task completion lists expandable/collapsible by default - Clear app badge on launch and when app becomes active - Update button styling with proper gradients and shadows - Improve form field focus states and accessibility 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
147 lines
4.3 KiB
Swift
147 lines
4.3 KiB
Swift
import SwiftUI
|
|
|
|
// MARK: - Design System
|
|
// Modern, sleek design system for MyCrib with Light and Dark mode support
|
|
|
|
struct AppSpacing {
|
|
static let xxs: CGFloat = 4
|
|
static let xs: CGFloat = 8
|
|
static let sm: CGFloat = 12
|
|
static let md: CGFloat = 16
|
|
static let lg: CGFloat = 24
|
|
static let xl: CGFloat = 32
|
|
static let xxl: CGFloat = 48
|
|
static let xxxl: CGFloat = 64
|
|
}
|
|
|
|
struct AppRadius {
|
|
static let xs: CGFloat = 4
|
|
static let sm: CGFloat = 8
|
|
static let md: CGFloat = 12
|
|
static let lg: CGFloat = 16
|
|
static let xl: CGFloat = 20
|
|
static let xxl: CGFloat = 24
|
|
static let full: CGFloat = 9999
|
|
}
|
|
|
|
struct AppShadow {
|
|
static let sm = Shadow(color: .black.opacity(0.05), radius: 2, y: 1)
|
|
static let md = Shadow(color: .black.opacity(0.1), radius: 4, y: 2)
|
|
static let lg = Shadow(color: .black.opacity(0.1), radius: 8, y: 4)
|
|
static let xl = Shadow(color: .black.opacity(0.15), radius: 16, y: 8)
|
|
|
|
struct Shadow {
|
|
let color: Color
|
|
let radius: CGFloat
|
|
let x: CGFloat
|
|
let y: CGFloat
|
|
|
|
init(color: Color, radius: CGFloat, x: CGFloat = 0, y: CGFloat) {
|
|
self.color = color
|
|
self.radius = radius
|
|
self.x = x
|
|
self.y = y
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - View Modifiers
|
|
|
|
struct CardStyle: ViewModifier {
|
|
var shadow: AppShadow.Shadow = AppShadow.md
|
|
var padding: CGFloat = AppSpacing.md
|
|
|
|
func body(content: Content) -> some View {
|
|
content
|
|
.background(Color(.secondarySystemGroupedBackground))
|
|
.cornerRadius(AppRadius.lg)
|
|
.shadow(color: shadow.color, radius: shadow.radius, x: shadow.x, y: shadow.y)
|
|
}
|
|
}
|
|
|
|
struct PrimaryButtonStyle: ButtonStyle {
|
|
var isLoading: Bool = false
|
|
|
|
func makeBody(configuration: Configuration) -> some View {
|
|
configuration.label
|
|
.font(.headline)
|
|
.foregroundColor(.white)
|
|
.frame(maxWidth: .infinity)
|
|
.frame(height: 56)
|
|
.background(
|
|
configuration.isPressed ? .blue : .blue
|
|
)
|
|
.cornerRadius(AppRadius.md)
|
|
.scaleEffect(configuration.isPressed ? 0.98 : 1.0)
|
|
.animation(.easeInOut(duration: 0.1), value: configuration.isPressed)
|
|
}
|
|
}
|
|
|
|
struct SecondaryButtonStyle: ButtonStyle {
|
|
func makeBody(configuration: Configuration) -> some View {
|
|
configuration.label
|
|
.font(.headline)
|
|
.foregroundColor(.blue)
|
|
.frame(maxWidth: .infinity)
|
|
.frame(height: 56)
|
|
.background(Color(.tertiarySystemGroupedBackground))
|
|
.cornerRadius(AppRadius.md)
|
|
.scaleEffect(configuration.isPressed ? 0.98 : 1.0)
|
|
.animation(.easeInOut(duration: 0.1), value: configuration.isPressed)
|
|
}
|
|
}
|
|
|
|
struct TextFieldStyle: ViewModifier {
|
|
func body(content: Content) -> some View {
|
|
content
|
|
.padding(AppSpacing.md)
|
|
.background(Color(.tertiarySystemGroupedBackground))
|
|
.cornerRadius(AppRadius.md)
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: AppRadius.md)
|
|
.stroke(Color(.opaqueSeparator), lineWidth: 1)
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - View Extensions
|
|
|
|
extension View {
|
|
func cardStyle(shadow: AppShadow.Shadow = AppShadow.md) -> some View {
|
|
modifier(CardStyle(shadow: shadow))
|
|
}
|
|
|
|
func textFieldStyle() -> some View {
|
|
modifier(TextFieldStyle())
|
|
}
|
|
}
|
|
|
|
// MARK: - Color Extension for Hex Support
|
|
|
|
extension Color {
|
|
init?(hex: String) {
|
|
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
|
|
var int: UInt64 = 0
|
|
Scanner(string: hex).scanHexInt64(&int)
|
|
let a, r, g, b: UInt64
|
|
switch hex.count {
|
|
case 3: // RGB (12-bit)
|
|
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
|
|
case 6: // RGB (24-bit)
|
|
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
|
|
case 8: // ARGB (32-bit)
|
|
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
|
|
default:
|
|
return nil
|
|
}
|
|
|
|
self.init(
|
|
.sRGB,
|
|
red: Double(r) / 255,
|
|
green: Double(g) / 255,
|
|
blue: Double(b) / 255,
|
|
opacity: Double(a) / 255
|
|
)
|
|
}
|
|
}
|