@rific/timer 0.1.4 → 0.2.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/README.md CHANGED
@@ -51,6 +51,6 @@ Pass an ISO timestamp string to `started` to begin the countdown. Set `started`
51
51
 
52
52
  ## Peer dependencies
53
53
 
54
- - `react >= 17.0.0`
55
- - `react-native >= 0.70.0`
54
+ - `react >= 18.0.0`
55
+ - `react-native >= 0.76.0`
56
56
  - `react-native-svg >= 13.0.0`
package/dist/index.d.mts CHANGED
@@ -1,5 +1,4 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { ReactNode } from 'react';
1
+ import React, { ReactNode } from 'react';
3
2
  import { ViewStyle } from 'react-native';
4
3
 
5
4
  type TimerProps = {
@@ -14,6 +13,6 @@ type TimerProps = {
14
13
  style?: ViewStyle;
15
14
  width?: number;
16
15
  };
17
- declare const Timer: ({ children, color, duration, onStart, onStop, radius, startProgress, started, style, width }: TimerProps) => react_jsx_runtime.JSX.Element;
16
+ declare const Timer: ({ children, color, duration, onStart, onStop, radius, startProgress, started, style, width }: TimerProps) => React.JSX.Element;
18
17
 
19
18
  export { Timer, type TimerProps };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { ReactNode } from 'react';
1
+ import React, { ReactNode } from 'react';
3
2
  import { ViewStyle } from 'react-native';
4
3
 
5
4
  type TimerProps = {
@@ -14,6 +13,6 @@ type TimerProps = {
14
13
  style?: ViewStyle;
15
14
  width?: number;
16
15
  };
17
- declare const Timer: ({ children, color, duration, onStart, onStop, radius, startProgress, started, style, width }: TimerProps) => react_jsx_runtime.JSX.Element;
16
+ declare const Timer: ({ children, color, duration, onStart, onStop, radius, startProgress, started, style, width }: TimerProps) => React.JSX.Element;
18
17
 
19
18
  export { Timer, type TimerProps };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rific/timer",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "Animated SVG progress ring timer for React Native",
5
5
  "keywords": [
6
6
  "react-native",
@@ -26,6 +26,7 @@
26
26
  "type": "commonjs",
27
27
  "exports": {
28
28
  ".": {
29
+ "react-native": "./src/index.ts",
29
30
  "types": "./dist/index.d.ts",
30
31
  "import": "./dist/index.mjs",
31
32
  "require": "./dist/index.js"
@@ -35,7 +36,10 @@
35
36
  "module": "dist/index.mjs",
36
37
  "types": "dist/index.d.ts",
37
38
  "files": [
38
- "dist"
39
+ "dist",
40
+ "src",
41
+ "!src/__tests__",
42
+ "!src/__mocks__"
39
43
  ],
40
44
  "scripts": {
41
45
  "build": "tsup src/index.ts --format cjs,esm --dts --clean",
@@ -49,7 +53,7 @@
49
53
  "test": "jest",
50
54
  "test:watch": "jest --watchAll",
51
55
  "typecheck": "tsc --noEmit",
52
- "preversion": "npm run lint && npm test"
56
+ "preversion": "npm run lint && npm test && npm run build"
53
57
  },
54
58
  "devDependencies": {
55
59
  "@testing-library/dom": "^10.4.1",
@@ -77,8 +81,8 @@
77
81
  "typescript-eslint": "^8.59.3"
78
82
  },
79
83
  "peerDependencies": {
80
- "react": ">=17.0.0",
81
- "react-native": ">=0.70.0",
84
+ "react": ">=18.0.0",
85
+ "react-native": ">=0.76.0",
82
86
  "react-native-svg": ">=13.0.0"
83
87
  },
84
88
  "publishConfig": {
package/src/Timer.tsx ADDED
@@ -0,0 +1,101 @@
1
+ import React, { type ReactNode, useCallback, useEffect, useRef } from 'react'
2
+ import { Animated, Easing, StyleSheet, View, ViewStyle } from 'react-native'
3
+ import Svg, { Circle, G } from 'react-native-svg'
4
+
5
+ const AnimatedCircle = Animated.createAnimatedComponent(Circle)
6
+
7
+ const useNativeDriver = true
8
+
9
+ export type TimerProps = {
10
+ children?: ReactNode
11
+ color: string
12
+ duration: number
13
+ onStart?: () => void
14
+ onStop?: () => void
15
+ radius: number
16
+ startProgress?: number
17
+ started: string | null
18
+ style?: ViewStyle
19
+ width?: number
20
+ }
21
+
22
+ export const Timer = ({ children, color, duration, onStart, onStop, radius, startProgress = 0, started, style, width = 6 }: TimerProps) => {
23
+ const durationRef = useRef(duration)
24
+ const progressRef = useRef(startProgress)
25
+ const startedRef = useRef<string | null>(null)
26
+ const circumference = 2 * Math.PI * radius
27
+ const animation = useRef(new Animated.Value(0)).current
28
+ const animatedProps = {
29
+ strokeDashoffset: animation.interpolate({
30
+ inputRange: [0, 1],
31
+ outputRange: [circumference, 0]
32
+ })
33
+ }
34
+ const startTimer = useCallback(
35
+ (callback?: () => void) => {
36
+ const clampedProgress = Math.max(0, Math.min(1, startProgress))
37
+ const remainingDurationMs = Math.max(0, duration * 1000 * (1 - clampedProgress))
38
+ animation.setValue(clampedProgress)
39
+ if (onStart) onStart()
40
+ Animated.timing(animation, {
41
+ toValue: 1,
42
+ duration: remainingDurationMs,
43
+ easing: Easing.linear,
44
+ useNativeDriver
45
+ }).start(({ finished }) => {
46
+ if (finished && onStop) onStop()
47
+ if (callback) callback()
48
+ })
49
+ },
50
+ [animation, duration, onStart, onStop, startProgress]
51
+ )
52
+ const stopTimer = useCallback(
53
+ (callback?: () => void) => {
54
+ animation.stopAnimation()
55
+ Animated.spring(animation, {
56
+ toValue: 0,
57
+ useNativeDriver
58
+ }).start(callback)
59
+ },
60
+ [animation]
61
+ )
62
+ useEffect(() => {
63
+ const startedChanged = started !== startedRef.current
64
+ const durationChanged = duration !== durationRef.current
65
+ const progressChanged = startProgress !== progressRef.current
66
+
67
+ if (duration === 0) {
68
+ stopTimer()
69
+ } else if (started) {
70
+ if (startedChanged || durationChanged || progressChanged) {
71
+ animation.stopAnimation(() => startTimer())
72
+ }
73
+ } else {
74
+ stopTimer()
75
+ }
76
+ durationRef.current = duration
77
+ progressRef.current = startProgress
78
+ startedRef.current = started
79
+ }, [animation, duration, startProgress, started, startTimer, stopTimer])
80
+ return (
81
+ <View style={[style, styles.root]}>
82
+ <Svg width={radius * 2 + width} height={radius * 2 + width} viewBox={`0 0 ${radius * 2 + width} ${radius * 2 + width}`} style={styles.svg}>
83
+ <G rotation='-90' origin={`${radius + width / 2}, ${radius + width / 2}`}>
84
+ <AnimatedCircle cx={radius + width / 2} cy={radius + width / 2} r={radius} stroke={color} strokeWidth={width} fill='none' strokeDasharray={circumference} {...animatedProps} />
85
+ </G>
86
+ </Svg>
87
+ <View style={styles.children}>{children}</View>
88
+ </View>
89
+ )
90
+ }
91
+
92
+ const styles = StyleSheet.create({
93
+ children: {
94
+ position: 'absolute'
95
+ },
96
+ root: {
97
+ alignItems: 'center',
98
+ justifyContent: 'center'
99
+ },
100
+ svg: {}
101
+ })
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export type { TimerProps } from './Timer'
2
+ export { Timer } from './Timer'