Răsfoiți Sursa

impl validation feedback

Yuki Takei 1 an în urmă
părinte
comite
1c7952ad73

+ 1 - 1
apps/app/src/components/ItemsTree/ItemsTree.module.scss

@@ -3,7 +3,7 @@
 // fix height
 // fix height
 .items-tree :global {
 .items-tree :global {
   li {
   li {
-    height: items-tree-variables.$list-item-height;
+    min-height: items-tree-variables.$list-item-height;
   }
   }
 }
 }
 
 

+ 2 - 20
apps/app/src/components/Sidebar/PageTreeItem/PageTreeItem.tsx

@@ -13,7 +13,6 @@ import { useDrag, useDrop } from 'react-dnd';
 
 
 import { apiv3Put } from '~/client/util/apiv3-client';
 import { apiv3Put } from '~/client/util/apiv3-client';
 import { toastWarning, toastError } from '~/client/util/toastr';
 import { toastWarning, toastError } from '~/client/util/toastr';
-import { AlertType } from '~/client/util/use-input-validator';
 import type { IPageForItem } from '~/interfaces/page';
 import type { IPageForItem } from '~/interfaces/page';
 import { mutatePageTree, useSWRxPageChildren } from '~/stores/page-listing';
 import { mutatePageTree, useSWRxPageChildren } from '~/stores/page-listing';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
@@ -64,7 +63,7 @@ export const PageTreeItem: FC<TreeItemProps> = (props) => {
   const { mutate: mutateChildren } = useSWRxPageChildren(isOpen ? page._id : null);
   const { mutate: mutateChildren } = useSWRxPageChildren(isOpen ? page._id : null);
 
 
   const {
   const {
-    showRenameInput, validationResult, Control, RenameInput,
+    showRenameInput, Control, RenameInput,
   } = usePageItemControl();
   } = usePageItemControl();
   const { isProcessingSubmission, Input: NewPageInput, CreateButton: NewPageCreateButton } = useNewPageInput();
   const { isProcessingSubmission, Input: NewPageInput, CreateButton: NewPageCreateButton } = useNewPageInput();
 
 
@@ -183,23 +182,6 @@ export const PageTreeItem: FC<TreeItemProps> = (props) => {
 
 
   const mainClassName = `${isOver ? 'grw-pagetree-is-over' : ''} ${shouldHide ? 'd-none' : ''}`;
   const mainClassName = `${isOver ? 'grw-pagetree-is-over' : ''} ${shouldHide ? 'd-none' : ''}`;
 
 
-  const ValidationResult = useCallback(() => {
-    if (validationResult == null) return <></>;
-
-    const {
-      type, typeLabel, message,
-    } = validationResult;
-
-    return (
-      <div
-        className={`mt-1 alert ${type === AlertType.ERROR ? 'text-danger' : 'text-warning'}`}
-        role="alert"
-      >
-        {typeLabel}: {message}
-      </div>
-    );
-  }, [validationResult]);
-
   return (
   return (
     <TreeItemLayout
     <TreeItemLayout
       targetPathOrId={props.targetPathOrId}
       targetPathOrId={props.targetPathOrId}
@@ -217,7 +199,7 @@ export const PageTreeItem: FC<TreeItemProps> = (props) => {
       mainClassName={mainClassName}
       mainClassName={mainClassName}
       customEndComponents={[CountBadgeForPageTreeItem]}
       customEndComponents={[CountBadgeForPageTreeItem]}
       customHoveredEndComponents={[Control, NewPageCreateButton]}
       customHoveredEndComponents={[Control, NewPageCreateButton]}
-      customNextComponents={[ValidationResult, NewPageInput]}
+      customNextComponents={[NewPageInput]}
       customNextToChildrenComponents={[() => <CreatingNewPageSpinner show={isProcessingSubmission} />]}
       customNextToChildrenComponents={[() => <CreatingNewPageSpinner show={isProcessingSubmission} />]}
       showAlternativeContent={showRenameInput}
       showAlternativeContent={showRenameInput}
       customAlternativeComponents={[RenameInput]}
       customAlternativeComponents={[RenameInput]}

+ 20 - 5
apps/app/src/components/Sidebar/PageTreeItem/use-page-item-control.tsx

@@ -32,14 +32,12 @@ type UsePageItemControl = {
   Control: FC<TreeItemToolProps>,
   Control: FC<TreeItemToolProps>,
   RenameInput: FC<TreeItemToolProps>,
   RenameInput: FC<TreeItemToolProps>,
   showRenameInput: boolean,
   showRenameInput: boolean,
-  validationResult?: InputValidationResult,
 }
 }
 
 
 export const usePageItemControl = (): UsePageItemControl => {
 export const usePageItemControl = (): UsePageItemControl => {
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
   const [showRenameInput, setShowRenameInput] = useState(false);
   const [showRenameInput, setShowRenameInput] = useState(false);
-  const [validationResult, setValidationResult] = useState<InputValidationResult>();
 
 
 
 
   const Control: FC<TreeItemToolProps> = (props) => {
   const Control: FC<TreeItemToolProps> = (props) => {
@@ -146,6 +144,9 @@ export const usePageItemControl = (): UsePageItemControl => {
     const parentRef = useRef<HTMLDivElement>(null);
     const parentRef = useRef<HTMLDivElement>(null);
     const [parentRect] = useRect(parentRef);
     const [parentRect] = useRect(parentRef);
 
 
+    const [validationResult, setValidationResult] = useState<InputValidationResult>();
+
+
     const inputValidator = useInputValidator(ValidationTarget.PAGE);
     const inputValidator = useInputValidator(ValidationTarget.PAGE);
 
 
     const changeHandler = useCallback(async(e: ChangeEvent<HTMLInputElement>) => {
     const changeHandler = useCallback(async(e: ChangeEvent<HTMLInputElement>) => {
@@ -195,21 +196,36 @@ export const usePageItemControl = (): UsePageItemControl => {
     }, [cancel, onRenamed, page._id, page.path, page.revision]);
     }, [cancel, onRenamed, page._id, page.path, page.revision]);
 
 
 
 
+    if (!showRenameInput) {
+      return <></>;
+    }
+
     const renameInputContainerClass = renameInputStyles['rename-input-container'] ?? '';
     const renameInputContainerClass = renameInputStyles['rename-input-container'] ?? '';
-    const maxWidth = (parentRect?.width ?? 0) - 12 * 2; // calculate the max-width minus the padding (12px * 2) because AutosizeInput has "box-sizing: content-box;"
+    const isInvalid = validationResult != null;
+
+    const maxWidth = (parentRect?.width ?? 0)
+      - 12 * 2 // minus the padding (12px * 2) because AutosizeInput has "box-sizing: content-box;"
+      - (isInvalid ? 24 : 0); // minus the width for the exclamation icon
+
 
 
     return (
     return (
       <div ref={parentRef} className={`${renameInputContainerClass}`}>
       <div ref={parentRef} className={`${renameInputContainerClass}`}>
         <AutosizeSubmittableInput
         <AutosizeSubmittableInput
           value={nodePath.basename(page.path ?? '')}
           value={nodePath.basename(page.path ?? '')}
-          inputClassName="form-control"
+          inputClassName={`form-control ${isInvalid ? 'is-invalid' : ''}`}
           inputStyle={{ maxWidth }}
           inputStyle={{ maxWidth }}
           placeholder={t('Input page name')}
           placeholder={t('Input page name')}
+          aria-describedby={isInvalid ? 'rename-feedback' : undefined}
           onChange={changeHandlerDebounced}
           onChange={changeHandlerDebounced}
           onSubmit={rename}
           onSubmit={rename}
           onCancel={cancel}
           onCancel={cancel}
           autoFocus
           autoFocus
         />
         />
+        { isInvalid && (
+          <div id="rename-feedback" className="invalid-feedback d-block my-1">
+            {validationResult.message}
+          </div>
+        ) }
       </div>
       </div>
     );
     );
   };
   };
@@ -219,7 +235,6 @@ export const usePageItemControl = (): UsePageItemControl => {
     Control,
     Control,
     RenameInput,
     RenameInput,
     showRenameInput,
     showRenameInput,
-    validationResult,
   };
   };
 
 
 };
 };