mirror of https://github.com/msgbyte/tailchat
feat: SidebarView
parent
e6e98e4dff
commit
06238c57a6
@ -0,0 +1,9 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { isDevelopment } from 'tailchat-shared';
|
||||
|
||||
export const DevContainer: React.FC = React.memo((props) => {
|
||||
return isDevelopment ? <Fragment>{props.children}</Fragment> : null;
|
||||
});
|
||||
DevContainer.displayName = 'DevContainer';
|
||||
|
||||
export default DevContainer;
|
@ -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) => (
|
||||
<div
|
||||
className={clsx(
|
||||
'rounded-sm px-1.5 py-2.5 mb-1 text-gray-300 cursor-pointer hover:bg-black hover:bg-opacity-10 hover:text-gray-200',
|
||||
{
|
||||
'bg-black bg-opacity-10 text-white': props.active,
|
||||
'text-red-500': props.isDanger,
|
||||
}
|
||||
)}
|
||||
style={{ width: 192, lineHeight: '20px' }}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
|
||||
interface SidebarViewContextProps {
|
||||
content: React.ReactNode;
|
||||
setContent: (content: React.ReactNode) => void;
|
||||
}
|
||||
export const SidebarViewContext =
|
||||
React.createContext<SidebarViewContextProps | null>(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<SidebarViewMenuProps> = React.memo(
|
||||
(props) => {
|
||||
const { menu } = props;
|
||||
const context = useContext(SidebarViewContext);
|
||||
|
||||
if (!context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { content, setContent } = context;
|
||||
|
||||
if (menu.type === 'group') {
|
||||
return (
|
||||
<div className="pb-2.5 mb-2.5 border-b last:border-0">
|
||||
<div className="px-1.5 py-2.5 pt-0 text-xs font-bold uppercase">
|
||||
{menu.title}
|
||||
</div>
|
||||
<div>
|
||||
{menu.children.map((sub, i) => (
|
||||
<SidebarViewMenuItem key={i} menu={sub} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (menu.type === 'item') {
|
||||
if (menu.hidden === true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const component = (
|
||||
<SidebarViewMenuItemTitle
|
||||
active={content === menu.content}
|
||||
onClick={() => setContent(menu.content)}
|
||||
>
|
||||
{menu.title}
|
||||
</SidebarViewMenuItemTitle>
|
||||
);
|
||||
|
||||
if (menu.isDev === true) {
|
||||
return <DevContainer>{component}</DevContainer>;
|
||||
} else {
|
||||
return <div>{component}</div>;
|
||||
}
|
||||
} else if (menu.type === 'link') {
|
||||
return (
|
||||
<div>
|
||||
<SidebarViewMenuItemTitle
|
||||
isDanger={menu.isDanger}
|
||||
onClick={menu.onClick}
|
||||
>
|
||||
{menu.title}
|
||||
</SidebarViewMenuItemTitle>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
);
|
||||
SidebarViewMenuItem.displayName = 'SidebarViewMenuItem';
|
||||
|
||||
interface SidebarViewProps {
|
||||
menu: SidebarViewMenuType[];
|
||||
|
||||
/**
|
||||
* 默认内容路径
|
||||
* @default "0.children.0.content"
|
||||
*/
|
||||
defaultContentPath: string;
|
||||
}
|
||||
export const SidebarView: React.FC<SidebarViewProps> = React.memo((props) => {
|
||||
const { menu, defaultContentPath = '0.children.0.content' } = props;
|
||||
const [content, setContent] = useState<React.ReactNode>(
|
||||
_get(menu, defaultContentPath, null)
|
||||
);
|
||||
|
||||
return (
|
||||
<SidebarViewContext.Provider value={{ content, setContent }}>
|
||||
<div className="flex w-full h-full">
|
||||
<div
|
||||
className="bg-black bg-opacity-10 flex flex-col justify-start items-end"
|
||||
style={{ flex: '1 0 218px', padding: '90px 10px 80px' }}
|
||||
>
|
||||
{menu.map((item, i) => (
|
||||
<SidebarViewMenuItem key={i} menu={item} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="overflow-auto"
|
||||
style={{ flex: '1 1 800px', padding: '90px 40px 80px' }}
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
</SidebarViewContext.Provider>
|
||||
);
|
||||
});
|
||||
SidebarView.displayName = 'SidebarView';
|
@ -0,0 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
export const SettingsAbout: React.FC = React.memo(() => {
|
||||
return <div>关于</div>;
|
||||
});
|
||||
SettingsAbout.displayName = 'SettingsAbout';
|
@ -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<SettingsViewProps> = 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: <SettingsAbout />,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<FullModal onChangeVisible={handleChangeVisible}>
|
||||
<SidebarView menu={menu} defaultContentPath="0.children.0.content" />
|
||||
</FullModal>
|
||||
);
|
||||
});
|
||||
SettingsView.displayName = 'SettingsView';
|
Loading…
Reference in New Issue