@sigmela/router 0.0.15 → 0.0.17
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/README.md +156 -255
- package/lib/module/styles.css +4 -26
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -1,25 +1,42 @@
|
|
|
1
|
-
#
|
|
1
|
+
# React Native Router
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Modern, predictable navigation for React Native and Web built on top of react-native-screens. Simple class-based stacks, optional bottom tabs, global modals, typed URL params, and first-class web History API support.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
-
|
|
7
|
-
- Bottom tab
|
|
8
|
-
-
|
|
9
|
-
-
|
|
5
|
+
Features
|
|
6
|
+
- Simple, chainable API: `new NavigationStack().addScreen('/users/:id', User)`
|
|
7
|
+
- Bottom tab bar with native and web renderers; supports custom tab bars
|
|
8
|
+
- Global stack for modals/overlays on top of root/tabs
|
|
9
|
+
- URL-first navigation: navigate using path strings; typed `useParams` and `useQueryParams`
|
|
10
|
+
- Works on web: integrates with pushState/replaceState/popstate and supports deep links
|
|
11
|
+
- Appearance control for headers, screens, and tab bar
|
|
10
12
|
|
|
11
13
|
Installation
|
|
12
|
-
|
|
13
14
|
```bash
|
|
14
15
|
yarn add @sigmela/router react-native-screens
|
|
16
|
+
# or
|
|
17
|
+
npm i @sigmela/router react-native-screens
|
|
15
18
|
```
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
Peer requirements
|
|
21
|
+
- react-native-screens >= 4.16.0
|
|
22
|
+
- react and react-native (versions matching your app)
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
Web CSS
|
|
25
|
+
- Import the bundled stylesheet once in your web entry to enable transitions and default tab styles:
|
|
26
|
+
```ts
|
|
27
|
+
import '@sigmela/router/styles.css';
|
|
28
|
+
```
|
|
20
29
|
|
|
30
|
+
Quick start (single stack)
|
|
21
31
|
```tsx
|
|
22
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
NavigationStack,
|
|
34
|
+
Router,
|
|
35
|
+
Navigation,
|
|
36
|
+
useRouter,
|
|
37
|
+
useParams,
|
|
38
|
+
useQueryParams,
|
|
39
|
+
} from '@sigmela/router';
|
|
23
40
|
|
|
24
41
|
function HomeScreen() {
|
|
25
42
|
const router = useRouter();
|
|
@@ -49,27 +66,37 @@ export default function App() {
|
|
|
49
66
|
```
|
|
50
67
|
|
|
51
68
|
Quick start (tabs + global stack)
|
|
52
|
-
|
|
53
69
|
```tsx
|
|
54
70
|
import { NavigationStack, Router, Navigation, TabBar } from '@sigmela/router';
|
|
55
71
|
|
|
56
|
-
const homeStack = new NavigationStack()
|
|
57
|
-
|
|
72
|
+
const homeStack = new NavigationStack().addScreen('/', HomeScreen, {
|
|
73
|
+
header: { title: 'Home' },
|
|
74
|
+
});
|
|
58
75
|
|
|
59
76
|
const catalogStack = new NavigationStack()
|
|
60
77
|
.addScreen('/catalog', CatalogScreen, { header: { title: 'Catalog' } })
|
|
61
|
-
.addScreen('/catalog/products/:productId', ProductScreen, {
|
|
62
|
-
header: { title: 'Product' }
|
|
78
|
+
.addScreen('/catalog/products/:productId', ProductScreen, {
|
|
79
|
+
header: { title: 'Product' },
|
|
63
80
|
});
|
|
64
81
|
|
|
65
|
-
const globalStack = new NavigationStack()
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
});
|
|
82
|
+
const globalStack = new NavigationStack().addModal('/auth', AuthScreen, {
|
|
83
|
+
header: { title: 'Sign in' },
|
|
84
|
+
});
|
|
69
85
|
|
|
70
|
-
const tabBar = new TabBar(
|
|
71
|
-
.addTab({
|
|
72
|
-
|
|
86
|
+
const tabBar = new TabBar()
|
|
87
|
+
.addTab({
|
|
88
|
+
key: 'home',
|
|
89
|
+
stack: homeStack,
|
|
90
|
+
title: 'Home',
|
|
91
|
+
// iOS: SF Symbols, Android/Web: image source
|
|
92
|
+
icon: { sfSymbolName: 'house' },
|
|
93
|
+
})
|
|
94
|
+
.addTab({
|
|
95
|
+
key: 'catalog',
|
|
96
|
+
stack: catalogStack,
|
|
97
|
+
title: 'Catalog',
|
|
98
|
+
icon: { sfSymbolName: 'bag' },
|
|
99
|
+
});
|
|
73
100
|
|
|
74
101
|
const router = new Router({ root: tabBar, global: globalStack });
|
|
75
102
|
|
|
@@ -78,40 +105,43 @@ export default function App() {
|
|
|
78
105
|
}
|
|
79
106
|
```
|
|
80
107
|
|
|
81
|
-
|
|
108
|
+
Custom tab bar (optional)
|
|
109
|
+
```tsx
|
|
110
|
+
import { TabBar, type TabBarProps } from '@sigmela/router';
|
|
82
111
|
|
|
83
|
-
|
|
112
|
+
function MyTabBar({ tabs, activeIndex, onTabPress }: TabBarProps) {
|
|
113
|
+
return (
|
|
114
|
+
<div className="my-tabs">
|
|
115
|
+
{tabs.map((t, i) => (
|
|
116
|
+
<button key={t.tabKey} onClick={() => onTabPress(i)} aria-pressed={i === activeIndex}>
|
|
117
|
+
{t.title}
|
|
118
|
+
</button>
|
|
119
|
+
))}
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const tabBar = new TabBar({ component: MyTabBar })
|
|
125
|
+
.addTab({ key: 'home', stack: homeStack, title: 'Home' })
|
|
126
|
+
.addTab({ key: 'catalog', stack: catalogStack, title: 'Catalog' });
|
|
127
|
+
```
|
|
84
128
|
|
|
129
|
+
Appearance
|
|
130
|
+
Pass `appearance` to `Navigation` to style headers, screens, and the tab bar.
|
|
85
131
|
```tsx
|
|
86
|
-
import {
|
|
132
|
+
import type { NavigationAppearance } from '@sigmela/router';
|
|
87
133
|
|
|
88
134
|
const appearance: NavigationAppearance = {
|
|
89
135
|
tabBar: {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
titleFontWeight: '600',
|
|
97
|
-
iconColor: '#999999',
|
|
98
|
-
iconColorActive: '#007AFF',
|
|
99
|
-
rippleColor: '#00000020',
|
|
100
|
-
activeIndicatorColor: '#007AFF',
|
|
101
|
-
},
|
|
102
|
-
// iOS-specific
|
|
103
|
-
tintColor: '#007AFF',
|
|
104
|
-
standardAppearance: {
|
|
105
|
-
tabBarBackgroundColor: '#ffffff',
|
|
106
|
-
tabBarShadowColor: 'transparent',
|
|
107
|
-
},
|
|
108
|
-
scrollEdgeAppearance: {
|
|
109
|
-
tabBarBackgroundColor: 'rgba(255,255,255,0.9)',
|
|
110
|
-
tabBarShadowColor: 'transparent',
|
|
111
|
-
},
|
|
136
|
+
backgroundColor: '#fff',
|
|
137
|
+
iconColor: '#8e8e93',
|
|
138
|
+
iconColorActive: '#000',
|
|
139
|
+
title: { fontSize: 11, color: '#555', activeColor: '#000' },
|
|
140
|
+
// Android-only options:
|
|
141
|
+
androidRippleColor: 'rgba(0,0,0,0.1)',
|
|
112
142
|
},
|
|
113
|
-
|
|
114
|
-
backgroundColor: '#
|
|
143
|
+
header: {
|
|
144
|
+
backgroundColor: '#fff',
|
|
115
145
|
},
|
|
116
146
|
};
|
|
117
147
|
|
|
@@ -120,227 +150,98 @@ export default function App() {
|
|
|
120
150
|
}
|
|
121
151
|
```
|
|
122
152
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
-
|
|
126
|
-
-
|
|
127
|
-
-
|
|
128
|
-
-
|
|
129
|
-
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
-
|
|
136
|
-
|
|
137
|
-
-
|
|
138
|
-
-
|
|
139
|
-
-
|
|
140
|
-
|
|
141
|
-
-
|
|
142
|
-
-
|
|
143
|
-
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
-
|
|
150
|
-
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
-
|
|
157
|
-
-
|
|
158
|
-
-
|
|
159
|
-
- Pops from the highest priority layer that can pop: global → current tab → root
|
|
160
|
-
- “Seed” screens (the very first screen of a stack) are protected from popping
|
|
161
|
-
- setRoot(nextRoot: TabBar | NavigationStack, options?: { transition?: ScreenOptions['stackAnimation'] }): void
|
|
162
|
-
- Switch between auth flow and main app, etc.; reseeds the new root
|
|
163
|
-
- transition is applied to the root layer when changing
|
|
164
|
-
- onTabIndexChange(index: number): void and setActiveTabIndex(index: number): void
|
|
165
|
-
- ensureTabSeed(index: number): void
|
|
166
|
-
- Ensures the first screen of a tab stack is seeded when the tab becomes active
|
|
167
|
-
- getVisibleRoute(): {
|
|
168
|
-
routeId: string; stackId?: string; tabIndex?: number; scope: 'global' | 'tab' | 'root'; params?; query?; path?; pattern?
|
|
169
|
-
} | null
|
|
170
|
-
- subscribe(listener): unsubscribe
|
|
171
|
-
- subscribeStack(stackId, listener): unsubscribe
|
|
172
|
-
- subscribeActiveTab(listener): unsubscribe
|
|
173
|
-
- getStackHistory(stackId): HistoryItem[] (useful for debugging/analytics)
|
|
174
|
-
- hasTabBar(): boolean, getRootStackId(): string | undefined, getGlobalStackId(): string | undefined, getRootTransition(): ScreenOptions['stackAnimation'] | undefined
|
|
175
|
-
|
|
176
|
-
Components
|
|
177
|
-
|
|
178
|
-
- Navigation: top-level view that renders the root layer and the global overlay. Usage: `<Navigation router={router} appearance={appearance} />`.
|
|
179
|
-
- StackRenderer: renders a single `NavigationStack` (advanced use, usually not needed directly).
|
|
180
|
-
|
|
181
|
-
Hooks
|
|
182
|
-
|
|
183
|
-
- useRouter(): Router
|
|
184
|
-
- useCurrentRoute(): VisibleRoute
|
|
185
|
-
- useParams<TParams>(): TParams
|
|
186
|
-
- useQueryParams<TQuery>(): TQuery
|
|
187
|
-
- useRoute(): { params, query, pattern?, path? }
|
|
188
|
-
|
|
189
|
-
TabBar builder
|
|
190
|
-
|
|
191
|
-
```ts
|
|
192
|
-
new TabBar({
|
|
193
|
-
sidebarAdaptable?: boolean,
|
|
194
|
-
disablePageAnimations?: boolean,
|
|
195
|
-
hapticFeedbackEnabled?: boolean,
|
|
196
|
-
scrollEdgeAppearance?: 'default' | 'opaque' | 'transparent',
|
|
197
|
-
minimizeBehavior?: 'automatic' | 'onScrollDown' | 'onScrollUp' | 'never',
|
|
198
|
-
})
|
|
199
|
-
.addTab({
|
|
200
|
-
stack?: NavigationStack,
|
|
201
|
-
screen?: React.ComponentType,
|
|
202
|
-
title?: string,
|
|
203
|
-
badge?: string,
|
|
204
|
-
icon?: ImageSource | AppleIcon | (({ focused }: { focused: boolean }) => ImageSource | AppleIcon),
|
|
205
|
-
activeTintColor?: ColorValue,
|
|
206
|
-
hidden?: boolean,
|
|
207
|
-
testID?: string,
|
|
208
|
-
role?: 'search',
|
|
209
|
-
freezeOnBlur?: boolean,
|
|
210
|
-
lazy?: boolean,
|
|
211
|
-
iconInsets?: { top?: number; bottom?: number; left?: number; right?: number },
|
|
212
|
-
})
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
You can update badges at runtime via:
|
|
216
|
-
- setBadge(tabIndex, badge: string | null)
|
|
217
|
-
- setTabBarConfig(partial)
|
|
218
|
-
|
|
219
|
-
For styling, use the `appearance` prop on the Navigation component instead.
|
|
153
|
+
API Reference
|
|
154
|
+
- Classes
|
|
155
|
+
- NavigationStack
|
|
156
|
+
- `constructor(idOrOptions?: string | ScreenOptions, defaults?: ScreenOptions)`
|
|
157
|
+
- `addScreen(path: string, component: Component | { component, controller? }, options?: ScreenOptions)`
|
|
158
|
+
- `addModal(path: string, component: Component | { component, controller? }, options?: ScreenOptions)`
|
|
159
|
+
- TabBar
|
|
160
|
+
- `constructor(config?: { component?: ComponentType<TabBarProps> })`
|
|
161
|
+
- `addTab({ key: string, stack?: NavigationStack, screen?: Component, title?: string, icon?: ImageSource | { sfSymbolName | imageSource | templateSource } })`
|
|
162
|
+
- `setBadge(index: number, badge: string | null)`
|
|
163
|
+
- Router
|
|
164
|
+
- `constructor({ root: TabBar | NavigationStack, global?: NavigationStack, screenOptions?: ScreenOptions })`
|
|
165
|
+
- `navigate(path: string)` — push a route (e.g. `/catalog/products/42?ref=home`)
|
|
166
|
+
- `replace(path: string, dedupe?: boolean)` — replace top route; `dedupe` avoids no-op replaces on web
|
|
167
|
+
- `goBack()` — pop a single screen within the active stack (or global)
|
|
168
|
+
- `setRoot(nextRoot: TabBar | NavigationStack, options?: { transition?: ScreenOptions['stackAnimation'] })`
|
|
169
|
+
- `getVisibleRoute()` — returns `{ scope, path, params, query, ... } | null`
|
|
170
|
+
|
|
171
|
+
- Components
|
|
172
|
+
- `Navigation` — the renderer. Props: `{ router: Router; appearance?: NavigationAppearance }`
|
|
173
|
+
|
|
174
|
+
- Hooks
|
|
175
|
+
- `useRouter()` — access the router instance
|
|
176
|
+
- `useCurrentRoute()` — subscribe to the currently visible route
|
|
177
|
+
- `useParams<T>()` — typed path params
|
|
178
|
+
- `useQueryParams<T>()` — typed query params
|
|
179
|
+
- `useRoute()` — raw route context `{ presentation, params, query, pattern, path }`
|
|
180
|
+
- `useTabBar()` — access the current `TabBar` (inside a tab screen)
|
|
181
|
+
|
|
182
|
+
- Utilities
|
|
183
|
+
- `createController<TParams, TQuery>(controller)` — build controllers for guarded navigation
|
|
184
|
+
|
|
185
|
+
- Types
|
|
186
|
+
- `ScreenOptions` — subset of `react-native-screens` Screen props plus `{ header?, tabBarIcon? }`
|
|
187
|
+
- `NavigationAppearance` — `{ tabBar?, screen?, header? }`
|
|
188
|
+
- `TabBarProps` — props passed to a custom tab bar component
|
|
220
189
|
|
|
221
190
|
Screen options
|
|
191
|
+
- `header`: `ScreenStackHeaderConfigProps` (from react-native-screens). If `title` is falsy, the header is hidden.
|
|
192
|
+
- `stackPresentation`: `'push' | 'modal' | ...'` (react-native-screens)
|
|
193
|
+
- `stackAnimation`: `'slide_from_right' | 'fade' | ...'` (react-native-screens)
|
|
194
|
+
- `tabBarIcon` (web helper): string or `{ sfSymbolName?: string }` for default web icon rendering
|
|
222
195
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
- Per-screen options come from `addScreen(path, component, options)`
|
|
226
|
-
- Per-stack defaults via `new NavigationStack(defaultOptions)`
|
|
227
|
-
- Global overrides via `new Router({ screenOptions })`
|
|
228
|
-
The effective options are merged in this order: stack defaults → per-screen → router overrides.
|
|
229
|
-
|
|
230
|
-
Header configuration:
|
|
231
|
-
```tsx
|
|
232
|
-
// Header with title (visible)
|
|
233
|
-
{ header: { title: 'My Screen' } }
|
|
234
|
-
|
|
235
|
-
// Hidden header (explicit)
|
|
236
|
-
{ header: { hidden: true } }
|
|
237
|
-
|
|
238
|
-
// No header specified = hidden by default
|
|
239
|
-
{ /* header will be hidden automatically */ }
|
|
240
|
-
|
|
241
|
-
// Custom header with background color
|
|
242
|
-
{ header: { title: 'Settings', backgroundColor: '#007AFF' } }
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
Modal screens:
|
|
196
|
+
Controllers (guarded/async navigation)
|
|
197
|
+
Controllers run before a screen is presented. Call `present(passProps?)` when you're ready to show the screen. Useful for auth checks, data prefetch, or conditional redirects.
|
|
246
198
|
```tsx
|
|
247
|
-
// Using addModal - automatically sets stackPresentation: 'modal'
|
|
248
|
-
const stack = new NavigationStack()
|
|
249
|
-
.addModal('/auth', AuthScreen, {
|
|
250
|
-
header: { title: 'Sign In' }
|
|
251
|
-
})
|
|
252
|
-
.addModal('/settings', SettingsScreen, {
|
|
253
|
-
header: { title: 'Settings' }
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
// Equivalent to using addScreen with explicit modal presentation
|
|
257
|
-
const stack = new NavigationStack()
|
|
258
|
-
.addScreen('/auth', AuthScreen, {
|
|
259
|
-
stackPresentation: 'modal',
|
|
260
|
-
header: { title: 'Sign In' }
|
|
261
|
-
});
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
Paths, params and query
|
|
265
|
-
|
|
266
|
-
- Paths use path-to-regexp under the hood. Examples:
|
|
267
|
-
- `/users/:userId`
|
|
268
|
-
- `/orders/:year/:month`
|
|
269
|
-
- Params are exposed via `useParams()`; query params via `useQueryParams()` and are parsed with query-string.
|
|
270
|
-
- When you call `router.navigate('/users/123?tab=posts')`, your screen receives `{ userId: '123' }` as params and `{ tab: 'posts' }` as query.
|
|
271
|
-
|
|
272
|
-
### Controllers: delay screen presentation and pass props
|
|
273
|
-
|
|
274
|
-
You can attach a controller to a route to perform checks or async work before the screen is shown. If a controller is present, the screen is NOT pushed until the controller calls `present(passProps)`.
|
|
275
|
-
|
|
276
|
-
Definition:
|
|
277
|
-
|
|
278
|
-
```ts
|
|
279
199
|
import { createController } from '@sigmela/router';
|
|
280
200
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
})
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
Attach to a route:
|
|
201
|
+
const Details = {
|
|
202
|
+
component: DetailsScreen,
|
|
203
|
+
controller: createController<{ id: string }, { from?: string }>(async ({ params }, present) => {
|
|
204
|
+
const isSignedIn = await auth.check();
|
|
205
|
+
if (!isSignedIn) {
|
|
206
|
+
router.navigate('/auth');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
present({ fetched: await api.load(params.id) });
|
|
210
|
+
}),
|
|
211
|
+
};
|
|
294
212
|
|
|
295
|
-
|
|
296
|
-
new NavigationStack()
|
|
297
|
-
.addScreen('/catalog/products/:productId', {
|
|
298
|
-
controller: ProductController,
|
|
299
|
-
component: ProductScreen,
|
|
300
|
-
}, {
|
|
301
|
-
header: { title: 'Product' },
|
|
302
|
-
});
|
|
213
|
+
new NavigationStack().addScreen('/details/:id', Details);
|
|
303
214
|
```
|
|
304
215
|
|
|
305
|
-
|
|
216
|
+
Web behavior
|
|
217
|
+
- On web, the router listens to `pushState`, `replaceState`, and `popstate`. You can navigate by calling `router.navigate('/path')` or by updating `window.history` yourself; the router will stay in sync.
|
|
218
|
+
- Initial load deep links are expanded into a stack chain: `/a/b/c` seeds the stack with `/a` → `/a/b` → `/a/b/c` if those routes exist in the same stack.
|
|
219
|
+
- `goBack()` pops within the active stack (or global). The router avoids creating duplicate entries when switching tabs by using `replace` under the hood in the web tab bar.
|
|
306
220
|
|
|
221
|
+
Root switching (auth flows)
|
|
222
|
+
Switch between a login stack and the main tab bar at runtime. Optionally pass a transition for the change.
|
|
307
223
|
```tsx
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
function ProductScreen(props: ProductScreenProps) {
|
|
311
|
-
const { productId } = useParams<ProductParams>();
|
|
312
|
-
const { coupon } = useQueryParams<ProductQuery>();
|
|
313
|
-
return <Text>{props.preloadedTitle} #{productId} coupon={coupon ?? '—'}</Text>;
|
|
314
|
-
}
|
|
224
|
+
router.setRoot(loggedIn ? mainTabs : authStack, { transition: 'fade' });
|
|
315
225
|
```
|
|
316
226
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
Behavior highlights (verified by tests)
|
|
323
|
-
|
|
324
|
-
- Initial seeding: the first screen of the active tab (or root stack) is pushed automatically.
|
|
325
|
-
- Duplicate navigate to the same top screen with the same params is ignored.
|
|
326
|
-
- goBack pops from the global stack first (if any), then from the current tab’s stack, then from the root stack; seed screens are protected.
|
|
327
|
-
- Navigating to a route inside a tab switches the active tab and seeds it if needed.
|
|
328
|
-
- setRoot switches between TabBar and NavigationStack, applies an optional transition, rebuilds the registry, and reseeds the new root; subscribers to `subscribeRoot` are notified.
|
|
329
|
-
- replace updates old/new stack slices atomically to avoid stale entries and keeps per-stack updates O(1) [[memory:6631860]].
|
|
330
|
-
|
|
331
|
-
TypeScript
|
|
227
|
+
Badges and programmatic tab control
|
|
228
|
+
```ts
|
|
229
|
+
// Show a badge on the second tab
|
|
230
|
+
tabBar.setBadge(1, '3');
|
|
332
231
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
- Hooks: `useRouter`, `useCurrentRoute`, `useParams`, `useQueryParams`
|
|
337
|
-
- Core classes: `Router`, `NavigationStack`
|
|
232
|
+
// Switch active tab (e.g., from a screen)
|
|
233
|
+
useRouter().onTabIndexChange(2);
|
|
234
|
+
```
|
|
338
235
|
|
|
339
|
-
|
|
236
|
+
Example app
|
|
237
|
+
- This repo contains an `example` app demonstrating tabs, stacks, and appearance.
|
|
340
238
|
|
|
341
|
-
|
|
342
|
-
-
|
|
239
|
+
Tips
|
|
240
|
+
- Prefer path-based navigation throughout your app: it keeps web and native in sync.
|
|
241
|
+
- Type your params and query with `useParams<T>()` and `useQueryParams<T>()` to get end-to-end type safety.
|
|
242
|
+
- On the web, remember to import `@sigmela/router/styles.css` once.
|
|
343
243
|
|
|
344
244
|
License
|
|
245
|
+
MIT
|
|
246
|
+
|
|
345
247
|
|
|
346
|
-
MIT
|
package/lib/module/styles.css
CHANGED
|
@@ -1,25 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
:root {
|
|
4
|
-
--tabs-transition: .2s ease-in-out;
|
|
5
|
-
--transition-standard-easing: cubic-bezier(.4, .0, .2, 1);
|
|
6
|
-
--transition-standard-in-time: .3s;
|
|
7
|
-
--transition-standard-out-time: .25s;
|
|
8
|
-
|
|
9
|
-
--transition-standard-in: var(--transition-standard-in-time) var(--transition-standard-easing);
|
|
10
|
-
--transition-standard-out: var(--transition-standard-out-time) var(--transition-standard-easing);
|
|
11
|
-
|
|
12
|
-
--background-color-true: #181818;
|
|
13
|
-
|
|
14
|
-
--background-color: var(--background-color-true);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
body {
|
|
19
|
-
margin: 0;
|
|
20
|
-
padding: 0;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
1
|
.screen-stack {
|
|
24
2
|
min-width: 100%;
|
|
25
3
|
width: 100%;
|
|
@@ -43,19 +21,19 @@ body {
|
|
|
43
21
|
}
|
|
44
22
|
|
|
45
23
|
.screen-stack[data-animation="navigation"].animating > .screen-stack-item {
|
|
46
|
-
transition: transform
|
|
24
|
+
transition: transform .3s cubic-bezier(.4, .0, .2, 1), filter .3s cubic-bezier(.4, .0, .2, 1)
|
|
47
25
|
}
|
|
48
26
|
|
|
49
27
|
.screen-stack[data-animation="navigation"].animating.backwards > .screen-stack-item {
|
|
50
|
-
transition: transform
|
|
28
|
+
transition: transform .25s cubic-bezier(.4, .0, .2, 1), filter .25s cubic-bezier(.4, .0, .2, 1);
|
|
51
29
|
}
|
|
52
30
|
|
|
53
31
|
.screen-stack[data-animation="modal"].animating > .screen-stack-item {
|
|
54
|
-
transition: transform
|
|
32
|
+
transition: transform .3s cubic-bezier(.4, .0, .2, 1), filter .3s cubic-bezier(.4, .0, .2, 1)
|
|
55
33
|
}
|
|
56
34
|
|
|
57
35
|
.screen-stack[data-animation="modal"].animating.backwards > .screen-stack-item {
|
|
58
|
-
transition: transform
|
|
36
|
+
transition: transform .25s cubic-bezier(.4, .0, .2, 1), filter .25s cubic-bezier(.4, .0, .2, 1);
|
|
59
37
|
}
|
|
60
38
|
|
|
61
39
|
.tab-stacks-container {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sigmela/router",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
4
4
|
"description": "React Native Router",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
"default": "./lib/module/index.js"
|
|
12
12
|
},
|
|
13
13
|
"./package.json": "./package.json",
|
|
14
|
-
"./styles.css":
|
|
14
|
+
"./styles.css": {
|
|
15
|
+
"source": "./src/styles.css",
|
|
16
|
+
"default": "./lib/module/styles.css"
|
|
17
|
+
}
|
|
15
18
|
},
|
|
16
19
|
"files": [
|
|
17
20
|
"lib",
|