diff --git a/Snapshots/iPad/AlertTests/testAlerts.1.png b/Snapshots/iPad/AlertTests/testAlerts.1.png index a3e868fc784..9fd90a1f5eb 100644 Binary files a/Snapshots/iPad/AlertTests/testAlerts.1.png and b/Snapshots/iPad/AlertTests/testAlerts.1.png differ diff --git a/Snapshots/iPad/ButtonLinkTests/testButtonLinks.1.png b/Snapshots/iPad/ButtonLinkTests/testButtonLinks.1.png index 48564e279d0..114b38f0152 100644 Binary files a/Snapshots/iPad/ButtonLinkTests/testButtonLinks.1.png and b/Snapshots/iPad/ButtonLinkTests/testButtonLinks.1.png differ diff --git a/Snapshots/iPad/ButtonLinkTests/testButtonLinks.2.png b/Snapshots/iPad/ButtonLinkTests/testButtonLinks.2.png index d2e7c73ee24..2c954f0c42b 100644 Binary files a/Snapshots/iPad/ButtonLinkTests/testButtonLinks.2.png and b/Snapshots/iPad/ButtonLinkTests/testButtonLinks.2.png differ diff --git a/Snapshots/iPad/ButtonTests/testButtons.1.png b/Snapshots/iPad/ButtonTests/testButtons.1.png index 21cd4eb68f0..922438846f3 100644 Binary files a/Snapshots/iPad/ButtonTests/testButtons.1.png and b/Snapshots/iPad/ButtonTests/testButtons.1.png differ diff --git a/Snapshots/iPad/ButtonTests/testButtons.2.png b/Snapshots/iPad/ButtonTests/testButtons.2.png index 26d205621d3..1ae6910456e 100644 Binary files a/Snapshots/iPad/ButtonTests/testButtons.2.png and b/Snapshots/iPad/ButtonTests/testButtons.2.png differ diff --git a/Snapshots/iPad/DialogTests/testDialog.1.png b/Snapshots/iPad/DialogTests/testDialog.1.png index 4a5bb6e13e5..7f371b2bd53 100644 Binary files a/Snapshots/iPad/DialogTests/testDialog.1.png and b/Snapshots/iPad/DialogTests/testDialog.1.png differ diff --git a/Snapshots/iPad/EmptyStateTests/testEmptyStates.1.png b/Snapshots/iPad/EmptyStateTests/testEmptyStates.1.png index c24143c8a37..d282b505628 100644 Binary files a/Snapshots/iPad/EmptyStateTests/testEmptyStates.1.png and b/Snapshots/iPad/EmptyStateTests/testEmptyStates.1.png differ diff --git a/Snapshots/iPad/ScreenLayoutModifierTests/testScreenLayoutModifier.1.png b/Snapshots/iPad/ScreenLayoutModifierTests/testScreenLayoutModifier.1.png index d1ad4758435..64b901bb3b5 100644 Binary files a/Snapshots/iPad/ScreenLayoutModifierTests/testScreenLayoutModifier.1.png and b/Snapshots/iPad/ScreenLayoutModifierTests/testScreenLayoutModifier.1.png differ diff --git a/Snapshots/iPad/SocialButtonTests/testSocialButtons.1.png b/Snapshots/iPad/SocialButtonTests/testSocialButtons.1.png index 85ad9fadf0b..96b347c77d2 100644 Binary files a/Snapshots/iPad/SocialButtonTests/testSocialButtons.1.png and b/Snapshots/iPad/SocialButtonTests/testSocialButtons.1.png differ diff --git a/Snapshots/iPhone/AlertTests/testAlerts.1.png b/Snapshots/iPhone/AlertTests/testAlerts.1.png index 2d5f90bb093..a527fbd9f2f 100644 Binary files a/Snapshots/iPhone/AlertTests/testAlerts.1.png and b/Snapshots/iPhone/AlertTests/testAlerts.1.png differ diff --git a/Snapshots/iPhone/ButtonLinkTests/testButtonLinks.1.png b/Snapshots/iPhone/ButtonLinkTests/testButtonLinks.1.png index 84d2b8f9d90..114b38f0152 100644 Binary files a/Snapshots/iPhone/ButtonLinkTests/testButtonLinks.1.png and b/Snapshots/iPhone/ButtonLinkTests/testButtonLinks.1.png differ diff --git a/Snapshots/iPhone/ButtonLinkTests/testButtonLinks.2.png b/Snapshots/iPhone/ButtonLinkTests/testButtonLinks.2.png index 0f14d39e2fd..c99d8e4e8e7 100644 Binary files a/Snapshots/iPhone/ButtonLinkTests/testButtonLinks.2.png and b/Snapshots/iPhone/ButtonLinkTests/testButtonLinks.2.png differ diff --git a/Snapshots/iPhone/ButtonTests/testButtons.1.png b/Snapshots/iPhone/ButtonTests/testButtons.1.png index 1f129521706..1f6ab1157c4 100644 Binary files a/Snapshots/iPhone/ButtonTests/testButtons.1.png and b/Snapshots/iPhone/ButtonTests/testButtons.1.png differ diff --git a/Snapshots/iPhone/ButtonTests/testButtons.2.png b/Snapshots/iPhone/ButtonTests/testButtons.2.png index 88be44550e0..049b78843d8 100644 Binary files a/Snapshots/iPhone/ButtonTests/testButtons.2.png and b/Snapshots/iPhone/ButtonTests/testButtons.2.png differ diff --git a/Snapshots/iPhone/DialogTests/testDialog.1.png b/Snapshots/iPhone/DialogTests/testDialog.1.png index 38e9a4a179c..2f29463a027 100644 Binary files a/Snapshots/iPhone/DialogTests/testDialog.1.png and b/Snapshots/iPhone/DialogTests/testDialog.1.png differ diff --git a/Snapshots/iPhone/EmptyStateTests/testEmptyStates.1.png b/Snapshots/iPhone/EmptyStateTests/testEmptyStates.1.png index cd8f1c37738..8d7f6f57571 100644 Binary files a/Snapshots/iPhone/EmptyStateTests/testEmptyStates.1.png and b/Snapshots/iPhone/EmptyStateTests/testEmptyStates.1.png differ diff --git a/Snapshots/iPhone/ScreenLayoutModifierTests/testScreenLayoutModifier.1.png b/Snapshots/iPhone/ScreenLayoutModifierTests/testScreenLayoutModifier.1.png index 84fc5b2a2d4..1d2e8e90feb 100644 Binary files a/Snapshots/iPhone/ScreenLayoutModifierTests/testScreenLayoutModifier.1.png and b/Snapshots/iPhone/ScreenLayoutModifierTests/testScreenLayoutModifier.1.png differ diff --git a/Snapshots/iPhone/SocialButtonTests/testSocialButtons.1.png b/Snapshots/iPhone/SocialButtonTests/testSocialButtons.1.png index 8acc098bca5..7330cf1010d 100644 Binary files a/Snapshots/iPhone/SocialButtonTests/testSocialButtons.1.png and b/Snapshots/iPhone/SocialButtonTests/testSocialButtons.1.png differ diff --git a/Sources/Orbit/Components/Alert.swift b/Sources/Orbit/Components/Alert.swift index 13c67256f99..664c2a4531a 100644 --- a/Sources/Orbit/Components/Alert.swift +++ b/Sources/Orbit/Components/Alert.swift @@ -65,8 +65,9 @@ public struct Alert: View { } if let button { - Button(button.label, type: primaryButtonType, size: .small, action: button.action) - .fixedSize(horizontal: true, vertical: false) + Button(button.label, type: primaryButtonType, action: button.action) + .buttonSize(.compact) + .idealSize() .padding(.xSmall) .padding(.top, 3) .accessibility(.alertButtonPrimary) @@ -119,7 +120,7 @@ public struct Alert: View { switch buttons { case .primary(let primaryButton), .primaryAndSecondary(let primaryButton, _): - Button(primaryButton.label, type: primaryButtonType, size: .small, action: primaryButton.action) + Button(primaryButton.label, type: primaryButtonType, action: primaryButton.action) .accessibility(.alertButtonPrimary) case .none, .secondary, .inline: EmptyView() @@ -128,12 +129,13 @@ public struct Alert: View { switch buttons { case .secondary(let secondaryButton), .primaryAndSecondary(_, let secondaryButton): - Button(secondaryButton.label, type: secondaryButtonType, size: .small, action: secondaryButton.action) + Button(secondaryButton.label, type: secondaryButtonType, action: secondaryButton.action) .accessibility(.alertButtonSecondary) case .none, .primary, .inline: EmptyView() } } + .buttonSize(.compact) case .none, .inline: EmptyView() } diff --git a/Sources/Orbit/Components/Button.swift b/Sources/Orbit/Components/Button.swift index 817078814e3..fb756822024 100644 --- a/Sources/Orbit/Components/Button.swift +++ b/Sources/Orbit/Components/Button.swift @@ -8,34 +8,28 @@ public struct Button: View { private let label: String private let type: ButtonType - private let size: ButtonSize + private let isTrailingIconSeparated: Bool private let action: () -> Void @ViewBuilder private let leadingIcon: LeadingIcon @ViewBuilder private let trailingIcon: TrailingIcon public var body: some View { - SwiftUI.Button(action: action) { - if #available(iOS 14, *) { - text - } else { - text - // Prevents text value animation issue due to different iOS13 behavior - .animation(nil) - } + SwiftUI.Button() { + action() + } label: { + Text(label) } .buttonStyle( - .orbit(type: type, size: size) { + OrbitButtonStyle( + type: type, + isTrailingIconSeparated: isTrailingIconSeparated + ) { leadingIcon } trailingIcon: { trailingIcon } ) } - - @ViewBuilder var text: some View { - Text(label) - .fontWeight(.medium) - } } // MARK: - Inits @@ -43,22 +37,25 @@ public extension Button { /// Creates Orbit Button component. /// + /// Button size can be specified using `.buttonSize()` modifier. + /// /// - Parameters: - /// - style: A visual style of component. A `status` style can be optionally modified using `status()` modifier when `nil` value is provided. + /// - type: A visual style of component. A style can be optionally modified using `status()` modifier when `nil` status value is provided. init( _ label: String = "", icon: Icon.Symbol? = nil, disclosureIcon: Icon.Symbol? = nil, type: ButtonType = .primary, - size: ButtonSize = .default, + isTrailingIconSeparated: Bool = false, action: @escaping () -> Void ) where LeadingIcon == Icon, TrailingIcon == Icon { self.init( label, type: type, - size: size, - action: action + isTrailingIconSeparated: isTrailingIconSeparated ) { + action() + } icon: { Icon(icon) } disclosureIcon: { Icon(disclosureIcon) @@ -67,19 +64,21 @@ public extension Button { /// Creates Orbit Button component with custom icons. /// + /// Button size can be specified using `.buttonSize()` modifier. + /// /// - Parameters: - /// - style: A visual style of component. A `status` style can be optionally modified using `status()` modifier when `nil` value is provided. + /// - type: A visual style of component. A style can be optionally modified using `status()` modifier when `nil` status value is provided. init( _ label: String = "", type: ButtonType = .primary, - size: ButtonSize = .default, + isTrailingIconSeparated: Bool = false, action: @escaping () -> Void, @ViewBuilder icon: () -> LeadingIcon, @ViewBuilder disclosureIcon: () -> TrailingIcon = { EmptyView() } ) { self.label = label self.type = type - self.size = size + self.isTrailingIconSeparated = isTrailingIconSeparated self.action = action self.leadingIcon = icon() self.trailingIcon = disclosureIcon() @@ -98,129 +97,48 @@ public enum ButtonType { case gradient(Gradient) } -public enum ButtonSize { - - case `default` - case small - - public var textSize: Text.Size { - switch self { - case .default: return .normal - case .small: return .small - } - } - - public var horizontalPadding: CGFloat { - switch self { - case .default: return .medium - case .small: return .small - } - } - - public var horizontalIconPadding: CGFloat { - switch self { - case .default: return .small - case .small: return verticalPadding - } - } - - public var verticalPadding: CGFloat { - switch self { - case .default: return .small // = 44 height @ normal size - case .small: return .xSmall // = 32 height @ normal size - } - } -} - +/// Button style matching Orbit Button component. public struct OrbitButtonStyle: PrimitiveButtonStyle { - @Environment(\.iconColor) private var iconColor - @Environment(\.idealSize) private var idealSize - @Environment(\.isHapticsEnabled) private var isHapticsEnabled - @Environment(\.sizeCategory) var sizeCategory + @Environment(\.buttonSize) private var buttonSize @Environment(\.status) private var status - @Environment(\.textColor) private var textColor - @Environment(\.textLinkColor) private var textLinkColor - @State private var isPressed = false - var type: ButtonType - var size: ButtonSize - var cornerRadius: CGFloat - let icon: LeadingIcon - let disclosureIcon: TrailingIcon + private var type: ButtonType + private var isTrailingIconSeparated: Bool + @ViewBuilder private let icon: LeadingIcon + @ViewBuilder private let disclosureIcon: TrailingIcon public init( type: ButtonType, - size: ButtonSize, - cornerRadius: CGFloat = BorderRadius.default, + isTrailingIconSeparated: Bool = false, @ViewBuilder icon: () -> LeadingIcon, @ViewBuilder trailingIcon: () -> TrailingIcon ) { self.type = type - self.size = size - self.cornerRadius = cornerRadius + self.isTrailingIconSeparated = isTrailingIconSeparated self.icon = icon() self.disclosureIcon = trailingIcon() } public func makeBody(configuration: Configuration) -> some View { - content(configuration.label) - ._onButtonGesture { isPressed in - self.isPressed = isPressed - } perform: { - if isHapticsEnabled { - HapticsProvider.sendHapticFeedback(hapticFeedback) - } - - configuration.trigger() - } - } - - @ViewBuilder func content(_ label: some View) -> some View { - HStack(spacing: 0) { - TextStrut() - .textSize(size.textSize) - - if disclosureIcon.isEmpty, idealSize.horizontal == nil { - Spacer(minLength: 0) - } - - HStack(spacing: .xSmall) { - icon - .font(.system(size: size.textSize.value)) - .iconColor(iconColor) - .foregroundColor(iconColor) - - label - .textLinkColor(textLinkColor ?? .custom(resolvedTextColor)) - } - - if idealSize.horizontal == nil { - Spacer(minLength: 0) - } - + OrbitCustomButtonContent( + configuration: configuration, + textColor: textColor, + horizontalPadding: horizontalPadding, + verticalPadding: verticalPadding, + isTrailingIconSeparated: isTrailingIconSeparated, + hapticFeedback: hapticFeedback + ) { + icon + } disclosureIcon: { disclosureIcon - .font(.system(size: size.textSize.value)) - .iconColor(iconColor) - .foregroundColor(iconColor) - } - .textSize(size.textSize) - .textColor(resolvedTextColor) - .padding(.leading, leadingPadding) - .padding(.trailing, trailingPadding) - .frame(maxWidth: idealSize.horizontal == true ? nil : .infinity) - .padding(.vertical, size.verticalPadding) - .contentShape(Rectangle()) - .background(background(forPressedState: isPressed)) - .cornerRadius(cornerRadius) - } - - @ViewBuilder func background(forPressedState isPressed: Bool) -> some View { - if isPressed { - backgroundActive - } else { + } background: { background + } backgroundActive: { + backgroundActive } + .textFontWeight(.medium) + .textSize(textSize) } @ViewBuilder var background: some View { @@ -253,11 +171,7 @@ public struct OrbitButtonStyle: Primitive status ?? .info } - var resolvedTextColor: Color { - textColor ?? styleTextColor - } - - var styleTextColor: Color { + var textColor: Color { switch type { case .primary: return .whiteNormal case .primarySubtle: return .productDark @@ -291,55 +205,25 @@ public struct OrbitButtonStyle: Primitive } } - var leadingPadding: CGFloat { - icon.isEmpty && disclosureIcon.isEmpty - ? size.horizontalPadding - : size.horizontalIconPadding - } - - var trailingPadding: CGFloat { - icon.isEmpty && disclosureIcon.isEmpty - ? size.horizontalPadding - : size.horizontalIconPadding - } -} - -public extension PrimitiveButtonStyle { - - static func orbit( - type: ButtonType, - size: ButtonSize, - cornerRadius: CGFloat = BorderRadius.default, - @ViewBuilder leadingIcon: () -> LeadingIcon, - @ViewBuilder trailingIcon: () -> TrailingIcon - ) -> Self where Self == OrbitButtonStyle { - Self(type: type, size: size, cornerRadius: cornerRadius, icon: leadingIcon, trailingIcon: trailingIcon) - } - - static func orbit( - type: ButtonType, - size: ButtonSize, - cornerRadius: CGFloat = BorderRadius.default, - @ViewBuilder leadingIcon: () -> LeadingIcon - ) -> Self where Self == OrbitButtonStyle { - Self(type: type, size: size, cornerRadius: cornerRadius, icon: leadingIcon, trailingIcon: { EmptyView() }) + var textSize: Text.Size { + switch buttonSize { + case .default: return .normal + case .compact: return .small + } } - static func orbit( - type: ButtonType, - size: ButtonSize, - cornerRadius: CGFloat = BorderRadius.default, - @ViewBuilder trailingIcon: () -> TrailingIcon - ) -> Self where Self == OrbitButtonStyle { - Self(type: type, size: size, cornerRadius: cornerRadius, icon: { EmptyView() }, trailingIcon: trailingIcon) + var horizontalPadding: CGFloat { + switch buttonSize { + case .default: return .medium + case .compact: return .small + } } - static func orbit( - type: ButtonType, - size: ButtonSize, - cornerRadius: CGFloat = BorderRadius.default - ) -> Self where Self == OrbitButtonStyle { - Self(type: type, size: size, cornerRadius: cornerRadius, icon: { EmptyView() }, trailingIcon: { EmptyView() }) + var verticalPadding: CGFloat { + switch buttonSize { + case .default: return .small // = 44 height @ normal size + case .compact: return .xSmall // = 32 height @ normal size + } } } @@ -404,10 +288,13 @@ struct ButtonPreviews: PreviewProvider { Button("Button", icon: .grid, action: {}) Button("Button\nmultiline", icon: .grid, action: {}) Button(icon: .grid, action: {}) - Button("Button small", size: .small, action: {}) - Button("Button small", icon: .grid, size: .small, action: {}) - Button(icon: .grid, size: .small, action: {}) - Button("Button\nmultiline", size: .small, action: {}) + Group { + Button("Button small", action: {}) + Button("Button small", icon: .grid, action: {}) + Button(icon: .grid, action: {}) + Button("Button\nmultiline", action: {}) + } + .buttonSize(.compact) } .measured() } @@ -493,11 +380,12 @@ struct ButtonPreviews: PreviewProvider { Spacer() } HStack(spacing: .small) { - Button("Label", type: type, size: .small, action: {}) + Button("Label", type: type, action: {}) .idealSize() - Button(icon: .grid, type: type, size: .small, action: {}) + Button(icon: .grid, type: type, action: {}) Spacer() } + .buttonSize(.compact) } } @@ -511,12 +399,12 @@ struct ButtonPreviews: PreviewProvider { @ViewBuilder static func statusButtons(_ type: ButtonType) -> some View { HStack(spacing: .xSmall) { Group { - Button("Label", type: type, size: .small, action: {}) - Button("Label", icon: .grid, disclosureIcon: .chevronForward, type: type, size: .small, action: {}) - Button("Label", disclosureIcon: .chevronForward, type: type, size: .small, action: {}) - Button(icon: .grid, type: type, size: .small, action: {}) + Button("Label", type: type, action: {}) + Button("Label", icon: .grid, disclosureIcon: .chevronForward, type: type, action: {}) + Button("Label", disclosureIcon: .chevronForward, type: type, action: {}) + Button(icon: .grid, type: type, action: {}) } - .idealSize() + .buttonSize(.compact) Spacer(minLength: 0) } diff --git a/Sources/Orbit/Components/ButtonLink.swift b/Sources/Orbit/Components/ButtonLink.swift index 2db9c4f2d4f..d017aca77df 100644 --- a/Sources/Orbit/Components/ButtonLink.swift +++ b/Sources/Orbit/Components/ButtonLink.swift @@ -3,75 +3,33 @@ import SwiftUI /// Displays a single, less important action a user can take. /// /// - Note: [Orbit definition](https://orbit.kiwi/components/buttonlink/) -public struct ButtonLink: View { +public struct ButtonLink: View { - @Environment(\.iconColor) private var iconColor - @Environment(\.status) private var status - @Environment(\.textColor) private var textColor - @Environment(\.isHapticsEnabled) private var isHapticsEnabled - - let label: String - let type: ButtonLinkType - let size: ButtonLinkSize - let action: () -> Void - @ViewBuilder let icon: Icon + private let label: String + private let type: ButtonLinkType + private let action: () -> Void + @ViewBuilder private let leadingIcon: LeadingIcon + @ViewBuilder private let trailingIcon: TrailingIcon public var body: some View { if isEmpty == false { - SwiftUI.Button( - action: { - if isHapticsEnabled { - HapticsProvider.sendHapticFeedback(.light(0.5)) - } - - action() - }, - label: { - HStack(spacing: .xSmall) { - icon - .textFontWeight(.medium) - .font(.system(size: Orbit.Icon.Size.normal.value)) - - Text(label) - .fontWeight(.medium) - // Ignore any potential `TextLinks` - .allowsHitTesting(false) - .textLinkColor(.custom(colors.normal)) - } - .padding(.vertical, verticalPadding) + SwiftUI.Button() { + action() + } label: { + Text(label) + } + .buttonStyle( + OrbitButtonLinkButtonStyle(type: type) { + leadingIcon + } trailingIcon: { + trailingIcon } ) - .buttonStyle(ButtonLinkButtonStyle(colors: textColors ?? colors, size: size)) - } - } - - public var colors: (normal: Color, active: Color) { - switch type { - case .primary: return (.productNormal, .productLightActive) - case .secondary: return (.inkDark, .cloudDark) - case .critical: return (.redNormal, .redLightActive) - case .status(let status): return (status ?? defaultStatus).colors - } - } - - public var textColors: (normal: Color, active: Color)? { - textColor.map { (normal: $0, active: $0.opacity(0.5)) } - } - - var defaultStatus: Status { - status ?? .info - } - - var verticalPadding: CGFloat { - switch size { - case .default: return 0 - case .button: return .small // = 44 height @ normal size - case .buttonSmall: return 6 // = 32 height @ normal size } } var isEmpty: Bool { - label.isEmpty && icon.isEmpty + label.isEmpty && leadingIcon.isEmpty && trailingIcon.isEmpty } } @@ -80,90 +38,173 @@ public extension ButtonLink { /// Creates Orbit ButtonLink component. /// + /// Button size can be specified using `.buttonSize()` modifier. + /// /// - Parameters: - /// - style: A visual style of component. A `status` style can be optionally modified using `status()` modifier when `nil` value is provided. + /// - type: A visual style of component. A style can be optionally modified using `status()` modifier when `nil` status value is provided. init( _ label: String = "", type: ButtonLinkType = .primary, icon: Icon.Symbol? = nil, - size: ButtonLinkSize = .default, + disclosureIcon: Icon.Symbol? = nil, action: @escaping () -> Void - ) where Icon == Orbit.Icon { - self.init( - label, - type: type, - size: size - ) { + ) where LeadingIcon == Orbit.Icon, TrailingIcon == Orbit.Icon { + self.init(label, type: type) { action() } icon: { Icon(icon) + } disclosureIcon: { + Icon(disclosureIcon) } } - /// Creates Orbit ButtonLink component with custom icon. + /// Creates Orbit ButtonLink component with custom icons. + /// + /// Button size can be specified using `.buttonSize()` modifier. /// /// - Parameters: - /// - style: A visual style of component. A `status` style can be optionally modified using `status()` modifier when `nil` value is provided. + /// - type: A visual style of component. A style can be optionally modified using `status()` modifier when `nil` status value is provided. init( _ label: String = "", type: ButtonLinkType = .primary, - size: ButtonLinkSize = .default, action: @escaping () -> Void, - @ViewBuilder icon: () -> Icon + @ViewBuilder icon: () -> LeadingIcon, + @ViewBuilder disclosureIcon: () -> TrailingIcon = { EmptyView() } ) { self.label = label self.type = type - self.size = size self.action = action - self.icon = icon() + self.leadingIcon = icon() + self.trailingIcon = disclosureIcon() } } // MARK: - Types public enum ButtonLinkType: Equatable { - case primary - case secondary case critical case status(_ status: Status?) } -public enum ButtonLinkSize: Equatable { - case `default` - case button - case buttonSmall +/// Button style matching Orbit ButtonLink component. +public struct OrbitButtonLinkButtonStyle: PrimitiveButtonStyle { + + @Environment(\.buttonSize) private var buttonSize + @Environment(\.status) private var status + + private var type: ButtonLinkType + @ViewBuilder private let icon: LeadingIcon + @ViewBuilder private let disclosureIcon: TrailingIcon - public var maxWidth: CGFloat? { - switch self { - case .default: return nil - case .button, .buttonSmall: return .infinity + public init( + type: ButtonLinkType, + @ViewBuilder icon: () -> LeadingIcon, + @ViewBuilder trailingIcon: () -> TrailingIcon + ) { + self.type = type + self.icon = icon() + self.disclosureIcon = trailingIcon() + } + + public func makeBody(configuration: Configuration) -> some View { + OrbitCustomButtonContent( + configuration: configuration, + textColor: textColor, + textActiveColor: textActiveColor, + horizontalPadding: horizontalPadding, + verticalPadding: verticalPadding, + horizontalBackgroundPadding: horizontalBackgroundPadding, + verticalBackgroundPadding: verticalBackgroundPadding, + hapticFeedback: hapticFeedback + ) { + icon + } disclosureIcon: { + disclosureIcon + } background: { + Color.clear + } backgroundActive: { + backgroundActive } + .textFontWeight(.medium) + .idealSize(horizontal: buttonSize == .compact) } -} -public struct ButtonLinkButtonStyle: ButtonStyle { + @ViewBuilder var backgroundActive: some View { + switch type { + case .primary: Color.productLightActive + case .critical: Color.redLightActive + case .status(let status): (status ?? defaultStatus).lightActiveColor + } + } - let colors: (normal: Color, active: Color) - let size: ButtonLinkSize + var textColor: Color { + switch type { + case .primary: return .productNormal + case .critical: return .redNormal + case .status(let status): return (status ?? defaultStatus).color + } + } - public init(colors: (normal: Color, active: Color), size: ButtonLinkSize) { - self.colors = colors - self.size = size + var textActiveColor: Color { + switch type { + case .primary: return .productDarkActive + case .critical: return .redDarkActive + case .status(let status): return (status ?? defaultStatus).darkHoverColor + } } - public func makeBody(configuration: Configuration) -> some View { - configuration.label - .textColor(configuration.isPressed ? colors.active : colors.normal) - .frame(maxWidth: size.maxWidth) - .contentShape(Rectangle()) + var defaultStatus: Status { + status ?? .info } -} -private extension Status { - var colors: (normal: Color, active: Color) { - (color, lightActiveColor) + var resolvedStatus: Status { + switch type { + case .status(let status): return status ?? self.status ?? .info + default: return .info + } + } + + var hapticFeedback: HapticsProvider.HapticFeedbackType { + switch type { + case .primary: return .light(1) + case .critical: return .notification(.error) + case .status: + switch resolvedStatus { + case .info, .success: return .light(0.5) + case .warning: return .notification(.warning) + case .critical: return .notification(.error) + } + } + } + + var horizontalPadding: CGFloat { + switch buttonSize { + case .default: return .medium + case .compact: return 0 + } + } + + var verticalPadding: CGFloat { + switch buttonSize { + case .default: return .small // = 44 height @ normal size + case .compact: return 6 // = 32 height @ normal size + } + } + + var horizontalBackgroundPadding: CGFloat { + switch buttonSize { + case .default: return 0 + case .compact: return .xSmall + } + } + + var verticalBackgroundPadding: CGFloat { + switch buttonSize { + case .default: return 0 + case .compact: return .xxxSmall + } } } @@ -185,6 +226,9 @@ struct ButtonLinkPreviews: PreviewProvider { static var standalone: some View { VStack(spacing: 0) { ButtonLink("ButtonLink", action: {}) + ButtonLink("ButtonLink", type: .critical, action: {}) + ButtonLink("ButtonLink", action: {}) + .buttonSize(.compact) ButtonLink("", action: {}) // EmptyView ButtonLink(action: {}) // EmptyView } @@ -195,12 +239,12 @@ struct ButtonLinkPreviews: PreviewProvider { static var sizing: some View { VStack(alignment: .leading, spacing: .xSmall) { Group { - ButtonLink("ButtonLink intrinsic", action: {}) - ButtonLink("ButtonLink intrinsic", icon: .grid, action: {}) - ButtonLink("ButtonLink button", size: .button, action: {}) - ButtonLink("ButtonLink button", icon: .grid, size: .button, action: {}) - ButtonLink("ButtonLink small button", size: .buttonSmall, action: {}) - ButtonLink("ButtonLink small button", icon: .grid, size: .buttonSmall, action: {}) + ButtonLink("ButtonLink", action: {}) + ButtonLink("ButtonLink", icon: .grid, action: {}) + ButtonLink("ButtonLink Compact", action: {}) + .buttonSize(.compact) + ButtonLink("ButtonLink Compact", icon: .grid, action: {}) + .buttonSize(.compact) } .border(.cloudNormal) .measured() @@ -213,15 +257,14 @@ struct ButtonLinkPreviews: PreviewProvider { HStack(spacing: .xxLarge) { VStack(alignment: .leading, spacing: .large) { ButtonLink("ButtonLink Primary", type: .primary, action: {}) - ButtonLink("ButtonLink Secondary", type: .secondary, action: {}) ButtonLink("ButtonLink Critical", type: .critical, action: {}) } VStack(alignment: .leading, spacing: .large) { ButtonLink("ButtonLink Primary", type: .primary, icon: .accommodation, action: {}) - ButtonLink("ButtonLink Secondary", type: .secondary, icon: .airplaneDown, action: {}) ButtonLink("ButtonLink Critical", type: .critical, icon: .alertCircle, action: {}) } } + .buttonSize(.compact) .padding(.medium) .previewDisplayName() } @@ -233,6 +276,7 @@ struct ButtonLinkPreviews: PreviewProvider { ButtonLink("ButtonLink Warning", type: .status(.warning), icon: .alert, action: {}) ButtonLink("ButtonLink Critical", type: .status(.critical), icon: .alertCircle, action: {}) } + .buttonSize(.compact) .padding(.medium) .previewDisplayName() } diff --git a/Sources/Orbit/Components/Card.swift b/Sources/Orbit/Components/Card.swift index ac19829b054..d223ceff108 100644 --- a/Sources/Orbit/Components/Card.swift +++ b/Sources/Orbit/Components/Card.swift @@ -62,8 +62,9 @@ public struct Card: View { case .buttonLink(let label, let type, let action): if label.isEmpty == false { ButtonLink(label, type: type, action: action) - .textColor(nil) + .buttonSize(.compact) .padding(.leading, .xxxSmall) + .padding(.top, -6) .accessibility(.cardActionButtonLink) } case .none: diff --git a/Sources/Orbit/Components/Dialog.swift b/Sources/Orbit/Components/Dialog.swift index 1520db852f3..11e5b747dc2 100644 --- a/Sources/Orbit/Components/Dialog.swift +++ b/Sources/Orbit/Components/Dialog.swift @@ -58,7 +58,7 @@ public struct Dialog: View { EmptyView() case .primaryAndSecondary(_, let secondaryButton), .primarySecondaryAndTertiary(_, let secondaryButton, _): - ButtonLink(secondaryButton.label, type: style.buttonLinkType, size: .button, action: secondaryButton.action) + ButtonLink(secondaryButton.label, type: style.buttonLinkType, action: secondaryButton.action) .accessibility(.dialogButtonSecondary) } @@ -66,7 +66,7 @@ public struct Dialog: View { case .primary, .primaryAndSecondary: EmptyView() case .primarySecondaryAndTertiary(_, _, let tertiaryButton): - ButtonLink(tertiaryButton.label, type: style.buttonLinkType, size: .button, action: tertiaryButton.action) + ButtonLink(tertiaryButton.label, type: style.buttonLinkType, action: tertiaryButton.action) .accessibility(.dialogButtonTertiary) } } diff --git a/Sources/Orbit/Components/ListChoice.swift b/Sources/Orbit/Components/ListChoice.swift index 657199448cf..543fc7e3fde 100644 --- a/Sources/Orbit/Components/ListChoice.swift +++ b/Sources/Orbit/Components/ListChoice.swift @@ -125,10 +125,15 @@ public struct ListChoice: View { } @ViewBuilder func disclosureButton(type: ListChoiceDisclosure.ButtonType) -> some View { - switch type { - case .add: Button(icon: .plus, type: .primarySubtle, size: .small, action: {}).fixedSize() - case .remove: Button(icon: .close, type: .criticalSubtle, size: .small, action: {}).fixedSize() + Button(type: type == .add ? .primarySubtle : .criticalSubtle) { + // No action + } icon: { + Orbit.Icon(.plus) + .rotationEffect(.degrees(type == .add ? 0 : 45)) + .animation(.easeOut(duration: 0.2), value: type) } + .buttonSize(.compact) + .idealSize() } @ViewBuilder var separator: some View { @@ -424,7 +429,15 @@ struct ListChoicePreviews: PreviewProvider { static var buttons: some View { Card(contentLayout: .fill) { - ListChoice(title, disclosure: addButton, action: {}) + StateWrapper(ListChoiceDisclosure.button(type: .add)) { button in + ListChoice(title, disclosure: button.wrappedValue) { + if .button(type: .add) ~= button.wrappedValue { + button.wrappedValue = .button(type: .remove) + } else { + button.wrappedValue = .button(type: .add) + } + } + } ListChoice(title, disclosure: removeButton, action: {}) ListChoice(title, description: description, disclosure: addButton, action: {}) ListChoice(title, description: description, disclosure: removeButton, action: {}) diff --git a/Sources/Orbit/Components/Skeleton.swift b/Sources/Orbit/Components/Skeleton.swift index ce74e441590..4068183d1ec 100644 --- a/Sources/Orbit/Components/Skeleton.swift +++ b/Sources/Orbit/Components/Skeleton.swift @@ -36,7 +36,7 @@ public struct Skeleton: View { case .button(let size): switch size { case .default: roundedRectangle.fill(color).frame(height: .xxLarge) - case .small: roundedRectangle.fill(color).frame(height: .xLarge) + case .compact: roundedRectangle.fill(color).frame(height: .xLarge) } case .card(let height), .image(let height): roundedRectangle.fill(color).frame(height: height) diff --git a/Sources/Orbit/Components/SocialButton.swift b/Sources/Orbit/Components/SocialButton.swift index 3361bc4e1ca..bd9b3fb2325 100644 --- a/Sources/Orbit/Components/SocialButton.swift +++ b/Sources/Orbit/Components/SocialButton.swift @@ -10,46 +10,35 @@ import SwiftUI public struct SocialButton: View { @Environment(\.colorScheme) private var colorScheme - @Environment(\.idealSize) private var idealSize @Environment(\.sizeCategory) private var sizeCategory - @Environment(\.textColor) private var textColor - @Environment(\.isHapticsEnabled) private var isHapticsEnabled private let label: String private let service: Service private let action: () -> Void public var body: some View { - SwiftUI.Button( - action: { - if isHapticsEnabled { - HapticsProvider.sendHapticFeedback(.light(0.5)) - } - - action() - }, - label: { - HStack(spacing: .xSmall) { - logo - .scaledToFit() - .frame(width: .large * sizeCategory.ratio) - - Text(label) - .fontWeight(.medium) - .padding(.vertical, ButtonSize.default.verticalPadding) - - if idealSize.horizontal == nil { - Spacer(minLength: 0) - } - - Icon(.chevronForward) - .iconSize(.large) - .iconColor(labelColor) - } - .textColor(textColor ?? labelColor) + SwiftUI.Button { + action() + } label: { + Text(label) + .fontWeight(.medium) + } + .buttonStyle( + OrbitCustomButtonStyle(textColor: textColor, isTrailingIconSeparated: true) { + logo + .scaledToFit() + .frame(width: .large * sizeCategory.ratio) + .padding([.leading, .vertical], -.xxSmall) + } disclosureIcon: { + Icon(.chevronForward) + .iconSize(.large) + .padding([.trailing, .vertical], -.xxSmall) + } background: { + background + } backgroundActive: { + backgroundActive } ) - .buttonStyle(OrbitStyle(backgroundColor: backgroundColor, idealSize: idealSize)) } @ViewBuilder var logo: some View { @@ -61,7 +50,7 @@ public struct SocialButton: View { } } - var labelColor: Color { + var textColor: Color { switch service { case .apple: return .whiteNormal case .google: return .inkDark @@ -70,15 +59,21 @@ public struct SocialButton: View { } } - var backgroundColor: OrbitStyle.BackgroundColor { + @ViewBuilder var background: some View { + switch service { + case .apple: colorScheme == .light ? Color.black : Color.white + case .google: Color.cloudNormal + case .facebook: Color.cloudNormal + case .email: Color.cloudNormal + } + } + + @ViewBuilder var backgroundActive: some View { switch service { - case .apple: return ( - colorScheme == .light ? .black : .white, - colorScheme == .light ? .inkNormalActive : .inkNormalActive - ) - case .google: return (.cloudNormal, .cloudNormalActive) - case .facebook: return (.cloudNormal, .cloudNormalActive) - case .email: return (.cloudNormal, .cloudNormalActive) + case .apple: colorScheme == .light ? Color.inkNormalActive : Color.inkNormalActive + case .google: Color.cloudNormalActive + case .facebook: Color.cloudNormalActive + case .email: Color.cloudNormalActive } } } @@ -107,24 +102,6 @@ extension SocialButton { case facebook case email } - - struct OrbitStyle: ButtonStyle { - - typealias BackgroundColor = (normal: Color, active: Color) - - let backgroundColor: BackgroundColor - let idealSize: IdealSizeValue - - func makeBody(configuration: Configuration) -> some View { - configuration.label - .frame(maxWidth: idealSize.horizontal == true ? nil : .infinity) - .padding(.horizontal, .small) - .background( - configuration.isPressed ? backgroundColor.active : backgroundColor.normal - ) - .cornerRadius(BorderRadius.default) - } - } } // MARK: - Previews @@ -134,6 +111,7 @@ struct SocialButtonPreviews: PreviewProvider { PreviewWrapper { standalone idealSize + sizing all } .padding(.medium) @@ -151,6 +129,21 @@ struct SocialButtonPreviews: PreviewProvider { .previewDisplayName() } + static var sizing: some View { + VStack(spacing: .medium) { + Group { + SocialButton("Sign in with Apple", service: .apple, action: {}) + SocialButton("Sign in with Facebook", service: .facebook, action: {}) + SocialButton("Sign in with Facebook", service: .facebook, action: {}) + .idealSize() + .buttonSize(.compact) + } + .measured() + } + .padding(.medium) + .previewDisplayName() + } + static var all: some View { content .previewDisplayName() @@ -169,6 +162,7 @@ struct SocialButtonPreviews: PreviewProvider { VStack(spacing: .medium) { all idealSize + sizing } .padding(.medium) } diff --git a/Sources/Orbit/Orbit.docc/Components/Extensions/ButtonLink.md b/Sources/Orbit/Orbit.docc/Components/Extensions/ButtonLink.md index b908740897a..edda2762fce 100644 --- a/Sources/Orbit/Orbit.docc/Components/Extensions/ButtonLink.md +++ b/Sources/Orbit/Orbit.docc/Components/Extensions/ButtonLink.md @@ -8,6 +8,5 @@ ### Customizing Appearance -- ``ButtonLinkButtonStyle`` -- ``ButtonLinkSize`` +- ``OrbitButtonLinkButtonStyle`` - ``ButtonLinkType`` diff --git a/Sources/Orbit/Support/Components/OrbitCustomButtonContent.swift b/Sources/Orbit/Support/Components/OrbitCustomButtonContent.swift new file mode 100644 index 00000000000..92e06632391 --- /dev/null +++ b/Sources/Orbit/Support/Components/OrbitCustomButtonContent.swift @@ -0,0 +1,181 @@ +import SwiftUI + +struct OrbitCustomButtonContent: View { + + @Environment(\.iconColor) private var iconColor + @Environment(\.idealSize) private var idealSize + @Environment(\.isHapticsEnabled) private var isHapticsEnabled + @Environment(\.sizeCategory) private var sizeCategory + @Environment(\.status) private var status + @Environment(\.textLinkColor) private var textLinkColor + @State private var isPressed = false + + let configuration: PrimitiveButtonStyleConfiguration + let textColor: Color + var textActiveColor: Color? = nil + var horizontalPadding: CGFloat = .medium + var verticalPadding: CGFloat = .small + var horizontalBackgroundPadding: CGFloat = 0 + var verticalBackgroundPadding: CGFloat = 0 + var cornerRadius: CGFloat = BorderRadius.default + var isTrailingIconSeparated = false + var hapticFeedback: HapticsProvider.HapticFeedbackType = .light() + @ViewBuilder let icon: LeadingIcon + @ViewBuilder let disclosureIcon: TrailingIcon + @ViewBuilder let background: Background + @ViewBuilder let backgroundActive: BackgroundActive + + var body: some View { + HStack(spacing: 0) { + TextStrut() + + if (disclosureIcon.isEmpty || isTrailingIconSeparated == false), idealSize.horizontal == nil { + Spacer(minLength: 0) + } + + HStack(spacing: .xSmall) { + icon + label + } + + if idealSize.horizontal == nil, isTrailingIconSeparated { + Spacer(minLength: 0) + } + + disclosureIcon + .padding(.leading, .xSmall) + + if idealSize.horizontal == nil, isTrailingIconSeparated == false { + Spacer(minLength: 0) + } + } + .padding(.horizontal, horizontalPadding) + .padding(.vertical, verticalPadding) + .frame(maxWidth: idealSize.horizontal == true ? nil : .infinity) + .contentShape(Rectangle()) + .background( + backgroundView + .cornerRadius(cornerRadius) + .padding(.horizontal, -horizontalBackgroundPadding) + .padding(.vertical, -verticalBackgroundPadding) + ) + .textColor(isPressed ? textActiveColor ?? textColor : textColor) + .foregroundColor(isPressed ? textActiveColor ?? textColor : textColor) + ._onButtonGesture { isPressed in + self.isPressed = isPressed + } perform: { + if isHapticsEnabled { + HapticsProvider.sendHapticFeedback(hapticFeedback) + } + + configuration.trigger() + } + } + + @ViewBuilder var label: some View { + if #available(iOS 14, *) { + configuration.label + } else { + configuration.label + // Prevents text value animation issue due to different iOS13 behavior + .animation(nil) + } + } + + @ViewBuilder var backgroundView: some View { + if isPressed { + backgroundActive + } else { + background + } + } +} + +// MARK: ButtonStyle + +struct OrbitCustomButtonStyle: PrimitiveButtonStyle { + + let textColor: Color + var textActiveColor: Color? = nil + var horizontalPadding: CGFloat = .medium + var verticalPadding: CGFloat = .small + var horizontalBackgroundPadding: CGFloat = 0 + var verticalBackgroundPadding: CGFloat = 0 + var cornerRadius: CGFloat = BorderRadius.default + var isTrailingIconSeparated = false + var hapticFeedback: HapticsProvider.HapticFeedbackType = .light() + @ViewBuilder let icon: LeadingIcon + @ViewBuilder let disclosureIcon: TrailingIcon + @ViewBuilder let background: Background + @ViewBuilder let backgroundActive: BackgroundActive + + public func makeBody(configuration: Configuration) -> some View { + OrbitCustomButtonContent( + configuration: configuration, + textColor: textColor, + textActiveColor: textActiveColor, + horizontalPadding: horizontalPadding, + verticalPadding: verticalPadding, + horizontalBackgroundPadding: horizontalBackgroundPadding, + verticalBackgroundPadding: verticalBackgroundPadding, + cornerRadius: cornerRadius, + isTrailingIconSeparated: isTrailingIconSeparated, + hapticFeedback: hapticFeedback + ) { + icon + } disclosureIcon: { + disclosureIcon + } background: { + background + } backgroundActive: { + backgroundActive + } + } +} + +// MARK: Previews +struct OrbitCustomButtonContentPreviews: PreviewProvider { + + static var previews: some View { + PreviewWrapper { + StateWrapper(false) { isPressed in + VStack(spacing: .medium) { + SwiftUI.Button { + // No action + } label: { + Text("Label") + } + .buttonStyle(PreviewButtonStyle()) + .border(Color.redNormal, width: .hairline) + } + } + .padding(.medium) + .previewLayout(.sizeThatFits) + } + } + + private struct PreviewButtonStyle: PrimitiveButtonStyle { + + public func makeBody(configuration: Configuration) -> some View { + OrbitCustomButtonContent( + configuration: configuration, + textColor: .blueDark, + textActiveColor: .redDark, + horizontalPadding: 4, + verticalPadding: 4, + horizontalBackgroundPadding: 4, + verticalBackgroundPadding: 4, + cornerRadius: 4, + isTrailingIconSeparated: true + ) { + Icon(.grid) + } disclosureIcon: { + Icon(.chevronForward) + } background: { + Color.orangeLight + } backgroundActive: { + Color.greenLight + } + } + } +} diff --git a/Sources/Orbit/Support/Environment Keys/ButtonSizeKey.swift b/Sources/Orbit/Support/Environment Keys/ButtonSizeKey.swift new file mode 100644 index 00000000000..ce89c2134ff --- /dev/null +++ b/Sources/Orbit/Support/Environment Keys/ButtonSizeKey.swift @@ -0,0 +1,31 @@ +import SwiftUI + +/// Orbit Button size. +public enum ButtonSize { + case `default` + case compact +} + +struct ButtonSizeKey: EnvironmentKey { + static let defaultValue = ButtonSize.default +} + +public extension EnvironmentValues { + + /// An Orbit `ButtonSize` value stored in a view’s environment. + var buttonSize: ButtonSize { + get { self[ButtonSizeKey.self] } + set { self[ButtonSizeKey.self] = newValue } + } +} + +public extension View { + + /// Set the button size for this view. + /// + /// - Parameters: + /// - size: A button size that will be used by all components in the view hierarchy. + func buttonSize(_ size: ButtonSize) -> some View { + environment(\.buttonSize, size) + } +} diff --git a/Sources/Orbit/Support/Layout/HorizontalScrollReader.swift b/Sources/Orbit/Support/Layout/HorizontalScrollReader.swift index 68028fadac4..2d3f2022253 100644 --- a/Sources/Orbit/Support/Layout/HorizontalScrollReader.swift +++ b/Sources/Orbit/Support/Layout/HorizontalScrollReader.swift @@ -92,15 +92,15 @@ struct HorizontalScrollReaderPreviews: PreviewProvider { // FIXME: Binding does not work correctly? .id(state.wrappedValue.1 ? 1 : 0) - Button("Scroll to First", size: .small) { + Button("Scroll to First") { scrollProxy.scrollTo(0, animated: state.wrappedValue.1) } - Button("Scroll to 2", size: .small) { + Button("Scroll to 2") { scrollProxy.scrollTo(2, animated: state.wrappedValue.1) } - Button("Scroll to Last", size: .small) { + Button("Scroll to Last") { scrollProxy.scrollTo(6, animated: state.wrappedValue.1) } } diff --git a/Sources/Orbit/Support/Status.swift b/Sources/Orbit/Support/Status.swift index 2ccc1f96a57..c2790f0eaef 100644 --- a/Sources/Orbit/Support/Status.swift +++ b/Sources/Orbit/Support/Status.swift @@ -80,6 +80,16 @@ public extension Status { } } + /// Dark active color associated with status. + var darkActiveColor: Color { + switch self { + case .info: return .blueDarkActive + case .success: return .greenDarkActive + case .warning: return .orangeDarkActive + case .critical: return .redDarkActive + } + } + /// Light active color associated with status. var lightActiveColor: Color { switch self { diff --git a/Sources/OrbitStorybook/Detail/Items/StorybookButton.swift b/Sources/OrbitStorybook/Detail/Items/StorybookButton.swift index 151c9e96ba3..393e7da8d19 100644 --- a/Sources/OrbitStorybook/Detail/Items/StorybookButton.swift +++ b/Sources/OrbitStorybook/Detail/Items/StorybookButton.swift @@ -66,11 +66,12 @@ struct StorybookButton { Spacer() } HStack(spacing: .small) { - Button("Label", type: type, size: .small, action: {}) + Button("Label", type: type, action: {}) .idealSize() - Button(icon: .grid, type: type, size: .small, action: {}) + Button(icon: .grid, type: type, action: {}) Spacer() } + .buttonSize(.compact) } } @@ -84,11 +85,12 @@ struct StorybookButton { @ViewBuilder static func statusButtons(_ type: ButtonType) -> some View { HStack(spacing: .xSmall) { Group { - Button("Label", type: type, size: .small, action: {}) - Button("Label", icon: .grid, disclosureIcon: .chevronForward, type: type, size: .small, action: {}) - Button("Label", disclosureIcon: .chevronForward, type: type, size: .small, action: {}) - Button(icon: .grid, type: type, size: .small, action: {}) + Button("Label", type: type, action: {}) + Button("Label", icon: .grid, disclosureIcon: .chevronForward, type: type, action: {}) + Button("Label", disclosureIcon: .chevronForward, type: type, action: {}) + Button(icon: .grid, type: type, action: {}) } + .buttonSize(.compact) .idealSize() Spacer(minLength: 0) diff --git a/Sources/OrbitStorybook/Detail/Items/StorybookButtonLink.swift b/Sources/OrbitStorybook/Detail/Items/StorybookButtonLink.swift index 73a4d62f483..97ce9efc862 100644 --- a/Sources/OrbitStorybook/Detail/Items/StorybookButtonLink.swift +++ b/Sources/OrbitStorybook/Detail/Items/StorybookButtonLink.swift @@ -7,15 +7,14 @@ struct StorybookButtonLink { HStack(spacing: .xxLarge) { VStack(alignment: .leading, spacing: .large) { ButtonLink("ButtonLink Primary", type: .primary, action: {}) - ButtonLink("ButtonLink Secondary", type: .secondary, action: {}) ButtonLink("ButtonLink Critical", type: .critical, action: {}) } VStack(alignment: .leading, spacing: .large) { ButtonLink("ButtonLink Primary", type: .primary, icon: .accommodation, action: {}) - ButtonLink("ButtonLink Secondary", type: .secondary, icon: .airplaneDown, action: {}) ButtonLink("ButtonLink Critical", type: .critical, icon: .alertCircle, action: {}) } } + .buttonSize(.compact) .previewDisplayName() } @@ -26,16 +25,16 @@ struct StorybookButtonLink { ButtonLink("ButtonLink Warning", type: .status(.warning), icon: .alert, action: {}) ButtonLink("ButtonLink Critical", type: .status(.critical), icon: .alertCircle, action: {}) } + .buttonSize(.compact) .previewDisplayName() } static var sizes: some View { VStack(alignment: .leading, spacing: .small) { ButtonLink("ButtonLink intrinsic size", icon: .baggageSet, action: {}) + .buttonSize(.compact) .border(.cloudNormal) - ButtonLink("ButtonLink small button size", icon: .baggageSet, size: .buttonSmall, action: {}) - .border(.cloudNormal) - ButtonLink("ButtonLink button size", icon: .baggageSet, size: .button, action: {}) + ButtonLink("ButtonLink button size", icon: .baggageSet, action: {}) .border(.cloudNormal) } .previewDisplayName() diff --git a/Sources/OrbitStorybook/Detail/Items/StorybookHorizontalScroll.swift b/Sources/OrbitStorybook/Detail/Items/StorybookHorizontalScroll.swift index 1917d8e12eb..a09ba637677 100644 --- a/Sources/OrbitStorybook/Detail/Items/StorybookHorizontalScroll.swift +++ b/Sources/OrbitStorybook/Detail/Items/StorybookHorizontalScroll.swift @@ -124,17 +124,20 @@ struct StorybookHorizontalScroll: View { // FIXME: Binding does not work correctly? .id(animatedScroll ? 1 : 0) - Button("Scroll to First", size: .small) { - scrollProxy.scrollTo(0, animated: animatedScroll) - } + Group { + Button("Scroll to First") { + scrollProxy.scrollTo(0, animated: animatedScroll) + } - Button("Scroll to 2", size: .small) { - scrollProxy.scrollTo(2, animated: animatedScroll) - } + Button("Scroll to 2") { + scrollProxy.scrollTo(2, animated: animatedScroll) + } - Button("Scroll to Last", size: .small) { - scrollProxy.scrollTo(6, animated: animatedScroll) + Button("Scroll to Last") { + scrollProxy.scrollTo(6, animated: animatedScroll) + } } + .buttonSize(.compact) } } .previewDisplayName()