index.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import { View, Textarea, InputProps } from "@tarojs/components";
  2. import TextPolish from '@/components/TextPolish'
  3. import styleIndex from "./index.module.less";
  4. import { useState, useRef, useCallback, useMemo, forwardRef, useImperativeHandle } from "react";
  5. import { countCharacters, getStrByMaxLength } from "@/utils/index";
  6. interface Props {
  7. aiType: "personality" | "greeting"
  8. defaultAiTips: string;
  9. placeholder?: string;
  10. value: string;
  11. cursorSpacing?: number;
  12. maxlength?: number;
  13. autoHeight?: boolean;
  14. disabled?: boolean;
  15. confirmType?: keyof InputProps.ConfirmType;
  16. extra?: () => JSX.Element | JSX.Element[] | undefined;
  17. prefix?: () => JSX.Element | JSX.Element[] | undefined;
  18. style?: Record<string, string>;
  19. onInput?: (value: string) => void;
  20. onBlur?: (value: string) => void;
  21. bgColor?: string;
  22. autoFocus?: boolean;
  23. extraClass?: string;
  24. onConfirm?: (value: string) => void;
  25. }
  26. const DEFAULT_TEXTAREA_MAXLENGTH = 10000;
  27. const Index = ({
  28. value,
  29. aiType,
  30. defaultAiTips,
  31. bgColor,
  32. extraClass,
  33. style,
  34. disabled,
  35. confirmType,
  36. prefix,
  37. autoHeight,
  38. autoFocus = false,
  39. placeholder = "请输入...",
  40. onInput,
  41. onBlur,
  42. cursorSpacing,
  43. maxlength,
  44. extra,
  45. onConfirm,
  46. }: Props, ref: any) => {
  47. const [focus, setFocus] = useState(false);
  48. const [isPolishing, setIsPolishing] = useState(false);
  49. const inputRef = useRef<HTMLInputElement>(null); // 创建一个 ref
  50. // Create ref for TextPolish component
  51. const textPolishRef = useRef<{ handleClick: () => void }>(null);
  52. // Expose TextPolish ref to parent components
  53. useImperativeHandle(ref, () => ({
  54. handleClick: async () => {
  55. if (textPolishRef.current) {
  56. return await textPolishRef.current.handleClick();
  57. }
  58. }
  59. }));
  60. // Memoize character count to avoid recalculation
  61. const currentLength = useMemo(() => countCharacters(value), [value]);
  62. const handleFocus = () => {
  63. setFocus(true);
  64. };
  65. const handleBlur = () => {
  66. console.log("textarea blur");
  67. setFocus(false);
  68. if (onBlur && inputRef.current) {
  69. onBlur(inputRef.current.value);
  70. }
  71. };
  72. const handleInput = useCallback((inputValue: string) => {
  73. if (!onInput) return;
  74. const len = countCharacters(inputValue);
  75. if (maxlength && len > maxlength) {
  76. const r = getStrByMaxLength(inputValue, maxlength);
  77. onInput(r);
  78. return;
  79. }
  80. onInput(inputValue);
  81. }, [onInput, maxlength]);
  82. const onPolished = (text: string | null) => {
  83. if (text && onInput) {
  84. onInput(text);
  85. }
  86. };
  87. const handleTextareaInput = (e: any) => {
  88. handleInput(e.target.value);
  89. };
  90. const handleConfirm = (e: any) => {
  91. onConfirm && onConfirm(e.detail.value);
  92. };
  93. // Memoize container style
  94. const containerStyle = useMemo(() => {
  95. return bgColor ? { backgroundColor: bgColor } : {};
  96. }, [bgColor]);
  97. const polishText = value?.length > 0 ? value : defaultAiTips;
  98. return (
  99. <View
  100. className={`${
  101. focus ? styleIndex.inputContainerFocused : styleIndex.inputContainer
  102. } p-12`}
  103. style={containerStyle}
  104. >
  105. <View className="flex w-full pb-12">
  106. <View className={styleIndex.label}>
  107. {prefix && prefix()}
  108. </View>
  109. <View className="flex items-center gap-4 text-14 font-pingfangSCMedium">
  110. <TextPolish ref={textPolishRef} text={polishText} type={aiType} onPolished={onPolished} onStateChange={setIsPolishing} />
  111. </View>
  112. </View>
  113. <View className={styleIndex.textareaContainer}>
  114. <Textarea
  115. ref={inputRef}
  116. value={value}
  117. disabled={disabled || isPolishing}
  118. confirmType={confirmType}
  119. style={style}
  120. onInput={handleTextareaInput}
  121. placeholder={placeholder}
  122. placeholderStyle="rgba(17,17,17,.25)"
  123. className={`${styleIndex.textInput} ${extraClass || ''}`}
  124. onFocus={handleFocus}
  125. onBlur={handleBlur}
  126. autoHeight={autoHeight}
  127. autoFocus={autoFocus}
  128. cursorSpacing={cursorSpacing}
  129. maxlength={DEFAULT_TEXTAREA_MAXLENGTH}
  130. onConfirm={handleConfirm}
  131. />
  132. <View className={`${styleIndex.textareaButtons} justify-end gap-8`}>
  133. {extra && extra()}
  134. {maxlength && (
  135. <View className="text-gray-4">
  136. {currentLength}/{maxlength}
  137. </View>
  138. )}
  139. </View>
  140. <View className={`${ isPolishing ? styleIndex.skelonton : styleIndex.skelontonHide}`}>
  141. <View className={styleIndex.skelontonItem}></View>
  142. <View className={styleIndex.skelontonItemShort}></View>
  143. </View>
  144. </View>
  145. </View>
  146. );
  147. };
  148. export default forwardRef(Index);