From ab9298332bc19f4da3acf2db53ea5a8a3ea5f600 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 5 Jun 2026 13:27:24 +0300 Subject: [PATCH 1/5] 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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)