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,