Przeglądaj źródła

Merge pull request #9973 from weseek/imprv/headers-and-copy-dropdown

imprv: Headers and CopyDropdown UX
Yuki Takei 10 miesięcy temu
rodzic
commit
b282b62396

+ 1 - 1
apps/app/src/client/components/PageAccessoriesModal/ShareLink/ShareLinkList.tsx

@@ -30,7 +30,7 @@ const ShareLinkTr = (props: ShareLinkTrProps): JSX.Element => {
         { isRelatedPageExists && (
           <CopyDropdown
             pagePath={relatedPage.path}
-            dropdownToggleId={`copydropdown-${shareLinkId}`}
+            dropdownToggleId={`copydropdown-for-share-link-list-${shareLinkId}`}
             pageId={shareLinkId}
             isShareLinkMode
           >

+ 4 - 2
apps/app/src/client/components/PageHeader/PageHeader.tsx

@@ -26,8 +26,10 @@ export const PageHeader = (): JSX.Element => {
       setMaxWidth(300);
       return;
     }
-    // At least 10px space between PageHeader and PageControls
-    const maxWidth = pageControlsX - pageHeaderRef.current.getBoundingClientRect().x - 10;
+
+    // PageControls.x - PageHeader.x
+    const maxWidth = pageControlsX - pageHeaderRef.current.getBoundingClientRect().x;
+
     setMaxWidth(maxWidth);
   }, [pageControlsX]);
 

+ 28 - 8
apps/app/src/client/components/PageHeader/PageTitleHeader.tsx

@@ -1,5 +1,7 @@
 import type { ChangeEvent, JSX } from 'react';
-import { useState, useCallback, useEffect } from 'react';
+import {
+  useState, useCallback, useEffect, useMemo,
+} from 'react';
 
 import nodePath from 'path';
 
@@ -101,9 +103,27 @@ export const PageTitleHeader = (props: Props): JSX.Element => {
 
   const isInvalid = validationResult != null;
 
-  const inputMaxWidth = maxWidth != null
-    ? getAdjustedMaxWidthForAutosizeInput(maxWidth, 'md', validationResult != null ? false : undefined) - 16
-    : undefined;
+  // calculate inputMaxWidth as the maximum width of AutoSizeInput minus the width of WIP badge and CopyDropdown
+  const inputMaxWidth = useMemo(() => {
+    if (maxWidth == null) {
+      return undefined;
+    }
+
+    const wipBadgeAndCopyDropdownWidth = 4 // me-1
+      + (currentPage.wip ? 49 : 0) // WIP badge + gap
+      + 24; // CopyDropdown
+
+    return getAdjustedMaxWidthForAutosizeInput(maxWidth, 'md', validationResult != null ? false : undefined) - wipBadgeAndCopyDropdownWidth;
+  }, [currentPage.wip, maxWidth, validationResult]);
+
+  // calculate h1MaxWidth as the inputMaxWidth plus padding
+  const h1MaxWidth = useMemo(() => {
+    if (inputMaxWidth == null) {
+      return undefined;
+    }
+
+    return inputMaxWidth + 16; // plus the padding of px-2 because AutosizeInput has "box-sizing: content-box;"
+  }, [inputMaxWidth]);
 
   return (
     <div className={`d-flex ${moduleClass} ${props.className ?? ''} position-relative`}>
@@ -129,22 +149,22 @@ export const PageTitleHeader = (props: Props): JSX.Element => {
             ${isRenameInputShown ? 'invisible' : ''} text-truncate
             ${isMovable ? 'border border-2 rounded-2' : ''} ${borderColorClass}
           `}
-          style={{ maxWidth: inputMaxWidth }}
+          style={{ maxWidth: h1MaxWidth }}
           onClick={onClickPageTitle}
         >
           {pageTitle}
         </h1>
       </div>
 
-      <div className={`${isRenameInputShown ? 'invisible' : ''} d-flex align-items-center`}>
+      <div className={`${isRenameInputShown ? 'invisible' : ''} d-flex align-items-center gap-2`}>
         { currentPage.wip && (
-          <span className="badge rounded-pill text-bg-secondary ms-2">WIP</span>
+          <span className="badge rounded-pill text-bg-secondary">WIP</span>
         )}
 
         <CopyDropdown
           pageId={currentPage._id}
           pagePath={currentPage.path}
-          dropdownToggleId={`copydropdown-${currentPage._id}`}
+          dropdownToggleId={`copydropdown-in-pagetitleheader-${currentPage._id}`}
           dropdownToggleClassName="p-1"
         >
           <span className="material-symbols-outlined fs-6">content_paste</span>

+ 19 - 11
apps/app/src/client/components/PagePathNavSticky/PagePathNavSticky.tsx

@@ -84,22 +84,30 @@ export const PagePathNavSticky = (props: PagePathNavLayoutProps): JSX.Element =>
     <div ref={pagePathNavRef}>
       <Sticky className={`${moduleClass} mb-4`} innerClass="pe-none" innerActiveClass="active mt-1">
         {({ status }) => {
-          const isCollapseParents = status === Sticky.STATUS_FIXED;
-          return (
-            // Controlling pointer-events
-            //  2. enable pointer-events with 'pe-auto' only against the children
-            //      which width is minimized by 'd-inline-block'
-            //
-            <div className="d-inline-block pe-auto">
-              { !isCollapseParents && <PagePathNav {...props} /> }
-              { isCollapseParents && (
+          const isParentsCollapsed = status === Sticky.STATUS_FIXED;
+
+          // Controlling pointer-events
+          //  2. enable pointer-events with 'pe-auto' only against the children
+          //      which width is minimized by 'd-inline-block'
+          //
+          if (isParentsCollapsed) {
+            return (
+              <div className="d-inline-block pe-auto">
                 <PagePathNavLayout
                   {...props}
                   latterLink={latterLink}
                   latterLinkClassName="fs-3 text-truncate"
-                  maxWidth={isCollapseParents ? navMaxWidth : undefined}
+                  maxWidth={navMaxWidth}
                 />
-              ) }
+              </div>
+            );
+          }
+
+          return (
+            // Use 'd-block' to make the children take the full width
+            // This is to improve UX when opening/closing CopyDropdown
+            <div className="d-block pe-auto">
+              <PagePathNav {...props} inline />
             </div>
           );
         }}

+ 1 - 1
apps/app/src/client/components/SearchPage/SearchResultContent.tsx

@@ -216,7 +216,7 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
       <RightComponent />
 
       <div className="container-lg grw-container-convertible pt-2 pb-2">
-        <PagePathNav pageId={page._id} pagePath={page.path} formerLinkClassName="small" latterLinkClassName="fs-3 text-truncate" />
+        <PagePathNav pageId={page._id} pagePath={page.path} isWipPage={page.wip} formerLinkClassName="small" latterLinkClassName="fs-3 text-truncate" />
       </div>
 
       <div

+ 12 - 8
apps/app/src/components/Common/PagePathNav/PagePathNavLayout.tsx

@@ -9,8 +9,9 @@ import styles from './PagePathNav.module.scss';
 const moduleClass = styles['grw-page-path-nav-layout'] ?? '';
 
 export type PagePathNavLayoutProps = {
-  className?: string,
   pagePath: string,
+  inline?: boolean,
+  className?: string,
   pageId?: string | null,
   isWipPage?: boolean,
   maxWidth?: number,
@@ -28,6 +29,7 @@ const CopyDropdown = dynamic(() => import('~/client/components/Common/CopyDropdo
 export const PagePathNavLayout = (props: Props): JSX.Element => {
   const {
     className = '',
+    inline = false,
     pageId, pagePath, isWipPage,
     formerLink,
     formerLinkClassName = '',
@@ -38,7 +40,9 @@ export const PagePathNavLayout = (props: Props): JSX.Element => {
 
   const { data: isNotFound } = useIsNotFound();
 
-  const copyDropdownId = `copydropdown-${pageId}`;
+  const copyDropdownId = `copydropdown-in-pagepathnavlayout-${pageId}`;
+
+  const containerLayoutClass = inline ? '' : 'd-flex align-items-center';
 
   return (
     <div
@@ -46,21 +50,21 @@ export const PagePathNavLayout = (props: Props): JSX.Element => {
       style={{ maxWidth }}
     >
       <span className={`${formerLinkClassName ?? ''} ${styles['grw-former-link']}`}>{formerLink}</span>
-      <div className="d-flex align-items-center">
-        <h1 className={`m-0 ${latterLinkClassName}`}>
+      <div className={containerLayoutClass}>
+        <h1 className={`m-0 d-inline align-bottom ${latterLinkClassName}`}>
           {latterLink}
         </h1>
         { pageId != null && !isNotFound && (
-          <div className="d-flex align-items-center ms-2">
+          <span className="d-inline-flex align-items-center align-bottom ms-2 gap-2">
             { isWipPage && (
-              <span className="badge text-bg-secondary ms-1 me-1">WIP</span>
+              <span className="badge text-bg-secondary">WIP</span>
             )}
-            <span className=" grw-page-path-nav-copydropdown">
+            <span className="grw-page-path-nav-copydropdown">
               <CopyDropdown pageId={pageId} pagePath={pagePath} dropdownToggleId={copyDropdownId} dropdownToggleClassName="p-2">
                 <span className="material-symbols-outlined">content_paste</span>
               </CopyDropdown>
             </span>
-          </div>
+          </span>
         ) }
       </div>
     </div>

+ 1 - 1
apps/app/src/components/PageView/PageView.tsx

@@ -101,7 +101,7 @@ export const PageView = (props: Props): JSX.Element => {
     }
   }, [isForbidden, isIdenticalPathPage, isNotCreatable]);
 
-  const headerContents = <PagePathNavTitle pageId={page?._id} pagePath={pagePath} />;
+  const headerContents = <PagePathNavTitle pageId={page?._id} pagePath={pagePath} isWipPage={page?.wip} />;
 
   const sideContents = !isNotFound && !isNotCreatable
     ? (

+ 1 - 1
apps/app/src/components/ShareLinkPageView/ShareLinkPageView.tsx

@@ -61,7 +61,7 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
     }
   }, [disableLinkSharing, props.disableLinkSharing]);
 
-  const headerContents = <PagePathNavTitle pageId={page?._id} pagePath={pagePath} />;
+  const headerContents = <PagePathNavTitle pageId={page?._id} pagePath={pagePath} isWipPage={page?.wip} />;
 
   const sideContents = !isNotFound
     ? (