@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,201 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import PropTypes from "prop-types";
|
|
4
|
+
import Breadcrumbs from "@mui/material/Breadcrumbs";
|
|
5
|
+
import Container, { ContainerProps } from "@mui/material/Container";
|
|
6
|
+
import Link from "@mui/material/Link";
|
|
7
|
+
import Stack from "@mui/material/Stack";
|
|
8
|
+
import Typography from "@mui/material/Typography";
|
|
9
|
+
import useSlotProps from "@mui/utils/useSlotProps";
|
|
10
|
+
import { styled } from "@mui/material";
|
|
11
|
+
import { Link as ToolpadLink } from "../shared/Link";
|
|
12
|
+
import {
|
|
13
|
+
PageContainerToolbar,
|
|
14
|
+
PageContainerToolbarProps
|
|
15
|
+
} from "./PageContainerToolbar";
|
|
16
|
+
import { getItemTitle } from "../shared/navigation";
|
|
17
|
+
import { useActivePage } from "../useActivePage";
|
|
18
|
+
import warnOnce from "../utils/warnOnce";
|
|
19
|
+
|
|
20
|
+
const PageContentHeader = styled("div")(({ theme }) => ({
|
|
21
|
+
display: "flex",
|
|
22
|
+
flexDirection: "row",
|
|
23
|
+
justifyContent: "space-between",
|
|
24
|
+
gap: theme.spacing(2)
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
export interface PageContainerSlotProps {
|
|
28
|
+
toolbar: PageContainerToolbarProps;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface PageContainerSlots {
|
|
32
|
+
/**
|
|
33
|
+
* The component that renders the actions toolbar.
|
|
34
|
+
* @default Snackbar
|
|
35
|
+
*/
|
|
36
|
+
toolbar: React.ElementType;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface Breadcrumb {
|
|
40
|
+
/**
|
|
41
|
+
* The title of the breadcrumb segment.
|
|
42
|
+
*/
|
|
43
|
+
title: string;
|
|
44
|
+
/**
|
|
45
|
+
* The path the breadcrumb links to.
|
|
46
|
+
*/
|
|
47
|
+
path: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// TODO: Remove in the next major version
|
|
51
|
+
/**
|
|
52
|
+
* @deprecated Use `Breadcrumb` instead.
|
|
53
|
+
*/
|
|
54
|
+
export type BreadCrumb = Breadcrumb;
|
|
55
|
+
|
|
56
|
+
export interface PageContainerProps extends ContainerProps {
|
|
57
|
+
children?: React.ReactNode;
|
|
58
|
+
/**
|
|
59
|
+
* The title of the page. Leave blank to use the active page title.
|
|
60
|
+
*/
|
|
61
|
+
title?: string;
|
|
62
|
+
/**
|
|
63
|
+
* The breadcrumbs of the page. Leave blank to use the active page breadcrumbs.
|
|
64
|
+
*/
|
|
65
|
+
breadcrumbs?: Breadcrumb[];
|
|
66
|
+
// TODO: Remove in the next major version
|
|
67
|
+
/**
|
|
68
|
+
* @deprecated Use `breadcrumbs` instead.
|
|
69
|
+
*/
|
|
70
|
+
breadCrumbs?: Breadcrumb[];
|
|
71
|
+
/**
|
|
72
|
+
* The components used for each slot inside.
|
|
73
|
+
*/
|
|
74
|
+
slots?: PageContainerSlots;
|
|
75
|
+
/**
|
|
76
|
+
* The props used for each slot inside.
|
|
77
|
+
*/
|
|
78
|
+
slotProps?: PageContainerSlotProps;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* A container component to provide a title and breadcrumbs for your pages.
|
|
83
|
+
*
|
|
84
|
+
* Demos:
|
|
85
|
+
*
|
|
86
|
+
* - [Page Container](https://mui.com/toolpad/core/react-page-container/)
|
|
87
|
+
*
|
|
88
|
+
* API:
|
|
89
|
+
*
|
|
90
|
+
* - [PageContainer API](https://mui.com/toolpad/core/api/page-container)
|
|
91
|
+
*/
|
|
92
|
+
function PageContainer(props: PageContainerProps) {
|
|
93
|
+
const { children, slots, slotProps, breadcrumbs, breadCrumbs, ...rest } =
|
|
94
|
+
props;
|
|
95
|
+
|
|
96
|
+
if (process.env.NODE_ENV !== "production" && breadCrumbs) {
|
|
97
|
+
warnOnce(
|
|
98
|
+
"The PageContainer `breadCrumbs` prop is deprecated. Use `breadcrumbs` instead."
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const activePage = useActivePage();
|
|
103
|
+
|
|
104
|
+
// TODO: Remove `props.breadCrumbs` in the next major version
|
|
105
|
+
const resolvedBreadcrumbs =
|
|
106
|
+
breadcrumbs ?? breadCrumbs ?? activePage?.breadcrumbs ?? [];
|
|
107
|
+
const title = props.title ?? activePage?.title ?? "";
|
|
108
|
+
|
|
109
|
+
const ToolbarComponent = props?.slots?.toolbar ?? PageContainerToolbar;
|
|
110
|
+
const toolbarSlotProps = useSlotProps({
|
|
111
|
+
elementType: ToolbarComponent,
|
|
112
|
+
ownerState: props,
|
|
113
|
+
externalSlotProps: props?.slotProps?.toolbar,
|
|
114
|
+
additionalProps: {}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<Container {...rest}>
|
|
119
|
+
<Stack sx={{ my: 2 }} spacing={2}>
|
|
120
|
+
<Stack>
|
|
121
|
+
<Breadcrumbs aria-label="breadcrumb">
|
|
122
|
+
{resolvedBreadcrumbs
|
|
123
|
+
? resolvedBreadcrumbs.map((item, index) => {
|
|
124
|
+
return index < resolvedBreadcrumbs.length - 1 ? (
|
|
125
|
+
<Link
|
|
126
|
+
key={item.path}
|
|
127
|
+
component={ToolpadLink}
|
|
128
|
+
underline="hover"
|
|
129
|
+
color="inherit"
|
|
130
|
+
href={item.path}
|
|
131
|
+
>
|
|
132
|
+
{getItemTitle(item)}
|
|
133
|
+
</Link>
|
|
134
|
+
) : (
|
|
135
|
+
<Typography key={item.path} color="text.primary">
|
|
136
|
+
{getItemTitle(item)}
|
|
137
|
+
</Typography>
|
|
138
|
+
);
|
|
139
|
+
})
|
|
140
|
+
: null}
|
|
141
|
+
</Breadcrumbs>
|
|
142
|
+
|
|
143
|
+
<PageContentHeader>
|
|
144
|
+
{title ? <Typography variant="h4">{title}</Typography> : null}
|
|
145
|
+
<ToolbarComponent {...toolbarSlotProps} />
|
|
146
|
+
</PageContentHeader>
|
|
147
|
+
</Stack>
|
|
148
|
+
<div>{children}</div>
|
|
149
|
+
</Stack>
|
|
150
|
+
</Container>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
PageContainer.propTypes /* remove-proptypes */ = {
|
|
155
|
+
// ┌────────────────────────────── Warning ──────────────────────────────┐
|
|
156
|
+
// │ These PropTypes are generated from the TypeScript type definitions. │
|
|
157
|
+
// │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
|
|
158
|
+
// └─────────────────────────────────────────────────────────────────────┘
|
|
159
|
+
/**
|
|
160
|
+
* The breadcrumbs of the page. Leave blank to use the active page breadcrumbs.
|
|
161
|
+
*/
|
|
162
|
+
breadcrumbs: PropTypes.arrayOf(
|
|
163
|
+
PropTypes.shape({
|
|
164
|
+
path: PropTypes.string.isRequired,
|
|
165
|
+
title: PropTypes.string.isRequired
|
|
166
|
+
})
|
|
167
|
+
),
|
|
168
|
+
/**
|
|
169
|
+
* @deprecated Use `breadcrumbs` instead.
|
|
170
|
+
*/
|
|
171
|
+
breadCrumbs: PropTypes.arrayOf(
|
|
172
|
+
PropTypes.shape({
|
|
173
|
+
path: PropTypes.string.isRequired,
|
|
174
|
+
title: PropTypes.string.isRequired
|
|
175
|
+
})
|
|
176
|
+
),
|
|
177
|
+
/**
|
|
178
|
+
* @ignore
|
|
179
|
+
*/
|
|
180
|
+
children: PropTypes.node,
|
|
181
|
+
/**
|
|
182
|
+
* The props used for each slot inside.
|
|
183
|
+
*/
|
|
184
|
+
slotProps: PropTypes.shape({
|
|
185
|
+
toolbar: PropTypes.shape({
|
|
186
|
+
children: PropTypes.node
|
|
187
|
+
}).isRequired
|
|
188
|
+
}),
|
|
189
|
+
/**
|
|
190
|
+
* The components used for each slot inside.
|
|
191
|
+
*/
|
|
192
|
+
slots: PropTypes.shape({
|
|
193
|
+
toolbar: PropTypes.elementType
|
|
194
|
+
}),
|
|
195
|
+
/**
|
|
196
|
+
* The title of the page. Leave blank to use the active page title.
|
|
197
|
+
*/
|
|
198
|
+
title: PropTypes.string
|
|
199
|
+
} as any;
|
|
200
|
+
|
|
201
|
+
export { PageContainer };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, test } from "vitest";
|
|
2
|
+
import { PageContainerToolbar } from "./PageContainerToolbar";
|
|
3
|
+
import describeConformance from "../utils/describeConformance";
|
|
4
|
+
|
|
5
|
+
describe("PageContainerToolbar", () => {
|
|
6
|
+
describeConformance(<PageContainerToolbar />, () => ({
|
|
7
|
+
skip: ["themeDefaultProps"]
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
test("dummy test", () => {});
|
|
11
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { styled } from "@mui/material";
|
|
4
|
+
|
|
5
|
+
const PageContainerToolbarRoot = styled("div")(({ theme }) => ({
|
|
6
|
+
display: "flex",
|
|
7
|
+
flexDirection: "row",
|
|
8
|
+
gap: theme.spacing(1),
|
|
9
|
+
// Ensure the toolbar is always on the right side, even after wrapping
|
|
10
|
+
marginLeft: "auto"
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
export interface PageContainerToolbarProps {
|
|
14
|
+
children?: React.ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
*
|
|
19
|
+
* Demos:
|
|
20
|
+
*
|
|
21
|
+
* - [Page Container](https://mui.com/toolpad/core/react-page-container/)
|
|
22
|
+
*
|
|
23
|
+
* API:
|
|
24
|
+
*
|
|
25
|
+
* - [PageContainerToolbar API](https://mui.com/toolpad/core/api/page-container-toolbar)
|
|
26
|
+
*/
|
|
27
|
+
function PageContainerToolbar(props: PageContainerToolbarProps) {
|
|
28
|
+
return <PageContainerToolbarRoot {...props} />;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { PageContainerToolbar };
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from "./AppProvider";
|
|
2
|
+
|
|
3
|
+
export * from "./DashboardLayout";
|
|
4
|
+
|
|
5
|
+
export * from "./Account";
|
|
6
|
+
|
|
7
|
+
export * from "./PageContainer";
|
|
8
|
+
|
|
9
|
+
export * from "./useActivePage";
|
|
10
|
+
|
|
11
|
+
export * from "./useLocalStorageState";
|
|
12
|
+
|
|
13
|
+
export * from "./useSessionStorageState";
|
|
14
|
+
|
|
15
|
+
export * from "./persistence/codec";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { describe, test, expect, vi } from "vitest";
|
|
3
|
+
import { render, screen } from "@testing-library/react";
|
|
4
|
+
import { AppProvider } from "./AppProvider";
|
|
5
|
+
import { Router } from "../AppProvider";
|
|
6
|
+
|
|
7
|
+
vi.mock("next/navigation", () => {
|
|
8
|
+
const searchParams = new URLSearchParams();
|
|
9
|
+
const push = () => {};
|
|
10
|
+
const replace = () => {};
|
|
11
|
+
const router = { push, replace };
|
|
12
|
+
return {
|
|
13
|
+
usePathname: () => "/",
|
|
14
|
+
useSearchParams: () => searchParams,
|
|
15
|
+
useRouter: () => router
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
vi.mock("next/router", () => ({ useRouter: () => null }));
|
|
20
|
+
|
|
21
|
+
vi.mock("next/compat/router", () => ({ useRouter: () => null }));
|
|
22
|
+
|
|
23
|
+
interface RouterTestProps {
|
|
24
|
+
children: React.ReactNode;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function RouterTest({ children }: RouterTestProps) {
|
|
28
|
+
const [pathname, setPathname] = React.useState("/page");
|
|
29
|
+
|
|
30
|
+
const router = React.useMemo<Router>(() => {
|
|
31
|
+
return {
|
|
32
|
+
pathname,
|
|
33
|
+
searchParams: new URLSearchParams(),
|
|
34
|
+
navigate: (path) => setPathname(String(path))
|
|
35
|
+
};
|
|
36
|
+
}, [pathname]);
|
|
37
|
+
|
|
38
|
+
return <AppProvider router={router}>{children}</AppProvider>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe("Nextjs AppProvider", () => {
|
|
42
|
+
test("renders content correctly", async () => {
|
|
43
|
+
// placeholder test
|
|
44
|
+
render(<RouterTest>Hello</RouterTest>);
|
|
45
|
+
|
|
46
|
+
expect(screen.getByText("Hello")).toBeTruthy();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useRouter } from "next/compat/router";
|
|
3
|
+
import { AppProviderNextApp } from "./AppProviderNextApp";
|
|
4
|
+
import { AppProviderNextPages } from "./AppProviderNextPages";
|
|
5
|
+
import type { AppProviderProps } from "../AppProvider";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @ignore - internal component.
|
|
9
|
+
*/
|
|
10
|
+
function AppProvider(props: AppProviderProps) {
|
|
11
|
+
const router = useRouter();
|
|
12
|
+
const AppProviderComponent = router
|
|
13
|
+
? AppProviderNextPages
|
|
14
|
+
: AppProviderNextApp;
|
|
15
|
+
return <AppProviderComponent {...props} />;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { AppProvider };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { usePathname, useSearchParams, useRouter } from "next/navigation";
|
|
3
|
+
import type { AppProviderProps, Navigate, Router } from "../AppProvider";
|
|
4
|
+
import { AppProvider } from "../AppProvider/AppProviderComponent";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @ignore - internal component.
|
|
8
|
+
*/
|
|
9
|
+
export function AppProviderNextApp(props: AppProviderProps) {
|
|
10
|
+
const pathname = usePathname();
|
|
11
|
+
const searchParams = useSearchParams();
|
|
12
|
+
const { push, replace } = useRouter();
|
|
13
|
+
|
|
14
|
+
const navigate = React.useCallback<Navigate>(
|
|
15
|
+
(url, { history = "auto" } = {}) => {
|
|
16
|
+
if (history === "auto" || history === "push") {
|
|
17
|
+
return push(String(url));
|
|
18
|
+
}
|
|
19
|
+
if (history === "replace") {
|
|
20
|
+
return replace(String(url));
|
|
21
|
+
}
|
|
22
|
+
throw new Error(`Invalid history option: ${history}`);
|
|
23
|
+
},
|
|
24
|
+
[push, replace]
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const routerImpl = React.useMemo<Router>(
|
|
28
|
+
() => ({
|
|
29
|
+
pathname,
|
|
30
|
+
searchParams,
|
|
31
|
+
navigate
|
|
32
|
+
}),
|
|
33
|
+
[pathname, navigate, searchParams]
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
return <AppProvider router={routerImpl} {...props} />;
|
|
37
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useRouter } from "next/router";
|
|
3
|
+
import type { AppProviderProps, Navigate, Router } from "../AppProvider";
|
|
4
|
+
import { asArray } from "../utils/collections";
|
|
5
|
+
import { AppProvider } from "../AppProvider/AppProviderComponent";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @ignore - internal component.
|
|
9
|
+
*/
|
|
10
|
+
export function AppProviderNextPages(props: AppProviderProps) {
|
|
11
|
+
const { push, replace, asPath, query } = useRouter();
|
|
12
|
+
|
|
13
|
+
const search = React.useMemo(() => {
|
|
14
|
+
const params = new URLSearchParams();
|
|
15
|
+
Object.entries(query ?? {}).forEach(([key, value]) => {
|
|
16
|
+
asArray(value ?? []).forEach((v) => {
|
|
17
|
+
params.append(key, v);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
return params.toString();
|
|
21
|
+
}, [query]);
|
|
22
|
+
|
|
23
|
+
// Stable search params object
|
|
24
|
+
const searchParams = React.useMemo(
|
|
25
|
+
() => new URLSearchParams(search),
|
|
26
|
+
[search]
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const navigate = React.useCallback<Navigate>(
|
|
30
|
+
(url, { history = "auto" } = {}) => {
|
|
31
|
+
if (history === "auto" || history === "push") {
|
|
32
|
+
return push(String(url));
|
|
33
|
+
}
|
|
34
|
+
if (history === "replace") {
|
|
35
|
+
return replace(String(url));
|
|
36
|
+
}
|
|
37
|
+
throw new Error(`Invalid history option: ${history}`);
|
|
38
|
+
},
|
|
39
|
+
[push, replace]
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const routerImpl = React.useMemo<Router>(
|
|
43
|
+
() => ({
|
|
44
|
+
pathname: asPath,
|
|
45
|
+
searchParams,
|
|
46
|
+
navigate
|
|
47
|
+
}),
|
|
48
|
+
[asPath, navigate, searchParams]
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return <AppProvider router={routerImpl} {...props} />;
|
|
52
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./AppProvider";
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
/**
|
|
21
|
+
* A codec that can encode and decode Date objects to and from strings.
|
|
22
|
+
*/
|
|
23
|
+
export const CODEC_DATE: Codec<Date> = {
|
|
24
|
+
parse: (value) => new Date(value),
|
|
25
|
+
stringify: (value) => value.toISOString()
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A codec that can encode and decode Date objects to and from strings, but only the date part.
|
|
30
|
+
*/
|
|
31
|
+
export const CODEC_DATE_ONLY: Codec<Date> = {
|
|
32
|
+
parse: (value) => new Date(value),
|
|
33
|
+
stringify: (value) => value.toISOString().split("T")[0]
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A codec that can encode and decode numbers to and from strings.
|
|
38
|
+
*/
|
|
39
|
+
export const CODEC_NUMBER: Codec<number> = {
|
|
40
|
+
parse: (value) => Number(value),
|
|
41
|
+
stringify: (value) => String(value)
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A codec that can encode and decode boolean values to and from strings.
|
|
46
|
+
*/
|
|
47
|
+
export const CODE_BOOLEAN: Codec<boolean> = {
|
|
48
|
+
parse: (value) => value === "true",
|
|
49
|
+
stringify: (value) => String(value)
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A codec that can encode and decode JSON values to and from strings.
|
|
54
|
+
*/
|
|
55
|
+
export const CODEC_JSON: Codec<unknown> = {
|
|
56
|
+
parse: (value) => {
|
|
57
|
+
try {
|
|
58
|
+
return JSON.parse(value);
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
stringify: (value) => JSON.stringify(value)
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A codec that can encode and decode JSON values to and from strings.
|
|
68
|
+
* If the JSON value is invalid, parsing will fail.
|
|
69
|
+
*/
|
|
70
|
+
export const CODEC_JSON_STRICT: Codec<unknown> = {
|
|
71
|
+
parse: (value) => JSON.parse(value),
|
|
72
|
+
stringify: (value) => JSON.stringify(value)
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* A codec that can encode and decode strings to and from strings.
|
|
77
|
+
*/
|
|
78
|
+
export const CODEC_STRING: Codec<string> = {
|
|
79
|
+
parse: (value) => value,
|
|
80
|
+
stringify: (value) => value
|
|
81
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useStorageState";
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from "vitest";
|
|
2
|
+
import { act, renderHook } from "@testing-library/react";
|
|
3
|
+
import { useStorageState } from "./useStorageState";
|
|
4
|
+
|
|
5
|
+
describe("useStorageState", () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
window.localStorage.clear();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("can do basic local storage with initial value", async () => {
|
|
11
|
+
const { result, rerender } = renderHook(() =>
|
|
12
|
+
useStorageState(window.localStorage, "foo", "bar")
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
expect(result.current[0]).toBe("bar");
|
|
16
|
+
|
|
17
|
+
act(() => {
|
|
18
|
+
result.current[1]("baz");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
rerender();
|
|
22
|
+
|
|
23
|
+
expect(result.current[0]).toBe("baz");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("can do basic local storage without initial value", async () => {
|
|
27
|
+
const { result, rerender } = renderHook(() =>
|
|
28
|
+
useStorageState(window.localStorage, "foo")
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
expect(result.current[0]).toBe(null);
|
|
32
|
+
|
|
33
|
+
act(() => {
|
|
34
|
+
result.current[1]("baz");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
rerender();
|
|
38
|
+
|
|
39
|
+
expect(result.current[0]).toBe("baz");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("can clear storage value, and reset to intiial value", async () => {
|
|
43
|
+
const { result, rerender } = renderHook(() =>
|
|
44
|
+
useStorageState(window.localStorage, "foo", "bar")
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
act(() => {
|
|
48
|
+
result.current[1]("baz");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
rerender();
|
|
52
|
+
|
|
53
|
+
expect(result.current[0]).toBe("baz");
|
|
54
|
+
|
|
55
|
+
act(() => {
|
|
56
|
+
result.current[1](null);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
rerender();
|
|
60
|
+
|
|
61
|
+
expect(result.current[0]).toBe("bar");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("can clear storage value", async () => {
|
|
65
|
+
const { result, rerender } = renderHook(() =>
|
|
66
|
+
useStorageState(window.localStorage, "foo")
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
act(() => {
|
|
70
|
+
result.current[1]("baz");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
rerender();
|
|
74
|
+
|
|
75
|
+
expect(result.current[0]).toBe("baz");
|
|
76
|
+
|
|
77
|
+
act(() => {
|
|
78
|
+
result.current[1](null);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
rerender();
|
|
82
|
+
|
|
83
|
+
expect(result.current[0]).toBe(null);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("can handle complex types", async () => {
|
|
87
|
+
const { result, rerender } = renderHook(() =>
|
|
88
|
+
useStorageState(window.localStorage, "foo", { a: 1 }, { codec: JSON })
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
expect(result.current[0]).toEqual({ a: 1 });
|
|
92
|
+
|
|
93
|
+
act(() => {
|
|
94
|
+
result.current[1]({ b: 2 });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
rerender();
|
|
98
|
+
|
|
99
|
+
expect(result.current[0]).toEqual({ b: 2 });
|
|
100
|
+
});
|
|
101
|
+
});
|