@pagopa/io-app-design-system 7.0.2 → 7.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/lib/commonjs/components/alert/Alert.js +6 -5
  2. package/lib/commonjs/components/alert/Alert.js.map +1 -1
  3. package/lib/commonjs/components/banner/Banner.js +7 -6
  4. package/lib/commonjs/components/banner/Banner.js.map +1 -1
  5. package/lib/commonjs/components/buttons/IOButton/IOButton.js +10 -8
  6. package/lib/commonjs/components/buttons/IOButton/IOButton.js.map +1 -1
  7. package/lib/commonjs/components/layout/ContentWrapper.js +5 -4
  8. package/lib/commonjs/components/layout/ContentWrapper.js.map +1 -1
  9. package/lib/commonjs/components/otpInput/OTPInput.js +6 -4
  10. package/lib/commonjs/components/otpInput/OTPInput.js.map +1 -1
  11. package/lib/commonjs/components/searchInput/SearchInput.js +8 -6
  12. package/lib/commonjs/components/searchInput/SearchInput.js.map +1 -1
  13. package/lib/commonjs/components/tabs/TabItem.js +6 -4
  14. package/lib/commonjs/components/tabs/TabItem.js.map +1 -1
  15. package/lib/commonjs/components/textInput/TextInputBase.js +8 -4
  16. package/lib/commonjs/components/textInput/TextInputBase.js.map +1 -1
  17. package/lib/commonjs/components/textInput/TextInputValidation.js +5 -3
  18. package/lib/commonjs/components/textInput/TextInputValidation.js.map +1 -1
  19. package/lib/commonjs/components/typography/Body.js +8 -5
  20. package/lib/commonjs/components/typography/Body.js.map +1 -1
  21. package/lib/commonjs/components/typography/BodyMonospace.js +4 -5
  22. package/lib/commonjs/components/typography/BodyMonospace.js.map +1 -1
  23. package/lib/commonjs/components/typography/BodySmall.js +8 -5
  24. package/lib/commonjs/components/typography/BodySmall.js.map +1 -1
  25. package/lib/commonjs/components/typography/ButtonText.js +4 -5
  26. package/lib/commonjs/components/typography/ButtonText.js.map +1 -1
  27. package/lib/commonjs/components/typography/Caption.js +4 -5
  28. package/lib/commonjs/components/typography/Caption.js.map +1 -1
  29. package/lib/commonjs/components/typography/H1.js +4 -5
  30. package/lib/commonjs/components/typography/H1.js.map +1 -1
  31. package/lib/commonjs/components/typography/H2.js +4 -5
  32. package/lib/commonjs/components/typography/H2.js.map +1 -1
  33. package/lib/commonjs/components/typography/H3.js +4 -5
  34. package/lib/commonjs/components/typography/H3.js.map +1 -1
  35. package/lib/commonjs/components/typography/H4.js +4 -5
  36. package/lib/commonjs/components/typography/H4.js.map +1 -1
  37. package/lib/commonjs/components/typography/H5.js +4 -5
  38. package/lib/commonjs/components/typography/H5.js.map +1 -1
  39. package/lib/commonjs/components/typography/H6.js +4 -5
  40. package/lib/commonjs/components/typography/H6.js.map +1 -1
  41. package/lib/commonjs/components/typography/Hero.js +4 -5
  42. package/lib/commonjs/components/typography/Hero.js.map +1 -1
  43. package/lib/commonjs/components/typography/IOText.js +16 -14
  44. package/lib/commonjs/components/typography/IOText.js.map +1 -1
  45. package/lib/commonjs/components/typography/LabelMini.js +8 -5
  46. package/lib/commonjs/components/typography/LabelMini.js.map +1 -1
  47. package/lib/commonjs/components/typography/markdown/MdH1.js +4 -5
  48. package/lib/commonjs/components/typography/markdown/MdH1.js.map +1 -1
  49. package/lib/commonjs/components/typography/markdown/MdH2.js +4 -5
  50. package/lib/commonjs/components/typography/markdown/MdH2.js.map +1 -1
  51. package/lib/commonjs/components/typography/markdown/MdH3.js +4 -5
  52. package/lib/commonjs/components/typography/markdown/MdH3.js.map +1 -1
  53. package/lib/commonjs/context/IOThemeContextProvider.js +3 -4
  54. package/lib/commonjs/context/IOThemeContextProvider.js.map +1 -1
  55. package/lib/module/components/alert/Alert.js +5 -5
  56. package/lib/module/components/alert/Alert.js.map +1 -1
  57. package/lib/module/components/banner/Banner.js +6 -6
  58. package/lib/module/components/banner/Banner.js.map +1 -1
  59. package/lib/module/components/buttons/IOButton/IOButton.js +10 -9
  60. package/lib/module/components/buttons/IOButton/IOButton.js.map +1 -1
  61. package/lib/module/components/layout/ContentWrapper.js +4 -4
  62. package/lib/module/components/layout/ContentWrapper.js.map +1 -1
  63. package/lib/module/components/otpInput/OTPInput.js +6 -5
  64. package/lib/module/components/otpInput/OTPInput.js.map +1 -1
  65. package/lib/module/components/searchInput/SearchInput.js +8 -7
  66. package/lib/module/components/searchInput/SearchInput.js.map +1 -1
  67. package/lib/module/components/tabs/TabItem.js +6 -5
  68. package/lib/module/components/tabs/TabItem.js.map +1 -1
  69. package/lib/module/components/textInput/TextInputBase.js +8 -4
  70. package/lib/module/components/textInput/TextInputBase.js.map +1 -1
  71. package/lib/module/components/textInput/TextInputValidation.js +5 -4
  72. package/lib/module/components/textInput/TextInputValidation.js.map +1 -1
  73. package/lib/module/components/typography/Body.js +7 -5
  74. package/lib/module/components/typography/Body.js.map +1 -1
  75. package/lib/module/components/typography/BodyMonospace.js +3 -5
  76. package/lib/module/components/typography/BodyMonospace.js.map +1 -1
  77. package/lib/module/components/typography/BodySmall.js +7 -5
  78. package/lib/module/components/typography/BodySmall.js.map +1 -1
  79. package/lib/module/components/typography/ButtonText.js +3 -5
  80. package/lib/module/components/typography/ButtonText.js.map +1 -1
  81. package/lib/module/components/typography/Caption.js +3 -5
  82. package/lib/module/components/typography/Caption.js.map +1 -1
  83. package/lib/module/components/typography/H1.js +3 -5
  84. package/lib/module/components/typography/H1.js.map +1 -1
  85. package/lib/module/components/typography/H2.js +3 -5
  86. package/lib/module/components/typography/H2.js.map +1 -1
  87. package/lib/module/components/typography/H3.js +3 -5
  88. package/lib/module/components/typography/H3.js.map +1 -1
  89. package/lib/module/components/typography/H4.js +3 -5
  90. package/lib/module/components/typography/H4.js.map +1 -1
  91. package/lib/module/components/typography/H5.js +3 -5
  92. package/lib/module/components/typography/H5.js.map +1 -1
  93. package/lib/module/components/typography/H6.js +3 -5
  94. package/lib/module/components/typography/H6.js.map +1 -1
  95. package/lib/module/components/typography/Hero.js +3 -5
  96. package/lib/module/components/typography/Hero.js.map +1 -1
  97. package/lib/module/components/typography/IOText.js +16 -15
  98. package/lib/module/components/typography/IOText.js.map +1 -1
  99. package/lib/module/components/typography/LabelMini.js +7 -5
  100. package/lib/module/components/typography/LabelMini.js.map +1 -1
  101. package/lib/module/components/typography/markdown/MdH1.js +3 -5
  102. package/lib/module/components/typography/markdown/MdH1.js.map +1 -1
  103. package/lib/module/components/typography/markdown/MdH2.js +3 -5
  104. package/lib/module/components/typography/markdown/MdH2.js.map +1 -1
  105. package/lib/module/components/typography/markdown/MdH3.js +3 -5
  106. package/lib/module/components/typography/markdown/MdH3.js.map +1 -1
  107. package/lib/module/context/IOThemeContextProvider.js +3 -4
  108. package/lib/module/context/IOThemeContextProvider.js.map +1 -1
  109. package/lib/typescript/components/alert/Alert.d.ts +3 -1
  110. package/lib/typescript/components/alert/Alert.d.ts.map +1 -1
  111. package/lib/typescript/components/banner/Banner.d.ts +3 -1
  112. package/lib/typescript/components/banner/Banner.d.ts.map +1 -1
  113. package/lib/typescript/components/buttons/IOButton/IOButton.d.ts +3 -2
  114. package/lib/typescript/components/buttons/IOButton/IOButton.d.ts.map +1 -1
  115. package/lib/typescript/components/layout/ContentWrapper.d.ts +10 -6
  116. package/lib/typescript/components/layout/ContentWrapper.d.ts.map +1 -1
  117. package/lib/typescript/components/otpInput/OTPInput.d.ts +3 -1
  118. package/lib/typescript/components/otpInput/OTPInput.d.ts.map +1 -1
  119. package/lib/typescript/components/searchInput/SearchInput.d.ts +3 -1
  120. package/lib/typescript/components/searchInput/SearchInput.d.ts.map +1 -1
  121. package/lib/typescript/components/tabs/TabItem.d.ts +3 -11
  122. package/lib/typescript/components/tabs/TabItem.d.ts.map +1 -1
  123. package/lib/typescript/components/textInput/TextInputBase.d.ts +2 -1
  124. package/lib/typescript/components/textInput/TextInputBase.d.ts.map +1 -1
  125. package/lib/typescript/components/textInput/TextInputValidation.d.ts +13 -24
  126. package/lib/typescript/components/textInput/TextInputValidation.d.ts.map +1 -1
  127. package/lib/typescript/components/typography/Body.d.ts +4 -2
  128. package/lib/typescript/components/typography/Body.d.ts.map +1 -1
  129. package/lib/typescript/components/typography/BodyMonospace.d.ts +2 -8
  130. package/lib/typescript/components/typography/BodyMonospace.d.ts.map +1 -1
  131. package/lib/typescript/components/typography/BodySmall.d.ts +4 -2
  132. package/lib/typescript/components/typography/BodySmall.d.ts.map +1 -1
  133. package/lib/typescript/components/typography/ButtonText.d.ts +2 -8
  134. package/lib/typescript/components/typography/ButtonText.d.ts.map +1 -1
  135. package/lib/typescript/components/typography/Caption.d.ts +2 -8
  136. package/lib/typescript/components/typography/Caption.d.ts.map +1 -1
  137. package/lib/typescript/components/typography/H1.d.ts +2 -8
  138. package/lib/typescript/components/typography/H1.d.ts.map +1 -1
  139. package/lib/typescript/components/typography/H2.d.ts +6 -10
  140. package/lib/typescript/components/typography/H2.d.ts.map +1 -1
  141. package/lib/typescript/components/typography/H3.d.ts +2 -8
  142. package/lib/typescript/components/typography/H3.d.ts.map +1 -1
  143. package/lib/typescript/components/typography/H4.d.ts +2 -8
  144. package/lib/typescript/components/typography/H4.d.ts.map +1 -1
  145. package/lib/typescript/components/typography/H5.d.ts +2 -8
  146. package/lib/typescript/components/typography/H5.d.ts.map +1 -1
  147. package/lib/typescript/components/typography/H6.d.ts +2 -8
  148. package/lib/typescript/components/typography/H6.d.ts.map +1 -1
  149. package/lib/typescript/components/typography/Hero.d.ts +2 -8
  150. package/lib/typescript/components/typography/Hero.d.ts.map +1 -1
  151. package/lib/typescript/components/typography/IOText.d.ts +5 -5
  152. package/lib/typescript/components/typography/IOText.d.ts.map +1 -1
  153. package/lib/typescript/components/typography/LabelMini.d.ts +4 -2
  154. package/lib/typescript/components/typography/LabelMini.d.ts.map +1 -1
  155. package/lib/typescript/components/typography/markdown/MdH1.d.ts +2 -8
  156. package/lib/typescript/components/typography/markdown/MdH1.d.ts.map +1 -1
  157. package/lib/typescript/components/typography/markdown/MdH2.d.ts +2 -8
  158. package/lib/typescript/components/typography/markdown/MdH2.d.ts.map +1 -1
  159. package/lib/typescript/components/typography/markdown/MdH3.d.ts +2 -8
  160. package/lib/typescript/components/typography/markdown/MdH3.d.ts.map +1 -1
  161. package/lib/typescript/context/IOThemeContextProvider.d.ts.map +1 -1
  162. package/package.json +6 -7
  163. package/src/components/alert/Alert.tsx +108 -112
  164. package/src/components/banner/Banner.tsx +121 -125
  165. package/src/components/buttons/IOButton/IOButton.tsx +204 -216
  166. package/src/components/layout/ContentWrapper.tsx +21 -24
  167. package/src/components/otpInput/OTPInput.tsx +156 -167
  168. package/src/components/searchInput/SearchInput.tsx +208 -217
  169. package/src/components/tabs/TabItem.tsx +143 -146
  170. package/src/components/textInput/TextInputBase.tsx +13 -4
  171. package/src/components/textInput/TextInputValidation.tsx +122 -122
  172. package/src/components/typography/Body.tsx +51 -52
  173. package/src/components/typography/BodyMonospace.tsx +19 -24
  174. package/src/components/typography/BodySmall.tsx +51 -52
  175. package/src/components/typography/ButtonText.tsx +14 -20
  176. package/src/components/typography/Caption.tsx +18 -23
  177. package/src/components/typography/H1.tsx +12 -20
  178. package/src/components/typography/H2.tsx +16 -23
  179. package/src/components/typography/H3.tsx +12 -20
  180. package/src/components/typography/H4.tsx +12 -20
  181. package/src/components/typography/H5.tsx +16 -24
  182. package/src/components/typography/H6.tsx +13 -21
  183. package/src/components/typography/Hero.tsx +14 -19
  184. package/src/components/typography/IOText.tsx +54 -59
  185. package/src/components/typography/LabelMini.tsx +45 -49
  186. package/src/components/typography/markdown/MdH1.tsx +14 -19
  187. package/src/components/typography/markdown/MdH2.tsx +14 -19
  188. package/src/components/typography/markdown/MdH3.tsx +14 -19
  189. package/src/context/IOThemeContextProvider.tsx +4 -12
@@ -1,4 +1,4 @@
1
- import { Ref, forwardRef, useCallback, useMemo } from "react";
1
+ import { Ref, useCallback, useMemo } from "react";
2
2
  import {
3
3
  GestureResponderEvent,
4
4
  Pressable,
@@ -25,6 +25,7 @@ type ColorMode = "light" | "dark";
25
25
  type TabItemState = "default" | "selected" | "disabled";
26
26
 
27
27
  export type TabItem = WithTestID<{
28
+ ref?: Ref<View>;
28
29
  label: string;
29
30
  color?: ColorMode;
30
31
  selected?: boolean;
@@ -57,155 +58,151 @@ type ColorStates = {
57
58
 
58
59
  const DISABLED_OPACITY = 0.5;
59
60
 
60
- const TabItem = forwardRef(
61
- (
62
- {
63
- label,
64
- color = "light",
65
- selected = false,
66
- accessibilityLabel,
67
- accessibilityHint,
68
- testID,
69
- onPress,
70
- disabled = false,
71
- icon,
72
- iconSelected
73
- }: TabItem,
74
- ref: Ref<View>
75
- ) => {
76
- const { onPressIn, onPressOut, scaleAnimatedStyle } =
77
- useScaleAnimation("medium");
78
- const theme = useIOTheme();
79
- const reducedMotion = useReducedMotion();
80
-
81
- const mapColorStates: Record<
82
- NonNullable<TabItem["color"]>,
83
- ColorStates
84
- > = useMemo(
85
- () => ({
86
- light: {
87
- border: {
88
- default: IOColors[theme["tab-item-border-default"]],
89
- selected: hexToRgba(
90
- IOColors[theme["tab-item-foreground-selected"]],
91
- 0.5
92
- )
93
- },
94
- background: {
95
- default: hexToRgba(
96
- IOColors[theme["tab-item-background-selected"]],
97
- 0
98
- ),
99
- selected: hexToRgba(
100
- IOColors[theme["tab-item-background-selected"]],
101
- 0.25
102
- ),
103
- pressed: IOColors[theme["appBackground-primary"]]
104
- },
105
- foreground: {
106
- default: theme["tab-item-foreground-default"],
107
- selected: theme["tab-item-foreground-selected"],
108
- disabled: "grey-700"
109
- }
61
+ const TabItem = ({
62
+ label,
63
+ color = "light",
64
+ selected = false,
65
+ accessibilityLabel,
66
+ accessibilityHint,
67
+ testID,
68
+ onPress,
69
+ disabled = false,
70
+ icon,
71
+ iconSelected,
72
+ ref
73
+ }: TabItem) => {
74
+ const { onPressIn, onPressOut, scaleAnimatedStyle } =
75
+ useScaleAnimation("medium");
76
+ const theme = useIOTheme();
77
+ const reducedMotion = useReducedMotion();
78
+
79
+ const mapColorStates: Record<
80
+ NonNullable<TabItem["color"]>,
81
+ ColorStates
82
+ > = useMemo(
83
+ () => ({
84
+ light: {
85
+ border: {
86
+ default: IOColors[theme["tab-item-border-default"]],
87
+ selected: hexToRgba(
88
+ IOColors[theme["tab-item-foreground-selected"]],
89
+ 0.5
90
+ )
110
91
  },
111
- dark: {
112
- border: {
113
- default: hexToRgba(IOColors.white, 0),
114
- selected: IOColors.white
115
- },
116
- background: {
117
- default: hexToRgba(IOColors.white, 0.1),
118
- selected: IOColors.white,
119
- pressed: IOColors.white
120
- },
121
- foreground: {
122
- default: "white",
123
- selected: "black",
124
- disabled: "white"
125
- }
126
- }
127
- }),
128
- [theme]
129
- );
130
-
131
- const itemState: TabItemState = selected
132
- ? "selected"
133
- : disabled
134
- ? "disabled"
135
- : "default";
136
-
137
- const foregroundColor = mapColorStates[color].foreground[itemState];
138
-
139
- const selectedStateTransition = useDerivedValue(() =>
140
- withSpring(selected ? 1 : 0, IOSpringValues.selection)
141
- );
142
-
143
- // Interpolate animation values from `pressed` values
144
- const animatedStyle = useAnimatedStyle(() => ({
145
- backgroundColor: interpolateColor(
146
- selectedStateTransition.value,
147
- [0, 1],
148
- [
149
- mapColorStates[color].background.default,
150
- mapColorStates[color].background.selected
151
- ]
152
- ),
153
- borderColor: interpolateColor(
154
- selectedStateTransition.value,
155
- [0, 1],
156
- [
157
- mapColorStates[color].border.default,
158
- mapColorStates[color].border.selected
159
- ]
160
- )
161
- }));
162
-
163
- const activeIcon = selected ? iconSelected ?? icon : icon;
164
-
165
- const handleOnPress = useCallback(
166
- (event: GestureResponderEvent) => {
167
- if (onPress) {
168
- ReactNativeHapticFeedback.trigger("impactLight");
169
- onPress(event);
92
+ background: {
93
+ default: hexToRgba(
94
+ IOColors[theme["tab-item-background-selected"]],
95
+ 0
96
+ ),
97
+ selected: hexToRgba(
98
+ IOColors[theme["tab-item-background-selected"]],
99
+ 0.25
100
+ ),
101
+ pressed: IOColors[theme["appBackground-primary"]]
102
+ },
103
+ foreground: {
104
+ default: theme["tab-item-foreground-default"],
105
+ selected: theme["tab-item-foreground-selected"],
106
+ disabled: "grey-700"
170
107
  }
171
108
  },
172
- [onPress]
173
- );
174
-
175
- return (
176
- <Pressable
177
- ref={ref}
178
- accessibilityLabel={accessibilityLabel}
179
- accessibilityHint={accessibilityHint}
180
- accessibilityRole="tab"
181
- accessibilityState={{ checked: !!selected }}
182
- testID={testID}
183
- onPress={handleOnPress}
184
- onPressIn={onPressIn}
185
- onPressOut={onPressOut}
186
- accessible={true}
187
- disabled={disabled}
109
+ dark: {
110
+ border: {
111
+ default: hexToRgba(IOColors.white, 0),
112
+ selected: IOColors.white
113
+ },
114
+ background: {
115
+ default: hexToRgba(IOColors.white, 0.1),
116
+ selected: IOColors.white,
117
+ pressed: IOColors.white
118
+ },
119
+ foreground: {
120
+ default: "white",
121
+ selected: "black",
122
+ disabled: "white"
123
+ }
124
+ }
125
+ }),
126
+ [theme]
127
+ );
128
+
129
+ const itemState: TabItemState = selected
130
+ ? "selected"
131
+ : disabled
132
+ ? "disabled"
133
+ : "default";
134
+
135
+ const foregroundColor = mapColorStates[color].foreground[itemState];
136
+
137
+ const selectedStateTransition = useDerivedValue(() =>
138
+ withSpring(selected ? 1 : 0, IOSpringValues.selection)
139
+ );
140
+
141
+ // Interpolate animation values from `pressed` values
142
+ const animatedStyle = useAnimatedStyle(() => ({
143
+ backgroundColor: interpolateColor(
144
+ selectedStateTransition.value,
145
+ [0, 1],
146
+ [
147
+ mapColorStates[color].background.default,
148
+ mapColorStates[color].background.selected
149
+ ]
150
+ ),
151
+ borderColor: interpolateColor(
152
+ selectedStateTransition.value,
153
+ [0, 1],
154
+ [
155
+ mapColorStates[color].border.default,
156
+ mapColorStates[color].border.selected
157
+ ]
158
+ )
159
+ }));
160
+
161
+ const activeIcon = selected ? iconSelected ?? icon : icon;
162
+
163
+ const handleOnPress = useCallback(
164
+ (event: GestureResponderEvent) => {
165
+ if (onPress) {
166
+ ReactNativeHapticFeedback.trigger("impactLight");
167
+ onPress(event);
168
+ }
169
+ },
170
+ [onPress]
171
+ );
172
+
173
+ return (
174
+ <Pressable
175
+ ref={ref}
176
+ accessibilityLabel={accessibilityLabel}
177
+ accessibilityHint={accessibilityHint}
178
+ accessibilityRole="tab"
179
+ accessibilityState={{ checked: !!selected }}
180
+ testID={testID}
181
+ onPress={handleOnPress}
182
+ onPressIn={onPressIn}
183
+ onPressOut={onPressOut}
184
+ accessible={true}
185
+ disabled={disabled}
186
+ >
187
+ <Animated.View
188
+ style={[
189
+ styles.container,
190
+ { columnGap: 4 },
191
+ !disabled && !reducedMotion && scaleAnimatedStyle,
192
+ animatedStyle,
193
+ disabled && { opacity: DISABLED_OPACITY }
194
+ ]}
188
195
  >
189
- <Animated.View
190
- style={[
191
- styles.container,
192
- { columnGap: 4 },
193
- !disabled && !reducedMotion && scaleAnimatedStyle,
194
- animatedStyle,
195
- disabled && { opacity: DISABLED_OPACITY }
196
- ]}
197
- >
198
- {activeIcon && (
199
- <Icon name={activeIcon} color={foregroundColor} size={16} />
200
- )}
201
- <IOText size={14} weight="Semibold" color={foregroundColor}>
202
- {label}
203
- </IOText>
204
- </Animated.View>
205
- </Pressable>
206
- );
207
- }
208
- );
196
+ {activeIcon && (
197
+ <Icon name={activeIcon} color={foregroundColor} size={16} />
198
+ )}
199
+ <IOText size={14} weight="Semibold" color={foregroundColor}>
200
+ {label}
201
+ </IOText>
202
+ </Animated.View>
203
+ </Pressable>
204
+ );
205
+ };
209
206
 
210
207
  const styles = StyleSheet.create({
211
208
  container: {
@@ -55,6 +55,7 @@ type InputTextProps = WithTestID<{
55
55
  icon?: IOIcons;
56
56
  rightElement?: ReactNode;
57
57
  counterLimit?: number;
58
+ showCounterOnlyWhenLimitReached?: boolean;
58
59
  accessibilityAnnounceLimitReached?: string;
59
60
  bottomMessage?: string;
60
61
  bottomMessageColor?: IOColors;
@@ -123,6 +124,7 @@ type InputTextHelperRow = Pick<
123
124
  InputTextProps,
124
125
  | "value"
125
126
  | "counterLimit"
127
+ | "showCounterOnlyWhenLimitReached"
126
128
  | "bottomMessage"
127
129
  | "bottomMessageColor"
128
130
  | "inputType"
@@ -132,6 +134,7 @@ type InputTextHelperRow = Pick<
132
134
  const HelperRow = ({
133
135
  value,
134
136
  counterLimit,
137
+ showCounterOnlyWhenLimitReached,
135
138
  bottomMessage,
136
139
  bottomMessageColor,
137
140
  inputType,
@@ -153,13 +156,17 @@ const HelperRow = ({
153
156
  const bottomMessageColorValue =
154
157
  bottomMessageColor ?? bottomMessageColorDefault;
155
158
 
159
+ const shouldShowCounter =
160
+ !!counterLimit &&
161
+ (!showCounterOnlyWhenLimitReached || valueCount >= counterLimit);
162
+
156
163
  const helperRowStyle: ViewStyle = useMemo(() => {
157
- if (counterLimit && bottomMessage) {
164
+ if (shouldShowCounter && bottomMessage) {
158
165
  return {
159
166
  justifyContent: "space-between"
160
167
  };
161
168
  }
162
- if (counterLimit) {
169
+ if (shouldShowCounter) {
163
170
  return {
164
171
  justifyContent: "flex-end"
165
172
  };
@@ -170,7 +177,7 @@ const HelperRow = ({
170
177
  };
171
178
  }
172
179
  return {};
173
- }, [counterLimit, bottomMessage]);
180
+ }, [shouldShowCounter, bottomMessage]);
174
181
 
175
182
  return (
176
183
  <View
@@ -193,7 +200,7 @@ const HelperRow = ({
193
200
  {bottomMessage}
194
201
  </BodySmall>
195
202
  )}
196
- {counterLimit && (
203
+ {shouldShowCounter && (
197
204
  <BodySmall
198
205
  accessibilityLiveRegion="polite"
199
206
  weight="Regular"
@@ -218,6 +225,7 @@ export const TextInputBase = ({
218
225
  icon,
219
226
  rightElement,
220
227
  counterLimit,
228
+ showCounterOnlyWhenLimitReached,
221
229
  accessibilityAnnounceLimitReached,
222
230
  bottomMessage,
223
231
  bottomMessageColor,
@@ -556,6 +564,7 @@ export const TextInputBase = ({
556
564
  bottomMessage={bottomMessage}
557
565
  bottomMessageColor={bottomMessageColor}
558
566
  counterLimit={counterLimit}
567
+ showCounterOnlyWhenLimitReached={showCounterOnlyWhenLimitReached}
559
568
  inputType={inputType}
560
569
  textInputProps={textInputProps}
561
570
  />
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ComponentProps,
3
- forwardRef,
3
+ Ref,
4
4
  useCallback,
5
5
  useImperativeHandle,
6
6
  useMemo,
@@ -25,6 +25,13 @@ type TextInputValidationProps = Omit<
25
25
  ComponentProps<typeof TextInputBase>,
26
26
  "rightElement" | "status" | "bottomMessageColor" | "isPassword"
27
27
  > & {
28
+ ref?: Ref<TextInputValidationRefProps>;
29
+ /**
30
+ * If true, the character counter will only be displayed/announced when the counter limit is reached.
31
+ * If false or undefined, the character counter will be displayed/announced whenever the counter is enabled,
32
+ * including before the user starts typing (for example, `0 / limit`).
33
+ */
34
+ showCounterOnlyWhenLimitReached?: boolean;
28
35
  /**
29
36
  * This function can return either a `boolean` or a `ValidationWithOptions` object.
30
37
  * If a `boolean` is returned and the field is not valid, the value of the errorMessage prop will be displayed/announced.
@@ -57,132 +64,125 @@ function isValidationWithOptions(
57
64
 
58
65
  const feedbackIconSize: IOIconSizeScale = 24;
59
66
 
60
- export const TextInputValidation = forwardRef<
61
- TextInputValidationRefProps,
62
- TextInputValidationProps
63
- >(
64
- (
65
- {
66
- onValidate,
67
- errorMessage,
68
- value,
69
- bottomMessage,
70
- onBlur,
71
- onFocus,
72
- validationMode = "onBlur",
73
- accessibilityErrorLabel,
74
- ...props
75
- },
76
- ref
77
- ) => {
78
- const theme = useIOTheme();
79
- const [isValid, setIsValid] = useState<boolean | undefined>(undefined);
80
- const [errMessage, setErrMessage] = useState(errorMessage);
81
-
82
- const getErrorFeedback = useCallback(
83
- (isValid: boolean, message: string) => {
84
- setIsValid(isValid);
85
- setErrMessage(message);
86
-
87
- if (!isValid) {
88
- triggerHaptic("notificationError");
89
- AccessibilityInfo.announceForAccessibilityWithOptions(
90
- accessibilityErrorLabel ?? message,
91
- {
92
- queue: true
93
- }
94
- );
95
- } else {
96
- triggerHaptic("notificationSuccess");
97
- }
98
- },
99
- [accessibilityErrorLabel]
100
- );
101
-
102
- const validateInput = useCallback(() => {
103
- const validation = onValidate(value);
104
-
105
- if (isValidationWithOptions(validation)) {
106
- getErrorFeedback(validation.isValid, validation.errorMessage);
67
+ export const TextInputValidation = ({
68
+ onValidate,
69
+ errorMessage,
70
+ value,
71
+ bottomMessage,
72
+ onBlur,
73
+ onFocus,
74
+ validationMode = "onBlur",
75
+ accessibilityErrorLabel,
76
+ ref,
77
+ ...props
78
+ }: TextInputValidationProps) => {
79
+ const theme = useIOTheme();
80
+ const [isValid, setIsValid] = useState<boolean | undefined>(undefined);
81
+ const [errMessage, setErrMessage] = useState(errorMessage);
82
+
83
+ const getErrorFeedback = useCallback(
84
+ (isValid: boolean, message: string) => {
85
+ setIsValid(isValid);
86
+ setErrMessage(message);
87
+
88
+ if (!isValid) {
89
+ triggerHaptic("notificationError");
90
+ AccessibilityInfo.announceForAccessibilityWithOptions(
91
+ accessibilityErrorLabel ?? message,
92
+ {
93
+ queue: true
94
+ }
95
+ );
107
96
  } else {
108
- getErrorFeedback(validation, errorMessage);
109
- }
110
- }, [value, errorMessage, onValidate, getErrorFeedback]);
111
-
112
- // Expose the validateInput function to the parent component
113
- useImperativeHandle(ref, () => ({
114
- validateInput
115
- }));
116
-
117
- const onBlurHandler = useCallback(() => {
118
- if (validationMode === "onBlur") {
119
- validateInput();
97
+ triggerHaptic("notificationSuccess");
120
98
  }
121
- onBlur?.();
122
- }, [validationMode, validateInput, onBlur]);
99
+ },
100
+ [accessibilityErrorLabel]
101
+ );
123
102
 
124
- const onFocusHandler = useCallback(() => {
125
- setIsValid(undefined);
126
- onFocus?.();
127
- }, [onFocus]);
103
+ const validateInput = useCallback(() => {
104
+ const validation = onValidate(value);
105
+
106
+ if (isValidationWithOptions(validation)) {
107
+ getErrorFeedback(validation.isValid, validation.errorMessage);
108
+ } else {
109
+ getErrorFeedback(validation, errorMessage);
110
+ }
111
+ }, [value, errorMessage, onValidate, getErrorFeedback]);
112
+
113
+ // Expose the validateInput function to the parent component
114
+ useImperativeHandle(ref, () => ({
115
+ validateInput
116
+ }));
117
+
118
+ const onBlurHandler = useCallback(() => {
119
+ if (validationMode === "onBlur") {
120
+ validateInput();
121
+ }
122
+ onBlur?.();
123
+ }, [validationMode, validateInput, onBlur]);
124
+
125
+ const onFocusHandler = useCallback(() => {
126
+ setIsValid(undefined);
127
+ onFocus?.();
128
+ }, [onFocus]);
129
+
130
+ const labelError = useMemo(
131
+ () => (isValid === false && errMessage ? errMessage : bottomMessage),
132
+ [isValid, errMessage, bottomMessage]
133
+ );
128
134
 
129
- const labelError = useMemo(
130
- () => (isValid === false && errMessage ? errMessage : bottomMessage),
131
- [isValid, errMessage, bottomMessage]
132
- );
135
+ const labelErrorColor: IOColors | undefined = useMemo(
136
+ () => (isValid === false && errMessage ? theme.errorText : undefined),
137
+ [isValid, errMessage, theme.errorText]
138
+ );
133
139
 
134
- const labelErrorColor: IOColors | undefined = useMemo(
135
- () => (isValid === false && errMessage ? theme.errorText : undefined),
136
- [isValid, errMessage, theme.errorText]
137
- );
140
+ const feedbackIconAttrMap: Record<
141
+ string,
142
+ { name: IOIcons; color: IOColors }
143
+ > = useMemo(
144
+ () => ({
145
+ valid: {
146
+ name: "success",
147
+ color: theme.successIcon
148
+ },
149
+ notValid: {
150
+ name: "errorFilled",
151
+ color: theme.errorIcon
152
+ }
153
+ }),
154
+ [theme]
155
+ );
138
156
 
139
- const feedbackIconAttrMap: Record<
140
- string,
141
- { name: IOIcons; color: IOColors }
142
- > = useMemo(
143
- () => ({
144
- valid: {
145
- name: "success",
146
- color: theme.successIcon
147
- },
148
- notValid: {
149
- name: "errorFilled",
150
- color: theme.errorIcon
151
- }
152
- }),
153
- [theme]
157
+ const feedbackIcon = useMemo(() => {
158
+ const validationStatus = isValid ? "valid" : "notValid";
159
+
160
+ return isValid !== undefined ? (
161
+ <Animated.View
162
+ entering={enterTransitionInputIcon}
163
+ exiting={exitTransitionInputIcon}
164
+ >
165
+ <Icon
166
+ name={feedbackIconAttrMap[validationStatus].name}
167
+ color={feedbackIconAttrMap[validationStatus].color}
168
+ size={feedbackIconSize}
169
+ />
170
+ </Animated.View>
171
+ ) : (
172
+ <View style={{ width: feedbackIconSize, height: feedbackIconSize }} />
154
173
  );
174
+ }, [feedbackIconAttrMap, isValid]);
155
175
 
156
- const feedbackIcon = useMemo(() => {
157
- const validationStatus = isValid ? "valid" : "notValid";
158
-
159
- return isValid !== undefined ? (
160
- <Animated.View
161
- entering={enterTransitionInputIcon}
162
- exiting={exitTransitionInputIcon}
163
- >
164
- <Icon
165
- name={feedbackIconAttrMap[validationStatus].name}
166
- color={feedbackIconAttrMap[validationStatus].color}
167
- size={feedbackIconSize}
168
- />
169
- </Animated.View>
170
- ) : (
171
- <View style={{ width: feedbackIconSize, height: feedbackIconSize }} />
172
- );
173
- }, [feedbackIconAttrMap, isValid]);
174
-
175
- return (
176
- <TextInputBase
177
- {...props}
178
- value={value}
179
- status={isValid === false ? "error" : undefined}
180
- bottomMessage={labelError}
181
- bottomMessageColor={labelErrorColor}
182
- rightElement={feedbackIcon}
183
- onBlur={onBlurHandler}
184
- onFocus={onFocusHandler}
185
- />
186
- );
187
- }
188
- );
176
+ return (
177
+ <TextInputBase
178
+ {...props}
179
+ value={value}
180
+ status={isValid === false ? "error" : undefined}
181
+ bottomMessage={labelError}
182
+ bottomMessageColor={labelErrorColor}
183
+ rightElement={feedbackIcon}
184
+ onBlur={onBlurHandler}
185
+ onFocus={onFocusHandler}
186
+ />
187
+ );
188
+ };