From b76719fb32c7faf9c514b0cdbb37d3d75a2c2c16 Mon Sep 17 00:00:00 2001 From: Nam Doan Date: Thu, 27 Nov 2025 14:02:13 +0700 Subject: [PATCH] feat: create components and map with data json file of 3bear design --- config/nam.3bear.design.json | 132 ++++++++++++++++++ .../layout/components/vt-cartbutton/index.tsx | 41 ++++-- .../layout/components/vt-dropdown/index.tsx | 70 ++++++++++ .../layout/components/vt-mega-menu/index.tsx | 29 +++- .../vt-mega-menu/mega-menu-wrapper.tsx | 6 +- .../components/vt-mega-menu/mega-menu.tsx | 11 +- .../layout/templates/vt-icon-button/index.tsx | 28 ++++ src/styles/globals.css | 6 +- src/vibentec/component-map.tsx | 4 + 9 files changed, 307 insertions(+), 20 deletions(-) create mode 100644 src/modules/layout/components/vt-dropdown/index.tsx create mode 100644 src/modules/layout/templates/vt-icon-button/index.tsx diff --git a/config/nam.3bear.design.json b/config/nam.3bear.design.json index 3f59388..c017b62 100644 --- a/config/nam.3bear.design.json +++ b/config/nam.3bear.design.json @@ -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" + } + } } ] } diff --git a/src/modules/layout/components/vt-cartbutton/index.tsx b/src/modules/layout/components/vt-cartbutton/index.tsx index d9ab53f..1893c48 100644 --- a/src/modules/layout/components/vt-cartbutton/index.tsx +++ b/src/modules/layout/components/vt-cartbutton/index.tsx @@ -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 ( + + + + ) +} + +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) + const variants = { + link: , + button: , + } + if (!props.variant) return null + const fallBackComp = variants[props.variant as keyof typeof variants] + return ( } > - + {fallBackComp} ) } -export default VtCartButton \ No newline at end of file +export default VtCartButton diff --git a/src/modules/layout/components/vt-dropdown/index.tsx b/src/modules/layout/components/vt-dropdown/index.tsx new file mode 100644 index 0000000..e15f8d6 --- /dev/null +++ b/src/modules/layout/components/vt-dropdown/index.tsx @@ -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 ( + + + {props.trigger.icon && ( + {props.trigger.text} + )} + {props.trigger.text} {props.trigger.isShowArrow && } + + + {props.items.map( + ( + item: { + text: string + className?: string + href?: string + icon?: string + }, + index: number + ) => ( + + {item.icon && ( + {item.text} + )} + {item.href ? ( + + {item.text} + + ) : ( + item.text + )} + + ) + )} + + + ) +} diff --git a/src/modules/layout/components/vt-mega-menu/index.tsx b/src/modules/layout/components/vt-mega-menu/index.tsx index a8f84db..8d55318 100644 --- a/src/modules/layout/components/vt-mega-menu/index.tsx +++ b/src/modules/layout/components/vt-mega-menu/index.tsx @@ -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 ( ) } - diff --git a/src/modules/layout/components/vt-mega-menu/mega-menu-wrapper.tsx b/src/modules/layout/components/vt-mega-menu/mega-menu-wrapper.tsx index 328e9c7..a0d66dc 100644 --- a/src/modules/layout/components/vt-mega-menu/mega-menu-wrapper.tsx +++ b/src/modules/layout/components/vt-mega-menu/mega-menu-wrapper.tsx @@ -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 + return } export default MegaMenuWrapper diff --git a/src/modules/layout/components/vt-mega-menu/mega-menu.tsx b/src/modules/layout/components/vt-mega-menu/mega-menu.tsx index 50e2dd1..b6cdd75 100644 --- a/src/modules/layout/components/vt-mega-menu/mega-menu.tsx +++ b/src/modules/layout/components/vt-mega-menu/mega-menu.tsx @@ -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" > - Products + {navLabel.text} {navLabel.isShowArrow && } {isHovered && (
diff --git a/src/modules/layout/templates/vt-icon-button/index.tsx b/src/modules/layout/templates/vt-icon-button/index.tsx new file mode 100644 index 0000000..93b26d0 --- /dev/null +++ b/src/modules/layout/templates/vt-icon-button/index.tsx @@ -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 ( + + + + ) +} diff --git a/src/styles/globals.css b/src/styles/globals.css index 1ebe912..35c79e3 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -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; +} \ No newline at end of file diff --git a/src/vibentec/component-map.tsx b/src/vibentec/component-map.tsx index d5a324e..1199f3c 100644 --- a/src/vibentec/component-map.tsx +++ b/src/vibentec/component-map.tsx @@ -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; @@ -54,9 +56,11 @@ export const componentMap: Record = { 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: {