@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/.turbo/turbo-build.log +2 -2
- package/CHANGELOG.md +10 -0
- package/es/index.js +246 -88
- package/lib/index.js +246 -88
- package/package.json +1 -1
- package/src/components/BottomSheet/index.tsx +2 -0
- package/src/components/Toolbar/StyledToolbar.tsx +50 -1
- package/src/components/Toolbar/ToolbarMessage.tsx +169 -0
- package/src/components/Toolbar/__tests__/ToolbarMessage.spec.tsx +161 -0
- package/src/components/Toolbar/__tests__/__snapshots__/ToolbarMessage.spec.tsx.snap +382 -0
- package/src/components/Toolbar/index.tsx +2 -0
- package/src/theme/__tests__/__snapshots__/index.spec.ts.snap +16 -0
- package/src/theme/components/toolbar.ts +19 -1
- package/stats/8.76.0/rn-stats.html +4844 -0
- package/types/components/Toolbar/StyledToolbar.d.ts +33 -2
- package/types/components/Toolbar/ToolbarMessage.d.ts +59 -0
- package/types/components/Toolbar/index.d.ts +1 -0
- package/types/theme/components/toolbar.d.ts +16 -0
package/package.json
CHANGED
|
@@ -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
|
+
});
|