mirror of
https://github.com/sqlchat/sqlchat.git
synced 2025-09-27 18:15:49 +08:00
feat: implement Dialog kit component
This commit is contained in:
@ -13,6 +13,7 @@
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"@mui/material": "^5.11.14",
|
||||
"@mui/styled-engine-sc": "^5.11.11",
|
||||
"@radix-ui/react-dialog": "^1.0.3",
|
||||
"@radix-ui/react-select": "^1.2.1",
|
||||
"@radix-ui/react-tooltip": "^1.0.5",
|
||||
"@vercel/analytics": "^0.1.11",
|
||||
|
30
pnpm-lock.yaml
generated
30
pnpm-lock.yaml
generated
@ -13,6 +13,9 @@ dependencies:
|
||||
'@mui/styled-engine-sc':
|
||||
specifier: ^5.11.11
|
||||
version: 5.11.11(styled-components@5.3.9)
|
||||
'@radix-ui/react-dialog':
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-select':
|
||||
specifier: ^1.2.1
|
||||
version: 1.2.1(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -876,6 +879,33 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-dialog@1.0.3(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-owNhq36kNPqC2/a+zJRioPg6HHnTn5B/sh/NjTY8r4W9g1L5VJlrzZIVcBr7R9Mg8iLjVmh6MGgMlfoVf/WO/A==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.21.0
|
||||
'@radix-ui/primitive': 1.0.0
|
||||
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-dismissable-layer': 1.0.3(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-focus-guards': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-focus-scope': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-id': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-portal': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-presence': 1.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-slot': 1.0.1(react@18.2.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.0.0(react@18.2.0)
|
||||
aria-hidden: 1.2.3
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
react-remove-scroll: 2.5.5(@types/react@18.0.28)(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-direction@1.0.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==}
|
||||
peerDependencies:
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Icon from "./Icon";
|
||||
import Dialog from "./kit/Dialog";
|
||||
|
||||
export interface ActionConfirmModalProps {
|
||||
title: string;
|
||||
@ -14,16 +14,11 @@ const ActionConfirmModal = (props: ActionConfirmModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="modal modal-middle modal-open">
|
||||
<div className="modal-box relative">
|
||||
<h3 className="font-bold text-lg">{title}</h3>
|
||||
<button className="btn btn-sm btn-circle absolute right-4 top-4" onClick={close}>
|
||||
<Icon.IoMdClose className="w-5 h-auto" />
|
||||
</button>
|
||||
<div className="w-full flex flex-col justify-start items-start space-y-3 pt-4">
|
||||
<Dialog title={title} onClose={close}>
|
||||
<div className="w-full flex flex-col justify-start items-start mt-2">
|
||||
<p className="text-gray-500">{content}</p>
|
||||
</div>
|
||||
<div className="modal-action">
|
||||
<div className="w-full flex flex-row justify-end items-center mt-4 space-x-2">
|
||||
<button className="btn btn-outline" onClick={close}>
|
||||
{t("common.close")}
|
||||
</button>
|
||||
@ -37,8 +32,7 @@ const ActionConfirmModal = (props: ActionConfirmModalProps) => {
|
||||
{t("common.confirm")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ClearDataConfirmModal from "./ClearDataConfirmModal";
|
||||
|
||||
@ -13,8 +12,7 @@ const ClearDataButton = () => {
|
||||
{t("common.clear")}
|
||||
</button>
|
||||
|
||||
{showClearDataConfirmModal &&
|
||||
createPortal(<ClearDataConfirmModal close={() => setShowClearDataConfirmModal(false)} />, document.body)}
|
||||
{showClearDataConfirmModal && <ClearDataConfirmModal close={() => setShowClearDataConfirmModal(false)} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Icon from "./Icon";
|
||||
import Dialog from "./kit/Dialog";
|
||||
|
||||
interface Props {
|
||||
close: () => void;
|
||||
@ -20,16 +20,11 @@ const ClearDataConfirmModal = (props: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="modal modal-middle modal-open">
|
||||
<div className="modal-box relative">
|
||||
<h3 className="font-bold text-lg">Clear all data</h3>
|
||||
<button className="btn btn-sm btn-circle absolute right-4 top-4" onClick={close}>
|
||||
<Icon.IoMdClose className="w-5 h-auto" />
|
||||
</button>
|
||||
<div className="w-full flex flex-col justify-start items-start space-y-3 pt-4">
|
||||
<Dialog title="Clear all data" onClose={close}>
|
||||
<div className="w-full flex flex-col justify-start items-start mt-2">
|
||||
<p className="text-gray-500">SQL Chat saves all your data in your local browser. Are you sure to clear all of them?</p>
|
||||
</div>
|
||||
<div className="modal-action">
|
||||
<div className="w-full flex flex-row justify-end items-center mt-4 space-x-2">
|
||||
<button className="btn btn-outline" onClick={close}>
|
||||
{t("common.close")}
|
||||
</button>
|
||||
@ -37,8 +32,7 @@ const ClearDataConfirmModal = (props: Props) => {
|
||||
{t("common.clear")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Drawer } from "@mui/material";
|
||||
import { head } from "lodash-es";
|
||||
import { useEffect, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useConversationStore, useConnectionStore, useLayoutStore, ResponsiveWidth } from "@/store";
|
||||
import { Conversation, Connection } from "@/types";
|
||||
@ -290,25 +289,17 @@ const ConnectionSidebar = () => {
|
||||
</div>
|
||||
</Drawer>
|
||||
|
||||
{createPortal(
|
||||
<CreateConnectionModal
|
||||
show={state.showCreateConnectionModal}
|
||||
connection={editConnectionModalContext}
|
||||
close={() => toggleCreateConnectionModal(false)}
|
||||
/>,
|
||||
document.body
|
||||
{state.showCreateConnectionModal && (
|
||||
<CreateConnectionModal connection={editConnectionModalContext} close={() => toggleCreateConnectionModal(false)} />
|
||||
)}
|
||||
|
||||
{createPortal(<SettingModal show={state.showSettingModal} close={() => toggleSettingModal(false)} />, document.body)}
|
||||
{state.showSettingModal && <SettingModal close={() => toggleSettingModal(false)} />}
|
||||
|
||||
{state.showEditConversationTitleModal &&
|
||||
editConversationTitleModalContext &&
|
||||
createPortal(
|
||||
{state.showEditConversationTitleModal && editConversationTitleModalContext && (
|
||||
<EditConversationTitleModal
|
||||
close={() => toggleEditConversationTitleModal(false)}
|
||||
conversation={editConversationTitleModalContext}
|
||||
/>,
|
||||
document.body
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@ -1,18 +1,17 @@
|
||||
import { cloneDeep, head } from "lodash-es";
|
||||
import { ChangeEvent, useEffect, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { toast } from "react-hot-toast";
|
||||
import TextareaAutosize from "react-textarea-autosize";
|
||||
import { useConnectionStore } from "@/store";
|
||||
import { Connection, Engine, ResponseObject } from "@/types";
|
||||
import Select from "./kit/Select";
|
||||
import TextField from "./kit/TextField";
|
||||
import Dialog from "./kit/Dialog";
|
||||
import Icon from "./Icon";
|
||||
import DataStorageBanner from "./DataStorageBanner";
|
||||
import ActionConfirmModal from "./ActionConfirmModal";
|
||||
import TextField from "./kit/TextField";
|
||||
|
||||
interface Props {
|
||||
show: boolean;
|
||||
connection?: Connection;
|
||||
close: () => void;
|
||||
}
|
||||
@ -47,7 +46,7 @@ const defaultConnection: Connection = {
|
||||
};
|
||||
|
||||
const CreateConnectionModal = (props: Props) => {
|
||||
const { show, connection: editConnection, close } = props;
|
||||
const { connection: editConnection, close } = props;
|
||||
const connectionStore = useConnectionStore();
|
||||
const [connection, setConnection] = useState<Connection>(defaultConnection);
|
||||
const [showDeleteConnectionModal, setShowDeleteConnectionModal] = useState(false);
|
||||
@ -59,7 +58,6 @@ const CreateConnectionModal = (props: Props) => {
|
||||
const allowSave = connection.host !== "" && connection.username !== "";
|
||||
|
||||
useEffect(() => {
|
||||
if (show) {
|
||||
const connection = isEditing ? editConnection : defaultConnection;
|
||||
setConnection(connection);
|
||||
if (connection.ssl) {
|
||||
@ -74,8 +72,7 @@ const CreateConnectionModal = (props: Props) => {
|
||||
setSelectedSSLField("ca");
|
||||
setIsRequesting(false);
|
||||
setShowDeleteConnectionModal(false);
|
||||
}
|
||||
}, [show]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let ssl = undefined;
|
||||
@ -209,12 +206,7 @@ const CreateConnectionModal = (props: Props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={`modal modal-middle ${show && "modal-open"}`}>
|
||||
<div className="modal-box relative">
|
||||
<h3 className="font-bold text-lg">{isEditing ? "Edit Connection" : "Create Connection"}</h3>
|
||||
<button className="btn btn-sm btn-circle absolute right-4 top-4" onClick={close}>
|
||||
<Icon.IoMdClose className="w-5 h-auto" />
|
||||
</button>
|
||||
<Dialog title={isEditing ? "Edit Connection" : "Create Connection"} onClose={close}>
|
||||
<div className="w-full flex flex-col justify-start items-start space-y-3 pt-4">
|
||||
<DataStorageBanner className="rounded-lg bg-white border py-2 !justify-start" alwaysShow={true} />
|
||||
<div className="w-full flex flex-col">
|
||||
@ -353,19 +345,16 @@ const CreateConnectionModal = (props: Props) => {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
{showDeleteConnectionModal &&
|
||||
createPortal(
|
||||
{showDeleteConnectionModal && (
|
||||
<ActionConfirmModal
|
||||
title="Delete Connection"
|
||||
content="Are you sure you want to delete this connection?"
|
||||
confirmButtonStyle="btn-error"
|
||||
close={() => setShowDeleteConnectionModal(false)}
|
||||
confirm={() => handleDeleteConnection()}
|
||||
/>,
|
||||
document.body
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@ -3,8 +3,8 @@ import { toast } from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useConversationStore } from "@/store";
|
||||
import { Conversation } from "@/types";
|
||||
import Icon from "./Icon";
|
||||
import TextField from "./kit/TextField";
|
||||
import Dialog from "./kit/Dialog";
|
||||
|
||||
interface Props {
|
||||
conversation: Conversation;
|
||||
@ -32,16 +32,11 @@ const EditConversationTitleModal = (props: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="modal modal-middle modal-open">
|
||||
<div className="modal-box relative">
|
||||
<h3 className="font-bold text-lg">{t("conversation.edit-title")}</h3>
|
||||
<button className="btn btn-sm btn-circle absolute right-4 top-4" onClick={close}>
|
||||
<Icon.IoMdClose className="w-5 h-auto" />
|
||||
</button>
|
||||
<div className="w-full flex flex-col justify-start items-start space-y-3 pt-4">
|
||||
<Dialog title={t("conversation.edit-title")} onClose={close}>
|
||||
<div className="w-full flex flex-col justify-start items-start mt-2">
|
||||
<TextField placeholder={t("conversation.conversation-title") || ""} value={title} onChange={(value) => setTitle(value)} />
|
||||
</div>
|
||||
<div className="modal-action">
|
||||
<div className="w-full flex flex-row justify-end items-center mt-4 space-x-2">
|
||||
<button className="btn btn-outline" onClick={close}>
|
||||
{t("common.close")}
|
||||
</button>
|
||||
@ -49,8 +44,7 @@ const EditConversationTitleModal = (props: Props) => {
|
||||
{t("common.save")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Dialog from "./kit/Dialog";
|
||||
import Icon from "./Icon";
|
||||
import WeChatQRCodeView from "./WeChatQRCodeView";
|
||||
import ClearDataButton from "./ClearDataButton";
|
||||
@ -6,21 +7,15 @@ import LocaleSelector from "./LocaleSelector";
|
||||
import OpenAIApiConfigView from "./OpenAIApiConfigView";
|
||||
|
||||
interface Props {
|
||||
show: boolean;
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
const SettingModal = (props: Props) => {
|
||||
const { show, close } = props;
|
||||
const { close } = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={`modal modal-middle ${show && "modal-open"}`}>
|
||||
<div className="modal-box relative">
|
||||
<h3 className="font-bold text-lg">{t("setting.self")}</h3>
|
||||
<button className="btn btn-sm btn-circle absolute right-4 top-4" onClick={close}>
|
||||
<Icon.IoMdClose className="w-5 h-auto" />
|
||||
</button>
|
||||
<Dialog title={t("setting.self")} onClose={close}>
|
||||
<div className="w-full flex flex-col justify-start items-start space-y-3 pt-4">
|
||||
<div className="w-full flex flex-row justify-start items-start flex-wrap">
|
||||
<a
|
||||
@ -52,8 +47,7 @@ const SettingModal = (props: Props) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
|
34
src/components/kit/Dialog.tsx
Normal file
34
src/components/kit/Dialog.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import * as DialogUI from "@radix-ui/react-dialog";
|
||||
import React, { ReactNode } from "react";
|
||||
import Icon from "../Icon";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
children: ReactNode;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const Dialog = (props: Props) => {
|
||||
const { children, title, onClose } = props;
|
||||
|
||||
return (
|
||||
<DialogUI.Root open={true}>
|
||||
<DialogUI.Portal>
|
||||
<DialogUI.Overlay className="fixed inset-0 bg-black bg-opacity-60 z-[9999]" />
|
||||
<DialogUI.Content className="bg-white rounded-xl p-4 px-5 fixed top-[50%] left-[50%] max-h-[85vh] w-[90vw] max-w-[450px] translate-x-[-50%] translate-y-[-50%] z-[9999]">
|
||||
<DialogUI.Title className="text-lg text-black font-medium mb-2">{title}</DialogUI.Title>
|
||||
<DialogUI.Close
|
||||
className="absolute top-3 right-3 outline-none w-8 h-8 p-1 bg-zinc-600 rounded-full text-white hover:opacity-80"
|
||||
aria-label="Close"
|
||||
onClick={onClose}
|
||||
>
|
||||
<Icon.IoClose className="w-full h-auto" />
|
||||
</DialogUI.Close>
|
||||
<div className="w-full flex flex-col justify-start items-start">{children}</div>
|
||||
</DialogUI.Content>
|
||||
</DialogUI.Portal>
|
||||
</DialogUI.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dialog;
|
Reference in New Issue
Block a user