blocks-dusted 0.1.11 → 0.1.13

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 (161) hide show
  1. package/package.json +5 -2
  2. package/src/cli.js +39 -3
  3. package/src/commands/shop.js +59 -0
  4. package/src/commands/warehouse.js +73 -0
  5. package/src/installers/patchRenderBlocks.js +1 -1
  6. package/src/registry/validateTemplateManifest.js +2 -1
  7. package/templates/blocks/BannerDD790432/Component.tsx +63 -0
  8. package/templates/blocks/BannerDD790432/config.ts +54 -0
  9. package/templates/blocks/BannerDD790432/manifest.json +80 -0
  10. package/templates/blocks/Button47/Component.tsx +124 -0
  11. package/templates/blocks/Button47/config.ts +75 -0
  12. package/templates/blocks/Button47/link.ts +22 -0
  13. package/templates/blocks/Button47/manifest.json +105 -0
  14. package/templates/blocks/Button47/styles.module.scss +75 -0
  15. package/templates/blocks/DD-Accordion/Component.tsx +55 -36
  16. package/templates/blocks/DD-Accordion/config.ts +71 -59
  17. package/templates/blocks/DD-AnimText/Component.tsx +50 -32
  18. package/templates/blocks/DD-AnimText/config.ts +296 -295
  19. package/templates/blocks/DD-AsideNav/Component.tsx +323 -319
  20. package/templates/blocks/DD-Banner/Component.tsx +54 -51
  21. package/templates/blocks/DD-BookCall/Component.tsx +10 -9
  22. package/templates/blocks/DD-ButtonPop/Component.tsx +118 -117
  23. package/templates/blocks/DD-Card/Component.tsx +4 -4
  24. package/templates/blocks/DD-CardTemplate01/Component.tsx +5 -5
  25. package/templates/blocks/DD-CardsWithVariantV3/Component.tsx +472 -0
  26. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/index.tsx +203 -0
  27. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/styles.ts +87 -0
  28. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/useInjectStyle.ts +39 -0
  29. package/templates/blocks/DD-CardsWithVariantV3/config.ts +326 -0
  30. package/templates/blocks/DD-CardsWithVariantV3/manifest.json +110 -0
  31. package/templates/blocks/DD-Carousel-A/Component.tsx +251 -249
  32. package/templates/blocks/DD-Carousel-B/Component.tsx +405 -403
  33. package/templates/blocks/DD-Chip/Component.tsx +14 -14
  34. package/templates/blocks/DD-ComparisonTable/Component.tsx +255 -256
  35. package/templates/blocks/DD-Contact/Component.tsx +433 -434
  36. package/templates/blocks/DD-Counter/Component.tsx +18 -13
  37. package/templates/blocks/DD-DeliCard/Component.tsx +303 -0
  38. package/templates/blocks/DD-DeliCard/config.ts +186 -0
  39. package/templates/blocks/DD-DeliCard/manifest.json +94 -0
  40. package/templates/blocks/DD-FeatCat/Component.tsx +302 -302
  41. package/templates/blocks/DD-FeatProd/CircularGallery.tsx +1179 -0
  42. package/templates/blocks/DD-FeatProd/Component.tsx +484 -0
  43. package/templates/blocks/DD-FeatProd/config.ts +173 -0
  44. package/templates/blocks/DD-FeatProd/manifest.json +93 -0
  45. package/templates/blocks/DD-FeatServ/Component.tsx +607 -0
  46. package/templates/blocks/DD-FeatServ/config.ts +213 -0
  47. package/templates/blocks/DD-FeatServ/manifest.json +113 -0
  48. package/templates/blocks/DD-FeatStrip/Component.tsx +584 -585
  49. package/templates/blocks/DD-FluidBtn/ButtonsEyeCatching.tsx +627 -628
  50. package/templates/blocks/DD-FluidBtn/Component.tsx +46 -43
  51. package/templates/blocks/DD-GooeyInfo/Component.tsx +150 -149
  52. package/templates/blocks/DD-GooeyInfo/config.ts +136 -135
  53. package/templates/blocks/DD-Hero/Component.tsx +16 -40
  54. package/templates/blocks/DD-HeroPlas/Component.tsx +50 -0
  55. package/templates/blocks/DD-HeroPlas/ThreeScene.tsx +109 -0
  56. package/templates/blocks/DD-HeroPlas/config.ts +33 -0
  57. package/templates/blocks/DD-HeroPlas/manifest.json +88 -0
  58. package/templates/blocks/DD-HorizontalScroll/Component.tsx +4 -5
  59. package/templates/blocks/DD-Icon/Component.tsx +46 -81
  60. package/templates/blocks/DD-IconButton/Component.tsx +13 -20
  61. package/templates/blocks/DD-IdTag/Component.tsx +22 -21
  62. package/templates/blocks/DD-IframeMedia/Component.tsx +14 -10
  63. package/templates/blocks/DD-LineAnim/Component.tsx +42 -36
  64. package/templates/blocks/DD-LogoMarq/Component.tsx +38 -48
  65. package/templates/blocks/DD-Lottie/Component.tsx +11 -3
  66. package/templates/blocks/DD-MasonaryMedia/Component.tsx +544 -547
  67. package/templates/blocks/DD-MasonaryMedia/config.ts +389 -389
  68. package/templates/blocks/DD-Masonry/Component.tsx +654 -644
  69. package/templates/blocks/DD-Masonry/config.ts +475 -475
  70. package/templates/blocks/DD-MaterialSwatches/Component.tsx +85 -0
  71. package/templates/blocks/DD-MaterialSwatches/config.ts +232 -0
  72. package/templates/blocks/DD-MaterialSwatches/manifest.json +84 -0
  73. package/templates/blocks/DD-MaterialSwatches/material.ts +107 -0
  74. package/templates/blocks/DD-NineCanvas/Component.tsx +129 -0
  75. package/templates/blocks/DD-NineCanvas/RenderBlocks.tsx +244 -0
  76. package/templates/blocks/DD-NineCanvas/allowedCanvasBlocks.ts +6 -0
  77. package/templates/blocks/DD-NineCanvas/blockComponents.tsx +8 -0
  78. package/templates/blocks/DD-NineCanvas/canvas/CanvasGrid.tsx +316 -0
  79. package/templates/blocks/DD-NineCanvas/canvas/RadialNav.tsx +432 -0
  80. package/templates/blocks/DD-NineCanvas/canvas/canvas-panels.css +889 -0
  81. package/templates/blocks/DD-NineCanvas/canvas/icons/custom-icons.tsx +364 -0
  82. package/templates/blocks/DD-NineCanvas/config.ts +61 -0
  83. package/templates/blocks/DD-NineCanvas/manifest.json +165 -0
  84. package/templates/blocks/DD-NineCanvas/styles.scss +152 -0
  85. package/templates/blocks/DD-NineCanvas/tooltip/get-strict-context.tsx +26 -0
  86. package/templates/blocks/DD-NineCanvas/tooltip/primitives.tsx +178 -0
  87. package/templates/blocks/DD-NineCanvas/tooltip/tooltip.tsx +71 -0
  88. package/templates/blocks/DD-NineCanvas/tooltip/use-controlled-state.tsx +33 -0
  89. package/templates/blocks/DD-NineCanvas/utilities/FadeOnScroll.tsx +68 -0
  90. package/templates/blocks/DD-NineCanvas/utilities/ParallaxWrapper.tsx +157 -0
  91. package/templates/blocks/DD-NineCanvas/utilities/renderCustomCSS.ts +5 -0
  92. package/templates/blocks/DD-NineCanvas/utilities/webglCleanup.ts +27 -0
  93. package/templates/blocks/DD-PayloadAIAssistant/Component.tsx +78 -0
  94. package/templates/blocks/DD-PayloadAIAssistant/DustedBot.tsx +590 -0
  95. package/templates/blocks/DD-PayloadAIAssistant/config.ts +37 -0
  96. package/templates/blocks/DD-PayloadAIAssistant/demo.css +125 -0
  97. package/templates/blocks/DD-PayloadAIAssistant/manifest.json +93 -0
  98. package/templates/blocks/DD-PayloadAIAssistant/source-defaults.ts +17 -0
  99. package/templates/blocks/DD-PayloadAIAssistant/styles.ts +306 -0
  100. package/templates/blocks/DD-Pricing/Component.tsx +368 -372
  101. package/templates/blocks/DD-Pricing/config.ts +215 -215
  102. package/templates/blocks/DD-Process/Component.tsx +147 -149
  103. package/templates/blocks/DD-Progress/Component.tsx +156 -141
  104. package/templates/blocks/DD-ProjHeader/Component.tsx +93 -91
  105. package/templates/blocks/DD-ProjSlider/Component.tsx +1 -1
  106. package/templates/blocks/DD-ProjSlider/config.ts +2 -2
  107. package/templates/blocks/DD-Section/Component.tsx +265 -266
  108. package/templates/blocks/DD-Separator/Component.tsx +66 -66
  109. package/templates/blocks/DD-Services/Component.tsx +176 -176
  110. package/templates/blocks/DD-ShowcaseGrid/Component.tsx +306 -307
  111. package/templates/blocks/DD-Slider-A/Component.tsx +237 -237
  112. package/templates/blocks/DD-StackCards/Component.tsx +474 -473
  113. package/templates/blocks/DD-Team/Component.tsx +247 -247
  114. package/templates/blocks/DD-TeamCards/Component.tsx +251 -251
  115. package/templates/blocks/DD-TechStack/Component.tsx +161 -168
  116. package/templates/blocks/DD-TestMarq/Component.tsx +158 -132
  117. package/templates/blocks/DD-Testim/Component.tsx +247 -247
  118. package/templates/blocks/DD-Testim2/Component.tsx +265 -256
  119. package/templates/blocks/DD-Testimonials/Component.tsx +139 -147
  120. package/templates/blocks/DD-TextMarq/Component.tsx +157 -157
  121. package/templates/blocks/DD-Thanks/Component.tsx +501 -500
  122. package/templates/blocks/DD-Work/Component.tsx +315 -315
  123. package/templates/blocks/DD-Work-B/Component.tsx +294 -294
  124. package/templates/blocks/DDHG-CTASection2/Component.tsx +183 -184
  125. package/templates/blocks/DDHG-HighlightArc/Component.tsx +246 -243
  126. package/templates/blocks/DDHG-LogoMarquee/Component.tsx +180 -93
  127. package/templates/blocks/DDHG-LogoMarquee/config.ts +154 -66
  128. package/templates/blocks/DDHG-NavCodeGrid/Component.tsx +225 -213
  129. package/templates/blocks/DDHG-ProcessSec/Component.tsx +38 -39
  130. package/templates/blocks/DDHG-ServiceDetails/Component.tsx +11 -11
  131. package/templates/blocks/DDHG-ServiceDetails/ServiceDetailCard.tsx +30 -47
  132. package/templates/blocks/DDHG-ServiceHero/Component.tsx +10 -10
  133. package/templates/blocks/DDHG-ServiceOverview/Component.tsx +23 -23
  134. package/templates/blocks/DDHG-StatsFAQ/Component.tsx +40 -40
  135. package/templates/blocks/DDHG-TeamCards/Component.tsx +140 -131
  136. package/templates/blocks/Form/Checkbox/index.tsx +45 -0
  137. package/templates/blocks/Form/Component.tsx +163 -0
  138. package/templates/blocks/Form/Country/index.tsx +65 -0
  139. package/templates/blocks/Form/Country/options.ts +982 -0
  140. package/templates/blocks/Form/Email/index.tsx +38 -0
  141. package/templates/blocks/Form/Error/index.tsx +15 -0
  142. package/templates/blocks/Form/Message/index.tsx +13 -0
  143. package/templates/blocks/Form/Number/index.tsx +36 -0
  144. package/templates/blocks/Form/Select/index.tsx +63 -0
  145. package/templates/blocks/Form/State/index.tsx +64 -0
  146. package/templates/blocks/Form/State/options.ts +52 -0
  147. package/templates/blocks/Form/Text/index.tsx +32 -0
  148. package/templates/blocks/Form/Textarea/index.tsx +40 -0
  149. package/templates/blocks/Form/Width/index.tsx +13 -0
  150. package/templates/blocks/Form/config.ts +51 -0
  151. package/templates/blocks/Form/fields.tsx +21 -0
  152. package/templates/blocks/Form/manifest.json +168 -0
  153. package/templates/blocks/MediaBlock/Component.tsx +67 -0
  154. package/templates/blocks/MediaBlock/config.ts +14 -0
  155. package/templates/blocks/MediaBlock/manifest.json +92 -0
  156. package/templates/blocks/TransitionsDustedShowcase/Component.tsx +9 -0
  157. package/templates/blocks/TransitionsDustedShowcase/config.ts +13 -0
  158. package/templates/blocks/TransitionsDustedShowcase/manifest.json +80 -0
  159. package/templates/blocks/rawHtml/Component.tsx +25 -0
  160. package/templates/blocks/rawHtml/config.ts +56 -0
  161. package/templates/blocks/rawHtml/manifest.json +80 -0
@@ -1,180 +1,173 @@
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 }
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
10
  type TechStackItem = {
11
11
  image?: string | MediaObj | null
12
12
  icon?: string | null
13
- iconName?: string | null
14
- label?: string | null
15
- showLabel?: boolean | null
16
- }
13
+ label?: string | null
14
+ showLabel?: boolean | null
15
+ }
17
16
  type TechStackRow = {
18
17
  duration?: number | null
19
- speed?: number | null
20
- direction?: 'left' | 'right' | null
21
- items?: TechStackItem[] | null
22
- }
23
-
24
- function getMediaUrl(media: string | MediaObj | null | undefined): string | null {
25
- if (!media) return null
26
- if (typeof media === 'string') return media
27
- return media.url ?? null
28
- }
29
-
30
- function getMediaAlt(media: string | MediaObj | null | undefined): string | null {
31
- if (!media || typeof media === 'string') return null
32
- return (media as MediaObj).alt ?? null
33
- }
34
-
35
- // ─── Dynamic Lucide Icon ──────────────────────────────────────────────────────
36
- function isLocalImageSrc(src?: string | null): src is string {
37
- return typeof src === 'string' && (src.startsWith('/api/media/file/') || src.startsWith('/assets/') || src.startsWith('/logo') || src.startsWith('data:'))
38
- }
39
-
40
- function DynamicIcon({
41
- name,
42
- size = 36,
43
- ...props
44
- }: { name?: string | null } & Omit<LucideIcons.LucideProps, 'name'>) {
45
- if (!name) return null
46
- const Icon = (LucideIcons as unknown as Record<string, React.FC<LucideIcons.LucideProps>>)[name]
47
- if (!Icon) return null
48
- return <Icon size={size} {...props} />
49
- }
50
-
51
- // ─── Tech Item Card ───────────────────────────────────────────────────────────
52
- function TechItem({
53
- imageUrl,
54
- imageAlt,
55
- icon,
56
- label,
57
- showLabel,
58
- }: {
59
- imageUrl?: string | null
60
- imageAlt?: string | null
61
- icon?: string | null
62
- label?: string | null
63
- showLabel?: boolean | null
64
- }) {
65
- const safeImageUrl = isLocalImageSrc(imageUrl) ? imageUrl : null
66
-
67
- return (
68
- <div className="relative group/tech text-center shrink-0">
69
- <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] ">
70
- {safeImageUrl ? (
71
- <Image
72
- src={safeImageUrl}
73
- alt={imageAlt ?? label ?? 'Tech logo'}
74
- width={40}
75
- height={40}
76
- loading="lazy"
77
- className="object-contain w-10 h-10"
78
- />
79
- ) : (
80
- <DynamicIcon className="text-[#9A9790]" name={icon} size={36} />
81
- )}
82
- </div>
83
- {showLabel !== false && label && (
84
- <span className="text-sm whitespace-nowrap text-[#9A9790] mt-2 block">{label}</span>
85
- )}
86
- </div>
87
- )
88
- }
89
-
90
- // ─── Component ────────────────────────────────────────────────────────────────
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 ────────────────────────────────────────────────────────────────
91
89
  export const DDTechStack: React.FC<DDTechStackProps> = ({
92
90
  eyebrow,
93
91
  heading,
94
- headlineMain,
95
- headlineAccent,
96
92
  techRows,
97
- rows: legacyRows,
98
93
  sectionId,
99
94
  customCSS,
100
95
  }) => {
101
- const resolvedHeading = heading ?? [headlineMain, headlineAccent].filter(Boolean).join(' ')
102
- const rows = (techRows ?? legacyRows ?? []) as TechStackRow[]
103
- if (!rows.length) return null
104
-
105
- const rowMaskStyle: React.CSSProperties = {
106
- maskImage: 'linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%)',
107
- WebkitMaskImage:
108
- 'linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%)',
109
- }
110
-
111
- return (
112
- <section
113
- id={sectionId ?? undefined}
114
- className="relative mx-auto w-full max-w-6xl bg-transparent px-4 py-10 md:px-6"
115
- >
116
- {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
117
-
118
- {(eyebrow || resolvedHeading) && (
119
- <div className="mb-12">
120
-
121
- {eyebrow && (
122
- <p className="text-sm uppercase tracking-widest text-[#9A9790] mb-2">{eyebrow}</p>
123
- )}
124
- {resolvedHeading && (
125
- <h2
126
- className="text-3xl font-bold tracking-tighter sm:text-4xl text-stone-900 dark:text-stone-50"
127
- style={{ fontFamily: "'Space Grotesk', sans-serif" }}
128
- >
129
- {resolvedHeading}
130
- </h2>
131
- )}
132
- </div>
133
- )}
134
-
135
- <div className="relative">
136
- {rows.map((row, rowIndex) => {
137
- const duration = row.duration ?? row.speed ?? 40
138
- const direction = row.direction ?? 'left'
139
- const items = row.items ?? []
140
-
141
- if (items.length === 0) return null
142
-
143
- // Quadruple items for a seamless infinite loop
144
- const duplicated = [...items, ...items, ...items, ...items]
145
-
146
- return (
147
- <div
148
- key={rowIndex}
149
- className="overflow-x-hidden overflow-y-visible py-6 relative"
150
- style={rowMaskStyle}
151
- >
152
- <div
153
- className="flex shrink-0 justify-around gap-8 flex-row animate-marquee"
154
- style={{
155
- animationDuration: `${duration}s`,
156
- animationDirection: direction === 'right' ? 'reverse' : 'normal',
157
- }}
158
- >
159
- {duplicated.map((item, idx) => {
160
- const imgUrl = getMediaUrl(item.image as string | MediaObj | null)
161
- const imgAlt = getMediaAlt(item.image as string | MediaObj | null)
162
- return (
163
- <TechItem
164
- key={idx}
165
- imageUrl={imgUrl}
166
- imageAlt={imgAlt}
167
- icon={item.icon ?? item.iconName}
168
- label={item.label}
169
- showLabel={item.showLabel}
170
- />
171
- )
172
- })}
173
- </div>
174
- </div>
175
- )
176
- })}
177
- </div>
178
- </section>
179
- )
180
- }
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
+ {eyebrow && (
115
+ <p className="text-sm uppercase tracking-widest text-[#9A9790] mb-2">{eyebrow}</p>
116
+ )}
117
+ {heading && (
118
+ <h2
119
+ className="text-3xl font-bold tracking-tighter sm:text-4xl text-stone-900 dark:text-stone-50"
120
+ style={{ fontFamily: "'Space Grotesk', sans-serif" }}
121
+ >
122
+ {heading}
123
+ </h2>
124
+ )}
125
+ </div>
126
+ )}
127
+
128
+ <div className="relative">
129
+ {rows.map((row, rowIndex) => {
130
+ const duration = row.duration ?? 40
131
+ const direction = row.direction ?? 'left'
132
+ const items = row.items ?? []
133
+
134
+ if (items.length === 0) return null
135
+
136
+ // Quadruple items for a seamless infinite loop
137
+ const duplicated = [...items, ...items, ...items, ...items]
138
+
139
+ return (
140
+ <div
141
+ key={rowIndex}
142
+ className="overflow-x-hidden overflow-y-visible py-6 relative"
143
+ style={rowMaskStyle}
144
+ >
145
+ <div
146
+ className="flex shrink-0 justify-around gap-8 flex-row animate-marquee"
147
+ style={{
148
+ animationDuration: `${duration}s`,
149
+ animationDirection: direction === 'right' ? 'reverse' : 'normal',
150
+ }}
151
+ >
152
+ {duplicated.map((item, idx) => {
153
+ const imgUrl = getMediaUrl(item.image as string | MediaObj | null)
154
+ const imgAlt = getMediaAlt(item.image as string | MediaObj | null)
155
+ return (
156
+ <TechItem
157
+ key={idx}
158
+ imageUrl={imgUrl}
159
+ imageAlt={imgAlt}
160
+ icon={item.icon}
161
+ label={item.label}
162
+ showLabel={item.showLabel}
163
+ />
164
+ )
165
+ })}
166
+ </div>
167
+ </div>
168
+ )
169
+ })}
170
+ </div>
171
+ </section>
172
+ )
173
+ }
@@ -1,132 +1,158 @@
1
- 'use client'
2
-
3
- import React from 'react'
4
- import { cn } from '@/utilities/ui'
5
- import type { Testimonial } from '@/payload-types'
6
-
7
- type TestimonialsMarqueeBlockProps = {
8
- heading?: string | null
9
- testimonials?: (Testimonial | string | number)[] | null
10
- row1Duration?: number | null
11
- row2Duration?: number | null
12
- sectionId?: string | null
13
- customCSS?: string | null
14
- }
15
- import RichText from '@/components/RichText/index'
16
-
17
- function TestimonialCard({ item }: { item: Testimonial }) {
18
- const logo =
19
- typeof item.sourceLogo === 'object' && item.sourceLogo !== null
20
- ? (item.sourceLogo as { url?: string | null }).url
21
- : null
22
-
23
- return (
24
- <div className="relative scale-90 -ml-2 -mr-2 h-fit max-w-sm shrink-0 cursor-pointer overflow-hidden rounded-xl border border-[#2A2A30] bg-[#1a1a1f] p-4 space-y-2.5">
25
- {item.title && <h2 className="mb-2.5 text-sm font-medium text-[#8B9F6B]">{item.title}</h2>}
26
-
27
- <div className="flex items-center gap-0">
28
- <div className="flex items-center text-xs">
29
- {item.ratingText && (
30
- <span className="ml-0 font-medium text-[#E8E6E1]">{item.ratingText}</span>
31
- )}
32
- {item.date && <span className="ml-3 text-[#6B6860]">{item.date}</span>}
33
- </div>
34
- </div>
35
-
36
- {item.content && (
37
- <div className="text-sm text-[#9A9790]">
38
- <RichText data={item.content} enableGutter={false} enableProse={false} />
39
- </div>
40
- )}
41
-
42
- {logo && (
43
- <img
44
- alt={item.sourceLogoAlt || 'Source Logo'}
45
- loading="lazy"
46
- width={50}
47
- height={50}
48
- decoding="async"
49
- src={logo}
50
- className="mt-2 h-auto w-[50px] object-contain"
51
- />
52
- )}
53
- </div>
54
- )
55
- }
56
-
57
- function MarqueeRow({
58
- items,
59
- reverse = false,
60
- duration = 30
61
- }: {
62
- items: Testimonial[]
63
- reverse?: boolean
64
- duration?: number
65
- }) {
66
- const duplicated = [...items, ...items, ...items, ...items]
67
-
68
- return (
69
- <div className="group flex overflow-hidden px-2 py-0 -mt-1 -mb-1 flex-row">
70
- <div
71
- className={cn(
72
- 'flex scale-90 shrink-0 justify-around gap-0 flex-row will-change-transform',
73
- reverse ? 'animate-marquee-reverse' : 'animate-marquee',
74
- 'group-hover:[animation-play-state:paused]'
75
- )}
76
- style={{ animationDuration: `${duration}s` }}
77
- >
78
- {duplicated.map((item, idx) => (
79
- <TestimonialCard key={idx} item={item} />
80
- ))}
81
- </div>
82
- </div>
83
- )
84
- }
85
-
86
- export const DDTestMarq: React.FC<TestimonialsMarqueeBlockProps> = (props) => {
87
- const {
88
- heading,
89
- testimonials,
90
- row1Duration = 35,
91
- row2Duration = 40,
92
- sectionId,
93
- customCSS
94
- } = props
95
-
96
- const populated = (testimonials ?? []).filter(
97
- (t: Testimonial | string | number): t is Testimonial => typeof t === 'object' && t !== null
98
- )
99
-
100
- if (populated.length === 0) return null
101
-
102
- const midpoint = Math.ceil(populated.length / 2)
103
- const row1 = populated.slice(0, midpoint)
104
- const row2 = populated.slice(midpoint)
105
-
106
- return (
107
- <section id={sectionId ?? 'testimonials'} className="mx-auto max-w-8xl px-0 py-0 md:px-0">
108
- {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
109
-
110
- {heading && (
111
- <h2
112
- className="max-w-5xl mx-auto mb-12 text-3xl font-bold tracking-tighter text-[#E8E6E1] sm:text-4xl"
113
- style={{ fontFamily: "'Space Grotesk', sans-serif" }}
114
- >
115
- {heading}
116
- </h2>
117
- )}
118
-
119
- <div className="relative flex w-full flex-col items-center justify-center overflow-hidden">
120
- {row1.length > 0 && <MarqueeRow items={row1} duration={row1Duration ?? 35} />}
121
- {row2.length > 0 && <MarqueeRow items={row2} reverse duration={row2Duration ?? 40} />}
122
-
123
- <div className="pointer-events-none absolute inset-y-0 left-0 w-[6rem] blur-md bg-[linear-gradient(90deg,#0b0b11,#0c0b12,transparent,transparent,transparent)] mix-blend-darken" />
124
- <div className="pointer-events-none absolute inset-y-0 left-0 w-[6rem] bg-[linear-gradient(90deg,#0b0b11,#0c0b12,transparent,transparent,transparent)] mix-blend-darken" />
125
- <div className="pointer-events-none absolute inset-y-0 right-0 w-[6rem] blur-md bg-[linear-gradient(270deg,#09090d,#09090d,transparent,transparent,transparent)] mix-blend-darken" />
126
- <div className="pointer-events-none absolute inset-y-0 right-0 w-[6rem] bg-[linear-gradient(270deg,#09090d,#09090d,transparent,transparent,transparent)] mix-blend-darken" />
127
- </div>
128
- </section>
129
- )
130
- }
131
-
132
- export default DDTestMarq
1
+ 'use client'
2
+
3
+ import React from 'react'
4
+ import { cn } from '@/utilities/ui'
5
+ import type { Testimonial } from '@/payload-types'
6
+
7
+ type TestimonialsMarqueeBlockProps = {
8
+ heading?: string | null
9
+ testimonials?: (Testimonial | string | number)[] | null
10
+ row1Duration?: number | null
11
+ row2Duration?: number | null
12
+ sectionId?: string | null
13
+ customCSS?: string | null
14
+ }
15
+ import RichText from '@/components/RichText/index'
16
+
17
+ function TestimonialCard({ item }: { item: Testimonial }) {
18
+ const logo =
19
+ typeof item.sourceLogo === 'object' && item.sourceLogo !== null
20
+ ? (item.sourceLogo as { url?: string | null }).url
21
+ : null
22
+
23
+ return (
24
+ <div className="relative scale-90 -ml-2 -mr-2 h-fit max-w-sm shrink-0 cursor-pointer overflow-hidden rounded-xl border border-[#2A2A30] bg-[#1a1a1f] p-4 space-y-2.5">
25
+ {item.title && <h2 className="mb-2.5 text-sm font-medium text-[#8B9F6B]">{item.title}</h2>}
26
+
27
+ <div className="flex items-center gap-0">
28
+ <div className="flex items-center text-xs">
29
+ {item.ratingText && (
30
+ <span className="ml-0 font-medium text-[#E8E6E1]">{item.ratingText}</span>
31
+ )}
32
+ {item.date && <span className="ml-3 text-[#6B6860]">{item.date}</span>}
33
+ </div>
34
+ </div>
35
+
36
+ {item.content && (
37
+ <div className="text-sm text-[#9A9790] [&_.payload-richtext]:!text-[#9A9790] [&_.payload-richtext_*]:!text-[#9A9790]">
38
+ <RichText data={item.content} enableGutter={false} enableProse={false} />
39
+ </div>
40
+ )}
41
+
42
+ {logo && (
43
+ <img
44
+ alt={item.sourceLogoAlt || 'Source Logo'}
45
+ loading="lazy"
46
+ width={50}
47
+ height={50}
48
+ decoding="async"
49
+ src={logo}
50
+ className="mt-2 h-auto w-[50px] object-contain"
51
+ />
52
+ )}
53
+ </div>
54
+ )
55
+ }
56
+
57
+ function MarqueeRow({
58
+ items,
59
+ reverse = false,
60
+ duration = 30,
61
+ }: {
62
+ items: Testimonial[]
63
+ reverse?: boolean
64
+ duration?: number
65
+ }) {
66
+ const duplicated = [...items, ...items]
67
+
68
+ return (
69
+ <div className="group flex w-full overflow-hidden px-0 py-0 -mt-1 -mb-1 flex-row">
70
+ <div
71
+ className={cn(
72
+ 'dd-test-marquee-track flex w-max shrink-0 justify-around gap-0 flex-row will-change-transform',
73
+ reverse ? 'dd-test-marquee-reverse' : 'dd-test-marquee-forward',
74
+ 'group-hover:[animation-play-state:paused]',
75
+ )}
76
+ style={{ '--dd-test-marquee-duration': `${duration}s` } as React.CSSProperties}
77
+ >
78
+ {duplicated.map((item, idx) => (
79
+ <TestimonialCard key={idx} item={item} />
80
+ ))}
81
+ </div>
82
+ </div>
83
+ )
84
+ }
85
+
86
+ export const DDTestMarq: React.FC<TestimonialsMarqueeBlockProps> = (props) => {
87
+ const {
88
+ heading,
89
+ testimonials,
90
+ row1Duration = 35,
91
+ row2Duration = 40,
92
+ sectionId,
93
+ customCSS,
94
+ } = props
95
+
96
+ const populated = (testimonials ?? []).filter(
97
+ (t: Testimonial | string | number): t is Testimonial => typeof t === 'object' && t !== null,
98
+ )
99
+
100
+ if (populated.length === 0) return null
101
+
102
+ const midpoint = Math.ceil(populated.length / 2)
103
+ const row1 = populated.slice(0, midpoint)
104
+ const row2 = populated.slice(midpoint)
105
+
106
+ return (
107
+ <section id={sectionId ?? 'testimonials'} className="mx-auto max-w-8xl px-0 py-0 md:px-0">
108
+ <style>{`
109
+ @keyframes ddTestMarqueeForward {
110
+ from { transform: translate3d(0, 0, 0); }
111
+ to { transform: translate3d(-50%, 0, 0); }
112
+ }
113
+ @keyframes ddTestMarqueeReverse {
114
+ from { transform: translate3d(-50%, 0, 0); }
115
+ to { transform: translate3d(0, 0, 0); }
116
+ }
117
+ .dd-test-marquee-forward {
118
+ animation: ddTestMarqueeForward var(--dd-test-marquee-duration) linear infinite;
119
+ }
120
+ .dd-test-marquee-reverse {
121
+ animation: ddTestMarqueeReverse var(--dd-test-marquee-duration) linear infinite;
122
+ }
123
+ `}</style>
124
+ {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
125
+
126
+ {heading && (
127
+ <h2
128
+ className="max-w-5xl mx-auto mb-12 text-3xl font-bold tracking-tighter text-stone-900 dark:text-stone-100 sm:text-4xl"
129
+ style={{ fontFamily: "'Space Grotesk', sans-serif" }}
130
+ >
131
+ {heading}
132
+ </h2>
133
+ )}
134
+
135
+ <div className="relative flex w-full flex-col items-center justify-center overflow-hidden">
136
+ {row1.length > 0 && <MarqueeRow items={row1} duration={row1Duration ?? 35} />}
137
+ {row2.length > 0 && <MarqueeRow items={row2} reverse duration={row2Duration ?? 40} />}
138
+
139
+ <div
140
+ className="pointer-events-none absolute inset-y-0 left-0 w-24"
141
+ style={{
142
+ background:
143
+ 'linear-gradient(90deg, var(--background) 0%, var(--background) 35%, transparent 100%)',
144
+ }}
145
+ />
146
+ <div
147
+ className="pointer-events-none absolute inset-y-0 right-0 w-24"
148
+ style={{
149
+ background:
150
+ 'linear-gradient(270deg, var(--background) 0%, var(--background) 35%, transparent 100%)',
151
+ }}
152
+ />
153
+ </div>
154
+ </section>
155
+ )
156
+ }
157
+
158
+ export default DDTestMarq