UNPKG

8.93 kBJavaScriptView Raw
1function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
3import * as React from 'react';
4import color from 'color';
5import { Animated, View, StyleSheet, Easing, ScrollView, Text, Platform, I18nManager } from 'react-native';
6import Surface from '../../Surface';
7import Icon from '../../Icon';
8import TouchableRipple from '../../TouchableRipple/TouchableRipple';
9import { withTheme } from '../../../core/theming';
10import { white, black } from '../../../styles/colors';
11import AnimatedText from '../../Typography/AnimatedText';
12import { getCombinedStyles } from './utils';
13const SIZE = 56;
14const BORDER_RADIUS = SIZE / 2;
15const SCALE = 0.9;
16
17const AnimatedFAB = _ref => {
18 let {
19 icon,
20 label,
21 accessibilityLabel = label,
22 accessibilityState,
23 color: customColor,
24 disabled,
25 onPress,
26 onLongPress,
27 theme,
28 style,
29 visible = true,
30 uppercase = true,
31 testID,
32 animateFrom = 'right',
33 extended = false,
34 iconMode = 'dynamic',
35 ...rest
36 } = _ref;
37 const isIOS = Platform.OS === 'ios';
38 const isAnimatedFromRight = animateFrom === 'right';
39 const isIconStatic = iconMode === 'static';
40 const {
41 isRTL
42 } = I18nManager;
43 const {
44 current: visibility
45 } = React.useRef(new Animated.Value(visible ? 1 : 0));
46 const {
47 current: animFAB
48 } = React.useRef(new Animated.Value(0));
49 const {
50 scale
51 } = theme.animation;
52 const [textWidth, setTextWidth] = React.useState(0);
53 const [textHeight, setTextHeight] = React.useState(0);
54 React.useEffect(() => {
55 if (visible) {
56 Animated.timing(visibility, {
57 toValue: 1,
58 duration: 200 * scale,
59 useNativeDriver: true
60 }).start();
61 } else {
62 Animated.timing(visibility, {
63 toValue: 0,
64 duration: 150 * scale,
65 useNativeDriver: true
66 }).start();
67 }
68 }, [visible, scale, visibility]);
69 const disabledColor = color(theme.dark ? white : black).alpha(0.12).rgb().string();
70 const {
71 backgroundColor = disabled ? disabledColor : theme.colors.accent
72 } = StyleSheet.flatten(style) || {};
73 let foregroundColor;
74
75 if (typeof customColor !== 'undefined') {
76 foregroundColor = customColor;
77 } else if (disabled) {
78 foregroundColor = color(theme.dark ? white : black).alpha(0.32).rgb().string();
79 } else {
80 foregroundColor = !color(backgroundColor).isLight() ? white : 'rgba(0, 0, 0, .54)';
81 }
82
83 const rippleColor = color(foregroundColor).alpha(0.32).rgb().string();
84 const extendedWidth = textWidth + 1.5 * SIZE;
85 const distance = isAnimatedFromRight ? -textWidth - BORDER_RADIUS : textWidth + BORDER_RADIUS;
86 React.useEffect(() => {
87 Animated.timing(animFAB, {
88 toValue: !extended ? 0 : distance,
89 duration: 150 * scale,
90 useNativeDriver: true,
91 easing: Easing.linear
92 }).start();
93 }, [animFAB, scale, distance, extended]);
94
95 const onTextLayout = _ref2 => {
96 let {
97 nativeEvent
98 } = _ref2;
99 const currentWidth = Math.ceil(nativeEvent.lines[0].width);
100 const currentHeight = Math.ceil(nativeEvent.lines[0].height);
101
102 if (currentWidth !== textWidth || currentHeight !== textHeight) {
103 setTextHeight(currentHeight);
104
105 if (isIOS) {
106 return setTextWidth(currentWidth - 12);
107 }
108
109 setTextWidth(currentWidth);
110 }
111 };
112
113 const propForDirection = right => {
114 if (isAnimatedFromRight) {
115 return right;
116 }
117
118 return right.reverse();
119 };
120
121 const combinedStyles = getCombinedStyles({
122 isAnimatedFromRight,
123 isIconStatic,
124 distance,
125 animFAB
126 });
127 return /*#__PURE__*/React.createElement(Surface, _extends({}, rest, {
128 style: [{
129 opacity: visibility,
130 transform: [{
131 scale: visibility
132 }],
133 elevation: isIOS ? 6 : 0
134 }, styles.container, disabled && styles.disabled, style]
135 }), /*#__PURE__*/React.createElement(Animated.View, {
136 style: [{
137 transform: [{
138 scaleY: animFAB.interpolate({
139 inputRange: propForDirection([distance, 0]),
140 outputRange: propForDirection([SCALE, 1])
141 })
142 }]
143 }, styles.standard]
144 }, /*#__PURE__*/React.createElement(View, {
145 style: [StyleSheet.absoluteFill, styles.shadowWrapper]
146 }, /*#__PURE__*/React.createElement(Animated.View, {
147 pointerEvents: "none",
148 style: [StyleSheet.absoluteFill, styles.shadow, {
149 width: extendedWidth,
150 opacity: animFAB.interpolate({
151 inputRange: propForDirection([distance, 0.9 * distance, 0]),
152 outputRange: propForDirection([1, 0.15, 0])
153 })
154 }]
155 }), /*#__PURE__*/React.createElement(Animated.View, {
156 pointerEvents: "none",
157 style: [StyleSheet.absoluteFill, styles.shadow, {
158 opacity: animFAB.interpolate({
159 inputRange: propForDirection([distance, 0.9 * distance, 0]),
160 outputRange: propForDirection([0, 0.85, 1])
161 }),
162 width: SIZE,
163 borderRadius: animFAB.interpolate({
164 inputRange: propForDirection([distance, 0]),
165 outputRange: propForDirection([SIZE / (extendedWidth / SIZE), BORDER_RADIUS])
166 })
167 }, combinedStyles.absoluteFill]
168 })), /*#__PURE__*/React.createElement(Animated.View, {
169 pointerEvents: "box-none",
170 style: [styles.innerWrapper]
171 }, /*#__PURE__*/React.createElement(Animated.View, {
172 style: [styles.standard, {
173 width: extendedWidth,
174 backgroundColor
175 }, combinedStyles.innerWrapper]
176 }, /*#__PURE__*/React.createElement(TouchableRipple, {
177 borderless: true,
178 onPress: onPress,
179 onLongPress: onLongPress,
180 rippleColor: rippleColor,
181 disabled: disabled,
182 accessibilityLabel: accessibilityLabel // @ts-expect-error We keep old a11y props for backwards compat with old RN versions
183 ,
184 accessibilityTraits: disabled ? ['button', 'disabled'] : 'button',
185 accessibilityComponentType: "button",
186 accessibilityRole: "button",
187 accessibilityState: { ...accessibilityState,
188 disabled
189 },
190 testID: testID,
191 style: styles.touchable
192 }, /*#__PURE__*/React.createElement(View, {
193 style: [styles.standard, {
194 width: extendedWidth
195 }]
196 }))))), /*#__PURE__*/React.createElement(Animated.View, {
197 style: [styles.iconWrapper, combinedStyles.iconWrapper],
198 pointerEvents: "none"
199 }, /*#__PURE__*/React.createElement(Icon, {
200 source: icon,
201 size: 24,
202 color: foregroundColor
203 })), /*#__PURE__*/React.createElement(View, {
204 pointerEvents: "none"
205 }, /*#__PURE__*/React.createElement(AnimatedText, {
206 numberOfLines: 1,
207 onTextLayout: isIOS ? onTextLayout : undefined,
208 ellipsizeMode: 'tail',
209 style: [{
210 [isAnimatedFromRight || isRTL ? 'right' : 'left']: isIconStatic ? isIOS ? SIZE - 10 : SIZE - 12 : BORDER_RADIUS
211 }, {
212 minWidth: textWidth,
213 top: -BORDER_RADIUS - textHeight / 2,
214 opacity: animFAB.interpolate({
215 inputRange: propForDirection([distance, 0.7 * distance, 0]),
216 outputRange: propForDirection([1, 0, 0])
217 }),
218 transform: [{
219 translateX: animFAB.interpolate({
220 inputRange: propForDirection([distance, 0]),
221 outputRange: propForDirection([0, SIZE])
222 })
223 }]
224 }, styles.label, uppercase && styles.uppercaseLabel, {
225 color: foregroundColor,
226 ...theme.fonts.medium
227 }]
228 }, label)), !isIOS &&
229 /*#__PURE__*/
230 // Method `onTextLayout` on Android returns sizes of text visible on the screen,
231 // however during render the text in `FAB` isn't fully visible. In order to get
232 // proper text measurements there is a need to additionaly render that text, but
233 // wrapped in absolutely positioned `ScrollView` which height is 0.
234 React.createElement(ScrollView, {
235 style: styles.textPlaceholderContainer
236 }, /*#__PURE__*/React.createElement(Text, {
237 onTextLayout: onTextLayout
238 }, label)));
239};
240
241const styles = StyleSheet.create({
242 standard: {
243 height: SIZE,
244 borderRadius: BORDER_RADIUS
245 },
246 disabled: {
247 elevation: 0
248 },
249 container: {
250 position: 'absolute',
251 backgroundColor: 'transparent',
252 borderRadius: BORDER_RADIUS
253 },
254 innerWrapper: {
255 flexDirection: 'row',
256 overflow: 'hidden',
257 borderRadius: BORDER_RADIUS
258 },
259 shadowWrapper: {
260 elevation: 0
261 },
262 shadow: {
263 elevation: 6,
264 borderRadius: BORDER_RADIUS
265 },
266 touchable: {
267 borderRadius: BORDER_RADIUS
268 },
269 iconWrapper: {
270 alignItems: 'center',
271 justifyContent: 'center',
272 position: 'absolute',
273 height: SIZE,
274 width: SIZE
275 },
276 label: {
277 position: 'absolute'
278 },
279 uppercaseLabel: {
280 textTransform: 'uppercase'
281 },
282 textPlaceholderContainer: {
283 height: 0,
284 position: 'absolute'
285 }
286});
287export default withTheme(AnimatedFAB);
288//# sourceMappingURL=AnimatedFAB.js.map
\No newline at end of file