@terreno/ui 0.11.4-beta.2 → 0.11.4-beta.3

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/Common.d.ts CHANGED
@@ -2533,5 +2533,18 @@ export interface SidebarNavigationPanelProps {
2533
2533
  * Additional styles applied to each navigation item.
2534
2534
  */
2535
2535
  itemStyle?: StyleProp<ViewStyle>;
2536
+ /**
2537
+ * Controlled open state. When provided, the panel hides its internal hamburger
2538
+ * button and defers open/close to the caller.
2539
+ *
2540
+ * @platform mobile — the web sidebar is always visible; this prop is ignored on web.
2541
+ */
2542
+ isOpen?: boolean;
2543
+ /**
2544
+ * Called when the panel requests an open or close (controlled mode only).
2545
+ *
2546
+ * @platform mobile — the web sidebar is always visible; this prop is ignored on web.
2547
+ */
2548
+ onOpenChange?: (isOpen: boolean) => void;
2536
2549
  }
2537
2550
  export {};
@@ -1,22 +1,14 @@
1
+ import { Screen } from "expo-router/build/views/Screen";
1
2
  import { type FC } from "react";
2
3
  import type { SidebarNavigationPanelProps, SidebarNavigationProps } from "./Common";
3
4
  /**
4
- * Renders the hamburger button, drawer overlay, and children. Works without expo-router Navigator context.
5
- */
6
- export declare const SidebarNavigationPanel: FC<SidebarNavigationPanelProps>;
7
- /**
8
- * Custom expo-router navigator with a hamburger-triggered slide-in drawer.
9
- * Use in _layout.tsx files:
5
+ * Renders the bottom sheet overlay and children. Works without expo-router Navigator context.
10
6
  *
11
- * ```tsx
12
- * export default function SidebarLayout() {
13
- * return (
14
- * <SidebarNavigation
15
- * topItems={[{label: "Home", route: "index", iconName: "house"}]}
16
- * bottomItems={[{label: "Settings", route: "settings", iconName: "gear"}]}
17
- * />
18
- * );
19
- * }
20
- * ```
7
+ * Supports two modes:
8
+ * - Uncontrolled (default): manages open state internally and shows a floating hamburger button.
9
+ * - Controlled: caller provides isOpen + onOpenChange and owns the trigger (e.g. a header button).
21
10
  */
22
- export declare const SidebarNavigation: FC<SidebarNavigationProps>;
11
+ export declare const SidebarNavigationPanel: FC<SidebarNavigationPanelProps>;
12
+ export declare const SidebarNavigation: FC<SidebarNavigationProps> & {
13
+ Screen: typeof Screen;
14
+ };
@@ -1,18 +1,24 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { TabRouter } from "@react-navigation/native";
3
3
  import { Navigator, Slot } from "expo-router";
4
- import { useCallback, useEffect, useRef, useState } from "react";
5
- import { Animated, Dimensions, Pressable, View } from "react-native";
4
+ // Screen is not exported from expo-router's public API (exports.d.ts only exposes ScreenProps).
5
+ // Stack.Screen and Tabs.Screen use this same internal path. If expo-router upgrades break this,
6
+ // update the import path here — this is the only place in the codebase that references it.
7
+ // eslint-disable-next-line import/no-internal-modules
8
+ import { Screen } from "expo-router/build/views/Screen";
9
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
10
+ import { Animated, Dimensions, PanResponder, Pressable, View, } from "react-native";
11
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
6
12
  import { Badge } from "./Badge";
7
13
  import { SIDEBAR_BADGE_STATUS_MAP } from "./Common";
8
14
  import { Icon } from "./Icon";
9
15
  import { Text } from "./Text";
10
16
  import { useTheme } from "./Theme";
11
- const DRAWER_WIDTH = 280;
12
- const ITEM_HEIGHT = 48;
17
+ const ITEM_HEIGHT = 44;
13
18
  const ICON_SIZE = 20;
14
19
  const BACKDROP_OPACITY = 0.5;
15
- const ANIMATION_DURATION = 250;
20
+ const ANIMATION_DURATION = 300;
21
+ const DISMISS_THRESHOLD = 0.3;
16
22
  const SidebarItem = ({ item, isActive, onPress, itemStyle }) => {
17
23
  var _a;
18
24
  const { theme } = useTheme();
@@ -22,136 +28,192 @@ const SidebarItem = ({ item, isActive, onPress, itemStyle }) => {
22
28
  return (_jsxs(Pressable, { accessibilityLabel: item.label, accessibilityRole: "button", onPress: handlePress, style: [
23
29
  {
24
30
  alignItems: "center",
25
- backgroundColor: isActive ? theme.surface.secondaryLight : "transparent",
31
+ backgroundColor: isActive ? theme.surface.neutralLight : "transparent",
26
32
  borderRadius: theme.radius.default,
27
33
  flexDirection: "row",
28
- gap: 14,
34
+ gap: 12,
29
35
  height: ITEM_HEIGHT,
30
- marginHorizontal: 12,
31
- paddingHorizontal: 14,
36
+ marginHorizontal: 8,
37
+ paddingHorizontal: 12,
32
38
  },
33
39
  itemStyle,
34
- ], children: [_jsxs(View, { style: { alignItems: "center", justifyContent: "center", width: ICON_SIZE }, children: [_jsx(Icon, { color: isActive ? "primary" : "secondaryDark", iconName: item.iconName, size: "md" }), Boolean(item.badge) && (_jsx(View, { style: {
40
+ ], children: [_jsxs(View, { style: { alignItems: "center", justifyContent: "center", width: ICON_SIZE }, children: [_jsx(Icon, { color: isActive ? "primary" : "secondaryLight", iconName: item.iconName, size: "lg" }), Boolean(item.badge) && (_jsx(View, { style: {
35
41
  bottom: item.badge === true ? -4 : undefined,
36
42
  position: "absolute",
37
43
  right: -6,
38
44
  top: item.badge === true ? undefined : -4,
39
- }, children: _jsx(Badge, { maxValue: 99, status: SIDEBAR_BADGE_STATUS_MAP[(_a = item.badgeStatus) !== null && _a !== void 0 ? _a : "error"], value: item.badge === true ? undefined : String(item.badge), variant: item.badge === true ? "iconOnly" : "numberOnly" }) }))] }), _jsx(Text, { bold: isActive, color: isActive ? "primary" : "secondaryDark", size: "md", children: item.label })] }));
45
+ }, children: _jsx(Badge, { maxValue: 99, status: SIDEBAR_BADGE_STATUS_MAP[(_a = item.badgeStatus) !== null && _a !== void 0 ? _a : "error"], value: item.badge === true ? undefined : String(item.badge), variant: item.badge === true ? "iconOnly" : "numberOnly" }) }))] }), _jsx(Text, { bold: isActive, color: isActive ? "primary" : "secondaryLight", size: "md", children: item.label })] }));
40
46
  };
47
+ const SidebarHamburger = ({ onOpen }) => (_jsx(Pressable, { accessibilityLabel: "Open navigation menu", accessibilityRole: "button", onPress: onOpen, style: { alignItems: "center", height: 40, justifyContent: "center", width: 40 }, children: _jsx(Icon, { color: "primary", iconName: "bars", size: "md" }) }));
41
48
  /**
42
- * Renders the hamburger button, drawer overlay, and children. Works without expo-router Navigator context.
49
+ * Renders the bottom sheet overlay and children. Works without expo-router Navigator context.
50
+ *
51
+ * Supports two modes:
52
+ * - Uncontrolled (default): manages open state internally and shows a floating hamburger button.
53
+ * - Controlled: caller provides isOpen + onOpenChange and owns the trigger (e.g. a header button).
43
54
  */
44
- export const SidebarNavigationPanel = ({ topItems, bottomItems, activeRoute, onNavigate, children, panelStyle, itemStyle, }) => {
55
+ export const SidebarNavigationPanel = ({ topItems, bottomItems, activeRoute, onNavigate, children, panelStyle, itemStyle, isOpen: isOpenProp, onOpenChange, }) => {
45
56
  const { theme } = useTheme();
46
- const [isOpen, setIsOpen] = useState(false);
47
- const slideAnim = useRef(new Animated.Value(-DRAWER_WIDTH)).current;
57
+ const insets = useSafeAreaInsets();
58
+ const isControlled = isOpenProp !== undefined;
59
+ const [isOpenInternal, setIsOpenInternal] = useState(false);
60
+ const isOpen = isControlled ? isOpenProp : isOpenInternal;
61
+ const sheetHeight = useMemo(() => Dimensions.get("window").height * 0.65, []);
62
+ const slideAnim = useRef(new Animated.Value(sheetHeight)).current;
48
63
  const backdropAnim = useRef(new Animated.Value(0)).current;
49
- // Animate drawer open/close
64
+ const capturedSlideValue = useRef(0);
65
+ // Play open animation whenever isOpen becomes true
50
66
  useEffect(() => {
51
- if (isOpen) {
52
- Animated.parallel([
53
- Animated.timing(slideAnim, {
54
- duration: ANIMATION_DURATION,
55
- toValue: 0,
56
- useNativeDriver: true,
57
- }),
58
- Animated.timing(backdropAnim, {
59
- duration: ANIMATION_DURATION,
60
- toValue: BACKDROP_OPACITY,
61
- useNativeDriver: true,
62
- }),
63
- ]).start();
67
+ if (!isOpen) {
68
+ return;
69
+ }
70
+ slideAnim.setValue(sheetHeight);
71
+ backdropAnim.setValue(0);
72
+ Animated.parallel([
73
+ Animated.timing(slideAnim, {
74
+ duration: ANIMATION_DURATION,
75
+ toValue: 0,
76
+ useNativeDriver: true,
77
+ }),
78
+ Animated.timing(backdropAnim, {
79
+ duration: ANIMATION_DURATION,
80
+ toValue: BACKDROP_OPACITY,
81
+ useNativeDriver: true,
82
+ }),
83
+ ]).start();
84
+ }, [isOpen, slideAnim, backdropAnim, sheetHeight]);
85
+ // Play close animation then update state
86
+ const handleClose = useCallback(() => {
87
+ Animated.parallel([
88
+ Animated.timing(slideAnim, {
89
+ duration: ANIMATION_DURATION,
90
+ toValue: sheetHeight,
91
+ useNativeDriver: true,
92
+ }),
93
+ Animated.timing(backdropAnim, {
94
+ duration: ANIMATION_DURATION,
95
+ toValue: 0,
96
+ useNativeDriver: true,
97
+ }),
98
+ ]).start(() => {
99
+ if (isControlled) {
100
+ onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(false);
101
+ }
102
+ else {
103
+ setIsOpenInternal(false);
104
+ }
105
+ });
106
+ }, [isControlled, onOpenChange, slideAnim, backdropAnim, sheetHeight]);
107
+ const handleOpen = useCallback(() => {
108
+ if (isControlled) {
109
+ onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(true);
64
110
  }
65
111
  else {
66
- Animated.parallel([
67
- Animated.timing(slideAnim, {
68
- duration: ANIMATION_DURATION,
69
- toValue: -DRAWER_WIDTH,
70
- useNativeDriver: true,
71
- }),
72
- Animated.timing(backdropAnim, {
73
- duration: ANIMATION_DURATION,
74
- toValue: 0,
75
- useNativeDriver: true,
76
- }),
77
- ]).start();
112
+ setIsOpenInternal(true);
78
113
  }
79
- }, [isOpen, slideAnim, backdropAnim]);
80
- const handleOpen = useCallback(() => setIsOpen(true), []);
81
- const handleClose = useCallback(() => setIsOpen(false), []);
114
+ }, [isControlled, onOpenChange]);
82
115
  const handleNavigate = useCallback((route) => {
83
- setIsOpen(false);
116
+ handleClose();
84
117
  onNavigate(route);
85
- }, [onNavigate]);
86
- const screenHeight = Dimensions.get("window").height;
87
- return (_jsxs(View, { style: { flex: 1 }, children: [children, _jsx(Pressable, { accessibilityLabel: "Open navigation menu", accessibilityRole: "button", onPress: handleOpen, style: {
118
+ }, [handleClose, onNavigate]);
119
+ const panResponder = useMemo(() => PanResponder.create({
120
+ onMoveShouldSetPanResponder: (_, { dx, dy }) => Math.abs(dy) > Math.abs(dx) && dy > 4,
121
+ onPanResponderGrant: () => {
122
+ slideAnim.stopAnimation((value) => {
123
+ capturedSlideValue.current = value;
124
+ });
125
+ backdropAnim.stopAnimation();
126
+ },
127
+ onPanResponderMove: (_, { dy }) => {
128
+ const next = capturedSlideValue.current + dy;
129
+ if (next < 0) {
130
+ return;
131
+ }
132
+ slideAnim.setValue(next);
133
+ backdropAnim.setValue(BACKDROP_OPACITY * Math.max(0, 1 - next / sheetHeight));
134
+ },
135
+ onPanResponderRelease: (_, { dy, vy }) => {
136
+ if (dy > sheetHeight * DISMISS_THRESHOLD || vy > 0.5) {
137
+ handleClose();
138
+ }
139
+ else {
140
+ Animated.parallel([
141
+ Animated.timing(slideAnim, {
142
+ duration: 200,
143
+ toValue: 0,
144
+ useNativeDriver: true,
145
+ }),
146
+ Animated.timing(backdropAnim, {
147
+ duration: 200,
148
+ toValue: BACKDROP_OPACITY,
149
+ useNativeDriver: true,
150
+ }),
151
+ ]).start();
152
+ }
153
+ },
154
+ }), [slideAnim, backdropAnim, sheetHeight, handleClose]);
155
+ return (_jsxs(View, { style: { flex: 1 }, children: [children, !isControlled && (_jsx(Pressable, { accessibilityLabel: "Open navigation menu", accessibilityRole: "button", onPress: handleOpen, style: {
88
156
  alignItems: "center",
89
- backgroundColor: theme.surface.primary,
90
- borderRadius: theme.radius.full,
91
- elevation: 4,
92
157
  height: 44,
93
158
  justifyContent: "center",
94
159
  left: 16,
95
160
  position: "absolute",
96
- shadowColor: "#000",
97
- shadowOffset: { height: 2, width: 0 },
98
- shadowOpacity: 0.25,
99
- shadowRadius: 4,
100
- top: 16,
161
+ top: insets.top + 16,
101
162
  width: 44,
102
163
  zIndex: 10,
103
- }, children: _jsx(Icon, { color: "inverted", iconName: "bars", size: "md" }) }), isOpen && (_jsx(Pressable, { onPress: handleClose, style: {
104
- bottom: 0,
105
- left: 0,
106
- position: "absolute",
107
- right: 0,
108
- top: 0,
109
- zIndex: 100,
110
- }, children: _jsx(Animated.View, { style: {
111
- backgroundColor: "#000",
112
- flex: 1,
113
- opacity: backdropAnim,
114
- } }) })), _jsxs(Animated.View, { style: [
115
- {
116
- backgroundColor: theme.surface.base,
117
- borderColor: theme.border.default,
118
- borderRightWidth: 1,
119
- height: screenHeight,
120
- justifyContent: "space-between",
121
- left: 0,
122
- paddingBottom: 32,
123
- paddingTop: 20,
124
- position: "absolute",
125
- top: 0,
126
- transform: [{ translateX: slideAnim }],
127
- width: DRAWER_WIDTH,
128
- zIndex: 200,
129
- },
130
- panelStyle,
131
- ], children: [_jsxs(View, { children: [_jsx(Pressable, { accessibilityLabel: "Close navigation menu", accessibilityRole: "button", onPress: handleClose, style: {
132
- alignItems: "center",
133
- alignSelf: "flex-end",
134
- height: 40,
135
- justifyContent: "center",
136
- marginRight: 12,
137
- width: 40,
138
- }, children: _jsx(Icon, { color: "secondaryDark", iconName: "xmark", size: "md" }) }), _jsx(View, { style: { gap: 4, marginTop: 8 }, children: topItems.map((item) => (_jsx(SidebarItem, { isActive: activeRoute === item.route, item: item, itemStyle: itemStyle, onPress: handleNavigate }, item.route))) })] }), _jsx(View, { style: { gap: 4 }, children: bottomItems.map((item) => (_jsx(SidebarItem, { isActive: activeRoute === item.route, item: item, onPress: handleNavigate }, item.route))) })] })] }));
164
+ }, children: _jsx(Icon, { color: "primary", iconName: "bars", size: "md" }) })), isOpen && (_jsxs(_Fragment, { children: [_jsx(Pressable, { accessibilityElementsHidden: true, onPress: handleClose, style: { bottom: 0, left: 0, position: "absolute", right: 0, top: 0, zIndex: 100 }, children: _jsx(Animated.View, { style: { backgroundColor: "#000", flex: 1, opacity: backdropAnim } }) }), _jsxs(Animated.View, { style: [
165
+ {
166
+ backgroundColor: theme.surface.base,
167
+ borderTopLeftRadius: 16,
168
+ borderTopRightRadius: 16,
169
+ bottom: 0,
170
+ height: sheetHeight,
171
+ left: 0,
172
+ position: "absolute",
173
+ right: 0,
174
+ transform: [{ translateY: slideAnim }],
175
+ zIndex: 200,
176
+ },
177
+ panelStyle,
178
+ ], children: [_jsx(View, Object.assign({}, panResponder.panHandlers, { accessibilityHint: "Drag down to close", accessibilityLabel: "Navigation menu drag handle", accessibilityRole: "adjustable", style: { alignItems: "center", paddingBottom: 8, paddingTop: 12 }, children: _jsx(View, { style: {
179
+ backgroundColor: theme.border.default,
180
+ borderRadius: 2,
181
+ height: 4,
182
+ width: 36,
183
+ } }) })), _jsx(View, { style: { gap: 4, paddingBottom: insets.bottom + 8 }, children: [...topItems, ...bottomItems].map((item) => (_jsx(SidebarItem, { isActive: activeRoute === item.route, item: item, itemStyle: itemStyle, onPress: handleNavigate }, item.route))) })] })] }))] }));
139
184
  };
140
- /**
141
- * Reads active route from Navigator context and renders the drawer + Slot.
142
- */
143
- const SidebarNavigatorContent = ({ topItems, bottomItems, onNavigate, panelStyle, itemStyle }) => {
144
- var _a;
185
+ const SidebarHeader = ({ onOpen }) => {
186
+ var _a, _b;
187
+ const { theme } = useTheme();
188
+ const insets = useSafeAreaInsets();
189
+ const { state, descriptors } = Navigator.useContext();
190
+ const activeRoute = state.routes[state.index];
191
+ const { headerLeft, headerRight, title } = ((_b = (_a = descriptors[activeRoute === null || activeRoute === void 0 ? void 0 : activeRoute.key]) === null || _a === void 0 ? void 0 : _a.options) !== null && _b !== void 0 ? _b : {});
192
+ return (_jsx(View, { style: {
193
+ backgroundColor: theme.surface.base,
194
+ borderBottomColor: theme.border.default,
195
+ borderBottomWidth: 1,
196
+ paddingTop: insets.top,
197
+ }, children: _jsxs(View, { style: {
198
+ alignItems: "center",
199
+ flexDirection: "row",
200
+ height: 44,
201
+ justifyContent: "space-between",
202
+ paddingHorizontal: 16,
203
+ }, children: [_jsxs(View, { style: { alignItems: "center", flexDirection: "row", gap: 12 }, children: [_jsx(SidebarHamburger, { onOpen: onOpen }), headerLeft === null || headerLeft === void 0 ? void 0 : headerLeft({}), Boolean(title) && (_jsx(Text, { bold: true, size: "lg", children: title }))] }), Boolean(headerRight) && _jsx(View, { style: { alignItems: "flex-end" }, children: headerRight === null || headerRight === void 0 ? void 0 : headerRight({}) })] }) }));
204
+ };
205
+ /** Renders the content panel and bottom sheet for the active screen. */
206
+ const SidebarNavigatorContent = ({ topItems, bottomItems, isOpen, onOpenChange, onNavigate, panelStyle, itemStyle }) => {
145
207
  const { state, navigation } = Navigator.useContext();
146
- const activeRoute = (_a = state.routes[state.index]) === null || _a === void 0 ? void 0 : _a.name;
208
+ const activeRoute = state.routes[state.index];
147
209
  const handleNavigate = useCallback((route) => {
148
210
  navigation.navigate(route);
149
211
  onNavigate === null || onNavigate === void 0 ? void 0 : onNavigate(route);
150
212
  }, [navigation, onNavigate]);
151
- return (_jsx(SidebarNavigationPanel, { activeRoute: activeRoute, bottomItems: bottomItems, itemStyle: itemStyle, onNavigate: handleNavigate, panelStyle: panelStyle, topItems: topItems, children: _jsx(Slot, {}) }));
213
+ return (_jsx(SidebarNavigationPanel, { activeRoute: activeRoute === null || activeRoute === void 0 ? void 0 : activeRoute.name, bottomItems: bottomItems, isOpen: isOpen, itemStyle: itemStyle, onNavigate: handleNavigate, onOpenChange: onOpenChange, panelStyle: panelStyle, topItems: topItems, children: _jsx(Slot, {}) }));
152
214
  };
153
215
  /**
154
- * Custom expo-router navigator with a hamburger-triggered slide-in drawer.
216
+ * Custom expo-router navigator with a header bar and hamburger-triggered bottom sheet.
155
217
  * Use in _layout.tsx files:
156
218
  *
157
219
  * ```tsx
@@ -165,7 +227,9 @@ const SidebarNavigatorContent = ({ topItems, bottomItems, onNavigate, panelStyle
165
227
  * }
166
228
  * ```
167
229
  */
168
- export const SidebarNavigation = ({ topItems, bottomItems, onNavigate, initialRouteName, screenOptions, panelStyle, itemStyle, }) => {
169
- return (_jsx(Navigator, { initialRouteName: initialRouteName, router: TabRouter, screenOptions: screenOptions, children: _jsx(SidebarNavigatorContent, { bottomItems: bottomItems, itemStyle: itemStyle, onNavigate: onNavigate, panelStyle: panelStyle, topItems: topItems }) }));
230
+ const SidebarNavigationBase = ({ topItems, bottomItems, onNavigate, initialRouteName, screenOptions, panelStyle, itemStyle, children, }) => {
231
+ const [isSheetOpen, setIsSheetOpen] = useState(false);
232
+ return (_jsxs(Navigator, { initialRouteName: initialRouteName, router: TabRouter, screenOptions: screenOptions, children: [_jsxs(View, { style: { flex: 1 }, children: [_jsx(SidebarHeader, { onOpen: () => setIsSheetOpen(true) }), _jsx(SidebarNavigatorContent, { bottomItems: bottomItems, isOpen: isSheetOpen, itemStyle: itemStyle, onNavigate: onNavigate, onOpenChange: setIsSheetOpen, panelStyle: panelStyle, topItems: topItems })] }), children] }));
170
233
  };
234
+ export const SidebarNavigation = Object.assign(SidebarNavigationBase, { Screen });
171
235
  //# sourceMappingURL=SidebarNavigation.native.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SidebarNavigation.native.js","sourceRoot":"","sources":["../src/SidebarNavigation.native.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SidebarNavigation.native.js","sourceRoot":"","sources":["../src/SidebarNavigation.native.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -137,5 +137,5 @@
137
137
  "test:coverage": "TZ=America/New_York bun run ../scripts/check-coverage.ts",
138
138
  "types": "bun typedoc"
139
139
  },
140
- "version": "0.11.4-beta.2"
140
+ "version": "0.11.4-beta.3"
141
141
  }
package/src/Common.ts CHANGED
@@ -3044,4 +3044,17 @@ export interface SidebarNavigationPanelProps {
3044
3044
  * Additional styles applied to each navigation item.
3045
3045
  */
3046
3046
  itemStyle?: StyleProp<ViewStyle>;
3047
+ /**
3048
+ * Controlled open state. When provided, the panel hides its internal hamburger
3049
+ * button and defers open/close to the caller.
3050
+ *
3051
+ * @platform mobile — the web sidebar is always visible; this prop is ignored on web.
3052
+ */
3053
+ isOpen?: boolean;
3054
+ /**
3055
+ * Called when the panel requests an open or close (controlled mode only).
3056
+ *
3057
+ * @platform mobile — the web sidebar is always visible; this prop is ignored on web.
3058
+ */
3059
+ onOpenChange?: (isOpen: boolean) => void;
3047
3060
  }
@@ -1,7 +1,21 @@
1
1
  import {TabRouter} from "@react-navigation/native";
2
2
  import {Navigator, Slot} from "expo-router";
3
- import {type FC, useCallback, useEffect, useRef, useState} from "react";
4
- import {Animated, Dimensions, Pressable, type StyleProp, View, type ViewStyle} from "react-native";
3
+ // Screen is not exported from expo-router's public API (exports.d.ts only exposes ScreenProps).
4
+ // Stack.Screen and Tabs.Screen use this same internal path. If expo-router upgrades break this,
5
+ // update the import path here — this is the only place in the codebase that references it.
6
+ // eslint-disable-next-line import/no-internal-modules
7
+ import {Screen} from "expo-router/build/views/Screen";
8
+ import {type FC, useCallback, useEffect, useMemo, useRef, useState} from "react";
9
+ import {
10
+ Animated,
11
+ Dimensions,
12
+ PanResponder,
13
+ Pressable,
14
+ type StyleProp,
15
+ View,
16
+ type ViewStyle,
17
+ } from "react-native";
18
+ import {useSafeAreaInsets} from "react-native-safe-area-context";
5
19
 
6
20
  import {Badge} from "./Badge";
7
21
  import type {
@@ -14,11 +28,11 @@ import {Icon} from "./Icon";
14
28
  import {Text} from "./Text";
15
29
  import {useTheme} from "./Theme";
16
30
 
17
- const DRAWER_WIDTH = 280;
18
- const ITEM_HEIGHT = 48;
31
+ const ITEM_HEIGHT = 44;
19
32
  const ICON_SIZE = 20;
20
33
  const BACKDROP_OPACITY = 0.5;
21
- const ANIMATION_DURATION = 250;
34
+ const ANIMATION_DURATION = 300;
35
+ const DISMISS_THRESHOLD = 0.3;
22
36
 
23
37
  const SidebarItem: FC<{
24
38
  item: SidebarNavigationItem;
@@ -40,19 +54,19 @@ const SidebarItem: FC<{
40
54
  style={[
41
55
  {
42
56
  alignItems: "center",
43
- backgroundColor: isActive ? theme.surface.secondaryLight : "transparent",
57
+ backgroundColor: isActive ? theme.surface.neutralLight : "transparent",
44
58
  borderRadius: theme.radius.default,
45
59
  flexDirection: "row",
46
- gap: 14,
60
+ gap: 12,
47
61
  height: ITEM_HEIGHT,
48
- marginHorizontal: 12,
49
- paddingHorizontal: 14,
62
+ marginHorizontal: 8,
63
+ paddingHorizontal: 12,
50
64
  },
51
65
  itemStyle,
52
66
  ]}
53
67
  >
54
68
  <View style={{alignItems: "center", justifyContent: "center", width: ICON_SIZE}}>
55
- <Icon color={isActive ? "primary" : "secondaryDark"} iconName={item.iconName} size="md" />
69
+ <Icon color={isActive ? "primary" : "secondaryLight"} iconName={item.iconName} size="lg" />
56
70
  {Boolean(item.badge) && (
57
71
  <View
58
72
  style={{
@@ -71,15 +85,30 @@ const SidebarItem: FC<{
71
85
  </View>
72
86
  )}
73
87
  </View>
74
- <Text bold={isActive} color={isActive ? "primary" : "secondaryDark"} size="md">
88
+ <Text bold={isActive} color={isActive ? "primary" : "secondaryLight"} size="md">
75
89
  {item.label}
76
90
  </Text>
77
91
  </Pressable>
78
92
  );
79
93
  };
80
94
 
95
+ const SidebarHamburger: FC<{onOpen: () => void}> = ({onOpen}) => (
96
+ <Pressable
97
+ accessibilityLabel="Open navigation menu"
98
+ accessibilityRole="button"
99
+ onPress={onOpen}
100
+ style={{alignItems: "center", height: 40, justifyContent: "center", width: 40}}
101
+ >
102
+ <Icon color="primary" iconName="bars" size="md" />
103
+ </Pressable>
104
+ );
105
+
81
106
  /**
82
- * Renders the hamburger button, drawer overlay, and children. Works without expo-router Navigator context.
107
+ * Renders the bottom sheet overlay and children. Works without expo-router Navigator context.
108
+ *
109
+ * Supports two modes:
110
+ * - Uncontrolled (default): manages open state internally and shows a floating hamburger button.
111
+ * - Controlled: caller provides isOpen + onOpenChange and owns the trigger (e.g. a header button).
83
112
  */
84
113
  export const SidebarNavigationPanel: FC<SidebarNavigationPanelProps> = ({
85
114
  topItems,
@@ -89,187 +118,262 @@ export const SidebarNavigationPanel: FC<SidebarNavigationPanelProps> = ({
89
118
  children,
90
119
  panelStyle,
91
120
  itemStyle,
121
+ isOpen: isOpenProp,
122
+ onOpenChange,
92
123
  }) => {
93
124
  const {theme} = useTheme();
94
- const [isOpen, setIsOpen] = useState(false);
95
- const slideAnim = useRef(new Animated.Value(-DRAWER_WIDTH)).current;
125
+ const insets = useSafeAreaInsets();
126
+ const isControlled = isOpenProp !== undefined;
127
+ const [isOpenInternal, setIsOpenInternal] = useState(false);
128
+ const isOpen = isControlled ? isOpenProp : isOpenInternal;
129
+
130
+ const sheetHeight = useMemo(() => Dimensions.get("window").height * 0.65, []);
131
+ const slideAnim = useRef(new Animated.Value(sheetHeight)).current;
96
132
  const backdropAnim = useRef(new Animated.Value(0)).current;
133
+ const capturedSlideValue = useRef(0);
97
134
 
98
- // Animate drawer open/close
135
+ // Play open animation whenever isOpen becomes true
99
136
  useEffect(() => {
100
- if (isOpen) {
101
- Animated.parallel([
102
- Animated.timing(slideAnim, {
103
- duration: ANIMATION_DURATION,
104
- toValue: 0,
105
- useNativeDriver: true,
106
- }),
107
- Animated.timing(backdropAnim, {
108
- duration: ANIMATION_DURATION,
109
- toValue: BACKDROP_OPACITY,
110
- useNativeDriver: true,
111
- }),
112
- ]).start();
113
- } else {
114
- Animated.parallel([
115
- Animated.timing(slideAnim, {
116
- duration: ANIMATION_DURATION,
117
- toValue: -DRAWER_WIDTH,
118
- useNativeDriver: true,
119
- }),
120
- Animated.timing(backdropAnim, {
121
- duration: ANIMATION_DURATION,
122
- toValue: 0,
123
- useNativeDriver: true,
124
- }),
125
- ]).start();
137
+ if (!isOpen) {
138
+ return;
126
139
  }
127
- }, [isOpen, slideAnim, backdropAnim]);
140
+ slideAnim.setValue(sheetHeight);
141
+ backdropAnim.setValue(0);
142
+ Animated.parallel([
143
+ Animated.timing(slideAnim, {
144
+ duration: ANIMATION_DURATION,
145
+ toValue: 0,
146
+ useNativeDriver: true,
147
+ }),
148
+ Animated.timing(backdropAnim, {
149
+ duration: ANIMATION_DURATION,
150
+ toValue: BACKDROP_OPACITY,
151
+ useNativeDriver: true,
152
+ }),
153
+ ]).start();
154
+ }, [isOpen, slideAnim, backdropAnim, sheetHeight]);
128
155
 
129
- const handleOpen = useCallback(() => setIsOpen(true), []);
130
- const handleClose = useCallback(() => setIsOpen(false), []);
156
+ // Play close animation then update state
157
+ const handleClose = useCallback(() => {
158
+ Animated.parallel([
159
+ Animated.timing(slideAnim, {
160
+ duration: ANIMATION_DURATION,
161
+ toValue: sheetHeight,
162
+ useNativeDriver: true,
163
+ }),
164
+ Animated.timing(backdropAnim, {
165
+ duration: ANIMATION_DURATION,
166
+ toValue: 0,
167
+ useNativeDriver: true,
168
+ }),
169
+ ]).start(() => {
170
+ if (isControlled) {
171
+ onOpenChange?.(false);
172
+ } else {
173
+ setIsOpenInternal(false);
174
+ }
175
+ });
176
+ }, [isControlled, onOpenChange, slideAnim, backdropAnim, sheetHeight]);
177
+
178
+ const handleOpen = useCallback(() => {
179
+ if (isControlled) {
180
+ onOpenChange?.(true);
181
+ } else {
182
+ setIsOpenInternal(true);
183
+ }
184
+ }, [isControlled, onOpenChange]);
131
185
 
132
186
  const handleNavigate = useCallback(
133
187
  (route: string) => {
134
- setIsOpen(false);
188
+ handleClose();
135
189
  onNavigate(route);
136
190
  },
137
- [onNavigate]
191
+ [handleClose, onNavigate]
138
192
  );
139
193
 
140
- const screenHeight = Dimensions.get("window").height;
194
+ const panResponder = useMemo(
195
+ () =>
196
+ PanResponder.create({
197
+ onMoveShouldSetPanResponder: (_, {dx, dy}) => Math.abs(dy) > Math.abs(dx) && dy > 4,
198
+ onPanResponderGrant: () => {
199
+ slideAnim.stopAnimation((value) => {
200
+ capturedSlideValue.current = value;
201
+ });
202
+ backdropAnim.stopAnimation();
203
+ },
204
+ onPanResponderMove: (_, {dy}) => {
205
+ const next = capturedSlideValue.current + dy;
206
+ if (next < 0) {
207
+ return;
208
+ }
209
+ slideAnim.setValue(next);
210
+ backdropAnim.setValue(BACKDROP_OPACITY * Math.max(0, 1 - next / sheetHeight));
211
+ },
212
+ onPanResponderRelease: (_, {dy, vy}) => {
213
+ if (dy > sheetHeight * DISMISS_THRESHOLD || vy > 0.5) {
214
+ handleClose();
215
+ } else {
216
+ Animated.parallel([
217
+ Animated.timing(slideAnim, {
218
+ duration: 200,
219
+ toValue: 0,
220
+ useNativeDriver: true,
221
+ }),
222
+ Animated.timing(backdropAnim, {
223
+ duration: 200,
224
+ toValue: BACKDROP_OPACITY,
225
+ useNativeDriver: true,
226
+ }),
227
+ ]).start();
228
+ }
229
+ },
230
+ }),
231
+ [slideAnim, backdropAnim, sheetHeight, handleClose]
232
+ );
141
233
 
142
234
  return (
143
235
  <View style={{flex: 1}}>
144
236
  {children}
145
237
 
146
- {/* Hamburger button */}
147
- <Pressable
148
- accessibilityLabel="Open navigation menu"
149
- accessibilityRole="button"
150
- onPress={handleOpen}
151
- style={{
152
- alignItems: "center",
153
- backgroundColor: theme.surface.primary,
154
- borderRadius: theme.radius.full,
155
- elevation: 4,
156
- height: 44,
157
- justifyContent: "center",
158
- left: 16,
159
- position: "absolute",
160
- shadowColor: "#000",
161
- shadowOffset: {height: 2, width: 0},
162
- shadowOpacity: 0.25,
163
- shadowRadius: 4,
164
- top: 16,
165
- width: 44,
166
- zIndex: 10,
167
- }}
168
- >
169
- <Icon color="inverted" iconName="bars" size="md" />
170
- </Pressable>
171
-
172
- {/* Backdrop */}
173
- {isOpen && (
238
+ {/* Floating hamburger — only shown in uncontrolled (standalone) mode */}
239
+ {!isControlled && (
174
240
  <Pressable
175
- onPress={handleClose}
241
+ accessibilityLabel="Open navigation menu"
242
+ accessibilityRole="button"
243
+ onPress={handleOpen}
176
244
  style={{
177
- bottom: 0,
178
- left: 0,
245
+ alignItems: "center",
246
+ height: 44,
247
+ justifyContent: "center",
248
+ left: 16,
179
249
  position: "absolute",
180
- right: 0,
181
- top: 0,
182
- zIndex: 100,
250
+ top: insets.top + 16,
251
+ width: 44,
252
+ zIndex: 10,
183
253
  }}
184
254
  >
185
- <Animated.View
186
- style={{
187
- backgroundColor: "#000",
188
- flex: 1,
189
- opacity: backdropAnim,
190
- }}
191
- />
255
+ <Icon color="primary" iconName="bars" size="md" />
192
256
  </Pressable>
193
257
  )}
194
258
 
195
- {/* Drawer */}
196
- <Animated.View
197
- style={[
198
- {
199
- backgroundColor: theme.surface.base,
200
- borderColor: theme.border.default,
201
- borderRightWidth: 1,
202
- height: screenHeight,
203
- justifyContent: "space-between",
204
- left: 0,
205
- paddingBottom: 32,
206
- paddingTop: 20,
207
- position: "absolute",
208
- top: 0,
209
- transform: [{translateX: slideAnim}],
210
- width: DRAWER_WIDTH,
211
- zIndex: 200,
212
- },
213
- panelStyle,
214
- ]}
215
- >
216
- {/* Close button */}
217
- <View>
259
+ {isOpen && (
260
+ <>
261
+ {/* Backdrop */}
218
262
  <Pressable
219
- accessibilityLabel="Close navigation menu"
220
- accessibilityRole="button"
263
+ accessibilityElementsHidden
221
264
  onPress={handleClose}
222
- style={{
223
- alignItems: "center",
224
- alignSelf: "flex-end",
225
- height: 40,
226
- justifyContent: "center",
227
- marginRight: 12,
228
- width: 40,
229
- }}
265
+ style={{bottom: 0, left: 0, position: "absolute", right: 0, top: 0, zIndex: 100}}
230
266
  >
231
- <Icon color="secondaryDark" iconName="xmark" size="md" />
267
+ <Animated.View style={{backgroundColor: "#000", flex: 1, opacity: backdropAnim}} />
232
268
  </Pressable>
233
- <View style={{gap: 4, marginTop: 8}}>
234
- {topItems.map((item) => (
235
- <SidebarItem
236
- isActive={activeRoute === item.route}
237
- item={item}
238
- itemStyle={itemStyle}
239
- key={item.route}
240
- onPress={handleNavigate}
269
+
270
+ {/* Bottom sheet */}
271
+ <Animated.View
272
+ style={[
273
+ {
274
+ backgroundColor: theme.surface.base,
275
+ borderTopLeftRadius: 16,
276
+ borderTopRightRadius: 16,
277
+ bottom: 0,
278
+ height: sheetHeight,
279
+ left: 0,
280
+ position: "absolute",
281
+ right: 0,
282
+ transform: [{translateY: slideAnim}],
283
+ zIndex: 200,
284
+ },
285
+ panelStyle,
286
+ ]}
287
+ >
288
+ {/* Drag bar */}
289
+ <View
290
+ {...panResponder.panHandlers}
291
+ accessibilityHint="Drag down to close"
292
+ accessibilityLabel="Navigation menu drag handle"
293
+ accessibilityRole="adjustable"
294
+ style={{alignItems: "center", paddingBottom: 8, paddingTop: 12}}
295
+ >
296
+ <View
297
+ style={{
298
+ backgroundColor: theme.border.default,
299
+ borderRadius: 2,
300
+ height: 4,
301
+ width: 36,
302
+ }}
241
303
  />
242
- ))}
243
- </View>
244
- </View>
304
+ </View>
245
305
 
246
- <View style={{gap: 4}}>
247
- {bottomItems.map((item) => (
248
- <SidebarItem
249
- isActive={activeRoute === item.route}
250
- item={item}
251
- key={item.route}
252
- onPress={handleNavigate}
253
- />
254
- ))}
306
+ {/* Nav items */}
307
+ <View style={{gap: 4, paddingBottom: insets.bottom + 8}}>
308
+ {[...topItems, ...bottomItems].map((item) => (
309
+ <SidebarItem
310
+ isActive={activeRoute === item.route}
311
+ item={item}
312
+ itemStyle={itemStyle}
313
+ key={item.route}
314
+ onPress={handleNavigate}
315
+ />
316
+ ))}
317
+ </View>
318
+ </Animated.View>
319
+ </>
320
+ )}
321
+ </View>
322
+ );
323
+ };
324
+
325
+ const SidebarHeader: FC<{onOpen: () => void}> = ({onOpen}) => {
326
+ const {theme} = useTheme();
327
+ const insets = useSafeAreaInsets();
328
+ const {state, descriptors} = Navigator.useContext();
329
+ const activeRoute = state.routes[state.index];
330
+ const {headerLeft, headerRight, title} = (descriptors[activeRoute?.key]?.options ?? {}) as any;
331
+
332
+ return (
333
+ <View
334
+ style={{
335
+ backgroundColor: theme.surface.base,
336
+ borderBottomColor: theme.border.default,
337
+ borderBottomWidth: 1,
338
+ paddingTop: insets.top,
339
+ }}
340
+ >
341
+ <View
342
+ style={{
343
+ alignItems: "center",
344
+ flexDirection: "row",
345
+ height: 44,
346
+ justifyContent: "space-between",
347
+ paddingHorizontal: 16,
348
+ }}
349
+ >
350
+ <View style={{alignItems: "center", flexDirection: "row", gap: 12}}>
351
+ <SidebarHamburger onOpen={onOpen} />
352
+ {headerLeft?.({})}
353
+ {Boolean(title) && (
354
+ <Text bold size="lg">
355
+ {title}
356
+ </Text>
357
+ )}
255
358
  </View>
256
- </Animated.View>
359
+ {Boolean(headerRight) && <View style={{alignItems: "flex-end"}}>{headerRight?.({})}</View>}
360
+ </View>
257
361
  </View>
258
362
  );
259
363
  };
260
364
 
261
- /**
262
- * Reads active route from Navigator context and renders the drawer + Slot.
263
- */
365
+ /** Renders the content panel and bottom sheet for the active screen. */
264
366
  const SidebarNavigatorContent: FC<{
265
367
  topItems: SidebarNavigationItem[];
266
368
  bottomItems: SidebarNavigationItem[];
369
+ isOpen: boolean;
370
+ onOpenChange: (isOpen: boolean) => void;
267
371
  onNavigate?: (route: string) => void;
268
372
  panelStyle?: StyleProp<ViewStyle>;
269
373
  itemStyle?: StyleProp<ViewStyle>;
270
- }> = ({topItems, bottomItems, onNavigate, panelStyle, itemStyle}) => {
374
+ }> = ({topItems, bottomItems, isOpen, onOpenChange, onNavigate, panelStyle, itemStyle}) => {
271
375
  const {state, navigation} = Navigator.useContext();
272
- const activeRoute = state.routes[state.index]?.name;
376
+ const activeRoute = state.routes[state.index];
273
377
 
274
378
  const handleNavigate = useCallback(
275
379
  (route: string) => {
@@ -281,10 +385,12 @@ const SidebarNavigatorContent: FC<{
281
385
 
282
386
  return (
283
387
  <SidebarNavigationPanel
284
- activeRoute={activeRoute}
388
+ activeRoute={activeRoute?.name}
285
389
  bottomItems={bottomItems}
390
+ isOpen={isOpen}
286
391
  itemStyle={itemStyle}
287
392
  onNavigate={handleNavigate}
393
+ onOpenChange={onOpenChange}
288
394
  panelStyle={panelStyle}
289
395
  topItems={topItems}
290
396
  >
@@ -294,7 +400,7 @@ const SidebarNavigatorContent: FC<{
294
400
  };
295
401
 
296
402
  /**
297
- * Custom expo-router navigator with a hamburger-triggered slide-in drawer.
403
+ * Custom expo-router navigator with a header bar and hamburger-triggered bottom sheet.
298
404
  * Use in _layout.tsx files:
299
405
  *
300
406
  * ```tsx
@@ -308,7 +414,7 @@ const SidebarNavigatorContent: FC<{
308
414
  * }
309
415
  * ```
310
416
  */
311
- export const SidebarNavigation: FC<SidebarNavigationProps> = ({
417
+ const SidebarNavigationBase: FC<SidebarNavigationProps> = ({
312
418
  topItems,
313
419
  bottomItems,
314
420
  onNavigate,
@@ -316,16 +422,27 @@ export const SidebarNavigation: FC<SidebarNavigationProps> = ({
316
422
  screenOptions,
317
423
  panelStyle,
318
424
  itemStyle,
425
+ children,
319
426
  }) => {
427
+ const [isSheetOpen, setIsSheetOpen] = useState(false);
428
+
320
429
  return (
321
430
  <Navigator initialRouteName={initialRouteName} router={TabRouter} screenOptions={screenOptions}>
322
- <SidebarNavigatorContent
323
- bottomItems={bottomItems}
324
- itemStyle={itemStyle}
325
- onNavigate={onNavigate}
326
- panelStyle={panelStyle}
327
- topItems={topItems}
328
- />
431
+ <View style={{flex: 1}}>
432
+ <SidebarHeader onOpen={() => setIsSheetOpen(true)} />
433
+ <SidebarNavigatorContent
434
+ bottomItems={bottomItems}
435
+ isOpen={isSheetOpen}
436
+ itemStyle={itemStyle}
437
+ onNavigate={onNavigate}
438
+ onOpenChange={setIsSheetOpen}
439
+ panelStyle={panelStyle}
440
+ topItems={topItems}
441
+ />
442
+ </View>
443
+ {children}
329
444
  </Navigator>
330
445
  );
331
446
  };
447
+
448
+ export const SidebarNavigation = Object.assign(SidebarNavigationBase, {Screen});