Skip to content

Commit 056320f

Browse files
authored
Merge pull request #1 from objectstack-ai/copilot/create-objectstack-mobile-runtime
2 parents e3c3077 + 640711c commit 056320f

25 files changed

Lines changed: 11927 additions & 2 deletions

.gitignore

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2+
3+
# dependencies
4+
node_modules/
5+
6+
# Expo
7+
.expo/
8+
dist/
9+
web-build/
10+
expo-env.d.ts
11+
12+
# Native
13+
.kotlin/
14+
*.orig.*
15+
*.jks
16+
*.p8
17+
*.p12
18+
*.key
19+
*.mobileprovision
20+
21+
# Metro
22+
.metro-health-check*
23+
24+
# debug
25+
npm-debug.*
26+
yarn-debug.*
27+
yarn-error.*
28+
29+
# macOS
30+
.DS_Store
31+
*.pem
32+
33+
# local env files
34+
.env*.local
35+
36+
# typescript
37+
*.tsbuildinfo
38+
39+
# generated native folders
40+
/ios
41+
/android

README.md

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,50 @@
1-
# mobile
2-
mobile app
1+
# ObjectStack Mobile
2+
3+
Enterprise low-code platform mobile runtime built with Expo, React Native, and TypeScript.
4+
5+
## Tech Stack
6+
7+
- **Framework:** Expo SDK 54 (Managed Workflow)
8+
- **Navigation:** Expo Router (file-based routing)
9+
- **Language:** TypeScript (strict mode)
10+
- **Styling:** NativeWind v4 (Tailwind CSS for React Native)
11+
- **UI Components:** shadcn/ui pattern (`components/ui/`)
12+
- **Icons:** lucide-react-native
13+
- **State Management:** Zustand
14+
- **Data Fetching:** TanStack Query (React Query)
15+
16+
## Project Structure
17+
18+
```
19+
├── app/ # Expo Router pages
20+
│ ├── _layout.tsx # Root layout (providers)
21+
│ └── (tabs)/ # Bottom tab navigation
22+
│ ├── _layout.tsx # Tab bar configuration
23+
│ ├── index.tsx # Home / Dashboard
24+
│ ├── apps.tsx # Apps listing
25+
│ ├── notifications.tsx
26+
│ └── profile.tsx
27+
├── components/
28+
│ └── ui/ # Reusable UI components (shadcn pattern)
29+
│ ├── Button.tsx
30+
│ ├── Card.tsx
31+
│ └── Input.tsx
32+
├── lib/
33+
│ └── utils.ts # cn() utility (clsx + tailwind-merge)
34+
├── global.css # Tailwind base + CSS variables (light/dark)
35+
├── tailwind.config.js # Tailwind configuration
36+
├── babel.config.js # Babel + NativeWind preset
37+
├── metro.config.js # Metro + NativeWind integration
38+
└── nativewind-env.d.ts # NativeWind TypeScript types
39+
```
40+
41+
## Getting Started
42+
43+
```bash
44+
npm install
45+
npx expo start
46+
```
47+
48+
## Design System
49+
50+
The app uses a CSS-variable-based design token system with light and dark mode support. Color tokens are defined in `global.css` and consumed via Tailwind classes.

app.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"expo": {
3+
"name": "ObjectStack Mobile",
4+
"slug": "objectstack-mobile",
5+
"version": "1.0.0",
6+
"orientation": "portrait",
7+
"icon": "./assets/icon.png",
8+
"scheme": "objectstack",
9+
"userInterfaceStyle": "automatic",
10+
"newArchEnabled": true,
11+
"splash": {
12+
"image": "./assets/splash-icon.png",
13+
"resizeMode": "contain",
14+
"backgroundColor": "#ffffff"
15+
},
16+
"ios": {
17+
"supportsTablet": true
18+
},
19+
"android": {
20+
"adaptiveIcon": {
21+
"foregroundImage": "./assets/adaptive-icon.png",
22+
"backgroundColor": "#ffffff"
23+
},
24+
"edgeToEdgeEnabled": true
25+
},
26+
"web": {
27+
"favicon": "./assets/favicon.png",
28+
"bundler": "metro"
29+
},
30+
"plugins": ["expo-router"]
31+
}
32+
}

app/(tabs)/_layout.tsx

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { Tabs } from "expo-router";
2+
import {
3+
Home,
4+
LayoutGrid,
5+
Bell,
6+
UserCircle,
7+
} from "lucide-react-native";
8+
9+
export default function TabLayout() {
10+
return (
11+
<Tabs
12+
screenOptions={{
13+
headerShown: true,
14+
headerStyle: { backgroundColor: "#ffffff" },
15+
headerTitleStyle: { fontWeight: "700", fontSize: 17 },
16+
headerShadowVisible: false,
17+
tabBarActiveTintColor: "#1e40af",
18+
tabBarInactiveTintColor: "#94a3b8",
19+
tabBarStyle: {
20+
borderTopColor: "#e2e8f0",
21+
backgroundColor: "#ffffff",
22+
},
23+
tabBarLabelStyle: {
24+
fontSize: 11,
25+
fontWeight: "600",
26+
},
27+
}}
28+
>
29+
<Tabs.Screen
30+
name="index"
31+
options={{
32+
title: "Home",
33+
tabBarIcon: ({ color, size }) => <Home size={size} color={color} />,
34+
}}
35+
/>
36+
<Tabs.Screen
37+
name="apps"
38+
options={{
39+
title: "Apps",
40+
tabBarIcon: ({ color, size }) => (
41+
<LayoutGrid size={size} color={color} />
42+
),
43+
}}
44+
/>
45+
<Tabs.Screen
46+
name="notifications"
47+
options={{
48+
title: "Notifications",
49+
tabBarIcon: ({ color, size }) => <Bell size={size} color={color} />,
50+
}}
51+
/>
52+
<Tabs.Screen
53+
name="profile"
54+
options={{
55+
title: "Profile",
56+
tabBarIcon: ({ color, size }) => (
57+
<UserCircle size={size} color={color} />
58+
),
59+
}}
60+
/>
61+
</Tabs>
62+
);
63+
}

app/(tabs)/apps.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { View, Text, ScrollView } from "react-native";
2+
import { SafeAreaView } from "react-native-safe-area-context";
3+
import { LayoutGrid } from "lucide-react-native";
4+
5+
export default function AppsScreen() {
6+
return (
7+
<SafeAreaView className="flex-1 bg-background" edges={["left", "right"]}>
8+
<ScrollView
9+
className="flex-1"
10+
contentContainerClassName="px-5 pb-8 pt-4"
11+
>
12+
<View className="flex-1 items-center justify-center pt-20">
13+
<View className="rounded-2xl bg-muted p-6">
14+
<LayoutGrid size={40} color="#94a3b8" />
15+
</View>
16+
<Text className="mt-5 text-lg font-semibold text-foreground">
17+
Apps
18+
</Text>
19+
<Text className="mt-2 text-center text-sm text-muted-foreground">
20+
Your enterprise applications will appear here.
21+
</Text>
22+
</View>
23+
</ScrollView>
24+
</SafeAreaView>
25+
);
26+
}

app/(tabs)/index.tsx

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { View, Text, ScrollView } from "react-native";
2+
import { SafeAreaView } from "react-native-safe-area-context";
3+
import { TrendingUp, TrendingDown, Users, ShoppingCart, DollarSign, Activity } from "lucide-react-native";
4+
import { Card, CardHeader, CardTitle, CardContent } from "~/components/ui/Card";
5+
6+
interface DashboardCardMeta {
7+
type: "card";
8+
title: string;
9+
value: string;
10+
trend: string;
11+
icon: string;
12+
}
13+
14+
const dashboardMetadata: DashboardCardMeta[] = [
15+
{ type: "card", title: "Monthly Sales", value: "$120,000", trend: "+12%", icon: "dollar-sign" },
16+
{ type: "card", title: "Active Users", value: "8,420", trend: "+5.2%", icon: "users" },
17+
{ type: "card", title: "Orders", value: "1,340", trend: "-2.1%", icon: "shopping-cart" },
18+
{ type: "card", title: "Revenue Growth", value: "23.5%", trend: "+8.7%", icon: "activity" },
19+
];
20+
21+
const iconMap: Record<string, React.ComponentType<{ size: number; color: string }>> = {
22+
"dollar-sign": DollarSign,
23+
users: Users,
24+
"shopping-cart": ShoppingCart,
25+
activity: Activity,
26+
};
27+
28+
function TrendBadge({ trend }: { trend: string }) {
29+
const isPositive = trend.startsWith("+");
30+
const TrendIcon = isPositive ? TrendingUp : TrendingDown;
31+
return (
32+
<View
33+
className={`flex-row items-center rounded-full px-2.5 py-1 ${
34+
isPositive ? "bg-emerald-50" : "bg-red-50"
35+
}`}
36+
>
37+
<TrendIcon size={12} color={isPositive ? "#059669" : "#dc2626"} />
38+
<Text
39+
className={`ml-1 text-xs font-semibold ${
40+
isPositive ? "text-emerald-700" : "text-red-600"
41+
}`}
42+
>
43+
{trend}
44+
</Text>
45+
</View>
46+
);
47+
}
48+
49+
function MetadataCardRenderer({ meta }: { meta: DashboardCardMeta }) {
50+
const IconComponent = iconMap[meta.icon] ?? Activity;
51+
52+
return (
53+
<Card className="mb-3">
54+
<CardHeader className="flex-row items-center justify-between pb-2">
55+
<CardTitle className="text-sm font-medium text-muted-foreground">
56+
{meta.title}
57+
</CardTitle>
58+
<View className="rounded-lg bg-primary/10 p-2">
59+
<IconComponent size={18} color="#1e40af" />
60+
</View>
61+
</CardHeader>
62+
<CardContent>
63+
<Text className="text-2xl font-bold text-card-foreground">
64+
{meta.value}
65+
</Text>
66+
<View className="mt-2">
67+
<TrendBadge trend={meta.trend} />
68+
</View>
69+
</CardContent>
70+
</Card>
71+
);
72+
}
73+
74+
function renderFromMetadata(metadata: DashboardCardMeta[]) {
75+
return metadata.map((item, index) => {
76+
switch (item.type) {
77+
case "card":
78+
return <MetadataCardRenderer key={index} meta={item} />;
79+
default:
80+
return null;
81+
}
82+
});
83+
}
84+
85+
export default function HomeScreen() {
86+
return (
87+
<SafeAreaView className="flex-1 bg-background" edges={["left", "right"]}>
88+
<ScrollView
89+
className="flex-1"
90+
contentContainerClassName="px-5 pb-8 pt-4"
91+
showsVerticalScrollIndicator={false}
92+
>
93+
<View className="mb-5">
94+
<Text className="text-2xl font-bold text-foreground">
95+
Dashboard
96+
</Text>
97+
<Text className="mt-1 text-sm text-muted-foreground">
98+
Welcome back. Here&apos;s your overview.
99+
</Text>
100+
</View>
101+
102+
{renderFromMetadata(dashboardMetadata)}
103+
</ScrollView>
104+
</SafeAreaView>
105+
);
106+
}

app/(tabs)/notifications.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { View, Text, ScrollView } from "react-native";
2+
import { SafeAreaView } from "react-native-safe-area-context";
3+
import { Bell } from "lucide-react-native";
4+
5+
export default function NotificationsScreen() {
6+
return (
7+
<SafeAreaView className="flex-1 bg-background" edges={["left", "right"]}>
8+
<ScrollView
9+
className="flex-1"
10+
contentContainerClassName="px-5 pb-8 pt-4"
11+
>
12+
<View className="flex-1 items-center justify-center pt-20">
13+
<View className="rounded-2xl bg-muted p-6">
14+
<Bell size={40} color="#94a3b8" />
15+
</View>
16+
<Text className="mt-5 text-lg font-semibold text-foreground">
17+
No Notifications
18+
</Text>
19+
<Text className="mt-2 text-center text-sm text-muted-foreground">
20+
You&apos;re all caught up. New notifications will appear here.
21+
</Text>
22+
</View>
23+
</ScrollView>
24+
</SafeAreaView>
25+
);
26+
}

app/(tabs)/profile.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { View, Text, ScrollView } from "react-native";
2+
import { SafeAreaView } from "react-native-safe-area-context";
3+
import { UserCircle } from "lucide-react-native";
4+
import { Button } from "~/components/ui/Button";
5+
6+
export default function ProfileScreen() {
7+
return (
8+
<SafeAreaView className="flex-1 bg-background" edges={["left", "right"]}>
9+
<ScrollView
10+
className="flex-1"
11+
contentContainerClassName="px-5 pb-8 pt-4"
12+
>
13+
<View className="items-center pt-10">
14+
<View className="rounded-full bg-muted p-5">
15+
<UserCircle size={56} color="#94a3b8" />
16+
</View>
17+
<Text className="mt-4 text-xl font-bold text-foreground">
18+
John Doe
19+
</Text>
20+
<Text className="mt-1 text-sm text-muted-foreground">
21+
john.doe@company.com
22+
</Text>
23+
</View>
24+
25+
<View className="mt-8 gap-3">
26+
<Button variant="outline">Edit Profile</Button>
27+
<Button variant="outline">Settings</Button>
28+
<Button variant="ghost">Help &amp; Support</Button>
29+
<Button variant="destructive">Sign Out</Button>
30+
</View>
31+
</ScrollView>
32+
</SafeAreaView>
33+
);
34+
}

0 commit comments

Comments
 (0)