From ab9298332bc19f4da3acf2db53ea5a8a3ea5f600 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 5 Jun 2026 13:27:24 +0300 Subject: [PATCH 01/15] add background style to su modals --- .../Helpers/ModalPreview+Helpers.swift | 7 +++ .../Modal/Models/BottomModalVM.swift | 3 ++ .../Modal/Models/CenterModalVM.swift | 3 ++ .../Components/Modal/Models/ModalVM.swift | 18 +++++-- .../Modal/SwiftUI/ModalContent.swift | 48 +++++++++++++++++-- .../Modal/SwiftUI/ModalOverlay.swift | 2 +- .../Modal/UIKit/UKModalController.swift | 2 +- .../Shared/Types/BackgroundStyle.swift | 11 +++++ 8 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 Sources/ComponentsKit/Shared/Types/BackgroundStyle.swift diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/ModalPreview+Helpers.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/ModalPreview+Helpers.swift index eb8fccea..7642b74a 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/ModalPreview+Helpers.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/ModalPreview+Helpers.swift @@ -65,6 +65,13 @@ struct ModalPreviewHelpers { Text("Warning Background").tag(UniversalColor.warningBackground) Text("Danger Background").tag(UniversalColor.dangerBackground) } + Picker("Background Style", selection: self.$model.backgroundStyle) { + Text("Solid").tag(BackgroundStyle.solid) + Text("Blur").tag(BackgroundStyle.blur) + if #available(iOS 26.0, *) { + Text("Liquid Glass").tag(BackgroundStyle.liquidGlass) + } + } BorderWidthPicker(selection: self.$model.borderWidth) Toggle("Closes On Overlay Tap", isOn: self.$model.closesOnOverlayTap) .disabled(self.footer == nil) diff --git a/Sources/ComponentsKit/Components/Modal/Models/BottomModalVM.swift b/Sources/ComponentsKit/Components/Modal/Models/BottomModalVM.swift index e16a4f8a..30dbfc31 100644 --- a/Sources/ComponentsKit/Components/Modal/Models/BottomModalVM.swift +++ b/Sources/ComponentsKit/Components/Modal/Models/BottomModalVM.swift @@ -5,6 +5,9 @@ public struct BottomModalVM: ModalVM { /// The background color of the modal. public var backgroundColor: UniversalColor? + /// Defines how modal renders its background. + public var backgroundStyle: BackgroundStyle = .solid + /// The border thickness of the modal. /// /// Defaults to `.small`. diff --git a/Sources/ComponentsKit/Components/Modal/Models/CenterModalVM.swift b/Sources/ComponentsKit/Components/Modal/Models/CenterModalVM.swift index ca0e0b47..ae739b7e 100644 --- a/Sources/ComponentsKit/Components/Modal/Models/CenterModalVM.swift +++ b/Sources/ComponentsKit/Components/Modal/Models/CenterModalVM.swift @@ -5,6 +5,9 @@ public struct CenterModalVM: ModalVM { /// The background color of the modal. public var backgroundColor: UniversalColor? + /// Defines how modal renders its background. + public var backgroundStyle: BackgroundStyle = .solid + /// The border thickness of the modal. /// /// Defaults to `.small`. diff --git a/Sources/ComponentsKit/Components/Modal/Models/ModalVM.swift b/Sources/ComponentsKit/Components/Modal/Models/ModalVM.swift index 55af7ea3..16cf0706 100644 --- a/Sources/ComponentsKit/Components/Modal/Models/ModalVM.swift +++ b/Sources/ComponentsKit/Components/Modal/Models/ModalVM.swift @@ -5,6 +5,9 @@ public protocol ModalVM: ComponentVM { /// The background color of the modal. var backgroundColor: UniversalColor? { get set } + /// Defines how modal renders its background. + var backgroundStyle: BackgroundStyle { get set } + /// The border thickness of the modal. var borderWidth: BorderWidth { get set } @@ -36,10 +39,15 @@ public protocol ModalVM: ComponentVM { // MARK: - Helpers extension ModalVM { - var preferredBackgroundColor: UniversalColor { - return self.backgroundColor ?? .themed( - light: UniversalColor.background.light, - dark: UniversalColor.secondaryBackground.dark - ) + var preferredBackgroundColor: UniversalColor? { + switch self.backgroundStyle { + case .solid: + return self.backgroundColor ?? .themed( + light: UniversalColor.background.light, + dark: UniversalColor.secondaryBackground.dark + ) + case .liquidGlass, .blur: + return nil + } } } diff --git a/Sources/ComponentsKit/Components/Modal/SwiftUI/ModalContent.swift b/Sources/ComponentsKit/Components/Modal/SwiftUI/ModalContent.swift index 96ab9a8f..b745556c 100644 --- a/Sources/ComponentsKit/Components/Modal/SwiftUI/ModalContent.swift +++ b/Sources/ComponentsKit/Components/Modal/SwiftUI/ModalContent.swift @@ -55,11 +55,9 @@ struct ModalContent: View { .padding(.bottom, self.model.contentPaddings.bottom) } .frame(maxWidth: self.model.size.maxWidth, alignment: .leading) - .background(self.model.preferredBackgroundColor.color) - .clipShape(RoundedRectangle(cornerRadius: self.model.cornerRadius.value)) - .overlay( - RoundedRectangle(cornerRadius: self.model.cornerRadius.value) - .strokeBorder(UniversalColor.divider.color, lineWidth: self.model.borderWidth.value) + .modalBackground( + shape: RoundedRectangle(cornerRadius: model.cornerRadius.value), + model: self.model ) .padding(self.model.outerPaddings.edgeInsets) } @@ -74,3 +72,43 @@ struct ModalContent: View { return self.bodySize.height + self.bodyTopPadding + self.bodyBottomPadding } } + +extension View { + @ViewBuilder + fileprivate func modalBackground( + shape: BackgroundShape, + model: any ModalVM + ) -> some View { + switch model.backgroundStyle { + case .solid: + self.background(model.preferredBackgroundColor?.color) + .clipShape(shape) + .overlay( + shape + .strokeBorder(UniversalColor.divider.color, lineWidth: model.borderWidth.value) + ) + case .blur: + self.background { + shape + .fill(.thinMaterial) + .overlay { + shape.fill(model.preferredBackgroundColor?.color ?? .clear) + } + .overlay { + shape.strokeBorder(UniversalColor.divider.color, lineWidth: model.borderWidth.value) + } + } + case .liquidGlass: + if #available(iOS 26.0, *) { + self.glassEffect( + .regular + .tint(model.preferredBackgroundColor?.color) + .interactive(), + in: shape + ) + } else { + self + } + } + } +} diff --git a/Sources/ComponentsKit/Components/Modal/SwiftUI/ModalOverlay.swift b/Sources/ComponentsKit/Components/Modal/SwiftUI/ModalOverlay.swift index 87f549c5..2cd2c4a1 100644 --- a/Sources/ComponentsKit/Components/Modal/SwiftUI/ModalOverlay.swift +++ b/Sources/ComponentsKit/Components/Modal/SwiftUI/ModalOverlay.swift @@ -17,7 +17,7 @@ struct ModalOverlay: View { Group { switch self.model.overlayStyle { case .dimmed: - Color.black.opacity(0.7) + Color.black.opacity(0.35) case .blurred: Color.clear.background(.ultraThinMaterial) case .transparent: diff --git a/Sources/ComponentsKit/Components/Modal/UIKit/UKModalController.swift b/Sources/ComponentsKit/Components/Modal/UIKit/UKModalController.swift index 51e3e238..7b96c9a1 100644 --- a/Sources/ComponentsKit/Components/Modal/UIKit/UKModalController.swift +++ b/Sources/ComponentsKit/Components/Modal/UIKit/UKModalController.swift @@ -260,7 +260,7 @@ extension UKModalController { } } static func contentView(_ view: UIView, model: VM) { - view.backgroundColor = model.preferredBackgroundColor.uiColor + view.backgroundColor = model.backgroundColor?.uiColor view.layer.cornerRadius = model.cornerRadius.value view.layer.borderColor = UniversalColor.divider.cgColor view.layer.borderWidth = model.borderWidth.value diff --git a/Sources/ComponentsKit/Shared/Types/BackgroundStyle.swift b/Sources/ComponentsKit/Shared/Types/BackgroundStyle.swift new file mode 100644 index 00000000..783ab0f7 --- /dev/null +++ b/Sources/ComponentsKit/Shared/Types/BackgroundStyle.swift @@ -0,0 +1,11 @@ +import Foundation + +/// Defines how a component renders its background. +public enum BackgroundStyle { + /// A regular filled background using the component's configured background color. + case solid + /// A system liquid glass effect that lets underlying content show through the component. + @available(iOS 26.0, *) case liquidGlass + /// A system blur material that softens content behind the component. + case blur +} From a159da77a667549564824efad1e91d74734c23c4 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 5 Jun 2026 13:48:20 +0300 Subject: [PATCH 02/15] add background style to `UKModalController` --- .../Components/Modal/Models/ModalVM.swift | 2 +- .../Modal/UIKit/UKModalController.swift | 36 ++++++++++++++++--- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/Sources/ComponentsKit/Components/Modal/Models/ModalVM.swift b/Sources/ComponentsKit/Components/Modal/Models/ModalVM.swift index 16cf0706..137e178b 100644 --- a/Sources/ComponentsKit/Components/Modal/Models/ModalVM.swift +++ b/Sources/ComponentsKit/Components/Modal/Models/ModalVM.swift @@ -47,7 +47,7 @@ extension ModalVM { dark: UniversalColor.secondaryBackground.dark ) case .liquidGlass, .blur: - return nil + return self.backgroundColor } } } diff --git a/Sources/ComponentsKit/Components/Modal/UIKit/UKModalController.swift b/Sources/ComponentsKit/Components/Modal/UIKit/UKModalController.swift index 7b96c9a1..d770282e 100644 --- a/Sources/ComponentsKit/Components/Modal/UIKit/UKModalController.swift +++ b/Sources/ComponentsKit/Components/Modal/UIKit/UKModalController.swift @@ -27,6 +27,8 @@ open class UKModalController: UIViewController { public var footer: UIView? /// The content view, holding the header, body, and footer. public let contentView = UIView() + /// The visual effect container used to render blur and liquid glass modal backgrounds. + public let backgroundEffectView = UIVisualEffectView() /// A scrollable wrapper for the body content. public let bodyWrapper: UIScrollView = ContentSizedScrollView() /// The overlay view that appears behind the modal. @@ -76,12 +78,13 @@ open class UKModalController: UIViewController { open func setup() { self.view.addSubview(self.overlay) self.view.addSubview(self.contentView) + self.contentView.addSubview(self.backgroundEffectView) if let header { - self.contentView.addSubview(header) + self.backgroundEffectView.contentView.addSubview(header) } - self.contentView.addSubview(self.bodyWrapper) + self.backgroundEffectView.contentView.addSubview(self.bodyWrapper) if let footer { - self.contentView.addSubview(footer) + self.backgroundEffectView.contentView.addSubview(footer) } self.bodyWrapper.addSubview(self.body) @@ -141,6 +144,7 @@ open class UKModalController: UIViewController { open func style() { Self.Style.overlay(self.overlay, model: self.model) Self.Style.contentView(self.contentView, model: self.model) + Self.Style.backgroundEffectView(self.backgroundEffectView, model: self.model) Self.Style.bodyWrapper(self.bodyWrapper) } @@ -149,6 +153,7 @@ open class UKModalController: UIViewController { /// Configures the layout of the modal's subviews. open func layout() { self.overlay.allEdges() + self.backgroundEffectView.allEdges() if let header { header.top(self.model.contentPaddings.top) @@ -242,6 +247,7 @@ open class UKModalController: UIViewController { @objc private func handleTraitChanges() { Self.Style.contentView(self.contentView, model: self.model) + Self.Style.backgroundEffectView(self.backgroundEffectView, model: self.model) } } @@ -252,7 +258,7 @@ extension UKModalController { static func overlay(_ view: UIView, model: VM) { switch model.overlayStyle { case .dimmed: - view.backgroundColor = .black.withAlphaComponent(0.7) + view.backgroundColor = .black.withAlphaComponent(0.35) case .transparent: view.backgroundColor = .clear case .blurred: @@ -260,11 +266,31 @@ extension UKModalController { } } static func contentView(_ view: UIView, model: VM) { - view.backgroundColor = model.backgroundColor?.uiColor + view.backgroundColor = model.preferredBackgroundColor?.uiColor view.layer.cornerRadius = model.cornerRadius.value view.layer.borderColor = UniversalColor.divider.cgColor view.layer.borderWidth = model.borderWidth.value } + static func backgroundEffectView(_ view: UIVisualEffectView, model: VM) { + view.layer.cornerRadius = model.cornerRadius.value + view.clipsToBounds = true + + switch model.backgroundStyle { + case .solid: + view.effect = nil + case .blur: + view.effect = UIBlurEffect(style: .systemThinMaterial) + case .liquidGlass: + if #available(iOS 26.0, *) { + let effect = UIGlassEffect(style: .regular) + effect.tintColor = model.preferredBackgroundColor?.uiColor + effect.isInteractive = true + view.effect = effect + } else { + view.effect = nil + } + } + } static func bodyWrapper(_ scrollView: UIScrollView) { scrollView.delaysContentTouches = false scrollView.contentInsetAdjustmentBehavior = .never From 83c25922a010ff9863dfc080d704dea4d725876a Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 5 Jun 2026 13:51:44 +0300 Subject: [PATCH 03/15] add alert background styles --- .../ComponentsPreview/PreviewPages/AlertPreview.swift | 7 +++++++ .../ComponentsKit/Components/Alert/Models/AlertVM.swift | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/AlertPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/AlertPreview.swift index 658a73d0..9a000131 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/AlertPreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/AlertPreview.swift @@ -89,6 +89,13 @@ struct AlertPreview: View { Text("Warning Background").tag(UniversalColor.warningBackground) Text("Danger Background").tag(UniversalColor.dangerBackground) } + Picker("Background Style", selection: self.$model.backgroundStyle) { + Text("Solid").tag(BackgroundStyle.solid) + Text("Blur").tag(BackgroundStyle.blur) + if #available(iOS 26.0, *) { + Text("Liquid Glass").tag(BackgroundStyle.liquidGlass) + } + } BorderWidthPicker(selection: self.$model.borderWidth) Toggle("Closes On Overlay Tap", isOn: self.$model.closesOnOverlayTap) Picker("Content Paddings", selection: self.$model.contentPaddings) { diff --git a/Sources/ComponentsKit/Components/Alert/Models/AlertVM.swift b/Sources/ComponentsKit/Components/Alert/Models/AlertVM.swift index 5999bfc2..a619a29a 100644 --- a/Sources/ComponentsKit/Components/Alert/Models/AlertVM.swift +++ b/Sources/ComponentsKit/Components/Alert/Models/AlertVM.swift @@ -21,6 +21,9 @@ public struct AlertVM: ComponentVM { /// The background color of the alert. public var backgroundColor: UniversalColor? + /// Defines how the alert renders its background. + public var backgroundStyle: BackgroundStyle = .solid + /// The border thickness of the alert. /// /// Defaults to `.small`. @@ -61,6 +64,7 @@ extension AlertVM { var modalVM: CenterModalVM { return CenterModalVM { $0.backgroundColor = self.backgroundColor + $0.backgroundStyle = self.backgroundStyle $0.borderWidth = self.borderWidth $0.closesOnOverlayTap = self.closesOnOverlayTap $0.contentPaddings = self.contentPaddings From c0d83dfff0a0c60f2ed27f41beb16340b44658dd Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 5 Jun 2026 13:54:30 +0300 Subject: [PATCH 04/15] add background style picker helper --- .../Helpers/ModalPreview+Helpers.swift | 8 +------- .../Helpers/PreviewPickers.swift | 16 ++++++++++++++++ .../PreviewPages/AlertPreview.swift | 8 +------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/ModalPreview+Helpers.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/ModalPreview+Helpers.swift index 7642b74a..272810a2 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/ModalPreview+Helpers.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/ModalPreview+Helpers.swift @@ -65,13 +65,7 @@ struct ModalPreviewHelpers { Text("Warning Background").tag(UniversalColor.warningBackground) Text("Danger Background").tag(UniversalColor.dangerBackground) } - Picker("Background Style", selection: self.$model.backgroundStyle) { - Text("Solid").tag(BackgroundStyle.solid) - Text("Blur").tag(BackgroundStyle.blur) - if #available(iOS 26.0, *) { - Text("Liquid Glass").tag(BackgroundStyle.liquidGlass) - } - } + BackgroundStylePicker(selection: self.$model.backgroundStyle) BorderWidthPicker(selection: self.$model.borderWidth) Toggle("Closes On Overlay Tap", isOn: self.$model.closesOnOverlayTap) .disabled(self.footer == nil) diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/PreviewPickers.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/PreviewPickers.swift index bf46eec7..72586a2f 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/PreviewPickers.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/PreviewPickers.swift @@ -32,6 +32,22 @@ struct AutocapitalizationPicker: View { } } +// MARK: - BackgroundStylePicker + +struct BackgroundStylePicker: View { + @Binding var selection: ComponentsKit.BackgroundStyle + + var body: some View { + Picker("Background Style", selection: self.$selection) { + Text("Solid").tag(ComponentsKit.BackgroundStyle.solid) + Text("Blur").tag(ComponentsKit.BackgroundStyle.blur) + if #available(iOS 26.0, *) { + Text("Liquid Glass").tag(ComponentsKit.BackgroundStyle.liquidGlass) + } + } + } +} + // MARK: - BorderWidthPicker struct BorderWidthPicker: View { diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/AlertPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/AlertPreview.swift index 9a000131..9615a69d 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/AlertPreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/AlertPreview.swift @@ -89,13 +89,7 @@ struct AlertPreview: View { Text("Warning Background").tag(UniversalColor.warningBackground) Text("Danger Background").tag(UniversalColor.dangerBackground) } - Picker("Background Style", selection: self.$model.backgroundStyle) { - Text("Solid").tag(BackgroundStyle.solid) - Text("Blur").tag(BackgroundStyle.blur) - if #available(iOS 26.0, *) { - Text("Liquid Glass").tag(BackgroundStyle.liquidGlass) - } - } + BackgroundStylePicker(selection: self.$model.backgroundStyle) BorderWidthPicker(selection: self.$model.borderWidth) Toggle("Closes On Overlay Tap", isOn: self.$model.closesOnOverlayTap) Picker("Content Paddings", selection: self.$model.contentPaddings) { From 8e3f4ca09dca10a746d09601f341147fa92a4992 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 5 Jun 2026 14:27:37 +0300 Subject: [PATCH 05/15] fix blur background when background color is applied --- .../Modal/SwiftUI/ModalContent.swift | 20 +++++++++---------- .../Modal/UIKit/UKModalController.swift | 7 ++++--- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Sources/ComponentsKit/Components/Modal/SwiftUI/ModalContent.swift b/Sources/ComponentsKit/Components/Modal/SwiftUI/ModalContent.swift index b745556c..67722c00 100644 --- a/Sources/ComponentsKit/Components/Modal/SwiftUI/ModalContent.swift +++ b/Sources/ComponentsKit/Components/Modal/SwiftUI/ModalContent.swift @@ -88,16 +88,16 @@ extension View { .strokeBorder(UniversalColor.divider.color, lineWidth: model.borderWidth.value) ) case .blur: - self.background { - shape - .fill(.thinMaterial) - .overlay { - shape.fill(model.preferredBackgroundColor?.color ?? .clear) - } - .overlay { - shape.strokeBorder(UniversalColor.divider.color, lineWidth: model.borderWidth.value) - } - } + self + .background { + shape + .fill(.thinMaterial) + .overlay { + shape.strokeBorder(UniversalColor.divider.color, lineWidth: model.borderWidth.value) + } + } + .background(model.preferredBackgroundColor?.color) + .clipShape(shape) case .liquidGlass: if #available(iOS 26.0, *) { self.glassEffect( diff --git a/Sources/ComponentsKit/Components/Modal/UIKit/UKModalController.swift b/Sources/ComponentsKit/Components/Modal/UIKit/UKModalController.swift index d770282e..d0bce106 100644 --- a/Sources/ComponentsKit/Components/Modal/UIKit/UKModalController.swift +++ b/Sources/ComponentsKit/Components/Modal/UIKit/UKModalController.swift @@ -266,20 +266,21 @@ extension UKModalController { } } static func contentView(_ view: UIView, model: VM) { - view.backgroundColor = model.preferredBackgroundColor?.uiColor view.layer.cornerRadius = model.cornerRadius.value - view.layer.borderColor = UniversalColor.divider.cgColor - view.layer.borderWidth = model.borderWidth.value } static func backgroundEffectView(_ view: UIVisualEffectView, model: VM) { view.layer.cornerRadius = model.cornerRadius.value + view.layer.borderColor = UniversalColor.divider.cgColor + view.layer.borderWidth = model.borderWidth.value view.clipsToBounds = true switch model.backgroundStyle { case .solid: view.effect = nil + view.backgroundColor = model.preferredBackgroundColor?.uiColor case .blur: view.effect = UIBlurEffect(style: .systemThinMaterial) + view.backgroundColor = model.preferredBackgroundColor?.uiColor case .liquidGlass: if #available(iOS 26.0, *) { let effect = UIGlassEffect(style: .regular) From d818ac507d5695c98ab976c4f4d467d7bc4b3ad4 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 5 Jun 2026 16:37:24 +0300 Subject: [PATCH 06/15] add background style in cards --- .../PreviewPages/CardPreview.swift | 2 + .../Components/Card/Models/CardVM.swift | 18 +++++- .../Components/Card/SUCard.swift | 57 ++++++++++++++++--- .../Components/Card/UKCard.swift | 51 ++++++++++++++--- 4 files changed, 109 insertions(+), 19 deletions(-) diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/CardPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/CardPreview.swift index ce9e600f..ca976dd0 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/CardPreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/CardPreview.swift @@ -17,6 +17,7 @@ struct CardPreview: View { Form { AnimationScalePicker(selection: self.$model.animationScale) Picker("Background Color", selection: self.$model.backgroundColor) { + Text("Clear").tag(Optional.none) Text("Background").tag(UniversalColor.background) Text("Secondary Background").tag(UniversalColor.secondaryBackground) Text("Accent Background").tag(UniversalColor.accentBackground) @@ -24,6 +25,7 @@ struct CardPreview: View { Text("Warning Background").tag(UniversalColor.warningBackground) Text("Danger Background").tag(UniversalColor.dangerBackground) } + BackgroundStylePicker(selection: self.$model.backgroundStyle) Picker("Border Color", selection: self.$model.borderColor) { Text("Divider").tag(UniversalColor.divider) Text("Primary").tag(UniversalColor.primary) diff --git a/Sources/ComponentsKit/Components/Card/Models/CardVM.swift b/Sources/ComponentsKit/Components/Card/Models/CardVM.swift index 2103b564..ce37b9ee 100644 --- a/Sources/ComponentsKit/Components/Card/Models/CardVM.swift +++ b/Sources/ComponentsKit/Components/Card/Models/CardVM.swift @@ -8,7 +8,10 @@ public struct CardVM: ComponentVM { public var animationScale: AnimationScale = .medium /// The background color of the card. - public var backgroundColor: UniversalColor = .background + public var backgroundColor: UniversalColor? = .background + + /// Defines how the card renders its background. + public var backgroundStyle: BackgroundStyle = .solid /// The border color of the card. public var borderColor: UniversalColor = .divider @@ -41,3 +44,16 @@ public struct CardVM: ComponentVM { /// Initializes a new instance of `CardVM` with default values. public init() {} } + +// MARK: - Helpers + +extension CardVM { + var isTapAnimationEnabled: Bool { + switch self.backgroundStyle { + case .solid, .blur: + return self.isTappable + case .liquidGlass: + return false + } + } +} diff --git a/Sources/ComponentsKit/Components/Card/SUCard.swift b/Sources/ComponentsKit/Components/Card/SUCard.swift index 63736e73..c6a1dc22 100644 --- a/Sources/ComponentsKit/Components/Card/SUCard.swift +++ b/Sources/ComponentsKit/Components/Card/SUCard.swift @@ -47,14 +47,9 @@ public struct SUCard: View { public var body: some View { self.content() .padding(self.model.contentPaddings.edgeInsets) - .background(self.model.backgroundColor.color) - .cornerRadius(self.model.cornerRadius.value) - .overlay( - RoundedRectangle(cornerRadius: self.model.cornerRadius.value) - .strokeBorder( - self.model.borderColor.color, - lineWidth: self.model.borderWidth.value - ) + .cardBackground( + shape: RoundedRectangle(cornerRadius: self.model.cornerRadius.value), + model: self.model ) .shadow(self.model.shadow) .observeSize { self.contentSize = $0 } @@ -71,9 +66,53 @@ public struct SUCard: View { .onEnded { _ in self.scale = 1.0 }, - isEnabled: self.model.isTappable + isEnabled: self.model.isTapAnimationEnabled ) .scaleEffect(self.scale, anchor: .center) .animation(.easeOut(duration: 0.05), value: self.scale) } } + +extension View { + @ViewBuilder + fileprivate func cardBackground( + shape: BackgroundShape, + model: CardVM + ) -> some View { + switch model.backgroundStyle { + case .solid: + self.background(model.backgroundColor?.color) + .clipShape(shape) + .overlay( + shape + .strokeBorder(model.borderColor.color, lineWidth: model.borderWidth.value) + ) + case .blur: + self + .background { + shape + .fill(.thinMaterial) + .overlay { + shape.strokeBorder(model.borderColor.color, lineWidth: model.borderWidth.value) + } + } + .background(model.backgroundColor?.color) + .clipShape(shape) + case .liquidGlass: + if #available(iOS 26.0, *) { + self + .overlay { + shape.strokeBorder(model.borderColor.color, lineWidth: model.borderWidth.value) + } + .glassEffect( + .regular + .tint(model.backgroundColor?.color) + .interactive(model.isTappable), + in: shape + ) + } else { + self + } + } + } +} diff --git a/Sources/ComponentsKit/Components/Card/UKCard.swift b/Sources/ComponentsKit/Components/Card/UKCard.swift index 8c620dd4..190f05b8 100644 --- a/Sources/ComponentsKit/Components/Card/UKCard.swift +++ b/Sources/ComponentsKit/Components/Card/UKCard.swift @@ -20,6 +20,8 @@ open class UKCard: UIView, UKComponent { /// The primary content of the card, provided as a custom view. public let content: Content + /// The visual effect container used to render blur and liquid glass card backgrounds. + public let backgroundEffectView = UIVisualEffectView() // MARK: - Public Properties @@ -29,6 +31,7 @@ open class UKCard: UIView, UKComponent { /// A Boolean value indicating whether the button is pressed. public private(set) var isPressed: Bool = false { didSet { + guard self.model.isTapAnimationEnabled else { return } UIView.animate(withDuration: 0.05, delay: 0, options: [.curveEaseOut]) { self.transform = self.isPressed ? .init( @@ -82,7 +85,8 @@ open class UKCard: UIView, UKComponent { /// Sets up the card's subviews. open func setup() { - self.addSubview(self.content) + self.addSubview(self.backgroundEffectView) + self.backgroundEffectView.contentView.addSubview(self.content) if #available(iOS 17.0, *) { self.registerForTraitChanges([UITraitUserInterfaceStyle.self]) { (view: Self, _: UITraitCollection) in @@ -96,24 +100,29 @@ open class UKCard: UIView, UKComponent { /// Applies styling to the card's subviews. open func style() { Self.Style.mainView(self, model: self.model) + Self.Style.backgroundEffectView(self.backgroundEffectView, model: self.model) } // MARK: - Layout /// Configures the layout. open func layout() { + self.backgroundEffectView.allEdges() self.contentConstraints = LayoutConstraints.merged { - self.content.top(self.model.contentPaddings.top) - self.content.bottom(self.model.contentPaddings.bottom) - self.content.leading(self.model.contentPaddings.leading) - self.content.trailing(self.model.contentPaddings.trailing) + self.content.top(self.model.contentPaddings.top, to: self.backgroundEffectView.contentView) + self.content.bottom(self.model.contentPaddings.bottom, to: self.backgroundEffectView.contentView) + self.content.leading(self.model.contentPaddings.leading, to: self.backgroundEffectView.contentView) + self.content.trailing(self.model.contentPaddings.trailing, to: self.backgroundEffectView.contentView) } } open override func layoutSubviews() { super.layoutSubviews() - self.layer.shadowPath = UIBezierPath(rect: self.bounds).cgPath + self.layer.shadowPath = UIBezierPath( + roundedRect: self.bounds, + cornerRadius: self.model.cornerRadius.value + ).cgPath } // MARK: - Update @@ -192,17 +201,41 @@ open class UKCard: UIView, UKComponent { @objc private func handleTraitChanges() { Self.Style.mainView(self, model: self.model) + Self.Style.backgroundEffectView(self.backgroundEffectView, model: self.model) } } extension UKCard { fileprivate enum Style { static func mainView(_ view: UIView, model: Model) { - view.backgroundColor = model.backgroundColor.uiColor view.layer.cornerRadius = model.cornerRadius.value - view.layer.borderWidth = model.borderWidth.value - view.layer.borderColor = model.borderColor.cgColor view.shadow(model.shadow) } + static func backgroundEffectView(_ view: UIVisualEffectView, model: Model) { + view.contentView.layer.cornerRadius = model.cornerRadius.value + view.layer.cornerRadius = model.cornerRadius.value + view.layer.borderColor = model.borderColor.cgColor + view.layer.borderWidth = model.borderWidth.value + view.clipsToBounds = true + + switch model.backgroundStyle { + case .solid: + view.effect = nil + view.backgroundColor = model.backgroundColor?.uiColor + case .blur: + view.effect = UIBlurEffect(style: .systemThinMaterial) + view.backgroundColor = model.backgroundColor?.uiColor + case .liquidGlass: + if #available(iOS 26.0, *) { + let effect = UIGlassEffect(style: .regular) + effect.tintColor = model.backgroundColor?.uiColor + effect.isInteractive = model.isTappable + view.effect = effect + view.backgroundColor = nil + } else { + view.effect = nil + } + } + } } } From 0153bfe8d84e867b8c087878da3450729d3bfd14 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 5 Jun 2026 18:41:36 +0300 Subject: [PATCH 07/15] add background style in buttons --- .../PreviewPages/ButtonPreview.swift | 1 + .../Components/Button/Models/ButtonVM.swift | 14 +++ .../Components/Button/SUButton.swift | 104 ++++++++++++------ .../Components/Button/UKButton.swift | 49 ++++++++- 4 files changed, 131 insertions(+), 37 deletions(-) diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift index 430476a8..b3e1c945 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift @@ -19,6 +19,7 @@ struct ButtonPreview: View { } Form { AnimationScalePicker(selection: self.$model.animationScale) + BackgroundStylePicker(selection: self.$model.backgroundStyle) ComponentOptionalColorPicker(selection: self.$model.color) Picker("Content Spacing", selection: self.$model.contentSpacing) { Text("4").tag(CGFloat(4)) diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index c42a8865..d910b380 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -10,6 +10,11 @@ public struct ButtonVM: ComponentVM { /// The color of the button. public var color: ComponentColor? + /// Defines how the button renders its background. + /// + /// Defaults to `.solid`. + public var backgroundStyle: BackgroundStyle = .solid + /// The spacing between the button's title and its image or loading indicator. /// /// Defaults to `8.0`. @@ -84,6 +89,14 @@ extension ButtonVM { var isInteractive: Bool { self.isEnabled && !self.isLoading } + var isTapAnimationEnabled: Bool { + switch self.backgroundStyle { + case .solid, .blur: + return self.isInteractive + case .liquidGlass: + return false + } + } var preferredLoadingVM: LoadingVM { return self.loadingVM ?? .init { $0.color = .init( @@ -246,6 +259,7 @@ extension ButtonVM { || self.imageWithLegacyFallback != oldModel.imageWithLegacyFallback || self.contentSpacing != oldModel.contentSpacing || self.title != oldModel.title + || self.style != oldModel.style } } diff --git a/Sources/ComponentsKit/Components/Button/SUButton.swift b/Sources/ComponentsKit/Components/Button/SUButton.swift index ef930ed5..0e301828 100644 --- a/Sources/ComponentsKit/Components/Button/SUButton.swift +++ b/Sources/ComponentsKit/Components/Button/SUButton.swift @@ -33,15 +33,28 @@ public struct SUButton: View { HStack(spacing: self.model.contentSpacing) { self.content } + .font(self.model.preferredFont.font) + .lineLimit(1) + .padding(.horizontal, self.model.horizontalPadding) + .frame(maxWidth: self.model.width) + .frame(height: self.model.height) + .contentShape(.rect) + .foregroundStyle(self.model.foregroundColor.color) + .buttonBackground( + shape: RoundedRectangle(cornerRadius: self.model.cornerRadius.value()), + model: self.model + ) } - .buttonStyle(CustomButtonStyle(model: self.model)) - .simultaneousGesture(DragGesture(minimumDistance: 0.0) - .onChanged { _ in - self.scale = self.model.animationScale.value - } - .onEnded { _ in - self.scale = 1.0 - } + .buttonStyle(CustomButtonStyle()) + .simultaneousGesture( + DragGesture(minimumDistance: 0.0) + .onChanged { _ in + self.scale = self.model.animationScale.value + } + .onEnded { _ in + self.scale = 1.0 + }, + isEnabled: self.model.isTapAnimationEnabled ) .disabled(!self.model.isInteractive) .scaleEffect(self.scale, anchor: .center) @@ -109,31 +122,60 @@ private struct ButtonImage: View { } private struct CustomButtonStyle: SwiftUI.ButtonStyle { - let model: ButtonVM - func makeBody(configuration: Configuration) -> some View { configuration.label - .font(self.model.preferredFont.font) - .lineLimit(1) - .padding(.horizontal, self.model.horizontalPadding) - .frame(maxWidth: self.model.width) - .frame(height: self.model.height) - .contentShape(.rect) - .foregroundStyle(self.model.foregroundColor.color) - .background(self.model.backgroundColor?.color ?? .clear) - .clipShape( - RoundedRectangle( - cornerRadius: self.model.cornerRadius.value() - ) - ) - .overlay { - RoundedRectangle( - cornerRadius: self.model.cornerRadius.value() - ) - .strokeBorder( - self.model.borderColor?.color ?? .clear, - lineWidth: self.model.borderWidth - ) + } +} + +extension View { + @ViewBuilder + fileprivate func buttonBackground( + shape: BackgroundShape, + model: ButtonVM + ) -> some View { + switch model.backgroundStyle { + case .solid: + self + .background(model.backgroundColor?.color ?? .clear) + .clipShape(shape) + .overlay { + shape.strokeBorder( + model.borderColor?.color ?? .clear, + lineWidth: model.borderWidth + ) + } + case .blur: + self + .background { + shape + .fill(.thinMaterial) + .overlay { + shape.strokeBorder( + model.borderColor?.color ?? .clear, + lineWidth: model.borderWidth + ) + } + } + .background(model.backgroundColor?.color) + .clipShape(shape) + case .liquidGlass: + if #available(iOS 26.0, *) { + self + .overlay { + shape.strokeBorder( + model.borderColor?.color ?? .clear, + lineWidth: model.borderWidth + ) + } + .glassEffect( + .regular + .tint(model.backgroundColor?.color) + .interactive(model.isInteractive), + in: shape + ) + } else { + self } + } } } diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index c95a8bd9..f2a55605 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -18,6 +18,7 @@ open class UKButton: FullWidthComponent, UKComponent { /// A Boolean value indicating whether the button is pressed. public private(set) var isPressed: Bool = false { didSet { + guard self.model.isTapAnimationEnabled else { return } UIView.animate(withDuration: 0.05, delay: 0, options: [.curveEaseOut]) { self.transform = self.isPressed && self.model.isInteractive ? .init( @@ -43,6 +44,9 @@ open class UKButton: FullWidthComponent, UKComponent { /// An optional image displayed alongside the title. public let imageView = UIImageView() + /// The visual effect container used to render blur and liquid glass button backgrounds. + public let backgroundEffectView = UIVisualEffectView() + // MARK: Private Properties private var imageViewConstraints = LayoutConstraints() @@ -79,7 +83,8 @@ open class UKButton: FullWidthComponent, UKComponent { // MARK: Setup private func setup() { - self.addSubview(self.stackView) + self.addSubview(self.backgroundEffectView) + self.backgroundEffectView.contentView.addSubview(self.stackView) self.stackView.addArrangedSubview(self.loaderView) self.stackView.addArrangedSubview(self.titleLabel) @@ -101,6 +106,7 @@ open class UKButton: FullWidthComponent, UKComponent { private func style() { Self.Style.mainView(self, model: self.model) + Self.Style.backgroundEffectView(self.backgroundEffectView, model: self.model) Self.Style.titleLabel(self.titleLabel, model: self.model) Self.Style.configureStackView(self.stackView, model: self.model) Self.Style.loaderView(self.loaderView, model: self.model) @@ -110,6 +116,7 @@ open class UKButton: FullWidthComponent, UKComponent { // MARK: Layout private func layout() { + self.backgroundEffectView.allEdges() self.stackView.center() self.imageViewConstraints = self.imageView.size( @@ -121,7 +128,10 @@ open class UKButton: FullWidthComponent, UKComponent { open override func layoutSubviews() { super.layoutSubviews() - self.layer.cornerRadius = self.model.cornerRadius.value(for: self.bounds.height) + let cornerRadius = self.model.cornerRadius.value(for: self.bounds.height) + self.layer.cornerRadius = cornerRadius + self.backgroundEffectView.layer.cornerRadius = cornerRadius + self.backgroundEffectView.contentView.layer.cornerRadius = cornerRadius } // MARK: Update @@ -218,7 +228,8 @@ open class UKButton: FullWidthComponent, UKComponent { // MARK: Helpers @objc private func handleTraitChanges() { - self.layer.borderColor = self.model.borderColor?.uiColor.cgColor + Self.Style.mainView(self, model: self.model) + Self.Style.backgroundEffectView(self.backgroundEffectView, model: self.model) } } @@ -227,13 +238,39 @@ open class UKButton: FullWidthComponent, UKComponent { extension UKButton { fileprivate enum Style { static func mainView(_ view: UIView, model: Model) { - view.layer.borderWidth = model.borderWidth - view.layer.borderColor = model.borderColor?.uiColor.cgColor - view.backgroundColor = model.backgroundColor?.uiColor + view.backgroundColor = nil view.layer.cornerRadius = model.cornerRadius.value( for: view.bounds.height ) } + static func backgroundEffectView(_ view: UIVisualEffectView, model: Model) { + let cornerRadius = model.cornerRadius.value(for: view.bounds.height) + view.contentView.layer.cornerRadius = cornerRadius + view.layer.cornerRadius = cornerRadius + view.layer.borderColor = model.borderColor?.uiColor.cgColor + view.layer.borderWidth = model.borderWidth + view.clipsToBounds = true + + switch model.backgroundStyle { + case .solid: + view.effect = nil + view.backgroundColor = model.backgroundColor?.uiColor + case .blur: + view.effect = UIBlurEffect(style: .systemThinMaterial) + view.backgroundColor = model.backgroundColor?.uiColor + case .liquidGlass: + if #available(iOS 26.0, *) { + let effect = UIGlassEffect(style: .regular) + effect.tintColor = model.backgroundColor?.uiColor + effect.isInteractive = model.isInteractive + view.effect = effect + view.backgroundColor = nil + } else { + view.effect = nil + view.backgroundColor = model.backgroundColor?.uiColor + } + } + } static func titleLabel(_ label: UILabel, model: Model) { label.textAlignment = .center label.text = model.title From 4c31088f638d85c6c491c2681480147339e00b71 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 5 Jun 2026 18:54:30 +0300 Subject: [PATCH 08/15] refactor: share SwiftUI component backgrounds --- .../Components/Button/SUButton.swift | 61 ++----------------- .../Components/Card/SUCard.swift | 52 ++-------------- .../Modal/SwiftUI/ModalContent.swift | 47 ++------------ .../SwiftUI/View+ComponentBackground.swift | 49 +++++++++++++++ 4 files changed, 66 insertions(+), 143 deletions(-) create mode 100644 Sources/ComponentsKit/Helpers/SwiftUI/View+ComponentBackground.swift diff --git a/Sources/ComponentsKit/Components/Button/SUButton.swift b/Sources/ComponentsKit/Components/Button/SUButton.swift index 0e301828..8bae3168 100644 --- a/Sources/ComponentsKit/Components/Button/SUButton.swift +++ b/Sources/ComponentsKit/Components/Button/SUButton.swift @@ -40,9 +40,13 @@ public struct SUButton: View { .frame(height: self.model.height) .contentShape(.rect) .foregroundStyle(self.model.foregroundColor.color) - .buttonBackground( + .componentBackground( shape: RoundedRectangle(cornerRadius: self.model.cornerRadius.value()), - model: self.model + backgroundStyle: self.model.backgroundStyle, + backgroundColor: self.model.backgroundColor?.color, + borderColor: self.model.borderColor?.color ?? .clear, + borderWidth: self.model.borderWidth, + isGlassInteractive: self.model.isInteractive ) } .buttonStyle(CustomButtonStyle()) @@ -126,56 +130,3 @@ private struct CustomButtonStyle: SwiftUI.ButtonStyle { configuration.label } } - -extension View { - @ViewBuilder - fileprivate func buttonBackground( - shape: BackgroundShape, - model: ButtonVM - ) -> some View { - switch model.backgroundStyle { - case .solid: - self - .background(model.backgroundColor?.color ?? .clear) - .clipShape(shape) - .overlay { - shape.strokeBorder( - model.borderColor?.color ?? .clear, - lineWidth: model.borderWidth - ) - } - case .blur: - self - .background { - shape - .fill(.thinMaterial) - .overlay { - shape.strokeBorder( - model.borderColor?.color ?? .clear, - lineWidth: model.borderWidth - ) - } - } - .background(model.backgroundColor?.color) - .clipShape(shape) - case .liquidGlass: - if #available(iOS 26.0, *) { - self - .overlay { - shape.strokeBorder( - model.borderColor?.color ?? .clear, - lineWidth: model.borderWidth - ) - } - .glassEffect( - .regular - .tint(model.backgroundColor?.color) - .interactive(model.isInteractive), - in: shape - ) - } else { - self - } - } - } -} diff --git a/Sources/ComponentsKit/Components/Card/SUCard.swift b/Sources/ComponentsKit/Components/Card/SUCard.swift index c6a1dc22..e3afc0e6 100644 --- a/Sources/ComponentsKit/Components/Card/SUCard.swift +++ b/Sources/ComponentsKit/Components/Card/SUCard.swift @@ -47,9 +47,13 @@ public struct SUCard: View { public var body: some View { self.content() .padding(self.model.contentPaddings.edgeInsets) - .cardBackground( + .componentBackground( shape: RoundedRectangle(cornerRadius: self.model.cornerRadius.value), - model: self.model + backgroundStyle: self.model.backgroundStyle, + backgroundColor: self.model.backgroundColor?.color, + borderColor: self.model.borderColor.color, + borderWidth: self.model.borderWidth.value, + isGlassInteractive: self.model.isTappable ) .shadow(self.model.shadow) .observeSize { self.contentSize = $0 } @@ -72,47 +76,3 @@ public struct SUCard: View { .animation(.easeOut(duration: 0.05), value: self.scale) } } - -extension View { - @ViewBuilder - fileprivate func cardBackground( - shape: BackgroundShape, - model: CardVM - ) -> some View { - switch model.backgroundStyle { - case .solid: - self.background(model.backgroundColor?.color) - .clipShape(shape) - .overlay( - shape - .strokeBorder(model.borderColor.color, lineWidth: model.borderWidth.value) - ) - case .blur: - self - .background { - shape - .fill(.thinMaterial) - .overlay { - shape.strokeBorder(model.borderColor.color, lineWidth: model.borderWidth.value) - } - } - .background(model.backgroundColor?.color) - .clipShape(shape) - case .liquidGlass: - if #available(iOS 26.0, *) { - self - .overlay { - shape.strokeBorder(model.borderColor.color, lineWidth: model.borderWidth.value) - } - .glassEffect( - .regular - .tint(model.backgroundColor?.color) - .interactive(model.isTappable), - in: shape - ) - } else { - self - } - } - } -} diff --git a/Sources/ComponentsKit/Components/Modal/SwiftUI/ModalContent.swift b/Sources/ComponentsKit/Components/Modal/SwiftUI/ModalContent.swift index 67722c00..59011e82 100644 --- a/Sources/ComponentsKit/Components/Modal/SwiftUI/ModalContent.swift +++ b/Sources/ComponentsKit/Components/Modal/SwiftUI/ModalContent.swift @@ -55,9 +55,12 @@ struct ModalContent: View { .padding(.bottom, self.model.contentPaddings.bottom) } .frame(maxWidth: self.model.size.maxWidth, alignment: .leading) - .modalBackground( + .componentBackground( shape: RoundedRectangle(cornerRadius: model.cornerRadius.value), - model: self.model + backgroundStyle: self.model.backgroundStyle, + backgroundColor: self.model.preferredBackgroundColor?.color, + borderColor: UniversalColor.divider.color, + borderWidth: self.model.borderWidth.value ) .padding(self.model.outerPaddings.edgeInsets) } @@ -72,43 +75,3 @@ struct ModalContent: View { return self.bodySize.height + self.bodyTopPadding + self.bodyBottomPadding } } - -extension View { - @ViewBuilder - fileprivate func modalBackground( - shape: BackgroundShape, - model: any ModalVM - ) -> some View { - switch model.backgroundStyle { - case .solid: - self.background(model.preferredBackgroundColor?.color) - .clipShape(shape) - .overlay( - shape - .strokeBorder(UniversalColor.divider.color, lineWidth: model.borderWidth.value) - ) - case .blur: - self - .background { - shape - .fill(.thinMaterial) - .overlay { - shape.strokeBorder(UniversalColor.divider.color, lineWidth: model.borderWidth.value) - } - } - .background(model.preferredBackgroundColor?.color) - .clipShape(shape) - case .liquidGlass: - if #available(iOS 26.0, *) { - self.glassEffect( - .regular - .tint(model.preferredBackgroundColor?.color) - .interactive(), - in: shape - ) - } else { - self - } - } - } -} diff --git a/Sources/ComponentsKit/Helpers/SwiftUI/View+ComponentBackground.swift b/Sources/ComponentsKit/Helpers/SwiftUI/View+ComponentBackground.swift new file mode 100644 index 00000000..a46a6927 --- /dev/null +++ b/Sources/ComponentsKit/Helpers/SwiftUI/View+ComponentBackground.swift @@ -0,0 +1,49 @@ +import SwiftUI + +extension View { + @ViewBuilder + func componentBackground( + shape: BackgroundShape, + backgroundStyle: BackgroundStyle, + backgroundColor: Color?, + borderColor: Color, + borderWidth: CGFloat, + isGlassInteractive: Bool = true + ) -> some View { + switch backgroundStyle { + case .solid: + self + .background(backgroundColor ?? .clear) + .clipShape(shape) + .overlay { + shape.strokeBorder(borderColor, lineWidth: borderWidth) + } + case .blur: + self + .background { + shape + .fill(.thinMaterial) + .overlay { + shape.strokeBorder(borderColor, lineWidth: borderWidth) + } + } + .background(backgroundColor) + .clipShape(shape) + case .liquidGlass: + if #available(iOS 26.0, *) { + self + .overlay { + shape.strokeBorder(borderColor, lineWidth: borderWidth) + } + .glassEffect( + .regular + .tint(backgroundColor) + .interactive(isGlassInteractive), + in: shape + ) + } else { + self + } + } + } +} From c1ace0815cb642b099229b347b9c91da573b7189 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 5 Jun 2026 19:03:54 +0300 Subject: [PATCH 09/15] refactor: share UIKit background styling --- .../Components/Button/UKButton.swift | 34 ++++------------- .../Components/Card/UKCard.swift | 32 ++++------------ .../Modal/UIKit/UKModalController.swift | 29 ++++---------- .../UIVisualEffectView+BackgroundStyle.swift | 38 +++++++++++++++++++ 4 files changed, 61 insertions(+), 72 deletions(-) create mode 100644 Sources/ComponentsKit/Helpers/UIKit/UIVisualEffectView+BackgroundStyle.swift diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index f2a55605..1f108daf 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -244,32 +244,14 @@ extension UKButton { ) } static func backgroundEffectView(_ view: UIVisualEffectView, model: Model) { - let cornerRadius = model.cornerRadius.value(for: view.bounds.height) - view.contentView.layer.cornerRadius = cornerRadius - view.layer.cornerRadius = cornerRadius - view.layer.borderColor = model.borderColor?.uiColor.cgColor - view.layer.borderWidth = model.borderWidth - view.clipsToBounds = true - - switch model.backgroundStyle { - case .solid: - view.effect = nil - view.backgroundColor = model.backgroundColor?.uiColor - case .blur: - view.effect = UIBlurEffect(style: .systemThinMaterial) - view.backgroundColor = model.backgroundColor?.uiColor - case .liquidGlass: - if #available(iOS 26.0, *) { - let effect = UIGlassEffect(style: .regular) - effect.tintColor = model.backgroundColor?.uiColor - effect.isInteractive = model.isInteractive - view.effect = effect - view.backgroundColor = nil - } else { - view.effect = nil - view.backgroundColor = model.backgroundColor?.uiColor - } - } + view.setBackgroundStyle( + model.backgroundStyle, + backgroundColor: model.backgroundColor?.uiColor, + borderColor: model.borderColor?.uiColor, + borderWidth: model.borderWidth, + cornerRadius: model.cornerRadius.value(for: view.bounds.height), + isGlassInteractive: model.isInteractive + ) } static func titleLabel(_ label: UILabel, model: Model) { label.textAlignment = .center diff --git a/Sources/ComponentsKit/Components/Card/UKCard.swift b/Sources/ComponentsKit/Components/Card/UKCard.swift index 190f05b8..14514b94 100644 --- a/Sources/ComponentsKit/Components/Card/UKCard.swift +++ b/Sources/ComponentsKit/Components/Card/UKCard.swift @@ -212,30 +212,14 @@ extension UKCard { view.shadow(model.shadow) } static func backgroundEffectView(_ view: UIVisualEffectView, model: Model) { - view.contentView.layer.cornerRadius = model.cornerRadius.value - view.layer.cornerRadius = model.cornerRadius.value - view.layer.borderColor = model.borderColor.cgColor - view.layer.borderWidth = model.borderWidth.value - view.clipsToBounds = true - - switch model.backgroundStyle { - case .solid: - view.effect = nil - view.backgroundColor = model.backgroundColor?.uiColor - case .blur: - view.effect = UIBlurEffect(style: .systemThinMaterial) - view.backgroundColor = model.backgroundColor?.uiColor - case .liquidGlass: - if #available(iOS 26.0, *) { - let effect = UIGlassEffect(style: .regular) - effect.tintColor = model.backgroundColor?.uiColor - effect.isInteractive = model.isTappable - view.effect = effect - view.backgroundColor = nil - } else { - view.effect = nil - } - } + view.setBackgroundStyle( + model.backgroundStyle, + backgroundColor: model.backgroundColor?.uiColor, + borderColor: model.borderColor.uiColor, + borderWidth: model.borderWidth.value, + cornerRadius: model.cornerRadius.value, + isGlassInteractive: model.isTappable + ) } } } diff --git a/Sources/ComponentsKit/Components/Modal/UIKit/UKModalController.swift b/Sources/ComponentsKit/Components/Modal/UIKit/UKModalController.swift index d0bce106..d8c35efb 100644 --- a/Sources/ComponentsKit/Components/Modal/UIKit/UKModalController.swift +++ b/Sources/ComponentsKit/Components/Modal/UIKit/UKModalController.swift @@ -269,28 +269,13 @@ extension UKModalController { view.layer.cornerRadius = model.cornerRadius.value } static func backgroundEffectView(_ view: UIVisualEffectView, model: VM) { - view.layer.cornerRadius = model.cornerRadius.value - view.layer.borderColor = UniversalColor.divider.cgColor - view.layer.borderWidth = model.borderWidth.value - view.clipsToBounds = true - - switch model.backgroundStyle { - case .solid: - view.effect = nil - view.backgroundColor = model.preferredBackgroundColor?.uiColor - case .blur: - view.effect = UIBlurEffect(style: .systemThinMaterial) - view.backgroundColor = model.preferredBackgroundColor?.uiColor - case .liquidGlass: - if #available(iOS 26.0, *) { - let effect = UIGlassEffect(style: .regular) - effect.tintColor = model.preferredBackgroundColor?.uiColor - effect.isInteractive = true - view.effect = effect - } else { - view.effect = nil - } - } + view.setBackgroundStyle( + model.backgroundStyle, + backgroundColor: model.preferredBackgroundColor?.uiColor, + borderColor: UniversalColor.divider.uiColor, + borderWidth: model.borderWidth.value, + cornerRadius: model.cornerRadius.value + ) } static func bodyWrapper(_ scrollView: UIScrollView) { scrollView.delaysContentTouches = false diff --git a/Sources/ComponentsKit/Helpers/UIKit/UIVisualEffectView+BackgroundStyle.swift b/Sources/ComponentsKit/Helpers/UIKit/UIVisualEffectView+BackgroundStyle.swift new file mode 100644 index 00000000..7f0c7f00 --- /dev/null +++ b/Sources/ComponentsKit/Helpers/UIKit/UIVisualEffectView+BackgroundStyle.swift @@ -0,0 +1,38 @@ +import UIKit + +extension UIVisualEffectView { + func setBackgroundStyle( + _ backgroundStyle: BackgroundStyle, + backgroundColor: UIColor?, + borderColor: UIColor?, + borderWidth: CGFloat, + cornerRadius: CGFloat, + isGlassInteractive: Bool = true + ) { + self.contentView.layer.cornerRadius = cornerRadius + self.layer.cornerRadius = cornerRadius + self.layer.borderColor = borderColor?.cgColor + self.layer.borderWidth = borderWidth + self.clipsToBounds = true + + switch backgroundStyle { + case .solid: + self.effect = nil + self.backgroundColor = backgroundColor + case .blur: + self.effect = UIBlurEffect(style: .systemThinMaterial) + self.backgroundColor = backgroundColor + case .liquidGlass: + if #available(iOS 26.0, *) { + let effect = UIGlassEffect(style: .regular) + effect.tintColor = backgroundColor + effect.isInteractive = isGlassInteractive + self.effect = effect + self.backgroundColor = nil + } else { + self.effect = nil + self.backgroundColor = backgroundColor + } + } + } +} From f815aa216e6f1ffef09b9f8f2364234b8ac08997 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 5 Jun 2026 19:09:46 +0300 Subject: [PATCH 10/15] address warning about custom tap animations in buttons --- Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift | 4 ++-- Sources/ComponentsKit/Components/Button/SUButton.swift | 2 +- Sources/ComponentsKit/Components/Button/UKButton.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index d910b380..fc90691e 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -89,10 +89,10 @@ extension ButtonVM { var isInteractive: Bool { self.isEnabled && !self.isLoading } - var isTapAnimationEnabled: Bool { + var isCustomTapAnimationEnabled: Bool { switch self.backgroundStyle { case .solid, .blur: - return self.isInteractive + return true case .liquidGlass: return false } diff --git a/Sources/ComponentsKit/Components/Button/SUButton.swift b/Sources/ComponentsKit/Components/Button/SUButton.swift index 0e301828..76f331af 100644 --- a/Sources/ComponentsKit/Components/Button/SUButton.swift +++ b/Sources/ComponentsKit/Components/Button/SUButton.swift @@ -54,7 +54,7 @@ public struct SUButton: View { .onEnded { _ in self.scale = 1.0 }, - isEnabled: self.model.isTapAnimationEnabled + isEnabled: self.model.isCustomTapAnimationEnabled ) .disabled(!self.model.isInteractive) .scaleEffect(self.scale, anchor: .center) diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index f2a55605..b730db11 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -18,7 +18,7 @@ open class UKButton: FullWidthComponent, UKComponent { /// A Boolean value indicating whether the button is pressed. public private(set) var isPressed: Bool = false { didSet { - guard self.model.isTapAnimationEnabled else { return } + guard self.model.isCustomTapAnimationEnabled else { return } UIView.animate(withDuration: 0.05, delay: 0, options: [.curveEaseOut]) { self.transform = self.isPressed && self.model.isInteractive ? .init( From 33a311cef88534dfe33cd9332e7f2a47804540f6 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 5 Jun 2026 19:14:42 +0300 Subject: [PATCH 11/15] remove deprecations --- .../Avatar/Models/AvatarImageSource.swift | 6 ---- .../Avatar/Models/AvatarPlaceholder.swift | 12 -------- .../Components/Button/Models/ButtonVM.swift | 29 ++----------------- .../Components/Button/SUButton.swift | 2 +- .../Components/Button/UKButton.swift | 2 +- .../CircularProgress/SUCircularProgress.swift | 13 --------- .../CircularProgress/UKCircularProgress.swift | 18 ------------ .../ProgressBar/SUProgressBar.swift | 13 --------- .../ProgressBar/UKProgressBar.swift | 18 ------------ 9 files changed, 4 insertions(+), 109 deletions(-) diff --git a/Sources/ComponentsKit/Components/Avatar/Models/AvatarImageSource.swift b/Sources/ComponentsKit/Components/Avatar/Models/AvatarImageSource.swift index 81fcc20d..80e0697a 100644 --- a/Sources/ComponentsKit/Components/Avatar/Models/AvatarImageSource.swift +++ b/Sources/ComponentsKit/Components/Avatar/Models/AvatarImageSource.swift @@ -13,11 +13,5 @@ extension AvatarVM { /// /// - Parameter image: See ``UniversalImage``. case local(_ image: UniversalImage) - - /// An image loaded from a local asset. - @available(*, deprecated, message: "Use `local(_:)` instead.") - public static func local(_ name: String, _ bundle: Bundle? = nil) -> Self { - return .local(.init(name, bundle: bundle)) - } } } diff --git a/Sources/ComponentsKit/Components/Avatar/Models/AvatarPlaceholder.swift b/Sources/ComponentsKit/Components/Avatar/Models/AvatarPlaceholder.swift index b1b05975..9ddb89c2 100644 --- a/Sources/ComponentsKit/Components/Avatar/Models/AvatarPlaceholder.swift +++ b/Sources/ComponentsKit/Components/Avatar/Models/AvatarPlaceholder.swift @@ -17,17 +17,5 @@ extension AvatarVM { /// /// - Parameter image: See ``UniversalImage``. case image(_ image: UniversalImage) - - /// A placeholder that displays an SF Symbol. - @available(*, deprecated, message: "Use `image(_:)` instead.") - public static func sfSymbol(_ name: String) -> Self { - return .image(.init(systemName: name)) - } - - /// A placeholder that displays a custom icon from an asset catalog. - @available(*, deprecated, message: "Use `image(_:)` instead.") - public static func icon(_ name: String, _ bundle: Bundle? = nil) -> Self { - return .image(.init(name, bundle: bundle)) - } } } diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index fc90691e..15e644fb 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -38,14 +38,6 @@ public struct ButtonVM: ComponentVM { /// Defaults to `.leading`. public var imageLocation: ImageLocation = .leading - /// Defines how image is rendered. - @available(*, deprecated, message: "Use `image.withRenderingMode(_:)` instead.") - public var imageRenderingMode: ImageRenderingMode? - - /// The source of the image to be displayed. - @available(*, deprecated, message: "Use `image` instead.") - public var imageSrc: ImageSource? - /// A Boolean value indicating whether the button is enabled or disabled. /// /// Defaults to `true`. @@ -200,30 +192,13 @@ extension ButtonVM { } } } - var imageWithLegacyFallback: UniversalImage? { - if let image { return image } - - guard let imageSrc else { return nil } - - let image = switch imageSrc { - case .sfSymbol(let name): - UniversalImage(systemName: name) - case .local(let name, let bundle): - UniversalImage(name, bundle: bundle) - } - if let imageRenderingMode { - return image.withRenderingMode(imageRenderingMode) - } else { - return image - } - } } // MARK: UIKit Helpers extension ButtonVM { var isImageHidden: Bool { - return self.isLoading || self.imageWithLegacyFallback.isNil + return self.isLoading || self.image.isNil } func preferredSize( for contentSize: CGSize, @@ -256,7 +231,7 @@ extension ButtonVM { || self.font != oldModel.font || self.isFullWidth != oldModel.isFullWidth || self.isLoading != oldModel.isLoading - || self.imageWithLegacyFallback != oldModel.imageWithLegacyFallback + || self.image != oldModel.image || self.contentSpacing != oldModel.contentSpacing || self.title != oldModel.title || self.style != oldModel.style diff --git a/Sources/ComponentsKit/Components/Button/SUButton.swift b/Sources/ComponentsKit/Components/Button/SUButton.swift index 76f331af..959fe573 100644 --- a/Sources/ComponentsKit/Components/Button/SUButton.swift +++ b/Sources/ComponentsKit/Components/Button/SUButton.swift @@ -63,7 +63,7 @@ public struct SUButton: View { @ViewBuilder private var content: some View { - switch (self.model.isLoading, self.model.imageWithLegacyFallback, self.model.imageLocation) { + switch (self.model.isLoading, self.model.image, self.model.imageLocation) { case (true, _, _) where self.model.title.isEmpty: SULoading(model: self.model.preferredLoadingVM) case (true, _, _): diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index b730db11..7a216f5d 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -292,7 +292,7 @@ extension UKButton { view.isVisible = model.isLoading } static func imageView(_ imageView: UIImageView, model: Model) { - imageView.image = model.imageWithLegacyFallback?.uiImage + imageView.image = model.image?.uiImage imageView.contentMode = .scaleAspectFit imageView.isHidden = model.isImageHidden imageView.tintColor = model.foregroundColor.uiColor diff --git a/Sources/ComponentsKit/Components/CircularProgress/SUCircularProgress.swift b/Sources/ComponentsKit/Components/CircularProgress/SUCircularProgress.swift index dee6ad47..357c3983 100644 --- a/Sources/ComponentsKit/Components/CircularProgress/SUCircularProgress.swift +++ b/Sources/ComponentsKit/Components/CircularProgress/SUCircularProgress.swift @@ -16,19 +16,6 @@ public struct SUCircularProgress: View { // MARK: - Initializer - /// Initializer. - /// - Parameters: - /// - currentValue: Current progress. - /// - model: A model that defines the appearance properties. - @available(*, deprecated, message: "Set `currentValue` in the model instead.") - public init( - currentValue: CGFloat = 0, - model: CircularProgressVM = .init() - ) { - self.currentValue = currentValue - self.model = model - } - /// Initializer. /// - Parameters: /// - model: A model that defines the appearance properties. diff --git a/Sources/ComponentsKit/Components/CircularProgress/UKCircularProgress.swift b/Sources/ComponentsKit/Components/CircularProgress/UKCircularProgress.swift index c1777454..dce73525 100644 --- a/Sources/ComponentsKit/Components/CircularProgress/UKCircularProgress.swift +++ b/Sources/ComponentsKit/Components/CircularProgress/UKCircularProgress.swift @@ -42,24 +42,6 @@ open class UKCircularProgress: UIView, UKComponent { // MARK: - Initialization - /// Initializer. - /// - Parameters: - /// - initialValue: The initial progress value. Defaults to `0`. - /// - model: The model that defines the appearance properties. - @available(*, deprecated, message: "Set `currentValue` in the model instead.") - public init( - initialValue: CGFloat = 0, - model: CircularProgressVM = .init() - ) { - self.model = model - self.currentValue = initialValue - super.init(frame: .zero) - - self.setup() - self.style() - self.layout() - } - /// Initializer. /// - Parameters: /// - model: The model that defines the appearance properties. diff --git a/Sources/ComponentsKit/Components/ProgressBar/SUProgressBar.swift b/Sources/ComponentsKit/Components/ProgressBar/SUProgressBar.swift index 96a1e0cc..8c8e790d 100644 --- a/Sources/ComponentsKit/Components/ProgressBar/SUProgressBar.swift +++ b/Sources/ComponentsKit/Components/ProgressBar/SUProgressBar.swift @@ -15,19 +15,6 @@ public struct SUProgressBar: View { // MARK: - Initializer - /// Initializer. - /// - Parameters: - /// - currentValue: The current progress value. - /// - model: A model that defines the appearance properties. - @available(*, deprecated, message: "Set `currentValue` in the model instead.") - public init( - currentValue: CGFloat, - model: ProgressBarVM = .init() - ) { - self.currentValue = currentValue - self.model = model - } - /// Initializer. /// - Parameters: /// - model: A model that defines the appearance properties. diff --git a/Sources/ComponentsKit/Components/ProgressBar/UKProgressBar.swift b/Sources/ComponentsKit/Components/ProgressBar/UKProgressBar.swift index 1583645b..a275336d 100644 --- a/Sources/ComponentsKit/Components/ProgressBar/UKProgressBar.swift +++ b/Sources/ComponentsKit/Components/ProgressBar/UKProgressBar.swift @@ -50,24 +50,6 @@ open class UKProgressBar: FullWidthComponent, UKComponent { // MARK: - Initialization - /// Initializer. - /// - Parameters: - /// - initialValue: The initial progress value. Defaults to `0`. - /// - model: A model that defines the appearance properties. - @available(*, deprecated, message: "Set `currentValue` in the model instead.") - public init( - initialValue: CGFloat = 0, - model: ProgressBarVM = .init() - ) { - self.currentValue = initialValue - self.model = model - super.init(frame: .zero) - - self.setup() - self.style() - self.layout() - } - /// Initializer. /// - Parameters: /// - model: A model that defines the appearance properties. From 62eae19af12814eee41b615456116b949e45d43c Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sat, 6 Jun 2026 15:17:55 +0300 Subject: [PATCH 12/15] make uk classes `open` --- Sources/ComponentsKit/Components/Alert/UKAlertController.swift | 2 +- Sources/ComponentsKit/Components/Countdown/UKCountdown.swift | 2 +- .../Components/Modal/UIKit/UKBottomModalController.swift | 2 +- .../Components/Modal/UIKit/UKCenterModalController.swift | 2 +- .../Components/RadioGroup/UIKit/RadioGroupItemView.swift | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/ComponentsKit/Components/Alert/UKAlertController.swift b/Sources/ComponentsKit/Components/Alert/UKAlertController.swift index 2af9e366..f6ec2cc2 100644 --- a/Sources/ComponentsKit/Components/Alert/UKAlertController.swift +++ b/Sources/ComponentsKit/Components/Alert/UKAlertController.swift @@ -30,7 +30,7 @@ import UIKit /// /// vc.present(alert, animated: true) /// ``` -public class UKAlertController: UKCenterModalController { +open class UKAlertController: UKCenterModalController { // MARK: - Properties /// A model that defines the appearance properties for an alert. diff --git a/Sources/ComponentsKit/Components/Countdown/UKCountdown.swift b/Sources/ComponentsKit/Components/Countdown/UKCountdown.swift index e45df5c5..f3a81f9c 100644 --- a/Sources/ComponentsKit/Components/Countdown/UKCountdown.swift +++ b/Sources/ComponentsKit/Components/Countdown/UKCountdown.swift @@ -3,7 +3,7 @@ import Combine import UIKit /// A UIKit timer component that counts down from a specified duration to zero. -public class UKCountdown: UIView, UKComponent { +open class UKCountdown: UIView, UKComponent { // MARK: - Public Properties /// A model that defines the appearance properties. diff --git a/Sources/ComponentsKit/Components/Modal/UIKit/UKBottomModalController.swift b/Sources/ComponentsKit/Components/Modal/UIKit/UKBottomModalController.swift index 802a14d7..81dd1ff6 100644 --- a/Sources/ComponentsKit/Components/Modal/UIKit/UKBottomModalController.swift +++ b/Sources/ComponentsKit/Components/Modal/UIKit/UKBottomModalController.swift @@ -28,7 +28,7 @@ import UIKit /// /// vc.present(bottomModal, animated: true) /// ``` -public class UKBottomModalController: UKModalController { +open class UKBottomModalController: UKModalController { // MARK: - Initialization /// Initializer. diff --git a/Sources/ComponentsKit/Components/Modal/UIKit/UKCenterModalController.swift b/Sources/ComponentsKit/Components/Modal/UIKit/UKCenterModalController.swift index 8f6387c2..3111cec1 100644 --- a/Sources/ComponentsKit/Components/Modal/UIKit/UKCenterModalController.swift +++ b/Sources/ComponentsKit/Components/Modal/UIKit/UKCenterModalController.swift @@ -28,7 +28,7 @@ import UIKit /// /// vc.present(centerModal, animated: true) /// ``` -public class UKCenterModalController: UKModalController { +open class UKCenterModalController: UKModalController { // MARK: - Initialization /// Initializer. diff --git a/Sources/ComponentsKit/Components/RadioGroup/UIKit/RadioGroupItemView.swift b/Sources/ComponentsKit/Components/RadioGroup/UIKit/RadioGroupItemView.swift index 532ecf7b..ccbdd523 100644 --- a/Sources/ComponentsKit/Components/RadioGroup/UIKit/RadioGroupItemView.swift +++ b/Sources/ComponentsKit/Components/RadioGroup/UIKit/RadioGroupItemView.swift @@ -2,7 +2,7 @@ import AutoLayout import UIKit /// A view representing a single radio button item in a radio group. -public class RadioGroupItemView: UIView { +open class RadioGroupItemView: UIView { // MARK: Properties /// A view that represents an outer circle and contains an inner circle. From d936146c8dc13b11b7accd564173f3ad6fa0f925 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sat, 6 Jun 2026 15:20:54 +0300 Subject: [PATCH 13/15] address compilation errors --- Sources/ComponentsKit/Components/Countdown/UKCountdown.swift | 2 +- .../Components/RadioGroup/UIKit/RadioGroupItemView.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/ComponentsKit/Components/Countdown/UKCountdown.swift b/Sources/ComponentsKit/Components/Countdown/UKCountdown.swift index f3a81f9c..570e48e2 100644 --- a/Sources/ComponentsKit/Components/Countdown/UKCountdown.swift +++ b/Sources/ComponentsKit/Components/Countdown/UKCountdown.swift @@ -61,7 +61,7 @@ open class UKCountdown: UIView, UKComponent { self.layout() } - required init?(coder: NSCoder) { + required public init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/Sources/ComponentsKit/Components/RadioGroup/UIKit/RadioGroupItemView.swift b/Sources/ComponentsKit/Components/RadioGroup/UIKit/RadioGroupItemView.swift index ccbdd523..2348da38 100644 --- a/Sources/ComponentsKit/Components/RadioGroup/UIKit/RadioGroupItemView.swift +++ b/Sources/ComponentsKit/Components/RadioGroup/UIKit/RadioGroupItemView.swift @@ -31,7 +31,7 @@ open class RadioGroupItemView: UIView { // MARK: Initialization - init( + public init( isSelected: Bool, groupVM: RadioGroupVM, itemVM: RadioItemVM @@ -47,7 +47,7 @@ open class RadioGroupItemView: UIView { self.layout() } - required init?(coder: NSCoder) { + public required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } From 89f7fdff116834d2d29893e4d58860cbab730189 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sat, 6 Jun 2026 16:34:26 +0300 Subject: [PATCH 14/15] add `backgroundStyle` to `AlertButtonVM` --- .../ComponentsPreview/PreviewPages/AlertPreview.swift | 1 + .../Components/Alert/Models/AlertButtonVM.swift | 5 +++++ Sources/ComponentsKit/Components/Alert/Models/AlertVM.swift | 1 + 3 files changed, 7 insertions(+) diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/AlertPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/AlertPreview.swift index 9615a69d..299e5862 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/AlertPreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/AlertPreview.swift @@ -112,6 +112,7 @@ struct AlertPreview: View { private func buttonPickers(for buttonVM: Binding) -> some View { Group { AnimationScalePicker(selection: buttonVM.animationScale) + BackgroundStylePicker(selection: buttonVM.backgroundStyle) ComponentOptionalColorPicker(selection: buttonVM.color) ComponentRadiusPicker(selection: buttonVM.cornerRadius) { Text("Custom: 20px").tag(ComponentRadius.custom(20)) diff --git a/Sources/ComponentsKit/Components/Alert/Models/AlertButtonVM.swift b/Sources/ComponentsKit/Components/Alert/Models/AlertButtonVM.swift index 10289e80..7e53de95 100644 --- a/Sources/ComponentsKit/Components/Alert/Models/AlertButtonVM.swift +++ b/Sources/ComponentsKit/Components/Alert/Models/AlertButtonVM.swift @@ -9,6 +9,11 @@ public struct AlertButtonVM: ComponentVM { /// /// Defaults to `.medium`. public var animationScale: AnimationScale = .medium + + /// Defines how the button renders its background. + /// + /// Defaults to `.solid`. + public var backgroundStyle: BackgroundStyle = .solid /// The color of the button. public var color: ComponentColor? diff --git a/Sources/ComponentsKit/Components/Alert/Models/AlertVM.swift b/Sources/ComponentsKit/Components/Alert/Models/AlertVM.swift index a619a29a..51a6eafb 100644 --- a/Sources/ComponentsKit/Components/Alert/Models/AlertVM.swift +++ b/Sources/ComponentsKit/Components/Alert/Models/AlertVM.swift @@ -92,6 +92,7 @@ extension AlertVM { return ButtonVM { $0.title = model.title $0.animationScale = model.animationScale + $0.backgroundStyle = model.backgroundStyle $0.color = model.color $0.cornerRadius = model.cornerRadius $0.style = model.style From 25ab5971d1349687947063635d2d8d5f26cdd2d0 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Mon, 8 Jun 2026 13:13:52 +0300 Subject: [PATCH 15/15] run swiftlint --- .../ComponentsKit/Components/Alert/Models/AlertButtonVM.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ComponentsKit/Components/Alert/Models/AlertButtonVM.swift b/Sources/ComponentsKit/Components/Alert/Models/AlertButtonVM.swift index 7e53de95..8355ffe6 100644 --- a/Sources/ComponentsKit/Components/Alert/Models/AlertButtonVM.swift +++ b/Sources/ComponentsKit/Components/Alert/Models/AlertButtonVM.swift @@ -9,7 +9,7 @@ public struct AlertButtonVM: ComponentVM { /// /// Defaults to `.medium`. public var animationScale: AnimationScale = .medium - + /// Defines how the button renders its background. /// /// Defaults to `.solid`.