feat: create design for 3bear and drsquatch hero banner, create variants cta section

This commit is contained in:
Nam Doan
2025-12-17 11:12:32 +07:00
parent 9e0f6b0071
commit 7afffb3f99
14 changed files with 280 additions and 129 deletions
@@ -9,14 +9,14 @@ export default function HeroDefault({ node, context }: { node: LayoutComponentDe
if (imageDisplayer) {
return (
<div className="absolute inset-0 z-10 w-full h-full">
<div className="absolute inset-0 z-auto w-full h-full">
<VtCarousel nodes={{ config: imageDisplayer.config }} context={context} />
</div>
)
}
return (
<div className="absolute inset-0 z-10 flex flex-col justify-center items-center text-center small:p-32 gap-6">
<div className="absolute inset-0 z-auto flex flex-col justify-center items-center text-center small:p-32 gap-6">
<span>
<Heading
level="h1"
@@ -1,28 +0,0 @@
import { Github } from "@medusajs/icons"
import { Button, clx, Heading } from "@medusajs/ui"
import {
LayoutComponentDefinition,
LayoutContext,
} from "@vibentec/component-map"
import { VtCarousel } from "../vt-carousel"
interface Props {
node: LayoutComponentDefinition
context: LayoutContext
}
const HeroVibentec = ({ node, context }: Props) => {
const props = node.config ?? {}
const imageDisplayer = props.ImageDisplayer
return (
<div className={clx(props.className)}>
{imageDisplayer ? (
<VtCarousel nodes={{ config: imageDisplayer.config }} context={context} />
) : (
<VtCarousel nodes={node} context={context} />
)}
</div>
)
}
export default HeroVibentec
+8 -21
View File
@@ -3,14 +3,9 @@ import {
LayoutContext,
} from "vibentec/component-map"
import { clx } from "@medusajs/ui"
import BannerHeroVibentec from "../hero/hero-vibentec"
import BannerHeroDefault from "../hero/hero-default"
import BannerHero from "./banner-hero"
import { DynamicLayoutRenderer } from "vibentec/renderer"
interface BannerProps {
variant: "vibentec" | "default"
className?: string
}
export default async function Hero({
nodes,
context,
@@ -18,7 +13,7 @@ export default async function Hero({
nodes: LayoutComponentDefinition
context: LayoutContext
}) {
const props = (nodes.config as BannerProps) ?? {}
const props = nodes.config ?? {}
const left = nodes.config?.left ?? []
const center = nodes.config?.center ?? []
const right = nodes.config?.right ?? []
@@ -26,29 +21,21 @@ export default async function Hero({
"min-h-[30rem] w-full border-b border-ui-border-base relative",
props.className
)
if (!props.variant) return null
const variants = {
vibentec: BannerHeroVibentec,
default: BannerHeroDefault,
}
const Component = variants[props.variant]
return (
<div className={heroClassName}>
<Component node={nodes} context={context} />
<div className="absolute inset-0 z-20 w-full h-full">
<nav className="content-container txt-xsmall-plus flex items-center justify-between w-full h-full text-small-regular">
<div className="flex items-center gap-x-4 w-full h-full">
<BannerHero node={nodes} context={context} />
<div className="absolute z-20">
<nav className="content-container txt-xsmall-plus flex items-center justify-between text-small-regular">
<div className="flex items-center gap-x-4">
{left && <DynamicLayoutRenderer nodes={left} context={context} />}
</div>
<div className="flex items-center gap-x-4 w-full h-full">
<div className="flex items-center gap-x-4">
{center && (
<DynamicLayoutRenderer nodes={center} context={context} />
)}
</div>
<div className="flex items-center gap-x-4 w-full h-full justify-end">
<div className="flex items-center gap-x-4 justify-end">
{right && <DynamicLayoutRenderer nodes={right} context={context} />}
</div>
</nav>
@@ -44,6 +44,7 @@
display: grid;
position: absolute;
top: 0;
z-index: 1000;
width: 100%;
height: 100%;
grid-template-columns: auto 1fr;
@@ -57,6 +58,7 @@
display: flex;
justify-content: space-between;
align-items: center;
z-index: 1000;
}
.embla__button {
--text-high-contrast-rgb-value: 49, 49, 49;
@@ -74,7 +76,7 @@
margin: 0;
width: 3.6rem;
height: 3.6rem;
z-index: 1;
z-index: 1001;
border-radius: 50%;
color: var(--text-body);
display: flex;
@@ -98,6 +100,7 @@
flex-wrap: wrap;
justify-content: flex-end;
align-items: center;
z-index: 1000;
}
.embla__dot {
--text-high-contrast-rgb-value: 49, 49, 49;
@@ -9,6 +9,8 @@ import Autoplay from "embla-carousel-autoplay"
import { useMemo } from "react"
import { DotButton, useDotButton } from "./carousel-dot-button"
import { NextButton, PrevButton, usePrevNextButtons } from "./carousel-arrow-button"
import LocalizedClientLink from "@modules/common/components/localized-client-link"
export function VtCarousel({
nodes,
context,
@@ -18,13 +20,21 @@ export function VtCarousel({
}) {
const props = nodes.config ?? {}
const { options } = props as any
const images: string[] = (props as any).images ?? (props as any).slides ?? []
const links: (string | undefined)[] = (props as any).links ?? []
const images: string[] = props.images ?? props.slides ?? []
const links: (string | undefined)[] = props.links ?? []
const durationSeconds: number = props.duration ?? 4
const showControls = images.length > 1
const plugins = useMemo(() => [
Autoplay({ delay: Math.max(1, durationSeconds) * 1000, stopOnInteraction: false, stopOnMouseEnter: true, })
], [durationSeconds])
const plugins = useMemo(() => {
if (!durationSeconds || durationSeconds <= 0) return []
return [
Autoplay({
delay: durationSeconds * 1000,
stopOnInteraction: false,
stopOnMouseEnter: true,
}),
]
}, [durationSeconds])
const [emblaRef, emblaApi] = useEmblaCarousel(options, plugins)
const { selectedIndex, scrollSnaps, onDotButtonClick } =
@@ -41,12 +51,12 @@ export function VtCarousel({
<div className={styles["embla__viewport"]} ref={emblaRef}>
<div className={styles["embla__container"]}>
{images && images.map((src: string, index: number) => (
<div className={styles["embla__slide"]} key={index}>
<div className={styles["embla__slide"]} key={index + src}>
<div className={styles["embla__slide__number"]}>
{links[index] ? (
<a href={links[index]}>
<LocalizedClientLink href={links[index]}>
<img src={src} alt={`slide-${index + 1}`} className={styles["embla__slide__image"]} />
</a>
</LocalizedClientLink>
) : (
<img src={src} alt={`slide-${index + 1}`} className={styles["embla__slide__image"]} />
)}
@@ -56,25 +66,27 @@ export function VtCarousel({
</div>
</div>
<div className={styles["embla__controls"]}>
<div className={styles["embla__buttons"]}>
<PrevButton onClick={onPrevButtonClick} disabled={prevBtnDisabled} />
<NextButton onClick={onNextButtonClick} disabled={nextBtnDisabled} />
</div>
{showControls && (
<div className={styles["embla__controls"]}>
<div className={styles["embla__buttons"]}>
<PrevButton onClick={onPrevButtonClick} disabled={prevBtnDisabled} />
<NextButton onClick={onNextButtonClick} disabled={nextBtnDisabled} />
</div>
<div className={styles["embla__dots"]}>
{scrollSnaps.map((_, index) => (
<DotButton
key={index}
onClick={() => onDotButtonClick(index)}
className={[
styles["embla__dot"],
index === selectedIndex ? styles["embla__dot--selected"] : "",
].filter(Boolean).join(" ")}
/>
))}
<div className={styles["embla__dots"]}>
{scrollSnaps.map((_, index) => (
<DotButton
key={index}
onClick={() => onDotButtonClick(index)}
className={[
styles["embla__dot"],
index === selectedIndex ? styles["embla__dot--selected"] : "",
].filter(Boolean).join(" ")}
/>
))}
</div>
</div>
</div>
)}
</section>
)
}
@@ -0,0 +1,35 @@
"use client"
import { Button, clx } from "@medusajs/ui"
import {
LayoutComponentDefinition,
LayoutContext,
} from "@vibentec/component-map"
export function BearCtaBanner({
nodes,
context,
}: {
nodes: LayoutComponentDefinition
context: LayoutContext
}) {
const props = nodes.config ?? {}
return (
<div className={clx(
"relative w-full p-12 flex flex-col items-start justify-center text-left",
props.className
)}>
<p className="text-[#0D382E] text-lg font-medium mb-2">
{props.tagText ?? "So einfach kann Frühstück sein mit unseren leckeren Overnight Oats."}
</p>
<h1 className="text-[#0D382E] font-black leading-tight text-[64px] mb-8 tracking-tight">
{props.titleText ?? "breakfast made easy."}
</h1>
<Button className="inline-flex items-center justify-center bg-[#FCEE56] hover:bg-[#FCEE56]/90 text-[#0D382E] px-8 py-3 rounded-full font-bold text-lg shadow-none border-none">
{props.buttonText ?? "Jetzt entdecken"}
</Button>
</div>
)
}
@@ -0,0 +1,52 @@
"use client"
import { Button, clx } from "@medusajs/ui"
import { ChevronRightMini } from "@medusajs/icons"
import {
LayoutComponentDefinition,
LayoutContext,
} from "@vibentec/component-map"
export function DefaultCtaBanner({
nodes,
context,
}: {
nodes: LayoutComponentDefinition
context: LayoutContext
}) {
const props = nodes.config ?? {}
return (
<div className={clx(
"relative w-[544px] bg-white rounded-[24px] border border-[#E6EFFC] shadow-[0_12px_40px_rgba(17,49,78,0.10)] p-6",
props.className
)}>
<div className={clx(
"inline-flex items-center rounded-full bg-[#FCE9DA] text-[#E68445] px-3 py-1 text-sm font-medium",
props.tagTextClassName
)}>
{props.tagText}
</div>
<h1 className={clx(
"mt-4 text-[#11314E] font-semibold leading-normal text-[56px]",
props.titleTextClassName
)}>
{props.titleText}
</h1>
<p className={clx(
"mt-5 text-[#285A86] text-[16px] sm:text-xl opacity-80",
props.descriptionTextClassName
)}>
{props.descriptionText}
</p>
<Button className={clx(
"mt-8 inline-flex items-center gap-2 bg-[#0F2740] hover:bg-[#173551] text-white px-6 py-3 rounded-[12px] shadow-md",
props.buttonTextClassName
)}>
{props.buttonText}
<ChevronRightMini />
</Button>
</div>
)
}
@@ -1,10 +1,10 @@
"use client"
import { Button, clx } from "@medusajs/ui"
import { ChevronRightMini } from "@medusajs/icons"
import {
LayoutComponentDefinition,
LayoutContext,
} from "@vibentec/component-map"
import { DefaultCtaBanner } from "./default-cta"
import { BearCtaBanner } from "./bear-cta"
export function VtCtaBanner({
nodes,
@@ -14,27 +14,14 @@ export function VtCtaBanner({
context: LayoutContext
}) {
const props = nodes.config ?? {}
return (
<div className={clx(
"relative w-[544px] bg-white rounded-[24px] border border-[#E6EFFC] shadow-[0_12px_40px_rgba(17,49,78,0.10)] p-6",
props.className
)}>
<div className="inline-flex items-center rounded-full bg-[#FCE9DA] text-[#E68445] px-3 py-1 text-sm font-medium">
{props.tagText}
</div>
const variant = props.variant ?? "default"
<h1 className="mt-4 text-[#11314E] font-semibold leading-tight text-[56px]">
{props.titleText}
</h1>
const variants: Record<string, any> = {
default: DefaultCtaBanner,
"3bear": BearCtaBanner,
}
<p className="mt-5 text-[#285A86] text-[16px] sm:text-xl opacity-80">
{props.descriptionText}
</p>
const Component = variants[variant] || DefaultCtaBanner
<Button className="mt-8 inline-flex items-center gap-2 bg-[#0F2740] hover:bg-[#173551] text-white px-6 py-3 rounded-[12px] shadow-md">
{props.buttonText}
<ChevronRightMini />
</Button>
</div>
)
return <Component nodes={nodes} context={context} />
}
+1 -1
View File
@@ -2,7 +2,7 @@ import fs from "fs"
import path from "path"
import { jsonFileNames } from "./devJsonFileNames";
const fileName = jsonFileNames.namVibentec;
const fileName = jsonFileNames.namStarter;
export async function loadDesignConfig() {
const filePath = path.join(process.cwd(), "config", fileName)