diff --git a/BrowserKit/Sources/Common/Theming/DarkTheme.swift b/BrowserKit/Sources/Common/Theming/DarkTheme.swift index c0906ab8cb15..78d3ca300822 100644 --- a/BrowserKit/Sources/Common/Theming/DarkTheme.swift +++ b/BrowserKit/Sources/Common/Theming/DarkTheme.swift @@ -57,6 +57,7 @@ private struct DarkColourPalette: ThemeColourPalette { ]) var layerSurfaceLow = FXColors.DarkGrey60 var layerSurfaceMedium = FXColors.DarkGrey80 + var layerSurfaceMediumAlpha = FXColors.DarkGrey80.withAlphaComponent(0.8) var layerSurfaceMediumAlt = FXColors.DarkGrey40 var layerGradientSummary = Gradient(colors: [ FXColors.Red70, diff --git a/BrowserKit/Sources/Common/Theming/LightTheme.swift b/BrowserKit/Sources/Common/Theming/LightTheme.swift index b35b8a9b0014..70bd18977b0e 100644 --- a/BrowserKit/Sources/Common/Theming/LightTheme.swift +++ b/BrowserKit/Sources/Common/Theming/LightTheme.swift @@ -46,6 +46,7 @@ private struct LightColourPalette: ThemeColourPalette { ]) var layerSurfaceLow = FXColors.LightGrey20 var layerSurfaceMedium = FXColors.White + var layerSurfaceMediumAlpha = FXColors.White.withAlphaComponent(0.8) var layerSurfaceMediumAlt = FXColors.LightGrey40 var layerGradientSummary = Gradient(colors: [ FXColors.Red70, diff --git a/BrowserKit/Sources/Common/Theming/PrivateModeTheme.swift b/BrowserKit/Sources/Common/Theming/PrivateModeTheme.swift index aba373d08e86..28b178092ce8 100644 --- a/BrowserKit/Sources/Common/Theming/PrivateModeTheme.swift +++ b/BrowserKit/Sources/Common/Theming/PrivateModeTheme.swift @@ -48,6 +48,7 @@ private struct PrivateModeColorPalette: ThemeColourPalette { ]) var layerSurfaceLow = UIColor(rgb: 0x342B4A) var layerSurfaceMedium = UIColor(rgb: 0x24183A) + var layerSurfaceMediumAlpha = UIColor(rgb: 0x24183A).withAlphaComponent(0.8) var layerSurfaceMediumAlt = UIColor(rgb: 0x24183A) var layerGradientSummary = Gradient(colors: [ FXColors.Red70, diff --git a/BrowserKit/Sources/Common/Theming/ThemeColourPalette.swift b/BrowserKit/Sources/Common/Theming/ThemeColourPalette.swift index e7e3d205ad83..c6e7e869800b 100644 --- a/BrowserKit/Sources/Common/Theming/ThemeColourPalette.swift +++ b/BrowserKit/Sources/Common/Theming/ThemeColourPalette.swift @@ -34,6 +34,7 @@ public protocol ThemeColourPalette { var layerGradientURL: Gradient { get } var layerSurfaceLow: UIColor { get } var layerSurfaceMedium: UIColor { get } + var layerSurfaceMediumAlpha: UIColor { get } var layerSurfaceMediumAlt: UIColor { get } var layerGradientSummary: Gradient { get } diff --git a/BrowserKit/Sources/ComponentLibrary/Buttons/ChipButton.swift b/BrowserKit/Sources/ComponentLibrary/Buttons/ChipButton.swift new file mode 100644 index 000000000000..dd1fb5b5f517 --- /dev/null +++ b/BrowserKit/Sources/ComponentLibrary/Buttons/ChipButton.swift @@ -0,0 +1,126 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/ + +import Common +import UIKit + +/// `ChipButton` is a capsule-style button used in chip pickers +public final class ChipButton: UIButton, ThemeApplicable { + private struct UX { + static let verticalInset: CGFloat = 8 + static let horizontalInset: CGFloat = 12 + } + + private var viewModel: ChipButtonViewModel? + + private var normalBackgroundColor: UIColor = .clear + private var normalForegroundColor: UIColor = .clear + private var selectedBackgroundColor: UIColor = .clear + private var selectedForegroundColor: UIColor = .clear + private var disabledBackgroundColor: UIColor = .clear + private var disabledForegroundColor: UIColor = .clear + + override public init(frame: CGRect) { + super.init(frame: frame) + + configuration = UIButton.Configuration.filled() + configuration?.cornerStyle = .capsule + configuration?.background.backgroundColorTransformer = nil + configuration?.titleLineBreakMode = .byTruncatingTail + + addTarget(self, action: #selector(tapped), for: .touchUpInside) + } + + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public var intrinsicContentSize: CGSize { + guard let title = configuration?.title, !title.isEmpty else { + return super.intrinsicContentSize + } + + // Reserve space for the wider selected/unselected title so the chip + // does not resize when its font weight changes with selection. + let regularFont = FXFontStyles.Regular.body.scaledFont() + let boldFont = FXFontStyles.Bold.body.scaledFont() + let regularWidth = title.size(withAttributes: [.font: regularFont]).width + let boldWidth = title.size(withAttributes: [.font: boldFont]).width + let reservedTextWidth = ceil(max(regularWidth, boldWidth)) + let reservedTextHeight = ceil(max(regularFont.lineHeight, boldFont.lineHeight)) + + return CGSize( + width: reservedTextWidth + UX.horizontalInset * 2, + height: reservedTextHeight + UX.verticalInset * 2 + ) + } + + override public func updateConfiguration() { + guard var updatedConfiguration = configuration else { return } + + let foregroundColor: UIColor + let backgroundColor: UIColor + + if !isEnabled { + foregroundColor = disabledForegroundColor + backgroundColor = disabledBackgroundColor + } else if isSelected { + foregroundColor = selectedForegroundColor + backgroundColor = selectedBackgroundColor + } else { + foregroundColor = normalForegroundColor + backgroundColor = normalBackgroundColor + } + + updatedConfiguration.baseForegroundColor = foregroundColor + updatedConfiguration.background.backgroundColor = backgroundColor + updatedConfiguration.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { incoming in + var outgoing = incoming + outgoing.font = self.isSelected ? FXFontStyles.Bold.body.scaledFont() : FXFontStyles.Regular.body.scaledFont() + outgoing.foregroundColor = foregroundColor + return outgoing + } + + configuration = updatedConfiguration + + if !isEnabled { + accessibilityTraits = [.button, .notEnabled] + } else if isSelected { + accessibilityTraits = [.button, .selected] + } else { + accessibilityTraits = [.button] + } + } + + public func configure(viewModel: ChipButtonViewModel) { + self.viewModel = viewModel + accessibilityIdentifier = viewModel.a11yIdentifier + isSelected = viewModel.isSelected + + guard var updatedConfiguration = configuration else { return } + updatedConfiguration.title = viewModel.title + updatedConfiguration.contentInsets = NSDirectionalEdgeInsets( + top: UX.verticalInset, + leading: UX.horizontalInset, + bottom: UX.verticalInset, + trailing: UX.horizontalInset, + ) + configuration = updatedConfiguration + } + + public func applyTheme(theme: Theme) { + normalBackgroundColor = theme.colors.layerSurfaceMediumAlpha + normalForegroundColor = theme.colors.textPrimary + selectedBackgroundColor = theme.colors.actionPrimary + selectedForegroundColor = theme.colors.textInverted + disabledBackgroundColor = theme.colors.layer2 + disabledForegroundColor = theme.colors.textDisabled + setNeedsUpdateConfiguration() + } + + @objc + private func tapped(sender: UIButton) { + viewModel?.tappedAction?(sender) + } +} diff --git a/BrowserKit/Sources/ComponentLibrary/Buttons/ChipButtonViewModel.swift b/BrowserKit/Sources/ComponentLibrary/Buttons/ChipButtonViewModel.swift new file mode 100644 index 000000000000..1a32574d0f6b --- /dev/null +++ b/BrowserKit/Sources/ComponentLibrary/Buttons/ChipButtonViewModel.swift @@ -0,0 +1,25 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/ + +import Foundation +import UIKit + +public struct ChipButtonViewModel { + public let title: String + public let a11yIdentifier: String? + public let isSelected: Bool + public var tappedAction: (@MainActor (UIButton) -> Void)? + + public init( + title: String, + a11yIdentifier: String, + isSelected: Bool, + touchUpAction: (@MainActor (UIButton) -> Void)? + ) { + self.title = title + self.a11yIdentifier = a11yIdentifier + self.isSelected = isSelected + self.tappedAction = touchUpAction + } +} diff --git a/firefox-ios/Client/Frontend/Browser/Tabs/Views/Animation/TabTrayPanelSwipePalette.swift b/firefox-ios/Client/Frontend/Browser/Tabs/Views/Animation/TabTrayPanelSwipePalette.swift index e2da463ada1f..76d335d6ee23 100644 --- a/firefox-ios/Client/Frontend/Browser/Tabs/Views/Animation/TabTrayPanelSwipePalette.swift +++ b/firefox-ios/Client/Frontend/Browser/Tabs/Views/Animation/TabTrayPanelSwipePalette.swift @@ -99,6 +99,7 @@ struct TabTrayPanelSwipePalette: ThemeColourPalette { var layerGradientURL: Gradient { base.layerGradientURL } var layerSurfaceLow: UIColor { base.layerSurfaceLow } var layerSurfaceMedium: UIColor { base.layerSurfaceMedium } + var layerSurfaceMediumAlpha: UIColor { base.layerSurfaceMediumAlpha } var layerSurfaceMediumAlt: UIColor { base.layerSurfaceMediumAlt } var layerGradientSummary: Gradient { base.layerGradientSummary }