@idealyst/mcp-server 1.2.24 → 1.2.26
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/dist/index.cjs +22366 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.js +22186 -1034
- package/dist/index.js.map +1 -1
- package/package.json +17 -7
- package/dist/data/cli-commands.d.ts +0 -2
- package/dist/data/cli-commands.d.ts.map +0 -1
- package/dist/data/cli-commands.js +0 -100
- package/dist/data/cli-commands.js.map +0 -1
- package/dist/data/components/Accordion.d.ts +0 -15
- package/dist/data/components/Accordion.d.ts.map +0 -1
- package/dist/data/components/Accordion.js +0 -113
- package/dist/data/components/Accordion.js.map +0 -1
- package/dist/data/components/ActivityIndicator.d.ts +0 -15
- package/dist/data/components/ActivityIndicator.d.ts.map +0 -1
- package/dist/data/components/ActivityIndicator.js +0 -80
- package/dist/data/components/ActivityIndicator.js.map +0 -1
- package/dist/data/components/Alert.d.ts +0 -15
- package/dist/data/components/Alert.d.ts.map +0 -1
- package/dist/data/components/Alert.js +0 -130
- package/dist/data/components/Alert.js.map +0 -1
- package/dist/data/components/Avatar.d.ts +0 -15
- package/dist/data/components/Avatar.d.ts.map +0 -1
- package/dist/data/components/Avatar.js +0 -91
- package/dist/data/components/Avatar.js.map +0 -1
- package/dist/data/components/Badge.d.ts +0 -15
- package/dist/data/components/Badge.d.ts.map +0 -1
- package/dist/data/components/Badge.js +0 -64
- package/dist/data/components/Badge.js.map +0 -1
- package/dist/data/components/Breadcrumb.d.ts +0 -15
- package/dist/data/components/Breadcrumb.d.ts.map +0 -1
- package/dist/data/components/Breadcrumb.js +0 -92
- package/dist/data/components/Breadcrumb.js.map +0 -1
- package/dist/data/components/Button.d.ts +0 -16
- package/dist/data/components/Button.d.ts.map +0 -1
- package/dist/data/components/Button.js +0 -118
- package/dist/data/components/Button.js.map +0 -1
- package/dist/data/components/Card.d.ts +0 -15
- package/dist/data/components/Card.d.ts.map +0 -1
- package/dist/data/components/Card.js +0 -75
- package/dist/data/components/Card.js.map +0 -1
- package/dist/data/components/Checkbox.d.ts +0 -15
- package/dist/data/components/Checkbox.d.ts.map +0 -1
- package/dist/data/components/Checkbox.js +0 -118
- package/dist/data/components/Checkbox.js.map +0 -1
- package/dist/data/components/Chip.d.ts +0 -15
- package/dist/data/components/Chip.d.ts.map +0 -1
- package/dist/data/components/Chip.js +0 -94
- package/dist/data/components/Chip.js.map +0 -1
- package/dist/data/components/Dialog.d.ts +0 -15
- package/dist/data/components/Dialog.d.ts.map +0 -1
- package/dist/data/components/Dialog.js +0 -137
- package/dist/data/components/Dialog.js.map +0 -1
- package/dist/data/components/Divider.d.ts +0 -15
- package/dist/data/components/Divider.d.ts.map +0 -1
- package/dist/data/components/Divider.js +0 -68
- package/dist/data/components/Divider.js.map +0 -1
- package/dist/data/components/Icon.d.ts +0 -15
- package/dist/data/components/Icon.d.ts.map +0 -1
- package/dist/data/components/Icon.js +0 -68
- package/dist/data/components/Icon.js.map +0 -1
- package/dist/data/components/Image.d.ts +0 -15
- package/dist/data/components/Image.d.ts.map +0 -1
- package/dist/data/components/Image.js +0 -119
- package/dist/data/components/Image.js.map +0 -1
- package/dist/data/components/Input.d.ts +0 -15
- package/dist/data/components/Input.d.ts.map +0 -1
- package/dist/data/components/Input.js +0 -155
- package/dist/data/components/Input.js.map +0 -1
- package/dist/data/components/Link.d.ts +0 -15
- package/dist/data/components/Link.d.ts.map +0 -1
- package/dist/data/components/Link.js +0 -142
- package/dist/data/components/Link.js.map +0 -1
- package/dist/data/components/List.d.ts +0 -15
- package/dist/data/components/List.d.ts.map +0 -1
- package/dist/data/components/List.js +0 -113
- package/dist/data/components/List.js.map +0 -1
- package/dist/data/components/Menu.d.ts +0 -15
- package/dist/data/components/Menu.d.ts.map +0 -1
- package/dist/data/components/Menu.js +0 -123
- package/dist/data/components/Menu.js.map +0 -1
- package/dist/data/components/Popover.d.ts +0 -15
- package/dist/data/components/Popover.d.ts.map +0 -1
- package/dist/data/components/Popover.js +0 -157
- package/dist/data/components/Popover.js.map +0 -1
- package/dist/data/components/Pressable.d.ts +0 -15
- package/dist/data/components/Pressable.d.ts.map +0 -1
- package/dist/data/components/Pressable.js +0 -125
- package/dist/data/components/Pressable.js.map +0 -1
- package/dist/data/components/Progress.d.ts +0 -15
- package/dist/data/components/Progress.d.ts.map +0 -1
- package/dist/data/components/Progress.js +0 -93
- package/dist/data/components/Progress.js.map +0 -1
- package/dist/data/components/RadioButton.d.ts +0 -15
- package/dist/data/components/RadioButton.d.ts.map +0 -1
- package/dist/data/components/RadioButton.js +0 -131
- package/dist/data/components/RadioButton.js.map +0 -1
- package/dist/data/components/SVGImage.d.ts +0 -15
- package/dist/data/components/SVGImage.d.ts.map +0 -1
- package/dist/data/components/SVGImage.js +0 -112
- package/dist/data/components/SVGImage.js.map +0 -1
- package/dist/data/components/Screen.d.ts +0 -15
- package/dist/data/components/Screen.d.ts.map +0 -1
- package/dist/data/components/Screen.js +0 -109
- package/dist/data/components/Screen.js.map +0 -1
- package/dist/data/components/Select.d.ts +0 -15
- package/dist/data/components/Select.d.ts.map +0 -1
- package/dist/data/components/Select.js +0 -141
- package/dist/data/components/Select.js.map +0 -1
- package/dist/data/components/Skeleton.d.ts +0 -15
- package/dist/data/components/Skeleton.d.ts.map +0 -1
- package/dist/data/components/Skeleton.js +0 -100
- package/dist/data/components/Skeleton.js.map +0 -1
- package/dist/data/components/Slider.d.ts +0 -15
- package/dist/data/components/Slider.d.ts.map +0 -1
- package/dist/data/components/Slider.js +0 -151
- package/dist/data/components/Slider.js.map +0 -1
- package/dist/data/components/Switch.d.ts +0 -15
- package/dist/data/components/Switch.d.ts.map +0 -1
- package/dist/data/components/Switch.js +0 -128
- package/dist/data/components/Switch.js.map +0 -1
- package/dist/data/components/TabBar.d.ts +0 -17
- package/dist/data/components/TabBar.d.ts.map +0 -1
- package/dist/data/components/TabBar.js +0 -244
- package/dist/data/components/TabBar.js.map +0 -1
- package/dist/data/components/Table.d.ts +0 -15
- package/dist/data/components/Table.d.ts.map +0 -1
- package/dist/data/components/Table.js +0 -159
- package/dist/data/components/Table.js.map +0 -1
- package/dist/data/components/Tabs.d.ts +0 -15
- package/dist/data/components/Tabs.d.ts.map +0 -1
- package/dist/data/components/Tabs.js +0 -150
- package/dist/data/components/Tabs.js.map +0 -1
- package/dist/data/components/Text.d.ts +0 -15
- package/dist/data/components/Text.d.ts.map +0 -1
- package/dist/data/components/Text.js +0 -97
- package/dist/data/components/Text.js.map +0 -1
- package/dist/data/components/TextArea.d.ts +0 -15
- package/dist/data/components/TextArea.d.ts.map +0 -1
- package/dist/data/components/TextArea.js +0 -156
- package/dist/data/components/TextArea.js.map +0 -1
- package/dist/data/components/Tooltip.d.ts +0 -15
- package/dist/data/components/Tooltip.d.ts.map +0 -1
- package/dist/data/components/Tooltip.js +0 -103
- package/dist/data/components/Tooltip.js.map +0 -1
- package/dist/data/components/Video.d.ts +0 -15
- package/dist/data/components/Video.d.ts.map +0 -1
- package/dist/data/components/Video.js +0 -166
- package/dist/data/components/Video.js.map +0 -1
- package/dist/data/components/View.d.ts +0 -15
- package/dist/data/components/View.d.ts.map +0 -1
- package/dist/data/components/View.js +0 -127
- package/dist/data/components/View.js.map +0 -1
- package/dist/data/components/index.d.ts +0 -38
- package/dist/data/components/index.d.ts.map +0 -1
- package/dist/data/components/index.js +0 -113
- package/dist/data/components/index.js.map +0 -1
- package/dist/data/framework-guides.d.ts +0 -2
- package/dist/data/framework-guides.d.ts.map +0 -1
- package/dist/data/framework-guides.js +0 -1730
- package/dist/data/framework-guides.js.map +0 -1
- package/dist/data/icon-guide.d.ts +0 -2
- package/dist/data/icon-guide.d.ts.map +0 -1
- package/dist/data/icon-guide.js +0 -285
- package/dist/data/icon-guide.js.map +0 -1
- package/dist/data/icons.json +0 -7452
- package/dist/data/navigation-guides.d.ts +0 -2
- package/dist/data/navigation-guides.d.ts.map +0 -1
- package/dist/data/navigation-guides.js +0 -2144
- package/dist/data/navigation-guides.js.map +0 -1
- package/dist/data/packages.d.ts +0 -39
- package/dist/data/packages.d.ts.map +0 -1
- package/dist/data/packages.js +0 -550
- package/dist/data/packages.js.map +0 -1
- package/dist/data/recipes.d.ts +0 -36
- package/dist/data/recipes.d.ts.map +0 -1
- package/dist/data/recipes.js +0 -2945
- package/dist/data/recipes.js.map +0 -1
- package/dist/data/storage-guides.d.ts +0 -2
- package/dist/data/storage-guides.d.ts.map +0 -1
- package/dist/data/storage-guides.js +0 -418
- package/dist/data/storage-guides.js.map +0 -1
- package/dist/data/translate-guides.d.ts +0 -2
- package/dist/data/translate-guides.d.ts.map +0 -1
- package/dist/data/translate-guides.js +0 -1030
- package/dist/data/translate-guides.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/tools/get-types.d.ts +0 -37
- package/dist/tools/get-types.d.ts.map +0 -1
- package/dist/tools/get-types.js +0 -148
- package/dist/tools/get-types.js.map +0 -1
|
@@ -1,2144 +0,0 @@
|
|
|
1
|
-
export const navigationGuides = {
|
|
2
|
-
"idealyst://navigation/overview": `# Navigation System Overview
|
|
3
|
-
|
|
4
|
-
The Idealyst navigation system provides a unified API for both React Native and web applications, handling routing seamlessly across platforms.
|
|
5
|
-
|
|
6
|
-
## Core Concepts
|
|
7
|
-
|
|
8
|
-
### Cross-Platform Routing
|
|
9
|
-
- **Mobile (React Native)**: Uses React Navigation for native navigation patterns
|
|
10
|
-
- **Web**: Uses React Router for browser-based routing
|
|
11
|
-
- **Unified API**: Same code works on both platforms
|
|
12
|
-
|
|
13
|
-
### Route Types
|
|
14
|
-
|
|
15
|
-
There are two fundamental route types:
|
|
16
|
-
|
|
17
|
-
#### 1. Screen Routes
|
|
18
|
-
Renders a component directly to the screen:
|
|
19
|
-
\`\`\`tsx
|
|
20
|
-
{
|
|
21
|
-
path: "profile",
|
|
22
|
-
type: 'screen',
|
|
23
|
-
component: ProfileScreen
|
|
24
|
-
}
|
|
25
|
-
\`\`\`
|
|
26
|
-
|
|
27
|
-
#### 2. Navigator Routes
|
|
28
|
-
Wraps child routes with navigation structure:
|
|
29
|
-
\`\`\`tsx
|
|
30
|
-
{
|
|
31
|
-
path: "/",
|
|
32
|
-
type: 'navigator',
|
|
33
|
-
layout: 'stack', // or 'tab', 'drawer', 'modal'
|
|
34
|
-
routes: [
|
|
35
|
-
{ path: "home", type: 'screen', component: HomeScreen },
|
|
36
|
-
{ path: "settings", type: 'screen', component: SettingsScreen },
|
|
37
|
-
]
|
|
38
|
-
}
|
|
39
|
-
\`\`\`
|
|
40
|
-
|
|
41
|
-
## Setup
|
|
42
|
-
|
|
43
|
-
### Basic Setup
|
|
44
|
-
|
|
45
|
-
\`\`\`tsx
|
|
46
|
-
import { NavigatorProvider } from '@idealyst/navigation';
|
|
47
|
-
|
|
48
|
-
function App() {
|
|
49
|
-
return (
|
|
50
|
-
<NavigatorProvider route={appRouter}>
|
|
51
|
-
{/* Content managed by router */}
|
|
52
|
-
</NavigatorProvider>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
\`\`\`
|
|
56
|
-
|
|
57
|
-
### Quick Start with Examples
|
|
58
|
-
|
|
59
|
-
Use the pre-built example router for instant working navigation:
|
|
60
|
-
|
|
61
|
-
\`\`\`tsx
|
|
62
|
-
import { ExampleNavigationRouter } from '@idealyst/navigation/examples';
|
|
63
|
-
|
|
64
|
-
<NavigatorProvider route={ExampleNavigationRouter} />
|
|
65
|
-
\`\`\`
|
|
66
|
-
|
|
67
|
-
The example router demonstrates:
|
|
68
|
-
- Stack-based navigation structure
|
|
69
|
-
- Custom web layouts with header and sidebar
|
|
70
|
-
- Tab navigation for nested sections
|
|
71
|
-
|
|
72
|
-
## Platform Differences
|
|
73
|
-
|
|
74
|
-
### Mobile (React Native)
|
|
75
|
-
- Stack navigator: Native stack navigation with animations
|
|
76
|
-
- Tab navigator: Bottom tab bar
|
|
77
|
-
- Drawer navigator: Side drawer with gestures
|
|
78
|
-
- Modal navigator: Full-screen modal presentation
|
|
79
|
-
|
|
80
|
-
### Web
|
|
81
|
-
- Stack navigator: Browser history-based routing
|
|
82
|
-
- Tab navigator: Top or side tab navigation
|
|
83
|
-
- All navigators: Use URL paths for routing
|
|
84
|
-
- Custom layouts: Can add headers, sidebars, footers
|
|
85
|
-
|
|
86
|
-
## Key Features
|
|
87
|
-
|
|
88
|
-
1. **Type-Safe Navigation**: Full TypeScript support
|
|
89
|
-
2. **Path Parameters**: \`/user/:id\` support
|
|
90
|
-
3. **Nested Routes**: Unlimited nesting depth
|
|
91
|
-
4. **Custom Layouts**: Web-specific layout components
|
|
92
|
-
5. **Theme Integration**: Works with @idealyst/theme
|
|
93
|
-
6. **Cross-Platform**: Write once, run everywhere
|
|
94
|
-
`,
|
|
95
|
-
"idealyst://navigation/route-configuration": `# Route Configuration
|
|
96
|
-
|
|
97
|
-
Learn how to define and structure routes in your Idealyst application.
|
|
98
|
-
|
|
99
|
-
## Route Definition
|
|
100
|
-
|
|
101
|
-
### Screen Route
|
|
102
|
-
|
|
103
|
-
The simplest route type - renders a component:
|
|
104
|
-
|
|
105
|
-
\`\`\`tsx
|
|
106
|
-
import { RouteParam } from '@idealyst/navigation';
|
|
107
|
-
|
|
108
|
-
const route: RouteParam = {
|
|
109
|
-
path: "profile",
|
|
110
|
-
type: 'screen',
|
|
111
|
-
component: ProfileScreen,
|
|
112
|
-
options: {
|
|
113
|
-
title: "User Profile",
|
|
114
|
-
headerShown: true
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
\`\`\`
|
|
118
|
-
|
|
119
|
-
### Navigator Route
|
|
120
|
-
|
|
121
|
-
A route that contains child routes:
|
|
122
|
-
|
|
123
|
-
\`\`\`tsx
|
|
124
|
-
const route: RouteParam = {
|
|
125
|
-
path: "/",
|
|
126
|
-
type: 'navigator',
|
|
127
|
-
layout: 'stack',
|
|
128
|
-
routes: [
|
|
129
|
-
{ path: "home", type: 'screen', component: HomeScreen },
|
|
130
|
-
{ path: "about", type: 'screen', component: AboutScreen },
|
|
131
|
-
],
|
|
132
|
-
options: {
|
|
133
|
-
headerShown: false
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
\`\`\`
|
|
137
|
-
|
|
138
|
-
## Path Configuration
|
|
139
|
-
|
|
140
|
-
### Basic Paths
|
|
141
|
-
|
|
142
|
-
\`\`\`tsx
|
|
143
|
-
{ path: "home", ... } // Relative: /parent/home
|
|
144
|
-
{ path: "/home", ... } // Absolute: /home
|
|
145
|
-
{ path: "", ... } // Index route
|
|
146
|
-
\`\`\`
|
|
147
|
-
|
|
148
|
-
### Path Parameters
|
|
149
|
-
|
|
150
|
-
\`\`\`tsx
|
|
151
|
-
{
|
|
152
|
-
path: "user/:id",
|
|
153
|
-
type: 'screen',
|
|
154
|
-
component: UserScreen
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Navigate with:
|
|
158
|
-
navigator.navigate({
|
|
159
|
-
path: "/user/:id",
|
|
160
|
-
vars: { id: "123" }
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
// Access in component:
|
|
164
|
-
import { useParams } from '@idealyst/navigation';
|
|
165
|
-
|
|
166
|
-
const params = useParams();
|
|
167
|
-
const userId = params.id;
|
|
168
|
-
\`\`\`
|
|
169
|
-
|
|
170
|
-
### Optional Parameters
|
|
171
|
-
|
|
172
|
-
\`\`\`tsx
|
|
173
|
-
{ path: "search/:query?" } // query is optional
|
|
174
|
-
\`\`\`
|
|
175
|
-
|
|
176
|
-
## Screen Options
|
|
177
|
-
|
|
178
|
-
### Common Options
|
|
179
|
-
|
|
180
|
-
\`\`\`tsx
|
|
181
|
-
type ScreenOptions = {
|
|
182
|
-
title?: string; // Screen title
|
|
183
|
-
headerShown?: boolean; // Show/hide header (mobile)
|
|
184
|
-
};
|
|
185
|
-
\`\`\`
|
|
186
|
-
|
|
187
|
-
### Tab-Specific Options
|
|
188
|
-
|
|
189
|
-
For tab navigators:
|
|
190
|
-
|
|
191
|
-
\`\`\`tsx
|
|
192
|
-
type TabBarScreenOptions = {
|
|
193
|
-
tabBarIcon?: (props: {
|
|
194
|
-
focused: boolean;
|
|
195
|
-
color: string;
|
|
196
|
-
size: string | number
|
|
197
|
-
}) => React.ReactElement;
|
|
198
|
-
|
|
199
|
-
tabBarLabel?: string; // Tab label
|
|
200
|
-
tabBarBadge?: string | number; // Badge count
|
|
201
|
-
tabBarVisible?: boolean; // Show/hide tab
|
|
202
|
-
} & ScreenOptions;
|
|
203
|
-
\`\`\`
|
|
204
|
-
|
|
205
|
-
Example:
|
|
206
|
-
\`\`\`tsx
|
|
207
|
-
{
|
|
208
|
-
path: "home",
|
|
209
|
-
type: 'screen',
|
|
210
|
-
component: HomeScreen,
|
|
211
|
-
options: {
|
|
212
|
-
tabBarLabel: "Home",
|
|
213
|
-
tabBarIcon: ({ focused, color }) => (
|
|
214
|
-
<Icon name="home" color={color} />
|
|
215
|
-
),
|
|
216
|
-
tabBarBadge: 5
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
\`\`\`
|
|
220
|
-
|
|
221
|
-
## Navigator Options
|
|
222
|
-
|
|
223
|
-
Options for navigator routes:
|
|
224
|
-
|
|
225
|
-
\`\`\`tsx
|
|
226
|
-
type NavigatorOptions = {
|
|
227
|
-
headerTitle?: React.ComponentType | React.ReactElement | string;
|
|
228
|
-
headerLeft?: React.ComponentType | React.ReactElement;
|
|
229
|
-
headerBackVisible?: boolean;
|
|
230
|
-
headerRight?: React.ComponentType | React.ReactElement;
|
|
231
|
-
headerShown?: boolean;
|
|
232
|
-
};
|
|
233
|
-
\`\`\`
|
|
234
|
-
|
|
235
|
-
Example:
|
|
236
|
-
\`\`\`tsx
|
|
237
|
-
{
|
|
238
|
-
path: "/app",
|
|
239
|
-
type: 'navigator',
|
|
240
|
-
layout: 'stack',
|
|
241
|
-
routes: [...],
|
|
242
|
-
options: {
|
|
243
|
-
headerTitle: "My App",
|
|
244
|
-
headerShown: true,
|
|
245
|
-
headerRight: <UserMenu />
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
\`\`\`
|
|
249
|
-
|
|
250
|
-
## Invalid Route Handling
|
|
251
|
-
|
|
252
|
-
Navigators can specify how to handle invalid routes:
|
|
253
|
-
|
|
254
|
-
\`\`\`tsx
|
|
255
|
-
import { NavigatorParam, NotFoundComponentProps } from '@idealyst/navigation';
|
|
256
|
-
|
|
257
|
-
const NotFoundPage = ({ path, params }: NotFoundComponentProps) => (
|
|
258
|
-
<View>
|
|
259
|
-
<Text>Page not found: {path}</Text>
|
|
260
|
-
</View>
|
|
261
|
-
);
|
|
262
|
-
|
|
263
|
-
const routes: NavigatorParam = {
|
|
264
|
-
path: "/",
|
|
265
|
-
type: 'navigator',
|
|
266
|
-
layout: 'stack',
|
|
267
|
-
// Component to show when route is invalid
|
|
268
|
-
notFoundComponent: NotFoundPage,
|
|
269
|
-
// Handler to redirect or show 404
|
|
270
|
-
onInvalidRoute: (path) => {
|
|
271
|
-
if (path.startsWith('/old-')) {
|
|
272
|
-
return { path: '/new-section', replace: true };
|
|
273
|
-
}
|
|
274
|
-
return undefined; // Show notFoundComponent
|
|
275
|
-
},
|
|
276
|
-
routes: [...]
|
|
277
|
-
};
|
|
278
|
-
\`\`\`
|
|
279
|
-
|
|
280
|
-
See the **Invalid Route Handling** guide for complete documentation.
|
|
281
|
-
|
|
282
|
-
## Nested Routes
|
|
283
|
-
|
|
284
|
-
Create hierarchical navigation:
|
|
285
|
-
|
|
286
|
-
\`\`\`tsx
|
|
287
|
-
const appRouter: RouteParam = {
|
|
288
|
-
path: "/",
|
|
289
|
-
type: 'navigator',
|
|
290
|
-
layout: 'stack',
|
|
291
|
-
routes: [
|
|
292
|
-
{
|
|
293
|
-
path: "dashboard",
|
|
294
|
-
type: 'navigator',
|
|
295
|
-
layout: 'tab',
|
|
296
|
-
routes: [
|
|
297
|
-
{ path: "overview", type: 'screen', component: OverviewScreen },
|
|
298
|
-
{ path: "analytics", type: 'screen', component: AnalyticsScreen },
|
|
299
|
-
{ path: "reports", type: 'screen', component: ReportsScreen },
|
|
300
|
-
]
|
|
301
|
-
},
|
|
302
|
-
{
|
|
303
|
-
path: "settings",
|
|
304
|
-
type: 'screen',
|
|
305
|
-
component: SettingsScreen
|
|
306
|
-
},
|
|
307
|
-
]
|
|
308
|
-
};
|
|
309
|
-
\`\`\`
|
|
310
|
-
|
|
311
|
-
## Full Path Resolution
|
|
312
|
-
|
|
313
|
-
Full paths are automatically computed:
|
|
314
|
-
- \`/dashboard/overview\`
|
|
315
|
-
- \`/dashboard/analytics\`
|
|
316
|
-
- \`/dashboard/reports\`
|
|
317
|
-
- \`/settings\`
|
|
318
|
-
|
|
319
|
-
## Best Practices
|
|
320
|
-
|
|
321
|
-
1. **Use relative paths** for child routes
|
|
322
|
-
2. **Keep nesting shallow** (3 levels max recommended)
|
|
323
|
-
3. **Group related screens** in navigators
|
|
324
|
-
4. **Use meaningful paths** that reflect content
|
|
325
|
-
5. **Define index routes** with empty path
|
|
326
|
-
6. **Set appropriate options** for each screen type
|
|
327
|
-
`,
|
|
328
|
-
"idealyst://navigation/navigator-types": `# Navigator Types
|
|
329
|
-
|
|
330
|
-
Idealyst supports four navigator types: stack, tab, drawer, and modal. Each provides different navigation patterns.
|
|
331
|
-
|
|
332
|
-
## Stack Navigator
|
|
333
|
-
|
|
334
|
-
Linear, hierarchical navigation - the most common pattern.
|
|
335
|
-
|
|
336
|
-
### Configuration
|
|
337
|
-
|
|
338
|
-
\`\`\`tsx
|
|
339
|
-
{
|
|
340
|
-
path: "/app",
|
|
341
|
-
type: 'navigator',
|
|
342
|
-
layout: 'stack',
|
|
343
|
-
routes: [
|
|
344
|
-
{ path: "", type: 'screen', component: HomeScreen },
|
|
345
|
-
{ path: "profile", type: 'screen', component: ProfileScreen },
|
|
346
|
-
{ path: "settings", type: 'screen', component: SettingsScreen },
|
|
347
|
-
]
|
|
348
|
-
}
|
|
349
|
-
\`\`\`
|
|
350
|
-
|
|
351
|
-
### Platform Behavior
|
|
352
|
-
|
|
353
|
-
**Mobile:**
|
|
354
|
-
- Native stack navigation with slide animations
|
|
355
|
-
- Hardware back button support
|
|
356
|
-
- Swipe-to-go-back gesture
|
|
357
|
-
|
|
358
|
-
**Web:**
|
|
359
|
-
- Browser history integration
|
|
360
|
-
- URL updates on navigation
|
|
361
|
-
- Back/forward browser buttons work
|
|
362
|
-
|
|
363
|
-
### Use Cases
|
|
364
|
-
- Main app navigation
|
|
365
|
-
- Detail views
|
|
366
|
-
- Settings flows
|
|
367
|
-
- Onboarding sequences
|
|
368
|
-
|
|
369
|
-
## Tab Navigator
|
|
370
|
-
|
|
371
|
-
Section-based navigation with a tab bar.
|
|
372
|
-
|
|
373
|
-
### Configuration
|
|
374
|
-
|
|
375
|
-
\`\`\`tsx
|
|
376
|
-
{
|
|
377
|
-
path: "/",
|
|
378
|
-
type: 'navigator',
|
|
379
|
-
layout: 'tab',
|
|
380
|
-
routes: [
|
|
381
|
-
{
|
|
382
|
-
path: "home",
|
|
383
|
-
type: 'screen',
|
|
384
|
-
component: HomeScreen,
|
|
385
|
-
options: {
|
|
386
|
-
tabBarLabel: "Home",
|
|
387
|
-
tabBarIcon: ({ color }) => <Icon name="home" color={color} />
|
|
388
|
-
}
|
|
389
|
-
},
|
|
390
|
-
{
|
|
391
|
-
path: "search",
|
|
392
|
-
type: 'screen',
|
|
393
|
-
component: SearchScreen,
|
|
394
|
-
options: {
|
|
395
|
-
tabBarLabel: "Search",
|
|
396
|
-
tabBarIcon: ({ color }) => <Icon name="search" color={color} />
|
|
397
|
-
}
|
|
398
|
-
},
|
|
399
|
-
]
|
|
400
|
-
}
|
|
401
|
-
\`\`\`
|
|
402
|
-
|
|
403
|
-
### Platform Behavior
|
|
404
|
-
|
|
405
|
-
**Mobile:**
|
|
406
|
-
- Bottom tab bar
|
|
407
|
-
- Tab icons and labels
|
|
408
|
-
- Badge support
|
|
409
|
-
- Active tab highlighting
|
|
410
|
-
|
|
411
|
-
**Web:**
|
|
412
|
-
- Top or side tabs
|
|
413
|
-
- Can use custom layout component
|
|
414
|
-
- URL-based tab switching
|
|
415
|
-
|
|
416
|
-
### Use Cases
|
|
417
|
-
- Main app sections
|
|
418
|
-
- Content categories
|
|
419
|
-
- Dashboard views
|
|
420
|
-
- Multi-section interfaces
|
|
421
|
-
|
|
422
|
-
## Drawer Navigator
|
|
423
|
-
|
|
424
|
-
Side menu navigation, primarily for desktop/tablet.
|
|
425
|
-
|
|
426
|
-
### Configuration
|
|
427
|
-
|
|
428
|
-
\`\`\`tsx
|
|
429
|
-
{
|
|
430
|
-
path: "/",
|
|
431
|
-
type: 'navigator',
|
|
432
|
-
layout: 'drawer',
|
|
433
|
-
routes: [
|
|
434
|
-
{ path: "dashboard", type: 'screen', component: DashboardScreen },
|
|
435
|
-
{ path: "users", type: 'screen', component: UsersScreen },
|
|
436
|
-
{ path: "settings", type: 'screen', component: SettingsScreen },
|
|
437
|
-
]
|
|
438
|
-
}
|
|
439
|
-
\`\`\`
|
|
440
|
-
|
|
441
|
-
### Platform Behavior
|
|
442
|
-
|
|
443
|
-
**Mobile:**
|
|
444
|
-
- Slide-out drawer
|
|
445
|
-
- Swipe gesture to open
|
|
446
|
-
- Overlay when open
|
|
447
|
-
|
|
448
|
-
**Web:**
|
|
449
|
-
- Sidebar navigation
|
|
450
|
-
- Can be persistent or overlay
|
|
451
|
-
- Responsive behavior
|
|
452
|
-
|
|
453
|
-
### Use Cases
|
|
454
|
-
- Admin panels
|
|
455
|
-
- Desktop applications
|
|
456
|
-
- Content management systems
|
|
457
|
-
- Multi-section apps
|
|
458
|
-
|
|
459
|
-
## Modal Navigator
|
|
460
|
-
|
|
461
|
-
Overlay navigation for temporary screens.
|
|
462
|
-
|
|
463
|
-
### Configuration
|
|
464
|
-
|
|
465
|
-
\`\`\`tsx
|
|
466
|
-
{
|
|
467
|
-
path: "/modals",
|
|
468
|
-
type: 'navigator',
|
|
469
|
-
layout: 'modal',
|
|
470
|
-
routes: [
|
|
471
|
-
{ path: "new-post", type: 'screen', component: NewPostScreen },
|
|
472
|
-
{ path: "edit-profile", type: 'screen', component: EditProfileScreen },
|
|
473
|
-
]
|
|
474
|
-
}
|
|
475
|
-
\`\`\`
|
|
476
|
-
|
|
477
|
-
### Platform Behavior
|
|
478
|
-
|
|
479
|
-
**Mobile:**
|
|
480
|
-
- Full-screen modal presentation
|
|
481
|
-
- Slide-up animation
|
|
482
|
-
- Close gesture support
|
|
483
|
-
|
|
484
|
-
**Web:**
|
|
485
|
-
- Overlay modal
|
|
486
|
-
- Background dimming
|
|
487
|
-
- Click-outside to close
|
|
488
|
-
|
|
489
|
-
### Use Cases
|
|
490
|
-
- Forms and data entry
|
|
491
|
-
- Action confirmations
|
|
492
|
-
- Image viewers
|
|
493
|
-
- Temporary content
|
|
494
|
-
|
|
495
|
-
## Combining Navigator Types
|
|
496
|
-
|
|
497
|
-
Navigators can be nested for complex flows:
|
|
498
|
-
|
|
499
|
-
\`\`\`tsx
|
|
500
|
-
const appRouter: RouteParam = {
|
|
501
|
-
path: "/",
|
|
502
|
-
type: 'navigator',
|
|
503
|
-
layout: 'stack',
|
|
504
|
-
routes: [
|
|
505
|
-
{
|
|
506
|
-
path: "main",
|
|
507
|
-
type: 'navigator',
|
|
508
|
-
layout: 'tab',
|
|
509
|
-
routes: [
|
|
510
|
-
{ path: "feed", type: 'screen', component: FeedScreen },
|
|
511
|
-
{ path: "profile", type: 'screen', component: ProfileScreen },
|
|
512
|
-
]
|
|
513
|
-
},
|
|
514
|
-
{
|
|
515
|
-
path: "modals",
|
|
516
|
-
type: 'navigator',
|
|
517
|
-
layout: 'modal',
|
|
518
|
-
routes: [
|
|
519
|
-
{ path: "create", type: 'screen', component: CreateScreen },
|
|
520
|
-
]
|
|
521
|
-
}
|
|
522
|
-
]
|
|
523
|
-
};
|
|
524
|
-
\`\`\`
|
|
525
|
-
|
|
526
|
-
## Choosing the Right Navigator
|
|
527
|
-
|
|
528
|
-
| Navigator | Mobile | Desktop | Use Case |
|
|
529
|
-
|-----------|--------|---------|----------|
|
|
530
|
-
| **Stack** | ✅ Primary | ✅ Primary | Hierarchical navigation |
|
|
531
|
-
| **Tab** | ✅ Primary | ✅ Secondary | Section-based navigation |
|
|
532
|
-
| **Drawer** | ⚠️ Secondary | ✅ Primary | Menu-based navigation |
|
|
533
|
-
| **Modal** | ✅ Common | ✅ Common | Temporary overlays |
|
|
534
|
-
|
|
535
|
-
## Best Practices
|
|
536
|
-
|
|
537
|
-
1. **Use stack for most flows** - It's the most universal pattern
|
|
538
|
-
2. **Limit tab count** - 3-5 tabs maximum for mobile
|
|
539
|
-
3. **Reserve drawers for complex apps** - Best on desktop
|
|
540
|
-
4. **Use modals sparingly** - For focused, temporary tasks
|
|
541
|
-
5. **Consider platform** - What works on mobile may not work on web
|
|
542
|
-
6. **Test navigation flow** - Ensure intuitive user experience
|
|
543
|
-
`,
|
|
544
|
-
"idealyst://navigation/custom-layouts": `# Custom Layouts (Web Only)
|
|
545
|
-
|
|
546
|
-
On web, navigators can use custom layout components to add headers, sidebars, and other UI around route content.
|
|
547
|
-
|
|
548
|
-
## GeneralLayout Component
|
|
549
|
-
|
|
550
|
-
The built-in \`GeneralLayout\` provides header and sidebar functionality:
|
|
551
|
-
|
|
552
|
-
### Basic Usage
|
|
553
|
-
|
|
554
|
-
\`\`\`tsx
|
|
555
|
-
import { GeneralLayout } from '@idealyst/navigation';
|
|
556
|
-
|
|
557
|
-
<GeneralLayout
|
|
558
|
-
header={{
|
|
559
|
-
enabled: true,
|
|
560
|
-
content: <Text>My App</Text>,
|
|
561
|
-
}}
|
|
562
|
-
sidebar={{
|
|
563
|
-
enabled: true,
|
|
564
|
-
content: <NavigationMenu />,
|
|
565
|
-
}}
|
|
566
|
-
>
|
|
567
|
-
{children}
|
|
568
|
-
</GeneralLayout>
|
|
569
|
-
\`\`\`
|
|
570
|
-
|
|
571
|
-
### Header Configuration
|
|
572
|
-
|
|
573
|
-
\`\`\`tsx
|
|
574
|
-
type HeaderConfig = {
|
|
575
|
-
enabled: boolean; // Show/hide header
|
|
576
|
-
height?: number; // Header height (default: 64)
|
|
577
|
-
content?: React.ReactNode; // Header content
|
|
578
|
-
style?: ViewStyle; // Custom styles
|
|
579
|
-
};
|
|
580
|
-
\`\`\`
|
|
581
|
-
|
|
582
|
-
Example:
|
|
583
|
-
\`\`\`tsx
|
|
584
|
-
header={{
|
|
585
|
-
enabled: true,
|
|
586
|
-
height: 80,
|
|
587
|
-
content: (
|
|
588
|
-
<View style={{ flexDirection: 'row', justifyContent: 'space-between', padding: 16 }}>
|
|
589
|
-
<Text size="lg" weight="bold">Dashboard</Text>
|
|
590
|
-
<UserMenu />
|
|
591
|
-
</View>
|
|
592
|
-
)
|
|
593
|
-
}}
|
|
594
|
-
\`\`\`
|
|
595
|
-
|
|
596
|
-
### Sidebar Configuration
|
|
597
|
-
|
|
598
|
-
\`\`\`tsx
|
|
599
|
-
type SidebarConfig = {
|
|
600
|
-
enabled: boolean; // Show/hide sidebar
|
|
601
|
-
collapsible?: boolean; // Allow collapse/expand
|
|
602
|
-
position?: 'left' | 'right'; // Sidebar position
|
|
603
|
-
initiallyExpanded?: boolean; // Initial state
|
|
604
|
-
expandedWidth?: number; // Width when expanded (default: 240)
|
|
605
|
-
collapsedWidth?: number; // Width when collapsed (default: 64)
|
|
606
|
-
content?: React.ReactNode; // Sidebar content
|
|
607
|
-
style?: ViewStyle; // Custom styles
|
|
608
|
-
};
|
|
609
|
-
\`\`\`
|
|
610
|
-
|
|
611
|
-
Example:
|
|
612
|
-
\`\`\`tsx
|
|
613
|
-
sidebar={{
|
|
614
|
-
enabled: true,
|
|
615
|
-
collapsible: true,
|
|
616
|
-
position: 'left',
|
|
617
|
-
initiallyExpanded: true,
|
|
618
|
-
expandedWidth: 280,
|
|
619
|
-
collapsedWidth: 72,
|
|
620
|
-
content: <NavigationSidebar />
|
|
621
|
-
}}
|
|
622
|
-
\`\`\`
|
|
623
|
-
|
|
624
|
-
## Using Layouts with Navigators
|
|
625
|
-
|
|
626
|
-
### Stack Navigator with Layout
|
|
627
|
-
|
|
628
|
-
\`\`\`tsx
|
|
629
|
-
import { CustomStackLayout } from './layouts/CustomStackLayout';
|
|
630
|
-
|
|
631
|
-
const router: NavigatorParam = {
|
|
632
|
-
path: "/",
|
|
633
|
-
type: 'navigator',
|
|
634
|
-
layout: 'stack',
|
|
635
|
-
layoutComponent: CustomStackLayout, // Web only!
|
|
636
|
-
routes: [
|
|
637
|
-
{ path: "", type: 'screen', component: HomeScreen },
|
|
638
|
-
{ path: "about", type: 'screen', component: AboutScreen },
|
|
639
|
-
]
|
|
640
|
-
};
|
|
641
|
-
\`\`\`
|
|
642
|
-
|
|
643
|
-
### Tab Navigator with Layout
|
|
644
|
-
|
|
645
|
-
\`\`\`tsx
|
|
646
|
-
import { CustomTabLayout } from './layouts/CustomTabLayout';
|
|
647
|
-
|
|
648
|
-
const router: NavigatorParam = {
|
|
649
|
-
path: "/",
|
|
650
|
-
type: 'navigator',
|
|
651
|
-
layout: 'tab',
|
|
652
|
-
layoutComponent: CustomTabLayout, // Web only!
|
|
653
|
-
routes: [
|
|
654
|
-
{ path: "feed", type: 'screen', component: FeedScreen },
|
|
655
|
-
{ path: "profile", type: 'screen', component: ProfileScreen },
|
|
656
|
-
]
|
|
657
|
-
};
|
|
658
|
-
\`\`\`
|
|
659
|
-
|
|
660
|
-
## Creating Custom Layouts
|
|
661
|
-
|
|
662
|
-
### Stack Layout Component
|
|
663
|
-
|
|
664
|
-
\`\`\`tsx
|
|
665
|
-
import { GeneralLayout } from '@idealyst/navigation';
|
|
666
|
-
import type { StackLayoutProps } from '@idealyst/navigation';
|
|
667
|
-
|
|
668
|
-
export const CustomStackLayout: React.FC<StackLayoutProps> = ({
|
|
669
|
-
children,
|
|
670
|
-
options,
|
|
671
|
-
routes,
|
|
672
|
-
currentPath
|
|
673
|
-
}) => {
|
|
674
|
-
return (
|
|
675
|
-
<GeneralLayout
|
|
676
|
-
header={{
|
|
677
|
-
enabled: true,
|
|
678
|
-
content: (
|
|
679
|
-
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
|
|
680
|
-
<Text>{options?.headerTitle || 'My App'}</Text>
|
|
681
|
-
{options?.headerRight}
|
|
682
|
-
</View>
|
|
683
|
-
)
|
|
684
|
-
}}
|
|
685
|
-
sidebar={{
|
|
686
|
-
enabled: true,
|
|
687
|
-
collapsible: true,
|
|
688
|
-
content: (
|
|
689
|
-
<NavigationMenu routes={routes} currentPath={currentPath} />
|
|
690
|
-
)
|
|
691
|
-
}}
|
|
692
|
-
>
|
|
693
|
-
{children}
|
|
694
|
-
</GeneralLayout>
|
|
695
|
-
);
|
|
696
|
-
};
|
|
697
|
-
\`\`\`
|
|
698
|
-
|
|
699
|
-
### Tab Layout Component
|
|
700
|
-
|
|
701
|
-
\`\`\`tsx
|
|
702
|
-
import type { TabLayoutProps } from '@idealyst/navigation';
|
|
703
|
-
|
|
704
|
-
export const CustomTabLayout: React.FC<TabLayoutProps> = ({
|
|
705
|
-
children,
|
|
706
|
-
routes,
|
|
707
|
-
currentPath
|
|
708
|
-
}) => {
|
|
709
|
-
const navigator = useNavigator();
|
|
710
|
-
|
|
711
|
-
return (
|
|
712
|
-
<View style={{ flex: 1 }}>
|
|
713
|
-
{/* Custom tab bar */}
|
|
714
|
-
<View style={{ flexDirection: 'row', borderBottom: '1px solid #ccc' }}>
|
|
715
|
-
{routes.map(route => (
|
|
716
|
-
<Pressable
|
|
717
|
-
key={route.path}
|
|
718
|
-
onPress={() => navigator.navigate({ path: route.fullPath, vars: {} })}
|
|
719
|
-
style={{
|
|
720
|
-
padding: 16,
|
|
721
|
-
borderBottom: currentPath === route.fullPath ? '2px solid blue' : 'none'
|
|
722
|
-
}}
|
|
723
|
-
>
|
|
724
|
-
<Text>{route.options?.tabBarLabel || route.path}</Text>
|
|
725
|
-
</Pressable>
|
|
726
|
-
))}
|
|
727
|
-
</View>
|
|
728
|
-
|
|
729
|
-
{/* Content */}
|
|
730
|
-
<View style={{ flex: 1 }}>
|
|
731
|
-
{children}
|
|
732
|
-
</View>
|
|
733
|
-
</View>
|
|
734
|
-
);
|
|
735
|
-
};
|
|
736
|
-
\`\`\`
|
|
737
|
-
|
|
738
|
-
## Layout Props Reference
|
|
739
|
-
|
|
740
|
-
### StackLayoutProps
|
|
741
|
-
|
|
742
|
-
\`\`\`tsx
|
|
743
|
-
type StackLayoutProps = {
|
|
744
|
-
options?: NavigatorOptions; // Navigator options
|
|
745
|
-
routes: RouteWithFullPath[]; // All routes with full paths
|
|
746
|
-
currentPath: string; // Current active path
|
|
747
|
-
children?: React.ReactNode; // Route content
|
|
748
|
-
};
|
|
749
|
-
\`\`\`
|
|
750
|
-
|
|
751
|
-
### TabLayoutProps
|
|
752
|
-
|
|
753
|
-
\`\`\`tsx
|
|
754
|
-
type TabLayoutProps = {
|
|
755
|
-
options?: NavigatorOptions; // Navigator options
|
|
756
|
-
routes: RouteWithFullPath<TabBarScreenOptions>[]; // Tab routes
|
|
757
|
-
currentPath: string; // Current active path
|
|
758
|
-
children?: React.ReactNode; // Route content
|
|
759
|
-
};
|
|
760
|
-
\`\`\`
|
|
761
|
-
|
|
762
|
-
## Real-World Examples
|
|
763
|
-
|
|
764
|
-
### Dashboard Layout
|
|
765
|
-
|
|
766
|
-
\`\`\`tsx
|
|
767
|
-
export const DashboardLayout: React.FC<StackLayoutProps> = ({
|
|
768
|
-
children,
|
|
769
|
-
routes,
|
|
770
|
-
currentPath
|
|
771
|
-
}) => {
|
|
772
|
-
return (
|
|
773
|
-
<GeneralLayout
|
|
774
|
-
header={{
|
|
775
|
-
enabled: true,
|
|
776
|
-
height: 72,
|
|
777
|
-
content: (
|
|
778
|
-
<View style={{
|
|
779
|
-
flexDirection: 'row',
|
|
780
|
-
justifyContent: 'space-between',
|
|
781
|
-
alignItems: 'center',
|
|
782
|
-
padding: 16
|
|
783
|
-
}}>
|
|
784
|
-
<Text size="xl" weight="bold">Dashboard</Text>
|
|
785
|
-
<View style={{ flexDirection: 'row', gap: 16 }}>
|
|
786
|
-
<NotificationBell />
|
|
787
|
-
<UserAvatar />
|
|
788
|
-
</View>
|
|
789
|
-
</View>
|
|
790
|
-
)
|
|
791
|
-
}}
|
|
792
|
-
sidebar={{
|
|
793
|
-
enabled: true,
|
|
794
|
-
collapsible: true,
|
|
795
|
-
position: 'left',
|
|
796
|
-
expandedWidth: 260,
|
|
797
|
-
content: <DashboardSidebar routes={routes} currentPath={currentPath} />
|
|
798
|
-
}}
|
|
799
|
-
>
|
|
800
|
-
{children}
|
|
801
|
-
</GeneralLayout>
|
|
802
|
-
);
|
|
803
|
-
};
|
|
804
|
-
\`\`\`
|
|
805
|
-
|
|
806
|
-
### Admin Panel Layout
|
|
807
|
-
|
|
808
|
-
\`\`\`tsx
|
|
809
|
-
export const AdminLayout: React.FC<StackLayoutProps> = ({ children }) => {
|
|
810
|
-
return (
|
|
811
|
-
<GeneralLayout
|
|
812
|
-
header={{
|
|
813
|
-
enabled: true,
|
|
814
|
-
content: <AdminHeader />,
|
|
815
|
-
style: { backgroundColor: '#1a1a1a', color: '#fff' }
|
|
816
|
-
}}
|
|
817
|
-
sidebar={{
|
|
818
|
-
enabled: true,
|
|
819
|
-
collapsible: true,
|
|
820
|
-
position: 'left',
|
|
821
|
-
initiallyExpanded: true,
|
|
822
|
-
content: <AdminNavigationMenu />,
|
|
823
|
-
style: { backgroundColor: '#2a2a2a' }
|
|
824
|
-
}}
|
|
825
|
-
>
|
|
826
|
-
<View style={{ padding: 24 }}>
|
|
827
|
-
{children}
|
|
828
|
-
</View>
|
|
829
|
-
</GeneralLayout>
|
|
830
|
-
);
|
|
831
|
-
};
|
|
832
|
-
\`\`\`
|
|
833
|
-
|
|
834
|
-
## Best Practices
|
|
835
|
-
|
|
836
|
-
1. **Use GeneralLayout as base** - Don't reinvent header/sidebar logic
|
|
837
|
-
2. **Keep layouts simple** - Complex logic belongs in screens
|
|
838
|
-
3. **Make responsive** - Consider different screen sizes
|
|
839
|
-
4. **Theme-aware** - Use theme colors and spacing
|
|
840
|
-
5. **Accessible** - Ensure keyboard and screen reader support
|
|
841
|
-
6. **Performance** - Memoize layout components when possible
|
|
842
|
-
7. **Consistent** - Use same layout for similar sections
|
|
843
|
-
|
|
844
|
-
## Platform Considerations
|
|
845
|
-
|
|
846
|
-
- **Web only**: Layout components only apply to web
|
|
847
|
-
- **Mobile**: Uses native navigation components
|
|
848
|
-
- **Conditional rendering**: Check platform if needed
|
|
849
|
-
- **Testing**: Test both platforms separately
|
|
850
|
-
`,
|
|
851
|
-
"idealyst://navigation/use-navigator": `# useNavigator Hook
|
|
852
|
-
|
|
853
|
-
The \`useNavigator\` hook provides navigation functionality and route information within your components.
|
|
854
|
-
|
|
855
|
-
## Basic Usage
|
|
856
|
-
|
|
857
|
-
\`\`\`tsx
|
|
858
|
-
import { useNavigator } from '@idealyst/navigation';
|
|
859
|
-
|
|
860
|
-
function MyComponent() {
|
|
861
|
-
const navigator = useNavigator();
|
|
862
|
-
|
|
863
|
-
return (
|
|
864
|
-
<Button onPress={() => navigator.navigate({ path: '/profile', vars: {} })}>
|
|
865
|
-
Go to Profile
|
|
866
|
-
</Button>
|
|
867
|
-
);
|
|
868
|
-
}
|
|
869
|
-
\`\`\`
|
|
870
|
-
|
|
871
|
-
## API Reference
|
|
872
|
-
|
|
873
|
-
### navigator.navigate()
|
|
874
|
-
|
|
875
|
-
Navigate to a route:
|
|
876
|
-
|
|
877
|
-
\`\`\`tsx
|
|
878
|
-
navigator.navigate({
|
|
879
|
-
path: string;
|
|
880
|
-
vars?: Record<string, string>;
|
|
881
|
-
replace?: boolean; // Replace history entry instead of push
|
|
882
|
-
});
|
|
883
|
-
\`\`\`
|
|
884
|
-
|
|
885
|
-
Examples:
|
|
886
|
-
\`\`\`tsx
|
|
887
|
-
// Simple navigation
|
|
888
|
-
navigator.navigate({ path: '/home' });
|
|
889
|
-
|
|
890
|
-
// With path parameters
|
|
891
|
-
navigator.navigate({
|
|
892
|
-
path: '/user/:id',
|
|
893
|
-
vars: { id: '123' }
|
|
894
|
-
});
|
|
895
|
-
|
|
896
|
-
// With query parameters
|
|
897
|
-
navigator.navigate({
|
|
898
|
-
path: '/search',
|
|
899
|
-
vars: { q: 'react', category: 'tutorial' }
|
|
900
|
-
});
|
|
901
|
-
|
|
902
|
-
// Replace current history entry (no back navigation to current page)
|
|
903
|
-
navigator.navigate({
|
|
904
|
-
path: '/dashboard',
|
|
905
|
-
replace: true
|
|
906
|
-
});
|
|
907
|
-
\`\`\`
|
|
908
|
-
|
|
909
|
-
### Replace vs Push Navigation
|
|
910
|
-
|
|
911
|
-
By default, navigation pushes a new entry onto the history stack. Use \`replace: true\` when you want to replace the current entry instead:
|
|
912
|
-
|
|
913
|
-
\`\`\`tsx
|
|
914
|
-
// After login, replace login page in history
|
|
915
|
-
navigator.navigate({ path: '/dashboard', replace: true });
|
|
916
|
-
|
|
917
|
-
// Redirect without adding to history
|
|
918
|
-
navigator.navigate({ path: '/new-location', replace: true });
|
|
919
|
-
\`\`\`
|
|
920
|
-
|
|
921
|
-
**Use cases for replace:**
|
|
922
|
-
- Post-login redirects (user shouldn't go back to login)
|
|
923
|
-
- After form submission redirects
|
|
924
|
-
- URL canonicalization/normalization
|
|
925
|
-
- Redirect from deprecated routes
|
|
926
|
-
|
|
927
|
-
## useParams Hook
|
|
928
|
-
|
|
929
|
-
Access current route path parameters:
|
|
930
|
-
|
|
931
|
-
\`\`\`tsx
|
|
932
|
-
import { useParams } from '@idealyst/navigation';
|
|
933
|
-
|
|
934
|
-
function UserScreen() {
|
|
935
|
-
const params = useParams();
|
|
936
|
-
const userId = params.id; // Path param from /user/:id
|
|
937
|
-
|
|
938
|
-
return <Text>User ID: {userId}</Text>;
|
|
939
|
-
}
|
|
940
|
-
\`\`\`
|
|
941
|
-
|
|
942
|
-
## useNavigationState Hook
|
|
943
|
-
|
|
944
|
-
Access navigation state passed via the \`state\` property:
|
|
945
|
-
|
|
946
|
-
\`\`\`tsx
|
|
947
|
-
import { useNavigationState } from '@idealyst/navigation';
|
|
948
|
-
|
|
949
|
-
// When navigating:
|
|
950
|
-
navigator.navigate({
|
|
951
|
-
path: '/recording',
|
|
952
|
-
state: { autostart: true, source: 'home' }
|
|
953
|
-
});
|
|
954
|
-
|
|
955
|
-
// In destination screen:
|
|
956
|
-
function RecordingScreen() {
|
|
957
|
-
const { autostart, source } = useNavigationState<{
|
|
958
|
-
autostart?: boolean;
|
|
959
|
-
source?: string;
|
|
960
|
-
}>();
|
|
961
|
-
|
|
962
|
-
// autostart = true, source = 'home'
|
|
963
|
-
}
|
|
964
|
-
\`\`\`
|
|
965
|
-
|
|
966
|
-
### Consuming State (Web)
|
|
967
|
-
|
|
968
|
-
Remove state from URL after reading:
|
|
969
|
-
|
|
970
|
-
\`\`\`tsx
|
|
971
|
-
// URL: /recording?autostart=true
|
|
972
|
-
const { autostart } = useNavigationState<{ autostart?: boolean }>({
|
|
973
|
-
consume: ['autostart']
|
|
974
|
-
});
|
|
975
|
-
// autostart = true, URL becomes: /recording (param removed)
|
|
976
|
-
\`\`\`
|
|
977
|
-
|
|
978
|
-
## useLocation (React Router)
|
|
979
|
-
|
|
980
|
-
Get the current route path on web:
|
|
981
|
-
|
|
982
|
-
\`\`\`tsx
|
|
983
|
-
import { useLocation } from '@idealyst/navigation';
|
|
984
|
-
|
|
985
|
-
function MyComponent() {
|
|
986
|
-
const location = useLocation();
|
|
987
|
-
console.log(location.pathname); // "/user/123"
|
|
988
|
-
}
|
|
989
|
-
\`\`\`
|
|
990
|
-
|
|
991
|
-
### navigator.canGoBack()
|
|
992
|
-
|
|
993
|
-
Check if back navigation is available:
|
|
994
|
-
|
|
995
|
-
\`\`\`tsx
|
|
996
|
-
const navigator = useNavigator();
|
|
997
|
-
|
|
998
|
-
if (navigator.canGoBack()) {
|
|
999
|
-
// Show back button
|
|
1000
|
-
}
|
|
1001
|
-
\`\`\`
|
|
1002
|
-
|
|
1003
|
-
**Platform behavior:**
|
|
1004
|
-
- **Web**: Returns \`true\` if there's a valid parent route in the route hierarchy (e.g., \`/users/123\` can go back to \`/users\`)
|
|
1005
|
-
- **Native**: Uses React Navigation's \`canGoBack()\` to check navigation stack
|
|
1006
|
-
|
|
1007
|
-
### navigator.goBack()
|
|
1008
|
-
|
|
1009
|
-
Navigate back in the route hierarchy:
|
|
1010
|
-
|
|
1011
|
-
\`\`\`tsx
|
|
1012
|
-
<Button onPress={() => navigator.goBack()}>
|
|
1013
|
-
Go Back
|
|
1014
|
-
</Button>
|
|
1015
|
-
\`\`\`
|
|
1016
|
-
|
|
1017
|
-
**Platform behavior:**
|
|
1018
|
-
- **Web**: Navigates to the parent route (e.g., \`/users/123/edit\` → \`/users/123\` → \`/users\` → \`/\`). Does NOT use browser history - navigates up the route tree.
|
|
1019
|
-
- **Native**: Uses React Navigation's \`goBack()\` to pop the navigation stack
|
|
1020
|
-
|
|
1021
|
-
**Important**: On web, this is NOT browser history back. It navigates to the parent path in the route hierarchy. Use this for "up" navigation within your app structure.
|
|
1022
|
-
|
|
1023
|
-
## Path Parameters
|
|
1024
|
-
|
|
1025
|
-
### Defining Parameters
|
|
1026
|
-
|
|
1027
|
-
In route configuration:
|
|
1028
|
-
\`\`\`tsx
|
|
1029
|
-
{
|
|
1030
|
-
path: "user/:id",
|
|
1031
|
-
type: 'screen',
|
|
1032
|
-
component: UserScreen
|
|
1033
|
-
}
|
|
1034
|
-
\`\`\`
|
|
1035
|
-
|
|
1036
|
-
### Accessing Parameters
|
|
1037
|
-
|
|
1038
|
-
In the screen component:
|
|
1039
|
-
\`\`\`tsx
|
|
1040
|
-
import { useParams } from '@idealyst/navigation';
|
|
1041
|
-
|
|
1042
|
-
function UserScreen() {
|
|
1043
|
-
const params = useParams();
|
|
1044
|
-
const userId = params.id;
|
|
1045
|
-
|
|
1046
|
-
return <Text>User ID: {userId}</Text>;
|
|
1047
|
-
}
|
|
1048
|
-
\`\`\`
|
|
1049
|
-
|
|
1050
|
-
### Multiple Parameters
|
|
1051
|
-
|
|
1052
|
-
\`\`\`tsx
|
|
1053
|
-
import { useParams } from '@idealyst/navigation';
|
|
1054
|
-
|
|
1055
|
-
// Route: "post/:postId/comment/:commentId"
|
|
1056
|
-
|
|
1057
|
-
// Navigate:
|
|
1058
|
-
navigator.navigate({
|
|
1059
|
-
path: '/post/:postId/comment/:commentId',
|
|
1060
|
-
vars: { postId: '42', commentId: '7' }
|
|
1061
|
-
});
|
|
1062
|
-
|
|
1063
|
-
// Access:
|
|
1064
|
-
const params = useParams();
|
|
1065
|
-
const postId = params.postId;
|
|
1066
|
-
const commentId = params.commentId;
|
|
1067
|
-
\`\`\`
|
|
1068
|
-
|
|
1069
|
-
## Query Parameters
|
|
1070
|
-
|
|
1071
|
-
### Passing Query Params
|
|
1072
|
-
|
|
1073
|
-
\`\`\`tsx
|
|
1074
|
-
navigator.navigate({
|
|
1075
|
-
path: '/search',
|
|
1076
|
-
vars: {
|
|
1077
|
-
q: 'typescript',
|
|
1078
|
-
category: 'tutorial',
|
|
1079
|
-
sort: 'recent'
|
|
1080
|
-
}
|
|
1081
|
-
});
|
|
1082
|
-
|
|
1083
|
-
// Results in: /search?q=typescript&category=tutorial&sort=recent
|
|
1084
|
-
\`\`\`
|
|
1085
|
-
|
|
1086
|
-
### Reading Query Params
|
|
1087
|
-
|
|
1088
|
-
\`\`\`tsx
|
|
1089
|
-
import { useNavigationState } from '@idealyst/navigation';
|
|
1090
|
-
|
|
1091
|
-
function SearchScreen() {
|
|
1092
|
-
// Query params are accessed via useNavigationState on web
|
|
1093
|
-
const { q, category, sort } = useNavigationState<{
|
|
1094
|
-
q?: string;
|
|
1095
|
-
category?: string;
|
|
1096
|
-
sort?: string;
|
|
1097
|
-
}>();
|
|
1098
|
-
|
|
1099
|
-
// Use params...
|
|
1100
|
-
}
|
|
1101
|
-
\`\`\`
|
|
1102
|
-
|
|
1103
|
-
## Navigation Patterns
|
|
1104
|
-
|
|
1105
|
-
### Programmatic Navigation
|
|
1106
|
-
|
|
1107
|
-
Navigate based on conditions:
|
|
1108
|
-
|
|
1109
|
-
\`\`\`tsx
|
|
1110
|
-
function LoginScreen() {
|
|
1111
|
-
const navigator = useNavigator();
|
|
1112
|
-
|
|
1113
|
-
const handleLogin = async () => {
|
|
1114
|
-
const success = await login(credentials);
|
|
1115
|
-
|
|
1116
|
-
if (success) {
|
|
1117
|
-
navigator.navigate({ path: '/dashboard', vars: {} });
|
|
1118
|
-
}
|
|
1119
|
-
};
|
|
1120
|
-
|
|
1121
|
-
return <Button onPress={handleLogin}>Login</Button>;
|
|
1122
|
-
}
|
|
1123
|
-
\`\`\`
|
|
1124
|
-
|
|
1125
|
-
### Navigation with Data
|
|
1126
|
-
|
|
1127
|
-
Pass data through navigation:
|
|
1128
|
-
|
|
1129
|
-
\`\`\`tsx
|
|
1130
|
-
// List screen
|
|
1131
|
-
<Button onPress={() => {
|
|
1132
|
-
navigator.navigate({
|
|
1133
|
-
path: '/product/:id',
|
|
1134
|
-
vars: { id: product.id }
|
|
1135
|
-
});
|
|
1136
|
-
}}>
|
|
1137
|
-
View Product
|
|
1138
|
-
</Button>
|
|
1139
|
-
|
|
1140
|
-
// Detail screen
|
|
1141
|
-
function ProductScreen() {
|
|
1142
|
-
const params = useParams();
|
|
1143
|
-
const productId = params.id;
|
|
1144
|
-
|
|
1145
|
-
const product = useProduct(productId);
|
|
1146
|
-
// Render product...
|
|
1147
|
-
}
|
|
1148
|
-
\`\`\`
|
|
1149
|
-
|
|
1150
|
-
### Conditional Navigation
|
|
1151
|
-
|
|
1152
|
-
Navigate based on user state:
|
|
1153
|
-
|
|
1154
|
-
\`\`\`tsx
|
|
1155
|
-
function ProtectedScreen() {
|
|
1156
|
-
const navigator = useNavigator();
|
|
1157
|
-
const { user } = useAuth();
|
|
1158
|
-
|
|
1159
|
-
useEffect(() => {
|
|
1160
|
-
if (!user) {
|
|
1161
|
-
navigator.navigate({ path: '/login', vars: {} });
|
|
1162
|
-
}
|
|
1163
|
-
}, [user]);
|
|
1164
|
-
|
|
1165
|
-
return user ? <Content /> : null;
|
|
1166
|
-
}
|
|
1167
|
-
\`\`\`
|
|
1168
|
-
|
|
1169
|
-
## Advanced Usage
|
|
1170
|
-
|
|
1171
|
-
### Navigation Guards
|
|
1172
|
-
|
|
1173
|
-
\`\`\`tsx
|
|
1174
|
-
import { useNavigator, useLocation } from '@idealyst/navigation';
|
|
1175
|
-
|
|
1176
|
-
function useAuthGuard() {
|
|
1177
|
-
const { navigate } = useNavigator();
|
|
1178
|
-
const location = useLocation();
|
|
1179
|
-
const { isAuthenticated } = useAuth();
|
|
1180
|
-
|
|
1181
|
-
useEffect(() => {
|
|
1182
|
-
if (!isAuthenticated && location.pathname !== '/login') {
|
|
1183
|
-
navigate({ path: '/login' });
|
|
1184
|
-
}
|
|
1185
|
-
}, [isAuthenticated, location.pathname]);
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
function App() {
|
|
1189
|
-
useAuthGuard();
|
|
1190
|
-
return <AppContent />;
|
|
1191
|
-
}
|
|
1192
|
-
\`\`\`
|
|
1193
|
-
|
|
1194
|
-
### Deep Linking
|
|
1195
|
-
|
|
1196
|
-
Handle deep links:
|
|
1197
|
-
|
|
1198
|
-
\`\`\`tsx
|
|
1199
|
-
useEffect(() => {
|
|
1200
|
-
const handleDeepLink = (url: string) => {
|
|
1201
|
-
// Parse URL and navigate
|
|
1202
|
-
const path = parseDeepLink(url);
|
|
1203
|
-
navigator.navigate({ path, vars: {} });
|
|
1204
|
-
};
|
|
1205
|
-
|
|
1206
|
-
// Add deep link listener
|
|
1207
|
-
const subscription = Linking.addEventListener('url', ({ url }) => {
|
|
1208
|
-
handleDeepLink(url);
|
|
1209
|
-
});
|
|
1210
|
-
|
|
1211
|
-
return () => subscription.remove();
|
|
1212
|
-
}, []);
|
|
1213
|
-
\`\`\`
|
|
1214
|
-
|
|
1215
|
-
### Navigation History
|
|
1216
|
-
|
|
1217
|
-
Track navigation history:
|
|
1218
|
-
|
|
1219
|
-
\`\`\`tsx
|
|
1220
|
-
import { useLocation } from '@idealyst/navigation';
|
|
1221
|
-
|
|
1222
|
-
function useNavigationHistory() {
|
|
1223
|
-
const location = useLocation();
|
|
1224
|
-
const [history, setHistory] = useState<string[]>([]);
|
|
1225
|
-
|
|
1226
|
-
useEffect(() => {
|
|
1227
|
-
setHistory(prev => [...prev, location.pathname]);
|
|
1228
|
-
}, [location.pathname]);
|
|
1229
|
-
|
|
1230
|
-
return history;
|
|
1231
|
-
}
|
|
1232
|
-
\`\`\`
|
|
1233
|
-
|
|
1234
|
-
## TypeScript Support
|
|
1235
|
-
|
|
1236
|
-
Type-safe navigation:
|
|
1237
|
-
|
|
1238
|
-
\`\`\`tsx
|
|
1239
|
-
type NavigateParams = {
|
|
1240
|
-
path: string;
|
|
1241
|
-
vars: Record<string, string>;
|
|
1242
|
-
};
|
|
1243
|
-
|
|
1244
|
-
// Strongly typed vars
|
|
1245
|
-
type UserRouteVars = {
|
|
1246
|
-
id: string;
|
|
1247
|
-
tab?: 'posts' | 'comments' | 'likes';
|
|
1248
|
-
};
|
|
1249
|
-
|
|
1250
|
-
// Usage
|
|
1251
|
-
navigator.navigate({
|
|
1252
|
-
path: '/user/:id',
|
|
1253
|
-
vars: { id: '123', tab: 'posts' } as UserRouteVars
|
|
1254
|
-
});
|
|
1255
|
-
\`\`\`
|
|
1256
|
-
|
|
1257
|
-
## Platform Differences
|
|
1258
|
-
|
|
1259
|
-
### React Native
|
|
1260
|
-
- \`canGoBack()\` checks React Navigation stack
|
|
1261
|
-
- \`goBack()\` uses native navigation stack
|
|
1262
|
-
- Hardware back button supported
|
|
1263
|
-
- Gesture-based navigation
|
|
1264
|
-
|
|
1265
|
-
### Web
|
|
1266
|
-
- \`canGoBack()\` checks for valid parent route in hierarchy
|
|
1267
|
-
- \`goBack()\` navigates to parent route (NOT browser history)
|
|
1268
|
-
- Browser back/forward buttons still work for browser history
|
|
1269
|
-
- URL updates automatically
|
|
1270
|
-
- Bookmarkable URLs
|
|
1271
|
-
|
|
1272
|
-
## Best Practices
|
|
1273
|
-
|
|
1274
|
-
1. **Always provide vars** - Even if empty: \`{}\`
|
|
1275
|
-
2. **Type your routes** - Use TypeScript for safety
|
|
1276
|
-
3. **Handle errors** - Check if navigation succeeded
|
|
1277
|
-
4. **Avoid navigation in render** - Use effects or handlers
|
|
1278
|
-
5. **Clean up listeners** - Remove event listeners on unmount
|
|
1279
|
-
6. **Test navigation flow** - Verify all paths work
|
|
1280
|
-
7. **Use path params for IDs** - Not query params
|
|
1281
|
-
8. **Use query params for filters** - Not path params
|
|
1282
|
-
|
|
1283
|
-
## Common Patterns
|
|
1284
|
-
|
|
1285
|
-
### Back Navigation
|
|
1286
|
-
|
|
1287
|
-
\`\`\`tsx
|
|
1288
|
-
// Conditionally show back button
|
|
1289
|
-
const { canGoBack, goBack } = useNavigator();
|
|
1290
|
-
|
|
1291
|
-
{canGoBack() && (
|
|
1292
|
-
<Button
|
|
1293
|
-
icon="arrow-left"
|
|
1294
|
-
onPress={goBack}
|
|
1295
|
-
>
|
|
1296
|
-
Back
|
|
1297
|
-
</Button>
|
|
1298
|
-
)}
|
|
1299
|
-
\`\`\`
|
|
1300
|
-
|
|
1301
|
-
### Tab Navigation
|
|
1302
|
-
|
|
1303
|
-
\`\`\`tsx
|
|
1304
|
-
import { useNavigator, useLocation } from '@idealyst/navigation';
|
|
1305
|
-
|
|
1306
|
-
const tabs = ['feed', 'search', 'profile'];
|
|
1307
|
-
|
|
1308
|
-
function TabBar() {
|
|
1309
|
-
const { navigate } = useNavigator();
|
|
1310
|
-
const location = useLocation();
|
|
1311
|
-
|
|
1312
|
-
return (
|
|
1313
|
-
<View>
|
|
1314
|
-
{tabs.map(tab => (
|
|
1315
|
-
<Button
|
|
1316
|
-
key={tab}
|
|
1317
|
-
onPress={() => navigate({ path: \`/\${tab}\` })}
|
|
1318
|
-
type={location.pathname === \`/\${tab}\` ? 'contained' : 'outlined'}
|
|
1319
|
-
>
|
|
1320
|
-
{tab}
|
|
1321
|
-
</Button>
|
|
1322
|
-
))}
|
|
1323
|
-
</View>
|
|
1324
|
-
);
|
|
1325
|
-
}
|
|
1326
|
-
\`\`\`
|
|
1327
|
-
|
|
1328
|
-
### Modal Navigation
|
|
1329
|
-
|
|
1330
|
-
\`\`\`tsx
|
|
1331
|
-
<Button onPress={() => {
|
|
1332
|
-
navigator.navigate({
|
|
1333
|
-
path: '/modal/create-post',
|
|
1334
|
-
vars: {}
|
|
1335
|
-
});
|
|
1336
|
-
}}>
|
|
1337
|
-
Create Post
|
|
1338
|
-
</Button>
|
|
1339
|
-
|
|
1340
|
-
// In modal:
|
|
1341
|
-
<Button onPress={() => navigator.goBack()}>
|
|
1342
|
-
Close
|
|
1343
|
-
</Button>
|
|
1344
|
-
\`\`\`
|
|
1345
|
-
`,
|
|
1346
|
-
"idealyst://navigation/invalid-route-handling": `# Invalid Route Handling
|
|
1347
|
-
|
|
1348
|
-
Handle 404 pages and invalid routes with customizable redirect logic and fallback components.
|
|
1349
|
-
|
|
1350
|
-
## Overview
|
|
1351
|
-
|
|
1352
|
-
The navigation system provides two mechanisms for handling invalid routes:
|
|
1353
|
-
|
|
1354
|
-
1. **\`onInvalidRoute\`** - A handler function that can redirect to a different route
|
|
1355
|
-
2. **\`notFoundComponent\`** - A fallback component to render when no redirect is specified
|
|
1356
|
-
|
|
1357
|
-
These can be configured at each navigator level and support bubbling up to parent navigators.
|
|
1358
|
-
|
|
1359
|
-
## Basic Setup
|
|
1360
|
-
|
|
1361
|
-
### Adding a 404 Page
|
|
1362
|
-
|
|
1363
|
-
\`\`\`tsx
|
|
1364
|
-
import { NavigatorParam, NotFoundComponentProps } from '@idealyst/navigation';
|
|
1365
|
-
|
|
1366
|
-
// 404 Component receives path and params
|
|
1367
|
-
const NotFoundPage = ({ path, params }: NotFoundComponentProps) => (
|
|
1368
|
-
<Screen>
|
|
1369
|
-
<View style={{ alignItems: 'center', padding: 24 }}>
|
|
1370
|
-
<Icon name="alert-circle" size={64} color="red" />
|
|
1371
|
-
<Text size="xl">Page Not Found</Text>
|
|
1372
|
-
<Text color="secondary">The path "{path}" doesn't exist.</Text>
|
|
1373
|
-
{params && Object.keys(params).length > 0 && (
|
|
1374
|
-
<Text size="sm">Params: {JSON.stringify(params)}</Text>
|
|
1375
|
-
)}
|
|
1376
|
-
</View>
|
|
1377
|
-
</Screen>
|
|
1378
|
-
);
|
|
1379
|
-
|
|
1380
|
-
const routes: NavigatorParam = {
|
|
1381
|
-
path: "/",
|
|
1382
|
-
type: 'navigator',
|
|
1383
|
-
layout: 'stack',
|
|
1384
|
-
notFoundComponent: NotFoundPage,
|
|
1385
|
-
routes: [
|
|
1386
|
-
{ path: "", type: 'screen', component: HomeScreen },
|
|
1387
|
-
{ path: "about", type: 'screen', component: AboutScreen },
|
|
1388
|
-
]
|
|
1389
|
-
};
|
|
1390
|
-
\`\`\`
|
|
1391
|
-
|
|
1392
|
-
## NotFoundComponentProps
|
|
1393
|
-
|
|
1394
|
-
The 404 component receives information about the attempted route:
|
|
1395
|
-
|
|
1396
|
-
\`\`\`tsx
|
|
1397
|
-
type NotFoundComponentProps = {
|
|
1398
|
-
/** The full path that was attempted */
|
|
1399
|
-
path: string;
|
|
1400
|
-
/** Any route parameters that were parsed from the path */
|
|
1401
|
-
params?: Record<string, string>;
|
|
1402
|
-
};
|
|
1403
|
-
\`\`\`
|
|
1404
|
-
|
|
1405
|
-
Example usage:
|
|
1406
|
-
\`\`\`tsx
|
|
1407
|
-
const NotFoundPage = ({ path, params }: NotFoundComponentProps) => {
|
|
1408
|
-
const { navigate } = useNavigator();
|
|
1409
|
-
|
|
1410
|
-
return (
|
|
1411
|
-
<Screen>
|
|
1412
|
-
<View style={{ padding: 16, gap: 24 }}>
|
|
1413
|
-
<Text size="xl">404 - Page Not Found</Text>
|
|
1414
|
-
<Text>Attempted: {path}</Text>
|
|
1415
|
-
{params?.id && <Text>User ID: {params.id}</Text>}
|
|
1416
|
-
<Button onPress={() => navigate({ path: '/', replace: true })}>
|
|
1417
|
-
Go Home
|
|
1418
|
-
</Button>
|
|
1419
|
-
</View>
|
|
1420
|
-
</Screen>
|
|
1421
|
-
);
|
|
1422
|
-
};
|
|
1423
|
-
\`\`\`
|
|
1424
|
-
|
|
1425
|
-
## Redirect Handler
|
|
1426
|
-
|
|
1427
|
-
Use \`onInvalidRoute\` to redirect certain invalid paths:
|
|
1428
|
-
|
|
1429
|
-
\`\`\`tsx
|
|
1430
|
-
const routes: NavigatorParam = {
|
|
1431
|
-
path: "/",
|
|
1432
|
-
type: 'navigator',
|
|
1433
|
-
layout: 'stack',
|
|
1434
|
-
notFoundComponent: NotFoundPage,
|
|
1435
|
-
onInvalidRoute: (invalidPath) => {
|
|
1436
|
-
// Redirect old URLs to new locations
|
|
1437
|
-
if (invalidPath.startsWith('/old-blog')) {
|
|
1438
|
-
return { path: '/blog', replace: true };
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
|
-
// Redirect legacy paths
|
|
1442
|
-
if (invalidPath === '/legacy-dashboard') {
|
|
1443
|
-
return { path: '/dashboard', replace: true };
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
// Return undefined to show notFoundComponent
|
|
1447
|
-
return undefined;
|
|
1448
|
-
},
|
|
1449
|
-
routes: [...]
|
|
1450
|
-
};
|
|
1451
|
-
\`\`\`
|
|
1452
|
-
|
|
1453
|
-
### Handler Return Values
|
|
1454
|
-
|
|
1455
|
-
The \`onInvalidRoute\` handler can return:
|
|
1456
|
-
|
|
1457
|
-
- **\`NavigateParams\`** - Redirect to a different route
|
|
1458
|
-
- **\`undefined\`** - Show the \`notFoundComponent\` (or bubble up)
|
|
1459
|
-
|
|
1460
|
-
\`\`\`tsx
|
|
1461
|
-
type NavigateParams = {
|
|
1462
|
-
path: string;
|
|
1463
|
-
vars?: Record<string, string>;
|
|
1464
|
-
replace?: boolean; // Recommended: true for redirects
|
|
1465
|
-
};
|
|
1466
|
-
|
|
1467
|
-
onInvalidRoute: (path: string) => NavigateParams | undefined
|
|
1468
|
-
\`\`\`
|
|
1469
|
-
|
|
1470
|
-
## Scoped Handlers (Nested Navigators)
|
|
1471
|
-
|
|
1472
|
-
Each navigator can have its own 404 handling:
|
|
1473
|
-
|
|
1474
|
-
\`\`\`tsx
|
|
1475
|
-
// Settings-specific 404 page
|
|
1476
|
-
const SettingsNotFound = ({ path }: NotFoundComponentProps) => (
|
|
1477
|
-
<Screen>
|
|
1478
|
-
<View style={{ alignItems: 'center' }}>
|
|
1479
|
-
<Icon name="cog-off" size={48} color="orange" />
|
|
1480
|
-
<Text>Settings page not found: {path}</Text>
|
|
1481
|
-
</View>
|
|
1482
|
-
</Screen>
|
|
1483
|
-
);
|
|
1484
|
-
|
|
1485
|
-
// Nested settings navigator with its own handler
|
|
1486
|
-
const SettingsNavigator: NavigatorParam = {
|
|
1487
|
-
path: "settings",
|
|
1488
|
-
type: 'navigator',
|
|
1489
|
-
layout: 'stack',
|
|
1490
|
-
notFoundComponent: SettingsNotFound,
|
|
1491
|
-
onInvalidRoute: (path) => {
|
|
1492
|
-
// Redirect deprecated settings paths
|
|
1493
|
-
if (path.includes('legacy-setting')) {
|
|
1494
|
-
return { path: '/settings/general', replace: true };
|
|
1495
|
-
}
|
|
1496
|
-
return undefined; // Show SettingsNotFound
|
|
1497
|
-
},
|
|
1498
|
-
routes: [
|
|
1499
|
-
{ path: "", type: 'screen', component: SettingsHome },
|
|
1500
|
-
{ path: "general", type: 'screen', component: GeneralSettings },
|
|
1501
|
-
{ path: "account", type: 'screen', component: AccountSettings },
|
|
1502
|
-
]
|
|
1503
|
-
};
|
|
1504
|
-
|
|
1505
|
-
// Root navigator with global 404
|
|
1506
|
-
const AppRouter: NavigatorParam = {
|
|
1507
|
-
path: "/",
|
|
1508
|
-
type: 'navigator',
|
|
1509
|
-
layout: 'stack',
|
|
1510
|
-
notFoundComponent: GlobalNotFound, // Fallback for non-settings routes
|
|
1511
|
-
routes: [
|
|
1512
|
-
{ path: "", type: 'screen', component: HomeScreen },
|
|
1513
|
-
SettingsNavigator, // Has its own 404 handling
|
|
1514
|
-
{ path: "about", type: 'screen', component: AboutScreen },
|
|
1515
|
-
]
|
|
1516
|
-
};
|
|
1517
|
-
\`\`\`
|
|
1518
|
-
|
|
1519
|
-
## Handler Bubbling
|
|
1520
|
-
|
|
1521
|
-
Invalid routes bubble up through the navigator hierarchy:
|
|
1522
|
-
|
|
1523
|
-
\`\`\`
|
|
1524
|
-
Invalid route detected: /settings/invalid-page
|
|
1525
|
-
↓
|
|
1526
|
-
Check /settings navigator's onInvalidRoute
|
|
1527
|
-
↓
|
|
1528
|
-
┌─────────────────────────────────────────┐
|
|
1529
|
-
│ Returns NavigateParams? │
|
|
1530
|
-
│ YES → Redirect to that route │
|
|
1531
|
-
│ NO (undefined) → Check notFoundComponent │
|
|
1532
|
-
└─────────────────────────────────────────┘
|
|
1533
|
-
↓
|
|
1534
|
-
Has notFoundComponent?
|
|
1535
|
-
YES → Render it with { path, params }
|
|
1536
|
-
NO → Bubble up to parent navigator
|
|
1537
|
-
↓
|
|
1538
|
-
No parent handles it?
|
|
1539
|
-
→ console.warn("No handler for invalid route")
|
|
1540
|
-
\`\`\`
|
|
1541
|
-
|
|
1542
|
-
## Platform Behavior
|
|
1543
|
-
|
|
1544
|
-
### Web
|
|
1545
|
-
|
|
1546
|
-
- Invalid routes trigger the catch-all route at each navigator level
|
|
1547
|
-
- The \`onInvalidRoute\` handler is called when the 404 route is rendered
|
|
1548
|
-
- If handler returns \`NavigateParams\`, navigation uses \`replace: true\` by default
|
|
1549
|
-
- URL stays at the invalid path when showing \`notFoundComponent\`
|
|
1550
|
-
|
|
1551
|
-
### Mobile (React Native)
|
|
1552
|
-
|
|
1553
|
-
- Invalid routes trigger navigation to a hidden 404 screen
|
|
1554
|
-
- The handler is called during the \`navigate()\` function
|
|
1555
|
-
- If handler returns \`NavigateParams\`, redirects to that route
|
|
1556
|
-
- If no handler/component, logs a warning
|
|
1557
|
-
|
|
1558
|
-
## Complete Example
|
|
1559
|
-
|
|
1560
|
-
\`\`\`tsx
|
|
1561
|
-
import { NavigatorParam, NotFoundComponentProps } from '@idealyst/navigation';
|
|
1562
|
-
import { Screen, View, Text, Button, Icon, Card } from '@idealyst/components';
|
|
1563
|
-
|
|
1564
|
-
// Global 404 - detailed error page
|
|
1565
|
-
const GlobalNotFound = ({ path, params }: NotFoundComponentProps) => {
|
|
1566
|
-
const { navigate } = useNavigator();
|
|
1567
|
-
|
|
1568
|
-
return (
|
|
1569
|
-
<Screen>
|
|
1570
|
-
<View style={{ padding: 24, gap: 24, alignItems: 'center', flex: 1, justifyContent: 'center' }}>
|
|
1571
|
-
<Icon name="alert-circle-outline" size={64} color="red" />
|
|
1572
|
-
<Text size="xl" weight="bold">Page Not Found</Text>
|
|
1573
|
-
<Text color="secondary">The page you're looking for doesn't exist.</Text>
|
|
1574
|
-
|
|
1575
|
-
<Card style={{ marginTop: 16, padding: 16 }}>
|
|
1576
|
-
<Text size="sm" weight="semibold">Attempted path:</Text>
|
|
1577
|
-
<Text size="sm" color="secondary">{path}</Text>
|
|
1578
|
-
{params && Object.keys(params).length > 0 && (
|
|
1579
|
-
<>
|
|
1580
|
-
<Text size="sm" weight="semibold" style={{ marginTop: 8 }}>Params:</Text>
|
|
1581
|
-
<Text size="sm" color="secondary">{JSON.stringify(params)}</Text>
|
|
1582
|
-
</>
|
|
1583
|
-
)}
|
|
1584
|
-
</Card>
|
|
1585
|
-
|
|
1586
|
-
<Button style={{ marginTop: 24 }} onPress={() => navigate({ path: '/', replace: true })}>
|
|
1587
|
-
Go Home
|
|
1588
|
-
</Button>
|
|
1589
|
-
</View>
|
|
1590
|
-
</Screen>
|
|
1591
|
-
);
|
|
1592
|
-
};
|
|
1593
|
-
|
|
1594
|
-
// Admin section 404 - simpler style
|
|
1595
|
-
const AdminNotFound = ({ path }: NotFoundComponentProps) => {
|
|
1596
|
-
const { navigate } = useNavigator();
|
|
1597
|
-
|
|
1598
|
-
return (
|
|
1599
|
-
<Screen>
|
|
1600
|
-
<View padding={16} style={{ alignItems: 'center' }}>
|
|
1601
|
-
<Icon name="shield-off" size={48} color="orange" />
|
|
1602
|
-
<Text size="lg">Admin page not found</Text>
|
|
1603
|
-
<Button
|
|
1604
|
-
type="outlined"
|
|
1605
|
-
size="sm"
|
|
1606
|
-
onPress={() => navigate({ path: '/admin', replace: true })}
|
|
1607
|
-
>
|
|
1608
|
-
Back to Admin
|
|
1609
|
-
</Button>
|
|
1610
|
-
</View>
|
|
1611
|
-
</Screen>
|
|
1612
|
-
);
|
|
1613
|
-
};
|
|
1614
|
-
|
|
1615
|
-
// Admin navigator with redirect logic
|
|
1616
|
-
const AdminNavigator: NavigatorParam = {
|
|
1617
|
-
path: "admin",
|
|
1618
|
-
type: 'navigator',
|
|
1619
|
-
layout: 'stack',
|
|
1620
|
-
notFoundComponent: AdminNotFound,
|
|
1621
|
-
onInvalidRoute: (path) => {
|
|
1622
|
-
// Redirect old admin paths
|
|
1623
|
-
if (path.includes('old-users')) {
|
|
1624
|
-
return { path: '/admin/users', replace: true };
|
|
1625
|
-
}
|
|
1626
|
-
if (path.includes('deprecated')) {
|
|
1627
|
-
return { path: '/admin', replace: true };
|
|
1628
|
-
}
|
|
1629
|
-
return undefined; // Show AdminNotFound
|
|
1630
|
-
},
|
|
1631
|
-
routes: [
|
|
1632
|
-
{ path: "", type: 'screen', component: AdminDashboard },
|
|
1633
|
-
{ path: "users", type: 'screen', component: AdminUsers },
|
|
1634
|
-
{ path: "settings", type: 'screen', component: AdminSettings },
|
|
1635
|
-
]
|
|
1636
|
-
};
|
|
1637
|
-
|
|
1638
|
-
// Root app router
|
|
1639
|
-
const AppRouter: NavigatorParam = {
|
|
1640
|
-
path: "/",
|
|
1641
|
-
type: 'navigator',
|
|
1642
|
-
layout: 'drawer',
|
|
1643
|
-
notFoundComponent: GlobalNotFound,
|
|
1644
|
-
onInvalidRoute: (path) => {
|
|
1645
|
-
// Global redirects
|
|
1646
|
-
if (path === '/home') {
|
|
1647
|
-
return { path: '/', replace: true };
|
|
1648
|
-
}
|
|
1649
|
-
return undefined;
|
|
1650
|
-
},
|
|
1651
|
-
routes: [
|
|
1652
|
-
{ path: "", type: 'screen', component: HomeScreen },
|
|
1653
|
-
AdminNavigator,
|
|
1654
|
-
{ path: "about", type: 'screen', component: AboutScreen },
|
|
1655
|
-
{ path: "contact", type: 'screen', component: ContactScreen },
|
|
1656
|
-
]
|
|
1657
|
-
};
|
|
1658
|
-
|
|
1659
|
-
export default AppRouter;
|
|
1660
|
-
\`\`\`
|
|
1661
|
-
|
|
1662
|
-
## Best Practices
|
|
1663
|
-
|
|
1664
|
-
1. **Always provide a root notFoundComponent** - Ensures all invalid routes are handled
|
|
1665
|
-
2. **Use scoped handlers for sections** - Different 404 styles for different app areas
|
|
1666
|
-
3. **Redirect deprecated URLs** - Use \`onInvalidRoute\` to maintain URL compatibility
|
|
1667
|
-
4. **Include helpful information** - Show the attempted path and suggest alternatives
|
|
1668
|
-
5. **Provide navigation options** - Add buttons to go home or back
|
|
1669
|
-
6. **Use \`replace: true\` for redirects** - Prevents invalid routes in browser history
|
|
1670
|
-
7. **Log unhandled routes** - Monitor for missing pages in production
|
|
1671
|
-
|
|
1672
|
-
## TypeScript Types
|
|
1673
|
-
|
|
1674
|
-
\`\`\`tsx
|
|
1675
|
-
import {
|
|
1676
|
-
NavigatorParam,
|
|
1677
|
-
NotFoundComponentProps,
|
|
1678
|
-
NavigateParams
|
|
1679
|
-
} from '@idealyst/navigation';
|
|
1680
|
-
|
|
1681
|
-
// NotFoundComponentProps
|
|
1682
|
-
type NotFoundComponentProps = {
|
|
1683
|
-
path: string;
|
|
1684
|
-
params?: Record<string, string>;
|
|
1685
|
-
};
|
|
1686
|
-
|
|
1687
|
-
// Handler signature
|
|
1688
|
-
type InvalidRouteHandler = (invalidPath: string) => NavigateParams | undefined;
|
|
1689
|
-
|
|
1690
|
-
// NavigateParams (used by handler return and navigate function)
|
|
1691
|
-
type NavigateParams = {
|
|
1692
|
-
path: string;
|
|
1693
|
-
vars?: Record<string, string>;
|
|
1694
|
-
replace?: boolean;
|
|
1695
|
-
};
|
|
1696
|
-
\`\`\`
|
|
1697
|
-
`,
|
|
1698
|
-
"idealyst://navigation/web-mobile-parity": `# Web/Mobile Navigation Parity
|
|
1699
|
-
|
|
1700
|
-
Understanding how to achieve the same navigation experience on web that you get for free on mobile.
|
|
1701
|
-
|
|
1702
|
-
## The Core Problem
|
|
1703
|
-
|
|
1704
|
-
On **React Native**, you get native navigation UI for free:
|
|
1705
|
-
- Stack navigator gives you headers with back buttons
|
|
1706
|
-
- Tab navigator gives you a bottom tab bar with icons
|
|
1707
|
-
- Drawer navigator gives you a slide-out menu
|
|
1708
|
-
|
|
1709
|
-
On **Web**, you get nothing but URL routing. No headers, no tab bars, no drawers. You must build these yourself using **layout components**.
|
|
1710
|
-
|
|
1711
|
-
## The Solution: Layout Components
|
|
1712
|
-
|
|
1713
|
-
The \`layoutComponent\` prop on navigators is how you achieve parity. It wraps your route content and provides the navigation UI.
|
|
1714
|
-
|
|
1715
|
-
\`\`\`tsx
|
|
1716
|
-
{
|
|
1717
|
-
path: "/",
|
|
1718
|
-
type: 'navigator',
|
|
1719
|
-
layout: 'stack',
|
|
1720
|
-
layoutComponent: MyStackLayout, // <-- This is the key!
|
|
1721
|
-
routes: [...]
|
|
1722
|
-
}
|
|
1723
|
-
\`\`\`
|
|
1724
|
-
|
|
1725
|
-
## What Layout Components Receive
|
|
1726
|
-
|
|
1727
|
-
Every layout component receives these props:
|
|
1728
|
-
|
|
1729
|
-
\`\`\`tsx
|
|
1730
|
-
type LayoutProps = {
|
|
1731
|
-
children: React.ReactNode; // The route content (renders via <Outlet />)
|
|
1732
|
-
options?: NavigatorOptions; // headerTitle, headerLeft, headerRight, etc.
|
|
1733
|
-
routes: RouteWithFullPath[]; // All child routes with their full paths
|
|
1734
|
-
currentPath: string; // Currently active route path
|
|
1735
|
-
};
|
|
1736
|
-
\`\`\`
|
|
1737
|
-
|
|
1738
|
-
**This gives you everything you need to build any navigation UI:**
|
|
1739
|
-
- \`options\` - What to show in headers
|
|
1740
|
-
- \`routes\` - What tabs/menu items to render
|
|
1741
|
-
- \`currentPath\` - Which one is active
|
|
1742
|
-
- \`children\` - Where to render the screen content
|
|
1743
|
-
|
|
1744
|
-
## Stack Navigator Parity
|
|
1745
|
-
|
|
1746
|
-
### What Native Gives You
|
|
1747
|
-
- Header bar with title
|
|
1748
|
-
- Back button (automatic)
|
|
1749
|
-
- Right-side actions
|
|
1750
|
-
- Smooth transitions
|
|
1751
|
-
|
|
1752
|
-
### Web Implementation
|
|
1753
|
-
|
|
1754
|
-
\`\`\`tsx
|
|
1755
|
-
import { Outlet } from 'react-router-dom';
|
|
1756
|
-
import { View, Text, IconButton, Pressable } from '@idealyst/components';
|
|
1757
|
-
import { useNavigator } from '@idealyst/navigation';
|
|
1758
|
-
import type { StackLayoutProps } from '@idealyst/navigation';
|
|
1759
|
-
|
|
1760
|
-
export function StackLayout({ options, currentPath }: StackLayoutProps) {
|
|
1761
|
-
const { canGoBack, goBack } = useNavigator();
|
|
1762
|
-
|
|
1763
|
-
return (
|
|
1764
|
-
<View style={{ flex: 1 }}>
|
|
1765
|
-
{/* Header - mimics native stack header */}
|
|
1766
|
-
{options?.headerShown !== false && (
|
|
1767
|
-
<View style={{
|
|
1768
|
-
height: 56,
|
|
1769
|
-
flexDirection: 'row',
|
|
1770
|
-
alignItems: 'center',
|
|
1771
|
-
paddingHorizontal: 16,
|
|
1772
|
-
borderBottomWidth: 1,
|
|
1773
|
-
borderBottomColor: '#e0e0e0',
|
|
1774
|
-
backgroundColor: '#fff',
|
|
1775
|
-
}}>
|
|
1776
|
-
{/* Back button - like native */}
|
|
1777
|
-
{options?.headerBackVisible !== false && canGoBack() && (
|
|
1778
|
-
<IconButton
|
|
1779
|
-
icon="arrow-left"
|
|
1780
|
-
onPress={goBack}
|
|
1781
|
-
style={{ marginRight: 8 }}
|
|
1782
|
-
/>
|
|
1783
|
-
)}
|
|
1784
|
-
|
|
1785
|
-
{/* Left slot */}
|
|
1786
|
-
{options?.headerLeft && (
|
|
1787
|
-
<View style={{ marginRight: 16 }}>
|
|
1788
|
-
{typeof options.headerLeft === 'function'
|
|
1789
|
-
? options.headerLeft({})
|
|
1790
|
-
: options.headerLeft}
|
|
1791
|
-
</View>
|
|
1792
|
-
)}
|
|
1793
|
-
|
|
1794
|
-
{/* Title - centered or left-aligned */}
|
|
1795
|
-
<View style={{ flex: 1 }}>
|
|
1796
|
-
{typeof options?.headerTitle === 'string' ? (
|
|
1797
|
-
<Text variant="title">{options.headerTitle}</Text>
|
|
1798
|
-
) : (
|
|
1799
|
-
options?.headerTitle
|
|
1800
|
-
)}
|
|
1801
|
-
</View>
|
|
1802
|
-
|
|
1803
|
-
{/* Right slot */}
|
|
1804
|
-
{options?.headerRight && (
|
|
1805
|
-
<View>
|
|
1806
|
-
{typeof options.headerRight === 'function'
|
|
1807
|
-
? options.headerRight({})
|
|
1808
|
-
: options.headerRight}
|
|
1809
|
-
</View>
|
|
1810
|
-
)}
|
|
1811
|
-
</View>
|
|
1812
|
-
)}
|
|
1813
|
-
|
|
1814
|
-
{/* Content area */}
|
|
1815
|
-
<View style={{ flex: 1 }}>
|
|
1816
|
-
<Outlet />
|
|
1817
|
-
</View>
|
|
1818
|
-
</View>
|
|
1819
|
-
);
|
|
1820
|
-
}
|
|
1821
|
-
\`\`\`
|
|
1822
|
-
|
|
1823
|
-
## Tab Navigator Parity
|
|
1824
|
-
|
|
1825
|
-
### What Native Gives You
|
|
1826
|
-
- Bottom tab bar
|
|
1827
|
-
- Icons for each tab
|
|
1828
|
-
- Labels
|
|
1829
|
-
- Badge counts
|
|
1830
|
-
- Active state highlighting
|
|
1831
|
-
|
|
1832
|
-
### Web Implementation
|
|
1833
|
-
|
|
1834
|
-
\`\`\`tsx
|
|
1835
|
-
import { Outlet } from 'react-router-dom';
|
|
1836
|
-
import { View, Text, Pressable, Icon, Badge } from '@idealyst/components';
|
|
1837
|
-
import { useNavigator } from '@idealyst/navigation';
|
|
1838
|
-
import type { TabLayoutProps } from '@idealyst/navigation';
|
|
1839
|
-
|
|
1840
|
-
export function TabLayout({ routes, currentPath }: TabLayoutProps) {
|
|
1841
|
-
const { navigate } = useNavigator();
|
|
1842
|
-
|
|
1843
|
-
return (
|
|
1844
|
-
<View style={{ flex: 1 }}>
|
|
1845
|
-
{/* Content area */}
|
|
1846
|
-
<View style={{ flex: 1 }}>
|
|
1847
|
-
<Outlet />
|
|
1848
|
-
</View>
|
|
1849
|
-
|
|
1850
|
-
{/* Bottom tab bar - mimics native */}
|
|
1851
|
-
<View style={{
|
|
1852
|
-
height: 56,
|
|
1853
|
-
flexDirection: 'row',
|
|
1854
|
-
borderTopWidth: 1,
|
|
1855
|
-
borderTopColor: '#e0e0e0',
|
|
1856
|
-
backgroundColor: '#fff',
|
|
1857
|
-
}}>
|
|
1858
|
-
{routes.map((route) => {
|
|
1859
|
-
const isActive = currentPath === route.fullPath;
|
|
1860
|
-
const options = route.options;
|
|
1861
|
-
|
|
1862
|
-
return (
|
|
1863
|
-
<Pressable
|
|
1864
|
-
key={route.fullPath}
|
|
1865
|
-
onPress={() => navigate({ path: route.fullPath })}
|
|
1866
|
-
style={{
|
|
1867
|
-
flex: 1,
|
|
1868
|
-
alignItems: 'center',
|
|
1869
|
-
justifyContent: 'center',
|
|
1870
|
-
paddingVertical: 8,
|
|
1871
|
-
}}
|
|
1872
|
-
>
|
|
1873
|
-
{/* Icon with optional badge */}
|
|
1874
|
-
<View style={{ position: 'relative' }}>
|
|
1875
|
-
{options?.tabBarIcon?.({
|
|
1876
|
-
focused: isActive,
|
|
1877
|
-
color: isActive ? '#007AFF' : '#8E8E93',
|
|
1878
|
-
size: 24,
|
|
1879
|
-
})}
|
|
1880
|
-
{options?.tabBarBadge && (
|
|
1881
|
-
<Badge
|
|
1882
|
-
count={options.tabBarBadge}
|
|
1883
|
-
style={{ position: 'absolute', top: -4, right: -8 }}
|
|
1884
|
-
/>
|
|
1885
|
-
)}
|
|
1886
|
-
</View>
|
|
1887
|
-
|
|
1888
|
-
{/* Label */}
|
|
1889
|
-
{options?.tabBarLabel && (
|
|
1890
|
-
<Text
|
|
1891
|
-
size="xs"
|
|
1892
|
-
style={{
|
|
1893
|
-
marginTop: 4,
|
|
1894
|
-
color: isActive ? '#007AFF' : '#8E8E93',
|
|
1895
|
-
}}
|
|
1896
|
-
>
|
|
1897
|
-
{options.tabBarLabel}
|
|
1898
|
-
</Text>
|
|
1899
|
-
)}
|
|
1900
|
-
</Pressable>
|
|
1901
|
-
);
|
|
1902
|
-
})}
|
|
1903
|
-
</View>
|
|
1904
|
-
</View>
|
|
1905
|
-
);
|
|
1906
|
-
}
|
|
1907
|
-
\`\`\`
|
|
1908
|
-
|
|
1909
|
-
## Drawer Navigator Parity
|
|
1910
|
-
|
|
1911
|
-
### What Native Gives You
|
|
1912
|
-
- Slide-out drawer from edge
|
|
1913
|
-
- Overlay when open
|
|
1914
|
-
- Gesture to open/close
|
|
1915
|
-
- Menu items
|
|
1916
|
-
|
|
1917
|
-
### Web Implementation
|
|
1918
|
-
|
|
1919
|
-
On web, drawers are typically persistent sidebars. Here's how to build both:
|
|
1920
|
-
|
|
1921
|
-
\`\`\`tsx
|
|
1922
|
-
import { Outlet } from 'react-router-dom';
|
|
1923
|
-
import { View, Text, Pressable, Icon } from '@idealyst/components';
|
|
1924
|
-
import { useNavigator } from '@idealyst/navigation';
|
|
1925
|
-
import type { StackLayoutProps } from '@idealyst/navigation';
|
|
1926
|
-
|
|
1927
|
-
export function DrawerLayout({ routes, currentPath, options }: StackLayoutProps) {
|
|
1928
|
-
const { navigate } = useNavigator();
|
|
1929
|
-
const [isCollapsed, setIsCollapsed] = useState(false);
|
|
1930
|
-
|
|
1931
|
-
return (
|
|
1932
|
-
<View style={{ flex: 1, flexDirection: 'row' }}>
|
|
1933
|
-
{/* Sidebar - always visible on web */}
|
|
1934
|
-
<View style={{
|
|
1935
|
-
width: isCollapsed ? 64 : 240,
|
|
1936
|
-
borderRightWidth: 1,
|
|
1937
|
-
borderRightColor: '#e0e0e0',
|
|
1938
|
-
backgroundColor: '#f8f8f8',
|
|
1939
|
-
transition: 'width 0.2s',
|
|
1940
|
-
}}>
|
|
1941
|
-
{/* Logo/Header */}
|
|
1942
|
-
<View style={{ height: 56, justifyContent: 'center', paddingHorizontal: 16 }}>
|
|
1943
|
-
{!isCollapsed && <Text variant="title">My App</Text>}
|
|
1944
|
-
</View>
|
|
1945
|
-
|
|
1946
|
-
{/* Menu Items */}
|
|
1947
|
-
{routes.map((route) => {
|
|
1948
|
-
const isActive = currentPath.startsWith(route.fullPath);
|
|
1949
|
-
return (
|
|
1950
|
-
<Pressable
|
|
1951
|
-
key={route.fullPath}
|
|
1952
|
-
onPress={() => navigate({ path: route.fullPath })}
|
|
1953
|
-
style={{
|
|
1954
|
-
flexDirection: 'row',
|
|
1955
|
-
alignItems: 'center',
|
|
1956
|
-
padding: 12,
|
|
1957
|
-
backgroundColor: isActive ? 'rgba(0,0,0,0.08)' : 'transparent',
|
|
1958
|
-
}}
|
|
1959
|
-
>
|
|
1960
|
-
<Icon
|
|
1961
|
-
name={route.options?.icon || 'circle'}
|
|
1962
|
-
size={24}
|
|
1963
|
-
color={isActive ? '#007AFF' : '#666'}
|
|
1964
|
-
/>
|
|
1965
|
-
{!isCollapsed && (
|
|
1966
|
-
<Text style={{ marginLeft: 12, color: isActive ? '#007AFF' : '#333' }}>
|
|
1967
|
-
{route.options?.title || route.path}
|
|
1968
|
-
</Text>
|
|
1969
|
-
)}
|
|
1970
|
-
</Pressable>
|
|
1971
|
-
);
|
|
1972
|
-
})}
|
|
1973
|
-
|
|
1974
|
-
{/* Collapse toggle */}
|
|
1975
|
-
<Pressable
|
|
1976
|
-
onPress={() => setIsCollapsed(!isCollapsed)}
|
|
1977
|
-
style={{ padding: 12, marginTop: 'auto' }}
|
|
1978
|
-
>
|
|
1979
|
-
<Icon name={isCollapsed ? 'chevron-right' : 'chevron-left'} size={24} />
|
|
1980
|
-
</Pressable>
|
|
1981
|
-
</View>
|
|
1982
|
-
|
|
1983
|
-
{/* Content */}
|
|
1984
|
-
<View style={{ flex: 1 }}>
|
|
1985
|
-
<Outlet />
|
|
1986
|
-
</View>
|
|
1987
|
-
</View>
|
|
1988
|
-
);
|
|
1989
|
-
}
|
|
1990
|
-
\`\`\`
|
|
1991
|
-
|
|
1992
|
-
## Using GeneralLayout Helper
|
|
1993
|
-
|
|
1994
|
-
The \`GeneralLayout\` component simplifies building layouts:
|
|
1995
|
-
|
|
1996
|
-
\`\`\`tsx
|
|
1997
|
-
import { GeneralLayout } from '@idealyst/navigation';
|
|
1998
|
-
|
|
1999
|
-
export function AppLayout({ options, routes, currentPath, children }: StackLayoutProps) {
|
|
2000
|
-
return (
|
|
2001
|
-
<GeneralLayout
|
|
2002
|
-
header={{
|
|
2003
|
-
enabled: true,
|
|
2004
|
-
height: 56,
|
|
2005
|
-
content: (
|
|
2006
|
-
<View style={{ flexDirection: 'row', alignItems: 'center', flex: 1 }}>
|
|
2007
|
-
<Text variant="title">{options?.headerTitle || 'App'}</Text>
|
|
2008
|
-
<View style={{ marginLeft: 'auto' }}>{options?.headerRight}</View>
|
|
2009
|
-
</View>
|
|
2010
|
-
),
|
|
2011
|
-
}}
|
|
2012
|
-
sidebar={{
|
|
2013
|
-
enabled: true,
|
|
2014
|
-
collapsible: true,
|
|
2015
|
-
expandedWidth: 240,
|
|
2016
|
-
collapsedWidth: 64,
|
|
2017
|
-
content: <SidebarMenu routes={routes} currentPath={currentPath} />,
|
|
2018
|
-
}}
|
|
2019
|
-
>
|
|
2020
|
-
{children}
|
|
2021
|
-
</GeneralLayout>
|
|
2022
|
-
);
|
|
2023
|
-
}
|
|
2024
|
-
\`\`\`
|
|
2025
|
-
|
|
2026
|
-
## Putting It All Together
|
|
2027
|
-
|
|
2028
|
-
Here's a complete router setup with web layouts:
|
|
2029
|
-
|
|
2030
|
-
\`\`\`tsx
|
|
2031
|
-
import { NavigatorParam } from '@idealyst/navigation';
|
|
2032
|
-
import { StackLayout } from './layouts/StackLayout';
|
|
2033
|
-
import { TabLayout } from './layouts/TabLayout';
|
|
2034
|
-
|
|
2035
|
-
const appRouter: NavigatorParam = {
|
|
2036
|
-
path: "/",
|
|
2037
|
-
type: 'navigator',
|
|
2038
|
-
layout: 'stack',
|
|
2039
|
-
layoutComponent: StackLayout, // Web header
|
|
2040
|
-
options: {
|
|
2041
|
-
headerTitle: "My App",
|
|
2042
|
-
headerRight: <UserMenu />,
|
|
2043
|
-
},
|
|
2044
|
-
routes: [
|
|
2045
|
-
{
|
|
2046
|
-
path: "main",
|
|
2047
|
-
type: 'navigator',
|
|
2048
|
-
layout: 'tab',
|
|
2049
|
-
layoutComponent: TabLayout, // Web tab bar
|
|
2050
|
-
routes: [
|
|
2051
|
-
{
|
|
2052
|
-
path: "home",
|
|
2053
|
-
type: 'screen',
|
|
2054
|
-
component: HomeScreen,
|
|
2055
|
-
options: {
|
|
2056
|
-
tabBarLabel: "Home",
|
|
2057
|
-
tabBarIcon: ({ color }) => <Icon name="home" color={color} />,
|
|
2058
|
-
},
|
|
2059
|
-
},
|
|
2060
|
-
{
|
|
2061
|
-
path: "search",
|
|
2062
|
-
type: 'screen',
|
|
2063
|
-
component: SearchScreen,
|
|
2064
|
-
options: {
|
|
2065
|
-
tabBarLabel: "Search",
|
|
2066
|
-
tabBarIcon: ({ color }) => <Icon name="magnify" color={color} />,
|
|
2067
|
-
},
|
|
2068
|
-
},
|
|
2069
|
-
],
|
|
2070
|
-
},
|
|
2071
|
-
{
|
|
2072
|
-
path: "settings",
|
|
2073
|
-
type: 'screen',
|
|
2074
|
-
component: SettingsScreen,
|
|
2075
|
-
},
|
|
2076
|
-
],
|
|
2077
|
-
};
|
|
2078
|
-
\`\`\`
|
|
2079
|
-
|
|
2080
|
-
## Key Insights
|
|
2081
|
-
|
|
2082
|
-
1. **layoutComponent is web-only** - Native ignores it and uses native navigators
|
|
2083
|
-
2. **Same route config, different UI** - Your routes stay the same, layouts differ
|
|
2084
|
-
3. **Options are your data source** - headerTitle, tabBarIcon, etc. drive your layout
|
|
2085
|
-
4. **routes array is navigation menu** - Use it to build sidebars, tab bars, menus
|
|
2086
|
-
5. **currentPath enables active states** - Compare to highlight current item
|
|
2087
|
-
6. **Outlet renders children** - From react-router-dom, this is where screen content goes
|
|
2088
|
-
|
|
2089
|
-
## Common Patterns
|
|
2090
|
-
|
|
2091
|
-
### Responsive Layout
|
|
2092
|
-
\`\`\`tsx
|
|
2093
|
-
function ResponsiveLayout(props: StackLayoutProps) {
|
|
2094
|
-
const { width } = useWindowDimensions();
|
|
2095
|
-
const isMobile = width < 768;
|
|
2096
|
-
|
|
2097
|
-
// Tabs on mobile, drawer on desktop
|
|
2098
|
-
return isMobile
|
|
2099
|
-
? <TabLayout {...props} />
|
|
2100
|
-
: <DrawerLayout {...props} />;
|
|
2101
|
-
}
|
|
2102
|
-
\`\`\`
|
|
2103
|
-
|
|
2104
|
-
### Nested Headers
|
|
2105
|
-
\`\`\`tsx
|
|
2106
|
-
// Parent navigator has app header
|
|
2107
|
-
// Child navigator has section header
|
|
2108
|
-
{
|
|
2109
|
-
path: "/",
|
|
2110
|
-
layoutComponent: AppHeaderLayout,
|
|
2111
|
-
routes: [{
|
|
2112
|
-
path: "admin",
|
|
2113
|
-
layoutComponent: AdminSectionHeader, // Adds another header
|
|
2114
|
-
routes: [...]
|
|
2115
|
-
}]
|
|
2116
|
-
}
|
|
2117
|
-
\`\`\`
|
|
2118
|
-
|
|
2119
|
-
### Hiding Navigation
|
|
2120
|
-
\`\`\`tsx
|
|
2121
|
-
function ConditionalLayout(props: StackLayoutProps) {
|
|
2122
|
-
// Hide navigation on certain routes
|
|
2123
|
-
if (props.currentPath.includes('/fullscreen')) {
|
|
2124
|
-
return <Outlet />; // No chrome
|
|
2125
|
-
}
|
|
2126
|
-
return <FullLayout {...props} />;
|
|
2127
|
-
}
|
|
2128
|
-
\`\`\`
|
|
2129
|
-
|
|
2130
|
-
## Summary
|
|
2131
|
-
|
|
2132
|
-
| Native Gets | Web Needs |
|
|
2133
|
-
|-------------|-----------|
|
|
2134
|
-
| Stack header | \`layoutComponent\` with header UI |
|
|
2135
|
-
| Tab bar | \`layoutComponent\` with tab buttons |
|
|
2136
|
-
| Drawer | \`layoutComponent\` with sidebar |
|
|
2137
|
-
| Back button | \`canGoBack()\` + \`goBack()\` |
|
|
2138
|
-
| Active states | Compare \`currentPath\` to \`route.fullPath\` |
|
|
2139
|
-
| Screen options | Access via \`options\` and \`route.options\` |
|
|
2140
|
-
|
|
2141
|
-
The key to web/mobile parity is understanding that **layout components give web everything native navigators provide automatically**.
|
|
2142
|
-
`,
|
|
2143
|
-
};
|
|
2144
|
-
//# sourceMappingURL=navigation-guides.js.map
|