@gosalesdeal/blog 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +265 -34
  2. package/dist/client/blogClient.js +36 -7
  3. package/dist/client/blogClient.js.map +1 -1
  4. package/dist/components/BlogPost.js +7 -8
  5. package/dist/components/BlogPost.js.map +1 -1
  6. package/dist/components/landing/BlogLayoutComponents.d.ts +14 -0
  7. package/dist/components/landing/BlogLayoutComponents.js +198 -0
  8. package/dist/components/landing/BlogLayoutComponents.js.map +1 -0
  9. package/dist/components/landing/FlexibleLayoutComponent.d.ts +40 -0
  10. package/dist/components/landing/FlexibleLayoutComponent.js +352 -0
  11. package/dist/components/landing/FlexibleLayoutComponent.js.map +1 -0
  12. package/dist/components/landing/HeroFeatureBanner.d.ts +36 -0
  13. package/dist/components/landing/HeroFeatureBanner.js +88 -0
  14. package/dist/components/landing/HeroFeatureBanner.js.map +1 -0
  15. package/dist/components/landing/HeroFeatureBannerComponent.d.ts +4 -0
  16. package/dist/components/landing/HeroFeatureBannerComponent.js +100 -0
  17. package/dist/components/landing/HeroFeatureBannerComponent.js.map +1 -0
  18. package/dist/components/landing/HeroFeatureBannerVideo.d.ts +34 -0
  19. package/dist/components/landing/HeroFeatureBannerVideo.js +261 -0
  20. package/dist/components/landing/HeroFeatureBannerVideo.js.map +1 -0
  21. package/dist/components/landing/HeroFeatureBannerVideoComponent.d.ts +5 -0
  22. package/dist/components/landing/HeroFeatureBannerVideoComponent.js +69 -0
  23. package/dist/components/landing/HeroFeatureBannerVideoComponent.js.map +1 -0
  24. package/dist/components/landing/VarietyReviewslistComponent.d.ts +4 -0
  25. package/dist/components/landing/VarietyReviewslistComponent.js +172 -0
  26. package/dist/components/landing/VarietyReviewslistComponent.js.map +1 -0
  27. package/dist/index.d.ts +11 -0
  28. package/dist/index.js +21 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/lib/resolveMediaSrc.d.ts +2 -0
  31. package/dist/lib/resolveMediaSrc.js +21 -0
  32. package/dist/lib/resolveMediaSrc.js.map +1 -0
  33. package/dist/render/BlockRenderer.js +5 -0
  34. package/dist/render/BlockRenderer.js.map +1 -1
  35. package/dist/render/LandingPageRenderer.d.ts +5 -0
  36. package/dist/render/LandingPageRenderer.js +50 -0
  37. package/dist/render/LandingPageRenderer.js.map +1 -0
  38. package/dist/render/landingPageHtml.d.ts +3 -1
  39. package/dist/render/landingPageHtml.js +18 -2
  40. package/dist/render/landingPageHtml.js.map +1 -1
  41. package/dist/styles.css +13 -0
  42. package/dist/types/blog.d.ts +5 -2
  43. package/package.json +6 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HeroFeatureBannerVideo.js","sources":["../../../src/components/landing/HeroFeatureBannerVideo.tsx"],"sourcesContent":["'use client';\n\nimport React, { useEffect, useRef, useState } from 'react';\n\nexport type HeroBullet = {\n icon?: string;\n text: React.ReactNode;\n};\n\nexport type HeroFeatureBannerVideoProps = {\n bgVideoSrc?: string;\n bgPosterSrc?: string;\n bgPosterAlt?: string;\n videoAutoplay?: boolean;\n videoMuted?: boolean;\n videoLoop?: boolean;\n videoLazyLoad?: boolean;\n /** Prioritize fetch/decode for above-the-fold hero usage */\n videoPriority?: boolean;\n heading: React.ReactNode;\n bullets?: HeroBullet[];\n cta?: {\n label: string;\n href?: string;\n onClick?: () => void;\n icon?: string;\n backgroundColor?: string;\n className?: string;\n };\n ctaPosition?: 'left' | 'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n height?: number;\n rounded?: string;\n leftTint?: boolean;\n contentMaxWidth?: number;\n className?: string;\n};\n\nfunction configureMobileVideo(video: HTMLVideoElement, muted: boolean) {\n video.muted = muted;\n video.defaultMuted = muted;\n video.playsInline = true;\n video.setAttribute('playsinline', '');\n video.setAttribute('webkit-playsinline', '');\n}\n\nconst HeroBackgroundVideo: React.FC<{\n src: string;\n autoplay: boolean;\n muted: boolean;\n loop: boolean;\n lazyLoad: boolean;\n priority: boolean;\n}> = ({ src, autoplay, muted, loop, lazyLoad, priority }) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const videoRef = useRef<HTMLVideoElement>(null);\n const [shouldLoad, setShouldLoad] = useState(!lazyLoad);\n const [isReady, setIsReady] = useState(false);\n const [reduceMotion, setReduceMotion] = useState(false);\n\n useEffect(() => {\n const mq = window.matchMedia('(prefers-reduced-motion: reduce)');\n const update = () => setReduceMotion(mq.matches);\n update();\n mq.addEventListener('change', update);\n return () => mq.removeEventListener('change', update);\n }, []);\n\n useEffect(() => {\n if (!lazyLoad || shouldLoad || reduceMotion) return;\n\n const node = containerRef.current;\n if (!node || typeof IntersectionObserver === 'undefined') {\n setShouldLoad(true);\n return;\n }\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n setShouldLoad(true);\n observer.disconnect();\n }\n },\n { rootMargin: '200px 0px', threshold: 0.01 }\n );\n\n observer.observe(node);\n return () => observer.disconnect();\n }, [lazyLoad, shouldLoad, reduceMotion]);\n\n useEffect(() => {\n setIsReady(false);\n }, [src]);\n\n useEffect(() => {\n const video = videoRef.current;\n if (!video || !shouldLoad || reduceMotion) return;\n\n configureMobileVideo(video, muted);\n\n const tryPlay = () => {\n setIsReady(true);\n if (!autoplay) return;\n configureMobileVideo(video, muted);\n void video.play().catch(() => {\n // Autoplay may be blocked until a user gesture on some mobile browsers\n });\n };\n\n const onLoadedData = () => tryPlay();\n const onCanPlay = () => tryPlay();\n\n const onVisibilityChange = () => {\n if (!document.hidden && autoplay && video.paused) {\n configureMobileVideo(video, muted);\n void video.play().catch(() => {});\n }\n };\n\n // iOS Safari often never fires canplaythrough for large files — use loadeddata/canplay instead\n const hardFallback = window.setTimeout(tryPlay, 2000);\n\n if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {\n tryPlay();\n }\n\n video.addEventListener('loadeddata', onLoadedData);\n video.addEventListener('canplay', onCanPlay);\n document.addEventListener('visibilitychange', onVisibilityChange);\n\n return () => {\n window.clearTimeout(hardFallback);\n video.removeEventListener('loadeddata', onLoadedData);\n video.removeEventListener('canplay', onCanPlay);\n document.removeEventListener('visibilitychange', onVisibilityChange);\n };\n }, [shouldLoad, autoplay, muted, reduceMotion, src]);\n\n const setVideoRef = (node: HTMLVideoElement | null) => {\n videoRef.current = node;\n if (node) {\n configureMobileVideo(node, muted);\n }\n };\n\n if (reduceMotion || !src) {\n return <div ref={containerRef} className=\"hfbv-media-layer\" aria-hidden=\"true\" />;\n }\n\n return (\n <div ref={containerRef} className=\"hfbv-media-layer\" aria-hidden=\"true\">\n {shouldLoad && (\n <video\n ref={setVideoRef}\n className={`hfbv-video${isReady ? ' hfbv-video--ready' : ''}`}\n src={src}\n muted={muted}\n loop={loop}\n autoPlay={autoplay}\n playsInline\n preload={priority ? 'auto' : 'metadata'}\n // @ts-expect-error fetchPriority is supported in modern browsers\n fetchPriority={priority ? 'high' : 'auto'}\n disablePictureInPicture\n disableRemotePlayback\n tabIndex={-1}\n />\n )}\n </div>\n );\n};\n\nconst HeroFeatureBannerVideo: React.FC<HeroFeatureBannerVideoProps> = ({\n bgVideoSrc,\n videoAutoplay = true,\n videoMuted = true,\n videoLoop = true,\n videoLazyLoad = false,\n videoPriority = true,\n heading,\n bullets = [],\n cta,\n ctaPosition = 'left',\n height = 560,\n rounded = '',\n leftTint = true,\n contentMaxWidth = 720,\n className = '',\n}) => {\n return (\n <section\n className={`relative w-full ${rounded} overflow-hidden ${className}`}\n aria-label=\"Hero feature banner\"\n style={{ minHeight: height }}\n >\n <style>{`\n .hfbv-media-stack { position: absolute; inset: 0; z-index: 0; overflow: hidden; }\n .hfbv-media-layer { position: absolute; inset: 0; width: 100%; height: 100%; }\n .hfbv-video {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n object-position: center right;\n opacity: 0;\n transition: opacity 0.35s ease;\n transform: translate3d(0, 0, 0);\n backface-visibility: hidden;\n -webkit-backface-visibility: hidden;\n }\n .hfbv-video--ready { opacity: 1; }\n .hfbv-tint { position: absolute; inset: 0; pointer-events: none; z-index: 1; }\n .hfbv-content { position: relative; z-index: 2; }\n .hfbv-bullet i { color: var(--primary-color); font-size: 1.1rem; }\n `}</style>\n\n <div className=\"hfbv-media-stack\">\n {bgVideoSrc && (\n <HeroBackgroundVideo\n src={bgVideoSrc}\n autoplay={videoAutoplay}\n muted={videoMuted}\n loop={videoLoop}\n lazyLoad={videoLazyLoad}\n priority={videoPriority}\n />\n )}\n </div>\n\n {leftTint && <div className=\"hfbv-tint\" />}\n\n <div className=\"hfbv-content w-full h-full container\" style={{ position: 'relative' }}>\n <div\n className=\"p-4 p-md-5\"\n style={{\n height: '100%',\n display: 'flex',\n flexDirection: 'column',\n justifyContent: ctaPosition === 'center' ? 'center' : 'flex-start',\n }}\n >\n <div style={{ maxWidth: contentMaxWidth }}>\n <h1 className=\"m-0 text-900\" style={{ fontSize: 56, lineHeight: 1.05, fontWeight: 800 }}>\n {heading}\n </h1>\n\n {bullets.length > 0 && (\n <ul className=\"list-none m-0 p-0 mt-4\">\n {bullets.map((b, i) => (\n <li key={i} className=\"flex align-items-center gap-3 mb-3 hfbv-bullet\">\n <span\n className=\"flex align-items-center justify-content-center\"\n style={{ width: 34, height: 34, background: 'rgba(255,100,50,.12)' }}\n >\n <i className={`pi ${b.icon ?? 'pi-check-circle'}`} />\n </span>\n <span className=\"text-800\" style={{ fontSize: 18 }}>\n {b.text}\n </span>\n </li>\n ))}\n </ul>\n )}\n\n {cta && ctaPosition === 'left' && (\n <div className=\"mt-5\">\n {cta.href ? (\n <a\n href={cta.href}\n className={`p-button p-component p-button-rounded p-button-lg ${cta.className || ''}`}\n style={{ textDecoration: 'none', backgroundColor: cta.backgroundColor }}\n >\n <span className=\"p-button-icon p-button-icon-left pi pi-arrow-right\" />\n <span className=\"p-button-label\">{cta.label}</span>\n </a>\n ) : (\n <button\n type=\"button\"\n className={`p-button p-component p-button-rounded p-button-lg ${cta.className || ''}`}\n onClick={cta.onClick}\n style={{ backgroundColor: cta.backgroundColor }}\n >\n <span className=\"p-button-icon p-button-icon-left pi pi-arrow-right\" />\n <span className=\"p-button-label\">{cta.label}</span>\n </button>\n )}\n </div>\n )}\n </div>\n </div>\n\n {cta && ctaPosition !== 'left' && (\n <div\n style={{\n position: 'absolute',\n zIndex: 3,\n ...(ctaPosition === 'center' && { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' }),\n ...(ctaPosition === 'top-left' && { top: '2rem', left: '2rem' }),\n ...(ctaPosition === 'top-right' && { top: '2rem', right: '2rem' }),\n ...(ctaPosition === 'bottom-left' && { bottom: '2rem', left: '2rem' }),\n ...(ctaPosition === 'bottom-right' && { bottom: '2rem', right: '2rem' }),\n }}\n >\n {cta.href ? (\n <a\n href={cta.href}\n className={`p-button p-component p-button-rounded p-button-lg ${cta.className || ''}`}\n style={{ textDecoration: 'none', backgroundColor: cta.backgroundColor }}\n >\n <span className=\"p-button-icon p-button-icon-left pi pi-arrow-right\" />\n <span className=\"p-button-label\">{cta.label}</span>\n </a>\n ) : (\n <button\n type=\"button\"\n className={`p-button p-component p-button-rounded p-button-lg ${cta.className || ''}`}\n onClick={cta.onClick}\n style={{ backgroundColor: cta.backgroundColor }}\n >\n <span className=\"p-button-icon p-button-icon-left pi pi-arrow-right\" />\n <span className=\"p-button-label\">{cta.label}</span>\n </button>\n )}\n </div>\n )}\n </div>\n </section>\n );\n};\n\nexport default HeroFeatureBannerVideo;\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { default as React } from 'react';
2
+ export declare const HeroFeatureBannerVideoComponent: React.FC<{
3
+ props: Record<string, any>;
4
+ isAboveFold?: boolean;
5
+ }>;
@@ -0,0 +1,69 @@
1
+ "use client";
2
+ import { jsx, Fragment, jsxs } from "react/jsx-runtime";
3
+ import React, { useEffect } from "react";
4
+ import HeroFeatureBannerVideo from "./HeroFeatureBannerVideo.js";
5
+ import { resolveMediaSrc } from "../../lib/resolveMediaSrc.js";
6
+ function useHeroVideoPreload(src, enabled = true) {
7
+ useEffect(() => {
8
+ if (!src || !enabled) return;
9
+ const existing = document.querySelector(`link[data-hfbv-preload="${src}"]`);
10
+ if (existing) return;
11
+ const link = document.createElement("link");
12
+ link.rel = "preload";
13
+ link.as = "video";
14
+ link.href = src;
15
+ link.setAttribute("fetchpriority", "high");
16
+ link.setAttribute("data-hfbv-preload", src);
17
+ document.head.appendChild(link);
18
+ return () => {
19
+ link.remove();
20
+ };
21
+ }, [src, enabled]);
22
+ }
23
+ const HeroFeatureBannerVideoComponent = ({ props, isAboveFold: _isAboveFold = false }) => {
24
+ const bullets = Array.isArray(props.bullets) ? props.bullets : [];
25
+ let headingNode = props.heading || "";
26
+ if (typeof headingNode === "string" && headingNode.includes("\n")) {
27
+ const parts = headingNode.split("\n");
28
+ headingNode = /* @__PURE__ */ jsx(Fragment, { children: parts.map((part, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
29
+ i > 0 && /* @__PURE__ */ jsx("br", {}),
30
+ part
31
+ ] }, i)) });
32
+ }
33
+ const cta = props.ctaLabel ? {
34
+ label: props.ctaLabel,
35
+ href: props.ctaHref,
36
+ icon: props.ctaIcon,
37
+ backgroundColor: props.ctaBgColor,
38
+ className: props.ctaClassName
39
+ } : void 0;
40
+ const bgVideoSrc = resolveMediaSrc(props.bgVideoSrc, props.bgVideoSrcNew);
41
+ useHeroVideoPreload(bgVideoSrc, Boolean(bgVideoSrc));
42
+ return /* @__PURE__ */ jsx("div", { className: `hero-feature-banner-video ${props.wrapperClassName || ""}`, children: /* @__PURE__ */ jsxs("div", { className: props.sectionBgClassName || "", children: [
43
+ props.customCSS && /* @__PURE__ */ jsx("style", { children: props.customCSS }),
44
+ /* @__PURE__ */ jsx(
45
+ HeroFeatureBannerVideo,
46
+ {
47
+ bgVideoSrc,
48
+ videoAutoplay: Boolean(bgVideoSrc),
49
+ videoMuted: true,
50
+ videoLoop: props.videoLoop !== false,
51
+ videoLazyLoad: false,
52
+ videoPriority: true,
53
+ heading: headingNode,
54
+ bullets,
55
+ cta,
56
+ ctaPosition: props.ctaPosition || "left",
57
+ height: props.height || 560,
58
+ rounded: props.rounded || "",
59
+ leftTint: props.leftTint !== false,
60
+ contentMaxWidth: props.contentMaxWidth || 720,
61
+ className: props.className || ""
62
+ }
63
+ )
64
+ ] }) });
65
+ };
66
+ export {
67
+ HeroFeatureBannerVideoComponent
68
+ };
69
+ //# sourceMappingURL=HeroFeatureBannerVideoComponent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HeroFeatureBannerVideoComponent.js","sources":["../../../src/components/landing/HeroFeatureBannerVideoComponent.tsx"],"sourcesContent":["'use client';\n\nimport React, { useEffect } from 'react';\nimport HeroFeatureBannerVideo from './HeroFeatureBannerVideo';\nimport { resolveMediaSrc } from '../../lib/resolveMediaSrc';\n\nfunction useHeroVideoPreload(src?: string, enabled = true) {\n useEffect(() => {\n if (!src || !enabled) return;\n\n const existing = document.querySelector<HTMLLinkElement>(`link[data-hfbv-preload=\"${src}\"]`);\n if (existing) return;\n\n const link = document.createElement('link');\n link.rel = 'preload';\n link.as = 'video';\n link.href = src;\n link.setAttribute('fetchpriority', 'high');\n link.setAttribute('data-hfbv-preload', src);\n document.head.appendChild(link);\n\n return () => {\n link.remove();\n };\n }, [src, enabled]);\n}\n\nexport const HeroFeatureBannerVideoComponent: React.FC<{\n props: Record<string, any>;\n isAboveFold?: boolean;\n}> = ({ props, isAboveFold: _isAboveFold = false }) => {\n const bullets: any[] = Array.isArray(props.bullets) ? props.bullets : [];\n\n let headingNode: React.ReactNode = props.heading || '';\n if (typeof headingNode === 'string' && headingNode.includes('\\n')) {\n const parts = (headingNode as string).split('\\n');\n headingNode = (\n <>\n {parts.map((part: string, i: number) => (\n <React.Fragment key={i}>\n {i > 0 && <br />}\n {part}\n </React.Fragment>\n ))}\n </>\n );\n }\n\n const cta =\n props.ctaLabel\n ? {\n label: props.ctaLabel,\n href: props.ctaHref,\n icon: props.ctaIcon,\n backgroundColor: props.ctaBgColor,\n className: props.ctaClassName\n }\n : undefined;\n\n const bgVideoSrc = resolveMediaSrc(props.bgVideoSrc, props.bgVideoSrcNew);\n\n // Hero videos must load immediately — lazy load breaks mobile autoplay (especially iOS Safari)\n useHeroVideoPreload(bgVideoSrc, Boolean(bgVideoSrc));\n\n return (\n <div className={`hero-feature-banner-video ${props.wrapperClassName || ''}`}>\n <div className={props.sectionBgClassName || ''}>\n {props.customCSS && <style>{props.customCSS}</style>}\n <HeroFeatureBannerVideo\n bgVideoSrc={bgVideoSrc}\n videoAutoplay={Boolean(bgVideoSrc)}\n videoMuted\n videoLoop={props.videoLoop !== false}\n videoLazyLoad={false}\n videoPriority\n heading={headingNode}\n bullets={bullets}\n cta={cta}\n ctaPosition={props.ctaPosition || 'left'}\n height={props.height || 560}\n rounded={props.rounded || ''}\n leftTint={props.leftTint !== false}\n contentMaxWidth={props.contentMaxWidth || 720}\n className={props.className || ''}\n />\n </div>\n </div>\n );\n};\n"],"names":[],"mappings":";;;;;AAMA,SAAS,oBAAoB,KAAc,UAAU,MAAM;AACzD,YAAU,MAAM;AACd,QAAI,CAAC,OAAO,CAAC,QAAS;AAEtB,UAAM,WAAW,SAAS,cAA+B,2BAA2B,GAAG,IAAI;AAC3F,QAAI,SAAU;AAEd,UAAM,OAAO,SAAS,cAAc,MAAM;AAC1C,SAAK,MAAM;AACX,SAAK,KAAK;AACV,SAAK,OAAO;AACZ,SAAK,aAAa,iBAAiB,MAAM;AACzC,SAAK,aAAa,qBAAqB,GAAG;AAC1C,aAAS,KAAK,YAAY,IAAI;AAE9B,WAAO,MAAM;AACX,WAAK,OAAA;AAAA,IACP;AAAA,EACF,GAAG,CAAC,KAAK,OAAO,CAAC;AACnB;AAEO,MAAM,kCAGR,CAAC,EAAE,OAAO,aAAa,eAAe,YAAY;AACrD,QAAM,UAAiB,MAAM,QAAQ,MAAM,OAAO,IAAI,MAAM,UAAU,CAAA;AAEtE,MAAI,cAA+B,MAAM,WAAW;AACpD,MAAI,OAAO,gBAAgB,YAAY,YAAY,SAAS,IAAI,GAAG;AACjE,UAAM,QAAS,YAAuB,MAAM,IAAI;AAChD,kBACE,oBAAA,UAAA,EACG,gBAAM,IAAI,CAAC,MAAc,MACxB,qBAAC,MAAM,UAAN,EACE,UAAA;AAAA,MAAA,IAAI,yBAAM,MAAA,CAAA,CAAG;AAAA,MACb;AAAA,IAAA,KAFkB,CAGrB,CACD,GACH;AAAA,EAEJ;AAEA,QAAM,MACJ,MAAM,WACF;AAAA,IACE,OAAO,MAAM;AAAA,IACb,MAAM,MAAM;AAAA,IACZ,MAAM,MAAM;AAAA,IACZ,iBAAiB,MAAM;AAAA,IACvB,WAAW,MAAM;AAAA,EAAA,IAEnB;AAEN,QAAM,aAAa,gBAAgB,MAAM,YAAY,MAAM,aAAa;AAGxE,sBAAoB,YAAY,QAAQ,UAAU,CAAC;AAEnD,SACE,oBAAC,OAAA,EAAI,WAAW,6BAA6B,MAAM,oBAAoB,EAAE,IACvE,UAAA,qBAAC,OAAA,EAAI,WAAW,MAAM,sBAAsB,IACzC,UAAA;AAAA,IAAA,MAAM,aAAa,oBAAC,SAAA,EAAO,UAAA,MAAM,WAAU;AAAA,IAC5C;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,eAAe,QAAQ,UAAU;AAAA,QACjC,YAAU;AAAA,QACV,WAAW,MAAM,cAAc;AAAA,QAC/B,eAAe;AAAA,QACf,eAAa;AAAA,QACb,SAAS;AAAA,QACT;AAAA,QACA;AAAA,QACA,aAAa,MAAM,eAAe;AAAA,QAClC,QAAQ,MAAM,UAAU;AAAA,QACxB,SAAS,MAAM,WAAW;AAAA,QAC1B,UAAU,MAAM,aAAa;AAAA,QAC7B,iBAAiB,MAAM,mBAAmB;AAAA,QAC1C,WAAW,MAAM,aAAa;AAAA,MAAA;AAAA,IAAA;AAAA,EAChC,EAAA,CACF,EAAA,CACF;AAEJ;"}
@@ -0,0 +1,4 @@
1
+ import { default as React } from 'react';
2
+ export declare const ReviewsListRender: React.FC<{
3
+ props: Record<string, any>;
4
+ }>;
@@ -0,0 +1,172 @@
1
+ "use client";
2
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
3
+ import React, { useState } from "react";
4
+ const resolveContainerMargin = (position) => {
5
+ switch (position) {
6
+ case "left":
7
+ return { marginLeft: 0, marginRight: "auto" };
8
+ case "right":
9
+ return { marginLeft: "auto", marginRight: 0 };
10
+ default:
11
+ return { marginLeft: "auto", marginRight: "auto" };
12
+ }
13
+ };
14
+ const StarRating = ({
15
+ rating,
16
+ total,
17
+ color,
18
+ size
19
+ }) => /* @__PURE__ */ jsx(Fragment, { children: Array.from({ length: total }).map((_, i) => /* @__PURE__ */ jsx(
20
+ "svg",
21
+ {
22
+ width: size,
23
+ height: size,
24
+ viewBox: "0 0 20 20",
25
+ fill: i < rating ? color : "#d1d5db",
26
+ style: { marginRight: "2px" },
27
+ xmlns: "http://www.w3.org/2000/svg",
28
+ children: /* @__PURE__ */ jsx("path", { d: "M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.286 3.967a1 1 0 00.95.69h4.174c.969 0 1.371 1.24.588 1.81l-3.377 2.454a1 1 0 00-.364 1.118l1.286 3.967c.3.921-.755 1.688-1.54 1.118L10 14.347l-3.377 2.454c-.784.57-1.838-.197-1.54-1.118l1.287-3.967a1 1 0 00-.364-1.118L2.629 9.394c-.783-.57-.38-1.81.588-1.81h4.174a1 1 0 00.95-.69l1.287-3.967z" })
29
+ },
30
+ i
31
+ )) });
32
+ const QARow = ({ item, props, isLast }) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
33
+ /* @__PURE__ */ jsxs("div", { style: { paddingTop: props.qaRowPaddingY, paddingBottom: props.qaRowPaddingY }, children: [
34
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "flex-start", marginBottom: "6px" }, children: [
35
+ /* @__PURE__ */ jsxs("span", { style: {
36
+ color: props.qaLabelColor,
37
+ fontSize: props.qaLabelFontSize,
38
+ fontWeight: props.qaLabelFontWeight,
39
+ minWidth: "20px",
40
+ marginRight: "6px",
41
+ flexShrink: 0
42
+ }, children: [
43
+ props.qaLabelQ,
44
+ ":"
45
+ ] }),
46
+ /* @__PURE__ */ jsx("span", { style: {
47
+ color: props.qaQuestionColor,
48
+ fontSize: props.qaQuestionFontSize,
49
+ fontWeight: props.qaQuestionFontWeight || "700",
50
+ lineHeight: "1.5"
51
+ }, children: item.question })
52
+ ] }),
53
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "flex-start", marginBottom: "8px" }, children: [
54
+ /* @__PURE__ */ jsxs("span", { style: {
55
+ color: props.qaLabelColor,
56
+ fontSize: props.qaLabelFontSize,
57
+ fontWeight: props.qaLabelFontWeight,
58
+ minWidth: "20px",
59
+ marginRight: "6px",
60
+ flexShrink: 0
61
+ }, children: [
62
+ props.qaLabelA,
63
+ ":"
64
+ ] }),
65
+ /* @__PURE__ */ jsx("span", { style: {
66
+ color: props.qaAnswerColor,
67
+ fontSize: props.qaAnswerFontSize,
68
+ lineHeight: "1.5"
69
+ }, children: item.answer })
70
+ ] }),
71
+ /* @__PURE__ */ jsxs("div", { style: {
72
+ color: props.qaAskedByColor,
73
+ fontSize: props.qaAskedByFontSize,
74
+ display: "flex",
75
+ alignItems: "center",
76
+ gap: "4px"
77
+ }, children: [
78
+ /* @__PURE__ */ jsx("span", { children: props.qaAskedByPrefix }),
79
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: "600", color: props.qaAskedByColor }, children: item.author }),
80
+ /* @__PURE__ */ jsx("span", { children: props.qaMetaSeparator }),
81
+ /* @__PURE__ */ jsx("span", { children: item.date })
82
+ ] })
83
+ ] }),
84
+ !isLast && /* @__PURE__ */ jsx("div", { style: { height: props.dividerHeight, backgroundColor: props.dividerColor, width: "100%" } })
85
+ ] });
86
+ const ReviewsListRender = ({ props }) => {
87
+ const [activeTab, setActiveTab] = useState(props.activeTabKey || "reviews");
88
+ const [currentPage, setCurrentPage] = useState(1);
89
+ const reviews = props.reviews || [];
90
+ const qaItems = props.qa || [];
91
+ const containerMargin = resolveContainerMargin(props.containerPosition || "center");
92
+ return /* @__PURE__ */ jsx("section", { className: props.sectionClassName, style: { backgroundColor: props.sectionBgColor }, children: /* @__PURE__ */ jsxs(
93
+ "div",
94
+ {
95
+ className: props.containerClassName,
96
+ style: { maxWidth: props.containerMaxWidth, ...containerMargin },
97
+ children: [
98
+ props.tabsEnabled && props.tabs?.length > 0 && /* @__PURE__ */ jsx("div", { className: props.tabsWrapperClassName, children: props.tabs.map((tab) => {
99
+ const isActive = activeTab === tab.key;
100
+ return /* @__PURE__ */ jsx(
101
+ "span",
102
+ {
103
+ onClick: () => setActiveTab(tab.key),
104
+ className: isActive ? props.activeTabClassName : props.inactiveTabClassName,
105
+ style: {
106
+ color: isActive ? props.activeTabColor : props.inactiveTabColor,
107
+ fontSize: isActive ? props.activeTabFontSize : props.inactiveTabFontSize,
108
+ borderBottomColor: isActive ? props.activeTabBorderColor : "transparent",
109
+ borderBottomStyle: "solid",
110
+ borderBottomWidth: isActive ? "2px" : "0",
111
+ paddingBottom: "8px",
112
+ marginRight: "16px",
113
+ cursor: "pointer",
114
+ display: "inline-block",
115
+ userSelect: "none"
116
+ },
117
+ children: tab.label
118
+ },
119
+ tab.key
120
+ );
121
+ }) }),
122
+ activeTab === "reviews" && /* @__PURE__ */ jsxs(Fragment, { children: [
123
+ reviews.map((review, index) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
124
+ /* @__PURE__ */ jsxs(
125
+ "div",
126
+ {
127
+ className: props.reviewRowClassName,
128
+ style: { display: "flex", paddingTop: "16px", paddingBottom: "16px" },
129
+ children: [
130
+ /* @__PURE__ */ jsx("div", { className: props.leftColumnClassName, style: { width: props.leftColumnWidth, flexShrink: 0 }, children: /* @__PURE__ */ jsx("span", { className: props.reviewerNameClassName, style: { color: props.reviewerNameColor, fontSize: props.reviewerNameFontSize, display: "block" }, children: review.reviewer }) }),
131
+ /* @__PURE__ */ jsxs("div", { className: props.middleColumnClassName, style: { flex: 1, paddingLeft: "12px", paddingRight: "12px" }, children: [
132
+ /* @__PURE__ */ jsx("div", { className: props.starsWrapperClassName, style: { display: "flex", alignItems: "center", marginBottom: "4px" }, children: /* @__PURE__ */ jsx(StarRating, { rating: review.rating, total: props.starCount || 5, color: props.starColor, size: props.starSize }) }),
133
+ /* @__PURE__ */ jsx("div", { className: props.reviewTitleClassName, style: { color: props.reviewTitleColor, fontSize: props.reviewTitleFontSize, fontWeight: "700", marginBottom: "4px" }, children: review.title }),
134
+ /* @__PURE__ */ jsx("div", { className: props.reviewBodyClassName, style: { color: props.reviewBodyColor, fontSize: props.reviewBodyFontSize, lineHeight: "1.5" }, children: review.body })
135
+ ] }),
136
+ /* @__PURE__ */ jsxs("div", { className: props.rightColumnClassName, style: { width: props.rightColumnWidth, flexShrink: 0, textAlign: "right" }, children: [
137
+ /* @__PURE__ */ jsx("div", { className: props.dateClassName, style: { color: props.dateColor, fontSize: props.dateFontSize, marginBottom: "4px" }, children: review.date }),
138
+ props.verifiedBadgeEnabled && review.verified && /* @__PURE__ */ jsx("div", { className: props.verifiedBadgeClassName, style: { color: props.verifiedBadgeColor, fontSize: props.verifiedBadgeFontSize }, children: props.verifiedBadgeText })
139
+ ] })
140
+ ]
141
+ }
142
+ ),
143
+ index < reviews.length - 1 && /* @__PURE__ */ jsx("div", { style: { height: props.dividerHeight, backgroundColor: props.dividerColor, width: "100%" } })
144
+ ] }, index)),
145
+ props.paginationEnabled && props.totalPages > 1 && /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center", alignItems: "center", gap: "6px", paddingTop: "16px", paddingBottom: "8px" }, children: Array.from({ length: props.totalPages }).map((_, i) => {
146
+ const page = i + 1;
147
+ const isActive = currentPage === page;
148
+ return /* @__PURE__ */ jsx("button", { onClick: () => setCurrentPage(page), style: {
149
+ width: props.paginationSize,
150
+ height: props.paginationSize,
151
+ borderRadius: props.paginationBorderRadius,
152
+ fontSize: props.paginationFontSize,
153
+ backgroundColor: isActive ? props.paginationActiveBgColor : props.paginationInactiveBgColor,
154
+ color: isActive ? props.paginationActiveColor : props.paginationInactiveColor,
155
+ border: "none",
156
+ cursor: "pointer",
157
+ display: "flex",
158
+ alignItems: "center",
159
+ justifyContent: "center",
160
+ fontWeight: isActive ? "700" : "500"
161
+ }, children: page }, page);
162
+ }) })
163
+ ] }),
164
+ activeTab === "qa" && /* @__PURE__ */ jsx(Fragment, { children: qaItems.length === 0 ? /* @__PURE__ */ jsx("div", { style: { color: "#9ca3af", fontSize: "0.9rem", padding: "24px 0" }, children: "No questions yet." }) : qaItems.map((item, index) => /* @__PURE__ */ jsx(QARow, { item, props, isLast: index === qaItems.length - 1 }, index)) })
165
+ ]
166
+ }
167
+ ) });
168
+ };
169
+ export {
170
+ ReviewsListRender
171
+ };
172
+ //# sourceMappingURL=VarietyReviewslistComponent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VarietyReviewslistComponent.js","sources":["../../../src/components/landing/VarietyReviewslistComponent.tsx"],"sourcesContent":["\"use client\"\nimport React, { useState } from \"react\";\n\n\n\n\nconst resolveContainerMargin = (position: string): React.CSSProperties => {\n switch (position) {\n case \"left\": return { marginLeft: 0, marginRight: \"auto\" };\n case \"right\": return { marginLeft: \"auto\", marginRight: 0 };\n default: return { marginLeft: \"auto\", marginRight: \"auto\" }; // center\n }\n};\n\n// ─── Star renderer ────────────────────────────────────────────────────────────\nconst StarRating: React.FC<{ rating: number; total: number; color: string; size: string }> = ({\n rating, total, color, size,\n}) => (\n <>\n {Array.from({ length: total }).map((_, i) => (\n <svg key={i} width={size} height={size} viewBox=\"0 0 20 20\"\n fill={i < rating ? color : \"#d1d5db\"}\n style={{ marginRight: \"2px\" }}\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.286 3.967a1 1 0 00.95.69h4.174c.969 0 1.371 1.24.588 1.81l-3.377 2.454a1 1 0 00-.364 1.118l1.286 3.967c.3.921-.755 1.688-1.54 1.118L10 14.347l-3.377 2.454c-.784.57-1.838-.197-1.54-1.118l1.287-3.967a1 1 0 00-.364-1.118L2.629 9.394c-.783-.57-.38-1.81.588-1.81h4.174a1 1 0 00.95-.69l1.287-3.967z\" />\n </svg>\n ))}\n </>\n);\n\n// ─── Q&A row ─────────────────────────────────────────────────────────────────\nconst QARow: React.FC<{ item: any; props: any; isLast: boolean }> = ({ item, props, isLast }) => (\n <React.Fragment>\n <div style={{ paddingTop: props.qaRowPaddingY, paddingBottom: props.qaRowPaddingY }}>\n\n {/* Q line */}\n <div style={{ display: \"flex\", alignItems: \"flex-start\", marginBottom: \"6px\" }}>\n <span style={{\n color: props.qaLabelColor,\n fontSize: props.qaLabelFontSize,\n fontWeight: props.qaLabelFontWeight,\n minWidth: \"20px\",\n marginRight: \"6px\",\n flexShrink: 0,\n }}>\n {props.qaLabelQ}:\n </span>\n <span style={{\n color: props.qaQuestionColor,\n fontSize: props.qaQuestionFontSize,\n fontWeight: props.qaQuestionFontWeight || \"700\",\n lineHeight: \"1.5\",\n }}>\n {item.question}\n </span>\n </div>\n\n {/* A line */}\n <div style={{ display: \"flex\", alignItems: \"flex-start\", marginBottom: \"8px\" }}>\n <span style={{\n color: props.qaLabelColor,\n fontSize: props.qaLabelFontSize,\n fontWeight: props.qaLabelFontWeight,\n minWidth: \"20px\",\n marginRight: \"6px\",\n flexShrink: 0,\n }}>\n {props.qaLabelA}:\n </span>\n <span style={{\n color: props.qaAnswerColor,\n fontSize: props.qaAnswerFontSize,\n lineHeight: \"1.5\",\n }}>\n {item.answer}\n </span>\n </div>\n\n {/* Meta — \"Asked by Ahmed • 12/08/25\" */}\n <div style={{\n color: props.qaAskedByColor,\n fontSize: props.qaAskedByFontSize,\n display: \"flex\",\n alignItems: \"center\",\n gap: \"4px\",\n }}>\n <span>{props.qaAskedByPrefix}</span>\n <span style={{ fontWeight: \"600\", color: props.qaAskedByColor }}>{item.author}</span>\n <span>{props.qaMetaSeparator}</span>\n <span>{item.date}</span>\n </div>\n </div>\n\n {/* Divider */}\n {!isLast && (\n <div style={{ height: props.dividerHeight, backgroundColor: props.dividerColor, width: \"100%\" }} />\n )}\n </React.Fragment>\n);\n\n// ─── Main render ──────────────────────────────────────────────────────────────\nexport const ReviewsListRender: React.FC<{ props: Record<string, any> }> = ({ props }) => {\n\n const [activeTab, setActiveTab] = useState<string>(props.activeTabKey || \"reviews\");\n const [currentPage, setCurrentPage] = useState<number>(1);\n\n const reviews: any[] = props.reviews || [];\n const qaItems: any[] = props.qa || [];\n\n const containerMargin = resolveContainerMargin(props.containerPosition || \"center\");\n\n return (\n <section className={props.sectionClassName} style={{ backgroundColor: props.sectionBgColor }}>\n <div\n className={props.containerClassName}\n style={{ maxWidth: props.containerMaxWidth, ...containerMargin }}\n >\n {/* ── Tabs ── */}\n {props.tabsEnabled && props.tabs?.length > 0 && (\n <div className={props.tabsWrapperClassName}>\n {props.tabs.map((tab: { label: string; key: string }) => {\n const isActive = activeTab === tab.key;\n return (\n <span\n key={tab.key}\n onClick={() => setActiveTab(tab.key)}\n className={isActive ? props.activeTabClassName : props.inactiveTabClassName}\n style={{\n color: isActive ? props.activeTabColor : props.inactiveTabColor,\n fontSize: isActive ? props.activeTabFontSize : props.inactiveTabFontSize,\n borderBottomColor: isActive ? props.activeTabBorderColor : \"transparent\",\n borderBottomStyle: \"solid\",\n borderBottomWidth: isActive ? \"2px\" : \"0\",\n paddingBottom: \"8px\",\n marginRight: \"16px\",\n cursor: \"pointer\",\n display: \"inline-block\",\n userSelect: \"none\",\n }}\n >\n {tab.label}\n </span>\n );\n })}\n </div>\n )}\n\n {/* ── Reviews tab ── */}\n {activeTab === \"reviews\" && (\n <>\n {reviews.map((review: any, index: number) => (\n <React.Fragment key={index}>\n <div\n className={props.reviewRowClassName}\n style={{ display: \"flex\", paddingTop: \"16px\", paddingBottom: \"16px\" }}\n >\n {/* Left — Reviewer name */}\n <div className={props.leftColumnClassName} style={{ width: props.leftColumnWidth, flexShrink: 0 }}>\n <span className={props.reviewerNameClassName} style={{ color: props.reviewerNameColor, fontSize: props.reviewerNameFontSize, display: \"block\" }}>\n {review.reviewer}\n </span>\n </div>\n\n {/* Middle — Stars, title, body */}\n <div className={props.middleColumnClassName} style={{ flex: 1, paddingLeft: \"12px\", paddingRight: \"12px\" }}>\n <div className={props.starsWrapperClassName} style={{ display: \"flex\", alignItems: \"center\", marginBottom: \"4px\" }}>\n <StarRating rating={review.rating} total={props.starCount || 5} color={props.starColor} size={props.starSize} />\n </div>\n <div className={props.reviewTitleClassName} style={{ color: props.reviewTitleColor, fontSize: props.reviewTitleFontSize, fontWeight: \"700\", marginBottom: \"4px\" }}>\n {review.title}\n </div>\n <div className={props.reviewBodyClassName} style={{ color: props.reviewBodyColor, fontSize: props.reviewBodyFontSize, lineHeight: \"1.5\" }}>\n {review.body}\n </div>\n </div>\n\n {/* Right — Date + Verified */}\n <div className={props.rightColumnClassName} style={{ width: props.rightColumnWidth, flexShrink: 0, textAlign: \"right\" }}>\n <div className={props.dateClassName} style={{ color: props.dateColor, fontSize: props.dateFontSize, marginBottom: \"4px\" }}>\n {review.date}\n </div>\n {props.verifiedBadgeEnabled && review.verified && (\n <div className={props.verifiedBadgeClassName} style={{ color: props.verifiedBadgeColor, fontSize: props.verifiedBadgeFontSize }}>\n {props.verifiedBadgeText}\n </div>\n )}\n </div>\n </div>\n\n {index < reviews.length - 1 && (\n <div style={{ height: props.dividerHeight, backgroundColor: props.dividerColor, width: \"100%\" }} />\n )}\n </React.Fragment>\n ))}\n\n {/* Pagination */}\n {props.paginationEnabled && props.totalPages > 1 && (\n <div style={{ display: \"flex\", justifyContent: \"center\", alignItems: \"center\", gap: \"6px\", paddingTop: \"16px\", paddingBottom: \"8px\" }}>\n {Array.from({ length: props.totalPages }).map((_, i) => {\n const page = i + 1;\n const isActive = currentPage === page;\n return (\n <button key={page} onClick={() => setCurrentPage(page)} style={{\n width: props.paginationSize,\n height: props.paginationSize,\n borderRadius: props.paginationBorderRadius,\n fontSize: props.paginationFontSize,\n backgroundColor: isActive ? props.paginationActiveBgColor : props.paginationInactiveBgColor,\n color: isActive ? props.paginationActiveColor : props.paginationInactiveColor,\n border: \"none\",\n cursor: \"pointer\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n fontWeight: isActive ? \"700\" : \"500\",\n }}>\n {page}\n </button>\n );\n })}\n </div>\n )}\n </>\n )}\n\n {/* ── Q&A tab ── */}\n {activeTab === \"qa\" && (\n <>\n {qaItems.length === 0 ? (\n <div style={{ color: \"#9ca3af\", fontSize: \"0.9rem\", padding: \"24px 0\" }}>\n No questions yet.\n </div>\n ) : (\n qaItems.map((item: any, index: number) => (\n <QARow key={index} item={item} props={props} isLast={index === qaItems.length - 1} />\n ))\n )}\n </>\n )}\n </div>\n </section>\n );\n};"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -2,10 +2,21 @@ export { BlogPost, type BlogPostProps } from './components/BlogPost';
2
2
  export { BlogPostHeader, type BlogPostHeaderProps } from './components/BlogPostHeader';
3
3
  export { BlogList, type BlogListProps } from './components/BlogList';
4
4
  export { JsonLd } from './components/JsonLd';
5
+ export { default as HeroFeatureBanner } from './components/landing/HeroFeatureBanner';
6
+ export type { HeroFeatureBannerProps, HeroBullet } from './components/landing/HeroFeatureBanner';
7
+ export { HeroFeatureBannerComponent } from './components/landing/HeroFeatureBannerComponent';
8
+ export { default as HeroFeatureBannerVideo } from './components/landing/HeroFeatureBannerVideo';
9
+ export type { HeroFeatureBannerVideoProps } from './components/landing/HeroFeatureBannerVideo';
10
+ export { HeroFeatureBannerVideoComponent } from './components/landing/HeroFeatureBannerVideoComponent';
11
+ export { ReviewsListRender } from './components/landing/VarietyReviewslistComponent';
12
+ export { BlogTwoColumnComponent, BlogThreeColumnComponent, BlogContainerComponent, } from './components/landing/BlogLayoutComponents';
13
+ export { FlexibleLayoutComponent } from './components/landing/FlexibleLayoutComponent';
5
14
  export { BlockRenderer, renderBlocks, type BlockRendererProps } from './render/BlockRenderer';
15
+ export { LandingPageRenderer } from './render/LandingPageRenderer';
6
16
  export { configureBlog, getDefaultConfig, resolveConfig, type BlogConfig } from './config';
7
17
  export { createBlogClient, getPostBySlug, getPosts, getPostsByCategory, getPublishedSlugs, getCategories, incrementView, type BlogClient, type GetPostsParams, } from './client/blogClient';
8
18
  export { generateBlogPostMetadata, generateBlogListMetadata, generateBlogPostJsonLd, generateBlogBreadcrumbJsonLd, type BlogMetadata, type SeoOptions, } from './seo';
9
19
  export { formatDate } from './lib/format';
20
+ export { resolveMediaSrc } from './lib/resolveMediaSrc';
10
21
  export { postHasLandingPageBody } from './lib/postUtils';
11
22
  export type * from './types/blog';
package/dist/index.js CHANGED
@@ -2,18 +2,37 @@ import { BlogPost } from "./components/BlogPost.js";
2
2
  import { BlogPostHeader } from "./components/BlogPostHeader.js";
3
3
  import { BlogList } from "./components/BlogList.js";
4
4
  import { JsonLd } from "./components/JsonLd.js";
5
+ import { default as default2 } from "./components/landing/HeroFeatureBanner.js";
6
+ import { HeroFeatureBannerComponent } from "./components/landing/HeroFeatureBannerComponent.js";
7
+ import { default as default3 } from "./components/landing/HeroFeatureBannerVideo.js";
8
+ import { HeroFeatureBannerVideoComponent } from "./components/landing/HeroFeatureBannerVideoComponent.js";
9
+ import { ReviewsListRender } from "./components/landing/VarietyReviewslistComponent.js";
10
+ import { BlogContainerComponent, BlogThreeColumnComponent, BlogTwoColumnComponent } from "./components/landing/BlogLayoutComponents.js";
11
+ import { FlexibleLayoutComponent } from "./components/landing/FlexibleLayoutComponent.js";
5
12
  import { BlockRenderer, renderBlocks } from "./render/BlockRenderer.js";
13
+ import { LandingPageRenderer } from "./render/LandingPageRenderer.js";
6
14
  import { configureBlog, getDefaultConfig, resolveConfig } from "./config.js";
7
15
  import { createBlogClient, getCategories, getPostBySlug, getPosts, getPostsByCategory, getPublishedSlugs, incrementView } from "./client/blogClient.js";
8
16
  import { generateBlogBreadcrumbJsonLd, generateBlogListMetadata, generateBlogPostJsonLd, generateBlogPostMetadata } from "./seo/index.js";
9
17
  import { formatDate } from "./lib/format.js";
18
+ import { resolveMediaSrc } from "./lib/resolveMediaSrc.js";
10
19
  import { postHasLandingPageBody } from "./lib/postUtils.js";
11
20
  export {
12
21
  BlockRenderer,
22
+ BlogContainerComponent,
13
23
  BlogList,
14
24
  BlogPost,
15
25
  BlogPostHeader,
26
+ BlogThreeColumnComponent,
27
+ BlogTwoColumnComponent,
28
+ FlexibleLayoutComponent,
29
+ default2 as HeroFeatureBanner,
30
+ HeroFeatureBannerComponent,
31
+ default3 as HeroFeatureBannerVideo,
32
+ HeroFeatureBannerVideoComponent,
16
33
  JsonLd,
34
+ LandingPageRenderer,
35
+ ReviewsListRender,
17
36
  configureBlog,
18
37
  createBlogClient,
19
38
  formatDate,
@@ -30,6 +49,7 @@ export {
30
49
  incrementView,
31
50
  postHasLandingPageBody,
32
51
  renderBlocks,
33
- resolveConfig
52
+ resolveConfig,
53
+ resolveMediaSrc
34
54
  };
35
55
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,2 @@
1
+ /** Resolve a media URL from a string field and/or its companion *New upload object. */
2
+ export declare function resolveMediaSrc(primary?: unknown, secondary?: unknown): string | undefined;
@@ -0,0 +1,21 @@
1
+ function urlFromValue(value) {
2
+ if (typeof value === "string") {
3
+ const trimmed = value.trim();
4
+ return trimmed || void 0;
5
+ }
6
+ if (value && typeof value === "object" && "url" in value) {
7
+ const url = value?.url;
8
+ if (typeof url === "string") {
9
+ const trimmed = url.trim();
10
+ return trimmed || void 0;
11
+ }
12
+ }
13
+ return void 0;
14
+ }
15
+ function resolveMediaSrc(primary, secondary) {
16
+ return urlFromValue(primary) ?? urlFromValue(secondary);
17
+ }
18
+ export {
19
+ resolveMediaSrc
20
+ };
21
+ //# sourceMappingURL=resolveMediaSrc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveMediaSrc.js","sources":["../../src/lib/resolveMediaSrc.ts"],"sourcesContent":["type MediaLike = { url?: unknown } | null | undefined;\n\nfunction urlFromValue(value: unknown): string | undefined {\n if (typeof value === 'string') {\n const trimmed = value.trim();\n return trimmed || undefined;\n }\n\n if (value && typeof value === 'object' && 'url' in value) {\n const url = (value as MediaLike)?.url;\n if (typeof url === 'string') {\n const trimmed = url.trim();\n return trimmed || undefined;\n }\n }\n\n return undefined;\n}\n\n/** Resolve a media URL from a string field and/or its companion *New upload object. */\nexport function resolveMediaSrc(primary?: unknown, secondary?: unknown): string | undefined {\n return urlFromValue(primary) ?? urlFromValue(secondary);\n}\n"],"names":[],"mappings":"AAEA,SAAS,aAAa,OAAoC;AACxD,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,UAAU,MAAM,KAAA;AACtB,WAAO,WAAW;AAAA,EACpB;AAEA,MAAI,SAAS,OAAO,UAAU,YAAY,SAAS,OAAO;AACxD,UAAM,MAAO,OAAqB;AAClC,QAAI,OAAO,QAAQ,UAAU;AAC3B,YAAM,UAAU,IAAI,KAAA;AACpB,aAAO,WAAW;AAAA,IACpB;AAAA,EACF;AAEA,SAAO;AACT;AAGO,SAAS,gBAAgB,SAAmB,WAAyC;AAC1F,SAAO,aAAa,OAAO,KAAK,aAAa,SAAS;AACxD;"}
@@ -1,5 +1,6 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { animationClass, blockSettingsToStyle } from "./blockStyle.js";
3
+ import { RichText } from "./RichText.js";
3
4
  import TextBlock from "./blocks/TextBlock.js";
4
5
  import HeadingBlock from "./blocks/HeadingBlock.js";
5
6
  import ImageBlock from "./blocks/ImageBlock.js";
@@ -41,6 +42,10 @@ function renderBlockContent(block) {
41
42
  return /* @__PURE__ */ jsx(ButtonBlock, { content: block.content });
42
43
  case "embed":
43
44
  return /* @__PURE__ */ jsx(EmbedBlock, { content: block.content });
45
+ case "html": {
46
+ const { html } = block.content ?? {};
47
+ return html ? /* @__PURE__ */ jsx(RichText, { className: "ggblog-html", html }) : null;
48
+ }
44
49
  case "gallery":
45
50
  return /* @__PURE__ */ jsx(GalleryBlock, { content: block.content });
46
51
  case "table":
@@ -1 +1 @@
1
- {"version":3,"file":"BlockRenderer.js","sources":["../../src/render/BlockRenderer.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport type {\n ContentBlock,\n TextBlockContent,\n HeadingBlockContent,\n ImageBlockContent,\n VideoBlockContent,\n QuoteBlockContent,\n CodeBlockContent,\n ListBlockContent,\n ButtonBlockContent,\n EmbedBlockContent,\n GalleryBlockContent,\n TableBlockContent,\n AccordionBlockContent,\n TabsBlockContent,\n AlertBlockContent,\n ColumnsBlockContent,\n SpacerBlockContent,\n DividerBlockContent,\n} from '../types/blog';\nimport { blockSettingsToStyle, animationClass } from './blockStyle';\nimport TextBlock from './blocks/TextBlock';\nimport HeadingBlock from './blocks/HeadingBlock';\nimport ImageBlock from './blocks/ImageBlock';\nimport VideoBlock from './blocks/VideoBlock';\nimport DividerBlock from './blocks/DividerBlock';\nimport SpacerBlock from './blocks/SpacerBlock';\nimport QuoteBlock from './blocks/QuoteBlock';\nimport CodeBlock from './blocks/CodeBlock';\nimport ListBlock from './blocks/ListBlock';\nimport ButtonBlock from './blocks/ButtonBlock';\nimport EmbedBlock from './blocks/EmbedBlock';\nimport GalleryBlock from './blocks/GalleryBlock';\nimport TableBlock from './blocks/TableBlock';\nimport AlertBlock from './blocks/AlertBlock';\nimport ColumnsBlock from './blocks/ColumnsBlock';\nimport AccordionBlock from './blocks/AccordionBlock';\nimport TabsBlock from './blocks/TabsBlock';\n\n/** Maps a single block to its read-only renderer. */\nfunction renderBlockContent(block: ContentBlock): ReactNode {\n switch (block.type) {\n case 'text':\n return <TextBlock content={block.content as TextBlockContent} />;\n case 'heading':\n return <HeadingBlock content={block.content as HeadingBlockContent} />;\n case 'image':\n return <ImageBlock content={block.content as ImageBlockContent} />;\n case 'video':\n return <VideoBlock content={block.content as VideoBlockContent} />;\n case 'divider':\n return <DividerBlock content={block.content as DividerBlockContent} />;\n case 'spacer':\n return <SpacerBlock content={block.content as SpacerBlockContent} />;\n case 'quote':\n return <QuoteBlock content={block.content as QuoteBlockContent} />;\n case 'code':\n return <CodeBlock content={block.content as CodeBlockContent} />;\n case 'list':\n return <ListBlock content={block.content as ListBlockContent} />;\n case 'button':\n return <ButtonBlock content={block.content as ButtonBlockContent} />;\n case 'embed':\n return <EmbedBlock content={block.content as EmbedBlockContent} />;\n case 'gallery':\n return <GalleryBlock content={block.content as GalleryBlockContent} />;\n case 'table':\n return <TableBlock content={block.content as TableBlockContent} />;\n case 'alert':\n return <AlertBlock content={block.content as AlertBlockContent} />;\n case 'accordion':\n return <AccordionBlock content={block.content as AccordionBlockContent} />;\n case 'tabs':\n return <TabsBlock content={block.content as TabsBlockContent} />;\n case 'columns':\n return <ColumnsBlock content={block.content as ColumnsBlockContent} />;\n default:\n return null;\n }\n}\n\nexport interface BlockRendererProps {\n block: ContentBlock;\n}\n\n/** Renders one content block wrapped with its layout settings. */\nexport function BlockRenderer({ block }: BlockRendererProps) {\n const className = `ggblog-block ggblog-block-${block.type} ${animationClass(block.settings)}`.trim();\n return (\n <div\n className={className}\n style={blockSettingsToStyle(block.settings)}\n data-block-type={block.type}\n data-block-id={block.id}\n >\n {renderBlockContent(block)}\n </div>\n );\n}\n\n/** Renders an ordered list of content blocks (used by posts and columns). */\nexport function renderBlocks(blocks: ContentBlock[]): ReactNode {\n return blocks.map((block) => <BlockRenderer key={block.id} block={block} />);\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAyCA,SAAS,mBAAmB,OAAgC;AAC1D,UAAQ,MAAM,MAAA;AAAA,IACZ,KAAK;AACH,aAAO,oBAAC,WAAA,EAAU,SAAS,MAAM,QAAA,CAA6B;AAAA,IAChE,KAAK;AACH,aAAO,oBAAC,cAAA,EAAa,SAAS,MAAM,QAAA,CAAgC;AAAA,IACtE,KAAK;AACH,aAAO,oBAAC,YAAA,EAAW,SAAS,MAAM,QAAA,CAA8B;AAAA,IAClE,KAAK;AACH,aAAO,oBAAC,YAAA,EAAW,SAAS,MAAM,QAAA,CAA8B;AAAA,IAClE,KAAK;AACH,aAAO,oBAAC,cAAA,EAAa,SAAS,MAAM,QAAA,CAAgC;AAAA,IACtE,KAAK;AACH,aAAO,oBAAC,aAAA,EAAY,SAAS,MAAM,QAAA,CAA+B;AAAA,IACpE,KAAK;AACH,aAAO,oBAAC,YAAA,EAAW,SAAS,MAAM,QAAA,CAA8B;AAAA,IAClE,KAAK;AACH,aAAO,oBAAC,WAAA,EAAU,SAAS,MAAM,QAAA,CAA6B;AAAA,IAChE,KAAK;AACH,aAAO,oBAAC,WAAA,EAAU,SAAS,MAAM,QAAA,CAA6B;AAAA,IAChE,KAAK;AACH,aAAO,oBAAC,aAAA,EAAY,SAAS,MAAM,QAAA,CAA+B;AAAA,IACpE,KAAK;AACH,aAAO,oBAAC,YAAA,EAAW,SAAS,MAAM,QAAA,CAA8B;AAAA,IAClE,KAAK;AACH,aAAO,oBAAC,cAAA,EAAa,SAAS,MAAM,QAAA,CAAgC;AAAA,IACtE,KAAK;AACH,aAAO,oBAAC,YAAA,EAAW,SAAS,MAAM,QAAA,CAA8B;AAAA,IAClE,KAAK;AACH,aAAO,oBAAC,YAAA,EAAW,SAAS,MAAM,QAAA,CAA8B;AAAA,IAClE,KAAK;AACH,aAAO,oBAAC,gBAAA,EAAe,SAAS,MAAM,QAAA,CAAkC;AAAA,IAC1E,KAAK;AACH,aAAO,oBAAC,WAAA,EAAU,SAAS,MAAM,QAAA,CAA6B;AAAA,IAChE,KAAK;AACH,aAAO,oBAAC,cAAA,EAAa,SAAS,MAAM,QAAA,CAAgC;AAAA,IACtE;AACE,aAAO;AAAA,EAAA;AAEb;AAOO,SAAS,cAAc,EAAE,SAA6B;AAC3D,QAAM,YAAY,6BAA6B,MAAM,IAAI,IAAI,eAAe,MAAM,QAAQ,CAAC,GAAG,KAAA;AAC9F,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,OAAO,qBAAqB,MAAM,QAAQ;AAAA,MAC1C,mBAAiB,MAAM;AAAA,MACvB,iBAAe,MAAM;AAAA,MAEpB,6BAAmB,KAAK;AAAA,IAAA;AAAA,EAAA;AAG/B;AAGO,SAAS,aAAa,QAAmC;AAC9D,SAAO,OAAO,IAAI,CAAC,8BAAW,eAAA,EAA6B,MAAA,GAAV,MAAM,EAAkB,CAAE;AAC7E;"}
1
+ {"version":3,"file":"BlockRenderer.js","sources":["../../src/render/BlockRenderer.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport type {\n ContentBlock,\n TextBlockContent,\n HeadingBlockContent,\n ImageBlockContent,\n VideoBlockContent,\n QuoteBlockContent,\n CodeBlockContent,\n ListBlockContent,\n ButtonBlockContent,\n EmbedBlockContent,\n HtmlBlockContent,\n GalleryBlockContent,\n TableBlockContent,\n AccordionBlockContent,\n TabsBlockContent,\n AlertBlockContent,\n ColumnsBlockContent,\n SpacerBlockContent,\n DividerBlockContent,\n} from '../types/blog';\nimport { blockSettingsToStyle, animationClass } from './blockStyle';\nimport { RichText } from './RichText';\nimport TextBlock from './blocks/TextBlock';\nimport HeadingBlock from './blocks/HeadingBlock';\nimport ImageBlock from './blocks/ImageBlock';\nimport VideoBlock from './blocks/VideoBlock';\nimport DividerBlock from './blocks/DividerBlock';\nimport SpacerBlock from './blocks/SpacerBlock';\nimport QuoteBlock from './blocks/QuoteBlock';\nimport CodeBlock from './blocks/CodeBlock';\nimport ListBlock from './blocks/ListBlock';\nimport ButtonBlock from './blocks/ButtonBlock';\nimport EmbedBlock from './blocks/EmbedBlock';\nimport GalleryBlock from './blocks/GalleryBlock';\nimport TableBlock from './blocks/TableBlock';\nimport AlertBlock from './blocks/AlertBlock';\nimport ColumnsBlock from './blocks/ColumnsBlock';\nimport AccordionBlock from './blocks/AccordionBlock';\nimport TabsBlock from './blocks/TabsBlock';\n\n/** Maps a single block to its read-only renderer. */\nfunction renderBlockContent(block: ContentBlock): ReactNode {\n switch (block.type) {\n case 'text':\n return <TextBlock content={block.content as TextBlockContent} />;\n case 'heading':\n return <HeadingBlock content={block.content as HeadingBlockContent} />;\n case 'image':\n return <ImageBlock content={block.content as ImageBlockContent} />;\n case 'video':\n return <VideoBlock content={block.content as VideoBlockContent} />;\n case 'divider':\n return <DividerBlock content={block.content as DividerBlockContent} />;\n case 'spacer':\n return <SpacerBlock content={block.content as SpacerBlockContent} />;\n case 'quote':\n return <QuoteBlock content={block.content as QuoteBlockContent} />;\n case 'code':\n return <CodeBlock content={block.content as CodeBlockContent} />;\n case 'list':\n return <ListBlock content={block.content as ListBlockContent} />;\n case 'button':\n return <ButtonBlock content={block.content as ButtonBlockContent} />;\n case 'embed':\n return <EmbedBlock content={block.content as EmbedBlockContent} />;\n case 'html': {\n const { html } = (block.content ?? {}) as HtmlBlockContent;\n return html ? <RichText className=\"ggblog-html\" html={html} /> : null;\n }\n case 'gallery':\n return <GalleryBlock content={block.content as GalleryBlockContent} />;\n case 'table':\n return <TableBlock content={block.content as TableBlockContent} />;\n case 'alert':\n return <AlertBlock content={block.content as AlertBlockContent} />;\n case 'accordion':\n return <AccordionBlock content={block.content as AccordionBlockContent} />;\n case 'tabs':\n return <TabsBlock content={block.content as TabsBlockContent} />;\n case 'columns':\n return <ColumnsBlock content={block.content as ColumnsBlockContent} />;\n default:\n return null;\n }\n}\n\nexport interface BlockRendererProps {\n block: ContentBlock;\n}\n\n/** Renders one content block wrapped with its layout settings. */\nexport function BlockRenderer({ block }: BlockRendererProps) {\n const className = `ggblog-block ggblog-block-${block.type} ${animationClass(block.settings)}`.trim();\n return (\n <div\n className={className}\n style={blockSettingsToStyle(block.settings)}\n data-block-type={block.type}\n data-block-id={block.id}\n >\n {renderBlockContent(block)}\n </div>\n );\n}\n\n/** Renders an ordered list of content blocks (used by posts and columns). */\nexport function renderBlocks(blocks: ContentBlock[]): ReactNode {\n return blocks.map((block) => <BlockRenderer key={block.id} block={block} />);\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AA2CA,SAAS,mBAAmB,OAAgC;AAC1D,UAAQ,MAAM,MAAA;AAAA,IACZ,KAAK;AACH,aAAO,oBAAC,WAAA,EAAU,SAAS,MAAM,QAAA,CAA6B;AAAA,IAChE,KAAK;AACH,aAAO,oBAAC,cAAA,EAAa,SAAS,MAAM,QAAA,CAAgC;AAAA,IACtE,KAAK;AACH,aAAO,oBAAC,YAAA,EAAW,SAAS,MAAM,QAAA,CAA8B;AAAA,IAClE,KAAK;AACH,aAAO,oBAAC,YAAA,EAAW,SAAS,MAAM,QAAA,CAA8B;AAAA,IAClE,KAAK;AACH,aAAO,oBAAC,cAAA,EAAa,SAAS,MAAM,QAAA,CAAgC;AAAA,IACtE,KAAK;AACH,aAAO,oBAAC,aAAA,EAAY,SAAS,MAAM,QAAA,CAA+B;AAAA,IACpE,KAAK;AACH,aAAO,oBAAC,YAAA,EAAW,SAAS,MAAM,QAAA,CAA8B;AAAA,IAClE,KAAK;AACH,aAAO,oBAAC,WAAA,EAAU,SAAS,MAAM,QAAA,CAA6B;AAAA,IAChE,KAAK;AACH,aAAO,oBAAC,WAAA,EAAU,SAAS,MAAM,QAAA,CAA6B;AAAA,IAChE,KAAK;AACH,aAAO,oBAAC,aAAA,EAAY,SAAS,MAAM,QAAA,CAA+B;AAAA,IACpE,KAAK;AACH,aAAO,oBAAC,YAAA,EAAW,SAAS,MAAM,QAAA,CAA8B;AAAA,IAClE,KAAK,QAAQ;AACX,YAAM,EAAE,KAAA,IAAU,MAAM,WAAW,CAAA;AACnC,aAAO,OAAO,oBAAC,UAAA,EAAS,WAAU,eAAc,MAAY,IAAK;AAAA,IACnE;AAAA,IACA,KAAK;AACH,aAAO,oBAAC,cAAA,EAAa,SAAS,MAAM,QAAA,CAAgC;AAAA,IACtE,KAAK;AACH,aAAO,oBAAC,YAAA,EAAW,SAAS,MAAM,QAAA,CAA8B;AAAA,IAClE,KAAK;AACH,aAAO,oBAAC,YAAA,EAAW,SAAS,MAAM,QAAA,CAA8B;AAAA,IAClE,KAAK;AACH,aAAO,oBAAC,gBAAA,EAAe,SAAS,MAAM,QAAA,CAAkC;AAAA,IAC1E,KAAK;AACH,aAAO,oBAAC,WAAA,EAAU,SAAS,MAAM,QAAA,CAA6B;AAAA,IAChE,KAAK;AACH,aAAO,oBAAC,cAAA,EAAa,SAAS,MAAM,QAAA,CAAgC;AAAA,IACtE;AACE,aAAO;AAAA,EAAA;AAEb;AAOO,SAAS,cAAc,EAAE,SAA6B;AAC3D,QAAM,YAAY,6BAA6B,MAAM,IAAI,IAAI,eAAe,MAAM,QAAQ,CAAC,GAAG,KAAA;AAC9F,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,OAAO,qBAAqB,MAAM,QAAQ;AAAA,MAC1C,mBAAiB,MAAM;AAAA,MACvB,iBAAe,MAAM;AAAA,MAEpB,6BAAmB,KAAK;AAAA,IAAA;AAAA,EAAA;AAG/B;AAGO,SAAS,aAAa,QAAmC;AAC9D,SAAO,OAAO,IAAI,CAAC,8BAAW,eAAA,EAA6B,MAAA,GAAV,MAAM,EAAkB,CAAE;AAC7E;"}
@@ -0,0 +1,5 @@
1
+ import { LandingPageSchema } from './landingPageHtml';
2
+ /** Render page-builder schema as React (with HTML fallback per component). */
3
+ export declare function LandingPageRenderer({ schema }: {
4
+ schema: LandingPageSchema;
5
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,50 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { HeroFeatureBannerComponent } from "../components/landing/HeroFeatureBannerComponent.js";
3
+ import { HeroFeatureBannerVideoComponent } from "../components/landing/HeroFeatureBannerVideoComponent.js";
4
+ import { ReviewsListRender } from "../components/landing/VarietyReviewslistComponent.js";
5
+ import { BlogContainerComponent, BlogThreeColumnComponent, BlogTwoColumnComponent } from "../components/landing/BlogLayoutComponents.js";
6
+ import { FlexibleLayoutComponent } from "../components/landing/FlexibleLayoutComponent.js";
7
+ import { RichText } from "./RichText.js";
8
+ import { generateHtmlFromSchema } from "./landingPageHtml.js";
9
+ function renderComponent(component) {
10
+ const { type, props = {} } = component;
11
+ if (!type) return null;
12
+ switch (type) {
13
+ case "hero_feature_banner":
14
+ return /* @__PURE__ */ jsx(HeroFeatureBannerComponent, { props }, component.id ?? type);
15
+ case "hero_feature_banner_video":
16
+ return /* @__PURE__ */ jsx(HeroFeatureBannerVideoComponent, { props }, component.id ?? type);
17
+ case "variety_reviews_lits":
18
+ return /* @__PURE__ */ jsx(ReviewsListRender, { props }, component.id ?? type);
19
+ case "blog_two_column":
20
+ return /* @__PURE__ */ jsx(BlogTwoColumnComponent, { props }, component.id ?? type);
21
+ case "blog_three_column":
22
+ return /* @__PURE__ */ jsx(BlogThreeColumnComponent, { props }, component.id ?? type);
23
+ case "blog_container":
24
+ return /* @__PURE__ */ jsx(BlogContainerComponent, { props }, component.id ?? type);
25
+ case "flexible_layout":
26
+ return /* @__PURE__ */ jsx(
27
+ FlexibleLayoutComponent,
28
+ {
29
+ props
30
+ },
31
+ component.id ?? type
32
+ );
33
+ default: {
34
+ const { html } = generateHtmlFromSchema({ components: [component] });
35
+ if (!html) return null;
36
+ return /* @__PURE__ */ jsx(RichText, { html }, component.id ?? type);
37
+ }
38
+ }
39
+ }
40
+ function LandingPageRenderer({ schema }) {
41
+ if (!schema.components?.length) return null;
42
+ return /* @__PURE__ */ jsxs("div", { className: "ggblog-landing-page", children: [
43
+ schema.settings?.customCss ? /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: schema.settings.customCss } }) : null,
44
+ /* @__PURE__ */ jsx("div", { className: "ggblog-landing-page-content", children: schema.components.map((component, index) => /* @__PURE__ */ jsx("div", { children: renderComponent(component) }, component.id ?? `${component.type}-${index}`)) })
45
+ ] });
46
+ }
47
+ export {
48
+ LandingPageRenderer
49
+ };
50
+ //# sourceMappingURL=LandingPageRenderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LandingPageRenderer.js","sources":["../../src/render/LandingPageRenderer.tsx"],"sourcesContent":["import type { ComponentProps, ReactNode } from 'react';\nimport { HeroFeatureBannerComponent } from '../components/landing/HeroFeatureBannerComponent';\nimport { HeroFeatureBannerVideoComponent } from '../components/landing/HeroFeatureBannerVideoComponent';\nimport { ReviewsListRender } from '../components/landing/VarietyReviewslistComponent';\nimport {\n BlogTwoColumnComponent,\n BlogThreeColumnComponent,\n BlogContainerComponent,\n} from '../components/landing/BlogLayoutComponents';\nimport { FlexibleLayoutComponent } from '../components/landing/FlexibleLayoutComponent';\nimport { RichText } from './RichText';\nimport { generateHtmlFromSchema, type LandingPageSchema } from './landingPageHtml';\n\nfunction renderComponent(component: {\n id?: string;\n type?: string;\n props?: Record<string, unknown>;\n}): ReactNode {\n const { type, props = {} } = component;\n if (!type) return null;\n\n switch (type) {\n case 'hero_feature_banner':\n return <HeroFeatureBannerComponent key={component.id ?? type} props={props} />;\n case 'hero_feature_banner_video':\n return <HeroFeatureBannerVideoComponent key={component.id ?? type} props={props} />;\n case 'variety_reviews_lits':\n return <ReviewsListRender key={component.id ?? type} props={props} />;\n case 'blog_two_column':\n return <BlogTwoColumnComponent key={component.id ?? type} props={props} />;\n case 'blog_three_column':\n return <BlogThreeColumnComponent key={component.id ?? type} props={props} />;\n case 'blog_container':\n return <BlogContainerComponent key={component.id ?? type} props={props} />;\n case 'flexible_layout':\n return (\n <FlexibleLayoutComponent\n key={component.id ?? type}\n props={props as ComponentProps<typeof FlexibleLayoutComponent>['props']}\n />\n );\n default: {\n const { html } = generateHtmlFromSchema({ components: [component] });\n if (!html) return null;\n return <RichText key={component.id ?? type} html={html} />;\n }\n }\n}\n\n/** Render page-builder schema as React (with HTML fallback per component). */\nexport function LandingPageRenderer({ schema }: { schema: LandingPageSchema }) {\n if (!schema.components?.length) return null;\n\n return (\n <div className=\"ggblog-landing-page\">\n {schema.settings?.customCss ? (\n <style dangerouslySetInnerHTML={{ __html: schema.settings.customCss }} />\n ) : null}\n <div className=\"ggblog-landing-page-content\">\n {schema.components.map((component, index) => (\n <div key={component.id ?? `${component.type}-${index}`}>\n {renderComponent(component)}\n </div>\n ))}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;;AAaA,SAAS,gBAAgB,WAIX;AACZ,QAAM,EAAE,MAAM,QAAQ,CAAA,MAAO;AAC7B,MAAI,CAAC,KAAM,QAAO;AAElB,UAAQ,MAAA;AAAA,IACN,KAAK;AACH,aAAO,oBAAC,4BAAA,EAAsD,MAAA,GAAtB,UAAU,MAAM,IAAoB;AAAA,IAC9E,KAAK;AACH,aAAO,oBAAC,iCAAA,EAA2D,MAAA,GAAtB,UAAU,MAAM,IAAoB;AAAA,IACnF,KAAK;AACH,aAAO,oBAAC,mBAAA,EAA6C,MAAA,GAAtB,UAAU,MAAM,IAAoB;AAAA,IACrE,KAAK;AACH,aAAO,oBAAC,wBAAA,EAAkD,MAAA,GAAtB,UAAU,MAAM,IAAoB;AAAA,IAC1E,KAAK;AACH,aAAO,oBAAC,0BAAA,EAAoD,MAAA,GAAtB,UAAU,MAAM,IAAoB;AAAA,IAC5E,KAAK;AACH,aAAO,oBAAC,wBAAA,EAAkD,MAAA,GAAtB,UAAU,MAAM,IAAoB;AAAA,IAC1E,KAAK;AACH,aACE;AAAA,QAAC;AAAA,QAAA;AAAA,UAEC;AAAA,QAAA;AAAA,QADK,UAAU,MAAM;AAAA,MAAA;AAAA,IAI3B,SAAS;AACP,YAAM,EAAE,SAAS,uBAAuB,EAAE,YAAY,CAAC,SAAS,GAAG;AACnE,UAAI,CAAC,KAAM,QAAO;AAClB,aAAO,oBAAC,UAAA,EAAoC,KAAA,GAAtB,UAAU,MAAM,IAAkB;AAAA,IAC1D;AAAA,EAAA;AAEJ;AAGO,SAAS,oBAAoB,EAAE,UAAyC;AAC7E,MAAI,CAAC,OAAO,YAAY,OAAQ,QAAO;AAEvC,SACE,qBAAC,OAAA,EAAI,WAAU,uBACZ,UAAA;AAAA,IAAA,OAAO,UAAU,YAChB,oBAAC,SAAA,EAAM,yBAAyB,EAAE,QAAQ,OAAO,SAAS,UAAA,EAAU,CAAG,IACrE;AAAA,IACJ,oBAAC,OAAA,EAAI,WAAU,+BACZ,UAAA,OAAO,WAAW,IAAI,CAAC,WAAW,UACjC,oBAAC,OAAA,EACE,0BAAgB,SAAS,KADlB,UAAU,MAAM,GAAG,UAAU,IAAI,IAAI,KAAK,EAEpD,CACD,EAAA,CACH;AAAA,EAAA,GACF;AAEJ;"}
@@ -20,7 +20,9 @@ export declare function generateHtmlFromSchema(schema: LandingPageSchema | null
20
20
  export declare function getLandingPageSchema(post: {
21
21
  landingPage?: {
22
22
  publishedVersion?: {
23
- schema_json?: LandingPageSchema;
23
+ schema_json?: LandingPageSchema | string;
24
24
  };
25
+ landing_page_schema?: LandingPageSchema | string;
25
26
  };
27
+ landing_page_schema?: LandingPageSchema | string;
26
28
  } | null | undefined): LandingPageSchema | null;