| 1 | import SwiftUI |
| 2 | |
| 3 | enum WatchKleponTypography { |
| 4 | static let heroTitle = Font.system(size: 22, weight: .semibold, design: .serif) |
| 5 | static let sectionTitle = Font.system(size: 16, weight: .semibold, design: .serif) |
| 6 | static let cardTitle = Font.system(size: 17, weight: .semibold, design: .default) |
| 7 | static let body = Font.system(size: 14, weight: .regular, design: .default) |
| 8 | static let bodySecondary = Font.system(size: 12, weight: .regular, design: .default) |
| 9 | static let caption = Font.system(size: 11, weight: .semibold, design: .default) |
| 10 | } |
| 11 | |
| 12 | struct WatchKleponCard<Content: View>: View { |
| 13 | let padding: CGFloat |
| 14 | @ViewBuilder var content: Content |
| 15 | |
| 16 | init(padding: CGFloat = 14, @ViewBuilder content: () -> Content) { |
| 17 | self.padding = padding |
| 18 | self.content = content() |
| 19 | } |
| 20 | |
| 21 | var body: some View { |
| 22 | content |
| 23 | .padding(padding) |
| 24 | .frame(maxWidth: .infinity, alignment: .leading) |
| 25 | .background( |
| 26 | RoundedRectangle(cornerRadius: 22, style: .continuous) |
| 27 | .fill(KleponColor.surface) |
| 28 | ) |
| 29 | .overlay( |
| 30 | RoundedRectangle(cornerRadius: 22, style: .continuous) |
| 31 | .stroke(KleponColor.divider, lineWidth: 1) |
| 32 | ) |
| 33 | } |
| 34 | } |
| 35 | |
| 36 | struct WatchKleponSectionHeader: View { |
| 37 | let title: String |
| 38 | let subtitle: String? |
| 39 | |
| 40 | init(_ title: String, subtitle: String? = nil) { |
| 41 | self.title = title |
| 42 | self.subtitle = subtitle |
| 43 | } |
| 44 | |
| 45 | var body: some View { |
| 46 | VStack(alignment: .leading, spacing: 3) { |
| 47 | Text(title) |
| 48 | .font(WatchKleponTypography.sectionTitle) |
| 49 | .foregroundStyle(KleponColor.textPrimary) |
| 50 | |
| 51 | if let subtitle { |
| 52 | Text(subtitle) |
| 53 | .font(WatchKleponTypography.bodySecondary) |
| 54 | .foregroundStyle(KleponColor.textSecondary) |
| 55 | } |
| 56 | } |
| 57 | .frame(maxWidth: .infinity, alignment: .leading) |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | struct WatchKleponChip: View { |
| 62 | let title: String |
| 63 | var icon: String? = nil |
| 64 | |
| 65 | var body: some View { |
| 66 | HStack(spacing: 5) { |
| 67 | if let icon { |
| 68 | Image(systemName: icon) |
| 69 | .font(.system(size: 10, weight: .semibold)) |
| 70 | } |
| 71 | |
| 72 | Text(title) |
| 73 | .font(WatchKleponTypography.caption) |
| 74 | .lineLimit(1) |
| 75 | .minimumScaleFactor(0.8) |
| 76 | } |
| 77 | .foregroundStyle(KleponColor.textPrimary) |
| 78 | .padding(.horizontal, 10) |
| 79 | .padding(.vertical, 6) |
| 80 | .background(Capsule().fill(KleponColor.chipBackground)) |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | struct WatchKleponChipGrid: View { |
| 85 | let items: [String] |
| 86 | |
| 87 | var body: some View { |
| 88 | LazyVGrid( |
| 89 | columns: [GridItem(.adaptive(minimum: 72), spacing: 8)], |
| 90 | alignment: .leading, |
| 91 | spacing: 8 |
| 92 | ) { |
| 93 | ForEach(items, id: \.self) { item in |
| 94 | WatchKleponChip(title: item) |
| 95 | } |
| 96 | } |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | struct WatchKleponFooterCard: View { |
| 101 | let message: String |
| 102 | |
| 103 | var body: some View { |
| 104 | WatchKleponCard { |
| 105 | Text(message) |
| 106 | .font(WatchKleponTypography.bodySecondary) |
| 107 | .foregroundStyle(KleponColor.textSecondary) |
| 108 | .lineSpacing(2) |
| 109 | } |
| 110 | } |
| 111 | } |