mirror of
https://github.com/sqlchat/sqlchat.git
synced 2025-09-27 01:56:16 +08:00
feat: implement Popover kit component
This commit is contained in:
@ -14,6 +14,7 @@
|
||||
"@mui/material": "^5.11.14",
|
||||
"@mui/styled-engine-sc": "^5.11.11",
|
||||
"@radix-ui/react-dialog": "^1.0.3",
|
||||
"@radix-ui/react-popover": "^1.0.5",
|
||||
"@radix-ui/react-select": "^1.2.1",
|
||||
"@radix-ui/react-tooltip": "^1.0.5",
|
||||
"@vercel/analytics": "^0.1.11",
|
||||
|
31
pnpm-lock.yaml
generated
31
pnpm-lock.yaml
generated
@ -16,6 +16,9 @@ dependencies:
|
||||
'@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-popover':
|
||||
specifier: ^1.0.5
|
||||
version: 1.0.5(@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)
|
||||
@ -964,6 +967,34 @@ packages:
|
||||
react: 18.2.0
|
||||
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:
|
||||
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-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-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-popper@1.1.1(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-keYDcdMPNMjSC8zTsZ8wezUMiWM9Yj14wtF3s0PTIs9srnEPC9Kt2Gny1T3T81mmSeyDjZxsD9N5WCwNNb712w==}
|
||||
peerDependencies:
|
||||
|
@ -18,10 +18,10 @@ const SettingModal = (props: Props) => {
|
||||
return (
|
||||
<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">
|
||||
<div className="w-full flex flex-row justify-start items-start flex-wrap gap-2">
|
||||
<a
|
||||
href="https://discord.gg/z6kakemDjm"
|
||||
className="w-auto px-4 py-2 rounded-full bg-indigo-600 text-white text-sm font-medium flex flex-row justify-center items-center mr-2 mb-2 hover:underline hover:shadow"
|
||||
className="w-auto px-4 py-2 rounded-full bg-indigo-600 text-white text-sm font-medium flex flex-row justify-center items-center hover:underline hover:shadow"
|
||||
target="_blank"
|
||||
>
|
||||
<Icon.BsDiscord className="w-4 h-auto mr-1" />
|
||||
@ -37,7 +37,7 @@ const SettingModal = (props: Props) => {
|
||||
<LocaleSelector />
|
||||
</div>
|
||||
<div className="w-full flex flex-row justify-between items-center gap-2">
|
||||
<span>Theme</span>
|
||||
<span>{t("setting.theme.self")}</span>
|
||||
<ThemeSelector />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSettingStore } from "@/store";
|
||||
import { Theme } from "@/types";
|
||||
import Select from "./kit/Select";
|
||||
@ -7,30 +8,31 @@ interface ThemeItem {
|
||||
label: string;
|
||||
}
|
||||
|
||||
const themeItemList: ThemeItem[] = [
|
||||
{
|
||||
value: "system",
|
||||
label: "System",
|
||||
},
|
||||
{
|
||||
value: "light",
|
||||
label: "Light",
|
||||
},
|
||||
{
|
||||
value: "dark",
|
||||
label: "Dark",
|
||||
},
|
||||
];
|
||||
|
||||
const ThemeSelector = () => {
|
||||
const { t } = useTranslation();
|
||||
const settingStore = useSettingStore();
|
||||
const theme = settingStore.setting.theme;
|
||||
|
||||
const themeItemList: ThemeItem[] = [
|
||||
{
|
||||
value: "system",
|
||||
label: t("setting.theme.system"),
|
||||
},
|
||||
{
|
||||
value: "light",
|
||||
label: t("setting.theme.light"),
|
||||
},
|
||||
{
|
||||
value: "dark",
|
||||
label: t("setting.theme.dark"),
|
||||
},
|
||||
];
|
||||
|
||||
const handleThemeChange = (theme: Theme) => {
|
||||
settingStore.setTheme(theme);
|
||||
};
|
||||
|
||||
return <Select className="w-28" value={theme} itemList={themeItemList} onValueChange={handleThemeChange} />;
|
||||
return <Select className="w-40" value={theme} itemList={themeItemList} onValueChange={handleThemeChange} />;
|
||||
};
|
||||
|
||||
export default ThemeSelector;
|
||||
|
@ -1,34 +1,21 @@
|
||||
import { Popover } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Icon from "./Icon";
|
||||
import Popover from "./kit/Popover";
|
||||
|
||||
const WeChatQRCodeView = () => {
|
||||
const { t } = useTranslation();
|
||||
const [wechatAnchorEl, setWeChatAnchorEl] = useState<HTMLElement | null>(null);
|
||||
const openWeChatQrCodePopover = Boolean(wechatAnchorEl);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="w-auto px-4 py-2 mr-2 mb-2 rounded-full cursor-pointer bg-green-600 text-white text-sm font-medium flex flex-row justify-center items-center hover:shadow"
|
||||
onClick={(e) => setWeChatAnchorEl(e.currentTarget)}
|
||||
>
|
||||
<Icon.BsWechat className="w-4 h-auto mr-1" />
|
||||
{t("social.join-wechat-group")}
|
||||
</div>
|
||||
<Popover
|
||||
open={openWeChatQrCodePopover}
|
||||
anchorEl={wechatAnchorEl}
|
||||
onClose={() => setWeChatAnchorEl(null)}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "left",
|
||||
}}
|
||||
>
|
||||
<img className="w-64 h-auto" src="/wechat-qrcode.webp" alt="wechat qrcode" />
|
||||
</Popover>
|
||||
</>
|
||||
<Popover
|
||||
tigger={
|
||||
<div className="w-auto px-4 py-2 rounded-full cursor-pointer bg-green-600 text-white text-sm font-medium flex flex-row justify-center items-center hover:shadow">
|
||||
<Icon.BsWechat className="w-4 h-auto mr-1" />
|
||||
{t("social.join-wechat-group")}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<img className="w-40 h-auto" src="/wechat-qrcode.webp" alt="wechat qrcode" />
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
|
25
src/components/kit/Popover.tsx
Normal file
25
src/components/kit/Popover.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import * as PopoverUI from "@radix-ui/react-popover";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
tigger: ReactNode;
|
||||
}
|
||||
|
||||
const Popover = (props: Props) => {
|
||||
const { className, children, tigger } = props;
|
||||
|
||||
return (
|
||||
<PopoverUI.Root>
|
||||
<PopoverUI.Trigger asChild>{tigger}</PopoverUI.Trigger>
|
||||
<PopoverUI.Portal>
|
||||
<PopoverUI.Content className={`${className || ""} z-[9999] p-2 bg-white dark:bg-zinc-700 shadow-lg rounded-lg`} sideOffset={5}>
|
||||
{children}
|
||||
</PopoverUI.Content>
|
||||
</PopoverUI.Portal>
|
||||
</PopoverUI.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export default Popover;
|
@ -40,7 +40,7 @@ const Select = (props: Props) => {
|
||||
<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) => (
|
||||
<SelectItem key={item.label} value={item.value}>
|
||||
<SelectItem key={item.label} className="whitespace-nowrap" value={item.value}>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
|
@ -36,6 +36,12 @@
|
||||
"self": "Basic",
|
||||
"language": "Language"
|
||||
},
|
||||
"theme": {
|
||||
"self": "Theme",
|
||||
"system": "Follow system",
|
||||
"light": "Light",
|
||||
"dark": "Dark"
|
||||
},
|
||||
"openai-api-configuration": {
|
||||
"self": "OpenAI API configuration"
|
||||
},
|
||||
|
@ -36,6 +36,12 @@
|
||||
"self": "基础",
|
||||
"language": "语言"
|
||||
},
|
||||
"theme": {
|
||||
"self": "主题",
|
||||
"system": "跟随系统",
|
||||
"light": "浅色",
|
||||
"dark": "深色"
|
||||
},
|
||||
"openai-api-configuration": {
|
||||
"self": "OpenAI API 配置"
|
||||
},
|
||||
|
Reference in New Issue
Block a user