diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/ModalPreview+Helpers.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/ModalPreview+Helpers.swift index f92c961c..a4cc203f 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/ModalPreview+Helpers.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/ModalPreview+Helpers.swift @@ -82,12 +82,8 @@ struct ModalPreviewHelpers { Text("16px").tag(Paddings(padding: 16)) Text("20px").tag(Paddings(padding: 20)) } - Picker("Corner Radius", selection: self.$model.cornerRadius) { - Text("None").tag(ModalRadius.none) - Text("Small").tag(ModalRadius.small) - Text("Medium").tag(ModalRadius.medium) - Text("Large").tag(ModalRadius.large) - Text("Custom 30px").tag(ModalRadius.custom(30)) + ContainerRadiusPicker(selection: self.$model.cornerRadius) { + Text("Custom 30px").tag(ContainerRadius.custom(30)) } Picker("Overlay Style", selection: self.$model.overlayStyle) { Text("Blurred").tag(ModalOverlayStyle.blurred) diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/PreviewPickers.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/PreviewPickers.swift index fc20a585..896f743b 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/PreviewPickers.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/Helpers/PreviewPickers.swift @@ -75,7 +75,7 @@ struct ComponentOptionalColorPicker: View { // MARK: - CornerRadiusPicker -struct CornerRadiusPicker: View { +struct ComponentRadiusPicker: View { @Binding var selection: ComponentRadius @ViewBuilder var custom: () -> Custom @@ -91,6 +91,21 @@ struct CornerRadiusPicker: View { } } +struct ContainerRadiusPicker: View { + @Binding var selection: ContainerRadius + @ViewBuilder var custom: () -> Custom + + var body: some View { + Picker("Corner Radius", selection: self.$selection) { + Text("None").tag(ContainerRadius.none) + Text("Small").tag(ContainerRadius.small) + Text("Medium").tag(ContainerRadius.medium) + Text("Large").tag(ContainerRadius.large) + self.custom() + } + } +} + // MARK: - FontPickers struct BodyFontPicker: View { diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift index e04c2292..dc3e340e 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift @@ -19,7 +19,7 @@ struct ButtonPreview: View { Form { AnimationScalePicker(selection: self.$model.animationScale) ComponentOptionalColorPicker(selection: self.$model.color) - CornerRadiusPicker(selection: self.$model.cornerRadius) { + ComponentRadiusPicker(selection: self.$model.cornerRadius) { Text("Custom: 20px").tag(ComponentRadius.custom(20)) } ButtonFontPicker(selection: self.$model.font) diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/CardPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/CardPreview.swift new file mode 100644 index 00000000..d798e3b2 --- /dev/null +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/CardPreview.swift @@ -0,0 +1,67 @@ +import ComponentsKit +import SwiftUI +import UIKit + +struct CardPreview: View { + @State private var model = CardVM() + + var body: some View { + VStack { + PreviewWrapper(title: "UIKit") { + UKCard(model: self.model, content: cardContent) + .preview + } + Form { + Picker("Background Color", selection: self.$model.backgroundColor) { + Text("Default").tag(Optional.none) + Text("Secondary Background").tag(UniversalColor.secondaryBackground) + Text("Accent Background").tag(ComponentColor.accent.background) + Text("Success Background").tag(ComponentColor.success.background) + Text("Warning Background").tag(ComponentColor.warning.background) + Text("Danger Background").tag(ComponentColor.danger.background) + } + Picker("Content Paddings", selection: self.$model.contentPaddings) { + Text("12px").tag(Paddings(padding: 12)) + Text("16px").tag(Paddings(padding: 16)) + Text("20px").tag(Paddings(padding: 20)) + } + ContainerRadiusPicker(selection: self.$model.cornerRadius) { + Text("Custom 4px").tag(ContainerRadius.custom(4)) + } + Picker("Shadow", selection: self.$model.shadow) { + Text("None").tag(Shadow.none) + Text("Small").tag(Shadow.small) + Text("Medium").tag(Shadow.medium) + Text("Large").tag(Shadow.large) + Text("Custom").tag(Shadow.custom(20.0, .zero, ComponentColor.accent.background)) + } + } + } + } +} + +#Preview { + CardPreview() +} + +// MARK: - Helpers + +private func cardContent() -> UIView { + let titleLabel = UILabel() + titleLabel.text = "Card" + titleLabel.font = UniversalFont.mdHeadline.uiFont + titleLabel.textColor = UniversalColor.foreground.uiColor + titleLabel.numberOfLines = 0 + + let subtitleLabel = UILabel() + subtitleLabel.text = "Card is a container for text, images, and other content." + subtitleLabel.font = UniversalFont.mdBody.uiFont + subtitleLabel.textColor = UniversalColor.secondaryForeground.uiColor + subtitleLabel.numberOfLines = 0 + + let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel]) + stackView.axis = .vertical + stackView.spacing = 8 + + return stackView +} diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/CheckboxPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/CheckboxPreview.swift index 8e4bff6d..a656f4b2 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/CheckboxPreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/CheckboxPreview.swift @@ -31,7 +31,7 @@ struct CheckboxPreview: View { Text("Long").tag("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.") } ComponentColorPicker(selection: self.$model.color) - CornerRadiusPicker(selection: self.$model.cornerRadius) { + ComponentRadiusPicker(selection: self.$model.cornerRadius) { Text("Custom: 2px").tag(ComponentRadius.custom(2)) } BodyFontPicker(selection: self.$model.font) diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/InputFieldPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/InputFieldPreview.swift index 5adc97d2..3851467d 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/InputFieldPreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/InputFieldPreview.swift @@ -35,7 +35,7 @@ struct InputFieldPreview: View { AutocapitalizationPicker(selection: self.$model.autocapitalization) Toggle("Autocorrection Enabled", isOn: self.$model.isAutocorrectionEnabled) ComponentOptionalColorPicker(selection: self.$model.color) - CornerRadiusPicker(selection: self.$model.cornerRadius) { + ComponentRadiusPicker(selection: self.$model.cornerRadius) { Text("Custom: 20px").tag(ComponentRadius.custom(20)) } Toggle("Enabled", isOn: self.$model.isEnabled) diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/SegmentedControlPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/SegmentedControlPreview.swift index e144f116..6566bdeb 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/SegmentedControlPreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/SegmentedControlPreview.swift @@ -42,7 +42,7 @@ struct SegmentedControlPreview: View { } Form { ComponentOptionalColorPicker(selection: self.$model.color) - CornerRadiusPicker(selection: self.$model.cornerRadius) { + ComponentRadiusPicker(selection: self.$model.cornerRadius) { Text("Custom: 4px").tag(ComponentRadius.custom(4)) } Toggle("Enabled", isOn: self.$model.isEnabled) diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/TextInputPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/TextInputPreview.swift index 2dd55593..0c3673b6 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/TextInputPreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/TextInputPreview.swift @@ -35,7 +35,7 @@ struct TextInputPreviewPreview: View { AutocapitalizationPicker(selection: self.$model.autocapitalization) Toggle("Autocorrection Enabled", isOn: self.$model.isAutocorrectionEnabled) ComponentOptionalColorPicker(selection: self.$model.color) - CornerRadiusPicker(selection: self.$model.cornerRadius) { + ComponentRadiusPicker(selection: self.$model.cornerRadius) { Text("Custom: 20px").tag(ComponentRadius.custom(20)) } Toggle("Enabled", isOn: self.$model.isEnabled) diff --git a/Examples/DemosApp/DemosApp/Core/App.swift b/Examples/DemosApp/DemosApp/Core/App.swift index 714bcec6..de6bfa47 100644 --- a/Examples/DemosApp/DemosApp/Core/App.swift +++ b/Examples/DemosApp/DemosApp/Core/App.swift @@ -9,6 +9,9 @@ struct App: View { NavigationLinkWithTitle("Button") { ButtonPreview() } + NavigationLinkWithTitle("Card") { + CardPreview() + } NavigationLinkWithTitle("Checkbox") { CheckboxPreview() } diff --git a/Sources/ComponentsKit/Components/Card/CardVM.swift b/Sources/ComponentsKit/Components/Card/CardVM.swift new file mode 100644 index 00000000..9834cdae --- /dev/null +++ b/Sources/ComponentsKit/Components/Card/CardVM.swift @@ -0,0 +1,33 @@ +import Foundation + +/// A model that defines the appearance properties for a card component. +public struct CardVM: ComponentVM { + /// The background color of the card. + public var backgroundColor: UniversalColor? + + /// The padding applied to the card's content area. + /// + /// Defaults to a padding value of `16` for all sides. + public var contentPaddings: Paddings = .init(padding: 16) + + /// The corner radius of the card. + /// + /// Defaults to `.medium`. + public var cornerRadius: ContainerRadius = .medium + + /// The shadow of the card. + /// + /// Defaults to `.medium`. + public var shadow: Shadow = .medium + + /// Initializes a new instance of `CardVM` with default values. + public init() {} +} + +// MARK: - Helpers + +extension CardVM { + var preferredBackgroundColor: UniversalColor { + return self.backgroundColor ?? .background + } +} diff --git a/Sources/ComponentsKit/Components/Card/UKCard.swift b/Sources/ComponentsKit/Components/Card/UKCard.swift new file mode 100644 index 00000000..d20d8148 --- /dev/null +++ b/Sources/ComponentsKit/Components/Card/UKCard.swift @@ -0,0 +1,152 @@ +import AutoLayout +import UIKit + +/// A UIKit component that serves as a container for provided content. +/// +/// - Example: +/// ```swift +/// let banner = UKCard( +/// model: .init(), +/// content: { _ in +/// let label = UILabel() +/// label.text = "This is the content of the card." +/// label.numberOfLines = 0 +/// return label +/// } +/// ) +open class UKCard: UIView, UKComponent { + // MARK: - Typealiases + + /// A closure that returns the content view to be displayed inside the card. + public typealias Content = () -> UIView + + // MARK: - Subviews + + /// The primary content of the card, provided as a custom view. + public let content: UIView + /// The container view that holds the card's content. + public let contentView = UIView() + + // MARK: - Properties + + private var contentConstraints = LayoutConstraints() + + /// A model that defines the appearance properties. + public var model: CardVM { + didSet { + self.update(oldValue) + } + } + + // MARK: - Initialization + + /// Initializer. + /// + /// - Parameters: + /// - model: A model that defines the appearance properties. + /// - content: The content that is displayed in the card. + public init(model: CardVM, content: @escaping Content) { + self.model = model + self.content = content() + + super.init(frame: .zero) + + self.setup() + self.style() + self.layout() + } + + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setup + + /// Sets up the card's subviews. + open func setup() { + self.addSubview(self.contentView) + self.contentView.addSubview(self.content) + + if #available(iOS 17.0, *) { + self.registerForTraitChanges([UITraitUserInterfaceStyle.self]) { (view: Self, _: UITraitCollection) in + view.handleTraitChanges() + } + } + } + + // MARK: - Style + + /// Applies styling to the card's subviews. + open func style() { + Self.Style.mainView(self, model: self.model) + Self.Style.contentView(self.contentView, model: self.model) + } + + // MARK: - Layout + + /// Configures the layout. + open func layout() { + self.contentView.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) + } + } + + open override func layoutSubviews() { + super.layoutSubviews() + + self.layer.shadowPath = UIBezierPath(rect: self.bounds).cgPath + } + + /// Updates appearance when the model changes. + open func update(_ oldValue: CardVM) { + guard self.model != oldValue else { return } + + self.style() + + if self.model.contentPaddings != oldValue.contentPaddings { + self.contentConstraints.top?.constant = self.model.contentPaddings.top + self.contentConstraints.bottom?.constant = -self.model.contentPaddings.bottom + self.contentConstraints.leading?.constant = self.model.contentPaddings.leading + self.contentConstraints.trailing?.constant = -self.model.contentPaddings.trailing + + self.layoutIfNeeded() + } + } + + // MARK: - UIView Methods + + open override func traitCollectionDidChange( + _ previousTraitCollection: UITraitCollection? + ) { + super.traitCollectionDidChange(previousTraitCollection) + self.handleTraitChanges() + } + + // MARK: - Helpers + + @objc private func handleTraitChanges() { + Self.Style.mainView(self, model: self.model) + } +} + +extension UKCard { + fileprivate enum Style { + static func mainView(_ view: UIView, model: Model) { + view.backgroundColor = UniversalColor.background.uiColor + view.layer.cornerRadius = model.cornerRadius.value + view.layer.borderWidth = 1 + view.layer.borderColor = UniversalColor.divider.cgColor + view.shadow(model.shadow) + } + + static func contentView(_ view: UIView, model: Model) { + view.backgroundColor = model.preferredBackgroundColor.uiColor + view.layer.cornerRadius = model.cornerRadius.value + } + } +} diff --git a/Sources/ComponentsKit/Configuration/Layout.swift b/Sources/ComponentsKit/Configuration/Layout.swift index 1c0c238b..e3c54d31 100644 --- a/Sources/ComponentsKit/Configuration/Layout.swift +++ b/Sources/ComponentsKit/Configuration/Layout.swift @@ -85,6 +85,65 @@ extension ComponentsKitConfig { } } + // MARK: - Shadow + + /// A structure that defines the parameters for a shadow effect. + public struct ShadowParams { + /// The blur radius of the shadow. + /// + /// A larger radius results in a more diffuse shadow. + public var radius: CGFloat + + /// The offset of the shadow, defining its position relative to the component. + /// + /// - `width`: Moves the shadow horizontally. + /// - `height`: Moves the shadow vertically. + public var offset: CGSize + + /// The color of the shadow. + public var color: UniversalColor + + // MARK: - Initialization + + /// Initializes a new `ShadowParams` instance with the specified radius, offset, and color. + /// + /// - Parameters: + /// - radius: The blur radius of the shadow. + /// - offset: The offset of the shadow as a `CGSize`. + /// - color: The color of the shadow. + public init(radius: CGFloat, offset: CGSize, color: UniversalColor) { + self.radius = radius + self.offset = offset + self.color = color + } + } + + /// A structure that defines shadow presets for small, medium, and large shadows. + public struct Shadow { + /// The shadow parameters for a small shadow. + public var small: ShadowParams + + /// The shadow parameters for a medium shadow. + public var medium: ShadowParams + + /// The shadow parameters for a large shadow. + public var large: ShadowParams + + // MARK: - Initialization + + /// Initializes a new `Shadow` instance with the specified small, medium, and large shadow parameters. + /// + /// - Parameters: + /// - small: The parameters for a small shadow. + /// - medium: The parameters for a medium shadow. + /// - large: The parameters for a large shadow. + public init(small: ShadowParams, medium: ShadowParams, large: ShadowParams) { + self.small = small + self.medium = medium + self.large = large + } + } + // MARK: - Typography /// A structure representing a set of fonts for different component sizes. @@ -154,6 +213,34 @@ extension ComponentsKitConfig { large: 26.0 ) + /// The shadow configuration for components. + public var shadow: Shadow = .init( + small: .init( + radius: 10.0, + offset: .init(width: 0, height: 6), + color: .themed( + light: .rgba(r: 0, g: 0, b: 0, a: 0.1), + dark: .rgba(r: 255, g: 255, b: 255, a: 0.1) + ) + ), + medium: .init( + radius: 16.0, + offset: .init(width: 0, height: 10), + color: .themed( + light: .rgba(r: 0, g: 0, b: 0, a: 0.15), + dark: .rgba(r: 255, g: 255, b: 255, a: 0.15) + ) + ), + large: .init( + radius: 20.0, + offset: .init(width: 0, height: 12), + color: .themed( + light: .rgba(r: 0, g: 0, b: 0, a: 0.2), + dark: .rgba(r: 255, g: 255, b: 255, a: 0.2) + ) + ) + ) + /// The border width configuration for components. public var borderWidth: BorderWidth = .init( small: 1.0, diff --git a/Sources/ComponentsKit/Configuration/Palette.swift b/Sources/ComponentsKit/Configuration/Palette.swift index d247e19d..0d0bb5e1 100644 --- a/Sources/ComponentsKit/Configuration/Palette.swift +++ b/Sources/ComponentsKit/Configuration/Palette.swift @@ -151,6 +151,9 @@ extension UniversalColor { public static var white: Self { return .universal(.hex("#FFFFFF")) } + public static var clear: Self { + return .universal(.uiColor(.clear)) + } } // MARK: - UniversalColor + Palette Colors diff --git a/Sources/ComponentsKit/Modal/Models/Paddings.swift b/Sources/ComponentsKit/Helpers/Paddings.swift similarity index 100% rename from Sources/ComponentsKit/Modal/Models/Paddings.swift rename to Sources/ComponentsKit/Helpers/Paddings.swift diff --git a/Sources/ComponentsKit/Modal/Models/BottomModalVM.swift b/Sources/ComponentsKit/Modal/Models/BottomModalVM.swift index c2f21a7a..d4edd9dd 100644 --- a/Sources/ComponentsKit/Modal/Models/BottomModalVM.swift +++ b/Sources/ComponentsKit/Modal/Models/BottomModalVM.swift @@ -2,7 +2,7 @@ import Foundation /// A model that defines the appearance properties for a bottom modal component. public struct BottomModalVM: ModalVM { - /// The background color of the modal's content area. + /// The background color of the modal. public var backgroundColor: UniversalColor? /// A Boolean value indicating whether the modal should close when tapping on the overlay. @@ -21,7 +21,7 @@ public struct BottomModalVM: ModalVM { /// The corner radius of the modal. /// /// Defaults to `.medium`. - public var cornerRadius: ModalRadius = .medium + public var cornerRadius: ContainerRadius = .medium /// A Boolean value indicating whether the modal should hide when it is swiped down. /// diff --git a/Sources/ComponentsKit/Modal/Models/CenterModalVM.swift b/Sources/ComponentsKit/Modal/Models/CenterModalVM.swift index 301398be..2cb39cf9 100644 --- a/Sources/ComponentsKit/Modal/Models/CenterModalVM.swift +++ b/Sources/ComponentsKit/Modal/Models/CenterModalVM.swift @@ -2,7 +2,7 @@ import Foundation /// A model that defines the appearance properties for a center modal component. public struct CenterModalVM: ModalVM { - /// The background color of the modal's content area. + /// The background color of the modal. public var backgroundColor: UniversalColor? /// A Boolean value indicating whether the modal should close when tapping on the overlay. @@ -21,7 +21,7 @@ public struct CenterModalVM: ModalVM { /// The corner radius of the modal. /// /// Defaults to `.medium`. - public var cornerRadius: ModalRadius = .medium + public var cornerRadius: ContainerRadius = .medium /// The style of the overlay displayed behind the modal. /// diff --git a/Sources/ComponentsKit/Modal/Models/ModalVM.swift b/Sources/ComponentsKit/Modal/Models/ModalVM.swift index b6590738..96b1b9c9 100644 --- a/Sources/ComponentsKit/Modal/Models/ModalVM.swift +++ b/Sources/ComponentsKit/Modal/Models/ModalVM.swift @@ -2,7 +2,7 @@ import Foundation /// A model that defines generic appearance properties that can be in any modal component. public protocol ModalVM: ComponentVM { - /// The background color of the modal's content area. + /// The background color of the modal. var backgroundColor: UniversalColor? { get set } /// A Boolean value indicating whether the modal should close when tapping on the overlay. @@ -15,7 +15,7 @@ public protocol ModalVM: ComponentVM { var contentSpacing: CGFloat { get set } /// The corner radius of the modal. - var cornerRadius: ModalRadius { get set } + var cornerRadius: ContainerRadius { get set } /// The style of the overlay displayed behind the modal. var overlayStyle: ModalOverlayStyle { get set } diff --git a/Sources/ComponentsKit/Modal/UIKit/UKModalController.swift b/Sources/ComponentsKit/Modal/UIKit/UKModalController.swift index 50a7d03f..fb33b4eb 100644 --- a/Sources/ComponentsKit/Modal/UIKit/UKModalController.swift +++ b/Sources/ComponentsKit/Modal/UIKit/UKModalController.swift @@ -106,7 +106,7 @@ open class UKModalController: UIViewController { // MARK: - Style - /// Applies styling to the modal's components based on the model. + /// Applies styling to the modal's subviews. open func style() { Self.Style.overlay(self.overlay, model: self.model) Self.Style.container(self.container, model: self.model) @@ -116,7 +116,7 @@ open class UKModalController: UIViewController { // MARK: - Layout - /// Configures the layout of the modal's components. + /// Configures the layout of the modal's subviews. open func layout() { self.overlay.allEdges() self.content.allEdges() diff --git a/Sources/ComponentsKit/Shared/Colors/UniversalColor.swift b/Sources/ComponentsKit/Shared/Colors/UniversalColor.swift index 19aa5f39..7cbed9e8 100644 --- a/Sources/ComponentsKit/Shared/Colors/UniversalColor.swift +++ b/Sources/ComponentsKit/Shared/Colors/UniversalColor.swift @@ -162,4 +162,9 @@ public struct UniversalColor: Hashable { public var color: Color { return Color(self.uiColor) } + + /// Returns the `CGColor` representation of the color. + public var cgColor: CGColor { + return self.uiColor.cgColor + } } diff --git a/Sources/ComponentsKit/Modal/Models/ModalRadius.swift b/Sources/ComponentsKit/Shared/Types/ContainerRadius.swift similarity index 84% rename from Sources/ComponentsKit/Modal/Models/ModalRadius.swift rename to Sources/ComponentsKit/Shared/Types/ContainerRadius.swift index 1aba60f9..419b988a 100644 --- a/Sources/ComponentsKit/Modal/Models/ModalRadius.swift +++ b/Sources/ComponentsKit/Shared/Types/ContainerRadius.swift @@ -1,7 +1,7 @@ import Foundation -/// Defines the corner radius options for a modal's content area. -public enum ModalRadius: Hashable { +/// Defines the corner radius options for a container's content area. +public enum ContainerRadius: Hashable { /// No corner radius is applied, resulting in sharp edges. case none /// A small corner radius is applied. @@ -16,7 +16,7 @@ public enum ModalRadius: Hashable { case custom(CGFloat) } -extension ModalRadius { +extension ContainerRadius { var value: CGFloat { return switch self { case .none: CGFloat(0) diff --git a/Sources/ComponentsKit/Shared/Types/Shadow.swift b/Sources/ComponentsKit/Shared/Types/Shadow.swift new file mode 100644 index 00000000..904d15d0 --- /dev/null +++ b/Sources/ComponentsKit/Shared/Types/Shadow.swift @@ -0,0 +1,63 @@ +import UIKit + +/// Defines shadow options for components. +public enum Shadow: Hashable { + /// No shadow is applied. + case none + /// A small shadow. + case small + /// A medium shadow. + case medium + /// A large shadow. + case large + /// A custom shadow with specific parameters. + /// + /// - Parameters: + /// - radius: The blur radius of the shadow. + /// - offset: The offset of the shadow. + /// - color: The color of the shadow. + case custom(_ radius: CGFloat, _ offset: CGSize, _ color: UniversalColor) +} + +extension Shadow { + var radius: CGFloat { + return switch self { + case .none: CGFloat(0) + case .small: ComponentsKitConfig.shared.layout.shadow.small.radius + case .medium: ComponentsKitConfig.shared.layout.shadow.medium.radius + case .large: ComponentsKitConfig.shared.layout.shadow.large.radius + case .custom(let radius, _, _): radius + } + } + + var offset: CGSize { + return switch self { + case .none: .zero + case .small: ComponentsKitConfig.shared.layout.shadow.small.offset + case .medium: ComponentsKitConfig.shared.layout.shadow.medium.offset + case .large: ComponentsKitConfig.shared.layout.shadow.large.offset + case .custom(_, let offset, _): offset + } + } + + var color: UniversalColor { + return switch self { + case .none: .clear + case .small: ComponentsKitConfig.shared.layout.shadow.small.color + case .medium: ComponentsKitConfig.shared.layout.shadow.medium.color + case .large: ComponentsKitConfig.shared.layout.shadow.large.color + case .custom(_, _, let color): color + } + } +} + +// MARK: - UIKit + Shadow + +extension UIView { + func shadow(_ shadow: Shadow) { + self.layer.shadowRadius = shadow.radius + self.layer.shadowOffset = shadow.offset + self.layer.shadowColor = shadow.color.cgColor + self.layer.shadowOpacity = 1 + } +}