Просмотр исходного кода

WIP: impl collapsed mode behavior

Yuki Takei 2 лет назад
Родитель
Сommit
bb4bd3fcd6

+ 9 - 7
apps/app/src/components/Sidebar/Sidebar.tsx

@@ -7,7 +7,7 @@ import dynamic from 'next/dynamic';
 import { scheduleToPut } from '~/client/services/user-ui-settings';
 import {
   useDrawerMode, useDrawerOpened,
-  useSidebarCollapsed,
+  useCollapsedMode, useCollapsedContentsOpened,
   useCurrentProductNavWidth,
   useSidebarResizeDisabled,
 } from '~/stores/ui';
@@ -30,7 +30,8 @@ export const SidebarSubstance = memo((): JSX.Element => {
 
   const { data: isDrawerMode } = useDrawerMode();
   const { data: currentProductNavWidth, mutate: mutateProductNavWidth } = useCurrentProductNavWidth();
-  const { data: isCollapsed, mutate: mutateSidebarCollapsed } = useSidebarCollapsed();
+  const { data: isCollapsedMode, mutate: mutateCollapsedMode } = useCollapsedMode();
+  const { mutate: mutateCollapsedContentsOpened } = useCollapsedContentsOpened();
   const { data: isResizeDisabled, mutate: mutateSidebarResizeDisabled } = useSidebarResizeDisabled();
 
   const [resizableAreaWidth, setResizableAreaWidth] = useState(0);
@@ -63,10 +64,11 @@ export const SidebarSubstance = memo((): JSX.Element => {
   }, [mutateProductNavWidth]);
 
   const collapsedByResizableAreaHandler = useCallback(() => {
-    mutateSidebarCollapsed(true);
+    mutateCollapsedMode(true);
+    mutateCollapsedContentsOpened(false);
     mutateProductNavWidth(sidebarMinWidth, false);
     scheduleToPut({ preferCollapsedModeByUser: true, currentProductNavWidth: sidebarMinWidth });
-  }, [mutateProductNavWidth, mutateSidebarCollapsed]);
+  }, [mutateCollapsedContentsOpened, mutateCollapsedMode, mutateProductNavWidth]);
 
   useEffect(() => {
     toggleDrawerMode(isDrawerMode);
@@ -77,16 +79,16 @@ export const SidebarSubstance = memo((): JSX.Element => {
     if (isDrawerMode) {
       setResizableAreaWidth(sidebarFixedWidthInDrawerMode);
     }
-    else if (isCollapsed) {
+    else if (isCollapsedMode) {
       setResizableAreaWidth(sidebarCollapsedWidth);
     }
     else {
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
       setResizableAreaWidth(currentProductNavWidth!);
     }
-  }, [currentProductNavWidth, isCollapsed, isDrawerMode]);
+  }, [currentProductNavWidth, isCollapsedMode, isDrawerMode]);
 
-  const disableResizing = isResizeDisabled || isDrawerMode || isCollapsed;
+  const disableResizing = isResizeDisabled || isDrawerMode || isCollapsedMode;
 
   return (
     <div className="data-layout-container">

+ 56 - 15
apps/app/src/components/Sidebar/SidebarNav/PrimaryItems.tsx

@@ -3,8 +3,10 @@ import { FC, memo, useCallback } from 'react';
 import dynamic from 'next/dynamic';
 
 import { scheduleToPut } from '~/client/services/user-ui-settings';
-import { SidebarContentsType } from '~/interfaces/ui';
-import { useCurrentSidebarContents } from '~/stores/ui';
+import { SidebarContentsType, SidebarMode } from '~/interfaces/ui';
+import {
+  useCurrentSidebarContents, useCollapsedMode, useDrawerMode, useCollapsedContentsOpened,
+} from '~/stores/ui';
 
 import styles from './PrimaryItems.module.scss';
 
@@ -17,23 +19,52 @@ type PrimaryItemProps = {
   contents: SidebarContentsType,
   label: string,
   iconName: string,
-  onItemSelected?: (contents: SidebarContentsType) => void,
+  sidebarMode: SidebarMode,
 }
 
 const PrimaryItem: FC<PrimaryItemProps> = (props: PrimaryItemProps) => {
   const {
-    contents, label, iconName, onItemSelected,
+    contents, label, iconName, sidebarMode,
   } = props;
 
-  const { data: currentContents, mutate } = useCurrentSidebarContents();
+  const { data: currentContents, mutate: mutateContents } = useCurrentSidebarContents();
+  const { mutate: mutateCollapsedContentsOpened } = useCollapsedContentsOpened();
 
   const isSelected = contents === currentContents;
 
-  const itemSelectedHandler = useCallback(() => {
-    mutate(contents, false);
+  const selectThisItem = useCallback(() => {
+    mutateContents(contents, false);
     scheduleToPut({ currentSidebarContents: contents });
-    onItemSelected?.(contents);
-  }, [contents, mutate, onItemSelected]);
+  }, [contents, mutateContents]);
+
+  const itemClickedHandler = useCallback(() => {
+    // do nothing ONLY WHEN the collapse mode
+    if (sidebarMode === SidebarMode.COLLAPSED) {
+      return;
+    }
+
+    selectThisItem();
+  }, [selectThisItem, sidebarMode]);
+
+  const mouseEnteredHandler = useCallback(() => {
+    // do nothing BUT the collapsed mode
+    if (sidebarMode !== SidebarMode.COLLAPSED) {
+      return;
+    }
+
+    selectThisItem();
+    mutateCollapsedContentsOpened(true);
+  }, [mutateCollapsedContentsOpened, selectThisItem, sidebarMode]);
+
+  const mouseLeavedHandler = useCallback(() => {
+    // do nothing BUT the collapsed mode
+    if (sidebarMode !== SidebarMode.COLLAPSED) {
+      return;
+    }
+
+    mutateCollapsedContentsOpened(false);
+  }, [mutateCollapsedContentsOpened, sidebarMode]);
+
 
   const labelForTestId = label.toLowerCase().replace(' ', '-');
 
@@ -42,7 +73,9 @@ const PrimaryItem: FC<PrimaryItemProps> = (props: PrimaryItemProps) => {
       type="button"
       data-testid={`grw-sidebar-nav-primary-${labelForTestId}`}
       className={`d-block btn btn-primary ${isSelected ? 'active' : ''}`}
-      onClick={itemSelectedHandler}
+      onClick={itemClickedHandler}
+      onMouseEnter={mouseEnteredHandler}
+      onMouseLeave={mouseLeavedHandler}
     >
       <i className="material-icons">{iconName}</i>
     </button>
@@ -50,13 +83,21 @@ const PrimaryItem: FC<PrimaryItemProps> = (props: PrimaryItemProps) => {
 };
 
 export const PrimaryItems: FC = memo(() => {
+  const { data: isCollapsedMode } = useCollapsedMode();
+  const { data: isDrawerMode } = useDrawerMode();
+
+  // eslint-disable-next-line no-nested-ternary
+  const sidebarMode = isDrawerMode
+    ? SidebarMode.DRAWER
+    : (isCollapsedMode ? SidebarMode.COLLAPSED : SidebarMode.DOCK);
+
   return (
     <div className={styles['grw-primary-items']}>
-      <PrimaryItem contents={SidebarContentsType.TREE} label="Page Tree" iconName="format_list_bulleted" />
-      <PrimaryItem contents={SidebarContentsType.CUSTOM} label="Custom Sidebar" iconName="code" />
-      <PrimaryItem contents={SidebarContentsType.RECENT} label="Recent Changes" iconName="update" />
-      <PrimaryItem contents={SidebarContentsType.BOOKMARKS} label="Bookmarks" iconName="bookmark" />
-      <PrimaryItem contents={SidebarContentsType.TAG} label="Tags" iconName="local_offer" />
+      <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.TREE} label="Page Tree" iconName="format_list_bulleted" />
+      <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.CUSTOM} label="Custom Sidebar" iconName="code" />
+      <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.RECENT} label="Recent Changes" iconName="update" />
+      <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.BOOKMARKS} label="Bookmarks" iconName="bookmark" />
+      <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.TAG} label="Tags" iconName="local_offer" />
       <InAppNotificationDropdown />
     </div>
   );

+ 6 - 6
apps/app/src/interfaces/ui.ts

@@ -1,12 +1,12 @@
 import type { Nullable } from '@growi/core';
 
 
-// export const SidebarMode = {
-//   DRAWER: 'drawer',
-//   COLLAPSED: 'collapsed',
-//   DOCK: 'dock',
-// } as const;
-// export type SidebarMode = typeof SidebarMode[keyof typeof SidebarMode];
+export const SidebarMode = {
+  DRAWER: 'drawer',
+  COLLAPSED: 'collapsed',
+  DOCK: 'dock',
+} as const;
+export type SidebarMode = typeof SidebarMode[keyof typeof SidebarMode];
 
 export const SidebarContentsType = {
   CUSTOM: 'custom',

+ 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, usePreferCollapsedModeByUser,
+  useCurrentProductNavWidth, useCurrentSidebarContents, useCollapsedMode,
 } 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
-  usePreferCollapsedModeByUser(userUISettings?.preferCollapsedModeByUser ?? sidebarConfig.isSidebarCollapsedMode);
+  useCollapsedMode(userUISettings?.preferCollapsedModeByUser ?? sidebarConfig.isSidebarCollapsedMode);
   useCurrentSidebarContents(userUISettings?.currentSidebarContents);
   useCurrentProductNavWidth(userUISettings?.currentProductNavWidth);
 };

+ 12 - 10
apps/app/src/stores/ui.tsx

@@ -13,9 +13,7 @@ import {
 import useSWRImmutable from 'swr/immutable';
 
 import type { IFocusable } from '~/client/interfaces/focusable';
-import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client';
 import type { IPageGrantData } from '~/interfaces/page';
-import type { ISidebarConfig } from '~/interfaces/sidebar-config';
 import { SidebarContentsType } from '~/interfaces/ui';
 import type { UpdateDescCountData } from '~/interfaces/websocket';
 import {
@@ -221,14 +219,6 @@ export const useIsDeviceSmallerThanLg = (): SWRResponse<boolean, Error> => {
 };
 
 
-export const usePreferCollapsedModeByUser = (initialData?: boolean): SWRResponse<boolean> => {
-  return useSWRStatic('preferCollapsedModeByUser', initialData);
-};
-
-export const useSidebarCollapsed = (initialData?: boolean): SWRResponse<boolean, Error> => {
-  return useSWRStatic('isSidebarCollapsed', initialData, { fallbackData: false });
-};
-
 export const useCurrentSidebarContents = (initialData?: SidebarContentsType): SWRResponse<SidebarContentsType, Error> => {
   return useSWRStatic('sidebarContents', initialData, { fallbackData: SidebarContentsType.TREE });
 };
@@ -265,6 +255,18 @@ export const useDrawerMode = (): SWRResponse<boolean, Error> => {
   );
 };
 
+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 useCollapsedContentsOpened = (initialData?: boolean): SWRResponse<boolean, Error> => {
+  return useSWRStatic('isCollapsedContentsOpened', initialData, { fallbackData: false });
+};
+
 export const useSidebarResizeDisabled = (isDisabled?: boolean): SWRResponse<boolean, Error> => {
   return useStaticSWR('isSidebarResizeDisabled', isDisabled, { fallbackData: false });
 };