@widergy/mobile-ui 1.31.3 → 1.32.1

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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [1.32.1](https://github.com/widergy/mobile-ui/compare/v1.32.0...v1.32.1) (2024-11-15)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * [UG-2249] disabled prop added in UTSwitch ([#388](https://github.com/widergy/mobile-ui/issues/388)) ([49181f4](https://github.com/widergy/mobile-ui/commit/49181f45b61662dbedc31c377f9b013ec4a5efa5))
7
+
8
+ # [1.32.0](https://github.com/widergy/mobile-ui/compare/v1.31.3...v1.32.0) (2024-11-06)
9
+
10
+
11
+ ### Features
12
+
13
+ * [UGC-924] animations ut tracker ([#386](https://github.com/widergy/mobile-ui/issues/386)) ([8032b66](https://github.com/widergy/mobile-ui/commit/8032b66d2c3712672cc7bd5391f4aac81a9a3aca))
14
+
1
15
  ## [1.31.3](https://github.com/widergy/mobile-ui/compare/v1.31.2...v1.31.3) (2024-11-05)
2
16
 
3
17
 
@@ -9,6 +9,7 @@ import ownStyles from './styles';
9
9
  import switchProptypes from './proptypes';
10
10
 
11
11
  const UTSwitch = ({
12
+ disabled,
12
13
  style,
13
14
  onColor,
14
15
  offColor,
@@ -96,7 +97,7 @@ const UTSwitch = ({
96
97
 
97
98
  return (
98
99
  <TouchableWithoutFeedback
99
- disabled={disablePress}
100
+ disabled={disablePress || disabled}
100
101
  onPressIn={onClick}
101
102
  onPressOut={onClickEnd}
102
103
  hitSlop={hitSlop}
@@ -116,6 +117,7 @@ const UTSwitch = ({
116
117
  ]
117
118
  })
118
119
  },
120
+ disabled && ownStyles.disabled,
119
121
  style
120
122
  ]}
121
123
  >
@@ -1,7 +1,8 @@
1
- import { string, func, any, shape, number } from 'prop-types';
1
+ import { string, func, any, shape, number, bool } from 'prop-types';
2
2
  import { ViewPropTypes } from 'deprecated-react-native-prop-types';
3
3
 
4
4
  export default {
5
+ disabled: bool,
5
6
  style: ViewPropTypes.style,
6
7
  onColor: string,
7
8
  offColor: string,
@@ -12,5 +12,8 @@ export default StyleSheet.create({
12
12
  backgroundColor: 'red',
13
13
  position: 'absolute',
14
14
  zIndex: -1
15
+ },
16
+ disabled: {
17
+ opacity: 0.5
15
18
  }
16
19
  });
@@ -0,0 +1,54 @@
1
+ import { bool, number, string } from 'prop-types';
2
+ import React, { useEffect, useRef, useState } from 'react';
3
+ import { Animated, View } from 'react-native';
4
+
5
+ import { useTheme } from '../../../../../../theming';
6
+ import { mergeMultipleStyles } from '../../../../../../utils/styleUtils';
7
+ import { BAR_HEIGHT_DELAY, BAR_HEIGHT_DURATION } from '../../constants';
8
+
9
+ import styles, { getVariantStyles } from './styles';
10
+
11
+ const BarMask = ({ stepCompleted, height, variant }) => {
12
+ const theme = useTheme();
13
+ const ownStyles = mergeMultipleStyles(styles, getVariantStyles(theme)[variant]);
14
+
15
+ const heightAnim = useRef(new Animated.Value(0)).current;
16
+ const [inverted, setInverted] = useState(false);
17
+
18
+ const animateCompleted = () => {
19
+ Animated.sequence([
20
+ Animated.delay(BAR_HEIGHT_DELAY),
21
+ Animated.timing(heightAnim, {
22
+ toValue: height,
23
+ duration: BAR_HEIGHT_DURATION / 2,
24
+ useNativeDriver: false
25
+ }),
26
+ Animated.timing(heightAnim, {
27
+ toValue: 0,
28
+ duration: BAR_HEIGHT_DURATION / 2,
29
+ useNativeDriver: false
30
+ })
31
+ ]).start();
32
+ setTimeout(() => setInverted(true), BAR_HEIGHT_DELAY + BAR_HEIGHT_DURATION / 2);
33
+ };
34
+
35
+ useEffect(() => {
36
+ if (stepCompleted) animateCompleted();
37
+ else setInverted(false);
38
+ // eslint-disable-next-line react-hooks/exhaustive-deps
39
+ }, [stepCompleted]);
40
+
41
+ return (
42
+ <View style={ownStyles.container(height)}>
43
+ <Animated.View style={[ownStyles.barMask, inverted && ownStyles.inverted, { height: heightAnim }]} />
44
+ </View>
45
+ );
46
+ };
47
+
48
+ BarMask.propTypes = {
49
+ stepCompleted: bool,
50
+ height: number,
51
+ variant: string
52
+ };
53
+
54
+ export default BarMask;
@@ -0,0 +1,31 @@
1
+ import { StyleSheet } from 'react-native';
2
+
3
+ import { OVAL_SIZE } from '../../../../styles';
4
+ import { ERROR, STANDARD } from '../../../../constants';
5
+
6
+ export default StyleSheet.create({
7
+ container: height => ({
8
+ height,
9
+ width: OVAL_SIZE,
10
+ top: 20,
11
+ position: 'absolute',
12
+ flex: 1,
13
+ justifyContent: 'center',
14
+ flexDirection: 'row',
15
+ zIndex: 10
16
+ }),
17
+ barMask: {
18
+ width: 2,
19
+ alignSelf: 'flex-start'
20
+ },
21
+ inverted: { alignSelf: 'flex-end' }
22
+ });
23
+
24
+ export const getVariantStyles = theme => ({
25
+ [ERROR]: {
26
+ barMask: { backgroundColor: theme.Palette.error['04'] }
27
+ },
28
+ [STANDARD]: {
29
+ barMask: { backgroundColor: theme.Palette.accent['04'] }
30
+ }
31
+ });
@@ -0,0 +1,84 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+ import { View, Animated } from 'react-native';
3
+ import MaskedView from '@react-native-masked-view/masked-view';
4
+ import { bool, string } from 'prop-types';
5
+
6
+ import { mergeMultipleStyles } from '../../../../../../utils/styleUtils';
7
+ import { useTheme } from '../../../../../../theming';
8
+ import {
9
+ ACTIVE_STEP_HEIGHT_DELAY,
10
+ ACTIVE_STEP_HEIGHT_DURATION,
11
+ COMPLETED_STEP_HEIGHT_DELAY,
12
+ COMPLETED_STEP_HEIGHT_DURATION
13
+ } from '../../constants';
14
+
15
+ import styles, { getVariantStyles, MASK_SIZE } from './styles';
16
+
17
+ const StepMask = ({ stepCompleted, stepActive, variant }) => {
18
+ const theme = useTheme();
19
+ const ownStyles = mergeMultipleStyles(styles, getVariantStyles(theme)[variant]);
20
+
21
+ const heightAnim = useRef(new Animated.Value(MASK_SIZE)).current;
22
+
23
+ const handleCompleted = () => {
24
+ heightAnim.setValue(MASK_SIZE);
25
+ Animated.sequence([
26
+ Animated.delay(COMPLETED_STEP_HEIGHT_DELAY),
27
+ Animated.timing(heightAnim, {
28
+ toValue: 0,
29
+ duration: COMPLETED_STEP_HEIGHT_DURATION,
30
+ useNativeDriver: false
31
+ })
32
+ ]).start();
33
+ };
34
+
35
+ const handleActive = () => {
36
+ heightAnim.setValue(0);
37
+ Animated.sequence([
38
+ Animated.delay(ACTIVE_STEP_HEIGHT_DELAY),
39
+ Animated.timing(heightAnim, {
40
+ toValue: MASK_SIZE,
41
+ duration: ACTIVE_STEP_HEIGHT_DURATION,
42
+ useNativeDriver: false
43
+ })
44
+ ]).start();
45
+ };
46
+
47
+ useEffect(() => {
48
+ if (stepCompleted) handleCompleted();
49
+ // eslint-disable-next-line react-hooks/exhaustive-deps
50
+ }, [stepCompleted]);
51
+
52
+ useEffect(() => {
53
+ if (stepActive) handleActive();
54
+ // eslint-disable-next-line react-hooks/exhaustive-deps
55
+ }, [stepActive]);
56
+
57
+ return (
58
+ <View style={ownStyles.container}>
59
+ <MaskedView
60
+ style={ownStyles.maskContainer}
61
+ maskElement={
62
+ <View style={ownStyles.mask}>
63
+ <View style={ownStyles.maskElement} />
64
+ </View>
65
+ }
66
+ >
67
+ {/* Shows behind the mask */}
68
+ {(stepCompleted || stepActive) && (
69
+ <Animated.View
70
+ style={[ownStyles.maskBackground, stepActive && ownStyles.maskActive, { height: heightAnim }]}
71
+ />
72
+ )}
73
+ </MaskedView>
74
+ </View>
75
+ );
76
+ };
77
+
78
+ StepMask.propTypes = {
79
+ stepCompleted: bool,
80
+ stepActive: bool,
81
+ variant: string
82
+ };
83
+
84
+ export default StepMask;
@@ -0,0 +1,38 @@
1
+ import { StyleSheet } from 'react-native';
2
+
3
+ import { ERROR, STANDARD } from '../../../../constants';
4
+
5
+ export const MASK_SIZE = 20;
6
+
7
+ export default StyleSheet.create({
8
+ container: {
9
+ height: MASK_SIZE,
10
+ width: MASK_SIZE,
11
+ position: 'absolute',
12
+ zIndex: 1
13
+ },
14
+ mask: {
15
+ // Transparent background because mask is based off alpha channel.
16
+ backgroundColor: 'transparent',
17
+ flex: 1
18
+ },
19
+ maskElement: {
20
+ height: MASK_SIZE,
21
+ width: MASK_SIZE,
22
+ borderColor: 'black',
23
+ borderWidth: 2,
24
+ borderRadius: MASK_SIZE / 2
25
+ },
26
+ maskBackground: { flex: 1, alignSelf: 'flex-end' },
27
+ maskContainer: { flex: 1, flexDirection: 'row', height: '100%' },
28
+ maskActive: { alignSelf: 'flex-start' }
29
+ });
30
+
31
+ export const getVariantStyles = theme => ({
32
+ [ERROR]: {
33
+ maskBackground: { backgroundColor: theme.Palette.error['04'] }
34
+ },
35
+ [STANDARD]: {
36
+ maskBackground: { backgroundColor: theme.Palette.accent['04'] }
37
+ }
38
+ });
@@ -0,0 +1,10 @@
1
+ export const COMPLETED_STEP_OPACITY_DURATION = 300;
2
+ export const COMPLETED_STEP_HEIGHT_DELAY = 800;
3
+ export const COMPLETED_STEP_HEIGHT_DURATION = 300;
4
+
5
+ export const ACTIVE_STEP_DELAY = 1800;
6
+ export const ACTIVE_STEP_HEIGHT_DELAY = 1100;
7
+ export const ACTIVE_STEP_HEIGHT_DURATION = 300;
8
+
9
+ export const BAR_HEIGHT_DELAY = 800;
10
+ export const BAR_HEIGHT_DURATION = 600;
@@ -1,6 +1,6 @@
1
- import React, { useEffect, useState } from 'react';
2
- import { View } from 'react-native';
3
- import { bool, func, number } from 'prop-types';
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import { View, Animated } from 'react-native';
3
+ import { array, bool, func, number } from 'prop-types';
4
4
  import merge from 'lodash/merge';
5
5
 
6
6
  import { StepPropTypes, VariantPropTypes } from '../../propTypes';
@@ -8,12 +8,26 @@ import UTIcon from '../../../UTIcon';
8
8
  import UTLabel from '../../../UTLabel';
9
9
  import { ERROR } from '../../constants';
10
10
  import { useTheme } from '../../../../theming';
11
+ import { OVAL_SIZE } from '../../styles';
11
12
 
12
13
  import { getVariantStyles, getStepStyles } from './styles';
14
+ import StepMask from './components/StepMask';
15
+ import BarMask from './components/BarMask';
16
+ import { ACTIVE_STEP_DELAY, COMPLETED_STEP_OPACITY_DURATION } from './constants';
13
17
 
14
18
  const ERROR_ICON_SIZE = 16;
15
19
 
16
- const Step = ({ first, index, isActive, isCompleted, setStepsPositions, step, style = {}, variant }) => {
20
+ const Step = ({
21
+ first,
22
+ index,
23
+ isActive,
24
+ isCompleted,
25
+ stepsPositions,
26
+ setStepsPositions,
27
+ step,
28
+ style = {},
29
+ variant
30
+ }) => {
17
31
  const stepCompleted = isCompleted(step.id);
18
32
  const stepActive = isActive(step.id);
19
33
 
@@ -23,6 +37,25 @@ const Step = ({ first, index, isActive, isCompleted, setStepsPositions, step, st
23
37
  const [stepIconOffset, setStepIconOffset] = useState(0);
24
38
  const [currentPosition, setCurrentPosition] = useState(0);
25
39
 
40
+ const opacityAnim = useRef(new Animated.Value(1)).current;
41
+ const animateCompleted = () => {
42
+ Animated.timing(opacityAnim, {
43
+ toValue: 0.5,
44
+ duration: COMPLETED_STEP_OPACITY_DURATION,
45
+ useNativeDriver: true
46
+ }).start();
47
+ };
48
+
49
+ const [delayedStepActive, setDelayedStepActive] = useState(false);
50
+ // eslint-disable-next-line consistent-return
51
+ useEffect(() => {
52
+ if (stepActive) {
53
+ const timer = setTimeout(() => setDelayedStepActive(true), index === 0 ? 0 : ACTIVE_STEP_DELAY);
54
+ return () => clearTimeout(timer);
55
+ }
56
+ setDelayedStepActive(false);
57
+ }, [index, stepActive]);
58
+
26
59
  useEffect(() => {
27
60
  setStepsPositions(prev => {
28
61
  const newPrev = [...prev];
@@ -31,43 +64,54 @@ const Step = ({ first, index, isActive, isCompleted, setStepsPositions, step, st
31
64
  });
32
65
  }, [stepIconOffset, currentPosition, setCurrentPosition, index, setStepsPositions]);
33
66
 
67
+ useEffect(() => {
68
+ if (stepCompleted) animateCompleted();
69
+ else opacityAnim.setValue(1);
70
+ // eslint-disable-next-line react-hooks/exhaustive-deps
71
+ }, [stepCompleted]);
72
+
73
+ const barHeight = Math.max((stepsPositions[index + 1] ?? 0) - (stepsPositions[index] ?? 0) - OVAL_SIZE, 24);
74
+
34
75
  return (
35
76
  <View
36
77
  onLayout={e => setCurrentPosition(e.nativeEvent.layout.y)}
37
78
  style={[
38
79
  themedStyles.outerContainer,
39
80
  stepCompleted && themedStyles.completedOuterContainer,
40
- stepActive && themedStyles.activeOuterContainer,
81
+ delayedStepActive && themedStyles.activeOuterContainer,
41
82
  !first && ownStyles.stepMargin
42
83
  ]}
43
84
  >
44
- <View
85
+ <StepMask stepCompleted={stepCompleted} stepActive={stepActive} variant={variant} />
86
+ <BarMask stepCompleted={stepCompleted} height={barHeight} variant={variant} />
87
+ <Animated.View
45
88
  onLayout={e => setStepIconOffset(e.nativeEvent.layout.y)}
46
89
  style={[
47
90
  themedStyles.container,
48
91
  stepCompleted && themedStyles.completedContainer,
49
- stepActive && themedStyles.activeContainer
92
+ { opacity: opacityAnim },
93
+ delayedStepActive && themedStyles.activeContainer
50
94
  ]}
51
95
  >
52
96
  <View
53
97
  style={[
54
98
  themedStyles.innerContainer,
55
99
  stepCompleted && themedStyles.completedInnerContainer,
56
- stepActive && themedStyles.activeInnerContainer
100
+ delayedStepActive && themedStyles.activeInnerContainer
57
101
  ]}
58
102
  >
59
- {stepActive && variant === ERROR && (
103
+ {delayedStepActive && variant === ERROR && (
60
104
  <UTIcon name="IconX" colorTheme="negative" size={ERROR_ICON_SIZE} style={themedStyles.icon} />
61
105
  )}
62
106
  </View>
63
- </View>
64
- <View style={themedStyles.textContainer}>
107
+ </Animated.View>
108
+ <Animated.View style={[themedStyles.textContainer, { opacity: opacityAnim }]}>
65
109
  {step.title && (
66
110
  <UTLabel
67
111
  variant="small"
68
112
  weight="medium"
69
113
  colorTheme={
70
- stepCompleted ? 'gray' : stepActive ? (variant === ERROR ? 'error' : 'accent') : 'dark'
114
+ stepCompleted ? 'gray' : delayedStepActive ? (variant === ERROR ? 'error' : 'accent') : 'dark'
71
115
  }
72
116
  >
73
117
  {step.title}
@@ -80,7 +124,7 @@ const Step = ({ first, index, isActive, isCompleted, setStepsPositions, step, st
80
124
  </UTLabel>
81
125
  </View>
82
126
  )}
83
- </View>
127
+ </Animated.View>
84
128
  </View>
85
129
  );
86
130
  };
@@ -91,6 +135,7 @@ Step.propTypes = {
91
135
  isActive: func,
92
136
  isCompleted: func,
93
137
  setStepsPositions: func,
138
+ stepsPositions: array,
94
139
  step: StepPropTypes,
95
140
  variant: VariantPropTypes
96
141
  };
@@ -17,9 +17,6 @@ export const getVariantStyles = theme => ({
17
17
  },
18
18
  completedInnerContainer: {
19
19
  backgroundColor: theme.Palette.error['04']
20
- },
21
- completedOuterContainer: {
22
- opacity: 0.5
23
20
  }
24
21
  },
25
22
  [STANDARD]: {
@@ -34,9 +31,6 @@ export const getVariantStyles = theme => ({
34
31
  },
35
32
  completedInnerContainer: {
36
33
  backgroundColor: theme.Palette.accent['04']
37
- },
38
- completedOuterContainer: {
39
- opacity: 0.5
40
34
  }
41
35
  }
42
36
  });
@@ -84,6 +84,7 @@ const UTTracker = ({
84
84
  index,
85
85
  isActive,
86
86
  isCompleted,
87
+ stepsPositions,
87
88
  setStepsPositions,
88
89
  step,
89
90
  variant
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@widergy/mobile-ui",
3
3
  "description": "Widergy Mobile Components",
4
4
  "author": "widergy",
5
- "version": "1.31.3",
5
+ "version": "1.32.1",
6
6
  "repository": "https://github.com/widergy/mobile-ui.git",
7
7
  "main": "lib/index.js",
8
8
  "files": [
@@ -35,6 +35,7 @@
35
35
  "react-native-vector-icons": "^10.0.0"
36
36
  },
37
37
  "dependencies": {
38
+ "@react-native-masked-view/masked-view": "^0.3.1",
38
39
  "@react-navigation/native": "^6.1.9",
39
40
  "@tabler/icons-react-native": "^3.3.0",
40
41
  "@widergy/web-utils": "^2.0.0",