返回 AiToEarn
index.tsx
1 /**
2 * EditTitleModal - 编辑标题弹窗组件
3 * 功能:编辑任务标题,支持字数限制和回车提交
4 */
5
6 'use client'
7
8 import { memo, useCallback, useEffect, useState } from 'react'
9 import { useTransClient } from '@/app/i18n/client'
10 import { Input } from '@/components/ui/input'
11 import { Label } from '@/components/ui/label'
12 import { Modal } from '@/components/ui/modal'
13
14 export interface IEditTitleModalProps {
15 /** 是否显示 */
16 open: boolean
17 /** 关闭回调 */
18 onOpenChange: (open: boolean) => void
19 /** 当前标题 */
20 currentTitle: string
21 /** 保存回调 */
22 onSave: (title: string) => Promise<void>
23 /** 最大字数限制 */
24 maxLength?: number
25 }
26
27 /** 内部内容组件 */
28 const ModalContent = memo(
29 ({ onOpenChange, currentTitle, onSave, maxLength = 100 }: Omit<IEditTitleModalProps, 'open'>) => {
30 const { t } = useTransClient('chat')
31 const [title, setTitle] = useState(currentTitle)
32 const [isLoading, setIsLoading] = useState(false)
33
34 // 当 currentTitle 变化时更新内部状态
35 useEffect(() => {
36 setTitle(currentTitle)
37 }, [currentTitle])
38
39 const handleSave = useCallback(async () => {
40 const trimmedTitle = title.trim()
41 if (!trimmedTitle || isLoading)
42 return
43
44 setIsLoading(true)
45 try {
46 await onSave(trimmedTitle)
47 onOpenChange(false)
48 }
49 finally {
50 setIsLoading(false)
51 }
52 }, [title, isLoading, onSave, onOpenChange])
53
54 const handleKeyDown = useCallback(
55 (e: React.KeyboardEvent) => {
56 if (e.key === 'Enter' && !e.shiftKey) {
57 e.preventDefault()
58 handleSave()
59 }
60 },
61 [handleSave],
62 )
63
64 const handleClose = useCallback(() => {
65 onOpenChange(false)
66 }, [onOpenChange])
67
68 return (
69 <Modal
70 open
71 title={t('task.editTitle')}
72 onCancel={handleClose}
73 onOk={handleSave}
74 confirmLoading={isLoading}
75 okText={t('rating.submit')}
76 cancelText={t('rating.cancel')}
77 width={400}
78 >
79 <div className="space-y-4 py-2">
80 <div className="space-y-2">
81 <Label htmlFor="task-title">{t('task.titleLabel')}</Label>
82 <Input
83 id="task-title"
84 value={title}
85 onChange={e => setTitle(e.target.value)}
86 onKeyDown={handleKeyDown}
87 placeholder={t('task.titlePlaceholder')}
88 maxLength={maxLength}
89 autoFocus
90 />
91 <div className="text-xs text-muted-foreground text-right">
92 {title.length}
93 {' '}
94 /
95 {maxLength}
96 </div>
97 </div>
98 </div>
99 </Modal>
100 )
101 },
102 )
103
104 /**
105 * EditTitleModal - 编辑标题弹窗
106 * 使用两层组件模式避免动态加载 namespace 导致闪烁
107 */
108 export function EditTitleModal({ open, ...props }: IEditTitleModalProps) {
109 // 只在打开时渲染内部组件,避免动态加载 namespace 导致闪烁
110 if (!open)
111 return null
112
113 return <ModalContent {...props} />
114 }
115
116 export default EditTitleModal
117
117 lines Plain Text