From 8805e6b71958601ce69c7c02263609031c74b6cb Mon Sep 17 00:00:00 2001 From: Pierre-Yves Lapersonne Date: Fri, 31 Jan 2025 18:34:06 +0100 Subject: [PATCH] feat: default layout (#264) Signed-off-by: Pierre-Yves Lapersonne --- .../Components/Checkbox/CheckboxPage.swift | 81 +++++++-- .../Sources/Checkbox/Internal/Checkbox.swift | 167 ++++++++++++++++++ .../Checkbox/Internal/CheckboxSelector.swift | 58 +++--- .../Internal/InternalCheckboxState.swift | 24 --- ...OUDSCheckboxStyle+BackgroundModifier.swift | 2 +- .../OUDSCheckboxStyle+BorderModifier.swift | 2 +- ...OUDSCheckboxStyle+ForegroundModifier.swift | 82 ++++++--- .../OUDSCheckboxStyle+NormalModifier.swift | 20 ++- .../Checkbox/Internal/OUDSCheckboxStyle.swift | 30 ++-- .../Sources/Checkbox/OUDSCheckbox.swift | 70 +++++++- 10 files changed, 421 insertions(+), 115 deletions(-) create mode 100644 OUDS/Core/Components/Sources/Checkbox/Internal/Checkbox.swift delete mode 100644 OUDS/Core/Components/Sources/Checkbox/Internal/InternalCheckboxState.swift diff --git a/DesignToolbox/DesignToolbox/Pages/Components/Checkbox/CheckboxPage.swift b/DesignToolbox/DesignToolbox/Pages/Components/Checkbox/CheckboxPage.swift index 88f87cdc7..639377707 100644 --- a/DesignToolbox/DesignToolbox/Pages/Components/Checkbox/CheckboxPage.swift +++ b/DesignToolbox/DesignToolbox/Pages/Components/Checkbox/CheckboxPage.swift @@ -20,21 +20,78 @@ import SwiftUI // TODO: #264 - Update page +// swiftlint:disable accessibility_label_for_image struct CheckboxPage: View { var body: some View { - // Enabled - OUDSCheckbox(status: .selected, style: .default) { print("@@@ high level tap") } - OUDSCheckbox(status: .unselected, style: .default) { } - OUDSCheckbox(status: .undeterminate, style: .default) { } - OUDSCheckbox(status: .errorSelected, style: .default) { } - OUDSCheckbox(status: .errorUnselected, style: .default) { } - OUDSCheckbox(status: .errorUndeterminate, style: .default) { } - - // Disabled - OUDSCheckbox(status: .selected, style: .default) { }.disabled(true) - OUDSCheckbox(status: .unselected, style: .default) { }.disabled(true) - OUDSCheckbox(status: .undeterminate, style: .default) { }.disabled(true) + VStack(spacing: 2) { + + // Enabled + OUDSCheckbox(label: "Hello world", icon: Image(systemName: "heart"), status: .selected, style: .default) { } + OUDSCheckbox(label: "Hello world", icon: Image(systemName: "heart"), status: .unselected, style: .default) { } + OUDSCheckbox(label: "Hello world", icon: Image(systemName: "heart"), status: .undeterminate, style: .default) { } + OUDSCheckbox(label: "Hello world", icon: Image(systemName: "heart"), status: .errorSelected, style: .default) { } + OUDSCheckbox(label: "Hello world", icon: Image(systemName: "heart"), status: .errorUnselected, style: .default) { } + OUDSCheckbox(label: "Hello world", icon: Image(systemName: "heart"), status: .errorUndeterminate, style: .default) { } + + // Disabled + OUDSCheckbox(label: "Hello world", icon: Image(systemName: "heart"), status: .selected, style: .default) { }.disabled(true) + OUDSCheckbox(label: "Hello world", icon: Image(systemName: "heart"), status: .unselected, style: .default) { }.disabled(true) + OUDSCheckbox(label: "Hello world", icon: Image(systemName: "heart"), status: .undeterminate, style: .default) { }.disabled(true) + + // Enabled + OUDSCheckbox(label: "Hello", helper: "World", icon: Image(systemName: "heart"), status: .selected, style: .default) { } + OUDSCheckbox(label: "Hello", helper: "World", icon: Image(systemName: "heart"), status: .unselected, style: .default) { } + OUDSCheckbox(label: "Hello", helper: "World", icon: Image(systemName: "heart"), status: .undeterminate, style: .default) { } + OUDSCheckbox(label: "Hello", helper: "World", icon: Image(systemName: "heart"), status: .errorSelected, style: .default) { } + OUDSCheckbox(label: "Hello", helper: "World", icon: Image(systemName: "heart"), status: .errorUnselected, style: .default) { } + OUDSCheckbox(label: "Hello", helper: "World", icon: Image(systemName: "heart"), status: .errorUndeterminate, style: .default) { } + + // Disabled + OUDSCheckbox(label: "Hello", helper: "World", icon: Image(systemName: "heart"), status: .selected, style: .default) { }.disabled(true) + OUDSCheckbox(label: "Hello", helper: "World", icon: Image(systemName: "heart"), status: .unselected, style: .default) { }.disabled(true) + OUDSCheckbox(label: "Hello", helper: "World", icon: Image(systemName: "heart"), status: .undeterminate, style: .default) { }.disabled(true) + + // Enabled + OUDSCheckbox(label: "Hello", helper: "World", status: .selected, style: .default) { } + OUDSCheckbox(label: "Hello", helper: "World", status: .unselected, style: .default) { } + OUDSCheckbox(label: "Hello", helper: "World", status: .undeterminate, style: .default) { } + OUDSCheckbox(label: "Hello", helper: "World", status: .errorSelected, style: .default) { } + OUDSCheckbox(label: "Hello", helper: "World", status: .errorUnselected, style: .default) { } + OUDSCheckbox(label: "Hello", helper: "World", status: .errorUndeterminate, style: .default) { } + + // Disabled + OUDSCheckbox(label: "Hello", helper: "World", status: .selected, style: .default) { }.disabled(true) + OUDSCheckbox(label: "Hello", helper: "World", status: .unselected, style: .default) { }.disabled(true) + OUDSCheckbox(label: "Hello", helper: "World", status: .undeterminate, style: .default) { }.disabled(true) + + // Enabled + OUDSCheckbox(label: "Hello world", status: .selected, style: .default) { } + OUDSCheckbox(label: "Hello world", status: .unselected, style: .default) { } + OUDSCheckbox(label: "Hello world", status: .undeterminate, style: .default) { } + OUDSCheckbox(label: "Hello world", status: .errorSelected, style: .default) { } + OUDSCheckbox(label: "Hello world", status: .errorUnselected, style: .default) { } + OUDSCheckbox(label: "Hello world", status: .errorUndeterminate, style: .default) { } + + // Disabled + OUDSCheckbox(label: "Hello world", status: .selected, style: .default) { }.disabled(true) + OUDSCheckbox(label: "Hello world", status: .unselected, style: .default) { }.disabled(true) + OUDSCheckbox(label: "Hello world", status: .undeterminate, style: .default) { }.disabled(true) + + // Enabled + OUDSCheckbox(status: .selected, style: .default) { print("@@@ high level tap") } + OUDSCheckbox(status: .unselected, style: .default) { } + OUDSCheckbox(status: .undeterminate, style: .default) { } + OUDSCheckbox(status: .errorSelected, style: .default) { } + OUDSCheckbox(status: .errorUnselected, style: .default) { } + OUDSCheckbox(status: .errorUndeterminate, style: .default) { } + + // Disabled + OUDSCheckbox(status: .selected, style: .default) { }.disabled(true) + OUDSCheckbox(status: .unselected, style: .default) { }.disabled(true) + OUDSCheckbox(status: .undeterminate, style: .default) { }.disabled(true) + } } } +// swiftlint:enable accessibility_label_for_image diff --git a/OUDS/Core/Components/Sources/Checkbox/Internal/Checkbox.swift b/OUDS/Core/Components/Sources/Checkbox/Internal/Checkbox.swift new file mode 100644 index 000000000..7af72a665 --- /dev/null +++ b/OUDS/Core/Components/Sources/Checkbox/Internal/Checkbox.swift @@ -0,0 +1,167 @@ +// +// 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 checkbox component with several layouts +struct Checkbox: View { + + // MARK: - Properties + + private let selectorOnly: Bool + private let label: String? + private let helper: String? + private let icon: Image? + private let hasDivider: Bool + private let status: OUDSCheckbox.Status + private let action: () -> Void + + @Environment(\.theme) private var theme + + // MARK: - Initializers + + init(status: OUDSCheckbox.Status, + action: @escaping () -> Void) { + selectorOnly = true + label = nil + helper = nil + icon = nil + hasDivider = false + self.status = status + self.action = action + } + + init(label: String, + status: OUDSCheckbox.Status, + hasDivider: Bool = false, + action: @escaping () -> Void) { + selectorOnly = false + self.label = label + helper = nil + icon = nil + self.hasDivider = hasDivider + self.status = status + self.action = action + } + + init(label: String, + helper: String, + status: OUDSCheckbox.Status, + hasDivider: Bool = false, + action: @escaping () -> Void) { + selectorOnly = false + self.label = label + self.helper = helper + icon = nil + self.hasDivider = hasDivider + self.status = status + self.action = action + } + + init(label: String, + icon: Image, + status: OUDSCheckbox.Status, + hasDivider: Bool = false, + action: @escaping () -> Void) { + selectorOnly = false + self.label = label + helper = nil + self.icon = icon + self.hasDivider = hasDivider + self.status = status + self.action = action + } + + init(label: String, + helper: String, + icon: Image, + status: OUDSCheckbox.Status, + hasDivider: Bool = false, + action: @escaping () -> Void) { + selectorOnly = false + self.label = label + self.helper = helper + self.icon = icon + self.hasDivider = hasDivider + self.status = status + self.action = action + } + + // MARK: - Body + + var body: some View { + appliedLayout() + .padding(theme.select.selectSpacePaddingInset) + .frame(minWidth: theme.listItem.listItemSizeMinWidth, + minHeight: theme.listItem.listItemSizeMinHeight) + .accessibilityAddTraits(.isButton) + .onTapGesture { + action() + } + .border(Color.black, width: 1) // NOTE: #264 - Debug + } + + // MARK: - Layouts + + @ViewBuilder + private func appliedLayout() -> some View { + if selectorOnly { + selectorOnlyLayout() + } else { + defaultLayout() + } + } + + private func selectorOnlyLayout() -> some View { + CheckboxSelector(isAlone: true, status: status) + .accessibilityAddTraits(.isButton) + .onTapGesture { + action() + } + .modifier(OUDSCheckboxStyle(status: status, item: .checkbox)) + } + + // We are sure here label can be uwnrapped because of precondition above (selectorOnly) + // swiftlint:disable force_unwrapping + private func defaultLayout() -> some View { + HStack(spacing: theme.listItem.listItemSpaceColumnGap) { + CheckboxSelector(isAlone: false, status: status) + .modifier(OUDSCheckboxStyle(status: status, item: .checkbox)) + + if let helper { + VStack(spacing: theme.listItem.listItemSpaceRowGap) { + Text(label!) + .typeLabelDefaultLarge(theme) + .modifier(OUDSCheckboxStyle(status: status, item: .label)) + + Text(helper) + .typeLabelDefaultMedium(theme) + .modifier(OUDSCheckboxStyle(status: status, item: .helper)) + } + } else { + Text(label!) + .typeLabelDefaultLarge(theme) + .modifier(OUDSCheckboxStyle(status: status, item: .label)) + } + + if let icon { + Spacer() + + icon + .frame(maxHeight: theme.checkRadio.checkRadioSizeMaxHeightAssetsContainer) + .accessibilityHidden(true) + } + } + } + // swiftlint:enable force_unwrapping +} diff --git a/OUDS/Core/Components/Sources/Checkbox/Internal/CheckboxSelector.swift b/OUDS/Core/Components/Sources/Checkbox/Internal/CheckboxSelector.swift index 5f23b1b67..a8cdf59bf 100644 --- a/OUDS/Core/Components/Sources/Checkbox/Internal/CheckboxSelector.swift +++ b/OUDS/Core/Components/Sources/Checkbox/Internal/CheckboxSelector.swift @@ -13,24 +13,10 @@ import SwiftUI -/// The checkbox component with only the selector +/// The square with the tick or not, i.e. the checkbox selector depending to the given ``OUDSCheckbox.Status`` struct CheckboxSelector: View { - let status: OUDSCheckbox.Status - let action: () -> Void - - var body: some View { - SelectorBox(status: status) - .accessibilityAddTraits(.isButton) - .onTapGesture { - action() - } - } -} - -/// The square with the tick or not, i.e. the checkbox selector depending to the given `OUDSCheckbox.Status` -private struct SelectorBox: View { - + let isAlone: Bool let status: OUDSCheckbox.Status @Environment(\.theme) private var theme @@ -41,14 +27,9 @@ private struct SelectorBox: View { tickImage(name: "checkmark") } else if status == .undeterminate || status == .errorUndeterminate { tickImage(name: "minus") - } else { + } else { // .unselected and .errorUnselected cases Color.clear - .frame(minWidth: theme.checkRadio.checkRadioSizeMinWidthSelectorOnly, - maxWidth: theme.checkRadio.checkRadioSizeMinWidthSelectorOnly, // TODO: #264 - No token for maw width! - minHeight: theme.checkRadio.checkRadioSizeMinHeightSelectorOnly, - maxHeight: theme.checkRadio.checkRadioSizeMaxHeightSelectorOnly) - .padding(4) - .modifier(OUDSCheckboxStyle(status: status)) + .modifier(SelectorFrameModifier(isAlone: isAlone)) } } @@ -56,12 +37,31 @@ private struct SelectorBox: View { Image(systemName: name) .resizable() .scaledToFit() - .frame(minWidth: theme.checkRadio.checkRadioSizeMinWidthSelectorOnly, - maxWidth: theme.checkRadio.checkRadioSizeMinWidthSelectorOnly, // TODO: #264 - No token for maw width! - minHeight: theme.checkRadio.checkRadioSizeMinHeightSelectorOnly, - maxHeight: theme.checkRadio.checkRadioSizeMaxHeightSelectorOnly) - .padding(4) - .modifier(OUDSCheckboxStyle(status: status)) + .modifier(SelectorFrameModifier(isAlone: isAlone)) .accessibilityHidden(true) } } + +/// `ViewModifier` for the checbkox slector to define the tokens to use depending to if the checkkox is alone or not +private struct SelectorFrameModifier: ViewModifier { + + let isAlone: Bool + @Environment(\.theme) private var theme + + // TODO: #264 - Ensure the rules for min/xax height/width are the good ones in Figma + func body(content: Content) -> some View { + content + .frame(width: theme.listItem.listItemSizeIcon, + height: theme.listItem.listItemSizeIcon) +// if isAlone { +// content +// .frame(minWidth: theme.checkRadio.checkRadioSizeMinWidthSelectorOnly, +// minHeight: theme.checkRadio.checkRadioSizeMinHeightSelectorOnly, +// maxHeight: theme.checkRadio.checkRadioSizeMaxHeightSelectorOnly) +// } else { +// content +// .frame(minHeight: theme.listItem.listItemSizeIcon, +// maxHeight: theme.checkRadio.checkRadioSizeMaxHeightAssetsContainer) +// } + } +} diff --git a/OUDS/Core/Components/Sources/Checkbox/Internal/InternalCheckboxState.swift b/OUDS/Core/Components/Sources/Checkbox/Internal/InternalCheckboxState.swift deleted file mode 100644 index 3e9255223..000000000 --- a/OUDS/Core/Components/Sources/Checkbox/Internal/InternalCheckboxState.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// 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 -// - -/// The internal state used by modifiers to handle all states of components. -enum InternalCheckboxState { - case enabled - case hover - case pressed - case disabled - case readOnly - // .loading not managed yet, for next version - // .focus not managed as not such customizable - // .skeleton not managed as dedicated view in the end -} diff --git a/OUDS/Core/Components/Sources/Checkbox/Internal/OUDSCheckboxStyle+BackgroundModifier.swift b/OUDS/Core/Components/Sources/Checkbox/Internal/OUDSCheckboxStyle+BackgroundModifier.swift index a4f5acb8e..66495e9c9 100644 --- a/OUDS/Core/Components/Sources/Checkbox/Internal/OUDSCheckboxStyle+BackgroundModifier.swift +++ b/OUDS/Core/Components/Sources/Checkbox/Internal/OUDSCheckboxStyle+BackgroundModifier.swift @@ -23,7 +23,7 @@ struct CheckboxSelectorBackgroundModifier: ViewModifier { // MARK: - Properties let status: OUDSCheckbox.Status - let state: InternalCheckboxState + let state: OUDSCheckbox.State // MARK: - Environment diff --git a/OUDS/Core/Components/Sources/Checkbox/Internal/OUDSCheckboxStyle+BorderModifier.swift b/OUDS/Core/Components/Sources/Checkbox/Internal/OUDSCheckboxStyle+BorderModifier.swift index 7fa9580f8..a03fb05a7 100644 --- a/OUDS/Core/Components/Sources/Checkbox/Internal/OUDSCheckboxStyle+BorderModifier.swift +++ b/OUDS/Core/Components/Sources/Checkbox/Internal/OUDSCheckboxStyle+BorderModifier.swift @@ -25,7 +25,7 @@ struct CheckboxSelectorBorderModifier: ViewModifier { // MARK: - Properties let status: OUDSCheckbox.Status - let state: InternalCheckboxState + let state: OUDSCheckbox.State // MARK: - Environment diff --git a/OUDS/Core/Components/Sources/Checkbox/Internal/OUDSCheckboxStyle+ForegroundModifier.swift b/OUDS/Core/Components/Sources/Checkbox/Internal/OUDSCheckboxStyle+ForegroundModifier.swift index f04c36985..5633ea07b 100644 --- a/OUDS/Core/Components/Sources/Checkbox/Internal/OUDSCheckboxStyle+ForegroundModifier.swift +++ b/OUDS/Core/Components/Sources/Checkbox/Internal/OUDSCheckboxStyle+ForegroundModifier.swift @@ -17,15 +17,16 @@ import OUDSTokensComponent import OUDSTokensSemantic import SwiftUI -/// A `ViewModifier` to apply to the selector of the ``OUDSCheckboxComponent`` inside its ``OUDSCheckboxStyle``. -/// Uses the given ``OUDSCheckbox.Status`` and ``InternalCheckboxState`` to use the suitable tokens for foreground style +/// A `ViewModifier` to apply to the ``OUDSCheckbox.Item`` of the ``OUDSCheckboxComponent``. +/// Uses the given ``OUDSCheckbox.Status`` and `` OUDSCheckbox.State`` to use the suitable tokens for foreground style /// like the colors to apply. -struct CheckboxSelectorForegroundModifier: ViewModifier { +struct CheckboxForegroundModifier: ViewModifier { // MARK: - Properties let status: OUDSCheckbox.Status - let state: InternalCheckboxState + let state: OUDSCheckbox.State + let item: OUDSCheckbox.Item // MARK: - Environment @@ -54,32 +55,71 @@ struct CheckboxSelectorForegroundModifier: ViewModifier { } private var enabledColor: MultipleColorSemanticTokens { - switch status { - case .selected, .undeterminate: - return theme.colors.colorActionSelected - case .unselected: - return theme.colors.colorActionEnabled - case .errorSelected, .errorUnselected, .errorUndeterminate: - return theme.colors.colorActionNegativeEnabled + if item == .checkbox { + switch status { + case .selected, .undeterminate: + return theme.colors.colorActionSelected + case .unselected: + return theme.colors.colorActionEnabled + case .errorSelected, .errorUnselected, .errorUndeterminate: + return theme.colors.colorActionNegativeEnabled + } } + + if item == .label { + return state == .disabled ? disabledColor : theme.colors.colorContentDefault + } + + if item == .helper { + return state == .disabled ? disabledColor : theme.colors.colorContentMuted + } + + OL.warning("Item of OUDSCheckbox not managed to compute enabled color in foreground modifier O_ô") + return theme.colors.colorActionEnabled } private var hoverColor: MultipleColorSemanticTokens { - switch status { - case .selected, .unselected, .undeterminate: - return theme.colors.colorActionHover - case .errorSelected, .errorUnselected, .errorUndeterminate: - return theme.colors.colorActionNegativeHover + if item == .checkbox { + switch status { + case .selected, .unselected, .undeterminate: + return theme.colors.colorActionHover + case .errorSelected, .errorUnselected, .errorUndeterminate: + return theme.colors.colorActionNegativeHover + } + } + + if item == .label { + return state == .disabled ? disabledColor : theme.colors.colorContentDefault } + + if item == .helper { + return state == .disabled ? disabledColor : theme.colors.colorContentMuted + } + + OL.warning("Item of OUDSCheckbox not managed to compute hover color in foreground modifier O_ô") + return theme.colors.colorActionEnabled } private var pressedColor: MultipleColorSemanticTokens { - switch status { - case .selected, .unselected, .undeterminate: - return theme.colors.colorActionPressed - case .errorSelected, .errorUnselected, .errorUndeterminate: - return theme.colors.colorActionNegativePressed + if item == .checkbox { + switch status { + case .selected, .unselected, .undeterminate: + return theme.colors.colorActionPressed + case .errorSelected, .errorUnselected, .errorUndeterminate: + return theme.colors.colorActionNegativePressed + } + } + + if item == .label { + return state == .disabled ? disabledColor : theme.colors.colorContentDefault } + + if item == .helper { + return state == .disabled ? disabledColor : theme.colors.colorContentMuted + } + + OL.warning("Item of OUDSCheckbox not managed to compute pressed color in foreground modifier O_ô") + return theme.colors.colorActionEnabled } private var disabledColor: MultipleColorSemanticTokens { diff --git a/OUDS/Core/Components/Sources/Checkbox/Internal/OUDSCheckboxStyle+NormalModifier.swift b/OUDS/Core/Components/Sources/Checkbox/Internal/OUDSCheckboxStyle+NormalModifier.swift index 3ad718cd8..fbdcbe7bc 100644 --- a/OUDS/Core/Components/Sources/Checkbox/Internal/OUDSCheckboxStyle+NormalModifier.swift +++ b/OUDS/Core/Components/Sources/Checkbox/Internal/OUDSCheckboxStyle+NormalModifier.swift @@ -15,21 +15,27 @@ import OUDS import OUDSTokensSemantic import SwiftUI -/// `ViewModifier` which has iin charge to apply styles for foreground, background and border elements -/// associated to the given ``OUDSCheckbox.Status`` and ``InternalCheckboxState`` of the ``ODUSCheckbox`` +/// `ViewModifier` which has in charge to apply styles for foreground, background and border elements for example. +/// The applied styles are based on the given ``OUDSCheckbox.Status``, ``OUDSCheckbox.State`` and ``OUDSCheckbox.Layout`` of the ``ODUSCheckbox``. struct CheckboxViewModifier: ViewModifier { // MARK: - Properties let status: OUDSCheckbox.Status - let state: InternalCheckboxState + let state: OUDSCheckbox.State + let item: OUDSCheckbox.Item // MARK: - Body func body(content: Content) -> some View { - content - .modifier(CheckboxSelectorBorderModifier(status: status, state: state)) - .modifier(CheckboxSelectorForegroundModifier(status: status, state: state)) - .modifier(CheckboxSelectorBackgroundModifier(status: status, state: state)) + if item == .checkbox { + content + .modifier(CheckboxSelectorBorderModifier(status: status, state: state)) + .modifier(CheckboxForegroundModifier(status: status, state: state, item: item)) + .modifier(CheckboxSelectorBackgroundModifier(status: status, state: state)) + } else { + content + .modifier(CheckboxForegroundModifier(status: status, state: state, item: item)) + } } } diff --git a/OUDS/Core/Components/Sources/Checkbox/Internal/OUDSCheckboxStyle.swift b/OUDS/Core/Components/Sources/Checkbox/Internal/OUDSCheckboxStyle.swift index 273a343e2..2371e7d92 100644 --- a/OUDS/Core/Components/Sources/Checkbox/Internal/OUDSCheckboxStyle.swift +++ b/OUDS/Core/Components/Sources/Checkbox/Internal/OUDSCheckboxStyle.swift @@ -15,24 +15,15 @@ import OUDS import OUDSTokensSemantic import SwiftUI -// TODO: #254 - Add more details in doc -/// Used to apply the right style on an ``OUDSCheckbox`` according to the its states like ``OUDSCheckbox.Status`` -/// -/// Six statuses are managed: -/// - selected: the checkbox is filled by the user -/// - unselected: the checkbox is empty -/// - undeterminate: the checkbox is prefilled -/// - errorSelected: the checkbox is filled by the user, and is in error state -/// - errorUnselected: the checkbox is empty, and is in error state -/// - errorUndeterminate: the checkbox is prefilled, and is in error state -/// -/// Three component states are managed: *enabled*, *hover* and *pressed*. -/// +/// Used to apply the right style on an ``OUDSCheckbox`` according to the its states like ``OUDSCheckbox.Status`` and ``OUDSCheckbox.Layout``. +/// It provides also the ``OUDSCheckbox.State`` for the embeded `ViewModifier` soa s to define the look and feel +/// Four component states are managed: *enabled*, *disabled*, *hover* and *pressed*. struct OUDSCheckboxStyle: ViewModifier { // MARK: - Properties private let status: OUDSCheckbox.Status + private let item: OUDSCheckbox.Item @State private var isHover: Bool @State private var isPressed: Bool @@ -44,9 +35,12 @@ struct OUDSCheckboxStyle: ViewModifier { /// Initialize the `OUDSCheckboxStyle`. /// - /// - Parameter status: The checkbox status - public init(status: OUDSCheckbox.Status) { + /// - Parameters: + /// - status: The checkbox status + /// - item: The object on which style must be applied + public init(status: OUDSCheckbox.Status, item: OUDSCheckbox.Item) { self.status = status + self.item = item isHover = false isPressed = false } @@ -59,12 +53,14 @@ struct OUDSCheckboxStyle: ViewModifier { self.isHover = isHover } .changeOnTap(isTapped: $isPressed) - .modifier(CheckboxViewModifier(status: status, state: internalState(isPressed: isPressed))) + .modifier(CheckboxViewModifier(status: status, + state: internalState(isPressed: isPressed), + item: item)) } // MARK: - Helpers - private func internalState(isPressed: Bool) -> InternalCheckboxState { + private func internalState(isPressed: Bool) -> OUDSCheckbox.State { if !isEnabled { return .disabled } diff --git a/OUDS/Core/Components/Sources/Checkbox/OUDSCheckbox.swift b/OUDS/Core/Components/Sources/Checkbox/OUDSCheckbox.swift index 81801002f..8cade0aed 100644 --- a/OUDS/Core/Components/Sources/Checkbox/OUDSCheckbox.swift +++ b/OUDS/Core/Components/Sources/Checkbox/OUDSCheckbox.swift @@ -41,6 +41,8 @@ public struct OUDSCheckbox: View { private let style: Style private let action: () -> Void + @Environment(\.theme) private var theme + // MARK: - Type /// All the combinations of options to define the checbkox component @@ -56,6 +58,31 @@ public struct OUDSCheckbox: View { case dividedLabelAndHelperAndIcon(label: String, helper: String, icon: Image) } + // MARK: - State + + /// The internal state used by modifiers to handle all states of the component + enum State { + case enabled + case hover + case pressed + case disabled + case readOnly + // .loading not managed yet, for next version + // .focus not managed as not such customizable + // .skeleton not managed as dedicated view in the end + } + + // MARK: - Component + + /// The objects we can find inside this component + enum Item { + case checkbox + case label + case helper + case image + case divider + } + // MARK: - Status /// Represents the status of a checkbox i.e. a kind of type or status @@ -148,6 +175,10 @@ public struct OUDSCheckbox: View { self.layout = layout self.style = style self.action = action + + if label.isEmpty { + OL.warning("Label for OUDSCheckbox is empty, is it expected?") + } } /// Create a checkbox with a label and an icon @@ -172,6 +203,10 @@ public struct OUDSCheckbox: View { self.layout = layout self.style = style self.action = action + + if label.isEmpty { + OL.warning("Label for OUDSCheckbox is empty, is it expected?") + } } /// Create a checkbox with a label and an helper text @@ -198,6 +233,14 @@ public struct OUDSCheckbox: View { self.status = status self.style = style self.action = action + + if label.isEmpty { + OL.warning("Label for OUDSCheckbox is empty, is it expected?") + } + + if helper.isEmpty { + OL.warning("Helper for OUDSCheckbox is empty, is it expected?") + } } /// Create a checkbox with a label, an helper text and an icon @@ -226,16 +269,37 @@ public struct OUDSCheckbox: View { self.status = status self.style = style self.action = action + + if label.isEmpty { + OL.warning("Label for OUDSCheckbox is empty, is it expected?") + } + + if helper.isEmpty { + OL.warning("Helper for OUDSCheckbox is empty, is it expected?") + } } // MARK: - Body public var body: some View { + body(for: type) + .padding(theme.listItem.listItemSpaceInset) + } + + @ViewBuilder + private func body(for type: `Type`) -> some View { switch type { case .selectorOnly: - CheckboxSelector(status: status, action: action) - .modifier(OUDSCheckboxStyle(status: status)) - // TODO: #264 - Implment other cases + Checkbox(status: status, action: action) + case .label(let label): + Checkbox(label: label, status: status, action: action) + case let .labelAndHelper(label, helper): + Checkbox(label: label, helper: helper, status: status, action: action) + case let .labelAndHelperAndIcon(label: label, helper: helper, icon: icon): + Checkbox(label: label, helper: helper, icon: icon, status: status, action: action) + case let .labelAndIcon(label, icon): + Checkbox(label: label, icon: icon, status: status, action: action) + // TODO: #264 - Implement other cases default: Text("Not implemented yet") }