@hero-design/rn 8.124.1 → 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 (33) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/assets/fonts/BeVietnamPro-Medium.ttf +0 -0
  3. package/assets/fonts/BeVietnamPro-MediumItalic.ttf +0 -0
  4. package/es/index.js +212 -138
  5. package/lib/index.js +212 -138
  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/Select/MultiSelect/index.tsx +6 -1
  10. package/src/components/Select/SingleSelect/index.tsx +10 -2
  11. package/src/components/TextInput/StyledTextInput.tsx +74 -24
  12. package/src/components/TextInput/index.tsx +126 -103
  13. package/src/components/Typography/Body/StyledBody.tsx +16 -8
  14. package/src/components/Typography/Body/index.tsx +12 -3
  15. package/src/components/Typography/Caption/StyledCaption.tsx +10 -2
  16. package/src/components/Typography/Caption/index.tsx +1 -1
  17. package/src/components/Typography/Label/StyledLabel.tsx +4 -5
  18. package/src/components/Typography/Label/index.tsx +7 -0
  19. package/src/components/Typography/types.ts +1 -0
  20. package/src/theme/components/textInput.ts +32 -19
  21. package/src/theme/components/typography.ts +2 -0
  22. package/src/theme/global/typography.ts +6 -0
  23. package/types/components/TextInput/StyledTextInput.d.ts +29 -15
  24. package/types/components/Typography/Body/StyledBody.d.ts +1 -1
  25. package/types/components/Typography/Body/index.d.ts +6 -3
  26. package/types/components/Typography/Caption/StyledCaption.d.ts +1 -1
  27. package/types/components/Typography/Caption/index.d.ts +1 -1
  28. package/types/components/Typography/Label/StyledLabel.d.ts +1 -0
  29. package/types/components/Typography/Label/index.d.ts +6 -1
  30. package/types/components/Typography/types.d.ts +1 -0
  31. package/types/theme/components/textInput.d.ts +17 -5
  32. package/types/theme/components/typography.d.ts +2 -0
  33. 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.1",
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,
@@ -107,6 +107,11 @@ function MultiSelect<V, T extends SelectOptionType<V>>({
107
107
  setOpen(true);
108
108
  }, []);
109
109
 
110
+ let selectSuffix: TextInputProps['suffix'];
111
+ if (editable && !disabled) {
112
+ selectSuffix = open ? 'arrow-up' : 'arrow-down';
113
+ }
114
+
110
115
  useEffect(() => {
111
116
  setSelectingValue(value);
112
117
  }, [open, value]);
@@ -131,7 +136,7 @@ function MultiSelect<V, T extends SelectOptionType<V>>({
131
136
  {...inputProps}
132
137
  label={label}
133
138
  value={renderSelectedValue ? rawValue : displayedValue}
134
- suffix="arrow-down"
139
+ suffix={selectSuffix}
135
140
  multiline
136
141
  error={error}
137
142
  editable={editable}
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useRef, useState } from 'react';
1
+ import React, { useCallback, useMemo, useRef, useState } from 'react';
2
2
  import type {
3
3
  TextInputProps as NativeTextInputProps,
4
4
  SectionList,
@@ -90,6 +90,14 @@ const SingleSelect = <V, T extends SelectOptionType<V>>({
90
90
  setOpen(true);
91
91
  }, []);
92
92
 
93
+ const selectSuffix: TextInputProps['suffix'] = useMemo(() => {
94
+ if (editable && !disabled) {
95
+ return open ? 'arrow-up' : 'arrow-down';
96
+ }
97
+
98
+ return undefined;
99
+ }, [editable, disabled, open]);
100
+
93
101
  return (
94
102
  <>
95
103
  <View
@@ -111,7 +119,7 @@ const SingleSelect = <V, T extends SelectOptionType<V>>({
111
119
  {...inputProps}
112
120
  label={label}
113
121
  value={renderSelectedValue ? rawValue : displayedValue}
114
- suffix="arrow-down"
122
+ suffix={selectSuffix}
115
123
  multiline
116
124
  error={error}
117
125
  editable={editable}
@@ -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,