mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2025-02-23 17:49:49 +08:00
impl slot trait
To implement slot trait, we do the following changes 1. Nestable components should embed <Slot /> inside itself. 2. Attachable components should use the slot trait to indicate which container they will be attached to. During app render, we will resolve the nested relation by traversing the components spec. This process will return top-level components and a resolved slots tree. Top-level components will be rendered directly. The resolved slots tree will be passed into every <Slot />, so the embed <Slot /> can render which has been attached to itself.
This commit is contained in:
parent
df84613296
commit
5850de1b33
@ -26,6 +26,66 @@
|
||||
},
|
||||
traits: [],
|
||||
},
|
||||
{
|
||||
id: "btn",
|
||||
type: "chakra_ui/v1/button",
|
||||
properties: {
|
||||
text: {
|
||||
raw: "in tab1",
|
||||
format: "plain",
|
||||
},
|
||||
},
|
||||
traits: [
|
||||
{
|
||||
type: "core/v1/slot",
|
||||
properties: {
|
||||
container: {
|
||||
id: "tabs",
|
||||
slot: "tab_content_0",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "text",
|
||||
type: "core/v1/text",
|
||||
properties: {
|
||||
value: {
|
||||
raw: "in tab2",
|
||||
format: "plain",
|
||||
},
|
||||
},
|
||||
traits: [
|
||||
{
|
||||
type: "core/v1/slot",
|
||||
properties: {
|
||||
container: {
|
||||
id: "tabs",
|
||||
slot: "tab_content_1",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "nested_tabs",
|
||||
type: "chakra_ui/v1/tabs",
|
||||
properties: {
|
||||
tabNames: ["Tab Three", "Tab Four"],
|
||||
},
|
||||
traits: [
|
||||
{
|
||||
type: "core/v1/slot",
|
||||
properties: {
|
||||
container: {
|
||||
id: "tabs",
|
||||
slot: "tab_content_1",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
@ -1,4 +1,10 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import {
|
||||
Application,
|
||||
createApplication,
|
||||
@ -7,10 +13,13 @@ import {
|
||||
import { merge } from "lodash";
|
||||
import { registry } from "./registry";
|
||||
import { setStore, useStore, emitter } from "./store";
|
||||
import { ContainerPropertySchema } from "./traits/core/slot";
|
||||
import { Static } from "@sinclair/typebox";
|
||||
|
||||
const ImplWrapper: React.FC<{
|
||||
component: RuntimeApplication["spec"]["components"][0];
|
||||
}> = ({ component: c }) => {
|
||||
slotsMap: SlotsMap | undefined;
|
||||
}> = ({ component: c, slotsMap }) => {
|
||||
const Impl = registry.getComponent(
|
||||
c.parsedType.version,
|
||||
c.parsedType.name
|
||||
@ -68,6 +77,7 @@ const ImplWrapper: React.FC<{
|
||||
{...traitsProps}
|
||||
mergeState={mergeState}
|
||||
subscribeMethods={subscribeMethods}
|
||||
slotsMap={slotsMap}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -107,13 +117,63 @@ const DebugEvent: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export type ComponentsMap = Map<string, SlotsMap>;
|
||||
export type SlotsMap = Map<string, Array<React.FC>>;
|
||||
export function resolveNestedComponents(app: RuntimeApplication): {
|
||||
topLevelComponents: RuntimeApplication["spec"]["components"];
|
||||
componentsMap: ComponentsMap;
|
||||
} {
|
||||
const topLevelComponents: RuntimeApplication["spec"]["components"] = [];
|
||||
const componentsMap: ComponentsMap = new Map();
|
||||
|
||||
for (const c of app.spec.components) {
|
||||
const slotTrait = c.traits.find((t) => t.parsedType.name === "slot");
|
||||
if (slotTrait) {
|
||||
const { id, slot } = (
|
||||
slotTrait.properties as {
|
||||
container: Static<typeof ContainerPropertySchema>;
|
||||
}
|
||||
).container;
|
||||
if (!componentsMap.has(id)) {
|
||||
componentsMap.set(id, new Map());
|
||||
}
|
||||
if (!componentsMap.get(id)?.has(slot)) {
|
||||
componentsMap.get(id)?.set(slot, []);
|
||||
}
|
||||
componentsMap
|
||||
.get(id)
|
||||
?.get(slot)
|
||||
?.push(() => (
|
||||
<ImplWrapper component={c} slotsMap={componentsMap.get(c.id)} />
|
||||
));
|
||||
} else {
|
||||
topLevelComponents.push(c);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
topLevelComponents,
|
||||
componentsMap,
|
||||
};
|
||||
}
|
||||
|
||||
const App: React.FC<{ options: Application }> = ({ options }) => {
|
||||
const app = createApplication(options);
|
||||
const { topLevelComponents, componentsMap } = useMemo(
|
||||
() => resolveNestedComponents(app),
|
||||
[app]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
{app.spec.components.map((c) => {
|
||||
return <ImplWrapper key={c.id} component={c} />;
|
||||
{topLevelComponents.map((c) => {
|
||||
return (
|
||||
<ImplWrapper
|
||||
key={c.id}
|
||||
component={c}
|
||||
slotsMap={componentsMap.get(c.id)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<DebugStore />
|
||||
<DebugEvent />
|
||||
|
17
packages/runtime/src/components/_internal/Slot.tsx
Normal file
17
packages/runtime/src/components/_internal/Slot.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
import { SlotsMap } from "../../App";
|
||||
|
||||
const Slot: React.FC<{ slotsMap: SlotsMap | undefined; slot: string }> = ({
|
||||
slotsMap,
|
||||
slot,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{(slotsMap?.get(slot) || []).map((ImplWrapper, idx) => (
|
||||
<ImplWrapper key={idx} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Slot;
|
@ -8,15 +8,16 @@ import {
|
||||
TabPanel,
|
||||
ChakraProvider,
|
||||
} from "@chakra-ui/react";
|
||||
import { ComponentImplementation } from "../../registry";
|
||||
import { Type, Static } from "@sinclair/typebox";
|
||||
import { ComponentImplementation } from "../../registry";
|
||||
import Slot from "../_internal/Slot";
|
||||
|
||||
const Tabs: ComponentImplementation<{
|
||||
tabNames: Static<typeof TabNamesPropertySchema>;
|
||||
initialSelectedTabIndex?: Static<
|
||||
typeof InitialSelectedTabIndexPropertySchema
|
||||
>;
|
||||
}> = ({ tabNames, mergeState, initialSelectedTabIndex }) => {
|
||||
}> = ({ tabNames, mergeState, initialSelectedTabIndex, slotsMap }) => {
|
||||
const [selectedTabIndex, setSelectedTabIndex] = useState(
|
||||
initialSelectedTabIndex ?? 0
|
||||
);
|
||||
@ -37,9 +38,9 @@ const Tabs: ComponentImplementation<{
|
||||
))}
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
{tabNames.map((name, idx) => (
|
||||
{tabNames.map((_, idx) => (
|
||||
<TabPanel key={idx}>
|
||||
<p>{name}</p>
|
||||
<Slot slotsMap={slotsMap} slot={`tab_content_${idx}`} />
|
||||
</TabPanel>
|
||||
))}
|
||||
</TabPanels>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import { RuntimeComponent, RuntimeTrait } from "@meta-ui/core";
|
||||
import { setStore } from "./store";
|
||||
import { SlotsMap } from "./App";
|
||||
// components
|
||||
import PlainButton from "./components/plain/Button";
|
||||
import CoreText from "./components/core/Text";
|
||||
@ -9,6 +10,7 @@ import ChakraUITabs from "./components/chakra-ui/Tabs";
|
||||
// traits
|
||||
import CoreState from "./traits/core/state";
|
||||
import CoreEvent from "./traits/core/event";
|
||||
import CoreSlot from "./traits/core/slot";
|
||||
|
||||
type ImplementedRuntimeComponent = RuntimeComponent & {
|
||||
impl: ComponentImplementation;
|
||||
@ -29,6 +31,7 @@ export type ComponentImplementation<T = any> = React.FC<
|
||||
T & {
|
||||
mergeState: MergeState;
|
||||
subscribeMethods: SubscribeMethods;
|
||||
slotsMap: SlotsMap | undefined;
|
||||
}
|
||||
>;
|
||||
|
||||
@ -93,3 +96,4 @@ registry.registerComponent(ChakraUITabs);
|
||||
|
||||
registry.registerTrait(CoreState);
|
||||
registry.registerTrait(CoreEvent);
|
||||
registry.registerTrait(CoreSlot);
|
||||
|
28
packages/runtime/src/traits/core/slot.ts
Normal file
28
packages/runtime/src/traits/core/slot.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { createTrait } from "@meta-ui/core";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
|
||||
export const ContainerPropertySchema = Type.Object({
|
||||
id: Type.String(),
|
||||
slot: Type.String(),
|
||||
});
|
||||
|
||||
export default {
|
||||
...createTrait({
|
||||
version: "core/v1",
|
||||
metadata: {
|
||||
name: "slot",
|
||||
description: "nested components by slots",
|
||||
},
|
||||
spec: {
|
||||
properties: [
|
||||
{
|
||||
name: "container",
|
||||
...ContainerPropertySchema,
|
||||
},
|
||||
],
|
||||
state: {},
|
||||
methods: [],
|
||||
},
|
||||
}),
|
||||
impl: () => {},
|
||||
};
|
Loading…
Reference in New Issue
Block a user