router working with navigation and links

This commit is contained in:
2026-03-03 19:58:37 -06:00
parent bf1f26d851
commit 0c7164b768
11 changed files with 72 additions and 73 deletions

View File

@@ -1,7 +1,10 @@
import "./link.scss";
import React, {PropsWithChildren} from "react";
import {useRouter} from "./RouterContext";
import {Path} from "./router";
export function Link(props: {className: string, href: string, replace?: boolean } & PropsWithChildren) {
export function Link(props: {className: string, href: Path, replace?: boolean } & PropsWithChildren) {
const {children, className, href, replace} = props;
const router = useRouter();
@@ -14,5 +17,5 @@ export function Link(props: {className: string, href: string, replace?: boolean
}
}
return <a onClick={onClick} href={href} className={className}>{children}</a>
return <a onClick={onClick} href={href} className={`${className} link click`}>{children}</a>
}

View File

@@ -6,7 +6,5 @@ export function Route(props: PropsWithChildren & {path: Path}) {
const {path, children} = props;
const router = useRouter();
return <>
{ router.current?.currentPage === path && children}
</>
return router.current === path ? <>{children}</> : <></>
}

View File

@@ -1,62 +1,36 @@
import {createContext, PropsWithChildren, useContext, useEffect, useSyncExternalStore} from "react";
import {HistoryState, Path, Router} from "./router";
import * as repl from "node:repl";
const DEFAULT : Router = {
current: {
currentPage: window.location.pathname as Path
},
pushPage: function(){},
replacePage: function(){}
};
import {createContext, PropsWithChildren, useContext, useEffect, useState, useSyncExternalStore} from "react";
import {DEFAULT, Path, Router} from "./router";
const RouterContext = createContext<Router>(DEFAULT);
export function useRouter() {
return useContext(RouterContext);
}
function subscribe(onChange: () => void) {
window.addEventListener("popstate", onChange);
// return unsubscribe function
return function() {
window.removeEventListener("popstate", onChange);
}
}
function getSnapshot(): HistoryState {
return window.history.state ?? DEFAULT;
}
function pushState(path: string) {
window.history.pushState({
currentPage: path as Path
},
"",
path);
}
function replaceState(path: string) {
window.history.replaceState({
currentPage: path as Path
},
"",
path);
}
export function RouterProvider(props : PropsWithChildren) {
const state = useSyncExternalStore<HistoryState>(subscribe, getSnapshot);
// path is replaced on every state push/replace/pop
const [path, setPath] = useState<Path>(window.location.pathname as Path);
// start history with initial page
// handle the back/forward button
useEffect(() => {
replaceState(window.location.pathname);
function subscribe(ev: PopStateEvent) {
setPath(ev.state as Path);
}
window.addEventListener("popstate", subscribe);
return () => window.removeEventListener("popstate", subscribe);
}, []);
return <RouterContext.Provider value={{
replacePage: replaceState,
pushPage: pushState,
current: state
pushPage: function(newPath: Path) {
window.history.pushState(newPath,"", newPath);
setPath(newPath as Path);
},
replacePage: function(newPath: Path) {
window.history.replaceState(newPath, "", newPath);
setPath(newPath as Path);
},
current: path
}}>
{props.children}
</RouterContext.Provider>

12
src/router/link.scss Normal file
View File

@@ -0,0 +1,12 @@
.link {
color: white;
text-decoration: underline;
}
.link:hover {
opacity: 90%;
}
.link:active {
opacity: 80%;
}

View File

@@ -1,10 +1,13 @@
export type Router = {
replacePage: (path: string) => void;
pushPage: (path: string) => void;
current: HistoryState
replacePage: (path: Path) => void;
pushPage: (path: Path) => void;
current: Path
}
export type Path = `/${string}`;
export type HistoryState = {
currentPage: Path
};
export const DEFAULT : Router = {
current: window.location.pathname as Path,
pushPage: function(){},
replacePage: function(){}
};