feat(layout-system basics): Added component-map, component-props, configloader and renderer.
Added vt-template for nav and poc example footer. Added ste.medusa-starter.design.json. Probable starting point (main)/layout.tsx refactored to use dynamic layout renderer instead of hardcoded template.
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import CartMismatchBanner from "@modules/layout/components/cart-mismatch-banner"
|
||||
import FreeShippingPriceNudge from "@modules/shipping/components/free-shipping-price-nudge"
|
||||
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
||||
import CartButton from "@modules/layout/components/cart-button"
|
||||
import { DynamicLayoutRenderer } from "./renderer"
|
||||
import React from "react"
|
||||
import VtNav from "@modules/layout/templates/vt-nav"
|
||||
import VtFooter from "@modules/layout/templates/vt-footer"
|
||||
|
||||
export interface LayoutComponentDefinition {
|
||||
props?: Record<string, any>
|
||||
children?: LayoutComponentNode[]
|
||||
}
|
||||
|
||||
//maps key = componentName to value = props + children
|
||||
export type LayoutComponentNode = Record<string, LayoutComponentDefinition>
|
||||
|
||||
export interface LayoutContext {
|
||||
customer: any;
|
||||
cart: any;
|
||||
shippingOptions: any[];
|
||||
contentChildren: React.ReactNode;
|
||||
}
|
||||
|
||||
export type ComponentRenderer = {
|
||||
render: (entry: LayoutComponentDefinition, ctx: LayoutContext) => React.ReactNode
|
||||
}
|
||||
|
||||
// Utility, wenn eine Komponente nur props hat und keine children
|
||||
const simple = (Component: React.ComponentType<any>): ComponentRenderer => ({
|
||||
render: (entry) => <Component {...entry.props} />
|
||||
})
|
||||
|
||||
// Helper für Kinder-Rendering
|
||||
const renderChildren = (entry: LayoutComponentDefinition, ctx: LayoutContext) =>
|
||||
entry.children ? <DynamicLayoutRenderer nodes={entry.children} context={ctx} /> : null
|
||||
|
||||
|
||||
// Component Map
|
||||
export const componentMap: Record<string, ComponentRenderer> = {
|
||||
Nav: {
|
||||
render: (entry: any, ctx: LayoutContext) => ( <VtNav nodes={entry.children} context={ctx} /> ),
|
||||
},
|
||||
Div: {
|
||||
render: (entry: any, ctx: LayoutContext) => (
|
||||
<div {...entry.props}>
|
||||
{entry.children ? <DynamicLayoutRenderer nodes={entry.children} context={ctx} /> : null}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
LocalizedClientLink: {
|
||||
render: (entry: any) => (
|
||||
<LocalizedClientLink {...entry.props}>{entry.props.label}</LocalizedClientLink>
|
||||
)
|
||||
},
|
||||
CartButton: simple(CartButton),
|
||||
Suspense: {
|
||||
render: (entry: any, ctx: LayoutContext) => (
|
||||
<React.Suspense
|
||||
fallback={
|
||||
entry.props?.fallback ? (
|
||||
<DynamicLayoutRenderer nodes={entry.props.fallback} context={ctx} />
|
||||
) : null
|
||||
}
|
||||
>
|
||||
{entry.children ? <DynamicLayoutRenderer nodes={entry.children} context={ctx} /> : null}
|
||||
</React.Suspense>
|
||||
),
|
||||
},
|
||||
CartMismatchBanner: simple(CartMismatchBanner),
|
||||
FreeShippingPriceNudge: simple(FreeShippingPriceNudge),
|
||||
PropsChildren: {
|
||||
render: (_props, ctx) => ctx.contentChildren, // PageLayout's props.children
|
||||
},
|
||||
Footer: simple(VtFooter),
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export interface FooterProps { copyrightText?: string }
|
||||
@@ -0,0 +1,10 @@
|
||||
import fs from "fs"
|
||||
import path from "path"
|
||||
|
||||
const fileName = "ste.medusa-starter.design.json";
|
||||
|
||||
export async function loadDesignConfig() {
|
||||
const filePath = path.join(process.cwd(), "config", fileName)
|
||||
const fileData = await fs.promises.readFile(filePath, "utf-8")
|
||||
return JSON.parse(fileData)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react"
|
||||
import { LayoutComponentNode, LayoutContext, componentMap } from "./component-map"
|
||||
|
||||
export interface DynamicLayoutRendererProps {
|
||||
nodes: LayoutComponentNode[]
|
||||
context: LayoutContext
|
||||
}
|
||||
|
||||
export function DynamicLayoutRenderer({ nodes, context } : DynamicLayoutRendererProps) {
|
||||
return nodes.map((entry, index) => {
|
||||
const [key, value] = Object.entries(entry)[0] as [string, any]
|
||||
const component = componentMap[key]
|
||||
if (!component) return null
|
||||
return <React.Fragment key={index}>{component.render(value, context)}</React.Fragment>
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user