Tokenize BadgeField (#1843)
* tokenize badgeField * update name * add back observer * address comments
This commit is contained in:
Родитель
429238537d
Коммит
461c0e861b
|
@ -22,6 +22,7 @@ struct Demos {
|
|||
DemoDescriptor("ActivityIndicator", ActivityIndicatorDemoController.self),
|
||||
DemoDescriptor("Avatar", AvatarDemoController.self),
|
||||
DemoDescriptor("AvatarGroup", AvatarGroupDemoController.self),
|
||||
DemoDescriptor("BadgeField", BadgeFieldDemoController.self),
|
||||
DemoDescriptor("BadgeView", BadgeViewDemoController.self),
|
||||
DemoDescriptor("BottomCommandingController", BottomCommandingDemoController.self),
|
||||
DemoDescriptor("BottomSheetController", BottomSheetDemoController.self),
|
||||
|
@ -60,7 +61,6 @@ struct Demos {
|
|||
]
|
||||
|
||||
static let controls: [DemoDescriptor] = [
|
||||
DemoDescriptor("BadgeField", BadgeFieldDemoController.self),
|
||||
DemoDescriptor("Card", CardViewDemoController.self),
|
||||
DemoDescriptor("DateTimePicker", DateTimePickerDemoController.self),
|
||||
DemoDescriptor("PeoplePicker", PeoplePickerDemoController.self),
|
||||
|
|
|
@ -7,6 +7,8 @@ import FluentUI
|
|||
import UIKit
|
||||
|
||||
class BadgeFieldDemoController: DemoController {
|
||||
private var badgeFields = [BadgeField]()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
|
@ -29,7 +31,9 @@ class BadgeFieldDemoController: DemoController {
|
|||
|
||||
let badgeField1 = setupBadgeField(label: "Send to:", dataSources: badgeDataSources1)
|
||||
badgeField1.numberOfLines = 1
|
||||
badgeFields.append(badgeField1)
|
||||
let badgeField2 = setupBadgeField(label: "Cc:", dataSources: badgeDataSources2)
|
||||
badgeFields.append(badgeField2)
|
||||
|
||||
addDescription(text: "Badge field with limited number of lines")
|
||||
container.addArrangedSubview(badgeField1)
|
||||
|
@ -66,3 +70,72 @@ extension BadgeFieldDemoController: BadgeFieldDelegate {
|
|||
showMessage("\(badge.dataSource?.text ?? "A selected badge") was tapped")
|
||||
}
|
||||
}
|
||||
|
||||
extension BadgeFieldDemoController: DemoAppearanceDelegate {
|
||||
func themeWideOverrideDidChange(isOverrideEnabled: Bool) {
|
||||
guard let fluentTheme = self.view.window?.fluentTheme else {
|
||||
return
|
||||
}
|
||||
|
||||
fluentTheme.register(tokenSetType: BadgeFieldTokenSet.self, tokenSet: isOverrideEnabled ? themeWideOverrideBadgeFieldTokens : nil)
|
||||
}
|
||||
|
||||
func perControlOverrideDidChange(isOverrideEnabled: Bool) {
|
||||
for badgeField in badgeFields {
|
||||
badgeField.tokenSet.replaceAllOverrides(with: isOverrideEnabled ? perControlOverrideBadgeFieldTokens : nil)
|
||||
}
|
||||
}
|
||||
|
||||
func isThemeWideOverrideApplied() -> Bool {
|
||||
return self.view.window?.fluentTheme.tokens(for: BadgeFieldTokenSet.self)?.isEmpty == false
|
||||
}
|
||||
|
||||
// MARK: - Custom tokens
|
||||
private var themeWideOverrideBadgeFieldTokens: [BadgeFieldTokenSet.Tokens: ControlTokenValue] {
|
||||
return [
|
||||
.backgroundColor: .uiColor {
|
||||
return UIColor(light: GlobalTokens.sharedColor(.lavender, .tint40),
|
||||
dark: GlobalTokens.sharedColor(.lavender, .shade30))
|
||||
},
|
||||
.labelFont: .uiFont {
|
||||
return UIFont(descriptor: .init(name: "Papyrus", size: 18.0), size: 18.0)
|
||||
},
|
||||
.placeholderFont: .uiFont {
|
||||
return UIFont(descriptor: .init(name: "Papyrus", size: 18.0), size: 18.0)
|
||||
},
|
||||
.textFieldFont: .uiFont {
|
||||
return UIFont(descriptor: .init(name: "Papyrus", size: 18.0), size: 18.0)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
private var perControlOverrideBadgeFieldTokens: [BadgeFieldTokenSet.Tokens: ControlTokenValue] {
|
||||
return [
|
||||
.backgroundColor: .uiColor {
|
||||
return UIColor(light: GlobalTokens.sharedColor(.orange, .tint40),
|
||||
dark: GlobalTokens.sharedColor(.orange, .shade30))
|
||||
},
|
||||
.labelColor: .uiColor {
|
||||
return UIColor(light: GlobalTokens.sharedColor(.navy, .tint40),
|
||||
dark: GlobalTokens.sharedColor(.navy, .shade30))
|
||||
},
|
||||
.placeholderColor: .uiColor {
|
||||
return UIColor(light: GlobalTokens.sharedColor(.navy, .tint40),
|
||||
dark: GlobalTokens.sharedColor(.navy, .shade30))
|
||||
},
|
||||
.textFieldColor: .uiColor {
|
||||
return UIColor(light: GlobalTokens.sharedColor(.navy, .tint40),
|
||||
dark: GlobalTokens.sharedColor(.navy, .shade30))
|
||||
},
|
||||
.labelFont: .uiFont {
|
||||
return UIFont(descriptor: .init(name: "Times", size: 18.0), size: 18.0)
|
||||
},
|
||||
.placeholderFont: .uiFont {
|
||||
return UIFont(descriptor: .init(name: "Times", size: 18.0), size: 18.0)
|
||||
},
|
||||
.textFieldFont: .uiFont {
|
||||
return UIFont(descriptor: .init(name: "Times", size: 18.0), size: 18.0)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
0A8E61FB291DC11F009E412D /* CommandBarTokenSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8E61FA291DC11F009E412D /* CommandBarTokenSet.swift */; };
|
||||
0AE3041D29F721B2003CDDD9 /* TableViewHeaderFooterViewTokenSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE3041C29F721B2003CDDD9 /* TableViewHeaderFooterViewTokenSet.swift */; };
|
||||
2A9745DE281733D700E1A1FD /* TableViewCellTokenSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A9745DD281733D700E1A1FD /* TableViewCellTokenSet.swift */; };
|
||||
3A9FC0F52A6AFAD40060A6BE /* BadgeFieldTokenSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A9FC0F42A6AFAD40060A6BE /* BadgeFieldTokenSet.swift */; };
|
||||
3AFB0FD629C1365600FEC1A9 /* MultilineCommandBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AFB0FD529C1365600FEC1A9 /* MultilineCommandBar.swift */; };
|
||||
43488C46270FAD1300124C71 /* FluentNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43488C44270FAD0200124C71 /* FluentNotification.swift */; };
|
||||
4B2E373D2991CB53008929B4 /* BottomSheetTokenSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E373C2991CB53008929B4 /* BottomSheetTokenSet.swift */; };
|
||||
|
@ -260,6 +261,7 @@
|
|||
1168630322E131CF0088B302 /* TabBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarView.swift; sourceTree = "<group>"; };
|
||||
118D9847230BBA2300BC0B72 /* TabBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarItem.swift; sourceTree = "<group>"; };
|
||||
2A9745DD281733D700E1A1FD /* TableViewCellTokenSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewCellTokenSet.swift; sourceTree = "<group>"; };
|
||||
3A9FC0F42A6AFAD40060A6BE /* BadgeFieldTokenSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeFieldTokenSet.swift; sourceTree = "<group>"; };
|
||||
3AFB0FD529C1365600FEC1A9 /* MultilineCommandBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineCommandBar.swift; sourceTree = "<group>"; };
|
||||
43488C44270FAD0200124C71 /* FluentNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FluentNotification.swift; sourceTree = "<group>"; };
|
||||
497DC2D724185885008D86F8 /* PillButtonBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PillButtonBar.swift; sourceTree = "<group>"; };
|
||||
|
@ -1067,6 +1069,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
B45EB78F219E310F008646A2 /* BadgeField.swift */,
|
||||
3A9FC0F42A6AFAD40060A6BE /* BadgeFieldTokenSet.swift */,
|
||||
B4A8BBCC21BF6D6900D5E3ED /* BadgeStringExtractor.swift */,
|
||||
B444D6B52183A9740002B4D4 /* BadgeView.swift */,
|
||||
6FC3705D29E7707F0096B239 /* BadgeViewTokenSet.swift */,
|
||||
|
@ -1505,6 +1508,7 @@
|
|||
EC5982DA27C703EE00FD048D /* ShapeCutout.swift in Sources */,
|
||||
5314E03A25F00E3D0099271A /* BadgeView.swift in Sources */,
|
||||
5314E1A325F01A7C0099271A /* TableViewHeaderFooterView.swift in Sources */,
|
||||
3A9FC0F52A6AFAD40060A6BE /* BadgeFieldTokenSet.swift in Sources */,
|
||||
5314E30325F0260E0099271A /* AccessibleViewDelegate.swift in Sources */,
|
||||
6EB4B25F270ED6B30005B808 /* BadgeLabel.swift in Sources */,
|
||||
EC98E2B2298D97EC00B9DF91 /* TextFieldTokenSet.swift in Sources */,
|
||||
|
|
|
@ -57,18 +57,13 @@ public protocol BadgeFieldDelegate: AnyObject {
|
|||
* voiceover and dynamic text sizing
|
||||
*/
|
||||
@objc(MSFBadgeField)
|
||||
open class BadgeField: UIView {
|
||||
open class BadgeField: UIView, TokenizedControlInternal {
|
||||
private struct Constants {
|
||||
static let badgeMarginHorizontal: CGFloat = 5
|
||||
static let badgeMarginVertical: CGFloat = 5
|
||||
static let emptyTextFieldString: String = ""
|
||||
static let dragAndDropMinimumPressDuration: TimeInterval = 0.2
|
||||
static let dragAndDropScaleAnimationDuration: TimeInterval = 0.3
|
||||
static let dragAndDropScaleFactor: CGFloat = 1.10
|
||||
static let dragAndDropPositioningAnimationDuration: TimeInterval = 0.2
|
||||
static let labelMarginRight: CGFloat = 5
|
||||
static let labelColorStyle: TextColorStyle = .secondary
|
||||
static let textFieldMinWidth: CGFloat = 100
|
||||
}
|
||||
|
||||
@objc open var label: String = "" {
|
||||
|
@ -141,6 +136,9 @@ open class BadgeField: UIView {
|
|||
|
||||
@objc public weak var badgeFieldDelegate: BadgeFieldDelegate?
|
||||
|
||||
public typealias TokenSetKeyType = BadgeFieldTokenSet.Tokens
|
||||
public var tokenSet: BadgeFieldTokenSet = .init()
|
||||
|
||||
var deviceOrientationIsChanging: Bool {
|
||||
originalDeviceOrientation != UIDevice.current.orientation
|
||||
}
|
||||
|
@ -161,17 +159,7 @@ open class BadgeField: UIView {
|
|||
|
||||
@objc public init() {
|
||||
super.init(frame: .zero)
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(themeDidChange),
|
||||
name: .didChangeTheme,
|
||||
object: nil)
|
||||
|
||||
updateBackgroundColor()
|
||||
|
||||
labelView.colorStyle = Constants.labelColorStyle
|
||||
addSubview(labelView)
|
||||
|
||||
placeholderView.colorStyle = Constants.labelColorStyle
|
||||
addSubview(placeholderView)
|
||||
|
||||
updateLabelsVisibility()
|
||||
|
@ -200,17 +188,29 @@ open class BadgeField: UIView {
|
|||
|
||||
// An accessible container must set isAccessibilityElement to false
|
||||
isAccessibilityElement = false
|
||||
}
|
||||
|
||||
@objc private func themeDidChange(_ notification: Notification) {
|
||||
guard let themeView = notification.object as? UIView, self.isDescendant(of: themeView) else {
|
||||
return
|
||||
// Update appearance whenever `tokenSet` changes.
|
||||
tokenSet.registerOnUpdate(for: self) { [weak self] in
|
||||
self?.updateAppearance()
|
||||
}
|
||||
updateBackgroundColor()
|
||||
}
|
||||
|
||||
private func updateBackgroundColor() {
|
||||
backgroundColor = fluentTheme.color(.background1)
|
||||
private func updateAppearance() {
|
||||
updateColors()
|
||||
updateFonts()
|
||||
}
|
||||
|
||||
private func updateColors() {
|
||||
backgroundColor = tokenSet[.backgroundColor].uiColor
|
||||
labelView.textColor = tokenSet[.labelColor].uiColor
|
||||
placeholderView.textColor = tokenSet[.placeholderColor].uiColor
|
||||
textField.textColor = tokenSet[.textFieldColor].uiColor
|
||||
}
|
||||
|
||||
private func updateFonts() {
|
||||
labelView.font = tokenSet[.labelFont].uiFont
|
||||
placeholderView.font = tokenSet[.placeholderFont].uiFont
|
||||
textField.font = tokenSet[.textFieldFont].uiFont
|
||||
}
|
||||
|
||||
public required init?(coder aDecoder: NSCoder) {
|
||||
|
@ -246,7 +246,6 @@ open class BadgeField: UIView {
|
|||
}
|
||||
|
||||
private func setupTextField(_ textField: UITextField) {
|
||||
textField.font = fluentTheme.typography(.body1)
|
||||
textField.autocapitalizationType = .none
|
||||
textField.autocorrectionType = .no
|
||||
textField.keyboardType = .emailAddress
|
||||
|
@ -291,7 +290,7 @@ open class BadgeField: UIView {
|
|||
let textFieldSize = textField.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
|
||||
let textFieldHeight = ceil(textFieldSize.height)
|
||||
let textFieldVerticalOffset = ceil((badgeHeight - textFieldHeight) / 2.0)
|
||||
let shouldAppendToCurrentLine = left + Constants.textFieldMinWidth <= frame.width
|
||||
let shouldAppendToCurrentLine = left + BadgeFieldTokenSet.textFieldMinWidth <= frame.width
|
||||
if !shouldAppendToCurrentLine {
|
||||
lineIndex += 1
|
||||
left = 0
|
||||
|
@ -316,6 +315,14 @@ open class BadgeField: UIView {
|
|||
return CGSize(width: UIView.noIntrinsicMetric, height: contentHeight(forBoundingWidth: frame.width))
|
||||
}
|
||||
|
||||
open override func willMove(toWindow newWindow: UIWindow?) {
|
||||
super.willMove(toWindow: newWindow)
|
||||
guard let newWindow else {
|
||||
return
|
||||
}
|
||||
tokenSet.update(newWindow.fluentTheme)
|
||||
}
|
||||
|
||||
private func shouldDragBadge(_ badge: BadgeView) -> Bool {
|
||||
if allowsDragAndDrop {
|
||||
return badgeFieldDelegate?.badgeField?(self, shouldDragBadge: badge) ?? true
|
||||
|
@ -355,7 +362,7 @@ open class BadgeField: UIView {
|
|||
if !moreBadges.isEmpty {
|
||||
let moreBadgesDataSources = moreBadges.compactMap { $0.dataSource }
|
||||
let moreBadge = createMoreBadge(withDataSources: moreBadgesDataSources)
|
||||
moreBadgeOffset = Constants.badgeMarginHorizontal + width(forBadge: moreBadge,
|
||||
moreBadgeOffset = BadgeFieldTokenSet.badgeMarginHorizontal + width(forBadge: moreBadge,
|
||||
isFirstBadge: false,
|
||||
isFirstBadgeOfLastDisplayedLine: false,
|
||||
moreBadgeOffset: 0,
|
||||
|
@ -388,7 +395,7 @@ open class BadgeField: UIView {
|
|||
}
|
||||
// Badge should be appended on current line: append and keep looping on following badges
|
||||
constrainedBadges.append(badge)
|
||||
return (false, currentLeft + badgeWidth + Constants.badgeMarginHorizontal, currentLineIndex)
|
||||
return (false, currentLeft + badgeWidth + BadgeFieldTokenSet.badgeMarginHorizontal, currentLineIndex)
|
||||
}
|
||||
|
||||
private func frameForBadge(_ badgeToInsert: BadgeView, boundingWidth: CGFloat) -> CGRect {
|
||||
|
@ -430,7 +437,7 @@ open class BadgeField: UIView {
|
|||
if shouldUseConstrainedBadges, let moreBadge = moreBadge {
|
||||
let moreBadgeSize = moreBadge.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
|
||||
let moreBadgeWidth = ceil(moreBadgeSize.width)
|
||||
return moreBadgeWidth + Constants.badgeMarginHorizontal
|
||||
return moreBadgeWidth + BadgeFieldTokenSet.badgeMarginHorizontal
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
|
@ -438,8 +445,8 @@ open class BadgeField: UIView {
|
|||
|
||||
@objc open func heightThatFits(badgeHeight: CGFloat, numberOfLines: Int) -> CGFloat {
|
||||
// Vertical margin not applicable to top line
|
||||
let heightForAllLines = CGFloat(numberOfLines) * (badgeHeight + Constants.badgeMarginVertical)
|
||||
return heightForAllLines - Constants.badgeMarginVertical
|
||||
let heightForAllLines = CGFloat(numberOfLines) * (badgeHeight + BadgeFieldTokenSet.badgeMarginVertical)
|
||||
return heightForAllLines - BadgeFieldTokenSet.badgeMarginVertical
|
||||
}
|
||||
|
||||
private func contentHeight(forBoundingWidth boundingWidth: CGFloat) -> CGFloat {
|
||||
|
@ -454,7 +461,7 @@ open class BadgeField: UIView {
|
|||
|
||||
let isFirstResponderOrHasTextFieldContent = isFirstResponder || !textFieldContent.isEmpty
|
||||
|
||||
if isEditable && isFirstResponderOrHasTextFieldContent && left + Constants.textFieldMinWidth > boundingWidth {
|
||||
if isEditable && isFirstResponderOrHasTextFieldContent && left + BadgeFieldTokenSet.textFieldMinWidth > boundingWidth {
|
||||
lineIndex += 1
|
||||
}
|
||||
let textFieldSize = textField.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
|
||||
|
@ -489,13 +496,13 @@ open class BadgeField: UIView {
|
|||
width: badgeWidth,
|
||||
height: badgeHeight)
|
||||
|
||||
left += badgeWidth + Constants.badgeMarginHorizontal
|
||||
left += badgeWidth + BadgeFieldTokenSet.badgeMarginHorizontal
|
||||
|
||||
return badgeFrame
|
||||
}
|
||||
|
||||
private func offsetForLine(badgeHeight: CGFloat, at lineIndex: Int) -> CGFloat {
|
||||
return CGFloat(lineIndex) * (badgeHeight + Constants.badgeMarginVertical)
|
||||
return CGFloat(lineIndex) * (badgeHeight + BadgeFieldTokenSet.badgeMarginVertical)
|
||||
}
|
||||
|
||||
// MARK: Badges
|
||||
|
@ -734,7 +741,7 @@ open class BadgeField: UIView {
|
|||
private let placeholderView = Label()
|
||||
|
||||
private var labelViewRightOffset: CGFloat {
|
||||
return labelView.isHidden ? 0 : labelViewWidth + Constants.labelMarginRight
|
||||
return labelView.isHidden ? 0 : labelViewWidth + BadgeFieldTokenSet.labelMarginRight
|
||||
}
|
||||
|
||||
private var labelViewWidth: CGFloat {
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
/// Design token set for the `BadgeField` control.
|
||||
public class BadgeFieldTokenSet: ControlTokenSet<BadgeFieldTokenSet.Tokens> {
|
||||
public enum Tokens: TokenSetKey {
|
||||
/// The background color of the BadgeField.
|
||||
case backgroundColor
|
||||
|
||||
/// The color of the BadgeField's label.
|
||||
case labelColor
|
||||
|
||||
/// The color of the BadgeField's placeholder.
|
||||
case placeholderColor
|
||||
|
||||
/// The color of the BadgeField's text field.
|
||||
case textFieldColor
|
||||
|
||||
/// The font of the BadgeField's label.
|
||||
case labelFont
|
||||
|
||||
/// The font of the BadgeField's placeholder.
|
||||
case placeholderFont
|
||||
|
||||
/// The font of the BadgeField's text field.
|
||||
case textFieldFont
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init { token, theme in
|
||||
switch token {
|
||||
case .backgroundColor:
|
||||
return .uiColor { theme.color(.background1) }
|
||||
case .labelColor, .placeholderColor, .textFieldColor:
|
||||
return .uiColor { theme.color(.foreground2) }
|
||||
case .labelFont, .placeholderFont, .textFieldFont:
|
||||
return .uiFont { theme.typography(.body1) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Constants
|
||||
extension BadgeFieldTokenSet {
|
||||
static let badgeMarginHorizontal: CGFloat = 5
|
||||
static let badgeMarginVertical: CGFloat = 5
|
||||
static let labelMarginRight: CGFloat = 5
|
||||
static let textFieldMinWidth: CGFloat = 100
|
||||
}
|
Загрузка…
Ссылка в новой задаче