Live video on the AT Protocol

Merge pull request #641 from streamplace/natb/a-href-in-sidebar

fix: add proper web link behavior in sidebar

authored by natalie and committed by GitHub 481326da c657e0e5

+81 -26
+19
js/app/components/aqlink.tsx
··· 2 Link, 3 NavigationProp, 4 ParamListBase, 5 useNavigation, 6 } from "@react-navigation/native"; 7 import usePlatform from "hooks/usePlatform"; ··· 54 }, []); 55 return <Loading />; 56 }
··· 2 Link, 3 NavigationProp, 4 ParamListBase, 5 + useLinkBuilder, 6 useNavigation, 7 } from "@react-navigation/native"; 8 import usePlatform from "hooks/usePlatform"; ··· 55 }, []); 56 return <Loading />; 57 } 58 + 59 + // generates the proper href for a given LinkParams object, for better web support 60 + export function useAQLinkHref(to: LinkParams): { href?: string } { 61 + const { isWeb } = usePlatform(); 62 + const buildLink = useLinkBuilder(); 63 + 64 + if (!isWeb) { 65 + return { href: undefined }; 66 + } 67 + 68 + try { 69 + const href = buildLink(to.screen, to.params); 70 + return { href }; 71 + } catch (e) { 72 + console.warn("Failed to build link for", to, e); 73 + return { href: undefined }; 74 + } 75 + }
+29 -11
js/app/components/sidebar/sidebar-item.tsx
··· 1 - import { Text, useTheme } from "@streamplace/components"; 2 import React, { ReactNode, useState } from "react"; 3 import { 4 PressableStateCallbackType, 5 StyleProp, 6 View, 7 ViewStyle, 8 } from "react-native"; 9 - import { Pressable } from "react-native-gesture-handler"; 10 11 export default function SidebarItem({ 12 icon, ··· 14 collapsed, 15 active, 16 onPress, 17 style = null, 18 tint = "rgba(189, 110, 134)", 19 }: { ··· 24 label: string | ReactNode; 25 collapsed: boolean; 26 active: boolean; 27 - onPress: () => void; 28 style?: 29 | StyleProp<ViewStyle> 30 | ((state: PressableStateCallbackType) => StyleProp<ViewStyle>); ··· 32 }) { 33 const [hover, setHover] = useState<boolean>(false); 34 const theme = useTheme(); 35 36 // Handle different icon types - component, JSX element, or function returning JSX 37 const renderIcon = () => { ··· 62 } 63 64 // Fallback 65 - console.log("tried to render item, but couldn't", (icon as any).$$typeof); 66 67 return <Text>📄</Text>; 68 }; ··· 73 style={style} 74 onHoverIn={() => setHover(true)} 75 onHoverOut={() => setHover(false)} 76 > 77 <View 78 style={[ 79 { 80 backgroundColor: 81 hover || active ··· 84 ", " + (active && !hover ? "0.1" : "0.25") + ")", 85 ) 86 : undefined, 87 - borderRadius: 12, 88 - flexDirection: "row", 89 - justifyContent: "flex-start", 90 - alignItems: "center", 91 - paddingHorizontal: 12, 92 - gap: 8, 93 overflow: "hidden", 94 }, 95 ]} 96 > 97 - <View style={[{ width: 32, paddingVertical: 12 }]}>{renderIcon()}</View> 98 {!collapsed && ( 99 <View 100 style={[
··· 1 + import { DrawerNavigationState, ParamListBase } from "@react-navigation/native"; 2 + import { Text, useTheme, zero } from "@streamplace/components"; 3 + import { useAQLinkHref } from "components/aqlink"; 4 import React, { ReactNode, useState } from "react"; 5 import { 6 + GestureResponderEvent, 7 + Pressable, 8 PressableStateCallbackType, 9 StyleProp, 10 View, 11 ViewStyle, 12 } from "react-native"; 13 14 export default function SidebarItem({ 15 icon, ··· 17 collapsed, 18 active, 19 onPress, 20 + route, 21 style = null, 22 tint = "rgba(189, 110, 134)", 23 }: { ··· 28 label: string | ReactNode; 29 collapsed: boolean; 30 active: boolean; 31 + onPress: (event: GestureResponderEvent) => void; 32 + route?: DrawerNavigationState<ParamListBase>["routes"][number]; 33 style?: 34 | StyleProp<ViewStyle> 35 | ((state: PressableStateCallbackType) => StyleProp<ViewStyle>); ··· 37 }) { 38 const [hover, setHover] = useState<boolean>(false); 39 const theme = useTheme(); 40 + const { href } = useAQLinkHref({ 41 + screen: route?.name || "Home", 42 + params: route?.params as any, 43 + }); 44 45 // Handle different icon types - component, JSX element, or function returning JSX 46 const renderIcon = () => { ··· 71 } 72 73 // Fallback 74 + console.log( 75 + "tried to render item for route", 76 + label, 77 + href, 78 + ", but couldn't", 79 + (icon as any).$$typeof, 80 + ); 81 82 return <Text>📄</Text>; 83 }; ··· 88 style={style} 89 onHoverIn={() => setHover(true)} 90 onHoverOut={() => setHover(false)} 91 + role="link" 92 + accessibilityLabel={typeof label === "string" ? label : "Link to " + href} 93 + // @ts-ignore This makes it render as <a> on web! 94 + href={href} 95 > 96 <View 97 style={[ 98 + zero.r.md, 99 + zero.layout.flex.row, 100 + zero.layout.flex.alignCenter, 101 + zero.px[3], 102 + zero.gap.all[2], 103 { 104 backgroundColor: 105 hover || active ··· 108 ", " + (active && !hover ? "0.1" : "0.25") + ")", 109 ) 110 : undefined, 111 overflow: "hidden", 112 }, 113 ]} 114 > 115 + <View style={[zero.w[8], zero.py[3]]}>{renderIcon()}</View> 116 {!collapsed && ( 117 <View 118 style={[
+33 -15
js/app/components/sidebar/sidebar.tsx
··· 6 ParamListBase, 7 useNavigation, 8 } from "@react-navigation/native"; 9 - import { Text, useTheme } from "@streamplace/components"; 10 import React from "react"; 11 import { Image, Platform, View } from "react-native"; 12 import Animated, { ··· 44 widthAnim, 45 externalItems = [], 46 }: SidebarProps) { 47 - const theme = useTheme(); 48 const navigation = useNavigation(); 49 - // Apply the defined type to the component props 50 const animatedSidebarStyle = useAnimatedStyle(() => { 51 return { 52 minWidth: widthAnim.value, ··· 61 return ( 62 <Animated.View 63 style={[ 64 - animatedSidebarStyle, // Apply the animated style 65 - { padding: 8, gap: 8, flexDirection: "column" }, 66 ]} 67 > 68 <View 69 style={[ 70 { 71 - marginTop: Platform.OS === "ios" ? 29 : 12, 72 marginBottom: 20, 73 - paddingLeft: 10, 74 - gap: 12, 75 - flexDirection: "row", 76 - justifyContent: "flex-start", 77 - alignItems: "center", 78 }, 79 ]} 80 > 81 <Image 82 source={require("../../assets/images/cube.png")} 83 - style={[{ height: 30, width: 28 }]} 84 /> 85 {!collapsed && <Text size="2xl">Streamplace</Text>} 86 </View> ··· 98 | React.ComponentType<any> 99 | undefined; 100 101 return ( 102 <SidebarItem 103 key={route.key} ··· 105 label={label} 106 active={descriptor.navigation.isFocused()} 107 collapsed={collapsed} 108 - onPress={() => { 109 if (route.name === "Home") { 110 - // copy logic for 'Home' to reset the stack 111 navigation.dispatch( 112 CommonActions.reset({ 113 index: 0, ··· 126 } 127 }} 128 style={options.drawerItemStyle} 129 - tint={options.drawerActiveTintColor as string} // Assuming tint is a string color or undefined 130 /> 131 ); 132 })}
··· 6 ParamListBase, 7 useNavigation, 8 } from "@react-navigation/native"; 9 + import { Text, zero } from "@streamplace/components"; 10 import React from "react"; 11 import { Image, Platform, View } from "react-native"; 12 import Animated, { ··· 44 widthAnim, 45 externalItems = [], 46 }: SidebarProps) { 47 const navigation = useNavigation(); 48 const animatedSidebarStyle = useAnimatedStyle(() => { 49 return { 50 minWidth: widthAnim.value, ··· 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> ··· 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} ··· 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, ··· 144 } 145 }} 146 style={options.drawerItemStyle} 147 + tint={options.drawerActiveTintColor as string} 148 /> 149 ); 150 })}