@sigmela/router 0.3.0 → 0.3.1

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 CHANGED
@@ -8,9 +8,11 @@ This library is **URL-first**: you navigate by **paths** (`/users/42?tab=posts`)
8
8
 
9
9
  - **Stacks**: predictable stack-based navigation
10
10
  - **Tabs**: `TabBar` with native + web renderers (or custom tab bar)
11
+ - **Drawer**: side-panel navigation (`Drawer`)
11
12
  - **Split view**: master/details navigation (`SplitView`)
12
13
  - **Modals & sheets**: via `stackPresentation` (`modal`, `sheet`, …)
13
14
  - **Controllers**: async/guarded navigation (only present when ready)
15
+ - **Appearance**: global styling via `NavigationAppearance` (tab bar colors, fonts, blur effects, etc.)
14
16
  - **Web History integration**: keeps Router state in sync with `pushState`, `replaceState`, `popstate`
15
17
  - **Dynamic root**: swap root navigation tree at runtime (`router.setRoot`)
16
18
  - **Type-safe hooks**: `useParams`, `useQueryParams`, `useRoute`, `useCurrentRoute`
@@ -27,7 +29,7 @@ yarn add @sigmela/native-sheet
27
29
 
28
30
  - `react`
29
31
  - `react-native`
30
- - `react-native-screens` (>= `4.18.0`)
32
+ - `react-native-screens` (>= `4.24.0`)
31
33
  - `@sigmela/native-sheet` (>= `0.0.1`) — only if you use sheets
32
34
 
33
35
  ### Web CSS
@@ -292,10 +294,45 @@ Key methods:
292
294
  Notes:
293
295
  - Exactly one of `stack`, `node`, `screen` must be provided.
294
296
  - Use `prefix` to mount a tab's routes under a base path (e.g. `/mail`).
297
+ - All `TabsScreenProps` from `react-native-screens` are forwarded to native. This includes lifecycle events (`onWillAppear`, `onDidAppear`, `onWillDisappear`, `onDidDisappear`), accessibility props (`testID`, `accessibilityLabel`, `tabBarItemTestID`, `tabBarItemAccessibilityLabel`), `orientation`, `systemItem`, `freezeContents`, `placeholder`, `scrollEdgeEffects`, badge styling, and more.
298
+
299
+ #### `setTabBarConfig()`
300
+
301
+ Runtime tab bar configuration:
302
+
303
+ ```tsx
304
+ tabBar.setTabBarConfig({
305
+ bottomAccessory: (environment) => <MiniPlayer layout={environment} />, // iOS 26+
306
+ experimentalControlNavigationStateInJS: true,
307
+ });
308
+ ```
295
309
 
296
310
  Web behavior note:
297
311
  - The built-in **web** tab bar renderer resets Router history on tab switch (to keep URL and Router state consistent) using `router.reset(firstRoutePath)`.
298
312
 
313
+ ### `Drawer`
314
+
315
+ `Drawer` provides side-panel navigation, similar to `TabBar` but with a slide-out panel.
316
+
317
+ ```tsx
318
+ import { Drawer, NavigationStack } from '@sigmela/router';
319
+
320
+ const homeStack = new NavigationStack().addScreen('/', HomeScreen);
321
+ const settingsStack = new NavigationStack().addScreen('/settings', SettingsScreen);
322
+
323
+ const drawer = new Drawer({ width: 280 })
324
+ .addTab({ key: 'home', stack: homeStack, title: 'Home' })
325
+ .addTab({ key: 'settings', stack: settingsStack, title: 'Settings' });
326
+ ```
327
+
328
+ Key methods:
329
+ - `addTab({ key, stack?, node?, screen?, prefix?, title?, icon?, ... })`
330
+ - `open()`, `close()`, `toggle()` — manage drawer state
331
+ - `getIsOpen()` — current open state
332
+ - `subscribeOpenState(listener)` — subscribe to open/close changes
333
+ - `onIndexChange(index)` — switch active tab
334
+ - `setBadge(index, badge | null)`
335
+
299
336
  ### `SplitView`
300
337
 
301
338
  `SplitView` renders **two stacks**: `primary` and `secondary`.
@@ -350,6 +387,54 @@ stack.addScreen('/users/:userId', UserDetails);
350
387
 
351
388
  If you never call `present()`, the screen is not pushed/replaced.
352
389
 
390
+ ## Appearance
391
+
392
+ Pass `NavigationAppearance` to `<Navigation>` to customize styling globally:
393
+
394
+ ```tsx
395
+ import { Navigation, type NavigationAppearance } from '@sigmela/router';
396
+
397
+ const appearance: NavigationAppearance = {
398
+ tabBar: {
399
+ backgroundColor: '#ffffff',
400
+ iconColor: '#999999',
401
+ iconColorActive: '#007AFF',
402
+ badgeBackgroundColor: '#FF3B30',
403
+ iOSShadowColor: '#00000020',
404
+ title: {
405
+ fontFamily: 'Inter',
406
+ fontSize: 10,
407
+ color: '#999999',
408
+ activeColor: '#007AFF',
409
+ activeFontSize: 12, // Android: active tab title font size
410
+ },
411
+
412
+ // Android-specific
413
+ androidActiveIndicatorEnabled: true,
414
+ androidActiveIndicatorColor: '#007AFF20',
415
+ androidRippleColor: '#007AFF10',
416
+ labelVisibilityMode: 'labeled',
417
+
418
+ // Tab bar behavior
419
+ hidden: false, // hide/show the tab bar
420
+ tintColor: '#007AFF', // iOS: selected tab tint + glow color
421
+ controllerMode: 'automatic', // iOS 18+: 'automatic' | 'tabBar' | 'tabSidebar'
422
+ minimizeBehavior: 'automatic', // iOS 26+: 'automatic' | 'never' | 'onScrollDown' | 'onScrollUp'
423
+ nativeContainerBackgroundColor: '#fff', // native container background
424
+ iOSBlurEffect: 'systemDefault', // iOS: tab bar blur effect
425
+ },
426
+ header: { /* ScreenStackHeaderConfigProps */ },
427
+ sheet: {
428
+ cornerRadius: 16,
429
+ backgroundColor: '#ffffff',
430
+ },
431
+ };
432
+
433
+ export default function App() {
434
+ return <Navigation router={router} appearance={appearance} />;
435
+ }
436
+ ```
437
+
353
438
  ## Hooks
354
439
 
355
440
  ### `useRouter()`
@@ -410,6 +495,27 @@ function ScreenInsideTabs() {
410
495
  }
411
496
  ```
412
497
 
498
+ ### `useTabBarHeight()`
499
+
500
+ Returns the tab bar height constant (`57`). Useful for bottom padding.
501
+
502
+ ### `useDrawer()`
503
+
504
+ Returns the nearest `Drawer` from context (only inside drawer screens).
505
+
506
+ ```tsx
507
+ import { useDrawer } from '@sigmela/router';
508
+
509
+ function ScreenInsideDrawer() {
510
+ const drawer = useDrawer();
511
+ return <Button title="Open menu" onPress={() => drawer.open()} />;
512
+ }
513
+ ```
514
+
515
+ ### `useSplitView()`
516
+
517
+ Returns the nearest `SplitView` from context (only inside split view screens).
518
+
413
519
  ## Web integration
414
520
 
415
521
  ### History API syncing
@@ -5,6 +5,7 @@ import { DrawerContext } from "./DrawerContext.js";
5
5
  import { useRouter } from "../RouterContext.js";
6
6
  import { ScreenStackItem } from 'react-native-screens';
7
7
  import { Pressable, StyleSheet, View, Text } from 'react-native';
8
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
8
9
  import { useCallback, useSyncExternalStore, memo, useEffect, useState, useMemo } from 'react';
9
10
  import Animated, { useSharedValue, useAnimatedStyle, withTiming, Easing } from 'react-native-reanimated';
10
11
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -43,6 +44,7 @@ export const RenderDrawer = /*#__PURE__*/memo(({
43
44
  appearance = {}
44
45
  }) => {
45
46
  const router = useRouter();
47
+ const insets = useSafeAreaInsets();
46
48
  const drawerWidth = drawer.width;
47
49
  const subscribe = useCallback(cb => drawer.subscribe(cb), [drawer]);
48
50
  const snapshot = useSyncExternalStore(subscribe, drawer.getState, drawer.getState);
@@ -143,7 +145,9 @@ export const RenderDrawer = /*#__PURE__*/memo(({
143
145
  isOpen: isOpen,
144
146
  onClose: handleOverlayPress
145
147
  }) : /*#__PURE__*/_jsx(View, {
146
- style: styles.sidebarContent,
148
+ style: [styles.sidebarContent, {
149
+ paddingTop: insets.top + 12
150
+ }],
147
151
  children: tabs.map((tab, i) => {
148
152
  const isActive = i === index;
149
153
  return /*#__PURE__*/_jsx(Pressable, {
@@ -200,8 +204,7 @@ const styles = StyleSheet.create({
200
204
  backgroundColor: '#ffffff'
201
205
  },
202
206
  sidebarContent: {
203
- flex: 1,
204
- paddingTop: 12
207
+ flex: 1
205
208
  },
206
209
  main: {
207
210
  flex: 1,
@@ -3,7 +3,7 @@
3
3
  import { StackRenderer } from "../StackRenderer.js";
4
4
  import { TabBarContext } from "./TabBarContext.js";
5
5
  import { useRouter } from "../RouterContext.js";
6
- import { BottomTabsScreen, BottomTabs, ScreenStackItem } from 'react-native-screens';
6
+ import { Tabs, ScreenStackItem } from 'react-native-screens';
7
7
  import { Platform, StyleSheet, View } from 'react-native';
8
8
  import { useCallback, useSyncExternalStore, memo, useEffect, useState, useMemo } from 'react';
9
9
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -178,7 +178,13 @@ export const RenderTabBar = /*#__PURE__*/memo(({
178
178
  title,
179
179
  backgroundColor,
180
180
  badgeBackgroundColor,
181
- iOSShadowColor
181
+ iOSShadowColor,
182
+ hidden,
183
+ tintColor,
184
+ controllerMode,
185
+ minimizeBehavior,
186
+ nativeContainerBackgroundColor,
187
+ iOSBlurEffect
182
188
  } = appearance?.tabBar ?? {};
183
189
  const onNativeFocusChange = useCallback(event => {
184
190
  const tabKey = event.nativeEvent.tabKey;
@@ -263,6 +269,7 @@ export const RenderTabBar = /*#__PURE__*/memo(({
263
269
  tabBarBackgroundColor: backgroundColor,
264
270
  tabBarItemTitleFontFamily: title?.fontFamily,
265
271
  tabBarItemTitleFontSize: title?.fontSize,
272
+ tabBarItemTitleFontSizeActive: title?.activeFontSize,
266
273
  tabBarItemTitleFontWeight: title?.fontWeight,
267
274
  tabBarItemTitleFontStyle: title?.fontStyle,
268
275
  tabBarItemTitleFontColor: title?.color,
@@ -272,35 +279,53 @@ export const RenderTabBar = /*#__PURE__*/memo(({
272
279
  tabBarItemActiveIndicatorColor: androidActiveIndicatorColor,
273
280
  tabBarItemActiveIndicatorEnabled: androidActiveIndicatorEnabled,
274
281
  tabBarItemRippleColor: androidRippleColor,
275
- tabBarItemLabelVisibilityMode: labelVisibilityMode
276
- }), [backgroundColor, title?.fontFamily, title?.fontSize, title?.fontWeight, title?.fontStyle, title?.color, title?.activeColor, iconColor, iconColorActive, androidActiveIndicatorColor, androidActiveIndicatorEnabled, androidRippleColor, labelVisibilityMode]);
277
- const iosState = useMemo(() => ({
282
+ tabBarItemLabelVisibilityMode: labelVisibilityMode,
283
+ tabBarHidden: hidden,
284
+ tabBarTintColor: tintColor,
285
+ tabBarControllerMode: controllerMode,
286
+ tabBarMinimizeBehavior: minimizeBehavior,
287
+ nativeContainerStyle: nativeContainerBackgroundColor ? {
288
+ backgroundColor: nativeContainerBackgroundColor
289
+ } : undefined
290
+ }), [backgroundColor, title?.fontFamily, title?.fontSize, title?.activeFontSize, title?.fontWeight, title?.fontStyle, title?.color, title?.activeColor, iconColor, iconColorActive, androidActiveIndicatorColor, androidActiveIndicatorEnabled, androidRippleColor, labelVisibilityMode, hidden, tintColor, controllerMode, minimizeBehavior, nativeContainerBackgroundColor]);
291
+ const iosNormalState = useMemo(() => ({
278
292
  tabBarItemTitleFontFamily: title?.fontFamily,
279
293
  tabBarItemTitleFontSize: title?.fontSize,
280
294
  tabBarItemTitleFontWeight: title?.fontWeight,
281
295
  tabBarItemTitleFontStyle: title?.fontStyle,
282
296
  tabBarItemTitleFontColor: title?.color,
283
297
  tabBarItemBadgeBackgroundColor: badgeBackgroundColor,
284
- tabBarItemTitleFontColorActive: title?.activeColor ?? title?.color,
285
- tabBarItemIconColorActive: iconColorActive,
286
298
  tabBarItemIconColor: iconColor
299
+ }), [title?.fontFamily, title?.fontSize, title?.fontWeight, title?.fontStyle, title?.color, badgeBackgroundColor, iconColor]);
300
+ const iosSelectedState = useMemo(() => ({
301
+ tabBarItemTitleFontFamily: title?.fontFamily,
302
+ tabBarItemTitleFontSize: title?.fontSize,
303
+ tabBarItemTitleFontWeight: title?.fontWeight,
304
+ tabBarItemTitleFontStyle: title?.fontStyle,
305
+ tabBarItemTitleFontColor: title?.activeColor ?? title?.color,
306
+ tabBarItemBadgeBackgroundColor: badgeBackgroundColor,
307
+ tabBarItemIconColor: iconColorActive ?? iconColor
287
308
  }), [title?.fontFamily, title?.fontSize, title?.fontWeight, title?.fontStyle, title?.color, title?.activeColor, badgeBackgroundColor, iconColorActive, iconColor]);
288
309
  const iosAppearance = useMemo(() => Platform.select({
289
310
  default: undefined,
290
311
  ios: {
291
312
  tabBarBackgroundColor: backgroundColor,
292
313
  tabBarShadowColor: iOSShadowColor,
314
+ tabBarBlurEffect: iOSBlurEffect,
293
315
  compactInline: {
294
- normal: iosState
316
+ normal: iosNormalState,
317
+ selected: iosSelectedState
295
318
  },
296
319
  stacked: {
297
- normal: iosState
320
+ normal: iosNormalState,
321
+ selected: iosSelectedState
298
322
  },
299
323
  inline: {
300
- normal: iosState
324
+ normal: iosNormalState,
325
+ selected: iosSelectedState
301
326
  }
302
327
  }
303
- }), [backgroundColor, iOSShadowColor, iosState]);
328
+ }), [backgroundColor, iOSShadowColor, iOSBlurEffect, iosNormalState, iosSelectedState]);
304
329
  const CustomTabBar = config.component;
305
330
  const tabIcons = useMemo(() => tabs.map(tab => getTabIcon(tab)), [tabs]);
306
331
  const [visited, setVisited] = useState({});
@@ -358,25 +383,30 @@ export const RenderTabBar = /*#__PURE__*/memo(({
358
383
  stackAnimation: "slide_from_right",
359
384
  children: /*#__PURE__*/_jsx(TabBarContext.Provider, {
360
385
  value: tabBar,
361
- children: /*#__PURE__*/_jsx(BottomTabs, {
386
+ children: /*#__PURE__*/_jsx(Tabs.Host, {
362
387
  onNativeFocusChange: onNativeFocusChange,
388
+ bottomAccessory: config.bottomAccessory,
389
+ experimentalControlNavigationStateInJS: config.experimentalControlNavigationStateInJS,
363
390
  ...containerProps,
364
391
  children: tabs.map((tab, i) => {
365
392
  const isFocused = tab.tabKey === tabs[index]?.tabKey;
366
393
  const stack = tabBar.stacks[tab.tabKey];
367
394
  const node = tabBar.nodes[tab.tabKey];
368
395
  const Screen = tabBar.screens[tab.tabKey];
369
- const icon = tabIcons[i];
370
- return /*#__PURE__*/_jsx(BottomTabsScreen, {
396
+ const convertedIcon = tabIcons[i];
397
+ const {
398
+ icon: _icon,
399
+ selectedIcon: _selectedIcon,
400
+ tabPrefix: _prefix,
401
+ ...tabScreenProps
402
+ } = tab;
403
+ return /*#__PURE__*/_jsx(Tabs.Screen, {
404
+ ...tabScreenProps,
371
405
  scrollEdgeAppearance: iosAppearance,
372
406
  standardAppearance: iosAppearance,
373
407
  isFocused: isFocused,
374
- tabKey: tab.tabKey,
375
- title: tab.title,
376
- badgeValue: tab.badgeValue,
377
- specialEffects: tab.specialEffects,
378
- icon: icon?.icon,
379
- selectedIcon: icon?.selectedIcon,
408
+ icon: convertedIcon?.icon,
409
+ selectedIcon: convertedIcon?.selectedIcon,
380
410
  children: stack ? /*#__PURE__*/_jsx(TabStackRenderer, {
381
411
  appearance: appearance,
382
412
  stack: stack
@@ -1094,9 +1094,18 @@
1094
1094
  }
1095
1095
 
1096
1096
  /* ==================== DESKTOP DRAWER (>= 641px) ==================== */
1097
+ /* Inverted logic: sidebar is visible by default, data-drawer-open='true' hides it.
1098
+ This way toggle() works on both viewports with a single boolean:
1099
+ isOpen=false (default) → mobile: hidden, desktop: visible
1100
+ isOpen=true (toggled) → mobile: visible, desktop: hidden */
1097
1101
  @media (min-width: 641px) {
1098
- /* On desktop, drawer is always visible — data-drawer-open is irrelevant */
1099
1102
  .drawer-sidebar {
1100
1103
  border-right: 1px solid rgba(0, 0, 0, 0.08);
1104
+ transition: margin-left var(--drawer-transition);
1105
+ }
1106
+
1107
+ .drawer-container[data-drawer-open='true'] .drawer-sidebar {
1108
+ margin-left: calc(-1 * var(--drawer-width));
1109
+ border-right: none;
1101
1110
  }
1102
1111
  }
@@ -4,7 +4,7 @@ import type { ComponentType } from 'react';
4
4
  import type { TabItem } from '../types';
5
5
  import React from 'react';
6
6
  import type { NavigationNode, NodeChild, NodeRoute } from '../navigationNode';
7
- import type { PlatformIcon } from 'react-native-screens';
7
+ import type { PlatformIcon, TabAccessoryComponentFactory } from 'react-native-screens';
8
8
  type LegacyIOSIconShape = {
9
9
  sfSymbolName: string;
10
10
  } | {
@@ -52,6 +52,14 @@ type TabBarConfig = Omit<InternalTabItem, 'tabKey' | 'key'> & {
52
52
  * Custom tab bar component (UI). Kept for compatibility.
53
53
  */
54
54
  component?: ComponentType<TabBarProps>;
55
+ /**
56
+ * iOS 26+ bottom accessory factory.
57
+ */
58
+ bottomAccessory?: TabAccessoryComponentFactory;
59
+ /**
60
+ * Experimental: control navigation state in JS.
61
+ */
62
+ experimentalControlNavigationStateInJS?: boolean;
55
63
  };
56
64
  type TabBarOptions = {
57
65
  component?: ComponentType<TabBarProps>;
@@ -1,5 +1,5 @@
1
1
  import type { ColorValue, StyleProp, ViewStyle, TextStyle } from 'react-native';
2
- import type { BottomTabsScreenProps, ScreenProps as RNSScreenProps, ScreenStackHeaderConfigProps, TabBarItemLabelVisibilityMode, TabBarMinimizeBehavior } from 'react-native-screens';
2
+ import type { TabsScreenProps, ScreenProps as RNSScreenProps, ScreenStackHeaderConfigProps, TabBarItemLabelVisibilityMode, TabBarMinimizeBehavior, TabBarControllerMode, TabAccessoryComponentFactory, TabsScreenBlurEffect } from 'react-native-screens';
3
3
  export type StackPresentationTypes = 'push' | 'modal' | 'modalRight' | 'transparentModal' | 'containedModal' | 'containedTransparentModal' | 'fullScreenModal' | 'formSheet' | 'pageSheet' | 'sheet';
4
4
  /**
5
5
  * Presentations that behave like modals (overlay on top of content).
@@ -9,7 +9,7 @@ export declare const MODAL_LIKE_PRESENTATIONS: ReadonlySet<StackPresentationType
9
9
  * Check if a presentation type is modal-like (renders as overlay).
10
10
  */
11
11
  export declare function isModalLikePresentation(presentation: StackPresentationTypes | undefined): boolean;
12
- export type TabItem = Omit<BottomTabsScreenProps, 'isFocused' | 'children'>;
12
+ export type TabItem = Omit<TabsScreenProps, 'isFocused' | 'children'>;
13
13
  export type NavigationState<Route extends TabItem> = {
14
14
  index: number;
15
15
  routes: Route[];
@@ -98,6 +98,8 @@ export type CompiledRoute = {
98
98
  };
99
99
  export type TabBarConfig = {
100
100
  tabBarMinimizeBehavior?: TabBarMinimizeBehavior;
101
+ bottomAccessory?: TabAccessoryComponentFactory;
102
+ experimentalControlNavigationStateInJS?: boolean;
101
103
  };
102
104
  export type SheetAppearance = {
103
105
  androidFullScreenTopInset?: number;
@@ -115,6 +117,12 @@ export interface NavigationAppearance {
115
117
  androidRippleColor?: ColorValue;
116
118
  labelVisibilityMode?: TabBarItemLabelVisibilityMode;
117
119
  iOSShadowColor?: ColorValue;
120
+ hidden?: boolean;
121
+ tintColor?: ColorValue;
122
+ controllerMode?: TabBarControllerMode;
123
+ minimizeBehavior?: TabBarMinimizeBehavior;
124
+ nativeContainerBackgroundColor?: ColorValue;
125
+ iOSBlurEffect?: TabsScreenBlurEffect;
118
126
  title: {
119
127
  fontFamily?: TextStyle['fontFamily'];
120
128
  fontSize?: TextStyle['fontSize'];
@@ -122,6 +130,7 @@ export interface NavigationAppearance {
122
130
  fontStyle?: TextStyle['fontStyle'];
123
131
  color?: TextStyle['color'];
124
132
  activeColor?: TextStyle['color'];
133
+ activeFontSize?: TextStyle['fontSize'];
125
134
  };
126
135
  };
127
136
  screen?: StyleProp<ViewStyle>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sigmela/router",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "React Native Router",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -71,8 +71,8 @@
71
71
  "@eslint/eslintrc": "^3.3.1",
72
72
  "@eslint/js": "^9.35.0",
73
73
  "@evilmartians/lefthook": "^1.12.3",
74
- "@react-native/babel-preset": "0.81.1",
75
- "@react-native/eslint-config": "^0.81.1",
74
+ "@react-native/babel-preset": "0.83.2",
75
+ "@react-native/eslint-config": "^0.83.2",
76
76
  "@release-it/conventional-changelog": "^10.0.1",
77
77
  "@sigmela/native-sheet": "^0.0.1",
78
78
  "@types/jest": "^29.5.14",
@@ -83,12 +83,13 @@
83
83
  "eslint-config-prettier": "^10.1.8",
84
84
  "eslint-plugin-prettier": "^5.5.4",
85
85
  "jest": "^29.7.0",
86
- "prettier": "^3.6.2",
87
- "react": "19.1.0",
88
- "react-native": "0.81.4",
89
- "react-native-builder-bob": "^0.40.13",
86
+ "prettier": "^3.8.1",
87
+ "react": "19.2.0",
88
+ "react-native": "0.83.2",
89
+ "react-native-builder-bob": "^0.40.18",
90
90
  "react-native-reanimated": "^4.2.2",
91
- "react-native-screens": "^4.18.0",
91
+ "react-native-safe-area-context": "^5.7.0",
92
+ "react-native-screens": "^4.24.0",
92
93
  "react-native-worklets": "^0.7.4",
93
94
  "release-it": "^19.0.4",
94
95
  "typescript": "^5.9.2"
@@ -98,6 +99,7 @@
98
99
  "react": "*",
99
100
  "react-native": ">=0.72.0",
100
101
  "react-native-reanimated": ">=3.0.0",
102
+ "react-native-safe-area-context": ">=4.0.0",
101
103
  "react-native-screens": ">=4.18.0"
102
104
  },
103
105
  "peerDependenciesMeta": {
@@ -106,6 +108,9 @@
106
108
  },
107
109
  "react-native-reanimated": {
108
110
  "optional": true
111
+ },
112
+ "react-native-safe-area-context": {
113
+ "optional": true
109
114
  }
110
115
  },
111
116
  "workspaces": [
@@ -182,6 +187,6 @@
182
187
  "nanoid": "^5.1.6",
183
188
  "path-to-regexp": "^8.3.0",
184
189
  "query-string": "^9.3.1",
185
- "react-transition-state": "^2.3.1"
190
+ "react-transition-state": "^2.3.3"
186
191
  }
187
192
  }