@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,36 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { describe, test, expect, vi } from "vitest";
4
+ import { render, screen } from "@testing-library/react";
5
+ import { AppProvider } from "./AppProvider";
6
+ vi.mock("next/navigation", () => {
7
+ const searchParams = new URLSearchParams();
8
+ const push = () => { };
9
+ const replace = () => { };
10
+ const router = { push, replace };
11
+ return {
12
+ usePathname: () => "/",
13
+ useSearchParams: () => searchParams,
14
+ useRouter: () => router
15
+ };
16
+ });
17
+ vi.mock("next/router", () => ({ useRouter: () => null }));
18
+ vi.mock("next/compat/router", () => ({ useRouter: () => null }));
19
+ function RouterTest({ children }) {
20
+ const [pathname, setPathname] = React.useState("/page");
21
+ const router = React.useMemo(() => {
22
+ return {
23
+ pathname,
24
+ searchParams: new URLSearchParams(),
25
+ navigate: (path) => setPathname(String(path))
26
+ };
27
+ }, [pathname]);
28
+ return _jsx(AppProvider, { router: router, children: children });
29
+ }
30
+ describe("Nextjs AppProvider", () => {
31
+ test("renders content correctly", async () => {
32
+ // placeholder test
33
+ render(_jsx(RouterTest, { children: "Hello" }));
34
+ expect(screen.getByText("Hello")).toBeTruthy();
35
+ });
36
+ });
@@ -0,0 +1,5 @@
1
+ import type { AppProviderProps } from "../AppProvider";
2
+ /**
3
+ * @ignore - internal component.
4
+ */
5
+ export declare function AppProviderNextApp(props: AppProviderProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { usePathname, useSearchParams, useRouter } from "next/navigation";
4
+ import { AppProvider } from "../AppProvider/AppProviderComponent";
5
+ /**
6
+ * @ignore - internal component.
7
+ */
8
+ export function AppProviderNextApp(props) {
9
+ const pathname = usePathname();
10
+ const searchParams = useSearchParams();
11
+ const { push, replace } = useRouter();
12
+ const navigate = React.useCallback((url, { history = "auto" } = {}) => {
13
+ if (history === "auto" || history === "push") {
14
+ return push(String(url));
15
+ }
16
+ if (history === "replace") {
17
+ return replace(String(url));
18
+ }
19
+ throw new Error(`Invalid history option: ${history}`);
20
+ }, [push, replace]);
21
+ const routerImpl = React.useMemo(() => ({
22
+ pathname,
23
+ searchParams,
24
+ navigate
25
+ }), [pathname, navigate, searchParams]);
26
+ return _jsx(AppProvider, { router: routerImpl, ...props });
27
+ }
@@ -0,0 +1,5 @@
1
+ import type { AppProviderProps } from "../AppProvider";
2
+ /**
3
+ * @ignore - internal component.
4
+ */
5
+ export declare function AppProviderNextPages(props: AppProviderProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { useRouter } from "next/router";
4
+ import { asArray } from "../utils/collections";
5
+ import { AppProvider } from "../AppProvider/AppProviderComponent";
6
+ /**
7
+ * @ignore - internal component.
8
+ */
9
+ export function AppProviderNextPages(props) {
10
+ const { push, replace, asPath, query } = useRouter();
11
+ const search = React.useMemo(() => {
12
+ const params = new URLSearchParams();
13
+ Object.entries(query ?? {}).forEach(([key, value]) => {
14
+ asArray(value ?? []).forEach((v) => {
15
+ params.append(key, v);
16
+ });
17
+ });
18
+ return params.toString();
19
+ }, [query]);
20
+ // Stable search params object
21
+ const searchParams = React.useMemo(() => new URLSearchParams(search), [search]);
22
+ const navigate = React.useCallback((url, { history = "auto" } = {}) => {
23
+ if (history === "auto" || history === "push") {
24
+ return push(String(url));
25
+ }
26
+ if (history === "replace") {
27
+ return replace(String(url));
28
+ }
29
+ throw new Error(`Invalid history option: ${history}`);
30
+ }, [push, replace]);
31
+ const routerImpl = React.useMemo(() => ({
32
+ pathname: asPath,
33
+ searchParams,
34
+ navigate
35
+ }), [asPath, navigate, searchParams]);
36
+ return _jsx(AppProvider, { router: routerImpl, ...props });
37
+ }
@@ -0,0 +1 @@
1
+ export * from "./AppProvider";
@@ -0,0 +1 @@
1
+ export * from "./AppProvider";
@@ -0,0 +1,47 @@
1
+ /**
2
+ * A codec that can encode and decode values of type V to and from strings.
3
+ * @typeParam V The type of values that can be encoded and decoded.
4
+ */
5
+ export interface Codec<V> {
6
+ /**
7
+ * Decodes a string value into a value of type V.
8
+ * @param value The value to decode.
9
+ * @returns The decoded value.
10
+ */
11
+ parse: (value: string) => V;
12
+ /**
13
+ * Encodes a value of type V into a string.
14
+ * @param value The value to encode.
15
+ * @returns The encoded value.
16
+ */
17
+ stringify: (value: V) => string;
18
+ }
19
+ /**
20
+ * A codec that can encode and decode Date objects to and from strings.
21
+ */
22
+ export declare const CODEC_DATE: Codec<Date>;
23
+ /**
24
+ * A codec that can encode and decode Date objects to and from strings, but only the date part.
25
+ */
26
+ export declare const CODEC_DATE_ONLY: Codec<Date>;
27
+ /**
28
+ * A codec that can encode and decode numbers to and from strings.
29
+ */
30
+ export declare const CODEC_NUMBER: Codec<number>;
31
+ /**
32
+ * A codec that can encode and decode boolean values to and from strings.
33
+ */
34
+ export declare const CODE_BOOLEAN: Codec<boolean>;
35
+ /**
36
+ * A codec that can encode and decode JSON values to and from strings.
37
+ */
38
+ export declare const CODEC_JSON: Codec<unknown>;
39
+ /**
40
+ * A codec that can encode and decode JSON values to and from strings.
41
+ * If the JSON value is invalid, parsing will fail.
42
+ */
43
+ export declare const CODEC_JSON_STRICT: Codec<unknown>;
44
+ /**
45
+ * A codec that can encode and decode strings to and from strings.
46
+ */
47
+ export declare const CODEC_STRING: Codec<string>;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * A codec that can encode and decode Date objects to and from strings.
3
+ */
4
+ export const CODEC_DATE = {
5
+ parse: (value) => new Date(value),
6
+ stringify: (value) => value.toISOString()
7
+ };
8
+ /**
9
+ * A codec that can encode and decode Date objects to and from strings, but only the date part.
10
+ */
11
+ export const CODEC_DATE_ONLY = {
12
+ parse: (value) => new Date(value),
13
+ stringify: (value) => value.toISOString().split("T")[0]
14
+ };
15
+ /**
16
+ * A codec that can encode and decode numbers to and from strings.
17
+ */
18
+ export const CODEC_NUMBER = {
19
+ parse: (value) => Number(value),
20
+ stringify: (value) => String(value)
21
+ };
22
+ /**
23
+ * A codec that can encode and decode boolean values to and from strings.
24
+ */
25
+ export const CODE_BOOLEAN = {
26
+ parse: (value) => value === "true",
27
+ stringify: (value) => String(value)
28
+ };
29
+ /**
30
+ * A codec that can encode and decode JSON values to and from strings.
31
+ */
32
+ export const CODEC_JSON = {
33
+ parse: (value) => {
34
+ try {
35
+ return JSON.parse(value);
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ },
41
+ stringify: (value) => JSON.stringify(value)
42
+ };
43
+ /**
44
+ * A codec that can encode and decode JSON values to and from strings.
45
+ * If the JSON value is invalid, parsing will fail.
46
+ */
47
+ export const CODEC_JSON_STRICT = {
48
+ parse: (value) => JSON.parse(value),
49
+ stringify: (value) => JSON.stringify(value)
50
+ };
51
+ /**
52
+ * A codec that can encode and decode strings to and from strings.
53
+ */
54
+ export const CODEC_STRING = {
55
+ parse: (value) => value,
56
+ stringify: (value) => value
57
+ };
@@ -0,0 +1 @@
1
+ export * from "./useStorageState";
@@ -0,0 +1 @@
1
+ export * from "./useStorageState";
@@ -0,0 +1,50 @@
1
+ import * as React from "react";
2
+ import { Codec } from "./codec";
3
+ export type StorageStateInitializer<T> = () => T | null;
4
+ export type UseStorageStateHookResult<T> = [
5
+ T | null,
6
+ React.Dispatch<React.SetStateAction<T | null>>
7
+ ];
8
+ export declare function useStorageStateServer<T = string>(): UseStorageStateHookResult<T>;
9
+ export interface DefaultStorageStateoptions<T = string> {
10
+ codec?: Codec<T>;
11
+ }
12
+ export interface StorageStateOptions<T> extends DefaultStorageStateoptions<T> {
13
+ codec: Codec<T>;
14
+ }
15
+ /**
16
+ * Sync state to local storage so that it persists through a page refresh. Usage is
17
+ * similar to useState except we pass in a storage key so that we can default
18
+ * to that value on page load instead of the specified initial value.
19
+ *
20
+ * Since the storage API isn't available in server-rendering environments, we
21
+ * return null during SSR and hydration.
22
+ */
23
+ export declare function useStorageState(area: Storage, key: string | null, initializer?: string | null | StorageStateInitializer<string>, options?: DefaultStorageStateoptions): UseStorageStateHookResult<string>;
24
+ export declare function useStorageState<T>(area: Storage, key: string | null, initializer: T | null | StorageStateInitializer<T>, options: StorageStateOptions<T>): UseStorageStateHookResult<T>;
25
+ export interface UseStorageState {
26
+ /**
27
+ * Sync state to local or session storage so that it persists through a page refresh. Usage is
28
+ * similar to useState except we pass in a storage key that uniquely identifies the value.
29
+ * @param key The key to use for storing the value in local or session storage.
30
+ * @param initializer The initial value to use if the key is not present in storage.
31
+ * @param options Additional options for the storage state.
32
+ */
33
+ (key: string | null, initializer?: string | null | StorageStateInitializer<string>, options?: DefaultStorageStateoptions): UseStorageStateHookResult<string>;
34
+ /**
35
+ * Sync state to local or session storage so that it persists through a page refresh. Usage is
36
+ * similar to useState except we pass in a storage key that uniquely identifies the value.
37
+ * @param key The key to use for storing the value in local or session storage.
38
+ * @param initializer The initial value to use if the key is not present in storage.
39
+ * @param options Additional options for the storage state.
40
+ */
41
+ <T>(key: string | null, initializer: T | null | StorageStateInitializer<T>, options: StorageStateOptions<T>): UseStorageStateHookResult<T>;
42
+ /**
43
+ * Sync state to local or session storage so that it persists through a page refresh. Usage is
44
+ * similar to useState except we pass in a storage key that uniquely identifies the value.
45
+ * @param key The key to use for storing the value in local or session storage.
46
+ * @param initializer The initial value to use if the key is not present in storage.
47
+ * @param options Additional options for the storage state.
48
+ */
49
+ <T>(key: string | null, initializer?: T | null | StorageStateInitializer<T>, options?: StorageStateOptions<T>): UseStorageStateHookResult<T>;
50
+ }
@@ -0,0 +1,116 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ import { CODEC_STRING } from "./codec";
4
+ // storage events only work across tabs, we'll use an event emitter to announce within the current tab
5
+ const currentTabChangeListeners = new Map();
6
+ function onCurrentTabStorageChange(key, handler) {
7
+ let listeners = currentTabChangeListeners.get(key);
8
+ if (!listeners) {
9
+ listeners = new Set();
10
+ currentTabChangeListeners.set(key, listeners);
11
+ }
12
+ listeners.add(handler);
13
+ }
14
+ function offCurrentTabStorageChange(key, handler) {
15
+ const listeners = currentTabChangeListeners.get(key);
16
+ if (!listeners) {
17
+ return;
18
+ }
19
+ listeners.delete(handler);
20
+ if (listeners.size === 0) {
21
+ currentTabChangeListeners.delete(key);
22
+ }
23
+ }
24
+ function emitCurrentTabStorageChange(key) {
25
+ const listeners = currentTabChangeListeners.get(key);
26
+ if (listeners) {
27
+ listeners.forEach((listener) => listener());
28
+ }
29
+ }
30
+ if (typeof window !== "undefined") {
31
+ const origSetItem = window.localStorage.setItem;
32
+ window.localStorage.setItem = function setItem(key, value) {
33
+ const result = origSetItem.call(this, key, value);
34
+ emitCurrentTabStorageChange(key);
35
+ return result;
36
+ };
37
+ }
38
+ function subscribe(area, key, callback) {
39
+ if (!key) {
40
+ return () => { };
41
+ }
42
+ const storageHandler = (event) => {
43
+ if (event.storageArea === area && event.key === key) {
44
+ callback();
45
+ }
46
+ };
47
+ window.addEventListener("storage", storageHandler);
48
+ onCurrentTabStorageChange(key, callback);
49
+ return () => {
50
+ window.removeEventListener("storage", storageHandler);
51
+ offCurrentTabStorageChange(key, callback);
52
+ };
53
+ }
54
+ function getSnapshot(area, key) {
55
+ if (!key) {
56
+ return null;
57
+ }
58
+ try {
59
+ return area.getItem(key);
60
+ }
61
+ catch {
62
+ // ignore
63
+ // See https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API#feature-detecting_localstorage
64
+ return null;
65
+ }
66
+ }
67
+ function setValue(area, key, value) {
68
+ if (!key) {
69
+ return;
70
+ }
71
+ try {
72
+ if (value === null) {
73
+ area.removeItem(key);
74
+ }
75
+ else {
76
+ area.setItem(key, String(value));
77
+ }
78
+ }
79
+ catch {
80
+ // ignore
81
+ // See https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API#feature-detecting_localstorage
82
+ return;
83
+ }
84
+ emitCurrentTabStorageChange(key);
85
+ }
86
+ const serverValue = [null, () => { }];
87
+ export function useStorageStateServer() {
88
+ return serverValue;
89
+ }
90
+ function encode(codec, value) {
91
+ return value === null ? null : codec.stringify(value);
92
+ }
93
+ function decode(codec, value) {
94
+ return value === null ? null : codec.parse(value);
95
+ }
96
+ // Start with null for the hydration, and then switch to the actual value.
97
+ const getKeyServerSnapshot = () => null;
98
+ export function useStorageState(area, key, initializer = null, options) {
99
+ const codec = (options?.codec ?? CODEC_STRING);
100
+ const [initialValue] = React.useState(initializer);
101
+ const encodedInitialValue = React.useMemo(() => encode(codec, initialValue), [codec, initialValue]);
102
+ const subscribeKey = React.useCallback((callback) => subscribe(area, key, callback), [area, key]);
103
+ const getKeySnapshot = React.useCallback(() => getSnapshot(area, key) ?? encodedInitialValue, [area, encodedInitialValue, key]);
104
+ const encodedStoredValue = React.useSyncExternalStore(subscribeKey, getKeySnapshot, getKeyServerSnapshot);
105
+ const storedValue = React.useMemo(() => decode(codec, encodedStoredValue), [codec, encodedStoredValue]);
106
+ const setStoredValue = React.useCallback((value) => {
107
+ const valueToStore = value instanceof Function ? value(storedValue) : value;
108
+ const encodedValueToStore = encode(codec, valueToStore);
109
+ setValue(area, key, encodedValueToStore);
110
+ }, [area, codec, storedValue, key]);
111
+ const [nonStoredValue, setNonStoredValue] = React.useState(initialValue);
112
+ if (!key) {
113
+ return [nonStoredValue, setNonStoredValue];
114
+ }
115
+ return [storedValue, setStoredValue];
116
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,61 @@
1
+ import { describe, test, expect, beforeEach } from "vitest";
2
+ import { act, renderHook } from "@testing-library/react";
3
+ import { useStorageState } from "./useStorageState";
4
+ describe("useStorageState", () => {
5
+ beforeEach(() => {
6
+ window.localStorage.clear();
7
+ });
8
+ test("can do basic local storage with initial value", async () => {
9
+ const { result, rerender } = renderHook(() => useStorageState(window.localStorage, "foo", "bar"));
10
+ expect(result.current[0]).toBe("bar");
11
+ act(() => {
12
+ result.current[1]("baz");
13
+ });
14
+ rerender();
15
+ expect(result.current[0]).toBe("baz");
16
+ });
17
+ test("can do basic local storage without initial value", async () => {
18
+ const { result, rerender } = renderHook(() => useStorageState(window.localStorage, "foo"));
19
+ expect(result.current[0]).toBe(null);
20
+ act(() => {
21
+ result.current[1]("baz");
22
+ });
23
+ rerender();
24
+ expect(result.current[0]).toBe("baz");
25
+ });
26
+ test("can clear storage value, and reset to intiial value", async () => {
27
+ const { result, rerender } = renderHook(() => useStorageState(window.localStorage, "foo", "bar"));
28
+ act(() => {
29
+ result.current[1]("baz");
30
+ });
31
+ rerender();
32
+ expect(result.current[0]).toBe("baz");
33
+ act(() => {
34
+ result.current[1](null);
35
+ });
36
+ rerender();
37
+ expect(result.current[0]).toBe("bar");
38
+ });
39
+ test("can clear storage value", async () => {
40
+ const { result, rerender } = renderHook(() => useStorageState(window.localStorage, "foo"));
41
+ act(() => {
42
+ result.current[1]("baz");
43
+ });
44
+ rerender();
45
+ expect(result.current[0]).toBe("baz");
46
+ act(() => {
47
+ result.current[1](null);
48
+ });
49
+ rerender();
50
+ expect(result.current[0]).toBe(null);
51
+ });
52
+ test("can handle complex types", async () => {
53
+ const { result, rerender } = renderHook(() => useStorageState(window.localStorage, "foo", { a: 1 }, { codec: JSON }));
54
+ expect(result.current[0]).toEqual({ a: 1 });
55
+ act(() => {
56
+ result.current[1]({ b: 2 });
57
+ });
58
+ rerender();
59
+ expect(result.current[0]).toEqual({ b: 2 });
60
+ });
61
+ });
@@ -0,0 +1,6 @@
1
+ import { type AppProviderProps } from "../AppProvider";
2
+ /**
3
+ * @ignore - internal component.
4
+ */
5
+ declare function AppProvider(props: AppProviderProps): import("react/jsx-runtime").JSX.Element;
6
+ export { AppProvider };
@@ -0,0 +1,29 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { useSearchParams, useLocation, useNavigate } from "react-router-dom";
5
+ import { AppProvider as AppProviderComponent } from "../AppProvider/AppProviderComponent";
6
+ /**
7
+ * @ignore - internal component.
8
+ */
9
+ function AppProvider(props) {
10
+ const { pathname } = useLocation();
11
+ const [searchParams] = useSearchParams();
12
+ const navigate = useNavigate();
13
+ const navigateImpl = React.useCallback((url, { history = "auto" } = {}) => {
14
+ if (history === "auto" || history === "push") {
15
+ return navigate(url);
16
+ }
17
+ if (history === "replace") {
18
+ return navigate(url, { replace: true });
19
+ }
20
+ throw new Error(`Invalid history option: ${history}`);
21
+ }, [navigate]);
22
+ const routerImpl = React.useMemo(() => ({
23
+ pathname,
24
+ searchParams,
25
+ navigate: navigateImpl
26
+ }), [pathname, searchParams, navigateImpl]);
27
+ return _jsx(AppProviderComponent, { router: routerImpl, ...props });
28
+ }
29
+ export { AppProvider };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { describe, test, expect } from "vitest";
3
+ import { render, screen } from "@testing-library/react";
4
+ import { BrowserRouter } from "react-router-dom";
5
+ import { AppProvider } from "./AppProvider";
6
+ describe("React Router AppProvider", () => {
7
+ test("renders content correctly", async () => {
8
+ // placeholder test
9
+ render(_jsx(BrowserRouter, { children: _jsx(AppProvider, { children: "Hello" }) }));
10
+ expect(screen.getByText("Hello")).toBeTruthy();
11
+ });
12
+ });
@@ -0,0 +1 @@
1
+ export * from "./AppProvider";
@@ -0,0 +1 @@
1
+ export * from "./AppProvider";
@@ -0,0 +1,8 @@
1
+ import * as React from "react";
2
+ /**
3
+ * @ignore - internal component.
4
+ */
5
+ export interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
6
+ history?: "auto" | "push" | "replace";
7
+ }
8
+ export declare const Link: React.ForwardRefExoticComponent<LinkProps & React.RefAttributes<HTMLAnchorElement>>;
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { RouterContext } from "./context";
4
+ export const Link = React.forwardRef(function Link(props, ref) {
5
+ const { children, href, onClick, history, ...rest } = props;
6
+ const routerContext = React.useContext(RouterContext);
7
+ const handleLinkClick = React.useMemo(() => {
8
+ if (!routerContext) {
9
+ return onClick;
10
+ }
11
+ return (event) => {
12
+ event.preventDefault();
13
+ const url = new URL(event.currentTarget.href);
14
+ routerContext.navigate(url.pathname, { history });
15
+ onClick?.(event);
16
+ };
17
+ }, [routerContext, onClick, history]);
18
+ return (_jsx("a", { ref: ref, href: href, ...rest, onClick: handleLinkClick, children: children }));
19
+ });
@@ -0,0 +1 @@
1
+ export declare function useApplicationTitle(): string;
@@ -0,0 +1,6 @@
1
+ import * as React from "react";
2
+ import { BrandingContext } from "./context";
3
+ export function useApplicationTitle() {
4
+ const branding = React.useContext(BrandingContext);
5
+ return branding?.title ?? "Toolpad";
6
+ }
@@ -0,0 +1,5 @@
1
+ export interface ErrorOverlayProps {
2
+ error?: unknown;
3
+ }
4
+ export declare function ErrorOverlay({ error }: ErrorOverlayProps): import("react/jsx-runtime").JSX.Element;
5
+ export declare function LoadingOverlay(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CircularProgress, Typography, styled } from "@mui/material";
3
+ import ErrorIcon from "@mui/icons-material/Error";
4
+ const OverlayRoot = styled("div")(({ theme }) => ({
5
+ position: "absolute",
6
+ inset: "0 0 0 0",
7
+ display: "flex",
8
+ flexDirection: "column",
9
+ alignItems: "center",
10
+ justifyContent: "center",
11
+ padding: theme.spacing(2)
12
+ }));
13
+ export function ErrorOverlay({ error }) {
14
+ return (_jsxs(OverlayRoot, { children: [_jsxs(Typography, { variant: "h6", sx: {
15
+ display: "flex",
16
+ flexDirection: "row",
17
+ gap: 1,
18
+ alignItems: "center"
19
+ }, children: [_jsx(ErrorIcon, { color: "error" }), " Error"] }), _jsx(Typography, { textAlign: "center", children: error?.message ?? "Unknown error" })] }));
20
+ }
21
+ export function LoadingOverlay() {
22
+ return (_jsx(OverlayRoot, { children: _jsx(CircularProgress, {}) }));
23
+ }
@@ -0,0 +1,12 @@
1
+ import * as React from "react";
2
+ import type { PaletteMode } from "@mui/material";
3
+ import type { Branding, Navigation, Router } from "../AppProvider";
4
+ export declare const BrandingContext: React.Context<Branding | null>;
5
+ export declare const NavigationContext: React.Context<Navigation>;
6
+ export declare const PaletteModeContext: React.Context<{
7
+ paletteMode: PaletteMode;
8
+ setPaletteMode: (mode: PaletteMode) => void;
9
+ isDualTheme: boolean;
10
+ }>;
11
+ export declare const RouterContext: React.Context<Router | null>;
12
+ export declare const WindowContext: React.Context<Window | undefined>;
@@ -0,0 +1,10 @@
1
+ import * as React from "react";
2
+ export const BrandingContext = React.createContext(null);
3
+ export const NavigationContext = React.createContext([]);
4
+ export const PaletteModeContext = React.createContext({
5
+ paletteMode: "light",
6
+ setPaletteMode: () => { },
7
+ isDualTheme: false
8
+ });
9
+ export const RouterContext = React.createContext(null);
10
+ export const WindowContext = React.createContext(undefined);
@@ -0,0 +1,19 @@
1
+ import * as React from "react";
2
+ export type LocaleContextType = {
3
+ signInLabel?: string;
4
+ signOutLabel?: string;
5
+ iconButtonAriaLabel?: string;
6
+ };
7
+ export declare const LocaleContext: React.Context<LocaleContextType>;
8
+ export interface LocaleProviderProps {
9
+ localeText?: Partial<LocaleContextType>;
10
+ children: React.ReactNode;
11
+ }
12
+ /**
13
+ * @ignore - internal component.
14
+ */
15
+ export declare function LocaleProvider({ localeText, children }: LocaleProviderProps): import("react/jsx-runtime").JSX.Element;
16
+ /**
17
+ * @ignore - internal hook.
18
+ */
19
+ export declare function useLocaleText(): LocaleContextType;