@etsoo/toolpad 1.0.0
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/.github/workflows/main.yml +49 -0
- package/.vscode/settings.json +3 -0
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/build/src/Account/Account.d.ts +71 -0
- package/build/src/Account/Account.js +117 -0
- package/build/src/Account/Account.test.d.ts +1 -0
- package/build/src/Account/Account.test.js +35 -0
- package/build/src/Account/AccountPopoverFooter.d.ts +6 -0
- package/build/src/Account/AccountPopoverFooter.js +21 -0
- package/build/src/Account/AccountPopoverHeader.d.ts +6 -0
- package/build/src/Account/AccountPopoverHeader.js +15 -0
- package/build/src/Account/AccountPreview.d.ts +67 -0
- package/build/src/Account/AccountPreview.js +76 -0
- package/build/src/Account/AccountPreview.test.d.ts +1 -0
- package/build/src/Account/AccountPreview.test.js +47 -0
- package/build/src/Account/SignInButton.d.ts +2 -0
- package/build/src/Account/SignInButton.js +26 -0
- package/build/src/Account/SignOutButton.d.ts +3 -0
- package/build/src/Account/SignOutButton.js +28 -0
- package/build/src/Account/index.d.ts +6 -0
- package/build/src/Account/index.js +6 -0
- package/build/src/AppProvider/AppProvider.d.ts +98 -0
- package/build/src/AppProvider/AppProvider.js +4 -0
- package/build/src/AppProvider/AppProvider.test.d.ts +4 -0
- package/build/src/AppProvider/AppProvider.test.js +16 -0
- package/build/src/AppProvider/AppProviderComponent.d.ts +17 -0
- package/build/src/AppProvider/AppProviderComponent.js +120 -0
- package/build/src/AppProvider/AppThemeProvider.d.ts +12 -0
- package/build/src/AppProvider/AppThemeProvider.js +73 -0
- package/build/src/AppProvider/index.d.ts +1 -0
- package/build/src/AppProvider/index.js +1 -0
- package/build/src/DashboardLayout/DashboardLayout.d.ts +83 -0
- package/build/src/DashboardLayout/DashboardLayout.js +286 -0
- package/build/src/DashboardLayout/DashboardLayout.test.d.ts +1 -0
- package/build/src/DashboardLayout/DashboardLayout.test.js +291 -0
- package/build/src/DashboardLayout/DashboardSidebarSubNavigation.d.ts +16 -0
- package/build/src/DashboardLayout/DashboardSidebarSubNavigation.js +149 -0
- package/build/src/DashboardLayout/ThemeSwitcher.d.ts +5 -0
- package/build/src/DashboardLayout/ThemeSwitcher.js +40 -0
- package/build/src/DashboardLayout/ToolbarActions.d.ts +5 -0
- package/build/src/DashboardLayout/ToolbarActions.js +9 -0
- package/build/src/DashboardLayout/index.d.ts +2 -0
- package/build/src/DashboardLayout/index.js +3 -0
- package/build/src/DashboardLayout/utils.d.ts +8 -0
- package/build/src/DashboardLayout/utils.js +16 -0
- package/build/src/PageContainer/PageContainer.d.ts +66 -0
- package/build/src/PageContainer/PageContainer.js +97 -0
- package/build/src/PageContainer/PageContainer.test.d.ts +1 -0
- package/build/src/PageContainer/PageContainer.test.js +127 -0
- package/build/src/PageContainer/PageContainerToolbar.d.ts +16 -0
- package/build/src/PageContainer/PageContainerToolbar.js +24 -0
- package/build/src/PageContainer/PageContainerToolbar.test.d.ts +1 -0
- package/build/src/PageContainer/PageContainerToolbar.test.js +10 -0
- package/build/src/PageContainer/index.d.ts +2 -0
- package/build/src/PageContainer/index.js +2 -0
- package/build/src/index.d.ts +8 -0
- package/build/src/index.js +8 -0
- package/build/src/nextjs/AppProvider.d.ts +6 -0
- package/build/src/nextjs/AppProvider.js +16 -0
- package/build/src/nextjs/AppProvider.test.d.ts +1 -0
- package/build/src/nextjs/AppProvider.test.js +36 -0
- package/build/src/nextjs/AppProviderNextApp.d.ts +5 -0
- package/build/src/nextjs/AppProviderNextApp.js +27 -0
- package/build/src/nextjs/AppProviderNextPages.d.ts +5 -0
- package/build/src/nextjs/AppProviderNextPages.js +37 -0
- package/build/src/nextjs/index.d.ts +1 -0
- package/build/src/nextjs/index.js +1 -0
- package/build/src/persistence/codec.d.ts +47 -0
- package/build/src/persistence/codec.js +57 -0
- package/build/src/persistence/index.d.ts +1 -0
- package/build/src/persistence/index.js +1 -0
- package/build/src/persistence/useStorageState.d.ts +50 -0
- package/build/src/persistence/useStorageState.js +116 -0
- package/build/src/persistence/useStorageState.test.d.ts +1 -0
- package/build/src/persistence/useStorageState.test.js +61 -0
- package/build/src/react-router-dom/AppProvider.d.ts +6 -0
- package/build/src/react-router-dom/AppProvider.js +29 -0
- package/build/src/react-router-dom/AppProvider.test.d.ts +1 -0
- package/build/src/react-router-dom/AppProvider.test.js +12 -0
- package/build/src/react-router-dom/index.d.ts +1 -0
- package/build/src/react-router-dom/index.js +1 -0
- package/build/src/shared/Link.d.ts +8 -0
- package/build/src/shared/Link.js +19 -0
- package/build/src/shared/branding.d.ts +1 -0
- package/build/src/shared/branding.js +6 -0
- package/build/src/shared/components.d.ts +5 -0
- package/build/src/shared/components.js +23 -0
- package/build/src/shared/context.d.ts +12 -0
- package/build/src/shared/context.js +10 -0
- package/build/src/shared/locales/LocaleContext.d.ts +19 -0
- package/build/src/shared/locales/LocaleContext.js +18 -0
- package/build/src/shared/locales/en.d.ts +6 -0
- package/build/src/shared/locales/en.js +8 -0
- package/build/src/shared/navigation.d.ts +16 -0
- package/build/src/shared/navigation.js +129 -0
- package/build/src/useActivePage/index.d.ts +1 -0
- package/build/src/useActivePage/index.js +1 -0
- package/build/src/useActivePage/useActivePage.d.ts +11 -0
- package/build/src/useActivePage/useActivePage.js +48 -0
- package/build/src/useLocalStorageState/index.d.ts +1 -0
- package/build/src/useLocalStorageState/index.js +1 -0
- package/build/src/useLocalStorageState/useLocalStorageState.d.ts +2 -0
- package/build/src/useLocalStorageState/useLocalStorageState.js +14 -0
- package/build/src/useSessionStorageState/index.d.ts +1 -0
- package/build/src/useSessionStorageState/index.js +1 -0
- package/build/src/useSessionStorageState/useSessionStorageState.d.ts +2 -0
- package/build/src/useSessionStorageState/useSessionStorageState.js +14 -0
- package/build/src/utils/collections.d.ts +44 -0
- package/build/src/utils/collections.js +43 -0
- package/build/src/utils/describeConformance.d.ts +7 -0
- package/build/src/utils/describeConformance.js +3 -0
- package/build/src/utils/events.d.ts +27 -0
- package/build/src/utils/events.js +54 -0
- package/build/src/utils/hooks/index.d.ts +2 -0
- package/build/src/utils/hooks/index.js +2 -0
- package/build/src/utils/hooks/useBoolean.d.ts +11 -0
- package/build/src/utils/hooks/useBoolean.js +11 -0
- package/build/src/utils/hooks/useDebounced.d.ts +8 -0
- package/build/src/utils/hooks/useDebounced.js +28 -0
- package/build/src/utils/hooks/useDebouncedHandler.d.ts +12 -0
- package/build/src/utils/hooks/useDebouncedHandler.js +41 -0
- package/build/src/utils/hooks/useLatest.d.ts +6 -0
- package/build/src/utils/hooks/useLatest.js +9 -0
- package/build/src/utils/hooks/usePageTitle.d.ts +4 -0
- package/build/src/utils/hooks/usePageTitle.js +13 -0
- package/build/src/utils/hooks/useSsr.d.ts +4 -0
- package/build/src/utils/hooks/useSsr.js +16 -0
- package/build/src/utils/hooks/useStorageState.d.ts +10 -0
- package/build/src/utils/hooks/useStorageState.js +51 -0
- package/build/src/utils/warnOnce.d.ts +5 -0
- package/build/src/utils/warnOnce.js +11 -0
- package/build/vitest.setup.d.ts +1 -0
- package/build/vitest.setup.js +19 -0
- package/package.json +60 -0
- package/src/Account/Account.test.tsx +56 -0
- package/src/Account/Account.tsx +226 -0
- package/src/Account/AccountPopoverFooter.tsx +33 -0
- package/src/Account/AccountPopoverHeader.tsx +20 -0
- package/src/Account/AccountPreview.test.tsx +79 -0
- package/src/Account/AccountPreview.tsx +189 -0
- package/src/Account/SignInButton.tsx +38 -0
- package/src/Account/SignOutButton.tsx +44 -0
- package/src/Account/index.ts +6 -0
- package/src/AppProvider/AppProvider.test.tsx +25 -0
- package/src/AppProvider/AppProvider.tsx +116 -0
- package/src/AppProvider/AppProviderComponent.tsx +162 -0
- package/src/AppProvider/AppThemeProvider.tsx +177 -0
- package/src/AppProvider/index.ts +1 -0
- package/src/DashboardLayout/DashboardLayout.test.tsx +429 -0
- package/src/DashboardLayout/DashboardLayout.tsx +558 -0
- package/src/DashboardLayout/DashboardSidebarSubNavigation.tsx +301 -0
- package/src/DashboardLayout/ThemeSwitcher.tsx +81 -0
- package/src/DashboardLayout/ToolbarActions.tsx +11 -0
- package/src/DashboardLayout/index.ts +4 -0
- package/src/DashboardLayout/utils.ts +23 -0
- package/src/PageContainer/PageContainer.test.tsx +194 -0
- package/src/PageContainer/PageContainer.tsx +201 -0
- package/src/PageContainer/PageContainerToolbar.test.tsx +11 -0
- package/src/PageContainer/PageContainerToolbar.tsx +31 -0
- package/src/PageContainer/index.ts +2 -0
- package/src/index.ts +15 -0
- package/src/nextjs/AppProvider.test.tsx +48 -0
- package/src/nextjs/AppProvider.tsx +18 -0
- package/src/nextjs/AppProviderNextApp.tsx +37 -0
- package/src/nextjs/AppProviderNextPages.tsx +52 -0
- package/src/nextjs/index.tsx +1 -0
- package/src/persistence/codec.tsx +81 -0
- package/src/persistence/index.ts +1 -0
- package/src/persistence/useStorageState.test.tsx +101 -0
- package/src/persistence/useStorageState.tsx +244 -0
- package/src/react-router-dom/AppProvider.test.tsx +17 -0
- package/src/react-router-dom/AppProvider.tsx +40 -0
- package/src/react-router-dom/index.tsx +1 -0
- package/src/shared/Link.tsx +37 -0
- package/src/shared/branding.ts +7 -0
- package/src/shared/components.tsx +46 -0
- package/src/shared/context.ts +21 -0
- package/src/shared/locales/LocaleContext.tsx +44 -0
- package/src/shared/locales/en.tsx +9 -0
- package/src/shared/navigation.tsx +197 -0
- package/src/useActivePage/index.ts +1 -0
- package/src/useActivePage/useActivePage.ts +66 -0
- package/src/useLocalStorageState/index.ts +1 -0
- package/src/useLocalStorageState/useLocalStorageState.tsx +23 -0
- package/src/useSessionStorageState/index.tsx +1 -0
- package/src/useSessionStorageState/useSessionStorageState.tsx +23 -0
- package/src/utils/collections.ts +125 -0
- package/src/utils/describeConformance.ts +14 -0
- package/src/utils/events.ts +81 -0
- package/src/utils/hooks/index.ts +2 -0
- package/src/utils/hooks/useBoolean.ts +12 -0
- package/src/utils/hooks/useDebounced.ts +39 -0
- package/src/utils/hooks/useDebouncedHandler.ts +71 -0
- package/src/utils/hooks/useLatest.ts +16 -0
- package/src/utils/hooks/usePageTitle.ts +14 -0
- package/src/utils/hooks/useSsr.ts +24 -0
- package/src/utils/hooks/useStorageState.ts +130 -0
- package/src/utils/warnOnce.ts +12 -0
- package/tsconfig.json +19 -0
- package/vite.config.ts +27 -0
- package/vitest.setup.ts +22 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import DEFAULT_LOCALE_TEXT from "./en";
|
|
5
|
+
export const LocaleContext = React.createContext(DEFAULT_LOCALE_TEXT);
|
|
6
|
+
/**
|
|
7
|
+
* @ignore - internal component.
|
|
8
|
+
*/
|
|
9
|
+
export function LocaleProvider({ localeText, children }) {
|
|
10
|
+
const mergedLocaleText = React.useMemo(() => ({ ...DEFAULT_LOCALE_TEXT, ...localeText }), [localeText]);
|
|
11
|
+
return (_jsx(LocaleContext.Provider, { value: mergedLocaleText, children: children }));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* @ignore - internal hook.
|
|
15
|
+
*/
|
|
16
|
+
export function useLocaleText() {
|
|
17
|
+
return React.useContext(LocaleContext);
|
|
18
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Navigation, NavigationItem, NavigationPageItem, NavigationSubheaderItem } from "../AppProvider";
|
|
2
|
+
export declare const getItemKind: (item: NavigationItem) => "page" | "header" | "divider";
|
|
3
|
+
export declare const isPageItem: (item: NavigationItem) => item is NavigationPageItem;
|
|
4
|
+
export declare const getItemTitle: (item: NavigationPageItem | NavigationSubheaderItem) => string;
|
|
5
|
+
export declare function getPageItemFullPath(basePath: string, navigationItem: NavigationPageItem): string;
|
|
6
|
+
export declare function isPageItemSelected(navigationItem: NavigationPageItem, basePath: string, pathname: string): boolean;
|
|
7
|
+
export declare function hasSelectedNavigationChildren(navigationItem: NavigationItem, basePath: string, pathname: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Matches a path against the navigation to find the active page. i.e. the page that should be
|
|
10
|
+
* marked as selected in the navigation.
|
|
11
|
+
*/
|
|
12
|
+
export declare function matchPath(navigation: Navigation, path: string): NavigationPageItem | null;
|
|
13
|
+
/**
|
|
14
|
+
* Gets the path for a specific navigation page item.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getItemPath(navigation: Navigation, item: NavigationPageItem): string;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { pathToRegexp } from "path-to-regexp";
|
|
2
|
+
import invariant from "invariant";
|
|
3
|
+
export const getItemKind = (item) => item.kind ?? "page";
|
|
4
|
+
export const isPageItem = (item) => getItemKind(item) === "page";
|
|
5
|
+
export const getItemTitle = (item) => {
|
|
6
|
+
return isPageItem(item) ? item.title ?? item.segment ?? "" : item.title;
|
|
7
|
+
};
|
|
8
|
+
export function getPageItemFullPath(basePath, navigationItem) {
|
|
9
|
+
return `${basePath}${basePath && !navigationItem.segment ? "" : "/"}${navigationItem.segment ?? ""}`;
|
|
10
|
+
}
|
|
11
|
+
export function isPageItemSelected(navigationItem, basePath, pathname) {
|
|
12
|
+
return navigationItem.pattern
|
|
13
|
+
? pathToRegexp(`${basePath}/${navigationItem.pattern}`).test(pathname)
|
|
14
|
+
: getPageItemFullPath(basePath, navigationItem) === pathname;
|
|
15
|
+
}
|
|
16
|
+
export function hasSelectedNavigationChildren(navigationItem, basePath, pathname) {
|
|
17
|
+
if (isPageItem(navigationItem) && navigationItem.children) {
|
|
18
|
+
const navigationItemFullPath = getPageItemFullPath(basePath, navigationItem);
|
|
19
|
+
return navigationItem.children.some((nestedNavigationItem) => {
|
|
20
|
+
if (!isPageItem(nestedNavigationItem)) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (nestedNavigationItem.children) {
|
|
24
|
+
return hasSelectedNavigationChildren(nestedNavigationItem, navigationItemFullPath, pathname);
|
|
25
|
+
}
|
|
26
|
+
return isPageItemSelected(nestedNavigationItem, navigationItemFullPath, pathname);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Builds a map of navigation page items to their respective paths. This map is used to quickly
|
|
33
|
+
* lookup the path of a navigation item. It will be cached for the lifetime of the navigation.
|
|
34
|
+
*/
|
|
35
|
+
function buildItemToPathMap(navigation) {
|
|
36
|
+
const map = new Map();
|
|
37
|
+
const visit = (item, base) => {
|
|
38
|
+
if (isPageItem(item)) {
|
|
39
|
+
const path = `${base}${item.segment ? `/${item.segment}` : ""}` || "/";
|
|
40
|
+
map.set(item, path);
|
|
41
|
+
if (item.children) {
|
|
42
|
+
for (const child of item.children) {
|
|
43
|
+
visit(child, path);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
for (const item of navigation) {
|
|
49
|
+
visit(item, "");
|
|
50
|
+
}
|
|
51
|
+
return map;
|
|
52
|
+
}
|
|
53
|
+
const itemToPathMapCache = new WeakMap();
|
|
54
|
+
/**
|
|
55
|
+
* Gets the cached map of navigation page items to their respective paths.
|
|
56
|
+
*/
|
|
57
|
+
function getItemToPathMap(navigation) {
|
|
58
|
+
let map = itemToPathMapCache.get(navigation);
|
|
59
|
+
if (!map) {
|
|
60
|
+
map = buildItemToPathMap(navigation);
|
|
61
|
+
itemToPathMapCache.set(navigation, map);
|
|
62
|
+
}
|
|
63
|
+
return map;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Build a lookup map of paths to navigation items. This map is used to match paths against
|
|
67
|
+
* to find the active page.
|
|
68
|
+
*/
|
|
69
|
+
function buildItemLookup(navigation) {
|
|
70
|
+
const map = new Map();
|
|
71
|
+
const visit = (item) => {
|
|
72
|
+
if (isPageItem(item)) {
|
|
73
|
+
const path = getItemPath(navigation, item);
|
|
74
|
+
if (map.has(path)) {
|
|
75
|
+
console.warn(`Duplicate path in navigation: ${path}`);
|
|
76
|
+
}
|
|
77
|
+
map.set(path, item);
|
|
78
|
+
if (item.pattern) {
|
|
79
|
+
const basePath = item.segment
|
|
80
|
+
? path.slice(0, -item.segment.length)
|
|
81
|
+
: path;
|
|
82
|
+
map.set(pathToRegexp(basePath + item.pattern), item);
|
|
83
|
+
}
|
|
84
|
+
if (item.children) {
|
|
85
|
+
for (const child of item.children) {
|
|
86
|
+
visit(child);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
for (const item of navigation) {
|
|
92
|
+
visit(item);
|
|
93
|
+
}
|
|
94
|
+
return map;
|
|
95
|
+
}
|
|
96
|
+
const itemLookupMapCache = new WeakMap();
|
|
97
|
+
function getItemLookup(navigation) {
|
|
98
|
+
let map = itemLookupMapCache.get(navigation);
|
|
99
|
+
if (!map) {
|
|
100
|
+
map = buildItemLookup(navigation);
|
|
101
|
+
itemLookupMapCache.set(navigation, map);
|
|
102
|
+
}
|
|
103
|
+
return map;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Matches a path against the navigation to find the active page. i.e. the page that should be
|
|
107
|
+
* marked as selected in the navigation.
|
|
108
|
+
*/
|
|
109
|
+
export function matchPath(navigation, path) {
|
|
110
|
+
const lookup = getItemLookup(navigation);
|
|
111
|
+
for (const [key, item] of lookup.entries()) {
|
|
112
|
+
if (typeof key === "string" && key === path) {
|
|
113
|
+
return item;
|
|
114
|
+
}
|
|
115
|
+
if (key instanceof RegExp && key.test(path)) {
|
|
116
|
+
return item;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Gets the path for a specific navigation page item.
|
|
123
|
+
*/
|
|
124
|
+
export function getItemPath(navigation, item) {
|
|
125
|
+
const map = getItemToPathMap(navigation);
|
|
126
|
+
const path = map.get(item);
|
|
127
|
+
invariant(path, `Item not found in navigation: ${item.title}`);
|
|
128
|
+
return path;
|
|
129
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useActivePage";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useActivePage";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Breadcrumb } from "../PageContainer";
|
|
2
|
+
export interface ActivePage {
|
|
3
|
+
title: string;
|
|
4
|
+
path: string;
|
|
5
|
+
/**
|
|
6
|
+
* @deprecated Use `breadcrumbs` instead.
|
|
7
|
+
*/
|
|
8
|
+
breadCrumbs: Breadcrumb[];
|
|
9
|
+
breadcrumbs: Breadcrumb[];
|
|
10
|
+
}
|
|
11
|
+
export declare function useActivePage(): ActivePage | null;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { NavigationContext, RouterContext } from "../shared/context";
|
|
4
|
+
import { getItemPath, getItemTitle, matchPath } from "../shared/navigation";
|
|
5
|
+
export function useActivePage() {
|
|
6
|
+
const navigationContext = React.useContext(NavigationContext);
|
|
7
|
+
const routerContext = React.useContext(RouterContext);
|
|
8
|
+
const pathname = routerContext?.pathname ?? "/";
|
|
9
|
+
const activeItem = matchPath(navigationContext, pathname);
|
|
10
|
+
const rootItem = matchPath(navigationContext, "/");
|
|
11
|
+
return React.useMemo(() => {
|
|
12
|
+
if (!activeItem) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const breadcrumbs = [];
|
|
16
|
+
if (rootItem) {
|
|
17
|
+
breadcrumbs.push({
|
|
18
|
+
title: getItemTitle(rootItem),
|
|
19
|
+
path: "/"
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
23
|
+
let prefix = "";
|
|
24
|
+
for (const segment of segments) {
|
|
25
|
+
const path = `${prefix}/${segment}`;
|
|
26
|
+
prefix = path;
|
|
27
|
+
const item = matchPath(navigationContext, path);
|
|
28
|
+
if (!item) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const itemPath = getItemPath(navigationContext, item);
|
|
32
|
+
const lastCrumb = breadcrumbs[breadcrumbs.length - 1];
|
|
33
|
+
if (lastCrumb?.path !== itemPath) {
|
|
34
|
+
breadcrumbs.push({
|
|
35
|
+
title: getItemTitle(item),
|
|
36
|
+
path: itemPath
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
title: getItemTitle(activeItem),
|
|
42
|
+
path: getItemPath(navigationContext, activeItem),
|
|
43
|
+
breadcrumbs,
|
|
44
|
+
// TODO: Remove in the next major version
|
|
45
|
+
breadCrumbs: breadcrumbs
|
|
46
|
+
};
|
|
47
|
+
}, [activeItem, rootItem, pathname, navigationContext]);
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useLocalStorageState";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useLocalStorageState";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useStorageState, useStorageStateServer } from "../persistence";
|
|
3
|
+
/**
|
|
4
|
+
* Sync state to local storage so that it persists through a page refresh. Usage is
|
|
5
|
+
* similar to useState except we pass in a storage key so that we can default
|
|
6
|
+
* to that value on page load instead of the specified initial value.
|
|
7
|
+
*
|
|
8
|
+
* Since the storage API isn't available in server-rendering environments, we
|
|
9
|
+
* return null during SSR and hydration.
|
|
10
|
+
*/
|
|
11
|
+
const useLocalStorageStateBrowser = (...args) => useStorageState(window.localStorage, ...args);
|
|
12
|
+
export const useLocalStorageState = typeof window === "undefined"
|
|
13
|
+
? useStorageStateServer
|
|
14
|
+
: useLocalStorageStateBrowser;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useSessionStorageState";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useSessionStorageState";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useStorageState, useStorageStateServer } from "../persistence";
|
|
3
|
+
/**
|
|
4
|
+
* Sync state to session storage so that it persists through a page refresh. Usage is
|
|
5
|
+
* similar to useState except we pass in a storage key so that we can default
|
|
6
|
+
* to that value on page load instead of the specified initial value.
|
|
7
|
+
*
|
|
8
|
+
* Since the storage API isn't available in server-rendering environments, we
|
|
9
|
+
* return null during SSR and hydration.
|
|
10
|
+
*/
|
|
11
|
+
const useSessionStorageStateBrowser = (...args) => useStorageState(window.sessionStorage, ...args);
|
|
12
|
+
export const useSessionStorageState = typeof window === "undefined"
|
|
13
|
+
? useStorageStateServer
|
|
14
|
+
: useSessionStorageStateBrowser;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export declare function asArray<T>(maybeArray: T | T[]): T[];
|
|
2
|
+
type PropertiesOf<P> = Extract<keyof P, string>;
|
|
3
|
+
type Require<T, K extends keyof T> = T & {
|
|
4
|
+
[P in K]-?: T[P];
|
|
5
|
+
};
|
|
6
|
+
type Ensure<U, K extends PropertyKey> = K extends keyof U ? Require<U, K> : U & Record<K, unknown>;
|
|
7
|
+
/**
|
|
8
|
+
* Type aware version of Object.protoype.hasOwnProperty.
|
|
9
|
+
* See https://fettblog.eu/typescript-hasownproperty/
|
|
10
|
+
*/
|
|
11
|
+
export declare function hasOwnProperty<X extends {}, Y extends PropertyKey>(obj: X, prop: Y): obj is Ensure<X, Y>;
|
|
12
|
+
/**
|
|
13
|
+
* Maps `obj` to a new object. The `mapper` function receives an entry array of key and value and
|
|
14
|
+
* is allowed to manipulate both. It can also return `null` to omit a property from the result.
|
|
15
|
+
*/
|
|
16
|
+
export declare function mapProperties<P, L extends PropertyKey, U>(obj: P, mapper: <K extends PropertiesOf<P>>(old: [K, P[K]]) => [L, U] | null): Record<L, U>;
|
|
17
|
+
/**
|
|
18
|
+
* Maps an objects' property keys. The result is a new object with the mapped keys, but the same values.
|
|
19
|
+
*/
|
|
20
|
+
export declare function mapKeys<U>(obj: Record<string, U>, mapper: (old: string) => string): Record<string, U>;
|
|
21
|
+
/**
|
|
22
|
+
* Maps an objects' property values. The result is a new object with the same keys, but mapped values.
|
|
23
|
+
*/
|
|
24
|
+
export declare function mapValues<P, V>(obj: P, mapper: (old: P[PropertiesOf<P>], key: PropertiesOf<P>) => V): Record<PropertiesOf<P>, V>;
|
|
25
|
+
/**
|
|
26
|
+
* Filters an objects' property values. Similar to `array.filter` but for objects. The result is a new
|
|
27
|
+
* object with all the properties removed for which `filter` returned `false`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function filterValues<K extends PropertyKey, P, Q extends P>(obj: Record<K, P>, filter: (old: P) => old is Q): Record<K, Q>;
|
|
30
|
+
export declare function filterValues<P>(obj: P, filter: (old: P[keyof P]) => boolean): Partial<P>;
|
|
31
|
+
export declare function filterValues<U>(obj: Record<string, U>, filter: (old: U) => boolean): Record<string, U>;
|
|
32
|
+
/**
|
|
33
|
+
* Filters an objects' property keys. Similar to `array.filter` but for objects. The result is a new
|
|
34
|
+
* object with all the properties removed for which `filter` returned `false`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function filterKeys<P>(obj: P, filter: (old: keyof P) => boolean): Partial<P>;
|
|
37
|
+
export declare function filterKeys<U>(obj: Record<string, U>, filter: (old: string) => boolean): Record<string, U>;
|
|
38
|
+
/**
|
|
39
|
+
* Compares the properties of two objects. Returns `true` if all properties are strictly equal, `false`
|
|
40
|
+
* otherwise.
|
|
41
|
+
* Pass a subset of properties to only compare those.
|
|
42
|
+
*/
|
|
43
|
+
export declare function equalProperties<P extends object>(obj1: P, obj2: P, subset?: (keyof P)[]): boolean;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export function asArray(maybeArray) {
|
|
2
|
+
return Array.isArray(maybeArray) ? maybeArray : [maybeArray];
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Type aware version of Object.protoype.hasOwnProperty.
|
|
6
|
+
* See https://fettblog.eu/typescript-hasownproperty/
|
|
7
|
+
*/
|
|
8
|
+
export function hasOwnProperty(obj, prop) {
|
|
9
|
+
return obj.hasOwnProperty(prop);
|
|
10
|
+
}
|
|
11
|
+
export function mapProperties(obj, mapper) {
|
|
12
|
+
return Object.fromEntries(Object.entries(obj).flatMap((entry) => {
|
|
13
|
+
const mapped = mapper(entry);
|
|
14
|
+
return mapped ? [mapped] : [];
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Maps an objects' property keys. The result is a new object with the mapped keys, but the same values.
|
|
19
|
+
*/
|
|
20
|
+
export function mapKeys(obj, mapper) {
|
|
21
|
+
return mapProperties(obj, ([key, value]) => [mapper(key), value]);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Maps an objects' property values. The result is a new object with the same keys, but mapped values.
|
|
25
|
+
*/
|
|
26
|
+
export function mapValues(obj, mapper) {
|
|
27
|
+
return mapProperties(obj, ([key, value]) => [key, mapper(value, key)]);
|
|
28
|
+
}
|
|
29
|
+
export function filterValues(obj, filter) {
|
|
30
|
+
return mapProperties(obj, ([key, value]) => filter(value) ? [key, value] : null);
|
|
31
|
+
}
|
|
32
|
+
export function filterKeys(obj, filter) {
|
|
33
|
+
return mapProperties(obj, ([key, value]) => filter(key) ? [key, value] : null);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Compares the properties of two objects. Returns `true` if all properties are strictly equal, `false`
|
|
37
|
+
* otherwise.
|
|
38
|
+
* Pass a subset of properties to only compare those.
|
|
39
|
+
*/
|
|
40
|
+
export function equalProperties(obj1, obj2, subset) {
|
|
41
|
+
const keysToCheck = new Set(subset ?? [...Object.keys(obj1), ...Object.keys(obj2)]);
|
|
42
|
+
return Array.from(keysToCheck).every((key) => Object.is(obj1[key], obj2[key]));
|
|
43
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface ConformanceOptions {
|
|
3
|
+
refInstanceof?: unknown;
|
|
4
|
+
inheritComponent?: React.ElementType;
|
|
5
|
+
skip?: Array<"themeDefaultProps">;
|
|
6
|
+
}
|
|
7
|
+
export default function describeConformance(minimalElement: React.ReactElement<unknown>, getOptions: () => ConformanceOptions): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type EventName = string | symbol;
|
|
2
|
+
export type EventHandlers = Record<EventName, unknown>;
|
|
3
|
+
export type EventHandler<T extends EventHandlers, K extends keyof T = keyof T> = (event: T[K]) => void;
|
|
4
|
+
export type AllEventsHandler<T extends EventHandlers, K extends keyof T = keyof T> = (type: K, event: T[K]) => void;
|
|
5
|
+
/**
|
|
6
|
+
* Lightweight event emitter
|
|
7
|
+
*/
|
|
8
|
+
export declare class Emitter<T extends EventHandlers = {}> {
|
|
9
|
+
private handlers;
|
|
10
|
+
/**
|
|
11
|
+
* Add a listener for an event
|
|
12
|
+
*/
|
|
13
|
+
on(name: "*", handler: AllEventsHandler<T>): void;
|
|
14
|
+
on<K extends keyof T>(name: K, handler: EventHandler<T, K>): void;
|
|
15
|
+
/**
|
|
16
|
+
* Remove a listener from an event
|
|
17
|
+
*/
|
|
18
|
+
off<K extends keyof T>(name: K, handler: EventHandler<T, K>): void;
|
|
19
|
+
/**
|
|
20
|
+
* Subscribe to an event and return an unsubscribe function.
|
|
21
|
+
*/
|
|
22
|
+
subscribe<K extends keyof T>(name: K, handler: EventHandler<T, K>): () => void;
|
|
23
|
+
/**
|
|
24
|
+
* Emit an event.
|
|
25
|
+
*/
|
|
26
|
+
emit<K extends keyof T>(name: K, event: T[K]): void;
|
|
27
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight event emitter
|
|
3
|
+
*/
|
|
4
|
+
export class Emitter {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.handlers = new Map();
|
|
7
|
+
}
|
|
8
|
+
on(name, handler) {
|
|
9
|
+
let eventHandlers = this.handlers.get(name);
|
|
10
|
+
if (!eventHandlers) {
|
|
11
|
+
eventHandlers = new Set();
|
|
12
|
+
this.handlers.set(name, eventHandlers);
|
|
13
|
+
}
|
|
14
|
+
eventHandlers.add(handler);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Remove a listener from an event
|
|
18
|
+
*/
|
|
19
|
+
off(name, handler) {
|
|
20
|
+
const eventHandlers = this.handlers.get(name);
|
|
21
|
+
if (eventHandlers) {
|
|
22
|
+
eventHandlers.delete(handler);
|
|
23
|
+
if (eventHandlers.size <= 0) {
|
|
24
|
+
this.handlers.delete(name);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Subscribe to an event and return an unsubscribe function.
|
|
30
|
+
*/
|
|
31
|
+
subscribe(name, handler) {
|
|
32
|
+
this.on(name, handler);
|
|
33
|
+
return () => {
|
|
34
|
+
this.off(name, handler);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Emit an event.
|
|
39
|
+
*/
|
|
40
|
+
emit(name, event) {
|
|
41
|
+
const eventHandlers = this.handlers.get(name);
|
|
42
|
+
if (eventHandlers) {
|
|
43
|
+
for (const eventHandler of eventHandlers) {
|
|
44
|
+
eventHandler(event);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const allHandlers = this.handlers.get("*");
|
|
48
|
+
if (allHandlers) {
|
|
49
|
+
for (const eventHandler of allHandlers) {
|
|
50
|
+
eventHandler(name, event);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* A utility with shortcuts to manipulate boolean values.
|
|
4
|
+
*/
|
|
5
|
+
export default function useBoolean(initialValue: boolean): {
|
|
6
|
+
value: boolean;
|
|
7
|
+
setValue: React.Dispatch<React.SetStateAction<boolean>>;
|
|
8
|
+
toggle: () => void;
|
|
9
|
+
setTrue: () => void;
|
|
10
|
+
setFalse: () => void;
|
|
11
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* A utility with shortcuts to manipulate boolean values.
|
|
4
|
+
*/
|
|
5
|
+
export default function useBoolean(initialValue) {
|
|
6
|
+
const [value, setValue] = React.useState(initialValue);
|
|
7
|
+
const toggle = React.useCallback(() => setValue((existing) => !existing), []);
|
|
8
|
+
const setTrue = React.useCallback(() => setValue(true), []);
|
|
9
|
+
const setFalse = React.useCallback(() => setValue(false), []);
|
|
10
|
+
return { value, setValue, toggle, setTrue, setFalse };
|
|
11
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This hook allows you to debounce any fast changing value. The debounced value will only
|
|
3
|
+
* reflect the latest value when the useDebounce hook has not been called for the specified
|
|
4
|
+
* time period.
|
|
5
|
+
*
|
|
6
|
+
* Inspired by https://usehooks.com/useDebounce/
|
|
7
|
+
*/
|
|
8
|
+
export default function useDebounced<T>(value: T, delay: number): T;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* This hook allows you to debounce any fast changing value. The debounced value will only
|
|
4
|
+
* reflect the latest value when the useDebounce hook has not been called for the specified
|
|
5
|
+
* time period.
|
|
6
|
+
*
|
|
7
|
+
* Inspired by https://usehooks.com/useDebounce/
|
|
8
|
+
*/
|
|
9
|
+
export default function useDebounced(value, delay) {
|
|
10
|
+
const [debouncedValue, setDebouncedValue] = React.useState(() => value);
|
|
11
|
+
const timeoutIdRef = React.useRef(null);
|
|
12
|
+
React.useEffect(() => () => {
|
|
13
|
+
if (timeoutIdRef.current) {
|
|
14
|
+
clearTimeout(timeoutIdRef.current);
|
|
15
|
+
timeoutIdRef.current = null;
|
|
16
|
+
}
|
|
17
|
+
}, []);
|
|
18
|
+
React.useEffect(() => {
|
|
19
|
+
timeoutIdRef.current = setTimeout(() => setDebouncedValue(() => value), delay);
|
|
20
|
+
return () => {
|
|
21
|
+
if (timeoutIdRef.current) {
|
|
22
|
+
clearTimeout(timeoutIdRef.current);
|
|
23
|
+
timeoutIdRef.current = null;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}, [value, delay]);
|
|
27
|
+
return debouncedValue;
|
|
28
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface Handler<P extends unknown[]> {
|
|
2
|
+
(...params: P): void;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Creates a debounced version of the handler that is passed. The invocation of [fn] is
|
|
6
|
+
* delayed for [delay] milliseconds from the last invocation of the debounced function.
|
|
7
|
+
*
|
|
8
|
+
* This implementation adds on the lodash implementation in that it handles updates to the
|
|
9
|
+
* delay value.
|
|
10
|
+
*/
|
|
11
|
+
export default function useDebouncedHandler<P extends unknown[]>(fn: Handler<P>, delay: number): Handler<P>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
function defer(fn, params, delay) {
|
|
3
|
+
const timeout = setTimeout(() => {
|
|
4
|
+
fn.current(...params);
|
|
5
|
+
}, delay);
|
|
6
|
+
return { startTime: Date.now(), timeout, params };
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Creates a debounced version of the handler that is passed. The invocation of [fn] is
|
|
10
|
+
* delayed for [delay] milliseconds from the last invocation of the debounced function.
|
|
11
|
+
*
|
|
12
|
+
* This implementation adds on the lodash implementation in that it handles updates to the
|
|
13
|
+
* delay value.
|
|
14
|
+
*/
|
|
15
|
+
export default function useDebouncedHandler(fn, delay) {
|
|
16
|
+
const fnRef = React.useRef(fn);
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
fnRef.current = fn;
|
|
19
|
+
}, [fn]);
|
|
20
|
+
const delayedInvocation = React.useRef(null);
|
|
21
|
+
const clearCurrent = React.useCallback(() => {
|
|
22
|
+
if (delayedInvocation.current) {
|
|
23
|
+
clearTimeout(delayedInvocation.current.timeout);
|
|
24
|
+
delayedInvocation.current = null;
|
|
25
|
+
}
|
|
26
|
+
}, []);
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
if (!delayedInvocation.current) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const { startTime, params } = delayedInvocation.current;
|
|
32
|
+
const elapsed = Date.now() - startTime;
|
|
33
|
+
const newDelay = Math.max(delay - elapsed, 0);
|
|
34
|
+
clearCurrent();
|
|
35
|
+
delayedInvocation.current = defer(fnRef, params, newDelay);
|
|
36
|
+
}, [delay, clearCurrent]);
|
|
37
|
+
return React.useCallback((...params) => {
|
|
38
|
+
clearCurrent();
|
|
39
|
+
delayedInvocation.current = defer(fnRef, params, delay);
|
|
40
|
+
}, [delay, clearCurrent]);
|
|
41
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Sets the current document title.
|
|
4
|
+
*/
|
|
5
|
+
export default function usePageTitle(title) {
|
|
6
|
+
React.useEffect(() => {
|
|
7
|
+
const original = document.title;
|
|
8
|
+
document.title = title;
|
|
9
|
+
return () => {
|
|
10
|
+
document.title = original;
|
|
11
|
+
};
|
|
12
|
+
}, [title]);
|
|
13
|
+
}
|