feat: implement Popover kit component

This commit is contained in:
steven
2023-04-12 17:00:21 +08:00
parent ce7ed9e821
commit 63ece9d969
9 changed files with 102 additions and 44 deletions

View File

@ -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
View File

@ -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:

View File

@ -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>

View File

@ -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[] = [
{
value: "system",
label: "System",
},
{
value: "light",
label: "Light",
},
{
value: "dark",
label: "Dark",
},
];
const ThemeSelector = () => { const ThemeSelector = () => {
const { t } = useTranslation();
const settingStore = useSettingStore(); const settingStore = useSettingStore();
const theme = settingStore.setting.theme; 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) => { 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;

View File

@ -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" />
> {t("social.join-wechat-group")}
<Icon.BsWechat className="w-4 h-auto mr-1" /> </div>
{t("social.join-wechat-group")} }
</div> >
<Popover <img className="w-40 h-auto" src="/wechat-qrcode.webp" alt="wechat qrcode" />
open={openWeChatQrCodePopover} </Popover>
anchorEl={wechatAnchorEl}
onClose={() => setWeChatAnchorEl(null)}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
>
<img className="w-64 h-auto" src="/wechat-qrcode.webp" alt="wechat qrcode" />
</Popover>
</>
); );
}; };

View 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;

View File

@ -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>
))} ))}

View File

@ -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"
}, },

View File

@ -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 配置"
}, },