mirror of
https://github.com/bitwarden/ios.git
synced 2025-12-10 00:42:29 -06:00
208 lines
6.7 KiB
Swift
208 lines
6.7 KiB
Swift
import BitwardenResources
|
|
import SwiftUI
|
|
|
|
// MARK: - ActionCard
|
|
|
|
/// A view that displays a card representing an action that the user needs to take.
|
|
///
|
|
public struct ActionCard<LeadingContent: View>: View {
|
|
// MARK: Types
|
|
|
|
/// A data model containing the properties for a button within an action card.
|
|
///
|
|
public struct ButtonState {
|
|
// MARK: Properties
|
|
|
|
/// An action to perform when the button is tapped.
|
|
let action: () async -> Void
|
|
|
|
/// The title of the button.
|
|
let title: String
|
|
|
|
// MARK: Initialization
|
|
|
|
/// Initialize a `ButtonState`.
|
|
///
|
|
/// - Parameters:
|
|
/// - title: The title of the button.
|
|
/// - action: An action to perform when the button is tapped.
|
|
///
|
|
public init(title: String, action: @escaping () async -> Void) {
|
|
self.action = action
|
|
self.title = title
|
|
}
|
|
}
|
|
|
|
// MARK: Properties
|
|
|
|
/// State that describes the action button.
|
|
let actionButtonState: ButtonState?
|
|
|
|
/// State that describes the dismiss button.
|
|
let dismissButtonState: ButtonState?
|
|
|
|
/// Optional content that is displayed at the leading edge of the title and message.
|
|
let leadingContent: LeadingContent?
|
|
|
|
/// The message to display in the card, below the title.
|
|
let message: String?
|
|
|
|
/// State that describes the secondary button.
|
|
let secondaryButtonState: ButtonState?
|
|
|
|
/// The title of the card.
|
|
let title: String
|
|
|
|
// MARK: View
|
|
|
|
public var body: some View {
|
|
VStack(alignment: .leading, spacing: 16) {
|
|
HStack(alignment: .top, spacing: 8) {
|
|
if let leadingContent {
|
|
leadingContent
|
|
}
|
|
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text(title)
|
|
.styleGuide(.title2, weight: .semibold, includeLinePadding: false, includeLineSpacing: false)
|
|
|
|
if let message {
|
|
Text(message)
|
|
.styleGuide(.callout)
|
|
}
|
|
}
|
|
|
|
Spacer(minLength: 0)
|
|
|
|
if let dismissButtonState {
|
|
AsyncButton(action: dismissButtonState.action) {
|
|
Image(asset: SharedAsset.Icons.close16, label: Text(dismissButtonState.title))
|
|
.imageStyle(.accessoryIcon16(color: SharedAsset.Colors.iconPrimary.swiftUIColor))
|
|
.padding(16) // Add padding to increase tappable area...
|
|
}
|
|
.padding(-16) // ...but remove it to not affect layout.
|
|
}
|
|
}
|
|
|
|
if actionButtonState != nil || secondaryButtonState != nil {
|
|
VStack(spacing: 4) {
|
|
if let actionButtonState {
|
|
AsyncButton(actionButtonState.title, action: actionButtonState.action)
|
|
.buttonStyle(.primary(size: .medium))
|
|
}
|
|
|
|
if let secondaryButtonState {
|
|
AsyncButton(secondaryButtonState.title, action: secondaryButtonState.action)
|
|
.buttonStyle(.bitwardenBorderless(size: .medium))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.foregroundStyle(SharedAsset.Colors.textPrimary.swiftUIColor)
|
|
.padding(16)
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 12)
|
|
.strokeBorder(SharedAsset.Colors.strokeBorder.swiftUIColor)
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 12)
|
|
.fill(SharedAsset.Colors.backgroundTertiary.swiftUIColor),
|
|
),
|
|
)
|
|
}
|
|
|
|
// MARK: Initialization
|
|
|
|
/// Initialize an `ActionCard` with leading content.
|
|
///
|
|
/// - Parameters:
|
|
/// - title: The title of the card.
|
|
/// - message: The message to display in the card.
|
|
/// - actionButtonState: State that describes the action button.
|
|
/// - dismissButtonState: State that describes the dismiss button.
|
|
/// - secondaryButtonState: State that describes the secondary button.
|
|
/// - leadingContent: Content that is displayed at the leading edge of the title and message.
|
|
///
|
|
public init(
|
|
title: String,
|
|
message: String? = nil,
|
|
actionButtonState: ButtonState? = nil,
|
|
dismissButtonState: ButtonState? = nil,
|
|
secondaryButtonState: ButtonState? = nil,
|
|
@ViewBuilder leadingContent: () -> LeadingContent,
|
|
) {
|
|
self.actionButtonState = actionButtonState
|
|
self.dismissButtonState = dismissButtonState
|
|
self.leadingContent = leadingContent()
|
|
self.message = message
|
|
self.secondaryButtonState = secondaryButtonState
|
|
self.title = title
|
|
}
|
|
|
|
/// Initialize an `ActionCard` with no leading content.
|
|
///
|
|
/// - Parameters:
|
|
/// - title: The title of the card.
|
|
/// - message: The message to display in the card.
|
|
/// - actionButtonState: State that describes the action button.
|
|
/// - dismissButtonState: State that describes the dismiss button.
|
|
/// - secondaryButtonState: State that describes the secondary button.
|
|
///
|
|
public init(
|
|
title: String,
|
|
message: String? = nil,
|
|
actionButtonState: ButtonState? = nil,
|
|
dismissButtonState: ButtonState? = nil,
|
|
secondaryButtonState: ButtonState? = nil,
|
|
) where LeadingContent == EmptyView {
|
|
self.actionButtonState = actionButtonState
|
|
self.dismissButtonState = dismissButtonState
|
|
leadingContent = nil
|
|
self.message = message
|
|
self.secondaryButtonState = secondaryButtonState
|
|
self.title = title
|
|
}
|
|
}
|
|
|
|
// MARK: - Previews
|
|
|
|
#if DEBUG
|
|
#Preview {
|
|
VStack {
|
|
ActionCard(
|
|
title: "Title",
|
|
message: "Message",
|
|
)
|
|
|
|
ActionCard(
|
|
title: "Title",
|
|
message: "Message",
|
|
actionButtonState: ActionCard.ButtonState(title: "Tap me!") {},
|
|
dismissButtonState: ActionCard.ButtonState(title: "Dismiss") {},
|
|
)
|
|
|
|
ActionCard(
|
|
title: "Title",
|
|
message: "Message",
|
|
actionButtonState: ActionCard.ButtonState(title: "Tap me!") {},
|
|
dismissButtonState: ActionCard.ButtonState(title: "Dismiss") {},
|
|
secondaryButtonState: ActionCard.ButtonState(title: "Secondary button") {},
|
|
)
|
|
|
|
ActionCard(
|
|
title: "Title",
|
|
message: "Message",
|
|
) {
|
|
SharedAsset.Icons.warning24.swiftUIImage
|
|
}
|
|
|
|
ActionCard(
|
|
title: "Title",
|
|
message: "Message",
|
|
) {
|
|
BitwardenBadge(badgeValue: "1")
|
|
}
|
|
}
|
|
.scrollView()
|
|
}
|
|
#endif
|