From 3291b3ae850ca8eb62f2402eec19e122c2894f30 Mon Sep 17 00:00:00 2001
From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com>
Date: Wed, 15 Jan 2025 16:32:19 +0900
Subject: [PATCH 1/6] =?UTF-8?q?[Feat]=20ControlButton=20=EC=9E=91=EC=84=B1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Contents.json | 12 +++
.../icn_toggle_selected.svg | 4 +
.../Contents.json | 12 +++
.../icn_toggle_unselected.svg | 4 +
.../Components/Control/ControlButton.swift | 87 +++++++++++++++++++
.../DesignSystem/Image+DesignSystem.swift | 2 +
6 files changed, 121 insertions(+)
create mode 100644 TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_selected.imageset/Contents.json
create mode 100644 TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_selected.imageset/icn_toggle_selected.svg
create mode 100644 TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_unselected.imageset/Contents.json
create mode 100644 TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_unselected.imageset/icn_toggle_unselected.svg
create mode 100644 TnT/Projects/DesignSystem/Sources/Components/Control/ControlButton.swift
diff --git a/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_selected.imageset/Contents.json b/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_selected.imageset/Contents.json
new file mode 100644
index 0000000..e178778
--- /dev/null
+++ b/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_selected.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "icn_toggle_selected.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_selected.imageset/icn_toggle_selected.svg b/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_selected.imageset/icn_toggle_selected.svg
new file mode 100644
index 0000000..2486023
--- /dev/null
+++ b/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_selected.imageset/icn_toggle_selected.svg
@@ -0,0 +1,4 @@
+
diff --git a/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_unselected.imageset/Contents.json b/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_unselected.imageset/Contents.json
new file mode 100644
index 0000000..77f3f72
--- /dev/null
+++ b/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_unselected.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "icn_toggle_unselected.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_unselected.imageset/icn_toggle_unselected.svg b/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_unselected.imageset/icn_toggle_unselected.svg
new file mode 100644
index 0000000..13a74cf
--- /dev/null
+++ b/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_unselected.imageset/icn_toggle_unselected.svg
@@ -0,0 +1,4 @@
+
diff --git a/TnT/Projects/DesignSystem/Sources/Components/Control/ControlButton.swift b/TnT/Projects/DesignSystem/Sources/Components/Control/ControlButton.swift
new file mode 100644
index 0000000..1e3bc03
--- /dev/null
+++ b/TnT/Projects/DesignSystem/Sources/Components/Control/ControlButton.swift
@@ -0,0 +1,87 @@
+//
+// TControlButton.swift
+// DesignSystem
+//
+// Created by 박민서 on 1/15/25.
+// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
+//
+
+import SwiftUI
+
+/// TnT 앱 내에서 전반적으로 사용되는 커스텀 컨트롤 버튼 컴포넌트입니다.
+public struct TControlButton: View {
+ /// 버틉 탭 액션
+ private let tapAction: () -> Void
+ /// 버튼 스타일
+ private let type: Style
+ /// 버튼 선택 상태
+ @Binding private var isSelected: Bool
+
+ /// TControlButton 생성자
+ /// - Parameters:
+ /// - type: 버튼의 스타일. `TControlButton.Style` 사용.
+ /// - isSelected: 버튼의 선택 상태를 관리하는 바인딩.
+ /// - action: 버튼이 탭되었을 때 실행할 액션. (기본값: 빈 클로저)
+ public init(
+ type: Style,
+ isSelected: Binding,
+ action: @escaping () -> Void = {}
+ ) {
+ self.type = type
+ self._isSelected = isSelected
+ self.tapAction = action
+ }
+
+ public var body: some View {
+ Button(action: {
+ isSelected.toggle()
+ tapAction()
+ }, label: {
+ type.image(isSelected: isSelected)
+ .resizable()
+ .scaledToFit()
+ .frame(width: type.defaultSize.width, height: type.defaultSize.height)
+ })
+ }
+}
+
+public extension TControlButton {
+ /// TControlButton의 스타일입니다.
+ enum Style {
+ case radio
+ case checkMark
+ case checkbox
+ case toggle
+ case star
+ case heart
+
+ /// 선택 상태에 따른 이미지 반환
+ func image(isSelected: Bool) -> Image {
+ switch self {
+
+ case .radio:
+ return Image(isSelected ? .icnRadioButtonSelected : .icnRadioButtonUnselected)
+ case .checkMark:
+ return Image(isSelected ? .icnCheckMarkFilled : .icnCheckMarkEmpty)
+ case .checkbox:
+ return Image(isSelected ? .icnCheckButtonSelected : .icnCheckButtonUnselected)
+ case .toggle:
+ return Image(isSelected ? .icnToggleSelected : .icnToggleUnselected)
+ case .star:
+ return Image(isSelected ? .icnStarFilled : .icnStarEmpty)
+ case .heart:
+ return Image(isSelected ? .icnHeartFilled : .icnHeartEmpty)
+ }
+ }
+
+ /// 스타일에 따른 기본 크기
+ var defaultSize: CGSize {
+ switch self {
+ case .toggle:
+ return .init(width: 44, height: 24)
+ default:
+ return .init(width: 24, height: 24)
+ }
+ }
+ }
+}
diff --git a/TnT/Projects/DesignSystem/Sources/DesignSystem/Image+DesignSystem.swift b/TnT/Projects/DesignSystem/Sources/DesignSystem/Image+DesignSystem.swift
index 2b1c705..c0bc548 100644
--- a/TnT/Projects/DesignSystem/Sources/DesignSystem/Image+DesignSystem.swift
+++ b/TnT/Projects/DesignSystem/Sources/DesignSystem/Image+DesignSystem.swift
@@ -40,6 +40,8 @@ public extension ImageResource {
static let icnStarFilled: ImageResource = DesignSystemAsset.icnStarFilled.imageResource
static let icnHeartEmpty: ImageResource = DesignSystemAsset.icnHeartEmpty.imageResource
static let icnHeartFilled: ImageResource = DesignSystemAsset.icnHeartFilled.imageResource
+ static let icnToggleUnselected: ImageResource = DesignSystemAsset.icnToggleUnselected.imageResource
+ static let icnToggleSelected: ImageResource = DesignSystemAsset.icnToggleSelected.imageResource
}
// MARK: Image
From 107bcc312946e1a7e69e7c5e7d2f043ab210728d Mon Sep 17 00:00:00 2001
From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com>
Date: Wed, 15 Jan 2025 19:12:43 +0900
Subject: [PATCH 2/6] =?UTF-8?q?[Feat]=20TPopUp=20=EC=BB=B4=ED=8F=AC?=
=?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=9E=91=EC=84=B1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Sources/Components/PopUp/TPopUp.swift | 189 ++++++++++++++++++
1 file changed, 189 insertions(+)
create mode 100644 TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUp.swift
diff --git a/TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUp.swift b/TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUp.swift
new file mode 100644
index 0000000..97d2077
--- /dev/null
+++ b/TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUp.swift
@@ -0,0 +1,189 @@
+//
+// TPopUp.swift
+// DesignSystem
+//
+// Created by 박민서 on 1/15/25.
+// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
+//
+
+import SwiftUI
+
+/// 팝업 관련 네임스페이스
+public struct TPopUp {
+ /// 팝업 내부 콘텐츠의 기본 패딩
+ public static let defaultInnerPadding: CGFloat = 20
+ /// 팝업 배경의 기본 불투명도
+ /// 0.0 = 완전 투명, 1.0 = 완전 불투명
+ public static let defaultBackgroundOpacity: Double = 0.8
+ /// 팝업의 기본 배경 색상
+ public static let defaultPopUpBackgroundColor: Color = .white
+ /// 팝업 모서리의 기본 곡률 (Corner Radius)
+ public static let defaultCornerRadius: CGFloat = 16
+ /// 팝업 그림자의 기본 반경
+ public static let defaultShadowRadius: CGFloat = 10
+ /// 팝업 콘텐츠의 기본 크기
+ public static let defaultContentSize: CGSize = .init(width: 297, height: 175)
+}
+
+extension TPopUp {
+ /// 팝업 컨테이너 (공통 레이아웃)
+ public struct Modifier: ViewModifier {
+
+ /// 팝업에 표시될 내부 콘텐츠 클로저
+ private let innerContent: () -> InnerContent
+ /// 팝업 표시 여부
+ @Binding private var isPresented: Bool
+
+ /// TPopupModifier 초기화 메서드
+ /// - Parameters:
+ /// - isPresented: 팝업 표시 여부를 제어하는 Binding
+ /// - newContent: 팝업에 표시될 내부 콘텐츠 클로저
+ public init(
+ isPresented: Binding,
+ newContent: @escaping () -> InnerContent
+ ) {
+ self._isPresented = isPresented
+ self.innerContent = newContent
+ }
+
+ public func body(content: Content) -> some View {
+ ZStack {
+ // 기존 뷰
+ content
+ .zIndex(0)
+
+ if isPresented {
+ // 반투명 배경
+ Color.black.opacity(defaultBackgroundOpacity)
+ .ignoresSafeArea()
+ .zIndex(1)
+ .onTapGesture {
+ isPresented = false
+ }
+
+ // 팝업 뷰
+ self.innerContent()
+ .frame(minWidth: TPopUp.defaultContentSize.width, minHeight: TPopUp.defaultContentSize.height)
+ .padding(defaultInnerPadding)
+ .background(defaultPopUpBackgroundColor)
+ .cornerRadius(defaultCornerRadius)
+ .shadow(radius: 10)
+ .padding()
+ .zIndex(2)
+ }
+ }
+ .animation(.bouncy, value: isPresented)
+ }
+ }
+}
+
+extension TPopUp {
+ /// 팝업 Alert컨텐츠
+ public struct Alert: View {
+ /// 팝업 제목
+ private let title: String
+ /// 팝업 메시지
+ private let message: String
+ /// 팝업 버튼 배열
+ private let buttons: [TPopUp.ButtonContent]
+
+ /// TPopUpAlert 초기화 메서드
+ /// - Parameters:
+ /// - title: 팝업 제목
+ /// - message: 팝업 메시지
+ /// - buttons: 팝업 버튼 배열
+ public init(title: String, message: String, buttons: [TPopUp.ButtonContent]) {
+ self.title = title
+ self.message = message
+ self.buttons = buttons
+ }
+
+ public var body: some View {
+ VStack(spacing: 20) {
+ // 텍스트 Section
+ VStack(spacing: 8) {
+ Text(title)
+ .typographyStyle(.heading4, with: .neutral900)
+ .multilineTextAlignment(.center)
+ .padding(.top, 20)
+
+ Text(message)
+ .typographyStyle(.body2Medium, with: .neutral500)
+ .multilineTextAlignment(.center)
+ }
+
+ // 버튼 Section
+ HStack {
+ // TODO: 버튼 컴포넌트 완성시 대체
+ ForEach(buttons, id: \.title) { button in
+ Button(action: button.action) {
+ Text(button.title)
+ .typographyStyle(.body1Medium, with: button.style == .primary ? Color.neutral50 : Color.neutral500)
+ .padding()
+ .frame(maxWidth: .infinity)
+ .background(button.style == .primary ? Color.neutral900 : Color.neutral100)
+ .foregroundColor(.white)
+ .cornerRadius(8)
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+// TODO: 버튼 컴포넌트 완성시 대체
+extension TPopUp {
+ /// 팝업 버튼 스타일
+ public struct ButtonContent {
+ /// 버튼 제목
+ let title: String
+ /// 버튼 스타일
+ let style: Style
+ /// 버튼 클릭 시 동작
+ let action: () -> Void
+
+ public enum Style {
+ case primary
+ case secondary
+ }
+
+ /// TPopUpButtonContent 초기화 메서드
+ /// - Parameters:
+ /// - title: 버튼 제목
+ /// - style: 버튼 스타일 (기본값: `.primary`)
+ /// - action: 버튼 클릭 시 동작
+ public init(
+ title: String,
+ style: Style = .primary,
+ action: @escaping () -> Void = {}
+ ) {
+ self.title = title
+ self.action = action
+ self.style = style
+ }
+ }
+}
+
+public extension View {
+ /// 팝업 표시를 위한 View Modifier
+ /// - Parameters:
+ /// - isPresented: 팝업 표시 여부를 제어하는 Binding
+ /// - content: 팝업 내부에 표시할 콘텐츠 클로저
+ /// - Returns: 팝업이 추가된 View
+ func tPopUp(
+ isPresented: Binding,
+ @ViewBuilder content: @escaping () -> Content
+ ) -> some View {
+ self.modifier(TPopUp.Modifier(isPresented: isPresented, newContent: content))
+ }
+
+ /// `TPopUp.Alert` 팝업 전용 View Modifier
+ /// - Parameters:
+ /// - isPresented: 팝업 표시 여부를 제어하는 Binding
+ /// - content: 팝업 알림 내용을 구성하는 클로저
+ /// - Returns: 팝업이 추가된 View
+ func tPopUp(isPresented: Binding, content: @escaping () -> TPopUp.Alert) -> some View {
+ self.modifier(TPopUp.Modifier(isPresented: isPresented, newContent: content))
+ }
+}
From 4c42ede92847a9dea47616e1f482b89bcdc70043 Mon Sep 17 00:00:00 2001
From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com>
Date: Thu, 16 Jan 2025 00:12:27 +0900
Subject: [PATCH 3/6] =?UTF-8?q?[Feat]=20TControl=EC=97=90=EC=84=9C=20Toggl?=
=?UTF-8?q?e=20=EB=B6=84=EB=A6=AC=20->=20TToggleStyle?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...ntrolButton.swift => TControlButton.swift} | 19 +++-------
.../Components/Control/TToggleStyle.swift | 35 +++++++++++++++++++
2 files changed, 39 insertions(+), 15 deletions(-)
rename TnT/Projects/DesignSystem/Sources/Components/Control/{ControlButton.swift => TControlButton.swift} (80%)
create mode 100644 TnT/Projects/DesignSystem/Sources/Components/Control/TToggleStyle.swift
diff --git a/TnT/Projects/DesignSystem/Sources/Components/Control/ControlButton.swift b/TnT/Projects/DesignSystem/Sources/Components/Control/TControlButton.swift
similarity index 80%
rename from TnT/Projects/DesignSystem/Sources/Components/Control/ControlButton.swift
rename to TnT/Projects/DesignSystem/Sources/Components/Control/TControlButton.swift
index 1e3bc03..497d411 100644
--- a/TnT/Projects/DesignSystem/Sources/Components/Control/ControlButton.swift
+++ b/TnT/Projects/DesignSystem/Sources/Components/Control/TControlButton.swift
@@ -10,7 +10,9 @@ import SwiftUI
/// TnT 앱 내에서 전반적으로 사용되는 커스텀 컨트롤 버튼 컴포넌트입니다.
public struct TControlButton: View {
- /// 버틉 탭 액션
+ /// 버튼 기본 사이즈
+ static private let defaultSize: CGSize = .init(width: 24, height: 24)
+ /// 버튼 탭 액션
private let tapAction: () -> Void
/// 버튼 스타일
private let type: Style
@@ -40,7 +42,7 @@ public struct TControlButton: View {
type.image(isSelected: isSelected)
.resizable()
.scaledToFit()
- .frame(width: type.defaultSize.width, height: type.defaultSize.height)
+ .frame(width: TControlButton.defaultSize.width, height: TControlButton.defaultSize.height)
})
}
}
@@ -51,7 +53,6 @@ public extension TControlButton {
case radio
case checkMark
case checkbox
- case toggle
case star
case heart
@@ -65,23 +66,11 @@ public extension TControlButton {
return Image(isSelected ? .icnCheckMarkFilled : .icnCheckMarkEmpty)
case .checkbox:
return Image(isSelected ? .icnCheckButtonSelected : .icnCheckButtonUnselected)
- case .toggle:
- return Image(isSelected ? .icnToggleSelected : .icnToggleUnselected)
case .star:
return Image(isSelected ? .icnStarFilled : .icnStarEmpty)
case .heart:
return Image(isSelected ? .icnHeartFilled : .icnHeartEmpty)
}
}
-
- /// 스타일에 따른 기본 크기
- var defaultSize: CGSize {
- switch self {
- case .toggle:
- return .init(width: 44, height: 24)
- default:
- return .init(width: 24, height: 24)
- }
- }
}
}
diff --git a/TnT/Projects/DesignSystem/Sources/Components/Control/TToggleStyle.swift b/TnT/Projects/DesignSystem/Sources/Components/Control/TToggleStyle.swift
new file mode 100644
index 0000000..7acd5a0
--- /dev/null
+++ b/TnT/Projects/DesignSystem/Sources/Components/Control/TToggleStyle.swift
@@ -0,0 +1,35 @@
+//
+// TToggleStyle.swift
+// DesignSystem
+//
+// Created by 박민서 on 1/15/25.
+// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
+//
+
+import SwiftUI
+
+/// TToggleStyle: ViewModifier
+/// SwiftUI의 `Toggle`에 TnT 스타일을 적용하기 위한 커스텀 ViewModifier입니다.
+/// 기본 크기와 토글 스타일을 설정하여 재사용 가능한 스타일링을 제공합니다.
+struct TToggleStyle: ViewModifier {
+ /// 기본 토글 크기
+ static let defaultSize: CGSize = .init(width: 44, height: 24)
+
+ /// `ViewModifier`가 적용된 뷰의 구성
+ /// - Parameter content: 스타일이 적용될 뷰
+ /// - Returns: TnT 스타일이 적용된 뷰
+ func body(content: Content) -> some View {
+ content
+ .toggleStyle(SwitchToggleStyle(tint: .red500))
+ .labelsHidden()
+ .frame(width: TToggleStyle.defaultSize.width, height: TToggleStyle.defaultSize.height)
+ }
+}
+
+/// Toggle 확장: TnT 스타일 적용
+public extension Toggle {
+ /// SwiftUI의 기본 `Toggle`에 TnT 스타일을 적용합니다.
+ func applyTToggleStyle() -> some View {
+ self.modifier(TToggleStyle())
+ }
+}
From be9b1c4161fee13e95555b866cdc1d5d35790a7e Mon Sep 17 00:00:00 2001
From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com>
Date: Thu, 16 Jan 2025 02:20:41 +0900
Subject: [PATCH 4/6] =?UTF-8?q?[Refactor]=20TPopUp=20=EC=BB=B4=ED=8F=AC?=
=?UTF-8?q?=EB=84=8C=ED=8A=B8=20TCA=20=EA=B3=A0=EB=A0=A4=ED=95=9C=20?=
=?UTF-8?q?=EA=B5=AC=EC=A1=B0=EB=A1=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0?=
=?UTF-8?q?=EB=A7=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Sources/Components/PopUp/TPopUp.swift | 189 ------------------
.../Components/PopUp/TPopUpAlertState.swift | 69 +++++++
.../Components/PopUp/TPopUpAlertView.swift | 87 ++++++++
.../Components/PopUp/TPopUpModifier.swift | 96 +++++++++
.../Sources/Utility/EquatableClosure.swift | 33 +++
5 files changed, 285 insertions(+), 189 deletions(-)
delete mode 100644 TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUp.swift
create mode 100644 TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUpAlertState.swift
create mode 100644 TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUpAlertView.swift
create mode 100644 TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUpModifier.swift
create mode 100644 TnT/Projects/DesignSystem/Sources/Utility/EquatableClosure.swift
diff --git a/TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUp.swift b/TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUp.swift
deleted file mode 100644
index 97d2077..0000000
--- a/TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUp.swift
+++ /dev/null
@@ -1,189 +0,0 @@
-//
-// TPopUp.swift
-// DesignSystem
-//
-// Created by 박민서 on 1/15/25.
-// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
-//
-
-import SwiftUI
-
-/// 팝업 관련 네임스페이스
-public struct TPopUp {
- /// 팝업 내부 콘텐츠의 기본 패딩
- public static let defaultInnerPadding: CGFloat = 20
- /// 팝업 배경의 기본 불투명도
- /// 0.0 = 완전 투명, 1.0 = 완전 불투명
- public static let defaultBackgroundOpacity: Double = 0.8
- /// 팝업의 기본 배경 색상
- public static let defaultPopUpBackgroundColor: Color = .white
- /// 팝업 모서리의 기본 곡률 (Corner Radius)
- public static let defaultCornerRadius: CGFloat = 16
- /// 팝업 그림자의 기본 반경
- public static let defaultShadowRadius: CGFloat = 10
- /// 팝업 콘텐츠의 기본 크기
- public static let defaultContentSize: CGSize = .init(width: 297, height: 175)
-}
-
-extension TPopUp {
- /// 팝업 컨테이너 (공통 레이아웃)
- public struct Modifier: ViewModifier {
-
- /// 팝업에 표시될 내부 콘텐츠 클로저
- private let innerContent: () -> InnerContent
- /// 팝업 표시 여부
- @Binding private var isPresented: Bool
-
- /// TPopupModifier 초기화 메서드
- /// - Parameters:
- /// - isPresented: 팝업 표시 여부를 제어하는 Binding
- /// - newContent: 팝업에 표시될 내부 콘텐츠 클로저
- public init(
- isPresented: Binding,
- newContent: @escaping () -> InnerContent
- ) {
- self._isPresented = isPresented
- self.innerContent = newContent
- }
-
- public func body(content: Content) -> some View {
- ZStack {
- // 기존 뷰
- content
- .zIndex(0)
-
- if isPresented {
- // 반투명 배경
- Color.black.opacity(defaultBackgroundOpacity)
- .ignoresSafeArea()
- .zIndex(1)
- .onTapGesture {
- isPresented = false
- }
-
- // 팝업 뷰
- self.innerContent()
- .frame(minWidth: TPopUp.defaultContentSize.width, minHeight: TPopUp.defaultContentSize.height)
- .padding(defaultInnerPadding)
- .background(defaultPopUpBackgroundColor)
- .cornerRadius(defaultCornerRadius)
- .shadow(radius: 10)
- .padding()
- .zIndex(2)
- }
- }
- .animation(.bouncy, value: isPresented)
- }
- }
-}
-
-extension TPopUp {
- /// 팝업 Alert컨텐츠
- public struct Alert: View {
- /// 팝업 제목
- private let title: String
- /// 팝업 메시지
- private let message: String
- /// 팝업 버튼 배열
- private let buttons: [TPopUp.ButtonContent]
-
- /// TPopUpAlert 초기화 메서드
- /// - Parameters:
- /// - title: 팝업 제목
- /// - message: 팝업 메시지
- /// - buttons: 팝업 버튼 배열
- public init(title: String, message: String, buttons: [TPopUp.ButtonContent]) {
- self.title = title
- self.message = message
- self.buttons = buttons
- }
-
- public var body: some View {
- VStack(spacing: 20) {
- // 텍스트 Section
- VStack(spacing: 8) {
- Text(title)
- .typographyStyle(.heading4, with: .neutral900)
- .multilineTextAlignment(.center)
- .padding(.top, 20)
-
- Text(message)
- .typographyStyle(.body2Medium, with: .neutral500)
- .multilineTextAlignment(.center)
- }
-
- // 버튼 Section
- HStack {
- // TODO: 버튼 컴포넌트 완성시 대체
- ForEach(buttons, id: \.title) { button in
- Button(action: button.action) {
- Text(button.title)
- .typographyStyle(.body1Medium, with: button.style == .primary ? Color.neutral50 : Color.neutral500)
- .padding()
- .frame(maxWidth: .infinity)
- .background(button.style == .primary ? Color.neutral900 : Color.neutral100)
- .foregroundColor(.white)
- .cornerRadius(8)
- }
- }
- }
- }
- }
- }
-}
-
-// TODO: 버튼 컴포넌트 완성시 대체
-extension TPopUp {
- /// 팝업 버튼 스타일
- public struct ButtonContent {
- /// 버튼 제목
- let title: String
- /// 버튼 스타일
- let style: Style
- /// 버튼 클릭 시 동작
- let action: () -> Void
-
- public enum Style {
- case primary
- case secondary
- }
-
- /// TPopUpButtonContent 초기화 메서드
- /// - Parameters:
- /// - title: 버튼 제목
- /// - style: 버튼 스타일 (기본값: `.primary`)
- /// - action: 버튼 클릭 시 동작
- public init(
- title: String,
- style: Style = .primary,
- action: @escaping () -> Void = {}
- ) {
- self.title = title
- self.action = action
- self.style = style
- }
- }
-}
-
-public extension View {
- /// 팝업 표시를 위한 View Modifier
- /// - Parameters:
- /// - isPresented: 팝업 표시 여부를 제어하는 Binding
- /// - content: 팝업 내부에 표시할 콘텐츠 클로저
- /// - Returns: 팝업이 추가된 View
- func tPopUp(
- isPresented: Binding,
- @ViewBuilder content: @escaping () -> Content
- ) -> some View {
- self.modifier(TPopUp.Modifier(isPresented: isPresented, newContent: content))
- }
-
- /// `TPopUp.Alert` 팝업 전용 View Modifier
- /// - Parameters:
- /// - isPresented: 팝업 표시 여부를 제어하는 Binding
- /// - content: 팝업 알림 내용을 구성하는 클로저
- /// - Returns: 팝업이 추가된 View
- func tPopUp(isPresented: Binding, content: @escaping () -> TPopUp.Alert) -> some View {
- self.modifier(TPopUp.Modifier(isPresented: isPresented, newContent: content))
- }
-}
diff --git a/TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUpAlertState.swift b/TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUpAlertState.swift
new file mode 100644
index 0000000..45f04e0
--- /dev/null
+++ b/TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUpAlertState.swift
@@ -0,0 +1,69 @@
+//
+// TPopupAlertState.swift
+// DesignSystem
+//
+// Created by 박민서 on 1/15/25.
+// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
+//
+
+import SwiftUI
+import ComposableArchitecture
+
+/// TPopUpAlertView에 표시하는 정보입니다.
+/// 팝업의 제목, 메시지, 버튼 정보를 포함.
+public struct TPopupAlertState: Equatable {
+ /// 팝업 제목
+ public var title: String
+ /// 팝업 메시지 (옵션)
+ public var message: String?
+ /// 팝업에 표시될 버튼 배열
+ public var buttons: [ButtonState]
+
+ /// TPopupAlertState 초기화 메서드
+ /// - Parameters:
+ /// - title: 팝업의 제목
+ /// - message: 팝업의 메시지 (선택 사항, 기본값: `nil`)
+ /// - buttons: 팝업에 표시할 버튼 배열 (기본값: 빈 배열)
+ public init(
+ title: String,
+ message: String? = nil,
+ buttons: [ButtonState] = []
+ ) {
+ self.title = title
+ self.message = message
+ self.buttons = buttons
+ }
+}
+
+public extension TPopupAlertState {
+ // TODO: 버튼 컴포넌트 완성 시 수정
+ /// TPopUpAlertView.AlertButton에 표시하는 정보입니다.
+ struct ButtonState: Equatable {
+ /// 버튼 제목
+ public let title: String
+ /// 버튼 스타일
+ public let style: Style
+ /// 버튼 클릭 시 동작
+ public let action: EquatableClosure
+
+ public enum Style {
+ case primary
+ case secondary
+ }
+
+ /// TPopupAlertState.ButtonState 초기화 메서드
+ /// - Parameters:
+ /// - title: 버튼 제목
+ /// - style: 버튼 스타일 (기본값: `.primary`)
+ /// - action: 버튼 클릭 시 동작
+ public init(
+ title: String,
+ style: Style = .primary,
+ action: EquatableClosure
+ ) {
+ self.action = action
+ self.title = title
+ self.style = style
+ }
+ }
+}
diff --git a/TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUpAlertView.swift b/TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUpAlertView.swift
new file mode 100644
index 0000000..7729fa8
--- /dev/null
+++ b/TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUpAlertView.swift
@@ -0,0 +1,87 @@
+//
+// TPopUpAlertView.swift
+// DesignSystem
+//
+// Created by 박민서 on 1/16/25.
+// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
+//
+
+import SwiftUI
+
+/// 팝업 Alert의 콘텐츠 뷰
+/// 타이틀, 메시지, 버튼 섹션으로 구성.
+public struct TPopUpAlertView: View {
+ /// 팝업 상태 정보
+ private let alertState: TPopupAlertState
+
+ /// - Parameter alertState: 팝업에 표시할 상태 정보
+ public init(alertState: TPopupAlertState) {
+ self.alertState = alertState
+ }
+
+ public var body: some View {
+ VStack(spacing: 20) {
+ // 텍스트 Section
+ VStack(spacing: 8) {
+ Text(alertState.title)
+ .typographyStyle(.heading4, with: .neutral900)
+ .multilineTextAlignment(.center)
+ .padding(.top, 20)
+ if let message = alertState.message {
+ Text(message)
+ .typographyStyle(.body2Medium, with: .neutral500)
+ .multilineTextAlignment(.center)
+ }
+ }
+
+ // 버튼 Section
+ HStack {
+ ForEach(alertState.buttons, id: \.title) { buttonState in
+ buttonState.toButton()
+ }
+ }
+ }
+ }
+}
+
+public extension TPopUpAlertView {
+ // TODO: 버튼 컴포넌트 완성 시 수정
+ struct AlertButton: View {
+ let title: String
+ let style: TPopupAlertState.ButtonState.Style
+ let action: () -> Void
+
+ init(
+ title: String,
+ style: TPopupAlertState.ButtonState.Style,
+ action: @escaping () -> Void
+ ) {
+ self.title = title
+ self.style = style
+ self.action = action
+ }
+
+ public var body: some View {
+ Button(action: action) {
+ Text(title)
+ .typographyStyle(.body1Medium, with: style == .primary ? Color.neutral50 : Color.neutral500)
+ .padding()
+ .frame(maxWidth: .infinity)
+ .background(style == .primary ? Color.neutral900 : Color.neutral100)
+ .foregroundColor(.white)
+ .cornerRadius(8)
+ }
+ }
+ }
+}
+
+extension TPopupAlertState.ButtonState {
+ /// `ButtonState`를 `AlertButton`으로 변환
+ func toButton() -> TPopUpAlertView.AlertButton {
+ TPopUpAlertView.AlertButton(
+ title: self.title,
+ style: self.style,
+ action: self.action.execute
+ )
+ }
+}
diff --git a/TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUpModifier.swift b/TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUpModifier.swift
new file mode 100644
index 0000000..28cedb7
--- /dev/null
+++ b/TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUpModifier.swift
@@ -0,0 +1,96 @@
+//
+// TPopUpModifier.swift
+// DesignSystem
+//
+// Created by 박민서 on 1/16/25.
+// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
+//
+
+import SwiftUI
+
+/// 팝업 컨테이너 (공통 레이아웃)
+public struct TPopUpModifier: ViewModifier {
+
+ /// 팝업 내부 콘텐츠의 기본 패딩
+ private let defaultInnerPadding: CGFloat = 20
+ /// 팝업 배경의 기본 불투명도
+ /// 0.0 = 완전 투명, 1.0 = 완전 불투명
+ private let defaultBackgroundOpacity: Double = 0.8
+ /// 팝업의 기본 배경 색상
+ private let defaultPopUpBackgroundColor: Color = .white
+ /// 팝업 모서리의 기본 곡률 (Corner Radius)
+ private let defaultCornerRadius: CGFloat = 16
+ /// 팝업 그림자의 기본 반경
+ private let defaultShadowRadius: CGFloat = 10
+ /// 팝업 콘텐츠의 기본 크기
+ private let defaultContentSize: CGSize = .init(width: 297, height: 175)
+
+ /// 팝업에 표시될 내부 콘텐츠 클로저
+ private let innerContent: () -> InnerContent
+ /// 팝업 표시 여부
+ @Binding private var isPresented: Bool
+
+ /// TPopupModifier 초기화 메서드
+ /// - Parameters:
+ /// - isPresented: 팝업 표시 여부를 제어하는 Binding
+ /// - newContent: 팝업에 표시될 내부 콘텐츠 클로저
+ public init(
+ isPresented: Binding,
+ newContent: @escaping () -> InnerContent
+ ) {
+ self._isPresented = isPresented
+ self.innerContent = newContent
+ }
+
+ public func body(content: Content) -> some View {
+ ZStack {
+ // 기존 뷰
+ content
+ .zIndex(0)
+
+ if isPresented {
+ // 반투명 배경
+ Color.black.opacity(defaultBackgroundOpacity)
+ .ignoresSafeArea()
+ .zIndex(1)
+ .onTapGesture {
+ isPresented = false
+ }
+
+ // 팝업 뷰
+ self.innerContent()
+ .frame(minWidth: defaultContentSize.width, minHeight: defaultContentSize.height)
+ .padding(defaultInnerPadding)
+ .background(defaultPopUpBackgroundColor)
+ .cornerRadius(defaultCornerRadius)
+ .shadow(radius: defaultShadowRadius)
+ .padding()
+ .zIndex(2)
+ }
+ }
+ .animation(.easeInOut, value: isPresented)
+ }
+}
+
+public extension View {
+ /// 팝업 표시를 위한 View Modifier
+ /// - Parameters:
+ /// - isPresented: 팝업 표시 여부를 제어하는 Binding
+ /// - content: 팝업 내부에 표시할 콘텐츠 클로저
+ /// - Returns: 팝업이 추가된 View
+ func tPopUp(
+ isPresented: Binding,
+ @ViewBuilder content: @escaping () -> Content
+ ) -> some View {
+ self.modifier(TPopUpModifier(isPresented: isPresented, newContent: content))
+ }
+
+ /// `TPopUp.Alert` 팝업 전용 View Modifier
+ /// - Parameters:
+ /// - isPresented: 팝업 표시 여부를 제어하는 Binding
+ /// - content: 팝업 알림 내용을 구성하는 클로저
+ /// - Returns: 팝업이 추가된 View
+ func tPopUp(isPresented: Binding, content: @escaping () -> TPopUpAlertView) -> some View {
+ self.modifier(TPopUpModifier(isPresented: isPresented, newContent: content))
+ }
+}
diff --git a/TnT/Projects/DesignSystem/Sources/Utility/EquatableClosure.swift b/TnT/Projects/DesignSystem/Sources/Utility/EquatableClosure.swift
new file mode 100644
index 0000000..691033a
--- /dev/null
+++ b/TnT/Projects/DesignSystem/Sources/Utility/EquatableClosure.swift
@@ -0,0 +1,33 @@
+//
+// EquatableClosure.swift
+// DesignSystem
+//
+// Created by 박민서 on 1/16/25.
+// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
+//
+
+import Foundation
+
+/// 클로저를 Equatable로 사용할 수 있도록 래핑한 구조체입니다.
+/// 고유 ID를 통해 클로저의 동등성을 비교하며, 내부에서 클로저를 실행할 수 있는 기능을 제공합니다.
+public struct EquatableClosure: Equatable {
+ /// EquatableClosure를 고유하게 식별할 수 있는 UUID입니다.
+ private let id: UUID = UUID()
+ /// 실행할 클로저
+ private let action: () -> Void
+
+ public static func == (lhs: EquatableClosure, rhs: EquatableClosure) -> Bool {
+ lhs.id == rhs.id
+ }
+
+ /// EquatableClosure 초기화 메서드
+ /// - Parameter action: 실행할 클로저
+ public init(action: @escaping () -> Void) {
+ self.action = action
+ }
+
+ /// 클로저를 실행하는 메서드
+ public func execute() {
+ action()
+ }
+}
From 5a7525ef1a19a36cde8d4c6be4da98377960b588 Mon Sep 17 00:00:00 2001
From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com>
Date: Thu, 16 Jan 2025 02:28:31 +0900
Subject: [PATCH 5/6] =?UTF-8?q?[Delete]=20=EB=B6=88=ED=95=84=EC=9A=94=20?=
=?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Icons/icn_toggle_selected.imageset/Contents.json | 12 ------------
.../icn_toggle_selected.svg | 4 ----
.../icn_toggle_unselected.imageset/Contents.json | 12 ------------
.../icn_toggle_unselected.svg | 4 ----
.../Sources/DesignSystem/Image+DesignSystem.swift | 2 --
5 files changed, 34 deletions(-)
delete mode 100644 TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_selected.imageset/Contents.json
delete mode 100644 TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_selected.imageset/icn_toggle_selected.svg
delete mode 100644 TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_unselected.imageset/Contents.json
delete mode 100644 TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_unselected.imageset/icn_toggle_unselected.svg
diff --git a/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_selected.imageset/Contents.json b/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_selected.imageset/Contents.json
deleted file mode 100644
index e178778..0000000
--- a/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_selected.imageset/Contents.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "images" : [
- {
- "filename" : "icn_toggle_selected.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
diff --git a/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_selected.imageset/icn_toggle_selected.svg b/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_selected.imageset/icn_toggle_selected.svg
deleted file mode 100644
index 2486023..0000000
--- a/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_selected.imageset/icn_toggle_selected.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_unselected.imageset/Contents.json b/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_unselected.imageset/Contents.json
deleted file mode 100644
index 77f3f72..0000000
--- a/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_unselected.imageset/Contents.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "images" : [
- {
- "filename" : "icn_toggle_unselected.svg",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
diff --git a/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_unselected.imageset/icn_toggle_unselected.svg b/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_unselected.imageset/icn_toggle_unselected.svg
deleted file mode 100644
index 13a74cf..0000000
--- a/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Icons/icn_toggle_unselected.imageset/icn_toggle_unselected.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/TnT/Projects/DesignSystem/Sources/DesignSystem/Image+DesignSystem.swift b/TnT/Projects/DesignSystem/Sources/DesignSystem/Image+DesignSystem.swift
index c0bc548..2b1c705 100644
--- a/TnT/Projects/DesignSystem/Sources/DesignSystem/Image+DesignSystem.swift
+++ b/TnT/Projects/DesignSystem/Sources/DesignSystem/Image+DesignSystem.swift
@@ -40,8 +40,6 @@ public extension ImageResource {
static let icnStarFilled: ImageResource = DesignSystemAsset.icnStarFilled.imageResource
static let icnHeartEmpty: ImageResource = DesignSystemAsset.icnHeartEmpty.imageResource
static let icnHeartFilled: ImageResource = DesignSystemAsset.icnHeartFilled.imageResource
- static let icnToggleUnselected: ImageResource = DesignSystemAsset.icnToggleUnselected.imageResource
- static let icnToggleSelected: ImageResource = DesignSystemAsset.icnToggleSelected.imageResource
}
// MARK: Image
From c0683f408c11952f7c48115258b62196ac07a476 Mon Sep 17 00:00:00 2001
From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com>
Date: Thu, 16 Jan 2025 02:43:48 +0900
Subject: [PATCH 6/6] =?UTF-8?q?[Chore]=20=EB=B6=88=ED=95=84=EC=9A=94=20?=
=?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=82=AD=EC=A0=9C,=20=EC=A0=91=EA=B7=BC?=
=?UTF-8?q?=EC=A0=9C=EC=96=B4=EC=9E=90=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Sources/Components/Control/TControlButton.swift | 2 --
.../DesignSystem/Sources/Components/PopUp/TPopUpAlertView.swift | 2 +-
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/TnT/Projects/DesignSystem/Sources/Components/Control/TControlButton.swift b/TnT/Projects/DesignSystem/Sources/Components/Control/TControlButton.swift
index 497d411..ae4b0ff 100644
--- a/TnT/Projects/DesignSystem/Sources/Components/Control/TControlButton.swift
+++ b/TnT/Projects/DesignSystem/Sources/Components/Control/TControlButton.swift
@@ -36,7 +36,6 @@ public struct TControlButton: View {
public var body: some View {
Button(action: {
- isSelected.toggle()
tapAction()
}, label: {
type.image(isSelected: isSelected)
@@ -59,7 +58,6 @@ public extension TControlButton {
/// 선택 상태에 따른 이미지 반환
func image(isSelected: Bool) -> Image {
switch self {
-
case .radio:
return Image(isSelected ? .icnRadioButtonSelected : .icnRadioButtonUnselected)
case .checkMark:
diff --git a/TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUpAlertView.swift b/TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUpAlertView.swift
index 7729fa8..8b3403b 100644
--- a/TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUpAlertView.swift
+++ b/TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUpAlertView.swift
@@ -75,7 +75,7 @@ public extension TPopUpAlertView {
}
}
-extension TPopupAlertState.ButtonState {
+public extension TPopupAlertState.ButtonState {
/// `ButtonState`를 `AlertButton`으로 변환
func toButton() -> TPopUpAlertView.AlertButton {
TPopUpAlertView.AlertButton(