updated badgeIndicator to final for this release for now
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
This commit is contained in:
parent
e276087d21
commit
3dc9fdfbb7
@ -10,6 +10,7 @@ import UIKit
|
|||||||
import VDSColorTokens
|
import VDSColorTokens
|
||||||
import VDSFormControlsTokens
|
import VDSFormControlsTokens
|
||||||
import Combine
|
import Combine
|
||||||
|
import VDSTypographyTokens
|
||||||
|
|
||||||
/// Badges are visual labels used to convey status or highlight supplemental information.
|
/// Badges are visual labels used to convey status or highlight supplemental information.
|
||||||
@objc(VDSBadgeIndicator)
|
@objc(VDSBadgeIndicator)
|
||||||
@ -21,6 +22,104 @@ open class BadgeIndicator: View {
|
|||||||
case red, yellow, green, orange, blue, gray, grayLowContrast, black, white
|
case red, yellow, green, orange, blue, gray, grayLowContrast, black, white
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum Kind: String, CaseIterable {
|
||||||
|
case simple, numbered
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MaxDigits: String, CaseIterable {
|
||||||
|
case one
|
||||||
|
case two
|
||||||
|
case three
|
||||||
|
case four
|
||||||
|
case five
|
||||||
|
case six
|
||||||
|
|
||||||
|
public var value: Int {
|
||||||
|
switch self {
|
||||||
|
case .two:
|
||||||
|
return 2
|
||||||
|
case .one:
|
||||||
|
return 1
|
||||||
|
case .three:
|
||||||
|
return 3
|
||||||
|
case .four:
|
||||||
|
return 4
|
||||||
|
case .five:
|
||||||
|
return 5
|
||||||
|
case .six:
|
||||||
|
return 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TextSize: String, CaseIterable {
|
||||||
|
case xxlarge = "2XLarge"
|
||||||
|
case xlarge = "XLarge"
|
||||||
|
case large = "Large"
|
||||||
|
case medium = "Medium"
|
||||||
|
case small = "Small"
|
||||||
|
|
||||||
|
public var minimumSize: CGFloat {
|
||||||
|
switch self {
|
||||||
|
case .xxlarge:
|
||||||
|
return 29
|
||||||
|
case .xlarge:
|
||||||
|
return 24
|
||||||
|
case .large:
|
||||||
|
return 20
|
||||||
|
case .medium:
|
||||||
|
return 18
|
||||||
|
case .small:
|
||||||
|
return 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var padding: CGFloat {
|
||||||
|
switch self {
|
||||||
|
case .xxlarge:
|
||||||
|
return 8
|
||||||
|
case .xlarge:
|
||||||
|
return 6
|
||||||
|
case .large:
|
||||||
|
return 6
|
||||||
|
case .medium:
|
||||||
|
return 6
|
||||||
|
case .small:
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var textStyle: TextStyle {
|
||||||
|
let style = TextStyle.bodySmall
|
||||||
|
var pointSize: CGFloat = VDSTypography.fontSizeBody12
|
||||||
|
|
||||||
|
switch self {
|
||||||
|
case .xxlarge:
|
||||||
|
pointSize = VDSTypography.fontSizeTitle24
|
||||||
|
|
||||||
|
case .xlarge:
|
||||||
|
pointSize = VDSTypography.fontSizeTitle20
|
||||||
|
|
||||||
|
case .large:
|
||||||
|
pointSize = VDSTypography.fontSizeBody16
|
||||||
|
|
||||||
|
case .medium:
|
||||||
|
pointSize = VDSTypography.fontSizeBody14
|
||||||
|
|
||||||
|
case .small:
|
||||||
|
pointSize = VDSTypography.fontSizeBody12
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return TextStyle(rawValue: "\(self.rawValue)BadgeIndicator",
|
||||||
|
fontFace: style.fontFace,
|
||||||
|
pointSize: pointSize,
|
||||||
|
lineHeight: style.lineHeight,
|
||||||
|
letterSpacing: style.letterSpacing)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Public Properties
|
// MARK: - Public Properties
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -28,24 +127,54 @@ open class BadgeIndicator: View {
|
|||||||
$0.setContentCompressionResistancePriority(.required, for: .vertical)
|
$0.setContentCompressionResistancePriority(.required, for: .vertical)
|
||||||
$0.adjustsFontSizeToFitWidth = false
|
$0.adjustsFontSizeToFitWidth = false
|
||||||
$0.lineBreakMode = .byTruncatingTail
|
$0.lineBreakMode = .byTruncatingTail
|
||||||
$0.textPosition = .left
|
$0.textPosition = .center
|
||||||
$0.textStyle = .boldBodySmall
|
$0.numberOfLines = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
open var borderColorLight: UIColor? {
|
||||||
|
didSet {
|
||||||
|
if let borderColorLight {
|
||||||
|
borderColorConfiguration.lightColor = borderColorLight
|
||||||
|
} else {
|
||||||
|
borderColorConfiguration.lightColor = VDSColor.paletteWhite
|
||||||
|
}
|
||||||
|
setNeedsUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open var borderColorDark: UIColor? {
|
||||||
|
didSet {
|
||||||
|
if let borderColorDark {
|
||||||
|
borderColorConfiguration.darkColor = borderColorDark
|
||||||
|
} else {
|
||||||
|
borderColorConfiguration.darkColor = VDSColor.paletteBlack
|
||||||
|
}
|
||||||
|
setNeedsUpdate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open var fillColor: FillColor = .red { didSet { setNeedsUpdate() }}
|
open var fillColor: FillColor = .red { didSet { setNeedsUpdate() }}
|
||||||
|
|
||||||
open var text: String = "" { didSet { setNeedsUpdate() }}
|
open var number: Int? { didSet { setNeedsUpdate() }}
|
||||||
|
|
||||||
open var maxWidth: CGFloat? { didSet { setNeedsUpdate() }}
|
open var kind: Kind = .simple { didSet { setNeedsUpdate() }}
|
||||||
|
|
||||||
open var numberOfLines: Int = 1 { didSet { setNeedsUpdate() }}
|
open var leadingCharacter: String? { didSet { setNeedsUpdate() }}
|
||||||
|
|
||||||
|
open var textSize: TextSize = .xxlarge { didSet { setNeedsUpdate() }}
|
||||||
|
|
||||||
|
open var hideDot: Bool = false { didSet { setNeedsUpdate() }}
|
||||||
|
|
||||||
|
open var hideBorder: Bool = false { didSet { setNeedsUpdate() }}
|
||||||
|
|
||||||
|
open var maxDigits: MaxDigits = .two { didSet { setNeedsUpdate() }}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Constraints
|
// MARK: - Constraints
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
private var maxWidthConstraint: NSLayoutConstraint?
|
private var labelWidthConstraint: NSLayoutConstraint?
|
||||||
private var minWidthConstraint: NSLayoutConstraint?
|
private var labelHeightConstraint: NSLayoutConstraint?
|
||||||
|
private var defaultBadgeSize: CGFloat = 16
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Lifecycle
|
// MARK: - Lifecycle
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -54,18 +183,17 @@ open class BadgeIndicator: View {
|
|||||||
super.setup()
|
super.setup()
|
||||||
|
|
||||||
accessibilityElements = [label]
|
accessibilityElements = [label]
|
||||||
layer.cornerRadius = 2
|
|
||||||
|
|
||||||
addSubview(label)
|
addSubview(label)
|
||||||
label.pinToSuperView(.init(top: 2,
|
label.pinToSuperView()
|
||||||
left: VDSLayout.Spacing.space1X.value,
|
|
||||||
bottom: 2,
|
|
||||||
right: VDSLayout.Spacing.space1X.value))
|
|
||||||
|
|
||||||
maxWidthConstraint = label.widthAnchor.constraint(lessThanOrEqualToConstant: 100)
|
NSLayoutConstraint.activate([
|
||||||
minWidthConstraint = label.widthAnchor.constraint(greaterThanOrEqualToConstant: 23)
|
label.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||||
minWidthConstraint?.isActive = true
|
label.centerYAnchor.constraint(equalTo: centerYAnchor)
|
||||||
|
])
|
||||||
|
|
||||||
|
labelWidthConstraint = label.widthGreaterThanEqualTo(constant: defaultBadgeSize).activate()
|
||||||
|
labelHeightConstraint = label.heightGreaterThanEqualTo(constant: defaultBadgeSize).activate()
|
||||||
}
|
}
|
||||||
|
|
||||||
open override func reset() {
|
open override func reset() {
|
||||||
@ -73,12 +201,9 @@ open class BadgeIndicator: View {
|
|||||||
shouldUpdateView = false
|
shouldUpdateView = false
|
||||||
label.reset()
|
label.reset()
|
||||||
label.lineBreakMode = .byTruncatingTail
|
label.lineBreakMode = .byTruncatingTail
|
||||||
label.textPosition = .left
|
label.textPosition = .center
|
||||||
label.textStyle = .boldBodySmall
|
|
||||||
fillColor = .red
|
fillColor = .red
|
||||||
text = ""
|
number = nil
|
||||||
maxWidth = nil
|
|
||||||
numberOfLines = 1
|
|
||||||
shouldUpdateView = true
|
shouldUpdateView = true
|
||||||
setNeedsUpdate()
|
setNeedsUpdate()
|
||||||
}
|
}
|
||||||
@ -86,6 +211,8 @@ open class BadgeIndicator: View {
|
|||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Configuration
|
// MARK: - Configuration
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
private var borderColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteWhite, VDSColor.paletteBlack)
|
||||||
|
|
||||||
private var backgroundColorConfiguration: AnyColorable = {
|
private var backgroundColorConfiguration: AnyColorable = {
|
||||||
let config = KeyedColorConfiguration<BadgeIndicator, FillColor>(keyPath: \.fillColor)
|
let config = KeyedColorConfiguration<BadgeIndicator, FillColor>(keyPath: \.fillColor)
|
||||||
config.setSurfaceColors(VDSColor.backgroundBrandhighlight, VDSColor.backgroundBrandhighlight, forKey: .red)
|
config.setSurfaceColors(VDSColor.backgroundBrandhighlight, VDSColor.backgroundBrandhighlight, forKey: .red)
|
||||||
@ -93,6 +220,8 @@ open class BadgeIndicator: View {
|
|||||||
config.setSurfaceColors(VDSColor.paletteGreen26, VDSColor.paletteGreen36, forKey: .green)
|
config.setSurfaceColors(VDSColor.paletteGreen26, VDSColor.paletteGreen36, forKey: .green)
|
||||||
config.setSurfaceColors(VDSColor.paletteOrange41, VDSColor.paletteOrange58, forKey: .orange)
|
config.setSurfaceColors(VDSColor.paletteOrange41, VDSColor.paletteOrange58, forKey: .orange)
|
||||||
config.setSurfaceColors(VDSColor.paletteBlue38, VDSColor.paletteBlue46, forKey: .blue)
|
config.setSurfaceColors(VDSColor.paletteBlue38, VDSColor.paletteBlue46, forKey: .blue)
|
||||||
|
config.setSurfaceColors(VDSColor.paletteGray44, VDSColor.paletteGray65, forKey: .gray)
|
||||||
|
config.setSurfaceColors(VDSColor.paletteGray85, VDSColor.paletteGray20, forKey: .grayLowContrast)
|
||||||
config.setSurfaceColors(VDSColor.backgroundPrimaryDark, VDSColor.backgroundPrimaryDark, forKey: .black)
|
config.setSurfaceColors(VDSColor.backgroundPrimaryDark, VDSColor.backgroundPrimaryDark, forKey: .black)
|
||||||
config.setSurfaceColors(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryLight, forKey: .white)
|
config.setSurfaceColors(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryLight, forKey: .white)
|
||||||
return config.eraseToAnyColorable()
|
return config.eraseToAnyColorable()
|
||||||
@ -105,7 +234,7 @@ open class BadgeIndicator: View {
|
|||||||
|
|
||||||
switch fillColor {
|
switch fillColor {
|
||||||
|
|
||||||
case .red, .black:
|
case .red, .black, .gray, .grayLowContrast:
|
||||||
textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOndark, forDisabled: false)
|
textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOndark, forDisabled: false)
|
||||||
textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOndark, forDisabled: true)
|
textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOndark, forDisabled: true)
|
||||||
|
|
||||||
@ -127,17 +256,70 @@ open class BadgeIndicator: View {
|
|||||||
|
|
||||||
backgroundColor = backgroundColorConfiguration.getColor(self)
|
backgroundColor = backgroundColorConfiguration.getColor(self)
|
||||||
|
|
||||||
|
label.useAttributedText = true
|
||||||
|
label.edgeInset = .init(top: 0, left: textSize.padding, bottom: 0, right: textSize.padding)
|
||||||
|
label.font = textSize.textStyle.font
|
||||||
|
label.textColor = textColorConfiguration.getColor(self)
|
||||||
label.textColorConfiguration = textColorConfiguration.eraseToAnyColorable()
|
label.textColorConfiguration = textColorConfiguration.eraseToAnyColorable()
|
||||||
label.numberOfLines = numberOfLines
|
label.text = getText()
|
||||||
label.text = text
|
|
||||||
label.surface = surface
|
label.surface = surface
|
||||||
label.disabled = disabled
|
label.disabled = disabled
|
||||||
|
label.sizeToFit()
|
||||||
|
setNeedsLayout()
|
||||||
|
layoutIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
if let maxWidth = maxWidth, let minWidth = minWidthConstraint?.constant, maxWidth > minWidth {
|
private func getText() -> String {
|
||||||
maxWidthConstraint?.constant = maxWidth
|
let badgeCount = number ?? 0
|
||||||
maxWidthConstraint?.isActive = true
|
var text: String = ""
|
||||||
|
if kind == .numbered {
|
||||||
|
let maxBadgetCount = limitDigits(number: badgeCount, maxDigits: maxDigits.value)
|
||||||
|
|
||||||
|
text = "\(maxBadgetCount)"
|
||||||
|
if maxDigits.value < "\(badgeCount)".count {
|
||||||
|
text = "\(maxBadgetCount)+"
|
||||||
|
}
|
||||||
|
if let leadingCharacter {
|
||||||
|
text = "\(leadingCharacter)\(text)"
|
||||||
|
} else {
|
||||||
|
text = "\(text)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
private func limitDigits(number: Int, maxDigits: Int) -> Int {
|
||||||
|
let maxNumber = Int(pow(10.0, Double(maxDigits))) - 1
|
||||||
|
return min(number, maxNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
open override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
labelWidthConstraint?.constant = textSize.minimumSize
|
||||||
|
labelHeightConstraint?.constant = textSize.minimumSize
|
||||||
|
layer.cornerRadius = frame.size.height / 2
|
||||||
|
|
||||||
|
if hideBorder {
|
||||||
|
layer.borderWidth = 0
|
||||||
} else {
|
} else {
|
||||||
maxWidthConstraint?.isActive = false
|
layer.borderColor = borderColorConfiguration.getColor(surface).cgColor
|
||||||
|
layer.borderWidth = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.remove(layerName: "dot")
|
||||||
|
if kind == .simple && !hideDot {
|
||||||
|
let dotSize: CGFloat = bounds.width * 0.1875
|
||||||
|
let dotLayer = CAShapeLayer()
|
||||||
|
dotLayer.name = "dot"
|
||||||
|
|
||||||
|
let centerX = (bounds.width - dotSize) / 2.0
|
||||||
|
let centerY = (bounds.width - dotSize) / 2.0
|
||||||
|
|
||||||
|
dotLayer.frame = .init(x: centerX, y: centerY, width: dotSize, height: dotSize)
|
||||||
|
dotLayer.path = UIBezierPath(ovalIn: .init(origin: .zero, size: .init(width: dotSize, height: dotSize))).cgPath
|
||||||
|
dotLayer.fillColor = textColorConfiguration.getColor(self).cgColor
|
||||||
|
|
||||||
|
layer.addSublayer(dotLayer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user