Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -85,23 +85,14 @@ struct ModalPreviewHelpers {
ContainerRadiusPicker(selection: self.$model.cornerRadius) {
Text("Custom 30px").tag(ContainerRadius.custom(30))
}
Picker("Overlay Style", selection: self.$model.overlayStyle) {
Text("Blurred").tag(ModalOverlayStyle.blurred)
Text("Dimmed").tag(ModalOverlayStyle.dimmed)
Text("Transparent").tag(ModalOverlayStyle.transparent)
}
OverlayStylePicker(selection: self.$model.overlayStyle)
Picker("Size", selection: self.$model.size) {
Text("Small").tag(ModalSize.small)
Text("Medium").tag(ModalSize.medium)
Text("Large").tag(ModalSize.large)
Text("Full").tag(ModalSize.full)
}
Picker("Transition", selection: self.$model.transition) {
Text("None").tag(ModalTransition.none)
Text("Fast").tag(ModalTransition.fast)
Text("Normal").tag(ModalTransition.normal)
Text("Slow").tag(ModalTransition.slow)
}
TransitionPicker(selection: self.$model.transition)
self.additionalPickers()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,20 @@ struct KeyboardTypePicker: View {
}
}

// MARK: - OverlayStylePicker

struct OverlayStylePicker: View {
@Binding var selection: ModalOverlayStyle

var body: some View {
Picker("Overlay Style", selection: self.$selection) {
Text("Blurred").tag(ModalOverlayStyle.blurred)
Text("Dimmed").tag(ModalOverlayStyle.dimmed)
Text("Transparent").tag(ModalOverlayStyle.transparent)
}
}
}

// MARK: - SizePicker

struct SizePicker: View {
Expand Down Expand Up @@ -243,6 +257,21 @@ struct SubmitTypePicker: View {
}
}

// MARK: - TransitionPicker

struct TransitionPicker: View {
@Binding var selection: ModalTransition

var body: some View {
Picker("Transition", selection: self.$selection) {
Text("None").tag(ModalTransition.none)
Text("Fast").tag(ModalTransition.fast)
Text("Normal").tag(ModalTransition.normal)
Text("Slow").tag(ModalTransition.slow)
}
}
}

// MARK: - UniversalColorPicker

struct UniversalColorPicker: View {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import ComponentsKit
import SwiftUI
import UIKit

struct AlertPreview: View {
@State private var model = AlertVM {
$0.title = Self.alertTitle
$0.message = AlertMessage.short.rawValue
$0.primaryButton = Self.initialPrimaryButton
$0.secondaryButton = Self.initialSecondaryButton
}

var body: some View {
VStack {
PreviewWrapper(title: "UIKit") {
UKButton(model: .init { $0.title = "Show Alert" }) {
UIApplication.shared.topViewController?.present(
UKAlertController(model: self.model),
animated: true
)
}
.preview
}
Form {
Section("Title") {
Toggle("Has Title", isOn: .init(
get: { return self.model.title != nil },
set: { newValue in
self.model.title = newValue ? Self.alertTitle : nil
}
))
}

Section("Message") {
Picker("Alert Message", selection: self.$model.message) {
Text("None").tag(Optional<String>.none)
Text("Short").tag(AlertMessage.short.rawValue)
Text("Long").tag(AlertMessage.long.rawValue)
}
}

Section("Primary Button") {
Toggle("Has Primary Button", isOn: .init(
get: { return self.model.primaryButton != nil },
set: { newValue in
self.model.primaryButton = newValue ? Self.initialPrimaryButton : nil
}
))
if self.model.primaryButton != nil {
Picker("Title", selection: self.primaryButtonVMOrDefault.title) {
Text("Short").tag(PrimaryButtonText.short.rawValue)
Text("Longer").tag(PrimaryButtonText.longer.rawValue)
}
self.buttonPickers(for: self.primaryButtonVMOrDefault)
}
}

Section("Secondary Button") {
Toggle("Has Secondary Button", isOn: .init(
get: { return self.model.secondaryButton != nil },
set: { newValue in
self.model.secondaryButton = newValue ? Self.initialSecondaryButton : nil
}
))
if self.model.secondaryButton != nil {
Picker("Title", selection: self.secondaryButtonVMOrDefault.title) {
Text("Short").tag(SecondaryButtonText.short.rawValue)
Text("Longer").tag(SecondaryButtonText.longer.rawValue)
}
self.buttonPickers(for: self.secondaryButtonVMOrDefault)
}
}

Section("Main Properties") {
Picker("Background Color", selection: self.$model.backgroundColor) {
Text("Default").tag(Optional<UniversalColor>.none)
Text("Accent Background").tag(ComponentColor.accent.background)
Text("Success Background").tag(ComponentColor.success.background)
Text("Warning Background").tag(ComponentColor.warning.background)
Text("Danger Background").tag(ComponentColor.danger.background)
}
Toggle("Closes On Overlay Tap", isOn: self.$model.closesOnOverlayTap)
Picker("Content Paddings", selection: self.$model.contentPaddings) {
Text("12px").tag(Paddings(padding: 12))
Text("16px").tag(Paddings(padding: 16))
Text("20px").tag(Paddings(padding: 20))
}
ContainerRadiusPicker(selection: self.$model.cornerRadius) {
Text("Custom 30px").tag(ContainerRadius.custom(30))
}
OverlayStylePicker(selection: self.$model.overlayStyle)
TransitionPicker(selection: self.$model.transition)
}
}
}
}

// MARK: - Reusable Pickers

private func buttonPickers(for buttonVM: Binding<AlertButtonVM>) -> some View {
Group {
AnimationScalePicker(selection: buttonVM.animationScale)
ComponentOptionalColorPicker(selection: buttonVM.color)
ComponentRadiusPicker(selection: buttonVM.cornerRadius) {
Text("Custom: 20px").tag(ComponentRadius.custom(20))
}
Picker("Style", selection: buttonVM.style) {
Text("Filled").tag(ButtonStyle.filled)
Text("Plain").tag(ButtonStyle.plain)
Text("Light").tag(ButtonStyle.light)
Text("Bordered with small border").tag(ButtonStyle.bordered(.small))
Text("Bordered with medium border").tag(ButtonStyle.bordered(.medium))
Text("Bordered with large border").tag(ButtonStyle.bordered(.large))
}
}
}

// MARK: - Helpers

enum AlertMessage: String {
case short = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
case long = """
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.

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.

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.
"""
}
enum PrimaryButtonText: String {
case short = "Continue"
case longer = "Remind me later"
}
enum SecondaryButtonText: String {
case short = "Cancel"
case longer = "Cancel, Don't Do That"
}
static let alertTitle = "Alert Title"
static let initialPrimaryButton = AlertButtonVM {
$0.title = PrimaryButtonText.short.rawValue
$0.style = .filled
$0.color = .primary
}
static let initialSecondaryButton = AlertButtonVM {
$0.title = SecondaryButtonText.short.rawValue
$0.style = .light
}

var primaryButtonVMOrDefault: Binding<AlertButtonVM> {
return .init(
get: { self.model.primaryButton ?? Self.initialPrimaryButton },
set: { self.model.primaryButton = $0 }
)
}
var secondaryButtonVMOrDefault: Binding<AlertButtonVM> {
return .init(
get: { self.model.secondaryButton ?? Self.initialSecondaryButton },
set: { self.model.secondaryButton = $0 }
)
}
}

#Preview {
AlertPreview()
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ struct ButtonPreview: View {
Toggle("Full Width", isOn: self.$model.isFullWidth)
SizePicker(selection: self.$model.size)
Picker("Style", selection: self.$model.style) {
Text("Filled").tag(ButtonVM.Style.filled)
Text("Plain").tag(ButtonVM.Style.plain)
Text("Light").tag(ButtonVM.Style.light)
Text("Bordered with small border").tag(ButtonVM.Style.bordered(.small))
Text("Bordered with medium border").tag(ButtonVM.Style.bordered(.medium))
Text("Bordered with large border").tag(ButtonVM.Style.bordered(.large))
Text("Filled").tag(ButtonStyle.filled)
Text("Plain").tag(ButtonStyle.plain)
Text("Light").tag(ButtonStyle.light)
Text("Bordered with small border").tag(ButtonStyle.bordered(.small))
Text("Bordered with medium border").tag(ButtonStyle.bordered(.medium))
Text("Bordered with large border").tag(ButtonStyle.bordered(.large))
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions Examples/DemosApp/DemosApp/Core/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ struct App: View {
}

Section("Modals") {
NavigationLinkWithTitle("Alert") {
AlertPreview()
}
NavigationLinkWithTitle("Bottom Modal") {
BottomModalPreview()
}
Expand Down
28 changes: 28 additions & 0 deletions Sources/ComponentsKit/Components/Alert/Models/AlertButtonVM.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Foundation

/// A model that defines the appearance properties for a button in the alert.
public struct AlertButtonVM: ComponentVM {
/// The text displayed on the button.
public var title: String = ""

/// The scaling factor for the button's press animation, with a value between 0 and 1.
///
/// Defaults to `.medium`.
public var animationScale: AnimationScale = .medium

/// The color of the button.
public var color: ComponentColor?

/// The corner radius of the button.
///
/// Defaults to `.medium`.
public var cornerRadius: ComponentRadius = .medium

/// The visual style of the button.
///
/// Defaults to `.filled`.
public var style: ButtonStyle = .filled

/// Initializes a new instance of `AlertButtonVM` with default values.
public init() {}
}
100 changes: 100 additions & 0 deletions Sources/ComponentsKit/Components/Alert/Models/AlertVM.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import Foundation

/// A model that defines the appearance properties for an alert.
public struct AlertVM: ComponentVM {
/// The title of the alert.
public var title: String?

/// The message of the alert.
public var message: String?

/// The modal that defines the appearance properties for a primary button in the alert.
///
/// If it is `nil`, the primary button will not be displayed.
public var primaryButton: AlertButtonVM?

/// The modal that defines the appearance properties for a secondary button in the alert.
///
/// If it is `nil`, the secondary button will not be displayed.
public var secondaryButton: AlertButtonVM?

/// The background color of the modal.
public var backgroundColor: UniversalColor?

/// A Boolean value indicating whether the modal should close when tapping on the overlay.
///
/// Defaults to `false`.
public var closesOnOverlayTap: Bool = false

/// The padding applied to the modal's content area.
///
/// Defaults to a padding value of `16` for all sides.
public var contentPaddings: Paddings = .init(padding: 16)

/// The corner radius of the modal.
///
/// Defaults to `.medium`.
public var cornerRadius: ContainerRadius = .medium

/// The style of the overlay displayed behind the modal.
///
/// Defaults to `.dimmed`.
public var overlayStyle: ModalOverlayStyle = .dimmed

/// The transition duration of the modal's appearance and dismissal animations.
///
/// Defaults to `.fast`.
public var transition: ModalTransition = .fast

/// Initializes a new instance of `AlertVM` with default values.
public init() {}
}

// MARK: - Helpers

extension AlertVM {
var modalVM: CenterModalVM {
return CenterModalVM {
$0.backgroundColor = self.backgroundColor
$0.closesOnOverlayTap = self.closesOnOverlayTap
$0.contentPaddings = self.contentPaddings
$0.cornerRadius = self.cornerRadius
$0.overlayStyle = self.overlayStyle
$0.transition = self.transition
$0.size = .small
}
}

var primaryButtonVM: ButtonVM? {
let buttonVM = self.primaryButton.map(self.mapAlertButtonVM)
if self.secondaryButton.isNotNil {
return buttonVM
} else {
return buttonVM ?? Self.defaultButtonVM
}
}

var secondaryButtonVM: ButtonVM? {
return self.secondaryButton.map(self.mapAlertButtonVM)
}

private func mapAlertButtonVM(_ model: AlertButtonVM) -> ButtonVM {
return ButtonVM {
$0.title = model.title
$0.animationScale = model.animationScale
$0.color = model.color
$0.cornerRadius = model.cornerRadius
$0.style = model.style
}
}
}

extension AlertVM {
static let buttonsSpacing: CGFloat = 12

static let defaultButtonVM = ButtonVM {
$0.title = "OK"
$0.color = .primary
$0.style = .filled
}
}
Loading
Loading