mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-27 16:33:49 +00:00
perf:textarea auto height (#2967)
* perf:textarea auto height * optimize editor height & fix variable label split
This commit is contained in:
@@ -61,6 +61,7 @@ export default function Editor({
|
|||||||
const [key, setKey] = useState(getNanoid(6));
|
const [key, setKey] = useState(getNanoid(6));
|
||||||
const [_, startSts] = useTransition();
|
const [_, startSts] = useTransition();
|
||||||
const [focus, setFocus] = useState(false);
|
const [focus, setFocus] = useState(false);
|
||||||
|
const [scrollHeight, setScrollHeight] = useState(0);
|
||||||
|
|
||||||
const initialConfig = {
|
const initialConfig = {
|
||||||
namespace: 'promptEditor',
|
namespace: 'promptEditor',
|
||||||
@@ -128,6 +129,8 @@ export default function Editor({
|
|||||||
<FocusPlugin focus={focus} setFocus={setFocus} />
|
<FocusPlugin focus={focus} setFocus={setFocus} />
|
||||||
<OnChangePlugin
|
<OnChangePlugin
|
||||||
onChange={(editorState, editor) => {
|
onChange={(editorState, editor) => {
|
||||||
|
const rootElement = editor.getRootElement();
|
||||||
|
setScrollHeight(rootElement?.scrollHeight || 0);
|
||||||
startSts(() => {
|
startSts(() => {
|
||||||
onChange?.(editorState, editor);
|
onChange?.(editorState, editor);
|
||||||
});
|
});
|
||||||
@@ -139,7 +142,7 @@ export default function Editor({
|
|||||||
<VariablePickerPlugin variables={variableLabels.length > 0 ? [] : variables} />
|
<VariablePickerPlugin variables={variableLabels.length > 0 ? [] : variables} />
|
||||||
<OnBlurPlugin onBlur={onBlur} />
|
<OnBlurPlugin onBlur={onBlur} />
|
||||||
</LexicalComposer>
|
</LexicalComposer>
|
||||||
{showOpenModal && (
|
{showOpenModal && scrollHeight > maxH && (
|
||||||
<Box
|
<Box
|
||||||
zIndex={10}
|
zIndex={10}
|
||||||
position={'absolute'}
|
position={'absolute'}
|
||||||
|
@@ -11,7 +11,9 @@ export default function VariableLabel({
|
|||||||
nodeAvatar: string;
|
nodeAvatar: string;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [parentLabel, childLabel] = variableLabel.split('.');
|
// avoid including '.' in the variable name.
|
||||||
|
const [parentLabel, ...childLabels] = variableLabel.split('.');
|
||||||
|
const childLabel = childLabels.join('.');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@@ -57,6 +57,7 @@
|
|||||||
"react-i18next": "14.1.2",
|
"react-i18next": "14.1.2",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
"react-syntax-highlighter": "^15.5.0",
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
|
"react-textarea-autosize": "^8.5.4",
|
||||||
"reactflow": "^11.7.4",
|
"reactflow": "^11.7.4",
|
||||||
"rehype-external-links": "^3.0.0",
|
"rehype-external-links": "^3.0.0",
|
||||||
"rehype-katex": "^7.0.0",
|
"rehype-katex": "^7.0.0",
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React, { useRef } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
@@ -13,11 +13,12 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
|||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
|
import ResizeTextarea from 'react-textarea-autosize';
|
||||||
|
|
||||||
type Props = TextareaProps & {
|
type Props = TextareaProps & {
|
||||||
title?: string;
|
title?: string;
|
||||||
iconSrc?: string;
|
iconSrc?: string;
|
||||||
// variables: string[];
|
autoHeight?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MyTextarea = React.forwardRef<HTMLTextAreaElement, Props>(function MyTextarea(props, ref) {
|
const MyTextarea = React.forwardRef<HTMLTextAreaElement, Props>(function MyTextarea(props, ref) {
|
||||||
@@ -28,6 +29,10 @@ const MyTextarea = React.forwardRef<HTMLTextAreaElement, Props>(function MyTexta
|
|||||||
const {
|
const {
|
||||||
title = t('common:core.app.edit.Prompt Editor'),
|
title = t('common:core.app.edit.Prompt Editor'),
|
||||||
iconSrc = 'modal/edit',
|
iconSrc = 'modal/edit',
|
||||||
|
autoHeight = false,
|
||||||
|
onChange,
|
||||||
|
maxH,
|
||||||
|
minH,
|
||||||
...childProps
|
...childProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -35,16 +40,27 @@ const MyTextarea = React.forwardRef<HTMLTextAreaElement, Props>(function MyTexta
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Editor textareaRef={TextareaRef} {...childProps} onOpenModal={onOpen} />
|
<Editor
|
||||||
|
textareaRef={TextareaRef}
|
||||||
|
autoHeight={autoHeight}
|
||||||
|
onChange={onChange}
|
||||||
|
maxH={maxH}
|
||||||
|
minH={minH}
|
||||||
|
showResize={!autoHeight}
|
||||||
|
{...childProps}
|
||||||
|
onOpenModal={onOpen}
|
||||||
|
/>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<MyModal iconSrc={iconSrc} title={title} isOpen onClose={onClose}>
|
<MyModal iconSrc={iconSrc} title={title} isOpen onClose={onClose}>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<Editor
|
<Editor
|
||||||
textareaRef={ModalTextareaRef}
|
textareaRef={ModalTextareaRef}
|
||||||
|
onChange={onChange}
|
||||||
{...childProps}
|
{...childProps}
|
||||||
minH={'300px'}
|
maxH={500}
|
||||||
maxH={'auto'}
|
minH={500}
|
||||||
minW={['100%', '512px']}
|
minW={['100%', '512px']}
|
||||||
|
showResize={false}
|
||||||
/>
|
/>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
@@ -71,17 +87,44 @@ export default React.memo(MyTextarea);
|
|||||||
const Editor = React.memo(function Editor({
|
const Editor = React.memo(function Editor({
|
||||||
onOpenModal,
|
onOpenModal,
|
||||||
textareaRef,
|
textareaRef,
|
||||||
|
autoHeight = false,
|
||||||
|
onChange,
|
||||||
|
maxH,
|
||||||
|
minH,
|
||||||
|
showResize,
|
||||||
...props
|
...props
|
||||||
}: Props & {
|
}: Props & {
|
||||||
textareaRef: React.RefObject<HTMLTextAreaElement>;
|
textareaRef: React.RefObject<HTMLTextAreaElement>;
|
||||||
onOpenModal?: () => void;
|
onOpenModal?: () => void;
|
||||||
|
showResize?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [scrollHeight, setScrollHeight] = useState(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box h={'100%'} w={'100%'} position={'relative'}>
|
<Box h={'100%'} w={'100%'} position={'relative'}>
|
||||||
<Textarea ref={textareaRef} maxW={'100%'} {...props} />
|
<Textarea
|
||||||
{onOpenModal && (
|
ref={textareaRef}
|
||||||
|
maxW={'100%'}
|
||||||
|
as={autoHeight ? ResizeTextarea : undefined}
|
||||||
|
sx={
|
||||||
|
!showResize
|
||||||
|
? {
|
||||||
|
'::-webkit-resizer': {
|
||||||
|
display: 'none'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
}
|
||||||
|
{...props}
|
||||||
|
maxH={`${maxH}px`}
|
||||||
|
minH={`${minH}px`}
|
||||||
|
onChange={(e) => {
|
||||||
|
setScrollHeight(e.target.scrollHeight);
|
||||||
|
onChange?.(e);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{onOpenModal && maxH && scrollHeight > Number(maxH) && (
|
||||||
<Box
|
<Box
|
||||||
zIndex={1}
|
zIndex={1}
|
||||||
position={'absolute'}
|
position={'absolute'}
|
||||||
|
@@ -23,12 +23,12 @@ import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
|
|||||||
import MyRadio from '@/components/common/MyRadio';
|
import MyRadio from '@/components/common/MyRadio';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||||
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
|
|
||||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
import SelectAiModel from '@/components/Select/AIModelSelector';
|
import SelectAiModel from '@/components/Select/AIModelSelector';
|
||||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||||
|
import MyTextarea from '@/components/common/Textarea/MyTextarea';
|
||||||
|
|
||||||
export type DatasetParamsProps = {
|
export type DatasetParamsProps = {
|
||||||
searchMode: `${DatasetSearchModeEnum}`;
|
searchMode: `${DatasetSearchModeEnum}`;
|
||||||
@@ -317,14 +317,14 @@ const DatasetParamsModal = ({
|
|||||||
></QuestionTip>
|
></QuestionTip>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Box mt={1}>
|
<Box mt={1}>
|
||||||
<PromptEditor
|
<MyTextarea
|
||||||
|
autoHeight
|
||||||
minH={150}
|
minH={150}
|
||||||
maxH={300}
|
maxH={300}
|
||||||
showOpenModal={false}
|
|
||||||
placeholder={t('common:core.module.QueryExtension.placeholder')}
|
placeholder={t('common:core.module.QueryExtension.placeholder')}
|
||||||
value={cfbBgDesc}
|
value={cfbBgDesc}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setValue('datasetSearchExtensionBg', e);
|
setValue('datasetSearchExtensionBg', e.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
@@ -24,6 +24,9 @@ const WelcomeTextConfig = (props: TextareaProps) => {
|
|||||||
fontSize={'sm'}
|
fontSize={'sm'}
|
||||||
bg={'myGray.50'}
|
bg={'myGray.50'}
|
||||||
placeholder={t('common:core.app.tip.welcomeTextTip')}
|
placeholder={t('common:core.app.tip.welcomeTextTip')}
|
||||||
|
autoHeight
|
||||||
|
minH={100}
|
||||||
|
maxH={200}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Controller, UseFormReturn } from 'react-hook-form';
|
import { Controller, UseFormReturn } from 'react-hook-form';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Input,
|
|
||||||
NumberDecrementStepper,
|
NumberDecrementStepper,
|
||||||
NumberIncrementStepper,
|
NumberIncrementStepper,
|
||||||
NumberInput,
|
NumberInput,
|
||||||
@@ -24,7 +23,7 @@ import { ChatBoxContext } from '../Provider';
|
|||||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||||
import { useDeepCompareEffect } from 'ahooks';
|
import { useDeepCompareEffect } from 'ahooks';
|
||||||
import { VariableItemType } from '@fastgpt/global/core/app/type';
|
import { VariableItemType } from '@fastgpt/global/core/app/type';
|
||||||
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
|
import MyTextarea from '@/components/common/Textarea/MyTextarea';
|
||||||
|
|
||||||
export const VariableInputItem = ({
|
export const VariableInputItem = ({
|
||||||
item,
|
item,
|
||||||
@@ -60,13 +59,13 @@ export const VariableInputItem = ({
|
|||||||
{item.description && <QuestionTip ml={1} label={item.description} />}
|
{item.description && <QuestionTip ml={1} label={item.description} />}
|
||||||
</Box>
|
</Box>
|
||||||
{item.type === VariableInputEnum.input && (
|
{item.type === VariableInputEnum.input && (
|
||||||
<PromptEditor
|
<MyTextarea
|
||||||
value={item.defaultValue}
|
autoHeight
|
||||||
onChange={(e) => setValue(item.key, e)}
|
|
||||||
bg={'myGray.50'}
|
|
||||||
minH={40}
|
minH={40}
|
||||||
maxH={150}
|
maxH={160}
|
||||||
showOpenModal={false}
|
bg={'myGray.50'}
|
||||||
|
value={item.defaultValue}
|
||||||
|
onChange={(e) => setValue(item.key, e.target.value)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{item.type === VariableInputEnum.textarea && (
|
{item.type === VariableInputEnum.textarea && (
|
||||||
|
@@ -38,7 +38,7 @@ import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/consta
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||||
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
|
import MyTextarea from '@/components/common/Textarea/MyTextarea';
|
||||||
|
|
||||||
type props = {
|
type props = {
|
||||||
value: UserChatItemValueItemType | AIChatItemValueItemType;
|
value: UserChatItemValueItemType | AIChatItemValueItemType;
|
||||||
@@ -221,12 +221,15 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({
|
|||||||
{input.description && <QuestionTip ml={1} label={input.description} />}
|
{input.description && <QuestionTip ml={1} label={input.description} />}
|
||||||
</Flex>
|
</Flex>
|
||||||
{input.type === FlowNodeInputTypeEnum.input && (
|
{input.type === FlowNodeInputTypeEnum.input && (
|
||||||
<PromptEditor
|
<MyTextarea
|
||||||
value={input.value}
|
isDisabled={interactive.params.submitted}
|
||||||
onChange={(e) => setValue(input.label, e)}
|
{...register(input.label, {
|
||||||
|
required: input.required
|
||||||
|
})}
|
||||||
|
bg={'white'}
|
||||||
|
autoHeight
|
||||||
minH={40}
|
minH={40}
|
||||||
maxH={100}
|
maxH={100}
|
||||||
showOpenModal={false}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{input.type === FlowNodeInputTypeEnum.textarea && (
|
{input.type === FlowNodeInputTypeEnum.textarea && (
|
||||||
|
@@ -33,7 +33,7 @@ import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
|||||||
import { AppContext } from '../../../context';
|
import { AppContext } from '../../../context';
|
||||||
import { VariableInputItem } from '@/components/core/chat/ChatContainer/ChatBox/components/VariableInput';
|
import { VariableInputItem } from '@/components/core/chat/ChatContainer/ChatBox/components/VariableInput';
|
||||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||||
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
|
import MyTextarea from '@/components/common/Textarea/MyTextarea';
|
||||||
|
|
||||||
const MyRightDrawer = dynamic(
|
const MyRightDrawer = dynamic(
|
||||||
() => import('@fastgpt/web/components/common/MyDrawer/MyRightDrawer')
|
() => import('@fastgpt/web/components/common/MyDrawer/MyRightDrawer')
|
||||||
@@ -270,16 +270,16 @@ export const useDebug = () => {
|
|||||||
const RenderInput = (() => {
|
const RenderInput = (() => {
|
||||||
if (input.valueType === WorkflowIOValueTypeEnum.string) {
|
if (input.valueType === WorkflowIOValueTypeEnum.string) {
|
||||||
return (
|
return (
|
||||||
<PromptEditor
|
<MyTextarea
|
||||||
|
autoHeight
|
||||||
|
minH={40}
|
||||||
|
maxH={160}
|
||||||
|
bg={'myGray.50'}
|
||||||
|
placeholder={t(input.placeholder || ('' as any))}
|
||||||
value={getValues(`nodeVariables.${input.key}`)}
|
value={getValues(`nodeVariables.${input.key}`)}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setValue(`nodeVariables.${input.key}`, e);
|
setValue(`nodeVariables.${input.key}`, e.target.value);
|
||||||
}}
|
}}
|
||||||
minH={50}
|
|
||||||
maxH={150}
|
|
||||||
showOpenModal={false}
|
|
||||||
placeholder={t(input.placeholder || ('' as any))}
|
|
||||||
bg={'myGray.50'}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -34,7 +34,7 @@ import { useFieldArray, UseFormReturn } from 'react-hook-form';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import DndDrag, { Draggable } from '@fastgpt/web/components/common/DndDrag';
|
import DndDrag, { Draggable } from '@fastgpt/web/components/common/DndDrag';
|
||||||
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
|
import MyTextarea from '@/components/common/Textarea/MyTextarea';
|
||||||
|
|
||||||
type ListValueType = { id: string; value: string; label: string }[];
|
type ListValueType = { id: string; value: string; label: string }[];
|
||||||
|
|
||||||
@@ -316,15 +316,12 @@ const InputTypeConfig = ({
|
|||||||
</NumberInput>
|
</NumberInput>
|
||||||
)}
|
)}
|
||||||
{inputType === FlowNodeInputTypeEnum.input && (
|
{inputType === FlowNodeInputTypeEnum.input && (
|
||||||
<PromptEditor
|
<MyTextarea
|
||||||
value={defaultValue}
|
{...register('defaultValue')}
|
||||||
onChange={(e) => {
|
|
||||||
setValue('defaultValue', e);
|
|
||||||
}}
|
|
||||||
minH={40}
|
|
||||||
maxH={200}
|
|
||||||
showOpenModal={false}
|
|
||||||
bg={'myGray.50'}
|
bg={'myGray.50'}
|
||||||
|
autoHeight
|
||||||
|
minH={40}
|
||||||
|
maxH={100}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{inputType === FlowNodeInputTypeEnum.JSONEditor && (
|
{inputType === FlowNodeInputTypeEnum.JSONEditor && (
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React, { Dispatch, useMemo, useState } from 'react';
|
import React, { Dispatch, useMemo } from 'react';
|
||||||
import { NodeProps } from 'reactflow';
|
import { NodeProps } from 'reactflow';
|
||||||
import NodeCard from '../render/NodeCard';
|
import NodeCard from '../render/NodeCard';
|
||||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||||
@@ -10,7 +10,7 @@ import MyTextarea from '@/components/common/Textarea/MyTextarea';
|
|||||||
import { AppContext } from '../../../../context';
|
import { AppContext } from '../../../../context';
|
||||||
import { AppChatConfigType, AppDetailType } from '@fastgpt/global/core/app/type';
|
import { AppChatConfigType, AppDetailType } from '@fastgpt/global/core/app/type';
|
||||||
import { getAppChatConfig } from '@fastgpt/global/core/workflow/utils';
|
import { getAppChatConfig } from '@fastgpt/global/core/workflow/utils';
|
||||||
import { useCreation, useMount } from 'ahooks';
|
import { useMount } from 'ahooks';
|
||||||
import ChatFunctionTip from '@/components/core/app/Tip';
|
import ChatFunctionTip from '@/components/core/app/Tip';
|
||||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||||
import { WorkflowContext } from '../../../context';
|
import { WorkflowContext } from '../../../context';
|
||||||
@@ -96,6 +96,9 @@ function Instruction({ chatConfig: { instruction }, setAppDetail }: ComponentPro
|
|||||||
resize={'both'}
|
resize={'both'}
|
||||||
placeholder={t('workflow:plugin.Instruction_Tip')}
|
placeholder={t('workflow:plugin.Instruction_Tip')}
|
||||||
value={instruction}
|
value={instruction}
|
||||||
|
autoHeight
|
||||||
|
minH={100}
|
||||||
|
maxH={240}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setAppDetail((state) => ({
|
setAppDetail((state) => ({
|
||||||
...state,
|
...state,
|
||||||
|
Reference in New Issue
Block a user