@widergy/mobile-ui 1.31.2 → 1.32.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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [1.32.0](https://github.com/widergy/mobile-ui/compare/v1.31.3...v1.32.0) (2024-11-06)
2
+
3
+
4
+ ### Features
5
+
6
+ * [UGC-924] animations ut tracker ([#386](https://github.com/widergy/mobile-ui/issues/386)) ([8032b66](https://github.com/widergy/mobile-ui/commit/8032b66d2c3712672cc7bd5391f4aac81a9a3aca))
7
+
8
+ ## [1.31.3](https://github.com/widergy/mobile-ui/compare/v1.31.2...v1.31.3) (2024-11-05)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * [EVE-4328] UTRating ([#384](https://github.com/widergy/mobile-ui/issues/384)) ([6485384](https://github.com/widergy/mobile-ui/commit/6485384e2fed5cef0c2c68b88b1cbf8e45d4f3c7))
14
+
1
15
  ## [1.31.2](https://github.com/widergy/mobile-ui/compare/v1.31.1...v1.31.2) (2024-11-05)
2
16
 
3
17
 
@@ -0,0 +1,44 @@
1
+ # UTRating
2
+
3
+ ## Description
4
+
5
+ `UTRating` is a configurable rating input component that enables users to easily provide feedback through visual indicators.
6
+
7
+ ## Props
8
+
9
+ | Name | Type | Default | Description |
10
+ | ------------------- | --------- | ------- | -------------------------------------------------------------------------------------------------------- |
11
+ | classNames | object | | Custom CSS class names for styling the component. |
12
+ | dataTestId | string | | Unique identifier for testing purposes. |
13
+ | disabled | bool | | Indicates whether the input is disabled and cannot be interacted with. |
14
+ | error | string | | Error message to display when validation fails or an issue occurs. |
15
+ | helpTextEnd | string | | Additional help text displayed at the end of the input for user guidance. |
16
+ | helpTextStart | string | | Additional help text displayed at the beginning of the input for user guidance. |
17
+ | onChange | func | | Callback function invoked when the input value changes. |
18
+ | options | array | | Array of options available for selection. |
19
+ | required | bool | | Indicates whether the input is mandatory for form submission. |
20
+ | title | string | | Title for the input, providing context to the user. |
21
+ | validations | array | | Array of validation rules to be applied to the input value. |
22
+ | value | string | | The current value of the input. |
23
+ | variant | string | `star` | Defines the visual style component. |
24
+
25
+
26
+ ### options
27
+
28
+ `options` is an array of objects with a `value` and a `name` key. The values should be in the range of 0-10, except for the `faces` variant. In desktop view, all options are displayed in one row; in responsive view, they are broken into 2 rows if there are more than 5 options.
29
+
30
+ ### variant
31
+
32
+ The value of `variant` must be one of the following:
33
+
34
+ - `faces`: the options are represented as face icons. Option values should exclusively be between 1 and 5, values outside this range are not supported.
35
+ - `star`: the options are represented as star icons.
36
+ - `text`: the options are represented as the option's `text`.
37
+
38
+ ### Structure of Validations
39
+
40
+ For detailed information about the structure of validations, please refer to the UTValidation component documentation.
41
+
42
+ ### Handling Errors
43
+
44
+ Errors can be displayed below the text input using either the `error` prop or the `validations` prop.
@@ -0,0 +1,32 @@
1
+ import { bool, func, object, string } from 'prop-types';
2
+ import React from 'react';
3
+
4
+ import { RATING_VARIANTS } from '../../constants';
5
+ import UTButton from '../../../UTButton';
6
+
7
+ import { getIcon, getVariant } from './utils';
8
+
9
+ const Option = ({ disabled, isSelected, name, onChange, value, variant, wrapperStyle }) => (
10
+ <UTButton
11
+ colorTheme={isSelected ? 'primary' : 'secondary'}
12
+ disabled={disabled}
13
+ Icon={getIcon(variant, value, isSelected)}
14
+ onPress={() => onChange(value)}
15
+ style={{ root: wrapperStyle }}
16
+ variant={getVariant(variant, isSelected)}
17
+ >
18
+ {RATING_VARIANTS.TEXT === variant ? name : null}
19
+ </UTButton>
20
+ );
21
+
22
+ Option.propTypes = {
23
+ disabled: bool,
24
+ isSelected: bool,
25
+ name: string,
26
+ onChange: func,
27
+ value: string,
28
+ variant: string,
29
+ wrapperStyle: object
30
+ };
31
+
32
+ export default Option;
@@ -0,0 +1,17 @@
1
+ import { RATING_VARIANTS } from '../../constants';
2
+
3
+ export const getIcon = (variant, value, isSelected) =>
4
+ ({
5
+ [RATING_VARIANTS.TEXT]: null,
6
+ [RATING_VARIANTS.FACES]: {
7
+ 1: 'IconMoodSad',
8
+ 2: 'IconMoodConfuzed',
9
+ 3: 'IconMoodEmpty',
10
+ 4: 'IconMoodSmile',
11
+ 5: 'IconMoodHappy'
12
+ }[value],
13
+ [RATING_VARIANTS.STAR]: isSelected ? 'IconStarFilled' : 'IconStar'
14
+ })[variant];
15
+
16
+ export const getVariant = (variant, isSelected) =>
17
+ !isSelected || variant === RATING_VARIANTS.STAR ? 'shadow' : 'outlined';
@@ -0,0 +1,5 @@
1
+ export const RATING_VARIANTS = {
2
+ FACES: 'faces',
3
+ STAR: 'star',
4
+ TEXT: 'text'
5
+ };
@@ -0,0 +1,101 @@
1
+ import { func, string, bool, object, array } from 'prop-types';
2
+ import { View } from 'react-native';
3
+ import React, { useCallback, useMemo, useState } from 'react';
4
+
5
+ import { formatErrorToValidation } from '../UTValidation/utils';
6
+ import { withTheme } from '../../theming';
7
+ import UTFieldLabel from '../UTFieldLabel';
8
+ import UTLabel from '../UTLabel';
9
+ import UTValidation from '../UTValidation';
10
+
11
+ import { RATING_VARIANTS } from './constants';
12
+ import Option from './components/Option';
13
+ import styles from './styles';
14
+
15
+ const UTRating = ({
16
+ classNames = {},
17
+ dataTestId,
18
+ disabled,
19
+ error,
20
+ helpTextEnd,
21
+ helpTextStart,
22
+ onChange,
23
+ options = [],
24
+ required,
25
+ theme,
26
+ title,
27
+ validations,
28
+ value,
29
+ variant = RATING_VARIANTS.STAR
30
+ }) => {
31
+ const validationData = useMemo(
32
+ () => validations || (error && formatErrorToValidation(error)),
33
+ [error, validations]
34
+ );
35
+
36
+ const valueIndex = options.map(({ value: optionValue }) => optionValue).indexOf(value);
37
+ const isSelected = index =>
38
+ valueIndex !== -1 && variant === RATING_VARIANTS.STAR ? index <= valueIndex : index === valueIndex;
39
+
40
+ const [containerWidth, setContainerWidth] = useState(0);
41
+
42
+ const onLayout = useCallback(event => {
43
+ const { width } = event.nativeEvent.layout;
44
+ setContainerWidth(width);
45
+ }, []);
46
+
47
+ return (
48
+ <View style={[styles.container, classNames.container]} data-testid={dataTestId}>
49
+ {title && (
50
+ <UTFieldLabel colorTheme="dark" required={required}>
51
+ {title}
52
+ </UTFieldLabel>
53
+ )}
54
+ <View style={styles.optionsContainer} onLayout={onLayout}>
55
+ {options.map((option, index) => (
56
+ <Option
57
+ {...option}
58
+ disabled={disabled}
59
+ error={error}
60
+ isSelected={isSelected(index)}
61
+ key={option.value}
62
+ onChange={onChange}
63
+ variant={variant}
64
+ wrapperStyle={styles.option(containerWidth, error, options, theme)}
65
+ />
66
+ ))}
67
+ </View>
68
+ {helpTextStart && (
69
+ <View style={styles.helpTextContainer}>
70
+ <UTLabel colorTheme="gray" variant="small">
71
+ {helpTextStart}
72
+ </UTLabel>
73
+ {helpTextEnd && (
74
+ <UTLabel colorTheme="gray" variant="small">
75
+ {helpTextEnd}
76
+ </UTLabel>
77
+ )}
78
+ </View>
79
+ )}
80
+ {validationData && <UTValidation validationData={validationData} />}
81
+ </View>
82
+ );
83
+ };
84
+
85
+ UTRating.propTypes = {
86
+ classNames: object,
87
+ dataTestId: string,
88
+ disabled: bool,
89
+ error: string,
90
+ helpTextEnd: string,
91
+ helpTextStart: string,
92
+ onChange: func,
93
+ options: array,
94
+ required: bool,
95
+ title: string,
96
+ validations: array,
97
+ value: string,
98
+ variant: string
99
+ };
100
+
101
+ export default withTheme(UTRating);
@@ -0,0 +1,38 @@
1
+ import { StyleSheet } from 'react-native';
2
+
3
+ export default StyleSheet.create({
4
+ container: {
5
+ display: 'flex',
6
+ flexDirection: 'column',
7
+ gap: 8,
8
+ width: '100%'
9
+ },
10
+ optionsContainer: {
11
+ display: 'flex',
12
+ flexDirection: 'row',
13
+ flexWrap: 'wrap',
14
+ gap: 4,
15
+ justifyContent: 'center',
16
+ width: '100%'
17
+ },
18
+ helpTextContainer: {
19
+ display: 'flex',
20
+ flexDirection: 'row',
21
+ justifyContent: 'space-between'
22
+ },
23
+ option: (containerWidth, error, options, theme) => {
24
+ const topItems = Math.ceil(options.length / 2);
25
+ const breakOptions = options.length > 5;
26
+ return {
27
+ alignContent: 'center',
28
+ display: 'flex',
29
+ flexBasis: breakOptions ? (containerWidth - (topItems - 1) * 4) / topItems : 0,
30
+ flexDirection: 'row',
31
+ flexGrow: breakOptions ? 0 : 1,
32
+ flexShrink: 0,
33
+ height: 48,
34
+ justifyContent: 'center',
35
+ ...(error ? { borderColor: theme.Palette.error['05'], borderWidth: 2 } : {})
36
+ };
37
+ }
38
+ });
@@ -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/lib/index.js CHANGED
@@ -57,6 +57,7 @@ export { default as UTPasswordField } from './components/UTPasswordField';
57
57
  export { default as UTPhoneInput } from './components/UTPhoneInput';
58
58
  export { default as UTProductItem } from './components/UTProductItem';
59
59
  export { default as UTProgressBar } from './components/UTProgressBar';
60
+ export { default as UTRating } from './components/UTRating';
60
61
  export { default as UTRoundView } from './components/UTRoundView';
61
62
  export { default as UTSearchField } from './components/UTSearchField';
62
63
  export { default as UTSelect } from './components/UTSelect';
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.2",
5
+ "version": "1.32.0",
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",