feat: Introduce a comprehensive set of AI agent skills, agents, and workflows, along with new UI components and application structure.
This commit is contained in:
1 parent
a2a512d41d
commit
93df87d7e9
317 files changed
+63716
-8727
No files matched your search
@@ -0,0 +1,118 @@
|
||||
import { Colors } from '@/constants/theme';
|
||||
import { useColorScheme } from '@/hooks/use-color-scheme';
|
||||
import React from 'react';
|
||||
import { ActivityIndicator, Pressable, StyleSheet, Text, TextStyle, ViewStyle } from 'react-native';
|
||||
|
||||
interface ButtonProps {
|
||||
title: string;
|
||||
onPress: () => void;
|
||||
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
style?: ViewStyle;
|
||||
textStyle?: TextStyle;
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Button({
|
||||
title,
|
||||
onPress,
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
loading = false,
|
||||
disabled = false,
|
||||
style,
|
||||
textStyle,
|
||||
icon
|
||||
}: ButtonProps) {
|
||||
const colorScheme = useColorScheme();
|
||||
const theme = Colors[colorScheme ?? 'dark'];
|
||||
|
||||
const getBackgroundColor = () => {
|
||||
if (disabled) return theme.surfaceHighlight;
|
||||
switch (variant) {
|
||||
case 'primary': return theme.primary;
|
||||
case 'secondary': return theme.surfaceHighlight;
|
||||
case 'outline': return 'transparent';
|
||||
case 'ghost': return 'transparent';
|
||||
default: return theme.primary;
|
||||
}
|
||||
};
|
||||
|
||||
const getTextColor = () => {
|
||||
if (disabled) return theme.textSecondary;
|
||||
switch (variant) {
|
||||
case 'primary': return theme.background; // Black text on white bg
|
||||
case 'secondary': return theme.text;
|
||||
case 'outline': return theme.text;
|
||||
case 'ghost': return theme.textSecondary;
|
||||
default: return theme.background;
|
||||
}
|
||||
};
|
||||
|
||||
const getBorder = () => {
|
||||
if (variant === 'outline') return { borderWidth: 1, borderColor: theme.border };
|
||||
return {};
|
||||
};
|
||||
|
||||
const getPadding = () => {
|
||||
switch (size) {
|
||||
case 'sm': return { paddingVertical: 8, paddingHorizontal: 12 };
|
||||
case 'lg': return { paddingVertical: 16, paddingHorizontal: 24 };
|
||||
default: return { paddingVertical: 12, paddingHorizontal: 20 };
|
||||
}
|
||||
};
|
||||
|
||||
const getFontSize = () => {
|
||||
switch (size) {
|
||||
case 'sm': return 13;
|
||||
case 'lg': return 17;
|
||||
default: return 15;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
disabled={disabled || loading}
|
||||
style={({ pressed }) => [
|
||||
styles.button,
|
||||
{ backgroundColor: getBackgroundColor() },
|
||||
getBorder(),
|
||||
getPadding(),
|
||||
{ opacity: pressed ? 0.8 : 1 },
|
||||
style
|
||||
]}
|
||||
>
|
||||
{loading ? (
|
||||
<ActivityIndicator color={getTextColor()} />
|
||||
) : (
|
||||
<>
|
||||
{icon}
|
||||
<Text style={[
|
||||
styles.text,
|
||||
{ color: getTextColor(), fontSize: getFontSize() },
|
||||
icon ? { marginLeft: 8 } : {},
|
||||
textStyle
|
||||
]}>
|
||||
{title}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
borderRadius: 12,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
text: {
|
||||
fontWeight: '600',
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
interface ConnectionBadgeProps {
|
||||
isConnected: boolean;
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
export function ConnectionBadge({ isConnected, compact }: ConnectionBadgeProps) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={[styles.dot, isConnected ? styles.dotConnected : styles.dotDisconnected]} />
|
||||
{!compact && (
|
||||
<Text style={[styles.text, isConnected ? styles.textConnected : styles.textDisconnected]}>
|
||||
{isConnected ? 'Connected' : 'Disconnected'}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
},
|
||||
dot: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: 4,
|
||||
},
|
||||
dotConnected: {
|
||||
backgroundColor: '#22C55E',
|
||||
},
|
||||
dotDisconnected: {
|
||||
backgroundColor: '#EF4444',
|
||||
},
|
||||
text: {
|
||||
fontSize: 12,
|
||||
fontWeight: '500',
|
||||
},
|
||||
textConnected: {
|
||||
color: '#22C55E',
|
||||
},
|
||||
textDisconnected: {
|
||||
color: '#EF4444',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Colors } from '@/constants/theme';
|
||||
import { useColorScheme } from '@/hooks/use-color-scheme';
|
||||
import { StyleSheet, View, ViewStyle } from 'react-native';
|
||||
|
||||
interface GlassCardProps {
|
||||
children: React.ReactNode;
|
||||
style?: ViewStyle;
|
||||
variant?: 'default' | 'highlight';
|
||||
}
|
||||
|
||||
export function GlassCard({ children, style, variant = 'default' }: GlassCardProps) {
|
||||
const colorScheme = useColorScheme();
|
||||
const theme = Colors[colorScheme ?? 'dark'];
|
||||
|
||||
return (
|
||||
<View style={[
|
||||
styles.card,
|
||||
{
|
||||
backgroundColor: variant === 'default' ? theme.surface : theme.surfaceHighlight,
|
||||
borderColor: theme.border,
|
||||
},
|
||||
style
|
||||
]}>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
borderRadius: 16,
|
||||
borderWidth: 1,
|
||||
padding: 16,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Colors } from '@/constants/theme';
|
||||
import { useColorScheme } from '@/hooks/use-color-scheme';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import { ScrollView, StyleSheet, View, ViewStyle } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
interface ScreenWrapperProps {
|
||||
children: React.ReactNode;
|
||||
style?: ViewStyle;
|
||||
contentContainerStyle?: ViewStyle;
|
||||
scrollable?: boolean;
|
||||
}
|
||||
|
||||
export function ScreenWrapper({
|
||||
children,
|
||||
style,
|
||||
contentContainerStyle,
|
||||
scrollable = false
|
||||
}: ScreenWrapperProps) {
|
||||
const colorScheme = useColorScheme();
|
||||
const theme = Colors[colorScheme ?? 'dark'];
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const containerStyle = [
|
||||
styles.container,
|
||||
{
|
||||
backgroundColor: theme.background,
|
||||
paddingTop: insets.top,
|
||||
paddingLeft: insets.left,
|
||||
paddingRight: insets.right,
|
||||
},
|
||||
style
|
||||
];
|
||||
|
||||
if (scrollable) {
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: theme.background }}>
|
||||
<StatusBar style={colorScheme === 'dark' ? 'light' : 'dark'} />
|
||||
<ScrollView
|
||||
style={containerStyle}
|
||||
contentContainerStyle={[
|
||||
{ paddingBottom: insets.bottom + 20 },
|
||||
contentContainerStyle
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={containerStyle}>
|
||||
<StatusBar style={colorScheme === 'dark' ? 'light' : 'dark'} />
|
||||
<View style={[{ flex: 1, paddingBottom: insets.bottom }, contentContainerStyle]}>
|
||||
{children}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
import { PropsWithChildren, useState } from 'react';
|
||||
import { StyleSheet, TouchableOpacity } from 'react-native';
|
||||
|
||||
import { ThemedText } from '@/components/themed-text';
|
||||
import { ThemedView } from '@/components/themed-view';
|
||||
import { IconSymbol } from '@/components/ui/icon-symbol';
|
||||
import { Colors } from '@/constants/theme';
|
||||
import { useColorScheme } from '@/hooks/use-color-scheme';
|
||||
|
||||
export function Collapsible({ children, title }: PropsWithChildren & { title: string }) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const theme = useColorScheme() ?? 'light';
|
||||
|
||||
return (
|
||||
<ThemedView>
|
||||
<TouchableOpacity
|
||||
style={styles.heading}
|
||||
onPress={() => setIsOpen((value) => !value)}
|
||||
activeOpacity={0.8}>
|
||||
<IconSymbol
|
||||
name="chevron.right"
|
||||
size={18}
|
||||
weight="medium"
|
||||
color={theme === 'light' ? Colors.light.icon : Colors.dark.icon}
|
||||
style={{ transform: [{ rotate: isOpen ? '90deg' : '0deg' }] }}
|
||||
/>
|
||||
|
||||
<ThemedText type="defaultSemiBold">{title}</ThemedText>
|
||||
</TouchableOpacity>
|
||||
{isOpen && <ThemedView style={styles.content}>{children}</ThemedView>}
|
||||
</ThemedView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
heading: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
},
|
||||
content: {
|
||||
marginTop: 6,
|
||||
marginLeft: 24,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import { SymbolView, SymbolViewProps, SymbolWeight } from 'expo-symbols';
|
||||
import { StyleProp, ViewStyle } from 'react-native';
|
||||
|
||||
export function IconSymbol({
|
||||
name,
|
||||
size = 24,
|
||||
color,
|
||||
style,
|
||||
weight = 'regular',
|
||||
}: {
|
||||
name: SymbolViewProps['name'];
|
||||
size?: number;
|
||||
color: string;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
weight?: SymbolWeight;
|
||||
}) {
|
||||
return (
|
||||
<SymbolView
|
||||
weight={weight}
|
||||
tintColor={color}
|
||||
resizeMode="scaleAspectFit"
|
||||
name={name}
|
||||
style={[
|
||||
{
|
||||
width: size,
|
||||
height: size,
|
||||
},
|
||||
style,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Fallback for using MaterialIcons on Android and web.
|
||||
|
||||
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||
import { SymbolWeight, SymbolViewProps } from 'expo-symbols';
|
||||
import { ComponentProps } from 'react';
|
||||
import { OpaqueColorValue, type StyleProp, type TextStyle } from 'react-native';
|
||||
|
||||
type IconMapping = Record<SymbolViewProps['name'], ComponentProps<typeof MaterialIcons>['name']>;
|
||||
type IconSymbolName = keyof typeof MAPPING;
|
||||
|
||||
/**
|
||||
* Add your SF Symbols to Material Icons mappings here.
|
||||
* - see Material Icons in the [Icons Directory](https://icons.expo.fyi).
|
||||
* - see SF Symbols in the [SF Symbols](https://developer.apple.com/sf-symbols/) app.
|
||||
*/
|
||||
const MAPPING = {
|
||||
'house.fill': 'home',
|
||||
'paperplane.fill': 'send',
|
||||
'chevron.left.forwardslash.chevron.right': 'code',
|
||||
'chevron.right': 'chevron-right',
|
||||
} as IconMapping;
|
||||
|
||||
/**
|
||||
* An icon component that uses native SF Symbols on iOS, and Material Icons on Android and web.
|
||||
* This ensures a consistent look across platforms, and optimal resource usage.
|
||||
* Icon `name`s are based on SF Symbols and require manual mapping to Material Icons.
|
||||
*/
|
||||
export function IconSymbol({
|
||||
name,
|
||||
size = 24,
|
||||
color,
|
||||
style,
|
||||
}: {
|
||||
name: IconSymbolName;
|
||||
size?: number;
|
||||
color: string | OpaqueColorValue;
|
||||
style?: StyleProp<TextStyle>;
|
||||
weight?: SymbolWeight;
|
||||
}) {
|
||||
return <MaterialIcons color={color} size={size} name={MAPPING[name]} style={style} />;
|
||||
}
|
||||
Reference in new issue
Block a user