mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 09:17:23 +00:00
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 './input';
|
||||
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.Link
|
||||
- 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