diff --git a/packages/backend.ai-ui/src/components/baiClient/FileExplorer/EditableFileName.tsx b/packages/backend.ai-ui/src/components/baiClient/FileExplorer/EditableFileName.tsx index 6e1318779a..e2d666cc25 100644 --- a/packages/backend.ai-ui/src/components/baiClient/FileExplorer/EditableFileName.tsx +++ b/packages/backend.ai-ui/src/components/baiClient/FileExplorer/EditableFileName.tsx @@ -9,7 +9,7 @@ import { App, Form, GetProps, Input, theme, Typography } from 'antd'; import { createStyles } from 'antd-style'; import _ from 'lodash'; import { CornerDownLeftIcon } from 'lucide-react'; -import { use, useState } from 'react'; +import { use, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; interface ServerError extends Error { @@ -62,6 +62,7 @@ const EditableFileName: React.FC = ({ style, ...props }) => { + 'use memo'; const { t } = useTranslation(); const { token } = theme.useToken(); const { modal, message } = App.useApp(); @@ -104,10 +105,20 @@ const EditableFileName: React.FC = ({ const isPendingRenamingAndRefreshing = renameMutation.isPending || optimisticName !== fileInfo.name; + // focus back to the text component after editing for better UX related to keyboard shortcuts + const textRef = useRef(null); + const focusFallback = () => { + setTimeout(() => { + textRef.current?.focus(); + }, 0); + }; + return ( <> {!isEditing || isPendingRenamingAndRefreshing ? ( = ({ : false } className={!disabled ? styles.hoverEdit : undefined} - style={style} + style={{ + // after editing, focus this element, remove outline + outline: 'none', + ...style, + }} {...props} > {fileInfo?.type === 'DIRECTORY' ? ( @@ -167,6 +182,7 @@ const EditableFileName: React.FC = ({ initialValues={{ newName: fileInfo?.name }} onFinish={(values) => { setIsEditing(false); + focusFallback(); setOptimisticName(values.newName); const variables = { target_path: _.join([currentPath, fileInfo?.name], '/'), @@ -242,7 +258,9 @@ const EditableFileName: React.FC = ({ autoFocus onKeyDown={(e) => { if (e.key === 'Escape') { + e.stopPropagation(); setIsEditing(false); + focusFallback(); } }} /> diff --git a/react/src/components/ComputeSessionNodeItems/EditableSessionName.tsx b/react/src/components/ComputeSessionNodeItems/EditableSessionName.tsx index 4455cb7211..6415529617 100644 --- a/react/src/components/ComputeSessionNodeItems/EditableSessionName.tsx +++ b/react/src/components/ComputeSessionNodeItems/EditableSessionName.tsx @@ -7,7 +7,7 @@ import { useCurrentProjectValue } from '../../hooks/useCurrentProject'; import { useValidateSessionName } from '../../hooks/useValidateSessionName'; import { theme, Form, Input, App, GetProps, Typography } from 'antd'; import { CornerDownLeftIcon } from 'lucide-react'; -import React, { useState } from 'react'; +import React, { useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { graphql, @@ -36,6 +36,7 @@ const EditableSessionName: React.FC = ({ style, ...otherProps }) => { + 'use memo'; const relayEvn = useRelayEnvironment(); const currentProject = useCurrentProjectValue(); const session = useFragment( @@ -89,10 +90,21 @@ const EditableSessionName: React.FC = ({ const isPendingRenamingAndRefreshing = renameSessionMutation.isPending || optimisticName !== session.name; + + // focus back to the text component after editing for better UX related to keyboard shortcuts + const textRef = useRef(null); + const focusFallback = () => { + setTimeout(() => { + textRef.current?.focus(); + }, 0); + }; + return ( <> {(!isEditing || isPendingRenamingAndRefreshing) && ( = ({ } copyable style={{ + // after editing, focus this element, remove outline + outline: 'none', ...style, color: isPendingRenamingAndRefreshing ? token.colorTextTertiary @@ -121,6 +135,7 @@ const EditableSessionName: React.FC = ({
{ setIsEditing(false); + focusFallback(); setOptimisticName(values.sessionName); // FIXME: This API does not return any response on success or error. renameSessionMutation.mutate(values.sessionName, { @@ -190,7 +205,9 @@ const EditableSessionName: React.FC = ({ onKeyDown={(e) => { // when press escape key, cancel editing if (e.key === 'Escape') { + e.stopPropagation(); setIsEditing(false); + focusFallback(); } }} /> diff --git a/react/src/components/EditableVFolderName.tsx b/react/src/components/EditableVFolderName.tsx index 48274c6164..2234bfad61 100644 --- a/react/src/components/EditableVFolderName.tsx +++ b/react/src/components/EditableVFolderName.tsx @@ -18,7 +18,7 @@ import { import { BAILink, toLocalId, useErrorMessageResolver } from 'backend.ai-ui'; import _ from 'lodash'; import { CornerDownLeftIcon } from 'lucide-react'; -import React, { useState } from 'react'; +import React, { useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { graphql, @@ -55,6 +55,7 @@ const EditableVFolderName: React.FC = ({ inputProps, ...otherProps }) => { + 'use memo'; const vfolder = useFragment( graphql` fragment EditableVFolderNameFragment on VirtualFolderNode { @@ -93,10 +94,20 @@ const EditableVFolderName: React.FC = ({ const isPendingRenameMutation = renameMutation.isPending || optimisticName !== vfolder.name; + // focus back to the text component after editing for better UX related to keyboard shortcuts + const textRef = useRef(null); + const focusFallback = () => { + setTimeout(() => { + textRef.current?.focus(); + }, 0); + }; + return ( <> {(!isEditing || isPendingRenameMutation) && ( = ({ : false } style={{ + // after editing, focus this element, remove outline + outline: 'none', ...style, color: isPendingRenameMutation ? token.colorTextTertiary @@ -142,6 +155,7 @@ const EditableVFolderName: React.FC = ({ { setIsEditing(false); + focusFallback(); if (values.vfolderName === vfolder.name) { return; } @@ -238,7 +252,9 @@ const EditableVFolderName: React.FC = ({ onKeyDown={(e) => { // when press escape key, cancel editing if (e.key === 'Escape') { + e.stopPropagation(); setIsEditing(false); + focusFallback(); onEditEnd?.(); } }} diff --git a/react/src/components/FolderExplorerModal.tsx b/react/src/components/FolderExplorerModal.tsx index 5915f5fe12..ab14b463f5 100644 --- a/react/src/components/FolderExplorerModal.tsx +++ b/react/src/components/FolderExplorerModal.tsx @@ -150,6 +150,7 @@ const FolderExplorerModal: React.FC = ({ className={styles.baiModalHeader} width={'90%'} centered + keyboard destroyOnHidden footer={null} title={