[PM-26063] Consolidates several view extensions to BitwardenKit (#2094)

This commit is contained in:
Matt Czech 2025-11-03 11:31:31 -06:00 committed by GitHub
parent ea01caa62d
commit 7a9aa82c90
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 65 additions and 229 deletions

View File

@ -1,23 +0,0 @@
import SwiftUI
// MARK: - NavigationBarViewModifier
/// A modifier that customizes a navigation bar's title and title display mode.
///
struct NavigationBarViewModifier: ViewModifier {
// MARK: Properties
/// The navigation bar title.
var title: String
/// The navigation bar title display mode.
var navigationBarTitleDisplayMode: NavigationBarItem.TitleDisplayMode
// MARK: View
func body(content: Content) -> some View {
content
.navigationBarTitleDisplayMode(navigationBarTitleDisplayMode)
.navigationTitle(title)
}
}

View File

@ -1,145 +0,0 @@
import BitwardenKit
import BitwardenResources
import SwiftUI
/// Helper functions extended off the `View` protocol for supporting buttons and menus in toolbars.
///
extension View {
// MARK: Buttons
/// Returns a toolbar button configured for adding an item.
///
/// - Parameters:
/// - hidden: Whether to hide the toolbar item.
/// - action: The action to perform when the button is tapped.
/// - Returns: A `Button` configured for adding an item.
///
func addToolbarButton(hidden: Bool = false, action: @escaping () -> Void) -> some View {
toolbarButton(asset: SharedAsset.Icons.plus16, label: Localizations.add, action: action)
.hidden(hidden)
.accessibilityIdentifier("AddItemButton")
}
/// Returns a toolbar button configured for cancelling an operation in a view.
///
/// - Parameter action: The action to perform when the button is tapped.
/// - Returns: A `Button` configured for cancelling an operation in a view.
///
func cancelToolbarButton(action: @escaping () -> Void) -> some View {
toolbarButton(asset: SharedAsset.Icons.close16, label: Localizations.cancel, action: action)
.accessibilityIdentifier("CancelButton")
}
/// Returns a toolbar button configured for closing a view.
///
/// - Parameter action: The action to perform when the button is tapped.
/// - Returns: A `Button` configured for closing a view.
///
func closeToolbarButton(action: @escaping () -> Void) -> some View {
toolbarButton(asset: SharedAsset.Icons.close16, label: Localizations.close, action: action)
.accessibilityIdentifier("CloseButton")
}
/// Returns a toolbar button configured for editing an item.
///
/// - Parameter action: The action to perform when the button is tapped.
/// - Returns: A `Button` configured for closing a view.
///
func editToolbarButton(action: @escaping () -> Void) -> some View {
toolbarButton(Localizations.edit, action: action)
.accessibilityIdentifier("EditItemButton")
}
/// Returns a `Button` that displays an image for use in a toolbar.
///
/// - Parameters:
/// - asset: The image asset to show in the button.
/// - label: The label associated with the image, used as an accessibility label.
/// - action: The action to perform when the button is tapped.
/// - Returns: A `Button` for displaying an image in a toolbar.
///
func toolbarButton(asset: SharedImageAsset, label: String, action: @escaping () -> Void) -> some View {
Button(action: action) {
Image(asset: asset, label: Text(label))
.imageStyle(.toolbarIcon)
}
// Ideally we would set both `minHeight` and `minWidth` to 44. Setting `minWidth` causes
// padding to be applied equally on both sides of the image. This results in extra padding
// along the margin though.
.frame(minHeight: 44)
}
/// Returns a `Button` that displays a text label for use in a toolbar.
///
/// - Parameters:
/// - label: The label to display in the button.
/// - action: The action to perform when the button is tapped.
/// - Returns: A `Button` for displaying a text label in a toolbar.
///
func toolbarButton(_ label: String, action: @escaping () async -> Void) -> some View {
AsyncButton(label, action: action)
.foregroundColor(Asset.Colors.primaryBitwarden.swiftUIColor)
// Ideally we would set both `minHeight` and `minWidth` to 44. Setting `minWidth` causes
// padding to be applied equally on both sides of the image. This results in extra padding
// along the margin though.
.frame(minHeight: 44)
}
// MARK: Menus
/// Returns a `Menu` for use in a toolbar.
///
/// - Parameter content: The content to display in the menu when the more icon is tapped.
/// - Returns: A `Menu` for use in a toolbar.
///
func optionsToolbarMenu(@ViewBuilder content: () -> some View) -> some View {
Menu {
content()
} label: {
Image(asset: SharedAsset.Icons.ellipsisVertical24, label: Text(Localizations.options))
.imageStyle(.toolbarIcon)
.accessibilityIdentifier("HeaderBarOptionsButton")
}
// Ideally we would set both `minHeight` and `minWidth` to 44. Setting `minWidth` causes
// padding to be applied equally on both sides of the image. This results in extra padding
// along the margin though.
.frame(height: 44)
}
// MARK: Toolbar Items
/// A `ToolbarItem` for views with an add button.
///
/// - Parameters:
/// - hidden: Whether to hide the toolbar item.
/// - action: The action to perform when the add button is tapped.
/// - Returns: A `ToolbarItem` with an add button.
///
func addToolbarItem(hidden: Bool = false, _ action: @escaping () -> Void) -> some ToolbarContent {
ToolbarItem(placement: .topBarTrailing) {
addToolbarButton(hidden: hidden, action: action)
}
}
/// A `ToolbarItem` for views with a dismiss button.
///
/// - Parameter action: The action to perform when the dismiss button is tapped.
/// - Returns: A `ToolbarItem` with a dismiss button.
///
func cancelToolbarItem(_ action: @escaping () -> Void) -> some ToolbarContent {
ToolbarItem(placement: .topBarTrailing) {
cancelToolbarButton(action: action)
}
}
/// A `ToolbarItem` for views with a more button.
///
/// - Parameter content: The content to display in the menu when the more icon is tapped.
/// - Returns: A `ToolbarItem` with a more button that shows a menu.
///
func optionsToolbarItem(@ViewBuilder _ content: () -> some View) -> some ToolbarContent {
ToolbarItem(placement: .topBarTrailing) {
optionsToolbarMenu(content: content)
}
}
}

View File

@ -3,15 +3,6 @@ import SwiftUI
/// Helper functions extended off the `View` protocol.
///
extension View {
@ViewBuilder var navStackWrapped: some View {
if #available(iOSApplicationExtension 16.0, *) {
NavigationStack { self }
} else {
NavigationView { self }
.navigationViewStyle(.stack)
}
}
/// On iOS 16+, configures the scroll view to dismiss the keyboard immediately.
///
func dismissKeyboardImmediately() -> some View {
@ -60,24 +51,6 @@ extension View {
}
}
/// Applies a custom navigation bar title and title display mode to a view.
///
/// - Parameters:
/// - title: The navigation bar title.
/// - titleDisplayMode: The navigation bar title display mode.
///
/// - Returns: A view with a custom navigation bar.
///
func navigationBar(
title: String,
titleDisplayMode: NavigationBarItem.TitleDisplayMode,
) -> some View {
modifier(NavigationBarViewModifier(
title: title,
navigationBarTitleDisplayMode: titleDisplayMode,
))
}
/// Applies the `ScrollViewModifier` to a view.
///
/// - Parameter addVerticalPadding: Whether or not to add vertical padding. Defaults to `true`.

View File

@ -34,3 +34,25 @@ public struct NavigationBarViewModifier: ViewModifier {
.navigationTitle(title)
}
}
// MARK: View + NavigationBarViewModifier
public extension View {
/// Applies a custom navigation bar title and title display mode to a view.
///
/// - Parameters:
/// - title: The navigation bar title.
/// - titleDisplayMode: The navigation bar title display mode.
///
/// - Returns: A view with a custom navigation bar.
///
func navigationBar(
title: String,
titleDisplayMode: NavigationBarItem.TitleDisplayMode,
) -> some View {
modifier(NavigationBarViewModifier(
title: title,
navigationBarTitleDisplayMode: titleDisplayMode,
))
}
}

View File

@ -1,12 +1,24 @@
import BitwardenKit
import BitwardenResources
import SwiftUI
/// Helper functions extended off the `View` protocol for supporting buttons and menus in toolbars.
///
extension View {
public extension View {
// MARK: Buttons
/// Returns a toolbar button configured for adding an item.
///
/// - Parameters:
/// - hidden: Whether to hide the toolbar item.
/// - action: The action to perform when the button is tapped.
/// - Returns: A `Button` configured for adding an item.
///
func addToolbarButton(hidden: Bool = false, action: @escaping () -> Void) -> some View {
toolbarButton(asset: SharedAsset.Icons.plus16, label: Localizations.add, action: action)
.hidden(hidden)
.accessibilityIdentifier("AddItemButton")
}
/// Returns a toolbar button configured for cancelling an operation in a view.
///
/// - Parameters:
@ -116,7 +128,7 @@ extension View {
/// - action: The action to perform when the button is tapped.
/// - Returns: A `Button` for displaying an image in a toolbar.
///
func toolbarButton(asset: ImageAsset, label: String, action: @escaping () -> Void) -> some View {
func toolbarButton(asset: SharedImageAsset, label: String, action: @escaping () -> Void) -> some View {
Button(action: action) {
Image(asset: asset, label: Text(label))
.imageStyle(.toolbarIcon)
@ -185,6 +197,19 @@ extension View {
// MARK: Toolbar Items
/// A `ToolbarItem` for views with an add button.
///
/// - Parameters:
/// - hidden: Whether to hide the toolbar item.
/// - action: The action to perform when the add button is tapped.
/// - Returns: A `ToolbarItem` with an add button.
///
func addToolbarItem(hidden: Bool = false, _ action: @escaping () -> Void) -> some ToolbarContent {
ToolbarItem(placement: .topBarTrailing) {
addToolbarButton(hidden: hidden, action: action)
}
}
/// A `ToolbarItem` for views with a cancel text button.
///
/// - Parameters:

View File

@ -5,6 +5,21 @@ import SwiftUI
/// Helper functions for fundamental `View` manipulation.
///
public extension View {
// MARK: Computed Properties
/// Wraps the view in a `NavigationStack`.
///
@ViewBuilder var navStackWrapped: some View {
if #available(iOSApplicationExtension 16.0, *) {
NavigationStack { self }
} else {
NavigationView { self }
.navigationViewStyle(.stack)
}
}
// MARK: Methods
/// Apply an arbitrary block of modifiers to a view. This is particularly useful
/// if the modifiers in question might only be available on particular versions
/// of iOS.

View File

@ -33,24 +33,6 @@ extension View {
}
}
/// Applies a custom navigation bar title and title display mode to a view.
///
/// - Parameters:
/// - title: The navigation bar title.
/// - titleDisplayMode: The navigation bar title display mode.
///
/// - Returns: A view with a custom navigation bar.
///
func navigationBar(
title: String,
titleDisplayMode: NavigationBarItem.TitleDisplayMode,
) -> some View {
modifier(NavigationBarViewModifier(
title: title,
navigationBarTitleDisplayMode: titleDisplayMode,
))
}
/// Returns a floating action button positioned at the bottom-right corner of the screen.
///
/// - Parameters:

View File

@ -443,16 +443,3 @@ class VaultItemCoordinator: NSObject, Coordinator, HasStackNavigator { // swiftl
extension VaultItemCoordinator: HasErrorAlertServices {
var errorAlertServices: ErrorAlertServices { services }
}
// MARK: - View Extension
extension View {
@ViewBuilder var navStackWrapped: some View {
if #available(iOSApplicationExtension 16.0, *) {
NavigationStack { self }
} else {
NavigationView { self }
.navigationViewStyle(.stack)
}
}
}