@utilitywarehouse/hearth-react-native 0.23.0 → 0.24.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.
Files changed (31) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +13 -13
  3. package/CHANGELOG.md +36 -0
  4. package/build/components/Modal/Modal.js +5 -4
  5. package/build/components/Modal/Modal.props.d.ts +10 -4
  6. package/build/components/ProgressBar/ProgressBar.d.ts +6 -0
  7. package/build/components/ProgressBar/ProgressBar.js +35 -0
  8. package/build/components/ProgressBar/ProgressBar.props.d.ts +60 -0
  9. package/build/components/ProgressBar/ProgressBar.props.js +1 -0
  10. package/build/components/ProgressBar/ProgressBarCircular.d.ts +6 -0
  11. package/build/components/ProgressBar/ProgressBarCircular.js +115 -0
  12. package/build/components/ProgressBar/ProgressBarLinear.d.ts +6 -0
  13. package/build/components/ProgressBar/ProgressBarLinear.js +79 -0
  14. package/build/components/ProgressBar/index.d.ts +2 -0
  15. package/build/components/ProgressBar/index.js +1 -0
  16. package/build/components/index.d.ts +1 -0
  17. package/build/components/index.js +1 -0
  18. package/docs/components/AllComponents.web.tsx +6 -0
  19. package/package.json +1 -1
  20. package/src/components/Modal/Modal.props.ts +13 -4
  21. package/src/components/Modal/Modal.stories.tsx +1 -1
  22. package/src/components/Modal/Modal.tsx +28 -11
  23. package/src/components/ProgressBar/ProgressBar.docs.mdx +90 -0
  24. package/src/components/ProgressBar/ProgressBar.figma.tsx +79 -0
  25. package/src/components/ProgressBar/ProgressBar.props.ts +60 -0
  26. package/src/components/ProgressBar/ProgressBar.stories.tsx +117 -0
  27. package/src/components/ProgressBar/ProgressBar.tsx +74 -0
  28. package/src/components/ProgressBar/ProgressBarCircular.tsx +181 -0
  29. package/src/components/ProgressBar/ProgressBarLinear.tsx +127 -0
  30. package/src/components/ProgressBar/index.ts +7 -0
  31. package/src/components/index.ts +1 -0
@@ -0,0 +1,181 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+ import { Text, View } from 'react-native';
3
+ import Animated, {
4
+ Easing,
5
+ useAnimatedProps,
6
+ useReducedMotion,
7
+ useSharedValue,
8
+ withTiming,
9
+ } from 'react-native-reanimated';
10
+ import { Circle, G, Svg } from 'react-native-svg';
11
+ import { StyleSheet } from 'react-native-unistyles';
12
+ import useTheme from '../../hooks/useTheme';
13
+ import { BodyText } from '../BodyText';
14
+ import type { ProgressBarInternalProps } from './ProgressBar.props';
15
+
16
+ const AnimatedCircle = Animated.createAnimatedComponent(Circle as React.ComponentType<any>);
17
+
18
+ const ProgressBarCircular = ({
19
+ percent,
20
+ label,
21
+ valueText,
22
+ hideLabel,
23
+ colorScheme,
24
+ size,
25
+ }: ProgressBarInternalProps) => {
26
+ const { components } = useTheme();
27
+ const isReducedMotion = useReducedMotion();
28
+ const progress = useSharedValue(0);
29
+ const hasMountedRef = useRef(false);
30
+
31
+ useEffect(() => {
32
+ const target = Math.max(0, Math.min(100, percent)) / 100;
33
+ if (isReducedMotion) {
34
+ progress.value = target;
35
+ hasMountedRef.current = true;
36
+ return;
37
+ }
38
+
39
+ if (!hasMountedRef.current) {
40
+ progress.value = target;
41
+ hasMountedRef.current = true;
42
+ return;
43
+ }
44
+
45
+ progress.value = withTiming(target, { duration: 300, easing: Easing.out(Easing.ease) });
46
+ }, [percent, isReducedMotion, progress]);
47
+
48
+ const circularTokens = components.progressBar.circular[size];
49
+ const barWidth = 'bar' in circularTokens ? circularTokens.bar.width : circularTokens.barWidth;
50
+ const diameter = circularTokens.height;
51
+ const radius = (diameter - barWidth) / 2;
52
+ const circumference = 2 * Math.PI * radius;
53
+
54
+ const animatedCircleProps = useAnimatedProps(() => ({
55
+ strokeDashoffset: circumference * (1 - progress.value),
56
+ }));
57
+
58
+ const indicatorColor =
59
+ colorScheme === 'success'
60
+ ? components.progressBar.progress.successColor
61
+ : colorScheme === 'danger'
62
+ ? components.progressBar.progress.dangerColor
63
+ : components.progressBar.progress.defaultColor;
64
+
65
+ styles.useVariants({ size });
66
+
67
+ return (
68
+ <View style={styles.container}>
69
+ <Svg
70
+ width={diameter}
71
+ height={diameter}
72
+ viewBox={`0 0 ${diameter} ${diameter}`}
73
+ style={styles.svg}
74
+ >
75
+ <G origin={`${diameter / 2}, ${diameter / 2}`} rotation={-90}>
76
+ <Circle
77
+ cx="50%"
78
+ cy="50%"
79
+ r={radius}
80
+ stroke={components.progressBar.barColor}
81
+ strokeWidth={barWidth}
82
+ fill="transparent"
83
+ />
84
+ <AnimatedCircle
85
+ cx="50%"
86
+ cy="50%"
87
+ r={radius}
88
+ stroke={indicatorColor}
89
+ strokeWidth={barWidth}
90
+ fill="transparent"
91
+ strokeLinecap="round"
92
+ strokeDasharray={circumference}
93
+ animatedProps={animatedCircleProps}
94
+ />
95
+ </G>
96
+ </Svg>
97
+ <View style={styles.content}>
98
+ <Text style={styles.valueText}>{valueText}</Text>
99
+ {!hideLabel && size === 'md' ? (
100
+ <BodyText style={styles.label} size="md" weight="semibold">
101
+ {label}
102
+ </BodyText>
103
+ ) : null}
104
+ </View>
105
+ </View>
106
+ );
107
+ };
108
+
109
+ ProgressBarCircular.displayName = 'ProgressBarCircular';
110
+
111
+ const styles = StyleSheet.create(theme => ({
112
+ container: {
113
+ alignItems: 'center',
114
+ justifyContent: 'center',
115
+ position: 'relative',
116
+ variants: {
117
+ size: {
118
+ md: {
119
+ width: theme.components.progressBar.circular.md.height,
120
+ height: theme.components.progressBar.circular.md.height,
121
+ },
122
+ sm: {
123
+ width: theme.components.progressBar.circular.sm.height,
124
+ height: theme.components.progressBar.circular.sm.height,
125
+ },
126
+ },
127
+ },
128
+ },
129
+ svg: {
130
+ position: 'absolute',
131
+ top: 0,
132
+ left: 0,
133
+ },
134
+ content: {
135
+ alignItems: 'center',
136
+ justifyContent: 'center',
137
+ _web: {
138
+ position: 'absolute',
139
+ top: 0,
140
+ left: 0,
141
+ width: '100%',
142
+ height: '100%',
143
+ },
144
+ variants: {
145
+ size: {
146
+ md: {
147
+ gap: theme.components.progressBar.circular.md.gap,
148
+ },
149
+ sm: {
150
+ gap: 0,
151
+ },
152
+ },
153
+ },
154
+ },
155
+ valueText: {
156
+ color: theme.color.text.primary,
157
+ textAlign: 'center',
158
+ variants: {
159
+ size: {
160
+ md: {
161
+ fontFamily: theme.components.progressBar.circular.md.label.fontFamily,
162
+ fontSize: theme.components.progressBar.circular.md.label.fontSize,
163
+ lineHeight: theme.components.progressBar.circular.md.label.lineHeight,
164
+ fontWeight: theme.components.progressBar.circular.md.label.fontWeight,
165
+ },
166
+ sm: {
167
+ fontFamily: theme.typography.mobile.bodyText.fontFamily,
168
+ fontSize: theme.typography.mobile.bodyText.md.fontSize,
169
+ lineHeight: theme.typography.mobile.bodyText.md.lineHeight,
170
+ fontWeight: theme.fontWeight.semibold,
171
+ },
172
+ },
173
+ },
174
+ },
175
+ label: {
176
+ textAlign: 'center',
177
+ maxWidth: 90,
178
+ },
179
+ }));
180
+
181
+ export default ProgressBarCircular;
@@ -0,0 +1,127 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { LayoutChangeEvent, Platform, View } from 'react-native';
3
+ import Animated, {
4
+ Easing,
5
+ useAnimatedStyle,
6
+ useReducedMotion,
7
+ useSharedValue,
8
+ withTiming,
9
+ } from 'react-native-reanimated';
10
+ import { StyleSheet } from 'react-native-unistyles';
11
+ import useTheme from '../../hooks/useTheme';
12
+ import { BodyText } from '../BodyText';
13
+ import type { ProgressBarInternalProps } from './ProgressBar.props';
14
+
15
+ const ProgressBarLinear = ({
16
+ percent,
17
+ label,
18
+ valueText,
19
+ hideLabel,
20
+ colorScheme,
21
+ }: ProgressBarInternalProps) => {
22
+ const { components } = useTheme();
23
+ const isReducedMotion = useReducedMotion();
24
+ const progress = useSharedValue(0);
25
+ const hasMountedRef = useRef(false);
26
+ const [trackWidth, setTrackWidth] = useState(0);
27
+
28
+ useEffect(() => {
29
+ const target = Math.max(0, Math.min(100, percent)) / 100;
30
+ if (isReducedMotion) {
31
+ progress.value = target;
32
+ hasMountedRef.current = true;
33
+ return;
34
+ }
35
+
36
+ if (!hasMountedRef.current) {
37
+ progress.value = target;
38
+ hasMountedRef.current = true;
39
+ return;
40
+ }
41
+
42
+ progress.value = withTiming(target, { duration: 300, easing: Easing.out(Easing.ease) });
43
+ }, [percent, isReducedMotion, progress]);
44
+
45
+ const animatedStyle = useAnimatedStyle(() => ({
46
+ width: trackWidth * progress.value,
47
+ }));
48
+
49
+ const indicatorColor =
50
+ colorScheme === 'success'
51
+ ? components.progressBar.progress.successColor
52
+ : colorScheme === 'danger'
53
+ ? components.progressBar.progress.dangerColor
54
+ : components.progressBar.progress.defaultColor;
55
+
56
+ const handleTrackLayout = (event: LayoutChangeEvent) => {
57
+ setTrackWidth(event.nativeEvent.layout.width);
58
+ };
59
+
60
+ return (
61
+ <View style={styles.container}>
62
+ {!hideLabel && (
63
+ <View style={styles.header}>
64
+ <BodyText size="md" weight="semibold" style={styles.label}>
65
+ {label}
66
+ </BodyText>
67
+ <BodyText size="md" style={styles.value}>
68
+ {valueText}
69
+ </BodyText>
70
+ </View>
71
+ )}
72
+ <View style={styles.track} onLayout={handleTrackLayout}>
73
+ {Platform.OS === 'web' ? (
74
+ <View
75
+ style={[
76
+ styles.indicator,
77
+ { width: `${Math.max(0, Math.min(100, percent))}%` },
78
+ { backgroundColor: indicatorColor },
79
+ ]}
80
+ />
81
+ ) : (
82
+ <Animated.View
83
+ style={[styles.indicator, animatedStyle, { backgroundColor: indicatorColor }]}
84
+ />
85
+ )}
86
+ </View>
87
+ </View>
88
+ );
89
+ };
90
+
91
+ ProgressBarLinear.displayName = 'ProgressBarLinear';
92
+
93
+ const styles = StyleSheet.create(theme => ({
94
+ container: {
95
+ width: '100%',
96
+ gap: theme.components.progressBar.linear.gap,
97
+ },
98
+ header: {
99
+ flexDirection: 'row',
100
+ alignItems: 'center',
101
+ justifyContent: 'space-between',
102
+ gap: theme.components.progressBar.linear.label.gap,
103
+ },
104
+ label: {
105
+ flex: 1,
106
+ },
107
+ value: {
108
+ flexShrink: 0,
109
+ textAlign: 'right',
110
+ },
111
+ track: {
112
+ width: '100%',
113
+ height: theme.components.progressBar.linear.bar.height,
114
+ backgroundColor: theme.components.progressBar.barColor,
115
+ borderRadius: theme.components.progressBar.linear.bar.borderRadius,
116
+ overflow: 'hidden',
117
+ },
118
+ indicator: {
119
+ height: '100%',
120
+ borderRadius: theme.components.progressBar.linear.bar.borderRadius,
121
+ _web: {
122
+ height: '100%',
123
+ },
124
+ },
125
+ }));
126
+
127
+ export default ProgressBarLinear;
@@ -0,0 +1,7 @@
1
+ export { default as ProgressBar } from './ProgressBar';
2
+ export type {
3
+ ProgressBarColorScheme,
4
+ ProgressBarProps,
5
+ ProgressBarSize,
6
+ ProgressBarVariant,
7
+ } from './ProgressBar.props';
@@ -41,6 +41,7 @@ export * from './List';
41
41
  export * from './Menu';
42
42
  export * from './Modal';
43
43
  export * from './PillGroup';
44
+ export * from './ProgressBar';
44
45
  export * from './ProgressStepper';
45
46
  export * from './Radio';
46
47
  export * from './RadioCard';