refactor: remove singular component and replace with wrapper component

This commit is contained in:
Nam Doan 2025-12-10 14:09:46 +07:00
parent baaa0e9c62
commit fe881d5aed
6 changed files with 133 additions and 93 deletions

View File

@ -41,11 +41,11 @@
}
},
{
"Button": {
"Link": {
"config": {
"label": "Mehr Info",
"href": "/",
"className": "text-[13px] w-fit text-white px-3 flex items-center bg-[#112638] gap-1 "
"className": "text-[13px] rounded-md hover:text-white w-[80px] text-white text-center flex items-center bg-[#112638] flex justify-center h-[28px] "
}
}
}
@ -89,7 +89,7 @@
"className": "h-24 bg-[white] text-[#11314E] gap-12 pl-16",
"left": [
{
"Image": {
"Logo": {
"config": {
"src": "/VibentecIT-logo.svg",
"alt": "MyShop",
@ -202,37 +202,18 @@
"rightClassName": "flex mt-[160px]",
"left": [
{
"Image": {
"config": {
"src": "/VibentecIT-logo.svg",
"alt": "MyShop",
"className": "h-[100px] w-[320px]",
"objectFit": "contain"
}
}
},
{
"VtMenuItem": {
"VtFooterHero": {
"config": {
"logoClassName": "h-[100px] w-[255px]",
"logoSrc": "/VibentecIT-logo.svg",
"logoAlt": "Vibentec IT",
"title": "Der Wegbereiter für innovative IT-Lösungen",
"className": "flex flex-col gap-y-2 ml-10 w-[320px] text-[24px] font-semibold text-[#11314E]",
"itemClassName": "text-ui-fg-subtle txt-small",
"items": [
{
"text": "Tauchen Sie ein in eine Welt modernster Technologien, zuverlässiger Support und proaktiver Innovation gemeinsam gestalten wir die digitale Zukunft Ihres Unternehmens.",
"href": "/"
}
]
}
}
},
{
"Button": {
"config": {
"label": "Kontaktieren Sie uns",
"className": "bg-black text-white items-center flex gap-2 ml-10 px-4 mt-[24px] py-5 rounded-md w-fit text-[14px]",
"icon": "ChevronRightMini",
"iconClassName": "order-1"
"description": "Tauchen Sie ein in eine Welt modernster Technologien, zuverlässiger Support und proaktiver Innovation gemeinsam gestalten wir die digitale Zukunft Ihres Unternehmens.",
"cta": { "label": "Kontaktieren Sie uns", "href": "/" },
"className": "",
"ctaClassName": "ml-8",
"titleClassName": "ml-8",
"descriptionClassName": "ml-8 w-[320px]"
}
}
}
@ -342,42 +323,13 @@
"Footer": {
"config": {
"className": "content-container h-[128px] w-full text text-[#11314E] flex items-center justify-between px-20 mt-2",
"leftClassName": "w-full",
"left": [
{
"Text": {
"VtFooterBottom": {
"config": {
"label": "©2025 Vibentec IT. All rights reserved",
"className": "text-[14px] font-[400] pt-2"
}
}
}
],
"center": [],
"right": [
{
"Button": {
"config": {
"href": "/",
"icon": "Mastercard",
"className": "shadow-none"
}
}
},
{
"Button": {
"config": {
"href": "/",
"icon": "PayPal",
"className": "shadow-none"
}
}
},
{
"Button": {
"config": {
"href": "/",
"icon": "Visa",
"className": "shadow-none"
"text": "©2025 Vibentec IT. All rights reserved",
"icons": ["Mastercard", "PayPal", "Visa"]
}
}
}

View File

@ -24,21 +24,21 @@ export default async function VtFooter({
return (
<footer className={props?.className ?? ""}>
{props?.left && (
<div className={clx("flex gap-x-4 h-full", props?.leftClassName)}>
{props.left && (
<DynamicLayoutRenderer nodes={props.left} context={context} />
)}
<DynamicLayoutRenderer nodes={props?.left} context={context} />
</div>
)}
{props?.center && (
<div className={clx("flex gap-x-4 h-full", props?.centerClassName)}>
{props.center && (
<DynamicLayoutRenderer nodes={props.center} context={context} />
)}
<DynamicLayoutRenderer nodes={props?.center} context={context} />
</div>
)}
{props?.right && (
<div className={clx("flex gap-x-4 h-full", props?.rightClassName)}>
{props.right && (
<DynamicLayoutRenderer nodes={props.right} context={context} />
)}
<DynamicLayoutRenderer nodes={props?.right} context={context} />
</div>
)}
</footer>
)
}

View File

@ -0,0 +1,31 @@
import * as MedusaIcons from "@medusajs/icons"
import { clx } from "@medusajs/ui"
import { LayoutComponentDefinition, LayoutContext } from "@vibentec/component-map"
export default function VtFooterBottom({
nodes,
context,
}: {
nodes: LayoutComponentDefinition
context: LayoutContext
}) {
const props = (nodes.config) ?? {}
const renderIcon = (name: string, idx: number) => {
const IconComp = (MedusaIcons as Record<string, any>)[name]
if (!IconComp) return null
return (
<IconComp key={`${name}-${idx}`} className="shadow-none" />
)
}
return (
<div className={clx("flex w-full items-center justify-between", props.className)}>
<span className={clx("text-[14px] font-[400] pt-2", props.textClassName)}>{props.text}</span>
<div className={clx("flex items-center gap-2", props.iconsClassName)}>
{(props.icons ?? []).map(renderIcon)}
</div>
</div>
)
}

View File

@ -0,0 +1,62 @@
import { clx } from "@medusajs/ui"
import { ChevronRightMini } from "@medusajs/icons"
import { LayoutComponentDefinition, LayoutContext } from "@vibentec/component-map"
import LocalizedClientLink from "@modules/common/components/localized-client-link"
interface VtFooterHeroConfig {
logoSrc: string
logoAlt?: string
title: string
description: string
cta?: { label: string; href: string }
className?: string
logoClassName?: string
titleClassName?: string
descriptionClassName?: string
ctaClassName?: string
}
export default function VtFooterHero({
nodes,
context,
}: {
nodes: LayoutComponentDefinition
context: LayoutContext
}) {
const props = (nodes.config as VtFooterHeroConfig) ?? {}
return (
<div className={clx("flex flex-col items-start", props.className)}>
{props.logoSrc && (
<img
src={props.logoSrc}
alt={props.logoAlt ?? "logo"}
className={clx("object-contain", props.logoClassName)}
/>
)}
{props.title && (
<h2 className={clx("mt-4 w-[320px] text-[24px] font-semibold text-[#11314E]", props.titleClassName)}>
{props.title}
</h2>
)}
{props.description && (
<p className={clx("mt-2 text-ui-fg-subtle txt-small", props.descriptionClassName)}>
{props.description}
</p>
)}
{props.cta && (
<LocalizedClientLink
href={props.cta.href}
className={clx(
"bg-black text-white items-center flex gap-2 px-4 mt-[24px] py-2 rounded-md w-fit text-[14px]",
props.ctaClassName
)}
>
<span>{props.cta.label}</span>
<ChevronRightMini className="order-1" />
</LocalizedClientLink>
)}
</div>
)
}

View File

@ -3,15 +3,13 @@ import {
LayoutComponentDefinition,
LayoutContext,
} from "@vibentec/component-map"
import Image from "next/image"
export interface VtImageConfig {
src: string
alt: string
className?: string,
objectFit?: string,
}
export default function VtImage({
export default function VtLogo({
nodes,
context,
}: {
@ -21,7 +19,7 @@ export default function VtImage({
const props = (nodes.config as VtImageConfig) ?? {}
return (
<div className={clx("relative", props.className)}>
<img src={props.src} alt={props.alt} className={clx("w-full h-full object-cover", props.objectFit)} />
<img src={props.src} alt={props.alt} className={clx("w-full h-full", props.objectFit)} />
</div>
)
}

View File

@ -12,17 +12,16 @@ import Banner from "@modules/layout/templates/vt-banner"
import VtMegaMenu from "@modules/layout/components/vt-mega-menu"
import VtLink from "@modules/layout/components/vt-linkbutton"
import VtSideMenu from "@modules/layout/components/vt-sidemenu"
import VtImage from "@modules/layout/templates/vt-image"
import VtDropdown from "@modules/layout/templates/vt-dropdown"
import VtButton from "@modules/layout/templates/vt-button"
import VtSearchInput from "@modules/layout/templates/vt-search-input"
import VtSection from "@modules/layout/templates/vt-section"
import VtText from "@modules/layout/templates/vt-text"
import VtInput from "@modules/layout/templates/vt-input"
import VtCurrencySelect from "@modules/layout/templates/vt-currency-select"
import VtMenuItem from "@modules/layout/templates/vt-menu-item"
import VtCountryCodeSelect from "@modules/layout/templates/vt-country-select/server"
import VtSocialLinks from "@modules/layout/templates/vt-social-link"
import VtFooterHero from "@modules/layout/templates/vt-footer/vt-footer-hero"
import VtFooterBottom from "@modules/layout/templates/vt-footer/vt-footer-bottom"
import VtLogo from "@modules/layout/templates/vt-logo"
type ComponentConfig = Record<string, any>;
@ -63,18 +62,14 @@ export const componentMap: Record<string, ComponentRenderer> = {
VtSideMenu: nodesContextRenderer(VtSideMenu),
Banner: nodesContextRenderer(Banner),
HomeButton: nodesContextRenderer(HomeButton),
Logo: nodesContextRenderer(VtLogo),
AccountButton: nodesContextRenderer(AccountButton),
Button: nodesContextRenderer(VtButton),
Section: nodesContextRenderer(VtSection),
SearchInput: nodesContextRenderer(VtSearchInput),
VtCartButton: nodesContextRenderer(VtCartButton),
VtCurrencySelect: nodesContextRenderer(VtCurrencySelect),
VtCountryCodeSelect: nodesContextRenderer(VtCountryCodeSelect),
VtSocialLinks: nodesContextRenderer(VtSocialLinks),
Link: nodesContextRenderer(VtLink),
Input: nodesContextRenderer(VtInput),
Image: nodesContextRenderer(VtImage),
Text: nodesContextRenderer(VtText),
Dropdown: nodesContextRenderer(VtDropdown),
VtMenuItem: nodesContextRenderer(VtMenuItem),
CartMismatchBanner: configOnly(CartMismatchBanner),
@ -82,6 +77,8 @@ export const componentMap: Record<string, ComponentRenderer> = {
PropsChildren: {
render: (_props, ctx) => ctx.contentChildren, // PageLayout's props.children
},
VtFooterHero: nodesContextRenderer(VtFooterHero),
VtFooterBottom: nodesContextRenderer(VtFooterBottom),
Footer: nodesContextRenderer(VtFooter)
}