related-ui-components 1.3.7 → 1.3.9

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 (44) hide show
  1. package/lib/commonjs/app.js +21 -19
  2. package/lib/commonjs/app.js.map +1 -1
  3. package/lib/commonjs/components/BrandIcon/BrandIcon.js +47 -39
  4. package/lib/commonjs/components/BrandIcon/BrandIcon.js.map +1 -1
  5. package/lib/commonjs/components/Marquee/Marquee.js +105 -0
  6. package/lib/commonjs/components/Marquee/Marquee.js.map +1 -0
  7. package/lib/commonjs/components/Marquee/index.js +29 -0
  8. package/lib/commonjs/components/Marquee/index.js.map +1 -0
  9. package/lib/commonjs/index.js +5 -0
  10. package/lib/commonjs/index.js.map +1 -1
  11. package/lib/module/app.js +23 -21
  12. package/lib/module/app.js.map +1 -1
  13. package/lib/module/components/BrandIcon/BrandIcon.js +49 -41
  14. package/lib/module/components/BrandIcon/BrandIcon.js.map +1 -1
  15. package/lib/module/components/Marquee/Marquee.js +99 -0
  16. package/lib/module/components/Marquee/Marquee.js.map +1 -0
  17. package/lib/module/components/Marquee/index.js +5 -0
  18. package/lib/module/components/Marquee/index.js.map +1 -0
  19. package/lib/module/index.js +4 -7
  20. package/lib/module/index.js.map +1 -1
  21. package/lib/typescript/commonjs/app.d.ts.map +1 -1
  22. package/lib/typescript/commonjs/components/BrandIcon/BrandIcon.d.ts +5 -2
  23. package/lib/typescript/commonjs/components/BrandIcon/BrandIcon.d.ts.map +1 -1
  24. package/lib/typescript/commonjs/components/Marquee/Marquee.d.ts +17 -0
  25. package/lib/typescript/commonjs/components/Marquee/Marquee.d.ts.map +1 -0
  26. package/lib/typescript/commonjs/components/Marquee/index.d.ts +3 -0
  27. package/lib/typescript/commonjs/components/Marquee/index.d.ts.map +1 -0
  28. package/lib/typescript/commonjs/index.d.ts +1 -0
  29. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  30. package/lib/typescript/module/app.d.ts.map +1 -1
  31. package/lib/typescript/module/components/BrandIcon/BrandIcon.d.ts +5 -2
  32. package/lib/typescript/module/components/BrandIcon/BrandIcon.d.ts.map +1 -1
  33. package/lib/typescript/module/components/Marquee/Marquee.d.ts +17 -0
  34. package/lib/typescript/module/components/Marquee/Marquee.d.ts.map +1 -0
  35. package/lib/typescript/module/components/Marquee/index.d.ts +3 -0
  36. package/lib/typescript/module/components/Marquee/index.d.ts.map +1 -0
  37. package/lib/typescript/module/index.d.ts +1 -0
  38. package/lib/typescript/module/index.d.ts.map +1 -1
  39. package/package.json +2 -2
  40. package/src/app.tsx +12 -8
  41. package/src/components/BrandIcon/BrandIcon.tsx +85 -72
  42. package/src/components/Marquee/Marquee.tsx +140 -0
  43. package/src/components/Marquee/index.ts +2 -0
  44. package/src/index.ts +4 -4
@@ -1,5 +1,5 @@
1
1
  import { useTheme, ThemeType } from "../../theme";
2
- import React from 'react';
2
+ import React from "react";
3
3
  import {
4
4
  View,
5
5
  Image,
@@ -9,7 +9,9 @@ import {
9
9
  ViewStyle,
10
10
  ImageSourcePropType,
11
11
  ImageStyle,
12
- } from 'react-native';
12
+ Text,
13
+ TextStyle,
14
+ } from "react-native";
13
15
 
14
16
  interface BrandIconProps {
15
17
  brand: {
@@ -19,40 +21,44 @@ interface BrandIconProps {
19
21
  };
20
22
  size?: number;
21
23
  selected?: boolean;
24
+ showName?: boolean;
22
25
  onPress?: () => void;
23
26
  disabled?: boolean;
27
+ // styles for the circular button
24
28
  containerStyle?: StyleProp<ViewStyle>;
29
+ // styles for the outer wrapper (align center + name)
30
+ wrapperStyle?: StyleProp<ViewStyle>;
31
+ nameStyle?: TextStyle;
25
32
  imageStyle?: StyleProp<ImageStyle>;
26
33
  selectionIndicatorStyle?: StyleProp<ViewStyle>;
27
- selectionIndicatorIcon?: React.ReactNode
34
+ selectionIndicatorIcon?: React.ReactNode;
28
35
  }
29
36
 
30
37
  const BrandIcon: React.FC<BrandIconProps> = ({
31
38
  brand,
32
39
  size = 66,
33
40
  selected = false,
41
+ showName = false,
34
42
  onPress,
35
43
  disabled = false,
36
44
  containerStyle,
45
+ wrapperStyle,
37
46
  imageStyle,
38
47
  selectionIndicatorStyle,
39
- selectionIndicatorIcon
48
+ selectionIndicatorIcon,
49
+ nameStyle
40
50
  }) => {
41
- const {theme} = useTheme();
42
- const styles = createStyles(theme); // Create styles using theme
51
+ const { theme } = useTheme();
52
+ const styles = createStyles(theme);
43
53
 
54
+ // fixed circle dimensions
44
55
  const iconSize = {
45
56
  width: size,
46
57
  height: size,
47
58
  borderRadius: size / 2,
48
59
  };
49
60
 
50
- const imageSize = {
51
- width: size,
52
- height: size,
53
- borderRadius: size / 2,
54
- };
55
-
61
+ // size for the check indicator
56
62
  const indicatorSize = {
57
63
  width: size * 0.3,
58
64
  height: size * 0.3,
@@ -65,70 +71,77 @@ const BrandIcon: React.FC<BrandIconProps> = ({
65
71
  };
66
72
 
67
73
  return (
68
- <TouchableOpacity
69
- style={[
70
- styles.container,
71
- iconSize,
72
- selected && styles.selectedContainer,
73
- containerStyle,
74
- ]}
75
- onPress={onPress}
76
- disabled={disabled || !onPress}
77
- activeOpacity={0.8}
78
- >
79
- <Image
80
- source={brand.logo}
81
- style={[styles.logo, imageSize, imageStyle]}
82
- resizeMode="contain"
83
- />
74
+ <View style={[styles.wrapper, wrapperStyle]}>
75
+ <TouchableOpacity
76
+ style={[
77
+ styles.container,
78
+ iconSize,
79
+ selected && styles.selectedContainer,
80
+ containerStyle,
81
+ ]}
82
+ onPress={onPress}
83
+ disabled={disabled || !onPress}
84
+ activeOpacity={0.8}
85
+ >
86
+ <Image
87
+ source={brand.logo}
88
+ style={[{ width: size, height: size, borderRadius: size / 2 }, imageStyle]}
89
+ resizeMode="contain"
90
+ />
84
91
 
85
- {selected && (
86
- <View
87
- style={[
88
- styles.selectionIndicator,
89
- indicatorSize,
90
- indicatorPosition,
91
- selectionIndicatorStyle,
92
- ]}
93
- >
92
+ {selected && (
93
+ <View
94
+ style={[
95
+ styles.selectionIndicator,
96
+ indicatorSize,
97
+ indicatorPosition,
98
+ selectionIndicatorStyle,
99
+ ]}
100
+ >
94
101
  {selectionIndicatorIcon}
95
- </View>
96
- )}
97
- </TouchableOpacity>
102
+ </View>
103
+ )}
104
+ </TouchableOpacity>
105
+
106
+ {showName && <Text style={[styles.name, {width: iconSize.width}, nameStyle]}>{brand.name}</Text>}
107
+ </View>
98
108
  );
99
109
  };
100
110
 
101
- // Function to create themed styles
102
- const createStyles = (theme: ThemeType) => StyleSheet.create({
103
- container: {
104
- backgroundColor: theme.surface, // Use surface for background
105
- justifyContent: 'center',
106
- alignItems: 'center',
107
- borderWidth: 1,
108
- borderColor: theme.border, // Use border color
109
- shadowColor: '#000000', // Keep shadow black for now
110
- shadowOffset: { width: 0, height: 2 },
111
- shadowOpacity: 0.1,
112
- shadowRadius: 2,
113
- elevation: 2,
114
- },
115
- selectedContainer: {
116
- borderColor: theme.primary, // Use primary for selected border
117
- borderWidth: 2, // Keep border width for selected state
118
- },
119
- logo: {},
120
- selectionIndicator: {
121
- position: 'absolute',
122
- backgroundColor: theme.primary, // Use primary for indicator background
123
- justifyContent: 'center',
124
- alignItems: 'center',
125
- zIndex: 2,
126
- },
127
- checkmark: { // Style is unused in current code, but if used:
128
- width: '60%',
129
- height: '60%',
130
- // tintColor: theme.onPrimary, // Use onPrimary if checkmark is an icon/image
131
- },
132
- });
111
+ const createStyles = (theme: ThemeType) =>
112
+ StyleSheet.create({
113
+ wrapper: {
114
+ alignItems: "center",
115
+ },
116
+ container: {
117
+ backgroundColor: theme.surface,
118
+ justifyContent: "center",
119
+ alignItems: "center",
120
+ borderWidth: 1,
121
+ borderColor: theme.border,
122
+ shadowColor: "#000000",
123
+ shadowOffset: { width: 0, height: 2 },
124
+ shadowOpacity: 0.1,
125
+ shadowRadius: 2,
126
+ elevation: 2,
127
+ },
128
+ selectedContainer: {
129
+ borderColor: theme.primary,
130
+ borderWidth: 2,
131
+ },
132
+ selectionIndicator: {
133
+ position: "absolute",
134
+ backgroundColor: theme.primary,
135
+ justifyContent: "center",
136
+ alignItems: "center",
137
+ zIndex: 2,
138
+ },
139
+ name: {
140
+ marginTop: 4,
141
+ color: theme.onSurface,
142
+ fontSize: 12,
143
+ textAlign: "center",
144
+ },
145
+ });
133
146
 
134
147
  export default BrandIcon;
@@ -0,0 +1,140 @@
1
+ import React, { useEffect, useMemo } from "react";
2
+ import {
3
+ View,
4
+ StyleSheet,
5
+ Dimensions,
6
+ ViewStyle,
7
+ } from "react-native";
8
+ import Animated, {
9
+ useSharedValue,
10
+ useAnimatedStyle,
11
+ withRepeat,
12
+ withTiming,
13
+ Easing,
14
+ cancelAnimation,
15
+ } from "react-native-reanimated";
16
+
17
+ const DEFAULT_ANIMATION_SPEED_MS = 15000;
18
+
19
+ type ReusableMarqueeProps<T> = {
20
+ data: T[];
21
+ renderItem: (item: T, index: number) => React.ReactElement;
22
+ itemWidth: number;
23
+ itemHeight: number;
24
+ itemSpacing: number;
25
+ animationSpeedMs?: number;
26
+ style?: ViewStyle
27
+ contentContainerStyle?: ViewStyle;
28
+ };
29
+
30
+ const ReusableMarquee = <T extends { id: string | number }>({
31
+ data: baseData,
32
+ renderItem,
33
+ itemWidth,
34
+ itemHeight,
35
+ itemSpacing,
36
+ animationSpeedMs = DEFAULT_ANIMATION_SPEED_MS,
37
+ style,
38
+ contentContainerStyle,
39
+ }: ReusableMarqueeProps<T>): React.ReactElement | null => {
40
+ const translateX = useSharedValue(0);
41
+
42
+ // Memoize calculations that depend on props to avoid unnecessary recalculations
43
+ const { ONE_SET_WIDTH, DATA } = useMemo(() => {
44
+ if (!baseData || baseData.length === 0) {
45
+ return { ONE_SET_WIDTH: 0, DATA: [] };
46
+ }
47
+ const calculatedWidth = baseData.reduce(
48
+ (sum) => sum + itemWidth + itemSpacing,
49
+ 0,
50
+ );
51
+ // Duplicate data for seamless looping
52
+ const duplicatedData = [...baseData, ...baseData];
53
+ return { ONE_SET_WIDTH: calculatedWidth, DATA: duplicatedData };
54
+ }, [baseData, itemWidth, itemSpacing]);
55
+
56
+ useEffect(() => {
57
+ if (DATA.length > 0 && ONE_SET_WIDTH > 0) {
58
+ translateX.value = 0; // Reset position when props change
59
+ const animation = withRepeat(
60
+ withTiming(-ONE_SET_WIDTH, {
61
+ duration: animationSpeedMs,
62
+ easing: Easing.linear,
63
+ }),
64
+ -1, // Infinite repeat
65
+ false, // Don't reverse
66
+ );
67
+ translateX.value = animation;
68
+
69
+ return () => {
70
+ cancelAnimation(translateX);
71
+ };
72
+ } else {
73
+ cancelAnimation(translateX);
74
+ translateX.value = 0;
75
+ return undefined;
76
+ }
77
+ }, [translateX, ONE_SET_WIDTH, DATA, animationSpeedMs]);
78
+
79
+ const animatedStyle = useAnimatedStyle(() => {
80
+ return {
81
+ transform: [{ translateX: translateX.value }],
82
+ };
83
+ });
84
+
85
+ if (DATA.length === 0) {
86
+ return null;
87
+ }
88
+
89
+ return (
90
+ <View
91
+ style={[
92
+ styles.container,
93
+ { height: itemHeight },
94
+ style,
95
+ ]}
96
+ >
97
+ {/* Animated container holds items and moves horizontally */}
98
+ <Animated.View
99
+ style={[
100
+ styles.animatedContainer,
101
+ { height: itemHeight },
102
+ animatedStyle,
103
+ contentContainerStyle,
104
+ ]}
105
+ >
106
+ {/* Map over the duplicated data */}
107
+ {DATA.map((item, index) => (
108
+ <View
109
+ key={`${item.id}-${index}`}
110
+ style={[
111
+ styles.itemWrapper,
112
+ {
113
+ width: itemWidth,
114
+ height: itemHeight,
115
+ marginRight: itemSpacing,
116
+ },
117
+ ]}
118
+ >
119
+ {renderItem(item, index)}
120
+ </View>
121
+ ))}
122
+ </Animated.View>
123
+ </View>
124
+ );
125
+ };
126
+
127
+ const styles = StyleSheet.create({
128
+ container: {
129
+ width: "100%",
130
+ overflow: "hidden",
131
+ },
132
+ animatedContainer: {
133
+ flexDirection: "row",
134
+ },
135
+ itemWrapper: {
136
+ },
137
+ });
138
+
139
+ export default ReusableMarquee;
140
+
@@ -0,0 +1,2 @@
1
+ export { default as Marquee } from "./Marquee"
2
+ export * from "./Marquee";
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";