mirror of
https://github.com/sqlchat/sqlchat.git
synced 2025-09-27 10:06:23 +08:00
feat: implement Popover kit component
This commit is contained in:
@ -14,6 +14,7 @@
|
|||||||
"@mui/material": "^5.11.14",
|
"@mui/material": "^5.11.14",
|
||||||
"@mui/styled-engine-sc": "^5.11.11",
|
"@mui/styled-engine-sc": "^5.11.11",
|
||||||
"@radix-ui/react-dialog": "^1.0.3",
|
"@radix-ui/react-dialog": "^1.0.3",
|
||||||
|
"@radix-ui/react-popover": "^1.0.5",
|
||||||
"@radix-ui/react-select": "^1.2.1",
|
"@radix-ui/react-select": "^1.2.1",
|
||||||
"@radix-ui/react-tooltip": "^1.0.5",
|
"@radix-ui/react-tooltip": "^1.0.5",
|
||||||
"@vercel/analytics": "^0.1.11",
|
"@vercel/analytics": "^0.1.11",
|
||||||
|
31
pnpm-lock.yaml
generated
31
pnpm-lock.yaml
generated
@ -16,6 +16,9 @@ dependencies:
|
|||||||
'@radix-ui/react-dialog':
|
'@radix-ui/react-dialog':
|
||||||
specifier: ^1.0.3
|
specifier: ^1.0.3
|
||||||
version: 1.0.3(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
|
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':
|
'@radix-ui/react-select':
|
||||||
specifier: ^1.2.1
|
specifier: ^1.2.1
|
||||||
version: 1.2.1(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
|
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
|
react: 18.2.0
|
||||||
dev: false
|
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):
|
/@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==}
|
resolution: {integrity: sha512-keYDcdMPNMjSC8zTsZ8wezUMiWM9Yj14wtF3s0PTIs9srnEPC9Kt2Gny1T3T81mmSeyDjZxsD9N5WCwNNb712w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -18,10 +18,10 @@ const SettingModal = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Dialog title={t("setting.self")} onClose={close}>
|
<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-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
|
<a
|
||||||
href="https://discord.gg/z6kakemDjm"
|
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"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<Icon.BsDiscord className="w-4 h-auto mr-1" />
|
<Icon.BsDiscord className="w-4 h-auto mr-1" />
|
||||||
@ -37,7 +37,7 @@ const SettingModal = (props: Props) => {
|
|||||||
<LocaleSelector />
|
<LocaleSelector />
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row justify-between items-center gap-2">
|
<div className="w-full flex flex-row justify-between items-center gap-2">
|
||||||
<span>Theme</span>
|
<span>{t("setting.theme.self")}</span>
|
||||||
<ThemeSelector />
|
<ThemeSelector />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { useSettingStore } from "@/store";
|
import { useSettingStore } from "@/store";
|
||||||
import { Theme } from "@/types";
|
import { Theme } from "@/types";
|
||||||
import Select from "./kit/Select";
|
import Select from "./kit/Select";
|
||||||
@ -7,30 +8,31 @@ interface ThemeItem {
|
|||||||
label: string;
|
label: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const themeItemList: ThemeItem[] = [
|
const ThemeSelector = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const settingStore = useSettingStore();
|
||||||
|
const theme = settingStore.setting.theme;
|
||||||
|
|
||||||
|
const themeItemList: ThemeItem[] = [
|
||||||
{
|
{
|
||||||
value: "system",
|
value: "system",
|
||||||
label: "System",
|
label: t("setting.theme.system"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "light",
|
value: "light",
|
||||||
label: "Light",
|
label: t("setting.theme.light"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "dark",
|
value: "dark",
|
||||||
label: "Dark",
|
label: t("setting.theme.dark"),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const ThemeSelector = () => {
|
|
||||||
const settingStore = useSettingStore();
|
|
||||||
const theme = settingStore.setting.theme;
|
|
||||||
|
|
||||||
const handleThemeChange = (theme: Theme) => {
|
const handleThemeChange = (theme: Theme) => {
|
||||||
settingStore.setTheme(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;
|
export default ThemeSelector;
|
||||||
|
@ -1,34 +1,21 @@
|
|||||||
import { Popover } from "@mui/material";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
|
import Popover from "./kit/Popover";
|
||||||
|
|
||||||
const WeChatQRCodeView = () => {
|
const WeChatQRCodeView = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [wechatAnchorEl, setWeChatAnchorEl] = useState<HTMLElement | null>(null);
|
|
||||||
const openWeChatQrCodePopover = Boolean(wechatAnchorEl);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Popover
|
||||||
<div
|
tigger={
|
||||||
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"
|
<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">
|
||||||
onClick={(e) => setWeChatAnchorEl(e.currentTarget)}
|
|
||||||
>
|
|
||||||
<Icon.BsWechat className="w-4 h-auto mr-1" />
|
<Icon.BsWechat className="w-4 h-auto mr-1" />
|
||||||
{t("social.join-wechat-group")}
|
{t("social.join-wechat-group")}
|
||||||
</div>
|
</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" />
|
<img className="w-40 h-auto" src="/wechat-qrcode.webp" alt="wechat qrcode" />
|
||||||
</Popover>
|
</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>
|
<SelectUI.Group>
|
||||||
{placeholder && <SelectUI.Label className="w-full px-3 mt-2 mb-1 text-sm text-gray-400">{placeholder}</SelectUI.Label>}
|
{placeholder && <SelectUI.Label className="w-full px-3 mt-2 mb-1 text-sm text-gray-400">{placeholder}</SelectUI.Label>}
|
||||||
{itemList.map((item) => (
|
{itemList.map((item) => (
|
||||||
<SelectItem key={item.label} value={item.value}>
|
<SelectItem key={item.label} className="whitespace-nowrap" value={item.value}>
|
||||||
{item.label}
|
{item.label}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
|
@ -36,6 +36,12 @@
|
|||||||
"self": "Basic",
|
"self": "Basic",
|
||||||
"language": "Language"
|
"language": "Language"
|
||||||
},
|
},
|
||||||
|
"theme": {
|
||||||
|
"self": "Theme",
|
||||||
|
"system": "Follow system",
|
||||||
|
"light": "Light",
|
||||||
|
"dark": "Dark"
|
||||||
|
},
|
||||||
"openai-api-configuration": {
|
"openai-api-configuration": {
|
||||||
"self": "OpenAI API configuration"
|
"self": "OpenAI API configuration"
|
||||||
},
|
},
|
||||||
|
@ -36,6 +36,12 @@
|
|||||||
"self": "基础",
|
"self": "基础",
|
||||||
"language": "语言"
|
"language": "语言"
|
||||||
},
|
},
|
||||||
|
"theme": {
|
||||||
|
"self": "主题",
|
||||||
|
"system": "跟随系统",
|
||||||
|
"light": "浅色",
|
||||||
|
"dark": "深色"
|
||||||
|
},
|
||||||
"openai-api-configuration": {
|
"openai-api-configuration": {
|
||||||
"self": "OpenAI API 配置"
|
"self": "OpenAI API 配置"
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user