mirror of
https://github.com/Yidadaa/ChatGPT-Next-Web.git
synced 2025-08-08 15:16:24 +08:00
Merge branch 'main' into bugfix-0503
This commit is contained in:
@@ -67,7 +67,10 @@ export function ChatItem(props: {
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className={styles["chat-item-delete"]} onClick={props.onDelete}>
|
||||
<div
|
||||
className={styles["chat-item-delete"]}
|
||||
onClickCapture={props.onDelete}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</div>
|
||||
</div>
|
||||
@@ -77,14 +80,14 @@ export function ChatItem(props: {
|
||||
}
|
||||
|
||||
export function ChatList(props: { narrow?: boolean }) {
|
||||
const [sessions, selectedIndex, selectSession, removeSession, moveSession] =
|
||||
useChatStore((state) => [
|
||||
const [sessions, selectedIndex, selectSession, moveSession] = useChatStore(
|
||||
(state) => [
|
||||
state.sessions,
|
||||
state.currentSessionIndex,
|
||||
state.selectSession,
|
||||
state.removeSession,
|
||||
state.moveSession,
|
||||
]);
|
||||
],
|
||||
);
|
||||
const chatStore = useChatStore();
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import { memo, useState, useRef, useEffect, useLayoutEffect } from "react";
|
||||
import { useState, useRef, useEffect, useLayoutEffect } from "react";
|
||||
|
||||
import SendWhiteIcon from "../icons/send-white.svg";
|
||||
import BrainIcon from "../icons/brain.svg";
|
||||
@@ -64,12 +64,9 @@ import {
|
||||
useMaskStore,
|
||||
} from "../store/mask";
|
||||
|
||||
const Markdown = dynamic(
|
||||
async () => memo((await import("./markdown")).Markdown),
|
||||
{
|
||||
loading: () => <LoadingIcon />,
|
||||
},
|
||||
);
|
||||
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
|
||||
loading: () => <LoadingIcon />,
|
||||
});
|
||||
|
||||
function exportMessages(messages: Message[], topic: string) {
|
||||
const mdText =
|
||||
@@ -394,7 +391,7 @@ export function Chat() {
|
||||
const onPromptSelect = (prompt: Prompt) => {
|
||||
setPromptHints([]);
|
||||
inputRef.current?.focus();
|
||||
setUserInput(prompt.content);
|
||||
setTimeout(() => setUserInput(prompt.content), 60);
|
||||
};
|
||||
|
||||
// auto grow input
|
||||
@@ -728,6 +725,7 @@ export function Chat() {
|
||||
}}
|
||||
fontSize={fontSize}
|
||||
parentRef={scrollRef}
|
||||
defaultShow={i >= messages.length - 10}
|
||||
/>
|
||||
</div>
|
||||
{!isUser && !message.preview && (
|
||||
|
@@ -9,6 +9,7 @@ import { useRef, useState, RefObject, useEffect } from "react";
|
||||
import { copyToClipboard } from "../utils";
|
||||
|
||||
import LoadingIcon from "../icons/three-dots.svg";
|
||||
import React from "react";
|
||||
|
||||
export function PreCode(props: { children: any }) {
|
||||
const ref = useRef<HTMLPreElement>(null);
|
||||
@@ -29,78 +30,94 @@ export function PreCode(props: { children: any }) {
|
||||
);
|
||||
}
|
||||
|
||||
function _MarkDownContent(props: { content: string }) {
|
||||
return (
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]}
|
||||
rehypePlugins={[
|
||||
RehypeKatex,
|
||||
[
|
||||
RehypeHighlight,
|
||||
{
|
||||
detect: false,
|
||||
ignoreMissing: true,
|
||||
},
|
||||
],
|
||||
]}
|
||||
components={{
|
||||
pre: PreCode,
|
||||
a: (aProps) => {
|
||||
const href = aProps.href || "";
|
||||
const isInternal = /^\/#/i.test(href);
|
||||
const target = isInternal ? "_self" : aProps.target ?? "_blank";
|
||||
return <a {...aProps} target={target} />;
|
||||
},
|
||||
}}
|
||||
>
|
||||
{props.content}
|
||||
</ReactMarkdown>
|
||||
);
|
||||
}
|
||||
|
||||
export const MarkdownContent = React.memo(_MarkDownContent);
|
||||
|
||||
export function Markdown(
|
||||
props: {
|
||||
content: string;
|
||||
loading?: boolean;
|
||||
fontSize?: number;
|
||||
parentRef: RefObject<HTMLDivElement>;
|
||||
defaultShow?: boolean;
|
||||
} & React.DOMAttributes<HTMLDivElement>,
|
||||
) {
|
||||
const mdRef = useRef<HTMLDivElement>(null);
|
||||
const renderedHeight = useRef(0);
|
||||
const inView = useRef(!!props.defaultShow);
|
||||
|
||||
const parent = props.parentRef.current;
|
||||
const md = mdRef.current;
|
||||
const rendered = useRef(true); // disable lazy loading for bad ux
|
||||
const [counter, setCounter] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
// to triggr rerender
|
||||
setCounter(counter + 1);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [props.loading]);
|
||||
const checkInView = () => {
|
||||
if (parent && md) {
|
||||
const parentBounds = parent.getBoundingClientRect();
|
||||
const twoScreenHeight = Math.max(500, parentBounds.height * 2);
|
||||
const mdBounds = md.getBoundingClientRect();
|
||||
const isInRange = (x: number) =>
|
||||
x <= parentBounds.bottom + twoScreenHeight &&
|
||||
x >= parentBounds.top - twoScreenHeight;
|
||||
inView.current = isInRange(mdBounds.top) || isInRange(mdBounds.bottom);
|
||||
}
|
||||
|
||||
const inView =
|
||||
rendered.current ||
|
||||
(() => {
|
||||
if (parent && md) {
|
||||
const parentBounds = parent.getBoundingClientRect();
|
||||
const mdBounds = md.getBoundingClientRect();
|
||||
const isInRange = (x: number) =>
|
||||
x <= parentBounds.bottom && x >= parentBounds.top;
|
||||
const inView = isInRange(mdBounds.top) || isInRange(mdBounds.bottom);
|
||||
if (inView.current && md) {
|
||||
renderedHeight.current = Math.max(
|
||||
renderedHeight.current,
|
||||
md.getBoundingClientRect().height,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (inView) {
|
||||
rendered.current = true;
|
||||
}
|
||||
|
||||
return inView;
|
||||
}
|
||||
})();
|
||||
|
||||
const shouldLoading = props.loading || !inView;
|
||||
checkInView();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="markdown-body"
|
||||
style={{ fontSize: `${props.fontSize ?? 14}px` }}
|
||||
style={{
|
||||
fontSize: `${props.fontSize ?? 14}px`,
|
||||
height:
|
||||
!inView.current && renderedHeight.current > 0
|
||||
? renderedHeight.current
|
||||
: "auto",
|
||||
}}
|
||||
ref={mdRef}
|
||||
onContextMenu={props.onContextMenu}
|
||||
onDoubleClickCapture={props.onDoubleClickCapture}
|
||||
>
|
||||
{shouldLoading ? (
|
||||
<LoadingIcon />
|
||||
) : (
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]}
|
||||
rehypePlugins={[
|
||||
RehypeKatex,
|
||||
[
|
||||
RehypeHighlight,
|
||||
{
|
||||
detect: false,
|
||||
ignoreMissing: true,
|
||||
},
|
||||
],
|
||||
]}
|
||||
components={{
|
||||
pre: PreCode,
|
||||
}}
|
||||
linkTarget={"_blank"}
|
||||
>
|
||||
{props.content}
|
||||
</ReactMarkdown>
|
||||
)}
|
||||
{inView.current &&
|
||||
(props.loading ? (
|
||||
<LoadingIcon />
|
||||
) : (
|
||||
<MarkdownContent content={props.content} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -1,16 +1,4 @@
|
||||
@import "../styles/animation.scss";
|
||||
|
||||
@keyframes search-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(5vh) scaleX(0.5);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scaleX(1);
|
||||
}
|
||||
}
|
||||
|
||||
.mask-page {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
@@ -23,8 +11,9 @@
|
||||
.mask-filter {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin-bottom: 10px;
|
||||
animation: search-in ease 0.3s;
|
||||
margin-bottom: 20px;
|
||||
animation: slide-in ease 0.3s;
|
||||
height: 40px;
|
||||
|
||||
display: flex;
|
||||
|
||||
@@ -32,8 +21,6 @@
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
margin-bottom: 20px;
|
||||
animation: search-in ease 0.3s;
|
||||
}
|
||||
|
||||
.mask-filter-lang {
|
||||
@@ -45,10 +32,7 @@
|
||||
height: 100%;
|
||||
margin-left: 10px;
|
||||
box-sizing: border-box;
|
||||
|
||||
button {
|
||||
padding: 10px;
|
||||
}
|
||||
min-width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -291,14 +291,16 @@ export function MaskPage() {
|
||||
))}
|
||||
</select>
|
||||
|
||||
<div className={styles["mask-create"]}>
|
||||
<IconButton
|
||||
icon={<AddIcon />}
|
||||
text={Locale.Mask.Page.Create}
|
||||
bordered
|
||||
onClick={() => maskStore.create()}
|
||||
/>
|
||||
</div>
|
||||
<IconButton
|
||||
className={styles["mask-create"]}
|
||||
icon={<AddIcon />}
|
||||
text={Locale.Mask.Page.Create}
|
||||
bordered
|
||||
onClick={() => {
|
||||
const createdMask = maskStore.create();
|
||||
setEditingMaskId(createdMask.id);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
@@ -59,10 +59,9 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.search-bar {
|
||||
.more {
|
||||
font-size: 12px;
|
||||
margin-right: 10px;
|
||||
width: 40vw;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -5,10 +5,11 @@ import { EmojiAvatar } from "./emoji";
|
||||
import styles from "./new-chat.module.scss";
|
||||
|
||||
import LeftIcon from "../icons/left.svg";
|
||||
import AddIcon from "../icons/lightning.svg";
|
||||
import LightningIcon from "../icons/lightning.svg";
|
||||
import EyeIcon from "../icons/eye.svg";
|
||||
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { createEmptyMask, Mask, useMaskStore } from "../store/mask";
|
||||
import { Mask, useMaskStore } from "../store/mask";
|
||||
import Locale from "../locales";
|
||||
import { useAppConfig, useChatStore } from "../store";
|
||||
import { MaskAvatar } from "./mask";
|
||||
@@ -148,20 +149,22 @@ export function NewChat() {
|
||||
<div className={styles["sub-title"]}>{Locale.NewChat.SubTitle}</div>
|
||||
|
||||
<div className={styles["actions"]}>
|
||||
<input
|
||||
className={styles["search-bar"]}
|
||||
placeholder={Locale.NewChat.More}
|
||||
type="text"
|
||||
onClick={() => navigate(Path.Masks)}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
text={Locale.NewChat.Skip}
|
||||
onClick={() => startChat()}
|
||||
icon={<AddIcon />}
|
||||
icon={<LightningIcon />}
|
||||
type="primary"
|
||||
shadow
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
className={styles["more"]}
|
||||
text={Locale.NewChat.More}
|
||||
onClick={() => navigate(Path.Masks)}
|
||||
icon={<EyeIcon />}
|
||||
bordered
|
||||
shadow
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles["masks"]}>
|
||||
|
@@ -7,6 +7,20 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.edit-prompt-modal {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.edit-prompt-title {
|
||||
max-width: unset;
|
||||
margin-bottom: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
.edit-prompt-content {
|
||||
max-width: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.user-prompt-modal {
|
||||
min-height: 40vh;
|
||||
|
||||
@@ -18,47 +32,42 @@
|
||||
}
|
||||
|
||||
.user-prompt-list {
|
||||
padding: 10px 0;
|
||||
border: var(--border-in-light);
|
||||
border-radius: 10px;
|
||||
|
||||
.user-prompt-item {
|
||||
margin-bottom: 10px;
|
||||
widows: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: var(--border-in-light);
|
||||
}
|
||||
|
||||
.user-prompt-header {
|
||||
display: flex;
|
||||
widows: 100%;
|
||||
margin-bottom: 5px;
|
||||
max-width: calc(100% - 100px);
|
||||
|
||||
.user-prompt-title {
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
margin-right: 5px;
|
||||
padding: 5px;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
line-height: 2;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.user-prompt-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.user-prompt-button {
|
||||
height: 100%;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
.user-prompt-content {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-prompt-content {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 5px;
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
flex-grow: 1;
|
||||
.user-prompt-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.user-prompt-button {
|
||||
height: 100%;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,10 +3,12 @@ import { useState, useEffect, useMemo, HTMLProps, useRef } from "react";
|
||||
import styles from "./settings.module.scss";
|
||||
|
||||
import ResetIcon from "../icons/reload.svg";
|
||||
import AddIcon from "../icons/add.svg";
|
||||
import CloseIcon from "../icons/close.svg";
|
||||
import CopyIcon from "../icons/copy.svg";
|
||||
import ClearIcon from "../icons/clear.svg";
|
||||
import EditIcon from "../icons/edit.svg";
|
||||
import EyeIcon from "../icons/eye.svg";
|
||||
import { Input, List, ListItem, Modal, PasswordInput, Popover } from "./ui-lib";
|
||||
import { ModelConfigList } from "./model-config";
|
||||
|
||||
@@ -30,6 +32,55 @@ import { InputRange } from "./input-range";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Avatar, AvatarPicker } from "./emoji";
|
||||
|
||||
function EditPromptModal(props: { id: number; onClose: () => void }) {
|
||||
const promptStore = usePromptStore();
|
||||
const prompt = promptStore.get(props.id);
|
||||
|
||||
return prompt ? (
|
||||
<div className="modal-mask">
|
||||
<Modal
|
||||
title={Locale.Settings.Prompt.EditModal.Title}
|
||||
onClose={props.onClose}
|
||||
actions={[
|
||||
<IconButton
|
||||
key=""
|
||||
onClick={props.onClose}
|
||||
text={Locale.UI.Confirm}
|
||||
bordered
|
||||
/>,
|
||||
]}
|
||||
>
|
||||
<div className={styles["edit-prompt-modal"]}>
|
||||
<input
|
||||
type="text"
|
||||
value={prompt.title}
|
||||
readOnly={!prompt.isUser}
|
||||
className={styles["edit-prompt-title"]}
|
||||
onInput={(e) =>
|
||||
promptStore.update(
|
||||
props.id,
|
||||
(prompt) => (prompt.title = e.currentTarget.value),
|
||||
)
|
||||
}
|
||||
></input>
|
||||
<Input
|
||||
value={prompt.content}
|
||||
readOnly={!prompt.isUser}
|
||||
className={styles["edit-prompt-content"]}
|
||||
rows={10}
|
||||
onInput={(e) =>
|
||||
promptStore.update(
|
||||
props.id,
|
||||
(prompt) => (prompt.content = e.currentTarget.value),
|
||||
)
|
||||
}
|
||||
></Input>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
||||
function UserPromptModal(props: { onClose?: () => void }) {
|
||||
const promptStore = usePromptStore();
|
||||
const userPrompts = promptStore.getUserPrompts();
|
||||
@@ -39,6 +90,8 @@ function UserPromptModal(props: { onClose?: () => void }) {
|
||||
const [searchPrompts, setSearchPrompts] = useState<Prompt[]>([]);
|
||||
const prompts = searchInput.length > 0 ? searchPrompts : allPrompts;
|
||||
|
||||
const [editingPromptId, setEditingPromptId] = useState<number>();
|
||||
|
||||
useEffect(() => {
|
||||
if (searchInput.length > 0) {
|
||||
const searchResult = SearchService.search(searchInput);
|
||||
@@ -56,8 +109,13 @@ function UserPromptModal(props: { onClose?: () => void }) {
|
||||
actions={[
|
||||
<IconButton
|
||||
key="add"
|
||||
onClick={() => promptStore.add({ title: "", content: "" })}
|
||||
icon={<ClearIcon />}
|
||||
onClick={() =>
|
||||
promptStore.add({
|
||||
title: "Empty Prompt",
|
||||
content: "Empty Prompt Content",
|
||||
})
|
||||
}
|
||||
icon={<AddIcon />}
|
||||
bordered
|
||||
text={Locale.Settings.Prompt.Modal.Add}
|
||||
/>,
|
||||
@@ -76,57 +134,51 @@ function UserPromptModal(props: { onClose?: () => void }) {
|
||||
{prompts.map((v, _) => (
|
||||
<div className={styles["user-prompt-item"]} key={v.id ?? v.title}>
|
||||
<div className={styles["user-prompt-header"]}>
|
||||
<input
|
||||
type="text"
|
||||
className={styles["user-prompt-title"]}
|
||||
value={v.title}
|
||||
readOnly={!v.isUser}
|
||||
onChange={(e) => {
|
||||
if (v.isUser) {
|
||||
promptStore.updateUserPrompts(
|
||||
v.id!,
|
||||
(prompt) => (prompt.title = e.currentTarget.value),
|
||||
);
|
||||
}
|
||||
}}
|
||||
></input>
|
||||
|
||||
<div className={styles["user-prompt-buttons"]}>
|
||||
{v.isUser && (
|
||||
<IconButton
|
||||
icon={<ClearIcon />}
|
||||
bordered
|
||||
className={styles["user-prompt-button"]}
|
||||
onClick={() => promptStore.remove(v.id!)}
|
||||
/>
|
||||
)}
|
||||
<IconButton
|
||||
icon={<CopyIcon />}
|
||||
bordered
|
||||
className={styles["user-prompt-button"]}
|
||||
onClick={() => copyToClipboard(v.content)}
|
||||
/>
|
||||
<div className={styles["user-prompt-title"]}>{v.title}</div>
|
||||
<div className={styles["user-prompt-content"] + " one-line"}>
|
||||
{v.content}
|
||||
</div>
|
||||
</div>
|
||||
<Input
|
||||
rows={2}
|
||||
value={v.content}
|
||||
className={styles["user-prompt-content"]}
|
||||
readOnly={!v.isUser}
|
||||
onChange={(e) => {
|
||||
if (v.isUser) {
|
||||
promptStore.updateUserPrompts(
|
||||
v.id!,
|
||||
(prompt) => (prompt.content = e.currentTarget.value),
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className={styles["user-prompt-buttons"]}>
|
||||
{v.isUser && (
|
||||
<IconButton
|
||||
icon={<ClearIcon />}
|
||||
className={styles["user-prompt-button"]}
|
||||
onClick={() => promptStore.remove(v.id!)}
|
||||
/>
|
||||
)}
|
||||
{v.isUser ? (
|
||||
<IconButton
|
||||
icon={<EditIcon />}
|
||||
className={styles["user-prompt-button"]}
|
||||
onClick={() => setEditingPromptId(v.id)}
|
||||
/>
|
||||
) : (
|
||||
<IconButton
|
||||
icon={<EyeIcon />}
|
||||
className={styles["user-prompt-button"]}
|
||||
onClick={() => setEditingPromptId(v.id)}
|
||||
/>
|
||||
)}
|
||||
<IconButton
|
||||
icon={<CopyIcon />}
|
||||
className={styles["user-prompt-button"]}
|
||||
onClick={() => copyToClipboard(v.content)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
{editingPromptId !== undefined && (
|
||||
<EditPromptModal
|
||||
id={editingPromptId!}
|
||||
onClose={() => setEditingPromptId(undefined)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -138,7 +138,11 @@ export function SideBar(props: { className?: string }) {
|
||||
<div className={styles["sidebar-action"] + " " + styles.mobile}>
|
||||
<IconButton
|
||||
icon={<CloseIcon />}
|
||||
onClick={chatStore.deleteSession}
|
||||
onClick={() => {
|
||||
if (confirm(Locale.Home.DeleteChat)) {
|
||||
chatStore.deleteSession(chatStore.currentSessionIndex);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles["sidebar-action"]}>
|
||||
|
@@ -158,6 +158,7 @@ export type ToastProps = {
|
||||
text: string;
|
||||
onClick: () => void;
|
||||
};
|
||||
onClose?: () => void;
|
||||
};
|
||||
|
||||
export function Toast(props: ToastProps) {
|
||||
@@ -167,7 +168,10 @@ export function Toast(props: ToastProps) {
|
||||
<span>{props.content}</span>
|
||||
{props.action && (
|
||||
<button
|
||||
onClick={props.action.onClick}
|
||||
onClick={() => {
|
||||
props.action?.onClick?.();
|
||||
props.onClose?.();
|
||||
}}
|
||||
className={styles["toast-action"]}
|
||||
>
|
||||
{props.action.text}
|
||||
@@ -201,7 +205,7 @@ export function showToast(
|
||||
close();
|
||||
}, delay);
|
||||
|
||||
root.render(<Toast content={content} action={action} />);
|
||||
root.render(<Toast content={content} action={action} onClose={close} />);
|
||||
}
|
||||
|
||||
export type InputProps = React.HTMLProps<HTMLTextAreaElement> & {
|
||||
|
Reference in New Issue
Block a user