1 | function _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 |
|
3 | import * as React from 'react';
|
4 | import color from 'color';
|
5 | import { Animated, View, StyleSheet, Easing, ScrollView, Text, Platform, I18nManager } from 'react-native';
|
6 | import Surface from '../../Surface';
|
7 | import Icon from '../../Icon';
|
8 | import TouchableRipple from '../../TouchableRipple/TouchableRipple';
|
9 | import { withTheme } from '../../../core/theming';
|
10 | import { white, black } from '../../../styles/colors';
|
11 | import AnimatedText from '../../Typography/AnimatedText';
|
12 | import { getCombinedStyles } from './utils';
|
13 | const SIZE = 56;
|
14 | const BORDER_RADIUS = SIZE / 2;
|
15 | const SCALE = 0.9;
|
16 |
|
17 | const 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 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 | }), 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 | }, React.createElement(View, {
|
145 | style: [StyleSheet.absoluteFill, styles.shadowWrapper]
|
146 | }, 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 | }), 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 | })), React.createElement(Animated.View, {
|
169 | pointerEvents: "box-none",
|
170 | style: [styles.innerWrapper]
|
171 | }, React.createElement(Animated.View, {
|
172 | style: [styles.standard, {
|
173 | width: extendedWidth,
|
174 | backgroundColor
|
175 | }, combinedStyles.innerWrapper]
|
176 | }, React.createElement(TouchableRipple, {
|
177 | borderless: true,
|
178 | onPress: onPress,
|
179 | onLongPress: onLongPress,
|
180 | rippleColor: rippleColor,
|
181 | disabled: disabled,
|
182 | accessibilityLabel: accessibilityLabel
|
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 | }, React.createElement(View, {
|
193 | style: [styles.standard, {
|
194 | width: extendedWidth
|
195 | }]
|
196 | }))))), React.createElement(Animated.View, {
|
197 | style: [styles.iconWrapper, combinedStyles.iconWrapper],
|
198 | pointerEvents: "none"
|
199 | }, React.createElement(Icon, {
|
200 | source: icon,
|
201 | size: 24,
|
202 | color: foregroundColor
|
203 | })), React.createElement(View, {
|
204 | pointerEvents: "none"
|
205 | }, 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 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 | React.createElement(ScrollView, {
|
235 | style: styles.textPlaceholderContainer
|
236 | }, React.createElement(Text, {
|
237 | onTextLayout: onTextLayout
|
238 | }, label)));
|
239 | };
|
240 |
|
241 | const 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 | });
|
287 | export default withTheme(AnimatedFAB);
|
288 |
|
\ | No newline at end of file |