@hero-design/rn 8.124.2 → 8.125.0

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 (31) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/assets/fonts/BeVietnamPro-Medium.ttf +0 -0
  3. package/assets/fonts/BeVietnamPro-MediumItalic.ttf +0 -0
  4. package/es/index.js +200 -136
  5. package/lib/index.js +200 -136
  6. package/package.json +1 -1
  7. package/src/components/RichTextEditor/RichTextEditor.tsx +88 -74
  8. package/src/components/Search/StyledSearch.tsx +1 -1
  9. package/src/components/TextInput/StyledTextInput.tsx +74 -24
  10. package/src/components/TextInput/index.tsx +126 -103
  11. package/src/components/Typography/Body/StyledBody.tsx +16 -8
  12. package/src/components/Typography/Body/index.tsx +12 -3
  13. package/src/components/Typography/Caption/StyledCaption.tsx +10 -2
  14. package/src/components/Typography/Caption/index.tsx +1 -1
  15. package/src/components/Typography/Label/StyledLabel.tsx +4 -5
  16. package/src/components/Typography/Label/index.tsx +7 -0
  17. package/src/components/Typography/types.ts +1 -0
  18. package/src/theme/components/textInput.ts +32 -19
  19. package/src/theme/components/typography.ts +2 -0
  20. package/src/theme/global/typography.ts +6 -0
  21. package/types/components/TextInput/StyledTextInput.d.ts +29 -15
  22. package/types/components/Typography/Body/StyledBody.d.ts +1 -1
  23. package/types/components/Typography/Body/index.d.ts +6 -3
  24. package/types/components/Typography/Caption/StyledCaption.d.ts +1 -1
  25. package/types/components/Typography/Caption/index.d.ts +1 -1
  26. package/types/components/Typography/Label/StyledLabel.d.ts +1 -0
  27. package/types/components/Typography/Label/index.d.ts +6 -1
  28. package/types/components/Typography/types.d.ts +1 -0
  29. package/types/theme/components/textInput.d.ts +17 -5
  30. package/types/theme/components/typography.d.ts +2 -0
  31. package/types/theme/global/typography.d.ts +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hero-design/rn",
3
- "version": "8.124.2",
3
+ "version": "8.125.0",
4
4
  "license": "MIT",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.js",
@@ -12,7 +12,6 @@ import { Animated, Easing } from 'react-native';
12
12
  import type { ReactElement, Ref } from 'react';
13
13
  import type { LayoutChangeEvent } from 'react-native';
14
14
  import { useTheme } from '../../theme';
15
- import Icon from '../Icon';
16
15
  import {
17
16
  StyledAsteriskLabelInsideTextInput,
18
17
  StyledBorderBackDrop,
@@ -22,6 +21,7 @@ import {
22
21
  StyledErrorAndMaxLengthContainer,
23
22
  StyledErrorContainer,
24
23
  StyledHelperText,
24
+ StyledInputContentContainer,
25
25
  StyledLabelContainerInsideTextInput,
26
26
  StyledLabelInsideTextInput,
27
27
  StyledTextInputAndLabelContainer,
@@ -102,10 +102,7 @@ const RichTextEditor = ({
102
102
  return 'default';
103
103
  }, [isFocused, error, isEmptyValue]);
104
104
 
105
- const [inputSize, setInputSize] = React.useState<{
106
- height: number;
107
- width: number;
108
- }>({ height: 0, width: 0 });
105
+ const [containerHeight, setContainerHeight] = React.useState(0);
109
106
 
110
107
  const focusAnimation = useRef(new Animated.Value(0)).current;
111
108
 
@@ -119,10 +116,11 @@ const RichTextEditor = ({
119
116
  }, [focusAnimation, isEmptyValue, isFocused]);
120
117
 
121
118
  const onLayout = useCallback((event: LayoutChangeEvent) => {
122
- const { height, width } = event.nativeEvent.layout;
123
- setInputSize((prev) => ({ ...prev, height, width }));
119
+ setContainerHeight(event.nativeEvent.layout.height);
124
120
  }, []);
125
121
 
122
+ const backgroundColor = theme.__hd__.textInput.colors.containerBackground;
123
+
126
124
  const handleEditorFocus = useCallback(() => {
127
125
  onFocus?.();
128
126
  setIsFocused(true);
@@ -135,91 +133,107 @@ const RichTextEditor = ({
135
133
 
136
134
  return (
137
135
  <StyledContainer testID={testID}>
138
- <StyledLabelContainerInsideTextInput
139
- themeHasPrefix={false}
136
+ <StyledTextInputContainer
137
+ onLayout={onLayout}
140
138
  themeVariant="text"
141
- pointerEvents="none"
142
- testID="input-label-container"
143
- style={[
144
- {
145
- transformOrigin: 'top left',
146
- },
147
- {
148
- transform: [
139
+ themeState={state}
140
+ >
141
+ <StyledBorderBackDrop
142
+ themeState={state}
143
+ themeFocused={isFocused}
144
+ themeFilled={!isEmptyValue}
145
+ style={{ backgroundColor }}
146
+ />
147
+
148
+ <StyledInputContentContainer themeHasLabel={!!label}>
149
+ <StyledLabelContainerInsideTextInput
150
+ themeVariant="text"
151
+ pointerEvents="none"
152
+ testID="input-label-container"
153
+ style={[
149
154
  {
150
- translateY: focusAnimation.interpolate({
151
- inputRange: [0, 1],
152
- outputRange: [inputSize.height / 2, theme.space.xsmall],
153
- }),
155
+ transformOrigin: 'top left',
154
156
  },
155
157
  {
156
- scale: focusAnimation.interpolate({
157
- inputRange: [0, 1],
158
- outputRange: [1, 0.75],
159
- }),
158
+ transform: [
159
+ {
160
+ translateY: focusAnimation.interpolate({
161
+ inputRange: [0, 1],
162
+ outputRange: [
163
+ Math.max(
164
+ 0,
165
+ (containerHeight -
166
+ theme.__hd__.textInput.lineHeights.label) /
167
+ 2
168
+ ),
169
+ theme.__hd__.textInput.space.labelFocusedTranslateY,
170
+ ],
171
+ }),
172
+ },
173
+ {
174
+ scale: focusAnimation.interpolate({
175
+ inputRange: [0, 1],
176
+ outputRange: [1, 0.75],
177
+ }),
178
+ },
179
+ ],
160
180
  },
161
- ],
162
- },
163
- ]}
164
- >
165
- {!!label && (
166
- <StyledLabelInsideTextInput
167
- style={{
168
- backgroundColor: theme.__hd__.textInput.colors.labelBackground,
169
- }}
170
- testID="input-label"
171
- themeState={state}
181
+ ]}
172
182
  >
173
- {required && (
174
- <StyledAsteriskLabelInsideTextInput
183
+ {!!label && (
184
+ <StyledLabelInsideTextInput
175
185
  style={{
176
186
  backgroundColor:
177
187
  theme.__hd__.textInput.colors.labelBackground,
178
188
  }}
189
+ testID="input-label"
179
190
  themeState={state}
180
191
  >
181
- *
182
- </StyledAsteriskLabelInsideTextInput>
192
+ {required && (
193
+ <StyledAsteriskLabelInsideTextInput
194
+ style={{
195
+ backgroundColor:
196
+ theme.__hd__.textInput.colors.labelBackground,
197
+ }}
198
+ themeState={state}
199
+ >
200
+ *
201
+ </StyledAsteriskLabelInsideTextInput>
202
+ )}
203
+ <Typography.Body intent="muted" numberOfLines={1}>
204
+ {label}
205
+ </Typography.Body>
206
+ </StyledLabelInsideTextInput>
183
207
  )}
184
- <Typography.Body numberOfLines={1}>{label}</Typography.Body>
185
- </StyledLabelInsideTextInput>
186
- )}
187
- </StyledLabelContainerInsideTextInput>
188
- <StyledTextInputContainer onLayout={onLayout}>
189
- <StyledBorderBackDrop themeState={state} themeFocused={isFocused} />
190
-
191
- <StyledTextInputAndLabelContainer>
192
- <RichTextEditorInput
193
- name={name}
194
- value={value}
195
- style={[
196
- style,
197
- {
198
- marginHorizontal:
199
- theme.__hd__.textInput.space.inputHorizontalMargin,
200
- },
201
- ]}
202
- testID="webview"
203
- onChange={onChange}
204
- autoFocus={autoFocus}
205
- editorRef={forwardedRef}
206
- placeholder={placeholder}
207
- onBlur={handleEditorBlur}
208
- onFocus={handleEditorFocus}
209
- onCursorChange={onCursorChange}
210
- />
211
- </StyledTextInputAndLabelContainer>
208
+ </StyledLabelContainerInsideTextInput>
209
+
210
+ <StyledTextInputAndLabelContainer themeHasLabel={!!label}>
211
+ <RichTextEditorInput
212
+ name={name}
213
+ value={value}
214
+ style={[
215
+ style,
216
+ {
217
+ marginHorizontal:
218
+ theme.__hd__.textInput.space.inputHorizontalMargin,
219
+ },
220
+ ]}
221
+ testID="webview"
222
+ onChange={onChange}
223
+ autoFocus={autoFocus}
224
+ editorRef={forwardedRef}
225
+ placeholder={placeholder}
226
+ onBlur={handleEditorBlur}
227
+ onFocus={handleEditorFocus}
228
+ onCursorChange={onCursorChange}
229
+ />
230
+ </StyledTextInputAndLabelContainer>
231
+ </StyledInputContentContainer>
212
232
  </StyledTextInputContainer>
213
233
  <StyledErrorAndHelpTextContainer>
214
234
  <StyledErrorAndMaxLengthContainer>
215
235
  {error ? (
216
236
  <StyledErrorContainer>
217
- <Icon
218
- testID="input-error-icon"
219
- icon="circle-info"
220
- size="xsmall"
221
- intent="danger"
222
- />
223
237
  <StyledError testID="input-error-message">{error}</StyledError>
224
238
  </StyledErrorContainer>
225
239
  ) : (
@@ -48,7 +48,7 @@ export const StyledSuffixContainer = styled(View)(({ theme }) => ({
48
48
  export const StyledInput = styled(TextInput)(({ theme }) => ({
49
49
  textAlignVertical: 'center',
50
50
  fontSize: theme.__hd__.search.fontSizes.text,
51
- color: theme.__hd__.textInput.colors.text,
51
+ color: theme.__hd__.textInput.colors.text.default,
52
52
  alignSelf: 'stretch',
53
53
  flexGrow: 1,
54
54
  flexShrink: 1,
@@ -1,10 +1,46 @@
1
1
  import { TextInput, View, StyleSheet, Animated } from 'react-native';
2
2
  import styled from '@emotion/native';
3
+ import type { Theme } from '@emotion/react';
3
4
  import Typography from '../Typography';
4
5
 
5
6
  export type State = 'default' | 'filled' | 'disabled' | 'readonly' | 'error';
6
7
  type Variant = 'text' | 'textarea';
7
8
 
9
+ const genBorderWidth = (
10
+ theme: Theme,
11
+ themeState: State,
12
+ themeFocused: boolean
13
+ ): number => {
14
+ if (themeState === 'readonly') return 0;
15
+ return themeFocused
16
+ ? theme.__hd__.textInput.borderWidths.container.focused
17
+ : theme.__hd__.textInput.borderWidths.container.normal;
18
+ };
19
+
20
+ const genBorderColor = (
21
+ theme: Theme,
22
+ themeState: State,
23
+ themeFocused: boolean,
24
+ themeFilled: boolean
25
+ ): string => {
26
+ if (themeState === 'error' && !themeFilled) {
27
+ return theme.__hd__.textInput.colors.borders.default;
28
+ }
29
+ if (themeState === 'error' && themeFilled) {
30
+ return theme.__hd__.textInput.colors.borders.error;
31
+ }
32
+ if (themeFocused) {
33
+ return (
34
+ theme.__hd__.textInput.colors.borders.focused ??
35
+ theme.__hd__.textInput.colors.borders.default
36
+ );
37
+ }
38
+ return (
39
+ theme.__hd__.textInput.colors.borders[themeState] ??
40
+ theme.__hd__.textInput.colors.borders.default
41
+ );
42
+ };
43
+
8
44
  const StyledContainer = styled(View)(({ theme }) => ({
9
45
  width: '100%',
10
46
  marginTop: theme.__hd__.textInput.space.containerMarginTop,
@@ -12,17 +48,14 @@ const StyledContainer = styled(View)(({ theme }) => ({
12
48
 
13
49
  const StyledLabelContainerInsideTextInput = styled(Animated.View)<{
14
50
  themeVariant: Variant;
15
- themeHasPrefix: boolean;
16
- }>(({ themeVariant, themeHasPrefix, theme }) => ({
51
+ }>(({ themeVariant, theme }) => ({
17
52
  flexDirection: 'row',
18
53
  alignItems: themeVariant === 'text' ? 'center' : 'flex-start',
19
54
  position: 'absolute',
20
55
  zIndex: 1,
21
- left: themeHasPrefix
22
- ? theme.space.xxlarge
23
- : theme.space.medium + theme.space.small,
56
+ left: 0,
24
57
  right: theme.space.medium,
25
- top: -theme.__hd__.textInput.space.labelTop,
58
+ top: 0,
26
59
  }));
27
60
 
28
61
  const StyledLabelInsideTextInput = styled(View)<{
@@ -52,7 +85,6 @@ const StyledErrorContainer = styled(View)(({ theme }) => ({
52
85
 
53
86
  const StyledError = styled(Typography.Caption)(({ theme }) => ({
54
87
  color: theme.__hd__.textInput.colors.error,
55
- marginLeft: theme.__hd__.textInput.space.errorMarginLeft,
56
88
  }));
57
89
 
58
90
  const StyledMaxLengthMessage = styled(Typography.Caption)<{
@@ -71,7 +103,7 @@ const StyledTextInput = styled(TextInput)<{ themeVariant: Variant }>(
71
103
  fontSize: theme.__hd__.textInput.fontSizes.text,
72
104
  alignSelf: 'stretch',
73
105
  flexGrow: 2,
74
- marginHorizontal: theme.__hd__.textInput.space.inputHorizontalMargin,
106
+ marginHorizontal: 0,
75
107
  paddingVertical: 0,
76
108
  maxHeight: theme.__hd__.textInput.sizes.textInputMaxHeight,
77
109
  height:
@@ -85,37 +117,54 @@ const StyledTextInput = styled(TextInput)<{ themeVariant: Variant }>(
85
117
  const StyledBorderBackDrop = styled(View)<{
86
118
  themeState: State;
87
119
  themeFocused: boolean;
88
- }>(({ theme, themeFocused, themeState }) => ({
120
+ themeFilled: boolean;
121
+ }>(({ theme, themeFocused, themeState, themeFilled }) => ({
89
122
  ...StyleSheet.absoluteFillObject,
90
- borderWidth: themeFocused
91
- ? theme.__hd__.textInput.borderWidths.container.focused
92
- : theme.__hd__.textInput.borderWidths.container.normal,
123
+ borderWidth: genBorderWidth(theme, themeState, themeFocused),
93
124
  borderRadius: theme.__hd__.textInput.radii.container,
94
- borderColor:
95
- theme.__hd__.textInput.colors.borders[themeState] ??
96
- theme.__hd__.textInput.colors.borders.default,
125
+ borderColor: genBorderColor(theme, themeState, themeFocused, themeFilled),
97
126
  }));
98
127
 
99
- const StyledTextInputContainer = styled(View)(({ theme }) => ({
128
+ const StyledTextInputContainer = styled(View)<{
129
+ themeVariant: Variant;
130
+ themeState: State;
131
+ }>(({ theme, themeVariant, themeState }) => ({
100
132
  flexDirection: 'row',
101
133
  alignItems: 'center',
102
- padding: theme.__hd__.textInput.space.containerPadding,
103
- backgroundColor: theme.__hd__.textInput.colors.containerBackground,
134
+ paddingHorizontal: theme.__hd__.textInput.space.containerHorizontalPadding,
135
+ paddingVertical: theme.__hd__.textInput.space.containerVerticalPadding,
104
136
  borderRadius: theme.__hd__.textInput.radii.container,
137
+ ...(themeVariant === 'text' && {
138
+ minHeight: theme.__hd__.textInput.sizes.containerMinHeight,
139
+ }),
140
+ ...(themeState === 'disabled' && { opacity: 0.5 }),
141
+ gap: theme.space.smallMedium,
105
142
  }));
106
143
 
107
- const StyledTextInputAndLabelContainer = styled(View)(() => ({
144
+ // Outer wrapper that owns flex-grow/shrink and provides the positioning context
145
+ // for StyledLabelContainerInsideTextInput (position: absolute).
146
+ const StyledInputContentContainer = styled(View)<{ themeHasLabel: boolean }>(
147
+ ({ themeHasLabel }) => ({
148
+ flexGrow: 2,
149
+ flexShrink: 1,
150
+ alignSelf: 'stretch',
151
+ justifyContent: themeHasLabel ? 'flex-start' : 'center',
152
+ })
153
+ );
154
+
155
+ const StyledTextInputAndLabelContainer = styled(View)<{
156
+ themeHasLabel: boolean;
157
+ }>(({ theme, themeHasLabel }) => ({
108
158
  flexDirection: 'row',
109
159
  alignItems: 'center',
110
160
  alignSelf: 'stretch',
111
- flexGrow: 2,
112
- flexShrink: 1,
161
+ ...(themeHasLabel && {
162
+ paddingTop: theme.__hd__.textInput.space.inputAndLabelContainerPaddingTop,
163
+ }),
113
164
  }));
114
165
 
115
166
  const StyledErrorAndHelpTextContainer = styled(View)(({ theme }) => ({
116
- paddingHorizontal:
117
- theme.__hd__.textInput.space.errorAndHelpTextContainerHorizontalPadding,
118
- minHeight: theme.__hd__.textInput.sizes.errorAndHelpTextContainerHeight,
167
+ minHeight: theme.__hd__.textInput.sizes.errorAndHelpTextContainerMinHeight,
119
168
  paddingTop: theme.__hd__.textInput.space.errorAndHelpTextContainerPaddingTop,
120
169
  }));
121
170
 
@@ -136,6 +185,7 @@ export {
136
185
  StyledContainer,
137
186
  StyledErrorContainer,
138
187
  StyledHelperText,
188
+ StyledInputContentContainer,
139
189
  StyledTextInputAndLabelContainer,
140
190
  StyledLabelContainerInsideTextInput,
141
191
  StyledErrorAndHelpTextContainer,