diff --git a/web/src/components/DevContainer.tsx b/web/src/components/DevContainer.tsx
new file mode 100644
index 00000000..8259e1cf
--- /dev/null
+++ b/web/src/components/DevContainer.tsx
@@ -0,0 +1,9 @@
+import React, { Fragment } from 'react';
+import { isDevelopment } from 'tailchat-shared';
+
+export const DevContainer: React.FC = React.memo((props) => {
+ return isDevelopment ? {props.children} : null;
+});
+DevContainer.displayName = 'DevContainer';
+
+export default DevContainer;
diff --git a/web/src/components/SidebarView.tsx b/web/src/components/SidebarView.tsx
new file mode 100644
index 00000000..903643f0
--- /dev/null
+++ b/web/src/components/SidebarView.tsx
@@ -0,0 +1,167 @@
+import React, { useState, useContext } from 'react';
+import _get from 'lodash/get';
+import { DevContainer } from './DevContainer';
+import clsx from 'clsx';
+
+export interface SidebarViewMenuItemType {
+ type: 'item';
+ title: string;
+ content: React.ReactNode;
+
+ /**
+ * 是否是仅开发者可见
+ */
+ isDev?: boolean;
+
+ /**
+ * 隐藏这个项
+ */
+ hidden?: boolean;
+}
+
+interface SidebarViewLinkType {
+ type: 'link';
+ title: string;
+ onClick: () => void;
+ isDanger?: boolean;
+}
+
+const SidebarViewMenuItemTitle: React.FC<{
+ active?: boolean;
+ isDanger?: boolean;
+ onClick: () => void;
+}> = (props) => (
+
+ {props.children}
+
+);
+
+interface SidebarViewContextProps {
+ content: React.ReactNode;
+ setContent: (content: React.ReactNode) => void;
+}
+export const SidebarViewContext =
+ React.createContext(null);
+SidebarViewContext.displayName = 'SidebarViewContext';
+
+export type SidebarViewMenuItem = SidebarViewMenuItemType | SidebarViewLinkType;
+export type SidebarViewMenuType =
+ | {
+ type: 'group';
+ title: string;
+ children: SidebarViewMenuItem[];
+ }
+ | SidebarViewMenuItem;
+
+interface SidebarViewMenuProps {
+ menu: SidebarViewMenuType;
+}
+const SidebarViewMenuItem: React.FC = React.memo(
+ (props) => {
+ const { menu } = props;
+ const context = useContext(SidebarViewContext);
+
+ if (!context) {
+ return null;
+ }
+
+ const { content, setContent } = context;
+
+ if (menu.type === 'group') {
+ return (
+
+
+ {menu.title}
+
+
+ {menu.children.map((sub, i) => (
+
+ ))}
+
+
+ );
+ } else if (menu.type === 'item') {
+ if (menu.hidden === true) {
+ return null;
+ }
+
+ const component = (
+ setContent(menu.content)}
+ >
+ {menu.title}
+
+ );
+
+ if (menu.isDev === true) {
+ return {component};
+ } else {
+ return {component}
;
+ }
+ } else if (menu.type === 'link') {
+ return (
+
+
+ {menu.title}
+
+
+ );
+ }
+
+ return null;
+ }
+);
+SidebarViewMenuItem.displayName = 'SidebarViewMenuItem';
+
+interface SidebarViewProps {
+ menu: SidebarViewMenuType[];
+
+ /**
+ * 默认内容路径
+ * @default "0.children.0.content"
+ */
+ defaultContentPath: string;
+}
+export const SidebarView: React.FC = React.memo((props) => {
+ const { menu, defaultContentPath = '0.children.0.content' } = props;
+ const [content, setContent] = useState(
+ _get(menu, defaultContentPath, null)
+ );
+
+ return (
+
+
+
+ {menu.map((item, i) => (
+
+ ))}
+
+
+
+ {content}
+
+
+
+ );
+});
+SidebarView.displayName = 'SidebarView';
diff --git a/web/src/components/modals/SettingsView/About.tsx b/web/src/components/modals/SettingsView/About.tsx
new file mode 100644
index 00000000..43c9eec6
--- /dev/null
+++ b/web/src/components/modals/SettingsView/About.tsx
@@ -0,0 +1,6 @@
+import React from 'react';
+
+export const SettingsAbout: React.FC = React.memo(() => {
+ return 关于
;
+});
+SettingsAbout.displayName = 'SettingsAbout';
diff --git a/web/src/components/modals/SettingsView/index.tsx b/web/src/components/modals/SettingsView/index.tsx
new file mode 100644
index 00000000..c87b8b25
--- /dev/null
+++ b/web/src/components/modals/SettingsView/index.tsx
@@ -0,0 +1,43 @@
+import { FullModal } from '@/components/FullModal';
+import { SidebarView, SidebarViewMenuType } from '@/components/SidebarView';
+import React, { useCallback, useMemo } from 'react';
+import { t } from 'tailchat-shared';
+import { SettingsAbout } from './About';
+
+interface SettingsViewProps {
+ onClose: () => void;
+}
+export const SettingsView: React.FC = React.memo((props) => {
+ const handleChangeVisible = useCallback(
+ (visible) => {
+ if (visible === false && typeof props.onClose === 'function') {
+ props.onClose();
+ }
+ },
+ [props.onClose]
+ );
+
+ const menu: SidebarViewMenuType[] = useMemo(
+ () => [
+ {
+ type: 'group',
+ title: t('通用'),
+ children: [
+ {
+ type: 'item',
+ title: t('关于'),
+ content: ,
+ },
+ ],
+ },
+ ],
+ []
+ );
+
+ return (
+
+
+
+ );
+});
+SettingsView.displayName = 'SettingsView';
diff --git a/web/src/routes/Main/Navbar/SettingBtn.tsx b/web/src/routes/Main/Navbar/SettingBtn.tsx
index 900e41b6..a84c77ae 100644
--- a/web/src/routes/Main/Navbar/SettingBtn.tsx
+++ b/web/src/routes/Main/Navbar/SettingBtn.tsx
@@ -1,15 +1,11 @@
-import { FullModal } from '@/components/FullModal';
import { closeModal, openModal } from '@/components/Modal';
+import { SettingsView } from '@/components/modals/SettingsView';
import { Icon } from '@iconify/react';
import React, { useCallback } from 'react';
export const SettingBtn: React.FC = React.memo(() => {
const handleClick = useCallback(() => {
- const key = openModal(
- closeModal(key)}>
- Setting View
-
- );
+ const key = openModal( closeModal(key)} />);
}, []);
return (
diff --git a/web/tailwind.config.js b/web/tailwind.config.js
index c091789e..c49c0599 100644
--- a/web/tailwind.config.js
+++ b/web/tailwind.config.js
@@ -74,6 +74,7 @@ module.exports = {
opacity: ['disabled'],
display: ['group-hover'],
borderRadius: ['hover'],
+ borderWidth: ['last'],
},
},
plugins: [tailchat],