-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #21 from YAPP-Github/TNT-139-createMinComponents-C…
…ontrolAndPopUp [TNT-139] TControlButton, TPopUp 컴포넌트 코드 작성
- Loading branch information
Showing
6 changed files
with
394 additions
and
0 deletions.
There are no files selected for viewing
74 changes: 74 additions & 0 deletions
74
TnT/Projects/DesignSystem/Sources/Components/Control/TControlButton.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
// | ||
// TControlButton.swift | ||
// DesignSystem | ||
// | ||
// Created by 박민서 on 1/15/25. | ||
// Copyright © 2025 yapp25thTeamTnT. All rights reserved. | ||
// | ||
|
||
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 | ||
/// 버튼 선택 상태 | ||
@Binding private var isSelected: Bool | ||
|
||
/// TControlButton 생성자 | ||
/// - Parameters: | ||
/// - type: 버튼의 스타일. `TControlButton.Style` 사용. | ||
/// - isSelected: 버튼의 선택 상태를 관리하는 바인딩. | ||
/// - action: 버튼이 탭되었을 때 실행할 액션. (기본값: 빈 클로저) | ||
public init( | ||
type: Style, | ||
isSelected: Binding<Bool>, | ||
action: @escaping () -> Void = {} | ||
) { | ||
self.type = type | ||
self._isSelected = isSelected | ||
self.tapAction = action | ||
} | ||
|
||
public var body: some View { | ||
Button(action: { | ||
tapAction() | ||
}, label: { | ||
type.image(isSelected: isSelected) | ||
.resizable() | ||
.scaledToFit() | ||
.frame(width: TControlButton.defaultSize.width, height: TControlButton.defaultSize.height) | ||
}) | ||
} | ||
} | ||
|
||
public extension TControlButton { | ||
/// TControlButton의 스타일입니다. | ||
enum Style { | ||
case radio | ||
case checkMark | ||
case checkbox | ||
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 .star: | ||
return Image(isSelected ? .icnStarFilled : .icnStarEmpty) | ||
case .heart: | ||
return Image(isSelected ? .icnHeartFilled : .icnHeartEmpty) | ||
} | ||
} | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
TnT/Projects/DesignSystem/Sources/Components/Control/TToggleStyle.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()) | ||
} | ||
} |
69 changes: 69 additions & 0 deletions
69
TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUpAlertState.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} | ||
} |
87 changes: 87 additions & 0 deletions
87
TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUpAlertView.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} | ||
} | ||
} | ||
|
||
public extension TPopupAlertState.ButtonState { | ||
/// `ButtonState`를 `AlertButton`으로 변환 | ||
func toButton() -> TPopUpAlertView.AlertButton { | ||
TPopUpAlertView.AlertButton( | ||
title: self.title, | ||
style: self.style, | ||
action: self.action.execute | ||
) | ||
} | ||
} |
96 changes: 96 additions & 0 deletions
96
TnT/Projects/DesignSystem/Sources/Components/PopUp/TPopUpModifier.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
// | ||
// TPopUpModifier.swift | ||
// DesignSystem | ||
// | ||
// Created by 박민서 on 1/16/25. | ||
// Copyright © 2025 yapp25thTeamTnT. All rights reserved. | ||
// | ||
|
||
import SwiftUI | ||
|
||
/// 팝업 컨테이너 (공통 레이아웃) | ||
public struct TPopUpModifier<InnerContent: View>: 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<Bool>, | ||
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<Content: View>( | ||
isPresented: Binding<Bool>, | ||
@ViewBuilder content: @escaping () -> Content | ||
) -> some View { | ||
self.modifier(TPopUpModifier<Content>(isPresented: isPresented, newContent: content)) | ||
} | ||
|
||
/// `TPopUp.Alert` 팝업 전용 View Modifier | ||
/// - Parameters: | ||
/// - isPresented: 팝업 표시 여부를 제어하는 Binding | ||
/// - content: 팝업 알림 내용을 구성하는 클로저 | ||
/// - Returns: 팝업이 추가된 View | ||
func tPopUp(isPresented: Binding<Bool>, content: @escaping () -> TPopUpAlertView) -> some View { | ||
self.modifier(TPopUpModifier(isPresented: isPresented, newContent: content)) | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
TnT/Projects/DesignSystem/Sources/Utility/EquatableClosure.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() | ||
} | ||
} |