diff --git a/src/app.tsx b/src/app.tsx
index 48249b1..461bb9a 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -1,6 +1,6 @@
import { Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
-import { createEffect, ErrorBoundary, Suspense } from "solid-js";
+import { createEffect, ErrorBoundary, Suspense, onMount, onCleanup } from "solid-js";
import "./app.css";
import { LeftBar, RightBar } from "./components/Bars";
import { TerminalSplash } from "./components/TerminalSplash";
@@ -10,14 +10,37 @@ import ErrorBoundaryFallback from "./components/ErrorBoundaryFallback";
import { BarsProvider, useBars } from "./context/bars";
function AppLayout(props: { children: any }) {
- const { leftBarSize, rightBarSize, setCenterWidth, centerWidth } = useBars();
+ const {
+ leftBarSize,
+ rightBarSize,
+ setCenterWidth,
+ centerWidth,
+ leftBarVisible,
+ rightBarVisible,
+ toggleLeftBar,
+ toggleRightBar,
+ setLeftBarVisible,
+ setRightBarVisible
+ } = useBars();
+
+ let lastScrollY = 0;
+ const SCROLL_THRESHOLD = 100;
createEffect(() => {
const handleResize = () => {
+ const isMobile = window.innerWidth < 768; // md breakpoint
+
+ // Show bars when switching to desktop
+ if (!isMobile) {
+ setLeftBarVisible(true);
+ setRightBarVisible(true);
+ }
+
const newWidth = window.innerWidth - leftBarSize() - rightBarSize();
setCenterWidth(newWidth);
};
+ // Call immediately and whenever dependencies change
handleResize();
window.addEventListener("resize", handleResize);
@@ -25,8 +48,98 @@ function AppLayout(props: { children: any }) {
return () => window.removeEventListener("resize", handleResize);
});
+ // Recalculate when bar sizes change (visibility or actual resize)
+ createEffect(() => {
+ const newWidth = window.innerWidth - leftBarSize() - rightBarSize();
+ setCenterWidth(newWidth);
+ });
+
+ // Auto-hide on scroll (mobile only)
+ onMount(() => {
+ const handleScroll = () => {
+ const currentScrollY = window.scrollY;
+ const isMobile = window.innerWidth < 768; // md breakpoint
+
+ if (isMobile && currentScrollY > SCROLL_THRESHOLD) {
+ // Scrolling down past threshold - hide left bar on mobile
+ if (currentScrollY > lastScrollY) {
+ setLeftBarVisible(false);
+ }
+ }
+
+ lastScrollY = currentScrollY;
+ };
+
+ window.addEventListener("scroll", handleScroll, { passive: true });
+
+ onCleanup(() => {
+ window.removeEventListener("scroll", handleScroll);
+ });
+ });
+
+ // Swipe gestures to reveal bars
+ onMount(() => {
+ let touchStartX = 0;
+ let touchStartY = 0;
+ const EDGE_THRESHOLD = 50; // pixels from edge to trigger
+ const SWIPE_THRESHOLD = 100; // minimum swipe distance
+
+ const handleTouchStart = (e: TouchEvent) => {
+ touchStartX = e.touches[0].clientX;
+ touchStartY = e.touches[0].clientY;
+ };
+
+ const handleTouchEnd = (e: TouchEvent) => {
+ const touchEndX = e.changedTouches[0].clientX;
+ const touchEndY = e.changedTouches[0].clientY;
+ const deltaX = touchEndX - touchStartX;
+ const deltaY = touchEndY - touchStartY;
+ const isMobile = window.innerWidth < 768; // md breakpoint
+
+ // Only trigger if horizontal swipe is dominant
+ if (Math.abs(deltaX) > Math.abs(deltaY)) {
+ // Mobile: Only left bar
+ if (isMobile) {
+ // Swipe right from left edge - reveal left bar
+ if (touchStartX < EDGE_THRESHOLD && deltaX > SWIPE_THRESHOLD) {
+ setLeftBarVisible(true);
+ }
+ } else {
+ // Desktop: Both bars
+ // Swipe right from left edge - reveal left bar
+ if (touchStartX < EDGE_THRESHOLD && deltaX > SWIPE_THRESHOLD) {
+ setLeftBarVisible(true);
+ }
+ // Swipe left from right edge - reveal right bar
+ else if (touchStartX > window.innerWidth - EDGE_THRESHOLD && deltaX < -SWIPE_THRESHOLD) {
+ setRightBarVisible(true);
+ }
+ }
+ }
+ };
+
+ window.addEventListener("touchstart", handleTouchStart, { passive: true });
+ window.addEventListener("touchend", handleTouchEnd, { passive: true });
+
+ onCleanup(() => {
+ window.removeEventListener("touchstart", handleTouchStart);
+ window.removeEventListener("touchend", handleTouchEnd);
+ });
+ });
+
return (
+ {/* Backdrop overlay - visible on mobile when sidebar is open */}
+
setLeftBarVisible(false)}
+ aria-label="Close sidebar"
+ />
+
{
if (ref) {
const updateSize = () => {
- setLeftBarSize(ref?.offsetWidth || 0);
+ actualWidth = ref?.offsetWidth || 0;
+ setLeftBarSize(leftBarVisible() ? actualWidth : 0);
};
-
+
updateSize();
-
- const resizeObserver = new ResizeObserver(updateSize);
+
+ const resizeObserver = new ResizeObserver((entries) => {
+ // Use requestAnimationFrame to avoid ResizeObserver loop error
+ requestAnimationFrame(() => {
+ actualWidth = ref?.offsetWidth || 0;
+ setLeftBarSize(leftBarVisible() ? actualWidth : 0);
+ });
+ });
resizeObserver.observe(ref);
-
+
return () => resizeObserver.disconnect();
}
});
+ // Update size when visibility changes
+ createEffect(() => {
+ setLeftBarSize(leftBarVisible() ? actualWidth : 0);
+ });
+
return (
@@ -55,28 +91,51 @@ export function LeftBar() {
}
export function RightBar() {
- const { setRightBarSize } = useBars();
+ const { setRightBarSize, rightBarVisible } = useBars();
let ref: HTMLDivElement | undefined;
+ let actualWidth = 0;
onMount(() => {
if (ref) {
const updateSize = () => {
- setRightBarSize(ref?.offsetWidth || 0);
+ actualWidth = ref?.offsetWidth || 0;
+ setRightBarSize(rightBarVisible() ? actualWidth : 0);
};
-
+
updateSize();
-
- const resizeObserver = new ResizeObserver(updateSize);
+
+ const resizeObserver = new ResizeObserver((entries) => {
+ // Use requestAnimationFrame to avoid ResizeObserver loop error
+ requestAnimationFrame(() => {
+ actualWidth = ref?.offsetWidth || 0;
+ setRightBarSize(rightBarVisible() ? actualWidth : 0);
+ });
+ });
resizeObserver.observe(ref);
-
+
return () => resizeObserver.disconnect();
}
});
+ // Update size when visibility changes
+ createEffect(() => {
+ setRightBarSize(rightBarVisible() ? actualWidth : 0);
+ });
+
return (
{p().title.replaceAll("_", " ")}