[PM-26064] Move BitwardenMenuField to BitwardenKit (#2012)

This commit is contained in:
Katherine Bertelsen 2025-10-07 08:52:49 -05:00 committed by GitHub
parent 327d949558
commit 2cf02939e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 71 additions and 286 deletions

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
import UIKit
@ -23,10 +24,10 @@ public enum AppTheme: String, Menuable {
// MARK: Properties
/// Specify the text for the default option.
static var defaultValueLocalizedName: String { Localizations.defaultSystem }
public static var defaultValueLocalizedName: String { Localizations.defaultSystem }
/// The name of the type to display in the dropdown menu.
var localizedName: String {
public var localizedName: String {
switch self {
case .dark:
Localizations.dark

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
import Foundation

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
import Foundation

View File

@ -21,7 +21,7 @@ extension SessionTimeoutValue: @retroactive CaseIterable, Menuable {
]
/// The localized string representation of a `SessionTimeoutValue`.
var localizedName: String {
public var localizedName: String {
switch self {
case .immediately:
Localizations.immediately

View File

@ -1,3 +1,5 @@
import BitwardenKit
// MARK: - ExportFormatType
/// An enum describing the format of the items export.

View File

@ -1,3 +1,5 @@
import BitwardenKit
// MARK: - ImportFormatType
/// An enum describing the format of the imported items file by provider.

View File

@ -1,3 +1,5 @@
import BitwardenKit
/// Defines the hash algorithms supported for TOTP.
///
enum TOTPCryptoHashAlgorithm: String, Menuable, CaseIterable {

View File

@ -1,200 +0,0 @@
import BitwardenResources
import SwiftUI
// MARK: - Menuable
/// A protocol that defines an object that can be represented and selected in
/// a `BitwardenMenuField`.
protocol Menuable: Equatable, Hashable {
/// The custom localizable title value for this default case, defaults to `Default`.
static var defaultValueLocalizedName: String { get }
/// A localized name value. This value is displayed in the Menu when the user
/// is making a selection between multiple options.
var localizedName: String { get }
}
extension Menuable {
static var defaultValueLocalizedName: String {
Localizations.default
}
}
// MARK: - BitwardenMenuField
/// A standard input field that allows the user to select between a predefined set of
/// options. This view is identical to `BitwardenTextField`, but uses a `Menu`
/// instead of a `TextField` as the input mechanism.
///
struct BitwardenMenuField<T, TrailingContent: View>: View where T: Menuable {
// MARK: Properties
/// The selection chosen from the menu.
@Binding var selection: T
/// The accessibility identifier for the view.
let accessibilityIdentifier: String?
/// The options displayed in the menu.
let options: [T]
/// The footer text displayed below the menu field.
let footer: String?
/// The title of the menu field.
let title: String?
/// Optional content view that is displayed on the trailing edge of the menu value.
let trailingContent: TrailingContent?
// MARK: View
var body: some View {
BitwardenField(title: title, footer: footer) {
menu
} accessoryContent: {
if let trailingContent {
trailingContent
}
}
}
// MARK: Private views
/// The menu that displays the list of options.
private var menu: some View {
Menu {
Picker(selection: $selection) {
ForEach(options, id: \.hashValue) { option in
Text(option.localizedName).tag(option)
}
} label: {
Text("")
}
.accessibilityIdentifier(accessibilityIdentifier ?? "")
} label: {
HStack {
Text(selection.localizedName)
Spacer()
}
.contentShape(Rectangle())
.transaction { transaction in
// Prevents any downstream animations from rendering a fade animation
// on this label.
transaction.animation = nil
}
}
.styleGuide(.body)
.foregroundColor(Asset.Colors.textPrimary.swiftUIColor)
}
// MARK: Initialization
/// Initializes a new `BitwardenMenuField`.
///
/// - Parameters:
/// - title: The title of the text field.
/// - footer: The footer text displayed below the menu field.
/// - accessibilityIdentifier: The accessibility identifier for the view.
/// - options: The options that the user can choose between.
/// - selection: A `Binding` for the currently selected option.
///
init(
title: String? = nil,
footer: String? = nil,
accessibilityIdentifier: String? = nil,
options: [T],
selection: Binding<T>,
) where TrailingContent == EmptyView {
self.accessibilityIdentifier = accessibilityIdentifier
self.footer = footer
self.options = options
_selection = selection
self.title = title
trailingContent = nil
}
/// Initializes a new `BitwardenMenuField`.
///
/// - Parameters:
/// - title: The title of the text field.
/// - footer: The footer text displayed below the menu field.
/// - accessibilityIdentifier: The accessibility identifier for the view.
/// - options: The options that the user can choose between.
/// - selection: A `Binding` for the currently selected option.
/// - trailingContent: Optional content view that is displayed to the right of the menu value.
///
init(
title: String? = nil,
footer: String? = nil,
accessibilityIdentifier: String? = nil,
options: [T],
selection: Binding<T>,
trailingContent: () -> TrailingContent,
) {
self.accessibilityIdentifier = accessibilityIdentifier
self.footer = footer
self.options = options
_selection = selection
self.title = title
self.trailingContent = trailingContent()
}
}
// MARK: Previews
#if DEBUG
private enum MenuPreviewOptions: CaseIterable, Menuable {
case bear, bird, dog
var localizedName: String {
switch self {
case .bear: "🧸"
case .bird: "🪿"
case .dog: "🐕"
}
}
}
#Preview("CipherType") {
VStack {
BitwardenMenuField(
title: "Animals",
options: MenuPreviewOptions.allCases,
selection: .constant(.dog),
)
.padding()
}
.background(Color(.systemGroupedBackground))
}
#Preview("Trailing Button") {
Group {
BitwardenMenuField(
title: "Animals",
options: MenuPreviewOptions.allCases,
selection: .constant(.dog),
) {
Button {} label: {
SharedAsset.Icons.camera16.swiftUIImage
}
.buttonStyle(.accessory)
}
.padding()
}
.background(Color(.systemGroupedBackground))
}
#Preview("Footer") {
Group {
BitwardenMenuField(
title: "Animals",
footer: "Select your favorite animal",
options: MenuPreviewOptions.allCases,
selection: .constant(.dog),
)
.padding()
}
.background(Color(.systemGroupedBackground))
}
#endif

View File

@ -1,57 +0,0 @@
import SwiftUI
import ViewInspector
import XCTest
@testable import AuthenticatorShared
class BitwardenMenuFieldTests: BitwardenTestCase {
// MARK: Types
enum TestValue: String, CaseIterable, Menuable {
case value1
case value2
var localizedName: String {
rawValue
}
}
// MARK: Properties
var selection: TestValue!
var subject: BitwardenMenuField<TestValue, EmptyView>!
// MARK: Setup & Teardown
override func setUp() {
super.setUp()
selection = .value1
let binding = Binding {
self.selection!
} set: { newValue in
self.selection = newValue
}
subject = BitwardenMenuField(
options: TestValue.allCases,
selection: binding,
)
}
override func tearDown() {
super.tearDown()
selection = nil
subject = nil
}
// MARK: Tests
func test_newSelection() throws {
let picker = try subject.inspect().find(ViewType.Picker.self)
try picker.select(value: TestValue.value2)
XCTAssertEqual(selection, .value2)
let menu = try subject.inspect().find(ViewType.Menu.self)
let label = try menu.labelView().find(ViewType.Text.self).string()
XCTAssertEqual(label, "value2")
}
}

View File

@ -1,3 +1,4 @@
import BitwardenKit
import SwiftUI
// MARK: - FormMenuField

View File

@ -1,3 +1,4 @@
import BitwardenKit
import SwiftUI
// MARK: - SettingsMenuField

View File

@ -1,3 +1,4 @@
import BitwardenKit
import SwiftUI
import ViewInspector
import XCTest

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
import SwiftUI

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
import SwiftUI

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
import BitwardenSdk
import Foundation

View File

@ -1,6 +1,6 @@
import SwiftUI
extension View {
public extension View {
/// A view modifier that calculates the size of the containing view.
///
/// - Parameter perform: A closure called when the size of the view changes.

View File

@ -5,7 +5,7 @@ import SwiftUI
/// A protocol that defines an object that can be represented and selected in
/// a `BitwardenMenuField`.
protocol Menuable: Equatable, Hashable {
public protocol Menuable: Equatable, Hashable {
/// The custom localizable title value for this default case, defaults to `Default`.
static var defaultValueLocalizedName: String { get }
@ -17,7 +17,7 @@ protocol Menuable: Equatable, Hashable {
var localizedName: String { get }
}
extension Menuable {
public extension Menuable {
static var defaultValueLocalizedName: String {
Localizations.default
}
@ -33,7 +33,7 @@ extension Menuable {
/// options. This view is identical to `BitwardenTextField`, but uses a `Menu`
/// instead of a `TextField` as the input mechanism.
///
struct BitwardenMenuField<
public struct BitwardenMenuField<
T,
AdditionalMenu: View,
TitleAccessory: View,
@ -74,7 +74,7 @@ struct BitwardenMenuField<
// MARK: View
var body: some View {
public var body: some View {
VStack(alignment: .leading, spacing: 0) {
menu
@ -175,7 +175,7 @@ struct BitwardenMenuField<
/// - options: The options that the user can choose between.
/// - selection: A `Binding` for the currently selected option.
///
init(
public init(
title: String? = nil,
footer: String? = nil,
accessibilityIdentifier: String? = nil,
@ -205,7 +205,7 @@ struct BitwardenMenuField<
/// - options: The options that the user can choose between.
/// - selection: A `Binding` for the currently selected option.
///
init(
public init(
title: String,
accessibilityIdentifier: String? = nil,
options: [T],
@ -233,7 +233,7 @@ struct BitwardenMenuField<
/// - titleAccessoryContent: Optional title accessory view that is displayed on the trailing edge of the title.
/// - trailingContent: Optional content view that is displayed to the right of the menu value.
///
init(
public init(
title: String? = nil,
footer: String? = nil,
accessibilityIdentifier: String? = nil,
@ -262,7 +262,7 @@ struct BitwardenMenuField<
/// - selection: A `Binding` for the currently selected option.
/// - trailingContent: Optional content view that is displayed to the right of the menu value.
///
init(
public init(
title: String? = nil,
footer: String? = nil,
accessibilityIdentifier: String? = nil,
@ -290,7 +290,7 @@ struct BitwardenMenuField<
/// - selection: A `Binding` for the currently selected option.
/// - titleAccessoryContent: Optional title accessory view that is displayed on the trailing edge of the title.
///
init(
public init(
title: String? = nil,
footer: String? = nil,
accessibilityIdentifier: String? = nil,
@ -319,7 +319,7 @@ struct BitwardenMenuField<
/// - additionalMenu: Additional menu options to display at the bottom of the menu.
///
@_disfavoredOverload
init(
public init(
title: String? = nil,
footer: String? = nil,
accessibilityIdentifier: String? = nil,

View File

@ -1,9 +1,8 @@
import BitwardenKit
import SwiftUI
import ViewInspector
import XCTest
@testable import BitwardenShared
class BitwardenMenuFieldTests: BitwardenTestCase {
// MARK: Types

View File

@ -1,3 +1,4 @@
import BitwardenKit
import Foundation
// MARK: - DefaultableType

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
import UIKit
@ -23,10 +24,10 @@ public enum AppTheme: String, Menuable, Sendable {
// MARK: Properties
/// Specify the text for the default option.
static var defaultValueLocalizedName: String { Localizations.defaultSystem }
public static var defaultValueLocalizedName: String { Localizations.defaultSystem }
/// The name of the type to display in the dropdown menu.
var localizedName: String {
public var localizedName: String {
switch self {
case .dark:
Localizations.dark

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
import Foundation

View File

@ -1,3 +1,5 @@
import BitwardenKit
// MARK: - ExportFormatType
/// An enum describing the format of the vault export.

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
import Foundation

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
/// The service used to generate a forwarded email alias.

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
/// The type of password to generate.

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
import Foundation

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
import BitwardenSdk
@ -18,7 +19,7 @@ public enum SendType: Int, CaseIterable, Codable, Equatable, Identifiable, Menua
// MARK: Properties
var accessibilityId: String {
public var accessibilityId: String {
switch self {
case .text: "SendTextButton"
case .file: "SendFileButton"

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
/// The value to use when generating a plus-addressed or catch-all email.

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
/// The type of username to generate.

View File

@ -737,11 +737,11 @@ extension BitwardenSdk.Folder {
}
extension BitwardenSdk.FolderView: Menuable, @unchecked @retroactive Sendable, TreeNodeModel {
static var defaultValueLocalizedName: String {
public static var defaultValueLocalizedName: String {
Localizations.folderNone
}
var localizedName: String {
public var localizedName: String {
name
}
}

View File

@ -1,3 +1,5 @@
import BitwardenKit
// MARK: - CipherOwner
/// A type to describe the owner of a cipher.
@ -15,7 +17,7 @@ public enum CipherOwner: Equatable, Hashable, Menuable, Sendable {
return true
}
var localizedName: String {
public var localizedName: String {
switch self {
case let .organization(_, name):
name

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
/// An enum describing the type of data contained in a cipher.
@ -51,7 +52,7 @@ extension CipherType: CaseIterable {
}
extension CipherType: Menuable {
var localizedName: String {
public var localizedName: String {
switch self {
case .card: Localizations.typeCard
case .identity: Localizations.typeIdentity

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
/// The type of data stored in a cipher custom field.

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
/// An enum that describes how a Identity title should be matched.

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
/// An enum that describes how a URI should be matched for autofill to occur.

View File

@ -1,3 +1,4 @@
import BitwardenKit
import SwiftUI
extension View {

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
import SwiftUI

View File

@ -1,3 +1,4 @@
import BitwardenKit
import SwiftUI
import ViewInspector
import XCTest

View File

@ -1,3 +1,4 @@
import BitwardenKit
import SwiftUI
// MARK: - FormMenuField

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
import SwiftUI

View File

@ -1,3 +1,4 @@
import BitwardenKit
import SwiftUI
import ViewInspector
import XCTest

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
import SwiftUI

View File

@ -21,7 +21,7 @@ public enum UnlockMethod {
/// An enumeration of session timeout values to choose from.
///
extension SessionTimeoutValue: @retroactive CaseIterable, Menuable {
extension SessionTimeoutValue: @retroactive CaseIterable, @retroactive Menuable {
/// All of the cases to show in the menu.
public static let allCases: [Self] = [
.immediately,
@ -37,7 +37,7 @@ extension SessionTimeoutValue: @retroactive CaseIterable, Menuable {
]
/// The localized string representation of a `SessionTimeoutValue`.
var localizedName: String {
public var localizedName: String {
switch self {
case .immediately:
Localizations.immediately
@ -77,7 +77,7 @@ public enum SessionTimeoutAction: Int, CaseIterable, Codable, Equatable, Menuabl
/// All of the cases to show in the menu.
public static let allCases: [SessionTimeoutAction] = [.lock, .logout]
var localizedName: String {
public var localizedName: String {
switch self {
case .lock:
Localizations.lock

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
import SwiftUI

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
// swiftlint:disable file_length
@ -23,7 +24,7 @@ public enum GeneratorType: CaseIterable, Equatable, Identifiable, Menuable, Send
localizedName
}
var localizedName: String {
public var localizedName: String {
switch self {
case .passphrase:
Localizations.passphrase

View File

@ -1,5 +1,6 @@
// MARK: - CardComponent
import BitwardenKit
import BitwardenResources
import Foundation

View File

@ -31,7 +31,7 @@ struct BitwardenMenuFieldType: BaseViewType {
static var typePrefix: String = "BitwardenMenuField"
static var namespacedPrefixes: [String] = [
"AuthenticatorShared.BitwardenMenuField",
"BitwardenKit.BitwardenMenuField",
]
}

View File

@ -67,7 +67,7 @@ struct BitwardenMenuFieldType: BaseViewType {
static var typePrefix: String = "BitwardenMenuField"
static var namespacedPrefixes: [String] = [
"BitwardenShared.BitwardenMenuField",
"BitwardenKit.BitwardenMenuField",
]
}