vibefast-cli 0.6.1 → 0.7.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.
@@ -0,0 +1,355 @@
1
+ /* eslint-disable */
2
+
3
+ import { Feather } from '@expo/vector-icons';
4
+ import React, { useEffect } from 'react';
5
+ import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
6
+ import Animated, {
7
+ FadeInDown,
8
+ interpolateColor,
9
+ LinearTransition,
10
+ useAnimatedStyle,
11
+ useSharedValue,
12
+ withSequence,
13
+ withSpring,
14
+ withTiming,
15
+ } from 'react-native-reanimated';
16
+
17
+ import { useThemeConfig } from '../../../lib/use-theme-config';
18
+ import type { TimelineProps } from './types';
19
+
20
+ const AnimatedTouchableOpacity =
21
+ Animated.createAnimatedComponent(TouchableOpacity);
22
+ const AnimatedFeather = Animated.createAnimatedComponent(Feather);
23
+
24
+ export const Timeline: React.FC<TimelineProps> &
25
+ React.FunctionComponent<TimelineProps> = ({
26
+ items = [],
27
+ activeColor: propsActiveColor,
28
+ inactiveColor: propsInactiveColor,
29
+ animated = true,
30
+ animationType = 'bounce',
31
+ onItemPress,
32
+ containerStyle,
33
+ titleStyle,
34
+ descriptionStyle,
35
+ timestampStyle,
36
+ lineWidth = 2,
37
+ iconSize = 20,
38
+ metaContainerStyle,
39
+ metaTextStyle,
40
+ }: TimelineProps): React.ReactNode & React.JSX.Element => {
41
+ const { colors } = useThemeConfig();
42
+ const activeColor = propsActiveColor ?? colors.primary;
43
+ const inactiveColor = propsInactiveColor ?? colors.muted;
44
+
45
+ const statusRefs = React.useRef<Record<string | number, string>>({});
46
+
47
+ useEffect(() => {
48
+ items.forEach((item) => {
49
+ statusRefs.current[item.id] = item.status || 'upcoming';
50
+ });
51
+
52
+ return () => {
53
+ statusRefs.current = {};
54
+ };
55
+ }, [items]);
56
+
57
+ if (!items || items.length === 0) {
58
+ return null as unknown as React.ReactNode & React.JSX.Element;
59
+ }
60
+
61
+ return (
62
+ <Animated.View style={[styles.container, containerStyle]}>
63
+ {items.map((item, index) => {
64
+ const isFirst = index === 0;
65
+ const isLast = index === items.length - 1;
66
+ const isComplete = item.status === 'complete';
67
+ const isCurrent = item.status === 'current';
68
+ const isUpcoming = item.status === 'upcoming' || !item.status;
69
+
70
+ const prevStatus = statusRefs.current[item.id];
71
+ const wasComplete = prevStatus === 'complete';
72
+ const statusChanged = prevStatus && prevStatus !== item.status;
73
+ statusRefs.current[item.id] = item.status || 'upcoming';
74
+
75
+ const dotScale = useSharedValue(statusChanged ? 1.5 : 1);
76
+ const dotColorAnimated = useSharedValue(
77
+ isComplete ? 1 : isCurrent ? 0.5 : 0,
78
+ );
79
+ const lineProgress = useSharedValue(isComplete ? 1 : 0);
80
+ const iconRotate = useSharedValue(statusChanged ? 0 : 1);
81
+
82
+ useEffect(() => {
83
+ if (statusChanged) {
84
+ switch (animationType) {
85
+ case 'bounce':
86
+ dotScale.value = withSequence(
87
+ withTiming(1.5, { duration: 200 }),
88
+ withSpring(1),
89
+ );
90
+ break;
91
+ case 'spring':
92
+ dotScale.value = withSpring(1.2, { damping: 2, stiffness: 80 });
93
+ break;
94
+ case 'rotate':
95
+ iconRotate.value = withSequence(
96
+ withTiming(0, { duration: 10 }),
97
+ withTiming(4, { duration: 800 }),
98
+ );
99
+ dotScale.value = withSequence(
100
+ withTiming(1.6, { duration: 200 }),
101
+ withSpring(1),
102
+ );
103
+ dotColorAnimated.value = withSequence(
104
+ withTiming(0, { duration: 200 }),
105
+ withTiming(isComplete ? 1 : isCurrent ? 0.5 : 0, {
106
+ duration: 400,
107
+ }),
108
+ );
109
+ break;
110
+ case 'fade':
111
+ dotColorAnimated.value = withSequence(
112
+ withTiming(0, { duration: 200 }),
113
+ withTiming(isComplete ? 1 : isCurrent ? 0.5 : 0, {
114
+ duration: 400,
115
+ }),
116
+ );
117
+ break;
118
+ case 'scale':
119
+ dotScale.value = withSequence(
120
+ withTiming(0.5, { duration: 200 }),
121
+ withTiming(1.2, { duration: 200 }),
122
+ withTiming(1, { duration: 200 }),
123
+ );
124
+ break;
125
+ default:
126
+ dotScale.value = withSequence(
127
+ withTiming(1.5, { duration: 200 }),
128
+ withSpring(1),
129
+ );
130
+ }
131
+
132
+ dotColorAnimated.value = withTiming(
133
+ isComplete ? 1 : isCurrent ? 0.5 : 0,
134
+ { duration: 400 },
135
+ );
136
+
137
+ iconRotate.value = withSequence(
138
+ withTiming(0, { duration: 10 }),
139
+ withTiming(1, { duration: 400 }),
140
+ );
141
+
142
+ if (isComplete) {
143
+ lineProgress.value = withTiming(1, { duration: 600 });
144
+ } else if (wasComplete) {
145
+ lineProgress.value = withTiming(0, { duration: 600 });
146
+ }
147
+ } else {
148
+ lineProgress.value = isComplete ? 1 : 0;
149
+ }
150
+ }, [
151
+ item.status,
152
+ isComplete,
153
+ isCurrent,
154
+ animationType,
155
+ dotColorAnimated,
156
+ dotScale,
157
+ iconRotate,
158
+ lineProgress,
159
+ statusChanged,
160
+ wasComplete,
161
+ ]);
162
+
163
+ const dotAnimatedStyle = useAnimatedStyle(() => {
164
+ return {
165
+ transform: [{ scale: dotScale.value }],
166
+ backgroundColor: interpolateColor(
167
+ dotColorAnimated.value,
168
+ [0, 0.5, 1],
169
+ [inactiveColor, activeColor, activeColor],
170
+ ),
171
+ };
172
+ });
173
+
174
+ const lineAnimatedStyle = useAnimatedStyle(() => {
175
+ return {
176
+ backgroundColor: interpolateColor(
177
+ lineProgress.value,
178
+ [0, 1],
179
+ [inactiveColor, activeColor],
180
+ ),
181
+ width: lineWidth,
182
+ transform: [{ scaleY: lineProgress.value }],
183
+ opacity: 0.6 + lineProgress.value * 0.4,
184
+ };
185
+ });
186
+
187
+ const iconAnimatedStyle = useAnimatedStyle(() => {
188
+ return {
189
+ transform: [{ rotate: `${iconRotate.value * 360}deg` }],
190
+ opacity: 0.8 + iconRotate.value * 0.2,
191
+ };
192
+ });
193
+
194
+ const iconName =
195
+ item.icon ||
196
+ (isComplete ? 'check' : isCurrent ? 'activity' : 'circle');
197
+
198
+ const textColor =
199
+ isComplete || isCurrent ? colors.foreground : colors.mutedForeground;
200
+
201
+ return (
202
+ <AnimatedTouchableOpacity
203
+ key={item.id}
204
+ style={styles.itemContainer}
205
+ activeOpacity={onItemPress ? 0.7 : 1}
206
+ onPress={() => onItemPress && onItemPress(item)}
207
+ entering={
208
+ animated ? FadeInDown.delay(index * 100).springify() : undefined
209
+ }
210
+ layout={LinearTransition.springify()}
211
+ >
212
+ <View style={styles.timelineColumn}>
213
+ <Animated.View style={[styles.dot, dotAnimatedStyle]}>
214
+ <Animated.View style={[{}, iconAnimatedStyle]}>
215
+ <AnimatedFeather
216
+ name={iconName as any}
217
+ size={iconSize * 0.7}
218
+ color={colors.primaryForeground}
219
+ />
220
+ </Animated.View>
221
+ </Animated.View>
222
+
223
+ {!isLast && (
224
+ <Animated.View style={[styles.line, lineAnimatedStyle]} />
225
+ )}
226
+ </View>
227
+
228
+ <View style={styles.contentColumn}>
229
+ <View style={styles.itemHeader}>
230
+ <Text style={[styles.title, { color: textColor }, titleStyle]}>
231
+ {item.title}
232
+ </Text>
233
+
234
+ {item.timestamp && (
235
+ <Text
236
+ style={[
237
+ styles.timestamp,
238
+ { color: colors.mutedForeground },
239
+ timestampStyle,
240
+ ]}
241
+ >
242
+ {item.timestamp}
243
+ </Text>
244
+ )}
245
+ </View>
246
+
247
+ {item.description && (
248
+ <Text
249
+ style={[
250
+ styles.description,
251
+ { color: colors.mutedForeground },
252
+ descriptionStyle,
253
+ ]}
254
+ >
255
+ {item.description}
256
+ </Text>
257
+ )}
258
+
259
+ {item.meta && (
260
+ <View style={[styles.metaContainer, metaContainerStyle]}>
261
+ <Text
262
+ style={[
263
+ styles.metaText,
264
+ { color: colors.mutedForeground },
265
+ metaTextStyle,
266
+ ]}
267
+ >
268
+ {item.meta}
269
+ </Text>
270
+ </View>
271
+ )}
272
+
273
+ {item.children && (
274
+ <View
275
+ style={[styles.childrenContainer, item.childrenContainer]}
276
+ >
277
+ {item.children}
278
+ </View>
279
+ )}
280
+ </View>
281
+ </AnimatedTouchableOpacity>
282
+ );
283
+ })}
284
+ </Animated.View>
285
+ );
286
+ };
287
+
288
+ const styles = StyleSheet.create({
289
+ container: {
290
+ padding: 16,
291
+ },
292
+ itemContainer: {
293
+ flexDirection: 'row',
294
+ marginBottom: 24,
295
+ },
296
+ timelineColumn: {
297
+ alignItems: 'center',
298
+ width: 40,
299
+ },
300
+ contentColumn: {
301
+ flex: 1,
302
+ marginLeft: 12,
303
+ marginTop: -4,
304
+ },
305
+ dot: {
306
+ width: 28,
307
+ height: 28,
308
+ borderRadius: 14,
309
+ justifyContent: 'center',
310
+ alignItems: 'center',
311
+ elevation: 2,
312
+ shadowColor: '#000',
313
+ shadowOffset: { width: 0, height: 1 },
314
+ shadowOpacity: 0.1,
315
+ shadowRadius: 1,
316
+ },
317
+ line: {
318
+ flex: 1,
319
+ marginTop: 4,
320
+ },
321
+ itemHeader: {
322
+ flexDirection: 'row',
323
+ justifyContent: 'space-between',
324
+ alignItems: 'flex-start',
325
+ marginBottom: 6,
326
+ },
327
+ title: {
328
+ fontSize: 16,
329
+ fontWeight: '600',
330
+ flex: 1,
331
+ },
332
+ description: {
333
+ fontSize: 14,
334
+ lineHeight: 20,
335
+ marginBottom: 8,
336
+ },
337
+ timestamp: {
338
+ fontSize: 12,
339
+ marginLeft: 8,
340
+ },
341
+ metaContainer: {
342
+ paddingHorizontal: 10,
343
+ paddingVertical: 6,
344
+ borderRadius: 6,
345
+ alignSelf: 'flex-start',
346
+ marginTop: 4,
347
+ marginBottom: 8,
348
+ },
349
+ metaText: {
350
+ fontSize: 12,
351
+ },
352
+ childrenContainer: {
353
+ padding: 12,
354
+ },
355
+ });
@@ -0,0 +1,31 @@
1
+ import type { Feather } from '@expo/vector-icons';
2
+ import type { StyleProp, TextStyle, ViewStyle } from 'react-native';
3
+
4
+ export interface TimelineItem {
5
+ id: string | number;
6
+ title: string;
7
+ description?: string;
8
+ timestamp?: string;
9
+ icon?: keyof typeof Feather.glyphMap;
10
+ status?: 'complete' | 'current' | 'upcoming';
11
+ meta?: string;
12
+ children?: React.ReactNode;
13
+ childrenContainer?: StyleProp<ViewStyle>;
14
+ }
15
+
16
+ export interface TimelineProps {
17
+ items: TimelineItem[];
18
+ activeColor?: string;
19
+ inactiveColor?: string;
20
+ animated?: boolean;
21
+ animationType?: 'bounce' | 'spring' | 'rotate' | 'fade' | 'scale';
22
+ onItemPress?: (item: TimelineItem) => void;
23
+ containerStyle?: ViewStyle;
24
+ metaContainerStyle?: StyleProp<ViewStyle>;
25
+ titleStyle?: TextStyle;
26
+ descriptionStyle?: TextStyle;
27
+ timestampStyle?: TextStyle;
28
+ lineWidth?: number;
29
+ iconSize?: number;
30
+ metaTextStyle?: StyleProp<TextStyle>;
31
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "0",
3
+ "version": "1.0.0",
4
+ "description": "Advanced UI component: 0",
5
+ "target": "native",
6
+ "copy": [
7
+ {
8
+ "from": "apps/native/src/components/advanced-ui/timeline",
9
+ "to": "apps/native/src/components/advanced-ui/timeline"
10
+ }
11
+ ],
12
+ "dependencies": {
13
+ "expo": [
14
+ "react-native-reanimated",
15
+ "react-native-gesture-handler"
16
+ ]
17
+ }
18
+ }