Skip to content

Commit f7b2047

Browse files
Merge pull request #44 from componentskit/ukalert
UKAlertController
2 parents 36cd493 + 05e7eed commit f7b2047

File tree

14 files changed

+574
-53
lines changed

14 files changed

+574
-53
lines changed

Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/ModalPreview+Helpers.swift

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,23 +85,14 @@ struct ModalPreviewHelpers {
8585
ContainerRadiusPicker(selection: self.$model.cornerRadius) {
8686
Text("Custom 30px").tag(ContainerRadius.custom(30))
8787
}
88-
Picker("Overlay Style", selection: self.$model.overlayStyle) {
89-
Text("Blurred").tag(ModalOverlayStyle.blurred)
90-
Text("Dimmed").tag(ModalOverlayStyle.dimmed)
91-
Text("Transparent").tag(ModalOverlayStyle.transparent)
92-
}
88+
OverlayStylePicker(selection: self.$model.overlayStyle)
9389
Picker("Size", selection: self.$model.size) {
9490
Text("Small").tag(ModalSize.small)
9591
Text("Medium").tag(ModalSize.medium)
9692
Text("Large").tag(ModalSize.large)
9793
Text("Full").tag(ModalSize.full)
9894
}
99-
Picker("Transition", selection: self.$model.transition) {
100-
Text("None").tag(ModalTransition.none)
101-
Text("Fast").tag(ModalTransition.fast)
102-
Text("Normal").tag(ModalTransition.normal)
103-
Text("Slow").tag(ModalTransition.slow)
104-
}
95+
TransitionPicker(selection: self.$model.transition)
10596
self.additionalPickers()
10697
}
10798
}

Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/PreviewPickers.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,20 @@ struct KeyboardTypePicker: View {
211211
}
212212
}
213213

214+
// MARK: - OverlayStylePicker
215+
216+
struct OverlayStylePicker: View {
217+
@Binding var selection: ModalOverlayStyle
218+
219+
var body: some View {
220+
Picker("Overlay Style", selection: self.$selection) {
221+
Text("Blurred").tag(ModalOverlayStyle.blurred)
222+
Text("Dimmed").tag(ModalOverlayStyle.dimmed)
223+
Text("Transparent").tag(ModalOverlayStyle.transparent)
224+
}
225+
}
226+
}
227+
214228
// MARK: - SizePicker
215229

216230
struct SizePicker: View {
@@ -243,6 +257,21 @@ struct SubmitTypePicker: View {
243257
}
244258
}
245259

260+
// MARK: - TransitionPicker
261+
262+
struct TransitionPicker: View {
263+
@Binding var selection: ModalTransition
264+
265+
var body: some View {
266+
Picker("Transition", selection: self.$selection) {
267+
Text("None").tag(ModalTransition.none)
268+
Text("Fast").tag(ModalTransition.fast)
269+
Text("Normal").tag(ModalTransition.normal)
270+
Text("Slow").tag(ModalTransition.slow)
271+
}
272+
}
273+
}
274+
246275
// MARK: - UniversalColorPicker
247276

248277
struct UniversalColorPicker: View {
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import ComponentsKit
2+
import SwiftUI
3+
import UIKit
4+
5+
struct AlertPreview: View {
6+
@State private var model = AlertVM {
7+
$0.title = Self.alertTitle
8+
$0.message = AlertMessage.short.rawValue
9+
$0.primaryButton = Self.initialPrimaryButton
10+
$0.secondaryButton = Self.initialSecondaryButton
11+
}
12+
13+
var body: some View {
14+
VStack {
15+
PreviewWrapper(title: "UIKit") {
16+
UKButton(model: .init { $0.title = "Show Alert" }) {
17+
UIApplication.shared.topViewController?.present(
18+
UKAlertController(model: self.model),
19+
animated: true
20+
)
21+
}
22+
.preview
23+
}
24+
Form {
25+
Section("Title") {
26+
Toggle("Has Title", isOn: .init(
27+
get: { return self.model.title != nil },
28+
set: { newValue in
29+
self.model.title = newValue ? Self.alertTitle : nil
30+
}
31+
))
32+
}
33+
34+
Section("Message") {
35+
Picker("Alert Message", selection: self.$model.message) {
36+
Text("None").tag(Optional<String>.none)
37+
Text("Short").tag(AlertMessage.short.rawValue)
38+
Text("Long").tag(AlertMessage.long.rawValue)
39+
}
40+
}
41+
42+
Section("Primary Button") {
43+
Toggle("Has Primary Button", isOn: .init(
44+
get: { return self.model.primaryButton != nil },
45+
set: { newValue in
46+
self.model.primaryButton = newValue ? Self.initialPrimaryButton : nil
47+
}
48+
))
49+
if self.model.primaryButton != nil {
50+
Picker("Title", selection: self.primaryButtonVMOrDefault.title) {
51+
Text("Short").tag(PrimaryButtonText.short.rawValue)
52+
Text("Longer").tag(PrimaryButtonText.longer.rawValue)
53+
}
54+
self.buttonPickers(for: self.primaryButtonVMOrDefault)
55+
}
56+
}
57+
58+
Section("Secondary Button") {
59+
Toggle("Has Secondary Button", isOn: .init(
60+
get: { return self.model.secondaryButton != nil },
61+
set: { newValue in
62+
self.model.secondaryButton = newValue ? Self.initialSecondaryButton : nil
63+
}
64+
))
65+
if self.model.secondaryButton != nil {
66+
Picker("Title", selection: self.secondaryButtonVMOrDefault.title) {
67+
Text("Short").tag(SecondaryButtonText.short.rawValue)
68+
Text("Longer").tag(SecondaryButtonText.longer.rawValue)
69+
}
70+
self.buttonPickers(for: self.secondaryButtonVMOrDefault)
71+
}
72+
}
73+
74+
Section("Main Properties") {
75+
Picker("Background Color", selection: self.$model.backgroundColor) {
76+
Text("Default").tag(Optional<UniversalColor>.none)
77+
Text("Accent Background").tag(ComponentColor.accent.background)
78+
Text("Success Background").tag(ComponentColor.success.background)
79+
Text("Warning Background").tag(ComponentColor.warning.background)
80+
Text("Danger Background").tag(ComponentColor.danger.background)
81+
}
82+
Toggle("Closes On Overlay Tap", isOn: self.$model.closesOnOverlayTap)
83+
Picker("Content Paddings", selection: self.$model.contentPaddings) {
84+
Text("12px").tag(Paddings(padding: 12))
85+
Text("16px").tag(Paddings(padding: 16))
86+
Text("20px").tag(Paddings(padding: 20))
87+
}
88+
ContainerRadiusPicker(selection: self.$model.cornerRadius) {
89+
Text("Custom 30px").tag(ContainerRadius.custom(30))
90+
}
91+
OverlayStylePicker(selection: self.$model.overlayStyle)
92+
TransitionPicker(selection: self.$model.transition)
93+
}
94+
}
95+
}
96+
}
97+
98+
// MARK: - Reusable Pickers
99+
100+
private func buttonPickers(for buttonVM: Binding<AlertButtonVM>) -> some View {
101+
Group {
102+
AnimationScalePicker(selection: buttonVM.animationScale)
103+
ComponentOptionalColorPicker(selection: buttonVM.color)
104+
ComponentRadiusPicker(selection: buttonVM.cornerRadius) {
105+
Text("Custom: 20px").tag(ComponentRadius.custom(20))
106+
}
107+
Picker("Style", selection: buttonVM.style) {
108+
Text("Filled").tag(ButtonStyle.filled)
109+
Text("Plain").tag(ButtonStyle.plain)
110+
Text("Light").tag(ButtonStyle.light)
111+
Text("Bordered with small border").tag(ButtonStyle.bordered(.small))
112+
Text("Bordered with medium border").tag(ButtonStyle.bordered(.medium))
113+
Text("Bordered with large border").tag(ButtonStyle.bordered(.large))
114+
}
115+
}
116+
}
117+
118+
// MARK: - Helpers
119+
120+
enum AlertMessage: String {
121+
case short = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
122+
case long = """
123+
Lorem ipsum odor amet, consectetuer adipiscing elit. Vitae vehicula pellentesque lectus orci fames. Cras suscipit dui tortor penatibus turpis ultrices. Laoreet montes adipiscing ante dapibus facilisis. Lorem per fames nec duis quis eleifend imperdiet. Tincidunt id interdum adipiscing eros dis quis platea varius. Potenti eleifend eu molestie laoreet varius sapien. Adipiscing nascetur platea penatibus curabitur tempus nibh laoreet porttitor. Augue et curabitur cras sed semper inceptos nunc montes mollis.
124+
125+
Lectus arcu pellentesque inceptos tempor fringilla nascetur. Erat curae convallis integer mi, quis facilisi tortor. Phasellus aliquam molestie vehicula odio in dis maximus diam elit. Rutrum gravida amet euismod feugiat fusce. Est egestas velit vulputate senectus sociosqu fringilla eget nibh. Nam pellentesque aenean mi platea tincidunt quam sem purus. Himenaeos suspendisse nec sapien habitasse ultricies maecenas libero odio. Rutrum senectus maximus ultrices, ad nam ultricies placerat.
126+
127+
Enim habitant laoreet inceptos scelerisque senectus, tellus molestie ut. Eros risus nibh morbi eu aenean. Velit ligula magnis aliquet at luctus. Dapibus vestibulum consectetur euismod vitae per ultrices litora quis. Aptent eleifend dapibus urna lacinia felis nisl. Sit amet fusce nullam feugiat posuere. Urna amet curae velit fermentum interdum vestibulum penatibus. Penatibus vivamus sem ultricies pellentesque congue id mattis diam. Aliquam efficitur mi gravida sollicitudin; amet imperdiet. Rutrum mollis risus justo tortor in duis cursus.
128+
"""
129+
}
130+
enum PrimaryButtonText: String {
131+
case short = "Continue"
132+
case longer = "Remind me later"
133+
}
134+
enum SecondaryButtonText: String {
135+
case short = "Cancel"
136+
case longer = "Cancel, Don't Do That"
137+
}
138+
static let alertTitle = "Alert Title"
139+
static let initialPrimaryButton = AlertButtonVM {
140+
$0.title = PrimaryButtonText.short.rawValue
141+
$0.style = .filled
142+
$0.color = .primary
143+
}
144+
static let initialSecondaryButton = AlertButtonVM {
145+
$0.title = SecondaryButtonText.short.rawValue
146+
$0.style = .light
147+
}
148+
149+
var primaryButtonVMOrDefault: Binding<AlertButtonVM> {
150+
return .init(
151+
get: { self.model.primaryButton ?? Self.initialPrimaryButton },
152+
set: { self.model.primaryButton = $0 }
153+
)
154+
}
155+
var secondaryButtonVMOrDefault: Binding<AlertButtonVM> {
156+
return .init(
157+
get: { self.model.secondaryButton ?? Self.initialSecondaryButton },
158+
set: { self.model.secondaryButton = $0 }
159+
)
160+
}
161+
}
162+
163+
#Preview {
164+
AlertPreview()
165+
}

Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ struct ButtonPreview: View {
2727
Toggle("Full Width", isOn: self.$model.isFullWidth)
2828
SizePicker(selection: self.$model.size)
2929
Picker("Style", selection: self.$model.style) {
30-
Text("Filled").tag(ButtonVM.Style.filled)
31-
Text("Plain").tag(ButtonVM.Style.plain)
32-
Text("Light").tag(ButtonVM.Style.light)
33-
Text("Bordered with small border").tag(ButtonVM.Style.bordered(.small))
34-
Text("Bordered with medium border").tag(ButtonVM.Style.bordered(.medium))
35-
Text("Bordered with large border").tag(ButtonVM.Style.bordered(.large))
30+
Text("Filled").tag(ButtonStyle.filled)
31+
Text("Plain").tag(ButtonStyle.plain)
32+
Text("Light").tag(ButtonStyle.light)
33+
Text("Bordered with small border").tag(ButtonStyle.bordered(.small))
34+
Text("Bordered with medium border").tag(ButtonStyle.bordered(.medium))
35+
Text("Bordered with large border").tag(ButtonStyle.bordered(.large))
3636
}
3737
}
3838
}

Examples/DemosApp/DemosApp/Core/App.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ struct App: View {
3939
}
4040

4141
Section("Modals") {
42+
NavigationLinkWithTitle("Alert") {
43+
AlertPreview()
44+
}
4245
NavigationLinkWithTitle("Bottom Modal") {
4346
BottomModalPreview()
4447
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import Foundation
2+
3+
/// A model that defines the appearance properties for a button in the alert.
4+
public struct AlertButtonVM: ComponentVM {
5+
/// The text displayed on the button.
6+
public var title: String = ""
7+
8+
/// The scaling factor for the button's press animation, with a value between 0 and 1.
9+
///
10+
/// Defaults to `.medium`.
11+
public var animationScale: AnimationScale = .medium
12+
13+
/// The color of the button.
14+
public var color: ComponentColor?
15+
16+
/// The corner radius of the button.
17+
///
18+
/// Defaults to `.medium`.
19+
public var cornerRadius: ComponentRadius = .medium
20+
21+
/// The visual style of the button.
22+
///
23+
/// Defaults to `.filled`.
24+
public var style: ButtonStyle = .filled
25+
26+
/// Initializes a new instance of `AlertButtonVM` with default values.
27+
public init() {}
28+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import Foundation
2+
3+
/// A model that defines the appearance properties for an alert.
4+
public struct AlertVM: ComponentVM {
5+
/// The title of the alert.
6+
public var title: String?
7+
8+
/// The message of the alert.
9+
public var message: String?
10+
11+
/// The modal that defines the appearance properties for a primary button in the alert.
12+
///
13+
/// If it is `nil`, the primary button will not be displayed.
14+
public var primaryButton: AlertButtonVM?
15+
16+
/// The modal that defines the appearance properties for a secondary button in the alert.
17+
///
18+
/// If it is `nil`, the secondary button will not be displayed.
19+
public var secondaryButton: AlertButtonVM?
20+
21+
/// The background color of the modal.
22+
public var backgroundColor: UniversalColor?
23+
24+
/// A Boolean value indicating whether the modal should close when tapping on the overlay.
25+
///
26+
/// Defaults to `false`.
27+
public var closesOnOverlayTap: Bool = false
28+
29+
/// The padding applied to the modal's content area.
30+
///
31+
/// Defaults to a padding value of `16` for all sides.
32+
public var contentPaddings: Paddings = .init(padding: 16)
33+
34+
/// The corner radius of the modal.
35+
///
36+
/// Defaults to `.medium`.
37+
public var cornerRadius: ContainerRadius = .medium
38+
39+
/// The style of the overlay displayed behind the modal.
40+
///
41+
/// Defaults to `.dimmed`.
42+
public var overlayStyle: ModalOverlayStyle = .dimmed
43+
44+
/// The transition duration of the modal's appearance and dismissal animations.
45+
///
46+
/// Defaults to `.fast`.
47+
public var transition: ModalTransition = .fast
48+
49+
/// Initializes a new instance of `AlertVM` with default values.
50+
public init() {}
51+
}
52+
53+
// MARK: - Helpers
54+
55+
extension AlertVM {
56+
var modalVM: CenterModalVM {
57+
return CenterModalVM {
58+
$0.backgroundColor = self.backgroundColor
59+
$0.closesOnOverlayTap = self.closesOnOverlayTap
60+
$0.contentPaddings = self.contentPaddings
61+
$0.cornerRadius = self.cornerRadius
62+
$0.overlayStyle = self.overlayStyle
63+
$0.transition = self.transition
64+
$0.size = .small
65+
}
66+
}
67+
68+
var primaryButtonVM: ButtonVM? {
69+
let buttonVM = self.primaryButton.map(self.mapAlertButtonVM)
70+
if self.secondaryButton.isNotNil {
71+
return buttonVM
72+
} else {
73+
return buttonVM ?? Self.defaultButtonVM
74+
}
75+
}
76+
77+
var secondaryButtonVM: ButtonVM? {
78+
return self.secondaryButton.map(self.mapAlertButtonVM)
79+
}
80+
81+
private func mapAlertButtonVM(_ model: AlertButtonVM) -> ButtonVM {
82+
return ButtonVM {
83+
$0.title = model.title
84+
$0.animationScale = model.animationScale
85+
$0.color = model.color
86+
$0.cornerRadius = model.cornerRadius
87+
$0.style = model.style
88+
}
89+
}
90+
}
91+
92+
extension AlertVM {
93+
static let buttonsSpacing: CGFloat = 12
94+
95+
static let defaultButtonVM = ButtonVM {
96+
$0.title = "OK"
97+
$0.color = .primary
98+
$0.style = .filled
99+
}
100+
}

0 commit comments

Comments
 (0)