diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c6bebdb2d2..92d19621dbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,16 +8,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `LocalizedStringKey` and `Bundle` initializers for components using `String` for texts and accessibility labels (Orange-OpenSource/ouds-ios#1366) - `oudsTintColor` view modifier helper to apply tint color from a `MultipleColorSemanticToken` (Orange-OpenSource/ouds-ios#1370) - `verbose` flag on `OUDSLogger` to suppress debug and log messages by default (Orange-OpenSource/ouds-ios#1365) ### Changed +- Update illustrations in documentation for `alert message` component (Orange-OpenSource/ouds-ios#1359) - View modifiers and methods prefixed by `ouds` are replaced by same names without such `ouds` (Orange-OpenSource/ouds-ios#1346) - Move from Xcode 26.2 to Xcode 26.3 (Orange-OpenSource/ouds-ios#1375) -### Changed - -- Update illustrations in documentation for `alert message` component (Orange-OpenSource/ouds-ios#1359) ## [1.3.0](https://github.com/Orange-OpenSource/ouds-ios/compare/1.2.0...1.3.0) - 2026-03-26 diff --git a/OUDS/Core/Components/Sources/Actions/Button/OUDSButton.swift b/OUDS/Core/Components/Sources/Actions/Button/OUDSButton.swift index 4eea5fabbb5..d18249ad073 100644 --- a/OUDS/Core/Components/Sources/Actions/Button/OUDSButton.swift +++ b/OUDS/Core/Components/Sources/Actions/Button/OUDSButton.swift @@ -59,6 +59,9 @@ import SwiftUI /// /// // Text and icon with strong appearance and button taking full width /// OUDSButton(text: "Validate", icon: Image("ic_heart"), appearance: .strong, isFullWidth: true) { /* the action to process */ } +/// +/// // Localizable from bundle can also be used +/// OUDSButton(LocalizedStringKey("validate_button"), bundle: Bundle.module, appearance: .strong) { } /// ``` /// /// If you need to flip your icon depending to the layout direction or not (e.g. if RTL mode lose semantics / meanings): @@ -173,6 +176,41 @@ public struct OUDSButton: View { // MARK: Initializers + // swiftlint:disable function_default_parameter_at_end + /// Creates a button with a localized text and icon, looking up the key in the given bundle.. + /// A raw string can also be given to be displayed. + /// + /// ```swift + /// // Use localizable + /// OUDSButton(LocalizedStringKey("validate_button"), bundle: Bundle.module, icon: Image("ic_checkmark"), appearance: .strong) { } + /// ``` + /// + /// - Parameters: + /// - key: A `LocalizedStringKey` used to look up the text in the given bundle, or a raw `String` to display + /// - 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`. + /// - icon: An image which shoud contains an icon + /// - flipIcon: Default set to `false`, set to `true` to reverse the image (i.e. flip vertically) + /// - appearance: The button appearance, default set to `.default` + /// - style: The button style, default set to `.default` + /// - isFullWidth: Flag to let button take all the screen width, set to *false* by default. + /// - action: The action to perform when the user triggers the button + public init(_ key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + icon: Image, + flipIcon: Bool = false, + appearance: Appearance = .default, + style: Style = .default, + isFullWidth: Bool = false, + action: @escaping () -> Void) + { + let resolvedText = key.resolved(tableName: tableName, bundle: bundle) + self.init(text: resolvedText, icon: icon, flipIcon: flipIcon, style: style, isFullWidth: isFullWidth, action: action) + } + + // swiftlint:enable function_default_parameter_at_end + /// Creates a button with text and icon. /// /// - Parameters: @@ -196,10 +234,35 @@ public struct OUDSButton: View { self.style = style self.isFullWidth = isFullWidth self.action = action - isHover = false } + /// Creates a button with an icon only. + /// + /// - Parameters: + /// - icon: An image which shoud contains an icon + /// - key: The text to vocalize with *Voice Over* describing the button action, 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`. + /// - flipIcon: Default set to `false`, set to `true` to reverse the image (i.e. flip vertically) + /// - appearance: The button appearance, default set to `.default` + /// - style: The button style, default set to `.default` + /// - isFullWidth: Flag to let button take all the screen width, set to *false* by default. + /// - action: The action to perform when the user triggers the button + public init(icon: Image, + accessibilityLabel key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + flipIcon: Bool = false, + appearance: Appearance = .default, + style: Style = .default, + isFullWidth: Bool = false, + action: @escaping () -> Void) + { + let resolvedText = key.resolved(tableName: tableName, bundle: bundle) + self.init(icon: icon, accessibilityLabel: resolvedText, flipIcon: flipIcon, appearance: appearance, style: style, isFullWidth: isFullWidth, action: action) + } + /// Creates a button with an icon only. /// /// - Parameters: @@ -226,6 +289,32 @@ public struct OUDSButton: View { isHover = false } + /// Creates a button with a localized text only, looking up the key in the given bundle. + /// + /// ```swift + /// OUDSButton(LocalizedStringKey("delete_button"), bundle: Bundle.module, appearance: .negative) { } + /// ``` + /// + /// - Parameters: + /// - key: A `LocalizedStringKey` used to look up the text in 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`. + /// - appearance: The button appearance, default set to `.default` + /// - style: The button style, default set to `.default` + /// - isFullWidth: Flag to let button take all the screen width, set to *false* by default. + /// - action: The action to perform when the user triggers the button + public init(_ key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + appearance: Appearance = .default, + style: Style = .default, + isFullWidth: Bool = false, + action: @escaping () -> Void) + { + let resolvedText = key.resolved(tableName: tableName, bundle: bundle) + self.init(text: resolvedText, appearance: appearance, style: style, isFullWidth: isFullWidth, action: action) + } + /// Create a button with a text only. /// /// - Parameters: diff --git a/OUDS/Core/Components/Sources/ContentDisplay/BulletList/OUDSBulletList.swift b/OUDS/Core/Components/Sources/ContentDisplay/BulletList/OUDSBulletList.swift index a5625eee331..b472848d74d 100644 --- a/OUDS/Core/Components/Sources/ContentDisplay/BulletList/OUDSBulletList.swift +++ b/OUDS/Core/Components/Sources/ContentDisplay/BulletList/OUDSBulletList.swift @@ -195,6 +195,38 @@ public struct OUDSBulletList: View { self.subListHasBoldText = subListHasBoldText self.subItems = subItems() } + + /// Creates a bullet list item with a localized text, looking up the key in the given bundle. + /// + /// ```swift + /// OUDSBulletList.Item(LocalizedStringKey("item_label"), bundle: Bundle.module) + /// ``` + /// + /// - Parameters: + /// - key: A `LocalizedStringKey` used to look up the text in 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`. + /// - subListType: The specific `OUDSBulletList.Type` for the nested sub-list, if any. If `nil`, + /// the type is inherited from the parent list. + /// - subListTextStyle: The specific `OUDSBulletList.TextStyle` for the nested sub-list, if any. If + /// `nil`, the text style is inherited from the parent list. + /// - subListHasBoldText: Whether the text of the nested sub-list should be bold. If `nil`, the bold + /// setting is inherited from the parent list. + /// - subItems: The sub items builder to add to the current item. **Remark** only three levels are allowed. + public init(_ key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + subListType: OUDSBulletList.`Type`? = nil, + subListTextStyle: OUDSBulletList.TextStyle? = nil, + subListHasBoldText: Bool? = nil, + @OUDSBulletListItemBuilder subItems: () -> [OUDSBulletList.Item] = { [] }) + { + text = key.resolved(tableName: tableName, bundle: bundle) + self.subListType = subListType + self.subListTextStyle = subListTextStyle + self.subListHasBoldText = subListHasBoldText + self.subItems = subItems() + } } // swiftlint:enable discouraged_optional_boolean diff --git a/OUDS/Core/Components/Sources/Controls/Checkbox/OUDSCheckbox.swift b/OUDS/Core/Components/Sources/Controls/Checkbox/OUDSCheckbox.swift index 3317a4330cf..95336269538 100644 --- a/OUDS/Core/Components/Sources/Controls/Checkbox/OUDSCheckbox.swift +++ b/OUDS/Core/Components/Sources/Controls/Checkbox/OUDSCheckbox.swift @@ -105,6 +105,28 @@ public struct OUDSCheckbox: View { // 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, + 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.** diff --git a/OUDS/Core/Components/Sources/Controls/Checkbox/OUDSCheckboxIndeterminate.swift b/OUDS/Core/Components/Sources/Controls/Checkbox/OUDSCheckboxIndeterminate.swift index 598a3c2f9d6..12ffb4b2092 100644 --- a/OUDS/Core/Components/Sources/Controls/Checkbox/OUDSCheckboxIndeterminate.swift +++ b/OUDS/Core/Components/Sources/Controls/Checkbox/OUDSCheckboxIndeterminate.swift @@ -105,6 +105,28 @@ public struct OUDSCheckboxIndeterminate: View { // 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, + 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.** diff --git a/OUDS/Core/Components/Sources/Controls/Checkbox/OUDSCheckboxItem.swift b/OUDS/Core/Components/Sources/Controls/Checkbox/OUDSCheckboxItem.swift index 5d9093ab122..3920c7bb938 100644 --- a/OUDS/Core/Components/Sources/Controls/Checkbox/OUDSCheckboxItem.swift +++ b/OUDS/Core/Components/Sources/Controls/Checkbox/OUDSCheckboxItem.swift @@ -70,6 +70,9 @@ import SwiftUI /// // The default layout will be used here. /// OUDSCheckboxItem("Hello world", isOn: $isOn) /// +/// // Localizable from bundle can also be used +/// OUDSCheckboxItem(LocalizedStringKey("agree_terms"), bundle: Bundle.module, isOn: $isOn) +/// /// // A leading checkbox with a label, but in read only mode (user cannot interact yet, but not disabled). /// // The default layout will be used here. /// OUDSCheckboxItem("Hello world", isOn: $isOn, isReadOnly: true) @@ -294,6 +297,61 @@ public struct OUDSCheckboxItem: View { self.action = action } + // swiftlint:disable function_default_parameter_at_end + /// Creates a checkbox with a localized label, looking up the key in the given bundle. + /// + /// ```swift + /// OUDSCheckboxItem(LocalizedStringKey("agree_terms"), bundle: Bundle.module, isOn: $isOn) + /// ``` + /// + /// **The design system does not allow to have both an error situation and a read only mode for the component.** + /// + /// - Parameters: + /// - key: A `LocalizedStringKey` used to look up the label in 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`. + /// - isOn: A binding to a property that determines whether the indicator is ticked (selected) or not (unselected) + /// - description: An additional helper text, a description, which should not be empty, default set to `nil` + /// - icon: An optional icon, default set to `nil` + /// - flipIcon: Default set to `false`, set to `true` to reverse the image (i.e. flip vertically) + /// - isReversed: `true` if the checkbox indicator must be in trailing position, `false` otherwise. Default to `false` + /// - isError: `true` if the look and feel of the component must reflect an error state, default set to `false` + /// - errorText: An optional error message to display at the bottom. This message is ignored if `isError` is `false`. + /// - isReadOnly: True if component is in read only, default set to `false` + /// - hasDivider: If `true` a divider is added at the bottom of the view, by default set to `false` + /// - constrainedMaxWidth: When `true`, the item width is constrained to a maximum value defined by the design system. + /// - action: An additional action to trigger when the checkbox has been pressed + public init(_ key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + isOn: Binding, + description: String? = nil, + icon: Image? = nil, + flipIcon: Bool = false, + isReversed: Bool = false, + isError: Bool = false, + errorText: String? = nil, + isReadOnly: Bool = false, + hasDivider: Bool = false, + constrainedMaxWidth: Bool = false, + action: (() -> Void)? = nil) + { + self.init(key.resolved(tableName: tableName, bundle: bundle), + isOn: isOn, + description: description, + icon: icon, + flipIcon: flipIcon, + isReversed: isReversed, + isError: isError, + errorText: errorText, + isReadOnly: isReadOnly, + hasDivider: hasDivider, + constrainedMaxWidth: constrainedMaxWidth, + action: action) + } + + // swiftlint:enable function_default_parameter_at_end + // MARK: Body public var body: some View { diff --git a/OUDS/Core/Components/Sources/Controls/Chip/OUDSFilterChip.swift b/OUDS/Core/Components/Sources/Controls/Chip/OUDSFilterChip.swift index 7f8251e9d1c..c6717bf96e9 100644 --- a/OUDS/Core/Components/Sources/Controls/Chip/OUDSFilterChip.swift +++ b/OUDS/Core/Components/Sources/Controls/Chip/OUDSFilterChip.swift @@ -27,6 +27,9 @@ import SwiftUI /// // Text only as not selected (default unselected) /// OUDSFilterChip(text: "Label") { /* the action to process */ } /// +/// // Text from a localizable and a bundle +/// OUDSFilterChip(LocalizedStringKey("category_filter"), bundle: Bundle.module) { } +/// /// // Text and icon as selected /// OUDSFilterChip(icon: Image("ic_heart"), text: "Label", selected: true) { /* the action to process */ } /// ``` @@ -66,6 +69,30 @@ public struct OUDSFilterChip: View { // MARK: - Initializers + /// Creates a filter chip with a localized text and icon, looking up the key in the given bundle. + /// + /// ```swift + /// OUDSFilterChip(icon: Image("ic_heart"), LocalizedStringKey("like_filter"), bundle: Bundle.module, selected: true) { } + /// ``` + /// + /// - Parameters: + /// - icon: An image which shoud contains an icon + /// - key: A `LocalizedStringKey` used to look up the text in 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`. + /// - selected: Flag to know if chip is selected, by default is unselected + /// - action: The action to perform when the user triggers the chip + public init(icon: Image, + _ key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + selected: Bool = false, + action: @escaping () -> Void) + { + let resolvedText = key.resolved(tableName: tableName, bundle: bundle) + self.init(icon: icon, text: resolvedText, selected: selected, action: action) + } + /// Creates a filter chip with text and icon. /// /// - Parameters: @@ -82,6 +109,26 @@ public struct OUDSFilterChip: View { self.selected = selected } + /// Create a chip with an icon only. + /// + /// - Parameters: + /// - icon: An image which shoud contains an icon + /// - 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`. + /// - selected: Flag to know if chip is selected, by default is unselected + /// - action: The action to perform when the user triggers the chip + public init(icon: Image, + accessibilityLabel key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + selected: Bool = false, + action: @escaping () -> Void) + { + let resolvedText = key.resolved(tableName: tableName, bundle: bundle) + self.init(icon: icon, accessibilityLabel: resolvedText, selected: selected, action: action) + } + /// Create a chip with an icon only. /// /// - Parameters: @@ -98,6 +145,28 @@ public struct OUDSFilterChip: View { self.selected = selected } + /// Creates a filter chip with a localized text only, looking up the key in the given bundle. + /// + /// ```swift + /// OUDSFilterChip(LocalizedStringKey("category_filter"), bundle: Bundle.module) { } + /// ``` + /// + /// - Parameters: + /// - key: A `LocalizedStringKey` used to look up the text in 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`. + /// - selected: Flag to know if chip is selected, by default is unselected + /// - action: The action to perform when the user triggers the chip + public init(_ key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + selected: Bool = false, + action: @escaping () -> Void) + { + let resolvedText = key.resolved(tableName: tableName, bundle: bundle) + self.init(text: resolvedText, selected: selected, action: action) + } + /// Create a chip with a text only. /// /// - Parameters: diff --git a/OUDS/Core/Components/Sources/Controls/Chip/OUDSSuggestionChip.swift b/OUDS/Core/Components/Sources/Controls/Chip/OUDSSuggestionChip.swift index 391188d3d14..06ccfef0330 100644 --- a/OUDS/Core/Components/Sources/Controls/Chip/OUDSSuggestionChip.swift +++ b/OUDS/Core/Components/Sources/Controls/Chip/OUDSSuggestionChip.swift @@ -34,6 +34,9 @@ import SwiftUI /// // Text only /// OUDSSuggestionChip(text: "Heart") { /* the action to process */ } /// +/// // Text from a localizable and a bundle +/// OUDSSuggestionChip(LocalizedStringKey("category_chip"), bundle: Bundle.module) { } +/// /// // Text and icon /// OUDSSuggestionChip(icon: Image("ic_heart"), text: "Heart") { /* the action to process */ } /// ``` @@ -72,6 +75,28 @@ public struct OUDSSuggestionChip: View { // MARK: - Initializers + /// Creates a chip with a localized text and icon, looking up the key in the given bundle. + /// + /// ```swift + /// OUDSSuggestionChip(icon: Image("ic_heart"), LocalizedStringKey("like_chip"), bundle: Bundle.module) { } + /// ``` + /// + /// - Parameters: + /// - icon: An image which shoud contains an icon + /// - key: A `LocalizedStringKey` used to look up the text in 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`. + /// - action: The action to perform when the user triggers the chip + public init(icon: Image, + _ key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + action: @escaping () -> Void) + { + let resolvedText = key.resolved(tableName: tableName, bundle: bundle) + self.init(icon: icon, text: resolvedText, action: action) + } + /// Creates a chip with text and icon. /// /// No accessibility hint is defined for this component. @@ -89,6 +114,24 @@ public struct OUDSSuggestionChip: View { self.action = action } + /// Creates a chip with an icon only. + /// + /// - Parameters: + /// - icon: An image which shoud contains an icon + /// - 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`. + /// - action: The action to perform when the user triggers the chip + public init(icon: Image, + accessibilityLabel key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + action: @escaping () -> Void) + { + let resolvedText = key.resolved(tableName: tableName, bundle: bundle) + self.init(icon: icon, accessibilityLabel: resolvedText, action: action) + } + /// Creates a chip with an icon only. /// /// - Parameters: @@ -103,6 +146,26 @@ public struct OUDSSuggestionChip: View { self.action = action } + /// Creates a chip with a localized text only, looking up the key in the given bundle. + /// + /// ```swift + /// OUDSSuggestionChip(LocalizedStringKey("category_chip"), bundle: Bundle.module) { } + /// ``` + /// + /// - Parameters: + /// - key: A `LocalizedStringKey` used to look up the text in 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`. + /// - action: The action to perform when the user triggers the chip + public init(_ key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + action: @escaping () -> Void) + { + let resolvedText = key.resolved(tableName: tableName, bundle: bundle) + self.init(text: resolvedText, action: action) + } + /// Creates a chip with a text only. /// /// - Parameters: diff --git a/OUDS/Core/Components/Sources/Controls/ChipPicker/OUDSChipPicker.swift b/OUDS/Core/Components/Sources/Controls/ChipPicker/OUDSChipPicker.swift index 3f862796512..dbd971e8395 100644 --- a/OUDS/Core/Components/Sources/Controls/ChipPicker/OUDSChipPicker.swift +++ b/OUDS/Core/Components/Sources/Controls/ChipPicker/OUDSChipPicker.swift @@ -143,6 +143,36 @@ public struct OUDSChipPicker: View { selectionType = .singleOrNone(selection) } + /// Defines the single selection picker view with a localized title, looking up the key in the given bundle. + /// + /// ```swift + /// OUDSChipPicker(title: LocalizedStringKey("picker_title"), bundle: Bundle.module, selection: $selection, chips: data) + /// ``` + /// + /// - Parameters: + /// - title: A `LocalizedStringKey` for the picker title, or `nil` if no title + /// - 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`. + /// - selection: The current selected value + /// - chips: The raw data to wrap in ``OUDSFilterChip`` for display + /// - itemsSpacing: The custom spacing to apply between items, default set to *nl*. If *nil* token *theme.spaces.fixedNone* will be used. + public init(title: LocalizedStringKey?, + tableName: String? = nil, + bundle: Bundle = .main, + selection: Binding, + chips: [OUDSChipPickerData], + itemsSpacing: SpaceSemanticToken? = nil) + { + let resolvedTitle = title.map { $0.resolved(tableName: tableName, bundle: bundle) } + if let resolvedTitle, resolvedTitle.isEmpty { + OL.warning("The title of the OUDSChipPicker is empty, prefer nil instead") + } + self.title = resolvedTitle + self.chips = chips + customItemsSpacing = itemsSpacing + selectionType = .singleOrNone(selection) + } + /// Defines the single selection picker view which displays using ``OUDSFilterChip`` view the ``OUDSChipPickerData`` /// The user will be able to choose only one option in this picker. /// @@ -161,6 +191,36 @@ public struct OUDSChipPicker: View { selectionType = .single(selection) } + /// Defines the single selection picker view with a localized title, looking up the key in the given bundle. + /// + /// ```swift + /// OUDSChipPicker(title: LocalizedStringKey("picker_title"), bundle: Bundle.module, selection: $selection, chips: data) + /// ``` + /// + /// - Parameters: + /// - title: A `LocalizedStringKey` for the picker title, or `nil` if no title + /// - 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`. + /// - selection: The current selected value + /// - chips: The raw data to wrap in ``OUDSFilterChip`` for display + /// - itemsSpacing: The custom spacing to apply between items, default set to *nl*. If *nil* token *theme.spaces.fixedNone* will be used. + public init(title: LocalizedStringKey?, + tableName: String? = nil, + bundle: Bundle = .main, + selection: Binding, + chips: [OUDSChipPickerData], + itemsSpacing: SpaceSemanticToken? = nil) + { + let resolvedTitle = title.map { $0.resolved(tableName: tableName, bundle: bundle) } + if let resolvedTitle, resolvedTitle.isEmpty { + OL.warning("The title of the OUDSChipPicker is empty, prefer nil instead") + } + self.title = resolvedTitle + self.chips = chips + customItemsSpacing = itemsSpacing + selectionType = .single(selection) + } + /// Defines the multiple selection picker view which displays using ``OUDSFilterChip`` view the ``OUDSChipPickerData``. /// The user will be able to choose zero or one or seevral options in this picker. /// @@ -179,6 +239,36 @@ public struct OUDSChipPicker: View { selectionType = .multiple(selections) } + /// Defines the multiple selection picker view with a localized title, looking up the key in the given bundle. + /// + /// ```swift + /// OUDSChipPicker(title: LocalizedStringKey("picker_title"), bundle: Bundle.module, selections: $selections, chips: data) + /// ``` + /// + /// - Parameters: + /// - title: A `LocalizedStringKey` for the picker title, or `nil` if no title + /// - 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`. + /// - selections: Current selected values + /// - chips: The raw data to wrap in ``OUDSFilterChip`` for display + /// - itemsSpacing: The custom spacing to apply between items, default set to *nl*. If *nil* token *theme.spaces.fixedNone* will be used. + public init(title: LocalizedStringKey?, + tableName: String? = nil, + bundle: Bundle = .main, + selections: Binding<[Tag]>, + chips: [OUDSChipPickerData], + itemsSpacing: SpaceSemanticToken? = nil) + { + let resolvedTitle = title.map { $0.resolved(tableName: tableName, bundle: bundle) } + if let resolvedTitle, resolvedTitle.isEmpty { + OL.warning("The title of the OUDSChipPicker is empty, prefer nil instead") + } + self.title = resolvedTitle + self.chips = chips + customItemsSpacing = itemsSpacing + selectionType = .multiple(selections) + } + // swiftlint:enable function_default_parameter_at_end // MARK: - Body diff --git a/OUDS/Core/Components/Sources/Controls/PasswordInput/OUDSPasswordInput.swift b/OUDS/Core/Components/Sources/Controls/PasswordInput/OUDSPasswordInput.swift index f37cb9c57f8..5164c11a122 100644 --- a/OUDS/Core/Components/Sources/Controls/PasswordInput/OUDSPasswordInput.swift +++ b/OUDS/Core/Components/Sources/Controls/PasswordInput/OUDSPasswordInput.swift @@ -41,6 +41,9 @@ import SwiftUI /// ```swift /// // An outlined text input /// OUDSPasswordInput(label: "Your password", password: $password, isOutlined: true) +/// +/// // With a localizable from a bundle +/// OUDSPasswordInput(LocalizedStringKey("password_label"), bundle: Bundle.module, password: $password) /// ``` /// /// ### Rounded layout @@ -166,6 +169,53 @@ public struct OUDSPasswordInput: View { self.constrainedMaxWidth = constrainedMaxWidth } + // swiftlint:disable function_default_parameter_at_end + /// Creates a password input with a localized label, looking up the key in the given bundle. + /// + /// ```swift + /// OUDSPasswordInput(LocalizedStringKey("password_label"), bundle: Bundle.module, password: $password) + /// ``` + /// + /// - Parameters: + /// - key: A `LocalizedStringKey` used to look up the label in 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`. + /// - password: The pasword to display and edit + /// - isHiddenPassword: Flag to hide or show the password, By default set to `true` + /// - placeholder: The text displayed when the password input is empty, by default is *nil* + /// - prefix: Text placed before the user's input, by default is *nil* + /// - lockIcon: When `true`, a lock icon is displayed, defaults to `false` + /// - helperText: An optional helper text, by default is *nil* + /// - isOutlined: Controls the style of the pasword input, by default is *false* + /// - constrainedMaxWidth: When `true`, the width is constrained, defaults to `false` + /// - status: The current status of the password input, default set to *enabled* + public init(_ key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + password: Binding, + isHiddenPassword: Binding = .constant(true), + placeholder: String? = nil, + prefix: String? = nil, + lockIcon: Bool = false, + helperText: String? = nil, + isOutlined: Bool = false, + constrainedMaxWidth: Bool = false, + status: OUDSTextInput.Status = .enabled) + { + self.init(label: key.resolved(tableName: tableName, bundle: bundle), + password: password, + isHiddenPassword: isHiddenPassword, + placeholder: placeholder, + prefix: prefix, + lockIcon: lockIcon, + helperText: helperText, + isOutlined: isOutlined, + constrainedMaxWidth: constrainedMaxWidth, + status: status) + } + + // swiftlint:enable function_default_parameter_at_end + // MARK: Body public var body: some View { diff --git a/OUDS/Core/Components/Sources/Controls/Radio/OUDSRadio.swift b/OUDS/Core/Components/Sources/Controls/Radio/OUDSRadio.swift index 752098fab3a..2a7a44a55a0 100644 --- a/OUDS/Core/Components/Sources/Controls/Radio/OUDSRadio.swift +++ b/OUDS/Core/Components/Sources/Controls/Radio/OUDSRadio.swift @@ -92,6 +92,28 @@ public struct OUDSRadio: View { // MARK: - Initializers + /// Creates a radio with only an indicator. + /// + /// **The design system does not allow to have both an error or a read only situation and a disabled state for the component.** + /// + /// - Parameters: + /// - isOn: A binding to a property that determines whether the toggle is on or off. + /// - 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 iif the component should be in read only mode, default set to `false` + public init(isOn: Binding, + 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 radio with only an indicator. /// /// **The design system does not allow to have both an error or a read only situation and a disabled state for the component.** diff --git a/OUDS/Core/Components/Sources/Controls/Radio/OUDSRadioItem.swift b/OUDS/Core/Components/Sources/Controls/Radio/OUDSRadioItem.swift index 46bfa8e88e5..c22583002da 100644 --- a/OUDS/Core/Components/Sources/Controls/Radio/OUDSRadioItem.swift +++ b/OUDS/Core/Components/Sources/Controls/Radio/OUDSRadioItem.swift @@ -71,6 +71,9 @@ import SwiftUI /// // The default layout will be used here. /// OUDSRadioItem("Lucy in the Sky with Diamonds", isOn: $selection) /// +/// // Localizable from bundle can also be used +/// OUDSRadioItem(LocalizedStringKey("option_label"), bundle: Bundle.module, isOn: $selection) +/// /// // A leading radio with a label, but in read only mode (user cannot interact yet, but not disabled). /// // The default layout will be used here. /// OUDSRadioItem("Lucy in the Sky with Diamonds", isOn: $selection, isReadOnly: true) @@ -318,6 +321,67 @@ public struct OUDSRadioItem: View { self.action = action } + // swiftlint:disable function_default_parameter_at_end + /// Creates a radio with a localized label, looking up the key in the given bundle. + /// + /// ```swift + /// OUDSRadioItem(LocalizedStringKey("option_label"), bundle: Bundle.module, isOn: $selection) + /// ``` + /// + /// **The design system does not allow to have both an error situation and a read only mode for the component.** + /// + /// - Parameters: + /// - key: A `LocalizedStringKey` used to look up the label in 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`. + /// - isOn: A binding to a property that determines whether the toggle is on or off + /// - extraLabel: An additional label text of the radio, default set to `nil` + /// - description: A description, like an helper text, should not be empty, default set to `nil` + /// - icon: An optional icon, default set to `nil` + /// - flipIcon: Default set to `false`, set to true to reverse the image (i.e. flip vertically) + /// - isOutlined: Flag to get an outlined radio, default set to `false` + /// - isReversed: `true` of the radio indicator must be in trailing position, `false` otherwise. Default to `false` + /// - isError: `true` if the look and feel of the component must reflect an error state, default set to `false` + /// - errorText: An optional error message to display at the bottom. This message is ignored if `isError` is `false`. + /// - isReadOnly: True if component is in read only, default set to `false` + /// - hasDivider: If `true` a divider is added at the bottom of the view + /// - constrainedMaxWidth: When `true`, the item width is constrained to a maximum value defined by the design system. + /// - action: An additional action to trigger when the radio button has been pressed + public init(_ key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + isOn: Binding, + extraLabel: String? = nil, + description: String? = nil, + icon: Image? = nil, + flipIcon: Bool = false, + isOutlined: Bool = false, + isReversed: Bool = false, + isError: Bool = false, + errorText: String? = nil, + isReadOnly: Bool = false, + hasDivider: Bool = false, + constrainedMaxWidth: Bool = false, + action: (() -> Void)? = nil) + { + self.init(key.resolved(tableName: tableName, bundle: bundle), + isOn: isOn, + extraLabel: extraLabel, + description: description, + icon: icon, + flipIcon: flipIcon, + isOutlined: isOutlined, + isReversed: isReversed, + isError: isError, + errorText: errorText, + isReadOnly: isReadOnly, + hasDivider: hasDivider, + constrainedMaxWidth: constrainedMaxWidth, + action: action) + } + + // swiftlint:enable function_default_parameter_at_end + // MARK: Body public var body: some View { diff --git a/OUDS/Core/Components/Sources/Controls/Switch/OUDSSwitch.swift b/OUDS/Core/Components/Sources/Controls/Switch/OUDSSwitch.swift index f3d418a0179..601488a77f3 100644 --- a/OUDS/Core/Components/Sources/Controls/Switch/OUDSSwitch.swift +++ b/OUDS/Core/Components/Sources/Controls/Switch/OUDSSwitch.swift @@ -78,6 +78,26 @@ public struct OUDSSwitch: View { // MARK: - Initializers + /// Creates a switch with only an indicator. + /// + /// **The design system does not allow to have both a read only situation and a disabled state for the component.** + /// + /// - Parameters: + /// - isOn: A binding to a property that determines whether the toggle is on or off. + /// - 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`. + /// - isReadOnly: True if the look and feel of the component must reflect a read only state, default set to `false` + public init(isOn: Binding, + accessibilityLabel key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + isReadOnly: Bool = false) + { + let resolvedText = key.resolved(tableName: tableName, bundle: bundle) + self.init(isOn: isOn, accessibilityLabel: resolvedText, isReadOnly: isReadOnly) + } + /// Creates a switch with only an indicator. /// /// **The design system does not allow to have both a read only situation and a disabled state for the component.** diff --git a/OUDS/Core/Components/Sources/Controls/Switch/OUDSSwitchItem.swift b/OUDS/Core/Components/Sources/Controls/Switch/OUDSSwitchItem.swift index 1322733a258..f8f128de4e0 100644 --- a/OUDS/Core/Components/Sources/Controls/Switch/OUDSSwitchItem.swift +++ b/OUDS/Core/Components/Sources/Controls/Switch/OUDSSwitchItem.swift @@ -60,6 +60,9 @@ import SwiftUI /// // The default layout will be used here. /// OUDSSwitchItem("Lucy in the Sky with Diamonds", isOn: $isOn) /// +/// // Localizable from bundle can also be used +/// OUDSSwitchItem(LocalizedStringKey("notifications_setting"), bundle: Bundle.module, isOn: $isOn) +/// /// // A leading switch with a label, but in read only mode (user cannot interact yet, but not disabled). /// // The default layout will be used here. /// OUDSSwitchItem("Lucy in the Sky with Diamonds", isOn: $isOn, isReadOnly: true) @@ -220,6 +223,58 @@ public struct OUDSSwitchItem: View { orientation: isReversed ? .reversed : .default) } + // swiftlint:disable function_default_parameter_at_end + /// Creates a switch with a localized label, looking up the key in the given bundle. + /// + /// ```swift + /// OUDSSwitchItem(LocalizedStringKey("notifications_setting"), bundle: Bundle.module, isOn: $isOn) + /// ``` + /// + /// **The design system does not allow to have both an error situation and a read only mode for the component.** + /// + /// - Parameters: + /// - key: A `LocalizedStringKey` used to look up the label in 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`. + /// - isOn: A binding to a property that determines whether the toggle is on or off + /// - description: An additional helper text, a description, should not be empty + /// - icon: An optional icon, default set to `nil` + /// - flipIcon: Default set to `false`, set to true to reverse the image (i.e. flip vertically) + /// - isReversed: `true` of the switch indicator must be in trailing position, `false` otherwise. Default to `true` + /// - isError: `true` if the look and feel of the component must reflect an error state, default set to `false` + /// - errorText: An optional error message to display at the bottom. This message is ignored if `isError` is `false`. + /// - isReadOnly: True if component is in read only, default set to `false` + /// - hasDivider: If `true` a divider is added at the bottom of the view + /// - constrainedMaxWidth: When `true`, the item width is constrained to a maximum value defined by the design system. + public init(_ key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + isOn: Binding, + description: String? = nil, + icon: Image? = nil, + flipIcon: Bool = false, + isReversed: Bool = true, + isError: Bool = false, + errorText: String? = nil, + isReadOnly: Bool = false, + hasDivider: Bool = false, + constrainedMaxWidth: Bool = false) + { + self.init(key.resolved(tableName: tableName, bundle: bundle), + isOn: isOn, + description: description, + icon: icon, + flipIcon: flipIcon, + isReversed: isReversed, + isError: isError, + errorText: errorText, + isReadOnly: isReadOnly, + hasDivider: hasDivider, + constrainedMaxWidth: constrainedMaxWidth) + } + + // swiftlint:enable function_default_parameter_at_end + // MARK: Body public var body: some View { diff --git a/OUDS/Core/Components/Sources/Controls/TextInput/OUDSTextInput.swift b/OUDS/Core/Components/Sources/Controls/TextInput/OUDSTextInput.swift index b1a57e0b131..0d8e8843c68 100644 --- a/OUDS/Core/Components/Sources/Controls/TextInput/OUDSTextInput.swift +++ b/OUDS/Core/Components/Sources/Controls/TextInput/OUDSTextInput.swift @@ -55,6 +55,9 @@ import SwiftUI /// ```swift /// // An outlined text input /// OUDSTextInput(label: "Label", text: $text, isOutlined: true) +/// +/// // With a localizable and a bundle +/// OUDSTextInput(LocalizedStringKey("label_wording"), bundle: Bundle.module, text: $text) /// ``` /// /// ### Rounded layout @@ -357,6 +360,62 @@ public struct OUDSTextInput: View { // TODO: #406 - Add documentation hyperlink self.constrainedMaxWidth = constrainedMaxWidth } + // swiftlint:disable function_default_parameter_at_end + /// Creates a text input with a localized label, looking up the key in the given bundle. + /// + /// ```swift + /// OUDSTextInput(LocalizedStringKey("email_label"), bundle: Bundle.module, text: $text) + /// ``` + /// + /// - Parameters: + /// - key: A `LocalizedStringKey` used to look up the label in 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`. + /// - text: The text to display and edit + /// - placeholder: The text displayed when the text input is empty, by default is *nil* + /// - prefix: Text placed before the user's input, by default is *nil* + /// - suffix: Text placed after the user's input, by default is *nil* + /// - leadingIcon: An optional leading icon, by default is *nil* + /// - flipLeadingIcon: Default set to *false*, set to *true* to mirror the leading icon + /// - trailingAction: An optional trailing action, by default is *nil* + /// - helperText: An optional helper text, by default is *nil* + /// - helperLink: An optional helper link, by default is *nil* + /// - isOutlined: Controls the style of the text input, by default is *false* + /// - constrainedMaxWidth: When `true`, the width is constrained, defaults to `false` + /// - status: The current status of the text input, default set to *enabled* + public init(_ key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + text: Binding, + placeholder: String? = nil, + prefix: String? = nil, + suffix: String? = nil, + leadingIcon: Image? = nil, + flipLeadingIcon: Bool = false, + trailingAction: Self.TrailingAction? = nil, + helperText: String? = nil, + helperLink: Self.Helperlink? = nil, + isOutlined: Bool = false, + constrainedMaxWidth: Bool = false, + status: Self.Status = .enabled) + { + self.init(label: key.resolved(tableName: tableName, bundle: bundle), + text: text, + placeholder: placeholder, + prefix: prefix, + suffix: suffix, + leadingIcon: leadingIcon, + flipLeadingIcon: flipLeadingIcon, + trailingAction: trailingAction, + helperText: helperText, + helperLink: helperLink, + isOutlined: isOutlined, + constrainedMaxWidth: constrainedMaxWidth, + status: status) + } + + // swiftlint:enable function_default_parameter_at_end + // MARK: Body public var body: some View { diff --git a/OUDS/Core/Components/Sources/Dialogs/Alert/OUDSAlertMessage.swift b/OUDS/Core/Components/Sources/Dialogs/Alert/OUDSAlertMessage.swift index cd15bf9253b..1f94b75403c 100644 --- a/OUDS/Core/Components/Sources/Dialogs/Alert/OUDSAlertMessage.swift +++ b/OUDS/Core/Components/Sources/Dialogs/Alert/OUDSAlertMessage.swift @@ -24,6 +24,8 @@ import SwiftUI /// ```swift /// // A basic positive alert message with text and badge /// OUDSAlertMessage(label: "Label") +/// // From a localizable in a bundle +/// OUDSAlertMessage(LocalizedStringKey("label_wording"), bundle: Bundle.module) /// /// // A more complex alert message for warning status with a description and a close action /// // to dismiss the message. @@ -156,6 +158,38 @@ public struct OUDSAlertMessage: View { self.bulletList = bulletList } + /// Creates an alert message with a localized label, looking up the key in the given bundle. + /// + /// ```swift + /// OUDSAlertMessage(LocalizedStringKey("error_message"), bundle: Bundle.module, status: .negative) + /// ``` + /// + /// - Parameters: + /// - key: A `LocalizedStringKey` used to look up the label in 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`. + /// - status: The status of the alert message, default set to *positive* + /// - description: An optional supplementary text, default set to *nil* + /// - bulletList: An optional list of bullet points, default set to empty array + /// - link: An optional link to be displayed in the alert message, default set to *nil* + /// - onClose: An optional callback invoked when the close button is clicked, default set to *nil* + public init(_ key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + status: OUDSAlertStatus = .positive, + description: String? = nil, + bulletList: [String] = [], + link: Self.Link? = nil, + onClose: (() -> Void)? = nil) + { + self.init(label: key.resolved(tableName: tableName, bundle: bundle), + status: status, + description: description, + bulletList: bulletList, + link: link, + onClose: onClose) + } + // MARK: - Body public var body: some View { diff --git a/OUDS/Core/Components/Sources/Dialogs/Alert/OUDSInlineAlert.swift b/OUDS/Core/Components/Sources/Dialogs/Alert/OUDSInlineAlert.swift index c3b95806475..a5184650374 100644 --- a/OUDS/Core/Components/Sources/Dialogs/Alert/OUDSInlineAlert.swift +++ b/OUDS/Core/Components/Sources/Dialogs/Alert/OUDSInlineAlert.swift @@ -24,6 +24,8 @@ import SwiftUI /// ```swift /// // A inline alert with a label and the default neutral status /// OUDSInlineAlert(label: "Label") +/// // From a localizable in a bundle +/// OUDSInlineAlert(LocalizedStringKey("label_wording"), bundle: Bundle.module) /// /// // An inline alert /// OUDSInlineAlert(label: "Warning", status: .warning) @@ -84,6 +86,25 @@ public struct OUDSInlineAlert: View { self.status = status } + /// Creates an inline alert with a localized label, looking up the key in the given bundle. + /// + /// ```swift + /// OUDSInlineAlert(LocalizedStringKey("info_message"), bundle: Bundle.module, status: .info) + /// ``` + /// + /// - Parameters: + /// - key: A `LocalizedStringKey` used to look up the label in 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`. + /// - status: The status of the inline alert, default set to *neutral* without icon + public init(_ key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + status: OUDSAlertStatus = .neutral()) + { + self.init(label: key.resolved(tableName: tableName, bundle: bundle), status: status) + } + // MARK: - Body public var body: some View { diff --git a/OUDS/Core/Components/Sources/Indicators/Badge/OUDSBadge.swift b/OUDS/Core/Components/Sources/Indicators/Badge/OUDSBadge.swift index 9a6e7db13b8..f4ca35effd5 100644 --- a/OUDS/Core/Components/Sources/Indicators/Badge/OUDSBadge.swift +++ b/OUDS/Core/Components/Sources/Indicators/Badge/OUDSBadge.swift @@ -167,6 +167,26 @@ public struct OUDSBadge: View { // MARK: Initializers + /// Creates a basic badge. + /// + /// Use the `View/disabled(_:)` method to have badge in disabled state. + /// + /// - Parameters: + /// - 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`. + /// - status: The status of this badge. The background color of the badge is based on this status, *neutral* by default + /// - size: The size of this badge, *medium* by default + public init(accessibilityLabel key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + status: Status = .neutral, + size: StandardSize = .medium) + { + let resolvedText = key.resolved(tableName: tableName, bundle: bundle) + self.init(accessibilityLabel: resolvedText, status: status, size: size) + } + /// Creates a basic badge. /// /// Use the `View/disabled(_:)` method to have badge in disabled state. @@ -197,6 +217,32 @@ public struct OUDSBadge: View { accessibilityLabel: accessibilityLabel) } + /// Creates a badge which displays numerical value (e.g., unread messages, notifications). + /// Minimum and maximum values are 0 and 99 respectively. If value is greater than 99, "+99" is displayed. + /// Negative values are not allowed by design. + /// The background color of the badge and the number color are based on the given `status`. + /// + /// Use the `View/disabled(_:)` method to have badge in disabled state. + /// + /// - Parameters: + /// - count:The number displayed in the badge. + /// - 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`. + /// - status: The status of this badge, default set to *neutral* + /// - size: The size of this badge, default set to *medium* + public init(count: UInt8, + accessibilityLabel key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + status: Status = .neutral, + size: IllustrationSize = .medium) + { + let resolvedText = key.resolved(tableName: tableName, bundle: bundle) + self.init(layout: .init(type: .count(value: count, size: size), status: status), + accessibilityLabel: resolvedText) + } + /// Creates a badge which displays an icon to visually reinforce meaning. /// It is used for status indicators (e.g., "New", "Pending", "Success"). /// The background color of the badge and the icon color are based on the given `status`. @@ -213,10 +259,33 @@ public struct OUDSBadge: View { size: IllustrationSize = .medium) { let layout = BadgeLayout(type: .icon(customIcon: status.icon?.image, flipIcon: status.icon?.flipped ?? false, size: size), status: status.status) - self.init(layout: layout, accessibilityLabel: accessibilityLabel) } + /// Creates a badge which displays an icon to visually reinforce meaning. + /// It is used for status indicators (e.g., "New", "Pending", "Success"). + /// The background color of the badge and the icon color are based on the given `status`. + /// + /// Use the `View/disabled(_:)` method to have badge in disabled state. + /// + /// - Parameters: + /// - status: The status of this badge with icon (for all status, a default icon is displayed except for **accent** + /// and **neutral** status whrere a decorative icon is required) + /// - 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`. + /// - size: The size of this badge, default set to *medium* + public init(status: StatusWithIcon, + accessibilityLabel key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + size: IllustrationSize = .medium) + { + let resolvedText = key.resolved(tableName: tableName, bundle: bundle) + let layout = BadgeLayout(type: .icon(customIcon: status.icon?.image, flipIcon: status.icon?.flipped ?? false, size: size), status: status.status) + self.init(layout: layout, accessibilityLabel: resolvedText) + } + private init(layout: BadgeLayout, accessibilityLabel: String) { if accessibilityLabel.isEmpty { OL.warning("The OUDSBadge should not have an empty accessibility label, think about your disabled users!") diff --git a/OUDS/Core/Components/Sources/Indicators/Tag/OUDSTag.swift b/OUDS/Core/Components/Sources/Indicators/Tag/OUDSTag.swift index f64108583c7..085d53f618e 100644 --- a/OUDS/Core/Components/Sources/Indicators/Tag/OUDSTag.swift +++ b/OUDS/Core/Components/Sources/Indicators/Tag/OUDSTag.swift @@ -98,6 +98,8 @@ import SwiftUI /// OUDSTag(label: "Label", status: .neutral(), appearance: .emphasized, shape: .rounded, size: .default) /// // Or also /// OUDSTag(label: "Label") +/// // Or from a localizale and a bundle +/// OUDSTag(LocalizedStringKey("label_wording"), bundle: Bundle.module) /// /// // Tag with neutral status with bullet /// OUDSTag(label: "Label", status: .neutral(leading: .bullet) @@ -355,6 +357,34 @@ public struct OUDSTag: View { type = hasLoader ? .loader(label: label) : .status(label: label, status: status) } + /// Creates a tag with a localized label, looking up the key in the given bundle. + /// + /// ```swift + /// OUDSTag(LocalizedStringKey("status_tag"), bundle: Bundle.module, status: .positive(leading: .none)) + /// ``` + /// + /// - Parameters: + /// - key: A `LocalizedStringKey` used to look up the label in 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`. + /// - status: The status of the tag, default set to *neutral* + /// - appearance: The importance of the tag, default set to *emphasized* + /// - shape: The shape of the tag, default set to *rounded* + /// - size: The size of the tag, default set to *default* + /// - hasLoader: If an optional loader is displayed, default set to *false* + public init(_ key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + status: Status = .neutral(), + appearance: Appearance = .emphasized, + shape: Shape = .rounded, + size: Size = .default, + hasLoader: Bool = false) + { + let resolvedLabel = key.resolved(tableName: tableName, bundle: bundle) + self.init(label: resolvedLabel, status: status, appearance: appearance, shape: shape, size: size, hasLoader: hasLoader) + } + /// Creates a tag in the loading state. /// /// The use the `View/disabled(_:)` method has no effect on this state. @@ -374,6 +404,28 @@ public struct OUDSTag: View { // "loadingLabel" instead of "label" to avoid doubts for users with init(label:status:appearance=shape:size:hasLoader) with default values } + /// Creates a tag in the loading state with a localized label, looking up the key in the given bundle. + /// + /// ```swift + /// OUDSTag(loadingKey: LocalizedStringKey("loading_tag"), bundle: Bundle.module) + /// ``` + /// + /// - Parameters: + /// - loadingKey: A `LocalizedStringKey` used to look up the label in 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`. + /// - shape: The shape of the tag, i.e. the corners style + /// - size: The size of the tag + public init(loadingKey: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + shape: Shape = .rounded, + size: Size = .default) + { + let resolvedLabel = loadingKey.resolved(tableName: tableName, bundle: bundle) + self.init(loadingLabel: resolvedLabel, shape: shape, size: size) + } + // MARK: Body public var body: some View { diff --git a/OUDS/Core/Components/Sources/Layouts/OUDSIcon.swift b/OUDS/Core/Components/Sources/Layouts/OUDSIcon.swift index bc535409203..abacc5a1b54 100644 --- a/OUDS/Core/Components/Sources/Layouts/OUDSIcon.swift +++ b/OUDS/Core/Components/Sources/Layouts/OUDSIcon.swift @@ -36,6 +36,27 @@ public struct OUDSIcon: View { // MARK: Initializers + // swiftlint:disable function_default_parameter_at_end + /// Create the icon with asset. + /// + /// - Parameters: + /// - asset: The asset + /// - flipped: If asset must be flipped, default set to `false` + /// - 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`. + public init(asset: Image, + flipped: Bool = false, + accessibilityLabel key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main) + { + let resolvedText = key.resolved(tableName: tableName, bundle: bundle) + self.init(asset: asset, flipped: flipped, accessibilityLabel: resolvedText) + } + + // swiftlint:enable function_default_parameter_at_end + /// Create the icon with asset. /// /// - Parameters: diff --git a/OUDS/Core/Components/Sources/Navigations/Link/OUDSLink.swift b/OUDS/Core/Components/Sources/Navigations/Link/OUDSLink.swift index 70cd0568433..3e521c51f86 100644 --- a/OUDS/Core/Components/Sources/Navigations/Link/OUDSLink.swift +++ b/OUDS/Core/Components/Sources/Navigations/Link/OUDSLink.swift @@ -27,6 +27,9 @@ import SwiftUI /// // Text only in small size /// OUDSLink(text: "Feedback", size: .small) { /* the action to process */ } /// +/// // From a localizable and a bundle +/// OUDSLink(LocalizedStringKey("feedback_link"), bundle: Bundle.module, size: .small) { } +/// /// // Text and icon in default size /// OUDSLink(text: "Feedback", icon: Image("ic_heart"), size: .default) { /* the action to process */ } /// ``` @@ -122,6 +125,36 @@ public struct OUDSLink: View { self.action = action } + /// Creates a link with a localized text and optional icon, looking up the key in the given bundle. + /// + /// ```swift + /// OUDSLink(LocalizedStringKey("feedback_link"), bundle: Bundle.module, size: .default) { } + /// ``` + /// + /// - Parameters: + /// - key: A `LocalizedStringKey` used to look up the text in 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`. + /// - icon: Icon displayed in the link + /// - size: Size of the link + /// - action: The action to perform when the user triggers the link + public init(_ key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + icon: Image? = nil, + size: Size = .default, + action: @escaping () -> Void) + { + if let icon { + layout = .textAndIcon(icon) + } else { + layout = .textOnly + } + text = key.resolved(tableName: tableName, bundle: bundle) + self.size = size + self.action = action + } + /// Create a link with a "before `Indicator`" (`OUDSLink.Indicator.back`) or "after indicator" (`OUDSLink.Indicator.next`) beside the text. /// /// - Parameters: @@ -138,6 +171,35 @@ public struct OUDSLink: View { self.action = action } + // swiftlint:disable function_default_parameter_at_end + /// Creates a link with a localized text and a navigation indicator, looking up the key in the given bundle. + /// + /// ```swift + /// OUDSLink(LocalizedStringKey("back_link"), bundle: Bundle.module, indicator: .back) { } + /// ``` + /// + /// - Parameters: + /// - key: A `LocalizedStringKey` used to look up the text in 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`. + /// - indicator: Indicator displayed in the link + /// - size: Size of the link + /// - action: The action to perform when the user triggers the link + public init(_ key: LocalizedStringKey, + tableName: String? = nil, + bundle: Bundle = .main, + indicator: Indicator, + size: Size = .default, + action: @escaping () -> Void) + { + layout = .indicator(indicator) + text = key.resolved(tableName: tableName, bundle: bundle) + self.size = size + self.action = action + } + + // swiftlint:enable function_default_parameter_at_end + // MARK: Body public var body: some View { diff --git a/OUDS/Core/Components/Sources/_/Extensions/LocalizedStringKey+keyString.swift b/OUDS/Core/Components/Sources/_/Extensions/LocalizedStringKey+keyString.swift new file mode 100644 index 00000000000..1e1f3188bc2 --- /dev/null +++ b/OUDS/Core/Components/Sources/_/Extensions/LocalizedStringKey+keyString.swift @@ -0,0 +1,47 @@ +// +// 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 Foundation +import SwiftUI + +// MARK: - LocalizedStringKey extension + +extension LocalizedStringKey { + + // swiftlint:disable nslocalizedstring_key + + /// Extracts the raw key `String` from a `LocalizedStringKey` using reflection. + /// This works reliably for simple, non-interpolated string keys. + /// + /// - Note: This relies on SwiftUI's internal representation of `LocalizedStringKey`, + /// which stores the key string in a child labeled `"key"`. While this has been + /// stable across SwiftUI versions, it is an implementation detail that may require + /// updating if Apple changes the internal structure in a future release. + /// + /// - Returns: The raw key string embedded in the `LocalizedStringKey`, or an empty string if extraction fails. + var keyString: String { + Mirror(reflecting: self).children.first(where: { $0.label == "key" })?.value as? String ?? "" + } + + /// Resolves the localized string for `self` using the provided bundle and optional table name. + /// + /// - Parameters: + /// - tableName: The name of the `.strings` file to use, or `nil` for the default table. + /// - bundle: The bundle to look up the string in. Defaults to `Bundle.main`. + /// - Returns: The localized string for the key, or the key itself if no localization is found. + func resolved(tableName: String? = nil, bundle: Bundle = .main) -> String { + NSLocalizedString(keyString, tableName: tableName, bundle: bundle, comment: "") + } + + // swiftlint:enable nslocalizedstring_key +}