blocks-dusted 0.1.10 → 0.1.12

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 (249) hide show
  1. package/README.md +35 -0
  2. package/package.json +5 -2
  3. package/src/cli.js +39 -3
  4. package/src/commands/shop.js +59 -0
  5. package/src/commands/warehouse.js +73 -0
  6. package/src/utils/paths.js +13 -13
  7. package/templates/blocks/DD-Accordion/Component.tsx +229 -0
  8. package/templates/blocks/DD-Accordion/README.md +3 -0
  9. package/templates/blocks/DD-Accordion/config.ts +73 -0
  10. package/templates/blocks/DD-Accordion/manifest.json +75 -0
  11. package/templates/blocks/DD-AnimText/Component.tsx +788 -0
  12. package/templates/blocks/DD-AnimText/README.md +3 -0
  13. package/templates/blocks/DD-AnimText/config.ts +295 -0
  14. package/templates/blocks/DD-AnimText/manifest.json +85 -0
  15. package/templates/blocks/DD-AsideNav/Component.tsx +321 -0
  16. package/templates/blocks/DD-AsideNav/config.ts +204 -0
  17. package/templates/blocks/DD-AsideNav/manifest.json +80 -0
  18. package/templates/blocks/DD-Banner/Component.tsx +51 -0
  19. package/templates/blocks/DD-Banner/config.ts +57 -0
  20. package/templates/blocks/DD-Banner/manifest.json +72 -0
  21. package/templates/blocks/DD-BgLightning/Component.tsx +44 -0
  22. package/templates/blocks/DD-BgLightning/README.md +3 -0
  23. package/templates/blocks/DD-BgLightning/config.ts +97 -0
  24. package/templates/blocks/DD-BgLightning/manifest.json +79 -0
  25. package/templates/blocks/DD-BookCall/Component.tsx +535 -535
  26. package/templates/blocks/DD-BookCall/config.ts +238 -238
  27. package/templates/blocks/DD-ButtonPop/Component.tsx +117 -0
  28. package/templates/blocks/DD-ButtonPop/config.ts +105 -0
  29. package/templates/blocks/DD-ButtonPop/manifest.json +88 -0
  30. package/templates/blocks/DD-ButtonPop/svg-icons/Dust2.tsx +167 -0
  31. package/templates/blocks/DD-Card/Component.tsx +251 -0
  32. package/templates/blocks/DD-Card/README.md +4 -0
  33. package/templates/blocks/DD-Card/config.ts +154 -0
  34. package/templates/blocks/DD-Card/manifest.json +82 -0
  35. package/templates/blocks/DD-CardTemplate01/Component.tsx +140 -140
  36. package/templates/blocks/DD-CardTemplate01/config.ts +106 -106
  37. package/templates/blocks/DD-Carousel-A/Component.tsx +249 -249
  38. package/templates/blocks/DD-Carousel-B/Component.tsx +403 -403
  39. package/templates/blocks/DD-Chip/Component.tsx +250 -0
  40. package/templates/blocks/DD-Chip/EXAMPLES.md +313 -0
  41. package/templates/blocks/DD-Chip/README.md +202 -0
  42. package/templates/blocks/DD-Chip/config.ts +369 -0
  43. package/templates/blocks/DD-Chip/manifest.json +74 -0
  44. package/templates/blocks/DD-ComparisonTable/Component.tsx +256 -256
  45. package/templates/blocks/DD-Contact/Component.tsx +435 -434
  46. package/templates/blocks/DD-Contact/config.ts +22 -22
  47. package/templates/blocks/DD-Counter/Component.tsx +190 -0
  48. package/templates/blocks/DD-Counter/README.md +4 -0
  49. package/templates/blocks/DD-Counter/config.ts +291 -0
  50. package/templates/blocks/DD-Counter/manifest.json +74 -0
  51. package/templates/blocks/DD-CustomCSS/Component.tsx +8 -0
  52. package/templates/blocks/DD-CustomCSS/README.md +4 -0
  53. package/templates/blocks/DD-CustomCSS/addToLayout.md +5 -0
  54. package/templates/blocks/DD-CustomCSS/config.ts +22 -0
  55. package/templates/blocks/DD-CustomCSS/manifest.json +72 -0
  56. package/templates/blocks/DD-CustomCSS/renderCustomCSS.md +7 -0
  57. package/templates/blocks/DD-FeatCat/Component.tsx +302 -302
  58. package/templates/blocks/DD-FeatCat/config.ts +201 -201
  59. package/templates/blocks/DD-FeatStrip/Component.tsx +585 -585
  60. package/templates/blocks/DD-FeatStrip/config.ts +134 -134
  61. package/templates/blocks/DD-FluidBtn/ButtonsEyeCatching.tsx +628 -0
  62. package/templates/blocks/DD-FluidBtn/Component.tsx +43 -0
  63. package/templates/blocks/DD-FluidBtn/README.md +6 -0
  64. package/templates/blocks/DD-FluidBtn/config.ts +102 -0
  65. package/templates/blocks/DD-FluidBtn/manifest.json +86 -0
  66. package/templates/blocks/DD-GooeyInfo/Component.tsx +149 -0
  67. package/templates/blocks/DD-GooeyInfo/config.ts +135 -0
  68. package/templates/blocks/DD-GooeyInfo/manifest.json +72 -0
  69. package/templates/blocks/DD-Hero/Component.tsx +297 -297
  70. package/templates/blocks/DD-Hero/config.ts +267 -267
  71. package/templates/blocks/DD-HorizontalScroll/Component.tsx +936 -936
  72. package/templates/blocks/DD-HorizontalScroll/config.ts +231 -231
  73. package/templates/blocks/DD-Icon/Component.tsx +287 -0
  74. package/templates/blocks/DD-Icon/README.md +4 -0
  75. package/templates/blocks/DD-Icon/config.ts +258 -0
  76. package/templates/blocks/DD-Icon/hooks/revalidateBlockIds.ts +66 -0
  77. package/templates/blocks/DD-Icon/manifest.json +82 -0
  78. package/templates/blocks/DD-IconButton/Component.tsx +455 -0
  79. package/templates/blocks/DD-IconButton/README.md +326 -0
  80. package/templates/blocks/DD-IconButton/config.ts +239 -0
  81. package/templates/blocks/DD-IconButton/manifest.json +85 -0
  82. package/templates/blocks/DD-IdTag/Component.tsx +21 -0
  83. package/templates/blocks/DD-IdTag/README.md +4 -0
  84. package/templates/blocks/DD-IdTag/config.ts +41 -0
  85. package/templates/blocks/DD-IdTag/manifest.json +72 -0
  86. package/templates/blocks/DD-Iframe/Component.tsx +45 -0
  87. package/templates/blocks/DD-Iframe/README.md +4 -0
  88. package/templates/blocks/DD-Iframe/config.ts +47 -0
  89. package/templates/blocks/DD-Iframe/manifest.json +72 -0
  90. package/templates/blocks/DD-IframeMedia/Component.tsx +133 -0
  91. package/templates/blocks/DD-IframeMedia/README.md +4 -0
  92. package/templates/blocks/DD-IframeMedia/config.ts +79 -0
  93. package/templates/blocks/DD-IframeMedia/manifest.json +72 -0
  94. package/templates/blocks/DD-LineAnim/Component.tsx +36 -0
  95. package/templates/blocks/DD-LineAnim/README.md +4 -0
  96. package/templates/blocks/DD-LineAnim/config.ts +90 -0
  97. package/templates/blocks/DD-LineAnim/manifest.json +80 -0
  98. package/templates/blocks/DD-LinkGrid/Component.tsx +119 -0
  99. package/templates/blocks/DD-LinkGrid/README.md +4 -0
  100. package/templates/blocks/DD-LinkGrid/config.ts +51 -0
  101. package/templates/blocks/DD-LinkGrid/manifest.json +74 -0
  102. package/templates/blocks/DD-LogoMarq/Component.tsx +132 -0
  103. package/templates/blocks/DD-LogoMarq/README.md +4 -0
  104. package/templates/blocks/DD-LogoMarq/config.ts +264 -0
  105. package/templates/blocks/DD-LogoMarq/manifest.json +82 -0
  106. package/templates/blocks/DD-Lottie/Component.tsx +459 -0
  107. package/templates/blocks/DD-Lottie/README.md +4 -0
  108. package/templates/blocks/DD-Lottie/config.ts +379 -0
  109. package/templates/blocks/DD-Lottie/index.tsx +28 -0
  110. package/templates/blocks/DD-Lottie/manifest.json +83 -0
  111. package/templates/blocks/DD-MagicBento/Component.tsx +777 -0
  112. package/templates/blocks/DD-MagicBento/README.md +4 -0
  113. package/templates/blocks/DD-MagicBento/config.ts +264 -0
  114. package/templates/blocks/DD-MagicBento/manifest.json +74 -0
  115. package/templates/blocks/DD-MasonaryMedia/Component.tsx +545 -545
  116. package/templates/blocks/DD-MasonaryMedia/config.ts +2 -2
  117. package/templates/blocks/DD-MasonaryMedia/vendor.d.ts +2 -2
  118. package/templates/blocks/DD-Masonry/Component.tsx +645 -0
  119. package/templates/blocks/DD-Masonry/README.md +4 -0
  120. package/templates/blocks/DD-Masonry/config.ts +475 -0
  121. package/templates/blocks/DD-Masonry/manifest.json +82 -0
  122. package/templates/blocks/DD-Pricing/Component.tsx +2 -5
  123. package/templates/blocks/DD-Process/Component.tsx +2 -4
  124. package/templates/blocks/DD-Process/config.ts +150 -150
  125. package/templates/blocks/DD-Progress/Component.tsx +156 -0
  126. package/templates/blocks/DD-Progress/README.md +4 -0
  127. package/templates/blocks/DD-Progress/config.ts +175 -0
  128. package/templates/blocks/DD-Progress/manifest.json +74 -0
  129. package/templates/blocks/DD-ProjHeader/Component.tsx +91 -0
  130. package/templates/blocks/DD-ProjHeader/README.md +4 -0
  131. package/templates/blocks/DD-ProjHeader/config.ts +96 -0
  132. package/templates/blocks/DD-ProjHeader/manifest.json +72 -0
  133. package/templates/blocks/DD-ProjSlider/Component.tsx +123 -0
  134. package/templates/blocks/DD-ProjSlider/README.md +4 -0
  135. package/templates/blocks/DD-ProjSlider/config.ts +64 -0
  136. package/templates/blocks/DD-ProjSlider/manifest.json +72 -0
  137. package/templates/blocks/DD-ScrollText/Component.tsx +114 -0
  138. package/templates/blocks/DD-ScrollText/README.md +4 -0
  139. package/templates/blocks/DD-ScrollText/config.ts +58 -0
  140. package/templates/blocks/DD-ScrollText/manifest.json +72 -0
  141. package/templates/blocks/DD-Section/Component.tsx +266 -266
  142. package/templates/blocks/DD-Separator/Component.tsx +66 -0
  143. package/templates/blocks/DD-Separator/README.md +4 -0
  144. package/templates/blocks/DD-Separator/config.ts +58 -0
  145. package/templates/blocks/DD-Separator/manifest.json +72 -0
  146. package/templates/blocks/DD-Services/Component.tsx +176 -176
  147. package/templates/blocks/DD-Services/config.ts +149 -149
  148. package/templates/blocks/DD-ShowcaseGrid/Component.tsx +307 -307
  149. package/templates/blocks/DD-Slider-A/Component.tsx +1 -1
  150. package/templates/blocks/DD-StackCards/Component.tsx +1 -1
  151. package/templates/blocks/DD-StackCards/config.ts +399 -399
  152. package/templates/blocks/DD-Team/Component.tsx +247 -247
  153. package/templates/blocks/DD-Team/config.ts +173 -173
  154. package/templates/blocks/DD-TeamCards/Component.tsx +251 -0
  155. package/templates/blocks/DD-TeamCards/README.md +4 -0
  156. package/templates/blocks/DD-TeamCards/config.ts +302 -0
  157. package/templates/blocks/DD-TeamCards/manifest.json +74 -0
  158. package/templates/blocks/DD-TechStack/Component.tsx +174 -174
  159. package/templates/blocks/DD-TechStack/config.ts +153 -153
  160. package/templates/blocks/DD-TestMarq/Component.tsx +132 -0
  161. package/templates/blocks/DD-TestMarq/README.md +4 -0
  162. package/templates/blocks/DD-TestMarq/RowLabel.tsx +9 -0
  163. package/templates/blocks/DD-TestMarq/config.ts +88 -0
  164. package/templates/blocks/DD-TestMarq/manifest.json +72 -0
  165. package/templates/blocks/DD-Testim/Component.tsx +247 -0
  166. package/templates/blocks/DD-Testim/README.md +4 -0
  167. package/templates/blocks/DD-Testim/config.ts +101 -0
  168. package/templates/blocks/DD-Testim/manifest.json +75 -0
  169. package/templates/blocks/DD-Testim2/Component.tsx +256 -0
  170. package/templates/blocks/DD-Testim2/README.md +4 -0
  171. package/templates/blocks/DD-Testim2/config.ts +100 -0
  172. package/templates/blocks/DD-Testim2/manifest.json +74 -0
  173. package/templates/blocks/DD-Testimonials/Component.tsx +139 -147
  174. package/templates/blocks/DD-Testimonials/config.ts +120 -120
  175. package/templates/blocks/DD-TextMarq/Component.tsx +157 -0
  176. package/templates/blocks/DD-TextMarq/README.md +4 -0
  177. package/templates/blocks/DD-TextMarq/config.ts +237 -0
  178. package/templates/blocks/DD-TextMarq/manifest.json +82 -0
  179. package/templates/blocks/DD-Thanks/Component.tsx +501 -0
  180. package/templates/blocks/DD-Thanks/README.md +4 -0
  181. package/templates/blocks/DD-Thanks/config.ts +224 -0
  182. package/templates/blocks/DD-Thanks/manifest.json +72 -0
  183. package/templates/blocks/DD-VertMarq/Component.tsx +154 -0
  184. package/templates/blocks/DD-VertMarq/README.md +4 -0
  185. package/templates/blocks/DD-VertMarq/config.ts +236 -0
  186. package/templates/blocks/DD-VertMarq/dist/index.css +6 -0
  187. package/templates/blocks/DD-VertMarq/dist/index.min.css +1 -0
  188. package/templates/blocks/DD-VertMarq/manifest.json +74 -0
  189. package/templates/blocks/DD-Work/Component.tsx +318 -315
  190. package/templates/blocks/DD-Work/config.ts +165 -165
  191. package/templates/blocks/DD-Work-B/Component.tsx +294 -294
  192. package/templates/blocks/DDHG-CTASection2/Component.tsx +185 -0
  193. package/templates/blocks/DDHG-CTASection2/config.ts +140 -0
  194. package/templates/blocks/DDHG-CTASection2/manifest.json +45 -0
  195. package/templates/blocks/DDHG-HighlightArc/Component.tsx +243 -0
  196. package/templates/blocks/DDHG-HighlightArc/config.ts +85 -0
  197. package/templates/blocks/DDHG-HighlightArc/manifest.json +46 -0
  198. package/templates/blocks/DDHG-LogoMarquee/Component.tsx +143 -0
  199. package/templates/blocks/DDHG-LogoMarquee/config.ts +154 -0
  200. package/templates/blocks/DDHG-LogoMarquee/manifest.json +39 -0
  201. package/templates/blocks/DDHG-NavCodeGrid/Component.tsx +227 -0
  202. package/templates/blocks/DDHG-NavCodeGrid/config.ts +64 -0
  203. package/templates/blocks/DDHG-NavCodeGrid/manifest.json +45 -0
  204. package/templates/blocks/DDHG-ProcessSec/Component.tsx +574 -0
  205. package/templates/blocks/DDHG-ProcessSec/config.ts +170 -0
  206. package/templates/blocks/DDHG-ProcessSec/manifest.json +56 -0
  207. package/templates/blocks/DDHG-ServiceDetails/Component.tsx +164 -0
  208. package/templates/blocks/DDHG-ServiceDetails/ServiceDetailCard.tsx +390 -0
  209. package/templates/blocks/DDHG-ServiceDetails/config.ts +77 -0
  210. package/templates/blocks/DDHG-ServiceDetails/manifest.json +50 -0
  211. package/templates/blocks/DDHG-ServiceHero/Component.tsx +234 -0
  212. package/templates/blocks/DDHG-ServiceHero/config.ts +49 -0
  213. package/templates/blocks/DDHG-ServiceHero/manifest.json +26 -0
  214. package/templates/blocks/DDHG-ServiceOverview/Component.tsx +277 -0
  215. package/templates/blocks/DDHG-ServiceOverview/config.ts +64 -0
  216. package/templates/blocks/DDHG-ServiceOverview/manifest.json +44 -0
  217. package/templates/blocks/DDHG-StatsFAQ/Component.tsx +389 -0
  218. package/templates/blocks/DDHG-StatsFAQ/config.ts +204 -0
  219. package/templates/blocks/DDHG-StatsFAQ/manifest.json +45 -0
  220. package/templates/blocks/DDHG-TeamCards/Component.tsx +169 -0
  221. package/templates/blocks/DDHG-TeamCards/config.ts +48 -0
  222. package/templates/blocks/DDHG-TeamCards/manifest.json +29 -0
  223. package/templates/shared/src/components/BgLightning/index.tsx +242 -0
  224. package/templates/shared/src/components/ui/ShareButton/index.tsx +80 -0
  225. package/templates/shared/src/components/ui/anims/FadeOnScroll.tsx +48 -0
  226. package/templates/shared/src/components/ui/anims/FadeUpOnInView.tsx +63 -0
  227. package/templates/shared/src/components/ui/anims/GlareHover.tsx +111 -0
  228. package/templates/shared/src/components/ui/anims/LineAnims.tsx +81 -0
  229. package/templates/shared/src/components/ui/anims/TitleAnims01.tsx +54 -0
  230. package/templates/shared/src/components/ui/anims/animation-container.tsx +29 -0
  231. package/templates/shared/src/components/ui/anims/blur-in.tsx +26 -0
  232. package/templates/shared/src/components/ui/anims/counter.tsx +53 -0
  233. package/templates/shared/src/components/ui/anims/fade.tsx +83 -0
  234. package/templates/shared/src/components/ui/anims/gradual-spacing.tsx +39 -0
  235. package/templates/shared/src/components/ui/anims/pull-up.tsx +93 -0
  236. package/templates/shared/src/components/ui/anims/reveal-text.tsx +76 -0
  237. package/templates/shared/src/components/ui/anims/rotate-words.tsx +43 -0
  238. package/templates/shared/src/components/ui/anims/shiny-text.tsx +58 -0
  239. package/templates/shared/src/components/ui/anims/shuffle-text.tsx +85 -0
  240. package/templates/shared/src/components/ui/anims/split-text.tsx +211 -0
  241. package/templates/shared/src/components/ui/anims/staggered-fade.tsx +133 -0
  242. package/templates/shared/src/components/ui/anims/text-scroll.tsx +111 -0
  243. package/templates/shared/src/components/ui/anims/typing-effect.tsx +112 -0
  244. package/templates/shared/src/components/ui/button-dusted-fluid/index.tsx +1521 -0
  245. package/templates/shared/src/components/ui/separator.tsx +85 -0
  246. package/templates/shared/src/hooks/useBodyScrollLock.ts +97 -0
  247. package/templates/shared/src/hooks/useClickOutside.ts +28 -0
  248. package/templates/shared/src/utilities/renderCustomCSS.ts +5 -0
  249. package/templates/shared/src/utilities/smoothScroll.ts +65 -0
@@ -1,174 +1,174 @@
1
- 'use client'
2
-
3
- import React from 'react'
4
- import Image from 'next/image'
5
- import * as LucideIcons from 'lucide-react'
6
- export type DDTechStackProps = Record<string, any>
7
-
8
- // ─── Types ────────────────────────────────────────────────────────────────────
9
- type MediaObj = { url?: string | null; alt?: string | null }
10
- type TechStackItem = {
11
- image?: string | MediaObj | null
12
- icon?: string | null
13
- label?: string | null
14
- showLabel?: boolean | null
15
- }
16
- type TechStackRow = {
17
- duration?: number | null
18
- direction?: 'left' | 'right' | null
19
- items?: TechStackItem[] | null
20
- }
21
-
22
- function getMediaUrl(media: string | MediaObj | null | undefined): string | null {
23
- if (!media) return null
24
- if (typeof media === 'string') return media
25
- return media.url ?? null
26
- }
27
-
28
- function getMediaAlt(media: string | MediaObj | null | undefined): string | null {
29
- if (!media || typeof media === 'string') return null
30
- return (media as MediaObj).alt ?? null
31
- }
32
-
33
- // ─── Dynamic Lucide Icon ──────────────────────────────────────────────────────
34
- function isLocalImageSrc(src?: string | null): src is string {
35
- return typeof src === 'string' && src.startsWith('/api/media/file/')
36
- }
37
-
38
- function DynamicIcon({
39
- name,
40
- size = 36,
41
- ...props
42
- }: { name?: string | null } & Omit<LucideIcons.LucideProps, 'name'>) {
43
- if (!name) return null
44
- const Icon = (LucideIcons as unknown as Record<string, React.FC<LucideIcons.LucideProps>>)[name]
45
- if (!Icon) return null
46
- return <Icon size={size} {...props} />
47
- }
48
-
49
- // ─── Tech Item Card ───────────────────────────────────────────────────────────
50
- function TechItem({
51
- imageUrl,
52
- imageAlt,
53
- icon,
54
- label,
55
- showLabel,
56
- }: {
57
- imageUrl?: string | null
58
- imageAlt?: string | null
59
- icon?: string | null
60
- label?: string | null
61
- showLabel?: boolean | null
62
- }) {
63
- const safeImageUrl = isLocalImageSrc(imageUrl) ? imageUrl : null
64
-
65
- return (
66
- <div className="relative group/tech text-center shrink-0">
67
- <div className="w-20 h-20 bg-linear-to-b from-[#1a1a1f] to-[#0f0f12] rounded-2xl p-4 transition-all duration-300 hover:scale-105 hover:shadow-[0_0_30px_rgba(140,138,132,0.15)] flex items-center justify-center border border-[#2A2A30] invert-[1] dark:invert-[0]">
68
- {safeImageUrl ? (
69
- <Image
70
- src={safeImageUrl}
71
- alt={imageAlt ?? label ?? 'Tech logo'}
72
- width={40}
73
- height={40}
74
- loading="lazy"
75
- className="object-contain w-10 h-10"
76
- />
77
- ) : (
78
- <DynamicIcon className="text-[#9A9790]" name={icon} size={36} />
79
- )}
80
- </div>
81
- {showLabel !== false && label && (
82
- <span className="text-sm whitespace-nowrap text-[#9A9790] mt-2 block">{label}</span>
83
- )}
84
- </div>
85
- )
86
- }
87
-
88
- // ─── Component ────────────────────────────────────────────────────────────────
89
- export const DDTechStack: React.FC<DDTechStackProps> = ({
90
- eyebrow,
91
- heading,
92
- techRows,
93
- sectionId,
94
- customCSS,
95
- }) => {
96
- const rows = (techRows ?? []) as TechStackRow[]
97
- if (!rows.length) return null
98
-
99
- const rowMaskStyle: React.CSSProperties = {
100
- maskImage: 'linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%)',
101
- WebkitMaskImage:
102
- 'linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%)',
103
- }
104
-
105
- return (
106
- <section
107
- id={sectionId ?? undefined}
108
- className="relative w-full bg-transparent py-12 px-4 md:px-6 max-w-6xl mx-auto"
109
- >
110
- {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
111
-
112
- {(eyebrow || heading) && (
113
- <div className="mb-12">
114
- #BlockName-DDTechStack
115
- {eyebrow && (
116
- <p className="text-sm uppercase tracking-widest text-[#9A9790] mb-2">{eyebrow}</p>
117
- )}
118
- {heading && (
119
- <h2
120
- className="text-3xl font-bold tracking-tighter sm:text-4xl text-stone-900 dark:text-stone-50"
121
- style={{ fontFamily: "'Space Grotesk', sans-serif" }}
122
- >
123
- {heading}
124
- </h2>
125
- )}
126
- </div>
127
- )}
128
-
129
- <div className="relative">
130
- {rows.map((row, rowIndex) => {
131
- const duration = row.duration ?? 40
132
- const direction = row.direction ?? 'left'
133
- const items = row.items ?? []
134
-
135
- if (items.length === 0) return null
136
-
137
- // Quadruple items for a seamless infinite loop
138
- const duplicated = [...items, ...items, ...items, ...items]
139
-
140
- return (
141
- <div
142
- key={rowIndex}
143
- className="overflow-x-hidden overflow-y-visible py-6 relative"
144
- style={rowMaskStyle}
145
- >
146
- <div
147
- className="flex shrink-0 justify-around gap-8 flex-row animate-marquee"
148
- style={{
149
- animationDuration: `${duration}s`,
150
- animationDirection: direction === 'right' ? 'reverse' : 'normal',
151
- }}
152
- >
153
- {duplicated.map((item, idx) => {
154
- const imgUrl = getMediaUrl(item.image as string | MediaObj | null)
155
- const imgAlt = getMediaAlt(item.image as string | MediaObj | null)
156
- return (
157
- <TechItem
158
- key={idx}
159
- imageUrl={imgUrl}
160
- imageAlt={imgAlt}
161
- icon={item.icon}
162
- label={item.label}
163
- showLabel={item.showLabel}
164
- />
165
- )
166
- })}
167
- </div>
168
- </div>
169
- )
170
- })}
171
- </div>
172
- </section>
173
- )
174
- }
1
+ 'use client'
2
+
3
+ import React from 'react'
4
+ import Image from 'next/image'
5
+ import * as LucideIcons from 'lucide-react'
6
+ export type DDTechStackProps = Record<string, any>
7
+
8
+ // ─── Types ────────────────────────────────────────────────────────────────────
9
+ type MediaObj = { url?: string | null; alt?: string | null }
10
+ type TechStackItem = {
11
+ image?: string | MediaObj | null
12
+ icon?: string | null
13
+ label?: string | null
14
+ showLabel?: boolean | null
15
+ }
16
+ type TechStackRow = {
17
+ duration?: number | null
18
+ direction?: 'left' | 'right' | null
19
+ items?: TechStackItem[] | null
20
+ }
21
+
22
+ function getMediaUrl(media: string | MediaObj | null | undefined): string | null {
23
+ if (!media) return null
24
+ if (typeof media === 'string') return media
25
+ return media.url ?? null
26
+ }
27
+
28
+ function getMediaAlt(media: string | MediaObj | null | undefined): string | null {
29
+ if (!media || typeof media === 'string') return null
30
+ return (media as MediaObj).alt ?? null
31
+ }
32
+
33
+ // ─── Dynamic Lucide Icon ──────────────────────────────────────────────────────
34
+ function isLocalImageSrc(src?: string | null): src is string {
35
+ return typeof src === 'string' && src.startsWith('/api/media/file/')
36
+ }
37
+
38
+ function DynamicIcon({
39
+ name,
40
+ size = 36,
41
+ ...props
42
+ }: { name?: string | null } & Omit<LucideIcons.LucideProps, 'name'>) {
43
+ if (!name) return null
44
+ const Icon = (LucideIcons as unknown as Record<string, React.FC<LucideIcons.LucideProps>>)[name]
45
+ if (!Icon) return null
46
+ return <Icon size={size} {...props} />
47
+ }
48
+
49
+ // ─── Tech Item Card ───────────────────────────────────────────────────────────
50
+ function TechItem({
51
+ imageUrl,
52
+ imageAlt,
53
+ icon,
54
+ label,
55
+ showLabel,
56
+ }: {
57
+ imageUrl?: string | null
58
+ imageAlt?: string | null
59
+ icon?: string | null
60
+ label?: string | null
61
+ showLabel?: boolean | null
62
+ }) {
63
+ const safeImageUrl = isLocalImageSrc(imageUrl) ? imageUrl : null
64
+
65
+ return (
66
+ <div className="relative group/tech text-center shrink-0">
67
+ <div className="w-20 h-20 bg-linear-to-b from-[#1a1a1f] to-[#0f0f12] rounded-2xl p-4 transition-all duration-300 hover:scale-105 hover:shadow-[0_0_30px_rgba(140,138,132,0.15)] flex items-center justify-center border border-[#2A2A30] invert-[1] dark:invert-[0]">
68
+ {safeImageUrl ? (
69
+ <Image
70
+ src={safeImageUrl}
71
+ alt={imageAlt ?? label ?? 'Tech logo'}
72
+ width={40}
73
+ height={40}
74
+ loading="lazy"
75
+ className="object-contain w-10 h-10"
76
+ />
77
+ ) : (
78
+ <DynamicIcon className="text-[#9A9790]" name={icon} size={36} />
79
+ )}
80
+ </div>
81
+ {showLabel !== false && label && (
82
+ <span className="text-sm whitespace-nowrap text-[#9A9790] mt-2 block">{label}</span>
83
+ )}
84
+ </div>
85
+ )
86
+ }
87
+
88
+ // ─── Component ────────────────────────────────────────────────────────────────
89
+ export const DDTechStack: React.FC<DDTechStackProps> = ({
90
+ eyebrow,
91
+ heading,
92
+ techRows,
93
+ sectionId,
94
+ customCSS,
95
+ }) => {
96
+ const rows = (techRows ?? []) as TechStackRow[]
97
+ if (!rows.length) return null
98
+
99
+ const rowMaskStyle: React.CSSProperties = {
100
+ maskImage: 'linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%)',
101
+ WebkitMaskImage:
102
+ 'linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%)',
103
+ }
104
+
105
+ return (
106
+ <section
107
+ id={sectionId ?? undefined}
108
+ className="relative w-full bg-transparent py-12 px-4 md:px-6 max-w-6xl mx-auto"
109
+ >
110
+ {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
111
+
112
+ {(eyebrow || heading) && (
113
+ <div className="mb-12">
114
+ #BlockName-DDTechStack
115
+ {eyebrow && (
116
+ <p className="text-sm uppercase tracking-widest text-[#9A9790] mb-2">{eyebrow}</p>
117
+ )}
118
+ {heading && (
119
+ <h2
120
+ className="text-3xl font-bold tracking-tighter sm:text-4xl text-stone-900 dark:text-stone-50"
121
+ style={{ fontFamily: "'Space Grotesk', sans-serif" }}
122
+ >
123
+ {heading}
124
+ </h2>
125
+ )}
126
+ </div>
127
+ )}
128
+
129
+ <div className="relative">
130
+ {rows.map((row, rowIndex) => {
131
+ const duration = row.duration ?? 40
132
+ const direction = row.direction ?? 'left'
133
+ const items = row.items ?? []
134
+
135
+ if (items.length === 0) return null
136
+
137
+ // Quadruple items for a seamless infinite loop
138
+ const duplicated = [...items, ...items, ...items, ...items]
139
+
140
+ return (
141
+ <div
142
+ key={rowIndex}
143
+ className="overflow-x-hidden overflow-y-visible py-6 relative"
144
+ style={rowMaskStyle}
145
+ >
146
+ <div
147
+ className="flex shrink-0 justify-around gap-8 flex-row animate-marquee"
148
+ style={{
149
+ animationDuration: `${duration}s`,
150
+ animationDirection: direction === 'right' ? 'reverse' : 'normal',
151
+ }}
152
+ >
153
+ {duplicated.map((item, idx) => {
154
+ const imgUrl = getMediaUrl(item.image as string | MediaObj | null)
155
+ const imgAlt = getMediaAlt(item.image as string | MediaObj | null)
156
+ return (
157
+ <TechItem
158
+ key={idx}
159
+ imageUrl={imgUrl}
160
+ imageAlt={imgAlt}
161
+ icon={item.icon}
162
+ label={item.label}
163
+ showLabel={item.showLabel}
164
+ />
165
+ )
166
+ })}
167
+ </div>
168
+ </div>
169
+ )
170
+ })}
171
+ </div>
172
+ </section>
173
+ )
174
+ }
@@ -1,153 +1,153 @@
1
- import type { Block } from 'payload'
2
- import { iconPicker } from '@/fields/lucide-icon-picker/field'
3
-
4
- export const DDTechStack: Block = {
5
- slug: 'DD-TechStack',
6
- interfaceName: 'DDTechStack',
7
- labels: {
8
- singular: 'Tech Stack',
9
- plural: 'Tech Stacks'
10
- },
11
- fields: [
12
- {
13
- type: 'tabs',
14
- tabs: [
15
- {
16
- label: 'Header',
17
- fields: [
18
- {
19
- name: 'eyebrow',
20
- type: 'text',
21
- label: 'Eyebrow Label',
22
- admin: { description: 'Small text above the heading (e.g. "Our Stack")' }
23
- },
24
- {
25
- name: 'heading',
26
- type: 'text',
27
- label: 'Heading',
28
- defaultValue: 'Technology Stack',
29
- admin: { description: 'Main section heading. Leave blank to hide.' }
30
- }
31
- ]
32
- },
33
- {
34
- label: 'Rows',
35
- fields: [
36
- {
37
- name: 'techRows',
38
- type: 'array',
39
- label: 'Tech Rows',
40
- minRows: 1,
41
- maxRows: 3,
42
- admin: {
43
- description: 'Up to 3 independently scrolling rows of tech stack items.'
44
- },
45
- fields: [
46
- {
47
- type: 'row',
48
- fields: [
49
- {
50
- name: 'direction',
51
- type: 'select',
52
- label: 'Scroll Direction',
53
- defaultValue: 'left',
54
- options: [
55
- { label: '← Left', value: 'left' },
56
- { label: '→ Right', value: 'right' }
57
- ],
58
- admin: { width: '50%' }
59
- },
60
- {
61
- name: 'duration',
62
- type: 'number',
63
- label: 'Duration (seconds)',
64
- defaultValue: 40,
65
- min: 5,
66
- max: 180,
67
- admin: {
68
- description: 'Scroll cycle time — lower = faster.',
69
- width: '50%'
70
- }
71
- }
72
- ]
73
- },
74
- {
75
- name: 'items',
76
- type: 'array',
77
- label: 'Tech Items',
78
- minRows: 1,
79
- admin: {
80
- description:
81
- 'Each item shows a logo image or Lucide icon with an optional label. Upload takes priority over icon.'
82
- },
83
- fields: [
84
- {
85
- name: 'image',
86
- type: 'upload',
87
- label: 'Custom Logo',
88
- relationTo: 'media',
89
- admin: {
90
- description:
91
- 'Upload SVG, PNG, JPG, WebP, or GIF. Takes priority over the Lucide icon.'
92
- }
93
- },
94
- {
95
- type: 'row',
96
- fields: [
97
- iconPicker({
98
- name: 'icon',
99
- required: false,
100
- description: 'Fallback if no logo is uploaded (e.g. Code2, Database)',
101
- overrides: (field) => ({
102
- ...field,
103
- label: 'Lucide Icon (fallback)',
104
- admin: { ...field.admin, width: '40%' }
105
- })
106
- }),
107
- {
108
- name: 'label',
109
- type: 'text',
110
- label: 'Label',
111
- admin: {
112
- description: 'e.g. "React", "TypeScript"',
113
- width: '40%'
114
- }
115
- },
116
- {
117
- name: 'showLabel',
118
- type: 'checkbox',
119
- label: 'Show Label',
120
- defaultValue: true,
121
- admin: { width: '20%' }
122
- }
123
- ]
124
- }
125
- ]
126
- }
127
- ]
128
- }
129
- ]
130
- },
131
- {
132
- label: 'Settings',
133
- fields: [
134
- {
135
- name: 'sectionId',
136
- type: 'text',
137
- label: 'Section ID',
138
- admin: {
139
- description: 'Unique ID for anchor linking (e.g. "tech-stack")'
140
- }
141
- },
142
- {
143
- name: 'customCSS',
144
- type: 'code',
145
- label: 'Custom CSS',
146
- admin: { language: 'css' }
147
- }
148
- ]
149
- }
150
- ]
151
- }
152
- ]
153
- }
1
+ import type { Block } from 'payload'
2
+ import { iconPicker } from '@/fields/lucide-icon-picker/field'
3
+
4
+ export const DDTechStack: Block = {
5
+ slug: 'DD-TechStack',
6
+ interfaceName: 'DDTechStack',
7
+ labels: {
8
+ singular: 'Tech Stack',
9
+ plural: 'Tech Stacks'
10
+ },
11
+ fields: [
12
+ {
13
+ type: 'tabs',
14
+ tabs: [
15
+ {
16
+ label: 'Header',
17
+ fields: [
18
+ {
19
+ name: 'eyebrow',
20
+ type: 'text',
21
+ label: 'Eyebrow Label',
22
+ admin: { description: 'Small text above the heading (e.g. "Our Stack")' }
23
+ },
24
+ {
25
+ name: 'heading',
26
+ type: 'text',
27
+ label: 'Heading',
28
+ defaultValue: 'Technology Stack',
29
+ admin: { description: 'Main section heading. Leave blank to hide.' }
30
+ }
31
+ ]
32
+ },
33
+ {
34
+ label: 'Rows',
35
+ fields: [
36
+ {
37
+ name: 'techRows',
38
+ type: 'array',
39
+ label: 'Tech Rows',
40
+ minRows: 1,
41
+ maxRows: 3,
42
+ admin: {
43
+ description: 'Up to 3 independently scrolling rows of tech stack items.'
44
+ },
45
+ fields: [
46
+ {
47
+ type: 'row',
48
+ fields: [
49
+ {
50
+ name: 'direction',
51
+ type: 'select',
52
+ label: 'Scroll Direction',
53
+ defaultValue: 'left',
54
+ options: [
55
+ { label: '← Left', value: 'left' },
56
+ { label: '→ Right', value: 'right' }
57
+ ],
58
+ admin: { width: '50%' }
59
+ },
60
+ {
61
+ name: 'duration',
62
+ type: 'number',
63
+ label: 'Duration (seconds)',
64
+ defaultValue: 40,
65
+ min: 5,
66
+ max: 180,
67
+ admin: {
68
+ description: 'Scroll cycle time — lower = faster.',
69
+ width: '50%'
70
+ }
71
+ }
72
+ ]
73
+ },
74
+ {
75
+ name: 'items',
76
+ type: 'array',
77
+ label: 'Tech Items',
78
+ minRows: 1,
79
+ admin: {
80
+ description:
81
+ 'Each item shows a logo image or Lucide icon with an optional label. Upload takes priority over icon.'
82
+ },
83
+ fields: [
84
+ {
85
+ name: 'image',
86
+ type: 'upload',
87
+ label: 'Custom Logo',
88
+ relationTo: 'media',
89
+ admin: {
90
+ description:
91
+ 'Upload SVG, PNG, JPG, WebP, or GIF. Takes priority over the Lucide icon.'
92
+ }
93
+ },
94
+ {
95
+ type: 'row',
96
+ fields: [
97
+ iconPicker({
98
+ name: 'icon',
99
+ required: false,
100
+ description: 'Fallback if no logo is uploaded (e.g. Code2, Database)',
101
+ overrides: (field) => ({
102
+ ...field,
103
+ label: 'Lucide Icon (fallback)',
104
+ admin: { ...field.admin, width: '40%' }
105
+ })
106
+ }),
107
+ {
108
+ name: 'label',
109
+ type: 'text',
110
+ label: 'Label',
111
+ admin: {
112
+ description: 'e.g. "React", "TypeScript"',
113
+ width: '40%'
114
+ }
115
+ },
116
+ {
117
+ name: 'showLabel',
118
+ type: 'checkbox',
119
+ label: 'Show Label',
120
+ defaultValue: true,
121
+ admin: { width: '20%' }
122
+ }
123
+ ]
124
+ }
125
+ ]
126
+ }
127
+ ]
128
+ }
129
+ ]
130
+ },
131
+ {
132
+ label: 'Settings',
133
+ fields: [
134
+ {
135
+ name: 'sectionId',
136
+ type: 'text',
137
+ label: 'Section ID',
138
+ admin: {
139
+ description: 'Unique ID for anchor linking (e.g. "tech-stack")'
140
+ }
141
+ },
142
+ {
143
+ name: 'customCSS',
144
+ type: 'code',
145
+ label: 'Custom CSS',
146
+ admin: { language: 'css' }
147
+ }
148
+ ]
149
+ }
150
+ ]
151
+ }
152
+ ]
153
+ }