From 5e9fc8673755c88e0bff9bd06b02061d45b66876 Mon Sep 17 00:00:00 2001 From: Ludovic PINEL Date: Tue, 28 Jan 2025 11:29:16 +0100 Subject: [PATCH 01/13] First version of switch without tokens for now --- .../Switch/Internal/OUDSSwitchModifier.swift | 142 ++++++++++++++++++ .../Sources/Switch/OUDSSwitch.swift | 67 +++++++++ 2 files changed, 209 insertions(+) create mode 100644 OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchModifier.swift create mode 100644 OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift diff --git a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchModifier.swift b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchModifier.swift new file mode 100644 index 000000000..ea4155810 --- /dev/null +++ b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchModifier.swift @@ -0,0 +1,142 @@ +// +// Software Name: OUDS iOS +// SPDX-FileCopyrightText: Copyright (c) Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license, +// the text of which is available at https://opensource.org/license/MIT/ +// or see the "LICENSE" file for more details. +// +// Authors: See CONTRIBUTORS.txt +// Software description: A SwiftUI components library with code examples for Orange Unified Design System +// + +import SwiftUI + +/// The internal state used by modifiers to handle all states of the button. +enum InternalSwitchState { + case enabled, hover, pressed, disabled +} + +/// Just here to catch the isPressed state on the button +struct SwitchButtonStyle: ButtonStyle { + + // MARK: Stored properties + + @Environment(\.isEnabled) private var isEnabled + @State private var isHover: Bool = false + let isOn: Bool + + // MARK: Body + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .onHover { isHover in + self.isHover = isHover + } + .modifier(SwitchButtonModifier(internalState: internalState(isPressed: configuration.isPressed), isOn: isOn)) + } + + // MARK: Private Helpers + + private func internalState(isPressed: Bool) -> InternalSwitchState { + if !isEnabled { + return .disabled + } + + if isPressed { + return .pressed + } + + if isHover { + return .hover + } + + return .enabled + } +} + +struct SwitchButtonModifier: ViewModifier { + + // MARK: Stored properties + + let internalState: InternalSwitchState + let isOn: Bool + + // MARK: Body + + func body(content: Content) -> some View { + HStack(alignment: .center, spacing: 10) { + HStack(alignment: .center, spacing: 10) { + cursor + } + .padding(10) + .frame(width: cursorWidth, height: cursorHeight, alignment: .center) + .background(Constants.inputSwitchColorCursor) + .cornerRadius(Constants.borderRadiusPill) + .shadow(color: Constants.elevationColorRaised, radius: Constants.elevationBlurRaised / 2, x: Constants.elevationXRaised, y: Constants.elevationYRaised) + } + .padding(.horizontal, spacePadding) + .padding(.vertical, 0) + .frame(width: Constants.inputSwitchSizeWidthTrack, height: Constants.inputSwitchSizeHeightTrack, alignment: cursorHorizontalAlignment) + .background(trackColor) + .cornerRadius(Constants.borderRadiusPill) + } + + // MARK: Private Helpers + + private var cursorWidth: Double { + switch internalState { + case .pressed: + Constants.inputSwitchSizeWidthCursorPressed + case .disabled, .enabled, .hover: + isOn ? Constants.inputSwitchSizeWidthCursorTrue : Constants.inputSwitchSizeWidthCursorFalse + } + } + + private var cursorHeight: Double { + isOn ? Constants.inputSwitchSizeWidthCursorTrue : Constants.inputSwitchSizeWidthCursorFalse + } + + private var cursorHorizontalAlignment: Alignment { + isOn ? .trailing : .leading + } + + private var spacePadding: Double { + isOn ? Constants.inputSwitchSpacePaddingInlineTrue : Constants.inputSwitchSpacePaddingInlineFalse + } + + private var trackColor: Color { + isOn ? .green : .gray + } + + @ViewBuilder + private var cursor: some View { + switch internalState { + case .enabled, .disabled, .hover: + Image(decorative: "Tick") + default: + Circle().fill(Color.white) + } + } +} + +// swiftlint:disable convenience_type +struct Constants { + static let inputSwitchColorTrackTrue = Color(red: 0.24, green: 0.89, blue: 0.35) + static let borderRadiusPill: CGFloat = 2_000.0 + static let inputSwitchSizeWidthTrack: CGFloat = 56 + static let inputSwitchSizeHeightTrack: CGFloat = 32 + static let inputSwitchSpacePaddingInlineTrue: CGFloat = 4 + static let inputSwitchColorCursor: Color = .white + static let inputSwitchSizeWidthCursorTrue: CGFloat = 24 + static let elevationXRaised: CGFloat = 0 + static let elevationYRaised: CGFloat = 1 + static let elevationBlurRaised: CGFloat = 2 + static let elevationColorRaised: Color = .black.opacity(0.32) + static let inputSwitchColorTrackFalse: Color = .black.opacity(0.44) + static let inputSwitchSpacePaddingInlineFalse: CGFloat = 8 + static let inputSwitchSizeWidthCursorFalse: CGFloat = 16 + static let inputSwitchSizeWidthCursorPressed: CGFloat = 32 +} +// swiftlint:enable convenience_type diff --git a/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift b/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift new file mode 100644 index 000000000..87914a7c6 --- /dev/null +++ b/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift @@ -0,0 +1,67 @@ +// +// Software Name: OUDS iOS +// SPDX-FileCopyrightText: Copyright (c) Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license, +// the text of which is available at https://opensource.org/license/MIT/ +// or see the "LICENSE" file for more details. +// +// Authors: See CONTRIBUTORS.txt +// Software description: A SwiftUI components library with code examples for Orange Unified Design System +// + +import OUDSFoundations +import SwiftUI + +// MARK: - OUDS Button + +/// The ``OUDSSwitch`` proposes layout with text only, icon only or text with icon. +/// +/// +/// ## Code samples +/// +/// ```swift +/// ``` +/// +/// ## Design documentation +/// +/// See +/// +/// - Since: 0.10.0 +public struct OUDSSwitch: View { + + // MARK: Stored Properties + var isOn: Binding + + // MARK: Initializers + + /// Create a switch with text and icon. + /// + /// - Parameters: + public init(isOn: Binding) { + self.isOn = isOn + } + + // MARK: Body + public var body: some View { + Button { + isOn.wrappedValue.toggle() + } label: { + Text("") + } + .buttonStyle(SwitchButtonStyle(isOn: isOn.wrappedValue)) + } +} + +struct SwitchTest: View { + @State var isOn: Bool + + var body: some View { + OUDSSwitch(isOn: $isOn) + } +} + +#Preview { + SwitchTest(isOn: true) +} From 691f1691a391c8028ca4447cb9f20b0031fb139f Mon Sep 17 00:00:00 2001 From: Ludovic PINEL Date: Tue, 28 Jan 2025 14:39:50 +0100 Subject: [PATCH 02/13] Add screen for switch component --- .../DesignToolbox.xcodeproj/project.pbxproj | 30 ++++- .../Pages/Components/ComponentsPage.swift | 1 + .../Switch/SwitchConfiguration.swift | 120 ++++++++++++++++++ .../Components/Switch/SwitchElement.swift | 32 +++++ .../Pages/Components/Switch/SwitchPage.swift | 92 ++++++++++++++ .../Contents.json | 12 ++ .../il_component_switch.svg | 19 +++ .../Resources/en.lproj/Localizable.strings | 4 + Package.resolved | 33 ----- 9 files changed, 308 insertions(+), 35 deletions(-) create mode 100644 DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift create mode 100644 DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchElement.swift create mode 100644 DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift create mode 100644 DesignToolbox/DesignToolbox/Resources/Assets.xcassets/Components/il_component_switch.imageset/Contents.json create mode 100644 DesignToolbox/DesignToolbox/Resources/Assets.xcassets/Components/il_component_switch.imageset/il_component_switch.svg delete mode 100644 Package.resolved diff --git a/DesignToolbox/DesignToolbox.xcodeproj/project.pbxproj b/DesignToolbox/DesignToolbox.xcodeproj/project.pbxproj index 6f5a73f77..6480801c6 100644 --- a/DesignToolbox/DesignToolbox.xcodeproj/project.pbxproj +++ b/DesignToolbox/DesignToolbox.xcodeproj/project.pbxproj @@ -58,6 +58,12 @@ 0765B4A02CC15BBC00E69359 /* NamedColor+Content.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0765B49F2CC15BBC00E69359 /* NamedColor+Content.swift */; }; 0765B4A42CC15C3E00E69359 /* NamedColor+Chart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0765B4A32CC15C3E00E69359 /* NamedColor+Chart.swift */; }; 0765B4A62CC15C9D00E69359 /* NamedColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0765B4A52CC15C9D00E69359 /* NamedColor.swift */; }; + 0771A06A2D50FD9D0050FF31 /* SwitchPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0771A0682D50FD9D0050FF31 /* SwitchPage.swift */; }; + 0771A06B2D50FD9D0050FF31 /* SwitchConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0771A0662D50FD9D0050FF31 /* SwitchConfiguration.swift */; }; + 0771A06C2D50FD9D0050FF31 /* SwitchElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0771A0672D50FD9D0050FF31 /* SwitchElement.swift */; }; + 0771A06D2D50FD9D0050FF31 /* SwitchPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0771A0682D50FD9D0050FF31 /* SwitchPage.swift */; }; + 0771A06E2D50FD9D0050FF31 /* SwitchConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0771A0662D50FD9D0050FF31 /* SwitchConfiguration.swift */; }; + 0771A06F2D50FD9D0050FF31 /* SwitchElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0771A0672D50FD9D0050FF31 /* SwitchElement.swift */; }; 077CCE572CB426090059CC28 /* SpaceTokenElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077CCE512CB426090059CC28 /* SpaceTokenElement.swift */; }; 077CCE582CB426090059CC28 /* SpaceTokenPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077CCE522CB426090059CC28 /* SpaceTokenPage.swift */; }; 077CCE592CB426090059CC28 /* DimensionTokenElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077CCE542CB426090059CC28 /* DimensionTokenElement.swift */; }; @@ -252,6 +258,9 @@ 0765B49F2CC15BBC00E69359 /* NamedColor+Content.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NamedColor+Content.swift"; sourceTree = ""; }; 0765B4A32CC15C3E00E69359 /* NamedColor+Chart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NamedColor+Chart.swift"; sourceTree = ""; }; 0765B4A52CC15C9D00E69359 /* NamedColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamedColor.swift; sourceTree = ""; }; + 0771A0662D50FD9D0050FF31 /* SwitchConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchConfiguration.swift; sourceTree = ""; }; + 0771A0672D50FD9D0050FF31 /* SwitchElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchElement.swift; sourceTree = ""; }; + 0771A0682D50FD9D0050FF31 /* SwitchPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchPage.swift; sourceTree = ""; }; 077CCE512CB426090059CC28 /* SpaceTokenElement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceTokenElement.swift; sourceTree = ""; }; 077CCE522CB426090059CC28 /* SpaceTokenPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceTokenPage.swift; sourceTree = ""; }; 077CCE542CB426090059CC28 /* DimensionTokenElement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DimensionTokenElement.swift; sourceTree = ""; }; @@ -508,6 +517,16 @@ path = NamedColor; sourceTree = ""; }; + 0771A0692D50FD9D0050FF31 /* Switch */ = { + isa = PBXGroup; + children = ( + 0771A0662D50FD9D0050FF31 /* SwitchConfiguration.swift */, + 0771A0672D50FD9D0050FF31 /* SwitchElement.swift */, + 0771A0682D50FD9D0050FF31 /* SwitchPage.swift */, + ); + path = Switch; + sourceTree = ""; + }; 077CCE532CB426090059CC28 /* Space */ = { isa = PBXGroup; children = ( @@ -786,10 +805,11 @@ 51BD760E2C466FCF0033365D /* Components */ = { isa = PBXGroup; children = ( - 07B3CCAD2D40E14C00DBB10A /* Link */, - 07F0AFE62CFE055300D334DD /* Utils */, 072141182D02F52A00B7B9C6 /* Button */, 07F0AFD52CFDAD9200D334DD /* EmptyState */, + 07B3CCAD2D40E14C00DBB10A /* Link */, + 0771A0692D50FD9D0050FF31 /* Switch */, + 07F0AFE62CFE055300D334DD /* Utils */, 07F0AFDD2CFDD1FD00D334DD /* ComponentsPage.swift */, ); path = Components; @@ -1142,6 +1162,9 @@ 0721411B2D02F52A00B7B9C6 /* ButtonElement.swift in Sources */, 6DB260E12CD0F0520091F72E /* NameSpace+PaddingInset.swift in Sources */, 51952A752D3F9D850068B807 /* DesignToolboxTokenIllustrationBackground.swift in Sources */, + 0771A06D2D50FD9D0050FF31 /* SwitchPage.swift in Sources */, + 0771A06E2D50FD9D0050FF31 /* SwitchConfiguration.swift in Sources */, + 0771A06F2D50FD9D0050FF31 /* SwitchElement.swift in Sources */, 6DB260E22CD0F0520091F72E /* NameSpace+PaddingStack.swift in Sources */, 6DB260E32CD0F0520091F72E /* NameSpace+GapInline.swift in Sources */, 6DB260E42CD0F0520091F72E /* NameSpace+GapStack.swift in Sources */, @@ -1212,6 +1235,9 @@ 51952A722D3F9D720068B807 /* DesignToolboxTokenIllustration.swift in Sources */, 0765B4982CC15A4000E69359 /* NamedColor+Always.swift in Sources */, 07F75A392CC6462D0004F1AD /* NamedSpace+Fixed.swift in Sources */, + 0771A06A2D50FD9D0050FF31 /* SwitchPage.swift in Sources */, + 0771A06B2D50FD9D0050FF31 /* SwitchConfiguration.swift in Sources */, + 0771A06C2D50FD9D0050FF31 /* SwitchElement.swift in Sources */, 07CF42842CA45DA9000BD03E /* BorderTokenElement.swift in Sources */, 07D7E1972D035566006472EB /* ButtonConfiguration.swift in Sources */, 07CF42802CA41325000BD03E /* OpacityTokenPage.swift in Sources */, diff --git a/DesignToolbox/DesignToolbox/Pages/Components/ComponentsPage.swift b/DesignToolbox/DesignToolbox/Pages/Components/ComponentsPage.swift index 5951acfed..5f07529ec 100644 --- a/DesignToolbox/DesignToolbox/Pages/Components/ComponentsPage.swift +++ b/DesignToolbox/DesignToolbox/Pages/Components/ComponentsPage.swift @@ -20,6 +20,7 @@ struct ComponentsPage: View { let componentElements: [DesignToolboxElement] = [ ButtonElement(), LinkElement(), + SwitchElement(), ] var body: some View { diff --git a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift new file mode 100644 index 000000000..84e19dc6d --- /dev/null +++ b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift @@ -0,0 +1,120 @@ +// +// Software Name: OUDS iOS +// SPDX-FileCopyrightText: Copyright (c) Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license, +// the text of which is available at https://opensource.org/license/MIT/ +// or see the "LICENSE" file for more details. +// +// Authors: See CONTRIBUTORS.txt +// Software description: A SwiftUI components library with code examples for Orange Unified Design System +// + +import OUDSComponents +import SwiftUI + +// MARK: - Switch Configuration Model + +/// The model shared between `SwitchPageConfiguration` view and `SwitchPageComponent` view. +final class SwitchConfigurationModel: ComponentConfiguration { + + // MARK: Published properties + + @Published var enabled: Bool = true { + didSet { updateCode() } + } + + @Published var layout: SwitchLayout { + didSet { updateCode() } + } + + // MARK: Initializer + + override init() { + self.enabled = true + self.layout = .textOnly + } + + deinit { } + + // MARK: Component Configuration + + private var disableCode: String { + ".disable(\(enabled ? "false" : "true"))" + } + + override func updateCode() { + switch layout { + case .textOnly: + code = + """ + OUDSSwitch(isOn: $isOn) + \(disableCode)) + """ + case .iconOnly: + code = + """ + OUDSSwitch(isOn: $isOn) + \(disableCode)) + """ + case .iconAndText: + code = + """ + OUDSSwitch(isOn: $isOn) + \(disableCode)) + """ + } + } +} + +// MARK: - Switch Layout + +enum SwitchLayout: CaseIterable, CustomStringConvertible { + case textOnly + case iconAndText + case iconOnly + + var description: String { + switch self { + case .textOnly: + "app_components_button_textOnlyLayout_label" + case .iconAndText: + "app_components_button_iconAndTextLayout_label" + case .iconOnly: + "app_components_button_iconOnlyLayout_label" + } + } + + var id: String { description } +} + +// MARK: - Switch Configuration View + +struct SwitchConfiguration: View { + + @Environment(\.theme) private var theme + @Environment(\.colorScheme) private var colorScheme + + @StateObject var model: SwitchConfigurationModel + + var body: some View { + VStack(alignment: .leading, spacing: theme.spaces.spaceFixedMedium) { + Toggle("app_common_enabled_label", isOn: $model.enabled) + .typeHeadingMedium(theme) + .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) + + VStack(alignment: .leading) { + Text(LocalizedStringKey("app_components_button_layout_label")) + .typeHeadingMedium(theme) + .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) + Picker("app_components_button_layout_label", selection: $model.layout) { + ForEach(SwitchLayout.allCases, id: \.id) { layout in + Text(LocalizedStringKey(layout.description)).tag(layout) + } + } + .pickerStyle(.segmented) + } + } + } +} diff --git a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchElement.swift b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchElement.swift new file mode 100644 index 000000000..6b36cb49e --- /dev/null +++ b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchElement.swift @@ -0,0 +1,32 @@ +// +// Software Name: OUDS iOS +// SPDX-FileCopyrightText: Copyright (c) Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license, +// the text of which is available at https://opensource.org/license/MIT/ +// or see the "LICENSE" file for more details. +// +// Authors: See CONTRIBUTORS.txt +// Software description: A SwiftUI components library with code examples for Orange Unified Design System +// + +import SwiftUI + +struct SwitchElement: DesignToolboxElement { + let name: String + let image: Image + let pageDescription: AnyView + + init() { + name = "app_components_switch_label" + image = Image(decorative: "il_component_switch").renderingMode(.original) + pageDescription = AnyView(DesignToolboxElementPage( + name: name, + image: nil, + description: "app_components_switch_description_text", + illustration: AnyView(SwitchPage()) + ) + ) + } +} diff --git a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift new file mode 100644 index 000000000..0c763e7e6 --- /dev/null +++ b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift @@ -0,0 +1,92 @@ +// +// Software Name: OUDS iOS +// SPDX-FileCopyrightText: Copyright (c) Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license, +// the text of which is available at https://opensource.org/license/MIT/ +// or see the "LICENSE" file for more details. +// +// Authors: See CONTRIBUTORS.txt +// Software description: A SwiftUI components library with code examples for Orange Unified Design System +// + +import OUDS +import OUDSComponents +import SwiftUI + +// MARK: Switch page + +struct SwitchPage: View { + + private let configuration = SwitchConfigurationModel() + + var body: some View { + ComponentConfigurationView( + configuration: configuration, + componentView: componentView, + configurationView: configurationView + ) + } + + @ViewBuilder + private func componentView(with configuration: ComponentConfiguration) -> some View { + if let model = configuration as? SwitchConfigurationModel { + SwitchIllustration(model: model) + } + } + + @ViewBuilder + private func configurationView(with configuration: ComponentConfiguration) -> some View { + if let model = configuration as? SwitchConfigurationModel { + SwitchConfiguration(model: model) + } + } +} + +// MARK: Switch Illustration + +struct SwitchIllustration: View { + + @Environment(\.colorScheme) private var colorScheme + + let model: SwitchConfigurationModel + + var body: some View { + VStack(alignment: .center) { + // TODO: Build a modifier to inverse colorscheme or force to a colorscheme + SwitchDemo(model: model) + .colorScheme(colorScheme == .dark ? .light : .dark) + SwitchDemo(model: model) + } + } +} + +// MARK: - Switch Demo + +private struct SwitchDemo: View { + + @Environment(\.theme) private var theme + + @StateObject var model: SwitchConfigurationModel + @State var isOn: Bool = true + + var body: some View { + HStack(alignment: .center) { + Spacer() + + switch model.layout { + case .iconOnly: + OUDSSwitch(isOn: $isOn) + case .textOnly: + OUDSSwitch(isOn: $isOn) + case .iconAndText: + OUDSSwitch(isOn: $isOn) + } + + Spacer() + } + .disabled(!model.enabled) + .padding(.all, theme.spaces.spaceFixedMedium) + } +} diff --git a/DesignToolbox/DesignToolbox/Resources/Assets.xcassets/Components/il_component_switch.imageset/Contents.json b/DesignToolbox/DesignToolbox/Resources/Assets.xcassets/Components/il_component_switch.imageset/Contents.json new file mode 100644 index 000000000..3fb0420a6 --- /dev/null +++ b/DesignToolbox/DesignToolbox/Resources/Assets.xcassets/Components/il_component_switch.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "il_component_switch.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DesignToolbox/DesignToolbox/Resources/Assets.xcassets/Components/il_component_switch.imageset/il_component_switch.svg b/DesignToolbox/DesignToolbox/Resources/Assets.xcassets/Components/il_component_switch.imageset/il_component_switch.svg new file mode 100644 index 000000000..2fef20f2b --- /dev/null +++ b/DesignToolbox/DesignToolbox/Resources/Assets.xcassets/Components/il_component_switch.imageset/il_component_switch.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings b/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings index a526fc342..94cc436a7 100644 --- a/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings +++ b/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings @@ -129,3 +129,7 @@ "app_components_link_arrowBack_label" = "Back"; "app_components_link_arrowNext_label" = "Next"; "app_components_link_size_label" = "Size"; + +// MARK: Components: Switch +"app_components_switch_label" = "Switch"; +"app_components_switch_description_text" = "A switch is a component that allows the user to toggle between two states, typically \"on\" and \"off\". It is often represented as a button or a slider that changes position or color to indicate the current state. Switches are used to enable or disable features, options, or settings in an intuitive and visual manner."; diff --git a/Package.resolved b/Package.resolved deleted file mode 100644 index 838e45776..000000000 --- a/Package.resolved +++ /dev/null @@ -1,33 +0,0 @@ -{ - "originHash" : "83401976e6d5078c716d3d81b7069a9a115aa96085292549b781eb6d5bfe69b2", - "pins" : [ - { - "identity" : "accessibility-statement-lib-ios", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Orange-OpenSource/accessibility-statement-lib-ios.git", - "state" : { - "revision" : "480bf4629b951e459308379030f1ca0ce28a0987", - "version" : "1.3.0" - } - }, - { - "identity" : "swift-snapshot-testing", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-snapshot-testing.git", - "state" : { - "revision" : "42a086182681cf661f5c47c9b7dc3931de18c6d7", - "version" : "1.17.6" - } - }, - { - "identity" : "swift-syntax", - "kind" : "remoteSourceControl", - "location" : "https://github.com/swiftlang/swift-syntax", - "state" : { - "revision" : "515f79b522918f83483068d99c68daeb5116342d", - "version" : "600.0.0-prerelease-2024-09-04" - } - } - ], - "version" : 3 -} From a018eeea648e2e051f0271c84cafaad85e67a5db Mon Sep 17 00:00:00 2001 From: Ludovic PINEL Date: Tue, 28 Jan 2025 16:32:32 +0100 Subject: [PATCH 03/13] Use first verion of tokens --- .../Components/Tick.imageset/Contents.json | 12 ++++ .../Components/Tick.imageset/Tick.svg | 5 ++ .../Switch/Internal/OUDSSwitchModifier.swift | 66 +++++++++++-------- .../Sources/Switch/OUDSSwitch.swift | 17 ++--- 4 files changed, 60 insertions(+), 40 deletions(-) create mode 100644 DesignToolbox/DesignToolbox/Resources/Assets.xcassets/Components/Tick.imageset/Contents.json create mode 100644 DesignToolbox/DesignToolbox/Resources/Assets.xcassets/Components/Tick.imageset/Tick.svg diff --git a/DesignToolbox/DesignToolbox/Resources/Assets.xcassets/Components/Tick.imageset/Contents.json b/DesignToolbox/DesignToolbox/Resources/Assets.xcassets/Components/Tick.imageset/Contents.json new file mode 100644 index 000000000..f10dee72b --- /dev/null +++ b/DesignToolbox/DesignToolbox/Resources/Assets.xcassets/Components/Tick.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Tick.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DesignToolbox/DesignToolbox/Resources/Assets.xcassets/Components/Tick.imageset/Tick.svg b/DesignToolbox/DesignToolbox/Resources/Assets.xcassets/Components/Tick.imageset/Tick.svg new file mode 100644 index 000000000..ab79a2559 --- /dev/null +++ b/DesignToolbox/DesignToolbox/Resources/Assets.xcassets/Components/Tick.imageset/Tick.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchModifier.swift b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchModifier.swift index ea4155810..c38503784 100644 --- a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchModifier.swift +++ b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchModifier.swift @@ -11,6 +11,9 @@ // Software description: A SwiftUI components library with code examples for Orange Unified Design System // +import OUDS +import OUDSTokensComponent +import OUDSTokensRaw import SwiftUI /// The internal state used by modifiers to handle all states of the button. @@ -60,27 +63,27 @@ struct SwitchButtonModifier: ViewModifier { // MARK: Stored properties + @Environment(\.theme) private var theme + @Environment(\.colorScheme) private var colorScheme + let internalState: InternalSwitchState let isOn: Bool // MARK: Body - func body(content: Content) -> some View { - HStack(alignment: .center, spacing: 10) { - HStack(alignment: .center, spacing: 10) { + HStack(alignment: .center) { + HStack(alignment: .center) { cursor } - .padding(10) .frame(width: cursorWidth, height: cursorHeight, alignment: .center) - .background(Constants.inputSwitchColorCursor) - .cornerRadius(Constants.borderRadiusPill) - .shadow(color: Constants.elevationColorRaised, radius: Constants.elevationBlurRaised / 2, x: Constants.elevationXRaised, y: Constants.elevationYRaised) + .background(corsorColor) + .clipShape(Capsule()) + .shadow(elevation: theme.elevations.elevationRaised.elevation(for: colorScheme)) } .padding(.horizontal, spacePadding) - .padding(.vertical, 0) - .frame(width: Constants.inputSwitchSizeWidthTrack, height: Constants.inputSwitchSizeHeightTrack, alignment: cursorHorizontalAlignment) + .frame(width: trackrWidth, height: trackHeight, alignment: cursorHorizontalAlignment) .background(trackColor) - .cornerRadius(Constants.borderRadiusPill) + .clipShape(Capsule()) } // MARK: Private Helpers @@ -95,47 +98,56 @@ struct SwitchButtonModifier: ViewModifier { } private var cursorHeight: Double { + // TODO: Use token isOn ? Constants.inputSwitchSizeWidthCursorTrue : Constants.inputSwitchSizeWidthCursorFalse } + private var corsorColor: Color { + theme.switch.switchColorCursor.color(for: colorScheme) + } + private var cursorHorizontalAlignment: Alignment { isOn ? .trailing : .leading } private var spacePadding: Double { - isOn ? Constants.inputSwitchSpacePaddingInlineTrue : Constants.inputSwitchSpacePaddingInlineFalse + isOn ? theme.switch.switchSpacePaddingInlineTrue : theme.switch.switchSpacePaddingInlineFalse } private var trackColor: Color { - isOn ? .green : .gray + (isOn ? theme.switch.switchColorTrackTrue : theme.switch.switchColorTrackFalse) + .color(for: colorScheme) + } + private var trackrWidth: Double { + Constants.inputSwitchSizeWidthTrack + } + + private var trackHeight: Double { + // TODO: use token + Constants.inputSwitchSizeHeightTrack } @ViewBuilder private var cursor: some View { - switch internalState { - case .enabled, .disabled, .hover: - Image(decorative: "Tick") - default: - Circle().fill(Color.white) + if isOn { + switch internalState { + case .enabled, .disabled, .hover: + // TODO: when, Link is merged, use right asset in ouds bundle not in main + Image(decorative: "Tick") + .renderingMode(.template) + .foregroundStyle(theme.switch.switchColorCheck.color(for: colorScheme)) + default: + EmptyView() + } } } } // swiftlint:disable convenience_type struct Constants { - static let inputSwitchColorTrackTrue = Color(red: 0.24, green: 0.89, blue: 0.35) - static let borderRadiusPill: CGFloat = 2_000.0 static let inputSwitchSizeWidthTrack: CGFloat = 56 static let inputSwitchSizeHeightTrack: CGFloat = 32 - static let inputSwitchSpacePaddingInlineTrue: CGFloat = 4 - static let inputSwitchColorCursor: Color = .white static let inputSwitchSizeWidthCursorTrue: CGFloat = 24 - static let elevationXRaised: CGFloat = 0 - static let elevationYRaised: CGFloat = 1 - static let elevationBlurRaised: CGFloat = 2 - static let elevationColorRaised: Color = .black.opacity(0.32) - static let inputSwitchColorTrackFalse: Color = .black.opacity(0.44) - static let inputSwitchSpacePaddingInlineFalse: CGFloat = 8 static let inputSwitchSizeWidthCursorFalse: CGFloat = 16 static let inputSwitchSizeWidthCursorPressed: CGFloat = 32 } diff --git a/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift b/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift index 87914a7c6..e5d98b691 100644 --- a/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift +++ b/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift @@ -36,14 +36,17 @@ public struct OUDSSwitch: View { // MARK: Initializers - /// Create a switch with text and icon. + /// Creates a switch with no label. /// /// - Parameters: + /// - isOn: A binding to a property that determines whether the switch is on + /// or off. public init(isOn: Binding) { self.isOn = isOn } // MARK: Body + public var body: some View { Button { isOn.wrappedValue.toggle() @@ -53,15 +56,3 @@ public struct OUDSSwitch: View { .buttonStyle(SwitchButtonStyle(isOn: isOn.wrappedValue)) } } - -struct SwitchTest: View { - @State var isOn: Bool - - var body: some View { - OUDSSwitch(isOn: $isOn) - } -} - -#Preview { - SwitchTest(isOn: true) -} From 770cdad65b7e135c50bfa559fd3fb5d00dcab821 Mon Sep 17 00:00:00 2001 From: Ludovic PINEL Date: Thu, 30 Jan 2025 14:15:43 +0100 Subject: [PATCH 04/13] Set ListItem as Button --- .../DesignToolbox.xcodeproj/project.pbxproj | 6 ++ .../Switch/SwitchConfiguration.swift | 88 +++++++----------- .../Pages/Components/Switch/SwitchPage.swift | 42 +++++---- .../DesignToolboxBackgroundModifier.swift | 39 ++++++++ ...hModifier.swift => OUDSSwitchButton.swift} | 87 +++++------------ .../Switch/Internal/OUDSSwitchLabel.swift | 93 +++++++++++++++++++ .../Internal/OUDSSwitchLabeledStyle.swift | 81 ++++++++++++++++ .../Internal/OUDSSwitchNestedStyle.swift | 53 +++++++++++ .../Sources/Switch/OUDSSwitch.swift | 51 ++++++++-- .../DividerModifier/DividerModifier.swift | 44 +++++++++ .../DividerModifier/View+Divider.swift | 33 +++++++ 11 files changed, 480 insertions(+), 137 deletions(-) create mode 100644 DesignToolbox/DesignToolbox/Pages/Utils/DesignToolboxBackgroundModifier.swift rename OUDS/Core/Components/Sources/Switch/Internal/{OUDSSwitchModifier.swift => OUDSSwitchButton.swift} (59%) create mode 100644 OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabel.swift create mode 100644 OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabeledStyle.swift create mode 100644 OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchNestedStyle.swift create mode 100644 OUDS/Core/Components/Sources/ViewModifiers/DividerModifier/DividerModifier.swift create mode 100644 OUDS/Core/Components/Sources/ViewModifiers/DividerModifier/View+Divider.swift diff --git a/DesignToolbox/DesignToolbox.xcodeproj/project.pbxproj b/DesignToolbox/DesignToolbox.xcodeproj/project.pbxproj index 6480801c6..9b75e77e8 100644 --- a/DesignToolbox/DesignToolbox.xcodeproj/project.pbxproj +++ b/DesignToolbox/DesignToolbox.xcodeproj/project.pbxproj @@ -69,6 +69,8 @@ 077CCE592CB426090059CC28 /* DimensionTokenElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077CCE542CB426090059CC28 /* DimensionTokenElement.swift */; }; 0784B2712CCB86C500299C10 /* NamedSize+IconWithTypography.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0784B26F2CCB86C500299C10 /* NamedSize+IconWithTypography.swift */; }; 0784B2732CCB8CC800299C10 /* NamedSize+IconDecorative.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0784B2722CCB8CC800299C10 /* NamedSize+IconDecorative.swift */; }; + 079475A32D5103B30081980A /* DesignToolboxBackgroundModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 079475A22D5103B30081980A /* DesignToolboxBackgroundModifier.swift */; }; + 079475A42D5103B30081980A /* DesignToolboxBackgroundModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 079475A22D5103B30081980A /* DesignToolboxBackgroundModifier.swift */; }; 07AB45872D4D08080001D237 /* DesignToolboxChoicePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07AB45862D4D08080001D237 /* DesignToolboxChoicePicker.swift */; }; 07AB45882D4D08080001D237 /* DesignToolboxChoicePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07AB45862D4D08080001D237 /* DesignToolboxChoicePicker.swift */; }; 07AB458A2D50CB9B0001D237 /* DesignToolboxTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07AB45892D50CB9B0001D237 /* DesignToolboxTextField.swift */; }; @@ -267,6 +269,7 @@ 077CCE5B2CB431C50059CC28 /* DesignToolboxVariantElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesignToolboxVariantElement.swift; sourceTree = ""; }; 0784B26F2CCB86C500299C10 /* NamedSize+IconWithTypography.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NamedSize+IconWithTypography.swift"; sourceTree = ""; }; 0784B2722CCB8CC800299C10 /* NamedSize+IconDecorative.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NamedSize+IconDecorative.swift"; sourceTree = ""; }; + 079475A22D5103B30081980A /* DesignToolboxBackgroundModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesignToolboxBackgroundModifier.swift; sourceTree = ""; }; 07AB45862D4D08080001D237 /* DesignToolboxChoicePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesignToolboxChoicePicker.swift; sourceTree = ""; }; 07AB45892D50CB9B0001D237 /* DesignToolboxTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesignToolboxTextField.swift; sourceTree = ""; }; 07B3CCAE2D40E34700DBB10A /* LinkConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkConfiguration.swift; sourceTree = ""; }; @@ -420,6 +423,7 @@ 073543142CA17244001187EA /* Utils */ = { isa = PBXGroup; children = ( + 079475A22D5103B30081980A /* DesignToolboxBackgroundModifier.swift */, 0735430F2CA15440001187EA /* Cards */, 0708222D2CFF617000570EC7 /* Elements */, 07F7533A2CC785620007450D /* DesignToolboxSectionHeaderStyle.swift */, @@ -1169,6 +1173,7 @@ 6DB260E32CD0F0520091F72E /* NameSpace+GapInline.swift in Sources */, 6DB260E42CD0F0520091F72E /* NameSpace+GapStack.swift in Sources */, 6DB260DD2CD0F01F0091F72E /* NamedSpace+Scaled.swift in Sources */, + 079475A42D5103B30081980A /* DesignToolboxBackgroundModifier.swift in Sources */, 6DB260DC2CD0F0000091F72E /* DesignToolboxSectionHeaderStyle.swift in Sources */, 6DB260DB2CD0EFD90091F72E /* NamedSize+IconWithTypography.swift in Sources */, 6DB260DA2CD0EF9D0091F72E /* NamedSize+IconDecorative.swift in Sources */, @@ -1231,6 +1236,7 @@ 075114DC2CB7D28D00B8B759 /* SizeTokenPage.swift in Sources */, 07F0AFD72CFDB3CC00D334DD /* ComponentPage.swift in Sources */, 077CCE592CB426090059CC28 /* DimensionTokenElement.swift in Sources */, + 079475A32D5103B30081980A /* DesignToolboxBackgroundModifier.swift in Sources */, 07CF42852CA45DA9000BD03E /* BorderTokenPage.swift in Sources */, 51952A722D3F9D720068B807 /* DesignToolboxTokenIllustration.swift in Sources */, 0765B4982CC15A4000E69359 /* NamedColor+Always.swift in Sources */, diff --git a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift index 84e19dc6d..4833a7f05 100644 --- a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift +++ b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift @@ -25,15 +25,21 @@ final class SwitchConfigurationModel: ComponentConfiguration { didSet { updateCode() } } - @Published var layout: SwitchLayout { - didSet { updateCode() } - } + @Published var switchOnly: Bool + @Published var helperText: Bool + @Published var icon: Bool + @Published var onError: Bool + @Published var divider: Bool // MARK: Initializer override init() { - self.enabled = true - self.layout = .textOnly + enabled = true + switchOnly = false + helperText = true + icon = true + onError = false + divider = true } deinit { } @@ -45,48 +51,12 @@ final class SwitchConfigurationModel: ComponentConfiguration { } override func updateCode() { - switch layout { - case .textOnly: - code = - """ - OUDSSwitch(isOn: $isOn) - \(disableCode)) - """ - case .iconOnly: - code = + code = """ OUDSSwitch(isOn: $isOn) \(disableCode)) """ - case .iconAndText: - code = - """ - OUDSSwitch(isOn: $isOn) - \(disableCode)) - """ - } - } -} - -// MARK: - Switch Layout - -enum SwitchLayout: CaseIterable, CustomStringConvertible { - case textOnly - case iconAndText - case iconOnly - - var description: String { - switch self { - case .textOnly: - "app_components_button_textOnlyLayout_label" - case .iconAndText: - "app_components_button_iconAndTextLayout_label" - case .iconOnly: - "app_components_button_iconOnlyLayout_label" - } } - - var id: String { description } } // MARK: - Switch Configuration View @@ -104,17 +74,29 @@ struct SwitchConfiguration: View { .typeHeadingMedium(theme) .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) - VStack(alignment: .leading) { - Text(LocalizedStringKey("app_components_button_layout_label")) - .typeHeadingMedium(theme) - .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) - Picker("app_components_button_layout_label", selection: $model.layout) { - ForEach(SwitchLayout.allCases, id: \.id) { layout in - Text(LocalizedStringKey(layout.description)).tag(layout) - } - } - .pickerStyle(.segmented) - } + Toggle("switch only", isOn: $model.switchOnly) + .typeHeadingMedium(theme) + .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) + + Toggle("Helper Text", isOn: $model.helperText) + .typeHeadingMedium(theme) + .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) + .disabled(model.switchOnly) + + Toggle("Icon", isOn: $model.icon) + .typeHeadingMedium(theme) + .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) + .disabled(model.switchOnly) + + Toggle("Divider", isOn: $model.divider) + .typeHeadingMedium(theme) + .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) + .disabled(model.switchOnly) + + Toggle("On error", isOn: $model.onError) + .typeHeadingMedium(theme) + .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) + .disabled(model.switchOnly) } } } diff --git a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift index 0c763e7e6..b89e2c3f2 100644 --- a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift +++ b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift @@ -49,15 +49,14 @@ struct SwitchPage: View { struct SwitchIllustration: View { @Environment(\.colorScheme) private var colorScheme - let model: SwitchConfigurationModel var body: some View { VStack(alignment: .center) { // TODO: Build a modifier to inverse colorscheme or force to a colorscheme SwitchDemo(model: model) - .colorScheme(colorScheme == .dark ? .light : .dark) SwitchDemo(model: model) + .colorScheme(colorScheme == .dark ? .light : .dark) } } } @@ -66,27 +65,38 @@ struct SwitchIllustration: View { private struct SwitchDemo: View { - @Environment(\.theme) private var theme + // MARK: Stored properties + @Environment(\.theme) private var theme @StateObject var model: SwitchConfigurationModel @State var isOn: Bool = true + // MARK: Body + var body: some View { - HStack(alignment: .center) { - Spacer() - - switch model.layout { - case .iconOnly: - OUDSSwitch(isOn: $isOn) - case .textOnly: - OUDSSwitch(isOn: $isOn) - case .iconAndText: - OUDSSwitch(isOn: $isOn) + Group { + if model.switchOnly { + HStack(alignment: .center) { + Spacer() + OUDSSwitch(isOn: $isOn) + .disabled(!model.enabled) + Spacer() + } + } else { + OUDSSwitch(isOn: $isOn, label: "Label", helperText: helperText, icon: icon, onError: model.onError, divider: model.divider) + .disabled(!model.enabled) } - - Spacer() } - .disabled(!model.enabled) .padding(.all, theme.spaces.spaceFixedMedium) + .designToolboxBackground(onColoredSurface: false) + } + + // MARK: Private helpers + + private var helperText: String? { + model.helperText ? "Helper Text" : nil + } + private var icon: Image? { + model.icon ? Image(decorative: "ic_heart") : nil } } diff --git a/DesignToolbox/DesignToolbox/Pages/Utils/DesignToolboxBackgroundModifier.swift b/DesignToolbox/DesignToolbox/Pages/Utils/DesignToolboxBackgroundModifier.swift new file mode 100644 index 000000000..6feaa4a5f --- /dev/null +++ b/DesignToolbox/DesignToolbox/Pages/Utils/DesignToolboxBackgroundModifier.swift @@ -0,0 +1,39 @@ +// +// Software Name: OUDS iOS +// SPDX-FileCopyrightText: Copyright (c) Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license, +// the text of which is available at https://opensource.org/license/MIT/ +// or see the "LICENSE" file for more details. +// +// Authors: See CONTRIBUTORS.txt +// Software description: A SwiftUI components library with code examples for Orange Unified Design System +// + +import OUDS +import OUDSTokensSemantic + +import SwiftUI + +private struct DesignToolboxBackgroundModifier: ViewModifier { + + @Environment(\.theme) private var theme + @Environment(\.colorScheme) private var colorScheme + + let coloredSurface: Bool + + func body(content: Content) -> some View { + if coloredSurface { + content.oudsColoredSurface(color: theme.colors.colorSurfaceBrandPrimary.color(for: colorScheme)) + } else { + content.background(theme.colors.colorBgSecondary.color(for: colorScheme)) + } + } +} + +extension View { + func designToolboxBackground(onColoredSurface: Bool) -> some View { + self.modifier(DesignToolboxBackgroundModifier(coloredSurface: onColoredSurface)) + } +} diff --git a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchModifier.swift b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchButton.swift similarity index 59% rename from OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchModifier.swift rename to OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchButton.swift index c38503784..d8483f6a8 100644 --- a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchModifier.swift +++ b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchButton.swift @@ -12,54 +12,10 @@ // import OUDS -import OUDSTokensComponent -import OUDSTokensRaw +import OUDSTokensSemantic import SwiftUI -/// The internal state used by modifiers to handle all states of the button. -enum InternalSwitchState { - case enabled, hover, pressed, disabled -} - -/// Just here to catch the isPressed state on the button -struct SwitchButtonStyle: ButtonStyle { - - // MARK: Stored properties - - @Environment(\.isEnabled) private var isEnabled - @State private var isHover: Bool = false - let isOn: Bool - - // MARK: Body - - func makeBody(configuration: Configuration) -> some View { - configuration.label - .onHover { isHover in - self.isHover = isHover - } - .modifier(SwitchButtonModifier(internalState: internalState(isPressed: configuration.isPressed), isOn: isOn)) - } - - // MARK: Private Helpers - - private func internalState(isPressed: Bool) -> InternalSwitchState { - if !isEnabled { - return .disabled - } - - if isPressed { - return .pressed - } - - if isHover { - return .hover - } - - return .enabled - } -} - -struct SwitchButtonModifier: ViewModifier { +struct OUDSSwitchButton: View { // MARK: Stored properties @@ -70,13 +26,14 @@ struct SwitchButtonModifier: ViewModifier { let isOn: Bool // MARK: Body - func body(content: Content) -> some View { + + var body: some View { HStack(alignment: .center) { HStack(alignment: .center) { cursor } .frame(width: cursorWidth, height: cursorHeight, alignment: .center) - .background(corsorColor) + .background(corsorBackgroundColor) .clipShape(Capsule()) .shadow(elevation: theme.elevations.elevationRaised.elevation(for: colorScheme)) } @@ -102,7 +59,7 @@ struct SwitchButtonModifier: ViewModifier { isOn ? Constants.inputSwitchSizeWidthCursorTrue : Constants.inputSwitchSizeWidthCursorFalse } - private var corsorColor: Color { + private var corsorBackgroundColor: Color { theme.switch.switchColorCursor.color(for: colorScheme) } @@ -115,9 +72,17 @@ struct SwitchButtonModifier: ViewModifier { } private var trackColor: Color { - (isOn ? theme.switch.switchColorTrackTrue : theme.switch.switchColorTrackFalse) - .color(for: colorScheme) + switch internalState { + case .enabled: + return (isOn ? theme.switch.switchColorTrackTrue : theme.switch.switchColorTrackFalse) + .color(for: colorScheme) + case .hover, .pressed: + return (isOn ? theme.switch.switchColorTrackTrueInteraction : theme.switch.switchColorTrackFalseInteraction).color(for: colorScheme) + case .disabled: + return theme.colors.colorActionDisabled.color(for: colorScheme) + } } + private var trackrWidth: Double { Constants.inputSwitchSizeWidthTrack } @@ -135,20 +100,18 @@ struct SwitchButtonModifier: ViewModifier { // TODO: when, Link is merged, use right asset in ouds bundle not in main Image(decorative: "Tick") .renderingMode(.template) - .foregroundStyle(theme.switch.switchColorCheck.color(for: colorScheme)) + .foregroundStyle(cursorColor) default: EmptyView() } } } + private var cursorColor: Color { + switch internalState { + case .enabled, .hover, .pressed: + return theme.switch.switchColorCheck.color(for: colorScheme) + case .disabled: + return theme.colors.colorActionDisabled.color(for: colorScheme) + } + } } - -// swiftlint:disable convenience_type -struct Constants { - static let inputSwitchSizeWidthTrack: CGFloat = 56 - static let inputSwitchSizeHeightTrack: CGFloat = 32 - static let inputSwitchSizeWidthCursorTrue: CGFloat = 24 - static let inputSwitchSizeWidthCursorFalse: CGFloat = 16 - static let inputSwitchSizeWidthCursorPressed: CGFloat = 32 -} -// swiftlint:enable convenience_type diff --git a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabel.swift b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabel.swift new file mode 100644 index 000000000..dbfe2716d --- /dev/null +++ b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabel.swift @@ -0,0 +1,93 @@ +// +// Software Name: OUDS iOS +// SPDX-FileCopyrightText: Copyright (c) Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license, +// the text of which is available at https://opensource.org/license/MIT/ +// or see the "LICENSE" file for more details. +// +// Authors: See CONTRIBUTORS.txt +// Software description: A SwiftUI components library with code examples for Orange Unified Design System +// + +import OUDS +import OUDSTokensSemantic +import SwiftUI + +struct OUDSSwitchLabel: View { + + // MARK: Stored properties + + @Environment(\.theme) private var theme + @Environment(\.colorScheme) private var colorScheme + + struct Label { + let label: String + let helperText: String? + let icon: Image? + let onError: Bool + let divider: Bool + } + + let internalState: InternalSwitchState + let label: Label + + // MARK: Body + + var body: some View { + HStack(spacing: theme.listItem.listItemSpaceColumnGap) { + VStack(alignment: .leading, spacing: 0) { + Text(LocalizedStringKey(label.label)) + .typeLabelDefaultLarge(theme) + .multilineTextAlignment(.leading) + .foregroundStyle(labelColor) + .frame(maxWidth: .infinity, alignment: .leading) + + if let helperText = label.helperText { + Text(LocalizedStringKey(helperText)) + .typeLabelDefaultMedium(theme) + .multilineTextAlignment(.leading) + .foregroundStyle(helperTextColor) + } + } + .frame(maxWidth: .infinity, alignment: .leading) + + if let icon = label.icon { + icon + .resizable() + .renderingMode(.template) + .foregroundStyle(iconColor) + .frame(width: theme.listItem.listItemSizeIcon, height: theme.listItem.listItemSizeIcon) + } + } + } + + private var labelColor: Color { + switch internalState { + case .enabled, .pressed, .hover: + (label.onError ? theme.colors.colorContentStatusNegative : theme.colors.colorContentDefault) + .color(for: colorScheme) + case .disabled: + theme.colors.colorContentDisabled.color(for: colorScheme) + } + } + + private var iconColor: Color { + switch internalState { + case .enabled, .pressed, .hover: + theme.colors.colorContentDefault.color(for: colorScheme) + case .disabled: + theme.colors.colorContentDisabled.color(for: colorScheme) + } + } + + private var helperTextColor: Color { + switch internalState { + case .enabled, .pressed, .hover: + theme.colors.colorContentMuted.color(for: colorScheme) + case .disabled: + theme.colors.colorContentDisabled.color(for: colorScheme) + } + } +} diff --git a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabeledStyle.swift b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabeledStyle.swift new file mode 100644 index 000000000..c6d92c50a --- /dev/null +++ b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabeledStyle.swift @@ -0,0 +1,81 @@ +// +// Software Name: OUDS iOS +// SPDX-FileCopyrightText: Copyright (c) Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license, +// the text of which is available at https://opensource.org/license/MIT/ +// or see the "LICENSE" file for more details. +// +// Authors: See CONTRIBUTORS.txt +// Software description: A SwiftUI components library with code examples for Orange Unified Design System +// + +import OUDS +import OUDSTokensSemantic +import SwiftUI + +/// The internal state used by modifiers to handle all states of the button. +enum InternalSwitchState { + case enabled, hover, pressed, disabled +} + +struct OUDSSwitchLabeledStyle: ButtonStyle { + + // MARK: Stored properties + + @Environment(\.isEnabled) private var isEnabled + @Environment(\.theme) private var theme + @Environment(\.colorScheme) private var colorScheme + @State private var isHover: Bool = false + + let isOn: Bool + let label: OUDSSwitchLabel.Label + + // MARK: Body + + func makeBody(configuration: Configuration) -> some View { + HStack(alignment: .center, spacing: theme.listItem.listItemSpaceColumnGap) { + OUDSSwitchButton(internalState: internalState(isPressed: configuration.isPressed), isOn: isOn) + + OUDSSwitchLabel(internalState: internalState(isPressed: configuration.isPressed), label: label) + } + .padding(.all, theme.listItem.listItemSpaceInset) + .oudsDivider(show: label.divider) + .background(backgroundColor(state: internalState(isPressed: configuration.isPressed))) + .onHover { isHover in + self.isHover = isHover + } + } + + // MARK: Private Helpers + + func backgroundColor(state: InternalSwitchState) -> Color { + switch state { + case .enabled: + theme.select.selectColorBgEnabled.color(for: colorScheme) + case .hover: + theme.select.selectColorBgHover.color(for: colorScheme) + case .pressed: + theme.select.selectColorBgPressed.color(for: colorScheme) + case .disabled: + theme.select.selectColorBgDisabled.color(for: colorScheme) + } + } + + private func internalState(isPressed: Bool) -> InternalSwitchState { + if !isEnabled { + return .disabled + } + + if isPressed { + return .pressed + } + + if isHover { + return .hover + } + + return .enabled + } +} diff --git a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchNestedStyle.swift b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchNestedStyle.swift new file mode 100644 index 000000000..cb86b6e42 --- /dev/null +++ b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchNestedStyle.swift @@ -0,0 +1,53 @@ +// +// Software Name: OUDS iOS +// SPDX-FileCopyrightText: Copyright (c) Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license, +// the text of which is available at https://opensource.org/license/MIT/ +// or see the "LICENSE" file for more details. +// +// Authors: See CONTRIBUTORS.txt +// Software description: A SwiftUI components library with code examples for Orange Unified Design System +// + +import OUDS +import OUDSTokensSemantic +import SwiftUI + +/// Just here to catch the isPressed state on the button +struct OUDSSwitchNestedStyle: ButtonStyle { + + // MARK: Stored properties + + @Environment(\.isEnabled) private var isEnabled + @State private var isHover: Bool = false + let isOn: Bool + + // MARK: Body + + func makeBody(configuration: Configuration) -> some View { + OUDSSwitchButton(internalState: internalState(isPressed: configuration.isPressed), isOn: isOn) + .onHover { isHover in + self.isHover = isHover + } + } + + // MARK: Private Helpers + + private func internalState(isPressed: Bool) -> InternalSwitchState { + if !isEnabled { + return .disabled + } + + if isPressed { + return .pressed + } + + if isHover { + return .hover + } + + return .enabled + } +} diff --git a/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift b/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift index e5d98b691..d1c2f6a21 100644 --- a/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift +++ b/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift @@ -32,7 +32,14 @@ import SwiftUI public struct OUDSSwitch: View { // MARK: Stored Properties - var isOn: Binding + + private var isOn: Binding + private let layout: Layout + + private enum Layout { + case labeled(OUDSSwitchLabel.Label) + case nested + } // MARK: Initializers @@ -43,16 +50,48 @@ public struct OUDSSwitch: View { /// or off. public init(isOn: Binding) { self.isOn = isOn + self.layout = .nested + } + + /// Creates a switch with label and optional helper text. + /// + /// - Parameters: + /// - isOn: A binding to a property that determines whether the switch is on + /// or off. + /// - label: The main label of the switch. + /// - helperText: An additonal helper text. + /// - icon: An optional icon + /// - onError: It the option is on error + /// - divider: If true a divider is added at the bottom of the view. + public init(isOn: Binding, label: String, helperText: String? = nil, icon: Image? = nil, onError: Bool = false, divider: Bool = false) { + self.isOn = isOn + self.layout = .labeled(.init(label: label, helperText: helperText, icon: icon, onError: onError, divider: divider)) } // MARK: Body public var body: some View { - Button { - isOn.wrappedValue.toggle() - } label: { - Text("") + switch layout { + case .labeled(let label): + Button("") { + isOn.wrappedValue.toggle() + } + .buttonStyle(OUDSSwitchLabeledStyle(isOn: isOn.wrappedValue, label: label)) + case .nested: + Button("") { + isOn.wrappedValue.toggle() + } + .buttonStyle(OUDSSwitchNestedStyle(isOn: isOn.wrappedValue)) } - .buttonStyle(SwitchButtonStyle(isOn: isOn.wrappedValue)) } } + +// swiftlint:disable convenience_type +struct Constants { + static let inputSwitchSizeWidthTrack: CGFloat = 56 + static let inputSwitchSizeHeightTrack: CGFloat = 32 + static let inputSwitchSizeWidthCursorTrue: CGFloat = 24 + static let inputSwitchSizeWidthCursorFalse: CGFloat = 16 + static let inputSwitchSizeWidthCursorPressed: CGFloat = 32 +} +// swiftlint:enable convenience_type diff --git a/OUDS/Core/Components/Sources/ViewModifiers/DividerModifier/DividerModifier.swift b/OUDS/Core/Components/Sources/ViewModifiers/DividerModifier/DividerModifier.swift new file mode 100644 index 000000000..8da36aa45 --- /dev/null +++ b/OUDS/Core/Components/Sources/ViewModifiers/DividerModifier/DividerModifier.swift @@ -0,0 +1,44 @@ +// +// Software Name: OUDS iOS +// SPDX-FileCopyrightText: Copyright (c) Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license, +// the text of which is available at https://opensource.org/license/MIT/ +// or see the "LICENSE" file for more details. +// +// Authors: See CONTRIBUTORS.txt +// Software description: A SwiftUI components library with code examples for Orange Unified Design System +// + +import OUDSTokensSemantic +import SwiftUI + +/// A `ViewModifier` which will apply a specific divider under a `View` using color semantic token. +public struct DividerModifier: ViewModifier { + + // MARK: - Stored properties + + @Environment(\.theme) private var theme + @Environment(\.colorScheme) private var colorScheme + let show: Bool + + // MARK: - Initializer + + public init(show: Bool) { + self.show = show + } + + // MARK: - Body + + public func body(content: Content) -> some View { + if show { + VStack(spacing: 0) { + content + Divider().foregroundStyle(theme.colors.colorBorderDefault.color(for: colorScheme)) + } + } else { + content + } + } +} diff --git a/OUDS/Core/Components/Sources/ViewModifiers/DividerModifier/View+Divider.swift b/OUDS/Core/Components/Sources/ViewModifiers/DividerModifier/View+Divider.swift new file mode 100644 index 000000000..c519cf11c --- /dev/null +++ b/OUDS/Core/Components/Sources/ViewModifiers/DividerModifier/View+Divider.swift @@ -0,0 +1,33 @@ +// +// Software Name: OUDS iOS +// SPDX-FileCopyrightText: Copyright (c) Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license, +// the text of which is available at https://opensource.org/license/MIT/ +// or see the "LICENSE" file for more details. +// +// Authors: See CONTRIBUTORS.txt +// Software description: A SwiftUI components library with code examples for Orange Unified Design System +// + +import OUDSTokensSemantic +import SwiftUI + +extension View { + + /// Modifies the current `View` to apply a divider under. + /// + /// ```swift + /// var body: some View { + /// SomeView() + /// .divider() + /// ``` + /// - Parameter: + /// - show: if true the divider is displayed, else there is no divider. + /// + /// - Returns some View: The current `View` but with a divider under. + public func oudsDivider(show: Bool = true) -> some View { + self.modifier(DividerModifier(show: show)) + } +} From 49b80ee9ded145674d0da301d6f08705b6f12600 Mon Sep 17 00:00:00 2001 From: Ludovic PINEL Date: Mon, 3 Feb 2025 15:46:58 +0100 Subject: [PATCH 05/13] Add wordings keys, code example and update changelog file --- CHANGELOG.md | 1 + .../Switch/SwitchConfiguration.swift | 82 +++++++++++++++---- .../Pages/Components/Switch/SwitchPage.swift | 4 +- .../Resources/en.lproj/Localizable.strings | 8 ++ 4 files changed, 75 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4051ae12..67dba7c49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - [DesignToolbox] Add text field in component configuration to customize text ([#436](https://github.com/Orange-OpenSource/ouds-ios/issues/436)) +- [Library] Switch component ([#405](https://github.com/Orange-OpenSource/ouds-ios/issues/405)) - [Library] Link component ([#400](https://github.com/Orange-OpenSource/ouds-ios/issues/400)) ## [0.10.0](https://github.com/Orange-OpenSource/ouds-ios/compare/0.9.0...0.10.0) - 2025-01-30 diff --git a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift index 4833a7f05..004a662e5 100644 --- a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift +++ b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift @@ -21,15 +21,24 @@ final class SwitchConfigurationModel: ComponentConfiguration { // MARK: Published properties - @Published var enabled: Bool = true { + @Published var enabled: Bool { + didSet { updateCode() } + } + @Published var switchOnly: Bool { + didSet { updateCode() } + } + @Published var helperText: Bool { + didSet { updateCode() } + } + @Published var icon: Bool { + didSet { updateCode() } + } + @Published var onError: Bool { + didSet { updateCode() } + } + @Published var divider: Bool { didSet { updateCode() } } - - @Published var switchOnly: Bool - @Published var helperText: Bool - @Published var icon: Bool - @Published var onError: Bool - @Published var divider: Bool // MARK: Initializer @@ -46,16 +55,53 @@ final class SwitchConfigurationModel: ComponentConfiguration { // MARK: Component Configuration + override func updateCode() { + if switchOnly { + code = + """ + OUDSSwitch(isOn: $isOn) + \(disableCode)) + """ + } else { + code = + """ + OUDSSwitch(isOn: $isOn, label: \"Label\"\(helperTextPatern)\(iconPatern)\(onErrorPatern)\(dividerPatern)) + \(disableCode)) + """ + } + } + private var disableCode: String { ".disable(\(enabled ? "false" : "true"))" } - override func updateCode() { - code = - """ - OUDSSwitch(isOn: $isOn) - \(disableCode)) - """ + private var helperTextPatern: String { + if helperText { + return ",helperText: \(String(localized: "app_components_switch_helperText_text"))" + } else { + return "" + } + } + private var iconPatern: String { + if icon { + return ", Image(decorative: \"ic_heart\")" + } else { + return "" + } + } + private var onErrorPatern: String { + if onError { + return ", onError: true" + } else { + return "" + } + } + private var dividerPatern: String { + if onError { + return ", divider: true" + } else { + return "" + } } } @@ -74,26 +120,26 @@ struct SwitchConfiguration: View { .typeHeadingMedium(theme) .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) - Toggle("switch only", isOn: $model.switchOnly) + Toggle("app_components_switch_switchOnly_label", isOn: $model.switchOnly) .typeHeadingMedium(theme) .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) - Toggle("Helper Text", isOn: $model.helperText) + Toggle("app_components_common_helperText_label", isOn: $model.helperText) .typeHeadingMedium(theme) .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) .disabled(model.switchOnly) - Toggle("Icon", isOn: $model.icon) + Toggle("app_components_common_icon_label", isOn: $model.icon) .typeHeadingMedium(theme) .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) .disabled(model.switchOnly) - Toggle("Divider", isOn: $model.divider) + Toggle("app_components_common_divider_label", isOn: $model.divider) .typeHeadingMedium(theme) .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) .disabled(model.switchOnly) - Toggle("On error", isOn: $model.onError) + Toggle("app_components_common_onError_label", isOn: $model.onError) .typeHeadingMedium(theme) .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) .disabled(model.switchOnly) diff --git a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift index b89e2c3f2..b77bd73f4 100644 --- a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift +++ b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift @@ -83,7 +83,7 @@ private struct SwitchDemo: View { Spacer() } } else { - OUDSSwitch(isOn: $isOn, label: "Label", helperText: helperText, icon: icon, onError: model.onError, divider: model.divider) + OUDSSwitch(isOn: $isOn, label: "app_components_switch_label_text", helperText: helperText, icon: icon, onError: model.onError, divider: model.divider) .disabled(!model.enabled) } } @@ -94,7 +94,7 @@ private struct SwitchDemo: View { // MARK: Private helpers private var helperText: String? { - model.helperText ? "Helper Text" : nil + model.helperText ? "app_components_switch_helperText_text" : nil } private var icon: Image? { model.icon ? Image(decorative: "ic_heart") : nil diff --git a/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings b/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings index 94cc436a7..16fa7cb6b 100644 --- a/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings +++ b/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings @@ -112,6 +112,10 @@ "app_components_common_iconAndTextLayout_label" = "Icon + text"; "app_components_common_style_label" = "Style"; "app_components_common_onColoredBackground_label" = "On colored background"; +"app_components_common_helperText_label" = "Helper text"; +"app_components_common_icon_label" = "Icon"; +"app_components_common_divider_label" = "Divider"; +"app_components_common_onError_label" = "On error"; // MARK: Components: Button @@ -131,5 +135,9 @@ "app_components_link_size_label" = "Size"; // MARK: Components: Switch + "app_components_switch_label" = "Switch"; "app_components_switch_description_text" = "A switch is a component that allows the user to toggle between two states, typically \"on\" and \"off\". It is often represented as a button or a slider that changes position or color to indicate the current state. Switches are used to enable or disable features, options, or settings in an intuitive and visual manner."; +"app_components_switch_label_text" = "Label"; +"app_components_switch_switchOnly_label" = "Switch only"; +"app_components_switch_helperText_text" = "Helper Text"; From a9b285d8f92e5d7ac85c29cbaa52dd2db064ea82 Mon Sep 17 00:00:00 2001 From: Ludovic PINEL Date: Mon, 3 Feb 2025 16:11:58 +0100 Subject: [PATCH 06/13] Add documentation --- .../Sources/Switch/OUDSSwitch.swift | 20 +++++++++++++------ .../_OUDSComponents.docc/OUDSComponents.md | 17 ++++++++++++++++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift b/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift index d1c2f6a21..c32c87910 100644 --- a/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift +++ b/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift @@ -16,19 +16,27 @@ import SwiftUI // MARK: - OUDS Button -/// The ``OUDSSwitch`` proposes layout with text only, icon only or text with icon. -/// +/// The ``OUDSSwitch`` proposes a layout as a nested element. +/// It also propose a more complex layout with text, icon and divider. For this layout, +/// if switch is in form it is possible to set it in error state. /// /// ## Code samples -/// /// ```swift +/// // Switch as nested element (with no text) +/// OUDSSwitch(isOn $isOn) +/// +/// // Text, icon and divider +/// OUDSSwtcih(isOn $isOn, label: "Allow notifications", icon: Image("ic_heart"), divider: true) +/// +/// // Text, icon and divider with error in form +/// OUDSSwtcih(isOn $isOn, label: "Allow notifications", icon: Image("ic_heart"), onError: true, divider: true) /// ``` /// /// ## Design documentation /// -/// See +/// See [#TODO] /// -/// - Since: 0.10.0 +/// - Since: 0.11.0 public struct OUDSSwitch: View { // MARK: Stored Properties @@ -53,7 +61,7 @@ public struct OUDSSwitch: View { self.layout = .nested } - /// Creates a switch with label and optional helper text. + /// Creates a switch with label and optional helper text, icon, divider. /// /// - Parameters: /// - isOn: A binding to a property that determines whether the switch is on diff --git a/OUDS/Core/Components/Sources/_OUDSComponents.docc/OUDSComponents.md b/OUDS/Core/Components/Sources/_OUDSComponents.docc/OUDSComponents.md index 6f22ce567..611dd299f 100644 --- a/OUDS/Core/Components/Sources/_OUDSComponents.docc/OUDSComponents.md +++ b/OUDS/Core/Components/Sources/_OUDSComponents.docc/OUDSComponents.md @@ -37,6 +37,23 @@ The link can be displayed in `small` or `medium` size. OUDSLink(text: "Back", arrow: .back, size: .medium) { /* the action to process */ } ``` +### Switch + +The ``OUDSSwitch`` proposes a layout as a nested element. +It also propose a more complex layout with text, icon and divider. For this layout, +if switch is in form it is possible to set it in error state. + +```swift + // Switch as nested element (with no text) + OUDSSwitch(isOn $isOn) + + // Text, icon and divider + OUDSSwtcih(isOn $isOn, label: "Allow notifications", icon: Image("ic_heart"), divider: true) + + // Text, icon and divider with error in form + OUDSSwtcih(isOn $isOn, label: "Allow notifications", icon: Image("ic_heart"), onError: true, divider: true) +``` + ## Customize components ### Apply a specific shadow effect (elevation tokens) From aa12111e44966c167e3e8d25c2237698b4897f3e Mon Sep 17 00:00:00 2001 From: Ludovic PINEL Date: Tue, 4 Feb 2025 10:10:32 +0100 Subject: [PATCH 07/13] Adjust the position of the switch and the icon at the top --- .../Resources/en.lproj/Localizable.strings | 2 +- .../Sources/Switch/Internal/OUDSSwitchLabel.swift | 15 +++++++++------ .../Switch/Internal/OUDSSwitchLabeledStyle.swift | 7 +++++-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings b/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings index 16fa7cb6b..9f3795d41 100644 --- a/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings +++ b/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings @@ -140,4 +140,4 @@ "app_components_switch_description_text" = "A switch is a component that allows the user to toggle between two states, typically \"on\" and \"off\". It is often represented as a button or a slider that changes position or color to indicate the current state. Switches are used to enable or disable features, options, or settings in an intuitive and visual manner."; "app_components_switch_label_text" = "Label"; "app_components_switch_switchOnly_label" = "Switch only"; -"app_components_switch_helperText_text" = "Helper Text"; +"app_components_switch_helperText_text" = "Helper text"; diff --git a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabel.swift b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabel.swift index dbfe2716d..2ec0613eb 100644 --- a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabel.swift +++ b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabel.swift @@ -36,7 +36,7 @@ struct OUDSSwitchLabel: View { // MARK: Body var body: some View { - HStack(spacing: theme.listItem.listItemSpaceColumnGap) { + HStack(alignment: .top, spacing: theme.listItem.listItemSpaceColumnGap) { VStack(alignment: .leading, spacing: 0) { Text(LocalizedStringKey(label.label)) .typeLabelDefaultLarge(theme) @@ -54,11 +54,14 @@ struct OUDSSwitchLabel: View { .frame(maxWidth: .infinity, alignment: .leading) if let icon = label.icon { - icon - .resizable() - .renderingMode(.template) - .foregroundStyle(iconColor) - .frame(width: theme.listItem.listItemSizeIcon, height: theme.listItem.listItemSizeIcon) + HStack(alignment: .center, spacing: 0) { + icon + .resizable() + .renderingMode(.template) + .foregroundStyle(iconColor) + .frame(width: theme.listItem.listItemSizeIcon, height: theme.listItem.listItemSizeIcon) + } + .frame(maxHeight: theme.checkRadio.checkRadioSizeMaxHeightAssetsContainer, alignment: .center) } } } diff --git a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabeledStyle.swift b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabeledStyle.swift index c6d92c50a..64bac7223 100644 --- a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabeledStyle.swift +++ b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabeledStyle.swift @@ -35,8 +35,11 @@ struct OUDSSwitchLabeledStyle: ButtonStyle { // MARK: Body func makeBody(configuration: Configuration) -> some View { - HStack(alignment: .center, spacing: theme.listItem.listItemSpaceColumnGap) { - OUDSSwitchButton(internalState: internalState(isPressed: configuration.isPressed), isOn: isOn) + HStack(alignment: .top, spacing: theme.listItem.listItemSpaceColumnGap) { + HStack(alignment: .center, spacing: 0) { + OUDSSwitchButton(internalState: internalState(isPressed: configuration.isPressed), isOn: isOn) + } + .frame(maxHeight: theme.checkRadio.checkRadioSizeMaxHeightAssetsContainer, alignment: .center) OUDSSwitchLabel(internalState: internalState(isPressed: configuration.isPressed), label: label) } From 0c0542a0896b18589b732bf1b1f26db97f265164 Mon Sep 17 00:00:00 2001 From: Ludovic PINEL Date: Tue, 4 Feb 2025 14:41:53 +0100 Subject: [PATCH 08/13] Add orientation --- .../Switch/SwitchConfiguration.swift | 79 +++++++++++++------ .../Pages/Components/Switch/SwitchPage.swift | 9 ++- .../Resources/en.lproj/Localizable.strings | 1 + .../Switch/Internal/OUDSSwitchLabel.swift | 62 +++++++++------ .../Internal/OUDSSwitchLabeledStyle.swift | 23 ++++-- .../Sources/Switch/OUDSSwitch.swift | 13 ++- 6 files changed, 134 insertions(+), 53 deletions(-) diff --git a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift index 004a662e5..e8effeb96 100644 --- a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift +++ b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift @@ -39,16 +39,20 @@ final class SwitchConfigurationModel: ComponentConfiguration { @Published var divider: Bool { didSet { updateCode() } } + @Published var orientation: OUDSSwitch.Orientation { + didSet { updateCode() } + } // MARK: Initializer override init() { - enabled = true - switchOnly = false - helperText = true - icon = true - onError = false - divider = true + enabled = true + switchOnly = false + helperText = true + icon = true + onError = false + divider = true + orientation = .default } deinit { } @@ -65,7 +69,7 @@ final class SwitchConfigurationModel: ComponentConfiguration { } else { code = """ - OUDSSwitch(isOn: $isOn, label: \"Label\"\(helperTextPatern)\(iconPatern)\(onErrorPatern)\(dividerPatern)) + OUDSSwitch(isOn: $isOn, label: \"Label\"\(helperTextPatern)\(iconPatern)\(onErrorPatern)\(dividerPatern)\(orienationPatern) \(disableCode)) """ } @@ -103,6 +107,13 @@ final class SwitchConfigurationModel: ComponentConfiguration { return "" } } + private var orienationPatern: String { + if onError { + return ", orientation: \(orientation.description)" + } else { + return "" + } + } } // MARK: - Switch Configuration View @@ -124,25 +135,47 @@ struct SwitchConfiguration: View { .typeHeadingMedium(theme) .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) - Toggle("app_components_common_helperText_label", isOn: $model.helperText) - .typeHeadingMedium(theme) - .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) - .disabled(model.switchOnly) + if !model.switchOnly { + DesignToolboxChoicePicker(title: "app_components_common_orientation_label", selection: $model.orientation) { + ForEach(OUDSSwitch.Orientation.allCases, id: \.id) { orientation in + Text(LocalizedStringKey(orientation.description)).tag(orientation) + } + } + + Toggle("app_components_common_helperText_label", isOn: $model.helperText) + .typeHeadingMedium(theme) + .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) + + Toggle("app_components_common_icon_label", isOn: $model.icon) + .typeHeadingMedium(theme) + .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) + + Toggle("app_components_common_divider_label", isOn: $model.divider) + .typeHeadingMedium(theme) + .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) + + Toggle("app_components_common_onError_label", isOn: $model.onError) + .typeHeadingMedium(theme) + .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) + } + } + } +} - Toggle("app_components_common_icon_label", isOn: $model.icon) - .typeHeadingMedium(theme) - .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) - .disabled(model.switchOnly) +// MARK: Switch Layout Orientation extension - Toggle("app_components_common_divider_label", isOn: $model.divider) - .typeHeadingMedium(theme) - .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) - .disabled(model.switchOnly) +extension OUDSSwitch.Orientation: @retroactive CaseIterable, @retroactive CustomStringConvertible { + nonisolated(unsafe) public static let allCases: [OUDSSwitch.Orientation] = [.default, .inverse] - Toggle("app_components_common_onError_label", isOn: $model.onError) - .typeHeadingMedium(theme) - .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) - .disabled(model.switchOnly) + // Note: Not localized because it is a technical name + public var description: String { + switch self { + case .default: + "Default" + case .inverse: + "Inverse" } } + + var id: String { description } } diff --git a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift index b77bd73f4..158bc7eb8 100644 --- a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift +++ b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift @@ -83,7 +83,14 @@ private struct SwitchDemo: View { Spacer() } } else { - OUDSSwitch(isOn: $isOn, label: "app_components_switch_label_text", helperText: helperText, icon: icon, onError: model.onError, divider: model.divider) + OUDSSwitch( + isOn: $isOn, + label: "app_components_switch_label_text", + helperText: helperText, + icon: icon, + onError: model.onError, + divider: model.divider, + orientation: model.orientation) .disabled(!model.enabled) } } diff --git a/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings b/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings index 9f3795d41..6989dfd47 100644 --- a/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings +++ b/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings @@ -116,6 +116,7 @@ "app_components_common_icon_label" = "Icon"; "app_components_common_divider_label" = "Divider"; "app_components_common_onError_label" = "On error"; +"app_components_common_orientation_label" = "Orientation"; // MARK: Components: Button diff --git a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabel.swift b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabel.swift index 2ec0613eb..c0d651d65 100644 --- a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabel.swift +++ b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabel.swift @@ -28,6 +28,7 @@ struct OUDSSwitchLabel: View { let icon: Image? let onError: Bool let divider: Bool + let orientation: OUDSSwitch.Orientation } let internalState: InternalSwitchState @@ -37,33 +38,50 @@ struct OUDSSwitchLabel: View { var body: some View { HStack(alignment: .top, spacing: theme.listItem.listItemSpaceColumnGap) { - VStack(alignment: .leading, spacing: 0) { - Text(LocalizedStringKey(label.label)) - .typeLabelDefaultLarge(theme) - .multilineTextAlignment(.leading) - .foregroundStyle(labelColor) - .frame(maxWidth: .infinity, alignment: .leading) + switch label.orientation { + case .default: + texts + icon + case .inverse: + icon + texts + } + } + } + + // MARK: Private helpers - if let helperText = label.helperText { - Text(LocalizedStringKey(helperText)) - .typeLabelDefaultMedium(theme) - .multilineTextAlignment(.leading) - .foregroundStyle(helperTextColor) - } + @ViewBuilder + private var icon: some View { + if let icon = label.icon { + HStack(alignment: .center, spacing: 0) { + icon + .resizable() + .renderingMode(.template) + .foregroundStyle(iconColor) + .frame(width: theme.listItem.listItemSizeIcon, height: theme.listItem.listItemSizeIcon) } - .frame(maxWidth: .infinity, alignment: .leading) + .frame(maxHeight: theme.checkRadio.checkRadioSizeMaxHeightAssetsContainer, alignment: .center) + } + } - if let icon = label.icon { - HStack(alignment: .center, spacing: 0) { - icon - .resizable() - .renderingMode(.template) - .foregroundStyle(iconColor) - .frame(width: theme.listItem.listItemSizeIcon, height: theme.listItem.listItemSizeIcon) - } - .frame(maxHeight: theme.checkRadio.checkRadioSizeMaxHeightAssetsContainer, alignment: .center) + @ViewBuilder + private var texts: some View { + VStack(alignment: .leading, spacing: 0) { + Text(LocalizedStringKey(label.label)) + .typeLabelDefaultLarge(theme) + .multilineTextAlignment(.leading) + .foregroundStyle(labelColor) + .frame(maxWidth: .infinity, alignment: .leading) + + if let helperText = label.helperText { + Text(LocalizedStringKey(helperText)) + .typeLabelDefaultMedium(theme) + .multilineTextAlignment(.leading) + .foregroundStyle(helperTextColor) } } + .frame(maxWidth: .infinity, alignment: .leading) } private var labelColor: Color { diff --git a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabeledStyle.swift b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabeledStyle.swift index 64bac7223..9bfdb4931 100644 --- a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabeledStyle.swift +++ b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabeledStyle.swift @@ -36,12 +36,14 @@ struct OUDSSwitchLabeledStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { HStack(alignment: .top, spacing: theme.listItem.listItemSpaceColumnGap) { - HStack(alignment: .center, spacing: 0) { - OUDSSwitchButton(internalState: internalState(isPressed: configuration.isPressed), isOn: isOn) + switch label.orientation { + case .default: + toggle(isPressed: configuration.isPressed) + label(isPressed: configuration.isPressed) + case .inverse: + label(isPressed: configuration.isPressed) + toggle(isPressed: configuration.isPressed) } - .frame(maxHeight: theme.checkRadio.checkRadioSizeMaxHeightAssetsContainer, alignment: .center) - - OUDSSwitchLabel(internalState: internalState(isPressed: configuration.isPressed), label: label) } .padding(.all, theme.listItem.listItemSpaceInset) .oudsDivider(show: label.divider) @@ -51,6 +53,17 @@ struct OUDSSwitchLabeledStyle: ButtonStyle { } } + private func toggle(isPressed: Bool) -> some View { + HStack(alignment: .center, spacing: 0) { + OUDSSwitchButton(internalState: internalState(isPressed: isPressed), isOn: isOn) + } + .frame(maxHeight: theme.checkRadio.checkRadioSizeMaxHeightAssetsContainer, alignment: .center) + } + + private func label(isPressed: Bool) -> some View { + OUDSSwitchLabel(internalState: internalState(isPressed: isPressed), label: label) + } + // MARK: Private Helpers func backgroundColor(state: InternalSwitchState) -> Color { diff --git a/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift b/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift index c32c87910..7c402432b 100644 --- a/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift +++ b/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift @@ -49,6 +49,12 @@ public struct OUDSSwitch: View { case nested } + /// Used to define the orientation of the Layout + public enum Orientation { + case `default` + case inverse + } + // MARK: Initializers /// Creates a switch with no label. @@ -61,6 +67,7 @@ public struct OUDSSwitch: View { self.layout = .nested } + // swiftlint:disable line_length /// Creates a switch with label and optional helper text, icon, divider. /// /// - Parameters: @@ -71,10 +78,12 @@ public struct OUDSSwitch: View { /// - icon: An optional icon /// - onError: It the option is on error /// - divider: If true a divider is added at the bottom of the view. - public init(isOn: Binding, label: String, helperText: String? = nil, icon: Image? = nil, onError: Bool = false, divider: Bool = false) { + /// - orientation: Specify the orientation of the layout. If Default the switch at the leading position, if inverse it is on trailing. + public init(isOn: Binding, label: String, helperText: String? = nil, icon: Image? = nil, onError: Bool = false, divider: Bool = false, orientation: Orientation = .default) { self.isOn = isOn - self.layout = .labeled(.init(label: label, helperText: helperText, icon: icon, onError: onError, divider: divider)) + self.layout = .labeled(.init(label: label, helperText: helperText, icon: icon, onError: onError, divider: divider, orientation: orientation)) } + // swiftlint:enable line_length // MARK: Body From 0c3e7f06a18b48c66626f62d6140ba13b8ea2c15 Mon Sep 17 00:00:00 2001 From: Ludovic PINEL Date: Tue, 4 Feb 2025 15:42:25 +0100 Subject: [PATCH 09/13] Add two texfields to get text for label and helper text --- .../Switch/SwitchConfiguration.swift | 23 ++++++++++++++----- .../Pages/Components/Switch/SwitchPage.swift | 8 +++---- .../Pages/Utils/DesignToolboxTextField.swift | 9 +++++++- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift index e8effeb96..c1e946630 100644 --- a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift +++ b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift @@ -42,6 +42,8 @@ final class SwitchConfigurationModel: ComponentConfiguration { @Published var orientation: OUDSSwitch.Orientation { didSet { updateCode() } } + @Published var labelContent: String + @Published var helperTextContent: String // MARK: Initializer @@ -53,6 +55,8 @@ final class SwitchConfigurationModel: ComponentConfiguration { onError = false divider = true orientation = .default + labelContent = String(localized: "app_components_switch_label_text") + helperTextContent = String(localized: "app_components_switch_helperText_text") } deinit { } @@ -136,12 +140,6 @@ struct SwitchConfiguration: View { .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) if !model.switchOnly { - DesignToolboxChoicePicker(title: "app_components_common_orientation_label", selection: $model.orientation) { - ForEach(OUDSSwitch.Orientation.allCases, id: \.id) { orientation in - Text(LocalizedStringKey(orientation.description)).tag(orientation) - } - } - Toggle("app_components_common_helperText_label", isOn: $model.helperText) .typeHeadingMedium(theme) .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) @@ -157,6 +155,19 @@ struct SwitchConfiguration: View { Toggle("app_components_common_onError_label", isOn: $model.onError) .typeHeadingMedium(theme) .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) + + DesignToolboxChoicePicker(title: "app_components_common_orientation_label", selection: $model.orientation) { + ForEach(OUDSSwitch.Orientation.allCases, id: \.id) { orientation in + Text(LocalizedStringKey(orientation.description)).tag(orientation) + } + } + + DisclosureGroup("Edit texts") { + DesignToolboxTextField(text: $model.labelContent, prompt: "app_component_common_userText_prompt", title: "app_components_switch_label_text") + if model.helperText { + DesignToolboxTextField(text: $model.helperTextContent, prompt: "app_component_common_userText_prompt", title: "app_components_common_helperText_label") + } + } } } } diff --git a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift index 158bc7eb8..ee055934e 100644 --- a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift +++ b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift @@ -85,8 +85,8 @@ private struct SwitchDemo: View { } else { OUDSSwitch( isOn: $isOn, - label: "app_components_switch_label_text", - helperText: helperText, + label: model.labelContent, + helperText: helperTextContent, icon: icon, onError: model.onError, divider: model.divider, @@ -100,8 +100,8 @@ private struct SwitchDemo: View { // MARK: Private helpers - private var helperText: String? { - model.helperText ? "app_components_switch_helperText_text" : nil + private var helperTextContent: String? { + model.helperText ? model.helperTextContent : nil } private var icon: Image? { model.icon ? Image(decorative: "ic_heart") : nil diff --git a/DesignToolbox/DesignToolbox/Pages/Utils/DesignToolboxTextField.swift b/DesignToolbox/DesignToolbox/Pages/Utils/DesignToolboxTextField.swift index e492b3189..726687264 100644 --- a/DesignToolbox/DesignToolbox/Pages/Utils/DesignToolboxTextField.swift +++ b/DesignToolbox/DesignToolbox/Pages/Utils/DesignToolboxTextField.swift @@ -22,10 +22,17 @@ struct DesignToolboxTextField: View { let text: Binding let prompt: String + let title: String + + init(text: Binding, prompt: String, title: String = "app_component_common_userText_label") { + self.title = title + self.text = text + self.prompt = prompt + } var body: some View { VStack(alignment: .leading) { - Text("app_component_common_userText_label") + Text(LocalizedStringKey(title)) .typeHeadingMedium(theme) .foregroundStyle(theme.colors.colorContentDefault.color(for: colorScheme)) From 703d86525182a40e288e7258ec50e9f1a071bca9 Mon Sep 17 00:00:00 2001 From: Ludovic PINEL Date: Wed, 5 Feb 2025 09:54:06 +0100 Subject: [PATCH 10/13] update wording and apply same rules on key naming --- .../Components/Button/ButtonConfiguration.swift | 2 +- .../Pages/Components/EmptyState/EmptyState.swift | 4 ++-- .../Pages/Components/Link/LinkConfiguration.swift | 2 +- .../Components/Switch/SwitchConfiguration.swift | 6 +++--- .../Pages/Utils/DesignToolboxTextField.swift | 2 +- .../Resources/en.lproj/Localizable.strings | 12 ++++++++---- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/DesignToolbox/DesignToolbox/Pages/Components/Button/ButtonConfiguration.swift b/DesignToolbox/DesignToolbox/Pages/Components/Button/ButtonConfiguration.swift index ce2fa69ed..964e57699 100644 --- a/DesignToolbox/DesignToolbox/Pages/Components/Button/ButtonConfiguration.swift +++ b/DesignToolbox/DesignToolbox/Pages/Components/Button/ButtonConfiguration.swift @@ -197,7 +197,7 @@ struct ButtonConfiguration: View { } if model.layout == .iconAndText || model.layout == .textOnly { - DesignToolboxTextField(text: $model.text, prompt: "app_component_common_userText_prompt") + DesignToolboxTextField(text: $model.text, prompt: "app_components_common_userText_prompt") } } } diff --git a/DesignToolbox/DesignToolbox/Pages/Components/EmptyState/EmptyState.swift b/DesignToolbox/DesignToolbox/Pages/Components/EmptyState/EmptyState.swift index e01307002..a1faf9747 100644 --- a/DesignToolbox/DesignToolbox/Pages/Components/EmptyState/EmptyState.swift +++ b/DesignToolbox/DesignToolbox/Pages/Components/EmptyState/EmptyState.swift @@ -26,9 +26,9 @@ struct EmptyState: View { .frame(width: 160, height: 160, alignment: .center) VStack(alignment: .center, spacing: theme.spaces.spaceFixedShorter) { - Text("app_component_emptyContent_text") + Text("app_components_emptyContent_text") .typeHeadingMedium(theme) - Text("app_component_emptyContent_description_text") + Text("app_components_emptyContent_description_text") .typeBodyDefaultSmall(theme) } .padding(.vertical, theme.spaces.spaceFixedMedium) diff --git a/DesignToolbox/DesignToolbox/Pages/Components/Link/LinkConfiguration.swift b/DesignToolbox/DesignToolbox/Pages/Components/Link/LinkConfiguration.swift index e18412f08..54ab0d966 100644 --- a/DesignToolbox/DesignToolbox/Pages/Components/Link/LinkConfiguration.swift +++ b/DesignToolbox/DesignToolbox/Pages/Components/Link/LinkConfiguration.swift @@ -167,7 +167,7 @@ struct LinkConfiguration: View { } } - DesignToolboxTextField(text: $model.text, prompt: "app_component_common_userText_prompt") + DesignToolboxTextField(text: $model.text, prompt: "app_components_common_userText_prompt") } } } diff --git a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift index c1e946630..57e3cc371 100644 --- a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift +++ b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift @@ -162,10 +162,10 @@ struct SwitchConfiguration: View { } } - DisclosureGroup("Edit texts") { - DesignToolboxTextField(text: $model.labelContent, prompt: "app_component_common_userText_prompt", title: "app_components_switch_label_text") + DisclosureGroup("app_components_common_editContent_label") { + DesignToolboxTextField(text: $model.labelContent, prompt: "app_components_common_userText_prompt", title: "app_components_switch_label_text") if model.helperText { - DesignToolboxTextField(text: $model.helperTextContent, prompt: "app_component_common_userText_prompt", title: "app_components_common_helperText_label") + DesignToolboxTextField(text: $model.helperTextContent, prompt: "app_components_common_userText_prompt", title: "app_components_common_helperText_label") } } } diff --git a/DesignToolbox/DesignToolbox/Pages/Utils/DesignToolboxTextField.swift b/DesignToolbox/DesignToolbox/Pages/Utils/DesignToolboxTextField.swift index 726687264..a5003d490 100644 --- a/DesignToolbox/DesignToolbox/Pages/Utils/DesignToolboxTextField.swift +++ b/DesignToolbox/DesignToolbox/Pages/Utils/DesignToolboxTextField.swift @@ -24,7 +24,7 @@ struct DesignToolboxTextField: View { let prompt: String let title: String - init(text: Binding, prompt: String, title: String = "app_component_common_userText_label") { + init(text: Binding, prompt: String, title: String = "app_components_common_userText_label") { self.title = title self.text = text self.prompt = prompt diff --git a/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings b/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings index 6989dfd47..0b6cc6153 100644 --- a/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings +++ b/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings @@ -103,10 +103,8 @@ // MARK: - Component Screen "app_components_common_viewCodeExample_label" = "View component code example"; -"app_component_emptyContent_text" = "No content"; -"app_component_emptyContent_description_text" = "This content is under construction and will be available very soon"; -"app_component_common_userText_prompt" = "Enter a text"; -"app_component_common_userText_label" = "Text"; +"app_components_common_userText_prompt" = "Enter a text"; +"app_components_common_userText_label" = "Text"; "app_components_common_layout_label" = "Layout"; "app_components_common_textOnlyLayout_label" = "Text only"; "app_components_common_iconAndTextLayout_label" = "Icon + text"; @@ -117,6 +115,12 @@ "app_components_common_divider_label" = "Divider"; "app_components_common_onError_label" = "On error"; "app_components_common_orientation_label" = "Orientation"; +"app_components_common_editContent_label" = "Edit content"; + +// MARK: Components: Empty state + +"app_components_emptyContent_text" = "No content"; +"app_components_emptyContent_description_text" = "This content is under construction and will be available very soon"; // MARK: Components: Button From dc7ccdf9b274aafcca88704e5feda306584e0370 Mon Sep 17 00:00:00 2001 From: Ludovic PINEL Date: Wed, 5 Feb 2025 11:51:21 +0100 Subject: [PATCH 11/13] review fix typo and remarks --- CHANGELOG.md | 2 +- .../Pages/Components/Switch/SwitchConfiguration.swift | 2 +- .../DividerModifier/DividerModifier.swift | 4 +++- .../ViewModifiers/DividerModifier/View+Divider.swift | 11 ++++++----- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67dba7c49..0f01fc100 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- [DesignToolbox] Add text field in component configuration to customize text ([#436](https://github.com/Orange-OpenSource/ouds-ios/issues/436)) - [Library] Switch component ([#405](https://github.com/Orange-OpenSource/ouds-ios/issues/405)) +- [DesignToolbox] Add text field in component configuration to customize text ([#436](https://github.com/Orange-OpenSource/ouds-ios/issues/436)) - [Library] Link component ([#400](https://github.com/Orange-OpenSource/ouds-ios/issues/400)) ## [0.10.0](https://github.com/Orange-OpenSource/ouds-ios/compare/0.9.0...0.10.0) - 2025-01-30 diff --git a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift index 57e3cc371..6fde42717 100644 --- a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift +++ b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift @@ -16,7 +16,7 @@ import SwiftUI // MARK: - Switch Configuration Model -/// The model shared between `SwitchPageConfiguration` view and `SwitchPageComponent` view. +/// The model shared between `SwitchConfiguration` view and `SwitchIllustration` view. final class SwitchConfigurationModel: ComponentConfiguration { // MARK: Published properties diff --git a/OUDS/Core/Components/Sources/ViewModifiers/DividerModifier/DividerModifier.swift b/OUDS/Core/Components/Sources/ViewModifiers/DividerModifier/DividerModifier.swift index 8da36aa45..494abc132 100644 --- a/OUDS/Core/Components/Sources/ViewModifiers/DividerModifier/DividerModifier.swift +++ b/OUDS/Core/Components/Sources/ViewModifiers/DividerModifier/DividerModifier.swift @@ -15,13 +15,15 @@ import OUDSTokensSemantic import SwiftUI /// A `ViewModifier` which will apply a specific divider under a `View` using color semantic token. + +/// - Since: 0.11.0 public struct DividerModifier: ViewModifier { // MARK: - Stored properties @Environment(\.theme) private var theme @Environment(\.colorScheme) private var colorScheme - let show: Bool + private let show: Bool // MARK: - Initializer diff --git a/OUDS/Core/Components/Sources/ViewModifiers/DividerModifier/View+Divider.swift b/OUDS/Core/Components/Sources/ViewModifiers/DividerModifier/View+Divider.swift index c519cf11c..292e0cdd0 100644 --- a/OUDS/Core/Components/Sources/ViewModifiers/DividerModifier/View+Divider.swift +++ b/OUDS/Core/Components/Sources/ViewModifiers/DividerModifier/View+Divider.swift @@ -16,17 +16,18 @@ import SwiftUI extension View { - /// Modifies the current `View` to apply a divider under. + /// Modifies the current `View` to apply a divider below. /// /// ```swift /// var body: some View { /// SomeView() - /// .divider() + /// .oudsDivider(show: true) /// ``` - /// - Parameter: - /// - show: if true the divider is displayed, else there is no divider. + /// - Parameter show: if true the divider is displayed, else there is no divider. /// - /// - Returns some View: The current `View` but with a divider under. + /// - Returns: The current `View` but with a divider below. + /// + /// - Since: 0.11.0 public func oudsDivider(show: Bool = true) -> some View { self.modifier(DividerModifier(show: show)) } From 637c45a647ebfabaa8d38217adbda6db96fd9e66 Mon Sep 17 00:00:00 2001 From: Ludovic PINEL Date: Wed, 5 Feb 2025 12:04:50 +0100 Subject: [PATCH 12/13] review: rename Label to LabelText --- .../Pages/Components/Switch/SwitchConfiguration.swift | 8 ++++---- .../Pages/Components/Switch/SwitchPage.swift | 2 +- .../DesignToolbox/Resources/en.lproj/Localizable.strings | 3 ++- .../Sources/Switch/Internal/OUDSSwitchLabel.swift | 4 ++-- OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift | 6 +++--- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift index 6fde42717..5c4931c71 100644 --- a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift +++ b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift @@ -42,7 +42,7 @@ final class SwitchConfigurationModel: ComponentConfiguration { @Published var orientation: OUDSSwitch.Orientation { didSet { updateCode() } } - @Published var labelContent: String + @Published var labelTextContent: String @Published var helperTextContent: String // MARK: Initializer @@ -55,7 +55,7 @@ final class SwitchConfigurationModel: ComponentConfiguration { onError = false divider = true orientation = .default - labelContent = String(localized: "app_components_switch_label_text") + labelTextContent = String(localized: "app_components_switch_labelText_text") helperTextContent = String(localized: "app_components_switch_helperText_text") } @@ -85,7 +85,7 @@ final class SwitchConfigurationModel: ComponentConfiguration { private var helperTextPatern: String { if helperText { - return ",helperText: \(String(localized: "app_components_switch_helperText_text"))" + return ", helperText: \(String(localized: "app_components_switch_helperText_text"))" } else { return "" } @@ -163,7 +163,7 @@ struct SwitchConfiguration: View { } DisclosureGroup("app_components_common_editContent_label") { - DesignToolboxTextField(text: $model.labelContent, prompt: "app_components_common_userText_prompt", title: "app_components_switch_label_text") + DesignToolboxTextField(text: $model.labelTextContent, prompt: "app_components_common_userText_prompt", title: "app_components_switch_labelText_label") if model.helperText { DesignToolboxTextField(text: $model.helperTextContent, prompt: "app_components_common_userText_prompt", title: "app_components_common_helperText_label") } diff --git a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift index ee055934e..f6d33a29b 100644 --- a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift +++ b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchPage.swift @@ -85,7 +85,7 @@ private struct SwitchDemo: View { } else { OUDSSwitch( isOn: $isOn, - label: model.labelContent, + labelText: model.labelTextContent, helperText: helperTextContent, icon: icon, onError: model.onError, diff --git a/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings b/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings index 0b6cc6153..2eb7a2c24 100644 --- a/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings +++ b/DesignToolbox/DesignToolbox/Resources/en.lproj/Localizable.strings @@ -111,6 +111,7 @@ "app_components_common_style_label" = "Style"; "app_components_common_onColoredBackground_label" = "On colored background"; "app_components_common_helperText_label" = "Helper text"; +"app_components_common_labelText_label" = "Label text"; "app_components_common_icon_label" = "Icon"; "app_components_common_divider_label" = "Divider"; "app_components_common_onError_label" = "On error"; @@ -143,6 +144,6 @@ "app_components_switch_label" = "Switch"; "app_components_switch_description_text" = "A switch is a component that allows the user to toggle between two states, typically \"on\" and \"off\". It is often represented as a button or a slider that changes position or color to indicate the current state. Switches are used to enable or disable features, options, or settings in an intuitive and visual manner."; -"app_components_switch_label_text" = "Label"; "app_components_switch_switchOnly_label" = "Switch only"; +"app_components_switch_labelText_text" = "Label text"; "app_components_switch_helperText_text" = "Helper text"; diff --git a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabel.swift b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabel.swift index c0d651d65..4609d5ce2 100644 --- a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabel.swift +++ b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabel.swift @@ -23,7 +23,7 @@ struct OUDSSwitchLabel: View { @Environment(\.colorScheme) private var colorScheme struct Label { - let label: String + let labelText: String let helperText: String? let icon: Image? let onError: Bool @@ -68,7 +68,7 @@ struct OUDSSwitchLabel: View { @ViewBuilder private var texts: some View { VStack(alignment: .leading, spacing: 0) { - Text(LocalizedStringKey(label.label)) + Text(LocalizedStringKey(label.labelText)) .typeLabelDefaultLarge(theme) .multilineTextAlignment(.leading) .foregroundStyle(labelColor) diff --git a/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift b/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift index 7c402432b..24831512d 100644 --- a/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift +++ b/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift @@ -73,15 +73,15 @@ public struct OUDSSwitch: View { /// - Parameters: /// - isOn: A binding to a property that determines whether the switch is on /// or off. - /// - label: The main label of the switch. + /// - labelText: The main label text of the switch. /// - helperText: An additonal helper text. /// - icon: An optional icon /// - onError: It the option is on error /// - divider: If true a divider is added at the bottom of the view. /// - orientation: Specify the orientation of the layout. If Default the switch at the leading position, if inverse it is on trailing. - public init(isOn: Binding, label: String, helperText: String? = nil, icon: Image? = nil, onError: Bool = false, divider: Bool = false, orientation: Orientation = .default) { + public init(isOn: Binding, labelText: String, helperText: String? = nil, icon: Image? = nil, onError: Bool = false, divider: Bool = false, orientation: Orientation = .default) { self.isOn = isOn - self.layout = .labeled(.init(label: label, helperText: helperText, icon: icon, onError: onError, divider: divider, orientation: orientation)) + self.layout = .labeled(.init(labelText: labelText, helperText: helperText, icon: icon, onError: onError, divider: divider, orientation: orientation)) } // swiftlint:enable line_length From eae0af07fae3647fb2f69b086b3ac0f78225d6ac Mon Sep 17 00:00:00 2001 From: Ludovic PINEL Date: Thu, 6 Feb 2025 11:41:05 +0100 Subject: [PATCH 13/13] review: some refacto, add doc, and animation on toggle state --- .../DesignToolbox.xcodeproj/project.pbxproj | 4 +- .../Switch/SwitchConfiguration.swift | 2 +- .../Switch/Internal/OUDSSwitchButton.swift | 4 +- .../Switch/Internal/OUDSSwitchLabel.swift | 20 +++--- .../Internal/OUDSSwitchLabeledStyle.swift | 8 +-- .../Sources/Switch/OUDSSwitch.swift | 64 ++++++++++++++----- 6 files changed, 69 insertions(+), 33 deletions(-) diff --git a/DesignToolbox/DesignToolbox.xcodeproj/project.pbxproj b/DesignToolbox/DesignToolbox.xcodeproj/project.pbxproj index 9b75e77e8..8637c4a21 100644 --- a/DesignToolbox/DesignToolbox.xcodeproj/project.pbxproj +++ b/DesignToolbox/DesignToolbox.xcodeproj/project.pbxproj @@ -423,14 +423,14 @@ 073543142CA17244001187EA /* Utils */ = { isa = PBXGroup; children = ( - 079475A22D5103B30081980A /* DesignToolboxBackgroundModifier.swift */, 0735430F2CA15440001187EA /* Cards */, 0708222D2CFF617000570EC7 /* Elements */, - 07F7533A2CC785620007450D /* DesignToolboxSectionHeaderStyle.swift */, + 079475A22D5103B30081980A /* DesignToolboxBackgroundModifier.swift */, 07AB45862D4D08080001D237 /* DesignToolboxChoicePicker.swift */, 6DCC57E12CEB984D000F35F8 /* DesignToolboxCode.swift */, 07F0AFE72CFE0FBD00D334DD /* DesignToolboxConfiguration.swift */, 07B3CCB42D40F36C00DBB10A /* DesignToolboxColoredBackgroundModifier.swift */, + 07F7533A2CC785620007450D /* DesignToolboxSectionHeaderStyle.swift */, 07AB45892D50CB9B0001D237 /* DesignToolboxTextField.swift */, ); path = Utils; diff --git a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift index 5c4931c71..279cd8589 100644 --- a/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift +++ b/DesignToolbox/DesignToolbox/Pages/Components/Switch/SwitchConfiguration.swift @@ -163,7 +163,7 @@ struct SwitchConfiguration: View { } DisclosureGroup("app_components_common_editContent_label") { - DesignToolboxTextField(text: $model.labelTextContent, prompt: "app_components_common_userText_prompt", title: "app_components_switch_labelText_label") + DesignToolboxTextField(text: $model.labelTextContent, prompt: "app_components_common_userText_prompt", title: "app_components_common_labelText_label") if model.helperText { DesignToolboxTextField(text: $model.helperTextContent, prompt: "app_components_common_userText_prompt", title: "app_components_common_helperText_label") } diff --git a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchButton.swift b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchButton.swift index d8483f6a8..e5823d4a3 100644 --- a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchButton.swift +++ b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchButton.swift @@ -38,7 +38,7 @@ struct OUDSSwitchButton: View { .shadow(elevation: theme.elevations.elevationRaised.elevation(for: colorScheme)) } .padding(.horizontal, spacePadding) - .frame(width: trackrWidth, height: trackHeight, alignment: cursorHorizontalAlignment) + .frame(width: trackWidth, height: trackHeight, alignment: cursorHorizontalAlignment) .background(trackColor) .clipShape(Capsule()) } @@ -83,7 +83,7 @@ struct OUDSSwitchButton: View { } } - private var trackrWidth: Double { + private var trackWidth: Double { Constants.inputSwitchSizeWidthTrack } diff --git a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabel.swift b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabel.swift index 4609d5ce2..be746e517 100644 --- a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabel.swift +++ b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabel.swift @@ -22,7 +22,7 @@ struct OUDSSwitchLabel: View { @Environment(\.theme) private var theme @Environment(\.colorScheme) private var colorScheme - struct Label { + struct Items { let labelText: String let helperText: String? let icon: Image? @@ -32,13 +32,13 @@ struct OUDSSwitchLabel: View { } let internalState: InternalSwitchState - let label: Label + let items: Items // MARK: Body var body: some View { HStack(alignment: .top, spacing: theme.listItem.listItemSpaceColumnGap) { - switch label.orientation { + switch items.orientation { case .default: texts icon @@ -53,7 +53,7 @@ struct OUDSSwitchLabel: View { @ViewBuilder private var icon: some View { - if let icon = label.icon { + if let icon = items.icon { HStack(alignment: .center, spacing: 0) { icon .resizable() @@ -68,26 +68,28 @@ struct OUDSSwitchLabel: View { @ViewBuilder private var texts: some View { VStack(alignment: .leading, spacing: 0) { - Text(LocalizedStringKey(label.labelText)) + Spacer() + Text(LocalizedStringKey(items.labelText)) .typeLabelDefaultLarge(theme) .multilineTextAlignment(.leading) - .foregroundStyle(labelColor) + .foregroundStyle(labelTextColor) .frame(maxWidth: .infinity, alignment: .leading) - if let helperText = label.helperText { + if let helperText = items.helperText { Text(LocalizedStringKey(helperText)) .typeLabelDefaultMedium(theme) .multilineTextAlignment(.leading) .foregroundStyle(helperTextColor) } + Spacer() } .frame(maxWidth: .infinity, alignment: .leading) } - private var labelColor: Color { + private var labelTextColor: Color { switch internalState { case .enabled, .pressed, .hover: - (label.onError ? theme.colors.colorContentStatusNegative : theme.colors.colorContentDefault) + (items.onError ? theme.colors.colorContentStatusNegative : theme.colors.colorContentDefault) .color(for: colorScheme) case .disabled: theme.colors.colorContentDisabled.color(for: colorScheme) diff --git a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabeledStyle.swift b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabeledStyle.swift index 9bfdb4931..351610c97 100644 --- a/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabeledStyle.swift +++ b/OUDS/Core/Components/Sources/Switch/Internal/OUDSSwitchLabeledStyle.swift @@ -30,13 +30,13 @@ struct OUDSSwitchLabeledStyle: ButtonStyle { @State private var isHover: Bool = false let isOn: Bool - let label: OUDSSwitchLabel.Label + let items: OUDSSwitchLabel.Items // MARK: Body func makeBody(configuration: Configuration) -> some View { HStack(alignment: .top, spacing: theme.listItem.listItemSpaceColumnGap) { - switch label.orientation { + switch items.orientation { case .default: toggle(isPressed: configuration.isPressed) label(isPressed: configuration.isPressed) @@ -46,7 +46,7 @@ struct OUDSSwitchLabeledStyle: ButtonStyle { } } .padding(.all, theme.listItem.listItemSpaceInset) - .oudsDivider(show: label.divider) + .oudsDivider(show: items.divider) .background(backgroundColor(state: internalState(isPressed: configuration.isPressed))) .onHover { isHover in self.isHover = isHover @@ -61,7 +61,7 @@ struct OUDSSwitchLabeledStyle: ButtonStyle { } private func label(isPressed: Bool) -> some View { - OUDSSwitchLabel(internalState: internalState(isPressed: isPressed), label: label) + OUDSSwitchLabel(internalState: internalState(isPressed: isPressed), items: items) } // MARK: Private Helpers diff --git a/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift b/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift index 24831512d..cd92324aa 100644 --- a/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift +++ b/OUDS/Core/Components/Sources/Switch/OUDSSwitch.swift @@ -22,14 +22,34 @@ import SwiftUI /// /// ## Code samples /// ```swift +/// // Supposing we have a switch in on state +/// @Published var isOn: Bool = false +/// /// // Switch as nested element (with no text) /// OUDSSwitch(isOn $isOn) /// -/// // Text, icon and divider -/// OUDSSwtcih(isOn $isOn, label: "Allow notifications", icon: Image("ic_heart"), divider: true) +/// // A leading switch with a label, an helper text and an icon. +/// // The default layout will be used here. +/// OUDSSwtcih(isOn $isOn, +/// label: "Allow notifications", +/// icon: Image("ic_heart")) +/// +/// // A leading switch with a label, an helper text, an icon, a divider and is about an error. +/// // The default layout will be used here. +/// OUDSSwtcih(isOn $isOn, +/// label: "Allow notifications", +/// icon: Image("ic_heart"), +/// onError: true, +/// divider: true) /// -/// // Text, icon and divider with error in form -/// OUDSSwtcih(isOn $isOn, label: "Allow notifications", icon: Image("ic_heart"), onError: true, divider: true) +/// // A leading switch with a label, an helper text, an icon, a divider and is about an error. +/// // The inverse layout will be used here. +/// OUDSSwtcih(isOn $isOn, +/// label: "Allow notifications", +/// icon: Image("ic_heart"), +/// onError: true, +/// divider: true, +/// orientation: .inverse) /// ``` /// /// ## Design documentation @@ -44,14 +64,20 @@ public struct OUDSSwitch: View { private var isOn: Binding private let layout: Layout + /// The two available layouts for this component private enum Layout { - case labeled(OUDSSwitchLabel.Label) - case nested + /// Switch selector with labled elements + /// Details are defined in the ``OUDSSwitchLabel.Items``. + case labeled(OUDSSwitchLabel.Items) + /// Displays only the switch selector + case selectorOnly } /// Used to define the orientation of the Layout public enum Orientation { + // Switch selector in leading position, icon in trailing position, like LTR mode. case `default` + /// Icon in leading position, switch selector in trailing position, like RTL mode case inverse } @@ -64,10 +90,9 @@ public struct OUDSSwitch: View { /// or off. public init(isOn: Binding) { self.isOn = isOn - self.layout = .nested + self.layout = .selectorOnly } - // swiftlint:disable line_length /// Creates a switch with label and optional helper text, icon, divider. /// /// - Parameters: @@ -79,24 +104,33 @@ public struct OUDSSwitch: View { /// - onError: It the option is on error /// - divider: If true a divider is added at the bottom of the view. /// - orientation: Specify the orientation of the layout. If Default the switch at the leading position, if inverse it is on trailing. - public init(isOn: Binding, labelText: String, helperText: String? = nil, icon: Image? = nil, onError: Bool = false, divider: Bool = false, orientation: Orientation = .default) { + public init(isOn: Binding, + labelText: String, + helperText: String? = nil, + icon: Image? = nil, + onError: Bool = false, + divider: Bool = false, + orientation: Orientation = .default) { self.isOn = isOn self.layout = .labeled(.init(labelText: labelText, helperText: helperText, icon: icon, onError: onError, divider: divider, orientation: orientation)) } - // swiftlint:enable line_length // MARK: Body public var body: some View { switch layout { - case .labeled(let label): + case .labeled(let items): Button("") { - isOn.wrappedValue.toggle() + withAnimation(.easeInOut) { + isOn.wrappedValue.toggle() + } } - .buttonStyle(OUDSSwitchLabeledStyle(isOn: isOn.wrappedValue, label: label)) - case .nested: + .buttonStyle(OUDSSwitchLabeledStyle(isOn: isOn.wrappedValue, items: items)) + case .selectorOnly: Button("") { - isOn.wrappedValue.toggle() + withAnimation(.easeInOut) { + isOn.wrappedValue.toggle() + } } .buttonStyle(OUDSSwitchNestedStyle(isOn: isOn.wrappedValue)) }