@teardown/cli 2.0.72 → 2.0.74
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.
- package/package.json +2 -2
- package/src/cli/commands/dev.ts +37 -1
- package/src/cli/commands/run.ts +36 -1
- package/src/utils/bundler.ts +27 -2
- package/src/utils/index.ts +2 -0
- package/src/utils/metro-config.ts +107 -0
- package/templates/.teardown/linking.generated.ts +11 -0
- package/templates/.teardown/manifest.json +18 -0
- package/templates/.teardown/register.d.ts +9 -0
- package/templates/.teardown/routeTree.generated.ts +42 -0
- package/templates/.teardown/routes.generated.ts +19 -0
- package/templates/src/_routes/home.tsx +7 -32
- package/templates/src/app/index.tsx +2 -4
- package/templates/src/components/ui/text.tsx +2 -6
- package/templates/src/providers/app.provider.tsx +3 -3
- package/templates/src/routes/home.tsx +94 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teardown/cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.74",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
78
|
"@biomejs/biome": "2.3.11",
|
|
79
|
-
"@teardown/tsconfig": "2.0.
|
|
79
|
+
"@teardown/tsconfig": "2.0.74",
|
|
80
80
|
"@types/bun": "1.3.5",
|
|
81
81
|
"@types/ejs": "^3.1.5",
|
|
82
82
|
"typescript": "5.9.3"
|
package/src/cli/commands/dev.ts
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
7
|
-
import { join } from "node:path";
|
|
7
|
+
import { join, resolve } from "node:path";
|
|
8
8
|
import chalk from "chalk";
|
|
9
9
|
import { Command } from "commander";
|
|
10
|
+
import { getNavigationConfig } from "../../utils/metro-config";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Check if a metro.config.js file exists in the project
|
|
@@ -48,6 +49,41 @@ export function createDevCommand(): Command {
|
|
|
48
49
|
console.log(chalk.gray("Tip: Add @teardown/metro-config for 36x faster builds\n"));
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
// Pre-generate routes if navigation-metro is installed
|
|
53
|
+
const navigationMetroPath = join(projectRoot, "node_modules/@teardown/navigation-metro");
|
|
54
|
+
const navConfig = getNavigationConfig(projectRoot);
|
|
55
|
+
|
|
56
|
+
if (existsSync(navigationMetroPath) && navConfig) {
|
|
57
|
+
const routesDir = resolve(projectRoot, navConfig.routesDir);
|
|
58
|
+
const generatedDir = resolve(projectRoot, navConfig.generatedDir);
|
|
59
|
+
|
|
60
|
+
if (existsSync(routesDir)) {
|
|
61
|
+
try {
|
|
62
|
+
// Dynamic require from user's node_modules
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
64
|
+
const { generateAllRouteFiles } = require(navigationMetroPath) as {
|
|
65
|
+
generateAllRouteFiles: (opts: {
|
|
66
|
+
routesDir: string;
|
|
67
|
+
generatedDir: string;
|
|
68
|
+
prefixes: string[];
|
|
69
|
+
verbose: boolean;
|
|
70
|
+
}) => void;
|
|
71
|
+
};
|
|
72
|
+
generateAllRouteFiles({
|
|
73
|
+
routesDir,
|
|
74
|
+
generatedDir,
|
|
75
|
+
prefixes: [],
|
|
76
|
+
verbose: options.verbose,
|
|
77
|
+
});
|
|
78
|
+
if (options.verbose) {
|
|
79
|
+
console.log(chalk.gray("Generated route types\n"));
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
// Ignore - Metro will regenerate
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
51
87
|
console.log(chalk.blue("Starting Metro bundler...\n"));
|
|
52
88
|
|
|
53
89
|
const args = ["react-native", "start", "--port", options.port];
|
package/src/cli/commands/run.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { type ChildProcess, exec, spawn } from "node:child_process";
|
|
14
14
|
import { existsSync } from "node:fs";
|
|
15
|
-
import { join } from "node:path";
|
|
15
|
+
import { join, resolve } from "node:path";
|
|
16
16
|
import { createInterface } from "node:readline";
|
|
17
17
|
import { promisify } from "node:util";
|
|
18
18
|
import chalk from "chalk";
|
|
@@ -20,6 +20,7 @@ import { Command } from "commander";
|
|
|
20
20
|
import ora from "ora";
|
|
21
21
|
import { attachBundlerToForeground, startBundlerBackground, waitForBundlerReady } from "../../utils/bundler";
|
|
22
22
|
import { getAppScheme, launchAppWithBundler } from "../../utils/deep-link";
|
|
23
|
+
import { getNavigationConfig } from "../../utils/metro-config";
|
|
23
24
|
|
|
24
25
|
const execAsync = promisify(exec);
|
|
25
26
|
|
|
@@ -336,6 +337,40 @@ export function createRunCommand(): Command {
|
|
|
336
337
|
|
|
337
338
|
// Start Metro bundler FIRST (unless --no-bundler or --release)
|
|
338
339
|
if (options.bundler && !options.release) {
|
|
340
|
+
// Pre-generate routes if navigation-metro is installed
|
|
341
|
+
const projectRoot = process.cwd();
|
|
342
|
+
const navigationMetroPath = join(projectRoot, "node_modules/@teardown/navigation-metro");
|
|
343
|
+
const navConfig = getNavigationConfig(projectRoot);
|
|
344
|
+
|
|
345
|
+
if (existsSync(navigationMetroPath) && navConfig) {
|
|
346
|
+
const routesDir = resolve(projectRoot, navConfig.routesDir);
|
|
347
|
+
const generatedDir = resolve(projectRoot, navConfig.generatedDir);
|
|
348
|
+
|
|
349
|
+
if (existsSync(routesDir)) {
|
|
350
|
+
try {
|
|
351
|
+
// Dynamic require from user's node_modules
|
|
352
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
353
|
+
const { generateAllRouteFiles } = require(navigationMetroPath) as {
|
|
354
|
+
generateAllRouteFiles: (opts: {
|
|
355
|
+
routesDir: string;
|
|
356
|
+
generatedDir: string;
|
|
357
|
+
prefixes: string[];
|
|
358
|
+
verbose: boolean;
|
|
359
|
+
}) => void;
|
|
360
|
+
};
|
|
361
|
+
const slug = getAppScheme(projectRoot) || "app";
|
|
362
|
+
generateAllRouteFiles({
|
|
363
|
+
routesDir,
|
|
364
|
+
generatedDir,
|
|
365
|
+
prefixes: [`${slug}://`],
|
|
366
|
+
verbose: false,
|
|
367
|
+
});
|
|
368
|
+
} catch {
|
|
369
|
+
// Ignore - Metro will regenerate
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
339
374
|
metroProcess = await startMetroAndWait(options.port, platform);
|
|
340
375
|
}
|
|
341
376
|
|
package/src/utils/bundler.ts
CHANGED
|
@@ -85,7 +85,7 @@ export function startBundlerBackground(options: StartBundlerOptions = {}): Child
|
|
|
85
85
|
const proc = spawn("npx", args, {
|
|
86
86
|
cwd,
|
|
87
87
|
shell: true,
|
|
88
|
-
stdio: ["
|
|
88
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
89
89
|
detached: false,
|
|
90
90
|
env: {
|
|
91
91
|
...process.env,
|
|
@@ -239,16 +239,41 @@ export async function killBundler(proc: ChildProcess, timeoutMs = 5000): Promise
|
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
/**
|
|
242
|
-
* Attach bundler process to foreground (pipe stdout/stderr)
|
|
242
|
+
* Attach bundler process to foreground (pipe stdin/stdout/stderr)
|
|
243
|
+
*
|
|
244
|
+
* Enables keyboard shortcuts like 'r' for reload and 'j' for debugger.
|
|
243
245
|
*
|
|
244
246
|
* @param proc - Metro process
|
|
245
247
|
*/
|
|
246
248
|
export function attachBundlerToForeground(proc: ChildProcess): void {
|
|
249
|
+
// Pipe output
|
|
247
250
|
proc.stdout?.pipe(process.stdout);
|
|
248
251
|
proc.stderr?.pipe(process.stderr);
|
|
249
252
|
|
|
253
|
+
// Connect stdin for keyboard shortcuts (r, j, d, etc.)
|
|
254
|
+
if (proc.stdin && process.stdin.isTTY) {
|
|
255
|
+
// Set raw mode to capture single keypresses
|
|
256
|
+
process.stdin.setRawMode(true);
|
|
257
|
+
process.stdin.resume();
|
|
258
|
+
process.stdin.pipe(proc.stdin);
|
|
259
|
+
|
|
260
|
+
// Handle Ctrl+C to exit gracefully
|
|
261
|
+
process.stdin.on("data", (data) => {
|
|
262
|
+
// Check for Ctrl+C (0x03)
|
|
263
|
+
if (data[0] === 0x03) {
|
|
264
|
+
proc.kill("SIGTERM");
|
|
265
|
+
process.exit(0);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
250
270
|
// Handle process exit
|
|
251
271
|
proc.on("close", (code) => {
|
|
272
|
+
// Restore terminal settings
|
|
273
|
+
if (process.stdin.isTTY) {
|
|
274
|
+
process.stdin.setRawMode(false);
|
|
275
|
+
}
|
|
276
|
+
|
|
252
277
|
if (code !== 0 && code !== null) {
|
|
253
278
|
console.error(chalk.red(`Metro bundler exited with code ${code}`));
|
|
254
279
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -41,5 +41,7 @@ export {
|
|
|
41
41
|
formatKeyValue,
|
|
42
42
|
formatList,
|
|
43
43
|
} from "./logger";
|
|
44
|
+
export type { NavigationConfig } from "./metro-config";
|
|
45
|
+
export { getNavigationConfig, parseNavigationConfig } from "./metro-config";
|
|
44
46
|
export type { ReporterStep, StepStatus, TerminalReporterConfig } from "./terminal-reporter";
|
|
45
47
|
export { createReporter, TerminalReporter } from "./terminal-reporter";
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metro Config Parser
|
|
3
|
+
*
|
|
4
|
+
* Extracts navigation configuration from metro.config.js
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Navigation options extracted from metro.config.js
|
|
12
|
+
*/
|
|
13
|
+
export interface NavigationConfig {
|
|
14
|
+
routesDir: string;
|
|
15
|
+
generatedDir: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Default navigation configuration
|
|
20
|
+
*/
|
|
21
|
+
const DEFAULT_CONFIG: NavigationConfig = {
|
|
22
|
+
routesDir: "./src/routes",
|
|
23
|
+
generatedDir: "./.teardown",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Finds metro.config.js in the project
|
|
28
|
+
*/
|
|
29
|
+
function findMetroConfig(projectRoot: string): string | null {
|
|
30
|
+
const configNames = ["metro.config.js", "metro.config.cjs", "metro.config.mjs"];
|
|
31
|
+
|
|
32
|
+
for (const name of configNames) {
|
|
33
|
+
const configPath = join(projectRoot, name);
|
|
34
|
+
if (existsSync(configPath)) {
|
|
35
|
+
return configPath;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Parses metro.config.js to extract withTeardownNavigation options
|
|
44
|
+
*
|
|
45
|
+
* This uses simple regex parsing since we can't safely evaluate the JS file.
|
|
46
|
+
* It looks for patterns like:
|
|
47
|
+
* withTeardownNavigation(config, { routesDir: "./src/_routes", generatedDir: "./.teardown" })
|
|
48
|
+
*/
|
|
49
|
+
export function parseNavigationConfig(projectRoot: string): NavigationConfig | null {
|
|
50
|
+
const configPath = findMetroConfig(projectRoot);
|
|
51
|
+
if (!configPath) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const content = readFileSync(configPath, "utf-8");
|
|
57
|
+
|
|
58
|
+
// Check if withTeardownNavigation is used
|
|
59
|
+
if (!content.includes("withTeardownNavigation")) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Extract routesDir using regex
|
|
64
|
+
// Matches: routesDir: "./src/_routes" or routesDir: './src/_routes'
|
|
65
|
+
const routesDirMatch = content.match(/routesDir:\s*['"]([^'"]+)['"]/);
|
|
66
|
+
const routesDir = routesDirMatch ? routesDirMatch[1] : DEFAULT_CONFIG.routesDir;
|
|
67
|
+
|
|
68
|
+
// Extract generatedDir using regex
|
|
69
|
+
const generatedDirMatch = content.match(/generatedDir:\s*['"]([^'"]+)['"]/);
|
|
70
|
+
const generatedDir = generatedDirMatch ? generatedDirMatch[1] : DEFAULT_CONFIG.generatedDir;
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
routesDir,
|
|
74
|
+
generatedDir,
|
|
75
|
+
};
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Gets navigation config for a project, with fallbacks
|
|
83
|
+
*/
|
|
84
|
+
export function getNavigationConfig(projectRoot: string): NavigationConfig | null {
|
|
85
|
+
// Try to parse from metro.config.js first
|
|
86
|
+
const parsedConfig = parseNavigationConfig(projectRoot);
|
|
87
|
+
if (parsedConfig) {
|
|
88
|
+
return parsedConfig;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Fallback: check if default routes dir exists
|
|
92
|
+
const defaultRoutesPath = join(projectRoot, DEFAULT_CONFIG.routesDir);
|
|
93
|
+
if (existsSync(defaultRoutesPath)) {
|
|
94
|
+
return DEFAULT_CONFIG;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Try alternate common location
|
|
98
|
+
const altRoutesPath = join(projectRoot, "src/_routes");
|
|
99
|
+
if (existsSync(altRoutesPath)) {
|
|
100
|
+
return {
|
|
101
|
+
routesDir: "./src/_routes",
|
|
102
|
+
generatedDir: "./.teardown",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Auto-generated by @teardown/navigation
|
|
2
|
+
import type { LinkingOptions } from "@react-navigation/native";
|
|
3
|
+
import type { RouteParams } from "./routes.generated";
|
|
4
|
+
|
|
5
|
+
export const generatedLinkingConfig: LinkingOptions<RouteParams>["config"] = {
|
|
6
|
+
screens: {
|
|
7
|
+
"/home": "home",
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const defaultPrefixes: string[] = ["app://"];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"generatedAt": "2026-01-31T19:48:52.201Z",
|
|
3
|
+
"routeCount": 1,
|
|
4
|
+
"routes": [
|
|
5
|
+
{
|
|
6
|
+
"path": "",
|
|
7
|
+
"file": "_layout.tsx",
|
|
8
|
+
"params": [],
|
|
9
|
+
"layoutType": "stack"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"path": "/home",
|
|
13
|
+
"file": "home.tsx",
|
|
14
|
+
"params": [],
|
|
15
|
+
"layoutType": "none"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
|
|
4
|
+
// Auto-generated by @teardown/navigation
|
|
5
|
+
// Do not edit this file directly
|
|
6
|
+
// Generated at: 2026-01-31T19:48:52.202Z
|
|
7
|
+
|
|
8
|
+
import type { NavigatorNode } from "@teardown/navigation";
|
|
9
|
+
|
|
10
|
+
import layout from "../src/_routes/_layout";
|
|
11
|
+
import home from "../src/_routes/home";
|
|
12
|
+
|
|
13
|
+
export const routeTree: NavigatorNode =
|
|
14
|
+
{
|
|
15
|
+
type: "stack",
|
|
16
|
+
layout: layout,
|
|
17
|
+
screens: {
|
|
18
|
+
"home": {
|
|
19
|
+
screen: home,
|
|
20
|
+
path: "/home",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Flat screens export for backwards compatibility
|
|
26
|
+
export const screens = {
|
|
27
|
+
"/home": home,
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
export const layouts = {
|
|
31
|
+
"src__routes": layout,
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
export type Screens = typeof screens;
|
|
35
|
+
export type Layouts = typeof layouts;
|
|
36
|
+
export type RouteTreeType = typeof routeTree;
|
|
37
|
+
|
|
38
|
+
export const routePaths = [
|
|
39
|
+
"/home",
|
|
40
|
+
] as const;
|
|
41
|
+
|
|
42
|
+
export type RoutePath = (typeof routePaths)[number];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Auto-generated by @teardown/navigation
|
|
2
|
+
// Do not edit this file directly
|
|
3
|
+
// Generated at: 2026-01-31T19:48:52.201Z
|
|
4
|
+
|
|
5
|
+
export interface RouteParams {
|
|
6
|
+
"/home": undefined;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type RoutePath = keyof RouteParams;
|
|
10
|
+
|
|
11
|
+
export type ParamsFor<T extends RoutePath> = RouteParams[T];
|
|
12
|
+
|
|
13
|
+
export type RouteWithParams = {
|
|
14
|
+
[K in RoutePath]: RouteParams[K] extends undefined
|
|
15
|
+
? { path: K }
|
|
16
|
+
: { path: K; params: RouteParams[K] };
|
|
17
|
+
}[RoutePath];
|
|
18
|
+
|
|
19
|
+
export type NavigatorType = "stack" | "tabs" | "drawer";
|
|
@@ -5,13 +5,12 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { defineScreen } from "@teardown/navigation/primitives";
|
|
8
|
-
import type React from "react";
|
|
9
8
|
import { ScrollView, Text, View } from "react-native";
|
|
10
9
|
import { Button } from "@/components/ui/button";
|
|
11
10
|
import { Card } from "@/components/ui/card";
|
|
12
11
|
import { APP_NAME } from "@/core/constants";
|
|
13
12
|
|
|
14
|
-
function HomeScreen()
|
|
13
|
+
function HomeScreen() {
|
|
15
14
|
return (
|
|
16
15
|
<ScrollView className="flex-1 bg-background" contentContainerClassName="p-6 gap-6">
|
|
17
16
|
{/* Hero Section */}
|
|
@@ -20,12 +19,8 @@ function HomeScreen(): React.JSX.Element {
|
|
|
20
19
|
<Text className="text-3xl font-bold text-primary-foreground">T</Text>
|
|
21
20
|
</View>
|
|
22
21
|
<View className="items-center gap-2">
|
|
23
|
-
<Text className="text-2xl font-bold text-foreground text-center">
|
|
24
|
-
|
|
25
|
-
</Text>
|
|
26
|
-
<Text className="text-base text-muted-foreground text-center">
|
|
27
|
-
Your React Native app is ready to go
|
|
28
|
-
</Text>
|
|
22
|
+
<Text className="text-2xl font-bold text-foreground text-center">Welcome to {APP_NAME}</Text>
|
|
23
|
+
<Text className="text-base text-muted-foreground text-center">Your React Native app is ready to go</Text>
|
|
29
24
|
</View>
|
|
30
25
|
</View>
|
|
31
26
|
|
|
@@ -45,21 +40,9 @@ function HomeScreen(): React.JSX.Element {
|
|
|
45
40
|
{/* Features */}
|
|
46
41
|
<View className="gap-4">
|
|
47
42
|
<Text className="text-lg font-semibold text-foreground">Features</Text>
|
|
48
|
-
<FeatureCard
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
description="Routes are fully typed with TypeScript"
|
|
52
|
-
/>
|
|
53
|
-
<FeatureCard
|
|
54
|
-
icon="📁"
|
|
55
|
-
title="File-Based Routing"
|
|
56
|
-
description="Routes generated from file structure"
|
|
57
|
-
/>
|
|
58
|
-
<FeatureCard
|
|
59
|
-
icon="🎨"
|
|
60
|
-
title="HeroUI Components"
|
|
61
|
-
description="Beautiful pre-built UI components"
|
|
62
|
-
/>
|
|
43
|
+
<FeatureCard icon="🔒" title="Type-Safe Navigation" description="Routes are fully typed with TypeScript" />
|
|
44
|
+
<FeatureCard icon="📁" title="File-Based Routing" description="Routes generated from file structure" />
|
|
45
|
+
<FeatureCard icon="🎨" title="HeroUI Components" description="Beautiful pre-built UI components" />
|
|
63
46
|
</View>
|
|
64
47
|
|
|
65
48
|
{/* Getting Started */}
|
|
@@ -78,15 +61,7 @@ function HomeScreen(): React.JSX.Element {
|
|
|
78
61
|
);
|
|
79
62
|
}
|
|
80
63
|
|
|
81
|
-
function FeatureCard({
|
|
82
|
-
icon,
|
|
83
|
-
title,
|
|
84
|
-
description,
|
|
85
|
-
}: {
|
|
86
|
-
icon: string;
|
|
87
|
-
title: string;
|
|
88
|
-
description: string;
|
|
89
|
-
}): React.JSX.Element {
|
|
64
|
+
function FeatureCard({ icon, title, description }: { icon: string; title: string; description: string }) {
|
|
90
65
|
return (
|
|
91
66
|
<Card>
|
|
92
67
|
<Card.Body className="flex-row items-center gap-4">
|
|
@@ -10,13 +10,11 @@
|
|
|
10
10
|
|
|
11
11
|
import "../global.css";
|
|
12
12
|
|
|
13
|
-
import
|
|
14
|
-
import { StatusBar, View } from "react-native";
|
|
15
|
-
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
|
|
13
|
+
import { SafeAreaProvider } from "react-native-safe-area-context";
|
|
16
14
|
import { Router } from "@/navigation";
|
|
17
15
|
import { AppProviders } from "@/providers";
|
|
18
16
|
|
|
19
|
-
export function App()
|
|
17
|
+
export function App() {
|
|
20
18
|
return (
|
|
21
19
|
<SafeAreaProvider>
|
|
22
20
|
<AppProviders>
|
|
@@ -97,17 +97,13 @@ const Text = (props: TextProps) => {
|
|
|
97
97
|
invert: invert,
|
|
98
98
|
}),
|
|
99
99
|
textClassName,
|
|
100
|
-
className
|
|
100
|
+
className
|
|
101
101
|
);
|
|
102
102
|
}, [variant, color, invert, textClassName, className]);
|
|
103
103
|
|
|
104
104
|
return (
|
|
105
105
|
<TextClassContext.Provider value={classNames}>
|
|
106
|
-
<Animated.Text
|
|
107
|
-
{...otherProps}
|
|
108
|
-
className={classNames}
|
|
109
|
-
allowFontScaling={false}
|
|
110
|
-
/>
|
|
106
|
+
<Animated.Text {...otherProps} className={classNames} allowFontScaling={false} />
|
|
111
107
|
</TextClassContext.Provider>
|
|
112
108
|
);
|
|
113
109
|
};
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { HeroUINativeProvider } from "heroui-native";
|
|
9
|
-
import type
|
|
9
|
+
import type { ReactNode } from "react";
|
|
10
10
|
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
|
11
11
|
|
|
12
12
|
interface AppProvidersProps {
|
|
13
|
-
children:
|
|
13
|
+
children: ReactNode;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export function AppProviders({ children }: AppProvidersProps)
|
|
16
|
+
export function AppProviders({ children }: AppProvidersProps) {
|
|
17
17
|
return (
|
|
18
18
|
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
19
19
|
<HeroUINativeProvider>{children}</HeroUINativeProvider>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Home Screen
|
|
3
|
+
*
|
|
4
|
+
* The main landing screen of the app.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { defineScreen } from "@teardown/navigation/primitives";
|
|
8
|
+
import type React from "react";
|
|
9
|
+
import { ScrollView, Text, View } from "react-native";
|
|
10
|
+
import { Button } from "@/components/ui/button";
|
|
11
|
+
import { Card } from "@/components/ui/card";
|
|
12
|
+
import { APP_NAME } from "@/core/constants";
|
|
13
|
+
|
|
14
|
+
function HomeScreen(): React.JSX.Element {
|
|
15
|
+
return (
|
|
16
|
+
<ScrollView className="flex-1 bg-background" contentContainerClassName="p-6 gap-6">
|
|
17
|
+
{/* Hero Section */}
|
|
18
|
+
<View className="items-center gap-4 py-8">
|
|
19
|
+
<View className="h-20 w-20 items-center justify-center rounded-2xl bg-primary">
|
|
20
|
+
<Text className="text-3xl font-bold text-primary-foreground">T</Text>
|
|
21
|
+
</View>
|
|
22
|
+
<View className="items-center gap-2">
|
|
23
|
+
<Text className="text-2xl font-bold text-foreground text-center">Welcome to {APP_NAME}</Text>
|
|
24
|
+
<Text className="text-base text-muted-foreground text-center">Your React Native app is ready to go</Text>
|
|
25
|
+
</View>
|
|
26
|
+
</View>
|
|
27
|
+
|
|
28
|
+
{/* Quick Actions */}
|
|
29
|
+
<Card>
|
|
30
|
+
<Card.Body className="gap-3">
|
|
31
|
+
<Card.Title>Quick Actions</Card.Title>
|
|
32
|
+
<Button onPress={() => console.log("Primary action")} fullWidth>
|
|
33
|
+
Get Started
|
|
34
|
+
</Button>
|
|
35
|
+
<Button variant="bordered" onPress={() => console.log("Secondary action")} fullWidth>
|
|
36
|
+
Learn More
|
|
37
|
+
</Button>
|
|
38
|
+
</Card.Body>
|
|
39
|
+
</Card>
|
|
40
|
+
|
|
41
|
+
{/* Features */}
|
|
42
|
+
<View className="gap-4">
|
|
43
|
+
<Text className="text-lg font-semibold text-foreground">Features</Text>
|
|
44
|
+
<FeatureCard icon="🔒" title="Type-Safe Navigation" description="Routes are fully typed with TypeScript" />
|
|
45
|
+
<FeatureCard icon="📁" title="File-Based Routing" description="Routes generated from file structure" />
|
|
46
|
+
<FeatureCard icon="🎨" title="HeroUI Components" description="Beautiful pre-built UI components" />
|
|
47
|
+
</View>
|
|
48
|
+
|
|
49
|
+
{/* Getting Started */}
|
|
50
|
+
<Card className="bg-primary/5 border-primary/20">
|
|
51
|
+
<Card.Body className="py-4">
|
|
52
|
+
<Card.Title>Getting Started</Card.Title>
|
|
53
|
+
<Card.Description>
|
|
54
|
+
1. Explore the components in src/components/ui/{"\n"}
|
|
55
|
+
2. Add new routes in src/routes/{"\n"}
|
|
56
|
+
3. Customize the theme in global.css{"\n"}
|
|
57
|
+
4. Build your app!
|
|
58
|
+
</Card.Description>
|
|
59
|
+
</Card.Body>
|
|
60
|
+
</Card>
|
|
61
|
+
</ScrollView>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function FeatureCard({
|
|
66
|
+
icon,
|
|
67
|
+
title,
|
|
68
|
+
description,
|
|
69
|
+
}: {
|
|
70
|
+
icon: string;
|
|
71
|
+
title: string;
|
|
72
|
+
description: string;
|
|
73
|
+
}): React.JSX.Element {
|
|
74
|
+
return (
|
|
75
|
+
<Card>
|
|
76
|
+
<Card.Body className="flex-row items-center gap-4">
|
|
77
|
+
<View className="h-12 w-12 items-center justify-center rounded-xl bg-muted">
|
|
78
|
+
<Text className="text-2xl">{icon}</Text>
|
|
79
|
+
</View>
|
|
80
|
+
<View className="flex-1">
|
|
81
|
+
<Card.Title>{title}</Card.Title>
|
|
82
|
+
<Card.Description>{description}</Card.Description>
|
|
83
|
+
</View>
|
|
84
|
+
</Card.Body>
|
|
85
|
+
</Card>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default defineScreen({
|
|
90
|
+
component: HomeScreen,
|
|
91
|
+
options: {
|
|
92
|
+
title: "Home",
|
|
93
|
+
},
|
|
94
|
+
});
|