Skip to content

Improve card #87

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,24 @@ struct CardPreview: View {
SUCard(model: self.model, content: self.suCardContent)
}
Form {
AnimationScalePicker(selection: self.$model.animationScale)
Picker("Background Color", selection: self.$model.backgroundColor) {
Text("Default").tag(Optional<UniversalColor>.none)
Text("Background").tag(UniversalColor.background)
Text("Secondary Background").tag(UniversalColor.secondaryBackground)
Text("Accent Background").tag(UniversalColor.accentBackground)
Text("Success Background").tag(UniversalColor.successBackground)
Text("Warning Background").tag(UniversalColor.warningBackground)
Text("Danger Background").tag(UniversalColor.dangerBackground)
}
Picker("Border Color", selection: self.$model.borderColor) {
Text("Divider").tag(UniversalColor.divider)
Text("Primary").tag(UniversalColor.primary)
Text("Accent").tag(UniversalColor.accent)
Text("Success").tag(UniversalColor.success)
Text("Warning").tag(UniversalColor.warning)
Text("Danger").tag(UniversalColor.danger)
Text("Custom").tag(UniversalColor.universal(.uiColor(.systemPurple)))
}
BorderWidthPicker(selection: self.$model.borderWidth)
Picker("Content Paddings", selection: self.$model.contentPaddings) {
Text("12px").tag(Paddings(padding: 12))
Expand All @@ -39,6 +49,7 @@ struct CardPreview: View {
Text("Large").tag(Shadow.large)
Text("Custom").tag(Shadow.custom(20.0, .zero, UniversalColor.accentBackground))
}
Toggle("Tappable", isOn: self.$model.isTappable)
}
}
}
Expand Down
23 changes: 14 additions & 9 deletions Sources/ComponentsKit/Components/Card/Models/CardVM.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@ import Foundation

/// A model that defines the appearance properties for a card component.
public struct CardVM: ComponentVM {
/// The scaling factor for the card's tap animation, with a value between 0 and 1.
///
/// Defaults to `.medium`.
public var animationScale: AnimationScale = .medium

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

/// The border color of the card.
public var borderColor: UniversalColor = .divider

/// The border thickness of the card.
///
Expand All @@ -20,6 +28,11 @@ public struct CardVM: ComponentVM {
/// Defaults to `.medium`.
public var cornerRadius: ContainerRadius = .medium

/// A Boolean value indicating whether the card should allow to be tapped.
///
/// Defaults to `true`.
public var isTappable: Bool = false

/// The shadow of the card.
///
/// Defaults to `.medium`.
Expand All @@ -28,11 +41,3 @@ public struct CardVM: ComponentVM {
/// Initializes a new instance of `CardVM` with default values.
public init() {}
}

// MARK: - Helpers

extension CardVM {
var preferredBackgroundColor: UniversalColor {
return self.backgroundColor ?? .background
}
}
37 changes: 34 additions & 3 deletions Sources/ComponentsKit/Components/Card/SUCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@ public struct SUCard<Content: View>: View {

/// A model that defines the appearance properties.
public let model: CardVM
/// A closure that is triggered when the card is tapped.
public var onTap: () -> Void

/// A Boolean value indicating whether the card is pressed.
@State public var isPressed: Bool = false

@ViewBuilder private let content: () -> Content
@State private var contentSize: CGSize = .zero

// MARK: - Initialization

Expand All @@ -28,23 +34,48 @@ public struct SUCard<Content: View>: View {
/// - content: The content that is displayed in the card.
public init(
model: CardVM = .init(),
content: @escaping () -> Content
content: @escaping () -> Content,
onTap: @escaping () -> Void = {}
) {
self.model = model
self.content = content
self.onTap = onTap
}

// MARK: - Body

public var body: some View {
self.content()
.padding(self.model.contentPaddings.edgeInsets)
.background(self.model.preferredBackgroundColor.color)
.background(self.model.backgroundColor.color)
.cornerRadius(self.model.cornerRadius.value)
.overlay(
RoundedRectangle(cornerRadius: self.model.cornerRadius.value)
.stroke(UniversalColor.divider.color, lineWidth: self.model.borderWidth.value)
.stroke(
self.model.borderColor.color,
lineWidth: self.model.borderWidth.value
)
)
.shadow(self.model.shadow)
.observeSize { self.contentSize = $0 }
.simultaneousGesture(DragGesture(minimumDistance: 0.0)
.onChanged { _ in
guard self.model.isTappable else { return }
self.isPressed = true
}
.onEnded { value in
guard self.model.isTappable else { return }

defer { self.isPressed = false }

if CGRect(origin: .zero, size: self.contentSize).contains(value.location) {
self.onTap()
}
}
)
.scaleEffect(
self.isPressed ? self.model.animationScale.value : 1,
anchor: .center
)
}
}
68 changes: 63 additions & 5 deletions Sources/ComponentsKit/Components/Card/UKCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,22 @@ open class UKCard<Content: UIView>: UIView, UKComponent {
/// The primary content of the card, provided as a custom view.
public let content: Content

// MARK: - Properties
// MARK: - Public Properties

private var contentConstraints = LayoutConstraints()
/// A closure that is triggered when the card is tapped.
public var onTap: () -> Void

/// A Boolean value indicating whether the button is pressed.
public private(set) var isPressed: Bool = false {
didSet {
self.transform = self.isPressed
? .init(
scaleX: self.model.animationScale.value,
y: self.model.animationScale.value
)
: .identity
}
}

/// A model that defines the appearance properties.
public var model: CardVM {
Expand All @@ -32,6 +45,10 @@ open class UKCard<Content: UIView>: UIView, UKComponent {
}
}

// MARK: - Private Properties

private var contentConstraints = LayoutConstraints()

// MARK: - Initialization

/// Initializer.
Expand All @@ -41,10 +58,12 @@ open class UKCard<Content: UIView>: UIView, UKComponent {
/// - content: The content that is displayed in the card.
public init(
model: CardVM = .init(),
content: @escaping () -> Content
content: @escaping () -> Content,
onTap: @escaping () -> Void = {}
) {
self.model = model
self.content = content()
self.onTap = onTap

super.init(frame: .zero)

Expand Down Expand Up @@ -95,6 +114,8 @@ open class UKCard<Content: UIView>: UIView, UKComponent {
self.layer.shadowPath = UIBezierPath(rect: self.bounds).cgPath
}

// MARK: - Update

/// Updates appearance when the model changes.
open func update(_ oldValue: CardVM) {
guard self.model != oldValue else { return }
Expand All @@ -113,6 +134,43 @@ open class UKCard<Content: UIView>: UIView, UKComponent {

// MARK: - UIView Methods

open override func touchesBegan(
_ touches: Set<UITouch>,
with event: UIEvent?
) {
super.touchesBegan(touches, with: event)

guard self.model.isTappable else { return }

self.isPressed = true
}

open override func touchesEnded(
_ touches: Set<UITouch>,
with event: UIEvent?
) {
super.touchesEnded(touches, with: event)

guard self.model.isTappable else { return }

defer { self.isPressed = false }

if self.model.isTappable,
let location = touches.first?.location(in: self),
self.bounds.contains(location) {
self.onTap()
}
}

open override func touchesCancelled(
_ touches: Set<UITouch>,
with event: UIEvent?
) {
super.touchesCancelled(touches, with: event)

self.isPressed = false
}

open override func traitCollectionDidChange(
_ previousTraitCollection: UITraitCollection?
) {
Expand All @@ -130,10 +188,10 @@ open class UKCard<Content: UIView>: UIView, UKComponent {
extension UKCard {
fileprivate enum Style {
static func mainView(_ view: UIView, model: Model) {
view.backgroundColor = model.preferredBackgroundColor.uiColor
view.backgroundColor = model.backgroundColor.uiColor
view.layer.cornerRadius = model.cornerRadius.value
view.layer.borderWidth = model.borderWidth.value
view.layer.borderColor = UniversalColor.divider.cgColor
view.layer.borderColor = model.borderColor.cgColor
view.shadow(model.shadow)
}
}
Expand Down