namds/refactor-base-layout #8

Merged
yen.nguyen merged 27 commits from namds/refactor-base-layout into main 2025-12-15 07:44:38 +00:00
9 changed files with 307 additions and 20 deletions
Showing only changes of commit b76719fb32 - Show all commits

View File

@ -55,6 +55,138 @@
"objectFit": "contain"
}
}
},
{
"VtMegaMenu": {
"config": {
"navLabel": {
"text": "Shop",
"className": "font-bold text-[1rem] text-[#003F31] flex items-center mr-8 gap-1 hover:text-[#009b93]",
"isShowArrow": true
}
}
}
},
{
"Dropdown": {
"config": {
"trigger": {
"text": "Über Uns",
"className": "font-bold text-[1rem] text-[#003F31] flex items-center mr-8 gap-1 hover:text-[#009b93]",
"isShowArrow": true
},
"items": [
{
"text": "Unser Unternehmen",
"href": "/"
},
{
"text": "Loren ipsum",
"href": "/"
},
{
"text": "Not a Link"
}
]
}
}
},
{
"Dropdown": {
"config": {
"trigger": {
"text": "Über unsere Produkte",
"className": "font-bold text-[1rem] text-[#003F31] flex items-center mr-8 gap-1 hover:text-[#009b93]",
"isShowArrow": true
},
"items": [
{
"text": "Unser Unternehmen",
"href": "/"
},
{
"text": "Loren ipsum",
"href": "/"
},
{
"text": "Not a Link"
}
]
}
}
},
{
"Link": {
"config": {
"label": "Rezepte",
"href": "/",
"className": "font-bold text-[1rem] text-[#003F31] flex items-center mr-8 gap-1 hover:text-[#009b93]"
}
}
},
{
"Link": {
"config": {
"label": "Triff Harry Kane",
"href": "/",
"className": "font-bold text-[1rem] text-[#003F31] flex items-center gap-1 hover:text-[#009b93]"
}
}
}
],
"right": [
{
"Dropdown": {
"config": {
"trigger": {
"icon": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/Flag_of_Germany.svg/1200px-Flag_of_Germany.svg.png",
"text": "Germany (EUR)",
"className": "font-bold text-[1rem] text-[#003F31] flex items-center gap-1 hover:text-[#009b93]",
"isShowArrow": true
},
"items": [
{
"icon": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/Flag_of_Germany.svg/1200px-Flag_of_Germany.svg.png",
"text": "Germany (EUR)",
"href": "/"
},
{
"icon": "https://upload.wikimedia.org/wikipedia/commons/2/20/Flag_of_the_Netherlands.svg",
"text": "Netherlands (EUR)",
"href": "/"
},
{
"icon": "https://upload.wikimedia.org/wikipedia/commons/8/88/Flag_of_Australia_%28converted%29.svg",
"text": "Australia (AUD)",
"href": "/"
}
]
}
}
},
{
"IconButton": {
"config": {
"variant": "search",
"className": "shadow-none"
}
}
},
{
"IconButton": {
"config": {
"variant": "user",
"className": "shadow-none"
}
}
},
{
"VtCartButton": {
"config": {
"variant": "button",
"className": "shadow-none"
}
}
}
]
}

View File

@ -1,13 +1,38 @@
import LocalizedClientLink from "@modules/common/components/localized-client-link"
import { LayoutComponentDefinition, LayoutContext } from "vibentec/component-map";
import { clx } from "@medusajs/ui"
import { Suspense } from "react";
import CartButton from "@modules/layout/components/cart-button";
export const VtCartButton = ({ nodes, context }: { nodes: LayoutComponentDefinition; context: LayoutContext }) => {
import {
LayoutComponentDefinition,
LayoutContext,
} from "vibentec/component-map"
import { clx, IconButton } from "@medusajs/ui"
import { Suspense } from "react"
import CartButton from "@modules/layout/components/cart-button"
import { ShoppingBag } from "@medusajs/icons"
const CartIconButtonComponent = ({ className }: { className?: string }) => {
return (
<IconButton className={className}>
<ShoppingBag />
</IconButton>
)
}
export const VtCartButton = ({
nodes,
context,
}: {
nodes: LayoutComponentDefinition
context: LayoutContext
}) => {
const props = nodes.config ?? {}
const className = clx("hover:text-ui-fg-base flex gap-2", props.className)
yen.nguyen marked this conversation as resolved
Review

Need data binding improvement

Need data binding improvement
Review

this part is component of medusajs we just clone it follow folder templates/nav/index. the data binding already implement below your highlight code, this is only the fallback when having any error of data response so will return this component

this part is component of medusajs we just clone it follow folder templates/nav/index. the data binding already implement below your highlight code, this is only the fallback when having any error of data response so will return this component
const variants = {
link: <CartButton />,
button: <CartIconButtonComponent className={className} />,
}
if (!props.variant) return null
const fallBackComp = variants[props.variant as keyof typeof variants]
return (
<Suspense
fallback={
@ -20,9 +45,9 @@ export const VtCartButton = ({ nodes, context }: { nodes: LayoutComponentDefinit
</LocalizedClientLink>
}
>
<CartButton />
{fallBackComp}
</Suspense>
)
}
export default VtCartButton
export default VtCartButton

View File

@ -0,0 +1,70 @@
"use client"
import { DropdownMenu } from "@medusajs/ui"
import LocalizedClientLink from "@modules/common/components/localized-client-link"
import ChevronDown from "@modules/common/icons/chevron-down"
import {
LayoutComponentDefinition,
LayoutContext,
} from "@vibentec/component-map"
export default function VtDropdown({
nodes,
context,
}: {
nodes: LayoutComponentDefinition
context: LayoutContext
}) {
const props = nodes.config ?? {}
if (!props.trigger.text || props.items.length === 0) {
return null
}
return (
<DropdownMenu>
<DropdownMenu.Trigger
className={props.trigger.className + " flex items-center gap-1"}
>
{props.trigger.icon && (
<img
src={props.trigger.icon}
alt={props.trigger.text}
className="w-5 h-5 rounded-[50%] mr-3"
/>
)}
{props.trigger.text} {props.trigger.isShowArrow && <ChevronDown />}
</DropdownMenu.Trigger>
<DropdownMenu.Content>
{props.items.map(
(
item: {
text: string
className?: string
href?: string
icon?: string
},
index: number
) => (
<DropdownMenu.Item
key={item.text + index}
className={item.className || ""}
>
{item.icon && (
<img
src={item.icon}
alt={item.text}
className="w-5 h-5 rounded-[50%] mr-3"
/>
)}
{item.href ? (
<LocalizedClientLink href={item.href}>
{item.text}
</LocalizedClientLink>
) : (
item.text
)}
</DropdownMenu.Item>
)
)}
</DropdownMenu.Content>
</DropdownMenu>
)
}

View File

@ -1,19 +1,34 @@
import { LayoutComponentDefinition, LayoutContext } from "vibentec/component-map";
import React, { Suspense } from "react";
import SkeletonMegaMenu from "@modules/skeletons/components/vt-skeleton-mega-menu";
import MegaMenuWrapper from "@modules/layout/components/vt-mega-menu/mega-menu-wrapper";
import {
LayoutComponentDefinition,
LayoutContext,
} from "vibentec/component-map"
import React, { Suspense } from "react"
import SkeletonMegaMenu from "@modules/skeletons/components/vt-skeleton-mega-menu"
import MegaMenuWrapper from "@modules/layout/components/vt-mega-menu/mega-menu-wrapper"
export default function VtMegaMenu({ nodes, context }: { nodes: LayoutComponentDefinition; context: LayoutContext }) {
interface MegaMenuProps {
navLabel: {
text: string
className?: string
}
}
export default function VtMegaMenu({
nodes,
context,
}: {
nodes: LayoutComponentDefinition
context: LayoutContext
}) {
const { navLabel } = nodes.config as MegaMenuProps ?? {}
return (
<nav>
<ul className="space-x-4 hidden small:flex">
<li>
<Suspense fallback={<SkeletonMegaMenu />}>
<MegaMenuWrapper />
<MegaMenuWrapper navLabel={navLabel} />
</Suspense>
</li>
</ul>
</nav>
)
}

View File

@ -1,10 +1,12 @@
import { listCategories } from "@lib/data/categories"
import MegaMenu from "./mega-menu"
export async function MegaMenuWrapper() {
export async function MegaMenuWrapper({ navLabel }: { navLabel: { text: string; className?: string } }) {
const categories = await listCategories().catch(() => [])
return <MegaMenu categories={categories} />
return <MegaMenu navLabel={navLabel} categories={categories} />
}
export default MegaMenuWrapper

View File

@ -3,12 +3,16 @@
import { HttpTypes } from "@medusajs/types"
import { clx } from "@medusajs/ui"
import LocalizedClientLink from "@modules/common/components/localized-client-link"
import ChevronDown from "@modules/common/icons/chevron-down"
import { usePathname } from "next/navigation"
import { useEffect, useState } from "react"
const MegaMenu = ({
navLabel,
categories,
}: {
navLabel: { text: string; className?: string, isShowArrow?: boolean },
categories: HttpTypes.StoreProductCategory[]
}) => {
const [isHovered, setIsHovered] = useState(false)
@ -81,10 +85,13 @@ const MegaMenu = ({
className="z-50"
>
<LocalizedClientLink
className="hover:text-ui-fg-base hover:bg-neutral-100 rounded-full px-3 py-2"
className={clx(
"hover:text-ui-fg-base hover:bg-neutral-100 rounded-full px-3 py-2",
navLabel.className
)}
href="/store"
>
Products
{navLabel.text} {navLabel.isShowArrow && <ChevronDown />}
</LocalizedClientLink>
{isHovered && (
<div className="absolute top-full left-0 right-0 flex gap-32 py-10 px-20 bg-white border-b border-neutral-200 ">

View File

@ -0,0 +1,28 @@
import { IconButton } from "@medusajs/ui"
import { MagnifyingGlass, User, ShoppingBag } from "@medusajs/icons"
import {
LayoutComponentDefinition,
LayoutContext,
} from "@vibentec/component-map"
export default function VtIconButton({
nodes,
context,
}: {
nodes: LayoutComponentDefinition
context: LayoutContext
}) {
const props = nodes.config || {}
const variantsIcon = {
search: MagnifyingGlass,
user: User,
cart: ShoppingBag
}
if (!props.variant) return null
const Icon = variantsIcon[props.variant as keyof typeof variantsIcon]
return (
<IconButton className={props?.className ?? ""}>
<Icon />
</IconButton>
)
}

View File

@ -51,7 +51,8 @@
@layer components {
.content-container {
@apply max-w-[1440px] w-full mx-auto px-6;
/* @apply max-w-[1440px] w-full mx-auto px-6; */
@apply w-full mx-auto px-6;
}
.contrast-btn {
@ -110,3 +111,6 @@
@apply text-[32px] leading-[44px] font-semibold;
}
}
[data-radix-popper-content-wrapper]{
z-index: 51 !important;
}

View File

@ -13,6 +13,8 @@ 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/components/vt-dropdown"
import VtIconButton from "@modules/layout/templates/vt-icon-button"
type ComponentConfig = Record<string, any>;
@ -54,9 +56,11 @@ export const componentMap: Record<string, ComponentRenderer> = {
Banner: nodesContextRenderer(Banner),
HomeButton: nodesContextRenderer(HomeButton),
AccountButton: nodesContextRenderer(AccountButton),
IconButton: nodesContextRenderer(VtIconButton),
VtCartButton: nodesContextRenderer(VtCartButton),
Link: nodesContextRenderer(VtLink),
Image: nodesContextRenderer(VtImage),
Dropdown: nodesContextRenderer(VtDropdown),
CartMismatchBanner: configOnly(CartMismatchBanner),
FreeShippingPriceNudge: configOnly(FreeShippingPriceNudge),
PropsChildren: {