Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ extension EditGoalListReducer {
case let .view(.weekCalendarSwipe(swipe)):
switch swipe {
case .next:
guard let nextWeekDate = TXCalendarUtil.dateByAddingWeek(
guard let nextWeekDate = TXCalendarUtil.dateByApplyingWeeklyBoundarySwipe(
from: state.calendarDate,
by: 1
) else {
Expand All @@ -59,7 +59,7 @@ extension EditGoalListReducer {
return .send(.internal(.setCalendarDate(nextWeekDate)))

case .previous:
guard let previousWeekDate = TXCalendarUtil.dateByAddingWeek(
guard let previousWeekDate = TXCalendarUtil.dateByApplyingWeeklyBoundarySwipe(
from: state.calendarDate,
by: -1
) else {
Expand Down
6 changes: 3 additions & 3 deletions Projects/Feature/Home/Sources/Home/HomeCalendarSection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,16 @@ struct HomeCalendarSection: View {
)
.frame(maxWidth: .infinity, maxHeight: 76)
.perfControl(slug: "home", element: "calendar")
.transaction { transaction in
transaction.animation = nil
}

if UITestMode.isProbeScenario {
calendarView.perfStateMarker(
slug: "home",
key: "calendar-month",
value: "\(store.calendarDate.year)-\(store.calendarDate.month)"
)
.transaction { transaction in
transaction.animation = nil
}
} else {
calendarView
}
Expand Down
4 changes: 2 additions & 2 deletions Projects/Feature/Home/Sources/Home/HomeReducer+Impl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ extension HomeReducer {
case let .view(.weekCalendarSwipe(swipe)):
switch swipe {
case .next:
guard let nextWeekDate = TXCalendarUtil.dateByAddingWeek(
guard let nextWeekDate = TXCalendarUtil.dateByApplyingWeeklyBoundarySwipe(
from: state.calendarDate,
by: 1
) else {
Expand All @@ -312,7 +312,7 @@ extension HomeReducer {
return .send(.internal(.setCalendarDate(nextWeekDate)))

case .previous:
guard let previousWeekDate = TXCalendarUtil.dateByAddingWeek(
guard let previousWeekDate = TXCalendarUtil.dateByApplyingWeeklyBoundarySwipe(
from: state.calendarDate,
by: -1
) else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ enum CalendarSheetConstants {
static let dragVelocityThreshold: CGFloat = 500
static let springResponse: Double = 0.35
static let springDamping: Double = 0.86
static let hiddenOffsetFallback: CGFloat = 1000
static let hiddenOffsetFallback: CGFloat = 1_000
}

// MARK: - Calendar Sheet Modifier
Expand Down Expand Up @@ -94,7 +94,11 @@ struct CalendarSheetModifier<ButtonContent: View>: ViewModifier {
.padding(.bottom, safeAreaBottom)
.background(Color.Common.white)
.clipShape(.rect(cornerRadii: topCornerRadii))
.transaction { $0.animation = nil }
.transaction { transaction in
if dragOffset != 0 {
transaction.animation = nil
}
}
}

@ViewBuilder
Expand All @@ -107,6 +111,7 @@ struct CalendarSheetModifier<ButtonContent: View>: ViewModifier {
onComplete: onComplete,
isDateEnabled: isDateEnabled
)

case let .custom(content):
TXCalendarBottomSheet(
selectedDate: $selectedDate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,15 +183,34 @@ public struct DefaultCalendarButton: View {

// MARK: - Private Views
private extension TXCalendarBottomSheet {
static var minimumMonthlyRowCount: Int { 6 }

static var calendarConfig: TXCalendar.Configuration {
.init(
monthlyHeaderSpacing: Spacing.spacing7,
monthlyRowSpacing: Spacing.spacing6
monthlyRowSpacing: Spacing.spacing6,
monthlyPaging: .init(minimumRowCount: minimumMonthlyRowCount)
)
}

var calendarConfig: TXCalendar.Configuration {
Self.calendarConfig
let isDateEnabled = isDateEnabled
return .init(
monthlyHeaderSpacing: Spacing.spacing7,
monthlyRowSpacing: Spacing.spacing6,
monthlyPaging: .init(
isEnabled: true,
pageSpacing: Spacing.spacing7,
minimumRowCount: Self.minimumMonthlyRowCount,
pageWeeks: { date in
let weeks = Self.makeCalendarData(for: date).weeks
return Self.applyDisabledStatus(
to: weeks,
isDateEnabled: isDateEnabled
)
}
)
)
}

static func makeCalendarData(for date: TXCalendarDate) -> CalendarPresentationData {
Expand All @@ -211,9 +230,9 @@ private extension TXCalendarBottomSheet {

guard !weeks.isEmpty else { return headerSectionHeight + verticalPadding }

let rowCount = CGFloat(weeks.count)
let rowSpacing = config.monthlyRowSpacing * CGFloat(weeks.count - 1)
let monthGridHeight = (config.dateStyle.size * rowCount) + rowSpacing
let rowCount = max(weeks.count, Self.minimumMonthlyRowCount)
let rowSpacing = config.monthlyRowSpacing * CGFloat(max(rowCount - 1, 0))
let monthGridHeight = (config.dateStyle.size * CGFloat(rowCount)) + rowSpacing

return headerSectionHeight + monthGridHeight + verticalPadding
}
Expand Down Expand Up @@ -244,7 +263,7 @@ private extension TXCalendarBottomSheet {
func datePickerView(height: CGFloat) -> some View {
HStack(spacing: 0) {
Picker("Year", selection: selectedYear) {
ForEach(1940...2099, id: \.self) { year in
ForEach(1_940...2_099, id: \.self) { year in
Text(verbatim: "\(year)년").tag(year)
}
}
Expand All @@ -261,7 +280,6 @@ private extension TXCalendarBottomSheet {
.padding(.horizontal, Spacing.spacing7)
}


var selectedYear: Binding<Int> {
Binding(
get: { selectedDate.year },
Expand Down Expand Up @@ -297,6 +315,16 @@ private extension TXCalendarBottomSheet {
}

func applyDisabledStatus(to weeks: [[TXCalendarDateItem]]) -> [[TXCalendarDateItem]] {
Self.applyDisabledStatus(
to: weeks,
isDateEnabled: isDateEnabled
)
}

static func applyDisabledStatus(
to weeks: [[TXCalendarDateItem]],
isDateEnabled: ((TXCalendarDateItem) -> Bool)?
) -> [[TXCalendarDateItem]] {
guard let isDateEnabled else { return weeks }
return weeks.map { week in
week.map { item in
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
//
// TXCalendar+Layout.swift
// SharedDesignSystem
//
// Created by Codex on 5/30/26.
//

import SwiftUI

// MARK: - Helpers
extension TXCalendar {
var headerSpacing: CGFloat {
switch mode {
case .weekly: config.weeklyHeaderSpacing
case .monthly: config.monthlyHeaderSpacing
}
}

var horizontalPadding: CGFloat {
switch mode {
case .weekly: config.weeklyHorizontalPadding
case .monthly: config.monthlyHorizontalPadding
}
}

var contentHeight: CGFloat {
let headerSectionHeight = config.weekdayHeight + headerSpacing
let verticalPadding: CGFloat = config.verticalPadding * 2

switch mode {
case .weekly: return headerSectionHeight + config.dateStyle.size + config.weeklyBottomPadding + verticalPadding
case .monthly: return headerSectionHeight + monthGridHeight + verticalPadding
}
}

var monthGridHeight: CGFloat {
guard !weeks.isEmpty else { return 0 }

let rowCount = max(weeks.count, config.monthlyPaging.minimumRowCount ?? 0)
let rowSpacing = config.monthlyRowSpacing * CGFloat(max(rowCount - 1, 0))
return (config.dateStyle.size * CGFloat(rowCount)) + rowSpacing
}

var monthlyPageHeight: CGFloat {
config.weekdayHeight + headerSpacing + monthGridHeight
}

var isMonthlyVisualPagingEnabled: Bool {
mode == .monthly && config.monthlyPaging.isEnabled && currentDate != nil
}

static var pagingAnimation: Animation {
.easeInOut(duration: 0.22)
}

func weeklyPageSpacing(dayColumnSpacing: CGFloat) -> CGFloat {
dayColumnSpacing
}

func weeklyPageDistance(pageWidth: CGFloat, dayColumnSpacing: CGFloat) -> CGFloat {
pageWidth + weeklyPageSpacing(dayColumnSpacing: dayColumnSpacing)
}

func monthlyPageSpacing(dayColumnSpacing: CGFloat) -> CGFloat {
max(config.monthlyPaging.pageSpacing, dayColumnSpacing)
}

func monthlyPageDate(monthOffset: Int) -> TXCalendarDate? {
guard var date = monthlyPagingBaseDate ?? currentDate?.wrappedValue else {
return nil
}

guard monthOffset != 0 else { return date }

if monthOffset > 0 {
for _ in 0..<monthOffset {
date.goToNextMonth()
}
} else {
for _ in 0..<abs(monthOffset) {
date.goToPreviousMonth()
}
}
return date
}

func monthlyPageWeeks(monthOffset: Int) -> [[TXCalendarDateItem]] {
guard let date = monthlyPageDate(monthOffset: monthOffset) else {
return weeks
}

if monthOffset == 0,
monthlyPagingBaseDate == nil {
return weeks
}

return config.monthlyPaging.pageWeeks?(date)
?? TXCalendarDataGenerator.generateMonthData(for: date)
}

func monthlyTargetDate(for swipe: SwipeGesture) -> TXCalendarDate? {
guard var date = monthlyPagingBaseDate ?? currentDate?.wrappedValue else {
return nil
}

switch swipe {
case .previous:
date.goToPreviousMonth()

case .next:
date.goToNextMonth()
}
return date
}

var weekDateItems: [TXCalendarDateItem] {
weeks.first ?? []
}

var activeWeeklyReferenceDate: TXCalendarDate? {
weeklyPagingReferenceDate ?? weeklyReferenceDate
}

func weeklyPageItems(weekOffset: Int) -> [TXCalendarDateItem] {
if let weeklyPagingReferenceDate {
guard weekOffset != 0 else {
return generatedWeeklyPageItems(
for: weeklyPagingReferenceDate,
weekOffset: 0
)
}

guard let targetDate = weeklyTargetDate(for: weekOffset) else {
return weekDateItems
}

return generatedWeeklyPageItems(
for: targetDate,
weekOffset: 0
)
}

guard weekOffset != 0 else {
return weekDateItems
}

guard let targetDate = weeklyTargetDate(for: weekOffset) else {
return weekDateItems
}

return generatedWeeklyPageItems(
for: targetDate,
weekOffset: 0
)
}

func generatedWeeklyPageItems(
for referenceDate: TXCalendarDate,
weekOffset: Int
) -> [TXCalendarDateItem] {
TXCalendarDataGenerator.generateWeekData(
for: referenceDate,
weekOffset: weekOffset
).first ?? []
}

func weeklyTargetDate(for swipe: SwipeGesture) -> TXCalendarDate? {
switch swipe {
case .previous:
return weeklyTargetDate(for: -1)

case .next:
return weeklyTargetDate(for: 1)
}
}

func weeklyTargetDate(for weekOffset: Int) -> TXCalendarDate? {
guard weekOffset != 0,
let referenceDate = activeWeeklyReferenceDate else {
return activeWeeklyReferenceDate
}

return TXCalendarUtil.dateByApplyingWeeklyBoundarySwipe(
from: referenceDate,
by: weekOffset
)
}

var weeklyReferenceDate: TXCalendarDate? {
if let currentDate, currentDate.wrappedValue.day != nil {
return currentDate.wrappedValue
}

let selectedItem = weekDateItems.first { item in
switch item.status {
case .selectedFilled, .selectedLine:
return item.dateComponents != nil

case .completed, .default, .lastDate:
return false
}
}

if let selectedItem,
let components = selectedItem.dateComponents {
return TXCalendarDate(components: components)
}

guard let components = weekDateItems.compactMap(\.dateComponents).first else {
return nil
}
return TXCalendarDate(components: components)
}

func weeklyHeaderTitle(index: Int, item: TXCalendarDateItem) -> String {
guard let components = item.dateComponents,
let year = components.year,
let month = components.month,
let day = components.day else {
return ""
}
let today = Calendar(identifier: .gregorian).dateComponents([.year, .month, .day], from: Date())
let isToday = today.year == year && today.month == month && today.day == day

return isToday ? "오늘" : weekdays[index]
}
}
Loading
Loading