feat: create navigation menu for 3bear design
This commit is contained in:
parent
f21a4251e3
commit
f35fd8b09a
|
|
@ -24,7 +24,7 @@
|
|||
"className": ""
|
||||
}
|
||||
],
|
||||
"className": "bg-[#009b93] text-white"
|
||||
"className": "sticky top-0 z-20 bg-[#009b93] text-white"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -55,79 +55,195 @@
|
|||
},
|
||||
"children": [
|
||||
{
|
||||
"DropdownMenus": {
|
||||
"NavMenu": {
|
||||
"props": {
|
||||
"label": "Shop",
|
||||
"className": "hover:text-ui-fg-base flex items-center gap-2",
|
||||
"className": "",
|
||||
"data-testid": "nav-categories-link",
|
||||
"isShowArrow": true
|
||||
},
|
||||
"children": [
|
||||
"menuItems": [
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "All Categories",
|
||||
"className": "hover:text-ui-fg-base",
|
||||
"data-testid": "nav-all-categories-link"
|
||||
}
|
||||
}
|
||||
"title": {
|
||||
"text": "Categories",
|
||||
"className": "text-[#003F31] text-xl font-bold"
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"label": "Overnight Oats",
|
||||
"href": "/categories/overnight-oats",
|
||||
"className": "text-red-500"
|
||||
},
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "New Arrivals",
|
||||
"className": "hover:text-ui-fg-base flex items-center gap-2",
|
||||
"data-testid": "nav-new-arrivals-link"
|
||||
}
|
||||
}
|
||||
"label": "Porridge",
|
||||
"href": "/categories/porridge"
|
||||
},
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "Best Sellers",
|
||||
"className": "hover:text-ui-fg-base flex items-center gap-2",
|
||||
"data-testid": "nav-best-sellers-link"
|
||||
"label": "Cereals",
|
||||
"href": "/categories/cereals"
|
||||
},
|
||||
{
|
||||
"label": "Granola",
|
||||
"href": "/categories/granola"
|
||||
},
|
||||
{
|
||||
"label": "Glasses & Bowls",
|
||||
"href": "/categories/glasses-bowls"
|
||||
},
|
||||
{
|
||||
"label": "Oat Bars",
|
||||
"href": "/categories/oat-bars"
|
||||
},
|
||||
{
|
||||
"label": "Nut butters",
|
||||
"href": "/categories/nut-butters"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": {
|
||||
"text": "Specials",
|
||||
"className": "text-[#003F31] text-xl font-bold"
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"label": "Advent calendar ✨",
|
||||
"href": "/collections/advent-calendar"
|
||||
},
|
||||
{
|
||||
"label": "Saver subscription",
|
||||
"href": "/collections/saver-subscription"
|
||||
},
|
||||
{
|
||||
"label": "bestseller",
|
||||
"href": "/collections/bestseller"
|
||||
},
|
||||
{
|
||||
"label": "New 🔥",
|
||||
"href": "/collections/new"
|
||||
},
|
||||
{
|
||||
"label": "Bluey Kidsrange",
|
||||
"href": "/collections/bluey-kidsrange"
|
||||
},
|
||||
{
|
||||
"label": "Value sets",
|
||||
"href": "/collections/value-sets"
|
||||
},
|
||||
{
|
||||
"label": "Sale",
|
||||
"href": "/collections/sale"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": {
|
||||
"text": "All products",
|
||||
"className": "text-[#003F31] text-xl font-bold"
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"label": "Shop all",
|
||||
"href": "/store"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenus": {
|
||||
"NavMenu": {
|
||||
"props": {
|
||||
"label": "About us",
|
||||
"className": "hover:text-ui-fg-base flex items-center gap-2",
|
||||
"className": "",
|
||||
"data-testid": "nav-categories-link",
|
||||
"isShowArrow": true
|
||||
},
|
||||
"children": [
|
||||
"menuItems": [
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "All Categories",
|
||||
"className": "hover:text-ui-fg-base flex items-center gap-2",
|
||||
"data-testid": "nav-all-categories-link"
|
||||
}
|
||||
}
|
||||
"title": {
|
||||
"text": "About us",
|
||||
"className": "text-[#003F31] text-xl font-bold"
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"label": "Check",
|
||||
"href": "/categories/overnight-oats",
|
||||
"className": "text-red-500"
|
||||
},
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "New Arrivals",
|
||||
"className": "hover:text-ui-fg-base flex items-center gap-2",
|
||||
"data-testid": "nav-new-arrivals-link"
|
||||
}
|
||||
}
|
||||
"label": "Porridge",
|
||||
"href": "/categories/porridge"
|
||||
},
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "Best Sellers",
|
||||
"className": "hover:text-ui-fg-base",
|
||||
"data-testid": "nav-best-sellers-link"
|
||||
"label": "Cereals",
|
||||
"href": "/categories/cereals"
|
||||
},
|
||||
{
|
||||
"label": "Granola",
|
||||
"href": "/categories/granola"
|
||||
},
|
||||
{
|
||||
"label": "Glasses & Bowls",
|
||||
"href": "/categories/glasses-bowls"
|
||||
},
|
||||
{
|
||||
"label": "Oat Bars",
|
||||
"href": "/categories/oat-bars"
|
||||
},
|
||||
{
|
||||
"label": "Nut butters",
|
||||
"href": "/categories/nut-butters"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": {
|
||||
"text": "Specials",
|
||||
"className": "text-[#003F31] text-xl font-bold"
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"label": "Advent calendar ✨",
|
||||
"href": "/collections/advent-calendar"
|
||||
},
|
||||
{
|
||||
"label": "Saver subscription",
|
||||
"href": "/collections/saver-subscription"
|
||||
},
|
||||
{
|
||||
"label": "bestseller",
|
||||
"href": "/collections/bestseller"
|
||||
},
|
||||
{
|
||||
"label": "New 🔥",
|
||||
"href": "/collections/new"
|
||||
},
|
||||
{
|
||||
"label": "Bluey Kidsrange",
|
||||
"href": "/collections/bluey-kidsrange"
|
||||
},
|
||||
{
|
||||
"label": "Value sets",
|
||||
"href": "/collections/value-sets"
|
||||
},
|
||||
{
|
||||
"label": "Sale",
|
||||
"href": "/collections/sale"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": {
|
||||
"text": "All products",
|
||||
"className": "text-[#003F31] text-xl font-bold"
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"label": "Shop all",
|
||||
"href": "/store"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,125 @@
|
|||
"use client"
|
||||
import * as React from "react"
|
||||
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
||||
import { clx } from "@medusajs/ui"
|
||||
import { ChevronDownMini } from "@medusajs/icons"
|
||||
|
||||
interface NavigationMenuProps {
|
||||
props: {
|
||||
label: string,
|
||||
className: string,
|
||||
"data-testid": string,
|
||||
isShowArrow: boolean
|
||||
},
|
||||
menuItems: MenuSection[]
|
||||
}
|
||||
type MenuSection = {
|
||||
title: { text: string; className?: string }
|
||||
links: MenuLink[]
|
||||
}
|
||||
type MenuLink = { label: string; href: string; className?: string }
|
||||
|
||||
// Structured menu data used to map UI
|
||||
const menuSections: MenuSection[] = [
|
||||
{
|
||||
title: { text: "Categories", className: "text-red" },
|
||||
links: [
|
||||
{
|
||||
label: "Overnight Oats",
|
||||
href: "/categories/overnight-oats",
|
||||
className: "text-red",
|
||||
},
|
||||
{ label: "Porridge", href: "/categories/porridge" },
|
||||
{ label: "Cereals", href: "/categories/cereals" },
|
||||
{ label: "Granola", href: "/categories/granola" },
|
||||
{ label: "Glasses & Bowls", href: "/categories/glasses-bowls" },
|
||||
{ label: "Oat Bars", href: "/categories/oat-bars" },
|
||||
{ label: "Nut butters", href: "/categories/nut-butters" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: { text: "Specials" },
|
||||
links: [
|
||||
{ label: "Advent calendar ✨", href: "/collections/advent-calendar" },
|
||||
{ label: "Saver subscription", href: "/collections/saver-subscription" },
|
||||
{ label: "bestseller", href: "/collections/bestseller" },
|
||||
{ label: "New 🔥", href: "/collections/new" },
|
||||
{ label: "Bluey Kidsrange", href: "/collections/bluey-kidsrange" },
|
||||
{ label: "Value sets", href: "/collections/value-sets" },
|
||||
{ label: "Sale", href: "/collections/sale" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: { text: "All products" },
|
||||
links: [{ label: "Shop all", href: "/store" }],
|
||||
},
|
||||
]
|
||||
|
||||
const MenuLinkItem = ({ href, label, className }: MenuLink) => (
|
||||
<li key={label}>
|
||||
<LocalizedClientLink
|
||||
href={href}
|
||||
className={clx(
|
||||
"block rounded-md py-2 hover:bg-ui-bg-subtle ",
|
||||
className ?? ""
|
||||
)}
|
||||
role="menuitem"
|
||||
>
|
||||
<div className="txt-small text-ui-fg-muted">{label}</div>
|
||||
</LocalizedClientLink>
|
||||
</li>
|
||||
)
|
||||
|
||||
const MenuSectionItem = ({ title, links }: MenuSection) => (
|
||||
<div key={title.text} className="space-y-1">
|
||||
<div className={clx("txt-small text-ui-fg-muted", title.className ?? "")}>
|
||||
{title.text}
|
||||
</div>
|
||||
<ul className="space-y-1">
|
||||
{links.map((link) => (
|
||||
<MenuLinkItem
|
||||
key={link.label}
|
||||
href={link.href}
|
||||
label={link.label}
|
||||
className={link.className ?? ""}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
export default function NavigationMenu({
|
||||
props,
|
||||
menuItems = menuSections,
|
||||
}: NavigationMenuProps) {
|
||||
return (
|
||||
<div className="group relative h-full flex items-center">
|
||||
<LocalizedClientLink
|
||||
href="/"
|
||||
className="txt-compact-xlarge-plus text-ui-fg-subtle hover:text-ui-fg-base group-hover:text-ui-fg-base flex items-center gap-2 transition-colors duration-200"
|
||||
>
|
||||
{props.label} {props?.isShowArrow ? (
|
||||
<span className="transition-transform duration-400 ease-out group-hover:rotate-180">
|
||||
<ChevronDownMini />
|
||||
</span>
|
||||
) : null}
|
||||
</LocalizedClientLink>
|
||||
|
||||
<div
|
||||
role="menu"
|
||||
className="pointer-events-none left-0 fixed top-[180px] invisible opacity-0 transform-gpu translate-y-2 transition-all duration-600 ease-out group-hover:visible group-hover:opacity-100 group-hover:translate-y-0 group-hover:pointer-events-auto group-focus-within:visible group-focus-within:opacity-100 group-focus-within:translate-y-0 group-focus-within:pointer-events-auto"
|
||||
>
|
||||
<div className="w-[100vw] bg-white shadow-borders-base py-4 px-20">
|
||||
<div className="grid grid-cols-1 small:grid-cols-3 gap-6">
|
||||
{menuItems.map((section) => (
|
||||
<MenuSectionItem
|
||||
key={section.title.text}
|
||||
title={section.title}
|
||||
links={section.links}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ export default async function VtNav({ nodes, context, className }: DynamicLayout
|
|||
const regions = await listRegions().then((regions: StoreRegion[]) => regions)
|
||||
|
||||
return (
|
||||
<div className="sticky top-0 inset-x-0 z-50 group">
|
||||
<div className="sticky top-[40px] inset-x-0 z-50">
|
||||
<header className={clx("relative mx-auto border-b duration-200 border-ui-border-base", className ?? "bg-white") }>
|
||||
<nav className="flex justify-between w-full items-center h-full">
|
||||
{/* <div className="flex-1 basis-0 h-full flex items-center">
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { User, MagnifyingGlassMini, Heart } from "@medusajs/icons"
|
|||
import DropdownMenuComponent from "@modules/layout/templates/dropdown-menu/dropdown-menu"
|
||||
import AnnouncementBannerVibenTec from "@modules/layout/templates/vibentec-template/announcement-bar"
|
||||
import SearchButton from "@modules/layout/components/search-button"
|
||||
import NavigationMenu from "@modules/layout/components/navigation-menu"
|
||||
|
||||
export interface LayoutComponentDefinition {
|
||||
props?: Record<string, any>
|
||||
|
|
@ -92,6 +93,11 @@ export const componentMap: Record<string, ComponentRenderer> = {
|
|||
<DropdownMenuComponent {...entry} />
|
||||
),
|
||||
},
|
||||
NavMenu: {
|
||||
render: (entry: any, ctx: LayoutContext) => (
|
||||
<NavigationMenu props={entry.props} menuItems={entry.menuItems} />
|
||||
),
|
||||
},
|
||||
LocalizedClientLink: {
|
||||
render: (entry: any) => (
|
||||
<LocalizedClientLink {...entry.props}>
|
||||
|
|
|
|||
|
|
@ -141,6 +141,38 @@ module.exports = {
|
|||
"0%": { transform: "translateY(-100%)" },
|
||||
"100%": { transform: "translateY(0)" },
|
||||
},
|
||||
enterFromRight: {
|
||||
from: { opacity: "0", transform: "translateX(200px)" },
|
||||
to: { opacity: "1", transform: "translateX(0)" },
|
||||
},
|
||||
enterFromLeft: {
|
||||
from: { opacity: "0", transform: "translateX(-200px)" },
|
||||
to: { opacity: "1", transform: "translateX(0)" },
|
||||
},
|
||||
exitToRight: {
|
||||
from: { opacity: "1", transform: "translateX(0)" },
|
||||
to: { opacity: "0", transform: "translateX(200px)" },
|
||||
},
|
||||
exitToLeft: {
|
||||
from: { opacity: "1", transform: "translateX(0)" },
|
||||
to: { opacity: "0", transform: "translateX(-200px)" },
|
||||
},
|
||||
scaleIn: {
|
||||
from: { opacity: "0", transform: "rotateX(-10deg) scale(0.9)" },
|
||||
to: { opacity: "1", transform: "rotateX(0deg) scale(1)" },
|
||||
},
|
||||
scaleOut: {
|
||||
from: { opacity: "1", transform: "rotateX(0deg) scale(1)" },
|
||||
to: { opacity: "0", transform: "rotateX(-10deg) scale(0.95)" },
|
||||
},
|
||||
fadeIn: {
|
||||
from: { opacity: "0" },
|
||||
to: { opacity: "1" },
|
||||
},
|
||||
fadeOut: {
|
||||
from: { opacity: "1" },
|
||||
to: { opacity: "0" },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
ring: "ring 2.2s cubic-bezier(0.5, 0, 0.5, 1) infinite",
|
||||
|
|
@ -156,6 +188,14 @@ module.exports = {
|
|||
enter: "enter 200ms ease-out",
|
||||
"slide-in": "slide-in 1.2s cubic-bezier(.41,.73,.51,1.02)",
|
||||
leave: "leave 150ms ease-in forwards",
|
||||
scaleIn: "scaleIn 200ms ease",
|
||||
scaleOut: "scaleOut 200ms ease",
|
||||
fadeIn: "fadeIn 200ms ease",
|
||||
fadeOut: "fadeOut 200ms ease",
|
||||
enterFromLeft: "enterFromLeft 250ms ease",
|
||||
enterFromRight: "enterFromRight 250ms ease",
|
||||
exitToLeft: "exitToLeft 250ms ease",
|
||||
exitToRight: "exitToRight 250ms ease",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue