From 759bc026d87a7cb2a50f8cf0b2ef2d7fc3040063 Mon Sep 17 00:00:00 2001 From: Ludovic PINEL Date: Tue, 4 Feb 2025 14:41:53 +0100 Subject: [PATCH] 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 ce434a21f..f9b066923 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