@hero-design/rn 8.75.0 → 8.76.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hero-design/rn",
3
- "version": "8.75.0",
3
+ "version": "8.76.0",
4
4
  "license": "MIT",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.js",
@@ -145,6 +145,8 @@ const BottomSheet = ({
145
145
 
146
146
  onAnimationEnd?.();
147
147
  });
148
+
149
+ return () => animation.stop();
148
150
  }, [open]);
149
151
 
150
152
  const interpolateY = animatedValue.current.interpolate({
@@ -1,9 +1,15 @@
1
1
  import styled from '@emotion/native';
2
- import { TouchableOpacity, View } from 'react-native';
2
+ import { TextInput, TouchableOpacity, View } from 'react-native';
3
3
  import type { ViewProps } from 'react-native';
4
4
  import Typography from '../Typography';
5
5
  import { BodyProps } from '../Typography/Body';
6
6
 
7
+ export type ToolbarMessageState =
8
+ | 'default'
9
+ | 'filled'
10
+ | 'disabled'
11
+ | 'readonly';
12
+
7
13
  const ToolbarWrapper = styled(View)<ViewProps>(({ theme }) => ({
8
14
  position: 'absolute',
9
15
  bottom: 0,
@@ -65,6 +71,48 @@ const StyledLabel = styled(Typography.Body)<{
65
71
  : theme.__hd__.typography.colors[intent],
66
72
  }));
67
73
 
74
+ const ToolbarMessageWrapper = styled(View)(({ theme }) => ({
75
+ flex: 1,
76
+ flexDirection: 'row',
77
+ justifyContent: 'space-between',
78
+ alignItems: 'center',
79
+ height: theme.__hd__.toolbar.sizes.messageWrapperHeight,
80
+ paddingVertical: theme.__hd__.toolbar.space.messageWrapperPaddingVertical,
81
+ paddingHorizontal: theme.__hd__.toolbar.space.messageWrapperPaddingHorizontal,
82
+ }));
83
+
84
+ export const StyledInputContainer = styled(View)(({ theme }) => {
85
+ return {
86
+ flexDirection: 'row',
87
+ alignItems: 'center',
88
+ flex: 1,
89
+ backgroundColor: theme.__hd__.toolbar.colors.inputContainerBackground,
90
+ borderRadius: theme.__hd__.toolbar.radii.messageContainer,
91
+ height: theme.__hd__.toolbar.sizes.messageInputHeight,
92
+ paddingHorizontal: theme.__hd__.toolbar.space.messageInputPaddingHorizontal,
93
+ paddingVertical: theme.__hd__.toolbar.space.messageInputPaddingVertical,
94
+ };
95
+ });
96
+
97
+ export const StyledInput = styled(TextInput)(({ theme }) => ({
98
+ textAlignVertical: 'center',
99
+ fontSize: theme.__hd__.toolbar.fontSizes.text,
100
+ alignSelf: 'stretch',
101
+ flexGrow: 1,
102
+ flexShrink: 1,
103
+ fontFamily: theme.__hd__.toolbar.fonts.text,
104
+ }));
105
+
106
+ export const StyledPrefix = styled(View)(({ theme }) => ({
107
+ marginRight: theme.__hd__.toolbar.space.affixInnerMargin,
108
+ maxHeight: '100%',
109
+ }));
110
+
111
+ export const StyledSuffix = styled(View)(({ theme }) => ({
112
+ marginLeft: theme.__hd__.toolbar.space.affixInnerMargin,
113
+ maxHeight: '100%',
114
+ }));
115
+
68
116
  export {
69
117
  ToolbarWrapper,
70
118
  ToolbarGroupWrapper,
@@ -72,4 +120,5 @@ export {
72
120
  IconButtonWrapper,
73
121
  IconButtonLabel,
74
122
  StyledLabel,
123
+ ToolbarMessageWrapper,
75
124
  };
@@ -0,0 +1,169 @@
1
+ import React, { forwardRef } from 'react';
2
+
3
+ import { TextInput as RNTextInput } from 'react-native';
4
+ import type {
5
+ TextInputProps as NativeTextInputProps,
6
+ StyleProp,
7
+ ViewStyle,
8
+ TextStyle,
9
+ } from 'react-native';
10
+
11
+ import {
12
+ StyledInput,
13
+ StyledInputContainer,
14
+ StyledPrefix,
15
+ StyledSuffix,
16
+ ToolbarMessageState,
17
+ ToolbarMessageWrapper,
18
+ } from './StyledToolbar';
19
+
20
+ export type ToolbarMessageHandles = Pick<
21
+ RNTextInput,
22
+ 'focus' | 'clear' | 'blur' | 'isFocused' | 'setNativeProps'
23
+ >;
24
+
25
+ export interface ToolbarMessageProps extends NativeTextInputProps {
26
+ /**
27
+ * Element to render on the left side of the input, before the user's cursor.
28
+ */
29
+ prefix?: React.ReactNode;
30
+ /**
31
+ * Element to render on the right side of the input.
32
+ */
33
+ suffix?: React.ReactNode;
34
+ /**
35
+ * Additional wrapper style.
36
+ */
37
+ style?: StyleProp<ViewStyle>;
38
+ /**
39
+ * Input text style.
40
+ */
41
+ textStyle?: StyleProp<TextStyle>;
42
+ /**
43
+ * Testing id of the component.
44
+ */
45
+ testID?: string;
46
+ /**
47
+ * Accessibility label for the input (Android).
48
+ */
49
+ accessibilityLabelledBy?: string;
50
+ /**
51
+ * Placeholder text to display.
52
+ * */
53
+ placeholder?: string;
54
+ /**
55
+ * Whether the input is editable.
56
+ * */
57
+ editable?: boolean;
58
+ /**
59
+ * Whether the input is disabled.
60
+ */
61
+ disabled?: boolean;
62
+ /**
63
+ * The max length of the input.
64
+ * If the max length is set, the input will display the current length and the max length.
65
+ * */
66
+ maxLength?: number;
67
+ /**
68
+ * Component ref.
69
+ */
70
+ ref?: React.Ref<ToolbarMessageHandles>;
71
+ }
72
+
73
+ export const getState = ({
74
+ disabled,
75
+ editable,
76
+ isEmptyValue,
77
+ }: {
78
+ disabled?: boolean;
79
+ editable?: boolean;
80
+ isEmptyValue?: boolean;
81
+ }): ToolbarMessageState => {
82
+ switch (true) {
83
+ case disabled:
84
+ return 'disabled';
85
+ case !editable:
86
+ return 'readonly';
87
+ case !isEmptyValue:
88
+ return 'filled';
89
+ default:
90
+ return 'default';
91
+ }
92
+ };
93
+
94
+ const ToolbarMessage = forwardRef<ToolbarMessageHandles, ToolbarMessageProps>(
95
+ (props, forwardedRef) => {
96
+ const {
97
+ prefix,
98
+ suffix,
99
+ style,
100
+ testID,
101
+ value,
102
+ defaultValue,
103
+ disabled,
104
+ editable = true,
105
+ textStyle,
106
+ ...nativeProps
107
+ } = props;
108
+
109
+ const innerTextInput = React.useRef<RNTextInput | undefined | null>();
110
+
111
+ const displayText = (value !== undefined ? value : defaultValue) ?? '';
112
+ const isEmptyValue = displayText.length === 0;
113
+
114
+ React.useImperativeHandle(
115
+ forwardedRef,
116
+ () => ({
117
+ isFocused: () => innerTextInput.current?.isFocused() || false,
118
+ getNativeTextInputRef: () => innerTextInput.current,
119
+ setNativeProps: (args: NativeTextInputProps) =>
120
+ innerTextInput.current?.setNativeProps(args),
121
+ focus: () => innerTextInput.current?.focus(),
122
+ blur: () => innerTextInput.current?.blur(),
123
+ clear: () => innerTextInput.current?.clear(),
124
+ }),
125
+ [innerTextInput.current]
126
+ );
127
+
128
+ const state = getState({
129
+ disabled,
130
+ editable,
131
+ isEmptyValue,
132
+ });
133
+
134
+ return (
135
+ <ToolbarMessageWrapper testID={testID} style={style}>
136
+ {prefix && (
137
+ <StyledPrefix testID={testID && `${testID}-prefix`}>
138
+ {prefix}
139
+ </StyledPrefix>
140
+ )}
141
+ <StyledInputContainer
142
+ pointerEvents={
143
+ state === 'disabled' || state === 'readonly' ? 'none' : 'auto'
144
+ }
145
+ testID={testID && `${testID}-input-container`}
146
+ >
147
+ <StyledInput
148
+ {...nativeProps}
149
+ value={value}
150
+ defaultValue={defaultValue}
151
+ editable={editable}
152
+ testID={testID && `${testID}-input`}
153
+ ref={(rnTextInputRef) => {
154
+ innerTextInput.current = rnTextInputRef;
155
+ }}
156
+ style={textStyle}
157
+ />
158
+ </StyledInputContainer>
159
+ {suffix && (
160
+ <StyledSuffix testID={testID && `${testID}-suffix`}>
161
+ {suffix}
162
+ </StyledSuffix>
163
+ )}
164
+ </ToolbarMessageWrapper>
165
+ );
166
+ }
167
+ );
168
+
169
+ export default ToolbarMessage;
@@ -0,0 +1,161 @@
1
+ import React from 'react';
2
+ import { fireEvent, within } from '@testing-library/react-native';
3
+ import renderWithTheme from '../../../testHelpers/renderWithTheme';
4
+ import ToolbarMessage, { getState } from '../ToolbarMessage';
5
+
6
+ describe('getState', () => {
7
+ it.each`
8
+ disabled | editable | isEmptyValue | expected
9
+ ${false} | ${true} | ${true} | ${'default'}
10
+ ${false} | ${true} | ${false} | ${'filled'}
11
+ ${false} | ${false} | ${true} | ${'readonly'}
12
+ ${true} | ${false} | ${true} | ${'disabled'}
13
+ `(
14
+ 'should return the correct state when disabled $disabled, editable $editable, isEmptyValue $isEmptyValue',
15
+ ({ disabled, editable, isEmptyValue, expected }) => {
16
+ expect(
17
+ getState({
18
+ disabled,
19
+ editable,
20
+ isEmptyValue,
21
+ })
22
+ ).toBe(expected);
23
+ }
24
+ );
25
+ });
26
+
27
+ describe('ToolbarMessage', () => {
28
+ describe('idle', () => {
29
+ it('renders correctly', () => {
30
+ const { getByTestId, toJSON } = renderWithTheme(
31
+ <ToolbarMessage
32
+ prefix="prefix"
33
+ suffix="suffix"
34
+ testID="toolbar-message"
35
+ placeholder="Message"
36
+ />
37
+ );
38
+
39
+ expect(toJSON()).toMatchSnapshot();
40
+ expect(getByTestId('toolbar-message')).toBeTruthy();
41
+ expect(
42
+ within(getByTestId('toolbar-message')).queryAllByTestId(
43
+ 'toolbar-message-input'
44
+ )
45
+ ).toHaveLength(1);
46
+ expect(
47
+ within(getByTestId('toolbar-message')).queryAllByTestId(
48
+ 'toolbar-message-prefix'
49
+ )
50
+ ).toHaveLength(1);
51
+ expect(
52
+ within(getByTestId('toolbar-message')).queryAllByTestId(
53
+ 'toolbar-message-suffix'
54
+ )
55
+ ).toHaveLength(1);
56
+ });
57
+
58
+ describe('handler', () => {
59
+ it('onChangeText work correctly', () => {
60
+ const onChangeText = jest.fn();
61
+ const { getByTestId } = renderWithTheme(
62
+ <ToolbarMessage
63
+ testID="toolbar-message"
64
+ onChangeText={onChangeText}
65
+ />
66
+ );
67
+ const input = within(getByTestId('toolbar-message')).getByTestId(
68
+ 'toolbar-message-input'
69
+ );
70
+ fireEvent.changeText(input, 'number one girl');
71
+ expect(onChangeText).toHaveBeenCalledWith('number one girl');
72
+ });
73
+
74
+ it('onFocus work correctly', () => {
75
+ const onFocus = jest.fn();
76
+ const { getByTestId } = renderWithTheme(
77
+ <ToolbarMessage testID="toolbar-message" onFocus={onFocus} />
78
+ );
79
+ const input = within(getByTestId('toolbar-message')).getByTestId(
80
+ 'toolbar-message-input'
81
+ );
82
+ fireEvent(input, 'focus');
83
+ expect(onFocus).toHaveBeenCalled();
84
+ });
85
+
86
+ it('onBlur work correctly', () => {
87
+ const onBlur = jest.fn();
88
+ const { getByTestId } = renderWithTheme(
89
+ <ToolbarMessage testID="toolbar-message" onBlur={onBlur} />
90
+ );
91
+ const input = within(getByTestId('toolbar-message')).getByTestId(
92
+ 'toolbar-message-input'
93
+ );
94
+ fireEvent(input, 'blur');
95
+ expect(onBlur).toHaveBeenCalled();
96
+ });
97
+ });
98
+ });
99
+
100
+ describe('filled', () => {
101
+ it('renders correctly', () => {
102
+ const { toJSON, queryAllByTestId, queryAllByDisplayValue } =
103
+ renderWithTheme(
104
+ <ToolbarMessage
105
+ testID="toolbar-message"
106
+ placeholder="Message"
107
+ value="Jennie Kim"
108
+ />
109
+ );
110
+
111
+ expect(toJSON()).toMatchSnapshot();
112
+ expect(queryAllByDisplayValue('Jennie Kim')).toHaveLength(1);
113
+ expect(queryAllByTestId('toolbar-message-input')).toHaveLength(1);
114
+ });
115
+ });
116
+
117
+ describe('readonly', () => {
118
+ it('renders correctly', () => {
119
+ const onChangeText = jest.fn();
120
+ const { toJSON, queryAllByTestId, queryAllByDisplayValue, getByTestId } =
121
+ renderWithTheme(
122
+ <ToolbarMessage
123
+ testID="toolbar-message"
124
+ placeholder="Message"
125
+ value="Lalisa"
126
+ onChangeText={onChangeText}
127
+ />
128
+ );
129
+
130
+ expect(toJSON()).toMatchSnapshot();
131
+ expect(queryAllByDisplayValue('Lalisa')).toHaveLength(1);
132
+ expect(queryAllByTestId('toolbar-message-input')).toHaveLength(1);
133
+ expect(getByTestId('toolbar-message-input')).not.toHaveProp(
134
+ 'editable',
135
+ 'false'
136
+ );
137
+ });
138
+ });
139
+
140
+ describe('disabled', () => {
141
+ it('renders correctly', () => {
142
+ const onChangeText = jest.fn();
143
+ const { toJSON, queryAllByTestId, getByTestId } = renderWithTheme(
144
+ <ToolbarMessage
145
+ testID="toolbar-message"
146
+ placeholder="Message"
147
+ value="Jisoo"
148
+ disabled
149
+ onChangeText={onChangeText}
150
+ />
151
+ );
152
+
153
+ expect(toJSON()).toMatchSnapshot();
154
+ expect(queryAllByTestId('toolbar-message-input')).toHaveLength(1);
155
+ expect(getByTestId('toolbar-message-input-container')).toHaveProp(
156
+ 'pointerEvents',
157
+ 'none'
158
+ );
159
+ });
160
+ });
161
+ });