PromptModal.tsx 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import { useEffect, useRef, useState } from 'react';
  2. import { Input, Modal } from 'antd';
  3. import type { InputRef } from 'antd';
  4. import { useTranslation } from 'react-i18next';
  5. import JsonEditor from '@/components/form/JsonEditor';
  6. interface PromptModalProps {
  7. open: boolean;
  8. onClose: () => void;
  9. title: string;
  10. okText?: string;
  11. type?: 'input' | 'textarea';
  12. initialValue?: string;
  13. loading?: boolean;
  14. json?: boolean;
  15. onConfirm: (value: string) => void;
  16. }
  17. export default function PromptModal({
  18. open,
  19. onClose,
  20. title,
  21. okText,
  22. type = 'input',
  23. initialValue = '',
  24. loading = false,
  25. json = false,
  26. onConfirm,
  27. }: PromptModalProps) {
  28. const { t } = useTranslation();
  29. const [value, setValue] = useState('');
  30. const textareaRef = useRef<HTMLTextAreaElement | null>(null);
  31. const inputRef = useRef<InputRef | null>(null);
  32. const [openedWith, setOpenedWith] = useState<string | null>(null);
  33. const openKey = open ? `${type}\u0000${initialValue}` : null;
  34. if (openKey !== openedWith) {
  35. setOpenedWith(openKey);
  36. if (open) setValue(initialValue);
  37. }
  38. useEffect(() => {
  39. if (!open) return;
  40. const id = setTimeout(() => {
  41. if (type === 'textarea') textareaRef.current?.focus();
  42. else inputRef.current?.focus();
  43. }, 50);
  44. return () => clearTimeout(id);
  45. }, [open, type]);
  46. function onKeydown(e: React.KeyboardEvent<HTMLTextAreaElement | HTMLInputElement>) {
  47. if (type !== 'textarea' && e.key === 'Enter') {
  48. e.preventDefault();
  49. onConfirm(value);
  50. return;
  51. }
  52. if (type === 'textarea' && e.ctrlKey && e.key.toLowerCase() === 's') {
  53. e.preventDefault();
  54. onConfirm(value);
  55. }
  56. }
  57. return (
  58. <Modal
  59. open={open}
  60. title={title}
  61. okText={okText ?? t('confirm')}
  62. cancelText={t('cancel')}
  63. mask={{ closable: false }}
  64. confirmLoading={loading}
  65. onOk={() => onConfirm(value)}
  66. onCancel={onClose}
  67. destroyOnHidden
  68. >
  69. {json ? (
  70. <JsonEditor value={value} onChange={setValue} minHeight="240px" maxHeight="60vh" />
  71. ) : type === 'textarea' ? (
  72. <Input.TextArea
  73. ref={(el) => {
  74. textareaRef.current =
  75. (el as unknown as { resizableTextArea?: { textArea: HTMLTextAreaElement } })
  76. ?.resizableTextArea?.textArea ?? null;
  77. }}
  78. aria-label={title}
  79. value={value}
  80. onChange={(e) => setValue(e.target.value)}
  81. autoSize={{ minRows: 10, maxRows: 20 }}
  82. onKeyDown={onKeydown}
  83. />
  84. ) : (
  85. <Input
  86. ref={inputRef}
  87. aria-label={title}
  88. value={value}
  89. onChange={(e) => setValue(e.target.value)}
  90. onKeyDown={onKeydown}
  91. />
  92. )}
  93. </Modal>
  94. );
  95. }