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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased](https://github.com/Orange-OpenSource/ouds-ios-design-system-toolbox/compare/0.17.0...develop)

### Changed

- [Library] Button version 3.0 (add brand hierarchy and rounded property, update minimal variant) (Orange-OpenSource/ouds-ios#887)

## [0.17.0](https://github.com/Orange-OpenSource/ouds-ios-design-system-toolbox/compare/0.16.0...0.17.0) - 2025-07-24

### Added
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ final class ButtonConfigurationModel: ComponentConfiguration {
didSet { updateCode() }
}

@Published var rounded: Bool {
didSet { updateCode() }
}

// MARK: Initializer

override init() {
Expand All @@ -47,13 +51,18 @@ final class ButtonConfigurationModel: ComponentConfiguration {
layout = .textOnly
hierarchy = .default
style = .default
rounded = false
}

deinit {}

// MARK: Component Configuration

private var disableCode: String {
private var roundedCodePattern: String {
rounded ? ".environment(\\.oudsRoundedButton, true)" : ""
}

private var disableCodePattern: String {
if case .default = style {
".disabled(\(enabled ? "false" : "true"))"
} else {
Expand All @@ -71,22 +80,22 @@ final class ButtonConfigurationModel: ComponentConfiguration {
code =
"""
OUDSButton(text: \"Button\", hierarchy: .\(hierarchy.description.lowercased()), style: .\(style.description.lowercased())) {}
\(disableCode)
\(coloredSurfaceCodeModifier)
\(disableCodePattern)
\(coloredSurfaceCodeModifier)\(roundedCodePattern)
"""
case .iconOnly:
code =
"""
OUDSButton(icon: Image(\"ic_heart\"), hierarchy: .\(hierarchy.description.lowercased()), style: .\(style.description.lowercased())) {}
\(disableCode)
\(coloredSurfaceCodeModifier)
\(disableCodePattern)
\(coloredSurfaceCodeModifier)\(roundedCodePattern)
"""
case .textAndIcon:
code =
"""
OUDSButton(icon: Image(\"ic_heart\", text: \"Button\"), hierarchy: .\(hierarchy.description.lowercased()), style: .\(style.description.lowercased())) {}
\(disableCode)
\(coloredSurfaceCodeModifier)
\(disableCodePattern)
\(coloredSurfaceCodeModifier)\(roundedCodePattern)
"""
}
}
Expand Down Expand Up @@ -134,7 +143,7 @@ extension OUDSButton.Style: @retroactive CaseIterable, @retroactive CustomString
// MARK: Button hierarchy extension

extension OUDSButton.Hierarchy: @retroactive CaseIterable, @retroactive CustomStringConvertible {
public nonisolated(unsafe) static let allCases: [OUDSButton.Hierarchy] = [.default, .strong, .minimal, .negative]
public nonisolated(unsafe) static let allCases: [OUDSButton.Hierarchy] = [.default, .strong, .brand, .minimal, .negative]

// Note: Not localized because it is a technical name
public var description: String {
Expand All @@ -143,6 +152,8 @@ extension OUDSButton.Hierarchy: @retroactive CaseIterable, @retroactive CustomSt
"Default"
case .strong:
"Strong"
case .brand:
"Brand"
case .minimal:
"Minimal"
case .negative:
Expand All @@ -168,6 +179,8 @@ struct ButtonConfigurationView: View {
.disabled(configurationModel.style != .default)

OUDSSwitchItem("app_components_common_onColoredSurface_label", isOn: $configurationModel.onColoredSurface)

OUDSSwitchItem("app_components_button_rounded_label", isOn: $configurationModel.rounded)
}

DesignToolboxChoicePicker(title: "app_components_button_hierarchy_label",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ private struct ButtonDemo: View {
HStack(alignment: .center) {
Spacer()

// It is not allowed to place a Negative button on colored surface
if configurationModel.hierarchy == .negative, configurationModel.onColoredSurface {
// It is not allowed to place a Negative or Brand button on colored surface
if configurationModel.onColoredSurface, configurationModel.hierarchy == .negative || configurationModel.hierarchy == .brand {
Text("app_components_button_negative_hierary_notAllowed_text")
} else {
switch configurationModel.layout {
Expand All @@ -56,15 +56,18 @@ private struct ButtonDemo: View {
accessibilityLabel: "app_components_button_icon_a11y".localized(),
hierarchy: configurationModel.hierarchy,
style: configurationModel.style) {}
.environment(\.oudsRoundedButton, configurationModel.rounded)
case .textOnly:
OUDSButton(text: configurationModel.text,
hierarchy: configurationModel.hierarchy,
style: configurationModel.style) {}
.environment(\.oudsRoundedButton, configurationModel.rounded)
case .textAndIcon:
OUDSButton(icon: Image(decorative: "ic_heart"),
text: configurationModel.text,
hierarchy: configurationModel.hierarchy,
style: configurationModel.style) {}
.environment(\.oudsRoundedButton, configurationModel.rounded)
}
}

Expand Down
25 changes: 25 additions & 0 deletions DesignToolbox/DesignToolbox/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -1323,6 +1323,31 @@
}
}
},
"app_components_button_rounded_label" : {
"comment" : "Component - Button",
"extractionState" : "manual",
"localizations" : {
"ar" : {
"stringUnit" : {
"state" : "translated",
"value" : "Rounded"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Rounded"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Rounded"
}
}
},
"shouldTranslate" : false
},
"app_components_checkbox_checkboxItem_label" : {
"comment" : "Component - Checkbox",
"extractionState" : "manual",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,33 @@ open class ButtonUITestsTestCase: XCTestCase {
@MainActor func testAllButtons(theme: OUDSTheme, interfaceStyle: UIUserInterfaceStyle) {
for hierarchy in OUDSButton.Hierarchy.allCases {
for layout in ButtonTest.Layout.allCases {
testButton(theme: theme, interfaceStyle: interfaceStyle, a11yContrast: .normal, layout: layout, hierarchy: hierarchy, disabled: false, onColoredSurface: false)
testButton(theme: theme, interfaceStyle: interfaceStyle, a11yContrast: .normal, layout: layout, hierarchy: hierarchy, disabled: true, onColoredSurface: false)
testButton(theme: theme, interfaceStyle: interfaceStyle, a11yContrast: .high, layout: layout, hierarchy: hierarchy, disabled: false, onColoredSurface: false)
testButton(theme: theme, interfaceStyle: interfaceStyle, a11yContrast: .high, layout: layout, hierarchy: hierarchy, disabled: true, onColoredSurface: false)
for rounded in [true, false] {
for disabled in [true, false] {
testButton(theme: theme,
interfaceStyle: interfaceStyle,
a11yContrast: .normal,
layout: layout,
hierarchy: hierarchy,
disabled: disabled,
rounded: rounded,
onColoredSurface: false)
testButton(theme: theme,
interfaceStyle: interfaceStyle,
a11yContrast: .high,
layout: layout,
hierarchy: hierarchy,
disabled: disabled,
rounded: rounded,
onColoredSurface: false)
}
}
}
}
}

/// This function tests all buttons configuration for the given themen and color schemes on aa colored surface (the `colorSurfaceBrandPrimary` token)
///
/// **/!\ It does not text the hover and pressed states.**
/// **/!\ It does not test the hover and pressed states.**
/// **The loading style is not tested yet as we face troubles with animations and snapshots.**
///
/// It iterates through all button `hierarchy`, for all `style` with* textOnly, textAndIcon and iconOnly layouts*
Expand All @@ -59,13 +75,29 @@ open class ButtonUITestsTestCase: XCTestCase {
/// - theme: The theme (`OUDSTheme) from which to retrieve color tokens.
/// - interfaceStyle: The user interface style (light or dark) for which to test the colors.
@MainActor func testAllButtonsOnColoredSurface(theme: OUDSTheme, interfaceStyle: UIUserInterfaceStyle) {
// Skip test for negative hierarchy because it is not allowed on colored surface
for hierarchy in OUDSButton.Hierarchy.allCases where hierarchy != .negative {
// Skip test for negative and brand hierarchy because it is not allowed on colored surface
for hierarchy in OUDSButton.Hierarchy.allCases where hierarchy != .negative && hierarchy != .brand {
for layout in ButtonTest.Layout.allCases {
testButton(theme: theme, interfaceStyle: interfaceStyle, a11yContrast: .normal, layout: layout, hierarchy: hierarchy, disabled: false, onColoredSurface: true)
testButton(theme: theme, interfaceStyle: interfaceStyle, a11yContrast: .normal, layout: layout, hierarchy: hierarchy, disabled: true, onColoredSurface: true)
testButton(theme: theme, interfaceStyle: interfaceStyle, a11yContrast: .high, layout: layout, hierarchy: hierarchy, disabled: false, onColoredSurface: true)
testButton(theme: theme, interfaceStyle: interfaceStyle, a11yContrast: .high, layout: layout, hierarchy: hierarchy, disabled: true, onColoredSurface: true)
for rounded in [true, false] {
for disabled in [true, false] {
testButton(theme: theme,
interfaceStyle: interfaceStyle,
a11yContrast: .normal,
layout: layout,
hierarchy: hierarchy,
disabled: disabled,
rounded: rounded,
onColoredSurface: true)
testButton(theme: theme,
interfaceStyle: interfaceStyle,
a11yContrast: .high,
layout: layout,
hierarchy: hierarchy,
disabled: disabled,
rounded: rounded,
onColoredSurface: true)
}
}
}
}
}
Expand All @@ -85,27 +117,31 @@ open class ButtonUITestsTestCase: XCTestCase {
/// - layout: the layout of the button
/// - hierarchy; the hierarchy of the button
/// - disabled: the disabled flag
/// - rounded: a flag to activate rounded behavior
/// - onColoredSurface: a flag to know if button is on a colored surface or not
@MainActor private func testButton(theme: OUDSTheme,
interfaceStyle: UIUserInterfaceStyle,
a11yContrast: UIAccessibilityContrast,
layout: ButtonTest.Layout,
hierarchy: OUDSButton.Hierarchy,
disabled: Bool,
rounded: Bool = false,
onColoredSurface: Bool = false)
{
// Generate the illustration for the specified configuration
let illustration = OUDSThemeableView(theme: theme) {
ButtonTest(layout: layout, hierarchy: hierarchy, style: .default, onColoredSurface: onColoredSurface)
.background(theme.colors.colorBgPrimary.color(for: interfaceStyle == .light ? .light : .dark))
.disabled(disabled)
.environment(\.oudsRoundedButton, rounded)
}

// Create a unique snapshot name based on the current configuration
let testName = "test_\(theme.name)Theme_\(interfaceStyle == .light ? "Light" : "Dark")_\(a11yContrast == .high ? "HighContrast" : "")"
let coloredSurfacePatern = onColoredSurface ? "ColoredSurface_" : ""
let disabledPatern = disabled ? "_Disabled" : ""
let name = "\(coloredSurfacePatern)\(layout.rawValue.camelCase)_\(hierarchy.description)_\(OUDSButton.Style.default.description)\(disabledPatern)"
let roundedPattern = rounded ? "_Rounded" : ""
let name = "\(coloredSurfacePatern)\(layout.rawValue.camelCase)_\(hierarchy.description)_\(OUDSButton.Style.default.description)\(disabledPatern)\(roundedPattern)"

// Capture the snapshot of the illustration with the correct user interface style and save it with the snapshot name
assertIllustration(illustration,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading