@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.
Files changed (202) hide show
  1. package/.github/workflows/main.yml +49 -0
  2. package/.vscode/settings.json +3 -0
  3. package/LICENSE +21 -0
  4. package/README.md +11 -0
  5. package/build/src/Account/Account.d.ts +71 -0
  6. package/build/src/Account/Account.js +117 -0
  7. package/build/src/Account/Account.test.d.ts +1 -0
  8. package/build/src/Account/Account.test.js +35 -0
  9. package/build/src/Account/AccountPopoverFooter.d.ts +6 -0
  10. package/build/src/Account/AccountPopoverFooter.js +21 -0
  11. package/build/src/Account/AccountPopoverHeader.d.ts +6 -0
  12. package/build/src/Account/AccountPopoverHeader.js +15 -0
  13. package/build/src/Account/AccountPreview.d.ts +67 -0
  14. package/build/src/Account/AccountPreview.js +76 -0
  15. package/build/src/Account/AccountPreview.test.d.ts +1 -0
  16. package/build/src/Account/AccountPreview.test.js +47 -0
  17. package/build/src/Account/SignInButton.d.ts +2 -0
  18. package/build/src/Account/SignInButton.js +26 -0
  19. package/build/src/Account/SignOutButton.d.ts +3 -0
  20. package/build/src/Account/SignOutButton.js +28 -0
  21. package/build/src/Account/index.d.ts +6 -0
  22. package/build/src/Account/index.js +6 -0
  23. package/build/src/AppProvider/AppProvider.d.ts +98 -0
  24. package/build/src/AppProvider/AppProvider.js +4 -0
  25. package/build/src/AppProvider/AppProvider.test.d.ts +4 -0
  26. package/build/src/AppProvider/AppProvider.test.js +16 -0
  27. package/build/src/AppProvider/AppProviderComponent.d.ts +17 -0
  28. package/build/src/AppProvider/AppProviderComponent.js +120 -0
  29. package/build/src/AppProvider/AppThemeProvider.d.ts +12 -0
  30. package/build/src/AppProvider/AppThemeProvider.js +73 -0
  31. package/build/src/AppProvider/index.d.ts +1 -0
  32. package/build/src/AppProvider/index.js +1 -0
  33. package/build/src/DashboardLayout/DashboardLayout.d.ts +83 -0
  34. package/build/src/DashboardLayout/DashboardLayout.js +286 -0
  35. package/build/src/DashboardLayout/DashboardLayout.test.d.ts +1 -0
  36. package/build/src/DashboardLayout/DashboardLayout.test.js +291 -0
  37. package/build/src/DashboardLayout/DashboardSidebarSubNavigation.d.ts +16 -0
  38. package/build/src/DashboardLayout/DashboardSidebarSubNavigation.js +149 -0
  39. package/build/src/DashboardLayout/ThemeSwitcher.d.ts +5 -0
  40. package/build/src/DashboardLayout/ThemeSwitcher.js +40 -0
  41. package/build/src/DashboardLayout/ToolbarActions.d.ts +5 -0
  42. package/build/src/DashboardLayout/ToolbarActions.js +9 -0
  43. package/build/src/DashboardLayout/index.d.ts +2 -0
  44. package/build/src/DashboardLayout/index.js +3 -0
  45. package/build/src/DashboardLayout/utils.d.ts +8 -0
  46. package/build/src/DashboardLayout/utils.js +16 -0
  47. package/build/src/PageContainer/PageContainer.d.ts +66 -0
  48. package/build/src/PageContainer/PageContainer.js +97 -0
  49. package/build/src/PageContainer/PageContainer.test.d.ts +1 -0
  50. package/build/src/PageContainer/PageContainer.test.js +127 -0
  51. package/build/src/PageContainer/PageContainerToolbar.d.ts +16 -0
  52. package/build/src/PageContainer/PageContainerToolbar.js +24 -0
  53. package/build/src/PageContainer/PageContainerToolbar.test.d.ts +1 -0
  54. package/build/src/PageContainer/PageContainerToolbar.test.js +10 -0
  55. package/build/src/PageContainer/index.d.ts +2 -0
  56. package/build/src/PageContainer/index.js +2 -0
  57. package/build/src/index.d.ts +8 -0
  58. package/build/src/index.js +8 -0
  59. package/build/src/nextjs/AppProvider.d.ts +6 -0
  60. package/build/src/nextjs/AppProvider.js +16 -0
  61. package/build/src/nextjs/AppProvider.test.d.ts +1 -0
  62. package/build/src/nextjs/AppProvider.test.js +36 -0
  63. package/build/src/nextjs/AppProviderNextApp.d.ts +5 -0
  64. package/build/src/nextjs/AppProviderNextApp.js +27 -0
  65. package/build/src/nextjs/AppProviderNextPages.d.ts +5 -0
  66. package/build/src/nextjs/AppProviderNextPages.js +37 -0
  67. package/build/src/nextjs/index.d.ts +1 -0
  68. package/build/src/nextjs/index.js +1 -0
  69. package/build/src/persistence/codec.d.ts +47 -0
  70. package/build/src/persistence/codec.js +57 -0
  71. package/build/src/persistence/index.d.ts +1 -0
  72. package/build/src/persistence/index.js +1 -0
  73. package/build/src/persistence/useStorageState.d.ts +50 -0
  74. package/build/src/persistence/useStorageState.js +116 -0
  75. package/build/src/persistence/useStorageState.test.d.ts +1 -0
  76. package/build/src/persistence/useStorageState.test.js +61 -0
  77. package/build/src/react-router-dom/AppProvider.d.ts +6 -0
  78. package/build/src/react-router-dom/AppProvider.js +29 -0
  79. package/build/src/react-router-dom/AppProvider.test.d.ts +1 -0
  80. package/build/src/react-router-dom/AppProvider.test.js +12 -0
  81. package/build/src/react-router-dom/index.d.ts +1 -0
  82. package/build/src/react-router-dom/index.js +1 -0
  83. package/build/src/shared/Link.d.ts +8 -0
  84. package/build/src/shared/Link.js +19 -0
  85. package/build/src/shared/branding.d.ts +1 -0
  86. package/build/src/shared/branding.js +6 -0
  87. package/build/src/shared/components.d.ts +5 -0
  88. package/build/src/shared/components.js +23 -0
  89. package/build/src/shared/context.d.ts +12 -0
  90. package/build/src/shared/context.js +10 -0
  91. package/build/src/shared/locales/LocaleContext.d.ts +19 -0
  92. package/build/src/shared/locales/LocaleContext.js +18 -0
  93. package/build/src/shared/locales/en.d.ts +6 -0
  94. package/build/src/shared/locales/en.js +8 -0
  95. package/build/src/shared/navigation.d.ts +16 -0
  96. package/build/src/shared/navigation.js +129 -0
  97. package/build/src/useActivePage/index.d.ts +1 -0
  98. package/build/src/useActivePage/index.js +1 -0
  99. package/build/src/useActivePage/useActivePage.d.ts +11 -0
  100. package/build/src/useActivePage/useActivePage.js +48 -0
  101. package/build/src/useLocalStorageState/index.d.ts +1 -0
  102. package/build/src/useLocalStorageState/index.js +1 -0
  103. package/build/src/useLocalStorageState/useLocalStorageState.d.ts +2 -0
  104. package/build/src/useLocalStorageState/useLocalStorageState.js +14 -0
  105. package/build/src/useSessionStorageState/index.d.ts +1 -0
  106. package/build/src/useSessionStorageState/index.js +1 -0
  107. package/build/src/useSessionStorageState/useSessionStorageState.d.ts +2 -0
  108. package/build/src/useSessionStorageState/useSessionStorageState.js +14 -0
  109. package/build/src/utils/collections.d.ts +44 -0
  110. package/build/src/utils/collections.js +43 -0
  111. package/build/src/utils/describeConformance.d.ts +7 -0
  112. package/build/src/utils/describeConformance.js +3 -0
  113. package/build/src/utils/events.d.ts +27 -0
  114. package/build/src/utils/events.js +54 -0
  115. package/build/src/utils/hooks/index.d.ts +2 -0
  116. package/build/src/utils/hooks/index.js +2 -0
  117. package/build/src/utils/hooks/useBoolean.d.ts +11 -0
  118. package/build/src/utils/hooks/useBoolean.js +11 -0
  119. package/build/src/utils/hooks/useDebounced.d.ts +8 -0
  120. package/build/src/utils/hooks/useDebounced.js +28 -0
  121. package/build/src/utils/hooks/useDebouncedHandler.d.ts +12 -0
  122. package/build/src/utils/hooks/useDebouncedHandler.js +41 -0
  123. package/build/src/utils/hooks/useLatest.d.ts +6 -0
  124. package/build/src/utils/hooks/useLatest.js +9 -0
  125. package/build/src/utils/hooks/usePageTitle.d.ts +4 -0
  126. package/build/src/utils/hooks/usePageTitle.js +13 -0
  127. package/build/src/utils/hooks/useSsr.d.ts +4 -0
  128. package/build/src/utils/hooks/useSsr.js +16 -0
  129. package/build/src/utils/hooks/useStorageState.d.ts +10 -0
  130. package/build/src/utils/hooks/useStorageState.js +51 -0
  131. package/build/src/utils/warnOnce.d.ts +5 -0
  132. package/build/src/utils/warnOnce.js +11 -0
  133. package/build/vitest.setup.d.ts +1 -0
  134. package/build/vitest.setup.js +19 -0
  135. package/package.json +60 -0
  136. package/src/Account/Account.test.tsx +56 -0
  137. package/src/Account/Account.tsx +226 -0
  138. package/src/Account/AccountPopoverFooter.tsx +33 -0
  139. package/src/Account/AccountPopoverHeader.tsx +20 -0
  140. package/src/Account/AccountPreview.test.tsx +79 -0
  141. package/src/Account/AccountPreview.tsx +189 -0
  142. package/src/Account/SignInButton.tsx +38 -0
  143. package/src/Account/SignOutButton.tsx +44 -0
  144. package/src/Account/index.ts +6 -0
  145. package/src/AppProvider/AppProvider.test.tsx +25 -0
  146. package/src/AppProvider/AppProvider.tsx +116 -0
  147. package/src/AppProvider/AppProviderComponent.tsx +162 -0
  148. package/src/AppProvider/AppThemeProvider.tsx +177 -0
  149. package/src/AppProvider/index.ts +1 -0
  150. package/src/DashboardLayout/DashboardLayout.test.tsx +429 -0
  151. package/src/DashboardLayout/DashboardLayout.tsx +558 -0
  152. package/src/DashboardLayout/DashboardSidebarSubNavigation.tsx +301 -0
  153. package/src/DashboardLayout/ThemeSwitcher.tsx +81 -0
  154. package/src/DashboardLayout/ToolbarActions.tsx +11 -0
  155. package/src/DashboardLayout/index.ts +4 -0
  156. package/src/DashboardLayout/utils.ts +23 -0
  157. package/src/PageContainer/PageContainer.test.tsx +194 -0
  158. package/src/PageContainer/PageContainer.tsx +201 -0
  159. package/src/PageContainer/PageContainerToolbar.test.tsx +11 -0
  160. package/src/PageContainer/PageContainerToolbar.tsx +31 -0
  161. package/src/PageContainer/index.ts +2 -0
  162. package/src/index.ts +15 -0
  163. package/src/nextjs/AppProvider.test.tsx +48 -0
  164. package/src/nextjs/AppProvider.tsx +18 -0
  165. package/src/nextjs/AppProviderNextApp.tsx +37 -0
  166. package/src/nextjs/AppProviderNextPages.tsx +52 -0
  167. package/src/nextjs/index.tsx +1 -0
  168. package/src/persistence/codec.tsx +81 -0
  169. package/src/persistence/index.ts +1 -0
  170. package/src/persistence/useStorageState.test.tsx +101 -0
  171. package/src/persistence/useStorageState.tsx +244 -0
  172. package/src/react-router-dom/AppProvider.test.tsx +17 -0
  173. package/src/react-router-dom/AppProvider.tsx +40 -0
  174. package/src/react-router-dom/index.tsx +1 -0
  175. package/src/shared/Link.tsx +37 -0
  176. package/src/shared/branding.ts +7 -0
  177. package/src/shared/components.tsx +46 -0
  178. package/src/shared/context.ts +21 -0
  179. package/src/shared/locales/LocaleContext.tsx +44 -0
  180. package/src/shared/locales/en.tsx +9 -0
  181. package/src/shared/navigation.tsx +197 -0
  182. package/src/useActivePage/index.ts +1 -0
  183. package/src/useActivePage/useActivePage.ts +66 -0
  184. package/src/useLocalStorageState/index.ts +1 -0
  185. package/src/useLocalStorageState/useLocalStorageState.tsx +23 -0
  186. package/src/useSessionStorageState/index.tsx +1 -0
  187. package/src/useSessionStorageState/useSessionStorageState.tsx +23 -0
  188. package/src/utils/collections.ts +125 -0
  189. package/src/utils/describeConformance.ts +14 -0
  190. package/src/utils/events.ts +81 -0
  191. package/src/utils/hooks/index.ts +2 -0
  192. package/src/utils/hooks/useBoolean.ts +12 -0
  193. package/src/utils/hooks/useDebounced.ts +39 -0
  194. package/src/utils/hooks/useDebouncedHandler.ts +71 -0
  195. package/src/utils/hooks/useLatest.ts +16 -0
  196. package/src/utils/hooks/usePageTitle.ts +14 -0
  197. package/src/utils/hooks/useSsr.ts +24 -0
  198. package/src/utils/hooks/useStorageState.ts +130 -0
  199. package/src/utils/warnOnce.ts +12 -0
  200. package/tsconfig.json +19 -0
  201. package/vite.config.ts +27 -0
  202. 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,6 @@
1
+ declare const TOOLPAD_CORE_DEFAULT_LOCALE_TEXT: {
2
+ signInLabel: string;
3
+ signOutLabel: string;
4
+ iconButtonAriaLabel: string;
5
+ };
6
+ export default TOOLPAD_CORE_DEFAULT_LOCALE_TEXT;
@@ -0,0 +1,8 @@
1
+ const TOOLPAD_CORE_DEFAULT_LOCALE_TEXT = {
2
+ // Account
3
+ signInLabel: "Sign In",
4
+ signOutLabel: "Sign Out",
5
+ // Account Preview
6
+ iconButtonAriaLabel: "Current User"
7
+ };
8
+ export default TOOLPAD_CORE_DEFAULT_LOCALE_TEXT;
@@ -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,2 @@
1
+ import { UseStorageState } from "../persistence";
2
+ export declare const useLocalStorageState: UseStorageState;
@@ -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,2 @@
1
+ import { UseStorageState } from "../persistence";
2
+ export declare const useSessionStorageState: UseStorageState;
@@ -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,3 @@
1
+ export default function describeConformance(minimalElement, getOptions) {
2
+ getOptions();
3
+ }
@@ -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,2 @@
1
+ export { default as useBoolean } from "./useBoolean";
2
+ export { default as usePageTitle } from "./usePageTitle";
@@ -0,0 +1,2 @@
1
+ export { default as useBoolean } from "./useBoolean";
2
+ export { default as usePageTitle } from "./usePageTitle";
@@ -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,6 @@
1
+ /**
2
+ * Returns the latest non-null, non-undefined value that has been passed to it.
3
+ */
4
+ declare function useLatest<T>(value: T): T;
5
+ declare function useLatest<T>(value: T | null | undefined): T | null | undefined;
6
+ export default useLatest;
@@ -0,0 +1,9 @@
1
+ import * as React from "react";
2
+ function useLatest(value) {
3
+ const [latest, setLatest] = React.useState(value);
4
+ if (latest !== value && value !== null && value !== undefined) {
5
+ setLatest(value);
6
+ }
7
+ return value ?? latest;
8
+ }
9
+ export default useLatest;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Sets the current document title.
3
+ */
4
+ export default function usePageTitle(title: string): void;
@@ -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
+ }