From 6c799924b99043560c5c50d63dac71b036840965 Mon Sep 17 00:00:00 2001
From: MUI bot <2109932+Janpot@users.noreply.github.com>
Date: Wed, 18 Sep 2024 18:44:11 +0200
Subject: [PATCH 01/14] Make PageContainer customizable
---
.../page-container/CustomPageContainer.js | 62 +++++++++++
.../page-container/CustomPageContainer.tsx | 66 ++++++++++++
.../CustomPageContainer.tsx.preview | 1 +
.../TitleBreadcrumbsPageContainer.js | 8 +-
.../TitleBreadcrumbsPageContainer.tsx | 8 +-
.../page-container/page-container.md | 7 +-
.../src/PageContainer/PageContainer.test.tsx | 33 ++++++
.../src/PageContainer/PageContainer.tsx | 97 +++--------------
packages/toolpad-core/src/index.ts | 2 +
packages/toolpad-core/src/internals/demo.tsx | 50 +++++++++
.../toolpad-core/src/shared/navigation.tsx | 102 +++++++++++++++++-
.../toolpad-core/src/useActivePage/index.ts | 1 +
.../src/useActivePage/useActivePage.ts | 54 ++++++++++
13 files changed, 393 insertions(+), 98 deletions(-)
create mode 100644 docs/data/toolpad/core/components/page-container/CustomPageContainer.js
create mode 100644 docs/data/toolpad/core/components/page-container/CustomPageContainer.tsx
create mode 100644 docs/data/toolpad/core/components/page-container/CustomPageContainer.tsx.preview
create mode 100644 packages/toolpad-core/src/useActivePage/index.ts
create mode 100644 packages/toolpad-core/src/useActivePage/useActivePage.ts
diff --git a/docs/data/toolpad/core/components/page-container/CustomPageContainer.js b/docs/data/toolpad/core/components/page-container/CustomPageContainer.js
new file mode 100644
index 00000000000..6b25087c47c
--- /dev/null
+++ b/docs/data/toolpad/core/components/page-container/CustomPageContainer.js
@@ -0,0 +1,62 @@
+import * as React from 'react';
+import { PageContainer } from '@toolpad/core/PageContainer';
+import { AppProvider } from '@toolpad/core/AppProvider';
+import { DemoBrowser, Link, useDemoRouter } from '@toolpad/core/internals';
+import { useActivePage } from '@toolpad/core/useActivePage';
+import { useTheme } from '@mui/material/styles';
+import Box from '@mui/material/Box';
+
+const NAVIGATION = [
+ {
+ segment: 'inbox',
+ title: 'Orders',
+ pattern: '/inbox/:id',
+ },
+];
+
+function Content({ router }) {
+ const id = Number(router.pathname.replace('/inbox/', ''));
+
+ const title = `Item ${id}`;
+
+ const activePage = useActivePage();
+ const breadCrumbs = [
+ ...(activePage?.breadCrumbs ?? []),
+ { title, path: `/inbox/${id}` },
+ ];
+
+ return (
+ // preview-start
+
+ {/* preview-end */}
+
+ previous
+ next
+
+
+ );
+}
+
+export default function CustomPageContainer() {
+ const router = useDemoRouter('/inbox/123');
+
+ const theme = useTheme();
+
+ let content = (
+
+ Item 123
+
+ );
+
+ if (router.pathname.startsWith('/inbox/')) {
+ content = ;
+ }
+
+ return (
+
+
+ {content}
+
+
+ );
+}
diff --git a/docs/data/toolpad/core/components/page-container/CustomPageContainer.tsx b/docs/data/toolpad/core/components/page-container/CustomPageContainer.tsx
new file mode 100644
index 00000000000..ab40fc0d3a4
--- /dev/null
+++ b/docs/data/toolpad/core/components/page-container/CustomPageContainer.tsx
@@ -0,0 +1,66 @@
+import * as React from 'react';
+import { PageContainer } from '@toolpad/core/PageContainer';
+import { AppProvider, Router } from '@toolpad/core/AppProvider';
+import { DemoBrowser, Link, useDemoRouter } from '@toolpad/core/internals';
+import { useActivePage } from '@toolpad/core/useActivePage';
+import { useTheme } from '@mui/material/styles';
+import Box from '@mui/material/Box';
+
+const NAVIGATION = [
+ {
+ segment: 'inbox',
+ title: 'Orders',
+ pattern: '/inbox/:id',
+ },
+];
+
+interface ContentProps {
+ router: Router;
+}
+
+function Content({ router }: ContentProps) {
+ const id = Number(router.pathname.replace('/inbox/', ''));
+
+ const title = `Item ${id}`;
+
+ const activePage = useActivePage();
+ const breadCrumbs = [
+ ...(activePage?.breadCrumbs ?? []),
+ { title, path: `/inbox/${id}` },
+ ];
+
+ return (
+ // preview-start
+
+ {/* preview-end */}
+
+ previous
+ next
+
+
+ );
+}
+
+export default function CustomPageContainer() {
+ const router = useDemoRouter('/inbox/123');
+
+ const theme = useTheme();
+
+ let content = (
+
+ Item 123
+
+ );
+
+ if (router.pathname.startsWith('/inbox/')) {
+ content = ;
+ }
+
+ return (
+
+
+ {content}
+
+
+ );
+}
diff --git a/docs/data/toolpad/core/components/page-container/CustomPageContainer.tsx.preview b/docs/data/toolpad/core/components/page-container/CustomPageContainer.tsx.preview
new file mode 100644
index 00000000000..06545ed2211
--- /dev/null
+++ b/docs/data/toolpad/core/components/page-container/CustomPageContainer.tsx.preview
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/data/toolpad/core/components/page-container/TitleBreadcrumbsPageContainer.js b/docs/data/toolpad/core/components/page-container/TitleBreadcrumbsPageContainer.js
index a90486b361e..f0604f19386 100644
--- a/docs/data/toolpad/core/components/page-container/TitleBreadcrumbsPageContainer.js
+++ b/docs/data/toolpad/core/components/page-container/TitleBreadcrumbsPageContainer.js
@@ -6,6 +6,7 @@ import { useTheme } from '@mui/material/styles';
import Paper from '@mui/material/Paper';
const NAVIGATION = [
+ { segment: '', title: 'ACME' },
{
segment: 'inbox',
title: 'Home',
@@ -24,12 +25,7 @@ export default function TitleBreadcrumbsPageContainer() {
const theme = useTheme();
return (
-
+
diff --git a/docs/data/toolpad/core/components/page-container/TitleBreadcrumbsPageContainer.tsx b/docs/data/toolpad/core/components/page-container/TitleBreadcrumbsPageContainer.tsx
index a90486b361e..f0604f19386 100644
--- a/docs/data/toolpad/core/components/page-container/TitleBreadcrumbsPageContainer.tsx
+++ b/docs/data/toolpad/core/components/page-container/TitleBreadcrumbsPageContainer.tsx
@@ -6,6 +6,7 @@ import { useTheme } from '@mui/material/styles';
import Paper from '@mui/material/Paper';
const NAVIGATION = [
+ { segment: '', title: 'ACME' },
{
segment: 'inbox',
title: 'Home',
@@ -24,12 +25,7 @@ export default function TitleBreadcrumbsPageContainer() {
const theme = useTheme();
return (
-
+
diff --git a/docs/data/toolpad/core/components/page-container/page-container.md b/docs/data/toolpad/core/components/page-container/page-container.md
index a39e4161402..657c5b30f70 100644
--- a/docs/data/toolpad/core/components/page-container/page-container.md
+++ b/docs/data/toolpad/core/components/page-container/page-container.md
@@ -22,7 +22,6 @@ For example, under the following navigation structure:
```tsx
{
test('renders nested', async () => {
const navigation = [
+ { segment: '', title: 'ACME' },
{
segment: 'home',
title: 'Home',
@@ -79,4 +80,36 @@ describe('PageContainer', () => {
expect(within(breadCrumbs).getByText('Home')).toBeTruthy();
expect(within(breadCrumbs).getByText('Orders')).toBeTruthy();
});
+
+ test('renders dynamic correctly', async () => {
+ const user = await userEvent.setup();
+ const router = {
+ pathname: '/orders/123',
+ searchParams: new URLSearchParams(),
+ navigate: vi.fn(),
+ };
+ render(
+
+
+ ,
+ );
+
+ const breadCrumbs = screen.getByRole('navigation', { name: 'breadcrumb' });
+
+ const homeLink = within(breadCrumbs).getByRole('link', { name: 'Home' });
+ await user.click(homeLink);
+
+ expect(router.navigate).toHaveBeenCalledWith('/', expect.objectContaining({}));
+ router.navigate.mockClear();
+
+ expect(within(breadCrumbs).getByText('Orders')).toBeTruthy();
+
+ expect(screen.getByText('Orders', { ignore: 'nav *' }));
+ });
});
diff --git a/packages/toolpad-core/src/PageContainer/PageContainer.tsx b/packages/toolpad-core/src/PageContainer/PageContainer.tsx
index ba5f5052932..fd3b3f020a7 100644
--- a/packages/toolpad-core/src/PageContainer/PageContainer.tsx
+++ b/packages/toolpad-core/src/PageContainer/PageContainer.tsx
@@ -10,77 +10,16 @@ import useSlotProps from '@mui/utils/useSlotProps';
import { styled } from '@mui/material';
import { Link as ToolpadLink } from '../shared/Link';
import { PageContainerToolbar, PageContainerToolbarProps } from './PageContainerToolbar';
-import { NavigationContext, RouterContext } from '../shared/context';
-import { getItemTitle, isPageItem } from '../shared/navigation';
-import { NavigationItem, NavigationPageItem, Navigation } from '../AppProvider';
-import { useApplicationTitle } from '../shared/branding';
+import { getItemTitle } from '../shared/navigation';
+import { useActivePage } from '../useActivePage';
const PageContentHeader = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'row',
- jusifyCOntent: 'space-between',
+ jusifyContent: 'space-between',
gap: theme.spacing(2),
}));
-const isRootPage = (item: NavigationItem) => isPageItem(item) && !item.segment;
-
-interface BreadCrumbItem extends NavigationPageItem {
- path: string;
-}
-
-function createPageLookup(
- navigation: Navigation,
- segments: BreadCrumbItem[] = [],
- base = '',
-): Map {
- const result = new Map();
-
- const resolveSegment = (segment?: string) => `${base}${segment ? `/${segment}` : ''}` || '/';
-
- const root = navigation.find((item) => isRootPage(item)) as NavigationPageItem | undefined;
- const rootCrumb = root ? { path: resolveSegment(''), ...root } : undefined;
-
- for (const item of navigation) {
- if (!isPageItem(item)) {
- continue;
- }
-
- const isNonProdEnv = process.env.NODE_ENV !== 'production';
-
- const path = resolveSegment(item.segment);
- if (isNonProdEnv && result.has(path)) {
- console.warn(`Duplicate path in navigation: ${path}`);
- }
-
- const itemCrumb: BreadCrumbItem = { path, ...item };
-
- const navigationSegments: BreadCrumbItem[] = [
- ...segments,
- ...(rootCrumb && !isRootPage(item) ? [rootCrumb] : []),
- itemCrumb,
- ];
-
- result.set(path, navigationSegments);
-
- if (item.children) {
- const childrenLookup = createPageLookup(item.children, navigationSegments, path);
- for (const [childPath, childItems] of childrenLookup) {
- if (isNonProdEnv && result.has(childPath)) {
- console.warn(`Duplicate path in navigation: ${childPath}`);
- }
- result.set(childPath, childItems);
- }
- }
- }
-
- return result;
-}
-
-function matchPath(navigation: Navigation, path: string): BreadCrumbItem[] | null {
- const lookup = createPageLookup(navigation);
- return lookup.get(path) ?? null;
-}
-
export interface PageContainerSlotProps {
toolbar: PageContainerToolbarProps;
}
@@ -93,12 +32,19 @@ export interface PageContainerSlots {
toolbar: React.ElementType;
}
+export interface BreadCrumb {
+ title: string;
+ path: string;
+}
+
export interface PageContainerProps extends ContainerProps {
children?: React.ReactNode;
title?: string;
+ breadCrumbs?: BreadCrumb[];
slots?: PageContainerSlots;
slotProps?: PageContainerSlotProps;
}
+
/**
*
* Demos:
@@ -111,28 +57,11 @@ export interface PageContainerProps extends ContainerProps {
*/
function PageContainer(props: PageContainerProps) {
const { children, slots, slotProps, ...rest } = props;
- const routerContext = React.useContext(RouterContext);
- const navigationContext = React.useContext(NavigationContext);
- const pathname = routerContext?.pathname ?? '/';
- const applicationTitle = useApplicationTitle();
- const breadCrumbs = React.useMemo(() => {
- let crumbs = matchPath(navigationContext, pathname) ?? [];
- if (crumbs.length <= 0 || crumbs[0].path !== '/') {
- crumbs = [
- {
- segment: '',
- path: '/',
- title: applicationTitle,
- },
- ...crumbs,
- ];
- }
- return crumbs;
- }, [navigationContext, pathname, applicationTitle]);
+ const activePage = useActivePage();
- const title =
- (breadCrumbs ? getItemTitle(breadCrumbs[breadCrumbs.length - 1]) : '') ?? props.title;
+ const breadCrumbs = props.breadCrumbs ?? activePage?.breadCrumbs ?? [];
+ const title = props.title ?? activePage?.title ?? '';
const ToolbarComponent = props?.slots?.toolbar ?? PageContainerToolbar;
const toolbarSlotProps = useSlotProps({
diff --git a/packages/toolpad-core/src/index.ts b/packages/toolpad-core/src/index.ts
index c8ff0dade87..dcb98090703 100644
--- a/packages/toolpad-core/src/index.ts
+++ b/packages/toolpad-core/src/index.ts
@@ -8,6 +8,8 @@ export * from './Account';
export * from './PageContainer';
+export * from './useActivePage';
+
export * from './useDialogs';
export * from './useNotifications';
diff --git a/packages/toolpad-core/src/internals/demo.tsx b/packages/toolpad-core/src/internals/demo.tsx
index 0b41c363166..bb7fe12105a 100644
--- a/packages/toolpad-core/src/internals/demo.tsx
+++ b/packages/toolpad-core/src/internals/demo.tsx
@@ -1,4 +1,5 @@
import * as React from 'react';
+import { styled } from '@mui/material/styles';
import { Router } from '../AppProvider';
/**
@@ -30,3 +31,52 @@ export function useDemoRouter(initialUrl: string = '/') {
return router;
}
+
+const BrowserRoot = styled('div')(({ theme }) => ({
+ display: 'flex',
+ flexDirection: 'column',
+ width: '100%',
+ height: '100%',
+ alignItems: 'stretch',
+ borderRadius: theme.shape.borderRadius,
+ overflow: 'hidden',
+}));
+
+const BrowserBar = styled('div')(({ theme }) => ({
+ padding: theme.spacing(1),
+ backgroundColor: theme.palette.grey[400],
+}));
+
+const UrlField = styled('div')(({ theme }) => ({
+ fontSize: theme.typography.caption.fontSize,
+ color: 'black',
+ padding: '0.1rem 12px',
+ backgroundColor: 'white',
+ borderRadius: '1em',
+}));
+
+const BrowserContent = styled('div')(({ theme }) => ({
+ flex: 1,
+ border: `1px solid ${theme.palette.divider}`,
+ borderTop: 0,
+ backgroundColor: theme.palette.background.default,
+}));
+
+export interface DemoBrowserProps {
+ router: Router;
+ children?: React.ReactNode;
+}
+
+export function DemoBrowser({ router, children }: DemoBrowserProps) {
+ const search = router.searchParams.toString();
+ return (
+
+
+ {router.pathname + (search ? `?${search}` : '')}
+
+ {children}
+
+ );
+}
+
+export { Link } from '../shared/Link';
diff --git a/packages/toolpad-core/src/shared/navigation.tsx b/packages/toolpad-core/src/shared/navigation.tsx
index 851f7902af2..38c14af4a8d 100644
--- a/packages/toolpad-core/src/shared/navigation.tsx
+++ b/packages/toolpad-core/src/shared/navigation.tsx
@@ -1,5 +1,11 @@
import { pathToRegexp } from 'path-to-regexp';
-import type { NavigationItem, NavigationPageItem, NavigationSubheaderItem } from '../AppProvider';
+import invariant from 'invariant';
+import type {
+ Navigation,
+ NavigationItem,
+ NavigationPageItem,
+ NavigationSubheaderItem,
+} from '../AppProvider';
export const getItemKind = (item: NavigationItem) => item.kind ?? 'page';
@@ -51,3 +57,97 @@ export function hasSelectedNavigationChildren(
return false;
}
+
+// maps navigation items to their full path
+function buildItemToPathMap(navigation: Navigation): Map {
+ const map = new Map();
+
+ const visit = (item: NavigationItem, base: string) => {
+ if (isPageItem(item)) {
+ const path = `${base}${item.segment ? `/${item.segment}` : ''}` || '/';
+ map.set(item, path);
+ if (item.children) {
+ for (const child of item.children) {
+ visit(child, path);
+ }
+ }
+ }
+ };
+
+ for (const item of navigation) {
+ visit(item, '');
+ }
+
+ return map;
+}
+
+const itemToPathMapCache = new WeakMap>();
+function getItemToPathMap(navigation: Navigation) {
+ let map = itemToPathMapCache.get(navigation);
+ if (!map) {
+ map = buildItemToPathMap(navigation);
+ itemToPathMapCache.set(navigation, map);
+ }
+ return map;
+}
+
+function buildItemLookup(navigation: Navigation) {
+ const map = new Map();
+ const visit = (item: NavigationItem) => {
+ if (isPageItem(item)) {
+ const path = getItemPath(navigation, item);
+ if (map.has(path)) {
+ console.warn(`Duplicate path in navigation: ${path}`);
+ }
+ map.set(path, item);
+ if (item.pattern) {
+ map.set(pathToRegexp(item.pattern), item);
+ }
+ if (item.children) {
+ for (const child of item.children) {
+ visit(child);
+ }
+ }
+ }
+ };
+ for (const item of navigation) {
+ visit(item);
+ }
+ return map;
+}
+const itemLookupMapCache = new WeakMap>();
+function getItemLookup(navigation: Navigation) {
+ let map = itemLookupMapCache.get(navigation);
+ if (!map) {
+ map = buildItemLookup(navigation);
+ itemLookupMapCache.set(navigation, map);
+ }
+ return map;
+}
+
+export function matchPath(navigation: Navigation, path: string): NavigationPageItem | null {
+ const lookup = getItemLookup(navigation);
+
+ for (const [key, item] of lookup.entries()) {
+ if (typeof key === 'string' && key === path) {
+ return item;
+ }
+ if (key instanceof RegExp && key.test(path)) {
+ return item;
+ }
+ }
+
+ return null;
+}
+
+export function getItemByPath(navigation: Navigation, path: string): NavigationPageItem | null {
+ const map = getItemLookup(navigation);
+ return map.get(path) ?? null;
+}
+
+export function getItemPath(navigation: Navigation, item: NavigationPageItem): string {
+ const map = getItemToPathMap(navigation);
+ const path = map.get(item);
+ invariant(path, `Item not found in navigation: ${item.title}`);
+ return path;
+}
diff --git a/packages/toolpad-core/src/useActivePage/index.ts b/packages/toolpad-core/src/useActivePage/index.ts
new file mode 100644
index 00000000000..52662dbd290
--- /dev/null
+++ b/packages/toolpad-core/src/useActivePage/index.ts
@@ -0,0 +1 @@
+export * from './useActivePage';
diff --git a/packages/toolpad-core/src/useActivePage/useActivePage.ts b/packages/toolpad-core/src/useActivePage/useActivePage.ts
new file mode 100644
index 00000000000..e1b6a9762d5
--- /dev/null
+++ b/packages/toolpad-core/src/useActivePage/useActivePage.ts
@@ -0,0 +1,54 @@
+'use client';
+import * as React from 'react';
+import { NavigationContext, RouterContext } from '../shared/context';
+import { getItemPath, getItemTitle, matchPath } from '../shared/navigation';
+import type { BreadCrumb } from '../PageContainer';
+
+export function useActivePage() {
+ const navigationContext = React.useContext(NavigationContext);
+ const routerContext = React.useContext(RouterContext);
+ const pathname = routerContext?.pathname ?? '/';
+ const activeItem = matchPath(navigationContext, pathname);
+
+ const rootItem = matchPath(navigationContext, '/');
+
+ return React.useMemo(() => {
+ if (!activeItem) {
+ return null;
+ }
+
+ const breadCrumbs: BreadCrumb[] = [];
+
+ if (rootItem) {
+ breadCrumbs.push({
+ title: getItemTitle(rootItem),
+ path: '/',
+ });
+ }
+
+ const segments = pathname.split('/').filter(Boolean);
+ let prefix = '';
+ for (const segment of segments) {
+ const path = `${prefix}/${segment}`;
+ prefix = path;
+ const item = matchPath(navigationContext, path);
+ if (!item) {
+ continue;
+ }
+ const itemPath = getItemPath(navigationContext, item);
+ const lastCrumb = breadCrumbs[breadCrumbs.length - 1];
+ if (lastCrumb?.path !== itemPath) {
+ breadCrumbs.push({
+ title: getItemTitle(item),
+ path: itemPath,
+ });
+ }
+ }
+
+ return {
+ title: getItemTitle(activeItem),
+ path: getItemPath(navigationContext, activeItem),
+ breadCrumbs,
+ };
+ }, [activeItem, rootItem, pathname, navigationContext]);
+}
From 5affccc5967085872af839fd1cc6e9bccd320dce Mon Sep 17 00:00:00 2001
From: MUI bot <2109932+Janpot@users.noreply.github.com>
Date: Thu, 19 Sep 2024 09:51:54 +0200
Subject: [PATCH 02/14] fix demo
---
.../page-container/CustomPageContainer.js | 35 +++++++++++---
.../page-container/CustomPageContainer.tsx | 11 ++---
packages/toolpad-core/src/internals/demo.tsx | 48 -------------------
3 files changed, 34 insertions(+), 60 deletions(-)
diff --git a/docs/data/toolpad/core/components/page-container/CustomPageContainer.js b/docs/data/toolpad/core/components/page-container/CustomPageContainer.js
index 6b25087c47c..583e3a37fa3 100644
--- a/docs/data/toolpad/core/components/page-container/CustomPageContainer.js
+++ b/docs/data/toolpad/core/components/page-container/CustomPageContainer.js
@@ -1,10 +1,12 @@
import * as React from 'react';
+import PropTypes from 'prop-types';
import { PageContainer } from '@toolpad/core/PageContainer';
import { AppProvider } from '@toolpad/core/AppProvider';
-import { DemoBrowser, Link, useDemoRouter } from '@toolpad/core/internals';
+import { Link, useDemoRouter } from '@toolpad/core/internals';
import { useActivePage } from '@toolpad/core/useActivePage';
import { useTheme } from '@mui/material/styles';
import Box from '@mui/material/Box';
+import Paper from '@mui/material/Paper';
const NAVIGATION = [
{
@@ -37,6 +39,29 @@ function Content({ router }) {
);
}
+Content.propTypes = {
+ router: PropTypes.shape({
+ navigate: PropTypes.func.isRequired,
+ pathname: PropTypes.string.isRequired,
+ searchParams: PropTypes.shape({
+ '__@iterator@334': PropTypes.func.isRequired,
+ append: PropTypes.func.isRequired,
+ delete: PropTypes.func.isRequired,
+ entries: PropTypes.func.isRequired,
+ forEach: PropTypes.func.isRequired,
+ get: PropTypes.func.isRequired,
+ getAll: PropTypes.func.isRequired,
+ has: PropTypes.func.isRequired,
+ keys: PropTypes.func.isRequired,
+ set: PropTypes.func.isRequired,
+ size: PropTypes.number.isRequired,
+ sort: PropTypes.func.isRequired,
+ toString: PropTypes.func.isRequired,
+ values: PropTypes.func.isRequired,
+ }).isRequired,
+ }).isRequired,
+};
+
export default function CustomPageContainer() {
const router = useDemoRouter('/inbox/123');
@@ -53,10 +78,8 @@ export default function CustomPageContainer() {
}
return (
-
-
- {content}
-
-
+
+ {content}
+
);
}
diff --git a/docs/data/toolpad/core/components/page-container/CustomPageContainer.tsx b/docs/data/toolpad/core/components/page-container/CustomPageContainer.tsx
index ab40fc0d3a4..546a45f0a29 100644
--- a/docs/data/toolpad/core/components/page-container/CustomPageContainer.tsx
+++ b/docs/data/toolpad/core/components/page-container/CustomPageContainer.tsx
@@ -1,10 +1,11 @@
import * as React from 'react';
import { PageContainer } from '@toolpad/core/PageContainer';
import { AppProvider, Router } from '@toolpad/core/AppProvider';
-import { DemoBrowser, Link, useDemoRouter } from '@toolpad/core/internals';
+import { Link, useDemoRouter } from '@toolpad/core/internals';
import { useActivePage } from '@toolpad/core/useActivePage';
import { useTheme } from '@mui/material/styles';
import Box from '@mui/material/Box';
+import Paper from '@mui/material/Paper';
const NAVIGATION = [
{
@@ -57,10 +58,8 @@ export default function CustomPageContainer() {
}
return (
-
-
- {content}
-
-
+
+ {content}
+
);
}
diff --git a/packages/toolpad-core/src/internals/demo.tsx b/packages/toolpad-core/src/internals/demo.tsx
index bb7fe12105a..bdda55216fe 100644
--- a/packages/toolpad-core/src/internals/demo.tsx
+++ b/packages/toolpad-core/src/internals/demo.tsx
@@ -1,5 +1,4 @@
import * as React from 'react';
-import { styled } from '@mui/material/styles';
import { Router } from '../AppProvider';
/**
@@ -32,51 +31,4 @@ export function useDemoRouter(initialUrl: string = '/') {
return router;
}
-const BrowserRoot = styled('div')(({ theme }) => ({
- display: 'flex',
- flexDirection: 'column',
- width: '100%',
- height: '100%',
- alignItems: 'stretch',
- borderRadius: theme.shape.borderRadius,
- overflow: 'hidden',
-}));
-
-const BrowserBar = styled('div')(({ theme }) => ({
- padding: theme.spacing(1),
- backgroundColor: theme.palette.grey[400],
-}));
-
-const UrlField = styled('div')(({ theme }) => ({
- fontSize: theme.typography.caption.fontSize,
- color: 'black',
- padding: '0.1rem 12px',
- backgroundColor: 'white',
- borderRadius: '1em',
-}));
-
-const BrowserContent = styled('div')(({ theme }) => ({
- flex: 1,
- border: `1px solid ${theme.palette.divider}`,
- borderTop: 0,
- backgroundColor: theme.palette.background.default,
-}));
-
-export interface DemoBrowserProps {
- router: Router;
- children?: React.ReactNode;
-}
-
-export function DemoBrowser({ router, children }: DemoBrowserProps) {
- const search = router.searchParams.toString();
- return (
-
-
- {router.pathname + (search ? `?${search}` : '')}
-
- {children}
-
- );
-}
-
export { Link } from '../shared/Link';
From 7aef586864bbbc8c2725cbcc27e30ed0a867c807 Mon Sep 17 00:00:00 2001
From: MUI bot <2109932+Janpot@users.noreply.github.com>
Date: Thu, 19 Sep 2024 09:57:20 +0200
Subject: [PATCH 03/14] gfhfg
---
.../page-container/CustomPageContainer.js | 14 +++++++++-----
.../page-container/CustomPageContainer.tsx | 14 +++++++++-----
2 files changed, 18 insertions(+), 10 deletions(-)
diff --git a/docs/data/toolpad/core/components/page-container/CustomPageContainer.js b/docs/data/toolpad/core/components/page-container/CustomPageContainer.js
index 583e3a37fa3..47619a443fb 100644
--- a/docs/data/toolpad/core/components/page-container/CustomPageContainer.js
+++ b/docs/data/toolpad/core/components/page-container/CustomPageContainer.js
@@ -7,6 +7,7 @@ import { useActivePage } from '@toolpad/core/useActivePage';
import { useTheme } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Paper from '@mui/material/Paper';
+import invariant from 'invariant';
const NAVIGATION = [
{
@@ -19,13 +20,16 @@ const NAVIGATION = [
function Content({ router }) {
const id = Number(router.pathname.replace('/inbox/', ''));
+ const activePage = useActivePage();
+ invariant(
+ activePage,
+ 'This component must be used on a page within the app navigation',
+ );
+
const title = `Item ${id}`;
+ const path = `${activePage.path}/${id}`;
- const activePage = useActivePage();
- const breadCrumbs = [
- ...(activePage?.breadCrumbs ?? []),
- { title, path: `/inbox/${id}` },
- ];
+ const breadCrumbs = [...activePage.breadCrumbs, { title, path }];
return (
// preview-start
diff --git a/docs/data/toolpad/core/components/page-container/CustomPageContainer.tsx b/docs/data/toolpad/core/components/page-container/CustomPageContainer.tsx
index 546a45f0a29..60c02065007 100644
--- a/docs/data/toolpad/core/components/page-container/CustomPageContainer.tsx
+++ b/docs/data/toolpad/core/components/page-container/CustomPageContainer.tsx
@@ -6,6 +6,7 @@ import { useActivePage } from '@toolpad/core/useActivePage';
import { useTheme } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Paper from '@mui/material/Paper';
+import invariant from 'invariant';
const NAVIGATION = [
{
@@ -22,13 +23,16 @@ interface ContentProps {
function Content({ router }: ContentProps) {
const id = Number(router.pathname.replace('/inbox/', ''));
+ const activePage = useActivePage();
+ invariant(
+ activePage,
+ 'This component must be used on a page within the app navigation',
+ );
+
const title = `Item ${id}`;
+ const path = `${activePage.path}/${id}`;
- const activePage = useActivePage();
- const breadCrumbs = [
- ...(activePage?.breadCrumbs ?? []),
- { title, path: `/inbox/${id}` },
- ];
+ const breadCrumbs = [...activePage.breadCrumbs, { title, path }];
return (
// preview-start
From 6c3dcc918a845aa097ece742dbdfddc51eb1cc8f Mon Sep 17 00:00:00 2001
From: MUI bot <2109932+Janpot@users.noreply.github.com>
Date: Thu, 19 Sep 2024 10:05:54 +0200
Subject: [PATCH 04/14] frwgrw
---
.../page-container/CustomPageContainer.js | 5 +----
.../page-container/CustomPageContainer.tsx | 5 +----
.../page-container/page-container.md | 20 ++++++++++++++++++-
3 files changed, 21 insertions(+), 9 deletions(-)
diff --git a/docs/data/toolpad/core/components/page-container/CustomPageContainer.js b/docs/data/toolpad/core/components/page-container/CustomPageContainer.js
index 47619a443fb..6e2a8db1b1d 100644
--- a/docs/data/toolpad/core/components/page-container/CustomPageContainer.js
+++ b/docs/data/toolpad/core/components/page-container/CustomPageContainer.js
@@ -21,10 +21,7 @@ function Content({ router }) {
const id = Number(router.pathname.replace('/inbox/', ''));
const activePage = useActivePage();
- invariant(
- activePage,
- 'This component must be used on a page within the app navigation',
- );
+ invariant(activePage, 'No navigation match');
const title = `Item ${id}`;
const path = `${activePage.path}/${id}`;
diff --git a/docs/data/toolpad/core/components/page-container/CustomPageContainer.tsx b/docs/data/toolpad/core/components/page-container/CustomPageContainer.tsx
index 60c02065007..3b913515a49 100644
--- a/docs/data/toolpad/core/components/page-container/CustomPageContainer.tsx
+++ b/docs/data/toolpad/core/components/page-container/CustomPageContainer.tsx
@@ -24,10 +24,7 @@ function Content({ router }: ContentProps) {
const id = Number(router.pathname.replace('/inbox/', ''));
const activePage = useActivePage();
- invariant(
- activePage,
- 'This component must be used on a page within the app navigation',
- );
+ invariant(activePage, 'No navigation match');
const title = `Item ${id}`;
const path = `${activePage.path}/${id}`;
diff --git a/docs/data/toolpad/core/components/page-container/page-container.md b/docs/data/toolpad/core/components/page-container/page-container.md
index 657c5b30f70..153c0986b77 100644
--- a/docs/data/toolpad/core/components/page-container/page-container.md
+++ b/docs/data/toolpad/core/components/page-container/page-container.md
@@ -45,10 +45,28 @@ The breadcrumbs contains **ACME / Home / Orders** when you visit the path **/hom
## Dynamic Routes
-When you use the `PageContainer` on a dynamic route, you'll likely want to set a title and breadcrumbs belonging to the specific path. You can achieve this with the `title` and `breadCrumbs` property of the `PageContainer`:
+When you use the `PageContainer` on a dynamic route, you'll likely want to set a title and breadcrumbs belonging to the specific path. You can achieve this with the `title` and `breadCrumbs` property of the `PageContainer`
{{"demo": "CustomPageContainer.js", "height": 300}}
+You can use the `useActivePage` hook to retrieve the title and breadcrumbs of the active page. This way you can extend the existing values.
+
+```tsx
+import { useActivePage } from '@toolpad/core/useActivePage';
+import { BreadCrumb } from '@toolpad/core/PageContainer';
+
+// Pass the id from your router of choice
+function useDynamicBreadCrumbs(id: string): BreadCrumb[] {
+ const activePage = useActivePage();
+ invariant(activePage, 'No navigation match');
+
+ const title = `Item ${id}`;
+ const path = `${activePage.path}/${id}`;
+
+ return [...activePage.breadCrumbs, { title, path }];
+}
+```
+
## Actions
You can configure additional actions in the area that is reserved on the right. To do so provide the `toolbar` slot to the `PageContainer` component. You can wrap the `PageContainerToolbar` component to create a custom toolbar component, as shown here:
From d7a762481638ec1b3cce102cf77c442d68df2f64 Mon Sep 17 00:00:00 2001
From: MUI bot <2109932+Janpot@users.noreply.github.com>
Date: Thu, 19 Sep 2024 10:07:01 +0200
Subject: [PATCH 05/14] Update PageContainer.tsx
---
.../toolpad-core/src/PageContainer/PageContainer.tsx | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/packages/toolpad-core/src/PageContainer/PageContainer.tsx b/packages/toolpad-core/src/PageContainer/PageContainer.tsx
index fd3b3f020a7..048d3752888 100644
--- a/packages/toolpad-core/src/PageContainer/PageContainer.tsx
+++ b/packages/toolpad-core/src/PageContainer/PageContainer.tsx
@@ -113,6 +113,15 @@ PageContainer.propTypes /* remove-proptypes */ = {
// │ These PropTypes are generated from the TypeScript type definitions. │
// │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
// └─────────────────────────────────────────────────────────────────────┘
+ /**
+ * @ignore
+ */
+ breadCrumbs: PropTypes.arrayOf(
+ PropTypes.shape({
+ path: PropTypes.string.isRequired,
+ title: PropTypes.string.isRequired,
+ }),
+ ),
/**
* @ignore
*/
From 06c6f76881db91c4dc41c6379356f8df8659ef1f Mon Sep 17 00:00:00 2001
From: MUI bot <2109932+Janpot@users.noreply.github.com>
Date: Thu, 19 Sep 2024 10:12:49 +0200
Subject: [PATCH 06/14] Update navigation.tsx
---
.../toolpad-core/src/shared/navigation.tsx | 25 ++++++++++++++-----
1 file changed, 19 insertions(+), 6 deletions(-)
diff --git a/packages/toolpad-core/src/shared/navigation.tsx b/packages/toolpad-core/src/shared/navigation.tsx
index 38c14af4a8d..f986a3fd67b 100644
--- a/packages/toolpad-core/src/shared/navigation.tsx
+++ b/packages/toolpad-core/src/shared/navigation.tsx
@@ -58,7 +58,10 @@ export function hasSelectedNavigationChildren(
return false;
}
-// maps navigation items to their full path
+/**
+ * Builds a map of navigation page items to their respective paths. This map is used to quickly
+ * lookup the path of a navigation item. It will be cached for the lifetime of the navigation.
+ */
function buildItemToPathMap(navigation: Navigation): Map {
const map = new Map();
@@ -82,6 +85,10 @@ function buildItemToPathMap(navigation: Navigation): Map>();
+
+/**
+ * Gets the cached map of navigation page items to their respective paths.
+ */
function getItemToPathMap(navigation: Navigation) {
let map = itemToPathMapCache.get(navigation);
if (!map) {
@@ -91,6 +98,10 @@ function getItemToPathMap(navigation: Navigation) {
return map;
}
+/**
+ * Build a lookup map of paths to navigation items. This map is used to match paths against
+ * to find the active page.
+ */
function buildItemLookup(navigation: Navigation) {
const map = new Map();
const visit = (item: NavigationItem) => {
@@ -125,6 +136,10 @@ function getItemLookup(navigation: Navigation) {
return map;
}
+/**
+ * Matches a path against the navigation to find the active page. i.e. the page that should be
+ * marked as selected in the navigation.
+ */
export function matchPath(navigation: Navigation, path: string): NavigationPageItem | null {
const lookup = getItemLookup(navigation);
@@ -140,11 +155,9 @@ export function matchPath(navigation: Navigation, path: string): NavigationPageI
return null;
}
-export function getItemByPath(navigation: Navigation, path: string): NavigationPageItem | null {
- const map = getItemLookup(navigation);
- return map.get(path) ?? null;
-}
-
+/**
+ * Gets the path for a specific navigation page item.
+ */
export function getItemPath(navigation: Navigation, item: NavigationPageItem): string {
const map = getItemToPathMap(navigation);
const path = map.get(item);
From db632e9759f252bfb74611fd50d66c0a80a73d33 Mon Sep 17 00:00:00 2001
From: MUI bot <2109932+Janpot@users.noreply.github.com>
Date: Thu, 19 Sep 2024 10:16:28 +0200
Subject: [PATCH 07/14] Update pnpm-lock.yaml
---
pnpm-lock.yaml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a9d41f31f46..b89431a8b94 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -195,7 +195,7 @@ importers:
version: 7.35.2(eslint@8.57.0)
eslint-plugin-react-compiler:
specifier: latest
- version: 0.0.0-experimental-ca16900-20240916(eslint@8.57.0)
+ version: 0.0.0-experimental-7670337-20240918(eslint@8.57.0)
eslint-plugin-react-hooks:
specifier: 4.6.2
version: 4.6.2(eslint@8.57.0)
@@ -5824,8 +5824,8 @@ packages:
peerDependencies:
eslint: '>=7.0.0'
- eslint-plugin-react-compiler@0.0.0-experimental-ca16900-20240916:
- resolution: {integrity: sha512-T3iL6Veei4EMNdBtn9p/kUDlXFZAXVQ0scfF1hOjJE88Lmlg7f1zHRAcGQ4Z6ClVPnOikZ1/yGNB4l/ui47Anw==}
+ eslint-plugin-react-compiler@0.0.0-experimental-7670337-20240918:
+ resolution: {integrity: sha512-FZXoYqGK3BiJw2BFQIUL0C9GgEF2QS+y6oiVPm4GooX6SXrg/UVtCi1nMAaBhqTpsF/KF2RjobgePY8KgbqXow==}
engines: {node: ^14.17.0 || ^16.0.0 || >= 18.0.0}
peerDependencies:
eslint: '>=7'
@@ -15598,7 +15598,7 @@ snapshots:
globals: 13.24.0
rambda: 7.5.0
- eslint-plugin-react-compiler@0.0.0-experimental-ca16900-20240916(eslint@8.57.0):
+ eslint-plugin-react-compiler@0.0.0-experimental-7670337-20240918(eslint@8.57.0):
dependencies:
'@babel/core': 7.25.2
'@babel/parser': 7.25.6
From 8bafeb910b19b6341fbea646c9d434e1045a3b6b Mon Sep 17 00:00:00 2001
From: MUI bot <2109932+Janpot@users.noreply.github.com>
Date: Thu, 19 Sep 2024 10:38:22 +0200
Subject: [PATCH 08/14] Update CustomPageContainer.js
---
.../core/components/page-container/CustomPageContainer.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/data/toolpad/core/components/page-container/CustomPageContainer.js b/docs/data/toolpad/core/components/page-container/CustomPageContainer.js
index 6e2a8db1b1d..78b14b2bcc5 100644
--- a/docs/data/toolpad/core/components/page-container/CustomPageContainer.js
+++ b/docs/data/toolpad/core/components/page-container/CustomPageContainer.js
@@ -45,7 +45,7 @@ Content.propTypes = {
navigate: PropTypes.func.isRequired,
pathname: PropTypes.string.isRequired,
searchParams: PropTypes.shape({
- '__@iterator@334': PropTypes.func.isRequired,
+ '__@iterator@271': PropTypes.func.isRequired,
append: PropTypes.func.isRequired,
delete: PropTypes.func.isRequired,
entries: PropTypes.func.isRequired,
From 35affe3e401e220b91ae0a36bd5149f8f6040c92 Mon Sep 17 00:00:00 2001
From: MUI bot <2109932+Janpot@users.noreply.github.com>
Date: Thu, 19 Sep 2024 15:07:27 +0200
Subject: [PATCH 09/14] sdfs
---
.../page-container/CustomPageContainer.js | 17 +-------------
package.json | 2 +-
.../src/AppProvider/AppProvider.tsx | 4 ++--
pnpm-lock.yaml | 22 ++++++++++++++-----
4 files changed, 20 insertions(+), 25 deletions(-)
diff --git a/docs/data/toolpad/core/components/page-container/CustomPageContainer.js b/docs/data/toolpad/core/components/page-container/CustomPageContainer.js
index 78b14b2bcc5..a792a7df846 100644
--- a/docs/data/toolpad/core/components/page-container/CustomPageContainer.js
+++ b/docs/data/toolpad/core/components/page-container/CustomPageContainer.js
@@ -44,22 +44,7 @@ Content.propTypes = {
router: PropTypes.shape({
navigate: PropTypes.func.isRequired,
pathname: PropTypes.string.isRequired,
- searchParams: PropTypes.shape({
- '__@iterator@271': PropTypes.func.isRequired,
- append: PropTypes.func.isRequired,
- delete: PropTypes.func.isRequired,
- entries: PropTypes.func.isRequired,
- forEach: PropTypes.func.isRequired,
- get: PropTypes.func.isRequired,
- getAll: PropTypes.func.isRequired,
- has: PropTypes.func.isRequired,
- keys: PropTypes.func.isRequired,
- set: PropTypes.func.isRequired,
- size: PropTypes.number.isRequired,
- sort: PropTypes.func.isRequired,
- toString: PropTypes.func.isRequired,
- values: PropTypes.func.isRequired,
- }).isRequired,
+ searchParams: PropTypes.instanceOf(URLSearchParams).isRequired,
}).isRequired,
};
diff --git a/package.json b/package.json
index 35a1d598f67..d7bff36d351 100644
--- a/package.json
+++ b/package.json
@@ -56,7 +56,7 @@
"@mui/internal-babel-plugin-resolve-imports": "1.0.18",
"@mui/internal-docs-utils": "1.0.13",
"@mui/internal-markdown": "1.0.14",
- "@mui/internal-scripts": "1.0.21",
+ "@mui/internal-scripts": "https://pkg.csb.dev/mui/material-ui/commit/9bd6acae/@mui/internal-scripts",
"@mui/monorepo": "github:mui/material-ui#3732be30e0ae3287b55dc6a96cf1ffcfdc194906",
"@mui/x-charts": "7.17.0",
"@next/eslint-plugin-next": "14.2.11",
diff --git a/packages/toolpad-core/src/AppProvider/AppProvider.tsx b/packages/toolpad-core/src/AppProvider/AppProvider.tsx
index 029960cdadc..ba183f2bc88 100644
--- a/packages/toolpad-core/src/AppProvider/AppProvider.tsx
+++ b/packages/toolpad-core/src/AppProvider/AppProvider.tsx
@@ -239,10 +239,10 @@ AppProvider.propTypes /* remove-proptypes */ = {
* Router implementation used inside Toolpad components.
* @default null
*/
- router: PropTypes /* @typescript-to-proptypes-ignore */.shape({
+ router: PropTypes.shape({
navigate: PropTypes.func.isRequired,
pathname: PropTypes.string.isRequired,
- searchParams: PropTypes.instanceOf(URLSearchParams),
+ searchParams: PropTypes.instanceOf(URLSearchParams).isRequired,
}),
/**
* Session info about the current user.
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c0b4090e51f..9471d5a97d2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -98,8 +98,8 @@ importers:
specifier: 1.0.14
version: 1.0.14
'@mui/internal-scripts':
- specifier: 1.0.21
- version: 1.0.21
+ specifier: https://pkg.csb.dev/mui/material-ui/commit/9bd6acae/@mui/internal-scripts
+ version: https://pkg.csb.dev/mui/material-ui/commit/9bd6acae/@mui/internal-scripts
'@mui/monorepo':
specifier: github:mui/material-ui#3732be30e0ae3287b55dc6a96cf1ffcfdc194906
version: https://codeload.github.com/mui/material-ui/tar.gz/3732be30e0ae3287b55dc6a96cf1ffcfdc194906(encoding@0.1.13)
@@ -2849,11 +2849,16 @@ packages:
'@mui/internal-docs-utils@1.0.13':
resolution: {integrity: sha512-q+EVFiJGqAbNtYQrRqRiE6EEtVyIgEPfM0QpMJ5ws/QY8wZterExWHLbtBP5eX60fIXqaVccfMBro8RPaeuvDQ==}
+ '@mui/internal-docs-utils@https://pkg.csb.dev/mui/material-ui/commit/9bd6acae/@mui/internal-docs-utils':
+ resolution: {tarball: https://pkg.csb.dev/mui/material-ui/commit/9bd6acae/@mui/internal-docs-utils}
+ version: 1.0.13
+
'@mui/internal-markdown@1.0.14':
resolution: {integrity: sha512-Df8Uo54TyE7Lm3otWWxw2OQzv3ztbdhRJMJ2sCYrTDqluAvGe/65KpVr95/AcY/An3qWpvBrbn5k5Oj/NG/lXw==}
- '@mui/internal-scripts@1.0.21':
- resolution: {integrity: sha512-nVjt+Yjjff83C+urxDD63LmUhIR1bMbGVexHdFq1v9odyVKXw3tgL1yiCoEETLUfOkBbXECmjuRcbw+meWu5+g==}
+ '@mui/internal-scripts@https://pkg.csb.dev/mui/material-ui/commit/9bd6acae/@mui/internal-scripts':
+ resolution: {tarball: https://pkg.csb.dev/mui/material-ui/commit/9bd6acae/@mui/internal-scripts}
+ version: 1.0.21
'@mui/joy@5.0.0-beta.48':
resolution: {integrity: sha512-OhTvjuGl9I5IvpBr0BQyDehIW/xb2yteW6YglHJMdOb/279nItn76X1NBtPV9ImldNlBjReGwvpOXmBTTGER9w==}
@@ -12033,6 +12038,11 @@ snapshots:
rimraf: 6.0.1
typescript: 5.6.2
+ '@mui/internal-docs-utils@https://pkg.csb.dev/mui/material-ui/commit/9bd6acae/@mui/internal-docs-utils':
+ dependencies:
+ rimraf: 6.0.1
+ typescript: 5.6.2
+
'@mui/internal-markdown@1.0.14':
dependencies:
'@babel/runtime': 7.25.6
@@ -12040,14 +12050,14 @@ snapshots:
marked: 13.0.3
prismjs: 1.29.0
- '@mui/internal-scripts@1.0.21':
+ '@mui/internal-scripts@https://pkg.csb.dev/mui/material-ui/commit/9bd6acae/@mui/internal-scripts':
dependencies:
'@babel/core': 7.25.2
'@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.25.2)
'@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.25.2)
'@babel/plugin-syntax-typescript': 7.25.4(@babel/core@7.25.2)
'@babel/types': 7.25.6
- '@mui/internal-docs-utils': 1.0.13
+ '@mui/internal-docs-utils': https://pkg.csb.dev/mui/material-ui/commit/9bd6acae/@mui/internal-docs-utils
doctrine: 3.0.0
lodash: 4.17.21
typescript: 5.6.2
From b22f4cb708a53c1f513a1a16a8ec42b7208e0696 Mon Sep 17 00:00:00 2001
From: MUI bot <2109932+Janpot@users.noreply.github.com>
Date: Thu, 19 Sep 2024 15:14:36 +0200
Subject: [PATCH 10/14] kfjwh
---
package.json | 2 +-
pnpm-lock.yaml | 22 ++++++----------------
2 files changed, 7 insertions(+), 17 deletions(-)
diff --git a/package.json b/package.json
index d7bff36d351..eabb25451d2 100644
--- a/package.json
+++ b/package.json
@@ -56,7 +56,7 @@
"@mui/internal-babel-plugin-resolve-imports": "1.0.18",
"@mui/internal-docs-utils": "1.0.13",
"@mui/internal-markdown": "1.0.14",
- "@mui/internal-scripts": "https://pkg.csb.dev/mui/material-ui/commit/9bd6acae/@mui/internal-scripts",
+ "@mui/internal-scripts": "1.0.21-dev.20240919-130050-82a6448768",
"@mui/monorepo": "github:mui/material-ui#3732be30e0ae3287b55dc6a96cf1ffcfdc194906",
"@mui/x-charts": "7.17.0",
"@next/eslint-plugin-next": "14.2.11",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9471d5a97d2..0824f1f9da0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -98,8 +98,8 @@ importers:
specifier: 1.0.14
version: 1.0.14
'@mui/internal-scripts':
- specifier: https://pkg.csb.dev/mui/material-ui/commit/9bd6acae/@mui/internal-scripts
- version: https://pkg.csb.dev/mui/material-ui/commit/9bd6acae/@mui/internal-scripts
+ specifier: 1.0.21-dev.20240919-130050-82a6448768
+ version: 1.0.21-dev.20240919-130050-82a6448768
'@mui/monorepo':
specifier: github:mui/material-ui#3732be30e0ae3287b55dc6a96cf1ffcfdc194906
version: https://codeload.github.com/mui/material-ui/tar.gz/3732be30e0ae3287b55dc6a96cf1ffcfdc194906(encoding@0.1.13)
@@ -2849,16 +2849,11 @@ packages:
'@mui/internal-docs-utils@1.0.13':
resolution: {integrity: sha512-q+EVFiJGqAbNtYQrRqRiE6EEtVyIgEPfM0QpMJ5ws/QY8wZterExWHLbtBP5eX60fIXqaVccfMBro8RPaeuvDQ==}
- '@mui/internal-docs-utils@https://pkg.csb.dev/mui/material-ui/commit/9bd6acae/@mui/internal-docs-utils':
- resolution: {tarball: https://pkg.csb.dev/mui/material-ui/commit/9bd6acae/@mui/internal-docs-utils}
- version: 1.0.13
-
'@mui/internal-markdown@1.0.14':
resolution: {integrity: sha512-Df8Uo54TyE7Lm3otWWxw2OQzv3ztbdhRJMJ2sCYrTDqluAvGe/65KpVr95/AcY/An3qWpvBrbn5k5Oj/NG/lXw==}
- '@mui/internal-scripts@https://pkg.csb.dev/mui/material-ui/commit/9bd6acae/@mui/internal-scripts':
- resolution: {tarball: https://pkg.csb.dev/mui/material-ui/commit/9bd6acae/@mui/internal-scripts}
- version: 1.0.21
+ '@mui/internal-scripts@1.0.21-dev.20240919-130050-82a6448768':
+ resolution: {integrity: sha512-feyp1s9pHvSOdO+ERv8oPGp2qGUCnngTt0+Yb+yTBo1tH8DN1Ks5JA7gIQR/elTnDusrbBQPpI7IAG5ZPrYlzQ==}
'@mui/joy@5.0.0-beta.48':
resolution: {integrity: sha512-OhTvjuGl9I5IvpBr0BQyDehIW/xb2yteW6YglHJMdOb/279nItn76X1NBtPV9ImldNlBjReGwvpOXmBTTGER9w==}
@@ -12038,11 +12033,6 @@ snapshots:
rimraf: 6.0.1
typescript: 5.6.2
- '@mui/internal-docs-utils@https://pkg.csb.dev/mui/material-ui/commit/9bd6acae/@mui/internal-docs-utils':
- dependencies:
- rimraf: 6.0.1
- typescript: 5.6.2
-
'@mui/internal-markdown@1.0.14':
dependencies:
'@babel/runtime': 7.25.6
@@ -12050,14 +12040,14 @@ snapshots:
marked: 13.0.3
prismjs: 1.29.0
- '@mui/internal-scripts@https://pkg.csb.dev/mui/material-ui/commit/9bd6acae/@mui/internal-scripts':
+ '@mui/internal-scripts@1.0.21-dev.20240919-130050-82a6448768':
dependencies:
'@babel/core': 7.25.2
'@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.25.2)
'@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.25.2)
'@babel/plugin-syntax-typescript': 7.25.4(@babel/core@7.25.2)
'@babel/types': 7.25.6
- '@mui/internal-docs-utils': https://pkg.csb.dev/mui/material-ui/commit/9bd6acae/@mui/internal-docs-utils
+ '@mui/internal-docs-utils': 1.0.13
doctrine: 3.0.0
lodash: 4.17.21
typescript: 5.6.2
From 05a8b50293174fbf6e8625c3f49b7f8db7def3bc Mon Sep 17 00:00:00 2001
From: MUI bot <2109932+Janpot@users.noreply.github.com>
Date: Thu, 19 Sep 2024 15:34:29 +0200
Subject: [PATCH 11/14] Update app-provider.json
---
docs/pages/toolpad/core/api/app-provider.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/pages/toolpad/core/api/app-provider.json b/docs/pages/toolpad/core/api/app-provider.json
index 222066ae3ea..27ff3b8a3f4 100644
--- a/docs/pages/toolpad/core/api/app-provider.json
+++ b/docs/pages/toolpad/core/api/app-provider.json
@@ -19,7 +19,7 @@
"router": {
"type": {
"name": "shape",
- "description": "{ navigate: func, pathname: string, searchParams?: URLSearchParams }"
+ "description": "{ navigate: func, pathname: string, searchParams: URLSearchParams }"
},
"default": "null"
},
From 8ab84c43b8bb716c3317a6c183ac30525831d347 Mon Sep 17 00:00:00 2001
From: MUI bot <2109932+Janpot@users.noreply.github.com>
Date: Thu, 19 Sep 2024 15:39:05 +0200
Subject: [PATCH 12/14] comments
---
.../toolpad/core/api/page-container.json | 12 ++++++++-
.../page-container/page-container.json | 11 ++++++--
.../src/PageContainer/PageContainer.tsx | 27 ++++++++++++++++---
3 files changed, 43 insertions(+), 7 deletions(-)
diff --git a/docs/pages/toolpad/core/api/page-container.json b/docs/pages/toolpad/core/api/page-container.json
index 82219960b02..79293eba86a 100644
--- a/docs/pages/toolpad/core/api/page-container.json
+++ b/docs/pages/toolpad/core/api/page-container.json
@@ -1,5 +1,15 @@
{
- "props": {},
+ "props": {
+ "breadCrumbs": {
+ "type": { "name": "arrayOf", "description": "Array<{ path: string, title: string }>" }
+ },
+ "slotProps": { "type": { "name": "shape", "description": "{ toolbar: { children?: node } }" } },
+ "slots": {
+ "type": { "name": "shape", "description": "{ toolbar?: elementType }" },
+ "additionalInfo": { "slotsApi": true }
+ },
+ "title": { "type": { "name": "string" } }
+ },
"name": "PageContainer",
"imports": [
"import { PageContainer } from '@toolpad/core/PageContainer';",
diff --git a/docs/translations/api-docs/page-container/page-container.json b/docs/translations/api-docs/page-container/page-container.json
index 643fe8e0f28..39823940d46 100644
--- a/docs/translations/api-docs/page-container/page-container.json
+++ b/docs/translations/api-docs/page-container/page-container.json
@@ -1,6 +1,13 @@
{
- "componentDescription": "",
- "propDescriptions": {},
+ "componentDescription": "A container component to provide a title and breadcrumbs for your pages.",
+ "propDescriptions": {
+ "breadCrumbs": {
+ "description": "The breadcrumbs of the page. Leave blank to use the active page breadcrumbs."
+ },
+ "slotProps": { "description": "The props used for each slot inside." },
+ "slots": { "description": "The components used for each slot inside." },
+ "title": { "description": "The title of the page. Leave blank to use the active page title." }
+ },
"classDescriptions": {
"disableGutters": {
"description": "Styles applied to {{nodeName}} if {{conditions}}.",
diff --git a/packages/toolpad-core/src/PageContainer/PageContainer.tsx b/packages/toolpad-core/src/PageContainer/PageContainer.tsx
index 048d3752888..7ff87f6caf2 100644
--- a/packages/toolpad-core/src/PageContainer/PageContainer.tsx
+++ b/packages/toolpad-core/src/PageContainer/PageContainer.tsx
@@ -33,19 +33,38 @@ export interface PageContainerSlots {
}
export interface BreadCrumb {
+ /**
+ * The title of the breadcrumb segment.
+ */
title: string;
+ /**
+ * The path the breadcrumb links to.
+ */
path: string;
}
export interface PageContainerProps extends ContainerProps {
children?: React.ReactNode;
+ /**
+ * The title of the page. Leave blank to use the active page title.
+ */
title?: string;
+ /**
+ * The breadcrumbs of the page. Leave blank to use the active page breadcrumbs.
+ */
breadCrumbs?: BreadCrumb[];
+ /**
+ * The components used for each slot inside.
+ */
slots?: PageContainerSlots;
+ /**
+ * The props used for each slot inside.
+ */
slotProps?: PageContainerSlotProps;
}
/**
+ * A container component to provide a title and breadcrumbs for your pages.
*
* Demos:
*
@@ -114,7 +133,7 @@ PageContainer.propTypes /* remove-proptypes */ = {
// │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
// └─────────────────────────────────────────────────────────────────────┘
/**
- * @ignore
+ * The breadcrumbs of the page. Leave blank to use the active page breadcrumbs.
*/
breadCrumbs: PropTypes.arrayOf(
PropTypes.shape({
@@ -127,7 +146,7 @@ PageContainer.propTypes /* remove-proptypes */ = {
*/
children: PropTypes.node,
/**
- * @ignore
+ * The props used for each slot inside.
*/
slotProps: PropTypes.shape({
toolbar: PropTypes.shape({
@@ -135,13 +154,13 @@ PageContainer.propTypes /* remove-proptypes */ = {
}).isRequired,
}),
/**
- * @ignore
+ * The components used for each slot inside.
*/
slots: PropTypes.shape({
toolbar: PropTypes.elementType,
}),
/**
- * @ignore
+ * The title of the page. Leave blank to use the active page title.
*/
title: PropTypes.string,
} as any;
From 02c6b01f28307bf6bdfaad8991418cf6b7c54977 Mon Sep 17 00:00:00 2001
From: MUI bot <2109932+Janpot@users.noreply.github.com>
Date: Fri, 20 Sep 2024 08:43:26 +0200
Subject: [PATCH 13/14] Add note
---
.../page-container/page-container.md | 29 +++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/docs/data/toolpad/core/components/page-container/page-container.md b/docs/data/toolpad/core/components/page-container/page-container.md
index 153c0986b77..81cd357c368 100644
--- a/docs/data/toolpad/core/components/page-container/page-container.md
+++ b/docs/data/toolpad/core/components/page-container/page-container.md
@@ -67,6 +67,35 @@ function useDynamicBreadCrumbs(id: string): BreadCrumb[] {
}
```
+For example, under the Next.js app router you would be able to obtain breadcrumbs for a dynamic route as follows:
+
+```tsx
+// ./src/app/example/[id]/page.tsx
+'use client';
+
+import { useParams } from 'next/navigation';
+import { PageContainer } from '@toolpad/core/PageContainer';
+import invariant from 'invariant';
+import { useActivePage } from '@toolpad/core/useActivePage';
+
+export default function Example() {
+ const params = useParams<{ id: string }>();
+ const activePage = useActivePage();
+ invariant(activePage, 'No navigation match');
+
+ const title = `Item ${params.id}`;
+ const path = `${activePage.path}/${params.id}`;
+
+ const breadCrumbs = [...activePage.breadCrumbs, { title, path }];
+
+ return (
+
+ ...
+
+ );
+}
+```
+
## Actions
You can configure additional actions in the area that is reserved on the right. To do so provide the `toolbar` slot to the `PageContainer` component. You can wrap the `PageContainerToolbar` component to create a custom toolbar component, as shown here:
From 7bbe5bfcfa770e29e2002ce89b91fc0239c52ab2 Mon Sep 17 00:00:00 2001
From: MUI bot <2109932+Janpot@users.noreply.github.com>
Date: Fri, 20 Sep 2024 09:18:13 +0200
Subject: [PATCH 14/14] Update pnpm-lock.yaml
---
pnpm-lock.yaml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0824f1f9da0..0af1141a20b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -195,7 +195,7 @@ importers:
version: 7.35.2(eslint@8.57.0)
eslint-plugin-react-compiler:
specifier: latest
- version: 0.0.0-experimental-7670337-20240918(eslint@8.57.0)
+ version: 0.0.0-experimental-92aaa43-20240919(eslint@8.57.0)
eslint-plugin-react-hooks:
specifier: 4.6.2
version: 4.6.2(eslint@8.57.0)
@@ -5824,8 +5824,8 @@ packages:
peerDependencies:
eslint: '>=7.0.0'
- eslint-plugin-react-compiler@0.0.0-experimental-7670337-20240918:
- resolution: {integrity: sha512-FZXoYqGK3BiJw2BFQIUL0C9GgEF2QS+y6oiVPm4GooX6SXrg/UVtCi1nMAaBhqTpsF/KF2RjobgePY8KgbqXow==}
+ eslint-plugin-react-compiler@0.0.0-experimental-92aaa43-20240919:
+ resolution: {integrity: sha512-l1tEUmxnZcMNkpUffbyNPAV91kZMZYLVeCCRZajK/s1QdP9FquunJQ9uT4c3f0RqdV6n0kloVfnJ0lPD1FTPlg==}
engines: {node: ^14.17.0 || ^16.0.0 || >= 18.0.0}
peerDependencies:
eslint: '>=7'
@@ -15628,7 +15628,7 @@ snapshots:
globals: 13.24.0
rambda: 7.5.0
- eslint-plugin-react-compiler@0.0.0-experimental-7670337-20240918(eslint@8.57.0):
+ eslint-plugin-react-compiler@0.0.0-experimental-92aaa43-20240919(eslint@8.57.0):
dependencies:
'@babel/core': 7.25.2
'@babel/parser': 7.25.6