diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 73225921..b13f7df5 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ 44604AD729CE196600E62B51 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD629CE196600E62B51 /* Line.swift */; }; 5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */; }; 5FC35BE328D51405004EBEAC /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FC35BE228D51405004EBEAC /* Button.swift */; }; + 7115BD3C2B84C0C200E0A610 /* TileContainerChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 7115BD3B2B84C0C200E0A610 /* TileContainerChangeLog.txt */; }; + 71BFA70A2B7F70E6000DCE33 /* Dropshadowable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BFA7092B7F70E6000DCE33 /* Dropshadowable.swift */; }; 71C02B382B7BD98F00E93E66 /* NotificationChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 71C02B372B7BD98F00E93E66 /* NotificationChangeLog.txt */; }; EA0B18022A9E236900F2D0CD /* SelectorGroupBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18012A9E236900F2D0CD /* SelectorGroupBase.swift */; }; EA0B18052A9E2D2D00F2D0CD /* SelectorBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18032A9E2D2D00F2D0CD /* SelectorBase.swift */; }; @@ -178,6 +180,8 @@ 44604AD629CE196600E62B51 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = ""; }; 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Useable.swift; sourceTree = ""; }; 5FC35BE228D51405004EBEAC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; + 7115BD3B2B84C0C200E0A610 /* TileContainerChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TileContainerChangeLog.txt; sourceTree = ""; }; + 71BFA7092B7F70E6000DCE33 /* Dropshadowable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dropshadowable.swift; sourceTree = ""; }; 71C02B372B7BD98F00E93E66 /* NotificationChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = NotificationChangeLog.txt; sourceTree = ""; }; EA0B18012A9E236900F2D0CD /* SelectorGroupBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorGroupBase.swift; sourceTree = ""; }; EA0B18032A9E2D2D00F2D0CD /* SelectorBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorBase.swift; sourceTree = ""; }; @@ -556,6 +560,7 @@ EA3361B7288B2AAA0071C351 /* ViewProtocol.swift */, EAB1D2CC28ABE76000DAE764 /* Withable.swift */, 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */, + 71BFA7092B7F70E6000DCE33 /* Dropshadowable.swift */, ); path = Protocols; sourceTree = ""; @@ -644,6 +649,7 @@ isa = PBXGroup; children = ( EA5E304B294CBDD00082B959 /* TileContainer.swift */, + 7115BD3B2B84C0C200E0A610 /* TileContainerChangeLog.txt */, ); path = TileContainer; sourceTree = ""; @@ -936,6 +942,7 @@ buildActionMask = 2147483647; files = ( EAEEECA42B1F934600531FC2 /* IconChangeLog.txt in Resources */, + 7115BD3C2B84C0C200E0A610 /* TileContainerChangeLog.txt in Resources */, EA3362042891E14D0071C351 /* VerizonNHGeTX-Bold.otf in Resources */, 71C02B382B7BD98F00E93E66 /* NotificationChangeLog.txt in Resources */, EAEEECA72B1F952000531FC2 /* TabsChangeLog.txt in Resources */, @@ -992,6 +999,7 @@ EAB2376229E9880400AABE9A /* TrailingTooltipLabel.swift in Sources */, EAB2376A29E9E59100AABE9A /* TooltipLaunchable.swift in Sources */, EAB2375D29E8789100AABE9A /* Tooltip.swift in Sources */, + 71BFA70A2B7F70E6000DCE33 /* Dropshadowable.swift in Sources */, EA0D1C452A6AD73000E5C127 /* RawRepresentable.swift in Sources */, EA985C23296E033A00F2FF2E /* TextArea.swift in Sources */, EAF7F0B3289B1ADC00B287F5 /* ActionLabelAttribute.swift in Sources */, diff --git a/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift b/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift index 922b9153..16f0416f 100644 --- a/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift +++ b/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift @@ -454,9 +454,9 @@ open class ButtonIcon: Control, Changeable, FormFieldable { } if let dropshadowable = currentConfig as? Dropshadowable { - addDropShadow(config: dropshadowable) + addDropShadow(dropshadowable) } else { - removeDropShadow() + removeDropShadows() } badgeIndicatorCenterXConstraint?.constant = badgeIndicatorOffset.x + badgeIndicatorDefaultSize.width/2 @@ -530,37 +530,11 @@ extension ButtonIcon: AppleGuidelinesTouchable { } } -extension UIView { - fileprivate func addDropShadow(config: Dropshadowable) { - layer.masksToBounds = false - layer.shadowColor = config.shadowColorConfiguration.getColor(self).cgColor - layer.shadowOpacity = Float(config.shadowOpacity) - layer.shadowOffset = config.shadowOffset - layer.shadowRadius = config.shadowRadius - layer.shouldRasterize = true - layer.rasterizationScale = UIScreen.main.scale - layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath - } - - fileprivate func removeDropShadow() { - layer.shadowOpacity = 0 - layer.shadowRadius = 0 - layer.shadowPath = nil - } -} - private protocol Borderable { var borderWidth: CGFloat { get set } var borderColorConfiguration: AnyColorable { get set } } -private protocol Dropshadowable { - var shadowColorConfiguration: AnyColorable { get set } - var shadowOpacity: CGFloat { get set } - var shadowOffset: CGSize { get set } - var shadowRadius: CGFloat { get set } -} - private protocol Configuration { var kind: ButtonIcon.Kind { get set } var surfaceType: ButtonIcon.SurfaceType { get set } diff --git a/VDS/Components/Notification/Notification.swift b/VDS/Components/Notification/Notification.swift index 2ed84116..126f83f0 100644 --- a/VDS/Components/Notification/Notification.swift +++ b/VDS/Components/Notification/Notification.swift @@ -398,13 +398,18 @@ open class Notification: View { } private func setConstraints() { + maxWidthConstraint?.deactivate() + labelViewAndButtonViewConstraint?.deactivate() + labelViewBottomConstraint?.deactivate() + buttonGroupCenterYConstraint?.deactivate() + buttonGroupBottomConstraint?.deactivate() maxWidthConstraint?.constant = maxViewWidth maxWidthConstraint?.isActive = UIDevice.isIPad labelViewAndButtonViewConstraint?.isActive = layout == .vertical && !buttonGroup.buttons.isEmpty - typeIconWidthConstraint?.constant = typeIcon.size.dimensions.width - closeIconWidthConstraint?.constant = closeButton.size.dimensions.width labelViewBottomConstraint?.isActive = layout == .horizontal || buttonGroup.buttons.isEmpty buttonGroupCenterYConstraint?.isActive = layout == .horizontal buttonGroupBottomConstraint?.isActive = layout == .vertical + typeIconWidthConstraint?.constant = typeIcon.size.dimensions.width + closeIconWidthConstraint?.constant = closeButton.size.dimensions.width } } diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index bad144e2..98090ae4 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -32,20 +32,36 @@ open class TileContainer: Control { // MARK: - Enums //-------------------------------------------------- /// Enum used to describe the background color choices used for this component. - public enum BackgroundColor: String, CaseIterable { + public enum BackgroundColor: Equatable { + + case primary + case secondary case white case black - case gray - case transparent + case custom(String) + + private var reflectedValue: String { String(reflecting: self) } + + public static func == (lhs: Self, rhs: Self) -> Bool { + lhs.reflectedValue == rhs.reflectedValue + } } + /// Enum used to describe the background effect choices used for this component. + public enum BackgroundEffect { + case transparency + case gradient(String, String) + case none + } + /// Enum used to describe the padding choices used for this component. - public enum Padding: String, CaseIterable { + public enum Padding { case padding2X case padding4X case padding6X case padding8X case padding12X + case custom(CGFloat) public var value: CGFloat { switch self { @@ -59,6 +75,8 @@ open class TileContainer: Control { return VDSLayout.Spacing.space8X.value case .padding12X: return VDSLayout.Spacing.space12X.value + case .custom(let padding): + return padding } } } @@ -106,8 +124,11 @@ open class TileContainer: Control { open var aspectRatio: AspectRatio = .ratio1x1 { didSet { setNeedsUpdate() } } /// Sets the background color for the component. - open var color: BackgroundColor = .white { didSet { setNeedsUpdate() } } + open var color: BackgroundColor = .secondary { didSet { setNeedsUpdate() } } + /// Sets the background effect for the component. + open var backgroundEffect: BackgroundEffect = .none { didSet { setNeedsUpdate() } } + /// Sets the inside padding for the component open var padding: Padding = .padding4X { didSet { setNeedsUpdate() } } @@ -165,6 +186,7 @@ open class TileContainer: Control { private let cornerRadius = VDSFormControls.borderradius * 2 private var backgroundColorConfiguration = BackgroundColorConfiguration() + private var dropshadowConfiguration = DropshadowConfiguration() private var borderColorConfiguration = SurfaceColorConfiguration().with { $0.lightColor = VDSColor.elementsLowcontrastOnlight @@ -201,24 +223,20 @@ open class TileContainer: Control { addSubview(highlightView) widthConstraint = layoutGuide.widthAnchor.constraint(equalToConstant: 0) - widthConstraint?.priority = .defaultHigh heightGreaterThanConstraint = layoutGuide.heightAnchor.constraint(greaterThanOrEqualToConstant: 44.0) heightGreaterThanConstraint?.isActive = false heightConstraint = layoutGuide.heightAnchor.constraint(equalToConstant: 0) - heightConstraint?.priority = .defaultHigh backgroundImageView.pin(layoutGuide) backgroundImageView.isUserInteractionEnabled = false backgroundImageView.isHidden = true - - containerView.backgroundColor = .clear containerTopConstraint = containerView.pinTop(anchor: layoutGuide.topAnchor, constant: padding.value) - containerBottomConstraint = containerView.pinBottom(anchor: layoutGuide.bottomAnchor, constant: padding.value) + containerBottomConstraint = layoutGuide.pinBottom(anchor: containerView.bottomAnchor, constant: padding.value) containerLeadingConstraint = containerView.pinLeading(anchor: layoutGuide.leadingAnchor, constant: padding.value) - containerTrailingConstraint = containerView.pinTrailing(anchor: layoutGuide.trailingAnchor, constant: padding.value) + containerTrailingConstraint = layoutGuide.pinTrailing(anchor: containerView.trailingAnchor, constant: padding.value) highlightView.pin(layoutGuide) highlightView.isHidden = true @@ -228,7 +246,6 @@ open class TileContainer: Control { layer.cornerRadius = cornerRadius backgroundImageView.layer.cornerRadius = cornerRadius highlightView.layer.cornerRadius = cornerRadius - } /// Resets to default settings. @@ -268,8 +285,8 @@ open class TileContainer: Control { containerTopConstraint?.constant = padding.value containerLeadingConstraint?.constant = padding.value - containerBottomConstraint?.constant = -padding.value - containerTrailingConstraint?.constant = -padding.value + containerBottomConstraint?.constant = padding.value + containerTrailingConstraint?.constant = padding.value if let width, aspectRatio == .none && height == nil{ widthConstraint?.constant = width @@ -293,6 +310,12 @@ open class TileContainer: Control { widthConstraint?.isActive = false heightConstraint?.isActive = false } + if showDropShadows, surface == .light { + addDropShadow(dropshadowConfiguration) + } else { + removeDropShadows() + } + applyBackgroundEffects() } //-------------------------------------------------- @@ -310,6 +333,36 @@ open class TileContainer: Control { //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- + + private func applyBackgroundEffects() { + let color = backgroundColorConfiguration.getColor(self) + var alphaConfiguration: CGFloat = 1.0 + let imageFallbackColor = imageFallbackColorConfiguration.getColor(self) + switch backgroundEffect { + case .transparency: + alphaConfiguration = 0.8 + removeGradientLayer() + case .gradient(let firstColor, let secondColor): + alphaConfiguration = 1.0 + addGradientLayer(with: UIColor(hexString: firstColor), secondColor: UIColor(hexString: secondColor)) + backgroundImageView.isHidden = true + backgroundImageView.alpha = 1.0 + case .none: + alphaConfiguration = 1.0 + removeGradientLayer() + } + if let backgroundImage { + backgroundImageView.image = backgroundImage + backgroundImageView.isHidden = false + backgroundImageView.alpha = alphaConfiguration + backgroundColor = imageFallbackColor.withAlphaComponent(alphaConfiguration) + } else { + backgroundImageView.isHidden = true + backgroundImageView.alpha = 1.0 + backgroundColor = color.withAlphaComponent(alphaConfiguration) + } + } + private func ratioSize(for width: CGFloat) -> CGSize { var height: CGFloat = width @@ -343,22 +396,40 @@ open class TileContainer: Control { } extension TileContainer { - class BackgroundColorConfiguration: ObjectColorable { + + struct DropshadowConfiguration: Dropshadowable { + var shadowColorConfiguration: AnyColorable = SurfaceColorConfiguration().with { + $0.lightColor = VDSColor.elementsPrimaryOnlight + }.eraseToAnyColorable() + var shadowOpacity: CGFloat = 0.01 + var shadowOffset: CGSize = .init(width: 0, height: 6) + var shadowRadius: CGFloat = 3 + } + + final class BackgroundColorConfiguration: ObjectColorable { + typealias ObjectType = TileContainer + let primaryColorConfig = SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryDark) + let secondaryColorConfig = SurfaceColorConfiguration(VDSColor.backgroundSecondaryLight, VDSColor.backgroundSecondaryDark) + let grayColorConfig = SurfaceColorConfiguration(VDSColor.backgroundSecondaryLight, VDSColor.backgroundSecondaryDark) + let whiteColorConfig = SurfaceColorConfiguration(VDSColor.paletteWhite, VDSColor.paletteWhite) + let blackColorConfig = SurfaceColorConfiguration(VDSColor.paletteBlack, VDSColor.paletteBlack) + required init() { } - + func getColor(_ object: TileContainer) -> UIColor { switch object.color { - + case .primary: + primaryColorConfig.getColor(object.surface) + case .secondary: + secondaryColorConfig.getColor(object.surface) case .white: - return VDSColor.backgroundPrimaryLight + whiteColorConfig.getColor(object.surface) case .black: - return VDSColor.backgroundPrimaryDark - case .gray: - return VDSColor.backgroundSecondaryLight - case .transparent: - return UIColor.clear + blackColorConfig.getColor(object.surface) + case .custom(let hexCode): + UIColor(hexString: hexCode) } } } diff --git a/VDS/Components/TileContainer/TileContainerChangeLog.txt b/VDS/Components/TileContainer/TileContainerChangeLog.txt new file mode 100644 index 00000000..fa0206e2 --- /dev/null +++ b/VDS/Components/TileContainer/TileContainerChangeLog.txt @@ -0,0 +1,90 @@ +MM/DD/YYYY +---------------- + +02/01/2022 +---------------- +- ACTION | Migrated Spec file from working file into VDS Brand 3.0 Core SPECs & Test App. + +02/02/2022 +---------------- +- Elements | Added option for user to manually define a custom Padding valueElements. +- Elements | Removed option for 40px Padding 
 +- Elements | Added background options of Hex code and Transparency. + +02/07/2022 +---------------- +- Anatomy | Updated descriptions to simplify. (Removed “Tile” from many) + +02/08/2022 +---------------- +- Elements | Background color section updated +- Elements | Removed option for 20px Padding value +- Configurations | Surface section added +- Configurations | Multiple sections updated +- Behaviors | Interaction section added +- States | Multiple sections updated/moved +- ACTION | Sent to Accessibility Team to review + +02/14/2022 +---------------- +- ACTION | Received sign off from Accessibility +- ACTION | Sent to Talia for design review + +02/21/2022 +---------------- +- Elements | Background colors and tokens updated. +- ACTION | Ready for dev review. + +02/22/2022 +---------------- +- Elements | Background names updated to infer surface prop selection. +- ACTION | Web dev handoff completed. + +12/15/2022 +---------------- +- States | Android drop shadow specs added, along with screenshot to the right of specs. +- States | Added "(web only)" to any instance of "keyboard focus". +- States | Replaced focus border pixel and style & spacing values with tokens. +- Elements | Updated border color values to use element tokens. +- Configurations | Updated border and drop shadow section titles to “Show border” and “Show drop shadow.” + +01/18/2023 +---------------- +- Anatomy | Updated item #2 to “Padding” from “Container Internal Padding” + +05/11/2023 +---------------- +- Removed showdropshadow prop from Configurations (dropshadow will be on automatically now for Surface=Light) +- Updated states frame to remove states featuring dropshadow suppression, clarified state names, and removed inaccurate dev notes. + +06/15/2023 +---------------- +- Added showDropShadow prop back into Configurations. +- Moved Padding to Configurations + +11/09/2023 +---------------- +- Updated showBorder section to match API prop names/values. +- Moved Padding to Configurations. + +11/20/2023 +---------------- +- Added corner radius token in the Anatomy +- Updated visuals to reflect new corner radius value - 12px +- Updated focus border corner radius to 14px +- View changes + +11/27/2023 +---------------- +- Updated “border radius” to “corner radius” in Anatomy +- Updated “focus border radius” to “focus corner radius” in States +- View changes + +12/14/2023 +---------------- +- Added backgroundColor configuration section, removed Background Colors element section +- Added secondary, primary backgroundColor options +- Simplified backgroundImage section to remove backgroundColor example +- Added background property section, with examples +- Deprecated the gray backgroundColor option +- View changes diff --git a/VDS/Protocols/Dropshadowable.swift b/VDS/Protocols/Dropshadowable.swift new file mode 100644 index 00000000..77f1a475 --- /dev/null +++ b/VDS/Protocols/Dropshadowable.swift @@ -0,0 +1,64 @@ +// +// Dropshadowable.swift +// VDS +// +// Created by Bandaru, Krishna Kishore on 16/02/24. +// + +import Foundation +import UIKit + +protocol Dropshadowable { + + var shadowColorConfiguration: AnyColorable { get set } + var shadowOpacity: CGFloat { get set } + var shadowOffset: CGSize { get set } + var shadowRadius: CGFloat { get set } +} + +extension ViewProtocol where Self: UIView { + + func addDropShadow(_ config: Dropshadowable) { + removeDropShadows() + layer.backgroundColor = backgroundColor?.cgColor + layer.masksToBounds = false + let shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius) + let shadowLayer = CALayer() + shadowLayer.shadowPath = shadowPath.cgPath + shadowLayer.frame = bounds + shadowLayer.position = center + shadowLayer.backgroundColor = UIColor.clear.cgColor + shadowLayer.cornerRadius = layer.cornerRadius + shadowLayer.shadowColor = config.shadowColorConfiguration.getColor(self).cgColor + shadowLayer.shadowOpacity = Float(config.shadowOpacity) + shadowLayer.shadowOffset = .init(width: config.shadowOffset.width, height: config.shadowOffset.height) + shadowLayer.shadowRadius = config.shadowRadius + shadowLayer.name = "dropShadowLayer" + shadowLayer.shouldRasterize = true + shadowLayer.rasterizationScale = UIScreen.main.scale + layer.insertSublayer(shadowLayer, at: 0) + } + + func removeDropShadows() { + layer.sublayers?.removeAll { $0.name == "dropShadowLayer" } + } + + func addGradientLayer(with firstColor: UIColor, secondColor: UIColor) { + removeGradientLayer() + let gradientLayer = CAGradientLayer() + gradientLayer.frame = bounds + gradientLayer.startPoint = CGPoint(x: 0, y: 1) + gradientLayer.endPoint = CGPoint(x: 1, y: 0) + gradientLayer.position = center + gradientLayer.shouldRasterize = true + gradientLayer.rasterizationScale = UIScreen.main.scale + gradientLayer.cornerRadius = layer.cornerRadius + gradientLayer.colors = [firstColor.cgColor, secondColor.cgColor] + gradientLayer.name = "gradientLayer" + layer.insertSublayer(gradientLayer, at: 0) + } + + func removeGradientLayer() { + layer.sublayers?.removeAll { $0.name == "gradientLayer" } + } +}