@etsoo/toolpad 1.0.29 → 1.0.31

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.
@@ -28,6 +28,7 @@ export interface NavigationPageItem {
28
28
  action?: React.ReactNode;
29
29
  children?: Navigation;
30
30
  hidden?: boolean;
31
+ pageHeader?: React.ReactNode;
31
32
  }
32
33
  export interface NavigationSubheaderItem {
33
34
  kind: "header";
@@ -25,8 +25,7 @@ export type PageData = {
25
25
  title?: string;
26
26
  page?: string;
27
27
  breadcrumbs?: Breadcrumb[];
28
- noBreadcrumbs?: boolean;
29
- noPageHeader?: boolean;
28
+ pageHeader?: React.ReactNode;
30
29
  };
31
30
  type PageDataAction = PageData;
32
31
  export declare const PageDataContext: React.Context<{
@@ -35,10 +34,6 @@ export declare const PageDataContext: React.Context<{
35
34
  }>;
36
35
  export declare function PageDataContextProvider(props: React.PropsWithChildren<PageData>): import("react/jsx-runtime").JSX.Element;
37
36
  type PageContainerBarProps = {
38
- /**
39
- * The default title of the page.
40
- */
41
- defaultTitle?: string;
42
37
  /**
43
38
  * The components used for each slot inside.
44
39
  */
@@ -47,6 +42,10 @@ type PageContainerBarProps = {
47
42
  * The props used for each slot inside.
48
43
  */
49
44
  slotProps?: PageContainerSlotProps;
45
+ /**
46
+ * The component that renders the actions toolbar.
47
+ */
48
+ titleBar?: false | ((title: string) => React.ReactNode);
50
49
  };
51
50
  export type PageContainerProps = React.PropsWithChildren<StackProps & PageContainerBarProps>;
52
51
  /**
@@ -64,7 +64,7 @@ function reducer(state, action) {
64
64
  let key;
65
65
  let isSame = true;
66
66
  for (key in action) {
67
- if (action[key] !== state[key]) {
67
+ if (action[key] != state[key]) {
68
68
  isSame = false;
69
69
  break;
70
70
  }
@@ -76,38 +76,30 @@ function reducer(state, action) {
76
76
  }
77
77
  function PageDataContextProvider(props) {
78
78
  // Destruct
79
- const { title, page, breadcrumbs, noBreadcrumbs, noPageHeader, ...rest } = props;
79
+ const { title, page, breadcrumbs, pageHeader, ...rest } = props;
80
80
  // useReducer hook to manage state with our reducer function and initial state
81
81
  const [state, dispatch] = React.useReducer(reducer, {
82
82
  title,
83
83
  page,
84
84
  breadcrumbs,
85
- noBreadcrumbs,
86
- noPageHeader
85
+ pageHeader
87
86
  });
88
87
  // Provide the state and dispatch function to the context value
89
88
  return (0, jsx_runtime_1.jsx)(exports.PageDataContext.Provider, { value: { state, dispatch }, ...rest });
90
89
  }
91
90
  function PageContainerBar(props) {
92
- const { defaultTitle, slots, slotProps } = props;
91
+ const { slots, slotProps, titleBar } = props;
93
92
  const { state } = React.useContext(exports.PageDataContext);
94
93
  const activePage = (0, useActivePage_1.useActivePage)();
95
- React.useLayoutEffect(() => {
96
- // Reset the state without rerendering
97
- state.breadcrumbs = undefined;
98
- state.noBreadcrumbs = undefined;
99
- state.noPageHeader = undefined;
100
- state.page = undefined;
101
- state.title = undefined;
94
+ React.useEffect(() => {
95
+ return () => {
96
+ // Reset the state when the component unmounts
97
+ state.breadcrumbs = undefined;
98
+ state.page = undefined;
99
+ state.pageHeader = undefined;
100
+ state.title = undefined;
101
+ };
102
102
  }, [activePage?.sourcePath]);
103
- let resolvedBreadcrumbs = state.breadcrumbs ?? activePage?.breadcrumbs ?? [];
104
- const title = state.title ?? defaultTitle ?? activePage?.title ?? "";
105
- if (state.page) {
106
- resolvedBreadcrumbs = [
107
- ...resolvedBreadcrumbs,
108
- { title: state.page, path: "#" }
109
- ];
110
- }
111
103
  const ToolbarComponent = slots?.toolbar ?? PageContainerToolbar_1.PageContainerToolbar;
112
104
  const toolbarSlotProps = (0, useSlotProps_1.default)({
113
105
  elementType: ToolbarComponent,
@@ -115,11 +107,21 @@ function PageContainerBar(props) {
115
107
  externalSlotProps: slotProps?.toolbar,
116
108
  additionalProps: {}
117
109
  });
118
- return state.noPageHeader !== true ? ((0, jsx_runtime_1.jsxs)(Stack_1.default, { children: [state.noBreadcrumbs !== true && ((0, jsx_runtime_1.jsx)(Breadcrumbs_1.default, { "aria-label": "breadcrumb", children: resolvedBreadcrumbs
119
- ? resolvedBreadcrumbs.map((item, index) => {
120
- return index < resolvedBreadcrumbs.length - 1 ? ((0, jsx_runtime_1.jsx)(Link_1.default, { component: Link_2.Link, underline: "hover", color: "inherit", href: item.path, children: (0, navigation_1.getItemTitle)(item) }, item.path)) : ((0, jsx_runtime_1.jsx)(Typography_1.default, { color: "text.primary", children: (0, navigation_1.getItemTitle)(item) }, item.path));
121
- })
122
- : null })), (0, jsx_runtime_1.jsxs)(PageContentHeader, { children: [title ? (0, jsx_runtime_1.jsx)(Typography_1.default, { variant: "h4", children: title }) : null, (0, jsx_runtime_1.jsx)(ToolbarComponent, { ...toolbarSlotProps })] })] })) : undefined;
110
+ const breadcrumbs = state.breadcrumbs ?? activePage?.breadcrumbs ?? [];
111
+ const title = state.title ?? activePage?.title ?? "";
112
+ const pageHeader = state.pageHeader ?? activePage?.pageHeader ?? null;
113
+ // No page header
114
+ if (pageHeader === false)
115
+ return undefined;
116
+ // Custom page header
117
+ if (pageHeader != null)
118
+ return pageHeader;
119
+ if (state.page) {
120
+ breadcrumbs.push({ title: state.page, path: "#" });
121
+ }
122
+ return ((0, jsx_runtime_1.jsxs)(Stack_1.default, { children: [breadcrumbs && ((0, jsx_runtime_1.jsx)(Breadcrumbs_1.default, { "aria-label": "breadcrumb", children: breadcrumbs.map((item, index) => {
123
+ return index < breadcrumbs.length - 1 ? ((0, jsx_runtime_1.jsx)(Link_1.default, { component: Link_2.Link, underline: "hover", color: "inherit", href: item.path, children: (0, navigation_1.getItemTitle)(item) }, item.path)) : ((0, jsx_runtime_1.jsx)(Typography_1.default, { color: "text.primary", children: (0, navigation_1.getItemTitle)(item) }, item.path));
124
+ }) })), (0, jsx_runtime_1.jsxs)(PageContentHeader, { children: [typeof titleBar === "function" ? (titleBar(title)) : titleBar === false ? undefined : ((0, jsx_runtime_1.jsx)(Typography_1.default, { variant: "h4", children: title })), (0, jsx_runtime_1.jsx)(ToolbarComponent, { ...toolbarSlotProps })] })] }));
123
125
  }
124
126
  /**
125
127
  * A container component to provide a title and breadcrumbs for your pages.
@@ -133,6 +135,6 @@ function PageContainerBar(props) {
133
135
  * - [PageContainer API](https://mui.com/toolpad/core/api/page-container)
134
136
  */
135
137
  function PageContainer(props) {
136
- const { children, defaultTitle, slots, slotProps, ...rest } = props;
137
- return ((0, jsx_runtime_1.jsxs)(Stack_1.default, { sx: { mx: 3, my: 2 }, spacing: 2, ...rest, children: [(0, jsx_runtime_1.jsx)(PageContainerBar, { defaultTitle: defaultTitle, slots: slots, slotProps: slotProps }), children] }));
138
+ const { children, slots, slotProps, titleBar, ...rest } = props;
139
+ return ((0, jsx_runtime_1.jsxs)(Stack_1.default, { sx: { mx: 3, my: 2 }, spacing: 2, ...rest, children: [(0, jsx_runtime_1.jsx)(PageContainerBar, { slots: slots, slotProps: slotProps, titleBar: titleBar }), children] }));
138
140
  }
@@ -1,8 +1,10 @@
1
+ import * as React from "react";
1
2
  import type { Breadcrumb } from "../PageContainer";
2
3
  export interface ActivePage {
3
4
  title: string;
4
5
  path: string;
5
6
  sourcePath: string;
6
7
  breadcrumbs: Breadcrumb[];
8
+ pageHeader?: React.ReactNode;
7
9
  }
8
10
  export declare function useActivePage(): ActivePage | null;
@@ -41,14 +41,13 @@ const navigation_1 = require("../shared/navigation");
41
41
  function useActivePage() {
42
42
  const navigationContext = React.useContext(context_1.NavigationContext);
43
43
  const routerContext = React.useContext(context_1.RouterContext);
44
- const pageRef = React.useRef(null);
45
- let pathname = routerContext?.pathname ?? "/";
44
+ const pathname = routerContext?.pathname ?? "/";
46
45
  const activeItem = (0, navigation_1.matchPath)(navigationContext, pathname);
47
46
  const rootItem = (0, navigation_1.matchPath)(navigationContext, "/");
48
- if (!activeItem) {
49
- pageRef.current = null;
50
- }
51
- else {
47
+ return React.useMemo(() => {
48
+ if (!activeItem) {
49
+ return null;
50
+ }
52
51
  const breadcrumbs = [];
53
52
  if (rootItem) {
54
53
  breadcrumbs.push({
@@ -74,19 +73,12 @@ function useActivePage() {
74
73
  });
75
74
  }
76
75
  }
77
- const title = (0, navigation_1.getItemTitle)(activeItem);
78
- const path = (0, navigation_1.getItemPath)(navigationContext, activeItem);
79
- if (pageRef.current == null ||
80
- pageRef.current.title !== title ||
81
- pageRef.current.path !== path ||
82
- pageRef.current.sourcePath !== pathname) {
83
- pageRef.current = {
84
- title,
85
- path,
86
- sourcePath: pathname,
87
- breadcrumbs
88
- };
89
- }
90
- }
91
- return pageRef.current;
76
+ return {
77
+ title: (0, navigation_1.getItemTitle)(activeItem),
78
+ path: (0, navigation_1.getItemPath)(navigationContext, activeItem),
79
+ sourcePath: pathname,
80
+ breadcrumbs,
81
+ pageHeader: activeItem.pageHeader
82
+ };
83
+ }, [activeItem, rootItem, pathname, navigationContext]);
92
84
  }
@@ -28,6 +28,7 @@ export interface NavigationPageItem {
28
28
  action?: React.ReactNode;
29
29
  children?: Navigation;
30
30
  hidden?: boolean;
31
+ pageHeader?: React.ReactNode;
31
32
  }
32
33
  export interface NavigationSubheaderItem {
33
34
  kind: "header";
@@ -25,8 +25,7 @@ export type PageData = {
25
25
  title?: string;
26
26
  page?: string;
27
27
  breadcrumbs?: Breadcrumb[];
28
- noBreadcrumbs?: boolean;
29
- noPageHeader?: boolean;
28
+ pageHeader?: React.ReactNode;
30
29
  };
31
30
  type PageDataAction = PageData;
32
31
  export declare const PageDataContext: React.Context<{
@@ -35,10 +34,6 @@ export declare const PageDataContext: React.Context<{
35
34
  }>;
36
35
  export declare function PageDataContextProvider(props: React.PropsWithChildren<PageData>): import("react/jsx-runtime").JSX.Element;
37
36
  type PageContainerBarProps = {
38
- /**
39
- * The default title of the page.
40
- */
41
- defaultTitle?: string;
42
37
  /**
43
38
  * The components used for each slot inside.
44
39
  */
@@ -47,6 +42,10 @@ type PageContainerBarProps = {
47
42
  * The props used for each slot inside.
48
43
  */
49
44
  slotProps?: PageContainerSlotProps;
45
+ /**
46
+ * The component that renders the actions toolbar.
47
+ */
48
+ titleBar?: false | ((title: string) => React.ReactNode);
50
49
  };
51
50
  export type PageContainerProps = React.PropsWithChildren<StackProps & PageContainerBarProps>;
52
51
  /**
@@ -23,7 +23,7 @@ function reducer(state, action) {
23
23
  let key;
24
24
  let isSame = true;
25
25
  for (key in action) {
26
- if (action[key] !== state[key]) {
26
+ if (action[key] != state[key]) {
27
27
  isSame = false;
28
28
  break;
29
29
  }
@@ -35,38 +35,30 @@ function reducer(state, action) {
35
35
  }
36
36
  export function PageDataContextProvider(props) {
37
37
  // Destruct
38
- const { title, page, breadcrumbs, noBreadcrumbs, noPageHeader, ...rest } = props;
38
+ const { title, page, breadcrumbs, pageHeader, ...rest } = props;
39
39
  // useReducer hook to manage state with our reducer function and initial state
40
40
  const [state, dispatch] = React.useReducer(reducer, {
41
41
  title,
42
42
  page,
43
43
  breadcrumbs,
44
- noBreadcrumbs,
45
- noPageHeader
44
+ pageHeader
46
45
  });
47
46
  // Provide the state and dispatch function to the context value
48
47
  return _jsx(PageDataContext.Provider, { value: { state, dispatch }, ...rest });
49
48
  }
50
49
  function PageContainerBar(props) {
51
- const { defaultTitle, slots, slotProps } = props;
50
+ const { slots, slotProps, titleBar } = props;
52
51
  const { state } = React.useContext(PageDataContext);
53
52
  const activePage = useActivePage();
54
- React.useLayoutEffect(() => {
55
- // Reset the state without rerendering
56
- state.breadcrumbs = undefined;
57
- state.noBreadcrumbs = undefined;
58
- state.noPageHeader = undefined;
59
- state.page = undefined;
60
- state.title = undefined;
53
+ React.useEffect(() => {
54
+ return () => {
55
+ // Reset the state when the component unmounts
56
+ state.breadcrumbs = undefined;
57
+ state.page = undefined;
58
+ state.pageHeader = undefined;
59
+ state.title = undefined;
60
+ };
61
61
  }, [activePage?.sourcePath]);
62
- let resolvedBreadcrumbs = state.breadcrumbs ?? activePage?.breadcrumbs ?? [];
63
- const title = state.title ?? defaultTitle ?? activePage?.title ?? "";
64
- if (state.page) {
65
- resolvedBreadcrumbs = [
66
- ...resolvedBreadcrumbs,
67
- { title: state.page, path: "#" }
68
- ];
69
- }
70
62
  const ToolbarComponent = slots?.toolbar ?? PageContainerToolbar;
71
63
  const toolbarSlotProps = useSlotProps({
72
64
  elementType: ToolbarComponent,
@@ -74,11 +66,21 @@ function PageContainerBar(props) {
74
66
  externalSlotProps: slotProps?.toolbar,
75
67
  additionalProps: {}
76
68
  });
77
- return state.noPageHeader !== true ? (_jsxs(Stack, { children: [state.noBreadcrumbs !== true && (_jsx(Breadcrumbs, { "aria-label": "breadcrumb", children: resolvedBreadcrumbs
78
- ? resolvedBreadcrumbs.map((item, index) => {
79
- return index < resolvedBreadcrumbs.length - 1 ? (_jsx(Link, { component: ToolpadLink, underline: "hover", color: "inherit", href: item.path, children: getItemTitle(item) }, item.path)) : (_jsx(Typography, { color: "text.primary", children: getItemTitle(item) }, item.path));
80
- })
81
- : null })), _jsxs(PageContentHeader, { children: [title ? _jsx(Typography, { variant: "h4", children: title }) : null, _jsx(ToolbarComponent, { ...toolbarSlotProps })] })] })) : undefined;
69
+ const breadcrumbs = state.breadcrumbs ?? activePage?.breadcrumbs ?? [];
70
+ const title = state.title ?? activePage?.title ?? "";
71
+ const pageHeader = state.pageHeader ?? activePage?.pageHeader ?? null;
72
+ // No page header
73
+ if (pageHeader === false)
74
+ return undefined;
75
+ // Custom page header
76
+ if (pageHeader != null)
77
+ return pageHeader;
78
+ if (state.page) {
79
+ breadcrumbs.push({ title: state.page, path: "#" });
80
+ }
81
+ return (_jsxs(Stack, { children: [breadcrumbs && (_jsx(Breadcrumbs, { "aria-label": "breadcrumb", children: breadcrumbs.map((item, index) => {
82
+ return index < breadcrumbs.length - 1 ? (_jsx(Link, { component: ToolpadLink, underline: "hover", color: "inherit", href: item.path, children: getItemTitle(item) }, item.path)) : (_jsx(Typography, { color: "text.primary", children: getItemTitle(item) }, item.path));
83
+ }) })), _jsxs(PageContentHeader, { children: [typeof titleBar === "function" ? (titleBar(title)) : titleBar === false ? undefined : (_jsx(Typography, { variant: "h4", children: title })), _jsx(ToolbarComponent, { ...toolbarSlotProps })] })] }));
82
84
  }
83
85
  /**
84
86
  * A container component to provide a title and breadcrumbs for your pages.
@@ -92,7 +94,7 @@ function PageContainerBar(props) {
92
94
  * - [PageContainer API](https://mui.com/toolpad/core/api/page-container)
93
95
  */
94
96
  function PageContainer(props) {
95
- const { children, defaultTitle, slots, slotProps, ...rest } = props;
96
- return (_jsxs(Stack, { sx: { mx: 3, my: 2 }, spacing: 2, ...rest, children: [_jsx(PageContainerBar, { defaultTitle: defaultTitle, slots: slots, slotProps: slotProps }), children] }));
97
+ const { children, slots, slotProps, titleBar, ...rest } = props;
98
+ return (_jsxs(Stack, { sx: { mx: 3, my: 2 }, spacing: 2, ...rest, children: [_jsx(PageContainerBar, { slots: slots, slotProps: slotProps, titleBar: titleBar }), children] }));
97
99
  }
98
100
  export { PageContainer };
@@ -1,8 +1,10 @@
1
+ import * as React from "react";
1
2
  import type { Breadcrumb } from "../PageContainer";
2
3
  export interface ActivePage {
3
4
  title: string;
4
5
  path: string;
5
6
  sourcePath: string;
6
7
  breadcrumbs: Breadcrumb[];
8
+ pageHeader?: React.ReactNode;
7
9
  }
8
10
  export declare function useActivePage(): ActivePage | null;
@@ -5,14 +5,13 @@ import { getItemPath, getItemTitle, matchPath } from "../shared/navigation";
5
5
  export function useActivePage() {
6
6
  const navigationContext = React.useContext(NavigationContext);
7
7
  const routerContext = React.useContext(RouterContext);
8
- const pageRef = React.useRef(null);
9
- let pathname = routerContext?.pathname ?? "/";
8
+ const pathname = routerContext?.pathname ?? "/";
10
9
  const activeItem = matchPath(navigationContext, pathname);
11
10
  const rootItem = matchPath(navigationContext, "/");
12
- if (!activeItem) {
13
- pageRef.current = null;
14
- }
15
- else {
11
+ return React.useMemo(() => {
12
+ if (!activeItem) {
13
+ return null;
14
+ }
16
15
  const breadcrumbs = [];
17
16
  if (rootItem) {
18
17
  breadcrumbs.push({
@@ -38,19 +37,12 @@ export function useActivePage() {
38
37
  });
39
38
  }
40
39
  }
41
- const title = getItemTitle(activeItem);
42
- const path = getItemPath(navigationContext, activeItem);
43
- if (pageRef.current == null ||
44
- pageRef.current.title !== title ||
45
- pageRef.current.path !== path ||
46
- pageRef.current.sourcePath !== pathname) {
47
- pageRef.current = {
48
- title,
49
- path,
50
- sourcePath: pathname,
51
- breadcrumbs
52
- };
53
- }
54
- }
55
- return pageRef.current;
40
+ return {
41
+ title: getItemTitle(activeItem),
42
+ path: getItemPath(navigationContext, activeItem),
43
+ sourcePath: pathname,
44
+ breadcrumbs,
45
+ pageHeader: activeItem.pageHeader
46
+ };
47
+ }, [activeItem, rootItem, pathname, navigationContext]);
56
48
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/toolpad",
3
- "version": "1.0.29",
3
+ "version": "1.0.31",
4
4
  "author": "ETSOO",
5
5
  "description": "Dashboard framework extention based on Toolpad Core",
6
6
  "main": "build/cjs/index.js",
@@ -36,6 +36,7 @@ export interface NavigationPageItem {
36
36
  action?: React.ReactNode;
37
37
  children?: Navigation;
38
38
  hidden?: boolean;
39
+ pageHeader?: React.ReactNode;
39
40
  }
40
41
 
41
42
  export interface NavigationSubheaderItem {
@@ -48,8 +48,7 @@ export type PageData = {
48
48
  title?: string;
49
49
  page?: string;
50
50
  breadcrumbs?: Breadcrumb[];
51
- noBreadcrumbs?: boolean;
52
- noPageHeader?: boolean;
51
+ pageHeader?: React.ReactNode;
53
52
  };
54
53
 
55
54
  type PageDataAction = PageData;
@@ -64,7 +63,7 @@ function reducer(state: PageData, action: PageDataAction) {
64
63
  let key: keyof PageDataAction;
65
64
  let isSame = true;
66
65
  for (key in action) {
67
- if (action[key] !== state[key]) {
66
+ if (action[key] != state[key]) {
68
67
  isSame = false;
69
68
  break;
70
69
  }
@@ -81,16 +80,14 @@ export function PageDataContextProvider(
81
80
  props: React.PropsWithChildren<PageData>
82
81
  ) {
83
82
  // Destruct
84
- const { title, page, breadcrumbs, noBreadcrumbs, noPageHeader, ...rest } =
85
- props;
83
+ const { title, page, breadcrumbs, pageHeader, ...rest } = props;
86
84
 
87
85
  // useReducer hook to manage state with our reducer function and initial state
88
86
  const [state, dispatch] = React.useReducer(reducer, {
89
87
  title,
90
88
  page,
91
89
  breadcrumbs,
92
- noBreadcrumbs,
93
- noPageHeader
90
+ pageHeader
94
91
  });
95
92
 
96
93
  // Provide the state and dispatch function to the context value
@@ -98,46 +95,39 @@ export function PageDataContextProvider(
98
95
  }
99
96
 
100
97
  type PageContainerBarProps = {
101
- /**
102
- * The default title of the page.
103
- */
104
- defaultTitle?: string;
105
98
  /**
106
99
  * The components used for each slot inside.
107
100
  */
108
101
  slots?: PageContainerSlots;
102
+
109
103
  /**
110
104
  * The props used for each slot inside.
111
105
  */
112
106
  slotProps?: PageContainerSlotProps;
107
+
108
+ /**
109
+ * The component that renders the actions toolbar.
110
+ */
111
+ titleBar?: false | ((title: string) => React.ReactNode);
113
112
  };
114
113
 
115
114
  function PageContainerBar(props: PageContainerBarProps) {
116
- const { defaultTitle, slots, slotProps } = props;
115
+ const { slots, slotProps, titleBar } = props;
117
116
 
118
117
  const { state } = React.useContext(PageDataContext);
119
118
 
120
119
  const activePage = useActivePage();
121
120
 
122
- React.useLayoutEffect(() => {
123
- // Reset the state without rerendering
124
- state.breadcrumbs = undefined;
125
- state.noBreadcrumbs = undefined;
126
- state.noPageHeader = undefined;
127
- state.page = undefined;
128
- state.title = undefined;
121
+ React.useEffect(() => {
122
+ return () => {
123
+ // Reset the state when the component unmounts
124
+ state.breadcrumbs = undefined;
125
+ state.page = undefined;
126
+ state.pageHeader = undefined;
127
+ state.title = undefined;
128
+ };
129
129
  }, [activePage?.sourcePath]);
130
130
 
131
- let resolvedBreadcrumbs = state.breadcrumbs ?? activePage?.breadcrumbs ?? [];
132
- const title = state.title ?? defaultTitle ?? activePage?.title ?? "";
133
-
134
- if (state.page) {
135
- resolvedBreadcrumbs = [
136
- ...resolvedBreadcrumbs,
137
- { title: state.page, path: "#" }
138
- ];
139
- }
140
-
141
131
  const ToolbarComponent = slots?.toolbar ?? PageContainerToolbar;
142
132
  const toolbarSlotProps = useSlotProps({
143
133
  elementType: ToolbarComponent,
@@ -146,37 +136,53 @@ function PageContainerBar(props: PageContainerBarProps) {
146
136
  additionalProps: {}
147
137
  });
148
138
 
149
- return state.noPageHeader !== true ? (
139
+ const breadcrumbs = state.breadcrumbs ?? activePage?.breadcrumbs ?? [];
140
+ const title = state.title ?? activePage?.title ?? "";
141
+ const pageHeader = state.pageHeader ?? activePage?.pageHeader ?? null;
142
+
143
+ // No page header
144
+ if (pageHeader === false) return undefined;
145
+
146
+ // Custom page header
147
+ if (pageHeader != null) return pageHeader;
148
+
149
+ if (state.page) {
150
+ breadcrumbs.push({ title: state.page, path: "#" });
151
+ }
152
+
153
+ return (
150
154
  <Stack>
151
- {state.noBreadcrumbs !== true && (
155
+ {breadcrumbs && (
152
156
  <Breadcrumbs aria-label="breadcrumb">
153
- {resolvedBreadcrumbs
154
- ? resolvedBreadcrumbs.map((item, index) => {
155
- return index < resolvedBreadcrumbs.length - 1 ? (
156
- <Link
157
- key={item.path}
158
- component={ToolpadLink}
159
- underline="hover"
160
- color="inherit"
161
- href={item.path}
162
- >
163
- {getItemTitle(item)}
164
- </Link>
165
- ) : (
166
- <Typography key={item.path} color="text.primary">
167
- {getItemTitle(item)}
168
- </Typography>
169
- );
170
- })
171
- : null}
157
+ {breadcrumbs.map((item, index) => {
158
+ return index < breadcrumbs.length - 1 ? (
159
+ <Link
160
+ key={item.path}
161
+ component={ToolpadLink}
162
+ underline="hover"
163
+ color="inherit"
164
+ href={item.path}
165
+ >
166
+ {getItemTitle(item)}
167
+ </Link>
168
+ ) : (
169
+ <Typography key={item.path} color="text.primary">
170
+ {getItemTitle(item)}
171
+ </Typography>
172
+ );
173
+ })}
172
174
  </Breadcrumbs>
173
175
  )}
174
176
  <PageContentHeader>
175
- {title ? <Typography variant="h4">{title}</Typography> : null}
177
+ {typeof titleBar === "function" ? (
178
+ titleBar(title)
179
+ ) : titleBar === false ? undefined : (
180
+ <Typography variant="h4">{title}</Typography>
181
+ )}
176
182
  <ToolbarComponent {...toolbarSlotProps} />
177
183
  </PageContentHeader>
178
184
  </Stack>
179
- ) : undefined;
185
+ );
180
186
  }
181
187
 
182
188
  export type PageContainerProps = React.PropsWithChildren<
@@ -195,14 +201,14 @@ export type PageContainerProps = React.PropsWithChildren<
195
201
  * - [PageContainer API](https://mui.com/toolpad/core/api/page-container)
196
202
  */
197
203
  function PageContainer(props: PageContainerProps) {
198
- const { children, defaultTitle, slots, slotProps, ...rest } = props;
204
+ const { children, slots, slotProps, titleBar, ...rest } = props;
199
205
 
200
206
  return (
201
207
  <Stack sx={{ mx: 3, my: 2 }} spacing={2} {...rest}>
202
208
  <PageContainerBar
203
- defaultTitle={defaultTitle}
204
209
  slots={slots}
205
210
  slotProps={slotProps}
211
+ titleBar={titleBar}
206
212
  />
207
213
  {children}
208
214
  </Stack>
@@ -9,22 +9,23 @@ export interface ActivePage {
9
9
  path: string;
10
10
  sourcePath: string;
11
11
  breadcrumbs: Breadcrumb[];
12
+ pageHeader?: React.ReactNode;
12
13
  }
13
14
 
14
15
  export function useActivePage(): ActivePage | null {
15
16
  const navigationContext = React.useContext(NavigationContext);
16
17
  const routerContext = React.useContext(RouterContext);
17
18
 
18
- const pageRef = React.useRef<ActivePage>(null);
19
-
20
- let pathname = routerContext?.pathname ?? "/";
19
+ const pathname = routerContext?.pathname ?? "/";
21
20
  const activeItem = matchPath(navigationContext, pathname);
22
21
 
23
22
  const rootItem = matchPath(navigationContext, "/");
24
23
 
25
- if (!activeItem) {
26
- pageRef.current = null;
27
- } else {
24
+ return React.useMemo(() => {
25
+ if (!activeItem) {
26
+ return null;
27
+ }
28
+
28
29
  const breadcrumbs: Breadcrumb[] = [];
29
30
 
30
31
  if (rootItem) {
@@ -53,23 +54,12 @@ export function useActivePage(): ActivePage | null {
53
54
  }
54
55
  }
55
56
 
56
- const title = getItemTitle(activeItem);
57
- const path = getItemPath(navigationContext, activeItem);
58
-
59
- if (
60
- pageRef.current == null ||
61
- pageRef.current.title !== title ||
62
- pageRef.current.path !== path ||
63
- pageRef.current.sourcePath !== pathname
64
- ) {
65
- pageRef.current = {
66
- title,
67
- path,
68
- sourcePath: pathname,
69
- breadcrumbs
70
- };
71
- }
72
- }
73
-
74
- return pageRef.current;
57
+ return {
58
+ title: getItemTitle(activeItem),
59
+ path: getItemPath(navigationContext, activeItem),
60
+ sourcePath: pathname,
61
+ breadcrumbs,
62
+ pageHeader: activeItem.pageHeader
63
+ };
64
+ }, [activeItem, rootItem, pathname, navigationContext]);
75
65
  }