tachybase_todo/packages/client/src/schema-component/antd/menu/Menu.tsx
chenos 74779a0d46
feat: block provider (#261)
* feat: block schema

* feat: improve code

* feat: improve code

* feat: improve code

* feat: improve code

* feat: styling for kanban

* feat: improve code

* feat: improve code

* fix(client): auto collapse menu items when overflow

* feat: improve code

* fix: fieldNames prop for calendar

* fix: improve code

* feat: improve code

* feat: improve code

* feat: improve code

* feat: improve code

* feat: improve code

* feat: improve code

* feat: improve code

* feat: improve code

* fix: list the sub-table data source

* fix: useFilterByTk

* feat: improve code

* feat: improve code

* feat: improve code
2022-04-04 00:30:40 +08:00

401 lines
11 KiB
TypeScript

import { css } from '@emotion/css';
import {
observer,
RecursionField,
Schema,
SchemaExpressionScopeContext,
useField,
useFieldSchema
} from '@formily/react';
import { Menu as AntdMenu } from 'antd';
import React, { createContext, useContext, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import { createDesignable, DndContext, SortableItem, useDesignable, useDesigner } from '../..';
import { Icon, useAPIClient, useSchemaInitializer } from '../../../';
import { MenuDesigner } from './Menu.Designer';
import { findKeysByUid, findMenuItem } from './util';
const subMenuDesignerCss = css`
position: relative;
display: inline-block;
margin-left: -24px;
margin-right: -34px;
padding: 0 34px 0 24px;
width: calc(100% + 58px);
&:hover {
> .general-schema-designer {
display: block;
}
}
&.nb-action-link {
> .general-schema-designer {
top: -10px;
bottom: -10px;
left: -10px;
right: -10px;
}
}
> .general-schema-designer {
position: absolute;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
background: rgba(241, 139, 98, 0.06);
border: 0;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: #f18b62;
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
}
}
}
`;
const designerCss = css`
position: relative;
display: flex;
justify-content: center;
align-items: center;
margin-left: -20px;
margin-right: -20px;
padding: 0 20px;
width: calc(100% + 40px);
&:hover {
> .general-schema-designer {
display: block;
}
}
&.nb-action-link {
> .general-schema-designer {
top: -10px;
bottom: -10px;
left: -10px;
right: -10px;
}
}
> .general-schema-designer {
position: absolute;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
background: rgba(241, 139, 98, 0.06);
border: 0;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: #f18b62;
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
}
}
}
`;
type ComposedMenu = React.FC<any> & {
Item?: React.FC<any>;
URL?: React.FC<any>;
SubMenu?: React.FC<any>;
Designer?: React.FC<any>;
};
const MenuModeContext = createContext(null);
const useSideMenuRef = () => {
const schema = useFieldSchema();
const scope = useContext(SchemaExpressionScopeContext);
const scopeKey = schema?.['x-component-props']?.['sideMenuRefScopeKey'];
if (!scopeKey) {
return;
}
return scope[scopeKey];
};
const MenuItemDesignerContext = createContext(null);
export const Menu: ComposedMenu = observer((props) => {
let {
onSelect,
mode,
defaultSelectedUid,
sideMenuRefScopeKey,
defaultSelectedKeys: dSelectedKeys,
defaultOpenKeys: dOpenKeys,
...others
} = props;
const Designer = useDesigner();
const schema = useFieldSchema();
const { refresh } = useDesignable();
const api = useAPIClient();
const { render } = useSchemaInitializer(schema['x-initializer']);
const sideMenuRef = useSideMenuRef();
const [defaultSelectedKeys, setDefaultSelectedKeys] = useState(() => {
if (dSelectedKeys) {
return dSelectedKeys;
}
if (defaultSelectedUid) {
return findKeysByUid(schema, defaultSelectedUid);
}
return [];
});
const [loading, setLoading] = useState(false);
const [defaultOpenKeys, setDefaultOpenKeys] = useState(() => {
if (['inline', 'mix'].includes(mode)) {
return dOpenKeys || defaultSelectedKeys;
}
return dOpenKeys;
});
const [sideMenuSchema, setSideMenuSchema] = useState<Schema>(() => {
const key = defaultSelectedKeys?.[0] || null;
if (mode === 'mix' && key) {
const s = schema.properties?.[key];
if (s['x-component'] === 'Menu.SubMenu') {
return s;
}
}
return null;
});
useEffect(() => {
if (['inline', 'mix'].includes(mode)) {
setDefaultOpenKeys(defaultSelectedKeys);
}
}, [defaultSelectedKeys]);
useEffect(() => {
const sideMenuElement = sideMenuRef?.current as HTMLElement;
if (!sideMenuElement) {
return;
}
sideMenuElement.style.display = sideMenuSchema?.['x-component'] === 'Menu.SubMenu' ? 'block' : 'none';
}, [sideMenuSchema?.name, sideMenuRef]);
const { designable } = useDesignable();
return (
<DndContext>
<MenuItemDesignerContext.Provider value={Designer}>
<MenuModeContext.Provider value={mode}>
<AntdMenu
{...others}
style={
{
// width: mode === 'mix' ? '100%' : undefined,
}
}
className={css`
.ant-menu-item:hover {
> .ant-menu-title-content > div {
.general-schema-designer {
display: block;
}
}
}
`}
onSelect={(info: any) => {
const s = schema.properties[info.key];
if (mode === 'mix') {
setSideMenuSchema(s);
if (s['x-component'] !== 'Menu.SubMenu') {
onSelect && onSelect(info);
} else {
const menuItemSchema = findMenuItem(s);
if (!menuItemSchema) {
return;
}
// TODO
setLoading(true);
const keys = findKeysByUid(schema, menuItemSchema['x-uid']);
setDefaultSelectedKeys(keys);
setTimeout(() => {
setLoading(false);
}, 100);
onSelect &&
onSelect({
key: menuItemSchema.name,
item: {
props: {
schema: menuItemSchema,
},
},
});
}
} else {
onSelect && onSelect(info);
}
}}
mode={mode === 'mix' ? 'horizontal' : mode}
defaultOpenKeys={defaultOpenKeys}
defaultSelectedKeys={defaultSelectedKeys}
>
{designable && (
<AntdMenu.Item disabled style={{ padding: '0 8px', order: 9999 }}>
{render({ style: { background: 'none' } })}
</AntdMenu.Item>
)}
{props.children}
</AntdMenu>
{loading
? null
: mode === 'mix' &&
sideMenuSchema?.['x-component'] === 'Menu.SubMenu' &&
sideMenuRef?.current?.firstChild &&
createPortal(
<MenuModeContext.Provider value={'inline'}>
<AntdMenu
mode={'inline'}
defaultOpenKeys={defaultOpenKeys}
defaultSelectedKeys={defaultSelectedKeys}
onSelect={(info) => {
onSelect && onSelect(info);
}}
className={css`
.ant-menu-item {
> .ant-menu-title-content {
margin-left: -24px;
margin-right: -16px;
padding: 0 16px 0 24px;
> div {
> .general-schema-designer {
right: 6px !important;
}
}
}
}
.ant-menu-submenu-title {
.ant-menu-title-content {
margin-left: -24px;
margin-right: -34px;
padding: 0 34px 0 24px;
> div {
> .general-schema-designer {
right: 6px !important;
}
> span.anticon {
margin-right: 10px;
}
}
}
}
`}
>
<RecursionField schema={sideMenuSchema} onlyRenderProperties />
{render({
style: { margin: 8 },
insert: (s) => {
console.log('createDesignable', s);
const dn = createDesignable({
api,
refresh,
current: sideMenuSchema,
});
dn.loadAPIClientEvents();
dn.insertAdjacent('beforeEnd', s);
},
})}
</AntdMenu>
</MenuModeContext.Provider>,
sideMenuRef.current.firstChild,
)}
</MenuModeContext.Provider>
</MenuItemDesignerContext.Provider>
</DndContext>
);
});
Menu.Item = observer((props) => {
const { icon, ...others } = props;
const schema = useFieldSchema();
const field = useField();
const Designer = useContext(MenuItemDesignerContext);
return (
<AntdMenu.Item {...others} key={schema.name} eventKey={schema.name} schema={schema}>
<SortableItem className={designerCss}>
<Icon type={icon} />
<span
className={css`
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
width: 100%;
vertical-align: middle;
`}
>
{field.title}
</span>
<Designer />
</SortableItem>
</AntdMenu.Item>
);
});
Menu.URL = observer((props) => {
const { icon, ...others } = props;
const schema = useFieldSchema();
const field = useField();
const Designer = useContext(MenuItemDesignerContext);
return (
<AntdMenu.Item
{...others}
key={schema.name}
eventKey={schema.name}
schema={schema}
onClick={() => {
window.open(props.href, '_blank');
}}
>
<SortableItem className={designerCss}>
<Icon type={icon} />
{field.title}
<Designer />
</SortableItem>
</AntdMenu.Item>
);
});
Menu.SubMenu = observer((props) => {
const { icon, ...others } = props;
const schema = useFieldSchema();
const field = useField();
const mode = useContext(MenuModeContext);
const Designer = useContext(MenuItemDesignerContext);
if (mode === 'mix') {
return <Menu.Item {...props} />;
}
return (
<AntdMenu.SubMenu
{...others}
key={schema.name}
eventKey={schema.name}
title={
<SortableItem className={subMenuDesignerCss}>
<Icon type={icon} />
{field.title}
<Designer />
</SortableItem>
}
>
<RecursionField schema={schema} onlyRenderProperties />
</AntdMenu.SubMenu>
);
});
Menu.Designer = MenuDesigner;