Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

# OUDS iOS library changelog

All notable changes to this project will be documented in this file.
Expand All @@ -6,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased](https://github.com/Orange-OpenSource/ouds-ios/compare/0.22.0..develop)

### Changed

- Update `switch` components to v1.5.0 (Orange-OpenSource/ouds-ios#1138)
- Update `radio` components to v1.4.0 (Orange-OpenSource/ouds-ios#1139)
- Update `checkbox` components to v2.4.0 (Orange-OpenSource/ouds-ios#1137)
- Read only variant for `checkbox` and `checkbox indeterminate` components (Orange-OpenSource/ouds-ios#1137)

## [0.22.0](https://github.com/Orange-OpenSource/ouds-ios/compare/0.21.0...0.22.0) - 2025-11-28

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ struct CheckboxIndicator: View {
OL.fatal("An OUDS Checkbox with a disabled state / read only mode and an error situation has been detected, which is not allowed"
+ " Only non-error situation are allowed to have a disabled state / read only mode.")
}

// Not error case
} else {
switch interactionState {
Expand All @@ -89,7 +88,9 @@ struct CheckboxIndicator: View {
theme.colors.actionHover
case .pressed:
theme.colors.actionPressed
case .disabled, .readOnly:
case .readOnly:
theme.colors.actionReadOnlyPrimary
case .disabled:
theme.colors.actionDisabled
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ struct CheckboxIndicatorModifier: ViewModifier {
func body(content: Content) -> some View {
content
.modifier(SizeFrameModifier())
.modifier(CheckboxIndicatorBorderModifier(interactionState: interactionState, indicatorState: indicatorState, isError: isError))
.modifier(CheckboxIndicatorForegroundModifier(interactionState: interactionState, indicatorState: indicatorState, isError: isError))
.modifier(CheckboxIndicatorBackgroundModifier(interactionState: interactionState, isError: isError))
.modifier(CheckboxIndicatorBorderModifier(interactionState: interactionState, indicatorState: indicatorState, isError: isError))
}
}

Expand Down Expand Up @@ -70,7 +70,9 @@ private struct CheckboxIndicatorForegroundModifier: ViewModifier {
hoverColor
case .pressed:
pressedColor
case .disabled, .readOnly:
case .readOnly:
readOnlyColor
case .disabled:
disabledColor
}
}
Expand All @@ -96,6 +98,14 @@ private struct CheckboxIndicatorForegroundModifier: ViewModifier {
isError ? theme.colors.actionNegativePressed : theme.colors.actionPressed
}

private var readOnlyColor: MultipleColorSemanticTokens {
guard !isError else {
OL.fatal("An OUDS Checkbox with a read only state and an error situation has been detected, which is not allowed."
+ " Only non-error situation are allowed to have a disabled state.")
}
return theme.colors.actionReadOnlyPrimary
}

private var disabledColor: MultipleColorSemanticTokens {
guard !isError else {
OL.fatal("An OUDS Checkbox with a disabled state and an error situation has been detected, which is not allowed."
Expand Down Expand Up @@ -134,7 +144,9 @@ private struct CheckboxIndicatorBackgroundModifier: ViewModifier {
hoverColor
case .pressed:
pressedColor
case .disabled, .readOnly:
case .readOnly:
readOnlyColor
case .disabled:
disabledColor
}
}
Expand All @@ -151,6 +163,14 @@ private struct CheckboxIndicatorBackgroundModifier: ViewModifier {
theme.controlItem.colorBgPressed.color(for: colorScheme)
}

private var readOnlyColor: Color {
guard !isError else {
OL.fatal("An OUDS Checkbox with a read only state and an error situation has been detected, which is not allowed."
+ " Only non-error situation are allowed to have a disabled state.")
}
return Color.clear
}

private var disabledColor: Color {
guard !isError else {
OL.fatal("An OUDS Checkbox with a disabled state and an error situation has been detected, which is not allowed."
Expand All @@ -160,7 +180,7 @@ private struct CheckboxIndicatorBackgroundModifier: ViewModifier {
}
}

// MARK: - Checkbox IndicIndicatorator Border Modifier
// MARK: - Checkbox Indicator Border Modifier

private struct CheckboxIndicatorBorderModifier: ViewModifier {

Expand All @@ -178,6 +198,7 @@ private struct CheckboxIndicatorBorderModifier: ViewModifier {

func body(content: Content) -> some View {
content
.clipShape(RoundedRectangle(cornerRadius: appliedBorderRadius))
.oudsBorder(style: theme.borders.styleDefault,
width: appliedBorderWidth,
radius: appliedBorderRadius,
Expand All @@ -194,7 +215,9 @@ private struct CheckboxIndicatorBorderModifier: ViewModifier {
hoverColor
case .pressed:
pressedColor
case .disabled, .readOnly:
case .readOnly:
readOnlyColor
case .disabled:
disabledColor
}
}
Expand Down Expand Up @@ -232,6 +255,14 @@ private struct CheckboxIndicatorBorderModifier: ViewModifier {
}
}

private var readOnlyColor: MultipleColorSemanticTokens {
guard !isError else {
OL.fatal("An OUDS Checkbox with a read only state and an error situation has been detected, which is not allowed"
+ " Only non-error situation are allowed to have a disabled state.")
}
return theme.colors.actionReadOnlySecondary
}

private var disabledColor: MultipleColorSemanticTokens {
guard !isError else {
OL.fatal("An OUDS Checkbox with a disabled state and an error situation has been detected, which is not allowed"
Expand All @@ -250,7 +281,9 @@ private struct CheckboxIndicatorBorderModifier: ViewModifier {
hoverWidth
case .pressed:
pressedWidth
case .disabled, .readOnly:
case .readOnly:
readOnlyWidth
case .disabled:
disabledWidth
}
}
Expand Down Expand Up @@ -282,6 +315,15 @@ private struct CheckboxIndicatorBorderModifier: ViewModifier {
}
}

private var readOnlyWidth: CGFloat {
switch indicatorState {
case .selected, .indeterminate:
theme.checkbox.borderWidthSelected
case .unselected:
theme.checkbox.borderWidthUnselected
}
}

private var disabledWidth: CGFloat {
switch indicatorState {
case .selected, .indeterminate:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import SwiftUI
///
/// ## Cases forbidden by design
///
/// **The design system does not allow to have both an error situation and a disabled component.**
/// **The design system does not allow to have both an error or a read only situation and a disabled component.**
///
/// ## Code samples
///
Expand Down Expand Up @@ -87,15 +87,16 @@ import SwiftUI
///
/// ![A checkbox component in light and dark mode with Wireframe theme](component_checkbox_Wireframe)
///
/// - Version: 2.3.0 (Figma component design version)
/// - Version: 2.4.0 (Figma component design version)
/// - Since: 0.12.0
@available(iOS 15, macOS 15, visionOS 1, watchOS 11, tvOS 16, *)
public struct OUDSCheckbox: View {

// MARK: Properties

private let isError: Bool
private let a11yLabel: String
private let isError: Bool
private let isReadOnly: Bool

@Environment(\.isEnabled) private var isEnabled
@Environment(\.theme) private var theme
Expand All @@ -106,28 +107,31 @@ public struct OUDSCheckbox: View {

/// 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.**
/// **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 wether 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)
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.isError = isError
a11yLabel = accessibilityLabel
self.isError = isError
self.isReadOnly = isReadOnly
}

// MARK: Body

public var body: some View {
InteractionButton {
InteractionButton(isReadOnly: isReadOnly) {
$isOn.wrappedValue.toggle()
} content: { interactionState in
CheckboxIndicator(interactionState: interactionState, indicatorState: convertedState, isError: isError)
Expand Down Expand Up @@ -159,7 +163,7 @@ public struct OUDSCheckbox: View {

/// Forges a string to vocalize with *Voice Over* describing the component hint
private func a11yHint() -> String {
if !isEnabled {
if !isEnabled || isReadOnly {
""
} else {
isOn
Expand All @@ -171,7 +175,7 @@ public struct OUDSCheckbox: View {
/// Forges a string to vocalize with *Voice Over* describing the component state
/// - Parameter isDisabled: True if component is disabled, false otherwise
private func a11yLabel(isDisabled: Bool) -> String {
let stateDescription = isDisabled ? "core_common_disabled_a11y".localized() : ""
let stateDescription = isDisabled || isReadOnly ? "core_common_disabled_a11y".localized() : ""
let errorDescription = isError ? "core_common_onError_a11y".localized() : ""
let checkboxA11yTrait = "core_checkbox_trait_a11y".localized() // Fake trait for Voice Over vocalization

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,16 @@ import SwiftUI
///
/// ![A checkbox component in light and dark mode with Wireframe theme](component_checkbox_Wireframe)
///
/// - Version: 2.3.0 (Figma component design version)
/// - Version: 2.4.0 (Figma component design version)
/// - Since: 0.12.0
@available(iOS 15, macOS 15, visionOS 1, watchOS 11, tvOS 16, *)
public struct OUDSCheckboxIndeterminate: View {

// MARK: - Properties

private let isError: Bool
private let a11yLabel: String
private let isError: Bool
private let isReadOnly: Bool

@Binding var selection: OUDSCheckboxIndicatorState
@Environment(\.isEnabled) private var isEnabled
Expand All @@ -112,22 +113,25 @@ public struct OUDSCheckboxIndeterminate: View {
/// - selection: A binding to a property that determines wether 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)
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.isError = isError
a11yLabel = accessibilityLabel
self.isError = isError
self.isReadOnly = isReadOnly
}

// MARK: Body

public var body: some View {
InteractionButton {
InteractionButton(isReadOnly: isReadOnly) {
$selection.wrappedValue.toggle()
} content: { interactionState in
CheckboxIndicator(interactionState: interactionState, indicatorState: $selection.wrappedValue, isError: isError)
Expand All @@ -140,13 +144,13 @@ public struct OUDSCheckboxIndeterminate: View {
.accessibilityRemoveTraits([.isButton]) // .isToggle trait for iOS 17+
.accessibilityLabel(a11yLabel(isDisabled: !isEnabled))
.accessibilityValue(selection.a11yDescription.localized())
.accessibilityHint(selection.a11yHint)
.accessibilityHint(isEnabled && !isReadOnly ? selection.a11yHint : "")
}

/// Forges a string to vocalize with *Voice Over* describing the component state
/// - Parameter isDisabled: True if component is disabled, false otherwise
private func a11yLabel(isDisabled: Bool) -> String {
let stateDescription = isDisabled ? "core_common_disabled_a11y".localized() : ""
let stateDescription = isDisabled || isReadOnly ? "core_common_disabled_a11y".localized() : ""
let errorDescription = isError ? "core_common_onError_a11y".localized() : ""
let checkboxA11yTrait = "core_checkbox_trait_a11y".localized() // Fake trait for Voice Over vocalization

Expand Down
Loading