Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions app/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ export default function TabLayout() {
tabBarIcon: ({ color }) => <IconSymbol size={28} name="paperplane.fill" color={color} />,
}}
/>
<Tabs.Screen
name="callstack"
options={{
title: 'Callstack',
tabBarIcon: ({ color }) => <IconSymbol size={28} name="briefcase.fill" color={color} />,
}}
/>
</Tabs>
);
}
107 changes: 107 additions & 0 deletions app/(tabs)/callstack.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { StyleSheet } from 'react-native';

import { ExternalLink } from '@/components/external-link';
import ParallaxScrollView from '@/components/parallax-scroll-view';
import { ThemedText } from '@/components/themed-text';
import { ThemedView } from '@/components/themed-view';
import { IconSymbol } from '@/components/ui/icon-symbol';
import { Fonts } from '@/constants/theme';

const services = [
{
title: 'Engineering',
body: 'Feature development, end-to-end product delivery, AI-assisted migrations, and architecture or performance work for React Native products.',
},
{
title: 'Infrastructure',
body: 'AI-driven QA and testing, custom agents, and self-hosted CI/CD infrastructure that helps teams turn agent output into releases.',
},
{
title: 'Adoption & governance',
body: 'AI tooling setup, adoption measurement, team training, private model strategy, compliance support, and AI security audits.',
},
];

const highlights = [
'React Foundation members and React Native core contributors',
'Open-source contributors across the React Native ecosystem since 2016',
'100+ enterprise clients and 10+ years helping teams ship cross-platform products',
];

export default function CallstackScreen() {
return (
<ParallaxScrollView
headerBackgroundColor={{ light: '#E9F6F4', dark: '#12302F' }}
headerImage={
<IconSymbol
size={260}
color="#0A7EA4"
name="briefcase.fill"
style={styles.headerImage}
/>
}>
<ThemedView style={styles.titleContainer}>
<ThemedText type="title" style={{ fontFamily: Fonts.rounded }}>
Callstack
</ThemedText>
</ThemedView>

<ThemedText>
Callstack helps companies ship production-grade React Native and AI-native products. Their
work spans product engineering, delivery infrastructure, AI adoption, and enterprise
governance.
</ThemedText>

<ThemedView style={styles.section}>
<ThemedText type="subtitle">What they offer</ThemedText>
{services.map((service) => (
<ThemedView key={service.title} style={styles.service}>
<ThemedText type="defaultSemiBold">{service.title}</ThemedText>
<ThemedText>{service.body}</ThemedText>
</ThemedView>
))}
</ThemedView>

<ThemedView style={styles.section}>
<ThemedText type="subtitle">Why teams choose them</ThemedText>
{highlights.map((highlight) => (
<ThemedView key={highlight} style={styles.highlight}>
<ThemedText type="defaultSemiBold">-</ThemedText>
<ThemedText style={styles.highlightText}>{highlight}</ThemedText>
</ThemedView>
))}
</ThemedView>

<ExternalLink href="https://www.callstack.com">
<ThemedText type="link">Visit callstack.com</ThemedText>
</ExternalLink>
</ParallaxScrollView>
);
}

const styles = StyleSheet.create({
headerImage: {
bottom: -48,
opacity: 0.42,
position: 'absolute',
right: -24,
},
highlight: {
alignItems: 'flex-start',
flexDirection: 'row',
gap: 8,
},
highlightText: {
flex: 1,
},
section: {
gap: 12,
},
service: {
gap: 4,
},
titleContainer: {
flexDirection: 'row',
gap: 8,
},
});
8 changes: 5 additions & 3 deletions components/ui/icon-symbol.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import { SymbolWeight, SymbolViewProps } from 'expo-symbols';
import { ComponentProps } from 'react';
import { type ComponentProps } from 'react';
import { OpaqueColorValue, type StyleProp, type TextStyle } from 'react-native';

type IconMapping = Record<SymbolViewProps['name'], ComponentProps<typeof MaterialIcons>['name']>;
type SymbolName = Extract<SymbolViewProps['name'], string>;
type MaterialIconName = ComponentProps<typeof MaterialIcons>['name'];
type IconSymbolName = keyof typeof MAPPING;

/**
Expand All @@ -16,9 +17,10 @@ type IconSymbolName = keyof typeof MAPPING;
const MAPPING = {
'house.fill': 'home',
'paperplane.fill': 'send',
'briefcase.fill': 'business',
'chevron.left.forwardslash.chevron.right': 'code',
'chevron.right': 'chevron-right',
} as IconMapping;
} satisfies Partial<Record<SymbolName, MaterialIconName>>;

/**
* An icon component that uses native SF Symbols on iOS, and Material Icons on Android and web.
Expand Down
8 changes: 7 additions & 1 deletion hooks/use-color-scheme.ts
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
export { useColorScheme } from 'react-native';
import { useColorScheme as useRNColorScheme } from 'react-native';

export type AppColorScheme = 'light' | 'dark';

export function useColorScheme(): AppColorScheme {
return useRNColorScheme() === 'dark' ? 'dark' : 'light';
}
6 changes: 4 additions & 2 deletions hooks/use-color-scheme.web.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useEffect, useState } from 'react';
import { useColorScheme as useRNColorScheme } from 'react-native';

import type { AppColorScheme } from '@/hooks/use-color-scheme';

/**
* To support static rendering, this value needs to be re-calculated on the client side for web
*/
export function useColorScheme() {
export function useColorScheme(): AppColorScheme {
const [hasHydrated, setHasHydrated] = useState(false);

useEffect(() => {
Expand All @@ -14,7 +16,7 @@ export function useColorScheme() {
const colorScheme = useRNColorScheme();

if (hasHydrated) {
return colorScheme;
return colorScheme === 'dark' ? 'dark' : 'light';
}

return 'light';
Expand Down