@terreno/ui 0.11.4-beta.4 → 0.11.5
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 +0 -13
- package/dist/CustomSelectField.js +1 -1
- package/dist/CustomSelectField.js.map +1 -1
- package/dist/DataTable.js +6 -3
- package/dist/DataTable.js.map +1 -1
- package/dist/ErrorBoundary.d.ts +3 -7
- package/dist/ErrorBoundary.js.map +1 -1
- package/dist/Link.d.ts +1 -1
- package/dist/Link.js +10 -1
- package/dist/Link.js.map +1 -1
- package/dist/MarkdownEditorField.js +2 -2
- package/dist/MarkdownEditorField.js.map +1 -1
- package/dist/Modal.js +1 -1
- package/dist/Modal.js.map +1 -1
- package/dist/SelectField.js +2 -2
- package/dist/SelectField.js.map +1 -1
- package/dist/SidebarNavigation.native.d.ts +17 -9
- package/dist/SidebarNavigation.native.js +103 -167
- package/dist/SidebarNavigation.native.js.map +1 -1
- package/dist/icons/MobileIcon.d.ts +2 -1
- package/dist/icons/MobileIcon.js.map +1 -1
- package/package.json +1 -1
- package/src/Common.ts +0 -13
- package/src/CustomSelectField.test.tsx +53 -1
- package/src/CustomSelectField.tsx +1 -1
- package/src/DataTable.tsx +35 -31
- package/src/ErrorBoundary.tsx +5 -5
- package/src/Link.tsx +11 -2
- package/src/MarkdownEditorField.tsx +3 -3
- package/src/Modal.tsx +3 -3
- package/src/SelectField.tsx +2 -2
- package/src/SidebarNavigation.native.tsx +158 -275
- package/src/ToastNotifications.test.tsx +206 -0
- package/src/icons/MobileIcon.tsx +2 -1
|
@@ -58,7 +58,7 @@ export const MarkdownEditorField: React.FC<MarkdownEditorFieldProps> = ({
|
|
|
58
58
|
|
|
59
59
|
return (
|
|
60
60
|
<View testID={testID}>
|
|
61
|
-
{title && <FieldTitle text={title} />}
|
|
61
|
+
{Boolean(title) && <FieldTitle text={title!} />}
|
|
62
62
|
<Box
|
|
63
63
|
border={errorText ? "error" : "default"}
|
|
64
64
|
direction={isWeb ? "row" : "column"}
|
|
@@ -151,8 +151,8 @@ export const MarkdownEditorField: React.FC<MarkdownEditorFieldProps> = ({
|
|
|
151
151
|
</Box>
|
|
152
152
|
</ScrollView>
|
|
153
153
|
</Box>
|
|
154
|
-
{errorText && <FieldError text={errorText} />}
|
|
155
|
-
{helperText && <FieldHelperText text={helperText} />}
|
|
154
|
+
{Boolean(errorText) && <FieldError text={errorText!} />}
|
|
155
|
+
{Boolean(helperText) && <FieldHelperText text={helperText!} />}
|
|
156
156
|
</View>
|
|
157
157
|
);
|
|
158
158
|
};
|
package/src/Modal.tsx
CHANGED
|
@@ -102,7 +102,7 @@ const ModalContent: FC<{
|
|
|
102
102
|
<Icon iconName="x" size="sm" />
|
|
103
103
|
</Pressable>
|
|
104
104
|
</View>
|
|
105
|
-
{title && (
|
|
105
|
+
{Boolean(title) && (
|
|
106
106
|
<View
|
|
107
107
|
accessibilityHint="Modal title"
|
|
108
108
|
aria-label={title}
|
|
@@ -112,7 +112,7 @@ const ModalContent: FC<{
|
|
|
112
112
|
<Heading size="lg">{title}</Heading>
|
|
113
113
|
</View>
|
|
114
114
|
)}
|
|
115
|
-
{subtitle && (
|
|
115
|
+
{Boolean(subtitle) && (
|
|
116
116
|
<View
|
|
117
117
|
accessibilityHint="Modal Sub Heading Text"
|
|
118
118
|
aria-label={subtitle}
|
|
@@ -122,7 +122,7 @@ const ModalContent: FC<{
|
|
|
122
122
|
<Text size="lg">{subtitle}</Text>
|
|
123
123
|
</View>
|
|
124
124
|
)}
|
|
125
|
-
{text && (
|
|
125
|
+
{Boolean(text) && (
|
|
126
126
|
<View
|
|
127
127
|
accessibilityHint="Modal body text"
|
|
128
128
|
aria-label={text}
|
package/src/SelectField.tsx
CHANGED
|
@@ -20,7 +20,7 @@ export const SelectField: FC<SelectFieldProps> = ({
|
|
|
20
20
|
|
|
21
21
|
return (
|
|
22
22
|
<View>
|
|
23
|
-
{title && <FieldTitle text={title} />}
|
|
23
|
+
{Boolean(title) && <FieldTitle text={title!} />}
|
|
24
24
|
{Boolean(errorText) && <FieldError text={errorText!} />}
|
|
25
25
|
<RNPickerSelect
|
|
26
26
|
disabled={disabled}
|
|
@@ -35,7 +35,7 @@ export const SelectField: FC<SelectFieldProps> = ({
|
|
|
35
35
|
placeholder={!requireValue ? clearOption : {}}
|
|
36
36
|
value={value ?? ""}
|
|
37
37
|
/>
|
|
38
|
-
{helperText && <FieldHelperText text={helperText} />}
|
|
38
|
+
{Boolean(helperText) && <FieldHelperText text={helperText!} />}
|
|
39
39
|
</View>
|
|
40
40
|
);
|
|
41
41
|
};
|
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
import {TabRouter} from "@react-navigation/native";
|
|
2
2
|
import {Navigator, Slot} from "expo-router";
|
|
3
|
-
|
|
4
|
-
|
|
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";
|
|
3
|
+
import {type FC, useCallback, useEffect, useRef, useState} from "react";
|
|
4
|
+
import {Animated, Dimensions, Pressable, type StyleProp, View, type ViewStyle} from "react-native";
|
|
19
5
|
|
|
20
6
|
import {Badge} from "./Badge";
|
|
21
7
|
import type {
|
|
@@ -28,11 +14,11 @@ import {Icon} from "./Icon";
|
|
|
28
14
|
import {Text} from "./Text";
|
|
29
15
|
import {useTheme} from "./Theme";
|
|
30
16
|
|
|
31
|
-
const
|
|
17
|
+
const DRAWER_WIDTH = 280;
|
|
18
|
+
const ITEM_HEIGHT = 48;
|
|
32
19
|
const ICON_SIZE = 20;
|
|
33
20
|
const BACKDROP_OPACITY = 0.5;
|
|
34
|
-
const ANIMATION_DURATION =
|
|
35
|
-
const DISMISS_THRESHOLD = 0.3;
|
|
21
|
+
const ANIMATION_DURATION = 250;
|
|
36
22
|
|
|
37
23
|
const SidebarItem: FC<{
|
|
38
24
|
item: SidebarNavigationItem;
|
|
@@ -54,19 +40,19 @@ const SidebarItem: FC<{
|
|
|
54
40
|
style={[
|
|
55
41
|
{
|
|
56
42
|
alignItems: "center",
|
|
57
|
-
backgroundColor: isActive ? theme.surface.
|
|
43
|
+
backgroundColor: isActive ? theme.surface.secondaryLight : "transparent",
|
|
58
44
|
borderRadius: theme.radius.default,
|
|
59
45
|
flexDirection: "row",
|
|
60
|
-
gap:
|
|
46
|
+
gap: 14,
|
|
61
47
|
height: ITEM_HEIGHT,
|
|
62
|
-
marginHorizontal:
|
|
63
|
-
paddingHorizontal:
|
|
48
|
+
marginHorizontal: 12,
|
|
49
|
+
paddingHorizontal: 14,
|
|
64
50
|
},
|
|
65
51
|
itemStyle,
|
|
66
52
|
]}
|
|
67
53
|
>
|
|
68
54
|
<View style={{alignItems: "center", justifyContent: "center", width: ICON_SIZE}}>
|
|
69
|
-
<Icon color={isActive ? "primary" : "
|
|
55
|
+
<Icon color={isActive ? "primary" : "secondaryDark"} iconName={item.iconName} size="md" />
|
|
70
56
|
{Boolean(item.badge) && (
|
|
71
57
|
<View
|
|
72
58
|
style={{
|
|
@@ -85,30 +71,15 @@ const SidebarItem: FC<{
|
|
|
85
71
|
</View>
|
|
86
72
|
)}
|
|
87
73
|
</View>
|
|
88
|
-
<Text bold={isActive} color={isActive ? "primary" : "
|
|
74
|
+
<Text bold={isActive} color={isActive ? "primary" : "secondaryDark"} size="md">
|
|
89
75
|
{item.label}
|
|
90
76
|
</Text>
|
|
91
77
|
</Pressable>
|
|
92
78
|
);
|
|
93
79
|
};
|
|
94
80
|
|
|
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
|
-
|
|
106
81
|
/**
|
|
107
|
-
* Renders the
|
|
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).
|
|
82
|
+
* Renders the hamburger button, drawer overlay, and children. Works without expo-router Navigator context.
|
|
112
83
|
*/
|
|
113
84
|
export const SidebarNavigationPanel: FC<SidebarNavigationPanelProps> = ({
|
|
114
85
|
topItems,
|
|
@@ -118,262 +89,187 @@ export const SidebarNavigationPanel: FC<SidebarNavigationPanelProps> = ({
|
|
|
118
89
|
children,
|
|
119
90
|
panelStyle,
|
|
120
91
|
itemStyle,
|
|
121
|
-
isOpen: isOpenProp,
|
|
122
|
-
onOpenChange,
|
|
123
92
|
}) => {
|
|
124
93
|
const {theme} = useTheme();
|
|
125
|
-
const
|
|
126
|
-
const
|
|
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;
|
|
94
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
95
|
+
const slideAnim = useRef(new Animated.Value(-DRAWER_WIDTH)).current;
|
|
132
96
|
const backdropAnim = useRef(new Animated.Value(0)).current;
|
|
133
|
-
const capturedSlideValue = useRef(0);
|
|
134
97
|
|
|
135
|
-
//
|
|
98
|
+
// Animate drawer open/close
|
|
136
99
|
useEffect(() => {
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
toValue: BACKDROP_OPACITY,
|
|
151
|
-
useNativeDriver: true,
|
|
152
|
-
}),
|
|
153
|
-
]).start();
|
|
154
|
-
}, [isOpen, slideAnim, backdropAnim, sheetHeight]);
|
|
155
|
-
|
|
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);
|
|
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();
|
|
181
113
|
} else {
|
|
182
|
-
|
|
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();
|
|
183
126
|
}
|
|
184
|
-
}, [
|
|
127
|
+
}, [isOpen, slideAnim, backdropAnim]);
|
|
128
|
+
|
|
129
|
+
const handleOpen = useCallback(() => setIsOpen(true), []);
|
|
130
|
+
const handleClose = useCallback(() => setIsOpen(false), []);
|
|
185
131
|
|
|
186
132
|
const handleNavigate = useCallback(
|
|
187
133
|
(route: string) => {
|
|
188
|
-
|
|
134
|
+
setIsOpen(false);
|
|
189
135
|
onNavigate(route);
|
|
190
136
|
},
|
|
191
|
-
[
|
|
137
|
+
[onNavigate]
|
|
192
138
|
);
|
|
193
139
|
|
|
194
|
-
const
|
|
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
|
-
);
|
|
140
|
+
const screenHeight = Dimensions.get("window").height;
|
|
233
141
|
|
|
234
142
|
return (
|
|
235
143
|
<View style={{flex: 1}}>
|
|
236
144
|
{children}
|
|
237
145
|
|
|
238
|
-
{/*
|
|
239
|
-
|
|
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 && (
|
|
240
174
|
<Pressable
|
|
241
|
-
|
|
242
|
-
accessibilityRole="button"
|
|
243
|
-
onPress={handleOpen}
|
|
175
|
+
onPress={handleClose}
|
|
244
176
|
style={{
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
justifyContent: "center",
|
|
248
|
-
left: 16,
|
|
177
|
+
bottom: 0,
|
|
178
|
+
left: 0,
|
|
249
179
|
position: "absolute",
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
zIndex:
|
|
180
|
+
right: 0,
|
|
181
|
+
top: 0,
|
|
182
|
+
zIndex: 100,
|
|
253
183
|
}}
|
|
254
184
|
>
|
|
255
|
-
<
|
|
185
|
+
<Animated.View
|
|
186
|
+
style={{
|
|
187
|
+
backgroundColor: "#000",
|
|
188
|
+
flex: 1,
|
|
189
|
+
opacity: backdropAnim,
|
|
190
|
+
}}
|
|
191
|
+
/>
|
|
256
192
|
</Pressable>
|
|
257
193
|
)}
|
|
258
194
|
|
|
259
|
-
{
|
|
260
|
-
|
|
261
|
-
|
|
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>
|
|
262
218
|
<Pressable
|
|
263
|
-
|
|
219
|
+
accessibilityLabel="Close navigation menu"
|
|
220
|
+
accessibilityRole="button"
|
|
264
221
|
onPress={handleClose}
|
|
265
|
-
style={{
|
|
222
|
+
style={{
|
|
223
|
+
alignItems: "center",
|
|
224
|
+
alignSelf: "flex-end",
|
|
225
|
+
height: 40,
|
|
226
|
+
justifyContent: "center",
|
|
227
|
+
marginRight: 12,
|
|
228
|
+
width: 40,
|
|
229
|
+
}}
|
|
266
230
|
>
|
|
267
|
-
<
|
|
231
|
+
<Icon color="secondaryDark" iconName="xmark" size="md" />
|
|
268
232
|
</Pressable>
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
}}
|
|
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}
|
|
303
241
|
/>
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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;
|
|
242
|
+
))}
|
|
243
|
+
</View>
|
|
244
|
+
</View>
|
|
331
245
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
-
)}
|
|
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
|
+
))}
|
|
358
255
|
</View>
|
|
359
|
-
|
|
360
|
-
</View>
|
|
256
|
+
</Animated.View>
|
|
361
257
|
</View>
|
|
362
258
|
);
|
|
363
259
|
};
|
|
364
260
|
|
|
365
|
-
/**
|
|
261
|
+
/**
|
|
262
|
+
* Reads active route from Navigator context and renders the drawer + Slot.
|
|
263
|
+
*/
|
|
366
264
|
const SidebarNavigatorContent: FC<{
|
|
367
265
|
topItems: SidebarNavigationItem[];
|
|
368
266
|
bottomItems: SidebarNavigationItem[];
|
|
369
|
-
isOpen: boolean;
|
|
370
|
-
onOpenChange: (isOpen: boolean) => void;
|
|
371
267
|
onNavigate?: (route: string) => void;
|
|
372
268
|
panelStyle?: StyleProp<ViewStyle>;
|
|
373
269
|
itemStyle?: StyleProp<ViewStyle>;
|
|
374
|
-
}> = ({topItems, bottomItems,
|
|
270
|
+
}> = ({topItems, bottomItems, onNavigate, panelStyle, itemStyle}) => {
|
|
375
271
|
const {state, navigation} = Navigator.useContext();
|
|
376
|
-
const activeRoute = state.routes[state.index];
|
|
272
|
+
const activeRoute = state.routes[state.index]?.name;
|
|
377
273
|
|
|
378
274
|
const handleNavigate = useCallback(
|
|
379
275
|
(route: string) => {
|
|
@@ -385,12 +281,10 @@ const SidebarNavigatorContent: FC<{
|
|
|
385
281
|
|
|
386
282
|
return (
|
|
387
283
|
<SidebarNavigationPanel
|
|
388
|
-
activeRoute={activeRoute
|
|
284
|
+
activeRoute={activeRoute}
|
|
389
285
|
bottomItems={bottomItems}
|
|
390
|
-
isOpen={isOpen}
|
|
391
286
|
itemStyle={itemStyle}
|
|
392
287
|
onNavigate={handleNavigate}
|
|
393
|
-
onOpenChange={onOpenChange}
|
|
394
288
|
panelStyle={panelStyle}
|
|
395
289
|
topItems={topItems}
|
|
396
290
|
>
|
|
@@ -400,7 +294,7 @@ const SidebarNavigatorContent: FC<{
|
|
|
400
294
|
};
|
|
401
295
|
|
|
402
296
|
/**
|
|
403
|
-
* Custom expo-router navigator with a
|
|
297
|
+
* Custom expo-router navigator with a hamburger-triggered slide-in drawer.
|
|
404
298
|
* Use in _layout.tsx files:
|
|
405
299
|
*
|
|
406
300
|
* ```tsx
|
|
@@ -414,7 +308,7 @@ const SidebarNavigatorContent: FC<{
|
|
|
414
308
|
* }
|
|
415
309
|
* ```
|
|
416
310
|
*/
|
|
417
|
-
const
|
|
311
|
+
export const SidebarNavigation: FC<SidebarNavigationProps> = ({
|
|
418
312
|
topItems,
|
|
419
313
|
bottomItems,
|
|
420
314
|
onNavigate,
|
|
@@ -422,27 +316,16 @@ const SidebarNavigationBase: FC<SidebarNavigationProps> = ({
|
|
|
422
316
|
screenOptions,
|
|
423
317
|
panelStyle,
|
|
424
318
|
itemStyle,
|
|
425
|
-
children,
|
|
426
319
|
}) => {
|
|
427
|
-
const [isSheetOpen, setIsSheetOpen] = useState(false);
|
|
428
|
-
|
|
429
320
|
return (
|
|
430
321
|
<Navigator initialRouteName={initialRouteName} router={TabRouter} screenOptions={screenOptions}>
|
|
431
|
-
<
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
onOpenChange={setIsSheetOpen}
|
|
439
|
-
panelStyle={panelStyle}
|
|
440
|
-
topItems={topItems}
|
|
441
|
-
/>
|
|
442
|
-
</View>
|
|
443
|
-
{children}
|
|
322
|
+
<SidebarNavigatorContent
|
|
323
|
+
bottomItems={bottomItems}
|
|
324
|
+
itemStyle={itemStyle}
|
|
325
|
+
onNavigate={onNavigate}
|
|
326
|
+
panelStyle={panelStyle}
|
|
327
|
+
topItems={topItems}
|
|
328
|
+
/>
|
|
444
329
|
</Navigator>
|
|
445
330
|
);
|
|
446
331
|
};
|
|
447
|
-
|
|
448
|
-
export const SidebarNavigation = Object.assign(SidebarNavigationBase, {Screen});
|