UNPKG

3.66 kBTypeScriptView Raw
1import * as React from 'react';
2import color from 'color';
3import {
4 Animated,
5 StyleSheet,
6 StyleProp,
7 TextStyle,
8 LayoutChangeEvent,
9} from 'react-native';
10import AnimatedText from './Typography/AnimatedText';
11import { withTheme } from '../core/theming';
12import type { $Omit } from '../types';
13
14type Props = $Omit<
15 $Omit<React.ComponentPropsWithRef<typeof AnimatedText>, 'padding'>,
16 'type'
17> & {
18 /**
19 * Type of the helper text.
20 */
21 type: 'error' | 'info';
22 /**
23 * Whether to display the helper text.
24 */
25 visible?: boolean;
26 /**
27 * Whether to apply padding to the helper text.
28 */
29 padding?: 'none' | 'normal';
30 /**
31 * Text content of the HelperText.
32 */
33 children: React.ReactNode;
34 style?: StyleProp<TextStyle>;
35 /**
36 * @optional
37 */
38 theme: ReactNativePaper.Theme;
39 /**
40 * TestID used for testing purposes
41 */
42 testID?: string;
43};
44
45/**
46 * Helper text is used in conjuction with input elements to provide additional hints for the user.
47 *
48 * <div class="screenshots">
49 * <img class="medium" src="screenshots/helper-text.gif" />
50 * </div>
51 *
52 * ## Usage
53 * ```js
54 * import * as React from 'react';
55 * import { View } from 'react-native';
56 * import { HelperText, TextInput } from 'react-native-paper';
57 *
58 * const MyComponent = () => {
59 * const [text, setText] = React.useState('');
60 *
61 * const onChangeText = text => setText(text);
62 *
63 * const hasErrors = () => {
64 * return !text.includes('@');
65 * };
66 *
67 * return (
68 * <View>
69 * <TextInput label="Email" value={text} onChangeText={onChangeText} />
70 * <HelperText type="error" visible={hasErrors()}>
71 * Email address is invalid!
72 * </HelperText>
73 * </View>
74 * );
75 * };
76 *
77 * export default MyComponent;
78 * ```
79 */
80const HelperText = ({
81 style,
82 type = 'info',
83 visible = true,
84 theme,
85 onLayout,
86 padding = 'normal',
87 ...rest
88}: Props) => {
89 const { current: shown } = React.useRef<Animated.Value>(
90 new Animated.Value(visible ? 1 : 0)
91 );
92
93 let { current: textHeight } = React.useRef<number>(0);
94
95 const { scale } = theme.animation;
96
97 React.useEffect(() => {
98 if (visible) {
99 // show text
100 Animated.timing(shown, {
101 toValue: 1,
102 duration: 150 * scale,
103 useNativeDriver: true,
104 }).start();
105 } else {
106 // hide text
107 Animated.timing(shown, {
108 toValue: 0,
109 duration: 180 * scale,
110 useNativeDriver: true,
111 }).start();
112 }
113 }, [visible, scale, shown]);
114
115 const handleTextLayout = (e: LayoutChangeEvent) => {
116 onLayout?.(e);
117 textHeight = e.nativeEvent.layout.height;
118 };
119
120 const { colors, dark } = theme;
121
122 const textColor =
123 type === 'error'
124 ? colors.error
125 : color(colors.text)
126 .alpha(dark ? 0.7 : 0.54)
127 .rgb()
128 .string();
129
130 return (
131 <AnimatedText
132 onLayout={handleTextLayout}
133 style={[
134 styles.text,
135 padding !== 'none' ? styles.padding : {},
136 {
137 color: textColor,
138 opacity: shown,
139 transform:
140 visible && type === 'error'
141 ? [
142 {
143 translateY: shown.interpolate({
144 inputRange: [0, 1],
145 outputRange: [-textHeight / 2, 0],
146 }),
147 },
148 ]
149 : [],
150 },
151 style,
152 ]}
153 {...rest}
154 >
155 {rest.children}
156 </AnimatedText>
157 );
158};
159
160const styles = StyleSheet.create({
161 text: {
162 fontSize: 12,
163 paddingVertical: 4,
164 },
165 padding: {
166 paddingHorizontal: 12,
167 },
168});
169
170export default withTheme(HelperText);