Преглед изворни кода

Merge pull request #8463 from weseek/feat/139773-adapting-styles-to-page-header

feat: Adapting styles to PageHeader
Yuki Takei пре 2 година
родитељ
комит
a2472a577e

+ 3 - 2
apps/app/package.json

@@ -69,8 +69,8 @@
     "@elastic/elasticsearch8": "npm:@elastic/elasticsearch@^8.7.0",
     "@godaddy/terminus": "^4.9.0",
     "@google-cloud/storage": "^5.8.5",
-    "@growi/custom-icons": "link:../../packages/custom-icons",
     "@growi/core": "link:../../packages/core",
+    "@growi/custom-icons": "link:../../packages/custom-icons",
     "@growi/pluginkit": "link:../../packages/pluginkit",
     "@growi/preset-templates": "link:../../packages/preset-templates",
     "@growi/preset-themes": "link:../../packages/preset-themes",
@@ -235,8 +235,8 @@
     "@types/jest": "^29.5.2",
     "@types/react-scroll": "^1.8.4",
     "@types/throttle-debounce": "^5.0.1",
-    "@types/url-join": "^4.0.2",
     "@types/unzip-stream": "^0.3.4",
+    "@types/url-join": "^4.0.2",
     "@vitejs/plugin-react": "^4.2.1",
     "@vitest/coverage-v8": "^0.34.6",
     "autoprefixer": "^9.0.0",
@@ -270,6 +270,7 @@
     "react-copy-to-clipboard": "^5.0.1",
     "react-dropzone": "^11.2.4",
     "react-hotkeys": "^2.0.0",
+    "react-input-autosize": "^3.0.0",
     "rehype-rewrite": "^3.0.6",
     "replacestream": "^4.0.3",
     "sass": "^1.53.0",

+ 22 - 18
apps/app/src/components/Common/ClosableTextInput.tsx

@@ -4,6 +4,7 @@ import React, {
 } from 'react';
 
 import { useTranslation } from 'next-i18next';
+import AutosizeInput from 'react-input-autosize';
 
 import type { AlertInfo } from '~/client/util/input-validator';
 import { AlertType, inputValidator } from '~/client/util/input-validator';
@@ -12,10 +13,11 @@ type ClosableTextInputProps = {
   value?: string
   placeholder?: string
   validationTarget?: string,
+  useAutosizeInput?: boolean
   onPressEnter?(inputText: string | null): void
   onPressEscape?: () => void
   onClickOutside?(): void
-  handleInputChange?: (string) => void
+  onChange?(inputText: string): void
 }
 
 const ClosableTextInput: FC<ClosableTextInputProps> = memo((props: ClosableTextInputProps) => {
@@ -43,7 +45,7 @@ const ClosableTextInput: FC<ClosableTextInputProps> = memo((props: ClosableTextI
     setInputText(inputText);
     setIsAbleToShowAlert(true);
 
-    props.handleInputChange?.(inputText);
+    props.onChange?.(inputText);
   };
 
   const onFocusHandler = async(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -115,25 +117,27 @@ const ClosableTextInput: FC<ClosableTextInputProps> = memo((props: ClosableTextI
     );
   };
 
+  const inputProps = {
+    'data-testid': 'closable-text-input',
+    value: inputText || '',
+    ref: inputRef,
+    type: 'text',
+    placeholder: props.placeholder,
+    name: 'input',
+    onFocus: onFocusHandler,
+    onChange: onChangeHandler,
+    onKeyDown: onKeyDownHandler,
+    onCompositionStart: () => setComposing(true),
+    onCompositionEnd: () => setComposing(false),
+    onBlur: onBlurHandler,
+  };
 
   return (
     <div>
-      <input
-        value={inputText || ''}
-        ref={inputRef}
-        type="text"
-        className="form-control"
-        placeholder={props.placeholder}
-        name="input"
-        data-testid="closable-text-input"
-        onFocus={onFocusHandler}
-        onChange={onChangeHandler}
-        onKeyDown={onKeyDownHandler}
-        onCompositionStart={() => setComposing(true)}
-        onCompositionEnd={() => setComposing(false)}
-        onBlur={onBlurHandler}
-        autoFocus={false}
-      />
+      { props.useAutosizeInput
+        ? <AutosizeInput {...inputProps} />
+        : <input className="form-control" {...inputProps} />
+      }
       {isAbleToShowAlert && <AlertInfo />}
     </div>
   );

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

@@ -439,7 +439,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
 
   return (
     <div data-testid="page-editor" id="page-editor" className={`flex-expand-vert ${props.visibility ? '' : 'd-none'}`}>
-      <div className="flex-expand-vert justify-content-center" style={{ minHeight: '72px' }}>
+      <div className="ms-3 mt-2">
         <PageHeader />
       </div>
       <div className={`flex-expand-horiz ${props.visibility ? '' : 'd-none'}`}>

+ 14 - 0
apps/app/src/components/PageHeader/PageHeader.module.scss

@@ -0,0 +1,14 @@
+.page-header :global {
+  .page-title-header-input {
+    input {
+      font-size: 2rem;
+    }
+  }
+
+  .page-path-header-buttons {
+    button {
+      width: 25px;
+      height: 20px;
+    }
+  }
+}

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

@@ -5,6 +5,8 @@ import { useSWRxCurrentPage } from '~/stores/page';
 import { PagePathHeader } from './PagePathHeader';
 import { PageTitleHeader } from './PageTitleHeader';
 
+import styles from './PageHeader.module.scss';
+
 
 export const PageHeader: FC = () => {
   const { data: currentPage } = useSWRxCurrentPage();
@@ -14,13 +16,13 @@ export const PageHeader: FC = () => {
   }
 
   return (
-    <>
+    <div className={`${styles['page-header']}`}>
       <PagePathHeader
         currentPage={currentPage}
       />
       <PageTitleHeader
         currentPage={currentPage}
       />
-    </>
+    </div>
   );
 };

+ 46 - 61
apps/app/src/components/PageHeader/PagePathHeader.tsx

@@ -1,42 +1,39 @@
-import {
-  useMemo, useState, useEffect, useCallback,
-} from 'react';
+import { useState, useEffect, useCallback } from 'react';
 import type { FC } from 'react';
 
 import type { IPagePopulatedToShowRevision } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 
 import { ValidationTarget } from '~/client/util/input-validator';
+import LinkedPagePath from '~/models/linked-page-path';
 import { usePageSelectModal } from '~/stores/modal';
-import { EditorMode, useEditorMode } from '~/stores/ui';
 
 import ClosableTextInput from '../Common/ClosableTextInput';
-import { PagePathNav } from '../Common/PagePathNav';
+import { PagePathHierarchicalLink } from '../Common/PagePathHierarchicalLink';
 import { PageSelectModal } from '../PageSelectModal/PageSelectModal';
 
 import { usePagePathRenameHandler } from './page-header-utils';
 
-
 export type Props = {
   currentPage: IPagePopulatedToShowRevision
 }
 
 export const PagePathHeader: FC<Props> = (props) => {
+  const { t } = useTranslation();
   const { currentPage } = props;
 
   const currentPagePath = currentPage.path;
+  const linkedPagePath = new LinkedPagePath(currentPagePath);
 
   const [isRenameInputShown, setRenameInputShown] = useState(false);
   const [isButtonsShown, setButtonShown] = useState(false);
   const [editedPagePath, setEditedPagePath] = useState(currentPagePath);
 
-  const { data: editorMode } = useEditorMode();
   const { data: PageSelectModalData, open: openPageSelectModal } = usePageSelectModal();
+  const isOpened = PageSelectModalData?.isOpened ?? false;
 
   const pagePathRenameHandler = usePagePathRenameHandler(currentPage);
 
-  const { t } = useTranslation();
-
   const onRenameFinish = useCallback(() => {
     setRenameInputShown(false);
   }, []);
@@ -66,22 +63,7 @@ export const PagePathHeader: FC<Props> = (props) => {
       setEditedPagePath(currentPagePath);
       setRenameInputShown(true);
     }
-  }, [currentPagePath, editedPagePath, isRenameInputShown, pagePathRenameHandler]);
-
-  const isOpened = PageSelectModalData?.isOpened ?? false;
-  const isViewMode = editorMode === EditorMode.View;
-  const isEditorMode = !isViewMode;
-
-  const PagePath = useMemo(() => (
-    <PagePathNav
-      pageId={currentPage._id}
-      pagePath={currentPagePath}
-      isSingleLineMode={isEditorMode}
-    />
-  ), [currentPage._id, currentPagePath, isEditorMode]);
-
-
-  const buttonStyle = isButtonsShown ? '' : 'd-none';
+  }, [currentPagePath, editedPagePath, isRenameInputShown, onRenameFailure, onRenameFinish, pagePathRenameHandler]);
 
   const clickOutSideHandler = useCallback((e) => {
     const container = document.getElementById('page-path-header');
@@ -97,51 +79,54 @@ export const PagePathHeader: FC<Props> = (props) => {
     return () => {
       document.removeEventListener('click', clickOutSideHandler);
     };
-  }, []);
+  }, [clickOutSideHandler]);
 
 
   return (
     <div
       id="page-path-header"
+      className="d-flex"
+      onMouseEnter={() => setButtonShown(true)}
       onMouseLeave={() => setButtonShown(false)}
     >
-      <div className="row">
-        <div
-          className="col-4"
-          onMouseEnter={() => setButtonShown(true)}
+      <div className="me-2">
+        {isRenameInputShown
+          ? (
+            <ClosableTextInput
+              useAutosizeInput
+              value={editedPagePath}
+              placeholder={t('Input page name')}
+              onPressEnter={onPressEnter}
+              onPressEscape={onPressEscape}
+              onChange={onInputChange}
+              validationTarget={ValidationTarget.PAGE}
+            />
+          )
+          : (
+            <PagePathHierarchicalLink linkedPagePath={linkedPagePath} />
+          )
+        }
+      </div>
+
+      <div className={`page-path-header-buttons d-flex align-items-center ${isButtonsShown ? '' : 'd-none'}`}>
+        <button
+          type="button"
+          className="btn btn-sm text-muted border border-secondary me-2 d-flex align-items-center justify-content-center"
+          onClick={onClickEditButton}
+        >
+          <span className="material-symbols-outlined fs-5 mt-1">{isRenameInputShown ? 'check_circle' : 'edit'}</span>
+        </button>
+
+        <button
+          type="button"
+          className="btn btn-sm text-muted border border-secondary d-flex align-items-center justify-content-center"
+          onClick={openPageSelectModal}
         >
-          {isRenameInputShown ? (
-            <div className="flex-fill">
-              <ClosableTextInput
-                value={editedPagePath}
-                placeholder={t('Input page name')}
-                onPressEnter={onPressEnter}
-                onPressEscape={onPressEscape}
-                validationTarget={ValidationTarget.PAGE}
-                handleInputChange={onInputChange}
-              />
-            </div>
-          ) : (
-            <>{ PagePath }</>
-          )}
-        </div>
-        <div className={`${buttonStyle} col-4 row`}>
-          <div className="col-4">
-            <button type="button" onClick={onClickEditButton}>
-              {isRenameInputShown ? <span className="material-symbols-outlined">check_circle</span> : <span className="material-symbols-outlined">edit</span>}
-            </button>
-          </div>
-          <div className="col-4">
-            <button type="button" onClick={openPageSelectModal}>
-              <span className="material-symbols-outlined">account_tree</span>
-            </button>
-          </div>
-        </div>
-        {isOpened
-          && (
-            <PageSelectModal />
-          )}
+          <span className="material-symbols-outlined fs-5 mt-1">account_tree</span>
+        </button>
       </div>
+
+      {isOpened && <PageSelectModal />}
     </div>
   );
 };

+ 33 - 30
apps/app/src/components/PageHeader/PageTitleHeader.tsx

@@ -9,12 +9,14 @@ import { useTranslation } from 'next-i18next';
 import { ValidationTarget } from '~/client/util/input-validator';
 
 import ClosableTextInput from '../Common/ClosableTextInput';
+import { CopyDropdown } from '../Common/CopyDropdown';
 
 import type { Props } from './PagePathHeader';
 import { usePagePathRenameHandler } from './page-header-utils';
 
 
 export const PageTitleHeader: FC<Props> = (props) => {
+  const { t } = useTranslation();
   const { currentPage } = props;
 
   const currentPagePath = currentPage.path;
@@ -26,8 +28,6 @@ export const PageTitleHeader: FC<Props> = (props) => {
 
   const pagePathRenameHandler = usePagePathRenameHandler(currentPage);
 
-  const { t } = useTranslation();
-
   const editedPageTitle = nodePath.basename(editedPagePath);
 
   const onRenameFinish = useCallback(() => {
@@ -54,42 +54,45 @@ export const PageTitleHeader: FC<Props> = (props) => {
     setRenameInputShown(false);
   }, [currentPagePath]);
 
-  const onClickButton = useCallback(() => {
-    pagePathRenameHandler(editedPagePath, onRenameFinish, onRenameFailure);
-  }, [editedPagePath, onRenameFailure, onRenameFinish, pagePathRenameHandler]);
-
   const onClickPageTitle = useCallback(() => {
     setEditedPagePath(currentPagePath);
     setRenameInputShown(true);
   }, [currentPagePath]);
 
-  const PageTitle = <div onClick={onClickPageTitle}>{pageTitle}</div>;
-
-  const buttonStyle = isRenameInputShown ? '' : 'd-none';
 
   return (
-    <div className="row">
-      <div className="col-4">
-        {isRenameInputShown ? (
-          <div className="flex-fill">
-            <ClosableTextInput
-              value={editedPageTitle}
-              placeholder={t('Input page name')}
-              onPressEnter={onPressEnter}
-              onPressEscape={onPressEscape}
-              validationTarget={ValidationTarget.PAGE}
-              handleInputChange={onInputChange}
-            />
-          </div>
-        ) : (
-          <>{ PageTitle }</>
-        )}
-      </div>
-      <div className={`col-4 ${buttonStyle}`}>
-        <button type="button" onClick={onClickButton}>
-          <span className="material-symbols-outlined">check_circle</span>
-        </button>
+    <div className="d-flex">
+      <div className="me-1">
+        {isRenameInputShown
+          ? (
+            <div className="page-title-header-input">
+              <ClosableTextInput
+                useAutosizeInput
+                value={editedPageTitle}
+                placeholder={t('Input page name')}
+                onPressEnter={onPressEnter}
+                onPressEscape={onPressEscape}
+                onChange={onInputChange}
+                onClickOutside={() => setRenameInputShown(false)}
+                validationTarget={ValidationTarget.PAGE}
+              />
+            </div>
+          )
+          : (
+            <h2 onClick={onClickPageTitle}>
+              {pageTitle}
+            </h2>
+          )}
       </div>
+
+      <CopyDropdown
+        pageId={currentPage._id}
+        pagePath={currentPage.path}
+        dropdownToggleId={`copydropdown-${currentPage._id}`}
+        dropdownToggleClassName="p-2"
+      >
+        <span className="material-symbols-outlined fs-5">content_paste</span>
+      </CopyDropdown>
     </div>
   );
 };

+ 7 - 0
yarn.lock

@@ -14799,6 +14799,13 @@ react-image-crop@^8.3.0:
     core-js "^3.2.1"
     prop-types "^15.7.2"
 
+react-input-autosize@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-3.0.0.tgz#6b5898c790d4478d69420b55441fcc31d5c50a85"
+  integrity sha512-nL9uS7jEs/zu8sqwFE5MAPx6pPkNAriACQ2rGLlqmKr2sPGtN7TXTyDdQt4lbNXVx7Uzadb40x8qotIuru6Rhg==
+  dependencies:
+    prop-types "^15.5.8"
+
 react-is@^16.13.1, react-is@^16.7.0:
   version "16.13.1"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"