@remit/web-client 0.0.161 → 0.0.163

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
- import { useLocation } from "@tanstack/react-router";
1
+ import { useLocation, useNavigate } from "@tanstack/react-router";
2
+ import { useCallback, useMemo } from "react";
2
3
  import { z } from "zod";
3
4
 
4
5
  /**
@@ -7,29 +8,91 @@ import { z } from "zod";
7
8
  * independent of in-memory data, so a cold load of the URL reproduces it
8
9
  * exactly.
9
10
  */
10
- export const panelFragments = [
11
- "intelligence",
12
- "nav",
13
- "shortcuts",
14
- "filters",
15
- ] as const;
11
+ export const panelFragments = ["intelligence", "nav", "shortcuts"] as const;
16
12
 
17
13
  export type PanelFragment = (typeof panelFragments)[number];
18
14
 
15
+ /**
16
+ * The panels that cover the view rather than sit in it. The rail is a pane in
17
+ * the shell's row and the other two are modal over it, so a sheet opening can
18
+ * never take the rail down with it — the address holds the pane and the overlay
19
+ * at once. Two overlays cannot be up together, because the second one hides the
20
+ * first.
21
+ */
22
+ export const overlayPanels = ["nav", "shortcuts"] as const;
23
+
24
+ export type OverlayPanel = (typeof overlayPanels)[number];
25
+
26
+ export const isOverlayPanel = (panel: PanelFragment): panel is OverlayPanel =>
27
+ (overlayPanels as readonly PanelFragment[]).includes(panel);
28
+
19
29
  const panelFragmentSchema = z.enum(panelFragments);
20
30
 
21
31
  /**
22
- * A hand-edited or stale URL is a normal thing to receive, so an unrecognised
23
- * fragment reads as "no panel open" instead of throwing.
32
+ * The panels the address asks for, in the union's own order so one set of open
33
+ * panels has one spelling. A hand-edited or stale URL is a normal thing to
34
+ * receive, so an unrecognised name is dropped instead of throwing.
24
35
  */
25
- export function parsePanelFragment(hash: string): PanelFragment | undefined {
26
- const parsed = panelFragmentSchema.safeParse(hash);
27
- return parsed.success ? parsed.data : undefined;
36
+ export function parseOpenPanels(hash: string): readonly PanelFragment[] {
37
+ const named = new Set(
38
+ hash
39
+ .split(",")
40
+ .map((token) => panelFragmentSchema.safeParse(token))
41
+ .filter((parsed) => parsed.success)
42
+ .map((parsed) => parsed.data),
43
+ );
44
+ return panelFragments.filter((panel) => named.has(panel));
45
+ }
46
+
47
+ /** The address a set of open panels is written as. */
48
+ export function formatOpenPanels(panels: readonly PanelFragment[]): string {
49
+ return panelFragments.filter((panel) => panels.includes(panel)).join(",");
28
50
  }
29
51
 
30
- /** The panel the URL currently asks for, if any. */
31
- export function useOpenPanel(): PanelFragment | undefined {
32
- return useLocation({
33
- select: (location) => parsePanelFragment(location.hash),
52
+ /** The panels the address currently asks for. */
53
+ export function useOpenPanels(): readonly PanelFragment[] {
54
+ const hash = useLocation({
55
+ select: (location) => formatOpenPanels(parseOpenPanels(location.hash)),
34
56
  });
57
+ return useMemo(() => parseOpenPanels(hash), [hash]);
58
+ }
59
+
60
+ /**
61
+ * A destination's `hash`, for a navigation that keeps the reader where they
62
+ * are. The router drops the fragment on every navigation unless the destination
63
+ * asks for it, so this is what a link or a `navigate` call passes to state the
64
+ * rule rather than restate it: a pane is chrome a reader keeps up while they
65
+ * move from one conversation to the next, and an overlay is dismissed by going
66
+ * somewhere, which is what closing it means.
67
+ *
68
+ * The parameter is optional because the router hands a `hash` updater the
69
+ * previous fragment as `string | undefined`.
70
+ */
71
+ export function retainOpenPanels(hash = ""): string {
72
+ return formatOpenPanels(
73
+ parseOpenPanels(hash).filter((panel) => !isOverlayPanel(panel)),
74
+ );
75
+ }
76
+
77
+ /**
78
+ * Puts a set of panels up, replacing whatever was up before.
79
+ *
80
+ * `replace`, because a panel is chrome over the current view rather than a place
81
+ * to go: Back belongs to the message the reader came from, not to the rail they
82
+ * just collapsed. The fragment names panels and never an element, so it is not
83
+ * an anchor to scroll to either.
84
+ */
85
+ export function useSetOpenPanels(): (panels: readonly PanelFragment[]) => void {
86
+ const navigate = useNavigate();
87
+ return useCallback(
88
+ (panels: readonly PanelFragment[]) => {
89
+ navigate({
90
+ search: true,
91
+ hash: formatOpenPanels(panels),
92
+ replace: true,
93
+ hashScrollIntoView: false,
94
+ });
95
+ },
96
+ [navigate],
97
+ );
35
98
  }
@@ -1,8 +1,14 @@
1
1
  export {
2
+ formatOpenPanels,
3
+ isOverlayPanel,
4
+ type OverlayPanel,
5
+ overlayPanels,
2
6
  type PanelFragment,
3
7
  panelFragments,
4
- parsePanelFragment,
5
- useOpenPanel,
8
+ parseOpenPanels,
9
+ retainOpenPanels,
10
+ useOpenPanels,
11
+ useSetOpenPanels,
6
12
  } from "./fragment";
7
13
  export { NavLink, type NavLinkProps } from "./nav-link";
8
14
  export {
@@ -7,7 +7,7 @@ import type {
7
7
  } from "@tanstack/react-router";
8
8
  import { createLink } from "@tanstack/react-router";
9
9
  import type { ReactElement } from "react";
10
- import type { PanelFragment } from "./fragment";
10
+ import { type PanelFragment, retainOpenPanels } from "./fragment";
11
11
 
12
12
  const RouterNavLink = createLink(NavLinkSurface);
13
13
 
@@ -42,6 +42,10 @@ export type NavLinkProps<
42
42
  * The application's only navigation link. `createLink` keeps the router's `to` /
43
43
  * `params` / `search` inference, so a route that does not exist or a param set
44
44
  * that does not match it fails to compile instead of rendering a blank pane.
45
+ *
46
+ * A link that names no panel carries the open panes across: the rail is up
47
+ * until the reader puts it down, and every row in a list is a link. Overlays
48
+ * are left behind, because going somewhere is what dismisses one.
45
49
  */
46
50
  export function NavLink<
47
51
  TRouter extends AnyRouter = RegisteredRouter,
@@ -54,5 +58,5 @@ export function NavLink({
54
58
  fragment,
55
59
  ...props
56
60
  }: CreateLinkProps & { fragment?: PanelFragment }): ReactElement {
57
- return <RouterNavLink {...props} hash={fragment} />;
61
+ return <RouterNavLink {...props} hash={fragment ?? retainOpenPanels} />;
58
62
  }