feat: add menu into schema component
This commit is contained in:
parent
d0f677a6e9
commit
cb3e171b31
@ -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';
|
99
packages/client/src/schema-component/antd/menu/Menu.tsx
Normal file
99
packages/client/src/schema-component/antd/menu/Menu.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
});
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
118
packages/client/src/schema-component/antd/menu/demos/demo3.tsx
Normal file
118
packages/client/src/schema-component/antd/menu/demos/demo3.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
@ -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"/>
|
||||||
|
1
packages/client/src/schema-component/antd/menu/index.ts
Normal file
1
packages/client/src/schema-component/antd/menu/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './Menu';
|
34
packages/client/src/schema-component/antd/menu/util.ts
Normal file
34
packages/client/src/schema-component/antd/menu/util.ts
Normal 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));
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user