From cb3e171b31c27f7fb4edfb1fe23b528655de232e Mon Sep 17 00:00:00 2001 From: chenos Date: Mon, 17 Jan 2022 23:04:33 +0800 Subject: [PATCH] feat: add menu into schema component --- .../client/src/schema-component/antd/index.ts | 1 + .../src/schema-component/antd/menu/Menu.tsx | 99 +++++++++++++++ .../antd/menu/demos/demo1.tsx | 93 ++++++++++++++ .../antd/menu/demos/demo2.tsx | 93 ++++++++++++++ .../antd/menu/demos/demo3.tsx | 118 ++++++++++++++++++ .../src/schema-component/antd/menu/index.md | 8 ++ .../src/schema-component/antd/menu/index.ts | 1 + .../src/schema-component/antd/menu/util.ts | 34 +++++ 8 files changed, 447 insertions(+) create mode 100644 packages/client/src/schema-component/antd/menu/Menu.tsx create mode 100644 packages/client/src/schema-component/antd/menu/demos/demo1.tsx create mode 100644 packages/client/src/schema-component/antd/menu/demos/demo2.tsx create mode 100644 packages/client/src/schema-component/antd/menu/demos/demo3.tsx create mode 100644 packages/client/src/schema-component/antd/menu/index.ts create mode 100644 packages/client/src/schema-component/antd/menu/util.ts diff --git a/packages/client/src/schema-component/antd/index.ts b/packages/client/src/schema-component/antd/index.ts index 1fd0a5b8e..07ae84d29 100644 --- a/packages/client/src/schema-component/antd/index.ts +++ b/packages/client/src/schema-component/antd/index.ts @@ -8,3 +8,4 @@ export * from './form-item'; export * from './grid'; export * from './input'; export * from './radio'; +export * from './menu'; \ No newline at end of file diff --git a/packages/client/src/schema-component/antd/menu/Menu.tsx b/packages/client/src/schema-component/antd/menu/Menu.tsx new file mode 100644 index 000000000..6fe8e26e8 --- /dev/null +++ b/packages/client/src/schema-component/antd/menu/Menu.tsx @@ -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 & { + Item?: React.FC; + SubMenu?: React.FC; +}; + +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(() => { + 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 ( + + { + const s = schema.properties[info.key]; + if (mode === 'mix') { + setSideMenuSchema(s); + } + onSelect && onSelect(info); + }} + mode={mode === 'mix' ? 'horizontal' : mode} + defaultOpenKeys={defaultOpenKeys} + defaultSelectedKeys={defaultSelectedKeys} + > + + + {mode === 'mix' && + sideMenuSchema.properties && + sideMenuRef.current?.firstChild && + createPortal( + + + + + , + sideMenuRef.current.firstChild, + )} + + ); +}); + +Menu.Item = observer((props) => { + const schema = useFieldSchema(); + return ( + } key={schema.name} eventKey={schema.name} schema={schema}> + {schema.title} + + ); +}); + +Menu.SubMenu = observer((props) => { + const schema = useFieldSchema(); + const mode = useContext(MenuModeContext); + if (mode === 'mix') { + return ; + } + return ( + } + title={schema.title} + key={schema.name} + eventKey={schema.name} + > + + + ); +}); diff --git a/packages/client/src/schema-component/antd/menu/demos/demo1.tsx b/packages/client/src/schema-component/antd/menu/demos/demo1.tsx new file mode 100644 index 000000000..01de323a9 --- /dev/null +++ b/packages/client/src/schema-component/antd/menu/demos/demo1.tsx @@ -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 ( + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/menu/demos/demo2.tsx b/packages/client/src/schema-component/antd/menu/demos/demo2.tsx new file mode 100644 index 000000000..d813452c6 --- /dev/null +++ b/packages/client/src/schema-component/antd/menu/demos/demo2.tsx @@ -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 ( + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/menu/demos/demo3.tsx b/packages/client/src/schema-component/antd/menu/demos/demo3.tsx new file mode 100644 index 000000000..10f390691 --- /dev/null +++ b/packages/client/src/schema-component/antd/menu/demos/demo3.tsx @@ -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 ( + + + + + + + + Content + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/menu/index.md b/packages/client/src/schema-component/antd/menu/index.md index 13b263c48..acbc0cc1e 100644 --- a/packages/client/src/schema-component/antd/menu/index.md +++ b/packages/client/src/schema-component/antd/menu/index.md @@ -17,3 +17,11 @@ group: - Menu.URL - Menu.Link - Menu.Action + +## Examples + + + + + + diff --git a/packages/client/src/schema-component/antd/menu/index.ts b/packages/client/src/schema-component/antd/menu/index.ts new file mode 100644 index 000000000..629d3d0aa --- /dev/null +++ b/packages/client/src/schema-component/antd/menu/index.ts @@ -0,0 +1 @@ +export * from './Menu'; diff --git a/packages/client/src/schema-component/antd/menu/util.ts b/packages/client/src/schema-component/antd/menu/util.ts new file mode 100644 index 000000000..33528990b --- /dev/null +++ b/packages/client/src/schema-component/antd/menu/util.ts @@ -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)); +}