@trackunit/react-router-components 0.0.0-stage → 1.0.3
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.
- package/README.md +40 -2
- package/index.cjs.js +1102 -0
- package/index.d.ts +1 -0
- package/index.esm.js +1094 -0
- package/migrations/entry.js +3 -0
- package/migrations/entry.js.map +1 -0
- package/migrations.json +3 -0
- package/package.json +23 -3
- package/src/Breadcrumb/Breadcrumb.d.ts +39 -0
- package/src/Breadcrumb/Breadcrumb.variant.d.ts +45 -0
- package/src/Breadcrumb/BreadcrumbContainer.d.ts +8 -0
- package/src/Breadcrumb/BreadcrumbForLargeScreen.d.ts +6 -0
- package/src/Breadcrumb/BreadcrumbForMediumScreen.d.ts +6 -0
- package/src/Breadcrumb/BreadcrumbForSmallScreen.d.ts +6 -0
- package/src/Breadcrumb/BreadcrumbItem.d.ts +6 -0
- package/src/Breadcrumb/utils/types.d.ts +26 -0
- package/src/Breadcrumb/utils/useBreadcrumbItemsToRender.d.ts +6 -0
- package/src/RouteLink/RouteLink.d.ts +17 -0
- package/src/index.d.ts +6 -0
- package/src/persistence/readPrimitiveSearchParam.d.ts +8 -0
- package/src/persistence/usePersistedState.d.ts +117 -0
- package/src/persistence/useSearchParamSync.d.ts +17 -0
- package/src/persistence/useUrlParamSync.d.ts +71 -0
- package/src/translation.d.ts +30 -0
- package/src/useCursorUrlSync/useCursorUrlSync.d.ts +23 -0
- package/src/useRouterDrawer/useRouterDrawer.d.ts +100 -0
- package/translation.cjs.js +8 -0
- package/translation.esm.js +6 -0
- package/translation10.cjs.js +8 -0
- package/translation10.esm.js +6 -0
- package/translation11.cjs.js +8 -0
- package/translation11.esm.js +6 -0
- package/translation12.cjs.js +8 -0
- package/translation12.esm.js +6 -0
- package/translation13.cjs.js +8 -0
- package/translation13.esm.js +6 -0
- package/translation14.cjs.js +8 -0
- package/translation14.esm.js +6 -0
- package/translation15.cjs.js +8 -0
- package/translation15.esm.js +6 -0
- package/translation16.cjs.js +8 -0
- package/translation16.esm.js +6 -0
- package/translation17.cjs.js +8 -0
- package/translation17.esm.js +6 -0
- package/translation2.cjs.js +8 -0
- package/translation2.esm.js +6 -0
- package/translation3.cjs.js +8 -0
- package/translation3.esm.js +6 -0
- package/translation4.cjs.js +8 -0
- package/translation4.esm.js +6 -0
- package/translation5.cjs.js +8 -0
- package/translation5.esm.js +6 -0
- package/translation6.cjs.js +8 -0
- package/translation6.esm.js +6 -0
- package/translation7.cjs.js +8 -0
- package/translation7.esm.js +6 -0
- package/translation8.cjs.js +8 -0
- package/translation8.esm.js +6 -0
- package/translation9.cjs.js +8 -0
- package/translation9.esm.js +6 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../../../../../libs/react/router-components/migrations/entry.ts"],"names":[],"mappings":"","sourcesContent":["export {};\n"]}
|
package/migrations.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-router-components",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"repository": "https://github.com/Trackunit/manager",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=24.x"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@trackunit/css-class-variance-utilities": "2.6.7",
|
|
11
|
+
"@trackunit/i18n-library-translation": "2.12.6",
|
|
12
|
+
"@trackunit/react-components": "4.0.1",
|
|
13
|
+
"@trackunit/react-drawer": "2.14.9",
|
|
14
|
+
"@trackunit/shared-utils": "1.22.7",
|
|
15
|
+
"dequal": "^2.0.3",
|
|
16
|
+
"zod": "^3.25.76"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"@tanstack/react-router": "^1.114.29",
|
|
20
|
+
"react": "^19.0.0"
|
|
21
|
+
},
|
|
22
|
+
"migrations": "./migrations.json",
|
|
23
|
+
"module": "./index.esm.js",
|
|
24
|
+
"main": "./index.cjs.js",
|
|
25
|
+
"types": "./index.d.ts"
|
|
6
26
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ReactElement } from "react";
|
|
2
|
+
import { BreadcrumbProps } from "./utils/types";
|
|
3
|
+
/**
|
|
4
|
+
* The breadcrumb component shows a user's location in a website or application. Breadcrumbs are particularly useful when a large amount of content is organized in a hierarchical manner. They streamline navigation, minimize the steps required to revisit previous pages, and offer contextual insights to the users.
|
|
5
|
+
*
|
|
6
|
+
* All items except the last are rendered as clickable links. The last item is rendered as plain text representing the current page.
|
|
7
|
+
* The component automatically adapts to the screen size: on large screens all items are visible, on medium screens middle items collapse behind an ellipsis button, and on small screens only the current page is shown.
|
|
8
|
+
*
|
|
9
|
+
* ### When to use
|
|
10
|
+
* Use breadcrumbs when deep navigation is available, on apps or pages where users might dive into content and could benefit from a quick way to return to higher levels. Breadcrumbs display the current screen as the final item in the list to make it clear where the user is.
|
|
11
|
+
*
|
|
12
|
+
* ### When not to use
|
|
13
|
+
* Do not use on the first hierarchal level, such as a home page or a main page, as there is no previous step the user can get to.
|
|
14
|
+
* Avoid using breadcrumbs to indicate forthcoming steps. For processes with sequential steps, use a wizard flow instead.
|
|
15
|
+
*
|
|
16
|
+
* @example Basic breadcrumb navigation
|
|
17
|
+
* ```tsx
|
|
18
|
+
* import { Breadcrumb } from "@trackunit/react-router-components";
|
|
19
|
+
* import { useNavigate } from "@tanstack/react-router";
|
|
20
|
+
*
|
|
21
|
+
* const AssetDetailBreadcrumb = () => {
|
|
22
|
+
* const navigate = useNavigate();
|
|
23
|
+
*
|
|
24
|
+
* return (
|
|
25
|
+
* <Breadcrumb
|
|
26
|
+
* breadcrumbItems={[
|
|
27
|
+
* { label: "Fleet", to: "/fleet" },
|
|
28
|
+
* { label: "Assets", to: "/fleet/assets" },
|
|
29
|
+
* { label: "Excavator 001", to: "/fleet/assets/123" },
|
|
30
|
+
* ]}
|
|
31
|
+
* onClickBack={() => navigate({ to: "/fleet/assets" })}
|
|
32
|
+
* />
|
|
33
|
+
* );
|
|
34
|
+
* };
|
|
35
|
+
* ```
|
|
36
|
+
* @param {BreadcrumbProps} props - The props for the Breadcrumb component
|
|
37
|
+
* @returns {ReactElement} Breadcrumb component
|
|
38
|
+
*/
|
|
39
|
+
export declare const Breadcrumb: ({ className, "data-testid": dataTestId, breadcrumbItems, onClickBack, style, ref, }: BreadcrumbProps) => ReactElement;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export declare const cvaBreadcrumb: import("cva").CVAComponent<Omit<{
|
|
2
|
+
base: string[];
|
|
3
|
+
}, "defaultVariants"> & {
|
|
4
|
+
variants: unknown;
|
|
5
|
+
defaultVariants: Omit<{}, never>;
|
|
6
|
+
}, unknown>;
|
|
7
|
+
export declare const cvaBreadcrumbItem: import("cva").CVAComponent<Omit<{
|
|
8
|
+
base: string[];
|
|
9
|
+
}, "defaultVariants"> & {
|
|
10
|
+
variants: unknown;
|
|
11
|
+
defaultVariants: Omit<{}, never>;
|
|
12
|
+
}, unknown>;
|
|
13
|
+
export declare const cvaBreadcrumbForLargeScreen: import("cva").CVAComponent<Omit<{
|
|
14
|
+
base: string[];
|
|
15
|
+
}, "defaultVariants"> & {
|
|
16
|
+
variants: unknown;
|
|
17
|
+
defaultVariants: Omit<{}, never>;
|
|
18
|
+
}, unknown>;
|
|
19
|
+
export declare const cvaBreadcrumbForMediumScreen: import("cva").CVAComponent<Omit<{
|
|
20
|
+
base: string[];
|
|
21
|
+
variants: {
|
|
22
|
+
expanded: {
|
|
23
|
+
true: string;
|
|
24
|
+
false: string;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
defaultVariants: {
|
|
28
|
+
expanded: false;
|
|
29
|
+
};
|
|
30
|
+
}, "defaultVariants"> & {
|
|
31
|
+
variants: {
|
|
32
|
+
expanded: {
|
|
33
|
+
true: string;
|
|
34
|
+
false: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
defaultVariants: Omit<{}, "expanded"> & {
|
|
38
|
+
expanded: false;
|
|
39
|
+
};
|
|
40
|
+
}, {
|
|
41
|
+
expanded: {
|
|
42
|
+
true: string;
|
|
43
|
+
false: string;
|
|
44
|
+
};
|
|
45
|
+
}>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ReactElement } from "react";
|
|
2
|
+
import { BreadcrumbContainerProps } from "./utils/types";
|
|
3
|
+
/**
|
|
4
|
+
* BreadcrumbContainer is a helper component that renders the breadcrumb items based on the screen size.
|
|
5
|
+
*
|
|
6
|
+
* @param {BreadcrumbContainerProps} props - The props for the BreadcrumbContainer component
|
|
7
|
+
*/
|
|
8
|
+
export declare const BreadcrumbContainer: ({ className, "data-testid": dataTestId, breadcrumbItems, style, ref, }: BreadcrumbContainerProps) => ReactElement;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ReactElement } from "react";
|
|
2
|
+
import { BreadcrumbContainerProps } from "./utils/types";
|
|
3
|
+
/**
|
|
4
|
+
* BreadcrumbForLargeScreen is a helper component that renders the breadcrumb items for large screens.
|
|
5
|
+
*/
|
|
6
|
+
export declare const BreadcrumbForLargeScreen: ({ "data-testid": dataTestId, breadcrumbItems, className, style, }: BreadcrumbContainerProps) => ReactElement;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ReactElement } from "react";
|
|
2
|
+
import { BreadcrumbForMediumScreenProps } from "./utils/types";
|
|
3
|
+
/**
|
|
4
|
+
* BreadcrumbForMediumScreen is a helper component that renders the breadcrumb items for medium screens.
|
|
5
|
+
*/
|
|
6
|
+
export declare const BreadcrumbForMediumScreen: ({ "data-testid": dataTestId, breadcrumbItems, className, style, }: BreadcrumbForMediumScreenProps) => ReactElement;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ReactElement } from "react";
|
|
2
|
+
import { BreadcrumbContainerProps } from "./utils/types";
|
|
3
|
+
/**
|
|
4
|
+
* BreadcrumbForSmallScreen is a helper component that renders the breadcrumb items for small screens.
|
|
5
|
+
*/
|
|
6
|
+
export declare const BreadcrumbForSmallScreen: ({ "data-testid": dataTestId, breadcrumbItems, className, style, }: BreadcrumbContainerProps) => ReactElement;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ReactElement } from "react";
|
|
2
|
+
import { BreadcrumbItemRenderProps } from "./utils/types";
|
|
3
|
+
/**
|
|
4
|
+
* BreadcrumbItem is a helper component that renders the individual breadcrumb item.
|
|
5
|
+
*/
|
|
6
|
+
export declare const BreadcrumbItem: ({ item, "data-testid": dataTestId, className, style, }: BreadcrumbItemRenderProps) => ReactElement;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CommonProps, Refable, Styleable } from "@trackunit/react-components";
|
|
2
|
+
import { MouseEventHandler, ReactElement } from "react";
|
|
3
|
+
export interface BreadcrumbItemProps {
|
|
4
|
+
/** The display text for this breadcrumb item. */
|
|
5
|
+
label: string;
|
|
6
|
+
/** The route path this breadcrumb item links to. */
|
|
7
|
+
to: string;
|
|
8
|
+
}
|
|
9
|
+
export interface BreadcrumbProps extends CommonProps, Styleable, Refable<HTMLDivElement> {
|
|
10
|
+
/**
|
|
11
|
+
* The ordered list of breadcrumb items representing the navigation hierarchy.
|
|
12
|
+
* The last item is rendered as plain text (the current page), all preceding items are rendered as clickable links.
|
|
13
|
+
*/
|
|
14
|
+
breadcrumbItems: Array<BreadcrumbItemProps>;
|
|
15
|
+
/**
|
|
16
|
+
* Click handler for the back arrow button. Typically navigates to the previous breadcrumb item (second-to-last in the list).
|
|
17
|
+
*/
|
|
18
|
+
onClickBack: MouseEventHandler<HTMLButtonElement>;
|
|
19
|
+
}
|
|
20
|
+
export interface BreadcrumbContainerProps extends CommonProps, Styleable, Refable<HTMLDivElement> {
|
|
21
|
+
breadcrumbItems: Array<ReactElement<BreadcrumbItemProps>>;
|
|
22
|
+
}
|
|
23
|
+
export type BreadcrumbForMediumScreenProps = BreadcrumbContainerProps;
|
|
24
|
+
export interface BreadcrumbItemRenderProps extends CommonProps, Styleable {
|
|
25
|
+
item: ReactElement<BreadcrumbItemProps>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ReactElement } from "react";
|
|
2
|
+
import { BreadcrumbItemProps } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* useBreadcrumbItemsToRender is a custom hook that takes an array of BreadcrumbItemProps and returns an array of React.ReactElement
|
|
5
|
+
*/
|
|
6
|
+
export declare const useBreadcrumbItemsToRender: (breadcrumbItems: Array<BreadcrumbItemProps>) => Array<ReactElement<BreadcrumbItemProps>>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CommonProps } from "@trackunit/react-components";
|
|
2
|
+
import { MouseEvent, ReactElement } from "react";
|
|
3
|
+
export interface RouteLinkProps extends CommonProps {
|
|
4
|
+
to: string;
|
|
5
|
+
text: string;
|
|
6
|
+
onClick?: (event: MouseEvent<HTMLAnchorElement, globalThis.MouseEvent>) => void;
|
|
7
|
+
className?: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A text link to a route inside the app, rendered through the router's `Link`.
|
|
12
|
+
* Use `ExternalLink` from `@trackunit/react-components` for links that leave the app.
|
|
13
|
+
*
|
|
14
|
+
* @param {RouteLinkProps} props The props for the RouteLink component.
|
|
15
|
+
* @returns {ReactElement} RouteLink component
|
|
16
|
+
*/
|
|
17
|
+
export declare const RouteLink: ({ to, text, title, onClick, className, "data-testid": dataTestId, }: RouteLinkProps) => ReactElement;
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from "./Breadcrumb/Breadcrumb";
|
|
2
|
+
export * from "./persistence/usePersistedState";
|
|
3
|
+
export * from "./persistence/useSearchParamSync";
|
|
4
|
+
export * from "./RouteLink/RouteLink";
|
|
5
|
+
export * from "./useCursorUrlSync/useCursorUrlSync";
|
|
6
|
+
export * from "./useRouterDrawer/useRouterDrawer";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads one search param from a TanStack `useSearch({ strict: false })` result.
|
|
3
|
+
*
|
|
4
|
+
* The registered route tree often has an empty or closed search schema, so a
|
|
5
|
+
* string key cannot index that type. Parsing to a string record keeps the
|
|
6
|
+
* lookup valid for every app router.
|
|
7
|
+
*/
|
|
8
|
+
export declare const readPrimitiveSearchParam: (search: unknown, key: string) => string | undefined;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { type UrlParamLocation } from "./useUrlParamSync";
|
|
2
|
+
/**
|
|
3
|
+
* Selects which part of the URL backs the persisted state.
|
|
4
|
+
*
|
|
5
|
+
* - `"search"` (default): `?<key>=<encoded>`. Subject to the AWS WAF
|
|
6
|
+
* managed-rule cap (~5000 bytes) — large states are dropped from the URL.
|
|
7
|
+
* - `"hash"`: `#<key>=<encoded>`. Stays on the client, so the cap is
|
|
8
|
+
* driven only by browser URL limits.
|
|
9
|
+
*/
|
|
10
|
+
export type UrlPersistenceLocation = UrlParamLocation;
|
|
11
|
+
type UsePersistedStateOptions<TState> = {
|
|
12
|
+
readonly key: string;
|
|
13
|
+
readonly validate: (raw: unknown) => TState | undefined;
|
|
14
|
+
readonly toUrlValue?: (state: TState) => string | object | Array<object>;
|
|
15
|
+
readonly fromUrlValue?: (decoded: unknown) => unknown;
|
|
16
|
+
readonly localStorageEnabled?: boolean;
|
|
17
|
+
readonly replace?: boolean;
|
|
18
|
+
readonly clientSideUserId?: string;
|
|
19
|
+
/**
|
|
20
|
+
* When `true` and both URL and localStorage have valid state, the read
|
|
21
|
+
* returns a shallow merge `{ ...storage, ...url }` where URL fields take
|
|
22
|
+
* precedence and storage fills in fields the URL omits.
|
|
23
|
+
*
|
|
24
|
+
* Use this when `toUrlValue` deliberately omits some fields from the URL
|
|
25
|
+
* (e.g. per-user layout details that shouldn't be shareable) so the
|
|
26
|
+
* omitted fields still survive reloads via localStorage.
|
|
27
|
+
*
|
|
28
|
+
* Default is `false`: URL state fully replaces localStorage state on read.
|
|
29
|
+
*/
|
|
30
|
+
readonly mergeWithStorageOnRead?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Which slot of the URL backs the persisted value. Default `"search"`.
|
|
33
|
+
* See {@link UrlPersistenceLocation} for the trade-offs.
|
|
34
|
+
*/
|
|
35
|
+
readonly urlLocation?: UrlPersistenceLocation;
|
|
36
|
+
/**
|
|
37
|
+
* When `true`, every URL write is stamped with a fingerprint of
|
|
38
|
+
* `clientSideUserId` (`{ o: <fingerprint>, v: <payload> }`). Reads are
|
|
39
|
+
* unaffected: the URL still wins on load for everyone, so links and
|
|
40
|
+
* bookmarks render for whoever opens them.
|
|
41
|
+
*
|
|
42
|
+
* The stamp exists for the login redirect. The URL is shared by every
|
|
43
|
+
* account that signs in from the same browser and `redirectToLogin`
|
|
44
|
+
* carries it through to the next account, which is how one account's
|
|
45
|
+
* state replaced another's on a shared PC (GLU-2396). At that boundary
|
|
46
|
+
* `useStripForeignIdentityHash` drops entries stamped by a different
|
|
47
|
+
* identity that has state stored in this browser. Default `false`.
|
|
48
|
+
*/
|
|
49
|
+
readonly stampUrlWithIdentity?: boolean;
|
|
50
|
+
};
|
|
51
|
+
type UsePersistedStateReturn<TState> = {
|
|
52
|
+
readonly initialState: TState | undefined;
|
|
53
|
+
/** Writes `state` to localStorage and the URL; each slot skips a value equal to its last write. */
|
|
54
|
+
readonly persistState: (state: TState) => void;
|
|
55
|
+
/**
|
|
56
|
+
* Writes `state` to localStorage only; `undefined` removes the entry.
|
|
57
|
+
* Use together with `persistToUrl` when the two writes should not share a
|
|
58
|
+
* schedule — e.g. storage immediately, URL debounced.
|
|
59
|
+
*/
|
|
60
|
+
readonly persistToStorage: (state: TState | undefined) => void;
|
|
61
|
+
/** Writes `state` to the URL only; `undefined` removes the entry. */
|
|
62
|
+
readonly persistToUrl: (state: TState | undefined) => void;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Generic persistence hook that loads state from a URL slot (search param
|
|
66
|
+
* or hash fragment, depending on `urlLocation`) with localStorage fallback,
|
|
67
|
+
* and writes changes to both.
|
|
68
|
+
*
|
|
69
|
+
* On mount the hook resolves the initial state as follows:
|
|
70
|
+
* - If the URL slot holds a value, decode it and pass through `validate`.
|
|
71
|
+
* - If localStorage is enabled, read and validate its value.
|
|
72
|
+
* - When `mergeWithStorageOnRead` is `false` (default): URL state wins
|
|
73
|
+
* entirely when present, otherwise fall back to localStorage.
|
|
74
|
+
* - When `mergeWithStorageOnRead` is `true`: shallow-merge URL fields over
|
|
75
|
+
* localStorage so fields stripped by `toUrlValue` survive via storage.
|
|
76
|
+
*
|
|
77
|
+
* A value that comes only from localStorage is never written to the URL on
|
|
78
|
+
* load. Copying it left a layout in history, bookmarks, and login redirects,
|
|
79
|
+
* which then replayed an older layout over the saved one (GLU-2396). The URL
|
|
80
|
+
* is written when the consumer persists a change.
|
|
81
|
+
*
|
|
82
|
+
* `persistState` writes to localStorage (`storageSerializer`) and to the URL
|
|
83
|
+
* (via `toUrlValue` + encoding).
|
|
84
|
+
* Each slot skips a value equal to its last write. The URL side compares what
|
|
85
|
+
* would land in the URL (the `toUrlValue` projection and the identity stamp),
|
|
86
|
+
* starting from the projection of the hydrated state and the stamp the URL
|
|
87
|
+
* carries: echoing `initialState`, or changing only a field the projection
|
|
88
|
+
* strips, writes no URL entry, while an equal state persisted after the
|
|
89
|
+
* identity resolves rewrites the URL with its stamp. The storage side starts
|
|
90
|
+
* from what localStorage held, so a consumer that persists `initialState`
|
|
91
|
+
* after opening a link saves that link's state for the account that opened
|
|
92
|
+
* it, and a reload of the account's own link writes nothing.
|
|
93
|
+
* `persistToStorage` and `persistToUrl` are the same two halves exposed
|
|
94
|
+
* separately for consumers that schedule them differently; passing `undefined`
|
|
95
|
+
* removes the entry rather than writing an empty value.
|
|
96
|
+
*
|
|
97
|
+
* The snapshot is re-read when the storage key or the storage/merge options
|
|
98
|
+
* change, in the same render, so a write in that commit already sees the
|
|
99
|
+
* state for the new key.
|
|
100
|
+
*
|
|
101
|
+
* @param options - Configuration for the persisted state.
|
|
102
|
+
* @param options.key - Unique identifier used for both the URL slot and the localStorage key.
|
|
103
|
+
* @param options.validate - Called with the decoded/parsed value; must return `TState` or `undefined`.
|
|
104
|
+
* @param options.toUrlValue - Transform applied before URL encoding (default: encode state as-is).
|
|
105
|
+
* @param options.fromUrlValue - Transform applied after URL decoding, before validation (default: identity).
|
|
106
|
+
* @param options.localStorageEnabled - When `false` localStorage is neither read nor written (default `true`).
|
|
107
|
+
* @param options.replace - Forwarded to the underlying URL sync hook.
|
|
108
|
+
* @param options.clientSideUserId - The user ID to use for the localStorage key.
|
|
109
|
+
* @param options.mergeWithStorageOnRead - Merge URL state on top of localStorage
|
|
110
|
+
* state on read (URL wins on shared keys). Use when `toUrlValue` deliberately
|
|
111
|
+
* omits fields so they still survive via localStorage. Default `false`.
|
|
112
|
+
* @param options.urlLocation - Which URL slot backs the value (`"search"` or `"hash"`). Default `"search"`.
|
|
113
|
+
* @param options.stampUrlWithIdentity - Stamp URL writes with the current identity so the login
|
|
114
|
+
* redirect can drop entries left by another account on this browser. Default `false`.
|
|
115
|
+
*/
|
|
116
|
+
export declare const usePersistedState: <TState extends object>({ key, validate, toUrlValue, fromUrlValue, localStorageEnabled, replace, clientSideUserId, mergeWithStorageOnRead, urlLocation, stampUrlWithIdentity, }: UsePersistedStateOptions<TState>) => UsePersistedStateReturn<TState>;
|
|
117
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type UseUrlParamSyncOptions, type UseUrlParamSyncReturn } from "./useUrlParamSync";
|
|
2
|
+
export { MAX_URL_LENGTH } from "./useUrlParamSync";
|
|
3
|
+
type UseSearchParamSyncOptions = Omit<UseUrlParamSyncOptions, "location">;
|
|
4
|
+
/**
|
|
5
|
+
* Syncs an encoded string value with a URL search parameter via Tanstack Router.
|
|
6
|
+
*
|
|
7
|
+
* Thin wrapper over {@link useUrlParamSync} with `location: "search"`; see it
|
|
8
|
+
* for the write queue, the size guard (`MAX_URL_LENGTH`), and external-change
|
|
9
|
+
* detection.
|
|
10
|
+
*
|
|
11
|
+
* @param options - Configuration for the search param sync.
|
|
12
|
+
* @param options.key - The URL search parameter name.
|
|
13
|
+
* @param options.enabled - Set to `false` to disable all URL interaction (default `true`).
|
|
14
|
+
* @param options.onExternalChange - Called when the param changes externally.
|
|
15
|
+
* @param options.replace - `true` to always use `replaceState`.
|
|
16
|
+
*/
|
|
17
|
+
export declare const useSearchParamSync: (options: UseSearchParamSyncOptions) => UseUrlParamSyncReturn;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maximum full URL length after adding one persisted search param before the
|
|
3
|
+
* URL write is skipped (localStorage still persists the full state).
|
|
4
|
+
*
|
|
5
|
+
* Sized to stay safely below the AWS WAF managed-rule default
|
|
6
|
+
* `SizeRestrictions_QUERYSTRING` cap of 5000 bytes for the entire
|
|
7
|
+
* host + path + query + hash string.
|
|
8
|
+
*/
|
|
9
|
+
export declare const MAX_URL_LENGTH = 5000;
|
|
10
|
+
/**
|
|
11
|
+
* Maximum length of the URL fragment after adding one persisted entry.
|
|
12
|
+
* Oversized new entries are skipped; oversized replacements remove the stale
|
|
13
|
+
* entry so localStorage remains authoritative.
|
|
14
|
+
*
|
|
15
|
+
* The fragment is never sent to the server, so the WAF cap above does not
|
|
16
|
+
* apply. 64 KB stays well below typical browser URL limits (Chromium ≳ 100 KB,
|
|
17
|
+
* Firefox ≳ 64 KB on the address bar) while leaving generous headroom for any
|
|
18
|
+
* realistic table state.
|
|
19
|
+
*/
|
|
20
|
+
export declare const MAX_HASH_LENGTH: number;
|
|
21
|
+
/**
|
|
22
|
+
* Which part of the URL a persisted value lives in.
|
|
23
|
+
*
|
|
24
|
+
* - `"search"`: `?<key>=<encoded>`. Subject to the AWS WAF cap
|
|
25
|
+
* ({@link MAX_URL_LENGTH}); large states are dropped from the URL.
|
|
26
|
+
* - `"hash"`: `#<key>=<encoded>`. Client-only, capped by
|
|
27
|
+
* {@link MAX_HASH_LENGTH}.
|
|
28
|
+
*/
|
|
29
|
+
export type UrlParamLocation = "search" | "hash";
|
|
30
|
+
export type UseUrlParamSyncOptions = {
|
|
31
|
+
readonly key: string;
|
|
32
|
+
readonly location: UrlParamLocation;
|
|
33
|
+
readonly enabled?: boolean;
|
|
34
|
+
readonly onExternalChange?: () => void;
|
|
35
|
+
readonly replace?: boolean;
|
|
36
|
+
};
|
|
37
|
+
export type UseUrlParamSyncReturn = {
|
|
38
|
+
/** The raw encoded value currently in the URL slot, or `undefined` when absent or disabled. */
|
|
39
|
+
readonly searchValue: string | undefined;
|
|
40
|
+
/** Writes `encodedValue` into the URL slot; `undefined` removes the entry. */
|
|
41
|
+
readonly updateSearchParam: (encodedValue: string | undefined) => void;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Syncs an encoded string value with a single named entry in the URL — either
|
|
45
|
+
* a search param or an entry inside the hash fragment — via Tanstack Router.
|
|
46
|
+
*
|
|
47
|
+
* Writes go through the application router so other search params and
|
|
48
|
+
* foreign fragment content are preserved. Each slot has a size guard
|
|
49
|
+
* ({@link MAX_URL_LENGTH} for search, {@link MAX_HASH_LENGTH} for hash); an
|
|
50
|
+
* oversized value is dropped from the URL and localStorage stays
|
|
51
|
+
* authoritative. External changes to the entry (browser back/forward, a
|
|
52
|
+
* pasted link, a foreign router push) fire `onExternalChange`.
|
|
53
|
+
*
|
|
54
|
+
* Writes are dispatched via a pending-write queue rather than calling
|
|
55
|
+
* `navigate` directly. Tanstack Router's `router.state.status === "pending"`
|
|
56
|
+
* is common during the first paint of a route (loaders, suspense), and an
|
|
57
|
+
* implementation that simply returned in that case lost the write outright.
|
|
58
|
+
* `updateSearchParam` stores the latest attempted write and schedules an rAF
|
|
59
|
+
* flush; the flush only navigates when the router is idle, and an effect
|
|
60
|
+
* retries whenever the router settles. A queued write is dropped on unmount so
|
|
61
|
+
* it cannot drain onto the next route.
|
|
62
|
+
*
|
|
63
|
+
* @param options - Configuration for the URL entry sync.
|
|
64
|
+
* @param options.key - The search param name or fragment entry name.
|
|
65
|
+
* @param options.location - `"search"` or `"hash"`.
|
|
66
|
+
* @param options.enabled - Set to `false` to disable all URL interaction (default `true`).
|
|
67
|
+
* @param options.onExternalChange - Called when the entry changes externally.
|
|
68
|
+
* @param options.replace - `true` to always use `replaceState`; `false` to always push.
|
|
69
|
+
* Default: replace when the entry is being added, push when it already has a value.
|
|
70
|
+
*/
|
|
71
|
+
export declare const useUrlParamSync: ({ key, location: urlLocation, enabled, onExternalChange, replace: replaceOption, }: UseUrlParamSyncOptions) => UseUrlParamSyncReturn;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { NamespaceTransProps, TransForLibs, TranslationResource, useNamespaceTranslation } from "@trackunit/i18n-library-translation";
|
|
2
|
+
import { ReactElement } from "react";
|
|
3
|
+
import defaultTranslations from "./locales/en/translation.json";
|
|
4
|
+
/** A type for all available translation keys in this library */
|
|
5
|
+
export type TranslationKeys = keyof typeof defaultTranslations;
|
|
6
|
+
/** The translation namespace for this library */
|
|
7
|
+
export declare const namespace = "react.react-router-components";
|
|
8
|
+
/**
|
|
9
|
+
* The TranslationResource for this Library.
|
|
10
|
+
* Holds lazy loaded imports for all languages supported by the library.
|
|
11
|
+
*
|
|
12
|
+
* This is used to register the translations for this library before initializing i18next.
|
|
13
|
+
*/
|
|
14
|
+
export declare const translations: TranslationResource<TranslationKeys>;
|
|
15
|
+
/**
|
|
16
|
+
* Local useTranslation for this specific library
|
|
17
|
+
*/
|
|
18
|
+
export declare const useTranslation: () => ReturnType<typeof useNamespaceTranslation<TranslationKeys>>;
|
|
19
|
+
/**
|
|
20
|
+
* Type of the t function for the local useTranslation for this specific library
|
|
21
|
+
*/
|
|
22
|
+
export type TranslationFunction = TransForLibs<TranslationKeys>;
|
|
23
|
+
/**
|
|
24
|
+
* Trans for this specific library.
|
|
25
|
+
*/
|
|
26
|
+
export declare const Trans: (props: NamespaceTransProps<TranslationKeys>) => ReactElement;
|
|
27
|
+
/**
|
|
28
|
+
* Registers the translations for this library
|
|
29
|
+
*/
|
|
30
|
+
export declare const setupLibraryTranslations: () => void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
type UseCursorUrlSyncOptions = {
|
|
2
|
+
readonly paramName?: string;
|
|
3
|
+
readonly debounceMs?: number;
|
|
4
|
+
};
|
|
5
|
+
type UseCursorUrlSyncResult = {
|
|
6
|
+
readonly initialCursor: string | undefined;
|
|
7
|
+
readonly updateCursor: (cursor: string | undefined) => void;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Reads a cursor from the URL search params on mount and provides a debounced
|
|
11
|
+
* updater for writing it back. Uses `replace: true` to avoid polluting browser
|
|
12
|
+
* history with every scroll position change.
|
|
13
|
+
*
|
|
14
|
+
* Wire `initialCursor` into `usePaginationQuery` and `updateCursor` into
|
|
15
|
+
* `useBidirectionalScroll`'s `onTopItemChange` callback (mapping index to cursor).
|
|
16
|
+
*
|
|
17
|
+
* A pending write is dropped whenever a path-changing navigation starts or the
|
|
18
|
+
* location has moved on: the write targets `to: "."`, so landing it after the
|
|
19
|
+
* user opened a row would replace their destination with the list they came
|
|
20
|
+
* from.
|
|
21
|
+
*/
|
|
22
|
+
export declare const useCursorUrlSync: ({ paramName, debounceMs, }?: UseCursorUrlSyncOptions) => UseCursorUrlSyncResult;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { type RegisteredRouter, type RouteIds } from "@tanstack/react-router";
|
|
2
|
+
import { type UseDrawerProps, type UseDrawerReturnValue } from "@trackunit/react-drawer";
|
|
3
|
+
/** URL slot that stores a drawer's selected id: a search param or a route path param. */
|
|
4
|
+
export type RouterDrawerParamKind = "search" | "path";
|
|
5
|
+
type RouterDrawerRouteId = RouteIds<RegisteredRouter["routeTree"]>;
|
|
6
|
+
type RouterSelectionOptions = {
|
|
7
|
+
/**
|
|
8
|
+
* Id of the route, or routes, that carry the selection. The id is read from whichever of them is
|
|
9
|
+
* matched; when several are, the outermost match (first in match order) wins. Every id must be
|
|
10
|
+
* registered in the router; an unknown id throws on render.
|
|
11
|
+
*/
|
|
12
|
+
from: RouterDrawerRouteId | ReadonlyArray<RouterDrawerRouteId>;
|
|
13
|
+
/** Name of the URL param that stores the selected id. */
|
|
14
|
+
param: string;
|
|
15
|
+
/**
|
|
16
|
+
* Which URL slot `param` lives in. `"search"` uses a search param. With `"path"`, the param is
|
|
17
|
+
* read from, and written to, the matched `from` route's path params. Opening from closed
|
|
18
|
+
* navigates to the `from` route, so it needs a single `from` id. Closing navigates one path
|
|
19
|
+
* segment up from it, so the `from` route's own path must be exactly `$param` with the list route
|
|
20
|
+
* one segment above it (e.g. `templates` → `templates/$templateId`).
|
|
21
|
+
*
|
|
22
|
+
* @default "search"
|
|
23
|
+
*/
|
|
24
|
+
paramKind?: RouterDrawerParamKind;
|
|
25
|
+
/**
|
|
26
|
+
* Guard for leaving the current selection: switching ids, closing (header X, Escape, outside
|
|
27
|
+
* click, `setId(undefined)`), browser Back, or navigating anywhere that does not keep the same
|
|
28
|
+
* selection. Return (or resolve) `true` to allow it; `false`, a throw or a rejected promise keeps
|
|
29
|
+
* the current URL. Nothing changes until the guard allows it. Opening from closed and navigations
|
|
30
|
+
* that keep the same selection are not guarded. The same selection means the same id under the
|
|
31
|
+
* same outermost matched `from` route: moving between child routes of it keeps the selection;
|
|
32
|
+
* moving to a sibling route of a `from` list does not. The guard is called once per attempt, and while it is pending
|
|
33
|
+
* every navigation is dropped, not queued. Reloading or closing the tab is not guarded. With
|
|
34
|
+
* several guarded drawers on one page, each guards its own selection, so leaving the route asks
|
|
35
|
+
* each guard in turn.
|
|
36
|
+
*
|
|
37
|
+
* Browser Forward runs the guard, but denying it does not keep the current entry (GLU-2432).
|
|
38
|
+
*/
|
|
39
|
+
onBeforeClose?: () => boolean | Promise<boolean>;
|
|
40
|
+
};
|
|
41
|
+
/** Options for {@link useRouterDrawer}. */
|
|
42
|
+
export type UseRouterDrawerOptions = RouterSelectionOptions & Pick<UseDrawerProps, "variant" | "trapFocus" | "position" | "dismiss" | "onOpen">;
|
|
43
|
+
/**
|
|
44
|
+
* Return value of {@link useRouterDrawer}: the spreadable `useDrawer` surface plus the URL selection.
|
|
45
|
+
* Open with `setId(id)`; `open()` and `toggle()` cannot select an id, so they do nothing while closed.
|
|
46
|
+
*/
|
|
47
|
+
export type UseRouterDrawerResult<TId extends string | number = string> = UseDrawerReturnValue & {
|
|
48
|
+
/**
|
|
49
|
+
* Selected id read from the URL, kept through the close animation: it stays the closing id until
|
|
50
|
+
* the drawer has fully exited, then becomes `undefined`. Feed query `variables`, `skip` and
|
|
51
|
+
* rendered content from it. For "is a selection in the URL right now", read `isOpen`.
|
|
52
|
+
* Not checked against `TId` by the hook: it is whatever the `from` route's validator produced.
|
|
53
|
+
*/
|
|
54
|
+
readonly id: TId | undefined;
|
|
55
|
+
/** Stable setter. Writes or clears the URL param, replacing the history entry. Setting the current id is a no-op. */
|
|
56
|
+
readonly setId: (nextId: TId | undefined) => void;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Ties a `Drawer`'s selection to a URL param. Wraps `useDrawer` and `useHold` so callers spread the
|
|
60
|
+
* result onto `Drawer` and never call either themselves.
|
|
61
|
+
*
|
|
62
|
+
* `id` is held through the close animation, so a query called as `useX(id, { skip: !id })` keeps
|
|
63
|
+
* serving the closing id's data while the drawer slides out, and is skipped once it has exited.
|
|
64
|
+
*
|
|
65
|
+
* When one component owns the URL state and a child renders `Drawer`, call this hook in the parent
|
|
66
|
+
* and pass the result down as props. The child spreads them onto `Drawer` and does not call `useDrawer`.
|
|
67
|
+
*
|
|
68
|
+
* Call the hook from a component that stays rendered while the `from` route is matched, such as the
|
|
69
|
+
* list or layout route that is an ancestor of `from`; otherwise the drawer is never shown.
|
|
70
|
+
*
|
|
71
|
+
* There is no `decode` option. `TId` is declared by the caller and not checked by the hook: the id
|
|
72
|
+
* is taken as the `from` route's `validateSearch` or `params.parse` produced it, so `TId` must match
|
|
73
|
+
* that output. Validate there, to produce numbers or reject bad values. A missing or empty param is
|
|
74
|
+
* treated as no selection, including a `params.parse` that falls back to `""` for invalid input.
|
|
75
|
+
*
|
|
76
|
+
* Every write (open, switch, close) replaces the current history entry, so Back never steps through
|
|
77
|
+
* drawer selections. Other search params and parent path params are kept. In path mode, switching
|
|
78
|
+
* ids navigates to the `from` route, dropping any deeper child segments, and closing navigates one
|
|
79
|
+
* path segment up from it.
|
|
80
|
+
*
|
|
81
|
+
* `onClose`, `onOpenChange` and `onBeforeClose` from `useDrawer` are not exposed: `onClose` is always
|
|
82
|
+
* `setId(undefined)`, and `onBeforeClose` runs through the router blocker, where forwarding it to
|
|
83
|
+
* `useDrawer` as well would guard twice. `onOpen` fires when the selection goes from none to an id, not on mount.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```tsx
|
|
87
|
+
* // Rendered by the `/templates` list route; `/templates/$templateId` parses the id.
|
|
88
|
+
* const { id, setId, ...drawer } = useRouterDrawer({
|
|
89
|
+
* from: "/templates/$templateId",
|
|
90
|
+
* param: "templateId",
|
|
91
|
+
* paramKind: "path",
|
|
92
|
+
* position: "right",
|
|
93
|
+
* onBeforeClose: async () => confirmDiscard(),
|
|
94
|
+
* });
|
|
95
|
+
* const { data, loading } = useTemplate(id, { skip: !id });
|
|
96
|
+
* return <Drawer {...drawer}>...</Drawer>;
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export declare const useRouterDrawer: <TId extends string | number = string>({ from, param, paramKind, onBeforeClose, variant, trapFocus, position, dismiss, onOpen, }: UseRouterDrawerOptions) => UseRouterDrawerResult<TId>;
|
|
100
|
+
export {};
|