feat: add menu into schema component

This commit is contained in:
chenos 2022-01-17 23:04:33 +08:00
parent d0f677a6e9
commit cb3e171b31
8 changed files with 447 additions and 0 deletions

View File

@ -8,3 +8,4 @@ export * from './form-item';
export * from './grid'; export * from './grid';
export * from './input'; export * from './input';
export * from './radio'; export * from './radio';
export * from './menu';

View File

@ -0,0 +1,99 @@
import React, { createContext, useContext, useEffect, useState } from 'react';
import { Menu as AntdMenu } from 'antd';
import { Schema, observer, useFieldSchema, useField, RecursionField } from '@formily/react';
import { DesktopOutlined } from '@ant-design/icons';
import { findKeysByUid } from './util';
import { createPortal } from 'react-dom';
type ComposedMenu = React.FC<any> & {
Item?: React.FC<any>;
SubMenu?: React.FC<any>;
};
const MenuModeContext = createContext(null);
export const Menu: ComposedMenu = observer((props) => {
let { onSelect, sideMenuRef, mode, defaultSelectedUid, defaultSelectedKeys, defaultOpenKeys, ...others } = props;
const schema = useFieldSchema();
if (defaultSelectedUid) {
defaultSelectedKeys = findKeysByUid(schema, defaultSelectedUid);
if (['inline', 'mix'].includes(mode)) {
defaultOpenKeys = defaultSelectedKeys;
}
}
const [sideMenuSchema, setSideMenuSchema] = useState<Schema>(() => {
if (mode === 'mix' && defaultSelectedKeys[0]) {
const s = schema.properties?.[defaultSelectedKeys[0]];
if (s['x-component'] === 'Menu.SubMenu') {
return s;
}
}
return null;
});
useEffect(() => {
const sideMenuElement = sideMenuRef?.current as HTMLElement;
if (!sideMenuElement) {
return;
}
sideMenuElement.style.display = sideMenuSchema?.properties ? 'block' : 'none';
}, [sideMenuSchema?.properties, sideMenuRef]);
console.log({ sideMenuRef, defaultSelectedKeys, sideMenuSchema });
return (
<MenuModeContext.Provider value={mode}>
<AntdMenu
{...others}
onSelect={(info) => {
const s = schema.properties[info.key];
if (mode === 'mix') {
setSideMenuSchema(s);
}
onSelect && onSelect(info);
}}
mode={mode === 'mix' ? 'horizontal' : mode}
defaultOpenKeys={defaultOpenKeys}
defaultSelectedKeys={defaultSelectedKeys}
>
<RecursionField schema={schema} onlyRenderProperties />
</AntdMenu>
{mode === 'mix' &&
sideMenuSchema.properties &&
sideMenuRef.current?.firstChild &&
createPortal(
<MenuModeContext.Provider value={'inline'}>
<AntdMenu mode={'inline'} defaultOpenKeys={defaultOpenKeys} defaultSelectedKeys={defaultSelectedKeys}>
<RecursionField schema={sideMenuSchema} onlyRenderProperties />
</AntdMenu>
</MenuModeContext.Provider>,
sideMenuRef.current.firstChild,
)}
</MenuModeContext.Provider>
);
});
Menu.Item = observer((props) => {
const schema = useFieldSchema();
return (
<AntdMenu.Item {...props} icon={<DesktopOutlined />} key={schema.name} eventKey={schema.name} schema={schema}>
{schema.title}
</AntdMenu.Item>
);
});
Menu.SubMenu = observer((props) => {
const schema = useFieldSchema();
const mode = useContext(MenuModeContext);
if (mode === 'mix') {
return <Menu.Item {...props} />;
}
return (
<AntdMenu.SubMenu
{...props}
icon={<DesktopOutlined />}
title={schema.title}
key={schema.name}
eventKey={schema.name}
>
<RecursionField schema={schema} onlyRenderProperties />
</AntdMenu.SubMenu>
);
});

View File

@ -0,0 +1,93 @@
/**
* title: Menu
*/
import React from 'react';
import { Menu, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
import { ISchema } from '@formily/react';
const schema: ISchema = {
type: 'object',
properties: {
menu1: {
type: 'void',
'x-component': 'Menu',
'x-component-props': {
mode: 'horizontal',
defaultSelectedUid: 'u8',
// style: {
// width: 260,
// },
},
properties: {
item1: {
type: 'void',
title: 'Menu Item 1',
'x-uid': 'u1',
'x-component': 'Menu.Item',
'x-component-props': {},
},
item2: {
type: 'void',
title: 'Menu Item 2',
'x-uid': 'u2',
'x-component': 'Menu.Item',
'x-component-props': {},
},
item3: {
type: 'void',
title: 'SubMenu 1',
'x-uid': 'u3',
'x-component': 'Menu.SubMenu',
'x-component-props': {},
properties: {
item4: {
type: 'void',
title: 'Menu Item 11',
'x-uid': 'u4',
'x-component': 'Menu.Item',
'x-component-props': {},
},
item5: {
type: 'void',
title: 'Menu Item 12',
'x-uid': 'u5',
'x-component': 'Menu.Item',
'x-component-props': {},
},
item6: {
type: 'void',
title: 'SubMenu 1',
'x-uid': 'u6',
'x-component': 'Menu.SubMenu',
'x-component-props': {},
properties: {
item7: {
type: 'void',
title: 'Menu Item 11',
'x-uid': 'u7',
'x-component': 'Menu.Item',
'x-component-props': {},
},
item8: {
type: 'void',
title: 'Menu Item 12',
'x-uid': 'u8',
'x-component': 'Menu.Item',
'x-component-props': {},
},
},
},
},
},
},
},
},
};
export default () => {
return (
<SchemaComponentProvider components={{ Menu }}>
<SchemaComponent schema={schema} />
</SchemaComponentProvider>
);
};

View File

@ -0,0 +1,93 @@
/**
* title: Menu
*/
import React from 'react';
import { Menu, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
import { ISchema } from '@formily/react';
const schema: ISchema = {
type: 'object',
properties: {
menu1: {
type: 'void',
'x-component': 'Menu',
'x-component-props': {
mode: 'inline',
defaultSelectedUid: 'u8',
style: {
width: 260,
},
},
properties: {
item1: {
type: 'void',
title: 'Menu Item 1',
'x-uid': 'u1',
'x-component': 'Menu.Item',
'x-component-props': {},
},
item2: {
type: 'void',
title: 'Menu Item 2',
'x-uid': 'u2',
'x-component': 'Menu.Item',
'x-component-props': {},
},
item3: {
type: 'void',
title: 'SubMenu 1',
'x-uid': 'u3',
'x-component': 'Menu.SubMenu',
'x-component-props': {},
properties: {
item4: {
type: 'void',
title: 'Menu Item 11',
'x-uid': 'u4',
'x-component': 'Menu.Item',
'x-component-props': {},
},
item5: {
type: 'void',
title: 'Menu Item 12',
'x-uid': 'u5',
'x-component': 'Menu.Item',
'x-component-props': {},
},
item6: {
type: 'void',
title: 'SubMenu 1',
'x-uid': 'u6',
'x-component': 'Menu.SubMenu',
'x-component-props': {},
properties: {
item7: {
type: 'void',
title: 'Menu Item 11',
'x-uid': 'u7',
'x-component': 'Menu.Item',
'x-component-props': {},
},
item8: {
type: 'void',
title: 'Menu Item 12',
'x-uid': 'u8',
'x-component': 'Menu.Item',
'x-component-props': {},
},
},
},
},
},
},
},
},
};
export default () => {
return (
<SchemaComponentProvider components={{ Menu }}>
<SchemaComponent schema={schema} />
</SchemaComponentProvider>
);
};

View File

@ -0,0 +1,118 @@
/**
* title: Menu
*/
import React, { useRef } from 'react';
import { Menu, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
import { ISchema } from '@formily/react';
import { Layout } from 'antd';
const schema: ISchema = {
type: 'object',
properties: {
menu1: {
type: 'void',
'x-component': 'Menu',
'x-component-props': {
mode: 'mix',
theme: 'dark',
defaultSelectedUid: 'u8',
sideMenuRef: '{{ sideMenuRef }}',
},
properties: {
item1: {
type: 'void',
title: 'Menu Item 1',
'x-uid': 'u1',
'x-component': 'Menu.Item',
'x-component-props': {},
},
item2: {
type: 'void',
title: 'Menu Item 2',
'x-uid': 'u2',
'x-component': 'Menu.Item',
'x-component-props': {},
},
item9: {
type: 'void',
title: 'SubMenu 2',
'x-uid': 'u9',
'x-component': 'Menu.SubMenu',
'x-component-props': {},
properties: {
item10: {
type: 'void',
title: 'Menu Item 10',
'x-uid': 'u10',
'x-component': 'Menu.Item',
'x-component-props': {},
},
},
},
item3: {
type: 'void',
title: 'SubMenu 1',
'x-uid': 'u3',
'x-component': 'Menu.SubMenu',
'x-component-props': {},
properties: {
item4: {
type: 'void',
title: 'Menu Item 11',
'x-uid': 'u4',
'x-component': 'Menu.Item',
'x-component-props': {},
},
item5: {
type: 'void',
title: 'Menu Item 12',
'x-uid': 'u5',
'x-component': 'Menu.Item',
'x-component-props': {},
},
item6: {
type: 'void',
title: 'SubMenu 1',
'x-uid': 'u6',
'x-component': 'Menu.SubMenu',
'x-component-props': {},
properties: {
item7: {
type: 'void',
title: 'Menu Item 11',
'x-uid': 'u7',
'x-component': 'Menu.Item',
'x-component-props': {},
},
item8: {
type: 'void',
title: 'Menu Item 12',
'x-uid': 'u8',
'x-component': 'Menu.Item',
'x-component-props': {},
},
},
},
},
},
},
},
},
};
export default () => {
const sideMenuRef = useRef();
return (
<SchemaComponentProvider components={{ Menu }}>
<Layout>
<Layout.Header>
<SchemaComponent scope={{ sideMenuRef }} schema={schema} />
</Layout.Header>
<Layout>
<Layout.Sider theme={'light'} ref={sideMenuRef}></Layout.Sider>
<Layout.Content>Content</Layout.Content>
</Layout>
</Layout>
</SchemaComponentProvider>
);
};

View File

@ -17,3 +17,11 @@ group:
- Menu.URL - Menu.URL
- Menu.Link - Menu.Link
- Menu.Action - Menu.Action
## Examples
<code src="./demos/demo1.tsx"/>
<code src="./demos/demo2.tsx"/>
<code src="./demos/demo3.tsx"/>

View File

@ -0,0 +1 @@
export * from './Menu';

View File

@ -0,0 +1,34 @@
import { Schema, observer, useFieldSchema, useField, RecursionField } from '@formily/react';
function findByUid(schema: Schema, uid: string) {
return schema.reduceProperties((buffter, s) => {
if (s['x-uid'] === uid) {
return s;
}
const ss = findByUid(s, uid);
if (ss) {
return ss;
}
return buffter;
}, null);
}
function findKeys(schema: Schema) {
if (!schema) {
return;
}
const keys = [];
keys.push(schema.name);
while (schema.parent) {
if (schema.parent['x-component'] === 'Menu') {
break;
}
keys.push(schema.parent.name);
schema = schema.parent;
}
return keys.reverse();
}
export function findKeysByUid(schema: Schema, uid: string) {
return findKeys(findByUid(schema, uid));
}