Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2026-01-27 09:50:42 -06:00
parent 23b9c90871
commit 76dda638cc
20 changed files with 107 additions and 176 deletions

View File

@ -21,16 +21,17 @@ struct EmptyStateCardView: View {
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: Design.Spacing.medium) { VStack(alignment: .leading, spacing: Design.Spacing.medium) {
Text(title) TitleLabel(title, style: .displaySmall, color: AppTextColors.primary)
.font(.title3)
.foregroundStyle(AppTextColors.primary) // Exception: Uses .bodyLarge which isn't a standard BodyLabel weight
.bold()
Text(message) Text(message)
.font(.body) .font(Design.Typography.bodyLarge)
.foregroundStyle(AppTextColors.secondary) .foregroundStyle(AppTextColors.secondary)
Button(action: action) { Button(action: action) {
// Exception: Needs .frame() modifiers
Text(actionTitle) Text(actionTitle)
.font(.headline) .font(Design.Typography.headline)
.foregroundStyle(AppTextColors.primary) .foregroundStyle(AppTextColors.primary)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.frame(height: AppMetrics.Size.buttonHeight) .frame(height: AppMetrics.Size.buttonHeight)

View File

@ -12,13 +12,9 @@ struct SectionHeaderView: View {
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: Design.Spacing.xxxSmall) { VStack(alignment: .leading, spacing: Design.Spacing.xxxSmall) {
Text(title) TitleLabel(title, style: .headline, color: AppTextColors.primary)
.font(.headline)
.foregroundStyle(AppTextColors.primary)
if let subtitle { if let subtitle {
Text(subtitle) BodyLabel(subtitle, emphasis: .secondary, color: AppTextColors.secondary)
.font(.subheadline)
.foregroundStyle(AppTextColors.secondary)
} }
} }
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)

View File

@ -22,31 +22,21 @@ struct InsightCardView: View {
private var cardContent: some View { private var cardContent: some View {
VStack(alignment: .leading, spacing: Design.Spacing.medium) { VStack(alignment: .leading, spacing: Design.Spacing.medium) {
HStack { HStack {
Text(card.title) BodyLabel(card.title, weight: .bold, color: AppTextColors.secondary)
.font(.subheadline)
.bold()
.foregroundStyle(AppTextColors.secondary)
Spacer() Spacer()
// Subtle tap affordance // Subtle tap affordance
Image(systemName: "chevron.right") SymbolIcon.chevron(color: AppTextColors.tertiary)
.font(.caption2)
.foregroundStyle(AppTextColors.tertiary)
.accessibilityHidden(true) .accessibilityHidden(true)
} }
// Show value prominently // Show value prominently
Text(card.value) TitleLabel(card.value, style: .displayMedium, color: AppTextColors.primary)
.font(.title)
.foregroundStyle(AppTextColors.primary)
.bold()
// Show caption if present (non-chart cards) // Show caption if present (non-chart cards)
if !card.caption.isEmpty { if !card.caption.isEmpty {
Text(card.caption) CaptionLabel(card.caption, color: AppTextColors.secondary)
.font(.caption)
.foregroundStyle(AppTextColors.secondary)
} }
// Show full-width mini bar chart at bottom (Athlytic style) // Show full-width mini bar chart at bottom (Athlytic style)

View File

@ -81,17 +81,15 @@ struct InsightDetailSheet: View {
private var headerSection: some View { private var headerSection: some View {
VStack(spacing: Design.Spacing.medium) { VStack(spacing: Design.Spacing.medium) {
Image(systemName: card.symbolName) SymbolIcon(card.symbolName, size: .hero, color: AppAccent.primary)
.font(.system(size: Design.IconSize.hero))
.foregroundStyle(AppAccent.primary)
.accessibilityHidden(true) .accessibilityHidden(true)
Text(card.value) Text(card.value)
.font(.system(size: Design.IconSize.hero, weight: .bold)) .font(.system(size: Design.SymbolSize.hero, weight: .bold))
.foregroundStyle(AppTextColors.primary) .foregroundStyle(AppTextColors.primary)
Text(card.caption) Text(card.caption)
.font(.subheadline) .font(Design.Typography.body)
.foregroundStyle(AppTextColors.secondary) .foregroundStyle(AppTextColors.secondary)
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)

View File

@ -146,9 +146,7 @@ struct FirstCheckInStepView: View {
Spacer() Spacer()
// Success icon // Success icon
Image(systemName: "checkmark.circle.fill") SymbolIcon("checkmark.circle.fill", size: .hero, color: AppStatus.success)
.font(.system(size: Design.IconSize.hero))
.foregroundStyle(AppStatus.success)
.scaleEffect(showCelebration ? 1 : 0.5) .scaleEffect(showCelebration ? 1 : 0.5)
.opacity(showCelebration ? 1 : 0) .opacity(showCelebration ? 1 : 0)

View File

@ -21,8 +21,7 @@ struct GoalSelectionStepView: View {
// Header // Header
VStack(spacing: Design.Spacing.small) { VStack(spacing: Design.Spacing.small) {
Text(String(localized: "What would you like to focus on?")) Text(String(localized: "What would you like to focus on?"))
.font(.title2) .font(Design.Typography.displaySmall)
.fontWeight(.bold)
.foregroundStyle(AppTextColors.primary) .foregroundStyle(AppTextColors.primary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
} }
@ -56,7 +55,7 @@ struct GoalSelectionStepView: View {
if !selectedGoals.isEmpty { if !selectedGoals.isEmpty {
Button(action: onContinue) { Button(action: onContinue) {
Text(String(localized: "Continue")) Text(String(localized: "Continue"))
.font(.headline) .font(Design.Typography.headline)
.foregroundStyle(AppTextColors.inverse) .foregroundStyle(AppTextColors.inverse)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.frame(height: AppMetrics.Size.buttonHeight) .frame(height: AppMetrics.Size.buttonHeight)
@ -95,18 +94,16 @@ private struct GoalCardView: View {
Button(action: onTap) { Button(action: onTap) {
VStack(spacing: Design.Spacing.medium) { VStack(spacing: Design.Spacing.medium) {
// Icon // Icon
Image(systemName: goal.symbolName) SymbolIcon(goal.symbolName, size: .card, color: isSelected ? AppAccent.primary : AppTextColors.secondary)
.font(.system(size: Design.IconSize.xxLarge))
.foregroundStyle(isSelected ? AppAccent.primary : AppTextColors.secondary)
// Text // Text
VStack(spacing: Design.Spacing.xSmall) { VStack(spacing: Design.Spacing.xSmall) {
Text(goal.displayName) Text(goal.displayName)
.font(.headline) .font(Design.Typography.headline)
.foregroundStyle(AppTextColors.primary) .foregroundStyle(AppTextColors.primary)
Text(goal.subtitle) Text(goal.subtitle)
.font(.caption) .font(Design.Typography.caption)
.foregroundStyle(AppTextColors.secondary) .foregroundStyle(AppTextColors.secondary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.lineLimit(2) .lineLimit(2)

View File

@ -16,8 +16,7 @@ struct TimeSelectionStepView: View {
// Header // Header
VStack(spacing: Design.Spacing.small) { VStack(spacing: Design.Spacing.small) {
Text(String(localized: "When do you want to build habits?")) Text(String(localized: "When do you want to build habits?"))
.font(.title2) .font(Design.Typography.displaySmall)
.fontWeight(.bold)
.foregroundStyle(AppTextColors.primary) .foregroundStyle(AppTextColors.primary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
} }
@ -51,7 +50,7 @@ struct TimeSelectionStepView: View {
if selectedTime != nil { if selectedTime != nil {
Button(action: onContinue) { Button(action: onContinue) {
Text(String(localized: "Continue")) Text(String(localized: "Continue"))
.font(.headline) .font(Design.Typography.headline)
.foregroundStyle(AppTextColors.inverse) .foregroundStyle(AppTextColors.inverse)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.frame(height: AppMetrics.Size.buttonHeight) .frame(height: AppMetrics.Size.buttonHeight)
@ -82,19 +81,17 @@ private struct TimeCardView: View {
Button(action: onTap) { Button(action: onTap) {
HStack(spacing: Design.Spacing.medium) { HStack(spacing: Design.Spacing.medium) {
// Icon // Icon
Image(systemName: time.symbolName) SymbolIcon(time.symbolName, size: .rowContainer, color: isSelected ? AppAccent.primary : AppTextColors.secondary)
.font(.system(size: Design.IconSize.xLarge)) .frame(width: Design.Size.actionRowMinHeight)
.foregroundStyle(isSelected ? AppAccent.primary : AppTextColors.secondary)
.frame(width: 44)
// Text // Text
VStack(alignment: .leading, spacing: Design.Spacing.xSmall) { VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
Text(time.displayName) Text(time.displayName)
.font(.headline) .font(Design.Typography.headline)
.foregroundStyle(AppTextColors.primary) .foregroundStyle(AppTextColors.primary)
Text(time.subtitle) Text(time.subtitle)
.font(.subheadline) .font(Design.Typography.body)
.foregroundStyle(AppTextColors.secondary) .foregroundStyle(AppTextColors.secondary)
} }
@ -102,9 +99,7 @@ private struct TimeCardView: View {
// Selection indicator // Selection indicator
if isSelected { if isSelected {
Image(systemName: "checkmark.circle.fill") SymbolIcon("checkmark.circle.fill", size: .rowContainer, color: AppAccent.primary)
.font(.title2)
.foregroundStyle(AppAccent.primary)
} }
} }
.padding(Design.Spacing.large) .padding(Design.Spacing.large)

View File

@ -21,13 +21,12 @@ struct WelcomeStepView: View {
VStack(spacing: Design.Spacing.medium) { VStack(spacing: Design.Spacing.medium) {
Text(String(localized: "Welcome to Rituals")) Text(String(localized: "Welcome to Rituals"))
.font(.largeTitle) .font(Design.Typography.displayLarge)
.fontWeight(.bold)
.foregroundStyle(AppTextColors.primary) .foregroundStyle(AppTextColors.primary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
Text(String(localized: "Build lasting habits through focused, time-bound journeys")) Text(String(localized: "Build lasting habits through focused, time-bound journeys"))
.font(.title3) .font(Design.Typography.bodyLarge)
.foregroundStyle(AppTextColors.secondary) .foregroundStyle(AppTextColors.secondary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.padding(.horizontal, Design.Spacing.xxLarge) .padding(.horizontal, Design.Spacing.xxLarge)
@ -40,7 +39,7 @@ struct WelcomeStepView: View {
// Get Started button // Get Started button
Button(action: onContinue) { Button(action: onContinue) {
Text(String(localized: "Get Started")) Text(String(localized: "Get Started"))
.font(.headline) .font(Design.Typography.headline)
.foregroundStyle(AppTextColors.inverse) .foregroundStyle(AppTextColors.inverse)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.frame(height: AppMetrics.Size.buttonHeight) .frame(height: AppMetrics.Size.buttonHeight)
@ -105,9 +104,7 @@ struct WelcomeStepView: View {
) )
// Center icon // Center icon
Image(systemName: "sparkles") SymbolIcon("sparkles", size: .card, color: AppAccent.primary)
.font(.system(size: Design.IconSize.xxLarge))
.foregroundStyle(AppAccent.primary)
.scaleEffect(animateRings ? 1.1 : 1.0) .scaleEffect(animateRings ? 1.1 : 1.0)
.animation( .animation(
.easeInOut(duration: 1.5).repeatForever(autoreverses: true), .easeInOut(duration: 1.5).repeatForever(autoreverses: true),

View File

@ -13,17 +13,14 @@ struct WhatsNextStepView: View {
// Header // Header
VStack(spacing: Design.Spacing.medium) { VStack(spacing: Design.Spacing.medium) {
Image(systemName: "checkmark.circle.fill") SymbolIcon("checkmark.circle.fill", size: .section, color: AppStatus.success)
.font(.system(size: Design.IconSize.display))
.foregroundStyle(AppStatus.success)
Text(String(localized: "You're all set!")) Text(String(localized: "You're all set!"))
.font(.largeTitle) .font(Design.Typography.displayLarge)
.fontWeight(.bold)
.foregroundStyle(AppTextColors.primary) .foregroundStyle(AppTextColors.primary)
Text(String(localized: "Here's how to get the most from Rituals")) Text(String(localized: "Here's how to get the most from Rituals"))
.font(.subheadline) .font(Design.Typography.body)
.foregroundStyle(AppTextColors.secondary) .foregroundStyle(AppTextColors.secondary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
} }

View File

@ -32,20 +32,19 @@ struct RitualCardView: View {
VStack(alignment: .leading, spacing: Design.Spacing.medium) { VStack(alignment: .leading, spacing: Design.Spacing.medium) {
HStack(spacing: Design.Spacing.small) { HStack(spacing: Design.Spacing.small) {
// Icon // Icon
Image(systemName: iconName) SymbolIcon(iconName, size: .row, color: hasActiveArc ? AppAccent.primary : AppTextColors.tertiary)
.foregroundStyle(hasActiveArc ? AppAccent.primary : AppTextColors.tertiary)
.accessibilityHidden(true) .accessibilityHidden(true)
// Title // Title
Text(title) Text(title)
.font(.headline) .font(Design.Typography.headline)
.foregroundStyle(hasActiveArc ? AppTextColors.primary : AppTextColors.tertiary) .foregroundStyle(hasActiveArc ? AppTextColors.primary : AppTextColors.tertiary)
Spacer(minLength: Design.Spacing.medium) Spacer(minLength: Design.Spacing.medium)
// Day label // Day label
Text(dayLabel) Text(dayLabel)
.font(.caption) .font(Design.Typography.caption)
.foregroundStyle(AppTextColors.secondary) .foregroundStyle(AppTextColors.secondary)
.padding(.horizontal, Design.Spacing.small) .padding(.horizontal, Design.Spacing.small)
.padding(.vertical, Design.Spacing.xxxSmall) .padding(.vertical, Design.Spacing.xxxSmall)
@ -55,11 +54,11 @@ struct RitualCardView: View {
} }
Text(theme) Text(theme)
.font(.subheadline) .font(Design.Typography.body)
.foregroundStyle(hasActiveArc ? AppTextColors.secondary : AppTextColors.tertiary) .foregroundStyle(hasActiveArc ? AppTextColors.secondary : AppTextColors.tertiary)
Text(completionSummary) Text(completionSummary)
.font(.caption) .font(Design.Typography.caption)
.foregroundStyle(AppTextColors.secondary) .foregroundStyle(AppTextColors.secondary)
} }
.padding(Design.Spacing.large) .padding(Design.Spacing.large)

View File

@ -199,20 +199,17 @@ struct RitualDetailView: View {
private var headerSection: some View { private var headerSection: some View {
HStack(spacing: Design.Spacing.medium) { HStack(spacing: Design.Spacing.medium) {
Image(systemName: ritual.iconName) SymbolIcon(ritual.iconName, size: .rowContainer, color: ritual.hasActiveArc ? AppAccent.primary : AppTextColors.secondary)
.font(.system(size: Design.IconSize.xLarge)) .frame(width: Design.Size.iconContainerLarge, height: Design.Size.iconContainerLarge)
.foregroundStyle(ritual.hasActiveArc ? AppAccent.primary : AppTextColors.secondary)
.frame(width: 56, height: 56)
.background((ritual.hasActiveArc ? AppAccent.primary : AppTextColors.secondary).opacity(0.1)) .background((ritual.hasActiveArc ? AppAccent.primary : AppTextColors.secondary).opacity(0.1))
.clipShape(.rect(cornerRadius: Design.CornerRadius.large)) .clipShape(.rect(cornerRadius: Design.CornerRadius.large))
VStack(alignment: .leading, spacing: Design.Spacing.xSmall) { VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
Text(ritual.title) Text(ritual.title)
.font(.title2) .font(Design.Typography.displaySmall)
.foregroundStyle(AppTextColors.primary) .foregroundStyle(AppTextColors.primary)
.bold()
Text(ritual.theme) Text(ritual.theme)
.font(.subheadline) .font(Design.Typography.body)
.foregroundStyle(AppTextColors.secondary) .foregroundStyle(AppTextColors.secondary)
} }

View File

@ -180,16 +180,14 @@ struct RitualsView: View {
private var currentEmptyState: some View { private var currentEmptyState: some View {
VStack(spacing: Design.Spacing.large) { VStack(spacing: Design.Spacing.large) {
Image(systemName: "sparkles") SymbolIcon("sparkles", size: .hero, color: AppAccent.primary)
.font(.system(size: Design.IconSize.hero))
.foregroundStyle(AppAccent.primary)
Text(String(localized: "No Active Rituals")) Text(String(localized: "No Active Rituals"))
.font(.headline) .font(Design.Typography.headline)
.foregroundStyle(AppTextColors.primary) .foregroundStyle(AppTextColors.primary)
Text(String(localized: "Create a custom ritual or browse our preset library to get started.")) Text(String(localized: "Create a custom ritual or browse our preset library to get started."))
.font(.subheadline) .font(Design.Typography.body)
.foregroundStyle(AppTextColors.secondary) .foregroundStyle(AppTextColors.secondary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
@ -212,7 +210,7 @@ struct RitualsView: View {
if !store.pastRituals.isEmpty { if !store.pastRituals.isEmpty {
Text(String(localized: "Or restart a past ritual from the Past tab.")) Text(String(localized: "Or restart a past ritual from the Past tab."))
.font(.caption) .font(Design.Typography.caption)
.foregroundStyle(AppTextColors.tertiary) .foregroundStyle(AppTextColors.tertiary)
.padding(.top, Design.Spacing.small) .padding(.top, Design.Spacing.small)
} }
@ -223,16 +221,14 @@ struct RitualsView: View {
private var pastEmptyState: some View { private var pastEmptyState: some View {
VStack(spacing: Design.Spacing.large) { VStack(spacing: Design.Spacing.large) {
Image(systemName: "clock.arrow.circlepath") SymbolIcon("clock.arrow.circlepath", size: .hero, color: AppTextColors.tertiary)
.font(.system(size: Design.IconSize.hero))
.foregroundStyle(AppTextColors.tertiary)
Text(String(localized: "No Past Rituals")) Text(String(localized: "No Past Rituals"))
.font(.headline) .font(Design.Typography.headline)
.foregroundStyle(AppTextColors.primary) .foregroundStyle(AppTextColors.primary)
Text(String(localized: "Rituals that have ended will appear here. You can restart them anytime.")) Text(String(localized: "Rituals that have ended will appear here. You can restart them anytime."))
.font(.subheadline) .font(Design.Typography.body)
.foregroundStyle(AppTextColors.secondary) .foregroundStyle(AppTextColors.secondary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
} }

View File

@ -63,17 +63,15 @@ struct ArcRenewalSheet: View {
private var celebrationHeader: some View { private var celebrationHeader: some View {
VStack(spacing: Design.Spacing.medium) { VStack(spacing: Design.Spacing.medium) {
Image(systemName: "checkmark.seal.fill") SymbolIcon("checkmark.seal.fill", size: .section, color: AppStatus.success)
.font(.system(size: Design.IconSize.display))
.foregroundStyle(AppStatus.success)
Text(ritual.title) Text(ritual.title)
.font(.title2.bold()) .font(Design.Typography.displaySmall)
.foregroundStyle(AppTextColors.primary) .foregroundStyle(AppTextColors.primary)
if let arc = completedArc { if let arc = completedArc {
Text(String(localized: "Arc \(arc.arcNumber) Complete")) Text(String(localized: "Arc \(arc.arcNumber) Complete"))
.font(.subheadline) .font(Design.Typography.body)
.foregroundStyle(AppTextColors.secondary) .foregroundStyle(AppTextColors.secondary)
} }
} }
@ -84,26 +82,24 @@ struct ArcRenewalSheet: View {
private var summaryCard: some View { private var summaryCard: some View {
VStack(alignment: .leading, spacing: Design.Spacing.medium) { VStack(alignment: .leading, spacing: Design.Spacing.medium) {
Text(String(localized: "Your Journey")) Text(String(localized: "Your Journey"))
.font(.headline) .font(Design.Typography.headline)
.foregroundStyle(AppTextColors.primary) .foregroundStyle(AppTextColors.primary)
HStack { HStack {
Image(systemName: "chart.line.uptrend.xyaxis") SymbolIcon("chart.line.uptrend.xyaxis", size: .row, color: AppAccent.primary)
.foregroundStyle(AppAccent.primary)
Text(arcSummary) Text(arcSummary)
.font(Design.Typography.body)
.foregroundStyle(AppTextColors.secondary) .foregroundStyle(AppTextColors.secondary)
} }
.font(.subheadline)
if let arc = completedArc { if let arc = completedArc {
let habitCount = (arc.habits ?? []).count let habitCount = (arc.habits ?? []).count
HStack { HStack {
Image(systemName: "checkmark.circle.fill") SymbolIcon("checkmark.circle.fill", size: .row, color: AppAccent.primary)
.foregroundStyle(AppAccent.primary)
Text(String(localized: "\(habitCount) habits tracked")) Text(String(localized: "\(habitCount) habits tracked"))
.font(Design.Typography.body)
.foregroundStyle(AppTextColors.secondary) .foregroundStyle(AppTextColors.secondary)
} }
.font(.subheadline)
} }
} }
.padding(Design.Spacing.large) .padding(Design.Spacing.large)
@ -115,13 +111,13 @@ struct ArcRenewalSheet: View {
private var durationSection: some View { private var durationSection: some View {
VStack(alignment: .leading, spacing: Design.Spacing.medium) { VStack(alignment: .leading, spacing: Design.Spacing.medium) {
Text(String(localized: "Next Arc Duration")) Text(String(localized: "Next Arc Duration"))
.font(.headline) .font(Design.Typography.headline)
.foregroundStyle(AppTextColors.primary) .foregroundStyle(AppTextColors.primary)
VStack(spacing: Design.Spacing.small) { VStack(spacing: Design.Spacing.small) {
HStack { HStack {
Text(String(localized: "\(Int(durationDays)) days")) Text(String(localized: "\(Int(durationDays)) days"))
.font(.title3.bold()) .font(Design.Typography.displaySmall)
.foregroundStyle(AppAccent.primary) .foregroundStyle(AppAccent.primary)
Spacer() Spacer()
} }
@ -131,11 +127,11 @@ struct ArcRenewalSheet: View {
HStack { HStack {
Text(String(localized: "1 week")) Text(String(localized: "1 week"))
.font(.caption) .font(Design.Typography.caption)
.foregroundStyle(AppTextColors.tertiary) .foregroundStyle(AppTextColors.tertiary)
Spacer() Spacer()
Text(String(localized: "1 year")) Text(String(localized: "1 year"))
.font(.caption) .font(Design.Typography.caption)
.foregroundStyle(AppTextColors.tertiary) .foregroundStyle(AppTextColors.tertiary)
} }
} }
@ -147,7 +143,7 @@ struct ArcRenewalSheet: View {
durationDays = Double(days) durationDays = Double(days)
} label: { } label: {
Text("\(days)") Text("\(days)")
.font(.caption.bold()) .font(Design.Typography.captionBold)
.foregroundStyle(Int(durationDays) == days ? AppTextColors.primary : AppTextColors.secondary) .foregroundStyle(Int(durationDays) == days ? AppTextColors.primary : AppTextColors.secondary)
.padding(.horizontal, Design.Spacing.small) .padding(.horizontal, Design.Spacing.small)
.padding(.vertical, Design.Spacing.xSmall) .padding(.vertical, Design.Spacing.xSmall)
@ -171,10 +167,10 @@ struct ArcRenewalSheet: View {
dismiss() dismiss()
} label: { } label: {
HStack { HStack {
Image(systemName: "arrow.clockwise") SymbolIcon("arrow.clockwise", size: .row, color: AppTextColors.primary)
Text(String(localized: "Continue with Same Habits")) Text(String(localized: "Continue with Same Habits"))
} }
.font(.headline) .font(Design.Typography.headline)
.foregroundStyle(AppTextColors.primary) .foregroundStyle(AppTextColors.primary)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding(Design.Spacing.medium) .padding(Design.Spacing.medium)
@ -189,10 +185,10 @@ struct ArcRenewalSheet: View {
showingEditSheet = true showingEditSheet = true
} label: { } label: {
HStack { HStack {
Image(systemName: "pencil") SymbolIcon("pencil", size: .row, color: AppAccent.primary)
Text(String(localized: "Continue with Changes")) Text(String(localized: "Continue with Changes"))
} }
.font(.headline) .font(Design.Typography.headline)
.foregroundStyle(AppAccent.primary) .foregroundStyle(AppAccent.primary)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding(Design.Spacing.medium) .padding(Design.Spacing.medium)
@ -211,10 +207,10 @@ struct ArcRenewalSheet: View {
dismiss() dismiss()
} label: { } label: {
HStack { HStack {
Image(systemName: "checkmark.circle") SymbolIcon("checkmark.circle", size: .row, color: AppTextColors.secondary)
Text(String(localized: "End This Ritual")) Text(String(localized: "End This Ritual"))
} }
.font(.subheadline) .font(Design.Typography.body)
.foregroundStyle(AppTextColors.secondary) .foregroundStyle(AppTextColors.secondary)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding(Design.Spacing.medium) .padding(Design.Spacing.medium)

View File

@ -200,19 +200,17 @@ struct PresetDetailSheet: View {
private var headerSection: some View { private var headerSection: some View {
VStack(spacing: Design.Spacing.medium) { VStack(spacing: Design.Spacing.medium) {
Image(systemName: preset.iconName) SymbolIcon(preset.iconName, size: .hero, color: AppAccent.primary)
.font(.system(size: Design.IconSize.hero))
.foregroundStyle(AppAccent.primary)
Text(preset.theme) Text(preset.theme)
.font(.title3) .font(Design.Typography.bodyLarge)
.foregroundStyle(AppTextColors.secondary) .foregroundStyle(AppTextColors.secondary)
HStack(spacing: Design.Spacing.large) { HStack(spacing: Design.Spacing.large) {
Label(preset.timeOfDay.displayName, systemImage: preset.timeOfDay.symbolName) Label(preset.timeOfDay.displayName, systemImage: preset.timeOfDay.symbolName)
Label(String(localized: "\(preset.durationDays) days"), systemImage: "calendar") Label(String(localized: "\(preset.durationDays) days"), systemImage: "calendar")
} }
.font(.caption) .font(Design.Typography.caption)
.foregroundStyle(AppTextColors.tertiary) .foregroundStyle(AppTextColors.tertiary)
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)

View File

@ -29,20 +29,17 @@ struct RitualFocusCardView: View {
VStack(alignment: .leading, spacing: Design.Spacing.medium) { VStack(alignment: .leading, spacing: Design.Spacing.medium) {
HStack(spacing: Design.Spacing.small) { HStack(spacing: Design.Spacing.small) {
// Icon // Icon
Image(systemName: iconName) SymbolIcon(iconName, size: .row, color: AppAccent.primary)
.foregroundStyle(AppAccent.primary)
.accessibilityHidden(true) .accessibilityHidden(true)
// Title // Title
Text(title) TitleLabel(title, style: .headline, color: AppTextColors.primary)
.font(.headline)
.foregroundStyle(AppTextColors.primary)
Spacer(minLength: Design.Spacing.medium) Spacer(minLength: Design.Spacing.medium)
// Day label // Day label - needs additional modifiers (padding, background)
Text(dayLabel) Text(dayLabel)
.font(.caption) .font(Design.Typography.caption)
.foregroundStyle(AppTextColors.secondary) .foregroundStyle(AppTextColors.secondary)
.padding(.horizontal, Design.Spacing.small) .padding(.horizontal, Design.Spacing.small)
.padding(.vertical, Design.Spacing.xxxSmall) .padding(.vertical, Design.Spacing.xxxSmall)
@ -51,16 +48,12 @@ struct RitualFocusCardView: View {
.accessibilityLabel(Text(dayLabel)) .accessibilityLabel(Text(dayLabel))
} }
Text(theme) BodyLabel(theme, color: AppTextColors.secondary)
.font(.subheadline)
.foregroundStyle(AppTextColors.secondary)
VStack(alignment: .leading, spacing: Design.Spacing.xSmall) { VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
ProgressView(value: progress) ProgressView(value: progress)
.tint(AppAccent.primary) .tint(AppAccent.primary)
Text(completionSummary) CaptionLabel(completionSummary, color: AppTextColors.secondary)
.font(.caption)
.foregroundStyle(AppTextColors.secondary)
} }
} }
.padding(Design.Spacing.large) .padding(Design.Spacing.large)

View File

@ -16,13 +16,11 @@ struct TodayEmptyStateView: View {
VStack(spacing: Design.Spacing.large) { VStack(spacing: Design.Spacing.large) {
// Icon // Icon
Image(systemName: "sparkles") SymbolIcon("sparkles", size: .hero, color: AppAccent.primary)
.font(.system(size: Design.IconSize.hero))
.foregroundStyle(AppAccent.primary)
.padding(.top, Design.Spacing.large) .padding(.top, Design.Spacing.large)
Text(String(localized: "Rituals help you build consistent habits through focused, time-bound journeys.")) Text(String(localized: "Rituals help you build consistent habits through focused, time-bound journeys."))
.font(.subheadline) .font(Design.Typography.body)
.foregroundStyle(AppTextColors.secondary) .foregroundStyle(AppTextColors.secondary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.padding(.horizontal, Design.Spacing.large) .padding(.horizontal, Design.Spacing.large)
@ -36,7 +34,7 @@ struct TodayEmptyStateView: View {
.fill(AppBorder.subtle) .fill(AppBorder.subtle)
.frame(height: 1) .frame(height: 1)
Text(String(localized: "or")) Text(String(localized: "or"))
.font(.caption) .font(Design.Typography.caption)
.foregroundStyle(AppTextColors.tertiary) .foregroundStyle(AppTextColors.tertiary)
Rectangle() Rectangle()
.fill(AppBorder.subtle) .fill(AppBorder.subtle)
@ -73,7 +71,7 @@ struct TodayEmptyStateView: View {
// Past rituals hint // Past rituals hint
if !store.pastRituals.isEmpty { if !store.pastRituals.isEmpty {
Text(String(localized: "You can also restart a past ritual from the Rituals tab.")) Text(String(localized: "You can also restart a past ritual from the Rituals tab."))
.font(.caption) .font(Design.Typography.caption)
.foregroundStyle(AppTextColors.tertiary) .foregroundStyle(AppTextColors.tertiary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.padding(.top, Design.Spacing.small) .padding(.top, Design.Spacing.small)
@ -97,8 +95,7 @@ struct TodayEmptyStateView: View {
private var quickStartSection: some View { private var quickStartSection: some View {
VStack(alignment: .leading, spacing: Design.Spacing.small) { VStack(alignment: .leading, spacing: Design.Spacing.small) {
Text(String(localized: "Quick Start")) Text(String(localized: "Quick Start"))
.font(.caption) .font(Design.Typography.captionMedium)
.fontWeight(.medium)
.foregroundStyle(AppTextColors.tertiary) .foregroundStyle(AppTextColors.tertiary)
.padding(.horizontal, Design.Spacing.medium) .padding(.horizontal, Design.Spacing.medium)
@ -135,12 +132,10 @@ private struct QuickStartButton: View {
var body: some View { var body: some View {
Button(action: action) { Button(action: action) {
HStack(spacing: Design.Spacing.small) { HStack(spacing: Design.Spacing.small) {
Image(systemName: goal.symbolName) SymbolIcon(goal.symbolName, size: .row, color: AppAccent.primary)
.font(.body)
.foregroundStyle(AppAccent.primary)
Text(goal.displayName) Text(goal.displayName)
.font(.subheadline) .font(Design.Typography.body)
.foregroundStyle(AppTextColors.primary) .foregroundStyle(AppTextColors.primary)
Spacer() Spacer()

View File

@ -25,22 +25,20 @@ struct TodayHabitRowView: View {
var body: some View { var body: some View {
Button(action: action) { Button(action: action) {
HStack(spacing: Design.Spacing.medium) { HStack(spacing: Design.Spacing.medium) {
Image(systemName: symbolName) SymbolIcon(symbolName, size: .row, color: isCompleted ? AppStatus.success : AppAccent.primary)
.font(.body)
.foregroundStyle(isCompleted ? AppStatus.success : AppAccent.primary)
.frame(width: AppMetrics.Size.iconLarge) .frame(width: AppMetrics.Size.iconLarge)
.accessibilityHidden(true) .accessibilityHidden(true)
Text(title) BodyLabel(title, color: AppTextColors.primary)
.font(.subheadline)
.foregroundStyle(AppTextColors.primary)
Spacer(minLength: Design.Spacing.medium) Spacer(minLength: Design.Spacing.medium)
Image(systemName: isCompleted ? "checkmark.circle.fill" : "circle") SymbolIcon(
.font(.body) isCompleted ? "checkmark.circle.fill" : "circle",
.foregroundStyle(isCompleted ? AppStatus.success : AppBorder.subtle) size: .row,
.accessibilityHidden(true) color: isCompleted ? AppStatus.success : AppBorder.subtle
)
.accessibilityHidden(true)
} }
.padding(.horizontal, horizontalPadding) .padding(.horizontal, horizontalPadding)
.padding(.vertical, Design.Spacing.medium) .padding(.vertical, Design.Spacing.medium)

View File

@ -10,13 +10,8 @@ struct TodayHeaderView: View {
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: Design.Spacing.xxxSmall) { VStack(alignment: .leading, spacing: Design.Spacing.xxxSmall) {
Text(String(localized: "Today")) TitleLabel(String(localized: "Today"), style: .displayLarge, color: AppTextColors.primary)
.font(.largeTitle) BodyLabel(dateText, color: AppTextColors.secondary)
.foregroundStyle(AppTextColors.primary)
.bold()
Text(dateText)
.font(.subheadline)
.foregroundStyle(AppTextColors.secondary)
} }
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.accessibilityElement(children: .combine) .accessibilityElement(children: .combine)

View File

@ -48,19 +48,17 @@ struct TodayNoRitualsForTimeView: View {
VStack(spacing: Design.Spacing.large) { VStack(spacing: Design.Spacing.large) {
// Icon // Icon
Image(systemName: currentTimePeriod.symbolName) SymbolIcon(currentTimePeriod.symbolName, size: .hero, color: AppAccent.primary.opacity(0.6))
.font(.system(size: Design.IconSize.hero))
.foregroundStyle(AppAccent.primary.opacity(0.6))
.padding(.top, Design.Spacing.large) .padding(.top, Design.Spacing.large)
VStack(spacing: Design.Spacing.xSmall) { VStack(spacing: Design.Spacing.xSmall) {
Text(String(localized: "No rituals scheduled for \(currentTimePeriod.displayName.lowercased()).")) Text(String(localized: "No rituals scheduled for \(currentTimePeriod.displayName.lowercased())."))
.font(.subheadline) .font(Design.Typography.body)
.foregroundStyle(AppTextColors.secondary) .foregroundStyle(AppTextColors.secondary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
Text(currentTimePeriod.timeRange) Text(currentTimePeriod.timeRange)
.font(.caption) .font(Design.Typography.caption)
.foregroundStyle(AppTextColors.tertiary) .foregroundStyle(AppTextColors.tertiary)
} }
@ -68,23 +66,21 @@ struct TodayNoRitualsForTimeView: View {
if !nextRituals.isEmpty { if !nextRituals.isEmpty {
VStack(alignment: .leading, spacing: Design.Spacing.medium) { VStack(alignment: .leading, spacing: Design.Spacing.medium) {
Text(String(localized: "Coming up later:")) Text(String(localized: "Coming up later:"))
.font(.caption) .font(Design.Typography.caption)
.foregroundStyle(AppTextColors.tertiary) .foregroundStyle(AppTextColors.tertiary)
ForEach(nextRituals) { ritual in ForEach(nextRituals) { ritual in
HStack(spacing: Design.Spacing.small) { HStack(spacing: Design.Spacing.small) {
Image(systemName: ritual.timeOfDay.symbolName) SymbolIcon(ritual.timeOfDay.symbolName, size: .chevron, color: AppTextColors.secondary)
.font(.caption)
.foregroundStyle(AppTextColors.secondary)
Text(ritual.title) Text(ritual.title)
.font(.subheadline) .font(Design.Typography.body)
.foregroundStyle(AppTextColors.primary) .foregroundStyle(AppTextColors.primary)
Spacer() Spacer()
Text(ritual.timeOfDay.displayName) Text(ritual.timeOfDay.displayName)
.font(.caption) .font(Design.Typography.caption)
.foregroundStyle(AppTextColors.tertiary) .foregroundStyle(AppTextColors.tertiary)
} }
.padding(Design.Spacing.small) .padding(Design.Spacing.small)
@ -100,7 +96,7 @@ struct TodayNoRitualsForTimeView: View {
tomorrowRitual.timeOfDay.displayName, tomorrowRitual.timeOfDay.displayName,
tomorrowRitual.timeOfDay.timeRange tomorrowRitual.timeOfDay.timeRange
)) ))
.font(.caption) .font(Design.Typography.caption)
.foregroundStyle(AppTextColors.tertiary) .foregroundStyle(AppTextColors.tertiary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.padding(.top, Design.Spacing.small) .padding(.top, Design.Spacing.small)
@ -108,7 +104,7 @@ struct TodayNoRitualsForTimeView: View {
// Motivational message // Motivational message
Text(String(localized: "Enjoy this moment. Your next ritual will appear when it's time.")) Text(String(localized: "Enjoy this moment. Your next ritual will appear when it's time."))
.font(.caption) .font(Design.Typography.caption)
.foregroundStyle(AppTextColors.tertiary) .foregroundStyle(AppTextColors.tertiary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.padding(.top, Design.Spacing.small) .padding(.top, Design.Spacing.small)

View File

@ -33,8 +33,7 @@ struct TodayRitualSectionView: View {
Spacer() Spacer()
// Time of day indicator // Time of day indicator
Image(systemName: timeOfDay.symbolName) SymbolIcon(timeOfDay.symbolName, size: .row, color: AppTextColors.tertiary)
.foregroundStyle(AppTextColors.tertiary)
.accessibilityLabel(timeOfDay.displayName) .accessibilityLabel(timeOfDay.displayName)
} }