From 87743e9c3ab0b23d77943ef8a662c8df66d90abd Mon Sep 17 00:00:00 2001 From: Pavel Holec Date: Sat, 19 Mar 2022 11:18:06 +0100 Subject: [PATCH 1/6] Added new xMedium spacing, update largest spacings. --- Sources/Orbit/Components/Badge.swift | 18 ++++---- Sources/Orbit/Components/ListChoice.swift | 2 +- Sources/Orbit/Components/TimelineStep.swift | 2 +- .../Orbit/Foundation/Spacing/Spacing.swift | 44 ++++++++++--------- 4 files changed, 35 insertions(+), 31 deletions(-) diff --git a/Sources/Orbit/Components/Badge.swift b/Sources/Orbit/Components/Badge.swift index 4249d305938..6cc08746592 100644 --- a/Sources/Orbit/Components/Badge.swift +++ b/Sources/Orbit/Components/Badge.swift @@ -192,14 +192,14 @@ struct BadgePreviews: PreviewProvider { } static var orbit: some View { - HStack(spacing: .xxLarge) { - VStack(alignment: .leading, spacing: .xxLarge) { - VStack(alignment: .leading, spacing: .xSmall) { + HStack { + VStack(alignment: .leading) { + VStack(alignment: .leading) { Badge("Light", style: .light) Badge("Light Inverted", style: .lightInverted) Badge("Neutral", style: .neutral) } - VStack(alignment: .leading, spacing: .xSmall) { + VStack(alignment: .leading) { Badge("Info", style: .status(.info)) Badge("Info Inverted", style: .status(.info, inverted: true)) Badge("Success", style: .status(.success)) @@ -209,20 +209,20 @@ struct BadgePreviews: PreviewProvider { Badge("Critical", style: .status(.critical)) Badge("Critical Inverted", style: .status(.critical, inverted: true)) } - VStack(alignment: .leading, spacing: .xSmall) { + VStack(alignment: .leading) { Badge("Orange Gradient", icon: .check, style: .gradient(.bundleBasic)) Badge("Purple Gradient", icon: .accommodation, style: .gradient(.bundleMedium)) Badge("Ink Gradient", icon: .airplaneUp, style: .gradient(.bundleTop)) } } - VStack(alignment: .leading, spacing: .xxLarge) { - VStack(alignment: .leading, spacing: .xSmall) { + VStack(alignment: .leading) { + VStack(alignment: .leading) { Badge("Light", icon: .sun, style: .light) Badge("Light Inverted", icon: .moon, style: .lightInverted) Badge("Neutral", icon: .airplaneUp, style: .neutral) } - VStack(alignment: .leading, spacing: .xSmall) { + VStack(alignment: .leading) { Badge("Info", icon: .wifi, style: .status(.info)) Badge("Info Inverted", icon: .bus, style: .status(.info, inverted: true)) Badge("Success", icon: .airplane, style: .status(.success)) @@ -232,7 +232,7 @@ struct BadgePreviews: PreviewProvider { Badge("Critical", icon: .accommodation, style: .status(.critical)) Badge("Critical Inverted", icon: .accommodation, style: .status(.critical, inverted: true)) } - VStack(alignment: .leading, spacing: .xSmall) { + VStack(alignment: .leading) { Badge("Orange Gradient", style: .gradient(.bundleBasic)) Badge("Purple Gradient", style: .gradient(.bundleMedium)) Badge("Ink Gradient", style: .gradient(.bundleTop)) diff --git a/Sources/Orbit/Components/ListChoice.swift b/Sources/Orbit/Components/ListChoice.swift index 03d86076758..d641bfb9b28 100644 --- a/Sources/Orbit/Components/ListChoice.swift +++ b/Sources/Orbit/Components/ListChoice.swift @@ -120,7 +120,7 @@ public struct ListChoice: View { return .medium } - return .xxLarge + .xxSmall + return .xxLarge } var isHeaderEmpty: Bool { diff --git a/Sources/Orbit/Components/TimelineStep.swift b/Sources/Orbit/Components/TimelineStep.swift index f71e814256f..30a700c8e62 100644 --- a/Sources/Orbit/Components/TimelineStep.swift +++ b/Sources/Orbit/Components/TimelineStep.swift @@ -147,7 +147,7 @@ struct TimelineStepPreviews: PreviewProvider { } static var snapshots: some View { - VStack(spacing: .xxLarge) { + VStack { TimelineStep( "Requested", sublabel: "3rd May 14:04", diff --git a/Sources/Orbit/Foundation/Spacing/Spacing.swift b/Sources/Orbit/Foundation/Spacing/Spacing.swift index 2a33eed17c4..26ce74d6661 100644 --- a/Sources/Orbit/Foundation/Spacing/Spacing.swift +++ b/Sources/Orbit/Foundation/Spacing/Spacing.swift @@ -6,43 +6,47 @@ import CoreGraphics /// /// - Note: [Orbit definition](https://orbit.kiwi/foundation/spacing/) public enum Spacing { - /// 2 pts spacing. + /// 2 pts. public static let xxxSmall: CGFloat = 2 - /// 4 pts spacing. + /// 4 pts. public static let xxSmall: CGFloat = 4 - /// 8 pts spacing. + /// 8 pts. public static let xSmall: CGFloat = 8 - /// 12 pts spacing. + /// 12 pts. public static let small: CGFloat = 12 - /// 16 pts spacing. + /// 16 pts. public static let medium: CGFloat = 16 - /// 24 pts spacing. + /// 20 pts. + public static let xMedium: CGFloat = 20 + /// 24 pts. public static let large: CGFloat = 24 - /// 32 pts spacing. + /// 32 pts. public static let xLarge: CGFloat = 32 - /// 40 pts spacing. - public static let xxLarge: CGFloat = 40 - /// 52 pts spacing. - public static let xxxLarge: CGFloat = 52 + /// 44 pts. + public static let xxLarge: CGFloat = 44 + /// 60 pts. + public static let xxxLarge: CGFloat = 60 } public extension CGFloat { - /// 2 pts spacing. + /// 2 pts static let xxxSmall = Spacing.xxxSmall - /// 4 pts spacing. + /// 4 pts. static let xxSmall = Spacing.xxSmall - /// 8 pts spacing. + /// 8 pts. static let xSmall = Spacing.xSmall - /// 12 pts spacing. + /// 12 pts. static let small = Spacing.small - /// 16 pts spacing. + /// 16 pts. static let medium = Spacing.medium - /// 24 pts spacing. + /// 20 pts. + static let xMedium = Spacing.xMedium + /// 24 pts. static let large = Spacing.large - /// 32 pts spacing. + /// 32 pts. static let xLarge = Spacing.xLarge - /// 40 pts spacing. + /// 44 pts. static let xxLarge = Spacing.xxLarge - /// 52 pts spacing. + /// 60 pts. static let xxxLarge = Spacing.xxxLarge } From 9320f505505add385a985d8f63970583d42ae2b7 Mon Sep 17 00:00:00 2001 From: Pavel Holec Date: Sat, 19 Mar 2022 15:33:23 +0100 Subject: [PATCH 2/6] Update Radio and Checkbox spacing. --- Sources/Orbit/Components/Checkbox.swift | 2 +- Sources/Orbit/Components/Radio.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Orbit/Components/Checkbox.swift b/Sources/Orbit/Components/Checkbox.swift index a8fd540df64..90ff1a68f6a 100644 --- a/Sources/Orbit/Components/Checkbox.swift +++ b/Sources/Orbit/Components/Checkbox.swift @@ -25,7 +25,7 @@ public struct Checkbox: View { }, label: { if label.isEmpty == false || description.isEmpty == false { - VStack(alignment: .leading, spacing: 1) { + VStack(alignment: .leading, spacing: 0) { Heading(label, style: .title5, color: labelColor) Text(description, size: .small, color: descriptionColor) } diff --git a/Sources/Orbit/Components/Radio.swift b/Sources/Orbit/Components/Radio.swift index bcf12cf4bd6..66a2a9ac135 100644 --- a/Sources/Orbit/Components/Radio.swift +++ b/Sources/Orbit/Components/Radio.swift @@ -26,7 +26,7 @@ public struct Radio: View { }, label: { if label.isEmpty == false || description.isEmpty == false { - VStack(alignment: .leading, spacing: 1) { + VStack(alignment: .leading, spacing: 0) { Heading(label, style: .title5, color: labelColor) Text(description, size: .small, color: descriptionColor) } From 814b47eefe8ac6aa918753200f0b4999d2da2d93 Mon Sep 17 00:00:00 2001 From: Pavel Holec Date: Sat, 19 Mar 2022 15:36:47 +0100 Subject: [PATCH 3/6] Code cleanup. --- Sources/Orbit/Components/Badge.swift | 19 +++++-------- Sources/Orbit/Components/Text.swift | 6 ++--- Sources/Orbit/Components/TextLink.swift | 1 + Sources/Orbit/Components/Tile.swift | 9 ------- .../Orbit/Support/Forms/FormFieldLabel.swift | 11 ++++++++ .../Support/Forms/FormFieldMessage.swift | 12 +++++++++ .../Support/SelectableLabelWrapper.swift | 9 ------- .../Support/Storybook/TutorialScreen.swift | 2 +- Sources/Orbit/WIP/Obsolete/MessageType.swift | 27 +++++++------------ 9 files changed, 44 insertions(+), 52 deletions(-) diff --git a/Sources/Orbit/Components/Badge.swift b/Sources/Orbit/Components/Badge.swift index 6cc08746592..dc754bf23b7 100644 --- a/Sources/Orbit/Components/Badge.swift +++ b/Sources/Orbit/Components/Badge.swift @@ -44,6 +44,7 @@ public struct Badge: View { Capsule() .strokeBorder(style.outlineColor, lineWidth: BorderWidth.thin) ) + .fixedSize() } } @@ -89,28 +90,22 @@ public extension Badge { public var height: CGFloat { switch self { - case .default: - return .large - case .compact: - return 18 + case .default: return .large + case .compact: return 18 } } public var padding: CGFloat { switch self { - case .default: - return .xSmall - case .compact: - return 6 + case .default: return .xSmall + case .compact: return 6 } } public var spacing: CGFloat { switch self { - case .default: - return 5 - case .compact: - return 3 + case .default: return 5 + case .compact: return 3 } } } diff --git a/Sources/Orbit/Components/Text.swift b/Sources/Orbit/Components/Text.swift index 173680a828c..d61ff63421e 100644 --- a/Sources/Orbit/Components/Text.swift +++ b/Sources/Orbit/Components/Text.swift @@ -127,7 +127,7 @@ public extension Text { lineSpacing: CGFloat? = nil, alignment: TextAlignment = .leading, accentColor: UIColor? = nil, - linkColor: UIColor = .productDark, + linkColor: UIColor = TextLink.defaultColor, isSelectable: Bool = false, linkAction: @escaping TextLink.Action = { _, _ in } ) { @@ -180,8 +180,8 @@ public extension Text { public var iconSize: CGFloat { switch self { - case .large: return 22 - default: return lineHeight + case .large: return 22 + default: return lineHeight } } } diff --git a/Sources/Orbit/Components/TextLink.swift b/Sources/Orbit/Components/TextLink.swift index 0e6aabb35d2..c62b054996d 100644 --- a/Sources/Orbit/Components/TextLink.swift +++ b/Sources/Orbit/Components/TextLink.swift @@ -12,6 +12,7 @@ public struct TextLink: UIViewRepresentable { /// An action handler for a link tapped inside the ``Text`` component. public typealias Action = (URL, String) -> Void + public static let defaultColor: UIColor = .productDark let content: NSAttributedString let size: CGSize diff --git a/Sources/Orbit/Components/Tile.swift b/Sources/Orbit/Components/Tile.swift index 9f4f81705a2..dc8cc18de79 100644 --- a/Sources/Orbit/Components/Tile.swift +++ b/Sources/Orbit/Components/Tile.swift @@ -65,7 +65,6 @@ public struct Tile: View { let status: Status? let backgroundColor: BackgroundColor? let titleStyle: Label.TitleStyle - let titleColor: Text.Color let descriptionColor: Text.Color let action: () -> Void let content: () -> Content @@ -184,7 +183,6 @@ public extension Tile { status: Status? = nil, backgroundColor: BackgroundColor? = nil, titleStyle: Label.TitleStyle = .title4, - titleColor: Text.Color = .inkNormal, descriptionColor: Text.Color = .inkLight, action: @escaping () -> Void = {}, @ViewBuilder content: @escaping () -> Content @@ -197,7 +195,6 @@ public extension Tile { self.status = status self.backgroundColor = backgroundColor self.titleStyle = titleStyle - self.titleColor = titleColor self.descriptionColor = descriptionColor self.action = action self.content = content @@ -216,7 +213,6 @@ public extension Tile { status: Status? = nil, backgroundColor: BackgroundColor? = nil, titleStyle: Label.TitleStyle = .title4, - titleColor: Text.Color = .inkNormal, descriptionColor: Text.Color = .inkLight, iconColor: Color = .inkNormal, action: @escaping () -> Void = {}, @@ -230,7 +226,6 @@ public extension Tile { self.status = status self.backgroundColor = backgroundColor self.titleStyle = titleStyle - self.titleColor = titleColor self.descriptionColor = descriptionColor self.action = action self.content = content @@ -249,7 +244,6 @@ public extension Tile { status: Status? = nil, backgroundColor: BackgroundColor? = nil, titleStyle: Label.TitleStyle = .title4, - titleColor: Text.Color = .inkNormal, descriptionColor: Text.Color = .inkLight, action: @escaping () -> Void = {} ) where Content == EmptyView { @@ -262,7 +256,6 @@ public extension Tile { status: status, backgroundColor: backgroundColor, titleStyle: titleStyle, - titleColor: titleColor, descriptionColor: descriptionColor, action: action, content: { EmptyView() } @@ -282,7 +275,6 @@ public extension Tile { status: Status? = nil, backgroundColor: BackgroundColor? = nil, titleStyle: Label.TitleStyle = .title4, - titleColor: Text.Color = .inkNormal, descriptionColor: Text.Color = .inkLight, iconColor: Color = .inkNormal, action: @escaping () -> Void = {} @@ -296,7 +288,6 @@ public extension Tile { status: status, backgroundColor: backgroundColor, titleStyle: titleStyle, - titleColor: titleColor, descriptionColor: descriptionColor, action: action, content: { EmptyView() } diff --git a/Sources/Orbit/Support/Forms/FormFieldLabel.swift b/Sources/Orbit/Support/Forms/FormFieldLabel.swift index da30e521b71..4679822a23a 100644 --- a/Sources/Orbit/Support/Forms/FormFieldLabel.swift +++ b/Sources/Orbit/Support/Forms/FormFieldLabel.swift @@ -14,3 +14,14 @@ public struct FormFieldLabel: View { self.label = label } } + +// MARK: - Previews +struct FormFieldLabelPreviews: PreviewProvider { + + static var previews: some View { + PreviewWrapper { + FormFieldLabel("Form Field Label") + } + .previewLayout(.sizeThatFits) + } +} diff --git a/Sources/Orbit/Support/Forms/FormFieldMessage.swift b/Sources/Orbit/Support/Forms/FormFieldMessage.swift index 46206c31c9d..19003e2d786 100644 --- a/Sources/Orbit/Support/Forms/FormFieldMessage.swift +++ b/Sources/Orbit/Support/Forms/FormFieldMessage.swift @@ -25,3 +25,15 @@ public struct FormFieldMessage: View { self.spacing = spacing } } + +// MARK: - Previews +struct FormFieldMessagePreviews: PreviewProvider { + + static var previews: some View { + PreviewWrapper { + FormFieldMessage(.normal("Form Field Message", icon: .informationCircle)) + FormFieldMessage(.error("Form Field Message", icon: .alertCircle)) + } + .previewLayout(.sizeThatFits) + } +} diff --git a/Sources/Orbit/Support/SelectableLabelWrapper.swift b/Sources/Orbit/Support/SelectableLabelWrapper.swift index 554c16224a0..12f6e861be5 100644 --- a/Sources/Orbit/Support/SelectableLabelWrapper.swift +++ b/Sources/Orbit/Support/SelectableLabelWrapper.swift @@ -26,15 +26,6 @@ struct SelectableLabelWrapper: UIViewRepresentable { } } -struct SelectableLabelWrapper_Previews: PreviewProvider { - - static var previews: some View { - SelectableLabelWrapper(text: "my cool label") - .fixedSize() - .previewLayout(.sizeThatFits) - } -} - private final class SelectableLabel: UILabel { /// Allows override default behaviour (copying whole text) by for example copying only part of it diff --git a/Sources/Orbit/Support/Storybook/TutorialScreen.swift b/Sources/Orbit/Support/Storybook/TutorialScreen.swift index 1eb4b8d39dd..3eea57668dd 100644 --- a/Sources/Orbit/Support/Storybook/TutorialScreen.swift +++ b/Sources/Orbit/Support/Storybook/TutorialScreen.swift @@ -8,7 +8,7 @@ struct TutorialScreen: View { var body: some View { VStack(spacing: .large) { - Card("Card Title", action: .buttonLink("Edit", action: {})) { + Card("Card Title", action: .buttonLink("Edit")) { List { ListItem("List Item 1", icon: .check, style: .custom(textColor: .greenNormal)) ListItem("List Item 2 with a TextLink", icon: .check) diff --git a/Sources/Orbit/WIP/Obsolete/MessageType.swift b/Sources/Orbit/WIP/Obsolete/MessageType.swift index 875eba40fdb..ecc57c7664b 100644 --- a/Sources/Orbit/WIP/Obsolete/MessageType.swift +++ b/Sources/Orbit/WIP/Obsolete/MessageType.swift @@ -10,34 +10,25 @@ public enum MessageType: Equatable, Hashable, CustomStringConvertible { public var description: String { switch self { - case .none: - return "" - case .normal(let text, _): - return text - case .help(let text, _): - return text - case .warning(let text, _): - return text - case .error(let text, _): - return text + case .none: return "" + case .normal(let text, _): return text + case .help(let text, _): return text + case .warning(let text, _): return text + case .error(let text, _): return text } } public var isEmphasis: Bool { switch self { - case .error, .help, .warning: - return true - case .none, .normal: - return false + case .error, .help, .warning: return true + case .none, .normal: return false } } public var isError: Bool { switch self { - case .error: - return true - case .none, .normal, .help, .warning: - return false + case .error: return true + case .none, .normal, .help, .warning: return false } } From beff6b01c186e05d99a832dc6bb33e20383954de Mon Sep 17 00:00:00 2001 From: Pavel Holec Date: Sat, 19 Mar 2022 15:37:15 +0100 Subject: [PATCH 4/6] Add hairline width to CGFloat extension. --- Sources/Orbit/Foundation/Borders/BorderWidth.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/Orbit/Foundation/Borders/BorderWidth.swift b/Sources/Orbit/Foundation/Borders/BorderWidth.swift index dc0a515ce32..516afdc2935 100644 --- a/Sources/Orbit/Foundation/Borders/BorderWidth.swift +++ b/Sources/Orbit/Foundation/Borders/BorderWidth.swift @@ -12,3 +12,8 @@ public enum BorderWidth { /// 2 pts border width used for actively selected component. public static let selection: CGFloat = 2.0 } + +public extension CGFloat { + /// 1 pixel + static let hairline: CGFloat = BorderWidth.hairline +} From 3e4aa30175ee4e9c60eafc4f90a51837739ddc7e Mon Sep 17 00:00:00 2001 From: Pavel Holec Date: Mon, 21 Mar 2022 09:47:59 +0100 Subject: [PATCH 5/6] Fix form field spacings. --- Sources/Orbit/Components/InputField.swift | 8 +++++--- Sources/Orbit/Components/Select.swift | 2 +- Sources/Orbit/Support/Forms/FormFieldLabel.swift | 2 +- Sources/Orbit/Support/Forms/FormFieldMessage.swift | 5 ++--- Sources/Orbit/Support/Views/TagGroup.swift | 2 +- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Sources/Orbit/Components/InputField.swift b/Sources/Orbit/Components/InputField.swift index d30ec039903..8fc0804483e 100644 --- a/Sources/Orbit/Components/InputField.swift +++ b/Sources/Orbit/Components/InputField.swift @@ -32,7 +32,7 @@ public struct InputField: View { let suffixAction: () -> Void public var body: some View { - VStack(alignment: .leading, spacing: 1) { + VStack(alignment: .leading, spacing: .xxSmall) { FormFieldLabel(label) InputContent( @@ -50,8 +50,10 @@ public struct InputField: View { } } - ContentHeightReader(height: $messageHeight.animation(.easeOut(duration: 0.2))) { - FormFieldMessage(message) + if message.isEmpty == false { + ContentHeightReader(height: $messageHeight.animation(.easeOut(duration: 0.2))) { + FormFieldMessage(message) + } } } } diff --git a/Sources/Orbit/Components/Select.swift b/Sources/Orbit/Components/Select.swift index edf65bc6206..f9db06ffa7d 100644 --- a/Sources/Orbit/Components/Select.swift +++ b/Sources/Orbit/Components/Select.swift @@ -22,7 +22,7 @@ public struct Select: View { let action: () -> Void public var body: some View { - VStack(alignment: .leading, spacing: 1) { + VStack(alignment: .leading, spacing: .xxSmall) { FormFieldLabel(label) SwiftUI.Button( diff --git a/Sources/Orbit/Support/Forms/FormFieldLabel.swift b/Sources/Orbit/Support/Forms/FormFieldLabel.swift index 4679822a23a..398356bab48 100644 --- a/Sources/Orbit/Support/Forms/FormFieldLabel.swift +++ b/Sources/Orbit/Support/Forms/FormFieldLabel.swift @@ -7,7 +7,7 @@ public struct FormFieldLabel: View { public var body: some View { Text(label, size: .normal, weight: .medium) - .padding(.bottom, .xxSmall) + .padding(.bottom, 1) } public init(_ label: String) { diff --git a/Sources/Orbit/Support/Forms/FormFieldMessage.swift b/Sources/Orbit/Support/Forms/FormFieldMessage.swift index 19003e2d786..909038bd642 100644 --- a/Sources/Orbit/Support/Forms/FormFieldMessage.swift +++ b/Sources/Orbit/Support/Forms/FormFieldMessage.swift @@ -12,15 +12,14 @@ public struct FormFieldMessage: View { Icon(message.icon, size: .small, color: message.color) Text(message.description, color: .custom(message.uiColor)) .alignmentGuide(.firstTextBaseline) { _ in - Text.Size.normal.value * 1.2 + Text.Size.small.value * 1.1 } } - .padding(.top, .xxSmall) .transition(.opacity.animation(.easeOut(duration: 0.2))) } } - public init(_ message: MessageType, spacing: CGFloat = 5) { + public init(_ message: MessageType, spacing: CGFloat = .xxSmall) { self.message = message self.spacing = spacing } diff --git a/Sources/Orbit/Support/Views/TagGroup.swift b/Sources/Orbit/Support/Views/TagGroup.swift index 1f169f713a8..31672e9ade5 100644 --- a/Sources/Orbit/Support/Views/TagGroup.swift +++ b/Sources/Orbit/Support/Views/TagGroup.swift @@ -28,7 +28,7 @@ public struct TagGroup: View { private let layout: Layout public var body: some View { - VStack(alignment: .leading, spacing: 1) { + VStack(alignment: .leading, spacing: .xxSmall) { if label.isEmpty == false { FormFieldLabel(label) } From 71384c2d9e824e114d9033df69800c10fae3cbb9 Mon Sep 17 00:00:00 2001 From: Pavel Holec Date: Mon, 21 Mar 2022 09:51:16 +0100 Subject: [PATCH 6/6] Remove description from Label component, fix icon size calculation. --- Sources/Orbit/Components/Alert.swift | 21 +- Sources/Orbit/Components/BadgeList.swift | 2 +- Sources/Orbit/Components/Card.swift | 30 +- Sources/Orbit/Components/ChoiceTile.swift | 32 +- Sources/Orbit/Components/CountryFlag.swift | 24 +- Sources/Orbit/Components/Dialog.swift | 2 +- Sources/Orbit/Components/Heading.swift | 49 +-- Sources/Orbit/Components/Icon.swift | 269 ++++++++++++-- Sources/Orbit/Components/List.swift | 20 +- Sources/Orbit/Components/ListChoice.swift | 36 +- Sources/Orbit/Components/ListItem.swift | 58 +-- Sources/Orbit/Components/Text.swift | 6 +- Sources/Orbit/Components/Tile.swift | 28 +- Sources/Orbit/Components/Toast.swift | 3 +- .../Support/Storybook/TutorialScreen.swift | 2 +- Sources/Orbit/Support/Views/Label.swift | 330 ++++++++++-------- 16 files changed, 584 insertions(+), 328 deletions(-) diff --git a/Sources/Orbit/Components/Alert.swift b/Sources/Orbit/Components/Alert.swift index e0389a73487..f7d21e20ec3 100644 --- a/Sources/Orbit/Components/Alert.swift +++ b/Sources/Orbit/Components/Alert.swift @@ -42,19 +42,17 @@ public struct Alert: View { let content: () -> Content public var body: some View { - VStack(alignment: .leading, spacing: .medium) { + HStack(alignment: .firstTextBaseline, spacing: .xSmall) { - Label( - title, - description: description, - iconContent: .icon(icon, color: status.color), - titleStyle: .text(weight: .bold), - descriptionStyle: .custom(.normal, color: .inkNormal, linkColor: .inkNormal), - descriptionSpacing: .xxSmall, - descriptionLinkAction: descriptionLinkAction - ) + Icon(icon, size: .normal, color: status.color) - Group { + VStack(alignment: .leading, spacing: .medium) { + + VStack(alignment: .leading, spacing: .xxSmall) { + Text(title, weight: .bold) + Text(description, linkColor: .inkNormal, linkAction: descriptionLinkAction) + } + content() switch buttons { @@ -65,7 +63,6 @@ public struct Alert: View { EmptyView() } } - .padding(.leading, icon == .none ? 0 : 28) } .frame(maxWidth: .infinity, alignment: .leading) .padding([.vertical, .trailing], .medium) diff --git a/Sources/Orbit/Components/BadgeList.swift b/Sources/Orbit/Components/BadgeList.swift index 56148beb100..dde5d067327 100644 --- a/Sources/Orbit/Components/BadgeList.swift +++ b/Sources/Orbit/Components/BadgeList.swift @@ -26,7 +26,7 @@ public struct BadgeList: View { Icon(iconContent, size: .small) ) .alignmentGuide(.firstTextBaseline) { size in - Text.Size.small.value * Text.firstBaselineRatio + size.height / 2 + Text.Size.small.lineHeight * Text.firstBaselineRatio + size.height / 2 } Text( diff --git a/Sources/Orbit/Components/Card.swift b/Sources/Orbit/Components/Card.swift index 16de501b241..87b9c5c9949 100644 --- a/Sources/Orbit/Components/Card.swift +++ b/Sources/Orbit/Components/Card.swift @@ -40,7 +40,7 @@ public struct Card: View { let contentLayout: CardContentLayout let contentAlignment: HorizontalAlignment let borderStyle: TileBorderStyle - let titleStyle: Label.TitleStyle + let titleStyle: Heading.Style let status: Status? let width: ContainerWidth let backgroundColor: Color? @@ -71,16 +71,15 @@ public struct Card: View { @ViewBuilder var header: some View { if isHeaderEmpty == false { - HStack(alignment: .firstTextBaseline, spacing: .small) { - - Label( - title, - description: description, - iconContent: iconContent, - titleStyle: titleStyle, - iconSpacing: .xSmall, - descriptionSpacing: .xxSmall - ) + HStack(alignment: .firstTextBaseline, spacing: 0) { + + Icon(iconContent, size: .heading(titleStyle)) + .padding(.trailing, .xSmall) + + VStack(alignment: .leading, spacing: .xxSmall) { + Heading(title, style: titleStyle) + Text(description, color: .inkLight) + } if case .expanding = width { Spacer(minLength: .xxxSmall) @@ -172,7 +171,7 @@ public extension Card { action: CardAction = .none, headerSpacing: CGFloat = .medium, borderStyle: TileBorderStyle = .iOS, - titleStyle: Label.TitleStyle = .title4, + titleStyle: Heading.Style = .title4, status: Status? = nil, width: ContainerWidth = .expanding(), backgroundColor: Color? = .white, @@ -203,7 +202,7 @@ public extension Card { action: CardAction = .none, headerSpacing: CGFloat = .medium, borderStyle: TileBorderStyle = .iOS, - titleStyle: Label.TitleStyle = .title4, + titleStyle: Heading.Style = .title4, status: Status? = nil, width: ContainerWidth = .expanding(), backgroundColor: Color? = .white @@ -231,7 +230,7 @@ public extension Card { action: CardAction = .none, headerSpacing: CGFloat = .medium, borderStyle: TileBorderStyle = .iOS, - titleStyle: Label.TitleStyle = .title4, + titleStyle: Heading.Style = .title4, status: Status? = nil, width: ContainerWidth = .expanding(), backgroundColor: Color? = .white, @@ -262,7 +261,7 @@ public extension Card { action: CardAction = .none, headerSpacing: CGFloat = .medium, borderStyle: TileBorderStyle = .iOS, - titleStyle: Label.TitleStyle = .title4, + titleStyle: Heading.Style = .title4, status: Status? = nil, width: ContainerWidth = .expanding(), backgroundColor: Color? = .white @@ -444,6 +443,7 @@ struct CardPreviews: PreviewProvider { Card( "Very very very long and multi-line title", description: "Very very very very very long and multi-line description", + icon: .grid, action: .buttonLink("Update"), borderStyle: .default, status: .critical diff --git a/Sources/Orbit/Components/ChoiceTile.swift b/Sources/Orbit/Components/ChoiceTile.swift index cc0463c1816..55fb98a56c9 100644 --- a/Sources/Orbit/Components/ChoiceTile.swift +++ b/Sources/Orbit/Components/ChoiceTile.swift @@ -103,7 +103,7 @@ public struct ChoiceTile: View { let iconContent: Icon.Content let illustration: Illustration.Image let indicator: ChoiceTileIndicator - let titleStyle: Label.TitleStyle + let titleStyle: Heading.Style let isSelected: Bool let isError: Bool let message: MessageType @@ -138,20 +138,29 @@ public struct ChoiceTile: View { switch alignment { case .default: HStack(alignment: .firstTextBaseline, spacing: 0) { - Label(title, description: description, iconContent: iconContent, titleStyle: titleStyle) + + Icon(iconContent, size: .heading(titleStyle)) + .padding(.trailing, .xSmall) + + VStack(alignment: .leading, spacing: .xxSmall) { + Heading(title, style: titleStyle) + Text(description, color: .inkLight) + } + Spacer(minLength: 0) + Badge(badge, style: .status(.info)) } case .center: VStack(spacing: .xxSmall) { if illustration == .none { - Icon(iconContent, size: .label(titleStyle)) + Icon(iconContent, size: .heading(titleStyle)) .padding(.bottom, .xxxSmall) } else { Illustration(illustration, layout: .resizeable) .frame(height: .xxLarge) } - centeredHeading + Heading(title, style: titleStyle, alignment: .center) Text(description, color: .inkLight, alignment: .center) Badge(badge, style: .neutral) } @@ -160,13 +169,6 @@ public struct ChoiceTile: View { } } - @ViewBuilder var centeredHeading: some View { - switch titleStyle { - case .heading(let style, let color): Heading(title, style: style, color: color, alignment: .center) - case .text(let size, let weight, let color): Text(title, size: size, color: color, weight: weight) - } - } - @ViewBuilder var messageView: some View { switch alignment { case .default: @@ -222,7 +224,7 @@ public extension ChoiceTile { badge: String = "", badgeOverlay: String = "", indicator: ChoiceTileIndicator = .radio, - titleStyle: Label.TitleStyle = .title3, + titleStyle: Heading.Style = .title3, isSelected: Bool = false, isError: Bool = false, message: MessageType = .none, @@ -255,7 +257,7 @@ public extension ChoiceTile { badge: String = "", badgeOverlay: String = "", indicator: ChoiceTileIndicator = .radio, - titleStyle: Label.TitleStyle = .title3, + titleStyle: Heading.Style = .title3, isSelected: Bool = false, isError: Bool = false, message: MessageType = .none, @@ -427,7 +429,7 @@ struct ChoiceTilePreviews: PreviewProvider { ChoiceTile( "Multiline long choice title label", - description: "Multiline and very long description", + description: "Multiline and very very very long description", icon: .grid, titleStyle: .title1, message: .help("Helpful multiline message") @@ -469,7 +471,7 @@ struct ChoiceTilePreviews: PreviewProvider { ChoiceTile( "Multiline long choice title label", - description: "Multiline and very long description", + description: "Multiline and very very very long description", icon: .grid, titleStyle: .title1, message: .help("Helpful multiline message"), diff --git a/Sources/Orbit/Components/CountryFlag.swift b/Sources/Orbit/Components/CountryFlag.swift index 335dc72f791..64b57ea9cd4 100644 --- a/Sources/Orbit/Components/CountryFlag.swift +++ b/Sources/Orbit/Components/CountryFlag.swift @@ -27,11 +27,14 @@ public struct CountryFlag: View { } var clipShape: some InsettableShape { + RoundedRectangle(cornerRadius: cornerRadius) + } + + var cornerRadius: CGFloat { switch border { - case .none: - return RoundedRectangle(cornerRadius: 0) - case .default: - return RoundedRectangle(cornerRadius: size.value / 10) + case .none: return 0 + case .default(let cornerRadius?): return cornerRadius + case .default: return size.value / 10 } } } @@ -40,7 +43,7 @@ public struct CountryFlag: View { public extension CountryFlag { /// Creates Orbit CountryFlag component. - init(_ countryCode: String, size: Icon.Size = .normal, border: Border = .default) { + init(_ countryCode: String, size: Icon.Size = .normal, border: Border = .default()) { self.countryCode = countryCode self.size = size self.border = border @@ -52,7 +55,7 @@ public extension CountryFlag { enum Border { case none - case `default` + case `default`(cornerRadius: CGFloat? = nil) var color: Color { switch self { @@ -68,7 +71,15 @@ struct CountryFlagPreviews: PreviewProvider { static var previews: some View { PreviewWrapper { + snapshots + } + .previewLayout(.sizeThatFits) + } + + static var snapshots: some View { + VStack { CountryFlag("cz") + CountryFlag("cz", border: .default(cornerRadius: 10)) CountryFlag("cz", border: .none) CountryFlag("sg") CountryFlag("jp") @@ -78,6 +89,5 @@ struct CountryFlagPreviews: PreviewProvider { CountryFlag("unknown", size: .xLarge) } .padding() - .previewLayout(.sizeThatFits) } } diff --git a/Sources/Orbit/Components/Dialog.swift b/Sources/Orbit/Components/Dialog.swift index d0d194586a7..1373370b760 100644 --- a/Sources/Orbit/Components/Dialog.swift +++ b/Sources/Orbit/Components/Dialog.swift @@ -20,7 +20,7 @@ public struct Dialog: View { } VStack(alignment: .center, spacing: .xSmall) { - Heading(title, style: .title3, alignment: .center) + Heading(title, style: .title4, alignment: .center) Text(description, color: .inkLight, alignment: .center) } diff --git a/Sources/Orbit/Components/Heading.swift b/Sources/Orbit/Components/Heading.swift index e83c2b6607b..295e0bab75e 100644 --- a/Sources/Orbit/Components/Heading.swift +++ b/Sources/Orbit/Components/Heading.swift @@ -110,8 +110,14 @@ public extension Heading { public var iconSize: CGFloat { switch self { + case .display: return 52 + case .displaySubtitle: return 30 + case .title1: return 38 + case .title2: return 30 + case .title3: return 26 case .title4: return 22 - default: return lineHeight + case .title5: return 20 + case .title6: return 18 } } @@ -130,12 +136,8 @@ struct HeadingPreviews: PreviewProvider { static var previews: some View { PreviewWrapper { standalone - - Label("Heading", icon: .grid, titleStyle: .heading(.title1, color: nil)) - .foregroundColor(.blueNormal) - .previewDisplayName("Label with color override") - snapshots + snapshotsMultiline } .previewLayout(.sizeThatFits) } @@ -160,24 +162,23 @@ struct HeadingPreviews: PreviewProvider { } static var snapshots: some View { - Group { - orbit - .padding(.vertical) - - VStack(alignment: .leading, spacing: .xSmall) { - Label("Display title, but very very very very very very very long", icon: .circle, titleStyle: .display) - Label("Display subtitle, also very very very very very long", icon: .email, titleStyle: .displaySubtitle) - Separator() - Label("Title 1, also very very very very very very very verylong", icon: .circle, titleStyle: .title1) - Label("Title 2, but very very very very very very very very long", icon: .circle, titleStyle: .title2) - Label("Title 3, but very very very very very very very very long", icon: .circle, titleStyle: .title3) - Label("Title 4, but very very very very very very very very long", icon: .circle, titleStyle: .title4) - Label("Title 5, but very very very very very very very very long", icon: .circle, titleStyle: .title5) - Label("Title 6, but very very very very very very very very long", icon: .circle, titleStyle: .title6) - } - .padding(.vertical) - .previewDisplayName("Longer text with icon using Label") + orbit + .padding() + } + + static var snapshotsMultiline: some View { + VStack(alignment: .leading, spacing: .xSmall) { + Heading("Display title, but very very very very very very very long", style: .display) + Heading("Display subtitle, also very very very very very long", style: .displaySubtitle) + Separator() + Heading("Title 1, also very very very very very very very verylong", style: .title1) + Heading("Title 2, but very very very very very very very very long", style: .title2) + Heading("Title 3, but very very very very very very very very long", style: .title3) + Heading("Title 4, but very very very very very very very very long", style: .title4) + Heading("Title 5, but very very very very very very very very long", style: .title5) + Heading("Title 6, but very very very very very very very very long", style: .title6) } - .padding(.horizontal) + .padding() + .previewDisplayName("Multiline") } } diff --git a/Sources/Orbit/Components/Icon.swift b/Sources/Orbit/Components/Icon.swift index 2f10f97cee8..9d2cb61f5ad 100644 --- a/Sources/Orbit/Components/Icon.swift +++ b/Sources/Orbit/Components/Icon.swift @@ -22,16 +22,28 @@ public struct Icon: View { SwiftUI.Text(verbatim: symbol.value) .foregroundColor(color) .font(.orbitIcon(size: size.value)) + .alignmentGuide(.firstTextBaseline) { dimensions in + size.textLineHeight * Text.firstBaselineRatio + dimensions.height / 2 + } case .image(let image, let mode): image .resizable() .aspectRatio(contentMode: mode) .frame(width: size.value, height: size.value) + .alignmentGuide(.firstTextBaseline) { dimensions in + size.textLineHeight * Text.firstBaselineRatio + dimensions.height / 2 + } case .countryFlag(let countryCode): CountryFlag(countryCode, size: size) + .alignmentGuide(.firstTextBaseline) { dimensions in + size.textLineHeight * Text.firstBaselineRatio + dimensions.height / 2 + } case .sfSymbol(let systemName): Image(systemName: systemName) - .font(.system(size: size.value - 2 * Self.averagePadding)) + .font(.system(size: size.value * 0.85)) + .alignmentGuide(.firstTextBaseline) { dimensions in + size.textLineHeight * Text.firstBaselineRatio + dimensions.height / 2 + } case .none: EmptyView() } @@ -116,8 +128,12 @@ public extension Icon { case xLarge /// Size based on Font size. case fontSize(CGFloat) + /// Size based on `Text` size. + case text(Text.Size) + /// Size based on `Heading` style. + case heading(Heading.Style) /// Size based on `Label` title style. - case label(Label.TitleStyle) + case label(Label.Style) /// Custom size case custom(CGFloat) @@ -127,7 +143,9 @@ public extension Icon { case .normal: return 20 case .large: return 24 case .xLarge: return 28 - case .fontSize(let size): return size + 1 + case .fontSize(let size): return size * 1.31 + case .text(let size): return size.iconSize + case .heading(let style): return style.iconSize case .label(let style): return style.iconSize case .custom(let size): return size } @@ -136,15 +154,37 @@ public extension Icon { public static func == (lhs: Icon.Size, rhs: Icon.Size) -> Bool { lhs.value == rhs.value } + + /// Default text line height for icon size. + public var textLineHeight: CGFloat { + switch self { + case .small: return Text.Size.small.iconSize + case .normal: return Text.Size.normal.iconSize + case .large: return Text.Size.large.iconSize + case .xLarge: return Text.Size.xLarge.iconSize + case .fontSize(let size): return size * 1.31 + case .text(let size): return size.iconSize + case .heading(let style): return style.iconSize + case .label(let style): return style.iconSize + case .custom(let size): return size + } + } } } // MARK: - Previews struct IconPreviews: PreviewProvider { + static let sfSymbol = "info.circle.fill" + static var previews: some View { PreviewWrapper { standalone + snapshotSizes + snapshotSizesText + snapshotSizesLabelText + snapshotSizesHeading + snapshotSizesLabelHeading snapshots } .previewLayout(.sizeThatFits) @@ -155,49 +195,214 @@ struct IconPreviews: PreviewProvider { } static var orbit: some View { - snapshots + snapshotSizes + } + + static var snapshotSizes: some View { + VStack(alignment: .leading, spacing: .small) { + HStack(spacing: .xSmall) { + Text("16", color: .custom(.redNormal)) + + HStack(alignment: .firstTextBaseline, spacing: .xxSmall) { + Icon(.passengers, size: .small) + Text("Lorem ipsum", size: .small) + } + .overlay(HairlineSeparator(), alignment: .top) + .overlay(HairlineSeparator(), alignment: .bottom) + } + HStack(spacing: .xSmall) { + Text("20", color: .custom(.orangeNormal)) + + HStack(alignment: .firstTextBaseline, spacing: .xxSmall) { + Icon(.passengers, size: .normal) + Text("Lorem ipsum", size: .normal) + } + .overlay(HairlineSeparator(), alignment: .top) + .overlay(HairlineSeparator(), alignment: .bottom) + } + HStack(spacing: .xSmall) { + Text("24", color: .custom(.greenNormal)) + + HStack(alignment: .firstTextBaseline, spacing: .xxSmall) { + Icon(.passengers, size: .large) + Text("Lorem ipsum", size: .large) + } + .overlay(HairlineSeparator(), alignment: .top) + .overlay(HairlineSeparator(), alignment: .bottom) + } + } + .padding() + .previewDisplayName("Default sizes") + } + + static func headingStack(_ style: Heading.Style) -> some View { + HStack(spacing: .xSmall) { + HStack(alignment: .firstTextBaseline, spacing: .xxSmall) { + Icon(.passengers, size: .heading(style)) + Heading("Heading", style: style) + } + .overlay(HairlineSeparator(), alignment: .top) + .overlay(HairlineSeparator(), alignment: .bottom) + } + } + + static func labelHeadingStack(_ style: Heading.Style) -> some View { + HStack(spacing: .xSmall) { + Label("Label Heading", icon: .passengers, style: .heading(style)) + .overlay(HairlineSeparator(), alignment: .top) + .overlay(HairlineSeparator(), alignment: .bottom) + } + } + + static func labelTextStack(_ size: Text.Size) -> some View { + HStack(spacing: .xSmall) { + Label("Label Text", icon: .passengers, style: .text(size)) + .overlay(HairlineSeparator(), alignment: .top) + .overlay(HairlineSeparator(), alignment: .bottom) + } + } + + static func textStack(_ size: Text.Size) -> some View { + HStack(spacing: .xSmall) { + HStack(alignment: .firstTextBaseline, spacing: .xxSmall) { + Icon(.passengers, size: .text(size)) + Text("Text", size: size) + } + .overlay(HairlineSeparator(), alignment: .top) + .overlay(HairlineSeparator(), alignment: .bottom) + } + } + + static var snapshotSizesText: some View { + VStack(alignment: .leading, spacing: .small) { + textStack(.small) + textStack(.normal) + textStack(.large) + textStack(.xLarge) + textStack(.custom(50)) + } + .padding() + .previewDisplayName("Calculated sizes for Text") + } + + static var snapshotSizesLabelText: some View { + VStack(alignment: .leading, spacing: .small) { + labelTextStack(.small) + labelTextStack(.normal) + labelTextStack(.large) + labelTextStack(.xLarge) + labelTextStack(.custom(50)) + } + .padding() + .previewDisplayName("Calculated sizes for Text in Label") + } + + static var snapshotSizesHeading: some View { + VStack(alignment: .leading, spacing: .small) { + headingStack(.title6) + headingStack(.title5) + headingStack(.title4) + headingStack(.title3) + headingStack(.title2) + headingStack(.title1) + headingStack(.displaySubtitle) + headingStack(.display) + } + .padding() + .previewDisplayName("Calculated sizes for Heading") + } + + static var snapshotSizesLabelHeading: some View { + VStack(alignment: .leading, spacing: .small) { + labelHeadingStack(.title6) + labelHeadingStack(.title5) + labelHeadingStack(.title4) + labelHeadingStack(.title3) + labelHeadingStack(.title2) + labelHeadingStack(.title1) + labelHeadingStack(.displaySubtitle) + labelHeadingStack(.display) + } + .padding() + .previewDisplayName("Calculated sizes for Heading in Label") } static var snapshots: some View { VStack(spacing: .small) { - HStack { - Icon(.flightNomad, size: .xLarge) - Icon(.flightNomad) - Icon(.flightNomad, size: .small) + HStack(alignment: .firstTextBaseline, spacing: 0) { + Group { + Icon(.sfSymbol(sfSymbol), size: .custom(Text.Size.xLarge.iconSize)) + Icon(.sfSymbol(sfSymbol), size: .fontSize(Text.Size.xLarge.value)) + Icon(.sfSymbol(sfSymbol), size: .label(.text(.xLarge))) + Icon(.informationCircle, size: .custom(Text.Size.xLarge.iconSize), color: nil) + Icon(.informationCircle, size: .fontSize(Text.Size.xLarge.value), color: nil) + Icon(.informationCircle, size: .label(.text(.xLarge)), color: nil) + Text("XLarge", size: .xLarge, color: nil) + } + .foregroundColor(.blueNormal) + .border(Color.cloudLightActive, width: .hairline) } + .background(HairlineSeparator(), alignment: .init(horizontal: .center, vertical: .firstTextBaseline)) - HStack { - Icon(.informationCircle, size: .xLarge, color: .inkLighter) - Icon(.informationCircle, color: .inkLighter) - Icon(.informationCircle, size: .small, color: .inkLighter) + HStack(alignment: .firstTextBaseline, spacing: 0) { + Group { + Icon(.sfSymbol(sfSymbol), size: .custom(Text.Size.small.iconSize)) + Icon(.sfSymbol(sfSymbol), size: .fontSize(Text.Size.small.value)) + Icon(.sfSymbol(sfSymbol), size: .label(.text(.small))) + Icon(.informationCircle, size: .custom(Text.Size.small.iconSize), color: nil) + Icon(.informationCircle, size: .fontSize(Text.Size.small.value), color: nil) + Icon(.informationCircle, size: .label(.text(.small)), color: nil) + Text("Small", size: .small, color: nil) + } + .foregroundColor(.blueNormal) + .border(Color.cloudLightActive, width: .hairline) } + .background(HairlineSeparator(), alignment: .init(horizontal: .center, vertical: .firstTextBaseline)) - HStack { - Icon(.grid, size: .xLarge, color: nil) - Icon(.grid) - Icon(.grid, size: .small, color: .red) + HStack(alignment: .firstTextBaseline, spacing: 0) { + Group { + Icon(.countryFlag("cz"), size: .xLarge) + Icon(.image(.orbit(.facebook)), size: .xLarge) + Icon(.sfSymbol(sfSymbol), size: .xLarge) + Text("Text", size: .custom(20), color: nil) + } + .foregroundColor(.blueNormal) + .border(Color.cloudLightActive, width: .hairline) } - .foregroundColor(.blueDark) + .background(HairlineSeparator(), alignment: .init(horizontal: .center, vertical: .firstTextBaseline)) - Separator() + HStack(alignment: .firstTextBaseline, spacing: 0) { + Group { + Icon(.countryFlag("cz"), size: .small) + Icon(.image(.orbit(.facebook)), size: .small) + Icon(.sfSymbol(sfSymbol), size: .small) + Text("Text", size: .small, color: nil) + } + .foregroundColor(.blueNormal) + .border(Color.cloudLightActive, width: .hairline) + } + .background(HairlineSeparator(), alignment: .init(horizontal: .center, vertical: .firstTextBaseline)) - HStack { - Icon(image: .orbit(.facebook)) - Icon(countryCode: "cz", size: .large) + HStack(alignment: .firstTextBaseline) { + Group { + Text("O", size: .custom(30)) + Icon(.informationCircle, size: .fontSize(30)) + Icon(.informationCircle, size: .fontSize(8)) + Text("O", size: .custom(8)) + } + .border(Color.cloudLightActive, width: .hairline) } + .background(HairlineSeparator(), alignment: .init(horizontal: .center, vertical: .firstTextBaseline)) - HStack(spacing: .xxSmall) { - Icon(sfSymbol: "info.circle.fill", size: .normal) - .foregroundColor(.greenNormal) - Icon(.informationCircle, size: .normal, color: nil) - .foregroundColor(.greenNormal) - Icon(sfSymbol: "info.circle.fill", size: .xLarge) - .foregroundColor(.greenNormal) - Icon(.informationCircle, size: .xLarge, color: nil) - .foregroundColor(.greenNormal) + HStack(alignment: .firstTextBaseline) { + Icon(.grid, size: .xLarge, color: nil) + Icon(.grid) + Icon(.grid, size: .small, color: .red) } + .foregroundColor(.blueDark) + .background(HairlineSeparator(), alignment: .init(horizontal: .center, vertical: .firstTextBaseline)) } - .frame(width: 120) - .padding() + .padding(.xSmall) + .previewDisplayName("firstTextBaseline alignment") } } diff --git a/Sources/Orbit/Components/List.swift b/Sources/Orbit/Components/List.swift index dd940dd6671..aaf29492fbe 100644 --- a/Sources/Orbit/Components/List.swift +++ b/Sources/Orbit/Components/List.swift @@ -14,7 +14,7 @@ public struct List: View { let content: () -> Content public var body: some View { - VStack(alignment: .listAlignment, spacing: spacing) { + VStack(alignment: .listTextLeading, spacing: spacing) { content() } } @@ -26,15 +26,10 @@ public struct List: View { } } -extension HorizontalAlignment { +// MARK: - Alignment +public extension HorizontalAlignment { - enum ListAlignment: AlignmentID { - static func defaultValue(in context: ViewDimensions) -> CGFloat { - context[.leading] - } - } - - static let listAlignment = HorizontalAlignment(ListAlignment.self) + static let listTextLeading = Self.labelTextLeading } // MARK: - Previews @@ -61,12 +56,13 @@ struct ListPreviews: PreviewProvider { static var snapshots: some View { Group { List { - ListItem("This is just a normal line", iconContent: .icon(.airplaneDown, color: .green)) - ListItem("This is just a normal line", iconContent: .icon(.chat, color: .inkNormal)) - ListItem("This is just a normal line", iconContent: .icon(.accountCircle, color: .orange)) + ListItem("This is just a normal a normal a normal a normal line just a normal line", iconContent: .icon(.airplaneDown, color: .green), size: .custom(20)) + ListItem("This is just a normal just a normal linejust a normal linejust a normal lineline", iconContent: .icon(.chat, color: .inkNormal)) + ListItem("This is just a normal line just a normal line just a normal line", iconContent: .icon(.accountCircle, color: .orange)) ListItem("This is just a normal line", iconContent: .icon(.document, color: .blue)) ListItem("This is just a normal line", icon: .none) } + .border(.red) .padding() List { diff --git a/Sources/Orbit/Components/ListChoice.swift b/Sources/Orbit/Components/ListChoice.swift index d641bfb9b28..a5f3b6ef71f 100644 --- a/Sources/Orbit/Components/ListChoice.swift +++ b/Sources/Orbit/Components/ListChoice.swift @@ -68,16 +68,19 @@ public struct ListChoice: View { } @ViewBuilder var header: some View { - Label( - title, - description: description, - iconContent: icon, - titleStyle: .text(weight: .medium), - descriptionStyle: .custom(.small), - iconSpacing: .xSmall, - descriptionSpacing: .xxxSmall - ) - .padding(.vertical, .small) + if isHeaderEmpty == false { + HStack(alignment: .firstTextBaseline, spacing: .xSmall) { + Icon(icon) + + if isHeaderTextEmpty == false { + VStack(alignment: .labelTextLeading, spacing: .xxxSmall) { + Text(title, weight: .medium) + Text(description, size: .small, color: .inkLight) + } + } + } + .padding(.vertical, .small) + } } @ViewBuilder var disclosureView: some View { @@ -124,7 +127,11 @@ public struct ListChoice: View { } var isHeaderEmpty: Bool { - icon.isEmpty && title.isEmpty && description.isEmpty + icon.isEmpty && isHeaderTextEmpty + } + + var isHeaderTextEmpty: Bool { + title.isEmpty && description.isEmpty } var accessibilityTraitsToAdd: AccessibilityTraits { @@ -329,8 +336,9 @@ struct ListChoicePreviews: PreviewProvider { ListChoice(disclosure: .none) { customContentPlaceholder } - .padding(.bottom) } + .padding(.vertical) + .background(Color.cloudLight) .previewDisplayName("No disclosure") } @@ -351,6 +359,8 @@ struct ListChoicePreviews: PreviewProvider { badge } } + .padding(.vertical) + .background(Color.cloudLight) .previewDisplayName("Chevron") } @@ -387,6 +397,8 @@ struct ListChoicePreviews: PreviewProvider { customContentPlaceholder } } + .padding(.vertical) + .background(Color.cloudLight) .previewDisplayName("Checkbox") } diff --git a/Sources/Orbit/Components/ListItem.swift b/Sources/Orbit/Components/ListItem.swift index 85303097bb7..71c76f41514 100644 --- a/Sources/Orbit/Components/ListItem.swift +++ b/Sources/Orbit/Components/ListItem.swift @@ -13,21 +13,15 @@ public struct ListItem: View { let size: Text.Size let spacing: CGFloat let style: ListItem.Style - let linkColor: UIColor let linkAction: TextLink.Action public var body: some View { - HStack(alignment: .firstTextBaseline, spacing: spacing) { - Icon(iconContent, size: .label(.text(size))) - .alignmentGuide(.firstTextBaseline) { size in - self.size.value * Text.firstBaselineRatio + size.height / 2 - } - .alignmentGuide(.listAlignment, computeValue: { dimensions in - dimensions.width + spacing - }) - - Text(text, size: size, color: style.textColor, linkColor: linkColor, linkAction: linkAction) - } + Label( + text, + iconContent: iconContent, + style: .text(size, weight: style.weight, color: style.textColor, linkAction: linkAction), + spacing: spacing + ) } } @@ -39,9 +33,8 @@ public extension ListItem { _ text: String = "", iconContent: Icon.Content, size: Text.Size = .normal, - spacing: CGFloat = .xSmall, + spacing: CGFloat = .xxSmall, style: ListItem.Style = .primary, - linkColor: UIColor = .productDark, linkAction: @escaping TextLink.Action = { _, _ in } ) { self.text = text @@ -49,7 +42,6 @@ public extension ListItem { self.size = size self.spacing = spacing self.style = style - self.linkColor = linkColor self.linkAction = linkAction } @@ -58,9 +50,8 @@ public extension ListItem { _ text: String = "", icon: Icon.Symbol = .circleSmall, size: Text.Size = .normal, - spacing: CGFloat = .xSmall, + spacing: CGFloat = .xxSmall, style: ListItem.Style = .primary, - linkColor: UIColor = .productDark, linkAction: @escaping TextLink.Action = { _, _ in } ) { self.init( @@ -69,7 +60,6 @@ public extension ListItem { size: size, spacing: spacing, style: style, - linkColor: linkColor, linkAction: linkAction ) } @@ -81,13 +71,29 @@ public extension ListItem { enum Style { case primary case secondary - case custom(textColor: UIColor) + case custom(color: UIColor = .inkNormal, linkColor: UIColor = TextLink.defaultColor, weight: Font.Weight = .regular) public var textColor: Text.Color { switch self { - case .primary: return .inkNormal - case .secondary: return .inkLight - case .custom(let textColor): return .custom(textColor) + case .primary: return .inkNormal + case .secondary: return .inkLight + case .custom(let color, _, _): return .custom(color) + } + } + + public var linkColor: UIColor { + switch self { + case .primary: return TextLink.defaultColor + case .secondary: return TextLink.defaultColor + case .custom(_, let linkColor, _): return linkColor + } + } + + public var weight: Font.Weight { + switch self { + case .primary: return .regular + case .secondary: return .regular + case .custom(_, _, let weight): return weight } } } @@ -141,9 +147,9 @@ struct ListItemPreviews: PreviewProvider { static var snapshotsLinks: some View { List { ListItem(#"ListItem containing TextLink or Two"#) - ListItem(#"ListItem containing TextLink or Two"#, size: .small, style: .secondary, linkColor: .redNormal) - ListItem(#"ListItem containing TextLink or Two"#, style: .custom(textColor: .greenNormal)) - ListItem(#"ListItem containing TextLink or Two"#, iconContent: .icon(.circleSmall, color: .inkNormal), style: .custom(textColor: .greenNormal)) + ListItem(#"ListItem containing TextLink or Two"#, size: .small, style: .secondary) + ListItem(#"ListItem containing TextLink or Two"#, style: .custom(color: .greenNormal)) + ListItem(#"ListItem containing TextLink or Two"#, iconContent: .icon(.circleSmall, color: .inkNormal), style: .custom(color: .greenNormal)) } .padding() .previewDisplayName("Snapshots - Links") @@ -154,7 +160,7 @@ struct ListItemPreviews: PreviewProvider { ListItem("ListItem with custom icon", iconContent: .icon(.check, color: .greenNormal)) ListItem("ListItem with custom icon", iconContent: .icon(.check)) ListItem("ListItem with custom icon", icon: .check) - ListItem("ListItem with custom icon", icon: .check, style: .custom(textColor: .blueDark)) + ListItem("ListItem with custom icon", icon: .check, style: .custom(color: .blueDark)) ListItem("ListItem with no icon", icon: .none) } .padding() diff --git a/Sources/Orbit/Components/Text.swift b/Sources/Orbit/Components/Text.swift index d61ff63421e..fcf0f50d46a 100644 --- a/Sources/Orbit/Components/Text.swift +++ b/Sources/Orbit/Components/Text.swift @@ -174,7 +174,7 @@ public extension Text { case .normal: return 20 case .large: return 24 case .xLarge: return 24 - case .custom(let size): return size + 4 + case .custom(let size): return size * 1.31 } } @@ -209,7 +209,9 @@ public extension Text { // MARK: - Constants extension Text { - public static var firstBaselineRatio: CGFloat { 0.35 } + + // Alignment ratio for text size. + public static var firstBaselineRatio: CGFloat { 0.26 } } // MARK: - Previews diff --git a/Sources/Orbit/Components/Tile.swift b/Sources/Orbit/Components/Tile.swift index dc8cc18de79..d5c0159bf67 100644 --- a/Sources/Orbit/Components/Tile.swift +++ b/Sources/Orbit/Components/Tile.swift @@ -64,7 +64,7 @@ public struct Tile: View { let border: TileBorder let status: Status? let backgroundColor: BackgroundColor? - let titleStyle: Label.TitleStyle + let titleStyle: Heading.Style let descriptionColor: Text.Color let action: () -> Void let content: () -> Content @@ -103,15 +103,15 @@ public struct Tile: View { } var header: some View { - HStack(alignment: .firstTextBaseline, spacing: .xxSmall) { - Label( - title, - description: description, - iconContent: iconContent, - titleStyle: titleStyle, - iconSpacing: .small, - descriptionSpacing: .xxSmall - ) + HStack(alignment: .firstTextBaseline, spacing: 0) { + + Icon(iconContent, size: .heading(titleStyle)) + .padding(.trailing, .xSmall) + + VStack(alignment: .labelTextLeading, spacing: .xxSmall) { + Heading(title, style: titleStyle) + Text(description, color: .inkLight) + } .padding(.vertical, .medium) Spacer(minLength: 0) @@ -182,7 +182,7 @@ public extension Tile { border: TileBorder = .default, status: Status? = nil, backgroundColor: BackgroundColor? = nil, - titleStyle: Label.TitleStyle = .title4, + titleStyle: Heading.Style = .title4, descriptionColor: Text.Color = .inkLight, action: @escaping () -> Void = {}, @ViewBuilder content: @escaping () -> Content @@ -212,7 +212,7 @@ public extension Tile { border: TileBorder = .default, status: Status? = nil, backgroundColor: BackgroundColor? = nil, - titleStyle: Label.TitleStyle = .title4, + titleStyle: Heading.Style = .title4, descriptionColor: Text.Color = .inkLight, iconColor: Color = .inkNormal, action: @escaping () -> Void = {}, @@ -243,7 +243,7 @@ public extension Tile { border: TileBorder = .default, status: Status? = nil, backgroundColor: BackgroundColor? = nil, - titleStyle: Label.TitleStyle = .title4, + titleStyle: Heading.Style = .title4, descriptionColor: Text.Color = .inkLight, action: @escaping () -> Void = {} ) where Content == EmptyView { @@ -274,7 +274,7 @@ public extension Tile { border: TileBorder = .default, status: Status? = nil, backgroundColor: BackgroundColor? = nil, - titleStyle: Label.TitleStyle = .title4, + titleStyle: Heading.Style = .title4, descriptionColor: Text.Color = .inkLight, iconColor: Color = .inkNormal, action: @escaping () -> Void = {} diff --git a/Sources/Orbit/Components/Toast.swift b/Sources/Orbit/Components/Toast.swift index 49eb53de0f6..a010f811bd9 100644 --- a/Sources/Orbit/Components/Toast.swift +++ b/Sources/Orbit/Components/Toast.swift @@ -65,7 +65,8 @@ public struct ToastContent: View { Label( description, iconContent: .icon(icon, color: .white), - titleStyle: .text(weight: .regular, color: .white) + style: .text(weight: .regular, color: .white), + spacing: .xSmall ) .padding(.small) diff --git a/Sources/Orbit/Support/Storybook/TutorialScreen.swift b/Sources/Orbit/Support/Storybook/TutorialScreen.swift index 3eea57668dd..4388cb85f34 100644 --- a/Sources/Orbit/Support/Storybook/TutorialScreen.swift +++ b/Sources/Orbit/Support/Storybook/TutorialScreen.swift @@ -10,7 +10,7 @@ struct TutorialScreen: View { VStack(spacing: .large) { Card("Card Title", action: .buttonLink("Edit")) { List { - ListItem("List Item 1", icon: .check, style: .custom(textColor: .greenNormal)) + ListItem("List Item 1", icon: .check, style: .custom(color: .greenNormal)) ListItem("List Item 2 with a TextLink", icon: .check) ListItem("List Item 3") } diff --git a/Sources/Orbit/Support/Views/Label.swift b/Sources/Orbit/Support/Views/Label.swift index 039ae52d536..18d37688c8f 100644 --- a/Sources/Orbit/Support/Views/Label.swift +++ b/Sources/Orbit/Support/Views/Label.swift @@ -1,64 +1,56 @@ import SwiftUI -/// Label that combines text, icon and optional description under text. +/// Label that prefixes text with icon of correct size, vertically aligned on `firstBaseline`. +/// +/// Offers two horizontal alignments: +/// - `labelTextLeading` +/// - `labelIconCenter` +/// +/// - Important: Using above alignments on horizontally expanding content will increase the container size. public struct Label: View { - public static let iconSpacing: CGFloat = .xSmall - public static let descriptionSpacing: CGFloat = .xxxSmall - + public static let defaultSpacing: CGFloat = .xSmall + let title: String - let description: String let iconContent: Icon.Content - let titleStyle: TitleStyle - let descriptionStyle: DescriptionStyle - let iconSpacing: CGFloat - let descriptionSpacing: CGFloat - let descriptionLinkAction: TextLink.Action + let style: Style + let spacing: CGFloat public var body: some View { if isEmpty == false { - HStack(alignment: .firstTextBaseline, spacing: iconSpacing) { - Icon(iconContent, size: .label(titleStyle)) - .alignmentGuide(.firstTextBaseline) { size in - self.titleStyle.size * Text.firstBaselineRatio + size.height / 2 - } - .alignmentGuide(.labelAlignment) { size in - size.width + iconSpacing - } + HStack(alignment: .firstTextBaseline, spacing: spacing) { + Icon(iconContent, size: .label(style)) + .alignmentGuide(.labelIconCenter) { $0[HorizontalAlignment.center] } - if isTextEmpty == false { - VStack(alignment: .leading, spacing: descriptionSpacing) { - titleView - descriptionView - } - } + titleView + .alignmentGuide(.labelTextLeading) { $0[.leading] } } } } @ViewBuilder var titleView: some View { - switch titleStyle { - case .heading(let style, let color): Heading(title, style: style, color: color) - case .text(let size, let weight, let color): Text(title, size: size, color: color, weight: weight) + switch style { + case .heading(let style, let color): + Heading(title, style: style, color: color) + case .text(let size, let weight, let color, let accentColor, let linkColor, let linkAction): + Text( + title, + size: size, + color: color, + weight: weight, + accentColor: accentColor, + linkColor: linkColor, + linkAction: linkAction + ) } } - @ViewBuilder var descriptionView: some View { - Text( - description, - size: descriptionStyle.size, - color: descriptionStyle.color, - linkColor: descriptionStyle.linkColor, - linkAction: descriptionLinkAction - ) - } - - var isTextEmpty: Bool { - title.isEmpty && description.isEmpty + var iconSize: CGFloat { + style.iconSize } var isEmpty: Bool { - isTextEmpty && iconContent.isEmpty + title.isEmpty && iconContent.isEmpty } } @@ -68,44 +60,28 @@ public extension Label { /// Creates Orbit Label component. init( _ title: String = "", - description: String = "", iconContent: Icon.Content, - titleStyle: TitleStyle = .title4, - descriptionStyle: DescriptionStyle = .default, - iconSpacing: CGFloat = Self.iconSpacing, - descriptionSpacing: CGFloat = Self.descriptionSpacing, - descriptionLinkAction: @escaping TextLink.Action = { _, _ in } + style: Style = .title4, + spacing: CGFloat = Self.defaultSpacing ) { self.title = title - self.description = description self.iconContent = iconContent - self.titleStyle = titleStyle - self.descriptionStyle = descriptionStyle - self.iconSpacing = iconSpacing - self.descriptionSpacing = descriptionSpacing - self.descriptionLinkAction = descriptionLinkAction + self.style = style + self.spacing = spacing } /// Creates Orbit Label component. init( _ title: String = "", - description: String = "", icon: Icon.Symbol = .none, - titleStyle: TitleStyle = .title4, - descriptionStyle: DescriptionStyle = .default, - iconSpacing: CGFloat = Self.iconSpacing, - descriptionSpacing: CGFloat = Self.descriptionSpacing, - descriptionLinkAction: @escaping TextLink.Action = { _, _ in } + style: Style = .title4, + spacing: CGFloat = Self.defaultSpacing ) { self.init( title: title, - description: description, - iconContent: .icon(icon, color: titleStyle.color), - titleStyle: titleStyle, - descriptionStyle: descriptionStyle, - iconSpacing: iconSpacing, - descriptionSpacing: descriptionSpacing, - descriptionLinkAction: descriptionLinkAction + iconContent: .icon(icon, color: style.color), + style: style, + spacing: spacing ) } } @@ -113,147 +89,195 @@ public extension Label { // MARK: - Types public extension Label { - enum TitleStyle { + enum Style { case heading(_ style: Heading.Style = .title4, color: Heading.Color? = .inkNormal) - case text(_ size: Text.Size = .normal, weight: Font.Weight = .medium, color: Text.Color? = .inkNormal) + case text( + _ size: Text.Size = .normal, + weight: Font.Weight = .regular, + color: Text.Color? = .inkNormal, + accentColor: UIColor = .inkNormal, + linkColor: UIColor = TextLink.defaultColor, + linkAction: TextLink.Action = { _, _ in } + ) + /// 40 pts. public static let display = Self.heading(.display) + /// 22 pts. public static let displaySubtitle = Self.heading(.displaySubtitle) + /// 28 pts. public static let title1 = Self.heading(.title1) + /// 22 pts. public static let title2 = Self.heading(.title2) + /// 18 pts. public static let title3 = Self.heading(.title3) + /// 16 pts. public static let title4 = Self.heading(.title4) + /// 14 pts. public static let title5 = Self.heading(.title5) + /// 12 pts. public static let title6 = Self.heading(.title6) var size: CGFloat { switch self { case .heading(let style, _): return style.size - case .text(let size, _, _): return size.value + case .text(let size, _, _, _, _, _): return size.value } } var iconSize: CGFloat { switch self { case .heading(let style, _): return style.iconSize - case .text(let size, _, _): return size.iconSize - } - } - - var color: Color? { - switch self { - case .heading(_ , let color): return color?.value - case .text(_, _, let color): return color?.value - } - } - } - - enum DescriptionStyle { - - case `default` - case custom(_ size: Text.Size = .normal, weight: Font.Weight = .regular, color: Text.Color? = .inkLight, linkColor: UIColor = .productDark) - - var size: Text.Size { - switch self { - case .default: return .normal - case .custom(let size, _, _, _): return size + case .text(let size, _, _, _, _, _): return size.iconSize } } - var weight: Font.Weight { + var lineHeight: CGFloat { switch self { - case .default: return .regular - case .custom(_ , let weight, _, _): return weight + case .heading(let style, _): return style.lineHeight + case .text(let size, _, _, _, _, _): return size.lineHeight } } - var color: Text.Color? { - switch self { - case .default: return .inkLight - case .custom(_, _, let color, _): return color - } + var iconSpacing: CGFloat { + .xxSmall } - var linkColor: UIColor { + var color: Color? { switch self { - case .default: return .productDark - case .custom(_, _, _, let linkColor): return linkColor + case .heading(_ , let color): return color?.value + case .text(_, _, let color, _, _, _): return color?.value } } } } -// MARK: - Alignment -extension HorizontalAlignment { +// MARK: - Alignments +public extension HorizontalAlignment { + + static let labelIconCenter = HorizontalAlignment(LabelIconCenterAlignment.self) + static let labelTextLeading = HorizontalAlignment(LabelTextLeadingAlignment.self) - enum LabelAlignment: AlignmentID { - static func defaultValue(in context: ViewDimensions) -> CGFloat { + enum LabelIconCenterAlignment: AlignmentID { + public static func defaultValue(in context: ViewDimensions) -> CGFloat { context[.leading] } } - static let labelAlignment = HorizontalAlignment(LabelAlignment.self) + enum LabelTextLeadingAlignment: AlignmentID { + public static func defaultValue(in context: ViewDimensions) -> CGFloat { + context[.leading] + } + } } - // MARK: - Previews -struct HeaderPreviews: PreviewProvider { +struct LabelPreviews: PreviewProvider { static var previews: some View { PreviewWrapper { - Group { - Label() - Label("Label") - Label("Label", description: "Description") - Label("No Icon", description: "Description", descriptionStyle: .custom(.large)) - Label("Orbit Icon", description: "Description", icon: .informationCircle, titleStyle: .heading(.title4, color: .none)) - .foregroundColor(.blueNormal) - Label("SF Symbol", description: "Description", iconContent: .sfSymbol("info.circle.fill"), titleStyle: .heading(.title4, color: .none)) - .foregroundColor(.blueNormal) - Label("CountryFlag", description: "Description", iconContent: .countryFlag("us"), titleStyle: .heading(.title4, color: .none)) - .foregroundColor(.blueNormal) - - Label( - "Label", - description: #"Description link"#, - icon: .grid, - titleStyle: .text(.large, weight: .bold, color: .none), - descriptionStyle: .custom(.custom(11), weight: .bold, color: .none, linkColor: .redDark) - ) - .foregroundColor(.blueDark) - - Label("Label", icon: .grid) - Label(icon: .grid) + snapshots + snapshotLabelLayout + snapshotStackLayout + snapshotColorOverride + } + .previewLayout(.sizeThatFits) + } + + @ViewBuilder static var snapshots: some View { + VStack(alignment: .leading, spacing: .large) { + + // No content + Label() + .border(Color.inkLighter) + + Label("Label") + + Label(icon: .grid) + + Label("Label", icon: .grid) + Label("Label", icon: .informationCircle) + + VStack(alignment: .leading, spacing: 0) { + Heading("leading alignment", style: .title5) + Label("Label", icon: .grid, style: .title1) + Label("Label", icon: .grid, style: .text(.small)) + Label("Label", iconContent: .countryFlag("us"), style: .title1) + Label("Label", iconContent: .countryFlag("us"), style: .text(.small)) + Label("Label", iconContent: .sfSymbol("info.circle.fill"), style: .title1) + Label("Label", iconContent: .sfSymbol("info.circle.fill"), style: .text(.small)) + } + + + VStack(alignment: .labelTextLeading, spacing: 0) { + Heading("labelTextLeading alignment", style: .title5) + Label("Label", icon: .grid, style: .title1) + Label("Label", icon: .grid, style: .text(.small)) + Label("Label", iconContent: .countryFlag("us"), style: .title1) + Label("Label", iconContent: .countryFlag("us"), style: .text(.small)) + Label("Label", iconContent: .sfSymbol("info.circle.fill"), style: .title1) + Label("Label", iconContent: .sfSymbol("info.circle.fill"), style: .text(.small)) } - Group { - Label("Label", description: "Description", icon: .grid, titleStyle: .text(.small, weight: .medium)) - Label("Label", description: "Description", icon: .grid, titleStyle: .text(.large, weight: .bold)) - Label("Label", description: "Description", icon: .grid, titleStyle: .text(.custom(.xxLarge), weight: .bold)) + + VStack(alignment: .labelIconCenter, spacing: 0) { + Heading("labelIconCenter alignment", style: .title5) + Label("Label", icon: .grid, style: .title1) + Label("Label", icon: .grid, style: .text(.small)) + Label("Label", iconContent: .countryFlag("us"), style: .title1) + Label("Label", iconContent: .countryFlag("us"), style: .text(.small)) + Label("Label", iconContent: .sfSymbol("info.circle.fill"), style: .title1) + Label("Label", iconContent: .sfSymbol("info.circle.fill"), style: .text(.small)) + } + } + .padding() + } + + @ViewBuilder static var snapshotLabelLayout: some View { + HStack(alignment: .firstTextBaseline, spacing: .xxSmall) { + VStack(alignment: .leading, spacing: .xSmall) { + Label("Label with long multiline text", icon: .grid, style: .title2) + Text("Description very very very very verylong multiline text", color: .inkLight) + customContentPlaceholder + } + Spacer() + Badge("Trailing") + } + .frame(width: 200, alignment: .leading) + .padding() + } + + @ViewBuilder static var snapshotStackLayout: some View { + HStack(alignment: .firstTextBaseline, spacing: .xSmall) { + Icon(.grid, size: .label(.title2)) + + VStack(alignment: .leading, spacing: .xSmall) { + Heading("Label with long multiline text", style: .title2) + Text("Description very very very very verylong multiline text", color: .inkLight) + customContentPlaceholder } - Label( - "Label with very very long text", - description: "Description with very very long text", - icon: .grid - ) - .frame(width: 200, alignment: .leading) + Spacer() + Badge("Trailing") + } + .frame(width: 200, alignment: .leading) + .padding() + } + + static var snapshotColorOverride: some View { + VStack(alignment: .leading, spacing: .xxxSmall) { + Label("Orbit Icon", icon: .informationCircle, style: .heading(.title5, color: .none)) + .foregroundColor(.blueNormal) + .border(Color.cloudLight) - Label( - "Label with very very long text", - description: "Description with very very long text", - icon: .grid, - titleStyle: .title1 - ) - .frame(width: 200, alignment: .leading) + Label("SF Symbol", iconContent: .sfSymbol("info.circle.fill"), style: .heading(.title5, color: .none)) + .foregroundColor(.blueNormal) + .border(Color.cloudLight) - Label( - description: "Description with very very long text", - icon: .grid - ) - .frame(width: 200, alignment: .leading) + Label("CountryFlag", iconContent: .countryFlag("us"), style: .heading(.title5, color: .none)) + .foregroundColor(.blueNormal) + .border(Color.cloudLight) } - .previewLayout(.sizeThatFits) + .padding() } }