@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.
@@ -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
+ }