diff --git a/MIGRATION.md b/MIGRATION.md index 1e2d2b226..92d99c594 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,5 +1,7 @@ # Migration Guide +- [v2.1.0 → v3.0.0](#v210--v300) +- [v2.0.0 → v2.1.0](#v200--v210) - [v1.3.1 → v2.0.0](#v131--v200) - [v1.3.0 → v1.3.1](#v130--v131) - [v1.2.0 → v1.3.0](#v120--v130) @@ -7,6 +9,126 @@ - [v1.0.0 → v1.1.1](#v100--v111) - [Support](#support) +## v2.1.0 → v3.0.0 + +### Overview + +This release adds the `List item` component and a typography tokens component, updates the icon library to v2.3.0, updates the `Button` component to v3.3.0 with tinted/untinted icon support, updates the `Link` component to v2.4.0, adds a `size` parameter to `OudsCircularProgressIndicator` for button integration, and fixes a crash in `OudsTabBar`. It also introduces a typed prefix/suffix icon configuration with tinting support for `OudsTextInput` and `OudsPhoneNumberInput`. + +### Before You Begin + +#### Prerequisites + +- Use version 2.1.0 or older + +### Breaking Changes + +#### 1. `OudsTextInput` / `OudsPhoneNumberInput` — Typed prefix/suffix icon configuration with tinting support + +`OudsFormInputDecoration.prefixIcon` and `suffixIcon` are no longer plain `String`/asset paths. They now take `OudsTextInputPrefixIcon` and `OudsTextInputSuffixIconButton` objects, which let you control whether the icon is tinted with the theme color or displayed with its original (multi-color) asset colors via a new `tinted` parameter (defaults to `true`). The `onSuffixPressed` parameter on the decoration has been removed; the press callback now lives on `OudsTextInputSuffixIconButton.onPressed`. + +**Impact**: Medium (breaking — any code setting `prefixIcon`, `suffixIcon`, or `onSuffixPressed` on `OudsInputDecoration`/`OudsFormInputDecoration` must be updated) + +**Before**: +```dart +OudsInputDecoration( + prefixIcon: 'assets/ic_heart.svg', + suffixIcon: 'assets/ic_heart.svg', + onSuffixPressed: () {}, +) +``` + +**After**: +```dart +OudsInputDecoration( + prefixIcon: OudsTextInputPrefixIcon( + icon: 'assets/ic_heart.svg', + tinted: true, // optional, defaults to true + ), + suffixIcon: OudsTextInputSuffixIconButton( + icon: 'assets/ic_heart.svg', + tinted: true, // optional, defaults to true + onPressed: () {}, + ), +) +``` + +**Required Action**: +- Wrap existing `prefixIcon` asset paths in `OudsTextInputPrefixIcon(icon: ...)` +- Wrap existing `suffixIcon` asset paths in `OudsTextInputSuffixIconButton(icon: ..., onPressed: ...)`, moving the `onSuffixPressed` callback into `onPressed` +- Set `tinted: false` if the icon asset is multi-color and should keep its original colors instead of being tinted with the theme color + +**Reason for Change**: Support untinted (multi-color) leading and trailing icons in text and phone number inputs, consistent with the `tinted` behavior available in `OudsButton` and `OudsLink` + +### Icon Library Update — v2.3.0 + +The OUDS icon library has been updated to version 2.3.0. Some icon names or asset paths may have changed. + +**Impact**: Medium (if using icon constants from the library) + +**Required Action**: +- Review any icon constants or asset paths you reference directly +- Update to the new icon names from v2.3.0 if you receive compilation errors or missing asset warnings + +### New Components Added + +| Component | Description | +|-----------|-------------| +| `OudsListItem` | Standard list item component with leading/trailing content support | + +### Component Updates (Non-breaking) + +| Component | Change | +|-----------|--------| +| Button | Updated to v3.3.0 — added `tinted` parameter (defaults to `true`) to control whether the icon follows the theme color or keeps its original (multi-color) asset colors | +| Link | Updated to v2.4.0 | +| Progress Indicator | Added `size` parameter to `OudsCircularProgressIndicator` for button integration | +| Tab Bar | Fixed `Invalid value: Not in inclusive range 0..2: 3` crash | +| Typography | Added typography tokens component | + +### Compatibility + +- **Backward Compatibility**: No (breaking change in form input decoration API) +- **v2.1.0 Support**: Ended with this release + +## v2.0.0 → v2.1.0 + +### Overview + +This release adds the `Navigation Button`, `Circular Progress Indicator`, and `Linear Progress Indicator` components, and updates design tokens to v2.6.0. + +### Before You Begin + +#### Prerequisites + +- Use version 2.0.0 or older + +### Breaking Changes + +None. + +### New Components Added + +| Component | Description | +|-----------|-------------| +| `OudsNavigationButton` | Navigation button component | +| `OudsCircularProgressIndicator` | Circular (indeterminate/determinate) progress indicator | +| `OudsLinearProgressIndicator` | Linear (indeterminate/determinate) progress indicator | + +### Token Updates — v2.6.0 + +Design tokens have been updated to version 2.6.0. + +**Impact**: Low (only if overriding tokens directly in a custom theme) + +**Required Action**: +- If you override tokens in a custom theme, audit your overrides against the new token names + +### Compatibility + +- **Backward Compatibility**: Yes +- **v2.0.0 Support**: Ended with this release + ## v1.3.1 → v2.0.0 ### Overview diff --git a/app/CHANGELOG.md b/app/CHANGELOG.md index 4387b4c42..3bf39d3fe 100644 --- a/app/CHANGELOG.md +++ b/app/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - [DemoApp][Library] Create component - `List item` ([#54](https://github.com/Orange-OpenSource/ouds-flutter/issues/54)) ### Changed +- [DemoApp][Library] For `Button` component, add with `tinted parameter ([#912](https://github.com/Orange-OpenSource/ouds-flutter/issues/912)) +- [DemoApp][Library] For `passwordInput` component, update to v1.3.1 ([#785](https://github.com/Orange-OpenSource/ouds-flutter/issues/785)) +- [DemoApp][Library] For `textInput` component, update to v1.4.1 ([#784](https://github.com/Orange-OpenSource/ouds-flutter/issues/784)) +- [DemoApp][Library] For `phoneNumberInput` component, update to v1.3.1 ([#839](https://github.com/Orange-OpenSource/ouds-flutter/issues/839)) - [Library] Add typography Tokens component ([#905](https://github.com/Orange-OpenSource/ouds-flutter/issues/905)) - [DemoApp][Library] update icons to use the icons pack 2.3.0 ([#863](https://github.com/Orange-OpenSource/ouds-flutter/issues/863)) - [DemoApp][Library] In library, update `link` component to v2.4.0 ([#861](https://github.com/Orange-OpenSource/ouds-flutter/issues/861)) diff --git a/app/lib/l10n/gen/ouds_flutter_app_localizations.dart b/app/lib/l10n/gen/ouds_flutter_app_localizations.dart index 8cde13cf1..8d6a3cbd2 100644 --- a/app/lib/l10n/gen/ouds_flutter_app_localizations.dart +++ b/app/lib/l10n/gen/ouds_flutter_app_localizations.dart @@ -634,9 +634,15 @@ abstract class AppLocalizations { /// No description provided for @app_components_common_tinted_tech. /// /// In en, this message translates to: - /// **'Tinted icon'** + /// **'Tinted'** String get app_components_common_tinted_tech; + /// No description provided for @app_components_common_untinted_tech. + /// + /// In en, this message translates to: + /// **'Untinted'** + String get app_components_common_untinted_tech; + /// No description provided for @app_components_alert_tech. /// /// In en, this message translates to: diff --git a/app/lib/l10n/gen/ouds_flutter_app_localizations_ar.dart b/app/lib/l10n/gen/ouds_flutter_app_localizations_ar.dart index 49d23cf85..9767ded69 100644 --- a/app/lib/l10n/gen/ouds_flutter_app_localizations_ar.dart +++ b/app/lib/l10n/gen/ouds_flutter_app_localizations_ar.dart @@ -290,7 +290,10 @@ class AppLocalizationsAr extends AppLocalizations { String get app_components_common_type_tech => 'Type'; @override - String get app_components_common_tinted_tech => 'Tinted icon'; + String get app_components_common_tinted_tech => 'Tinted'; + + @override + String get app_components_common_untinted_tech => 'Untinted'; @override String get app_components_alert_tech => 'Alert'; diff --git a/app/lib/l10n/gen/ouds_flutter_app_localizations_en.dart b/app/lib/l10n/gen/ouds_flutter_app_localizations_en.dart index 74db3782a..a765d0948 100644 --- a/app/lib/l10n/gen/ouds_flutter_app_localizations_en.dart +++ b/app/lib/l10n/gen/ouds_flutter_app_localizations_en.dart @@ -290,7 +290,10 @@ class AppLocalizationsEn extends AppLocalizations { String get app_components_common_type_tech => 'Type'; @override - String get app_components_common_tinted_tech => 'Tinted icon'; + String get app_components_common_tinted_tech => 'Tinted'; + + @override + String get app_components_common_untinted_tech => 'Untinted'; @override String get app_components_alert_tech => 'Alert'; diff --git a/app/lib/l10n/gen/ouds_flutter_app_localizations_fr.dart b/app/lib/l10n/gen/ouds_flutter_app_localizations_fr.dart index 792d88730..6876f6426 100644 --- a/app/lib/l10n/gen/ouds_flutter_app_localizations_fr.dart +++ b/app/lib/l10n/gen/ouds_flutter_app_localizations_fr.dart @@ -292,7 +292,10 @@ class AppLocalizationsFr extends AppLocalizations { String get app_components_common_type_tech => 'Type'; @override - String get app_components_common_tinted_tech => 'Tinted icon'; + String get app_components_common_tinted_tech => 'Tinted'; + + @override + String get app_components_common_untinted_tech => 'Untinted'; @override String get app_components_alert_tech => 'Alert'; diff --git a/app/lib/l10n/ouds_flutter_en.arb b/app/lib/l10n/ouds_flutter_en.arb index ea1ad5fa6..04d44027d 100644 --- a/app/lib/l10n/ouds_flutter_en.arb +++ b/app/lib/l10n/ouds_flutter_en.arb @@ -138,7 +138,8 @@ "app_components_common_description_tech": "Description", "app_components_common_edgeToEdge_tech": "Edge to edge", "app_components_common_type_tech" : "Type", - "app_components_common_tinted_tech": "Tinted icon", + "app_components_common_tinted_tech": "Tinted", + "app_components_common_untinted_tech": "Untinted", "@_components_alert": {}, "app_components_alert_tech": "Alert", diff --git a/app/lib/ui/components/button/button_code_generator.dart b/app/lib/ui/components/button/button_code_generator.dart index c0bfa98cb..2064570cc 100644 --- a/app/lib/ui/components/button/button_code_generator.dart +++ b/app/lib/ui/components/button/button_code_generator.dart @@ -55,12 +55,12 @@ class ButtonCodeGenerator { case OudsButtonLayout.iconOnly: code = - """${coloredSurfaceCodeModifier(context)}$buttonSizeConstructor(\nicon: 'assets/ic_heart.svg',\nappearance: ${appearance.toString()},${fullWidthCodeModifier(context)}${loaderCodeModifier(context)}\n${disableCode(context)}"""; + """${coloredSurfaceCodeModifier(context)}$buttonSizeConstructor(\nicon: 'assets/ic_heart.svg',\nappearance: ${appearance.toString()},${fullWidthCodeModifier(context)}${tintedCodeModifier(context)}${loaderCodeModifier(context)}\n${disableCode(context)}"""; break; case OudsButtonLayout.iconAndText: code = - """${coloredSurfaceCodeModifier(context)}$buttonSizeConstructor(\nicon: 'assets/ic_heart.svg',\nlabel: "$label",\nappearance: ${appearance.toString()},${fullWidthCodeModifier(context)}${loaderCodeModifier(context)}\n${disableCode(context)}"""; + """${coloredSurfaceCodeModifier(context)}$buttonSizeConstructor(\nicon: 'assets/ic_heart.svg',\nlabel: "$label",\nappearance: ${appearance.toString()},${fullWidthCodeModifier(context)}${tintedCodeModifier(context)}${loaderCodeModifier(context)}\n${disableCode(context)}"""; break; } @@ -101,6 +101,19 @@ class ButtonCodeGenerator { } } + // Method to generate the tinted code modifier for icon-bearing buttons. + // Only emitted when untinted is selected, since tinted is the default. + static String tintedCodeModifier(BuildContext context) { + final ButtonCustomizationState? customizationState = ButtonCustomization.of( + context, + ); + if (customizationState?.isTinted == false) { + return "\ntinted: false,"; + } else { + return ""; + } + } + // Method to generate the constructor name based on the selected size: // `OudsButton.small` when small size is selected, `OudsButton` otherwise static String buttonVariant(BuildContext context) { diff --git a/app/lib/ui/components/button/button_customization.dart b/app/lib/ui/components/button/button_customization.dart index b19c2f9a8..eeaf3851a 100644 --- a/app/lib/ui/components/button/button_customization.dart +++ b/app/lib/ui/components/button/button_customization.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:ouds_flutter_demo/ui/components/button/button_enum.dart'; import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_widget_state.dart'; +import 'package:ouds_flutter_demo/ui/utilities/customizable/tinted_enum.dart'; /// Section for InheritedWidget to pass data down the widget tree class _ButtonCustomization extends InheritedWidget { @@ -37,6 +38,7 @@ class ButtonCustomizationState late final ChevronState chevronState; late final NavigationAppearanceState navigationAppearanceState; late final SizeState sizeState; + late final TintedState tintedState; @override void initState() { @@ -51,6 +53,7 @@ class ButtonCustomizationState onColoredBoxState, ); sizeState = SizeState(setState); + tintedState = TintedState(setState); } // Getter to determine if the 'OnColoredBox' should be disabled @@ -92,6 +95,16 @@ class ButtonCustomizationState ButtonEnumSize get selectedSize => sizeState.selected; set selectedSize(ButtonEnumSize value) => sizeState.selected = value; + bool get isTinted => tintedState.selected == TintedEnum.tinted; + + TintedEnum get selectedTinted => tintedState.selected; + set selectedTinted(TintedEnum value) => tintedState.selected = value; + + // Getter to determine if the icon should be shown in the demo (used to gate the tinted chip) + bool get hasIcon => + selectedLayout == ButtonEnumLayout.iconAndText || + selectedLayout == ButtonEnumLayout.iconOnly; + @override Widget build(BuildContext context) { return _ButtonCustomization(data: this, child: widget.child); @@ -187,6 +200,25 @@ class FullWidthState { } } +/// Tinted icon state management (relevant when the button displays an icon) +class TintedState { + TintedState(this._setState); + + final void Function(void Function()) _setState; + + final List _tintedList = [TintedEnum.tinted, TintedEnum.untinted]; + TintedEnum _selectedTinted = TintedEnum.tinted; + + List get list => _tintedList; + + TintedEnum get selected => _selectedTinted; + set selected(TintedEnum newValue) { + _setState(() { + _selectedTinted = newValue; + }); + } +} + /// Error handling for specific button behavior class ButtonErrorCases { // OnColoredBox behavior: Disable if appearance is 'Negative' diff --git a/app/lib/ui/components/button/button_customization_utils.dart b/app/lib/ui/components/button/button_customization_utils.dart index 67728b098..a57a3ad4a 100644 --- a/app/lib/ui/components/button/button_customization_utils.dart +++ b/app/lib/ui/components/button/button_customization_utils.dart @@ -75,15 +75,19 @@ class ButtonCustomizationUtils { } /// Determines the icon to display based on the selected layout. + /// + /// Uses a single-color, theme-tintable asset when [ButtonCustomizationState.isTinted] + /// is true, or a multi-color asset kept as-is (untinted) otherwise. This mirrors + /// the behavior used in the Link component demo. static String? getIcon( ButtonCustomizationState? customizationState, ThemeController themeController, ) { if (customizationState?.selectedLayout == ButtonEnumLayout.iconOnly || customizationState?.selectedLayout == ButtonEnumLayout.iconAndText) { - return AppAssets.icons.functionalSocialAndEngagementHeartRecommend( - themeController, - ); + return customizationState?.isTinted == true + ? AppAssets.icons.assistanceTipsAndTricks(themeController) + : AppAssets.icons.icUntintedSquare; } return null; } diff --git a/app/lib/ui/components/button/button_demo_screen.dart b/app/lib/ui/components/button/button_demo_screen.dart index bb84b7bf3..eb207cc0a 100644 --- a/app/lib/ui/components/button/button_demo_screen.dart +++ b/app/lib/ui/components/button/button_demo_screen.dart @@ -25,6 +25,7 @@ import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_chips.d import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_section.dart'; import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_switch.dart'; import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_textfield.dart'; +import 'package:ouds_flutter_demo/ui/utilities/customizable/tinted_enum.dart'; import 'package:ouds_flutter_demo/ui/utilities/detail_screen_header.dart'; import 'package:ouds_flutter_demo/ui/utilities/dismiss_keyboard.dart'; import 'package:ouds_flutter_demo/ui/utilities/light_dark_box.dart'; @@ -154,6 +155,7 @@ class _ButtonDemoState extends State<_ButtonDemo> { isLoading: loader, onPressed: onPressed, isFullWidth: isFullWidth, + tinted: customizationState?.isTinted ?? true, ) : OudsButton( label: label, @@ -162,6 +164,7 @@ class _ButtonDemoState extends State<_ButtonDemo> { isLoading: loader, onPressed: onPressed, isFullWidth: isFullWidth, + tinted: customizationState?.isTinted ?? true, ); } @@ -288,6 +291,18 @@ class _CustomizationContentState extends State<_CustomizationContent> { }); }, ), + if (customizationState.hasIcon) + CustomizableChips( + title: TintedEnum.enumName(context), + options: customizationState.tintedState.list, + selectedOption: customizationState.selectedTinted, + getText: (option) => option.stringValue(context), + onSelected: (selectedOption) { + setState(() { + customizationState.selectedTinted = selectedOption; + }); + }, + ), CustomizableTextField( title: context.l10n.app_components_common_label_label, text: customizationState.textValue, diff --git a/app/lib/ui/components/form_input/form_fields_code_generator.dart b/app/lib/ui/components/form_input/form_fields_code_generator.dart index 67773610c..c2ff051fc 100644 --- a/app/lib/ui/components/form_input/form_fields_code_generator.dart +++ b/app/lib/ui/components/form_input/form_fields_code_generator.dart @@ -15,11 +15,20 @@ import 'package:ouds_flutter_demo/ui/components/form_input/form_fields_customiza import 'package:ouds_flutter_demo/ui/components/form_input/form_fields_enum.dart'; class FormFieldsCodeGenerator { - static String updateCode(BuildContext context, FormFieldsTypeEnum inputTypeEnum) { - final FormFieldsCustomizationState? state = FormFieldsCustomization.of(context); - String boolPropertiesCode = generateBoolPropertiesCode(state, inputTypeEnum); + static String updateCode( + BuildContext context, + FormFieldsTypeEnum inputTypeEnum, + ) { + final FormFieldsCustomizationState? state = FormFieldsCustomization.of( + context, + ); + String boolPropertiesCode = generateBoolPropertiesCode( + state, + inputTypeEnum, + ); String linkCode = generateLinkCode(state); - String controllerAndFocusPropertiesCode = "controller: controller,\nfocusNode: textInputFocus,"; + String controllerAndFocusPropertiesCode = + "controller: controller,\nfocusNode: textInputFocus,"; List codeParts; String decoration = decorationCode( @@ -31,6 +40,8 @@ class FormFieldsCodeGenerator { state?.helperText ?? '', state?.hasTrailingIcon, state?.hasLeadingIcon, + state?.trailingIconTinted ?? true, + state?.leadingIconTinted ?? true, state?.hasLoader ?? false, state?.hasOutlined ?? false, state?.hasError == true, @@ -40,13 +51,31 @@ class FormFieldsCodeGenerator { switch (inputTypeEnum) { case FormFieldsTypeEnum.textInput: - codeParts = ["OudsTextField(", controllerAndFocusPropertiesCode, if (boolPropertiesCode.trim().isNotEmpty) boolPropertiesCode, linkCode, decoration, "),"]; + codeParts = [ + "OudsTextField(", + controllerAndFocusPropertiesCode, + if (boolPropertiesCode.trim().isNotEmpty) boolPropertiesCode, + linkCode, + decoration, + "),", + ]; break; case FormFieldsTypeEnum.phoneNumberInput: - codeParts = ["OudsPhoneNumberInput(", if (boolPropertiesCode.trim().isNotEmpty) boolPropertiesCode, decoration, "),"]; + codeParts = [ + "OudsPhoneNumberInput(", + if (boolPropertiesCode.trim().isNotEmpty) boolPropertiesCode, + decoration, + "),", + ]; break; case FormFieldsTypeEnum.passwordInput: - codeParts = ["OudsPasswordInput(", controllerAndFocusPropertiesCode, if (boolPropertiesCode.trim().isNotEmpty) boolPropertiesCode, decoration, "),"]; + codeParts = [ + "OudsPasswordInput(", + controllerAndFocusPropertiesCode, + if (boolPropertiesCode.trim().isNotEmpty) boolPropertiesCode, + decoration, + "),", + ]; break; } @@ -59,7 +88,10 @@ class FormFieldsCodeGenerator { " onPressed: () {},\n),"; } - static String generateBoolPropertiesCode(FormFieldsCustomizationState? state, FormFieldsTypeEnum inputTypeEnum) { + static String generateBoolPropertiesCode( + FormFieldsCustomizationState? state, + FormFieldsTypeEnum inputTypeEnum, + ) { if (state == null) return ""; List lines = []; @@ -72,8 +104,11 @@ class FormFieldsCodeGenerator { lines.add('readOnly: true,'); } - if (state.hasCountrySelector == true && inputTypeEnum == FormFieldsTypeEnum.phoneNumberInput) { - lines.add('countrySelector: CountrySelector(countryFilter:\n CountryFilter.custom,\n codes: ["fr", "tn", "us"],\n onCountryChanged: (country) {},\n ),'); + if (state.hasCountrySelector == true && + inputTypeEnum == FormFieldsTypeEnum.phoneNumberInput) { + lines.add( + 'countrySelector: CountrySelector(countryFilter:\n CountryFilter.custom,\n codes: ["fr", "tn", "us"],\n onCountryChanged: (country) {},\n ),', + ); } return lines.join("\n"); @@ -88,6 +123,8 @@ class FormFieldsCodeGenerator { String helperText, bool? suffixIcon, bool? prefixIcon, + bool trailingIconTinted, + bool leadingIconTinted, bool hasLoader, bool? hasOutlined, bool hasError, @@ -108,6 +145,11 @@ class FormFieldsCodeGenerator { switch (inputTypeEnum) { case FormFieldsTypeEnum.phoneNumberInput: + if (prefixIcon == true) { + lines.add( + " prefixIcon: OudsTextInputPrefixIcon(\n icon: 'assets/ic_heart.svg',\n ),", + ); + } break; case FormFieldsTypeEnum.passwordInput: decorationClass = "OudsPasswordInputDecoration"; @@ -115,10 +157,20 @@ class FormFieldsCodeGenerator { if (hasError) lines.add(' errorText: "Please enter your password.",'); break; default: - if (suffixIcon == true) lines.add(" suffixIcon: 'assets/ic_heart.svg',\n onSuffixPressed: () {},"); - if (prefixIcon == true) lines.add(" prefixIcon: 'assets/ic_heart.svg',"); + if (suffixIcon == true) { + lines.add( + " suffixIcon: OudsTextInputSuffixIconButton(\n icon: 'assets/ic_heart.svg',\n tinted: $trailingIconTinted,\n onPressed: () {},\n ),", + ); + } + if (prefixIcon == true) { + lines.add( + " prefixIcon: OudsTextInputPrefixIcon(\n icon: 'assets/ic_heart.svg',\n tinted: $leadingIconTinted,\n ),", + ); + } if (hasError) lines.add(' errorText: "This field can’t..",'); - if (hasConstrainedMaxWidth == true) lines.add(' hasConstrainedMaxWidth: true,'); + if (hasConstrainedMaxWidth == true) { + lines.add(' hasConstrainedMaxWidth: true,'); + } } if (lines.isEmpty) return "decoration: $decorationClass(),"; diff --git a/app/lib/ui/components/form_input/form_fields_customization.dart b/app/lib/ui/components/form_input/form_fields_customization.dart index 9752764d6..b1d8bdea8 100644 --- a/app/lib/ui/components/form_input/form_fields_customization.dart +++ b/app/lib/ui/components/form_input/form_fields_customization.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:ouds_core/components/form_input/internal/ouds_form_input_decoration.dart'; import 'package:ouds_flutter_demo/ui/components/form_input/form_fields_enum.dart'; +import 'package:ouds_flutter_demo/ui/theme/theme_controller.dart'; +import 'package:ouds_flutter_demo/ui/utilities/app_assets.dart'; import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_widget_state.dart'; /// Section for InheritedWidget to pass data down the widget tree class _FormFieldsCustomization extends InheritedWidget { - const _FormFieldsCustomization({ - required super.child, - required this.data, - }); + const _FormFieldsCustomization({required super.child, required this.data}); final FormFieldsCustomizationState data; @@ -30,12 +30,15 @@ class FormFieldsCustomization extends StatefulWidget { FormFieldsCustomizationState createState() => FormFieldsCustomizationState(); static FormFieldsCustomizationState? of(BuildContext context) { - return (context.dependOnInheritedWidgetOfExactType<_FormFieldsCustomization>())?.data; + return (context + .dependOnInheritedWidgetOfExactType<_FormFieldsCustomization>()) + ?.data; } } /// TextInput customization state management -class FormFieldsCustomizationState extends CustomizationWidgetState { +class FormFieldsCustomizationState + extends CustomizationWidgetState { late final ErrorState errorState; late final LeadingIconState leadingIconState; late final TrailingIconState trailingIconState; @@ -89,28 +92,83 @@ class FormFieldsCustomizationState extends CustomizationWidgetState leadingIconState.value; - set hasLeadingIcon(bool value) => leadingIconState.value = value; + bool get hasLeadingIcon => + leadingIconState.selected != LeadingIconOptionEnum.none; + set hasLeadingIcon(bool value) => leadingIconState.selected = value + ? LeadingIconOptionEnum.tinted + : LeadingIconOptionEnum.none; // Proxy getters and setters to expose state values directly - bool get hasTrailingIcon => trailingIconState.value; - set hasTrailingIcon(bool value) => trailingIconState.value = value; + bool get hasTrailingIcon => + trailingIconState.selected != TrailingIconOptionEnum.none; + + // Proxy getters and setters to expose the combined leading icon option directly + LeadingIconOptionEnum get selectedLeadingIcon => leadingIconState.selected; + set selectedLeadingIcon(LeadingIconOptionEnum value) => + leadingIconState.selected = value; + + // Proxy getters and setters to expose the combined trailing action option directly + TrailingIconOptionEnum get selectedTrailingIcon => trailingIconState.selected; + set selectedTrailingIcon(TrailingIconOptionEnum value) => + trailingIconState.selected = value; + + // Proxy getters and setters to expose tinted state values directly + bool get leadingIconTinted => + leadingIconState.selected == LeadingIconOptionEnum.tinted; + + bool get trailingIconTinted => + trailingIconState.selected == TrailingIconOptionEnum.tinted; + + /// Returns the prefixIcon as OudsTextInputPrefixIcon for use in decoration. + /// This must be called within a build context where the theme controller is available. + /// + /// Uses a single-color, theme-tintable asset when tinted, or the shared + /// multi-color `icUntintedSquare` asset (kept as-is) when untinted — mirroring + /// the behavior used in the Button and Link component demos. + OudsTextInputPrefixIcon? getPrefixIcon(ThemeController themeController) => + hasLeadingIcon + ? OudsTextInputPrefixIcon( + icon: leadingIconTinted + ? AppAssets.icons.assistanceTipsAndTricks(themeController) + : AppAssets.icons.icUntintedSquare, + tinted: leadingIconTinted, + ) + : null; + + /// Returns the suffixIcon as OudsTextInputSuffixIconButton for use in decoration. + /// This must be called within a build context where the theme controller is available. + OudsTextInputSuffixIconButton? getSuffixIcon( + ThemeController themeController, + ) => hasTrailingIcon + ? OudsTextInputSuffixIconButton( + icon: AppAssets.icons.assistanceTipsAndTricks(themeController), + tinted: trailingIconTinted, + onPressed: () {}, + ) + : null; // Proxy getters and setters to expose state values directly bool get hasLoader => loaderState.value; set hasLoader(bool value) => loaderState.value = value; + /// Returns the loader as OudsTextInputLoader for use in decoration. + OudsTextInputLoader? get loader => + hasLoader ? const OudsTextInputLoader() : null; + // Proxy getters and setters to expose state values directly bool get hasOutlined => outlinedState.value; set hasOutlined(bool value) => outlinedState.value = value; // Proxy getters and setters to expose the 'constrainedMaxWidthState' value directly. bool get hasConstrainedMaxWidth => constrainedMaxWidthState.value; - set hasConstrainedMaxWidth(bool value) => constrainedMaxWidthState.value = value; + set hasConstrainedMaxWidth(bool value) => + constrainedMaxWidthState.value = value; // Proxy getters and setters to expose the 'labelTextState' value directly. String get labelText => labelTextState.value; @@ -175,7 +233,10 @@ class FormFieldsCustomizationState extends CustomizationWidgetState _hasLeadingIcon; - set value(bool newValue) { + final List _optionList = const [ + LeadingIconOptionEnum.none, + LeadingIconOptionEnum.tinted, + LeadingIconOptionEnum.untinted, + ]; + LeadingIconOptionEnum _selected = LeadingIconOptionEnum.none; + + List get list => _optionList; + + LeadingIconOptionEnum get selected => _selected; + set selected(LeadingIconOptionEnum newValue) { _setState(() { - _hasLeadingIcon = newValue; + _selected = newValue; }); } } /// TrailingIcon State Management +/// +/// Combines presence and tinting into a single [TrailingIconOptionEnum] +/// selection: [TrailingIconOptionEnum.none] hides the trailing action, +/// [TrailingIconOptionEnum.tinted] displays it tinted with the theme color. class TrailingIconState { TrailingIconState(this._setState); final void Function(void Function()) _setState; - bool _hasTrailingIcon = false; - bool get value => _hasTrailingIcon; - set value(bool newValue) { + final List _optionList = [ + TrailingIconOptionEnum.none, + TrailingIconOptionEnum.tinted, + ]; + TrailingIconOptionEnum _selected = TrailingIconOptionEnum.none; + + List get list => _optionList; + + TrailingIconOptionEnum get selected => _selected; + set selected(TrailingIconOptionEnum newValue) { _setState(() { - _hasTrailingIcon = newValue; + _selected = newValue; }); } } @@ -287,7 +374,8 @@ class ConstrainedMaxWidthState { /// LabelText State Management class LabelTextState { - LabelTextState(this._setState, this.inputType) : _labelTextValue = inputType.labelValue; + LabelTextState(this._setState, this.inputType) + : _labelTextValue = inputType.labelValue; final void Function(void Function()) _setState; final FormFieldsTypeEnum inputType; @@ -355,7 +443,8 @@ class PlaceholderTextState { /// HelperText State Management class HelperTextState { - HelperTextState(this._setState, this.inputType) : _helperTextValue = inputType.helperValue; + HelperTextState(this._setState, this.inputType) + : _helperTextValue = inputType.helperValue; final void Function(void Function()) _setState; final FormFieldsTypeEnum inputType; @@ -515,7 +604,10 @@ class FormFieldsErrorCases { /// /// @param hasReadOnly Whether the input is currently read-only. /// @return `true` if the error should be shown, `false` otherwise. - static bool isCountrySelectorWhenReadOnlyAndEnable(bool hasReadOnly, bool hasEnabled) { + static bool isCountrySelectorWhenReadOnlyAndEnable( + bool hasReadOnly, + bool hasEnabled, + ) { return hasReadOnly || !hasEnabled; } @@ -526,7 +618,9 @@ class FormFieldsErrorCases { /// /// @param placeholderTextState The current state of the placeholder text. /// @return `true` if the placeholder is not empty, indicating the widget should be enabled, `false` otherwise. - static bool isEnabledWhenPlaceHolderIsNotEmpty(PlaceholderTextState placeholderTextState) { + static bool isEnabledWhenPlaceHolderIsNotEmpty( + PlaceholderTextState placeholderTextState, + ) { return placeholderTextState.value.isNotEmpty; } diff --git a/app/lib/ui/components/form_input/form_fields_enum.dart b/app/lib/ui/components/form_input/form_fields_enum.dart index 687aa0596..220a8bb78 100644 --- a/app/lib/ui/components/form_input/form_fields_enum.dart +++ b/app/lib/ui/components/form_input/form_fields_enum.dart @@ -1,9 +1,8 @@ +import 'package:flutter/widgets.dart'; +import 'package:ouds_flutter_demo/l10n/app_localizations.dart'; + /// Enum to choose wich input type to use -enum FormFieldsTypeEnum { - textInput, - phoneNumberInput, - passwordInput; -} +enum FormFieldsTypeEnum { textInput, phoneNumberInput, passwordInput } extension CustomLabelLayout on FormFieldsTypeEnum { String get labelValue { @@ -30,3 +29,58 @@ extension CustomHelperLayout on FormFieldsTypeEnum { } } } + +/// Represents the leading icon customization option: hidden, tinted (single +/// color, following the theme color) or untinted (original, potentially +/// multi-color, asset colors). Merges presence and tinting into a single +/// selector. +enum LeadingIconOptionEnum { + none, + tinted, + untinted; + + static String enumName(BuildContext context) { + return context.l10n.app_components_textInput_leadingIcon_label; + } +} + +extension CustomLeadingIconOption on LeadingIconOptionEnum { + String stringValue(BuildContext context) { + final l10n = context.l10n; + switch (this) { + case LeadingIconOptionEnum.none: + return l10n.app_components_common_none_label; + case LeadingIconOptionEnum.tinted: + return l10n.app_components_common_tinted_tech; + case LeadingIconOptionEnum.untinted: + return l10n.app_components_common_untinted_tech; + } + } +} + +/// Represents the trailing action customization option: hidden or tinted. +/// +/// Unlike the leading icon, the trailing action is rendered through an +/// [OudsButton], whose own `tinted` handling already provides the untinted +/// (brand-background) rendering when needed, so only presence and tinted +/// states are exposed here. +enum TrailingIconOptionEnum { + none, + tinted; + + static String enumName(BuildContext context) { + return context.l10n.app_components_textInput_trailingAction_label; + } +} + +extension CustomTrailingIconOption on TrailingIconOptionEnum { + String stringValue(BuildContext context) { + final l10n = context.l10n; + switch (this) { + case TrailingIconOptionEnum.none: + return l10n.app_components_common_none_label; + case TrailingIconOptionEnum.tinted: + return l10n.app_components_common_tinted_tech; + } + } +} diff --git a/app/lib/ui/components/form_input/password_input/password_input_demo_screen.dart b/app/lib/ui/components/form_input/password_input/password_input_demo_screen.dart index e2e43932b..6c94eae17 100644 --- a/app/lib/ui/components/form_input/password_input/password_input_demo_screen.dart +++ b/app/lib/ui/components/form_input/password_input/password_input_demo_screen.dart @@ -221,7 +221,7 @@ class _TextInputDemoState extends State<_TextInputDemo> { errorText: customizationState.hasError ? context.l10n.app_components_passwordInput_error_label : null, - loader: customizationState.hasLoader, + loader: customizationState.loader, outlined: customizationState.hasOutlined, constrainedMaxWidth: customizationState.hasConstrainedMaxWidth ? true diff --git a/app/lib/ui/components/form_input/phone_number/phone_number_input_demo_screen.dart b/app/lib/ui/components/form_input/phone_number/phone_number_input_demo_screen.dart index 479b1ed11..c7f501bda 100644 --- a/app/lib/ui/components/form_input/phone_number/phone_number_input_demo_screen.dart +++ b/app/lib/ui/components/form_input/phone_number/phone_number_input_demo_screen.dart @@ -244,12 +244,14 @@ class _PhoneNumberInputDemoState extends State<_PhoneNumberInputDemo> { : null, hasPrefix: customizationState.hasPrefix, prefixIcon: customizationState.hasLeadingIcon - ? AppAssets.icons.deviceSmartphone(themeController) + ? OudsTextInputPrefixIcon( + icon: AppAssets.icons.deviceSmartphone(themeController), + ) : null, errorText: customizationState.hasError ? context.l10n.app_components_phoneNumberInput_error_label : null, - loader: customizationState.hasLoader, + loader: customizationState.loader, outlined: customizationState.hasOutlined, constrainedMaxWidth: customizationState.hasConstrainedMaxWidth ? true diff --git a/app/lib/ui/components/form_input/text_input/text_input_demo_screen.dart b/app/lib/ui/components/form_input/text_input/text_input_demo_screen.dart index d717d7dfb..777f79130 100644 --- a/app/lib/ui/components/form_input/text_input/text_input_demo_screen.dart +++ b/app/lib/ui/components/form_input/text_input/text_input_demo_screen.dart @@ -23,8 +23,8 @@ import 'package:ouds_flutter_demo/ui/components/form_input/form_fields_customiza import 'package:ouds_flutter_demo/ui/components/form_input/form_fields_customization_utils.dart'; import 'package:ouds_flutter_demo/ui/components/form_input/form_fields_enum.dart'; import 'package:ouds_flutter_demo/ui/theme/theme_controller.dart'; -import 'package:ouds_flutter_demo/ui/utilities/app_assets.dart'; import 'package:ouds_flutter_demo/ui/utilities/code.dart'; +import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_chips.dart'; import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_section.dart'; import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_switch.dart'; import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_textfield.dart'; @@ -236,35 +236,22 @@ class _TextInputDemoState extends State<_TextInputDemo> { customizationState, ) : null, - suffixIcon: customizationState.hasTrailingIcon - ? AppAssets.icons.functionalSocialAndEngagementHeartRecommend( - themeController, - ) - : null, + suffixIcon: customizationState.getSuffixIcon(themeController), suffix: customizationState.suffixText.isNotEmpty ? FormFieldsCustomizationUtils.getSuffixText(customizationState) : null, - prefixIcon: customizationState.hasLeadingIcon - ? AppAssets.icons.functionalSocialAndEngagementHeartRecommend( - themeController, - ) - : null, + prefixIcon: customizationState.getPrefixIcon(themeController), prefix: customizationState.prefixText.isNotEmpty ? FormFieldsCustomizationUtils.getPrefixText(customizationState) : null, errorText: customizationState.hasError ? context.l10n.app_components_textInput_error_label : null, - loader: customizationState.hasLoader, + loader: customizationState.loader, outlined: customizationState.hasOutlined, constrainedMaxWidth: customizationState.hasConstrainedMaxWidth ? true : false, - onSuffixPressed: () { - /// - /// To Be implemented if needed - /// - }, ), ), ); @@ -355,18 +342,22 @@ class _CustomizationContentState extends State<_CustomizationContent> { customizationState.hasError = value; }, ), - CustomizableSwitch( - title: context.l10n.app_components_textInput_leadingIcon_label, - value: customizationState.hasLeadingIcon, - onChanged: (value) { - customizationState.hasLeadingIcon = value; + CustomizableChips( + title: LeadingIconOptionEnum.enumName(context), + options: customizationState.leadingIconState.list, + selectedOption: customizationState.selectedLeadingIcon, + getText: (option) => option.stringValue(context), + onSelected: (selectedOption) { + customizationState.selectedLeadingIcon = selectedOption; }, ), - CustomizableSwitch( - title: context.l10n.app_components_textInput_trailingAction_label, - value: customizationState.hasTrailingIcon, - onChanged: (value) { - customizationState.hasTrailingIcon = value; + CustomizableChips( + title: TrailingIconOptionEnum.enumName(context), + options: customizationState.trailingIconState.list, + selectedOption: customizationState.selectedTrailingIcon, + getText: (option) => option.stringValue(context), + onSelected: (selectedOption) { + customizationState.selectedTrailingIcon = selectedOption; }, ), CustomizableSwitch( diff --git a/app/lib/ui/components/link/link_customization.dart b/app/lib/ui/components/link/link_customization.dart index 8534b7aad..ee1eed2ca 100644 --- a/app/lib/ui/components/link/link_customization.dart +++ b/app/lib/ui/components/link/link_customization.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:ouds_flutter_demo/ui/components/link/link_enum.dart'; import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_widget_state.dart'; +import 'package:ouds_flutter_demo/ui/utilities/customizable/tinted_enum.dart'; /// Section for InheritedWidget to pass data down the widget tree class _LinkCustomization extends InheritedWidget { @@ -58,8 +59,10 @@ class LinkCustomizationState LinkEnumDensity get selectedDensity => densityState.selected; set selectedDensity(LinkEnumDensity value) => densityState.selected = value; - bool get isTinted => tintedState.value; - set isTinted(bool value) => tintedState.value = value; + bool get isTinted => tintedState.selected == TintedEnum.tinted; + + TintedEnum get selectedTinted => tintedState.selected; + set selectedTinted(TintedEnum value) => tintedState.selected = value; @override Widget build(BuildContext context) { @@ -138,12 +141,16 @@ class TintedState { TintedState(this._setState); final void Function(void Function()) _setState; - bool _tinted = true; - bool get value => _tinted; - set value(bool newValue) { + final List _tintedList = [TintedEnum.tinted, TintedEnum.untinted]; + TintedEnum _selectedTinted = TintedEnum.tinted; + + List get list => _tintedList; + + TintedEnum get selected => _selectedTinted; + set selected(TintedEnum newValue) { _setState(() { - _tinted = newValue; + _selectedTinted = newValue; }); } } diff --git a/app/lib/ui/components/link/link_demo_screen.dart b/app/lib/ui/components/link/link_demo_screen.dart index a796d7b6c..71a137754 100644 --- a/app/lib/ui/components/link/link_demo_screen.dart +++ b/app/lib/ui/components/link/link_demo_screen.dart @@ -24,6 +24,7 @@ import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_chips.d import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_section.dart'; import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_switch.dart'; import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_textfield.dart'; +import 'package:ouds_flutter_demo/ui/utilities/customizable/tinted_enum.dart'; import 'package:ouds_flutter_demo/ui/utilities/detail_screen_header.dart'; import 'package:ouds_flutter_demo/ui/utilities/dismiss_keyboard.dart'; import 'package:ouds_flutter_demo/ui/utilities/light_dark_box.dart'; @@ -245,12 +246,14 @@ class _CustomizationContentState extends State<_CustomizationContent> { }, ), if (customizationState.selectedLayout == LinkEnumLayout.textAndIcon) - CustomizableSwitch( - title: context.l10n.app_components_common_tinted_tech, - value: customizationState.isTinted, - onChanged: (value) { + CustomizableChips( + title: TintedEnum.enumName(context), + options: customizationState.tintedState.list, + selectedOption: customizationState.selectedTinted, + getText: (option) => option.stringValue(context), + onSelected: (selectedOption) { setState(() { - customizationState.isTinted = value; + customizationState.selectedTinted = selectedOption; }); }, ), diff --git a/app/lib/ui/utilities/customizable/customizable_textfield.dart b/app/lib/ui/utilities/customizable/customizable_textfield.dart index dd1a5e5a7..0b56b78d0 100644 --- a/app/lib/ui/utilities/customizable/customizable_textfield.dart +++ b/app/lib/ui/utilities/customizable/customizable_textfield.dart @@ -266,19 +266,21 @@ class CustomizableTextFieldState extends State { decoration: OudsInputDecoration( hintText: '', labelText: widget.title, - suffixIcon: AppAssets.icons.functionalActionsDelete( - themeController, + suffixIcon: OudsTextInputSuffixIconButton( + icon: AppAssets.icons.functionalActionsDelete( + themeController, + ), + onPressed: () { + _textController.clear(); + if (!widget.focusNode.hasFocus) { + widget.focusNode.unfocus(); + } + setState(() {}); + }, ), suffix: widget.suffixText, helperText: widget.helperText, errorText: widget.errorText, - onSuffixPressed: () { - _textController.clear(); - if (!widget.focusNode.hasFocus) { - widget.focusNode.unfocus(); - } - setState(() {}); - }, ), keyboardType: widget.keyboardType, ); diff --git a/app/lib/ui/utilities/customizable/tinted_enum.dart b/app/lib/ui/utilities/customizable/tinted_enum.dart new file mode 100644 index 000000000..d20c9fe22 --- /dev/null +++ b/app/lib/ui/utilities/customizable/tinted_enum.dart @@ -0,0 +1,41 @@ +// +// Software Name: OUDS Flutter +// 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. +// +// Software description: Flutter library of reusable graphical components +// + +import 'package:flutter/cupertino.dart'; +import 'package:ouds_flutter_demo/l10n/app_localizations.dart'; + +/// Represents whether an icon should be displayed tinted (single color, following +/// the theme color) or untinted (original, potentially multi-color, asset colors). +/// +/// Shared between components exposing a `tinted` customization option, such as +/// [OudsButton] and [OudsLink]. +enum TintedEnum { + tinted, + untinted; + + static String enumName(BuildContext context) { + return context.l10n.app_components_common_tinted_tech; + } +} + +extension CustomElementTinted on TintedEnum { + String stringValue(BuildContext context) { + final l10n = context.l10n; + + switch (this) { + case TintedEnum.tinted: + return l10n.app_components_common_tinted_tech; + case TintedEnum.untinted: + return l10n.app_components_common_untinted_tech; + } + } +} diff --git a/ouds_core/CHANGELOG.md b/ouds_core/CHANGELOG.md index 76d9aa7b3..2b1519bbf 100644 --- a/ouds_core/CHANGELOG.md +++ b/ouds_core/CHANGELOG.md @@ -8,12 +8,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - [Library] Create component - `List item` ([#54](https://github.com/Orange-OpenSource/ouds-flutter/issues/54)) ### Changed +- [Library] For `Button` component, add with `tinted parameter ([#912](https://github.com/Orange-OpenSource/ouds-flutter/issues/912)) +- [Library] For `passwordInput` component, update to v1.3.1 ([#785](https://github.com/Orange-OpenSource/ouds-flutter/issues/785)) +- [Library] For `textInput` component, update to v1.4.1 ([#784](https://github.com/Orange-OpenSource/ouds-flutter/issues/784)) +- [Library] For `phoneNumberInput` component, update to v1.3.1 ([#839](https://github.com/Orange-OpenSource/ouds-flutter/issues/839)) - [Library] Add typography Tokens component ([#905](https://github.com/Orange-OpenSource/ouds-flutter/issues/905)) - [Library] update icons to use the icons pack 2.3.0 ([#863](https://github.com/Orange-OpenSource/ouds-flutter/issues/863)) - [Library] In library, update `link` component to v2.4.0 ([#861](https://github.com/Orange-OpenSource/ouds-flutter/issues/861)) - [Library] In library, update `link` component to v2.3.0 ([#838](https://github.com/Orange-OpenSource/ouds-flutter/issues/838)) - [Library] For `button` component, update to version 3.3.0 ([#832](https://github.com/Orange-OpenSource/ouds-flutter/issues/832)) - [Library] Update `progress-indicator`: add size parameter to OudsCircularProgressIndicator for button integration ([#876](https://github.com/Orange-OpenSource/ouds-flutter/issues/876)) +- [Library] deps update dependency `dlibphonenumber` to 1.1.70 ### Fixed - [Library] Invalid value: Not in inclusive range 0..2: 3 dans OudsTabBar` ([#896](https://github.com/Orange-OpenSource/ouds-flutter/issues/896)) diff --git a/ouds_core/lib/components/button/ouds_button.dart b/ouds_core/lib/components/button/ouds_button.dart index 4d39ba311..ac8efc659 100644 --- a/ouds_core/lib/components/button/ouds_button.dart +++ b/ouds_core/lib/components/button/ouds_button.dart @@ -183,6 +183,11 @@ class OudsButton extends StatefulWidget { final String? package; final bool? isFullWidth; + /// Controls whether the icon should be tinted with the theme color. + /// Defaults to `true`. + /// When set to `false`, the icon displays with its original colors (useful for multi-color icons). + final bool tinted; + /// The button size based on its [OudsButtonSize], set to [OudsButtonSize.defaultSize] by default. final OudsButtonSize _size; @@ -208,6 +213,7 @@ class OudsButton extends StatefulWidget { required this.appearance, this.package, this.isFullWidth = false, + this.tinted = true, }) : _size = OudsButtonSize.defaultSize, _component = OudsButtonComponent.defaultButton, _navigationLayout = null, @@ -237,6 +243,7 @@ class OudsButton extends StatefulWidget { required this.appearance, this.package, this.isFullWidth = false, + this.tinted = true, }) : _size = OudsButtonSize.small, _component = OudsButtonComponent.defaultButton, _navigationLayout = null, @@ -260,7 +267,8 @@ class OudsButton extends StatefulWidget { }) : _size = size, _component = OudsButtonComponent.navigationButton, _navigationLayout = navigationLayout, - _semanticsLabel = semanticsLabel; + _semanticsLabel = semanticsLabel, + tinted = true; @override State createState() => _OudsButtonState(); @@ -976,6 +984,11 @@ class _OudsButtonState extends State { final OudsButtonLayout layout, final OudsButtonControlState buttonState, ) { + final bool isTinted = widget.tinted; + final Color? iconColor = isTinted + ? OudsButtonIconModifier.getIconColor(context, buttonState, appearance) + : null; + // navigation button final textScaleFactor = MediaQuery.textScalerOf(context).scale(1.0); final baseIconSize = OudsButtonIconModifier.getIconSize( @@ -995,14 +1008,9 @@ class _OudsButtonState extends State { fit: BoxFit.contain, width: scaledIconSize, height: scaledIconSize, - colorFilter: ColorFilter.mode( - OudsButtonIconModifier.getIconColor( - context, - buttonState, - appearance, - ), - BlendMode.srcIn, - ), + colorFilter: iconColor != null + ? ColorFilter.mode(iconColor, BlendMode.srcIn) + : null, ); case OudsNavigationButtonLayout.previous: @@ -1021,20 +1029,15 @@ class _OudsButtonState extends State { layout, size: widget._size, ), - colorFilter: ColorFilter.mode( - OudsButtonIconModifier.getIconColor( - context, - buttonState, - appearance, - ), - BlendMode.srcIn, - ), + colorFilter: iconColor != null + ? ColorFilter.mode(iconColor, BlendMode.srcIn) + : null, ); case null: throw UnimplementedError(); } } - return SvgPicture.asset( + final Widget iconWidget = SvgPicture.asset( excludeFromSemantics: true, package: widget.package, assetName, @@ -1050,10 +1053,18 @@ class _OudsButtonState extends State { layout, size: widget._size, ), - colorFilter: ColorFilter.mode( - OudsButtonIconModifier.getIconColor(context, buttonState, appearance), - BlendMode.srcIn, - ), + colorFilter: iconColor != null + ? ColorFilter.mode(iconColor, BlendMode.srcIn) + : null, + ); + + // When untinted, the icon asset is expected to be a plain white shape + // (no embedded background), so it needs a brand-colored background to + // remain visible — matching the behavior of OudsLink and OudsListItem. + if (isTinted) return iconWidget; + return Container( + color: OudsTheme.of(context).colorScheme(context).surfaceBrandPrimary, + child: iconWidget, ); } } diff --git a/ouds_core/lib/components/form_input/internal/modifier/ouds_form_input_background_modifier.dart b/ouds_core/lib/components/form_input/internal/modifier/ouds_form_input_background_modifier.dart index 521f3adb3..a96157435 100644 --- a/ouds_core/lib/components/form_input/internal/modifier/ouds_form_input_background_modifier.dart +++ b/ouds_core/lib/components/form_input/internal/modifier/ouds_form_input_background_modifier.dart @@ -22,7 +22,11 @@ class OudsFormFieldsBackgroundColorModifier { OudsFormFieldsBackgroundColorModifier(this.context); - Color? getBackgroundColor(OudsFormFieldsControlState state, [bool isError = false, bool? outlined]) { + Color? getBackgroundColor( + OudsFormFieldsControlState state, [ + bool isError = false, + bool? outlined, + ]) { final theme = OudsTheme.of(context); final error = isError == true; final defaultStyle = outlined == false; @@ -31,36 +35,51 @@ class OudsFormFieldsBackgroundColorModifier { // Error switch (state) { case OudsFormFieldsControlState.enabled: - return defaultStyle ? theme.colorScheme(context).surfaceStatusNegativeMuted : null; + return defaultStyle + ? theme.colorScheme(context).surfaceStatusNegativeMuted + : null; case OudsFormFieldsControlState.disabled: - throw UnimplementedError("Error status for Disabled state is not relevant"); + throw UnimplementedError( + "Error status for Disabled state is not relevant", + ); case OudsFormFieldsControlState.hovered: return theme.colorScheme(context).surfaceStatusNegativeMuted; case OudsFormFieldsControlState.pressed: // TODO: Handle this case. throw UnimplementedError(); case OudsFormFieldsControlState.focused: - return defaultStyle ? theme.colorScheme(context).surfaceStatusNegativeMuted : null; + return defaultStyle + ? theme.colorScheme(context).surfaceStatusNegativeMuted + : null; case OudsFormFieldsControlState.readOnly: - throw UnimplementedError("Error status for Read only state is not relevant"); + throw UnimplementedError( + "Error status for Read only state is not relevant", + ); case OudsFormFieldsControlState.loading: - throw UnimplementedError("Error status for Loading state is not relevant"); + throw UnimplementedError( + "Error status for Loading state is not relevant", + ); } } else { switch (state) { case OudsFormFieldsControlState.enabled: - return defaultStyle ? theme.colorScheme(context).actionSupportEnabled : null; + return defaultStyle + ? theme.colorScheme(context).actionSupportEnabled + : null; case OudsFormFieldsControlState.disabled: return theme.colorScheme(context).actionSupportDisabled; case OudsFormFieldsControlState.hovered: return theme.colorScheme(context).actionSupportHover; case OudsFormFieldsControlState.pressed: - // TODO: Handle this case. - throw UnimplementedError(); + return theme.colorScheme(context).actionSupportPressed; case OudsFormFieldsControlState.focused: - return defaultStyle ? theme.colorScheme(context).actionSupportPressed : null; + return defaultStyle + ? theme.colorScheme(context).actionSupportPressed + : null; case OudsFormFieldsControlState.readOnly: - return defaultStyle ? null : theme.colorScheme(context).actionSupportDisabled; + return defaultStyle + ? null + : theme.colorScheme(context).actionSupportDisabled; case OudsFormFieldsControlState.loading: return theme.colorScheme(context).actionSupportLoading; } diff --git a/ouds_core/lib/components/form_input/internal/ouds_form_input_decoration.dart b/ouds_core/lib/components/form_input/internal/ouds_form_input_decoration.dart index 1f9325ca2..dfe6e8287 100644 --- a/ouds_core/lib/components/form_input/internal/ouds_form_input_decoration.dart +++ b/ouds_core/lib/components/form_input/internal/ouds_form_input_decoration.dart @@ -16,6 +16,71 @@ library; import 'dart:ui'; +/// Configuration for a leading (prefix) icon in a text input. +/// +/// Provides properties to customize the icon and its tinting behavior. +/// +/// Parameters: +/// - [icon]: The name or path of the SVG asset to display. +/// - [tinted]: When `true` (default), the icon is colored with the theme's foreground color. +/// When `false`, the icon displays with its original colors (useful for multi-color icons). +/// Note: Untinted icons must ensure sufficient contrast with the background for accessibility. +class OudsTextInputPrefixIcon { + /// The name or path of the SVG asset to display. + final String icon; + + /// Controls whether the icon should be tinted with the theme color. + /// Defaults to `true`. + final bool tinted; + + const OudsTextInputPrefixIcon({required this.icon, this.tinted = true}); +} + +/// Configuration for a trailing (suffix) icon button in a text input. +/// +/// Provides properties to customize the icon, its tinting behavior, and the press callback. +/// +/// Parameters: +/// - [icon]: The name or path of the SVG asset to display. +/// - [tinted]: When `true` (default), the icon is colored with the theme's foreground color. +/// When `false`, the icon displays with its original colors (useful for multi-color icons). +/// Note: Untinted icons must ensure sufficient contrast with the background for accessibility. +/// - [onPressed]: Callback invoked when the icon button is pressed. If `null`, the button is disabled. +class OudsTextInputSuffixIconButton { + /// The name or path of the SVG asset to display. + final String icon; + + /// Controls whether the icon should be tinted with the theme color. + /// Defaults to `true`. + final bool tinted; + + /// Callback invoked when the icon button is pressed. + /// If `null`, tapping the suffix icon will have no effect. + final VoidCallback? onPressed; + + const OudsTextInputSuffixIconButton({ + required this.icon, + this.tinted = true, + this.onPressed, + }); +} + +/// A circular loading indicator displayed in the text input. +/// +/// @param progress The loading progress, where 0.0 represents no progress and 1.0 represents full progress. +/// Values outside of this range are coerced into the range. +/// Set this value to `null` to display a circular indeterminate progress indicator. +class OudsTextInputLoader { + /// The progress value for determinate loading (0.0 to 1.0). + /// If null, shows an indeterminate progress indicator. + final double? progress; + + const OudsTextInputLoader({this.progress}); + + /// Returns true if loading is active. + bool get isLoading => progress != null; +} + /// Alias class for [OudsFormInputDecoration]. /// /// This class provides a shorter and more convenient name, [OudsInputDecoration], @@ -40,7 +105,6 @@ class OudsInputDecoration extends OudsFormInputDecoration { super.loader, super.outlined = false, super.constrainedMaxWidth = false, - super.onSuffixPressed, }); } @@ -60,14 +124,16 @@ class OudsInputDecoration extends OudsFormInputDecoration { /// - [hintText]: A short placeholder or hint shown inside the input when empty, /// describing the expected input. /// -/// - [suffixIcon]: A widget displayed at the end of the input field, +/// - [suffixIcon]: An [OudsTextInputSuffixIconButton] displayed at the end of the input field, /// commonly used for actions like clearing or toggling visibility. +/// Use [OudsTextInputSuffixIconButton] with [OudsTextInputSuffixIconButton.icon] to specify the icon, +/// [OudsTextInputSuffixIconButton.tinted] to control tinting (default: true), +/// and [OudsTextInputSuffixIconButton.onPressed] to handle press events. /// -/// - [onSuffixPressed]: - Assign a function to handle specific actions, that is invoked when the [suffixIcon] is pressed. -/// - If `null`, tapping the suffix icon will have no effect. -/// -/// - [prefixIcon]: The name or path of an icon displayed at the start of the input field, +/// - [prefixIcon]: An [OudsTextInputPrefixIcon] displayed at the start of the input field, /// typically to indicate the type or purpose of input. +/// Use [OudsTextInputPrefixIcon.icon] to specify the icon and +/// [OudsTextInputPrefixIcon.tinted] to control tinting (default: true). /// /// - [prefix]: A string displayed before the user's input, usually static text or units. /// @@ -76,7 +142,9 @@ class OudsInputDecoration extends OudsFormInputDecoration { /// - [errorText]: Text shown below the input indicating an error state or invalid input. /// Supports strong text formatting using `**bold**`. /// -/// - [loader]: When true, displays a loading indicator inside the input. +/// - [loader]: An [OudsTextInputLoader] instance to display a loading indicator inside the input. +/// Use [OudsTextInputLoader] with optional [progress] value (0.0 to 1.0) for determinate loading, +/// or without progress for indeterminate loading. Use `null` to disable the loader. /// /// - [outlined]: A boolean that determines if the input uses an outlined style or not. /// @@ -84,20 +152,18 @@ class OudsInputDecoration extends OudsFormInputDecoration { /// When `false`, no specific width constraint is applied, allowing the component to size itself or follow external modifiers. /// Defaults to `false`. /// - class OudsFormInputDecoration { final String? labelText; final String? helperText; final String? hintText; - final String? suffixIcon; - final String? prefixIcon; + final OudsTextInputSuffixIconButton? suffixIcon; + final OudsTextInputPrefixIcon? prefixIcon; final String? prefix; final bool hasPrefix; final String? suffix; final String? errorText; - final bool? loader; + final OudsTextInputLoader? loader; final bool? outlined; - final VoidCallback? onSuffixPressed; final bool constrainedMaxWidth; const OudsFormInputDecoration({ @@ -112,7 +178,6 @@ class OudsFormInputDecoration { this.errorText, this.loader, this.outlined = false, - this.onSuffixPressed, this.constrainedMaxWidth = false, }); } diff --git a/ouds_core/lib/components/form_input/ouds_phone_number_input.dart b/ouds_core/lib/components/form_input/ouds_phone_number_input.dart index 68d7d3c3a..d15161086 100644 --- a/ouds_core/lib/components/form_input/ouds_phone_number_input.dart +++ b/ouds_core/lib/components/form_input/ouds_phone_number_input.dart @@ -27,6 +27,7 @@ import 'package:ouds_core/components/form_input/internal/modifier/ouds_form_inpu import 'package:ouds_core/components/form_input/internal/modifier/ouds_form_input_text_modifier.dart'; import 'package:ouds_core/components/form_input/internal/ouds_form_input_control_state.dart'; import 'package:ouds_core/components/form_input/internal/ouds_form_input_decoration.dart'; +import 'package:ouds_core/components/progress_indicator/ouds_circular_progress_indicator.dart'; import 'package:ouds_core/components/utilities/app_assets.dart'; import 'package:ouds_core/components/utilities/markdown_span_builder.dart'; import 'package:ouds_core/l10n/gen/ouds_localizations.dart'; @@ -114,7 +115,7 @@ class OudsPhoneNumberInput extends StatefulWidget { required this.decoration, this.onEditingComplete, }) : assert( - !(decoration.loader == true && decoration.errorText != null), + !(decoration.loader != null && decoration.errorText != null), "Error status for Loading state is not relevant", ); @@ -239,7 +240,7 @@ class _OudsPhoneNumberInputState extends State { enabled: widget.enabled ?? true, isFocused: effectiveIsFocused, isHovered: _isHovered, - isLoading: widget.decoration.loader ?? false, + isLoading: widget.decoration.loader != null, isReadOnly: widget.readOnly ?? false, ); @@ -513,7 +514,7 @@ class _OudsPhoneNumberInputState extends State { ? Text( widget.decoration.labelText ?? "", style: theme.typographyTokens - .typeLabelDefaultLarge(context) + .typeLabelModerateLarge(context) .copyWith( color: inputTextTextModifier.getTextColor(state, isError), ), @@ -536,7 +537,7 @@ class _OudsPhoneNumberInputState extends State { maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.typographyTokens - .typeLabelDefaultLarge(context) + .typeLabelModerateLarge(context) .copyWith( color: inputTextTextModifier.getHintTextColor(state), ), @@ -705,7 +706,7 @@ class _OudsPhoneNumberInputState extends State { Text( prefixToDisplay, style: theme.typographyTokens - .typeLabelDefaultLarge(context) + .typeLabelModerateLarge(context) .copyWith( color: inputTextTextModifier.getSuffixPrefixTextColor(state), ), @@ -755,7 +756,7 @@ class _OudsPhoneNumberInputState extends State { MarkdownSpanBuilder.buildBoldOnly( text, baseStyle: theme.typographyTokens - .typeLabelDefaultMedium(context) + .typeLabelModerateMedium(context) .copyWith( color: inputTextTextModifier.getHelperTextColor(state, isError), ), @@ -800,7 +801,7 @@ class _OudsPhoneNumberInputState extends State { if (widget.decoration.prefixIcon != null) ...[ OudsPhoneNumberInput.buildIcon( context, - widget.decoration.prefixIcon!, + widget.decoration.prefixIcon!.icon, state, false, ), @@ -821,8 +822,8 @@ class _OudsPhoneNumberInputState extends State { /// /// Cases handled: /// - /// 1. **Loader active** (`loader == true`): - /// - Displays a minimal hierarchy [OudsButton] in loading style. + /// 1. **Loader active** (`loader != null`): + /// - Displays a circular loading indicator using [OudsCircularProgressIndicator]. /// - Adds horizontal spacing before the loader for visual alignment. /// /// 2. **Only error state** (`errorText != null`): @@ -846,19 +847,13 @@ class _OudsPhoneNumberInputState extends State { ); // Case 1: loader active - if (widget.decoration.loader == true) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(width: textInput.spaceColumnGapDefault), - OudsButton( - icon: AppAssets.icons.functionalSocialAndEngagementHeartRecommend, - package: OudsTheme.of(context).packageName, - appearance: OudsButtonAppearance.minimal, - isLoading: true, - onPressed: () {}, - ), - ], + if (widget.decoration.loader != null) { + return OudsButton( + icon: AppAssets.icons.communicationAssistanceTipsAndTricks, + package: OudsTheme.of(context).packageName, + appearance: OudsButtonAppearance.minimal, + isLoading: true, + onPressed: () {}, ); } diff --git a/ouds_core/lib/components/form_input/ouds_text_input.dart b/ouds_core/lib/components/form_input/ouds_text_input.dart index 333726323..c8d21c1d9 100644 --- a/ouds_core/lib/components/form_input/ouds_text_input.dart +++ b/ouds_core/lib/components/form_input/ouds_text_input.dart @@ -96,7 +96,7 @@ class OudsTextField extends StatefulWidget { this.helperLink, this.trailingIconContentDescription, }) : assert( - !(decoration.loader == true && decoration.errorText != null), + !(decoration.loader != null && decoration.errorText != null), "Error status for Loading state is not relevant", ); @@ -104,22 +104,34 @@ class OudsTextField extends StatefulWidget { BuildContext context, String assetName, OudsFormFieldsControlState controlTextInputState, - bool isError, - ) { + bool isError, { + bool tinted = true, + }) { final inputTextForegroundModifier = OudsFormFieldsForegroundColorModifier( context, ); final theme = OudsTheme.of(context); - return SvgPicture.asset( + final Widget iconWidget = SvgPicture.asset( excludeFromSemantics: true, assetName, fit: BoxFit.contain, height: theme.componentsTokens(context).textInput.sizeLeadingIcon, width: theme.componentsTokens(context).textInput.sizeLeadingIcon, - colorFilter: ColorFilter.mode( - inputTextForegroundModifier.getIconColor(controlTextInputState), - BlendMode.srcIn, - ), + colorFilter: tinted + ? ColorFilter.mode( + inputTextForegroundModifier.getIconColor(controlTextInputState), + BlendMode.srcIn, + ) + : null, + ); + + // When untinted, the icon asset is expected to be a plain white shape + // (no embedded background), so it needs a brand-colored background to + // remain visible — matching the behavior of OudsButton and OudsLink. + if (tinted) return iconWidget; + return Container( + color: theme.colorScheme(context).surfaceBrandPrimary, + child: iconWidget, ); } @@ -214,7 +226,7 @@ class _OudsTextInputState extends State { enabled: widget.enabled ?? true, isFocused: effectiveIsFocused, isHovered: _isHovered, - isLoading: (widget.decoration.loader == true && _isTyping) ? true : false, + isLoading: (widget.decoration.loader != null && _isTyping) ? true : false, isReadOnly: widget.readOnly ?? false, ); @@ -260,7 +272,7 @@ class _OudsTextInputState extends State { final hintLabel = contentText.isEmpty ? widget.decoration.hintText ?? "" : ""; - final loadingLabel = widget.decoration.loader == true + final loadingLabel = widget.decoration.loader != null ? l10n?.core_common_loading_a11y : ''; @@ -279,7 +291,7 @@ class _OudsTextInputState extends State { return Semantics( label: semanticsValue, - hint: widget.decoration.loader == true ? '' : l10n?.core_common_hint_a11y, + hint: widget.decoration.loader != null ? '' : l10n?.core_common_hint_a11y, value: isError ? l10n?.core_common_error_a11y : null, focused: effectiveFocusNode != null, focusable: true, @@ -351,7 +363,7 @@ class _OudsTextInputState extends State { child: Text( widget.decoration.prefix!, style: theme.typographyTokens - .typeLabelDefaultLarge(context) + .typeLabelModerateLarge(context) .copyWith( color: inputTextTextModifier .getSuffixPrefixTextColor(state), @@ -368,7 +380,7 @@ class _OudsTextInputState extends State { child: ExcludeSemantics( child: widget.readOnly == true || - (widget.decoration.loader == true && + (widget.decoration.loader != null && _isTyping) ? IgnorePointer( child: _buildTextField( @@ -411,7 +423,7 @@ class _OudsTextInputState extends State { child: Text( widget.decoration.suffix!, style: theme.typographyTokens - .typeLabelDefaultLarge(context) + .typeLabelModerateLarge(context) .copyWith( color: inputTextTextModifier .getSuffixPrefixTextColor(state), @@ -425,7 +437,7 @@ class _OudsTextInputState extends State { Semantics( label: widget.decoration.suffixIcon != null && - widget.decoration.loader == false + widget.decoration.loader == null ? widget.trailingIconContentDescription : null, container: true, @@ -543,7 +555,7 @@ class _OudsTextInputState extends State { overflow: TextOverflow.ellipsis, widget.decoration.labelText ?? "", style: theme.typographyTokens - .typeLabelDefaultLarge(context) + .typeLabelModerateLarge(context) .copyWith( color: inputTextTextModifier.getTextColor( state, @@ -568,7 +580,7 @@ class _OudsTextInputState extends State { overflow: TextOverflow.ellipsis, widget.decoration.hintText!, style: theme.typographyTokens - .typeLabelDefaultLarge(context) + .typeLabelModerateLarge(context) .copyWith( color: inputTextTextModifier.getHintTextColor(state), ), @@ -587,7 +599,7 @@ class _OudsTextInputState extends State { child: Text( widget.decoration.prefix!, style: theme.typographyTokens - .typeLabelDefaultLarge(context) + .typeLabelModerateLarge(context) .copyWith( color: inputTextTextModifier .getSuffixPrefixTextColor(state), @@ -615,7 +627,7 @@ class _OudsTextInputState extends State { child: Text( widget.decoration.suffix!, style: theme.typographyTokens - .typeLabelDefaultLarge(context) + .typeLabelModerateLarge(context) .copyWith( color: inputTextTextModifier .getSuffixPrefixTextColor(state), @@ -671,7 +683,7 @@ class _OudsTextInputState extends State { MarkdownSpanBuilder.buildBoldOnly( text, baseStyle: theme.typographyTokens - .typeLabelDefaultMedium(context) + .typeLabelModerateMedium(context) .copyWith( color: inputTextTextModifier.getHelperTextColor(state, isError), ), @@ -688,8 +700,8 @@ class _OudsTextInputState extends State { /// /// Cases handled: /// - /// 1. **Loader active** (`loader == true`): - /// - Displays a circular loading indicator. + /// 1. **Loader active** (`loader != null`): + /// - Displays a circular loading indicator using [OudsCircularProgressIndicator]. /// /// 2. **Suffix icon provided** (`suffixIcon != null`): /// - Displays the suffix icon inside a minimal hierarchy [OudsButton]. @@ -717,34 +729,19 @@ class _OudsTextInputState extends State { ); // Case 1: loader active - if (widget.decoration.loader == true && _isTyping) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(width: textInput.spaceColumnGapDefault), - ConstrainedBox( - constraints: BoxConstraints( - minWidth: buttonTokens.sizeMinWidthDefault, - minHeight: buttonTokens.sizeMinHeightDefault, - maxHeight: buttonTokens.sizeMaxSizeIconOnlyDefault, - ), - child: Padding( - padding: EdgeInsetsGeometry.all( - buttonTokens.spaceInsetIconOnlyDefault, // to see - ), - child: Center( - child: OudsCircularProgressIndicator( - color: theme.colorScheme(context).contentDefault, - ), - ), - ), - ), - ], + if (widget.decoration.loader != null && _isTyping) { + return OudsButton( + icon: AppAssets.icons.communicationAssistanceTipsAndTricks, + package: OudsTheme.of(context).packageName, + appearance: OudsButtonAppearance.minimal, + isLoading: true, + onPressed: () {}, ); } // Case 2: display suffixIcon + optional error icon if (widget.decoration.suffixIcon != null) { + final suffixIcon = widget.decoration.suffixIcon!; return Row( mainAxisSize: MainAxisSize.min, children: [ @@ -777,10 +774,11 @@ class _OudsTextInputState extends State { ), child: OudsButton( appearance: OudsButtonAppearance.minimal, - icon: widget.decoration.suffixIcon, + icon: suffixIcon.icon, + tinted: suffixIcon.tinted, onPressed: ((widget.enabled ?? true) && !(widget.readOnly ?? false)) - ? widget.decoration.onSuffixPressed + ? suffixIcon.onPressed : null, ), ), @@ -837,9 +835,10 @@ class _OudsTextInputState extends State { if (widget.decoration.prefixIcon != null) ...[ OudsTextField.buildIcon( context, - widget.decoration.prefixIcon!, + widget.decoration.prefixIcon!.icon, state, false, + tinted: widget.decoration.prefixIcon!.tinted, ), SizedBox(width: textInput.spaceColumnGapDefault), ], diff --git a/ouds_core/lib/components/form_input/password_input/ouds_password_input.dart b/ouds_core/lib/components/form_input/password_input/ouds_password_input.dart index bf874cb26..e26467c55 100644 --- a/ouds_core/lib/components/form_input/password_input/ouds_password_input.dart +++ b/ouds_core/lib/components/form_input/password_input/ouds_password_input.dart @@ -83,7 +83,7 @@ class OudsPasswordInput extends StatefulWidget { this.onEditingComplete, required this.decoration, }) : assert( - !(decoration.loader == true && decoration.errorText != null), + !(decoration.loader != null && decoration.errorText != null), "Error status for Loading state is not relevant", ); @@ -207,7 +207,7 @@ class _OudsPasswordInputState extends State { enabled: widget.enabled ?? true, isFocused: effectiveIsFocused, isHovered: _isHovered, - isLoading: (widget.decoration.loader == true && _isTyping) ? true : false, + isLoading: (widget.decoration.loader != null && _isTyping) ? true : false, isReadOnly: widget.readOnly ?? false, ); @@ -284,7 +284,7 @@ class _OudsPasswordInputState extends State { hint: helperText, child: widget.readOnly == true || - (widget.decoration.loader == true && _isTyping) + (widget.decoration.loader != null && _isTyping) ? IgnorePointer( child: _buildTextField( inputTextTextModifier, @@ -413,7 +413,7 @@ class _OudsPasswordInputState extends State { widget.decoration.labelText ?? "", overflow: TextOverflow.ellipsis, style: theme.typographyTokens - .typeLabelDefaultLarge(context) + .typeLabelModerateLarge(context) .copyWith( color: inputTextTextModifier.getTextColor(state, isError), ), @@ -433,7 +433,7 @@ class _OudsPasswordInputState extends State { widget.decoration.hintText!, overflow: TextOverflow.ellipsis, style: theme.typographyTokens - .typeLabelDefaultLarge(context) + .typeLabelModerateLarge(context) .copyWith( color: inputTextTextModifier.getHintTextColor(state), ), @@ -452,7 +452,7 @@ class _OudsPasswordInputState extends State { child: Text( widget.decoration.prefix!, style: theme.typographyTokens - .typeLabelDefaultLarge(context) + .typeLabelModerateLarge(context) .copyWith( color: inputTextTextModifier .getSuffixPrefixTextColor(state), @@ -511,7 +511,7 @@ class _OudsPasswordInputState extends State { MarkdownSpanBuilder.buildBoldOnly( text, baseStyle: theme.typographyTokens - .typeLabelDefaultMedium(context) + .typeLabelModerateMedium(context) .copyWith( color: inputTextTextModifier.getHelperTextColor(state, isError), ), @@ -559,17 +559,13 @@ class _OudsPasswordInputState extends State { final l10n = OudsLocalizations.of(context); // Case 1: loader active - if (widget.decoration.loader == true && _isTyping) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - OudsButton( - icon: 'assets/ic_password_lock.svg', - appearance: OudsButtonAppearance.minimal, - isLoading: true, - onPressed: () {}, - ), - ], + if (widget.decoration.loader != null && _isTyping) { + return OudsButton( + icon: AppAssets.icons.communicationAssistanceTipsAndTricks, + package: OudsTheme.of(context).packageName, + appearance: OudsButtonAppearance.minimal, + isLoading: true, + onPressed: () {}, ); } diff --git a/ouds_core/lib/components/form_input/password_input/ouds_password_input_decoration.dart b/ouds_core/lib/components/form_input/password_input/ouds_password_input_decoration.dart index 238cca7a9..213ca8311 100644 --- a/ouds_core/lib/components/form_input/password_input/ouds_password_input_decoration.dart +++ b/ouds_core/lib/components/form_input/password_input/ouds_password_input_decoration.dart @@ -14,6 +14,8 @@ /// {@category Password input} library; +import 'package:ouds_core/components/form_input/internal/ouds_form_input_decoration.dart'; + /// Configuration for decorating the [OudsPasswordInput] widget. /// /// Provides properties to customize labels, hints, icons, helper and error texts, @@ -36,7 +38,9 @@ library; /// /// - [errorText]: Text shown below the input indicating an error state or invalid input. /// -/// - [loader]: When true, displays a loading indicator inside the input. +/// - [loader]: An [OudsTextInputLoader] instance to display a loading indicator inside the input. +/// Use [OudsTextInputLoader] with optional [progress] value (0.0 to 1.0) for determinate loading, +/// or without progress for indeterminate loading. Use `null` to disable the loader. /// /// - [constrainedMaxWidth]: When `true`, the item width is constrained to a maximum value defined by the design system. /// When `false`, no specific width constraint is applied, allowing the component to size itself or follow external modifiers. @@ -50,7 +54,7 @@ class OudsPasswordInputDecoration { final bool? prefixIcon; final String? prefix; final String? errorText; - final bool? loader; + final OudsTextInputLoader? loader; final bool? outlined; final bool constrainedMaxWidth; diff --git a/ouds_core/lib/components/utilities/app_assets.dart b/ouds_core/lib/components/utilities/app_assets.dart index 2599d1844..d835a7f16 100644 --- a/ouds_core/lib/components/utilities/app_assets.dart +++ b/ouds_core/lib/components/utilities/app_assets.dart @@ -79,6 +79,8 @@ class _Icons { 'assets/communication/accessibility/accessibility-vision.svg'; final String communicationSecurityAndSafetyLockClosed = 'assets/communication/security-and-safety/lock-closed.svg'; + final String communicationAssistanceTipsAndTricks = + 'assets/communication/assistance/tips-and-tricks.svg'; final String settingsAndToolsAccessibilityHide = 'assets/functional/settings-and-tools/accessibility-hide.svg'; diff --git a/ouds_theme_orange/CHANGELOG.md b/ouds_theme_orange/CHANGELOG.md index d0bbc5d6b..e1668904b 100644 --- a/ouds_theme_orange/CHANGELOG.md +++ b/ouds_theme_orange/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/Orange-OpenSource/ouds-flutter/compare/2.1.0...develop) ### Added ### Changed +- [Library] For `textInput` component, update to v1.4.1 ([#784](https://github.com/Orange-OpenSource/ouds-flutter/issues/784)) - [Library] Add typography Tokens component ([#905](https://github.com/Orange-OpenSource/ouds-flutter/issues/905)) - [Library] update icons to use the icons pack 2.3.0 ([#863](https://github.com/Orange-OpenSource/ouds-flutter/issues/863)) ### Fixed diff --git a/ouds_theme_orange/assets/communication/assistance/tips-and-tricks.svg b/ouds_theme_orange/assets/communication/assistance/tips-and-tricks.svg new file mode 100644 index 000000000..37de0d6cc --- /dev/null +++ b/ouds_theme_orange/assets/communication/assistance/tips-and-tricks.svg @@ -0,0 +1,3 @@ + + + diff --git a/ouds_theme_orange_compact/CHANGELOG.md b/ouds_theme_orange_compact/CHANGELOG.md index 9afc1d848..7d135f58a 100644 --- a/ouds_theme_orange_compact/CHANGELOG.md +++ b/ouds_theme_orange_compact/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/Orange-OpenSource/ouds-flutter/compare/2.1.0...develop) ### Added ### Changed +- [Library] For `textInput` component, update to v1.4.1 ([#784](https://github.com/Orange-OpenSource/ouds-flutter/issues/784)) - [Library] Add typography Tokens component ([#905](https://github.com/Orange-OpenSource/ouds-flutter/issues/905)) - [Library] update icons to use the icons pack 2.3.0 ([#863](https://github.com/Orange-OpenSource/ouds-flutter/issues/863)) ### Fixed diff --git a/ouds_theme_orange_compact/assets/communication/assistance/tips-and-tricks.svg b/ouds_theme_orange_compact/assets/communication/assistance/tips-and-tricks.svg new file mode 100644 index 000000000..37de0d6cc --- /dev/null +++ b/ouds_theme_orange_compact/assets/communication/assistance/tips-and-tricks.svg @@ -0,0 +1,3 @@ + + + diff --git a/ouds_theme_sosh/CHANGELOG.md b/ouds_theme_sosh/CHANGELOG.md index 2c7ccd9bd..42096c2a8 100644 --- a/ouds_theme_sosh/CHANGELOG.md +++ b/ouds_theme_sosh/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/Orange-OpenSource/ouds-flutter/compare/2.1.0...develop) ### Added ### Changed +- [Library] For `textInput` component, update to v1.4.1 ([#784](https://github.com/Orange-OpenSource/ouds-flutter/issues/784)) - [Library] Add typography Tokens component ([#905](https://github.com/Orange-OpenSource/ouds-flutter/issues/905)) - [Library] update icons to use the icons pack 2.3.0 ([#863](https://github.com/Orange-OpenSource/ouds-flutter/issues/863)) ### Fixed diff --git a/ouds_theme_sosh/assets/communication/assistance/tips-and-tricks.svg b/ouds_theme_sosh/assets/communication/assistance/tips-and-tricks.svg new file mode 100644 index 000000000..0d11c137c --- /dev/null +++ b/ouds_theme_sosh/assets/communication/assistance/tips-and-tricks.svg @@ -0,0 +1,3 @@ + + + diff --git a/ouds_theme_wireframe/CHANGELOG.md b/ouds_theme_wireframe/CHANGELOG.md index 073fa8b68..703d4f4d4 100644 --- a/ouds_theme_wireframe/CHANGELOG.md +++ b/ouds_theme_wireframe/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/Orange-OpenSource/ouds-flutter/compare/2.1.0...develop) ### Added ### Changed +- [Library] For `textInput` component, update to v1.4.1 ([#784](https://github.com/Orange-OpenSource/ouds-flutter/issues/784)) - [Library] Add typography Tokens component ([#905](https://github.com/Orange-OpenSource/ouds-flutter/issues/905)) - [Library] update icons to use the icons pack 2.3.0 ([#863](https://github.com/Orange-OpenSource/ouds-flutter/issues/863)) ### Fixed diff --git a/ouds_theme_wireframe/assets/communication/assistance/tips-and-tricks.svg b/ouds_theme_wireframe/assets/communication/assistance/tips-and-tricks.svg new file mode 100644 index 000000000..93407bf4e --- /dev/null +++ b/ouds_theme_wireframe/assets/communication/assistance/tips-and-tricks.svg @@ -0,0 +1,4 @@ + + + + diff --git a/pubspec.lock b/pubspec.lock index b0da4c7dc..460615bcf 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -117,10 +117,10 @@ packages: dependency: transitive description: name: dlibphonenumber - sha256: c24a8e0223fcc30f2d456e7ed9556be2fd0ecb71ce7d94bce35882771e9abd0d + sha256: b467588e1d09972b5b650517de484c6f9beed23a27dcc78dbde901f99db8899e url: "https://pub.dev" source: hosted - version: "1.1.56" + version: "1.1.70" dotted_border: dependency: transitive description: diff --git a/skills/ouds-flutter-migration-guide/SKILL.md b/skills/ouds-flutter-migration-guide/SKILL.md index cbca9cc01..167af256a 100644 --- a/skills/ouds-flutter-migration-guide/SKILL.md +++ b/skills/ouds-flutter-migration-guide/SKILL.md @@ -681,6 +681,8 @@ Before proposing changes: | Migration | Compatibility | Migration required | Main topics | |-----------|---------------|--------------------|-------------| +| `v2.1.0 → v3.0.0` | No | Yes | Typed `OudsTextInput`/`OudsPhoneNumberInput` prefix/suffix icons with `tinted` support (breaking), `OudsButton.tinted`, `Link` v2.4.0, icon pack v2.3.0, `OudsListItem`, typography tokens | +| `v2.0.0 → v2.1.0` | Full | No | `OudsNavigationButton`, `OudsCircularProgressIndicator`, `OudsLinearProgressIndicator`, tokens v2.6.0 | | `v1.3.1 → v2.0.0` | Partial | Yes for deprecated APIs | `OudsLink` named constructors (`.icon`, `.previous`, `.next`, `.external`), Markdown support, token/icon updates | | `v1.3.0 → v1.3.1` | Full | No | Maintenance release, bug fixes, accessibility improvements | | `v1.2.0 → v1.3.0` | Partial | Yes for deprecated APIs | `OudsTag` named constructors, `OudsBadge` named constructors, `OudsPinCodeInput.keyboardType`, new alert/bottom-sheet components | @@ -733,6 +735,42 @@ OudsLink.external(label: 'Label') --- +#### 3.3.0bis Typed prefix/suffix icons with `tinted` support (`v2.1.0 → v3.0.0`) + +`OudsInputDecoration`/`OudsFormInputDecoration.prefixIcon` and `suffixIcon` are no longer `String`/`VoidCallback` values. They now take `OudsTextInputPrefixIcon` and `OudsTextInputSuffixIconButton` objects with a `tinted` flag (defaults to `true`) controlling whether the icon follows the theme color or keeps its original (multi-color) asset colors. `onSuffixPressed` has been removed; use `OudsTextInputSuffixIconButton.onPressed` instead. + +**Before:** + +```dart +OudsInputDecoration( + prefixIcon: 'assets/ic_heart.svg', + suffixIcon: 'assets/ic_heart.svg', + onSuffixPressed: () {}, +) +``` + +**After:** + +```dart +OudsInputDecoration( + prefixIcon: OudsTextInputPrefixIcon(icon: 'assets/ic_heart.svg', tinted: true), + suffixIcon: OudsTextInputSuffixIconButton( + icon: 'assets/ic_heart.svg', + tinted: true, + onPressed: () {}, + ), +) +``` + +**Required actions:** + +- Wrap `prefixIcon` asset paths in `OudsTextInputPrefixIcon(icon: ...)`. +- Wrap `suffixIcon` asset paths in `OudsTextInputSuffixIconButton(icon: ..., onPressed: ...)`, moving `onSuffixPressed` into `onPressed`. +- Set `tinted: false` to keep a multi-color icon's original asset colors instead of theme tinting. +- `OudsButton` gained the same `tinted` parameter (defaults to `true`); no action required unless you want untinted icons. + +--- + #### 3.3.1 `OudsTag` → named constructors (`v1.2.x` → `v1.3.0`) **Before:**