Skip to content

Commit 2cba7d7

Browse files
committed
feat: add read only variant for radio only component (#1139)
Signed-off-by: Pierre-Yves Lapersonne <[email protected]>
1 parent b8a1c62 commit 2cba7d7

File tree

14 files changed

+126
-87
lines changed

14 files changed

+126
-87
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import SwiftUI
4242
///
4343
/// ## Cases forbidden by design
4444
///
45-
/// **The design system does not allow to have both an error situation and a disabled component.**
45+
/// **The design system does not allow to have both an error or a read-only situation and a disabled component.**
4646
///
4747
/// ## Code samples
4848
///

OUDS/Core/Components/Sources/Controls/Radio/Internal/RadioIndicatorModifiers.swift

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ private struct RadioIndicatorForegroundModifier: ViewModifier {
7171
hoverColor
7272
case .pressed:
7373
pressedColor
74-
case .disabled, .readOnly:
74+
case .readOnly:
75+
readOnlyColor
76+
case .disabled:
7577
disabledColor
7678
}
7779
}
@@ -96,6 +98,14 @@ private struct RadioIndicatorForegroundModifier: ViewModifier {
9698
isError ? theme.colors.actionNegativePressed : theme.colors.actionPressed
9799
}
98100

101+
private var readOnlyColor: MultipleColorSemanticTokens {
102+
guard !isError else {
103+
OL.fatal("An OUDSRadio with a read only state and an error situation has been detected, which is not allowed."
104+
+ " Only non-error situation are allowed to have a read-only state.")
105+
}
106+
return theme.colors.actionReadOnlyPrimary
107+
}
108+
99109
private var disabledColor: MultipleColorSemanticTokens {
100110
guard !isError else {
101111
OL.fatal("An OUDSRadio with a disabled state and an error situation has been detected, which is not allowed."
@@ -191,7 +201,9 @@ private struct RadioIndicatorBorderModifier: ViewModifier {
191201
hoverColor
192202
case .pressed:
193203
pressedColor
194-
case .disabled, .readOnly:
204+
case .readOnly:
205+
readOnlyColor
206+
case .disabled:
195207
disabledColor
196208
}
197209
}
@@ -224,6 +236,14 @@ private struct RadioIndicatorBorderModifier: ViewModifier {
224236
}
225237
}
226238

239+
private var readOnlyColor: MultipleColorSemanticTokens {
240+
guard !isError else {
241+
OL.fatal("An OUDSRadio with a read-only state and an error situation has been detected, which is not allowed"
242+
+ " Only non-error situation are allowed to have a disabled state.")
243+
}
244+
return theme.colors.actionReadOnlySecondary
245+
}
246+
227247
private var disabledColor: MultipleColorSemanticTokens {
228248
guard !isError else {
229249
OL.fatal("An OUDSRadio with a disabled state and an error situation has been detected, which is not allowed"

OUDS/Core/Components/Sources/Controls/Radio/OUDSRadio.swift

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import SwiftUI
3434
///
3535
/// ## Cases forbidden by design
3636
///
37-
/// **The design system does not allow to have both an error situation and a disabled component.**
37+
/// **The design system does not allow to have both an error or read-only situation and a disabled component.**
3838
///
3939
/// ## Code samples
4040
///
@@ -75,15 +75,16 @@ import SwiftUI
7575
///
7676
/// ![A radio button component in light and dark mode with Wireframe theme](component_radio_Wireframe)
7777
///
78-
/// - Version: 1.3.0 (Figma component design version)
78+
/// - Version: 1.4.0 (Figma component design version)
7979
/// - Since: 0.12.0
8080
@available(iOS 15, macOS 15, visionOS 1, watchOS 11, tvOS 16, *)
8181
public struct OUDSRadio: View {
8282

8383
// MARK: - Properties
8484

85-
private let isError: Bool
8685
private let accessibilityLabel: String
86+
private let isError: Bool
87+
private let isReadOnly: Bool
8788

8889
@Binding var isOn: Bool
8990
@Environment(\.isEnabled) private var isEnabled
@@ -93,28 +94,31 @@ public struct OUDSRadio: View {
9394

9495
/// Creates a radio with only an indicator.
9596
///
96-
/// **The design system does not allow to have both an error situation and a disabled state for the component.**
97+
/// **The design system does not allow to have both an error or a read-only situation and a disabled state for the component.**
9798
///
9899
/// - Parameters:
99100
/// - isOn: A binding to a property that determines whether the toggle is on or off.
100101
/// - accessibilityLabel: The accessibility label the component must have
101102
/// - isError: True if the look and feel of the component must reflect an error state, default set to `false`
103+
/// - isReadOnly: True iif the component should be in read only mode, default set to `false`
102104
public init(isOn: Binding<Bool>,
103105
accessibilityLabel: String,
104-
isError: Bool = false)
106+
isError: Bool = false,
107+
isReadOnly: Bool = false)
105108
{
106109
if accessibilityLabel.isEmpty {
107110
OL.warning("The OUDSRadio should not have an empty accessibility label, think about your disabled users!")
108111
}
109112
_isOn = isOn
110113
self.accessibilityLabel = accessibilityLabel.localized()
111114
self.isError = isError
115+
self.isReadOnly = isReadOnly
112116
}
113117

114118
// MARK: Body
115119

116120
public var body: some View {
117-
InteractionButton {
121+
InteractionButton(isReadOnly: isReadOnly) {
118122
$isOn.wrappedValue.toggle()
119123
} content: { interactionState in
120124
RadioIndicator(interactionState: interactionState, isOn: isOn, isError: isError)
@@ -131,7 +135,7 @@ public struct OUDSRadio: View {
131135

132136
/// Forges a string to vocalize with *Voice Over* describing the component state
133137
private var a11yLabel: String {
134-
let stateDescription = isEnabled ? "" : "core_common_disabled_a11y".localized()
138+
let stateDescription = !isEnabled || isReadOnly ? "core_common_disabled_a11y".localized() : ""
135139
let errorDescription = isError ? "core_common_onError_a11y".localized() : ""
136140
let radioA11yTrait = "core_radio_trait_a11y".localized() // Fake trait for Voice Over vocalization
137141

@@ -146,7 +150,7 @@ public struct OUDSRadio: View {
146150

147151
/// The text to vocalize with *Voice Over* to explain to the user to which state the component will move when tapped
148152
private var a11yHint: String {
149-
if !isEnabled {
153+
if !isEnabled || isReadOnly {
150154
""
151155
} else {
152156
_isOn.wrappedValue

OUDS/Core/Components/Sources/Controls/Radio/OUDSRadioItem.swift

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import SwiftUI
2323
///
2424
/// The component can be rendered as two different layouts:
2525
///
26-
/// - **default**: the component has a leading indicator, a label and optional helper texts, and an optional trailing decorative icon
26+
/// - **default**: the component has a leading indicator, a label and optional texts, and an optional trailing decorative icon
2727
/// - **reversed**: like the *default* layout but with a trailing radio indicator and a leading optional decorative icon
2828
///
2929
/// ## Indicator states
@@ -50,7 +50,7 @@ import SwiftUI
5050
///
5151
/// ## Accessibility considerations
5252
///
53-
/// *Voice Over* will use several elements to describe the component: if component disabled / read only, if error context, the label and helper texts and a custom radio trait.
53+
/// *Voice Over* will use several elements to describe the component: if component disabled / read only, if error context, the label and optional texts and a custom radio trait.
5454
/// No accessibility identifier is defined in OUDS side as this value remains in the users hands.
5555
///
5656
/// ## Forbidden by design
@@ -81,13 +81,13 @@ import SwiftUI
8181
///
8282
/// // A leading radio with an additional label.
8383
/// // The default layout will be used here.
84-
/// OUDSRadioItem(isOn: $selection, label: "Lucy in the Sky with Diamonds", additionalLabel: "The Beatles", helper: "1967")
84+
/// OUDSRadioItem(isOn: $selection, label: "Lucy in the Sky with Diamonds", additionalLabel: "The Beatles", description: "1967")
8585
///
86-
/// // A trailing radio with a label, an helper text, an icon, a divider and is about an error.
86+
/// // A trailing radio with a label, a description, an icon, a divider and is about an error.
8787
/// // The reversed layout will be used here.
8888
/// OUDSRadioItem(isOn: $selection,
8989
/// label: "Rescue from this world!",
90-
/// helper: "Put your hand in mine",
90+
/// description: "Put your hand in mine",
9191
/// icon: Image(decorative: "ic_heart"),
9292
/// isReversed: true,
9393
/// isError: true,
@@ -144,7 +144,7 @@ import SwiftUI
144144
///
145145
/// ![A radio item component in light and dark mode with Wireframe theme](component_radioItem_Wireframe)
146146
///
147-
/// - Version: 1.3.0 (Figma component design version)
147+
/// - Version: 1.4.0 (Figma component design version)
148148
/// - Since: 0.12.0
149149
@available(iOS 15, macOS 15, visionOS 1, watchOS 11, tvOS 16, *)
150150
public struct OUDSRadioItem: View {
@@ -160,14 +160,14 @@ public struct OUDSRadioItem: View {
160160

161161
// MARK: - Initializer
162162

163-
/// Creates a radio with label and optional helper text, icon, divider.
163+
/// Creates a radio with label and optional helper text as description, icon, divider.
164164
/// Supposed to be integrated inside a ``OUDSRadioPicker``.
165165
///
166166
/// ```swift
167167
/// OUDSRadioItem(isOn: $selection,
168168
/// label: "Virgin Holy Lava",
169169
/// additionalLabel: "Very spicy",
170-
/// helper: "No alcohol, only tasty flavors",
170+
/// description: "No alcohol, only tasty flavors",
171171
/// icon: Image(systemName: "flame")
172172
/// ```
173173
///
@@ -177,7 +177,7 @@ public struct OUDSRadioItem: View {
177177
/// - isOn: A binding to a property that determines whether the toggle is on or off.
178178
/// - label: The main label text of the radio.
179179
/// - additionalLabel: An additional label text of the radio, default set to `nil`
180-
/// - helper: An additonal helper text, should not be empty, default set to `nil`
180+
/// - description: An description, like an helper text, should not be empty, default set to `nil`
181181
/// - icon: An optional icon, default set to `nil`
182182
/// - flipIcon: Default set to `false`, set to true to reverse the image (i.e. flip vertically)
183183
/// - isOutlined: Flag to get an outlined radio, default set to `false`
@@ -191,12 +191,12 @@ public struct OUDSRadioItem: View {
191191
///
192192
/// **Remark 1: As divider and outline effect are not supposed to be displayed at the same time, the divider is not displayed if the outline effect is active.**
193193
///
194-
/// **Remark 2: If `label` and `helper` strings are wording keys from strings catalog stored in `Bundle.main`, they are automatically localized. Else, prefer to
194+
/// **Remark 2: If `label` and `description` strings are wording keys from strings catalog stored in `Bundle.main`, they are automatically localized. Else, prefer to
195195
/// provide the localized string if key is stored in another bundle.**
196196
public init(isOn: Binding<Bool>,
197197
label: String,
198198
additionalLabel: String? = nil,
199-
helper: String? = nil,
199+
description: String? = nil,
200200
icon: Image? = nil,
201201
flipIcon: Bool = false,
202202
isOutlined: Bool = false,
@@ -211,8 +211,8 @@ public struct OUDSRadioItem: View {
211211
OL.fatal("It is forbidden by design to have an OUDSRadioItem in an error context and in read only mode")
212212
}
213213

214-
if let helper, helper.isEmpty {
215-
OL.warning("Helper text given to an OUDSRadioItem is defined but empty, is it expected? Prefer use of `nil` value instead")
214+
if let description, description.isEmpty {
215+
OL.warning("Description text given to an OUDSRadioItem is defined but empty, is it expected? Prefer use of `nil` value instead")
216216
}
217217

218218
if let additionalLabel, additionalLabel.isEmpty {
@@ -229,7 +229,7 @@ public struct OUDSRadioItem: View {
229229
layoutData = .init(
230230
label: label.localized(),
231231
additionalLabel: additionalLabel?.localized(),
232-
helper: helper?.localized(),
232+
description: description?.localized(),
233233
icon: icon,
234234
flipIcon: flipIcon,
235235
isOutlined: isOutlined,
@@ -264,7 +264,7 @@ public struct OUDSRadioItem: View {
264264
let errorDescription = "\(errorPrefix), \(errorText)"
265265
let radioA11yTrait = "core_radio_trait_a11y".localized() // Fake trait for Voice Over vocalization
266266

267-
let result = "\(stateDescription), \(layoutData.label), \(layoutData.additionalLabel ?? ""), \(layoutData.helper ?? "") \(errorDescription), \(radioA11yTrait)"
267+
let result = "\(stateDescription), \(layoutData.label), \(layoutData.additionalLabel ?? ""), \(layoutData.description ?? "") \(errorDescription), \(radioA11yTrait)"
268268
return result
269269
}
270270

OUDS/Core/Components/Sources/Controls/RadioPicker/OUDSRadioPicker.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,12 @@ import SwiftUI
4444
/// OUDSRadioPickerData<String>(tag: "Choice_1",
4545
/// label: "Virgin Holy Lava",
4646
/// additionalLabel: "Very spicy",
47-
/// helper: "No alcohol, only tasty flavors",
47+
/// description: "No alcohol, only tasty flavors",
4848
/// icon: Image(systemName: "flame")),
4949
///
5050
/// OUDSRadioPickerData<String>(tag: "Choice_2",
5151
/// label: "IPA beer",
52-
/// helper: "From Brewdog company",
52+
/// description: "From Brewdog company",
5353
/// icon: Image(systemName: "dog.fill")),
5454
///
5555
/// OUDSRadioPickerData<String>(tag: "Choice_3",
@@ -201,7 +201,7 @@ public struct OUDSRadioPicker<Tag>: View where Tag: Hashable {
201201
OUDSRadioItem(isOn: selection.wrappedValue == radio.tag ? .constant(true) : .constant(false),
202202
label: radio.label,
203203
additionalLabel: radio.additionalLabel,
204-
helper: radio.helper,
204+
description: radio.description,
205205
icon: radio.icon,
206206
isOutlined: isOutlined ? true : radio.isOutlined,
207207
isReversed: isReversed ? true : radio.isReversed,

OUDS/Core/Components/Sources/Controls/RadioPicker/OUDSRadioPickerData.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ public struct OUDSRadioPickerData<Tag> where Tag: Hashable {
3030
/// An optional additional label the ``OUDSRadioItem`` can have
3131
let additionalLabel: String?
3232

33-
/// An optional helper text the ``OUDSRadioItem`` can have
34-
let helper: String?
33+
/// A description the ``OUDSRadioItem`` can have
34+
let description: String?
3535

3636
/// An optional image the ``OUDSRadioItem`` can have
3737
let icon: Image?
@@ -61,7 +61,7 @@ public struct OUDSRadioPickerData<Tag> where Tag: Hashable {
6161
/// - tag: a value to discriminate one radio to another
6262
/// - label: the mandatory text to add to ``OUDSRadioItem``
6363
/// - additionalLabel: An optional additinal text, default set to nil
64-
/// - helper: Another optional text, default set to nil
64+
/// - description: Another optional text, a description, default set to nil
6565
/// - icon: An optional image, default set to nil
6666
/// - isOutlined: True to outline the ``OUDSRadioItem``, false otherwise (default)
6767
/// - isReversed: True to use to reversed layour of the ``OUDSRadioItem``, false otherwise (default)
@@ -75,7 +75,7 @@ public struct OUDSRadioPickerData<Tag> where Tag: Hashable {
7575
public init(tag: Tag,
7676
label: String,
7777
additionalLabel: String? = nil,
78-
helper: String? = nil,
78+
description: String? = nil,
7979
icon: Image? = nil,
8080
isOutlined: Bool = false,
8181
isReversed: Bool = false,
@@ -87,7 +87,7 @@ public struct OUDSRadioPickerData<Tag> where Tag: Hashable {
8787
self.tag = tag
8888
self.label = label
8989
self.additionalLabel = additionalLabel
90-
self.helper = helper
90+
self.description = description
9191
self.icon = icon
9292
self.isOutlined = isOutlined
9393
self.isReversed = isReversed

OUDS/Core/Components/Sources/Controls/Switch/OUDSSwitch.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ import SwiftUI
2424
/// It is a good pratice (at least) to define a label for a component without text for accessibility reasons. This label will be vocalized by *Voice Over*.
2525
/// The vocalization tool will also use, after the label, a description of the component (if disabled, if error context), and a fake trait for switch.
2626
///
27+
/// ## Cases forbidden by design
28+
///
29+
/// **The design system does not allow to have both a read-only situation and a disabled component.**
30+
///
2731
/// ## Code samples
2832
///
2933
/// ```swift
@@ -75,6 +79,8 @@ public struct OUDSSwitch: View {
7579

7680
/// Creates a switch with only an indicator.
7781
///
82+
/// **The design system does not allow to have both a read only situation and a disabled state for the component.**
83+
///
7884
/// - Parameters:
7985
/// - isOn: A binding to a property that determines whether the toggle is on or off.
8086
/// - accessibilityLabel: The accessibility label the component must have

OUDS/Core/Components/Sources/Controls/Switch/OUDSSwitchItem.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,13 @@ public struct OUDSSwitchItem: View {
230230
/// Forges a string to vocalize with *Voice Over* describing the component state.
231231
private var a11yLabel: String {
232232
let stateDescription: String = layoutData.isReadOnly || !isEnabled ? "core_common_disabled_a11y".localized() : ""
233-
let errorPrefix = layoutData.isError ? "core_common_onError_a11y".localized() : ""
234-
let errorText = layoutData.errorText?.localized() ?? ""
235-
let errorDescription = "\(errorPrefix), \(errorText)"
233+
234+
var errorDescription = ""
235+
if layoutData.isError {
236+
let errorPrefix = "core_common_onError_a11y".localized()
237+
let errorText = layoutData.errorText?.localized() ?? ""
238+
errorDescription = "\(errorPrefix), \(errorText)"
239+
}
236240

237241
let switchA11yTrait = "core_switch_trait_a11y".localized() // Fake trait for Voice Over vocalization
238242

0 commit comments

Comments
 (0)