v3: consistent tabs url across app, maxy small fixes
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 42s
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 42s
This commit is contained in:
47
src/hooks/useTabParam.ts
Normal file
47
src/hooks/useTabParam.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { useCallback } from "react";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
|
||||
const SEP = "/~/";
|
||||
|
||||
/**
|
||||
* Reflects the active tab in the URL path using a `/~/` delimiter, e.g.
|
||||
* `/users/vincent/~/playlists` or `/~/new`. The segment before `/~/` is the
|
||||
* base path and the segment after is the tab. When the base is non-empty the
|
||||
* default tab collapses to the clean base path (`/users/vincent`); at the root
|
||||
* the base is empty, so the default stays explicit (`/~/hot`). Generic and
|
||||
* path-agnostic: the base is derived from the current location, so the same
|
||||
* hook works for any route.
|
||||
*
|
||||
* Returns the validated active tab (falling back to `defaultTab`) and a setter
|
||||
* that navigates to the matching path (replacing history).
|
||||
*/
|
||||
export function useTabParam<T extends string>(
|
||||
validTabs: Iterable<T>,
|
||||
defaultTab: T,
|
||||
): [T, (tab: T) => void] {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const valid = new Set<string>(validTabs as Iterable<string>);
|
||||
const path = location.pathname;
|
||||
const sepAt = path.indexOf(SEP);
|
||||
|
||||
let base = sepAt === -1 ? path : path.slice(0, sepAt);
|
||||
if (base.endsWith("/")) base = base.slice(0, -1); // root "/" → ""
|
||||
|
||||
const raw = sepAt === -1
|
||||
? null
|
||||
: path.slice(sepAt + SEP.length).replace(/\/$/, "");
|
||||
const tab = (raw && valid.has(raw) ? raw : defaultTab) as T;
|
||||
|
||||
const setTab = useCallback(
|
||||
(t: T) => {
|
||||
const target = t === defaultTab && base ? base : `${base}${SEP}${t}`;
|
||||
// Preserve any unrelated query string (e.g. the search page's `?q=`).
|
||||
navigate(`${target}${location.search}`, { replace: true });
|
||||
},
|
||||
[navigate, base, defaultTab, location.search],
|
||||
);
|
||||
|
||||
return [tab, setTab];
|
||||
}
|
||||
Reference in New Issue
Block a user