From a635ac114a21a80ffa3c7e1591da789706130ec0 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Wed, 8 Mar 2023 11:54:28 -0500 Subject: [PATCH 1/4] Move initial PageDrawer implementation from tackle2-ui Signed-off-by: Mike Turley --- .../PageDrawer/PageDrawer.stories.mdx | 0 src/components/PageDrawer/PageDrawer.test.tsx | 0 src/components/PageDrawer/PageDrawer.tsx | 153 ++++++++++++++++++ src/components/PageDrawer/index.ts | 1 + 4 files changed, 154 insertions(+) create mode 100644 src/components/PageDrawer/PageDrawer.stories.mdx create mode 100644 src/components/PageDrawer/PageDrawer.test.tsx create mode 100644 src/components/PageDrawer/PageDrawer.tsx create mode 100644 src/components/PageDrawer/index.ts diff --git a/src/components/PageDrawer/PageDrawer.stories.mdx b/src/components/PageDrawer/PageDrawer.stories.mdx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/PageDrawer/PageDrawer.test.tsx b/src/components/PageDrawer/PageDrawer.test.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/PageDrawer/PageDrawer.tsx b/src/components/PageDrawer/PageDrawer.tsx new file mode 100644 index 0000000..7ceea46 --- /dev/null +++ b/src/components/PageDrawer/PageDrawer.tsx @@ -0,0 +1,153 @@ +import * as React from 'react'; +import { + Drawer, + DrawerActions, + DrawerCloseButton, + DrawerContent, + DrawerContentBody, + DrawerHead, + DrawerPanelContent, +} from '@patternfly/react-core'; +import pageStyles from '@patternfly/react-styles/css/components/Page/page'; + +const usePageDrawerState = () => { + const [isDrawerMounted, setIsDrawerMounted] = React.useState(false); + const [isDrawerExpanded, setIsDrawerExpanded] = React.useState(false); + const [drawerChildren, setDrawerChildren] = React.useState(null); + const drawerFocusRef = React.useRef(document.createElement('span')); + return { + isDrawerMounted, + setIsDrawerMounted, + isDrawerExpanded, + setIsDrawerExpanded, + drawerChildren, + setDrawerChildren, + drawerFocusRef: drawerFocusRef as typeof drawerFocusRef | null, + }; +}; + +type PageDrawerState = ReturnType; + +const PageDrawerContext = React.createContext({ + isDrawerMounted: false, + setIsDrawerMounted: () => {}, + isDrawerExpanded: false, + setIsDrawerExpanded: () => {}, + drawerChildren: null, + setDrawerChildren: () => {}, + drawerFocusRef: null, +}); + +// PageContentWithDrawerProvider should only be rendered as the direct child of a PatternFly Page component. +interface IPageContentWithDrawerProviderProps { + children: React.ReactNode; // The entire content of the page. See usage in client/src/app/layout/DefaultLayout. +} +export const PageContentWithDrawerProvider: React.FC = ({ + children, +}) => { + const pageDrawerState = usePageDrawerState(); + const { isDrawerMounted, isDrawerExpanded, drawerFocusRef, drawerChildren } = pageDrawerState; + return ( + + {isDrawerMounted ? ( +
+ drawerFocusRef?.current?.focus()} + position="right" + > + + {drawerChildren} + + } + > + {children} + + +
+ ) : ( + children + )} +
+ ); +}; + +let numPageDrawerContentInstances = 0; + +// PageDrawerContent can be rendered anywhere, but must have only one instance rendered at a time. +export interface IPageDrawerContentProps { + isExpanded: boolean; + onCloseClick: () => void; // Should be used to update local state such that `isExpanded` becomes false. + children: React.ReactNode; // The content to show in the drawer when `isExpanded` is true. + focusKey?: string | number; // A unique key representing the object being described in the drawer. When this changes, the drawer will regain focus. +} + +export const PageDrawerContent: React.FC = ({ + isExpanded: localIsExpandedProp, + onCloseClick, + children, + focusKey, +}) => { + const { + setIsDrawerMounted, + setIsDrawerExpanded, + drawerFocusRef, + setDrawerChildren, + } = React.useContext(PageDrawerContext); + + // Prevent Drawer boilerplate from being rendered in PageContentWithDrawerProvider if no PageDrawerContent exists. + // Also, warn if we are trying to render more than one PageDrawerContent (they'll fight over the same state). + React.useEffect(() => { + numPageDrawerContentInstances++; + setIsDrawerMounted(true); + return () => { + numPageDrawerContentInstances--; + setIsDrawerMounted(false); + }; + }, []); + if (numPageDrawerContentInstances > 1) { + console.warn( + `${numPageDrawerContentInstances} instances of PageDrawerContent are currently rendered! Only one instance of this component should be rendered at a time.` + ); + } + + // Lift the value of isExpanded out to the context, but derive it from local state such as a selected table row. + // This is the ONLY place where `setIsDrawerExpanded` should be called. + // To expand/collapse the drawer, use the `isExpanded` prop when rendering PageDrawerContent. + React.useEffect(() => { + setIsDrawerExpanded(localIsExpandedProp); + return () => { + setIsDrawerExpanded(false); + }; + }, [localIsExpandedProp]); + + // If the drawer is already expanded describing app A, then the user clicks app B, we want to send focus back to the drawer. + React.useEffect(() => { + drawerFocusRef?.current?.focus(); + }, [focusKey]); + + React.useEffect(() => { + setDrawerChildren( + + + {children} + + + + + + ); + }, [children]); + return null; +}; diff --git a/src/components/PageDrawer/index.ts b/src/components/PageDrawer/index.ts new file mode 100644 index 0000000..031897d --- /dev/null +++ b/src/components/PageDrawer/index.ts @@ -0,0 +1 @@ +export * from './PageDrawer'; From 00e6e9105d2e17e82d04339f30999361dc700cfc Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Wed, 8 Mar 2023 13:45:35 -0500 Subject: [PATCH 2/4] Address lint errors and fix tabIndex Signed-off-by: Mike Turley --- src/components/PageDrawer/PageDrawer.tsx | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/components/PageDrawer/PageDrawer.tsx b/src/components/PageDrawer/PageDrawer.tsx index 7ceea46..b604a4a 100644 --- a/src/components/PageDrawer/PageDrawer.tsx +++ b/src/components/PageDrawer/PageDrawer.tsx @@ -30,10 +30,13 @@ type PageDrawerState = ReturnType; const PageDrawerContext = React.createContext({ isDrawerMounted: false, + // eslint-disable-next-line @typescript-eslint/no-empty-function setIsDrawerMounted: () => {}, isDrawerExpanded: false, + // eslint-disable-next-line @typescript-eslint/no-empty-function setIsDrawerExpanded: () => {}, drawerChildren: null, + // eslint-disable-next-line @typescript-eslint/no-empty-function setDrawerChildren: () => {}, drawerFocusRef: null, }); @@ -81,15 +84,15 @@ export const PageContentWithDrawerProvider: React.FC void; // Should be used to update local state such that `isExpanded` becomes false. children: React.ReactNode; // The content to show in the drawer when `isExpanded` is true. focusKey?: string | number; // A unique key representing the object being described in the drawer. When this changes, the drawer will regain focus. } -export const PageDrawerContent: React.FC = ({ +export const PageDrawer: React.FC = ({ isExpanded: localIsExpandedProp, onCloseClick, children, @@ -126,17 +129,20 @@ export const PageDrawerContent: React.FC = ({ return () => { setIsDrawerExpanded(false); }; - }, [localIsExpandedProp]); + }, [localIsExpandedProp, setIsDrawerExpanded]); // If the drawer is already expanded describing app A, then the user clicks app B, we want to send focus back to the drawer. React.useEffect(() => { drawerFocusRef?.current?.focus(); - }, [focusKey]); + }, [drawerFocusRef, focusKey]); React.useEffect(() => { + console.log( + 'DRAWER CHILDREN UPDATING -- do we need to memoize onCloseClick or break useEffect deps array rules?' + ); setDrawerChildren( - + {children} @@ -148,6 +154,6 @@ export const PageDrawerContent: React.FC = ({ ); - }, [children]); + }, [children, drawerFocusRef, localIsExpandedProp, onCloseClick, setDrawerChildren]); return null; }; From 91f55c50bd8294e039ee56b2ad611b86e94505ed Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Tue, 14 Mar 2023 11:03:26 -0400 Subject: [PATCH 3/4] Reapply Prettier Signed-off-by: Mike Turley --- src/components/PageDrawer/PageDrawer.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/PageDrawer/PageDrawer.tsx b/src/components/PageDrawer/PageDrawer.tsx index b604a4a..269e93d 100644 --- a/src/components/PageDrawer/PageDrawer.tsx +++ b/src/components/PageDrawer/PageDrawer.tsx @@ -98,12 +98,8 @@ export const PageDrawer: React.FC = ({ children, focusKey, }) => { - const { - setIsDrawerMounted, - setIsDrawerExpanded, - drawerFocusRef, - setDrawerChildren, - } = React.useContext(PageDrawerContext); + const { setIsDrawerMounted, setIsDrawerExpanded, drawerFocusRef, setDrawerChildren } = + React.useContext(PageDrawerContext); // Prevent Drawer boilerplate from being rendered in PageContentWithDrawerProvider if no PageDrawerContent exists. // Also, warn if we are trying to render more than one PageDrawerContent (they'll fight over the same state). From d0b4cd4f80013cb4e92f1ff84cf4bdfc5dd3eaa6 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Tue, 14 Mar 2023 11:11:01 -0400 Subject: [PATCH 4/4] Fix lint warning Signed-off-by: Mike Turley --- src/components/PageDrawer/PageDrawer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PageDrawer/PageDrawer.tsx b/src/components/PageDrawer/PageDrawer.tsx index 269e93d..4222018 100644 --- a/src/components/PageDrawer/PageDrawer.tsx +++ b/src/components/PageDrawer/PageDrawer.tsx @@ -110,7 +110,7 @@ export const PageDrawer: React.FC = ({ numPageDrawerContentInstances--; setIsDrawerMounted(false); }; - }, []); + }, [setIsDrawerMounted]); if (numPageDrawerContentInstances > 1) { console.warn( `${numPageDrawerContentInstances} instances of PageDrawerContent are currently rendered! Only one instance of this component should be rendered at a time.`