Skip to content

Commit f9ea617

Browse files
authored
refactor: internal interaction state used for radio and checkbox components (#560) (#563)
Refactor and mutualize the interaction state for radio and checkbox components to reduce the number of lines of code. Reviewed-by: Pierre-Yves Lapersonne <[email protected]>
1 parent 35266b4 commit f9ea617

30 files changed

+531
-492
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313

1414
### Changed
1515

16+
- [Library] Rafactor the interal interation state used by radio and checkbox components ([#560](https://github.com/Orange-OpenSource/ouds-ios/issues/560))
1617
- [Library] Font family, font weight semantic tokens (tokens library v0.8.0) ([#529](https://github.com/Orange-OpenSource/ouds-ios/issues/529))
1718
- [Library] Switch component tokens (tokens library v0.8.0) ([#529](https://github.com/Orange-OpenSource/ouds-ios/issues/529))
1819
- [Library] Color semantic tokens (tokens library v0.8.0) ([#529](https://github.com/Orange-OpenSource/ouds-ios/issues/529))

DesignToolbox/DesignToolbox/Pages/Utils/Elements/DesignToolboxElementPage.swift

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ struct DesignToolboxElementPage: View {
4545

4646
Text(LocalizedStringKey(description))
4747
.typeBodyDefaultLarge(theme)
48+
.multilineTextAlignment(.leading)
49+
.frame(maxWidth: .infinity, alignment: .leading)
4850
.accessibilityFocused($requestFocus)
4951
.padding(.horizontal, theme.spaces.spaceFixedMedium)
5052
}

OUDS/Core/Components/Sources/Checkbox/Internal/CheckboxIndicator.swift

+15-21
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ import OUDSTokensSemantic
1717
import SwiftUI
1818

1919
/// The indicator of the checkbox.
20-
/// Its content depends to the ``ControlItemInternalState`` and the ``OUDSCheckboxIndicatorState`` also.
20+
/// Its content depends to the ``InteractionState`` and the ``OUDSCheckboxIndicatorState`` also.
2121
struct CheckboxIndicator: View {
2222

2323
// MARK: - Properties
2424

25-
let internalState: ControlItemInternalState
25+
let interactionState: InteractionState
2626
let indicatorState: OUDSCheckboxIndicatorState
2727
let isError: Bool
2828

@@ -32,8 +32,17 @@ struct CheckboxIndicator: View {
3232
// MARK: - Body
3333

3434
var body: some View {
35-
indicator()
36-
.modifier(CheckboxIndicatorStyle(state: internalState, indicatorState: indicatorState, isError: isError))
35+
ZStack {
36+
Color.clear
37+
.frame(minWidth: theme.checkbox.checkboxSizeMinWidth,
38+
maxWidth: theme.checkbox.checkboxSizeMinWidth,
39+
minHeight: theme.checkbox.checkboxSizeMinHeight,
40+
maxHeight: theme.checkbox.checkboxSizeMaxHeight)
41+
.contentShape(Rectangle())
42+
43+
indicator()
44+
.modifier(CheckboxIndicatorModifier(interactionState: interactionState, indicatorState: indicatorState, isError: isError))
45+
}
3746
}
3847

3948
// MARK: - Indicator
@@ -46,22 +55,20 @@ struct CheckboxIndicator: View {
4655
tickImage(name: "ic_form_dash")
4756
} else { // .unselected
4857
Color.clear
49-
.modifier(IndicatorFrameModifier())
5058
}
5159
}
5260

5361
private func tickImage(name: String) -> some View {
5462
Image(decorative: name, bundle: Bundle.OUDSComponents)
5563
.resizable()
5664
.scaledToFit()
57-
.modifier(IndicatorFrameModifier())
5865
.accessibilityHidden(true)
5966
.foregroundColor(appliedColor.color(for: colorScheme))
6067
}
6168

6269
private var appliedColor: MultipleColorSemanticTokens {
6370
if isError {
64-
switch internalState {
71+
switch interactionState {
6572
case .enabled:
6673
return theme.colors.colorActionNegativeEnabled
6774
case .hover:
@@ -73,7 +80,7 @@ struct CheckboxIndicator: View {
7380
+ " Only non-error situation are allowed to have a disabled state / read only mode.")
7481
}
7582
} else {
76-
switch internalState {
83+
switch interactionState {
7784
case .enabled:
7885
return theme.colors.colorActionSelected
7986
case .hover:
@@ -86,16 +93,3 @@ struct CheckboxIndicator: View {
8693
}
8794
}
8895
}
89-
90-
// MARK: - Indicator Frame Modifier
91-
92-
private struct IndicatorFrameModifier: ViewModifier {
93-
94-
@Environment(\.theme) private var theme
95-
96-
func body(content: Content) -> some View {
97-
content
98-
.frame(width: theme.checkbox.checkboxSizeIndicator,
99-
height: theme.checkbox.checkboxSizeIndicator)
100-
}
101-
}

OUDS/Core/Components/Sources/Checkbox/Internal/CheckboxSelectorStyle.swift renamed to OUDS/Core/Components/Sources/Checkbox/Internal/CheckboxIndicatorModifiers.swift

+28-14
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,27 @@ import OUDSFoundations
1616
import OUDSTokensSemantic
1717
import SwiftUI
1818

19-
// MARK: - Checkbox Indicator Style
19+
// MARK: - Checkbox Indicator Modifier
2020

2121
/// A `ViewModier` to apply to the ``CheckboxIndicator`` component.
22-
/// It will define the look and feel of the indicator depending to the ``ControlItemInternalState``,
22+
/// It will define the look and feel of the indicator depending to the ``InteractionState``,
2323
/// the ``OUDSCheckboxIndicatorState`` and if there is an error context or not.
24-
struct CheckboxIndicatorStyle: ViewModifier {
24+
struct CheckboxIndicatorModifier: ViewModifier {
2525

2626
// MARK: - Properties
2727

28-
let state: ControlItemInternalState
28+
let interactionState: InteractionState
2929
let indicatorState: OUDSCheckboxIndicatorState
3030
let isError: Bool
3131

3232
// MARK: - Body
3333

3434
func body(content: Content) -> some View {
3535
content
36-
.modifier(CheckboxIndicatorBorderModifier(state: state, indicatorState: indicatorState, isError: isError))
37-
.modifier(CheckboxIndicatorForegroundModifier(state: state, indicatorState: indicatorState, isError: isError))
38-
.modifier(CheckboxIndicatorBackgroundModifier(state: state, indicatorState: indicatorState, isError: isError))
36+
.modifier(SizeFrameModifier())
37+
.modifier(CheckboxIndicatorBorderModifier(interactionState: interactionState, indicatorState: indicatorState, isError: isError))
38+
.modifier(CheckboxIndicatorForegroundModifier(interactionState: interactionState, indicatorState: indicatorState, isError: isError))
39+
.modifier(CheckboxIndicatorBackgroundModifier(interactionState: interactionState, indicatorState: indicatorState, isError: isError))
3940
}
4041
}
4142

@@ -45,7 +46,7 @@ private struct CheckboxIndicatorForegroundModifier: ViewModifier {
4546

4647
// MARK: - Properties
4748

48-
let state: ControlItemInternalState
49+
let interactionState: InteractionState
4950
let indicatorState: OUDSCheckboxIndicatorState
5051
let isError: Bool
5152

@@ -62,7 +63,7 @@ private struct CheckboxIndicatorForegroundModifier: ViewModifier {
6263
// MARK: - Colors
6364

6465
private var appliedColor: Color {
65-
switch state {
66+
switch interactionState {
6667
case .enabled:
6768
return enabledColor.color(for: colorScheme)
6869
case .hover:
@@ -110,7 +111,7 @@ private struct CheckboxIndicatorBackgroundModifier: ViewModifier {
110111

111112
// MARK: - Properties
112113

113-
let state: ControlItemInternalState
114+
let interactionState: InteractionState
114115
let indicatorState: OUDSCheckboxIndicatorState
115116
let isError: Bool
116117

@@ -127,7 +128,7 @@ private struct CheckboxIndicatorBackgroundModifier: ViewModifier {
127128
// MARK: - Colors
128129

129130
private var appliedColor: Color {
130-
switch state {
131+
switch interactionState {
131132
case .enabled:
132133
return enabledColor
133134
case .hover:
@@ -166,7 +167,7 @@ private struct CheckboxIndicatorBorderModifier: ViewModifier {
166167

167168
// MARK: - Properties
168169

169-
let state: ControlItemInternalState
170+
let interactionState: InteractionState
170171
let indicatorState: OUDSCheckboxIndicatorState
171172
let isError: Bool
172173

@@ -186,7 +187,7 @@ private struct CheckboxIndicatorBorderModifier: ViewModifier {
186187
// MARK: - Colors
187188

188189
private var appliedColor: MultipleColorSemanticTokens {
189-
switch state {
190+
switch interactionState {
190191
case .enabled:
191192
return enabledColor
192193
case .hover:
@@ -238,7 +239,7 @@ private struct CheckboxIndicatorBorderModifier: ViewModifier {
238239
// MARK: - Border width
239240

240241
private var appliedBorderWidth: CGFloat {
241-
switch state {
242+
switch interactionState {
242243
case .enabled:
243244
return enabledWidth
244245
case .hover:
@@ -292,3 +293,16 @@ private struct CheckboxIndicatorBorderModifier: ViewModifier {
292293
theme.checkbox.checkboxBorderRadius
293294
}
294295
}
296+
297+
// MARK: - Size Frame Modifier
298+
299+
private struct SizeFrameModifier: ViewModifier {
300+
301+
@Environment(\.theme) private var theme
302+
303+
func body(content: Content) -> some View {
304+
content
305+
.frame(width: theme.checkbox.checkboxSizeIndicator,
306+
height: theme.checkbox.checkboxSizeIndicator)
307+
}
308+
}

OUDS/Core/Components/Sources/Checkbox/Internal/CheckboxOnlyButtonStyle.swift

-67
This file was deleted.

OUDS/Core/Components/Sources/Checkbox/OUDSCheckbox.swift

+9-7
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,6 @@ public struct OUDSCheckbox: View {
7272
private let a11yLabel: String
7373

7474
@Binding var isOn: Bool
75-
76-
private var convertedState: OUDSCheckboxIndicatorState {
77-
isOn ? .selected : .unselected
78-
}
79-
8075
@Environment(\.isEnabled) private var isEnabled
8176

8277
// MARK: - Initializers
@@ -103,14 +98,21 @@ public struct OUDSCheckbox: View {
10398
// MARK: Body
10499

105100
public var body: some View {
106-
Button("") {
101+
InteractionButton {
107102
$isOn.wrappedValue.toggle()
103+
} content: { interactionState in
104+
CheckboxIndicator(interactionState: interactionState, indicatorState: convertedState, isError: isError)
108105
}
109106
.accessibilityRemoveTraits([.isButton]) // .isToggle trait for iOS 17+
110107
.accessibilityLabel(a11yLabel(isDisabled: !isEnabled))
111108
.accessibilityValue(a11yValue())
112109
.accessibilityHint(a11yHint())
113-
.buttonStyle(CheckboxOnlyButtonStyle(indicatorState: convertedState, isError: isError))
110+
}
111+
112+
// MARK: - Computed value
113+
114+
private var convertedState: OUDSCheckboxIndicatorState {
115+
isOn ? .selected : .unselected
114116
}
115117

116118
// MARK: - A11Y helpers

OUDS/Core/Components/Sources/Checkbox/OUDSCheckboxIndeterminate.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,15 @@ public struct OUDSCheckboxIndeterminate: View {
9999
// MARK: Body
100100

101101
public var body: some View {
102-
Button("") {
102+
InteractionButton {
103103
$selection.wrappedValue.toggle()
104+
} content: { interactionState in
105+
CheckboxIndicator(interactionState: interactionState, indicatorState: $selection.wrappedValue, isError: isError)
104106
}
105107
.accessibilityRemoveTraits([.isButton]) // .isToggle trait for iOS 17+
106108
.accessibilityLabel(a11yLabel(isDisabled: !isEnabled))
107109
.accessibilityValue(selection.a11yDescription.localized())
108110
.accessibilityHint(selection.a11yHint)
109-
.buttonStyle(CheckboxOnlyButtonStyle(indicatorState: $selection.wrappedValue, isError: isError))
110111
}
111112

112113
/// Forges a string to vocalize with *Voice Over* describing the component state

OUDS/Core/Components/Sources/Checkbox/OUDSCheckboxItem.swift

+11-15
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,6 @@ public struct OUDSCheckboxItem: View {
110110
private let layoutData: ControlItemLabel.LayoutData
111111

112112
@Binding private var isOn: Bool
113-
114-
private var convertedState: Binding<OUDSCheckboxIndicatorState> {
115-
Binding(get: { isOn ? .selected : .unselected }, set: { isOn = ($0 == .selected ? true : false) })
116-
}
117-
118113
@Environment(\.isEnabled) private var isEnabled
119114

120115
// MARK: - Initializers
@@ -164,16 +159,17 @@ public struct OUDSCheckboxItem: View {
164159
// MARK: Body
165160

166161
public var body: some View {
167-
Button("") {
168-
if !layoutData.isReadOnly {
169-
$isOn.wrappedValue.toggle()
170-
}
171-
}
172-
.accessibilityRemoveTraits([.isButton]) // .isToggle trait for iOS 17+
173-
.accessibilityLabel(a11yLabel(layoutData: layoutData))
174-
.accessibilityValue(a11yValue())
175-
.accessibilityHint(a11yHint(isReadOnly: layoutData.isReadOnly, indicatorState: convertedState.wrappedValue))
176-
.buttonStyle(ControlItemStyle(indicatorType: .checkBox(convertedState), layoutData: layoutData))
162+
ControlItem(indicatorType: .checkBox(convertedState), layoutData: layoutData)
163+
.accessibilityRemoveTraits([.isButton]) // .isToggle trait for iOS 17+
164+
.accessibilityLabel(a11yLabel(layoutData: layoutData))
165+
.accessibilityValue(a11yValue())
166+
.accessibilityHint(a11yHint(isReadOnly: layoutData.isReadOnly, indicatorState: convertedState.wrappedValue))
167+
}
168+
169+
// MARK: - Computed value
170+
171+
private var convertedState: Binding<OUDSCheckboxIndicatorState> {
172+
Binding(get: { isOn ? .selected : .unselected }, set: { isOn = ($0 == .selected ? true : false) })
177173
}
178174

179175
// MARK: - A11Y helpers

0 commit comments

Comments
 (0)