@teardown/navigation 2.0.43
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 +79 -0
- package/src/components/index.ts +14 -0
- package/src/hooks/index.ts +15 -0
- package/src/hooks/use-typed-navigation.ts +195 -0
- package/src/hooks/use-typed-params.ts +46 -0
- package/src/hooks/use-typed-route.ts +83 -0
- package/src/index.ts +70 -0
- package/src/primitives/create-param-schema.ts +243 -0
- package/src/primitives/define-layout.ts +191 -0
- package/src/primitives/define-screen.ts +123 -0
- package/src/primitives/index.ts +37 -0
- package/src/types/index.ts +28 -0
- package/src/types/route-types.ts +75 -0
- package/src/types/type-utils.test.ts +175 -0
- package/src/types/type-utils.ts +91 -0
- package/src/utils/build-url.test.ts +133 -0
- package/src/utils/build-url.ts +164 -0
- package/src/utils/index.ts +5 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Param schema utilities for @teardown/navigation
|
|
3
|
+
* Provides helpers for creating Zod schemas for route params
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Basic param schema interface (Zod-like)
|
|
8
|
+
* This allows the library to work without Zod as a hard dependency
|
|
9
|
+
*/
|
|
10
|
+
export interface ParamSchema<T = unknown> {
|
|
11
|
+
parse: (value: unknown) => T;
|
|
12
|
+
safeParse: (value: unknown) => { success: true; data: T } | { success: false; error: unknown };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Shape type for defining param schemas
|
|
17
|
+
*/
|
|
18
|
+
export type ParamSchemaShape = Record<string, ParamSchema>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Infer the output type from a param schema shape
|
|
22
|
+
*/
|
|
23
|
+
export type InferParamSchemaOutput<T extends ParamSchemaShape> = {
|
|
24
|
+
[K in keyof T]: T[K] extends ParamSchema<infer U> ? U : unknown;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Creates a param schema object from a shape definition
|
|
29
|
+
* This is a lightweight wrapper that validates params at runtime
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* // With Zod (recommended)
|
|
34
|
+
* import { z } from 'zod';
|
|
35
|
+
* import { createParamSchema } from '@teardown/navigation/primitives';
|
|
36
|
+
*
|
|
37
|
+
* export const paramsSchema = createParamSchema({
|
|
38
|
+
* userId: z.string().uuid(),
|
|
39
|
+
* tab: z.enum(['posts', 'comments', 'likes']),
|
|
40
|
+
* });
|
|
41
|
+
*
|
|
42
|
+
* // Without Zod (basic validation)
|
|
43
|
+
* import { createParamSchema, paramValidators } from '@teardown/navigation/primitives';
|
|
44
|
+
*
|
|
45
|
+
* export const paramsSchema = createParamSchema({
|
|
46
|
+
* userId: paramValidators.uuid(),
|
|
47
|
+
* page: paramValidators.numericId(),
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export function createParamSchema<T extends ParamSchemaShape>(
|
|
52
|
+
shape: T
|
|
53
|
+
): ParamSchema<InferParamSchemaOutput<T>> & { shape: T } {
|
|
54
|
+
return {
|
|
55
|
+
shape,
|
|
56
|
+
parse(value: unknown) {
|
|
57
|
+
if (typeof value !== "object" || value === null) {
|
|
58
|
+
throw new Error("Expected object for params");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const input = value as Record<string, unknown>;
|
|
62
|
+
const result: Record<string, unknown> = {};
|
|
63
|
+
|
|
64
|
+
for (const [key, schema] of Object.entries(shape)) {
|
|
65
|
+
result[key] = schema.parse(input[key]);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return result as InferParamSchemaOutput<T>;
|
|
69
|
+
},
|
|
70
|
+
safeParse(value: unknown) {
|
|
71
|
+
try {
|
|
72
|
+
const data = this.parse(value);
|
|
73
|
+
return { success: true, data };
|
|
74
|
+
} catch (error) {
|
|
75
|
+
return { success: false, error };
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Common param validators for use without Zod
|
|
83
|
+
* These provide basic validation for common param patterns
|
|
84
|
+
*/
|
|
85
|
+
export const paramValidators = {
|
|
86
|
+
/**
|
|
87
|
+
* String validator
|
|
88
|
+
*/
|
|
89
|
+
string(): ParamSchema<string> {
|
|
90
|
+
return {
|
|
91
|
+
parse(value: unknown) {
|
|
92
|
+
if (typeof value !== "string") {
|
|
93
|
+
throw new Error(`Expected string, got ${typeof value}`);
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
},
|
|
97
|
+
safeParse(value: unknown) {
|
|
98
|
+
try {
|
|
99
|
+
const data = this.parse(value);
|
|
100
|
+
return { success: true, data };
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return { success: false, error };
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* UUID string validator
|
|
110
|
+
*/
|
|
111
|
+
uuid(): ParamSchema<string> {
|
|
112
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
113
|
+
return {
|
|
114
|
+
parse(value: unknown) {
|
|
115
|
+
if (typeof value !== "string" || !uuidRegex.test(value)) {
|
|
116
|
+
throw new Error("Expected valid UUID string");
|
|
117
|
+
}
|
|
118
|
+
return value;
|
|
119
|
+
},
|
|
120
|
+
safeParse(value: unknown) {
|
|
121
|
+
try {
|
|
122
|
+
const data = this.parse(value);
|
|
123
|
+
return { success: true, data };
|
|
124
|
+
} catch (error) {
|
|
125
|
+
return { success: false, error };
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Numeric string that parses to integer
|
|
133
|
+
*/
|
|
134
|
+
numericId(): ParamSchema<number> {
|
|
135
|
+
return {
|
|
136
|
+
parse(value: unknown) {
|
|
137
|
+
if (typeof value !== "string" || !/^\d+$/.test(value)) {
|
|
138
|
+
throw new Error("Expected numeric string");
|
|
139
|
+
}
|
|
140
|
+
return Number.parseInt(value, 10);
|
|
141
|
+
},
|
|
142
|
+
safeParse(value: unknown) {
|
|
143
|
+
try {
|
|
144
|
+
const data = this.parse(value);
|
|
145
|
+
return { success: true, data };
|
|
146
|
+
} catch (error) {
|
|
147
|
+
return { success: false, error };
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Slug-friendly string (lowercase letters, numbers, hyphens)
|
|
155
|
+
*/
|
|
156
|
+
slug(): ParamSchema<string> {
|
|
157
|
+
return {
|
|
158
|
+
parse(value: unknown) {
|
|
159
|
+
if (typeof value !== "string" || !/^[a-z0-9-]+$/.test(value)) {
|
|
160
|
+
throw new Error("Expected slug string (lowercase alphanumeric with hyphens)");
|
|
161
|
+
}
|
|
162
|
+
return value;
|
|
163
|
+
},
|
|
164
|
+
safeParse(value: unknown) {
|
|
165
|
+
try {
|
|
166
|
+
const data = this.parse(value);
|
|
167
|
+
return { success: true, data };
|
|
168
|
+
} catch (error) {
|
|
169
|
+
return { success: false, error };
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Optional string with default
|
|
177
|
+
*/
|
|
178
|
+
optionalWithDefault(defaultValue: string): ParamSchema<string> {
|
|
179
|
+
return {
|
|
180
|
+
parse(value: unknown) {
|
|
181
|
+
if (value === undefined || value === null || value === "") {
|
|
182
|
+
return defaultValue;
|
|
183
|
+
}
|
|
184
|
+
if (typeof value !== "string") {
|
|
185
|
+
throw new Error(`Expected string, got ${typeof value}`);
|
|
186
|
+
}
|
|
187
|
+
return value;
|
|
188
|
+
},
|
|
189
|
+
safeParse(value: unknown) {
|
|
190
|
+
try {
|
|
191
|
+
const data = this.parse(value);
|
|
192
|
+
return { success: true, data };
|
|
193
|
+
} catch (error) {
|
|
194
|
+
return { success: false, error };
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Enum-like string (one of specified values)
|
|
202
|
+
*/
|
|
203
|
+
oneOf<T extends string>(values: readonly T[]): ParamSchema<T> {
|
|
204
|
+
return {
|
|
205
|
+
parse(value: unknown) {
|
|
206
|
+
if (typeof value !== "string" || !values.includes(value as T)) {
|
|
207
|
+
throw new Error(`Expected one of: ${values.join(", ")}`);
|
|
208
|
+
}
|
|
209
|
+
return value as T;
|
|
210
|
+
},
|
|
211
|
+
safeParse(value: unknown) {
|
|
212
|
+
try {
|
|
213
|
+
const data = this.parse(value);
|
|
214
|
+
return { success: true, data };
|
|
215
|
+
} catch (error) {
|
|
216
|
+
return { success: false, error };
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Array of strings (for catch-all routes)
|
|
224
|
+
*/
|
|
225
|
+
stringArray(): ParamSchema<string[]> {
|
|
226
|
+
return {
|
|
227
|
+
parse(value: unknown) {
|
|
228
|
+
if (!Array.isArray(value) || !value.every((v) => typeof v === "string")) {
|
|
229
|
+
throw new Error("Expected array of strings");
|
|
230
|
+
}
|
|
231
|
+
return value as string[];
|
|
232
|
+
},
|
|
233
|
+
safeParse(value: unknown) {
|
|
234
|
+
try {
|
|
235
|
+
const data = this.parse(value);
|
|
236
|
+
return { success: true, data };
|
|
237
|
+
} catch (error) {
|
|
238
|
+
return { success: false, error };
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
},
|
|
243
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* defineLayout primitive for @teardown/navigation
|
|
3
|
+
* Defines navigator configuration for a route group
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ComponentType } from "react";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Navigator types supported by the library
|
|
10
|
+
*/
|
|
11
|
+
export type NavigatorType = "stack" | "tabs" | "drawer";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Common screen options for all navigator types
|
|
15
|
+
*/
|
|
16
|
+
export interface BaseScreenOptions {
|
|
17
|
+
headerShown?: boolean;
|
|
18
|
+
title?: string;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Stack navigator specific options
|
|
24
|
+
*/
|
|
25
|
+
export interface StackScreenOptions extends BaseScreenOptions {
|
|
26
|
+
presentation?:
|
|
27
|
+
| "card"
|
|
28
|
+
| "modal"
|
|
29
|
+
| "transparentModal"
|
|
30
|
+
| "containedModal"
|
|
31
|
+
| "containedTransparentModal"
|
|
32
|
+
| "fullScreenModal"
|
|
33
|
+
| "formSheet";
|
|
34
|
+
animation?:
|
|
35
|
+
| "default"
|
|
36
|
+
| "fade"
|
|
37
|
+
| "fade_from_bottom"
|
|
38
|
+
| "flip"
|
|
39
|
+
| "simple_push"
|
|
40
|
+
| "slide_from_bottom"
|
|
41
|
+
| "slide_from_right"
|
|
42
|
+
| "slide_from_left"
|
|
43
|
+
| "none";
|
|
44
|
+
gestureEnabled?: boolean;
|
|
45
|
+
gestureDirection?: "horizontal" | "vertical";
|
|
46
|
+
headerBackTitleVisible?: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Tab navigator specific options
|
|
51
|
+
*/
|
|
52
|
+
export interface TabScreenOptions extends BaseScreenOptions {
|
|
53
|
+
tabBarActiveTintColor?: string;
|
|
54
|
+
tabBarInactiveTintColor?: string;
|
|
55
|
+
tabBarStyle?: object;
|
|
56
|
+
tabBarLabelStyle?: object;
|
|
57
|
+
tabBarIconStyle?: object;
|
|
58
|
+
tabBarShowLabel?: boolean;
|
|
59
|
+
tabBarHideOnKeyboard?: boolean;
|
|
60
|
+
lazy?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Drawer navigator specific options
|
|
65
|
+
*/
|
|
66
|
+
export interface DrawerScreenOptions extends BaseScreenOptions {
|
|
67
|
+
drawerActiveTintColor?: string;
|
|
68
|
+
drawerInactiveTintColor?: string;
|
|
69
|
+
drawerStyle?: object;
|
|
70
|
+
drawerLabelStyle?: object;
|
|
71
|
+
drawerPosition?: "left" | "right";
|
|
72
|
+
drawerType?: "front" | "back" | "slide" | "permanent";
|
|
73
|
+
swipeEnabled?: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Per-screen options that can be specified in the layout
|
|
78
|
+
*/
|
|
79
|
+
export interface PerScreenOptions {
|
|
80
|
+
tabBarIcon?: (props: { focused: boolean; color: string; size: number }) => React.ReactNode;
|
|
81
|
+
tabBarLabel?: string;
|
|
82
|
+
tabBarBadge?: string | number;
|
|
83
|
+
drawerIcon?: (props: { focused: boolean; color: string; size: number }) => React.ReactNode;
|
|
84
|
+
drawerLabel?: string;
|
|
85
|
+
[key: string]: unknown;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Base layout configuration
|
|
90
|
+
*/
|
|
91
|
+
interface BaseLayoutConfig {
|
|
92
|
+
type: NavigatorType;
|
|
93
|
+
initialRouteName?: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Stack layout configuration
|
|
98
|
+
*/
|
|
99
|
+
export interface StackLayoutConfig extends BaseLayoutConfig {
|
|
100
|
+
type: "stack";
|
|
101
|
+
screenOptions?: StackScreenOptions;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Tab layout configuration
|
|
106
|
+
*/
|
|
107
|
+
export interface TabsLayoutConfig extends BaseLayoutConfig {
|
|
108
|
+
type: "tabs";
|
|
109
|
+
screenOptions?: TabScreenOptions;
|
|
110
|
+
tabBar?: ComponentType<{
|
|
111
|
+
state: unknown;
|
|
112
|
+
descriptors: unknown;
|
|
113
|
+
navigation: unknown;
|
|
114
|
+
}>;
|
|
115
|
+
screens?: Record<string, Partial<PerScreenOptions>>;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Drawer layout configuration
|
|
120
|
+
*/
|
|
121
|
+
export interface DrawerLayoutConfig extends BaseLayoutConfig {
|
|
122
|
+
type: "drawer";
|
|
123
|
+
screenOptions?: DrawerScreenOptions;
|
|
124
|
+
drawerContent?: ComponentType<{
|
|
125
|
+
state: unknown;
|
|
126
|
+
navigation: unknown;
|
|
127
|
+
descriptors: unknown;
|
|
128
|
+
}>;
|
|
129
|
+
screens?: Record<string, Partial<PerScreenOptions>>;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Union of all layout configurations
|
|
134
|
+
*/
|
|
135
|
+
export type LayoutConfig = StackLayoutConfig | TabsLayoutConfig | DrawerLayoutConfig;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Brand type for layout definitions
|
|
139
|
+
*/
|
|
140
|
+
export type LayoutDefinition<T extends LayoutConfig = LayoutConfig> = T & {
|
|
141
|
+
__brand: "TeardownLayout";
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Defines a layout with type-safe configuration
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```tsx
|
|
149
|
+
* // Stack layout
|
|
150
|
+
* import { defineLayout } from '@teardown/navigation/primitives';
|
|
151
|
+
*
|
|
152
|
+
* export default defineLayout({
|
|
153
|
+
* type: 'stack',
|
|
154
|
+
* screenOptions: {
|
|
155
|
+
* headerShown: true,
|
|
156
|
+
* headerBackTitleVisible: false,
|
|
157
|
+
* },
|
|
158
|
+
* });
|
|
159
|
+
*
|
|
160
|
+
* // Tabs layout
|
|
161
|
+
* export default defineLayout({
|
|
162
|
+
* type: 'tabs',
|
|
163
|
+
* screenOptions: {
|
|
164
|
+
* tabBarActiveTintColor: '#007AFF',
|
|
165
|
+
* },
|
|
166
|
+
* screens: {
|
|
167
|
+
* home: {
|
|
168
|
+
* tabBarIcon: ({ color }) => <HomeIcon color={color} />,
|
|
169
|
+
* tabBarLabel: 'Home',
|
|
170
|
+
* },
|
|
171
|
+
* settings: {
|
|
172
|
+
* tabBarIcon: ({ color }) => <SettingsIcon color={color} />,
|
|
173
|
+
* tabBarLabel: 'Settings',
|
|
174
|
+
* },
|
|
175
|
+
* },
|
|
176
|
+
* });
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
export function defineLayout<T extends LayoutConfig>(config: T): LayoutDefinition<T> {
|
|
180
|
+
return {
|
|
181
|
+
...config,
|
|
182
|
+
__brand: "TeardownLayout" as const,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Type guard to check if a value is a layout definition
|
|
188
|
+
*/
|
|
189
|
+
export function isLayoutDefinition(value: unknown): value is LayoutDefinition {
|
|
190
|
+
return typeof value === "object" && value !== null && (value as LayoutDefinition).__brand === "TeardownLayout";
|
|
191
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* defineScreen primitive for @teardown/navigation
|
|
3
|
+
* Defines a screen component with type-safe configuration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ComponentType } from "react";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Screen options that can be passed to React Navigation
|
|
10
|
+
*/
|
|
11
|
+
export interface ScreenOptions {
|
|
12
|
+
title?: string;
|
|
13
|
+
headerShown?: boolean;
|
|
14
|
+
headerTitle?: string | (() => React.ReactNode);
|
|
15
|
+
headerLeft?: () => React.ReactNode;
|
|
16
|
+
headerRight?: () => React.ReactNode;
|
|
17
|
+
headerStyle?: object;
|
|
18
|
+
headerTitleStyle?: object;
|
|
19
|
+
headerBackTitle?: string;
|
|
20
|
+
headerBackTitleVisible?: boolean;
|
|
21
|
+
presentation?:
|
|
22
|
+
| "card"
|
|
23
|
+
| "modal"
|
|
24
|
+
| "transparentModal"
|
|
25
|
+
| "containedModal"
|
|
26
|
+
| "containedTransparentModal"
|
|
27
|
+
| "fullScreenModal"
|
|
28
|
+
| "formSheet";
|
|
29
|
+
animation?:
|
|
30
|
+
| "default"
|
|
31
|
+
| "fade"
|
|
32
|
+
| "fade_from_bottom"
|
|
33
|
+
| "flip"
|
|
34
|
+
| "simple_push"
|
|
35
|
+
| "slide_from_bottom"
|
|
36
|
+
| "slide_from_right"
|
|
37
|
+
| "slide_from_left"
|
|
38
|
+
| "none";
|
|
39
|
+
gestureEnabled?: boolean;
|
|
40
|
+
gestureDirection?: "horizontal" | "vertical";
|
|
41
|
+
animationDuration?: number;
|
|
42
|
+
// Tab bar options
|
|
43
|
+
tabBarLabel?: string;
|
|
44
|
+
tabBarIcon?: (props: { focused: boolean; color: string; size: number }) => React.ReactNode;
|
|
45
|
+
tabBarBadge?: string | number;
|
|
46
|
+
tabBarButton?: (props: object) => React.ReactNode;
|
|
47
|
+
// Drawer options
|
|
48
|
+
drawerLabel?: string;
|
|
49
|
+
drawerIcon?: (props: { focused: boolean; color: string; size: number }) => React.ReactNode;
|
|
50
|
+
// Allow additional custom options
|
|
51
|
+
[key: string]: unknown;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Screen configuration
|
|
56
|
+
*/
|
|
57
|
+
export interface ScreenConfig<TParams = unknown> {
|
|
58
|
+
/**
|
|
59
|
+
* The React component to render for this screen
|
|
60
|
+
*/
|
|
61
|
+
component: ComponentType;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Navigation options for this screen
|
|
65
|
+
*/
|
|
66
|
+
options?: ScreenOptions | ((props: { route: { params?: TParams }; navigation: unknown }) => ScreenOptions);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Optional param schema for runtime validation
|
|
70
|
+
* Should be a Zod schema
|
|
71
|
+
*/
|
|
72
|
+
params?: TParams;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Listeners for navigation events
|
|
76
|
+
*/
|
|
77
|
+
listeners?: {
|
|
78
|
+
focus?: () => void;
|
|
79
|
+
blur?: () => void;
|
|
80
|
+
beforeRemove?: (e: { preventDefault: () => void }) => void;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Brand type for screen definitions
|
|
86
|
+
*/
|
|
87
|
+
export interface ScreenDefinition<TParams = unknown> extends ScreenConfig<TParams> {
|
|
88
|
+
__brand: "TeardownScreen";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Defines a screen with type-safe configuration
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```tsx
|
|
96
|
+
* import { defineScreen } from '@teardown/navigation/primitives';
|
|
97
|
+
*
|
|
98
|
+
* function UserProfileScreen() {
|
|
99
|
+
* return <View><Text>User Profile</Text></View>;
|
|
100
|
+
* }
|
|
101
|
+
*
|
|
102
|
+
* export default defineScreen({
|
|
103
|
+
* component: UserProfileScreen,
|
|
104
|
+
* options: {
|
|
105
|
+
* title: 'User Profile',
|
|
106
|
+
* headerShown: true,
|
|
107
|
+
* },
|
|
108
|
+
* });
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export function defineScreen<TParams = unknown>(config: ScreenConfig<TParams>): ScreenDefinition<TParams> {
|
|
112
|
+
return {
|
|
113
|
+
...config,
|
|
114
|
+
__brand: "TeardownScreen" as const,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Type guard to check if a value is a screen definition
|
|
120
|
+
*/
|
|
121
|
+
export function isScreenDefinition(value: unknown): value is ScreenDefinition {
|
|
122
|
+
return typeof value === "object" && value !== null && (value as ScreenDefinition).__brand === "TeardownScreen";
|
|
123
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Primitives for @teardown/navigation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Param schema utilities
|
|
6
|
+
export {
|
|
7
|
+
createParamSchema,
|
|
8
|
+
type InferParamSchemaOutput,
|
|
9
|
+
type ParamSchema,
|
|
10
|
+
type ParamSchemaShape,
|
|
11
|
+
paramValidators,
|
|
12
|
+
} from "./create-param-schema";
|
|
13
|
+
|
|
14
|
+
// Layout definitions
|
|
15
|
+
export {
|
|
16
|
+
type BaseScreenOptions,
|
|
17
|
+
type DrawerLayoutConfig,
|
|
18
|
+
type DrawerScreenOptions,
|
|
19
|
+
defineLayout,
|
|
20
|
+
isLayoutDefinition,
|
|
21
|
+
type LayoutConfig,
|
|
22
|
+
type LayoutDefinition,
|
|
23
|
+
type NavigatorType,
|
|
24
|
+
type PerScreenOptions,
|
|
25
|
+
type StackLayoutConfig,
|
|
26
|
+
type StackScreenOptions,
|
|
27
|
+
type TabScreenOptions,
|
|
28
|
+
type TabsLayoutConfig,
|
|
29
|
+
} from "./define-layout";
|
|
30
|
+
// Screen definitions
|
|
31
|
+
export {
|
|
32
|
+
defineScreen,
|
|
33
|
+
isScreenDefinition,
|
|
34
|
+
type ScreenConfig,
|
|
35
|
+
type ScreenDefinition,
|
|
36
|
+
type ScreenOptions,
|
|
37
|
+
} from "./define-screen";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type utilities and definitions for @teardown/navigation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Route type definitions
|
|
6
|
+
export type {
|
|
7
|
+
NavigationState,
|
|
8
|
+
NavigatorType,
|
|
9
|
+
ParamsFor,
|
|
10
|
+
Register,
|
|
11
|
+
RegisteredRouteParams,
|
|
12
|
+
RegisteredRoutePath,
|
|
13
|
+
RouteConfig,
|
|
14
|
+
RouteWithParams,
|
|
15
|
+
} from "./route-types";
|
|
16
|
+
// Type utilities for parameter extraction
|
|
17
|
+
export type {
|
|
18
|
+
ExtractAllParams,
|
|
19
|
+
ExtractParam,
|
|
20
|
+
ExtractParamName,
|
|
21
|
+
HasRequiredParams,
|
|
22
|
+
InferParams,
|
|
23
|
+
IsCatchAllSegment,
|
|
24
|
+
IsDynamicSegment,
|
|
25
|
+
IsOptionalSegment,
|
|
26
|
+
NavigateArgs,
|
|
27
|
+
Simplify,
|
|
28
|
+
} from "./type-utils";
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route type definitions for @teardown/navigation
|
|
3
|
+
* These types are augmented by the generated types from @teardown/navigation-metro
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Register interface - augmented by generated types
|
|
8
|
+
* This allows the hooks to infer types without explicit generics
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* // In .teardown/register.d.ts (auto-generated)
|
|
12
|
+
* declare module '@teardown/navigation' {
|
|
13
|
+
* interface Register {
|
|
14
|
+
* routeParams: RouteParams;
|
|
15
|
+
* routePath: RoutePath;
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
*/
|
|
19
|
+
export interface Register {
|
|
20
|
+
// These will be augmented by the generated .teardown/register.d.ts
|
|
21
|
+
routeParams: Record<string, unknown>;
|
|
22
|
+
routePath: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get the RouteParams from the Register interface
|
|
27
|
+
*/
|
|
28
|
+
export type RegisteredRouteParams = Register["routeParams"];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get the RoutePath from the Register interface
|
|
32
|
+
*/
|
|
33
|
+
export type RegisteredRoutePath = Register["routePath"];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get params for a specific route path
|
|
37
|
+
*/
|
|
38
|
+
export type ParamsFor<T extends RegisteredRoutePath> = T extends keyof RegisteredRouteParams
|
|
39
|
+
? RegisteredRouteParams[T]
|
|
40
|
+
: undefined;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Union type representing all routes with their params
|
|
44
|
+
* Useful for creating type-safe route objects
|
|
45
|
+
*/
|
|
46
|
+
export type RouteWithParams = {
|
|
47
|
+
[K in RegisteredRoutePath]: ParamsFor<K> extends undefined ? { path: K } : { path: K; params: ParamsFor<K> };
|
|
48
|
+
}[RegisteredRoutePath];
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Navigator types supported by the library
|
|
52
|
+
*/
|
|
53
|
+
export type NavigatorType = "stack" | "tabs" | "drawer";
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Configuration for a single route
|
|
57
|
+
*/
|
|
58
|
+
export interface RouteConfig {
|
|
59
|
+
path: RegisteredRoutePath;
|
|
60
|
+
component: React.ComponentType;
|
|
61
|
+
navigator: NavigatorType;
|
|
62
|
+
parent?: RegisteredRoutePath;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Navigation state type
|
|
67
|
+
*/
|
|
68
|
+
export interface NavigationState<T extends RegisteredRoutePath = RegisteredRoutePath> {
|
|
69
|
+
index: number;
|
|
70
|
+
routes: Array<{
|
|
71
|
+
name: T;
|
|
72
|
+
key: string;
|
|
73
|
+
params?: ParamsFor<T>;
|
|
74
|
+
}>;
|
|
75
|
+
}
|