diff --git a/examples/select/multi.json b/examples/select/multi.json new file mode 100644 index 00000000..ba5a555c --- /dev/null +++ b/examples/select/multi.json @@ -0,0 +1,67 @@ +{ + "app": { + "version": "example/v1", + "metadata": { + "name": "multi select", + "description": "multi select" + }, + "spec": { + "components": [ + { + "id": "root", + "type": "chakra_ui/v1/root", + "properties": {}, + "traits": [] + }, + { + "id": "stack", + "type": "chakra_ui/v1/stack", + "properties": {}, + "traits": [ + { + "type": "core/v1/slot", + "properties": { + "container": { + "id": "root", + "slot": "root" + } + } + } + ] + }, + { + "id": "select1", + "type": "chakra_ui/v1/multiSelect", + "properties": { + "placeholder": "Select option", + "options": [ + { + "value": "1", + "label": "Option 1" + }, + { + "value": "2", + "label": "Option 2" + }, + { + "value": "3", + "label": "Option 3" + } + ] + }, + "traits": [ + { + "type": "core/v1/slot", + "properties": { + "container": { + "id": "stack", + "slot": "content" + } + } + } + ] + } + ] + } + } +} diff --git a/examples/select/tree.json b/examples/select/tree.json new file mode 100644 index 00000000..65a8c3db --- /dev/null +++ b/examples/select/tree.json @@ -0,0 +1,101 @@ +{ + "app": { + "version": "example/v1", + "metadata": { + "name": "tree select", + "description": "tree select" + }, + "spec": { + "components": [ + { + "id": "select1", + "type": "antd/v1/treeSelect", + "properties": { + "placeholder": "Select option", + "treeData": [ + { + "title": "Shoes", + "value": "shoes", + "key": "shoes", + "children": [ + { + "title": "Atheletic", + "value": "athletic", + "key": "athletic", + "children": [ + { + "title": "Tennis Shoes", + "value": "tennis", + "key": "tennis" + }, + { + "title": "Running", + "value": "running", + "key": "running" + } + ] + }, + { + "title": "Dress Shoes", + "value": "dress", + "key": "dress" + }, + { + "title": "Sandals", + "value": "sandals", + "key": "sandals", + "children": [ + { + "title": "Flip-Flop", + "value": "flipflop", + "key": "flipflop" + }, + { + "title": "Thong", + "value": "thong", + "key": "thong" + }, + { + "title": "Fisherman", + "value": "fisherman", + "key": "fisherman" + } + ] + } + ] + }, + { + "title": "Jewelry", + "value": "jewelry", + "key": "jewelry", + "children": [ + { + "title": "Necklace", + "value": "necklace", + "key": "necklace" + }, + { + "title": "Bracelet", + "value": "bracelet", + "key": "bracelet" + }, + { + "title": "Ring", + "value": "ring", + "key": "ring" + } + ] + }, + { + "title": "Misc", + "value": "misc", + "key": "misc" + } + ] + }, + "traits": [] + } + ] + } + } +} diff --git a/packages/editor/src/components/Editor.tsx b/packages/editor/src/components/Editor.tsx index b959a2c1..ab9cfb6f 100644 --- a/packages/editor/src/components/Editor.tsx +++ b/packages/editor/src/components/Editor.tsx @@ -24,6 +24,7 @@ type Props = { App: ReturnOfInit['App']; registry: ReturnOfInit['registry']; stateStore: ReturnOfInit['stateManager']['store']; + apiService: ReturnOfInit['apiService']; appModelManager: AppModelManager; }; @@ -32,6 +33,7 @@ export const Editor: React.FC = ({ registry, stateStore, appModelManager, + apiService, }) => { const { app } = useAppModel(); const [selectedComponentId, setSelectedComponentId] = useState( diff --git a/packages/editor/src/main.tsx b/packages/editor/src/main.tsx index 22da7402..82fd51e7 100644 --- a/packages/editor/src/main.tsx +++ b/packages/editor/src/main.tsx @@ -25,6 +25,7 @@ export default function renderApp( const App = metaUI.App; const registry = metaUI.registry; + const apiService = metaUI.apiService; const stateStore = metaUI.stateManager.store; const appModelManager = new AppModelManager(app, registry); @@ -41,6 +42,7 @@ export default function renderApp( registry={registry} stateStore={stateStore} appModelManager={appModelManager} + apiService={apiService} /> , diff --git a/packages/editor/src/playground.tsx b/packages/editor/src/playground.tsx index 4ca60a51..28d9a99c 100644 --- a/packages/editor/src/playground.tsx +++ b/packages/editor/src/playground.tsx @@ -21,13 +21,14 @@ type Example = { const Playground: React.FC<{ examples: Example[] }> = ({ examples }) => { const [example, setExample] = useState(examples[0]); - const { App, registry, stateStore, appModelManager } = useMemo(() => { + const { App, registry, stateStore, appModelManager, apiService } = useMemo(() => { if (!example) { return {}; } const metaUI = initMetaUI(); const App = metaUI.App; const registry = metaUI.registry; + const apiService = metaUI.apiService; const stateStore = metaUI.stateManager.store; const { app, modules = [] } = example.value; @@ -42,6 +43,7 @@ const Playground: React.FC<{ examples: Example[] }> = ({ examples }) => { registry, stateStore, appModelManager, + apiService, }; }, [example]); @@ -86,6 +88,7 @@ const Playground: React.FC<{ examples: Example[] }> = ({ examples }) => { registry={registry!} stateStore={stateStore!} appModelManager={appModelManager} + apiService={apiService!} /> )} diff --git a/packages/runtime/package.json b/packages/runtime/package.json index fc04a87b..270418bf 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -37,6 +37,8 @@ "@sinclair/typebox": "^0.20.5", "@vue/reactivity": "^3.1.5", "@vue/shared": "^3.2.20", + "antd": "^4.16.13", + "chakra-react-select": "^1.3.2", "copy-to-clipboard": "^3.3.1", "dayjs": "^1.10.6", "framer-motion": "^4", @@ -44,6 +46,7 @@ "mitt": "^3.0.0", "nanoid": "^3.1.23", "path-to-regexp": "^6.2.0", + "performant-array-to-tree": "^1.9.1", "react": "^17.0.0", "react-dom": "^17.0.0", "react-grid-layout": "^1.3.0", diff --git a/packages/runtime/src/components/_internal/Text.tsx b/packages/runtime/src/components/_internal/Text.tsx index d8964c53..8678421b 100644 --- a/packages/runtime/src/components/_internal/Text.tsx +++ b/packages/runtime/src/components/_internal/Text.tsx @@ -1,5 +1,6 @@ import { css } from '@emotion/react'; import ReactMarkdown from 'react-markdown'; +import { Text as BaseText } from '@chakra-ui/react'; import { Static, Type } from '@sinclair/typebox'; export const TextPropertySchema = Type.Object({ @@ -22,13 +23,13 @@ const Text: React.FC = ({ value, cssStyle }) => { return {value.raw}; } return ( - {value.raw} - + ); }; diff --git a/packages/runtime/src/components/antd/TreeSelect.tsx b/packages/runtime/src/components/antd/TreeSelect.tsx new file mode 100644 index 00000000..0c8a92dc --- /dev/null +++ b/packages/runtime/src/components/antd/TreeSelect.tsx @@ -0,0 +1,174 @@ +import { useState, useEffect } from 'react'; +import { createComponent } from '@meta-ui/core'; +import { Static, Type } from '@sinclair/typebox'; +import { uniq } from 'lodash'; +import { TreeSelect } from 'antd'; +import { Box } from '@chakra-ui/react'; +import { css } from '@emotion/react'; +import { ComponentImplementation } from '../../services/registry'; +import 'antd/lib/select/style/index.css'; +import 'antd/lib/empty/style/index.css'; +import 'antd/lib/tree-select/style/index.css'; + +const StateSchema = Type.Object({ + value: Type.String(), +}); + +const TreeSelectImpl: ComponentImplementation> = ({ + treeData, + placeholder, + mergeState, + customStyle, + multiple, + treeDefaultExpandAll, +}) => { + const [value, setValue] = useState(); + useEffect(() => { + mergeState({ value }); + }, [value]); + + function getAncestors(value: string): string[] { + let ans: string[] = []; + + function traverse(tree: Static, prev: string[] = []) { + tree.forEach(node => { + if (!node.children) return; + const next = prev.concat([node.value]); + if (node.children?.find(child => child.value === value)) { + ans = next; + return; + } + traverse(node.children, next); + }); + } + + traverse(treeData); + return ans; + } + + const onChange = (value: string[]) => { + const valueWithAncestors = value.reduce((res, val) => { + return res.concat([val]).concat(getAncestors(val)); + }, []); + const newValue = uniq(valueWithAncestors); + setValue(newValue); + mergeState({ newValue }); + }; + + return ( + + + + ); +}; + +const TreeDataSchema = Type.Array( + Type.Rec(Self => + Type.Object({ + title: Type.String(), + value: Type.String(), + key: Type.String(), + children: Type.Optional(Type.Array(Self)), + }) + ) +); + +const PropsSchema = Type.Object({ + treeData: TreeDataSchema, + multiple: Type.Boolean(), + placeholder: Type.Optional(Type.String()), + treeDefaultExpandAll: Type.Boolean(), +}); + +const exampleProperties = { + treeData: [ + { + title: 'Development', + value: 'Development', + key: 'Development', + children: [ + { + title: 'FrontEnd', + value: 'FrontEnd', + key: 'FrontEnd', + children: [ + { + title: 'React', + value: 'React', + key: 'React', + }, + { + title: 'Angular', + value: 'Angular', + key: 'Angular', + }, + ], + }, + { + title: 'BackEnd', + value: 'BackEnd', + key: 'BackEnd', + }, + { + title: 'Design', + value: 'Design', + key: 'Design', + children: [ + { + title: 'figma', + value: 'figma', + key: 'figma', + }, + { + title: 'sketch', + value: 'sketch', + key: 'sketch', + }, + ], + }, + ], + }, + { + title: 'Others', + value: 'Others', + key: 'Others', + }, + ], +}; + +export default { + ...createComponent({ + version: 'antd/v1', + metadata: { + name: 'treeSelect', + displayName: 'treeSelect', + description: 'antd treeSelect', + isResizable: true, + isDraggable: true, + exampleProperties, + exampleSize: [4, 1], + }, + spec: { + properties: PropsSchema, + state: StateSchema, + methods: [], + slots: [], + styleSlots: [], + events: [], + }, + }), + impl: TreeSelectImpl, +}; diff --git a/packages/runtime/src/components/chakra-ui/Form/Form.tsx b/packages/runtime/src/components/chakra-ui/Form/Form.tsx index acc88542..dec1074b 100644 --- a/packages/runtime/src/components/chakra-ui/Form/Form.tsx +++ b/packages/runtime/src/components/chakra-ui/Form/Form.tsx @@ -14,6 +14,7 @@ const FormImpl: ComponentImplementation> = ({ slotsMap, callbackMap, services, + customStyle, }) => { const [invalidArray, setInvalidArray] = useState([]); const [isFormInvalid, setIsFormInvalid] = useState(false); @@ -99,19 +100,20 @@ const FormImpl: ComponentImplementation> = ({ callbackMap?.onSubmit(); }; - const style = css` - display: flex; - flex-direction: column; - width: 100%; - height: 100%; - padding: var(--chakra-space-4); - background: white; - border: 1px solid var(--chakra-colors-gray-200); - border-radius: 4px; - `; - return ( - + {hideSubmit ? undefined : (