diff --git a/changelogs/upcoming/7663.md b/changelogs/upcoming/7663.md
new file mode 100644
index 00000000000..332eae19c12
--- /dev/null
+++ b/changelogs/upcoming/7663.md
@@ -0,0 +1 @@
+- Refactored `EuiPageTemplate` to use context only to update and pass props to subcomponents
\ No newline at end of file
diff --git a/src-docs/src/views/page_template/examples.tsx b/src-docs/src/views/page_template/examples.tsx
index 043fb6bd47c..f93d5799342 100644
--- a/src-docs/src/views/page_template/examples.tsx
+++ b/src-docs/src/views/page_template/examples.tsx
@@ -270,13 +270,7 @@ export const PageTemplateExample = () => (
iconType="warning"
color="warning"
title="Sidebars must be direct children declared in the same component."
- >
-
- In order for the template configurations to properly account for
- the existence of a sidebar, it needs to clone the element which
- can only be performed on direct children.
-
-
+ />
;
+ section: Partial;
+ header: Partial;
+ emptyPrompt: Partial<_EuiPageEmptyPromptProps>;
+ bottomBar: Partial<_EuiPageBottomBarProps>;
+}
+
+interface PageTemplateContext extends PageTemplateComponentProps {
+ setContext: (context: Partial) => void;
+}
+
+const initialComponentProps: PageTemplateComponentProps = {
+ sidebar: {},
section: {},
header: {},
emptyPrompt: {},
bottomBar: {},
+};
+
+export const TemplateContext = createContext({
+ ...initialComponentProps,
+ setContext: () => {},
});
+/**
+ * Wrapper to attach state and undate function to the page context
+ */
+const TemplateContextProvider: FunctionComponent<{
+ children: ReactNode;
+}> = ({ children }) => {
+ const [context, setContext] = useState(
+ initialComponentProps
+ );
+
+ const updateContext = (
+ nextContext: Partial
+ ): void => {
+ if (isEqual(nextContext, context)) return;
+
+ setContext((currentContext) => ({
+ ...currentContext,
+ ...nextContext,
+ }));
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
export type EuiPageTemplateProps = _EuiPageOuterProps &
// We re-define the `border` prop below to be named more appropriately
Omit<_EuiPageInnerProps, 'border' | 'component'> &
@@ -77,7 +133,7 @@ export type EuiPageTemplateProps = _EuiPageOuterProps &
component?: ComponentTypes;
};
-/**
+/*
* Consumed via `EuiPageTemplate`,
* it controls and propogates most of the shared props per direct child
*/
@@ -149,21 +205,14 @@ export const _EuiPageTemplate: FunctionComponent = ({
const innerBordered = () =>
contentBorder !== undefined ? contentBorder : Boolean(sidebar.length > 0);
- React.Children.toArray(children).forEach((child, index) => {
+ React.Children.toArray(children).forEach((child) => {
if (!React.isValidElement(child)) return; // Skip non-components
if (
- child.type === EuiPageSidebar ||
- child.props.__EMOTION_TYPE_PLEASE_DO_NOT_USE__ === EuiPageSidebar
+ child.type === _EuiPageSidebar ||
+ child.props.__EMOTION_TYPE_PLEASE_DO_NOT_USE__ === _EuiPageSidebar
) {
- sidebar.push(
- React.cloneElement(child, {
- key: `sidebar${index}`,
- ...getSideBarProps(),
- // Allow their props overridden by appending the child props spread at the end
- ...child.props,
- })
- );
+ sidebar.push(child);
} else {
sections.push(child);
}
@@ -178,57 +227,80 @@ export const _EuiPageTemplate: FunctionComponent = ({
...rest.style,
};
- templateContext.header = getHeaderProps();
- templateContext.section = getSectionProps();
- templateContext.emptyPrompt = {
- panelled: innerPanelled() ? true : panelled,
- grow: true,
- };
- templateContext.bottomBar = getBottomBarProps();
+ // using useEffect without dependencies to run on every render while ensuring functionality
+ // of context state update when used outside of the Provider (e.g. in tests)
+ useEffect(() => {
+ templateContext.setContext({
+ sidebar: getSideBarProps(),
+ header: getHeaderProps(),
+ section: getSectionProps(),
+ emptyPrompt: {
+ panelled: innerPanelled() ? true : panelled,
+ grow: true,
+ },
+ bottomBar: getBottomBarProps(),
+ });
+ });
return (
-
-
+ {sidebar}
+
+
- {sidebar}
-
-
- {sections}
-
-
-
+ {sections}
+
+
);
};
+const _EuiPageTemplateComponent: FunctionComponent = (
+ props
+) => {
+ const Component = _EuiPageTemplate;
+
+ return (
+
+
+
+ );
+};
+
+const _EuiPageSidebar: FunctionComponent = (props) => {
+ const { sidebar } = useContext(TemplateContext);
+
+ return ;
+};
+
const _EuiPageSection: FunctionComponent = (props) => {
- const templateContext = useContext(TemplateContext);
+ const { section } = useContext(TemplateContext);
- return ;
+ return ;
};
const _EuiPageHeader: FunctionComponent = (props) => {
- const templateContext = useContext(TemplateContext);
+ const { header } = useContext(TemplateContext);
- return ;
+ return ;
};
const _EuiPageEmptyPrompt: FunctionComponent<_EuiPageEmptyPromptProps> = (
props
) => {
- const templateContext = useContext(TemplateContext);
+ const { emptyPrompt } = useContext(TemplateContext);
- return ;
+ return ;
};
const _EuiPageBottomBar: FunctionComponent<_EuiPageBottomBarProps> = (
@@ -239,8 +311,8 @@ const _EuiPageBottomBar: FunctionComponent<_EuiPageBottomBarProps> = (
return ;
};
-export const EuiPageTemplate = Object.assign(_EuiPageTemplate, {
- Sidebar: EuiPageSidebar,
+export const EuiPageTemplate = Object.assign(_EuiPageTemplateComponent, {
+ Sidebar: _EuiPageSidebar,
Header: _EuiPageHeader,
Section: _EuiPageSection,
BottomBar: _EuiPageBottomBar,