fix resizing bugs (conflicting handlers)
This commit is contained in:
26
src/app.tsx
26
src/app.tsx
@@ -14,6 +14,7 @@ import { TerminalSplash } from "./components/TerminalSplash";
|
|||||||
import { MetaProvider } from "@solidjs/meta";
|
import { MetaProvider } from "@solidjs/meta";
|
||||||
import ErrorBoundaryFallback from "./components/ErrorBoundaryFallback";
|
import ErrorBoundaryFallback from "./components/ErrorBoundaryFallback";
|
||||||
import { BarsProvider, useBars } from "./context/bars";
|
import { BarsProvider, useBars } from "./context/bars";
|
||||||
|
import { createWindowWidth, isMobile } from "~/lib/resize-utils";
|
||||||
|
|
||||||
function AppLayout(props: { children: any }) {
|
function AppLayout(props: { children: any }) {
|
||||||
const {
|
const {
|
||||||
@@ -28,15 +29,16 @@ function AppLayout(props: { children: any }) {
|
|||||||
barsInitialized
|
barsInitialized
|
||||||
} = useBars();
|
} = useBars();
|
||||||
|
|
||||||
|
const windowWidth = createWindowWidth();
|
||||||
let lastScrollY = 0;
|
let lastScrollY = 0;
|
||||||
const SCROLL_THRESHOLD = 100;
|
const SCROLL_THRESHOLD = 100;
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
const isMobile = window.innerWidth < 768; // md breakpoint
|
const currentIsMobile = isMobile(windowWidth());
|
||||||
|
|
||||||
// Show bars when switching to desktop
|
// Show bars when switching to desktop
|
||||||
if (!isMobile) {
|
if (!currentIsMobile) {
|
||||||
setLeftBarVisible(true);
|
setLeftBarVisible(true);
|
||||||
setRightBarVisible(true);
|
setRightBarVisible(true);
|
||||||
}
|
}
|
||||||
@@ -48,10 +50,6 @@ function AppLayout(props: { children: any }) {
|
|||||||
|
|
||||||
// Call immediately and whenever dependencies change
|
// Call immediately and whenever dependencies change
|
||||||
handleResize();
|
handleResize();
|
||||||
|
|
||||||
window.addEventListener("resize", handleResize);
|
|
||||||
|
|
||||||
return () => window.removeEventListener("resize", handleResize);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Recalculate when bar sizes change (visibility or actual resize)
|
// Recalculate when bar sizes change (visibility or actual resize)
|
||||||
@@ -65,9 +63,9 @@ function AppLayout(props: { children: any }) {
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
const currentScrollY = window.scrollY;
|
const currentScrollY = window.scrollY;
|
||||||
const isMobile = window.innerWidth < 768; // md breakpoint
|
const currentIsMobile = isMobile(windowWidth());
|
||||||
|
|
||||||
if (isMobile && currentScrollY > SCROLL_THRESHOLD) {
|
if (currentIsMobile && currentScrollY > SCROLL_THRESHOLD) {
|
||||||
// Scrolling down past threshold - hide left bar on mobile
|
// Scrolling down past threshold - hide left bar on mobile
|
||||||
if (currentScrollY > lastScrollY) {
|
if (currentScrollY > lastScrollY) {
|
||||||
setLeftBarVisible(false);
|
setLeftBarVisible(false);
|
||||||
@@ -87,9 +85,9 @@ function AppLayout(props: { children: any }) {
|
|||||||
// ESC key to close sidebars on mobile
|
// ESC key to close sidebars on mobile
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
const isMobile = window.innerWidth < 768; // md breakpoint
|
const currentIsMobile = isMobile(windowWidth());
|
||||||
|
|
||||||
if (e.key === "Escape" && isMobile) {
|
if (e.key === "Escape" && currentIsMobile) {
|
||||||
if (leftBarVisible()) {
|
if (leftBarVisible()) {
|
||||||
setLeftBarVisible(false);
|
setLeftBarVisible(false);
|
||||||
}
|
}
|
||||||
@@ -122,12 +120,12 @@ function AppLayout(props: { children: any }) {
|
|||||||
const touchEndY = e.changedTouches[0].clientY;
|
const touchEndY = e.changedTouches[0].clientY;
|
||||||
const deltaX = touchEndX - touchStartX;
|
const deltaX = touchEndX - touchStartX;
|
||||||
const deltaY = touchEndY - touchStartY;
|
const deltaY = touchEndY - touchStartY;
|
||||||
const isMobile = window.innerWidth < 768; // md breakpoint
|
const currentIsMobile = isMobile(windowWidth());
|
||||||
|
|
||||||
// Only trigger if horizontal swipe is dominant
|
// Only trigger if horizontal swipe is dominant
|
||||||
if (Math.abs(deltaX) > Math.abs(deltaY)) {
|
if (Math.abs(deltaX) > Math.abs(deltaY)) {
|
||||||
// Mobile: Only left bar
|
// Mobile: Only left bar
|
||||||
if (isMobile) {
|
if (currentIsMobile) {
|
||||||
// Swipe right anywhere - reveal left bar
|
// Swipe right anywhere - reveal left bar
|
||||||
if (deltaX > SWIPE_THRESHOLD) {
|
if (deltaX > SWIPE_THRESHOLD) {
|
||||||
setLeftBarVisible(true);
|
setLeftBarVisible(true);
|
||||||
@@ -160,10 +158,10 @@ function AppLayout(props: { children: any }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleCenterTapRelease = (e: MouseEvent | TouchEvent) => {
|
const handleCenterTapRelease = (e: MouseEvent | TouchEvent) => {
|
||||||
const isMobile = window.innerWidth < 768;
|
const currentIsMobile = isMobile(windowWidth());
|
||||||
|
|
||||||
// Only hide left bar on mobile when it's visible
|
// Only hide left bar on mobile when it's visible
|
||||||
if (isMobile && leftBarVisible()) {
|
if (currentIsMobile && leftBarVisible()) {
|
||||||
const target = e.target as HTMLElement;
|
const target = e.target as HTMLElement;
|
||||||
const isInteractive = target.closest(
|
const isInteractive = target.closest(
|
||||||
"a, button, input, select, textarea, [onclick]"
|
"a, button, input, select, textarea, [onclick]"
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
import { createSignal, createEffect, onMount, onCleanup, children as resolveChildren, type ParentComponent, createMemo, For } from "solid-js";
|
import {
|
||||||
|
createSignal,
|
||||||
|
createEffect,
|
||||||
|
onMount,
|
||||||
|
onCleanup,
|
||||||
|
children as resolveChildren,
|
||||||
|
type ParentComponent,
|
||||||
|
createMemo,
|
||||||
|
For
|
||||||
|
} from "solid-js";
|
||||||
import { animate } from "motion";
|
import { animate } from "motion";
|
||||||
|
import { createWindowWidth } from "~/lib/resize-utils";
|
||||||
|
|
||||||
type ParallaxBackground = {
|
type ParallaxBackground = {
|
||||||
imageSet: { [key: number]: string };
|
imageSet: { [key: number]: string };
|
||||||
@@ -21,11 +31,17 @@ type ParallaxLayerProps = {
|
|||||||
|
|
||||||
function ParallaxLayer(props: ParallaxLayerProps) {
|
function ParallaxLayer(props: ParallaxLayerProps) {
|
||||||
let containerRef: HTMLDivElement | undefined;
|
let containerRef: HTMLDivElement | undefined;
|
||||||
|
|
||||||
const layerDepthFactor = createMemo(() => props.layer / (Object.keys(props.caveParallax.imageSet).length - 1));
|
const layerDepthFactor = createMemo(
|
||||||
const layerVerticalOffset = createMemo(() => props.verticalOffsetPixels * layerDepthFactor());
|
() => props.layer / (Object.keys(props.caveParallax.imageSet).length - 1)
|
||||||
|
);
|
||||||
|
const layerVerticalOffset = createMemo(
|
||||||
|
() => props.verticalOffsetPixels * layerDepthFactor()
|
||||||
|
);
|
||||||
const speed = createMemo(() => (120 - props.layer * 10) * 1000);
|
const speed = createMemo(() => (120 - props.layer * 10) * 1000);
|
||||||
const targetX = createMemo(() => props.direction * -props.caveParallax.size.width * props.imagesNeeded);
|
const targetX = createMemo(
|
||||||
|
() => props.direction * -props.caveParallax.size.width * props.imagesNeeded
|
||||||
|
);
|
||||||
|
|
||||||
const containerStyle = createMemo(() => ({
|
const containerStyle = createMemo(() => ({
|
||||||
width: `${props.caveParallax.size.width * props.imagesNeeded * 3}px`,
|
width: `${props.caveParallax.size.width * props.imagesNeeded * 3}px`,
|
||||||
@@ -33,19 +49,19 @@ function ParallaxLayer(props: ParallaxLayerProps) {
|
|||||||
left: `${(props.dimensions.width - props.scaledWidth) / 2}px`,
|
left: `${(props.dimensions.width - props.scaledWidth) / 2}px`,
|
||||||
top: `${(props.dimensions.height - props.scaledHeight) / 2 + layerVerticalOffset()}px`,
|
top: `${(props.dimensions.height - props.scaledHeight) / 2 + layerVerticalOffset()}px`,
|
||||||
"transform-origin": "center center",
|
"transform-origin": "center center",
|
||||||
"will-change": "transform",
|
"will-change": "transform"
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Set up animation when component mounts or when direction/speed changes
|
// Set up animation when component mounts or when direction/speed changes
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!containerRef) return;
|
if (!containerRef) return;
|
||||||
|
|
||||||
const target = targetX();
|
const target = targetX();
|
||||||
const duration = speed() / 1000;
|
const duration = speed() / 1000;
|
||||||
|
|
||||||
const controls = animate(
|
const controls = animate(
|
||||||
containerRef,
|
containerRef,
|
||||||
{
|
{
|
||||||
transform: [
|
transform: [
|
||||||
`translateX(0px) scale(${props.scale})`,
|
`translateX(0px) scale(${props.scale})`,
|
||||||
`translateX(${target}px) scale(${props.scale})`
|
`translateX(${target}px) scale(${props.scale})`
|
||||||
@@ -54,7 +70,7 @@ function ParallaxLayer(props: ParallaxLayerProps) {
|
|||||||
{
|
{
|
||||||
duration,
|
duration,
|
||||||
easing: "linear",
|
easing: "linear",
|
||||||
repeat: Infinity,
|
repeat: Infinity
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -68,7 +84,7 @@ function ParallaxLayer(props: ParallaxLayerProps) {
|
|||||||
style={{
|
style={{
|
||||||
left: `${groupOffset * props.caveParallax.size.width * props.imagesNeeded}px`,
|
left: `${groupOffset * props.caveParallax.size.width * props.imagesNeeded}px`,
|
||||||
width: `${props.caveParallax.size.width * props.imagesNeeded}px`,
|
width: `${props.caveParallax.size.width * props.imagesNeeded}px`,
|
||||||
height: `${props.caveParallax.size.height}px`,
|
height: `${props.caveParallax.size.height}px`
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Array.from({ length: props.imagesNeeded }).map((_, index) => (
|
{Array.from({ length: props.imagesNeeded }).map((_, index) => (
|
||||||
@@ -77,7 +93,7 @@ function ParallaxLayer(props: ParallaxLayerProps) {
|
|||||||
style={{
|
style={{
|
||||||
width: `${props.caveParallax.size.width}px`,
|
width: `${props.caveParallax.size.width}px`,
|
||||||
height: `${props.caveParallax.size.height}px`,
|
height: `${props.caveParallax.size.height}px`,
|
||||||
left: `${index * props.caveParallax.size.width}px`,
|
left: `${index * props.caveParallax.size.width}px`
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@@ -86,7 +102,12 @@ function ParallaxLayer(props: ParallaxLayerProps) {
|
|||||||
width={props.caveParallax.size.width}
|
width={props.caveParallax.size.width}
|
||||||
height={props.caveParallax.size.height}
|
height={props.caveParallax.size.height}
|
||||||
style={{ "object-fit": "cover" }}
|
style={{ "object-fit": "cover" }}
|
||||||
loading={props.layer > Object.keys(props.caveParallax.imageSet).length - 3 ? "eager" : "lazy"}
|
loading={
|
||||||
|
props.layer >
|
||||||
|
Object.keys(props.caveParallax.imageSet).length - 3
|
||||||
|
? "eager"
|
||||||
|
: "lazy"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -95,11 +116,7 @@ function ParallaxLayer(props: ParallaxLayerProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div ref={containerRef} class="absolute" style={containerStyle()}>
|
||||||
ref={containerRef}
|
|
||||||
class="absolute"
|
|
||||||
style={containerStyle()}
|
|
||||||
>
|
|
||||||
{imageGroups()}
|
{imageGroups()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -107,9 +124,17 @@ function ParallaxLayer(props: ParallaxLayerProps) {
|
|||||||
|
|
||||||
const SimpleParallax: ParentComponent = (props) => {
|
const SimpleParallax: ParentComponent = (props) => {
|
||||||
let containerRef: HTMLDivElement | undefined;
|
let containerRef: HTMLDivElement | undefined;
|
||||||
const [dimensions, setDimensions] = createSignal({ width: 0, height: 0 });
|
const windowWidth = createWindowWidth(100);
|
||||||
|
const [windowHeight, setWindowHeight] = createSignal(
|
||||||
|
typeof window !== "undefined" ? window.innerHeight : 800
|
||||||
|
);
|
||||||
const [direction, setDirection] = createSignal(1);
|
const [direction, setDirection] = createSignal(1);
|
||||||
|
|
||||||
|
const dimensions = createMemo(() => ({
|
||||||
|
width: windowWidth(),
|
||||||
|
height: windowHeight()
|
||||||
|
}));
|
||||||
|
|
||||||
const caveParallax = createMemo<ParallaxBackground>(() => ({
|
const caveParallax = createMemo<ParallaxBackground>(() => ({
|
||||||
imageSet: {
|
imageSet: {
|
||||||
0: "/Cave/0.png",
|
0: "/Cave/0.png",
|
||||||
@@ -119,33 +144,27 @@ const SimpleParallax: ParentComponent = (props) => {
|
|||||||
4: "/Cave/4.png",
|
4: "/Cave/4.png",
|
||||||
5: "/Cave/5.png",
|
5: "/Cave/5.png",
|
||||||
6: "/Cave/6.png",
|
6: "/Cave/6.png",
|
||||||
7: "/Cave/7.png",
|
7: "/Cave/7.png"
|
||||||
},
|
},
|
||||||
size: { width: 384, height: 216 },
|
size: { width: 384, height: 216 },
|
||||||
verticalOffset: 0.4,
|
verticalOffset: 0.4
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const layerCount = createMemo(() => Object.keys(caveParallax().imageSet).length - 1);
|
const layerCount = createMemo(
|
||||||
|
() => Object.keys(caveParallax().imageSet).length - 1
|
||||||
|
);
|
||||||
const imagesNeeded = 3;
|
const imagesNeeded = 3;
|
||||||
|
|
||||||
const updateDimensions = () => {
|
|
||||||
if (containerRef) {
|
|
||||||
setDimensions({
|
|
||||||
width: window.innerWidth,
|
|
||||||
height: window.innerHeight,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
let timeoutId: ReturnType<typeof setTimeout>;
|
let timeoutId: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
timeoutId = setTimeout(updateDimensions, 100);
|
timeoutId = setTimeout(() => {
|
||||||
|
setWindowHeight(window.innerHeight);
|
||||||
|
}, 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
updateDimensions();
|
|
||||||
window.addEventListener("resize", handleResize);
|
window.addEventListener("resize", handleResize);
|
||||||
|
|
||||||
const intervalId = setInterval(() => {
|
const intervalId = setInterval(() => {
|
||||||
@@ -166,7 +185,7 @@ const SimpleParallax: ParentComponent = (props) => {
|
|||||||
scale: 0,
|
scale: 0,
|
||||||
scaledWidth: 0,
|
scaledWidth: 0,
|
||||||
scaledHeight: 0,
|
scaledHeight: 0,
|
||||||
verticalOffsetPixels: 0,
|
verticalOffsetPixels: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +198,7 @@ const SimpleParallax: ParentComponent = (props) => {
|
|||||||
scale,
|
scale,
|
||||||
scaledWidth: cave.size.width * scale,
|
scaledWidth: cave.size.width * scale,
|
||||||
scaledHeight: cave.size.height * scale,
|
scaledHeight: cave.size.height * scale,
|
||||||
verticalOffsetPixels: cave.verticalOffset * dims.height,
|
verticalOffsetPixels: cave.verticalOffset * dims.height
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -214,13 +233,13 @@ const SimpleParallax: ParentComponent = (props) => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
class="fixed inset-0 w-screen h-screen overflow-hidden"
|
class="fixed inset-0 h-screen w-screen overflow-hidden"
|
||||||
>
|
>
|
||||||
<div class="absolute inset-0 bg-black"></div>
|
<div class="absolute inset-0 bg-black"></div>
|
||||||
<div
|
<div
|
||||||
class="absolute inset-0"
|
class="absolute inset-0"
|
||||||
style={{
|
style={{
|
||||||
"margin-top": `${calculations().verticalOffsetPixels}px`,
|
"margin-top": `${calculations().verticalOffsetPixels}px`
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{parallaxLayers()}
|
{parallaxLayers()}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import type {
|
|||||||
CommentReaction,
|
CommentReaction,
|
||||||
UserPublicData
|
UserPublicData
|
||||||
} from "~/types/comment";
|
} from "~/types/comment";
|
||||||
import { debounce } from "es-toolkit";
|
import { createWindowWidth } from "~/lib/resize-utils";
|
||||||
import UserDefaultImage from "~/components/icons/UserDefaultImage";
|
import UserDefaultImage from "~/components/icons/UserDefaultImage";
|
||||||
import ReplyIcon from "~/components/icons/ReplyIcon";
|
import ReplyIcon from "~/components/icons/ReplyIcon";
|
||||||
import TrashIcon from "~/components/icons/TrashIcon";
|
import TrashIcon from "~/components/icons/TrashIcon";
|
||||||
@@ -32,7 +32,7 @@ export default function CommentBlock(props: CommentBlockProps) {
|
|||||||
const [replyBoxShowing, setReplyBoxShowing] = createSignal(false);
|
const [replyBoxShowing, setReplyBoxShowing] = createSignal(false);
|
||||||
const [toggleHeight, setToggleHeight] = createSignal(0);
|
const [toggleHeight, setToggleHeight] = createSignal(0);
|
||||||
const [reactions, setReactions] = createSignal<CommentReaction[]>([]);
|
const [reactions, setReactions] = createSignal<CommentReaction[]>([]);
|
||||||
const [windowWidth, setWindowWidth] = createSignal(0);
|
const windowWidth = createWindowWidth(200);
|
||||||
const [deletionLoading, setDeletionLoading] = createSignal(false);
|
const [deletionLoading, setDeletionLoading] = createSignal(false);
|
||||||
const [userData, setUserData] = createSignal<UserPublicData | null>(null);
|
const [userData, setUserData] = createSignal<UserPublicData | null>(null);
|
||||||
|
|
||||||
@@ -45,19 +45,6 @@ export default function CommentBlock(props: CommentBlockProps) {
|
|||||||
setCommentCollapsed(props.level >= 4);
|
setCommentCollapsed(props.level >= 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Window resize handler
|
|
||||||
onMount(() => {
|
|
||||||
const handleResize = debounce(() => {
|
|
||||||
setWindowWidth(window.innerWidth);
|
|
||||||
}, 200);
|
|
||||||
|
|
||||||
window.addEventListener("resize", handleResize);
|
|
||||||
|
|
||||||
onCleanup(() => {
|
|
||||||
window.removeEventListener("resize", handleResize);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Find user data from comment map
|
// Find user data from comment map
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (props.userCommentMap) {
|
if (props.userCommentMap) {
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
import {
|
import { Accessor, createContext, useContext, createMemo } from "solid-js";
|
||||||
Accessor,
|
|
||||||
createContext,
|
|
||||||
useContext,
|
|
||||||
createMemo,
|
|
||||||
onMount,
|
|
||||||
onCleanup
|
|
||||||
} from "solid-js";
|
|
||||||
import { createSignal } from "solid-js";
|
import { createSignal } from "solid-js";
|
||||||
|
import { createWindowWidth, isMobile } from "~/lib/resize-utils";
|
||||||
|
|
||||||
const BarsContext = createContext<{
|
const BarsContext = createContext<{
|
||||||
leftBarSize: Accessor<number>;
|
leftBarSize: Accessor<number>;
|
||||||
@@ -45,30 +39,15 @@ export function BarsProvider(props: { children: any }) {
|
|||||||
const [_rightBarNaturalSize, _setRightBarNaturalSize] = createSignal(0);
|
const [_rightBarNaturalSize, _setRightBarNaturalSize] = createSignal(0);
|
||||||
const [syncedBarSize, setSyncedBarSize] = createSignal(0);
|
const [syncedBarSize, setSyncedBarSize] = createSignal(0);
|
||||||
const [centerWidth, setCenterWidth] = createSignal(0);
|
const [centerWidth, setCenterWidth] = createSignal(0);
|
||||||
const initialWindowWidth =
|
const windowWidth = createWindowWidth();
|
||||||
typeof window !== "undefined" ? window.innerWidth : 1024;
|
const initialIsMobile = isMobile(windowWidth());
|
||||||
const isMobile = initialWindowWidth < 768;
|
const [leftBarVisible, setLeftBarVisible] = createSignal(!initialIsMobile);
|
||||||
const [leftBarVisible, setLeftBarVisible] = createSignal(!isMobile);
|
|
||||||
const [rightBarVisible, setRightBarVisible] = createSignal(true);
|
const [rightBarVisible, setRightBarVisible] = createSignal(true);
|
||||||
const [barsInitialized, setBarsInitialized] = createSignal(false);
|
const [barsInitialized, setBarsInitialized] = createSignal(false);
|
||||||
const [windowWidth, setWindowWidth] = createSignal(initialWindowWidth);
|
|
||||||
|
|
||||||
let leftBarSized = false;
|
let leftBarSized = false;
|
||||||
let rightBarSized = false;
|
let rightBarSized = false;
|
||||||
|
|
||||||
// Track window width reactively for mobile/desktop detection
|
|
||||||
onMount(() => {
|
|
||||||
const handleResize = () => {
|
|
||||||
setWindowWidth(window.innerWidth);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("resize", handleResize);
|
|
||||||
|
|
||||||
onCleanup(() => {
|
|
||||||
window.removeEventListener("resize", handleResize);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const wrappedSetLeftBarSize = (size: number) => {
|
const wrappedSetLeftBarSize = (size: number) => {
|
||||||
if (!barsInitialized()) {
|
if (!barsInitialized()) {
|
||||||
// Before initialization, capture natural size
|
// Before initialization, capture natural size
|
||||||
@@ -84,14 +63,10 @@ export function BarsProvider(props: { children: any }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Initialize immediately on mobile if left bar starts hidden
|
// Initialize immediately on mobile if left bar starts hidden
|
||||||
onMount(() => {
|
if (initialIsMobile && !leftBarVisible()) {
|
||||||
const isMobile = typeof window !== "undefined" && window.innerWidth < 768;
|
// Skip waiting for left bar size on mobile when it starts hidden
|
||||||
if (isMobile && !leftBarVisible()) {
|
leftBarSized = true;
|
||||||
// Skip waiting for left bar size on mobile when it starts hidden
|
}
|
||||||
leftBarSized = true;
|
|
||||||
checkAndSync();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const wrappedSetRightBarSize = (size: number) => {
|
const wrappedSetRightBarSize = (size: number) => {
|
||||||
if (!barsInitialized()) {
|
if (!barsInitialized()) {
|
||||||
@@ -108,8 +83,8 @@ export function BarsProvider(props: { children: any }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const checkAndSync = () => {
|
const checkAndSync = () => {
|
||||||
const isMobile = typeof window !== "undefined" && window.innerWidth < 768;
|
const currentIsMobile = isMobile(windowWidth());
|
||||||
const bothBarsReady = leftBarSized && (isMobile || rightBarSized);
|
const bothBarsReady = leftBarSized && (currentIsMobile || rightBarSized);
|
||||||
|
|
||||||
if (bothBarsReady) {
|
if (bothBarsReady) {
|
||||||
const maxWidth = Math.max(_leftBarNaturalSize(), _rightBarNaturalSize());
|
const maxWidth = Math.max(_leftBarNaturalSize(), _rightBarNaturalSize());
|
||||||
@@ -123,8 +98,8 @@ export function BarsProvider(props: { children: any }) {
|
|||||||
const naturalSize = _leftBarNaturalSize();
|
const naturalSize = _leftBarNaturalSize();
|
||||||
if (naturalSize === 0) return 0; // Hidden
|
if (naturalSize === 0) return 0; // Hidden
|
||||||
// On mobile (<768px), always return 0 for layout (overlay mode)
|
// On mobile (<768px), always return 0 for layout (overlay mode)
|
||||||
const isMobile = windowWidth() < 768;
|
const currentIsMobile = isMobile(windowWidth());
|
||||||
if (isMobile) return 0;
|
if (currentIsMobile) return 0;
|
||||||
return barsInitialized() ? syncedBarSize() : naturalSize;
|
return barsInitialized() ? syncedBarSize() : naturalSize;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
57
src/lib/resize-utils.ts
Normal file
57
src/lib/resize-utils.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { createSignal, onMount, onCleanup, Accessor } from "solid-js";
|
||||||
|
|
||||||
|
export const MOBILE_BREAKPOINT = 768;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a reactive window width signal that updates on resize
|
||||||
|
* @param debounceMs Optional debounce delay in milliseconds
|
||||||
|
* @returns Accessor for current window width
|
||||||
|
*/
|
||||||
|
export function createWindowWidth(debounceMs?: number): Accessor<number> {
|
||||||
|
const initialWidth = typeof window !== "undefined" ? window.innerWidth : 1024;
|
||||||
|
const [width, setWidth] = createSignal(initialWidth);
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
if (debounceMs) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
setWidth(window.innerWidth);
|
||||||
|
}, debounceMs);
|
||||||
|
} else {
|
||||||
|
setWidth(window.innerWidth);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
window.removeEventListener("resize", handleResize);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current window width is in mobile viewport
|
||||||
|
* @param width Current window width
|
||||||
|
* @returns true if mobile viewport
|
||||||
|
*/
|
||||||
|
export function isMobile(width: number): boolean {
|
||||||
|
return width < MOBILE_BREAKPOINT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a derived signal for mobile state
|
||||||
|
* @param windowWidth Window width accessor
|
||||||
|
* @returns Accessor for mobile state
|
||||||
|
*/
|
||||||
|
export function createIsMobile(
|
||||||
|
windowWidth: Accessor<number>
|
||||||
|
): Accessor<boolean> {
|
||||||
|
return () => isMobile(windowWidth());
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Title, Meta } from "@solidjs/meta";
|
import { Title, Meta } from "@solidjs/meta";
|
||||||
|
import { DarkModeToggle } from "~/components/DarkModeToggle";
|
||||||
import { Typewriter } from "~/components/Typewriter";
|
import { Typewriter } from "~/components/Typewriter";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
@@ -7,10 +8,10 @@ export default function Home() {
|
|||||||
<Title>Home | Michael Freno</Title>
|
<Title>Home | Michael Freno</Title>
|
||||||
<Meta
|
<Meta
|
||||||
name="description"
|
name="description"
|
||||||
content="Michael Freno - Software Engineer based in Brooklyn, NY. Passionate about dev tooling, game development, and open source software."
|
content="Michael Freno - Software Engineer based in Brooklyn, NY"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<main class="flex h-full flex-col gap-8 text-xl">
|
<main class="flex h-full flex-col gap-8 px-4 text-xl">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<Typewriter speed={30} keepAlive={2000}>
|
<Typewriter speed={30} keepAlive={2000}>
|
||||||
<div class="text-4xl">Hey!</div>
|
<div class="text-4xl">Hey!</div>
|
||||||
@@ -147,9 +148,12 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="max-w-3/4 pt-8 md:max-w-1/2">
|
<Typewriter speed={160} class="max-w-3/4 pt-8 md:max-w-1/2">
|
||||||
And if you love the color schemes of this site (which of course you
|
And if you love the color schemes of this site
|
||||||
do), you can see{" "}
|
<div class="mx-auto w-fit">
|
||||||
|
<DarkModeToggle />
|
||||||
|
</div>
|
||||||
|
(which of course you do), you can see{" "}
|
||||||
<a
|
<a
|
||||||
href="https://github.com/mikefreno/dots/blob/master/mac/nvim/lua/colors.lua"
|
href="https://github.com/mikefreno/dots/blob/master/mac/nvim/lua/colors.lua"
|
||||||
class="text-blue hover-underline-animation"
|
class="text-blue hover-underline-animation"
|
||||||
@@ -159,7 +163,7 @@ export default function Home() {
|
|||||||
- and also see the rest of my various dot files idk. There's a macos
|
- and also see the rest of my various dot files idk. There's a macos
|
||||||
and arch linux rice in there if you're into that kinda thing and a
|
and arch linux rice in there if you're into that kinda thing and a
|
||||||
home server setup too. Which I will write about soon™.
|
home server setup too. Which I will write about soon™.
|
||||||
</div>
|
</Typewriter>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col items-end gap-4 pr-4">
|
<div class="flex flex-col items-end gap-4 pr-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user