-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathOUDSCheckbox.swift
More file actions
201 lines (183 loc) · 8.59 KB
/
OUDSCheckbox.swift
File metadata and controls
201 lines (183 loc) · 8.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
//
// 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 OUDSTokensComponent
import SwiftUI
// MARK: - OUDS Checkbox
/// Checkbox is a UI element that allows to select multiple options from a set of mutually non exclusive choices.
/// Checkbox that does not show icon or text, provides greater flexibility when creating other components that require a checkbox to be displayed.
///
/// ## Indicator states
///
/// This checkbox indicator has two available states:
/// - **selected**: the checkbox is filled with a tick, the user has made the action to select the checkbox
/// - **unselected**: the checkbox is empty, does not contain a tick, the user has made the action to unselect or did not select yet the checkbox
///
/// If you need to use a tri-state checkbox instead, refer to ``OUDSCheckboxIndeterminate``.
///
/// ## Particular cases
///
/// An ``OUDSCheckbox`` can be related to an error situation, for example troubles for a formular.
/// A dedicated look-and-feel is implemented for that if the `isError` flag is risen.
///
/// ## Accessibility considerations
///
/// Note also the component must be instanciated with a string parameter used as accessibility label.
/// 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*.
/// The vocalization tool will also use, after the label, a description of the component (if disabled, if error context), and a fake trait for checkbox.
/// No accessibility identifier is defined in OUDS side as this value remains in the users hands.
///
/// ## Cases forbidden by design
///
/// **The design system does not allow to have both an error or a read only situation and a disabled component.**
///
/// ## Code samples
///
/// ```swift
/// // Supposing we have a selected checkbox
/// @Published var isOn: Bool = true
///
/// // A simple checkbox, no error, not in read only mode
/// OUDSCheckbox(isOn: $isOn, accessibilityLabel: "The cake is a lie")
///
/// // A simple checkbox, but is an error context
/// OUDSCheckbox(isOn: $isOn, accessibilityLabel: "The cake is a lie"), isError: true)
///
/// // Never disable an error-related checkbox as it will crash
/// // This is forbidden by design!
/// OUDSCheckbox(isOn: $isOn, accessibilityLabel: "The cake is a lie"), isError: true).disabled(true) // fatal error
/// ```
///
/// ## Suggestions
///
/// According to the [documentation](https://r.orange.fr/r/S-ouds-doc-checkbox), the checkbox by default must be used in unselected state.
///
/// ## Design documentation
///
/// [unified-design-system.orange.com](https://r.orange.fr/r/S-ouds-doc-checkbox)
///
/// ## Themes rendering
///
/// ### Orange
///
/// 
///
/// ### Orange Compact
///
/// 
///
/// ### Sosh
///
/// 
///
/// ### Wireframe
///
/// 
///
/// - Version: 2.4.0 (Figma component design version)
/// - Since: 0.12.0
@available(iOS 15, macOS 13, visionOS 1, watchOS 11, tvOS 16, *)
public struct OUDSCheckbox: View {
// MARK: Properties
private let accessibilityLabel: String
private let isError: Bool
private let isReadOnly: Bool
@Environment(\.isEnabled) private var isEnabled
@Environment(\.theme) private var theme
@Binding var isOn: Bool
// MARK: Initializers
/// Creates a checkbox with only an indicator.
///
/// **The design system does not allow to have both an error or read only situation and a disabled state for the component.**
///
/// - Parameters:
/// - isOn: A binding to a property that determines whether the indicator is ticked (selected) or not (not selected)
/// - key: The text to vocalize with *Voice Over* the component must have, as as `LocalizedStringKey` for the given `Bundle`
/// - tableName: The name of the `.strings` file, or `nil` for the default
/// - bundle: The bundle in which to look up the localized string. Defaults to `Bundle.main`.
/// - isError: True if the look and feel of the component must reflect an error state, default set to `false`
/// - isReadOnly: True if the look and feel of the component must reflect a read only state, default set to `false`
public init(isOn: Binding<Bool>,
accessibilityLabel key: LocalizedStringKey,
tableName: String? = nil,
bundle: Bundle = .main,
isError: Bool = false,
isReadOnly: Bool = false)
{
let resolvedText = key.resolved(tableName: tableName, bundle: bundle)
self.init(isOn: isOn, accessibilityLabel: resolvedText, isError: isError, isReadOnly: isReadOnly)
}
/// Creates a checkbox with only an indicator.
///
/// **The design system does not allow to have both an error or read only situation and a disabled state for the component.**
///
/// - Parameters:
/// - isOn: A binding to a property that determines whether the indicator is ticked (selected) or not (not selected)
/// - accessibilityLabel: The accessibility label the component must have
/// - isError: True if the look and feel of the component must reflect an error state, default set to `false`
/// - isReadOnly: True if the look and feel of the component must reflect a read only state, default set to `false`
public init(isOn: Binding<Bool>,
accessibilityLabel: String,
isError: Bool = false,
isReadOnly: Bool = false)
{
if accessibilityLabel.isEmpty {
OL.warning("The OUDSCheckbox should not have an empty accessibility label, think about your disabled users!")
}
_isOn = isOn
self.accessibilityLabel = accessibilityLabel
self.isError = isError
self.isReadOnly = isReadOnly
}
// MARK: Body
public var body: some View {
InteractionButton(isReadOnly: isReadOnly) {
$isOn.wrappedValue.toggle()
} content: { interactionState in
CheckboxIndicator(interactionState: interactionState, indicatorState: convertedState, isError: isError)
.frame(minWidth: theme.checkbox.sizeMinWidth,
maxWidth: theme.checkbox.sizeMinWidth,
minHeight: theme.checkbox.sizeMinHeight,
maxHeight: theme.checkbox.sizeMaxHeight)
.contentShape(Rectangle())
.modifier(CheckboxBackgroundColorModifier(interactionState: interactionState))
}
.accessibilityRemoveTraits([.isButton]) // .isToggle trait for iOS 17+
.accessibilityLabel(accessibilityLabel)
.accessibilityValue(accessibilityValue)
.accessibilityHint(accessibilityHint)
}
// MARK: Computed value
private var convertedState: OUDSCheckboxIndicatorState {
isOn ? .selected : .unselected
}
// MARK: - A11Y helpers
/// Forges a string to vocalize with *Voice Over* describing the component trait, value, state and error
private var accessibilityValue: String {
let traitDescription = "core_checkbox_trait_a11y".localized() // Fake trait for Voice Over vocalization
let valueDescription = isOn ? "core_checkbox_checked_a11y".localized() : "core_checkbox_unchecked_a11y".localized()
let stateDescription = !isEnabled || isReadOnly ? "core_common_disabled_a11y".localized() : ""
let errorDescription = isError ? "core_common_onError_a11y".localized() : ""
return "\(traitDescription). \(valueDescription). \(stateDescription). \(errorDescription)"
}
/// Forges a string to vocalize with *Voice Over* describing the component hint
private var accessibilityHint: String {
if !isEnabled || isReadOnly {
""
} else {
isOn
? "core_checkbox_hint_a11y" <- "core_checkbox_unchecked_a11y".localized()
: "core_checkbox_hint_a11y" <- "core_checkbox_checked_a11y".localized()
}
}
}