Browse Source

refactor hooks for switching sidebar mode

Yuki Takei 2 years ago
parent
commit
b32b63b543

+ 29 - 45
apps/app/src/components/Sidebar/Sidebar.tsx

@@ -6,10 +6,13 @@ import React, {
 import dynamic from 'next/dynamic';
 
 import { scheduleToPut } from '~/client/services/user-ui-settings';
+import { SidebarMode } from '~/interfaces/ui';
 import {
-  useDrawerMode, useDrawerOpened,
-  useCollapsedMode, useCollapsedContentsOpened,
+  useDrawerOpened,
+  useCollapsedContentsOpened,
   useCurrentProductNavWidth,
+  usePreferCollapsedMode,
+  useSidebarMode,
 } from '~/stores/ui';
 
 import { ResizableArea } from './ResizableArea/ResizableArea';
@@ -34,32 +37,13 @@ const ResizableContainer = memo((props: ResizableContainerProps): JSX.Element =>
 
   const { children } = props;
 
-  const { data: isDrawerMode } = useDrawerMode();
+  const { isDrawerMode, isCollapsedMode, isDockMode } = useSidebarMode();
   const { data: currentProductNavWidth, mutate: mutateProductNavWidth } = useCurrentProductNavWidth();
-  const { data: isCollapsedMode, mutate: mutateCollapsedMode } = useCollapsedMode();
+  const { mutate: mutatePreferCollapsedMode } = usePreferCollapsedMode();
   const { mutate: mutateCollapsedContentsOpened } = useCollapsedContentsOpened();
 
-  const [isResizeDisabled, setResizeDisabled] = useState(false);
   const [resizableAreaWidth, setResizableAreaWidth] = useState<number|undefined>(undefined);
 
-  const toggleDrawerMode = useCallback((bool) => {
-    const isStateModified = isResizeDisabled !== bool;
-    if (!isStateModified) {
-      return;
-    }
-
-    // Drawer <-- Dock
-    if (bool) {
-      // disable resize
-      setResizeDisabled(true);
-    }
-    // Drawer --> Dock
-    else {
-      // enable resize
-      setResizeDisabled(false);
-    }
-  }, [isResizeDisabled]);
-
   const resizeHandler = useCallback((newWidth: number) => {
     setResizableAreaWidth(newWidth);
   }, []);
@@ -70,22 +54,18 @@ const ResizableContainer = memo((props: ResizableContainerProps): JSX.Element =>
   }, [mutateProductNavWidth]);
 
   const collapsedByResizableAreaHandler = useCallback(() => {
-    mutateCollapsedMode(true);
+    mutatePreferCollapsedMode(true);
     mutateCollapsedContentsOpened(false);
     scheduleToPut({ preferCollapsedModeByUser: true });
-  }, [mutateCollapsedContentsOpened, mutateCollapsedMode]);
+  }, [mutateCollapsedContentsOpened, mutatePreferCollapsedMode]);
 
 
-  useEffect(() => {
-    toggleDrawerMode(isDrawerMode);
-  }, [isDrawerMode, toggleDrawerMode]);
-
   // open/close resizable container when drawer mode
   useEffect(() => {
-    if (isDrawerMode) {
+    if (isDrawerMode()) {
       setResizableAreaWidth(undefined);
     }
-    else if (isCollapsedMode) {
+    else if (isCollapsedMode()) {
       setResizableAreaWidth(resizableAreaCollapsedWidth);
     }
     else {
@@ -94,14 +74,12 @@ const ResizableContainer = memo((props: ResizableContainerProps): JSX.Element =>
     }
   }, [currentProductNavWidth, isCollapsedMode, isDrawerMode]);
 
-  const disableResizing = isResizeDisabled || isDrawerMode || isCollapsedMode;
-
   return (
     <ResizableArea
       className="flex-expand-vert"
       width={resizableAreaWidth}
       minWidth={resizableAreaMinWidth}
-      disabled={disableResizing}
+      disabled={!isDockMode()}
       onResize={resizeHandler}
       onResizeDone={resizeDoneHandler}
       onCollapsed={collapsedByResizableAreaHandler}
@@ -123,15 +101,15 @@ const CollapsibleContainer = memo((props: CollapsibleContainerProps): JSX.Elemen
 
   const { Nav, className, children } = props;
 
+  const { isCollapsedMode } = useSidebarMode();
   const { data: currentProductNavWidth } = useCurrentProductNavWidth();
-  const { data: isCollapsedMode } = useCollapsedMode();
   const { data: isCollapsedContentsOpened, mutate: mutateCollapsedContentsOpened } = useCollapsedContentsOpened();
 
 
   // open menu when collapsed mode
   const primaryItemHoverHandler = useCallback(() => {
     // reject other than collapsed mode
-    if (!isCollapsedMode) {
+    if (!isCollapsedMode()) {
       return;
     }
 
@@ -141,7 +119,7 @@ const CollapsibleContainer = memo((props: CollapsibleContainerProps): JSX.Elemen
   // close menu when collapsed mode
   const mouseLeaveHandler = useCallback(() => {
     // reject other than collapsed mode
-    if (!isCollapsedMode) {
+    if (!isCollapsedMode()) {
       return;
     }
 
@@ -149,7 +127,7 @@ const CollapsibleContainer = memo((props: CollapsibleContainerProps): JSX.Elemen
   }, [isCollapsedMode, mutateCollapsedContentsOpened]);
 
   const openClass = `${isCollapsedContentsOpened ? 'open' : ''}`;
-  const collapsibleContentsWidth = isCollapsedMode ? currentProductNavWidth : undefined;
+  const collapsibleContentsWidth = isCollapsedMode() ? currentProductNavWidth : undefined;
 
   return (
     <div className={`flex-expand-horiz ${className}`} onMouseLeave={mouseLeaveHandler}>
@@ -186,17 +164,23 @@ const DrawableContainer = memo((props: DrawableContainerProps): JSX.Element => {
 
 export const Sidebar = (): JSX.Element => {
 
-  const { data: isDrawerMode } = useDrawerMode();
-  const { data: isCollapsedMode } = useCollapsedMode();
+  const { data: sidebarMode } = useSidebarMode();
 
   // css styles
   const grwSidebarClass = styles['grw-sidebar'];
   // eslint-disable-next-line no-nested-ternary
-  const modeClass = isDrawerMode
-    ? 'grw-sidebar-drawer'
-    : isCollapsedMode
-      ? 'grw-sidebar-collapsed'
-      : 'grw-sidebar-dock';
+  let modeClass;
+  switch (sidebarMode) {
+    case SidebarMode.DRAWER:
+      modeClass = 'grw-sidebar-drawer';
+      break;
+    case SidebarMode.COLLAPSED:
+      modeClass = 'grw-sidebar-collapsed';
+      break;
+    case SidebarMode.DOCK:
+      modeClass = 'grw-sidebar-dock';
+      break;
+  }
 
   return (
     <DrawableContainer className={`${grwSidebarClass} ${modeClass} border-end vh-100`} data-testid="grw-sidebar">

+ 3 - 3
apps/app/src/components/Sidebar/SidebarContents.tsx

@@ -1,7 +1,7 @@
 import React, { memo, useMemo } from 'react';
 
 import { SidebarContentsType } from '~/interfaces/ui';
-import { useCollapsedContentsOpened, useCollapsedMode, useCurrentSidebarContents } from '~/stores/ui';
+import { useCollapsedContentsOpened, useCurrentSidebarContents, useSidebarMode } from '~/stores/ui';
 
 
 import { Bookmarks } from './Bookmarks';
@@ -14,7 +14,7 @@ import styles from './SidebarContents.module.scss';
 
 
 export const SidebarContents = memo(() => {
-  const { data: isCollapsedMode } = useCollapsedMode();
+  const { isCollapsedMode } = useSidebarMode();
   const { data: isCollapsedContentsOpened } = useCollapsedContentsOpened();
 
   const { data: currentSidebarContents } = useCurrentSidebarContents();
@@ -22,7 +22,7 @@ export const SidebarContents = memo(() => {
   const Contents = useMemo(() => {
 
     // return an empty element when the collapsed mode and it is closed
-    if (isCollapsedMode && !isCollapsedContentsOpened) {
+    if (isCollapsedMode() && !isCollapsedContentsOpened) {
       return () => <></>;
     }
 

+ 2 - 4
apps/app/src/components/Sidebar/SidebarHead/SidebarHead.tsx

@@ -5,7 +5,7 @@ import React, {
 import Link from 'next/link';
 
 import { useIsDefaultLogo } from '~/stores/context';
-import { useCollapsedContentsOpened, useCollapsedMode } from '~/stores/ui';
+import { useCollapsedContentsOpened } from '~/stores/ui';
 
 import { SidebarBrandLogo } from '../SidebarBrandLogo';
 
@@ -16,13 +16,11 @@ import styles from './SidebarHead.module.scss';
 
 export const SidebarHead: FC = memo(() => {
 
-  const { data: isCollapsedMode, mutate: mutateCollapsedMode } = useCollapsedMode();
+  // const { data: isCollapsedMode, mutate: mutateCollapsedMode } = useCollapsedMode();
   const { mutate: mutateCollapsedContentsOpened } = useCollapsedContentsOpened();
 
   const { data: isDefaultLogo } = useIsDefaultLogo();
 
-  const popoutClass = isCollapsedMode ? 'popout' : '';
-
   return (
     <div className={`${styles['grw-sidebar-head']} d-flex justify-content-end w-100`}>
       {/* Brand Logo  */}

+ 22 - 8
apps/app/src/components/Sidebar/SidebarHead/ToggleCollapseButton.tsx

@@ -1,6 +1,8 @@
 import { memo, useCallback } from 'react';
 
-import { useCollapsedContentsOpened, useCollapsedMode } from '~/stores/ui';
+import {
+  useCollapsedContentsOpened, usePreferCollapsedMode, useDrawerOpened, useSidebarMode,
+} from '~/stores/ui';
 
 
 import styles from './ToggleCollapseButton.module.scss';
@@ -8,19 +10,31 @@ import styles from './ToggleCollapseButton.module.scss';
 
 export const ToggleCollapseButton = memo((): JSX.Element => {
 
-  const { data: isCollapsedMode, mutate: mutateCollapsedMode } = useCollapsedMode();
+  const { isDrawerMode, isCollapsedMode, isDockMode } = useSidebarMode();
+  const { data: isDrawerOpened, mutate: mutateDrawerOpened } = useDrawerOpened();
+  const { mutate: mutatePreferCollapsedMode } = usePreferCollapsedMode();
   const { mutate: mutateCollapsedContentsOpened } = useCollapsedContentsOpened();
 
-  const toggle = useCallback(() => {
-    mutateCollapsedMode(!isCollapsedMode);
+  const toggleDrawer = useCallback(() => {
+    mutateDrawerOpened(!isDrawerOpened);
+  }, [isDrawerOpened, mutateDrawerOpened]);
+
+  const toggleCollapsed = useCallback(() => {
+    mutatePreferCollapsedMode(!isCollapsedMode());
     mutateCollapsedContentsOpened(false);
-  }, [isCollapsedMode, mutateCollapsedContentsOpened, mutateCollapsedMode]);
+  }, [isCollapsedMode, mutateCollapsedContentsOpened, mutatePreferCollapsedMode]);
 
-  const rotationClass = isCollapsedMode ? 'rotate180' : '';
-  const icon = isCollapsedMode ? 'keyboard_double_arrow_left' : 'first_page';
+  const rotationClass = isCollapsedMode() ? 'rotate180' : '';
+  const icon = isDrawerMode() || isDockMode()
+    ? 'first_page'
+    : 'keyboard_double_arrow_left';
 
   return (
-    <button type="button" className={`btn btn-primary ${styles['btn-toggle-collapse']} p-2`} onClick={toggle}>
+    <button
+      type="button"
+      className={`btn btn-primary ${styles['btn-toggle-collapse']} p-2`}
+      onClick={isDrawerMode() ? toggleDrawer : toggleCollapsed}
+    >
       <span className={`material-icons fs-2 ${rotationClass}`}>{icon}</span>
     </button>
   );

+ 5 - 9
apps/app/src/components/Sidebar/SidebarNav/PrimaryItems.tsx

@@ -4,9 +4,7 @@ import dynamic from 'next/dynamic';
 
 import { scheduleToPut } from '~/client/services/user-ui-settings';
 import { SidebarContentsType, SidebarMode } from '~/interfaces/ui';
-import {
-  useCurrentSidebarContents, useCollapsedMode, useDrawerMode,
-} from '~/stores/ui';
+import { useCurrentSidebarContents, useSidebarMode } from '~/stores/ui';
 
 import styles from './PrimaryItems.module.scss';
 
@@ -81,13 +79,11 @@ type Props = {
 export const PrimaryItems = memo((props: Props) => {
   const { onItemHover } = props;
 
-  const { data: isCollapsedMode } = useCollapsedMode();
-  const { data: isDrawerMode } = useDrawerMode();
+  const { data: sidebarMode } = useSidebarMode();
 
-  // eslint-disable-next-line no-nested-ternary
-  const sidebarMode = isDrawerMode
-    ? SidebarMode.DRAWER
-    : (isCollapsedMode ? SidebarMode.COLLAPSED : SidebarMode.DOCK);
+  if (sidebarMode == null) {
+    return <></>;
+  }
 
   return (
     <div className={styles['grw-primary-items']}>

+ 2 - 2
apps/app/src/pages/utils/commons.ts

@@ -14,7 +14,7 @@ import type { IUserUISettings } from '~/interfaces/user-ui-settings';
 import type { PageDocument } from '~/server/models/page';
 import type { UserUISettingsDocument } from '~/server/models/user-ui-settings';
 import {
-  useCurrentProductNavWidth, useCurrentSidebarContents, useCollapsedMode,
+  useCurrentProductNavWidth, useCurrentSidebarContents, usePreferCollapsedMode,
 } from '~/stores/ui';
 
 export type CommonProps = {
@@ -165,7 +165,7 @@ export const generateCustomTitleForPage = (props: CommonProps, pagePath: string)
 
 export const useInitSidebarConfig = (sidebarConfig: ISidebarConfig, userUISettings?: IUserUISettings): void => {
   // UserUISettings
-  useCollapsedMode(userUISettings?.preferCollapsedModeByUser ?? sidebarConfig.isSidebarCollapsedMode);
+  usePreferCollapsedMode(userUISettings?.preferCollapsedModeByUser ?? sidebarConfig.isSidebarCollapsedMode);
   useCurrentSidebarContents(userUISettings?.currentSidebarContents);
   useCurrentProductNavWidth(userUISettings?.currentProductNavWidth);
 };

+ 38 - 3
apps/app/src/stores/ui.tsx

@@ -14,7 +14,7 @@ import useSWRImmutable from 'swr/immutable';
 
 import type { IFocusable } from '~/client/interfaces/focusable';
 import type { IPageGrantData } from '~/interfaces/page';
-import { SidebarContentsType } from '~/interfaces/ui';
+import { SidebarContentsType, SidebarMode } from '~/interfaces/ui';
 import type { UpdateDescCountData } from '~/interfaces/websocket';
 import {
   useIsNotFound, useCurrentPagePath, useIsTrashPage, useCurrentPageId,
@@ -259,14 +259,49 @@ export const useDrawerOpened = (isOpened?: boolean): SWRResponse<boolean, Error>
   return useSWRStatic('isDrawerOpened', isOpened, { fallbackData: false });
 };
 
-export const useCollapsedMode = (initialData?: boolean): SWRResponse<boolean, Error> => {
-  return useSWRStatic('isCollapsedMode', initialData, { fallbackData: false });
+export const usePreferCollapsedMode = (initialData?: boolean): SWRResponse<boolean, Error> => {
+  return useSWRStatic('isPreferCollapsedMode', initialData, { fallbackData: false });
 };
 
 export const useCollapsedContentsOpened = (initialData?: boolean): SWRResponse<boolean, Error> => {
   return useSWRStatic('isCollapsedContentsOpened', initialData, { fallbackData: false });
 };
 
+type DetectSidebarModeUtils = {
+  isDrawerMode(): boolean
+  isCollapsedMode(): boolean
+  isDockMode(): boolean
+}
+
+export const useSidebarMode = (): SWRResponseWithUtils<DetectSidebarModeUtils, SidebarMode> => {
+  const { data: isDrawerMode } = useDrawerMode();
+  const { data: isCollapsedModeUnderDockMode } = usePreferCollapsedMode();
+
+  const condition = isDrawerMode != null && isCollapsedModeUnderDockMode != null;
+
+  const swrResponse = useSWRImmutable(
+    condition ? ['sidebarMode', isDrawerMode, isCollapsedModeUnderDockMode] : null,
+    // calcDrawerMode,
+    ([, isDrawerMode, isCollapsedModeUnderDockMode]) => {
+      if (isDrawerMode) {
+        return SidebarMode.DRAWER;
+      }
+      return isCollapsedModeUnderDockMode ? SidebarMode.COLLAPSED : SidebarMode.DOCK;
+    },
+  );
+
+  const _isDrawerMode = useCallback(() => swrResponse.data === SidebarMode.DRAWER, [swrResponse.data]);
+  const _isCollapsedMode = useCallback(() => swrResponse.data === SidebarMode.COLLAPSED, [swrResponse.data]);
+  const _isDockMode = useCallback(() => swrResponse.data === SidebarMode.DOCK, [swrResponse.data]);
+
+  return {
+    ...swrResponse,
+    isDrawerMode: _isDrawerMode,
+    isCollapsedMode: _isCollapsedMode,
+    isDockMode: _isDockMode,
+  };
+};
+
 export const useSelectedGrant = (initialData?: Nullable<IPageGrantData>): SWRResponse<Nullable<IPageGrantData>, Error> => {
   return useStaticSWR<Nullable<IPageGrantData>, Error>('selectedGrant', initialData, { fallbackData: { grant: PageGrant.GRANT_PUBLIC } });
 };