feat: add consistent keyboard shortcut support and visual indicators across all app creation dialogs (#17138)

This commit is contained in:
诗浓 2025-04-10 14:58:39 +08:00 committed by GitHub
parent 88cb81d3d6
commit d0d02be711
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 64 additions and 7 deletions

View File

@ -1,4 +1,6 @@
'use client'
import { useCallback } from 'react'
import { useKeyPress } from 'ahooks'
import AppList from './app-list'
import FullScreenModal from '@/app/components/base/fullscreen-modal'
@ -10,6 +12,13 @@ type CreateAppDialogProps = {
}
const CreateAppTemplateDialog = ({ show, onSuccess, onClose, onCreateFromBlank }: CreateAppDialogProps) => {
const handleEscKeyPress = useCallback(() => {
if (show)
onClose()
}, [show, onClose])
useKeyPress('esc', handleEscKeyPress)
return (
<FullScreenModal
open={show}

View File

@ -5,7 +5,8 @@ import { useMemo, useRef, useState } from 'react'
import { useRouter } from 'next/navigation'
import { useContext } from 'use-context-selector'
import { useTranslation } from 'react-i18next'
import { RiCloseLine } from '@remixicon/react'
import { RiCloseLine, RiCommandLine, RiCornerDownLeftLine } from '@remixicon/react'
import { useDebounceFn, useKeyPress } from 'ahooks'
import Uploader from './uploader'
import Button from '@/app/components/base/button'
import Input from '@/app/components/base/input'
@ -143,6 +144,18 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
isCreatingRef.current = false
}
const { run: handleCreateApp } = useDebounceFn(onCreate, { wait: 300 })
useKeyPress(['meta.enter', 'ctrl.enter'], () => {
if (show && !isAppsFull && ((currentTab === CreateFromDSLModalTab.FROM_FILE && currentFile) || (currentTab === CreateFromDSLModalTab.FROM_URL && dslUrlValue)))
handleCreateApp()
})
useKeyPress('esc', () => {
if (show && !showErrorModal)
onClose()
})
const onDSLConfirm: MouseEventHandler = async () => {
try {
if (!importId)
@ -266,7 +279,18 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
)}
<div className='flex justify-end px-6 py-5'>
<Button className='mr-2' onClick={onClose}>{t('app.newApp.Cancel')}</Button>
<Button disabled={buttonDisabled} variant="primary" onClick={onCreate}>{t('app.newApp.Create')}</Button>
<Button
disabled={buttonDisabled}
variant="primary"
onClick={handleCreateApp}
className="gap-1"
>
<span>{t('app.newApp.Create')}</span>
<div className='flex gap-0.5'>
<RiCommandLine size={14} className='system-kbd rounded-sm bg-components-kbd-bg-white p-0.5' />
<RiCornerDownLeftLine size={14} className='system-kbd rounded-sm bg-components-kbd-bg-white p-0.5' />
</div>
</Button>
</div>
</Modal>
<Modal

View File

@ -1,7 +1,8 @@
'use client'
import React, { useState } from 'react'
import React, { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { RiCloseLine } from '@remixicon/react'
import { RiCloseLine, RiCommandLine, RiCornerDownLeftLine } from '@remixicon/react'
import { useDebounceFn, useKeyPress } from 'ahooks'
import AppIconPicker from '../../base/app-icon-picker'
import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button'
@ -66,7 +67,7 @@ const CreateAppModal = ({
const { plan, enableBilling } = useProviderContext()
const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps)
const submit = () => {
const submit = useCallback(() => {
if (!name.trim()) {
Toast.notify({ type: 'error', message: t('explore.appCustomize.nameRequired') })
return
@ -80,7 +81,19 @@ const CreateAppModal = ({
use_icon_as_answer_icon: useIconAsAnswerIcon,
})
onHide()
}
}, [name, appIcon, description, useIconAsAnswerIcon, onConfirm, onHide, t])
const { run: handleSubmit } = useDebounceFn(submit, { wait: 300 })
useKeyPress(['meta.enter', 'ctrl.enter'], () => {
if (show && !(!isEditModal && isAppsFull) && name.trim())
handleSubmit()
})
useKeyPress('esc', () => {
if (show)
onHide()
})
return (
<>
@ -146,7 +159,18 @@ const CreateAppModal = ({
{!isEditModal && isAppsFull && <AppsFull className='mt-4' loc='app-explore-create' />}
</div>
<div className='flex flex-row-reverse'>
<Button disabled={!isEditModal && isAppsFull} className='ml-2 w-24' variant='primary' onClick={submit}>{!isEditModal ? t('common.operation.create') : t('common.operation.save')}</Button>
<Button
disabled={(!isEditModal && isAppsFull) || !name.trim()}
className='ml-2 w-24 gap-1'
variant='primary'
onClick={handleSubmit}
>
<span>{!isEditModal ? t('common.operation.create') : t('common.operation.save')}</span>
<div className='flex gap-0.5'>
<RiCommandLine size={14} className='system-kbd rounded-sm bg-components-kbd-bg-white p-0.5' />
<RiCornerDownLeftLine size={14} className='system-kbd rounded-sm bg-components-kbd-bg-white p-0.5' />
</div>
</Button>
<Button className='w-24' onClick={onHide}>{t('common.operation.cancel')}</Button>
</div>
</Modal>