Initial commit: KisaniCal iOS app
Task management, Eisenhower matrix, workout logging with persistence, and calendar with 6 view modes (Month, List, Year, Week, 3 Day, Day). Custom floating tab bar, dynamic calendar icon, and workout data seeded.
This commit is contained in:
184
KisaniCal/Components/SharedComponents.swift
Normal file
184
KisaniCal/Components/SharedComponents.swift
Normal file
@@ -0,0 +1,184 @@
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Press Button Styles
|
||||
struct PressButtonStyle: ButtonStyle {
|
||||
var scale: CGFloat = 0.95
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.scaleEffect(configuration.isPressed ? scale : 1.0)
|
||||
.animation(KisaniSpring.micro, value: configuration.isPressed)
|
||||
}
|
||||
}
|
||||
|
||||
struct FabButtonStyle: ButtonStyle {
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.scaleEffect(configuration.isPressed ? 0.88 : 1.0)
|
||||
.opacity(configuration.isPressed ? 0.92 : 1.0)
|
||||
.animation(KisaniSpring.micro, value: configuration.isPressed)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Icon Button (circular)
|
||||
struct IButton: View {
|
||||
let icon: String
|
||||
var action: (() -> Void)? = nil
|
||||
@Environment(\.colorScheme) private var cs
|
||||
|
||||
var body: some View {
|
||||
Button { action?() } label: {
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 13, weight: .medium))
|
||||
.foregroundColor(AppColors.text2(cs))
|
||||
.frame(width: 34, height: 34)
|
||||
.background(AppColors.surface2(cs))
|
||||
.clipShape(Circle())
|
||||
.overlay(Circle().stroke(AppColors.border(cs), lineWidth: 1))
|
||||
}
|
||||
.buttonStyle(PressButtonStyle(scale: 0.92))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Month Pill
|
||||
struct MonthPill: View {
|
||||
let title: String
|
||||
var action: (() -> Void)? = nil
|
||||
@Environment(\.colorScheme) private var cs
|
||||
|
||||
var body: some View {
|
||||
Button { action?() } label: {
|
||||
HStack(spacing: 6) {
|
||||
Text(title)
|
||||
.font(AppFonts.sans(12.5, weight: .medium))
|
||||
.foregroundColor(AppColors.text(cs))
|
||||
Image(systemName: "chevron.down")
|
||||
.font(.system(size: 9, weight: .semibold))
|
||||
.foregroundColor(AppColors.text3(cs))
|
||||
}
|
||||
.padding(.horizontal, 13)
|
||||
.padding(.vertical, 6)
|
||||
.background(AppColors.surface2(cs))
|
||||
.clipShape(Capsule())
|
||||
.overlay(Capsule().stroke(AppColors.border(cs), lineWidth: 1))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Floating Action Button
|
||||
struct FAB: View {
|
||||
var action: (() -> Void)? = nil
|
||||
|
||||
var body: some View {
|
||||
Button { action?() } label: {
|
||||
Image(systemName: "plus")
|
||||
.font(.system(size: 20, weight: .light))
|
||||
.foregroundColor(.white)
|
||||
.frame(width: 46, height: 46)
|
||||
.background(AppColors.accent)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: AppColors.accent.opacity(0.45), radius: 12, x: 0, y: 4)
|
||||
.shadow(color: AppColors.accent.opacity(0.14), radius: 30, x: 0, y: 0)
|
||||
}
|
||||
.buttonStyle(FabButtonStyle())
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Section Label
|
||||
struct SectionLabel: View {
|
||||
let text: String
|
||||
@Environment(\.colorScheme) private var cs
|
||||
|
||||
var body: some View {
|
||||
Text(text)
|
||||
.font(AppFonts.sans(10.5, weight: .semibold))
|
||||
.foregroundColor(AppColors.text3(cs))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Badge
|
||||
struct AppBadge: View {
|
||||
let text: String
|
||||
let bg: Color
|
||||
let fg: Color
|
||||
|
||||
var body: some View {
|
||||
Text(text)
|
||||
.font(AppFonts.sans(9, weight: .semibold))
|
||||
.foregroundColor(fg)
|
||||
.padding(.horizontal, 7)
|
||||
.padding(.vertical, 3)
|
||||
.background(bg)
|
||||
.cornerRadius(5)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Dashed Add Button
|
||||
struct DashedAddButton: View {
|
||||
let label: String
|
||||
let action: () -> Void
|
||||
@Environment(\.colorScheme) private var cs
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "plus")
|
||||
.font(.system(size: 14))
|
||||
.foregroundColor(AppColors.text3(cs))
|
||||
Text(label)
|
||||
.font(AppFonts.sans(12, weight: .semibold))
|
||||
.foregroundColor(AppColors.text3(cs))
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(11)
|
||||
.background(Color.clear)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: AppRadius.large)
|
||||
.strokeBorder(style: StrokeStyle(lineWidth: 1.5, dash: [5, 4]))
|
||||
.foregroundColor(AppColors.borderHi(cs))
|
||||
)
|
||||
}
|
||||
.buttonStyle(PressButtonStyle())
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Tag Chip
|
||||
struct TagChip: View {
|
||||
let text: String
|
||||
let color: Color
|
||||
let bg: Color
|
||||
|
||||
var body: some View {
|
||||
Text(text)
|
||||
.font(AppFonts.sans(9, weight: .semibold))
|
||||
.foregroundColor(color)
|
||||
.padding(.horizontal, 7)
|
||||
.padding(.vertical, 3)
|
||||
.background(bg)
|
||||
.cornerRadius(5)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Card style modifier
|
||||
struct CardStyle: ViewModifier {
|
||||
@Environment(\.colorScheme) private var cs
|
||||
var radius: CGFloat = AppRadius.large
|
||||
var borderColor: Color? = nil
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.background(AppColors.surface(cs))
|
||||
.clipShape(RoundedRectangle(cornerRadius: radius))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: radius)
|
||||
.stroke(borderColor ?? AppColors.border(cs), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
func cardStyle(radius: CGFloat = AppRadius.large, borderColor: Color? = nil) -> some View {
|
||||
modifier(CardStyle(radius: radius, borderColor: borderColor))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user