blocks-dusted 0.1.9 → 0.1.11

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 (248) hide show
  1. package/README.md +35 -0
  2. package/package.json +1 -1
  3. package/src/utils/paths.js +13 -13
  4. package/templates/blocks/DD-Accordion/Component.tsx +229 -0
  5. package/templates/blocks/DD-Accordion/README.md +3 -0
  6. package/templates/blocks/DD-Accordion/config.ts +73 -0
  7. package/templates/blocks/DD-Accordion/manifest.json +75 -0
  8. package/templates/blocks/DD-AnimText/Component.tsx +788 -0
  9. package/templates/blocks/DD-AnimText/README.md +3 -0
  10. package/templates/blocks/DD-AnimText/config.ts +295 -0
  11. package/templates/blocks/DD-AnimText/manifest.json +85 -0
  12. package/templates/blocks/DD-AsideNav/Component.tsx +321 -0
  13. package/templates/blocks/DD-AsideNav/config.ts +204 -0
  14. package/templates/blocks/DD-AsideNav/manifest.json +80 -0
  15. package/templates/blocks/DD-Banner/Component.tsx +51 -0
  16. package/templates/blocks/DD-Banner/config.ts +57 -0
  17. package/templates/blocks/DD-Banner/manifest.json +72 -0
  18. package/templates/blocks/DD-BgLightning/Component.tsx +44 -0
  19. package/templates/blocks/DD-BgLightning/README.md +3 -0
  20. package/templates/blocks/DD-BgLightning/config.ts +97 -0
  21. package/templates/blocks/DD-BgLightning/manifest.json +79 -0
  22. package/templates/blocks/DD-BookCall/Component.tsx +10 -11
  23. package/templates/blocks/DD-BookCall/config.ts +238 -238
  24. package/templates/blocks/DD-ButtonPop/Component.tsx +117 -0
  25. package/templates/blocks/DD-ButtonPop/config.ts +105 -0
  26. package/templates/blocks/DD-ButtonPop/manifest.json +88 -0
  27. package/templates/blocks/DD-ButtonPop/svg-icons/Dust2.tsx +167 -0
  28. package/templates/blocks/DD-Card/Component.tsx +251 -0
  29. package/templates/blocks/DD-Card/README.md +4 -0
  30. package/templates/blocks/DD-Card/config.ts +154 -0
  31. package/templates/blocks/DD-Card/manifest.json +82 -0
  32. package/templates/blocks/DD-CardTemplate01/Component.tsx +5 -6
  33. package/templates/blocks/DD-CardTemplate01/config.ts +106 -106
  34. package/templates/blocks/DD-Carousel-A/Component.tsx +249 -249
  35. package/templates/blocks/DD-Carousel-B/Component.tsx +403 -403
  36. package/templates/blocks/DD-Chip/Component.tsx +250 -0
  37. package/templates/blocks/DD-Chip/EXAMPLES.md +313 -0
  38. package/templates/blocks/DD-Chip/README.md +202 -0
  39. package/templates/blocks/DD-Chip/config.ts +369 -0
  40. package/templates/blocks/DD-Chip/manifest.json +74 -0
  41. package/templates/blocks/DD-ComparisonTable/Component.tsx +256 -256
  42. package/templates/blocks/DD-Contact/Component.tsx +434 -434
  43. package/templates/blocks/DD-Contact/config.ts +22 -22
  44. package/templates/blocks/DD-Counter/Component.tsx +190 -0
  45. package/templates/blocks/DD-Counter/README.md +4 -0
  46. package/templates/blocks/DD-Counter/config.ts +291 -0
  47. package/templates/blocks/DD-Counter/manifest.json +74 -0
  48. package/templates/blocks/DD-CustomCSS/Component.tsx +8 -0
  49. package/templates/blocks/DD-CustomCSS/README.md +4 -0
  50. package/templates/blocks/DD-CustomCSS/addToLayout.md +5 -0
  51. package/templates/blocks/DD-CustomCSS/config.ts +22 -0
  52. package/templates/blocks/DD-CustomCSS/manifest.json +72 -0
  53. package/templates/blocks/DD-CustomCSS/renderCustomCSS.md +7 -0
  54. package/templates/blocks/DD-FeatCat/Component.tsx +302 -302
  55. package/templates/blocks/DD-FeatCat/config.ts +201 -201
  56. package/templates/blocks/DD-FeatStrip/Component.tsx +585 -585
  57. package/templates/blocks/DD-FeatStrip/config.ts +134 -134
  58. package/templates/blocks/DD-FluidBtn/ButtonsEyeCatching.tsx +628 -0
  59. package/templates/blocks/DD-FluidBtn/Component.tsx +43 -0
  60. package/templates/blocks/DD-FluidBtn/README.md +6 -0
  61. package/templates/blocks/DD-FluidBtn/config.ts +102 -0
  62. package/templates/blocks/DD-FluidBtn/manifest.json +86 -0
  63. package/templates/blocks/DD-GooeyInfo/Component.tsx +149 -0
  64. package/templates/blocks/DD-GooeyInfo/config.ts +135 -0
  65. package/templates/blocks/DD-GooeyInfo/manifest.json +72 -0
  66. package/templates/blocks/DD-Hero/Component.tsx +40 -17
  67. package/templates/blocks/DD-Hero/config.ts +267 -267
  68. package/templates/blocks/DD-HorizontalScroll/Component.tsx +4 -2
  69. package/templates/blocks/DD-HorizontalScroll/config.ts +231 -231
  70. package/templates/blocks/DD-Icon/Component.tsx +287 -0
  71. package/templates/blocks/DD-Icon/README.md +4 -0
  72. package/templates/blocks/DD-Icon/config.ts +258 -0
  73. package/templates/blocks/DD-Icon/hooks/revalidateBlockIds.ts +66 -0
  74. package/templates/blocks/DD-Icon/manifest.json +82 -0
  75. package/templates/blocks/DD-IconButton/Component.tsx +455 -0
  76. package/templates/blocks/DD-IconButton/README.md +326 -0
  77. package/templates/blocks/DD-IconButton/config.ts +239 -0
  78. package/templates/blocks/DD-IconButton/manifest.json +85 -0
  79. package/templates/blocks/DD-IdTag/Component.tsx +21 -0
  80. package/templates/blocks/DD-IdTag/README.md +4 -0
  81. package/templates/blocks/DD-IdTag/config.ts +41 -0
  82. package/templates/blocks/DD-IdTag/manifest.json +72 -0
  83. package/templates/blocks/DD-Iframe/Component.tsx +45 -0
  84. package/templates/blocks/DD-Iframe/README.md +4 -0
  85. package/templates/blocks/DD-Iframe/config.ts +47 -0
  86. package/templates/blocks/DD-Iframe/manifest.json +72 -0
  87. package/templates/blocks/DD-IframeMedia/Component.tsx +129 -0
  88. package/templates/blocks/DD-IframeMedia/README.md +4 -0
  89. package/templates/blocks/DD-IframeMedia/config.ts +79 -0
  90. package/templates/blocks/DD-IframeMedia/manifest.json +72 -0
  91. package/templates/blocks/DD-LineAnim/Component.tsx +36 -0
  92. package/templates/blocks/DD-LineAnim/README.md +4 -0
  93. package/templates/blocks/DD-LineAnim/config.ts +90 -0
  94. package/templates/blocks/DD-LineAnim/manifest.json +80 -0
  95. package/templates/blocks/DD-LinkGrid/Component.tsx +119 -0
  96. package/templates/blocks/DD-LinkGrid/README.md +4 -0
  97. package/templates/blocks/DD-LinkGrid/config.ts +51 -0
  98. package/templates/blocks/DD-LinkGrid/manifest.json +74 -0
  99. package/templates/blocks/DD-LogoMarq/Component.tsx +132 -0
  100. package/templates/blocks/DD-LogoMarq/README.md +4 -0
  101. package/templates/blocks/DD-LogoMarq/config.ts +264 -0
  102. package/templates/blocks/DD-LogoMarq/manifest.json +82 -0
  103. package/templates/blocks/DD-Lottie/Component.tsx +459 -0
  104. package/templates/blocks/DD-Lottie/README.md +4 -0
  105. package/templates/blocks/DD-Lottie/config.ts +379 -0
  106. package/templates/blocks/DD-Lottie/index.tsx +28 -0
  107. package/templates/blocks/DD-Lottie/manifest.json +83 -0
  108. package/templates/blocks/DD-MagicBento/Component.tsx +777 -0
  109. package/templates/blocks/DD-MagicBento/README.md +4 -0
  110. package/templates/blocks/DD-MagicBento/config.ts +264 -0
  111. package/templates/blocks/DD-MagicBento/manifest.json +74 -0
  112. package/templates/blocks/DD-MasonaryMedia/Component.tsx +547 -545
  113. package/templates/blocks/DD-MasonaryMedia/config.ts +2 -2
  114. package/templates/blocks/DD-MasonaryMedia/vendor.d.ts +2 -2
  115. package/templates/blocks/DD-Masonry/Component.tsx +645 -0
  116. package/templates/blocks/DD-Masonry/README.md +4 -0
  117. package/templates/blocks/DD-Masonry/config.ts +475 -0
  118. package/templates/blocks/DD-Masonry/manifest.json +82 -0
  119. package/templates/blocks/DD-Pricing/Component.tsx +372 -372
  120. package/templates/blocks/DD-Process/Component.tsx +149 -149
  121. package/templates/blocks/DD-Process/config.ts +150 -150
  122. package/templates/blocks/DD-Progress/Component.tsx +141 -0
  123. package/templates/blocks/DD-Progress/README.md +4 -0
  124. package/templates/blocks/DD-Progress/config.ts +175 -0
  125. package/templates/blocks/DD-Progress/manifest.json +74 -0
  126. package/templates/blocks/DD-ProjHeader/Component.tsx +91 -0
  127. package/templates/blocks/DD-ProjHeader/README.md +4 -0
  128. package/templates/blocks/DD-ProjHeader/config.ts +96 -0
  129. package/templates/blocks/DD-ProjHeader/manifest.json +72 -0
  130. package/templates/blocks/DD-ProjSlider/Component.tsx +123 -0
  131. package/templates/blocks/DD-ProjSlider/README.md +4 -0
  132. package/templates/blocks/DD-ProjSlider/config.ts +64 -0
  133. package/templates/blocks/DD-ProjSlider/manifest.json +72 -0
  134. package/templates/blocks/DD-ScrollText/Component.tsx +114 -0
  135. package/templates/blocks/DD-ScrollText/README.md +4 -0
  136. package/templates/blocks/DD-ScrollText/config.ts +58 -0
  137. package/templates/blocks/DD-ScrollText/manifest.json +72 -0
  138. package/templates/blocks/DD-Section/Component.tsx +266 -266
  139. package/templates/blocks/DD-Separator/Component.tsx +66 -0
  140. package/templates/blocks/DD-Separator/README.md +4 -0
  141. package/templates/blocks/DD-Separator/config.ts +58 -0
  142. package/templates/blocks/DD-Separator/manifest.json +72 -0
  143. package/templates/blocks/DD-Services/Component.tsx +176 -176
  144. package/templates/blocks/DD-Services/config.ts +149 -149
  145. package/templates/blocks/DD-ShowcaseGrid/Component.tsx +307 -307
  146. package/templates/blocks/DD-Slider-A/Component.tsx +237 -237
  147. package/templates/blocks/DD-StackCards/Component.tsx +473 -473
  148. package/templates/blocks/DD-StackCards/config.ts +399 -399
  149. package/templates/blocks/DD-Team/Component.tsx +247 -247
  150. package/templates/blocks/DD-Team/config.ts +173 -173
  151. package/templates/blocks/DD-TeamCards/Component.tsx +251 -0
  152. package/templates/blocks/DD-TeamCards/README.md +4 -0
  153. package/templates/blocks/DD-TeamCards/config.ts +302 -0
  154. package/templates/blocks/DD-TeamCards/manifest.json +74 -0
  155. package/templates/blocks/DD-TechStack/Component.tsx +168 -162
  156. package/templates/blocks/DD-TechStack/config.ts +153 -153
  157. package/templates/blocks/DD-TestMarq/Component.tsx +132 -0
  158. package/templates/blocks/DD-TestMarq/README.md +4 -0
  159. package/templates/blocks/DD-TestMarq/RowLabel.tsx +9 -0
  160. package/templates/blocks/DD-TestMarq/config.ts +88 -0
  161. package/templates/blocks/DD-TestMarq/manifest.json +72 -0
  162. package/templates/blocks/DD-Testim/Component.tsx +247 -0
  163. package/templates/blocks/DD-Testim/README.md +4 -0
  164. package/templates/blocks/DD-Testim/config.ts +101 -0
  165. package/templates/blocks/DD-Testim/manifest.json +75 -0
  166. package/templates/blocks/DD-Testim2/Component.tsx +256 -0
  167. package/templates/blocks/DD-Testim2/README.md +4 -0
  168. package/templates/blocks/DD-Testim2/config.ts +100 -0
  169. package/templates/blocks/DD-Testim2/manifest.json +74 -0
  170. package/templates/blocks/DD-Testimonials/Component.tsx +147 -147
  171. package/templates/blocks/DD-Testimonials/config.ts +120 -120
  172. package/templates/blocks/DD-TextMarq/Component.tsx +157 -0
  173. package/templates/blocks/DD-TextMarq/README.md +4 -0
  174. package/templates/blocks/DD-TextMarq/config.ts +237 -0
  175. package/templates/blocks/DD-TextMarq/manifest.json +82 -0
  176. package/templates/blocks/DD-Thanks/Component.tsx +500 -0
  177. package/templates/blocks/DD-Thanks/README.md +4 -0
  178. package/templates/blocks/DD-Thanks/config.ts +224 -0
  179. package/templates/blocks/DD-Thanks/manifest.json +72 -0
  180. package/templates/blocks/DD-VertMarq/Component.tsx +154 -0
  181. package/templates/blocks/DD-VertMarq/README.md +4 -0
  182. package/templates/blocks/DD-VertMarq/config.ts +236 -0
  183. package/templates/blocks/DD-VertMarq/dist/index.css +6 -0
  184. package/templates/blocks/DD-VertMarq/dist/index.min.css +1 -0
  185. package/templates/blocks/DD-VertMarq/manifest.json +74 -0
  186. package/templates/blocks/DD-Work/Component.tsx +318 -315
  187. package/templates/blocks/DD-Work/config.ts +165 -165
  188. package/templates/blocks/DD-Work-B/Component.tsx +294 -294
  189. package/templates/blocks/DDHG-CTASection2/Component.tsx +185 -0
  190. package/templates/blocks/DDHG-CTASection2/config.ts +140 -0
  191. package/templates/blocks/DDHG-CTASection2/manifest.json +45 -0
  192. package/templates/blocks/DDHG-HighlightArc/Component.tsx +243 -0
  193. package/templates/blocks/DDHG-HighlightArc/config.ts +85 -0
  194. package/templates/blocks/DDHG-HighlightArc/manifest.json +46 -0
  195. package/templates/blocks/DDHG-LogoMarquee/Component.tsx +93 -0
  196. package/templates/blocks/DDHG-LogoMarquee/config.ts +66 -0
  197. package/templates/blocks/DDHG-LogoMarquee/manifest.json +39 -0
  198. package/templates/blocks/DDHG-NavCodeGrid/Component.tsx +213 -0
  199. package/templates/blocks/DDHG-NavCodeGrid/config.ts +64 -0
  200. package/templates/blocks/DDHG-NavCodeGrid/manifest.json +45 -0
  201. package/templates/blocks/DDHG-ProcessSec/Component.tsx +575 -0
  202. package/templates/blocks/DDHG-ProcessSec/config.ts +170 -0
  203. package/templates/blocks/DDHG-ProcessSec/manifest.json +56 -0
  204. package/templates/blocks/DDHG-ServiceDetails/Component.tsx +164 -0
  205. package/templates/blocks/DDHG-ServiceDetails/ServiceDetailCard.tsx +394 -0
  206. package/templates/blocks/DDHG-ServiceDetails/config.ts +77 -0
  207. package/templates/blocks/DDHG-ServiceDetails/manifest.json +50 -0
  208. package/templates/blocks/DDHG-ServiceHero/Component.tsx +234 -0
  209. package/templates/blocks/DDHG-ServiceHero/config.ts +49 -0
  210. package/templates/blocks/DDHG-ServiceHero/manifest.json +26 -0
  211. package/templates/blocks/DDHG-ServiceOverview/Component.tsx +277 -0
  212. package/templates/blocks/DDHG-ServiceOverview/config.ts +64 -0
  213. package/templates/blocks/DDHG-ServiceOverview/manifest.json +44 -0
  214. package/templates/blocks/DDHG-StatsFAQ/Component.tsx +389 -0
  215. package/templates/blocks/DDHG-StatsFAQ/config.ts +204 -0
  216. package/templates/blocks/DDHG-StatsFAQ/manifest.json +45 -0
  217. package/templates/blocks/DDHG-TeamCards/Component.tsx +131 -0
  218. package/templates/blocks/DDHG-TeamCards/config.ts +48 -0
  219. package/templates/blocks/DDHG-TeamCards/manifest.json +29 -0
  220. package/templates/components/HeaderDD02/MobileBottomNav/index.tsx +352 -257
  221. package/templates/components/HeaderDD02/index.tsx +313 -229
  222. package/templates/shared/src/components/BgLightning/index.tsx +242 -0
  223. package/templates/shared/src/components/ui/ShareButton/index.tsx +80 -0
  224. package/templates/shared/src/components/ui/anims/FadeOnScroll.tsx +48 -0
  225. package/templates/shared/src/components/ui/anims/FadeUpOnInView.tsx +63 -0
  226. package/templates/shared/src/components/ui/anims/GlareHover.tsx +111 -0
  227. package/templates/shared/src/components/ui/anims/LineAnims.tsx +81 -0
  228. package/templates/shared/src/components/ui/anims/TitleAnims01.tsx +54 -0
  229. package/templates/shared/src/components/ui/anims/animation-container.tsx +29 -0
  230. package/templates/shared/src/components/ui/anims/blur-in.tsx +26 -0
  231. package/templates/shared/src/components/ui/anims/counter.tsx +53 -0
  232. package/templates/shared/src/components/ui/anims/fade.tsx +83 -0
  233. package/templates/shared/src/components/ui/anims/gradual-spacing.tsx +39 -0
  234. package/templates/shared/src/components/ui/anims/pull-up.tsx +93 -0
  235. package/templates/shared/src/components/ui/anims/reveal-text.tsx +76 -0
  236. package/templates/shared/src/components/ui/anims/rotate-words.tsx +43 -0
  237. package/templates/shared/src/components/ui/anims/shiny-text.tsx +58 -0
  238. package/templates/shared/src/components/ui/anims/shuffle-text.tsx +85 -0
  239. package/templates/shared/src/components/ui/anims/split-text.tsx +211 -0
  240. package/templates/shared/src/components/ui/anims/staggered-fade.tsx +133 -0
  241. package/templates/shared/src/components/ui/anims/text-scroll.tsx +111 -0
  242. package/templates/shared/src/components/ui/anims/typing-effect.tsx +112 -0
  243. package/templates/shared/src/components/ui/button-dusted-fluid/index.tsx +1521 -0
  244. package/templates/shared/src/components/ui/separator.tsx +85 -0
  245. package/templates/shared/src/hooks/useBodyScrollLock.ts +97 -0
  246. package/templates/shared/src/hooks/useClickOutside.ts +28 -0
  247. package/templates/shared/src/utilities/renderCustomCSS.ts +5 -0
  248. package/templates/shared/src/utilities/smoothScroll.ts +65 -0
@@ -1,237 +1,237 @@
1
- 'use client'
2
-
3
- import React, { useState, useCallback, useEffect } from 'react'
4
- import Image from 'next/image'
5
- import { ChevronLeft, ChevronRight } from 'lucide-react'
6
- import { cn } from '@/utilities/ui'
7
- import useEmblaCarousel from 'embla-carousel-react'
8
-
9
- type MediaObj = { url?: string | null; alt?: string | null }
10
-
11
- type Slide = {
12
- id?: string | null
13
- title: string
14
- category?: string | null
15
- description?: string | null
16
- image?: MediaObj | string | null
17
- imageFallbackUrl?: string | null
18
- }
19
-
20
- export type DDSliderAProps = {
21
- blockType: 'DD-Slider-A'
22
- blockName?: string | null
23
- eyebrow?: string | null
24
- heading?: string | null
25
- subtext?: string | null
26
- slides?: Slide[] | null
27
- sectionId?: string | null
28
- customCSS?: string | null
29
- }
30
-
31
- function isValidImageSrc(src?: string | null): src is string {
32
- return Boolean(src && src.startsWith('/api/media/file/'))
33
- }
34
-
35
- function getImageSrc(slide: Slide): string {
36
- const candidateSrc =
37
- slide.image && typeof slide.image === 'object' && (slide.image as MediaObj).url
38
- ? (slide.image as MediaObj).url!
39
- : (slide.imageFallbackUrl ?? '')
40
-
41
- return isValidImageSrc(candidateSrc) ? candidateSrc : ''
42
- }
43
-
44
- export const DDSliderA: React.FC<DDSliderAProps> = ({
45
- eyebrow,
46
- heading,
47
- subtext,
48
- slides: slidesProp,
49
- sectionId,
50
- customCSS,
51
- }) => {
52
- const slides = slidesProp ?? []
53
- const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true })
54
- const [activeIdx, setActiveIdx] = useState(0)
55
- const [loadedSlides, setLoadedSlides] = useState<Record<number, boolean>>({})
56
- const [loadedThumbs, setLoadedThumbs] = useState<Record<number, boolean>>({})
57
-
58
- const onSelect = useCallback(() => {
59
- if (!emblaApi) return
60
- setActiveIdx(emblaApi.selectedScrollSnap())
61
- }, [emblaApi])
62
-
63
- useEffect(() => {
64
- if (!emblaApi) return
65
- onSelect()
66
- emblaApi.on('select', onSelect)
67
- emblaApi.on('reInit', onSelect)
68
- return () => {
69
- emblaApi.off('select', onSelect)
70
- emblaApi.off('reInit', onSelect)
71
- }
72
- }, [emblaApi, onSelect])
73
-
74
- const scrollPrev = useCallback(() => emblaApi?.scrollPrev(), [emblaApi])
75
- const scrollNext = useCallback(() => emblaApi?.scrollNext(), [emblaApi])
76
- const scrollTo = useCallback((i: number) => emblaApi?.scrollTo(i), [emblaApi])
77
-
78
- if (!slides.length) return null
79
-
80
- return (
81
- <section id={sectionId ?? undefined} className="py-20 overflow-hidden">
82
- {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
83
- <div className="max-w-6xl lg:max-w-7xl mx-auto px-4 md:px-6">
84
- {/* Optional section header */}
85
- #BlockName-DDSliderA
86
- {(eyebrow || heading || subtext) && (
87
- <div className="mb-10 text-center">
88
- {eyebrow && (
89
- <p className="text-lime-500 text-xs font-bold uppercase tracking-widest mb-2">
90
- {eyebrow}
91
- </p>
92
- )}
93
- {heading && (
94
- <h2 className="text-4xl lg:text-5xl font-bold text-stone-900 dark:text-stone-50 leading-tight">
95
- {heading}
96
- </h2>
97
- )}
98
- {subtext && (
99
- <p className="text-stone-400 mt-3 text-base max-w-2xl mx-auto">{subtext}</p>
100
- )}
101
- </div>
102
- )}
103
- <div className="relative rounded-3xl overflow-hidden">
104
- {/* Embla viewport */}
105
- <div
106
- ref={emblaRef}
107
- className="overflow-hidden select-none cursor-grab active:cursor-grabbing h-[480px] lg:h-[560px]"
108
- >
109
- <div className="flex h-full">
110
- {slides.map((slide, i) => {
111
- const imgSrc = getImageSrc(slide)
112
- return (
113
- <div key={slide.id ?? i} className="flex-none w-full h-full relative">
114
- {imgSrc ? (
115
- <>
116
- {!loadedSlides[i] && (
117
- <div className="absolute inset-0 bg-stone-900 animate-pulse" />
118
- )}
119
- <Image
120
- src={imgSrc}
121
- alt={slide.title}
122
- fill
123
- sizes="100vw"
124
- priority={i === 0}
125
- className="w-full h-full object-cover object-top"
126
- draggable={false}
127
- onLoad={() => setLoadedSlides((prev) => ({ ...prev, [i]: true }))}
128
- />
129
- </>
130
- ) : (
131
- <div className="w-full h-full bg-gradient-to-br from-violet-950 to-indigo-950" />
132
- )}
133
-
134
- {/* Gradient overlay */}
135
- <div className="absolute inset-0 bg-gradient-to-r from-stone-950/80 via-stone-950/40 to-transparent pointer-events-none" />
136
-
137
- {/* Slide content */}
138
- <div className="absolute bottom-0 left-0 right-0 p-8 lg:p-12 pointer-events-none">
139
- <div className="max-w-lg">
140
- {slide.category && (
141
- <span className="inline-block bg-lime-500/50 border-lime-500 border text-stone-200 text-xs font-bold px-3 py-1 rounded-full mb-3 uppercase tracking-widest scale-90 origin-top-left">
142
- {slide.category}
143
- </span>
144
- )}
145
- <h3 className="text-stone-50 font-black text-3xl lg:text-4xl mb-3">
146
- {slide.title}
147
- </h3>
148
- {slide.description && (
149
- <p className="text-stone-50/70 text-base leading-relaxed">
150
- {slide.description}
151
- </p>
152
- )}
153
- </div>
154
- </div>
155
- </div>
156
- )
157
- })}
158
- </div>
159
- </div>
160
-
161
- {/* Navigation arrows */}
162
- <button
163
- onClick={scrollPrev}
164
- aria-label="Previous slide"
165
- className="absolute left-1 top-1/2 -translate-y-1/2 z-20 w-12 h-12 flex items-center justify-center rounded-full bg-stone-50/5 hover:bg-lime-500 text-stone-50 backdrop-blur-sm transition-all cursor-pointer"
166
- >
167
- <ChevronLeft className="w-5 h-5" />
168
- </button>
169
- <button
170
- onClick={scrollNext}
171
- aria-label="Next slide"
172
- className="absolute right-1 top-1/2 -translate-y-1/2 z-20 w-12 h-12 flex items-center justify-center rounded-full bg-stone-50/5 hover:bg-lime-500 text-stone-50 backdrop-blur-sm transition-all cursor-pointer"
173
- >
174
- <ChevronRight className="w-5 h-5" />
175
- </button>
176
-
177
- {/* Thumbnail strip */}
178
- <div className="absolute bottom-4 right-4 lg:right-8 z-20 flex gap-2">
179
- {slides.map((slide, i) => {
180
- const imgSrc = getImageSrc(slide)
181
- return (
182
- <button
183
- key={slide.id ?? i}
184
- onClick={() => scrollTo(i)}
185
- aria-label={`Go to slide: ${slide.title}`}
186
- className={cn(
187
- 'relative w-16 h-12 rounded-lg overflow-hidden border-2 transition-all cursor-pointer',
188
- i === activeIdx
189
- ? 'border-lime-500 scale-110'
190
- : 'border-white/30 opacity-60 hover:opacity-100',
191
- )}
192
- >
193
- {imgSrc ? (
194
- <>
195
- {!loadedThumbs[i] && (
196
- <div className="absolute inset-0 bg-stone-800 animate-pulse" />
197
- )}
198
- <Image
199
- src={imgSrc}
200
- alt={slide.title}
201
- fill
202
- sizes="64px"
203
- className="w-full h-full object-cover object-top"
204
- draggable={false}
205
- onLoad={() => setLoadedThumbs((prev) => ({ ...prev, [i]: true }))}
206
- />
207
- </>
208
- ) : (
209
- <div className="w-full h-full bg-stone-800" />
210
- )}
211
- </button>
212
- )
213
- })}
214
- </div>
215
- </div>
216
- {/* Dot indicators */}
217
- {slides.length > 1 && (
218
- <div className="flex justify-center gap-2 mt-6">
219
- {slides.map((_, i) => (
220
- <button
221
- key={i}
222
- onClick={() => scrollTo(i)}
223
- aria-label={`Go to slide ${i + 1}`}
224
- className={cn(
225
- 'rounded-full transition-all duration-300 cursor-pointer',
226
- i === activeIdx
227
- ? 'w-8 h-2.5 bg-lime-500'
228
- : 'w-2.5 h-2.5 bg-stone-600 hover:bg-stone-400',
229
- )}
230
- />
231
- ))}
232
- </div>
233
- )}
234
- </div>
235
- </section>
236
- )
237
- }
1
+ 'use client'
2
+
3
+ import React, { useState, useCallback, useEffect } from 'react'
4
+ import Image from 'next/image'
5
+ import { ChevronLeft, ChevronRight } from 'lucide-react'
6
+ import { cn } from '@/utilities/ui'
7
+ import useEmblaCarousel from 'embla-carousel-react'
8
+
9
+ type MediaObj = { url?: string | null; alt?: string | null }
10
+
11
+ type Slide = {
12
+ id?: string | null
13
+ title: string
14
+ category?: string | null
15
+ description?: string | null
16
+ image?: MediaObj | string | null
17
+ imageFallbackUrl?: string | null
18
+ }
19
+
20
+ export type DDSliderAProps = {
21
+ blockType: 'DD-Slider-A'
22
+ blockName?: string | null
23
+ eyebrow?: string | null
24
+ heading?: string | null
25
+ subtext?: string | null
26
+ slides?: Slide[] | null
27
+ sectionId?: string | null
28
+ customCSS?: string | null
29
+ }
30
+
31
+ function isValidImageSrc(src?: string | null): src is string {
32
+ return Boolean(src && src.startsWith('/api/media/file/'))
33
+ }
34
+
35
+ function getImageSrc(slide: Slide): string {
36
+ const candidateSrc =
37
+ slide.image && typeof slide.image === 'object' && (slide.image as MediaObj).url
38
+ ? (slide.image as MediaObj).url!
39
+ : (slide.imageFallbackUrl ?? '')
40
+
41
+ return isValidImageSrc(candidateSrc) ? candidateSrc : ''
42
+ }
43
+
44
+ export const DDSliderA: React.FC<DDSliderAProps> = ({
45
+ eyebrow,
46
+ heading,
47
+ subtext,
48
+ slides: slidesProp,
49
+ sectionId,
50
+ customCSS,
51
+ }) => {
52
+ const slides = slidesProp ?? []
53
+ const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true })
54
+ const [activeIdx, setActiveIdx] = useState(0)
55
+ const [loadedSlides, setLoadedSlides] = useState<Record<number, boolean>>({})
56
+ const [loadedThumbs, setLoadedThumbs] = useState<Record<number, boolean>>({})
57
+
58
+ const onSelect = useCallback(() => {
59
+ if (!emblaApi) return
60
+ setActiveIdx(emblaApi.selectedScrollSnap())
61
+ }, [emblaApi])
62
+
63
+ useEffect(() => {
64
+ if (!emblaApi) return
65
+ onSelect()
66
+ emblaApi.on('select', onSelect)
67
+ emblaApi.on('reInit', onSelect)
68
+ return () => {
69
+ emblaApi.off('select', onSelect)
70
+ emblaApi.off('reInit', onSelect)
71
+ }
72
+ }, [emblaApi, onSelect])
73
+
74
+ const scrollPrev = useCallback(() => emblaApi?.scrollPrev(), [emblaApi])
75
+ const scrollNext = useCallback(() => emblaApi?.scrollNext(), [emblaApi])
76
+ const scrollTo = useCallback((i: number) => emblaApi?.scrollTo(i), [emblaApi])
77
+
78
+ if (!slides.length) return null
79
+
80
+ return (
81
+ <section id={sectionId ?? undefined} className="py-20 overflow-hidden">
82
+ {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
83
+ <div className="max-w-6xl lg:max-w-7xl mx-auto px-4 md:px-6">
84
+ {/* Optional section header */}
85
+
86
+ {(eyebrow || heading || subtext) && (
87
+ <div className="mb-10 text-center">
88
+ {eyebrow && (
89
+ <p className="text-lime-500 text-xs font-bold uppercase tracking-widest mb-2">
90
+ {eyebrow}
91
+ </p>
92
+ )}
93
+ {heading && (
94
+ <h2 className="text-4xl lg:text-5xl font-bold text-stone-900 dark:text-stone-50 leading-tight">
95
+ {heading}
96
+ </h2>
97
+ )}
98
+ {subtext && (
99
+ <p className="text-stone-400 mt-3 text-base max-w-2xl mx-auto">{subtext}</p>
100
+ )}
101
+ </div>
102
+ )}
103
+ <div className="relative rounded-3xl overflow-hidden">
104
+ {/* Embla viewport */}
105
+ <div
106
+ ref={emblaRef}
107
+ className="overflow-hidden select-none cursor-grab active:cursor-grabbing h-[480px] lg:h-[560px]"
108
+ >
109
+ <div className="flex h-full">
110
+ {slides.map((slide, i) => {
111
+ const imgSrc = getImageSrc(slide)
112
+ return (
113
+ <div key={slide.id ?? i} className="flex-none w-full h-full relative">
114
+ {imgSrc ? (
115
+ <>
116
+ {!loadedSlides[i] && (
117
+ <div className="absolute inset-0 bg-stone-900 animate-pulse" />
118
+ )}
119
+ <Image
120
+ src={imgSrc}
121
+ alt={slide.title}
122
+ fill
123
+ sizes="100vw"
124
+ priority={i === 0}
125
+ className="w-full h-full object-cover object-top"
126
+ draggable={false}
127
+ onLoad={() => setLoadedSlides((prev) => ({ ...prev, [i]: true }))}
128
+ />
129
+ </>
130
+ ) : (
131
+ <div className="w-full h-full bg-gradient-to-br from-violet-950 to-indigo-950" />
132
+ )}
133
+
134
+ {/* Gradient overlay */}
135
+ <div className="absolute inset-0 bg-gradient-to-r from-stone-950/80 via-stone-950/40 to-transparent pointer-events-none" />
136
+
137
+ {/* Slide content */}
138
+ <div className="absolute bottom-0 left-0 right-0 p-8 lg:p-12 pointer-events-none">
139
+ <div className="max-w-lg">
140
+ {slide.category && (
141
+ <span className="inline-block bg-lime-500/50 border-lime-500 border text-stone-200 text-xs font-bold px-3 py-1 rounded-full mb-3 uppercase tracking-widest scale-90 origin-top-left">
142
+ {slide.category}
143
+ </span>
144
+ )}
145
+ <h3 className="text-stone-50 font-black text-3xl lg:text-4xl mb-3">
146
+ {slide.title}
147
+ </h3>
148
+ {slide.description && (
149
+ <p className="text-stone-50/70 text-base leading-relaxed">
150
+ {slide.description}
151
+ </p>
152
+ )}
153
+ </div>
154
+ </div>
155
+ </div>
156
+ )
157
+ })}
158
+ </div>
159
+ </div>
160
+
161
+ {/* Navigation arrows */}
162
+ <button
163
+ onClick={scrollPrev}
164
+ aria-label="Previous slide"
165
+ className="absolute left-1 top-1/2 -translate-y-1/2 z-20 w-12 h-12 flex items-center justify-center rounded-full bg-stone-50/5 hover:bg-lime-500 text-stone-50 backdrop-blur-sm transition-all cursor-pointer"
166
+ >
167
+ <ChevronLeft className="w-5 h-5" />
168
+ </button>
169
+ <button
170
+ onClick={scrollNext}
171
+ aria-label="Next slide"
172
+ className="absolute right-1 top-1/2 -translate-y-1/2 z-20 w-12 h-12 flex items-center justify-center rounded-full bg-stone-50/5 hover:bg-lime-500 text-stone-50 backdrop-blur-sm transition-all cursor-pointer"
173
+ >
174
+ <ChevronRight className="w-5 h-5" />
175
+ </button>
176
+
177
+ {/* Thumbnail strip */}
178
+ <div className="absolute bottom-4 right-4 lg:right-8 z-20 flex gap-2">
179
+ {slides.map((slide, i) => {
180
+ const imgSrc = getImageSrc(slide)
181
+ return (
182
+ <button
183
+ key={slide.id ?? i}
184
+ onClick={() => scrollTo(i)}
185
+ aria-label={`Go to slide: ${slide.title}`}
186
+ className={cn(
187
+ 'relative w-16 h-12 rounded-lg overflow-hidden border-2 transition-all cursor-pointer',
188
+ i === activeIdx
189
+ ? 'border-lime-500 scale-110'
190
+ : 'border-white/30 opacity-60 hover:opacity-100',
191
+ )}
192
+ >
193
+ {imgSrc ? (
194
+ <>
195
+ {!loadedThumbs[i] && (
196
+ <div className="absolute inset-0 bg-stone-800 animate-pulse" />
197
+ )}
198
+ <Image
199
+ src={imgSrc}
200
+ alt={slide.title}
201
+ fill
202
+ sizes="64px"
203
+ className="w-full h-full object-cover object-top"
204
+ draggable={false}
205
+ onLoad={() => setLoadedThumbs((prev) => ({ ...prev, [i]: true }))}
206
+ />
207
+ </>
208
+ ) : (
209
+ <div className="w-full h-full bg-stone-800" />
210
+ )}
211
+ </button>
212
+ )
213
+ })}
214
+ </div>
215
+ </div>
216
+ {/* Dot indicators */}
217
+ {slides.length > 1 && (
218
+ <div className="flex justify-center gap-2 mt-6">
219
+ {slides.map((_, i) => (
220
+ <button
221
+ key={i}
222
+ onClick={() => scrollTo(i)}
223
+ aria-label={`Go to slide ${i + 1}`}
224
+ className={cn(
225
+ 'rounded-full transition-all duration-300 cursor-pointer',
226
+ i === activeIdx
227
+ ? 'w-8 h-2.5 bg-lime-500'
228
+ : 'w-2.5 h-2.5 bg-stone-600 hover:bg-stone-400',
229
+ )}
230
+ />
231
+ ))}
232
+ </div>
233
+ )}
234
+ </div>
235
+ </section>
236
+ )
237
+ }