related-ui-components 1.5.9 → 1.6.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.
Files changed (31) hide show
  1. package/lib/commonjs/app.js +28 -9
  2. package/lib/commonjs/app.js.map +1 -1
  3. package/lib/commonjs/components/Marquee/Marquee.js +9 -10
  4. package/lib/commonjs/components/Marquee/Marquee.js.map +1 -1
  5. package/lib/commonjs/components/ScratchCard/ScratchCard.js +131 -87
  6. package/lib/commonjs/components/ScratchCard/ScratchCard.js.map +1 -1
  7. package/lib/commonjs/index.js +5 -0
  8. package/lib/commonjs/index.js.map +1 -1
  9. package/lib/module/app.js +31 -12
  10. package/lib/module/app.js.map +1 -1
  11. package/lib/module/components/Marquee/Marquee.js +9 -10
  12. package/lib/module/components/Marquee/Marquee.js.map +1 -1
  13. package/lib/module/components/ScratchCard/ScratchCard.js +141 -90
  14. package/lib/module/components/ScratchCard/ScratchCard.js.map +1 -1
  15. package/lib/module/index.js +4 -7
  16. package/lib/module/index.js.map +1 -1
  17. package/lib/typescript/commonjs/app.d.ts.map +1 -1
  18. package/lib/typescript/commonjs/components/Marquee/Marquee.d.ts.map +1 -1
  19. package/lib/typescript/commonjs/components/ScratchCard/ScratchCard.d.ts.map +1 -1
  20. package/lib/typescript/commonjs/index.d.ts +1 -0
  21. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  22. package/lib/typescript/module/app.d.ts.map +1 -1
  23. package/lib/typescript/module/components/Marquee/Marquee.d.ts.map +1 -1
  24. package/lib/typescript/module/components/ScratchCard/ScratchCard.d.ts.map +1 -1
  25. package/lib/typescript/module/index.d.ts +1 -0
  26. package/lib/typescript/module/index.d.ts.map +1 -1
  27. package/package.json +1 -1
  28. package/src/app.tsx +19 -1
  29. package/src/components/Marquee/Marquee.tsx +16 -12
  30. package/src/components/ScratchCard/ScratchCard.tsx +156 -93
  31. package/src/index.ts +4 -4
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useMemo } from "react";
2
- import { View, StyleSheet, Dimensions, ViewStyle } from "react-native";
2
+ import { View, StyleSheet, ViewStyle } from "react-native";
3
3
  import Animated, {
4
4
  useSharedValue,
5
5
  useAnimatedStyle,
@@ -36,11 +36,11 @@ const ReusableMarquee = <T extends { id: string | number }>({
36
36
  }: ReusableMarqueeProps<T>): React.ReactElement | null => {
37
37
  const translateX = useSharedValue(0);
38
38
 
39
- // Memoize calculations that depend on props to avoid unnecessary recalculations
40
39
  const { ONE_SET_WIDTH, DATA } = useMemo(() => {
41
40
  if (!baseData || baseData.length === 0) {
42
41
  return { ONE_SET_WIDTH: 0, DATA: [] };
43
42
  }
43
+ // Calculate width of one set of items including spacing
44
44
  const calculatedWidth = baseData.reduce(
45
45
  (sum) => sum + itemWidth + itemSpacing,
46
46
  0
@@ -52,15 +52,20 @@ const ReusableMarquee = <T extends { id: string | number }>({
52
52
 
53
53
  useEffect(() => {
54
54
  if (DATA.length > 0 && ONE_SET_WIDTH > 0) {
55
- translateX.value = 0; // Reset position when props change
55
+ const initialTranslateX = moveTo === "left" ? 0 : -ONE_SET_WIDTH;
56
+ const targetTranslateX = moveTo === "left" ? -ONE_SET_WIDTH : 0;
57
+
58
+ translateX.value = initialTranslateX;
59
+
56
60
  const animation = withRepeat(
57
- withTiming(moveTo == "left" ? -ONE_SET_WIDTH : +ONE_SET_WIDTH, {
61
+ withTiming(targetTranslateX, {
58
62
  duration: animationSpeedMs,
59
63
  easing: Easing.linear,
60
64
  }),
61
- -1, // Infinite repeat
62
- false // Don't reverse
65
+ -1,
66
+ false
63
67
  );
68
+
64
69
  translateX.value = animation;
65
70
 
66
71
  return () => {
@@ -69,9 +74,9 @@ const ReusableMarquee = <T extends { id: string | number }>({
69
74
  } else {
70
75
  cancelAnimation(translateX);
71
76
  translateX.value = 0;
72
- return undefined;
77
+ return undefined;
73
78
  }
74
- }, [translateX, ONE_SET_WIDTH, DATA, animationSpeedMs]);
79
+ }, [translateX, ONE_SET_WIDTH, DATA, animationSpeedMs, moveTo]); //
75
80
 
76
81
  const animatedStyle = useAnimatedStyle(() => {
77
82
  return {
@@ -85,7 +90,6 @@ const ReusableMarquee = <T extends { id: string | number }>({
85
90
 
86
91
  return (
87
92
  <View style={[styles.container, { height: itemHeight }, style]}>
88
- {/* Animated container holds items and moves horizontally */}
89
93
  <Animated.View
90
94
  style={[
91
95
  styles.animatedContainer,
@@ -94,7 +98,6 @@ const ReusableMarquee = <T extends { id: string | number }>({
94
98
  contentContainerStyle,
95
99
  ]}
96
100
  >
97
- {/* Map over the duplicated data */}
98
101
  {DATA.map((item, index) => (
99
102
  <View
100
103
  key={`${item.id}-${index}`}
@@ -107,7 +110,7 @@ const ReusableMarquee = <T extends { id: string | number }>({
107
110
  },
108
111
  ]}
109
112
  >
110
- {renderItem(item, index)}
113
+ {renderItem(item, index % baseData.length)}
111
114
  </View>
112
115
  ))}
113
116
  </Animated.View>
@@ -123,7 +126,8 @@ const styles = StyleSheet.create({
123
126
  animatedContainer: {
124
127
  flexDirection: "row",
125
128
  },
126
- itemWrapper: {},
129
+ itemWrapper: {
130
+ },
127
131
  });
128
132
 
129
133
  export default ReusableMarquee;
@@ -1,4 +1,9 @@
1
- import React, { useRef, useState } from "react";
1
+ import React, {
2
+ useRef,
3
+ useState,
4
+ useEffect,
5
+ useCallback, // Import useCallback
6
+ } from "react";
2
7
  import {
3
8
  Canvas,
4
9
  Group,
@@ -7,11 +12,13 @@ import {
7
12
  Path,
8
13
  Rect,
9
14
  Skia,
10
- LinearGradient,
11
- vec,
12
15
  useImage,
13
16
  Text,
14
17
  useFont,
18
+ notifyChange,
19
+ SkPath, // Import SkPath type
20
+ SkFont, // Import SkFont type
21
+ SkImage, // Import SkImage type
15
22
  } from "@shopify/react-native-skia";
16
23
  import {
17
24
  StyleProp,
@@ -19,15 +26,21 @@ import {
19
26
  ViewStyle,
20
27
  StyleSheet,
21
28
  ImageRequireSource,
22
- NativeTouchEvent,
29
+ LogBox, // Optional: for ignoring specific logs if needed
23
30
  } from "react-native";
31
+ // Import runOnJS and useSharedValue
32
+ import { runOnJS, useSharedValue } from "react-native-reanimated";
33
+ import { Gesture, GestureDetector } from "react-native-gesture-handler";
34
+
35
+ // Ignore specific warning if it appears, related to path mutation - use cautiously
36
+ // LogBox.ignoreLogs(['Skia: SkPath.Make()']);
24
37
 
25
38
  type ScratchCardProps = {
26
39
  style?: StyleProp<ViewStyle>;
27
40
  image?: ImageRequireSource;
28
41
  children?: React.ReactNode;
29
42
  brushStrokeWidth?: number;
30
- revealThreshold?: number;
43
+ revealThreshold?: number; // Percentage (0 to 1) - Based on bounding box
31
44
  width?: number;
32
45
  height?: number;
33
46
  backgroundColor?: string;
@@ -35,15 +48,15 @@ type ScratchCardProps = {
35
48
  textFont?: ImageRequireSource;
36
49
  textFontSize?: number;
37
50
  textFontColor?: string;
38
- onScratched?: () => void;
51
+ onScratched?: () => void;
39
52
  };
40
53
 
41
54
  const ScratchCard: React.FC<ScratchCardProps> = ({
42
55
  style,
43
56
  children,
44
- image = null,
57
+ image,
45
58
  brushStrokeWidth = 50,
46
- revealThreshold = 0.8,
59
+ revealThreshold = 0.8,
47
60
  width = 300,
48
61
  height = 300,
49
62
  backgroundColor = "#CCCCCC",
@@ -53,112 +66,162 @@ const ScratchCard: React.FC<ScratchCardProps> = ({
53
66
  textFontSize = 16,
54
67
  onScratched,
55
68
  }) => {
56
- const img = useImage(image);
57
- const font = useFont(textFont, textFontSize);
69
+ const loadedImg = useImage(image);
70
+ const loadedFont = useFont(textFont, textFontSize);
58
71
 
59
72
  const [[areaWidth, areaHeight], setSize] = useState([0, 0]);
60
73
  const [isScratched, setScratched] = useState(false);
61
- const [_, setRefresh] = useState(0);
74
+ const [isLayoutReady, setLayoutReady] = useState(false);
75
+
76
+ const isThresholdReached = useSharedValue(false);
62
77
 
63
- const path = useRef(Skia.Path.Make());
64
- const lastKnownY = useRef<number | null>(null);
78
+ const path = useSharedValue<SkPath>(Skia.Path.Make());
65
79
 
66
- //Check scratch progress
67
- const checkScratchProgress = () => {
68
- const bounds = path.current.getBounds();
69
- const scratchedArea = bounds.height * bounds.width;
70
- const totalArea = areaWidth * areaHeight;
80
+ useEffect(() => {
81
+ path.value = Skia.Path.Make();
82
+ isThresholdReached.value = false
83
+ setScratched(false);
84
+ }, [areaWidth, areaHeight]);
71
85
 
72
- if (scratchedArea / totalArea > revealThreshold && !isScratched) {
73
- setScratched(true);
74
- if (onScratched) {
75
- onScratched();
86
+ const handleLayout = useCallback((event: any) => {
87
+ const { width: newWidth, height: newHeight } = event.nativeEvent.layout;
88
+ if (newWidth > 0 && newHeight > 0) {
89
+ if (newWidth !== areaWidth || newHeight !== areaHeight) {
90
+ setSize([newWidth, newHeight]);
76
91
  }
92
+ if (!isLayoutReady) {
93
+ setLayoutReady(true);
94
+ }
95
+ } else {
96
+ setLayoutReady(false);
77
97
  }
78
- };
98
+ }, [areaWidth, areaHeight, isLayoutReady]);
79
99
 
80
- //Handle path movement on canvas
81
- const handleMove = (nativeEvent: NativeTouchEvent) => {
82
- let { locationX, locationY } = nativeEvent;
100
+ const revealCardOnJS = useCallback(() => {
101
+ setScratched(true);
102
+ onScratched?.();
103
+ }, [onScratched]);
83
104
 
84
- if (
85
- lastKnownY.current != null &&
86
- nativeEvent.locationY - lastKnownY.current > 50
87
- ) {
88
- return;
89
- }
105
+ const pan = Gesture.Pan()
106
+ .averageTouches(true)
107
+ .maxPointers(1)
108
+ .onBegin((e) => {
109
+ if (!isLayoutReady) return;
110
+ try {
111
+ const newPath = path.value.copy();
112
+ newPath.moveTo(e.x, e.y);
113
+ newPath.lineTo(e.x + 0.001, e.y + 0.001);
114
+ path.value = newPath;
115
+ notifyChange(path as any);
116
+ } catch (error) {
117
+ console.error("ScratchCard: Error in onBegin:", error);
118
+ }
119
+ })
120
+ .onChange((e) => {
121
+ if (!isLayoutReady || isThresholdReached.value) return;
122
+
123
+ try {
124
+ const newPath = path.value.copy();
125
+ newPath.lineTo(e.x, e.y);
126
+ path.value = newPath;
127
+ notifyChange(path as any);
128
+
129
+ const bounds = path.value.getBounds();
130
+
131
+ if (!bounds || areaWidth <= 0 || areaHeight <= 0) {
132
+ return;
133
+ }
90
134
 
91
- path.current.lineTo(locationX, locationY);
92
- lastKnownY.current = locationY;
135
+ const scratchedArea = bounds.width * bounds.height;
136
+ const totalArea = areaWidth * areaHeight;
93
137
 
94
- setRefresh((prev) => prev + 1);
95
- checkScratchProgress();
96
- };
138
+ if (totalArea > 0 && scratchedArea / totalArea > revealThreshold) {
139
+ if (!isThresholdReached.value) {
140
+ isThresholdReached.value = true;
141
+ runOnJS(revealCardOnJS)();
142
+ }
143
+ }
144
+ } catch (error) {
145
+ console.error("ScratchCard: Error in onChange (UI Thread):", error);
146
+ }
147
+ })
148
+
149
+ const textMetrics = React.useMemo(() => {
150
+ if (loadedFont && text && areaWidth > 0 && areaHeight > 0) {
151
+ const metrics = loadedFont.measureText(text);
152
+ const textX = areaWidth / 2 - metrics.width / 2;
153
+ const textY = areaHeight / 2 + metrics.height / 3;
154
+ return { x: textX, y: textY, width: metrics.width, height: metrics.height };
155
+ }
156
+ return null;
157
+ }, [loadedFont, text, areaWidth, areaHeight]);
158
+
159
+ const canRenderCanvas = isLayoutReady && areaWidth > 0 && areaHeight > 0;
97
160
 
98
161
  return (
99
162
  <View
100
- onLayout={(e) => {
101
- setSize([e.nativeEvent.layout.width, e.nativeEvent.layout.height]);
102
- }}
163
+ onLayout={handleLayout}
103
164
  style={[styles.container, style, { width, height }]}
104
165
  >
105
166
  <View style={styles.content}>{children}</View>
106
167
 
107
- {!isScratched && (
108
- <Canvas
109
- style={styles.canvas}
110
- onTouchStart={({ nativeEvent }) => {
111
- path.current.moveTo(nativeEvent.locationX, nativeEvent.locationY);
112
- setRefresh((prev) => prev + 1);
113
- }}
114
- onTouchMove={({ nativeEvent }) => handleMove(nativeEvent)}
115
- onTouchEnd={() => (lastKnownY.current = null)}
116
- >
117
- <Mask
118
- mode="luminance"
119
- mask={
120
- <Group>
121
- <Rect x={0} y={0} width={1000} height={1000} color="white" />
122
- <Path
123
- path={path.current}
124
- color="black"
125
- style="stroke"
126
- strokeJoin="round"
127
- strokeCap="round"
128
- strokeWidth={brushStrokeWidth}
129
- />
130
- </Group>
131
- }
132
- >
133
- {img ? (
134
- <Image
135
- image={img}
136
- fit="cover"
137
- x={0}
138
- y={0}
139
- width={areaWidth}
140
- height={areaHeight}
141
- />
142
- ) : (
143
- <Group>
144
- <Rect
168
+ {!isScratched && canRenderCanvas && (
169
+ <GestureDetector gesture={pan}>
170
+ <Canvas style={styles.canvas}>
171
+ <Mask
172
+ mode="luminance"
173
+ mask={
174
+ <Group>
175
+ <Rect
176
+ x={0}
177
+ y={0}
178
+ width={areaWidth}
179
+ height={areaHeight}
180
+ color="white"
181
+ />
182
+ <Path
183
+ path={path}
184
+ color="black"
185
+ style="stroke"
186
+ strokeJoin="round"
187
+ strokeCap="round"
188
+ strokeWidth={brushStrokeWidth}
189
+ />
190
+ </Group>
191
+ }
192
+ >
193
+ {loadedImg ? (
194
+ <Image
195
+ image={loadedImg}
196
+ fit="cover"
145
197
  x={0}
146
198
  y={0}
147
199
  width={areaWidth}
148
200
  height={areaHeight}
149
- color={backgroundColor}
150
- />
151
- <Text
152
- x={areaWidth / 2 - (font?.measureText(text).width || 0) / 2}
153
- y={areaHeight / 2 + (font?.measureText(text).height || 0) / 2}
154
- text={text}
155
- color={textFontColor}
156
- font={font}
157
201
  />
158
- </Group>
159
- )}
160
- </Mask>
161
- </Canvas>
202
+ ) : (
203
+ <Group>
204
+ <Rect
205
+ x={0}
206
+ y={0}
207
+ width={areaWidth}
208
+ height={areaHeight}
209
+ color={backgroundColor}
210
+ />
211
+ {loadedFont && textMetrics && text ? (
212
+ <Text
213
+ x={textMetrics.x}
214
+ y={textMetrics.y}
215
+ text={text}
216
+ color={textFontColor}
217
+ font={loadedFont}
218
+ />
219
+ ) : null}
220
+ </Group>
221
+ )}
222
+ </Mask>
223
+ </Canvas>
224
+ </GestureDetector>
162
225
  )}
163
226
  </View>
164
227
  );
@@ -168,8 +231,6 @@ const styles = StyleSheet.create({
168
231
  container: {
169
232
  position: "relative",
170
233
  overflow: "hidden",
171
- width: "100%",
172
- height: "100%",
173
234
  },
174
235
  content: {
175
236
  position: "absolute",
@@ -177,6 +238,7 @@ const styles = StyleSheet.create({
177
238
  left: 0,
178
239
  width: "100%",
179
240
  height: "100%",
241
+ zIndex: 1,
180
242
  },
181
243
  canvas: {
182
244
  position: "absolute",
@@ -184,7 +246,8 @@ const styles = StyleSheet.create({
184
246
  left: 0,
185
247
  width: "100%",
186
248
  height: "100%",
249
+ zIndex: 2,
187
250
  },
188
251
  });
189
252
 
190
- export default ScratchCard;
253
+ export default ScratchCard;
package/src/index.ts CHANGED
@@ -1,10 +1,10 @@
1
- // import { registerRootComponent } from 'expo';
2
- // import "react-native-reanimated";
1
+ import { registerRootComponent } from 'expo';
2
+ import "react-native-reanimated";
3
3
 
4
4
 
5
- // import App from "./app";
5
+ import App from "./app";
6
6
 
7
- // registerRootComponent(App);
7
+ registerRootComponent(App);
8
8
 
9
9
  export * from "./theme"
10
10
  export * from "./components";