Live video on the AT Protocol
1import { DrawerNavigationOptions } from "@react-navigation/drawer";
2import { DrawerDescriptorMap } from "@react-navigation/drawer/lib/typescript/src/types";
3import {
4 CommonActions,
5 DrawerNavigationState,
6 ParamListBase,
7 useNavigation,
8} from "@react-navigation/native";
9import { Text, zero } from "@streamplace/components";
10import React from "react";
11import { Image, Platform, View } from "react-native";
12import Animated, {
13 SharedValue,
14 useAnimatedStyle,
15} from "react-native-reanimated";
16import SidebarItem from "./sidebar-item";
17
18export interface ExternalDrawerItem {
19 item:
20 | React.ComponentType<any>
21 | React.ReactElement
22 | (() => React.ReactElement);
23 label: React.ComponentType<any> | React.ReactElement | string;
24 onPress: () => void;
25}
26
27interface CustomSidebarProps {
28 collapsed: boolean;
29 hidden: boolean;
30 widthAnim: SharedValue<number>;
31 descriptors: DrawerDescriptorMap;
32 state: DrawerNavigationState<ParamListBase>;
33 externalItems?: ExternalDrawerItem[];
34}
35
36// Combine standard drawer props with custom props
37type SidebarProps = CustomSidebarProps & DrawerNavigationOptions;
38
39export default function Sidebar({
40 state,
41 descriptors,
42 collapsed,
43 hidden,
44 widthAnim,
45 externalItems = [],
46}: SidebarProps) {
47 const navigation = useNavigation();
48 const animatedSidebarStyle = useAnimatedStyle(() => {
49 return {
50 minWidth: widthAnim.value,
51 maxWidth: widthAnim.value,
52 };
53 });
54
55 if (hidden) {
56 return <View />;
57 }
58
59 return (
60 <Animated.View
61 style={[
62 animatedSidebarStyle,
63 zero.p[2],
64 zero.gap.all[2],
65 zero.layout.flex.column,
66 ]}
67 >
68 <View
69 style={[
70 zero.layout.flex.row,
71 zero.layout.flex.alignCenter,
72 zero.gap.all[3],
73 {
74 marginTop: Platform.OS === "ios" ? 29 : 8,
75 marginBottom: 20,
76 paddingLeft: 11,
77 },
78 ]}
79 >
80 <Image
81 source={require("../../assets/images/cube.png")}
82 height={30}
83 width={28}
84 style={{ width: 28, height: 30, resizeMode: "contain" }}
85 />
86 {!collapsed && <Text size="2xl">Streamplace</Text>}
87 </View>
88
89 {state.routes.map((route) => {
90 const descriptor = descriptors[route.key];
91 const options = descriptor?.options ?? {};
92
93 const label =
94 typeof options.drawerLabel === "function"
95 ? options.drawerLabel({ focused: false, color: "$color" })
96 : (options.drawerLabel ?? options.title ?? route.name);
97
98 const IconComponent = options.drawerIcon as
99 | React.ComponentType<any>
100 | undefined;
101
102 // if we have style display: none on the drawer item, completely skip rendering it
103 const drawerItemStyle = options.drawerItemStyle;
104 let isHidden = false;
105 if (
106 drawerItemStyle &&
107 typeof drawerItemStyle === "object" &&
108 "display" in drawerItemStyle &&
109 (drawerItemStyle as any).display === "none"
110 ) {
111 isHidden = true;
112 }
113 if (isHidden) {
114 return null;
115 }
116
117 return (
118 <SidebarItem
119 key={route.key}
120 icon={IconComponent ? IconComponent : () => <Text>📄</Text>}
121 label={label}
122 active={descriptor.navigation.isFocused()}
123 collapsed={collapsed}
124 route={route}
125 onPress={(ev) => {
126 ev.preventDefault();
127 if (route.name === "Home") {
128 // reset the stack (b/c streamlist is in the same stack as home)
129 navigation.dispatch(
130 CommonActions.reset({
131 index: 0,
132 routes: [
133 {
134 name: "Home",
135 state: {
136 routes: [{ name: "StreamList" }],
137 },
138 },
139 ],
140 }),
141 );
142 } else {
143 navigation.navigate(route.name as any);
144 }
145 }}
146 style={options.drawerItemStyle}
147 tint={options.drawerActiveTintColor as string}
148 />
149 );
150 })}
151 {externalItems.map((i, num) => {
152 // Handle different label types - string, JSX element, or component
153 const renderLabel = () => {
154 if (typeof i.label === "string") {
155 return i.label;
156 }
157
158 // If it's already a JSX element, return it directly
159 if (React.isValidElement(i.label)) {
160 return i.label;
161 }
162
163 // If it's a function (component), call it
164 if (typeof i.label === "function") {
165 const LabelComponent = i.label;
166 return <LabelComponent />;
167 }
168
169 // Fallback
170 return "Item";
171 };
172
173 return (
174 <SidebarItem
175 key={num}
176 icon={i.item}
177 label={renderLabel()}
178 active={false}
179 collapsed={collapsed}
180 onPress={() => i.onPress()}
181 tint="rgba(189, 110, 134)"
182 />
183 );
184 })}
185 </Animated.View>
186 );
187}