feat: implement Dropdown kit component

This commit is contained in:
Steven
2023-04-14 08:10:25 +08:00
parent 81ad3408fb
commit d43fefc7f3
11 changed files with 163 additions and 45 deletions

4
next.config.js Normal file
View File

@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
module.exports = {
output: "standalone",
};

View File

@ -13,6 +13,7 @@
"@emotion/styled": "^11.10.6",
"@mui/material": "^5.11.14",
"@mui/styled-engine-sc": "^5.11.11",
"@radix-ui/react-dropdown-menu": "^2.0.4",
"@radix-ui/react-popover": "^1.0.5",
"@radix-ui/react-select": "^1.2.1",
"@radix-ui/react-tooltip": "^1.0.5",

74
pnpm-lock.yaml generated
View File

@ -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-dropdown-menu':
specifier: ^2.0.4
version: 2.0.4(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-popover':
specifier: ^1.0.5
version: 1.0.5(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
@ -901,6 +904,26 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-dropdown-menu@2.0.4(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-y6AT9+MydyXcByivdK1+QpjWoKaC7MLjkS/cH1Q3keEyMvDkiY85m8o2Bi6+Z1PPUlCsMULopxagQOSfN0wahg==}
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-id': 1.0.0(react@18.2.0)
'@radix-ui/react-menu': 2.0.4(@types/react@18.0.28)(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-use-controllable-state': 1.0.0(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
transitivePeerDependencies:
- '@types/react'
dev: false
/@radix-ui/react-focus-guards@1.0.0(react@18.2.0):
resolution: {integrity: sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==}
peerDependencies:
@ -934,6 +957,37 @@ packages:
react: 18.2.0
dev: false
/@radix-ui/react-menu@2.0.4(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-mzKR47tZ1t193trEqlQoJvzY4u9vYfVH16ryBrVrCAGZzkgyWnMQYEZdUkM7y8ak9mrkKtJiqB47TlEnubeOFQ==}
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-collection': 1.0.2(react-dom@18.2.0)(react@18.2.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-direction': 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-popper': 1.1.1(@types/react@18.0.28)(react-dom@18.2.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-roving-focus': 1.0.3(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot': 1.0.1(react@18.2.0)
'@radix-ui/react-use-callback-ref': 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-popover@1.0.5(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-GRHZ8yD12MrN2NLobHPE8Rb5uHTxd9x372DE9PPNnBjpczAQHcZ5ne0KXG4xpf+RDdXSzdLv9ym6mYJCDTaUZg==}
peerDependencies:
@ -1022,6 +1076,26 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-roving-focus@1.0.3(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-stjCkIoMe6h+1fWtXlA6cRfikdBzCLp3SnVk7c48cv/uy3DTGoXhN76YaOYUJuy3aEDvDIKwKR5KSmvrtPvQPQ==}
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-collection': 1.0.2(react-dom@18.2.0)(react@18.2.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-direction': 1.0.0(react@18.2.0)
'@radix-ui/react-id': 1.0.0(react@18.2.0)
'@radix-ui/react-primitive': 1.0.2(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.0(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-select@1.2.1(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-GULRMITaOHNj79BZvQs3iZO0+f2IgI8g5HDhMi7Bnc13t7IlG86NFtOCfTLme4PNZdEtU+no+oGgcl6IFiphpQ==}
peerDependencies:

View File

@ -6,6 +6,7 @@ import { useConversationStore, useConnectionStore, useLayoutStore, ResponsiveWid
import { Conversation, Connection } from "@/types";
import Select from "./kit/Select";
import Tooltip from "./kit/Tooltip";
import Dropdown, { DropdownItem } from "./kit/Dropdown";
import Icon from "./Icon";
import EngineIcon from "./EngineIcon";
import LocaleSwitch from "./LocaleSwitch";
@ -243,7 +244,7 @@ const ConnectionSidebar = () => {
{conversationList.map((conversation) => (
<div
key={conversation.id}
className={`w-full mt-2 first:mt-4 py-3 pl-4 pr-2 rounded-lg flex flex-row justify-start items-center cursor-pointer dark:text-gray-300 border border-transparent group hover:bg-gray-50 dark:hover:bg-zinc-800 ${
className={`w-full mt-2 first:mt-4 py-3 pl-4 pr-2 rounded-lg flex flex-row justify-start items-center cursor-pointer dark:text-gray-300 border border-transparent group hover:bg-white dark:hover:bg-zinc-800 ${
conversation.id === conversationStore.currentConversation?.id && "bg-white dark:bg-zinc-800 border-gray-200 font-medium"
}`}
onClick={() => handleConversationSelect(conversation)}
@ -254,22 +255,30 @@ const ConnectionSidebar = () => {
<Icon.IoChatbubbleOutline className="w-5 h-auto mr-1.5 opacity-80 shrink-0" />
)}
<span className="truncate grow">{conversation.title || "SQL Chat"}</span>
<span className="ml-0.5 shrink-0 hidden group-hover:flex flex-row justify-end items-center space-x-1">
<Icon.FiEdit3
className="w-4 h-auto opacity-60 hover:opacity-80"
onClick={(e) => {
e.stopPropagation();
handleEditConversationTitle(conversation);
}}
/>
<Icon.IoClose
className="w-5 h-auto opacity-60 hover:opacity-80"
onClick={(e) => {
e.stopPropagation();
handleDeleteConversation(conversation);
}}
/>
</span>
<Dropdown
tigger={
<button className="w-4 h-4 shrink-0 group-hover:visible invisible flex justify-center items-center text-gray-400 hover:text-gray-500">
<Icon.FiMoreHorizontal className="w-full h-auto" />
</button>
}
>
<div className="p-1 flex flex-col justify-start items-start bg-white dark:bg-zinc-900 shadow-lg rounded-lg">
<DropdownItem
className="w-full p-1 px-2 flex flex-row justify-start items-center rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-zinc-800"
onClick={() => handleEditConversationTitle(conversation)}
>
<Icon.FiEdit3 className="w-4 h-auto mr-1 opacity-70" />
{t("common.edit")}
</DropdownItem>
<DropdownItem
className="w-full p-1 px-2 flex flex-row justify-start items-center rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-zinc-800"
onClick={() => handleDeleteConversation(conversation)}
>
<Icon.IoTrash className="w-4 h-auto mr-1 opacity-70" />
{t("common.delete")}
</DropdownItem>
</div>
</Dropdown>
</div>
))}
<button

View File

@ -1,4 +1,3 @@
import { Menu, MenuItem } from "@mui/material";
import dayjs from "dayjs";
import { ReactElement, useState } from "react";
import { useTranslation } from "react-i18next";
@ -7,6 +6,7 @@ import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { useConversationStore, useConnectionStore, useMessageStore, useUserStore, useSettingStore } from "@/store";
import { Message } from "@/types";
import Dropdown, { DropdownItem } from "../kit/Dropdown";
import Icon from "../Icon";
import { CodeBlock } from "../CodeBlock";
import EngineIcon from "../EngineIcon";
@ -112,30 +112,30 @@ const MessageView = (props: Props) => {
</span>
</div>
<div className={`invisible group-hover:visible ${showMenu && "!visible"}`}>
<button
className="w-6 h-6 ml-1 mt-2 flex justify-center items-center text-gray-400 hover:text-gray-500"
onClick={handleMoreMenuClick}
<Dropdown
tigger={
<button className="w-6 h-6 ml-1 mt-2 flex justify-center items-center text-gray-400 hover:text-gray-500">
<Icon.IoMdMore className="w-5 h-auto" />
</button>
}
>
<Icon.IoMdMore className="w-5 h-auto" />
</button>
<Menu
className="mt-1"
anchorEl={menuAnchorEl}
open={showMenu}
onClose={() => setMenuAnchorEl(null)}
MenuListProps={{
"aria-labelledby": "basic-button",
}}
>
<MenuItem onClick={copyMessage}>
<Icon.BiClipboard className="w-4 h-auto mr-2 opacity-70" />
{t("common.copy")}
</MenuItem>
<MenuItem onClick={() => deleteMessage(message)}>
<Icon.BiTrash className="w-4 h-auto mr-2 opacity-70" />
{t("common.delete")}
</MenuItem>
</Menu>
<div className="p-1 flex flex-col justify-start items-start bg-white dark:bg-zinc-900 rounded-lg">
<DropdownItem
className="w-full p-1 px-2 flex flex-row justify-start items-center rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-zinc-800"
onClick={copyMessage}
>
<Icon.BiClipboard className="w-4 h-auto mr-2 opacity-70" />
{t("common.copy")}
</DropdownItem>
<DropdownItem
className="w-full p-1 px-2 flex flex-row justify-start items-center rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-zinc-800"
onClick={() => deleteMessage(message)}
>
<Icon.BiTrash className="w-4 h-auto mr-2 opacity-70" />
{t("common.delete")}
</DropdownItem>
</div>
</Dropdown>
</div>
</>
)}

View File

@ -0,0 +1,28 @@
import * as DropdownUI from "@radix-ui/react-dropdown-menu";
import { ReactNode } from "react";
interface Props {
children: ReactNode;
tigger: ReactNode;
}
const Dropdown = (props: Props) => {
const { children, tigger } = props;
return (
<DropdownUI.Root modal={false}>
<DropdownUI.Trigger asChild onClick={(e) => e.stopPropagation()}>
{tigger}
</DropdownUI.Trigger>
<DropdownUI.Portal>
<DropdownUI.Content className="z-[999] drop-shadow" sideOffset={5}>
{children}
</DropdownUI.Content>
</DropdownUI.Portal>
</DropdownUI.Root>
);
};
export const DropdownItem = DropdownUI.Item;
export default Dropdown;

View File

@ -17,7 +17,7 @@ const Modal = (props: Props) => {
<div
className={`${
className || ""
} flex flex-col bg-white dark:bg-zinc-800 rounded-xl p-4 fixed top-[50%] left-[50%] h-auto max-h-[85vh] w-[90vw] max-w-[90vw] sm:max-w-lg translate-x-[-50%] translate-y-[-50%] z-100`}
} flex flex-col bg-white dark:bg-zinc-800 rounded-xl p-4 fixed top-[50%] left-[50%] h-auto max-h-[85vh] w-[90vw] max-w-[90vw] sm:max-w-lg translate-x-[-50%] translate-y-[-50%] z-100 outline-none`}
>
<p className="text-lg pl-1 text-black dark:text-gray-300 font-medium mb-2">{title}</p>
<button

View File

@ -16,7 +16,7 @@ const Popover = (props: Props) => {
<PopoverUI.Portal>
<PopoverUI.Content
asChild
className={`${className || ""} z-[9999] p-2 bg-white dark:bg-zinc-700 shadow-lg rounded-lg`}
className={`${className || ""} z-[999] p-2 bg-white dark:bg-zinc-700 drop-shadow rounded-lg`}
sideOffset={5}
>
{children}

View File

@ -30,13 +30,13 @@ const Select = (props: Props) => {
</SelectUI.Trigger>
<SelectUI.Portal>
<SelectUI.Content
className="z-[999] -mt-px"
className="z-[999] mt-1"
style={{
width: "var(--radix-select-trigger-width)",
}}
position="popper"
>
<SelectUI.Viewport className="bg-white dark:bg-zinc-700 border dark:border-zinc-800 shadow p-1 rounded-lg">
<SelectUI.Viewport className="bg-white dark:bg-zinc-700 border dark:border-zinc-800 drop-shadow-lg p-1 rounded-lg">
<SelectUI.Group>
{placeholder && <SelectUI.Label className="w-full px-3 mt-2 mb-1 text-sm text-gray-400">{placeholder}</SelectUI.Label>}
{itemList.map((item) => (

View File

@ -4,6 +4,7 @@
"close": "Close",
"confirm": "Confirm",
"save": "Save",
"edit": "Edit",
"loading": "Loading",
"setting": "Setting",
"copy": "Copy",

View File

@ -4,6 +4,7 @@
"close": "关闭",
"confirm": "确认",
"save": "保存",
"edit": "编辑",
"loading": "加载中",
"setting": "设置",
"copy": "复制",