refactor: cart button with icon

This commit is contained in:
Nam Doan 2025-12-08 11:29:41 +07:00
parent 043cc4d11a
commit d8e78b71e4
5 changed files with 265 additions and 44 deletions

View File

@ -0,0 +1,221 @@
[
{
"Header": {
"config": {
"sticky": true
},
"children": [
{
"Banner": {
"config": {
"variant": "nav",
"className": "h-12 bg-[#E6EFFC] text-[#11314E] gap-12 pl-16",
"left": [
{
"Link": {
"config": {
"label": "Über Uns",
"href": "/",
"className": "text-[13px] flex items-center gap-1 cursor-pointer"
}
}
},
{
"Link": {
"config": {
"label": "Kontaktieren Uns",
"href": "/",
"className": "text-[13px] flex items-center gap-1"
}
}
}
],
"center": [
{
"Link": {
"config": {
"label": "Einsparung durch Digitalisierung in der Arztpraxis",
"href": "/",
"className": "text-[13px] flex items-center gap-1 "
}
}
},
{
"Button": {
"config": {
"label": "Mehr Info",
"href": "/",
"className": "text-[13px] flex items-center bg-[#112638] gap-1 "
}
}
}
],
"right": [
{
"Dropdown": {
"config": {
"trigger": {
"text": "EURO",
"className": "font-bold text-[13px] text-[#11314E] 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": "EURO",
"href": "/"
}
]
}
}
},
{
"VtCountryCodeSelect": {
"config": {
"trigger": {
"className": "w-auto font-bold text-[13px] text-[#11314E] flex justify-start items-center gap-1 hover:text-[#009b93] bg-transparent shadow-none hover:bg-transparent",
"isFlag": false
}
}
}
}
]
}
}
},
{
"Nav": {
"config": {
"left": [
{
"VtSideMenu": {}
},
{
"VtMegaMenu": {
"config": {
"navLabel": {
"text": "Sale",
"className": "text-[13px] text-[#11314E] flex items-center mr-8 gap-1 hover:bg-transparent hover:underline hover:text-[#009b93]"
}
}
}
}
],
"center": [
{
"HomeButton": {
"config": {
"label": "Medusa Store"
}
}
}
],
"right": [
{
"AccountButton": {
"config": {
"label": "Account",
"className": "hover:text-ui-fg-base"
}
}
},
{
"VtCartButton": {
"config": {
"variant": "link",
"className": "hover:text-ui-fg-base"
}
}
}
]
}
}
}
]
}
},
{
"CartMismatchBanner": {
"config": {
"show": true
}
}
},
{
"FreeShippingPriceNudge": {
"config": {
"variant": "popup"
}
}
},
{
"PropsChildren": {}
},
{
"Footer": {
"config": {
"className": "content-container flex w-full border h-[300px] justify-between",
"left": [
{
"VtMenuItem": {
"config": {
"title": "category",
"className": "flex flex-col gap-y-2",
"itemClassName": "text-ui-fg-subtle txt-small ml-3",
"items": [
{
"text": "Clothing",
"href": "/"
},
{
"text": "Shoes",
"href": "/categories/shoes"
},
{
"text": "Accessories",
"href": "/categories/accessories"
}
]
}
}
}
],
"center": [
{
"VtMenuItem": {
"config": {
"title": "category",
"className": "flex flex-col gap-y-2",
"itemClassName": "text-ui-fg-subtle txt-small ml-3",
"items": [
{
"text": "Clothing",
"href": "/"
},
{
"text": "Shoes",
"href": "/categories/shoes"
},
{
"text": "Accessories",
"href": "/categories/accessories"
}
]
}
}
}
],
"right": [
{
"Text": {
"config": {
"label": "Medusa Check",
"className": "text-[13px] text-[#A6A6A6]"
}
}
}
]
}
}
}
]

View File

@ -1,8 +1,8 @@
import { retrieveCart } from "@lib/data/cart" import { retrieveCart } from "@lib/data/cart"
import CartDropdown from "../cart-dropdown" import CartDropdown from "../cart-dropdown"
export default async function CartButton() { export default async function CartButton({ iconName }: { iconName?: string }) {
const cart = await retrieveCart().catch(() => null) const cart = await retrieveCart().catch(() => null)
return <CartDropdown cart={cart} /> return <CartDropdown cart={cart} iconName={iconName} />
} }

View File

@ -9,6 +9,7 @@ import {
import { convertToLocale } from "@lib/util/money" import { convertToLocale } from "@lib/util/money"
import { HttpTypes } from "@medusajs/types" import { HttpTypes } from "@medusajs/types"
import { Button } from "@medusajs/ui" import { Button } from "@medusajs/ui"
import * as MedusaIcons from "@medusajs/icons"
import DeleteButton from "@modules/common/components/delete-button" import DeleteButton from "@modules/common/components/delete-button"
import LineItemOptions from "@modules/common/components/line-item-options" import LineItemOptions from "@modules/common/components/line-item-options"
import LineItemPrice from "@modules/common/components/line-item-price" import LineItemPrice from "@modules/common/components/line-item-price"
@ -19,8 +20,10 @@ import { Fragment, useEffect, useRef, useState } from "react"
const CartDropdown = ({ const CartDropdown = ({
cart: cartState, cart: cartState,
iconName,
}: { }: {
cart?: HttpTypes.StoreCart | null cart?: HttpTypes.StoreCart | null
iconName?: string
}) => { }) => {
const [activeTimer, setActiveTimer] = useState<NodeJS.Timer | undefined>( const [activeTimer, setActiveTimer] = useState<NodeJS.Timer | undefined>(
undefined undefined
@ -73,6 +76,10 @@ const CartDropdown = ({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [totalItems, itemRef.current]) }, [totalItems, itemRef.current])
const Icon = iconName
? (MedusaIcons as Record<string, React.ComponentType<any>>)[iconName]
: undefined
return ( return (
<div <div
className="h-full z-50" className="h-full z-50"
@ -81,11 +88,15 @@ const CartDropdown = ({
> >
<Popover className="relative h-full"> <Popover className="relative h-full">
<PopoverButton className="h-full"> <PopoverButton className="h-full">
<LocalizedClientLink {Icon ? (
className="hover:text-ui-fg-base" <Icon />
href="/cart" ) : (
data-testid="nav-cart-link" <LocalizedClientLink
>{`Cart (${totalItems})`}</LocalizedClientLink> className="hover:text-ui-fg-base"
href="/cart"
data-testid="nav-cart-link"
>{`Cart (${totalItems})`}</LocalizedClientLink>
)}
</PopoverButton> </PopoverButton>
<Transition <Transition
show={cartDropdownOpen} show={cartDropdownOpen}

View File

@ -1,26 +1,38 @@
import LocalizedClientLink from "@modules/common/components/localized-client-link" import LocalizedClientLink from "@modules/common/components/localized-client-link"
import { LayoutComponentDefinition, LayoutContext } from "vibentec/component-map"; import {
LayoutComponentDefinition,
LayoutContext,
} from "vibentec/component-map"
import { clx } from "@medusajs/ui" import { clx } from "@medusajs/ui"
import * as MedusaIcons from "@medusajs/icons"
export const AccountButton = ({ nodes, context }: { nodes: LayoutComponentDefinition; context: LayoutContext }) => { export const AccountButton = ({
nodes,
context,
}: {
nodes: LayoutComponentDefinition
context: LayoutContext
}) => {
const props = nodes.config ?? {} const props = nodes.config ?? {}
const className = clx("hover:text-ui-fg-base", props.className); const className = clx("hover:text-ui-fg-base", props.className)
const style: React.CSSProperties = {}; const style: React.CSSProperties = {}
if (props.bgColor) style.backgroundColor = props.bgColor; if (props.bgColor) style.backgroundColor = props.bgColor
if (props.textColor) style.color = props.textColor; if (props.textColor) style.color = props.textColor
const href = props.href ?? "/account" const href = props.href ?? "/account"
const label = props.label ?? "Account" const label = props.label ?? "Account"
const iconName = props.icon
const Icon = iconName
? (MedusaIcons as Record<string, React.ComponentType<any>>)[iconName]
: undefined
return ( return (
<div className="flex items-center h-full" style={style}> <div className="flex items-center h-full">
<LocalizedClientLink <LocalizedClientLink
href={href} href={href}
className={className} className={className}
data-testid="nav-account-link" data-testid="nav-account-link"
> >
{label} {Icon ? <Icon /> : label}
</LocalizedClientLink> </LocalizedClientLink>
</div> </div>
) )
} }

View File

@ -3,23 +3,9 @@ import {
LayoutComponentDefinition, LayoutComponentDefinition,
LayoutContext, LayoutContext,
} from "vibentec/component-map" } from "vibentec/component-map"
import { clx, IconButton } from "@medusajs/ui" import { clx } from "@medusajs/ui"
import { Suspense } from "react" import { Suspense } from "react"
import CartButton from "@modules/layout/components/cart-button" import CartButton from "@modules/layout/components/cart-button"
import { ShoppingBag, ShoppingCart } from "@medusajs/icons"
const IconButtonComponent = ({ className, variant }: { className?: string; variant?: "shoppingBag" | "cart" }) => {
const variants = {
shoppingBag: ShoppingBag,
cart: ShoppingCart,
}
const Icon = variants[variant ?? "shoppingBag"]
return (
<IconButton className={className}>
<Icon />
</IconButton>
)
}
export const VtCartButton = ({ export const VtCartButton = ({
nodes, nodes,
@ -30,15 +16,6 @@ export const VtCartButton = ({
}) => { }) => {
const props = nodes.config ?? {} const props = nodes.config ?? {}
const className = clx("hover:text-ui-fg-base flex gap-2", props.className) const className = clx("hover:text-ui-fg-base flex gap-2", props.className)
const variants = {
link: <CartButton />,
shoppingBagbutton: <IconButtonComponent className={className} variant="shoppingBag" />,
cartIconButton: <IconButtonComponent className={className} variant="cart" />,
}
if (!props.variant) return null
const fallBackComp = variants[props.variant as keyof typeof variants]
return ( return (
<Suspense <Suspense
fallback={ fallback={
@ -51,7 +28,7 @@ export const VtCartButton = ({
</LocalizedClientLink> </LocalizedClientLink>
} }
> >
{fallBackComp} <CartButton iconName={props.icon} />
</Suspense> </Suspense>
) )
} }