Skip to content

Commit

Permalink
New FluentButtonToggleStyle (#1993)
Browse files Browse the repository at this point in the history
* Creating new `FluentButtonToggleStyle`

* Simplify corner radius calculation for buttons

* Typos, GlobalTokens, and renaming Presssed -> On
  • Loading branch information
mischreiber authored Apr 3, 2024
1 parent e7dd82f commit 585aa32
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ extension ButtonDemoControllerSwiftUI: UIPopoverPresentationControllerDelegate {
struct ButtonDemoView: View {
public var body: some View {
VStack {
demoButton(style, size, isDisabled: isDisabled)
if showToggle {
demoToggle(size, isDisabled: isDisabled)
} else {
demoButton(style, size, isDisabled: isDisabled)
}
demoOptions
}
}
Expand All @@ -77,33 +81,53 @@ struct ButtonDemoView: View {
@State var showAlert: Bool = false
@State var size: ControlSize = .large
@State var style: FluentUI.ButtonStyle = .accent
@State var showToggle: Bool = false

@Environment(\.fluentTheme) var fluentTheme: FluentTheme

@State var isToggleOn: Bool = false

@ViewBuilder
private var buttonLabel: some View {
HStack {
if showImage {
Image("Placeholder_24")
}
if showLabel && text.count > 0 {
Text(text)
}
}
}

@ViewBuilder
private func demoButton(_ buttonStyle: FluentUI.ButtonStyle, _ controlSize: ControlSize, isDisabled: Bool) -> some View {
Button(action: {
showAlert = true
}, label: {
HStack {
if showImage {
Image("Placeholder_24")
}
if showLabel && text.count > 0 {
Text(text)
}
}
buttonLabel
})
.buttonStyle(FluentButtonStyle(style: buttonStyle))
.controlSize(controlSize)
.disabled(isDisabled)
.fixedSize()
.padding(8.0)
.padding(GlobalTokens.spacing(.size80))
.alert(isPresented: $showAlert, content: {
Alert(title: Text("Button tapped"))
})
}

@ViewBuilder
private func demoToggle(_ controlSize: ControlSize, isDisabled: Bool) -> some View {
Toggle(isOn: $isToggleOn, label: {
buttonLabel
})
.toggleStyle(FluentButtonToggleStyle())
.controlSize(controlSize)
.disabled(isDisabled)
.fixedSize()
.padding(GlobalTokens.spacing(.size80))
}

@ViewBuilder
private var demoOptions: some View {
Form {
Expand All @@ -124,9 +148,12 @@ struct ButtonDemoView: View {
}

Section("Style and Size") {
Picker("Style", selection: $style) {
ForEach(Array(FluentUI.ButtonStyle.allCases.enumerated()), id: \.element) { _, buttonStyle in
Text("\(buttonStyle.description)").tag(buttonStyle.rawValue)
FluentUIDemoToggle(titleKey: "Present as toggle", isOn: $showToggle)
if !showToggle {
Picker("Style", selection: $style) {
ForEach(Array(FluentUI.ButtonStyle.allCases.enumerated()), id: \.element) { _, buttonStyle in
Text("\(buttonStyle.description)").tag(buttonStyle.rawValue)
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions ios/FluentUI.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@
9298798B2669A875002B1EB4 /* PersonaButtonTokenSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927E34C62668350800998031 /* PersonaButtonTokenSet.swift */; };
929DD257266ED3AC00E8175E /* PersonaButtonCarouselTokenSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 929DD255266ED3AC00E8175E /* PersonaButtonCarouselTokenSet.swift */; };
929DD25A266ED3B600E8175E /* PersonaButtonCarousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 929DD258266ED3B600E8175E /* PersonaButtonCarousel.swift */; };
929F2ACF2BB77ED100683EA8 /* FluentButtonToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 929F2ACE2BB77ED100683EA8 /* FluentButtonToggleStyle.swift */; };
92A1E4F526A791590007ED60 /* MSFCardNudge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92A1E4F326A791590007ED60 /* MSFCardNudge.swift */; };
92B7E6A326864AE900EFC15E /* MSFPersonaButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92B7E6A12684262900EFC15E /* MSFPersonaButton.swift */; };
92D49054278FF4E50085C018 /* PersonaButtonCarouselModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92D49053278FF4E50085C018 /* PersonaButtonCarouselModifiers.swift */; };
Expand Down Expand Up @@ -367,6 +368,7 @@
929215B82B6C75E500D4EA9F /* AvatarTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarTests.swift; sourceTree = "<group>"; };
929DD255266ED3AC00E8175E /* PersonaButtonCarouselTokenSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersonaButtonCarouselTokenSet.swift; sourceTree = "<group>"; };
929DD258266ED3B600E8175E /* PersonaButtonCarousel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersonaButtonCarousel.swift; sourceTree = "<group>"; };
929F2ACE2BB77ED100683EA8 /* FluentButtonToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FluentButtonToggleStyle.swift; sourceTree = "<group>"; };
92A1E4F326A791590007ED60 /* MSFCardNudge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSFCardNudge.swift; sourceTree = "<group>"; };
92B7E6A12684262900EFC15E /* MSFPersonaButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSFPersonaButton.swift; sourceTree = "<group>"; };
92D49053278FF4E50085C018 /* PersonaButtonCarouselModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonaButtonCarouselModifiers.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -668,6 +670,7 @@
A5CEC23020E451D00016922A /* Button.swift */,
EC65F78F292EDCEF002A1A23 /* ButtonTokenSet.swift */,
92279B322B97C7DC00994D88 /* FluentButtonStyle.swift */,
929F2ACE2BB77ED100683EA8 /* FluentButtonToggleStyle.swift */,
);
path = Button;
sourceTree = "<group>";
Expand Down Expand Up @@ -1733,6 +1736,7 @@
925728F7276D6AF800EE1019 /* ShadowInfo.swift in Sources */,
66963D0A29CA7F89006F5FA9 /* TwoLineTitleViewTokenSet.swift in Sources */,
5328D97326FBA3D700F3723B /* IndeterminateProgressBarModifiers.swift in Sources */,
929F2ACF2BB77ED100683EA8 /* FluentButtonToggleStyle.swift in Sources */,
923DB9D7274CB66D00D8E58A /* ControlHostingView.swift in Sources */,
5314E03B25F00E3D0099271A /* BadgeStringExtractor.swift in Sources */,
EC1C31732923022E00CF052C /* SegmentedControlTokenSet.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion ios/FluentUI/Button/ButtonTokenSet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ public class ButtonTokenSet: ControlTokenSet<ButtonToken> {
}
case .cornerRadius:
return .float {
if Compatibility.isDeviceIdiomVision() {
if style().isFloating || Compatibility.isDeviceIdiomVision() {
return ButtonTokenSet.minContainerHeight(style: style(), size: size()) / 2
}
switch size() {
Expand Down
34 changes: 20 additions & 14 deletions ios/FluentUI/Button/FluentButtonStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import SwiftUI
import UIKit

/// ButtonStyle which configures the Button View according to its state and design tokens.
/// `ButtonStyle` which configures the `Button` according to its state and design tokens.
public struct FluentButtonStyle: SwiftUI.ButtonStyle {
public init(style: ButtonStyle) {
self.style = style
Expand All @@ -16,10 +16,10 @@ public struct FluentButtonStyle: SwiftUI.ButtonStyle {
let isPressed = configuration.isPressed
let isDisabled = !isEnabled
let isFocused = isFocused
let isFloatingStyle = style.isFloating
let size = size

let tokenSet = ButtonTokenSet(style: { style }, size: { size })
tokenSet.replaceAllOverrides(with: tokenOverrides)
tokenSet.update(fluentTheme)

let cornerRadius = tokenSet[.cornerRadius].float
Expand All @@ -44,27 +44,23 @@ public struct FluentButtonStyle: SwiftUI.ButtonStyle {
}

@ViewBuilder var backgroundView: some View {
if isFloatingStyle {
backgroundColor.clipShape(Capsule())
} else {
if backgroundColor != Color(.clear) {
backgroundColor.clipShape(RoundedRectangle(cornerRadius: cornerRadius))
}
}

@ViewBuilder var overlayView: some View {
if borderColor != Color(.clear) {
if isFloatingStyle {
Capsule()
.stroke(style: .init(lineWidth: tokenSet[.borderWidth].float))
.foregroundStyle(borderColor)
} else {
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
.stroke(style: .init(lineWidth: tokenSet[.borderWidth].float))
.foregroundStyle(borderColor)
}
contentShape
.stroke(style: .init(lineWidth: tokenSet[.borderWidth].float))
.foregroundStyle(borderColor)
}
}

@ViewBuilder var contentShape: some Shape {
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
}

return configuration.label
.font(Font(tokenSet[.titleFont].uiFont))
.foregroundStyle(foregroundColor)
Expand All @@ -73,6 +69,7 @@ public struct FluentButtonStyle: SwiftUI.ButtonStyle {
.background(backgroundView)
.overlay { overlayView }
.applyFluentShadow(shadowInfo: shadowInfo)
.contentShape(contentShape)
.pointerInteraction(isEnabled)
}

Expand Down Expand Up @@ -108,4 +105,13 @@ public struct FluentButtonStyle: SwiftUI.ButtonStyle {
trailing: style.isFloating ? fabAlternativePadding : horizontalPadding
)
}

private var tokenOverrides: [ButtonToken: ControlTokenValue]?
}

public extension FluentButtonStyle {
/// Provide override values for various `ButtonToken` values.
mutating func overrideTokens(_ overrides: [ButtonToken: ControlTokenValue]) {
tokenOverrides = overrides
}
}
49 changes: 49 additions & 0 deletions ios/FluentUI/Button/FluentButtonToggleStyle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//

import SwiftUI

/// `ToggleStyle` which configures the `Toggle` according to its state and design tokens.
public struct FluentButtonToggleStyle: ToggleStyle {
public init() {}

@Environment(\.fluentTheme) private var fluentTheme: FluentTheme

public func makeBody(configuration: Configuration) -> some View {
SwiftUI.Button(action: {
configuration.isOn.toggle()
}, label: {
configuration.label
})
.buttonStyle(configuration.isOn ? buttonStyleOn : buttonStyle)
}

private var buttonStyle: FluentButtonStyle {
var style = FluentButtonStyle(style: .transparentNeutral)
style.overrideTokens(buttonTokens)
return style
}

private var buttonStyleOn: FluentButtonStyle {
var style = FluentButtonStyle(style: .subtle)
style.overrideTokens(buttonOnTokens)
return style
}

private var buttonTokens: [ButtonToken: ControlTokenValue] {
[
.cornerRadius: .float { GlobalTokens.corner(.radius40) }
]
}

private var buttonOnTokens: [ButtonToken: ControlTokenValue] {
let backgroundColor = fluentTheme.color(.brandBackgroundTint)
return buttonTokens.merging([
.backgroundColor: .uiColor { backgroundColor },
.backgroundPressedColor: .uiColor { backgroundColor },
.backgroundFocusedColor: .uiColor { backgroundColor }
]) { (_, new) in new }
}
}

0 comments on commit 585aa32

Please sign in to comment.