mirror of
https://github.com/bitwarden/ios.git
synced 2025-12-10 00:42:29 -06:00
[PM-27246] Update Authenticator to use ActionCard (#2123)
This commit is contained in:
parent
0bebf577b4
commit
0e2991802d
@ -1,27 +0,0 @@
|
||||
// swiftlint:disable:this file_name
|
||||
import BitwardenResources
|
||||
import SnapshotTesting
|
||||
import XCTest
|
||||
|
||||
@testable import AuthenticatorShared
|
||||
|
||||
// MARK: - ItemListCardViewTests
|
||||
|
||||
class ItemListCardViewTests: BitwardenTestCase {
|
||||
// MARK: Tests
|
||||
|
||||
/// Test a snapshot of the ItemListView previews.
|
||||
func disabletest_snapshot_ItemListCardView_previews() {
|
||||
for preview in ItemListCardView_Previews._allPreviews {
|
||||
let name = preview.displayName ?? "Unknown"
|
||||
assertSnapshots(
|
||||
of: preview.content,
|
||||
as: [
|
||||
"\(name)-portrait": .defaultPortrait,
|
||||
"\(name)-portraitDark": .defaultPortraitDark,
|
||||
"\(name)-portraitAX5": .tallPortraitAX5(heightMultiple: 3),
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
// swiftlint:disable:this file_name
|
||||
import BitwardenResources
|
||||
import ViewInspector
|
||||
import XCTest
|
||||
|
||||
@testable import AuthenticatorShared
|
||||
|
||||
// MARK: - ItemListCardViewTests
|
||||
|
||||
class ItemListCardViewTests: BitwardenTestCase {
|
||||
// MARK: Tests
|
||||
|
||||
/// Test the actions are properly wired up in the ItemListCardView.
|
||||
func test_ItemListCardView_actions() throws {
|
||||
let expectationAction = expectation(description: "action Tapped")
|
||||
let expectationClose = expectation(description: "close Tapped")
|
||||
let subject = ItemListCardView(
|
||||
bodyText: Localizations
|
||||
.allowAuthenticatorAppSyncingInSettingsToViewAllYourVerificationCodesHere,
|
||||
buttonText: Localizations.takeMeToTheAppSettings,
|
||||
leftImage: {},
|
||||
titleText: Localizations.syncWithTheBitwardenApp,
|
||||
actionTapped: {
|
||||
expectationAction.fulfill()
|
||||
},
|
||||
closeTapped: {
|
||||
expectationClose.fulfill()
|
||||
},
|
||||
)
|
||||
|
||||
try subject.inspect().find(buttonWithAccessibilityLabel: Localizations.close).tap()
|
||||
wait(for: [expectationClose])
|
||||
|
||||
try subject.inspect().find(button: Localizations.takeMeToTheAppSettings).tap()
|
||||
wait(for: [expectationAction])
|
||||
}
|
||||
}
|
||||
@ -1,136 +0,0 @@
|
||||
import BitwardenResources
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - ItemListCardView
|
||||
|
||||
/// An item list card view,
|
||||
///
|
||||
struct ItemListCardView<ImageContent: View>: View {
|
||||
// MARK: Properties
|
||||
|
||||
/// The body text to display in the card.
|
||||
var bodyText: String
|
||||
|
||||
/// The button text to display in the card.
|
||||
var buttonText: String
|
||||
|
||||
/// The image to display in the card.
|
||||
@ViewBuilder let leftImage: ImageContent
|
||||
|
||||
/// The button text for the secondary button in the card.
|
||||
var secondaryButtonText: String?
|
||||
|
||||
/// The title text to display in the card.
|
||||
var titleText: String
|
||||
|
||||
// MARK: Closures
|
||||
|
||||
/// The callback action to perform.
|
||||
var actionTapped: () -> Void
|
||||
|
||||
/// The close callback to perform.
|
||||
var closeTapped: () -> Void
|
||||
|
||||
/// The action to perform when the secondary button is tapped.
|
||||
var secondaryActionTapped: (() -> Void)?
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
HStack(alignment: .top, spacing: 16) {
|
||||
leftImage
|
||||
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Group {
|
||||
Text(titleText)
|
||||
.styleGuide(.headline)
|
||||
.foregroundColor(Asset.Colors.textPrimary.swiftUIColor)
|
||||
|
||||
Text(bodyText)
|
||||
.styleGuide(.subheadline)
|
||||
.foregroundColor(Asset.Colors.textSecondary.swiftUIColor)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
Button {
|
||||
closeTapped()
|
||||
} label: {
|
||||
Image(decorative: SharedAsset.Icons.close16)
|
||||
.padding(16) // Add padding to increase tappable area...
|
||||
}
|
||||
.padding(-16) // ...but remove it to not affect layout.
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.accessibilityLabel(Localizations.close)
|
||||
}
|
||||
|
||||
VStack(spacing: 0) {
|
||||
Button {
|
||||
actionTapped()
|
||||
} label: {
|
||||
Text(buttonText)
|
||||
}
|
||||
.buttonStyle(.primary())
|
||||
|
||||
if let secondaryButtonText, let secondaryActionTapped {
|
||||
Button {
|
||||
secondaryActionTapped()
|
||||
} label: {
|
||||
Text(secondaryButtonText)
|
||||
}
|
||||
.buttonStyle(.bitwardenBorderless)
|
||||
.padding(.bottom, -8) // Remove extra padding below the borderless button.
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
.background {
|
||||
Asset.Colors.backgroundPrimary.swiftUIColor
|
||||
.clipShape(.rect(cornerRadius: 16))
|
||||
.shadow(color: .black.opacity(0.45), radius: 2, x: 0, y: 1)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Previews
|
||||
|
||||
#if DEBUG
|
||||
struct ItemListCardView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ScrollView {
|
||||
VStack {
|
||||
ItemListCardView(
|
||||
bodyText: Localizations
|
||||
.allowAuthenticatorAppSyncingInSettingsToViewAllYourVerificationCodesHere,
|
||||
buttonText: Localizations.takeMeToTheAppSettings,
|
||||
leftImage: {
|
||||
Image(decorative: SharedAsset.Icons.arrowSync24)
|
||||
.foregroundColor(Asset.Colors.primaryBitwardenLight.swiftUIColor)
|
||||
.frame(width: 24, height: 24)
|
||||
},
|
||||
titleText: Localizations.syncWithTheBitwardenApp,
|
||||
actionTapped: {},
|
||||
closeTapped: {},
|
||||
)
|
||||
|
||||
ItemListCardView(
|
||||
bodyText: Localizations
|
||||
.allowAuthenticatorAppSyncingInSettingsToViewAllYourVerificationCodesHere,
|
||||
buttonText: Localizations.takeMeToTheAppSettings,
|
||||
leftImage: {
|
||||
Image(decorative: SharedAsset.Icons.arrowSync24)
|
||||
.foregroundColor(Asset.Colors.primaryBitwardenLight.swiftUIColor)
|
||||
.frame(width: 24, height: 24)
|
||||
},
|
||||
secondaryButtonText: Localizations.learnMore,
|
||||
titleText: Localizations.syncWithTheBitwardenApp,
|
||||
actionTapped: {},
|
||||
closeTapped: {},
|
||||
secondaryActionTapped: {},
|
||||
)
|
||||
}
|
||||
.padding(16)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -43,7 +43,7 @@ class ItemListViewTests: BitwardenTestCase {
|
||||
|
||||
/// Test the close taps trigger the associated effect.
|
||||
@MainActor
|
||||
func test_itemListCardView_close_download() throws {
|
||||
func test_actionCard_close_download() async throws {
|
||||
let state = ItemListState(
|
||||
itemListCardState: .passwordManagerDownload,
|
||||
loadingState: .data([ItemListSection.fixture()]),
|
||||
@ -54,16 +54,15 @@ class ItemListViewTests: BitwardenTestCase {
|
||||
timeProvider: timeProvider,
|
||||
)
|
||||
|
||||
try subject.inspect().find(buttonWithAccessibilityLabel: Localizations.close).tap()
|
||||
|
||||
waitFor(!processor.effects.isEmpty)
|
||||
let actionCard = try subject.inspect().find(actionCard: Localizations.downloadTheBitwardenApp)
|
||||
try await actionCard.find(asyncButton: Localizations.close).tap()
|
||||
|
||||
XCTAssertEqual(processor.effects.last, .closeCard(.passwordManagerDownload))
|
||||
}
|
||||
|
||||
/// Test the close taps trigger the associated effect.
|
||||
@MainActor
|
||||
func test_itemListCardView_close_sync() throws {
|
||||
func test_actionCard_close_sync() async throws {
|
||||
let state = ItemListState(
|
||||
itemListCardState: .passwordManagerSync,
|
||||
loadingState: .data([]),
|
||||
@ -74,9 +73,8 @@ class ItemListViewTests: BitwardenTestCase {
|
||||
timeProvider: timeProvider,
|
||||
)
|
||||
|
||||
try subject.inspect().find(buttonWithAccessibilityLabel: Localizations.close).tap()
|
||||
|
||||
waitFor(!processor.effects.isEmpty)
|
||||
let actionCard = try subject.inspect().find(actionCard: Localizations.syncWithTheBitwardenApp)
|
||||
try await actionCard.find(asyncButton: Localizations.close).tap()
|
||||
|
||||
XCTAssertEqual(processor.effects.last, .closeCard(.passwordManagerSync))
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ import SwiftUI
|
||||
// MARK: - SearchableItemListView
|
||||
|
||||
/// A view that displays the items in a single vault group.
|
||||
private struct SearchableItemListView: View { // swiftlint:disable:this type_body_length
|
||||
private struct SearchableItemListView: View {
|
||||
// MARK: Properties
|
||||
|
||||
/// A flag indicating if the search bar is focused.
|
||||
@ -131,52 +131,42 @@ private struct SearchableItemListView: View { // swiftlint:disable:this type_bod
|
||||
|
||||
/// The Password Manager download card definition.
|
||||
private var itemListCardPasswordManagerInstall: some View {
|
||||
ItemListCardView(
|
||||
bodyText: Localizations.storeAllOfYourLoginsAndSyncVerificationCodesDirectlyWithTheAuthenticatorApp,
|
||||
buttonText: Localizations.downloadTheBitwardenApp,
|
||||
leftImage: {
|
||||
Image(decorative: SharedAsset.Icons.shield24)
|
||||
.foregroundColor(Asset.Colors.primaryBitwardenLight.swiftUIColor)
|
||||
.frame(width: 24, height: 24)
|
||||
},
|
||||
titleText: Localizations.downloadTheBitwardenApp,
|
||||
actionTapped: {
|
||||
ActionCard(
|
||||
title: Localizations.downloadTheBitwardenApp,
|
||||
message: Localizations.storeAllOfYourLoginsAndSyncVerificationCodesDirectlyWithTheAuthenticatorApp,
|
||||
actionButtonState: ActionCard.ButtonState(title: Localizations.downloadTheBitwardenApp) {
|
||||
openURL(ExternalLinksConstants.passwordManagerLink)
|
||||
},
|
||||
closeTapped: {
|
||||
Task {
|
||||
dismissButtonState: ActionCard.ButtonState(title: Localizations.close) {
|
||||
await store.perform(.closeCard(.passwordManagerDownload))
|
||||
}
|
||||
},
|
||||
)
|
||||
) {
|
||||
Image(decorative: SharedAsset.Icons.shield24)
|
||||
.foregroundColor(SharedAsset.Colors.iconSecondary.swiftUIColor)
|
||||
.frame(width: 24, height: 24)
|
||||
}
|
||||
.padding(.top, 16)
|
||||
}
|
||||
|
||||
/// The Password Manager sync card definition.
|
||||
private var itemListCardSync: some View {
|
||||
ItemListCardView(
|
||||
bodyText: Localizations
|
||||
.allowAuthenticatorAppSyncingInSettingsToViewAllYourVerificationCodesHere,
|
||||
buttonText: Localizations.takeMeToTheAppSettings,
|
||||
leftImage: {
|
||||
Image(decorative: SharedAsset.Icons.arrowSync24)
|
||||
.foregroundColor(Asset.Colors.primaryBitwardenLight.swiftUIColor)
|
||||
.frame(width: 24, height: 24)
|
||||
},
|
||||
secondaryButtonText: Localizations.learnMore,
|
||||
titleText: Localizations.syncWithTheBitwardenApp,
|
||||
actionTapped: {
|
||||
ActionCard(
|
||||
title: Localizations.syncWithTheBitwardenApp,
|
||||
message: Localizations.allowAuthenticatorAppSyncingInSettingsToViewAllYourVerificationCodesHere,
|
||||
actionButtonState: ActionCard.ButtonState(title: Localizations.takeMeToTheAppSettings) {
|
||||
openURL(ExternalLinksConstants.passwordManagerSettings)
|
||||
},
|
||||
closeTapped: {
|
||||
Task {
|
||||
dismissButtonState: ActionCard.ButtonState(title: Localizations.close) {
|
||||
await store.perform(.closeCard(.passwordManagerSync))
|
||||
}
|
||||
},
|
||||
secondaryActionTapped: {
|
||||
secondaryButtonState: ActionCard.ButtonState(title: Localizations.learnMore) {
|
||||
openURL(ExternalLinksConstants.totpSyncHelp)
|
||||
},
|
||||
)
|
||||
) {
|
||||
Image(decorative: SharedAsset.Icons.arrowSync24)
|
||||
.foregroundColor(SharedAsset.Colors.iconSecondary.swiftUIColor)
|
||||
.frame(width: 24, height: 24)
|
||||
}
|
||||
.padding(.top, 16)
|
||||
}
|
||||
|
||||
@ -722,6 +712,36 @@ struct ItemListView_Previews: PreviewProvider { // swiftlint:disable:this type_b
|
||||
timeProvider: PreviewTimeProvider(),
|
||||
)
|
||||
}.previewDisplayName("SharedItems")
|
||||
|
||||
NavigationView {
|
||||
ItemListView(
|
||||
store: Store(
|
||||
processor: StateProcessor(
|
||||
state: ItemListState(
|
||||
itemListCardState: .passwordManagerDownload,
|
||||
loadingState: .data([]),
|
||||
),
|
||||
),
|
||||
),
|
||||
timeProvider: PreviewTimeProvider(),
|
||||
)
|
||||
}
|
||||
.previewDisplayName("Password Manager Download Card")
|
||||
|
||||
NavigationView {
|
||||
ItemListView(
|
||||
store: Store(
|
||||
processor: StateProcessor(
|
||||
state: ItemListState(
|
||||
itemListCardState: .passwordManagerSync,
|
||||
loadingState: .data([]),
|
||||
),
|
||||
),
|
||||
),
|
||||
timeProvider: PreviewTimeProvider(),
|
||||
)
|
||||
}
|
||||
.previewDisplayName("Password Manager Sync Card")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 167 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 584 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 160 KiB |
@ -18,12 +18,22 @@ public struct BitwardenBorderlessButtonStyle: ButtonStyle {
|
||||
: SharedAsset.Colors.buttonOutlinedDisabledForeground.swiftUIColor
|
||||
}
|
||||
|
||||
/// If this button should fill to take up as much width as possible.
|
||||
var shouldFillWidth = false
|
||||
|
||||
/// The size of the button.
|
||||
var size: ButtonStyleSize?
|
||||
|
||||
// MARK: ButtonStyle
|
||||
|
||||
public func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.foregroundStyle(foregroundColor)
|
||||
.styleGuide(.subheadlineSemibold)
|
||||
.styleGuide(size?.fontStyle ?? .subheadlineSemibold)
|
||||
.padding(.vertical, size?.verticalPadding ?? 0)
|
||||
.padding(.horizontal, size?.horizontalPadding ?? 0)
|
||||
.frame(maxWidth: shouldFillWidth ? .infinity : nil, minHeight: size?.minimumHeight ?? nil)
|
||||
.contentShape(Capsule())
|
||||
.opacity(configuration.isPressed ? 0.5 : 1)
|
||||
}
|
||||
}
|
||||
@ -33,15 +43,31 @@ public struct BitwardenBorderlessButtonStyle: ButtonStyle {
|
||||
public extension ButtonStyle where Self == BitwardenBorderlessButtonStyle {
|
||||
/// The style for a borderless button in this application.
|
||||
///
|
||||
/// This style does not add any padding to the button. Padding should be applied by the caller.
|
||||
///
|
||||
static var bitwardenBorderless: BitwardenBorderlessButtonStyle {
|
||||
BitwardenBorderlessButtonStyle()
|
||||
}
|
||||
|
||||
/// The style for a borderless button in this application with padding and font size based on
|
||||
/// the button size.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - shouldFillWidth: A flag indicating if this button should fill all available space.
|
||||
/// - size: The size of the button, which determines the padding and font size applied.
|
||||
///
|
||||
static func bitwardenBorderless(
|
||||
shouldFillWidth: Bool = true,
|
||||
size: ButtonStyleSize,
|
||||
) -> BitwardenBorderlessButtonStyle {
|
||||
BitwardenBorderlessButtonStyle(shouldFillWidth: shouldFillWidth, size: size)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Previews
|
||||
|
||||
#if DEBUG
|
||||
#Preview() {
|
||||
#Preview("States") {
|
||||
VStack {
|
||||
Button("Bitwarden") {}
|
||||
|
||||
@ -49,6 +75,20 @@ public extension ButtonStyle where Self == BitwardenBorderlessButtonStyle {
|
||||
.disabled(true)
|
||||
}
|
||||
.buttonStyle(.bitwardenBorderless)
|
||||
.padding(.vertical, 14)
|
||||
.padding()
|
||||
}
|
||||
|
||||
#Preview("Sizes") {
|
||||
VStack {
|
||||
Button("Small") {}
|
||||
.buttonStyle(.bitwardenBorderless(size: .small))
|
||||
|
||||
Button("Medium") {}
|
||||
.buttonStyle(.bitwardenBorderless(size: .medium))
|
||||
|
||||
Button("Large") {}
|
||||
.buttonStyle(.bitwardenBorderless(size: .large))
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
// swiftlint:disable:this file_name
|
||||
import SwiftUI
|
||||
import ViewInspectorTestHelpers
|
||||
import XCTest
|
||||
|
||||
@testable import BitwardenShared
|
||||
@testable import BitwardenKit
|
||||
|
||||
final class ActionCardTests: BitwardenTestCase {
|
||||
// MARK: Tests
|
||||
@ -38,4 +39,22 @@ final class ActionCardTests: BitwardenTestCase {
|
||||
|
||||
XCTAssertTrue(dismissButtonTapped)
|
||||
}
|
||||
|
||||
/// Tapping the secondary button should call the secondary button state's action closure.
|
||||
@MainActor
|
||||
func test_secondaryButton_tap() async throws {
|
||||
var secondaryButtonTapped = false
|
||||
let subject = ActionCard(
|
||||
title: "Title",
|
||||
message: "Message",
|
||||
secondaryButtonState: ActionCard.ButtonState(title: "Secondary") {
|
||||
secondaryButtonTapped = true
|
||||
},
|
||||
)
|
||||
|
||||
let button = try subject.inspect().find(asyncButton: "Secondary")
|
||||
try await button.tap()
|
||||
|
||||
XCTAssertTrue(secondaryButtonTapped)
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import SwiftUI
|
||||
|
||||
@ -6,12 +5,12 @@ import SwiftUI
|
||||
|
||||
/// A view that displays a card representing an action that the user needs to take.
|
||||
///
|
||||
struct ActionCard<LeadingContent: View>: View {
|
||||
public struct ActionCard<LeadingContent: View>: View {
|
||||
// MARK: Types
|
||||
|
||||
/// A data model containing the properties for a button within an action card.
|
||||
///
|
||||
struct ButtonState {
|
||||
public struct ButtonState {
|
||||
// MARK: Properties
|
||||
|
||||
/// An action to perform when the button is tapped.
|
||||
@ -28,7 +27,7 @@ struct ActionCard<LeadingContent: View>: View {
|
||||
/// - title: The title of the button.
|
||||
/// - action: An action to perform when the button is tapped.
|
||||
///
|
||||
init(title: String, action: @escaping () async -> Void) {
|
||||
public init(title: String, action: @escaping () async -> Void) {
|
||||
self.action = action
|
||||
self.title = title
|
||||
}
|
||||
@ -48,12 +47,15 @@ struct ActionCard<LeadingContent: View>: View {
|
||||
/// 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
|
||||
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
HStack(alignment: .top, spacing: 8) {
|
||||
if let leadingContent {
|
||||
@ -82,10 +84,19 @@ struct ActionCard<LeadingContent: View>: View {
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@ -108,19 +119,22 @@ struct ActionCard<LeadingContent: View>: View {
|
||||
/// - 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.
|
||||
///
|
||||
init(
|
||||
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
|
||||
}
|
||||
|
||||
@ -131,17 +145,20 @@ struct ActionCard<LeadingContent: View>: View {
|
||||
/// - 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.
|
||||
///
|
||||
init(
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -163,6 +180,14 @@ struct ActionCard<LeadingContent: View>: View {
|
||||
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",
|
||||
@ -12,7 +12,7 @@ public struct ActionCardType: BaseViewType {
|
||||
public static var typePrefix: String = "ActionCard"
|
||||
|
||||
public static var namespacedPrefixes: [String] = [
|
||||
"BitwardenShared.ActionCard",
|
||||
"BitwardenKit.ActionCard",
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user