-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #16 from rice-apps/sidebar
feat: jost font support, schedule tab, more dynamic sidebar resizing and memopt
- Loading branch information
Showing
14 changed files
with
430 additions
and
361 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,117 +1,134 @@ | ||
import React, { createContext, useContext, useState } from 'react'; | ||
import { View, Dimensions, SafeAreaView } from 'react-native'; | ||
import React, { useState } from "react"; | ||
import { View, Dimensions, SafeAreaView } from "react-native"; | ||
import Animated, { | ||
useSharedValue, | ||
useAnimatedStyle, | ||
withTiming, | ||
Easing, | ||
} from 'react-native-reanimated'; | ||
import { GestureDetector, Gesture } from 'react-native-gesture-handler'; | ||
import { Slot } from 'expo-router'; | ||
import Sidebar, { SidebarContext, useSidebarContext } from '../../components/Sidebar'; | ||
runOnJS, | ||
} from "react-native-reanimated"; | ||
import { GestureDetector, Gesture } from "react-native-gesture-handler"; | ||
import { Slot } from "expo-router"; | ||
import Sidebar, { | ||
SidebarContext, | ||
useSidebarContext, | ||
useTransitionCustomization, | ||
} from "@/components/Sidebar"; | ||
|
||
// Customize transition settings | ||
const TransitionCustomization = createContext({ | ||
transitionEasing: Easing.out(Easing.cubic), | ||
transitionDuration: 400, // in ms | ||
// Add more | ||
}) | ||
|
||
export const useTransitionCustomization = () => useContext(TransitionCustomization); | ||
|
||
export default function Layout() { | ||
// Sidebar state, dimensions | ||
const DrawerLayout = () => { | ||
// Sidebar state, dimensions, and transition settings | ||
const { transitionEasing, transitionDuration } = useTransitionCustomization(); | ||
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); | ||
const [isSidebarOpen, setIsSidebarOpen] = useState(true); | ||
const [isTransitioning, setIsTransitioning] = useState(false); | ||
const { openSidebarWidth, closedSidebarWidth } = useSidebarContext(); | ||
const { | ||
openSidebarWidth, | ||
closedSidebarWidth, | ||
activeIconTextColor, | ||
defaultIconTextColor, | ||
activeTabColor, | ||
defaultTabColor, | ||
buttonColor, | ||
buttonSize, | ||
} = useSidebarContext(); | ||
// Tie animations to initial sidebar state (currently set to open) | ||
const sidebarAnimatedValue = useSharedValue(isSidebarOpen ? 1 : 0); | ||
const mainScreenWidth = useSharedValue( | ||
Dimensions.get('window').width - (openSidebarWidth + closedSidebarWidth)); | ||
// Transition settings | ||
const { transitionEasing, transitionDuration } = useTransitionCustomization(); | ||
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); | ||
Dimensions.get("window").width - (openSidebarWidth + closedSidebarWidth) | ||
); | ||
|
||
// Toggle sidebar state | ||
const toggleSidebar = async () => { | ||
// Perform transitions | ||
setIsTransitioning(true); | ||
setIsSidebarOpen(!isSidebarOpen); | ||
transitionMainScreen(); | ||
transitionSidebar(); | ||
// Allow swipes after 400ms duration | ||
await delay(transitionDuration); | ||
setIsTransitioning(false); | ||
if ( | ||
(sidebarAnimatedValue.value === 1 && isSidebarOpen) || | ||
(sidebarAnimatedValue.value === 0 && !isSidebarOpen) | ||
) { | ||
setIsTransitioning(true); | ||
setIsSidebarOpen(!isSidebarOpen); | ||
transitionSidebar(); | ||
transitionMainScreen(); | ||
// Allow swipes after complete transition | ||
await delay(transitionDuration); | ||
setIsTransitioning(false); | ||
} | ||
}; | ||
|
||
// Set main screen dynamic width | ||
const mainScreenAnimatedStyle = useAnimatedStyle(() => { | ||
return { width: mainScreenWidth.value }; | ||
}); | ||
|
||
// Transitions sidebar for 400ms duration | ||
const transitionSidebar = () => { | ||
sidebarAnimatedValue.value = withTiming(isSidebarOpen ? 0 : 1, { | ||
duration: transitionDuration, | ||
easing: transitionEasing, | ||
}); | ||
}; | ||
|
||
// Transitions main screen for 400ms duration | ||
const transitionMainScreen = () => { | ||
mainScreenWidth.value = withTiming( | ||
(mainScreenWidth.value === Dimensions.get('window').width - (openSidebarWidth + closedSidebarWidth) && isSidebarOpen) ? | ||
Dimensions.get('window').width - closedSidebarWidth : | ||
Dimensions.get('window').width - (openSidebarWidth + closedSidebarWidth), | ||
{ | ||
duration: transitionDuration, | ||
easing: transitionEasing, | ||
} | ||
); | ||
}; | ||
|
||
// Transitions sidebar for 400ms duration | ||
const transitionSidebar = () => { | ||
sidebarAnimatedValue.value = withTiming( | ||
sidebarAnimatedValue.value === 1 && isSidebarOpen ? 0 : 1, { | ||
isSidebarOpen | ||
? Dimensions.get("window").width - closedSidebarWidth | ||
: Dimensions.get("window").width - (openSidebarWidth + closedSidebarWidth), | ||
{ | ||
duration: transitionDuration, | ||
easing: transitionEasing, | ||
} | ||
); | ||
}; | ||
|
||
// Horizontal swipes trigger | ||
const swipeGesture = Gesture.Pan() | ||
.onUpdate((event) => { | ||
// Horizontal swipe triggers sidebar toggle | ||
const swipeGesture = Gesture.Pan().onUpdate((event) => { | ||
// Ignore swipes mid-transition | ||
if (isTransitioning) return; | ||
// Threshold horizontal distance in px for swipe to trigger | ||
if (event.translationX > 50 && !isSidebarOpen) { | ||
// Swipe right opens sidebar | ||
toggleSidebar(); | ||
} else if (event.translationX < -50 && isSidebarOpen) { | ||
// Swipe left closes sidebar | ||
toggleSidebar(); | ||
// Threshold horizontal distance is 50 px to trigger | ||
if ( | ||
(event.translationX > 50 && !isSidebarOpen) || | ||
(event.translationX < -50 && isSidebarOpen) | ||
) { | ||
runOnJS(toggleSidebar)(); | ||
} | ||
}); | ||
|
||
return ( | ||
<SafeAreaView> | ||
<SidebarContext.Provider value={{ isSidebarOpen, toggleSidebar, openSidebarWidth, closedSidebarWidth }}> | ||
<SidebarContext.Provider | ||
value={{ | ||
isSidebarOpen, | ||
toggleSidebar, | ||
openSidebarWidth, | ||
closedSidebarWidth, | ||
activeIconTextColor, | ||
defaultIconTextColor, | ||
activeTabColor, | ||
defaultTabColor, | ||
buttonColor, | ||
buttonSize, | ||
}} | ||
> | ||
<GestureDetector gesture={swipeGesture}> | ||
<View className="flex-1 flex-row" collapsable={false}> | ||
|
||
{/* Sidebar */} | ||
<Sidebar animatedValue={sidebarAnimatedValue} /> | ||
|
||
{/* Main screen in ./(drawer)/ */} | ||
<Animated.View style={[ | ||
mainScreenAnimatedStyle, | ||
{ | ||
position: 'absolute', | ||
right: 0, | ||
height: Dimensions.get('window').height, | ||
zIndex: 0, // Covered by sidebar layer | ||
}, | ||
]}> | ||
<Slot /> | ||
<Animated.View | ||
style={[ | ||
mainScreenAnimatedStyle, | ||
{ | ||
position: "absolute", | ||
right: 0, | ||
height: Dimensions.get("window").height, | ||
zIndex: 0, // Covered by sidebar layer | ||
}, | ||
]} | ||
> | ||
<Slot /> | ||
</Animated.View> | ||
|
||
</View> | ||
</GestureDetector> | ||
</SidebarContext.Provider> | ||
</SafeAreaView> | ||
); | ||
} | ||
}; | ||
export default DrawerLayout; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import React from "react"; | ||
import { View, Text } from "react-native"; | ||
|
||
export default function SchedulePage() { | ||
return ( | ||
<View className="flex-1 items-center justify-center bg-gray-100"> | ||
<Text>Placeholder schedule page</Text> | ||
</View> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes
Binary file not shown.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.