@10play/expo-air 0.12.0 → 0.12.2

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,161 @@
1
+ import React, { useRef, useEffect } from "react";
2
+ import { View, Text, StyleSheet, TouchableOpacity, Animated, Easing, type TextProps } from "react-native";
3
+ import { SPACING, LAYOUT, COLORS, TYPOGRAPHY, SIZES } from "../constants/design";
4
+
5
+ // Typed animated components for React 19 compatibility
6
+ const AnimatedText = Animated.Text as React.ComponentClass<Animated.AnimatedProps<TextProps>>;
7
+
8
+ export type TabType = "chat" | "changes";
9
+
10
+ interface TabBarProps {
11
+ activeTab: TabType;
12
+ onTabChange: (tab: TabType) => void;
13
+ onNewSession: () => void;
14
+ canStartNew: boolean;
15
+ hasPR: boolean;
16
+ hasChanges: boolean;
17
+ prNumber?: string;
18
+ onCreatePR: () => void;
19
+ onCommit: () => void;
20
+ onViewPR: () => void;
21
+ }
22
+
23
+ export function TabBar({
24
+ activeTab,
25
+ onTabChange,
26
+ onNewSession,
27
+ canStartNew,
28
+ hasPR,
29
+ hasChanges,
30
+ prNumber,
31
+ onCreatePR,
32
+ onCommit,
33
+ onViewPR,
34
+ }: TabBarProps) {
35
+ // Determine which CTA to show for Changes tab
36
+ const renderCTA = () => {
37
+ if (activeTab === "chat") {
38
+ return (
39
+ <TouchableOpacity
40
+ onPress={onNewSession}
41
+ style={[styles.ctaButton, !canStartNew && styles.ctaButtonDisabled]}
42
+ disabled={!canStartNew}
43
+ >
44
+ <Text style={[styles.ctaText, !canStartNew && styles.ctaTextDisabled]}>New</Text>
45
+ </TouchableOpacity>
46
+ );
47
+ }
48
+
49
+ // Changes tab - show smart CTA with breathing animation
50
+ if (!hasPR && hasChanges) {
51
+ return <BreathingButton onPress={onCreatePR}>Create PR</BreathingButton>;
52
+ }
53
+ if (hasPR && hasChanges) {
54
+ return <BreathingButton onPress={onCommit}>Commit</BreathingButton>;
55
+ }
56
+ if (hasPR && !hasChanges && prNumber) {
57
+ return <BreathingButton onPress={onViewPR}>#{prNumber}</BreathingButton>;
58
+ }
59
+ return null; // no PR + no changes = nothing
60
+ };
61
+
62
+ return (
63
+ <View style={styles.tabBar}>
64
+ <View style={styles.tabButtons}>
65
+ <TouchableOpacity onPress={() => onTabChange("chat")}>
66
+ <Text style={[
67
+ styles.tabText,
68
+ activeTab === "chat" ? styles.tabTextActive : styles.tabTextInactive
69
+ ]}>
70
+ Chat
71
+ </Text>
72
+ </TouchableOpacity>
73
+ <TouchableOpacity onPress={() => onTabChange("changes")}>
74
+ <Text style={[
75
+ styles.tabText,
76
+ activeTab === "changes" ? styles.tabTextActive : styles.tabTextInactive
77
+ ]}>
78
+ Changes
79
+ </Text>
80
+ </TouchableOpacity>
81
+ </View>
82
+ {renderCTA()}
83
+ </View>
84
+ );
85
+ }
86
+
87
+ function BreathingButton({ children, onPress }: React.PropsWithChildren<{ onPress: () => void }>) {
88
+ const opacityAnim = useRef(new Animated.Value(0.6)).current;
89
+
90
+ useEffect(() => {
91
+ const animation = Animated.loop(
92
+ Animated.sequence([
93
+ Animated.timing(opacityAnim, {
94
+ toValue: 0.9,
95
+ duration: 1500,
96
+ easing: Easing.inOut(Easing.ease),
97
+ useNativeDriver: true,
98
+ }),
99
+ Animated.timing(opacityAnim, {
100
+ toValue: 0.6,
101
+ duration: 1500,
102
+ easing: Easing.inOut(Easing.ease),
103
+ useNativeDriver: true,
104
+ }),
105
+ ])
106
+ );
107
+ animation.start();
108
+ return () => animation.stop();
109
+ }, [opacityAnim]);
110
+
111
+ return (
112
+ <TouchableOpacity onPress={onPress} style={styles.ctaButton} activeOpacity={0.7}>
113
+ <AnimatedText style={[styles.ctaText, { opacity: opacityAnim }]}>
114
+ {children}
115
+ </AnimatedText>
116
+ </TouchableOpacity>
117
+ );
118
+ }
119
+
120
+ const styles = StyleSheet.create({
121
+ tabBar: {
122
+ flexDirection: "row",
123
+ alignItems: "center",
124
+ justifyContent: "space-between",
125
+ paddingHorizontal: LAYOUT.CONTENT_PADDING_H,
126
+ paddingVertical: SPACING.SM + 2, // 10px
127
+ borderBottomWidth: 1,
128
+ borderBottomColor: COLORS.BORDER,
129
+ },
130
+ tabButtons: {
131
+ flexDirection: "row",
132
+ gap: SPACING.XL,
133
+ },
134
+ tabText: {
135
+ fontSize: TYPOGRAPHY.SIZE_LG,
136
+ fontWeight: TYPOGRAPHY.WEIGHT_MEDIUM,
137
+ },
138
+ tabTextActive: {
139
+ color: COLORS.TEXT_PRIMARY,
140
+ },
141
+ tabTextInactive: {
142
+ color: COLORS.TEXT_MUTED,
143
+ },
144
+ ctaButton: {
145
+ paddingHorizontal: SIZES.CTA_PADDING_H,
146
+ paddingVertical: SIZES.CTA_PADDING_V,
147
+ borderRadius: LAYOUT.BORDER_RADIUS_SM,
148
+ backgroundColor: COLORS.BACKGROUND_INTERACTIVE,
149
+ },
150
+ ctaButtonDisabled: {
151
+ opacity: 0.4,
152
+ },
153
+ ctaText: {
154
+ color: COLORS.TEXT_PRIMARY,
155
+ fontSize: TYPOGRAPHY.SIZE_SM,
156
+ fontWeight: TYPOGRAPHY.WEIGHT_SEMIBOLD,
157
+ },
158
+ ctaTextDisabled: {
159
+ opacity: 0.6,
160
+ },
161
+ });
@@ -160,6 +160,7 @@ export interface BranchInfo {
160
160
  prNumber?: string;
161
161
  prTitle?: string;
162
162
  lastCommitDate?: string;
163
+ isRemote?: boolean;
163
164
  }
164
165
 
165
166
  export interface BranchesListMessage {