-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathOUDSCheckboxIndeterminate.swift
More file actions
186 lines (172 loc) · 8.34 KB
/
OUDSCheckboxIndeterminate.swift
File metadata and controls
186 lines (172 loc) · 8.34 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
//
// 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 Indeterminate
/// 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
///
/// The checkbox indicator has three 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
/// - **indeterminate**: like a prefilled or preticked checkbox, the user did not do anything on it yet
///
/// In you are looking for a checkbox with only two possible values, refer to ``OUDSCheckbox``.
///
/// ## Particular cases
///
/// An ``OUDSCheckboxIndeterminate`` 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.
///
/// ## Cases forbidden by design
///
/// **The design system does not allow to have both an error situation and a disabled component.**
///
/// ## Code samples
///
/// ```swift
/// // Supposing we have an indeterminate state checkbox
/// @Published var selection: OUDSCheckboxIndicatorState = .indeterminate
///
/// // A simple checkbox, no error, not in read only mode
/// OUDSCheckboxIndeterminate(selection: $selection, accessibilityLabel: "The cake is a lie")
///
/// // A simple checkbox, but is an error context
/// OUDSCheckboxIndeterminate(selection: $selection, accessibilityLabel: "The cake is a lie"), isError: true)
///
/// // Never disable an error-related checkbox as it will crash
/// // This is forbidden by design!
/// OUDSCheckboxIndeterminate(selection: $selection, 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 OUDSCheckboxIndeterminate: View {
// MARK: - Properties
private let accessibilityLabel: String
private let isError: Bool
private let isReadOnly: Bool
@Binding var selection: OUDSCheckboxIndicatorState
@Environment(\.isEnabled) private var isEnabled
@Environment(\.theme) private var theme
// MARK: - Initializers
/// Creates a checkbox with only an indicator.
///
/// **The design system does not allow to have both an error situation and a disabled state for the component.**
///
/// - Parameters:
/// - selection: A binding to a property that determines whether the indicator is ticked, unticked or preticked.
/// - 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(selection: Binding<OUDSCheckboxIndicatorState>,
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(selection: selection, accessibilityLabel: resolvedText, isError: isError, isReadOnly: isReadOnly)
}
/// Creates a checkbox with only an indicator.
///
/// **The design system does not allow to have both an error situation and a disabled state for the component.**
///
/// - Parameters:
/// - selection: A binding to a property that determines whether the indicator is ticked, unticked or preticked.
/// - 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(selection: Binding<OUDSCheckboxIndicatorState>,
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!")
}
_selection = selection
self.accessibilityLabel = accessibilityLabel
self.isError = isError
self.isReadOnly = isReadOnly
}
// MARK: Body
public var body: some View {
InteractionButton(isReadOnly: isReadOnly) {
$selection.wrappedValue.toggle()
} content: { interactionState in
CheckboxIndicator(interactionState: interactionState, indicatorState: $selection.wrappedValue, isError: isError)
.frame(minWidth: theme.checkbox.sizeMinWidth,
maxWidth: theme.checkbox.sizeMinWidth,
minHeight: theme.checkbox.sizeMinHeight,
maxHeight: theme.checkbox.sizeMaxHeight)
.modifier(CheckboxBackgroundColorModifier(interactionState: interactionState))
}
.accessibilityRemoveTraits([.isButton]) // .isToggle trait for iOS 17+
.accessibilityLabel(accessibilityLabel)
.accessibilityValue(accessibilityValue)
.accessibilityHint(accessibilityHint)
}
/// 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 = selection.a11yDescription
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 {
!isEnabled && isReadOnly ? "" : selection.a11yHint
}
}