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
@@ -42,7 +42,7 @@ export const Counter: React.FC<CounterItem> = ({
42
42
  prefix,
43
43
  suffix,
44
44
  prefixClass,
45
- suffixClass
45
+ suffixClass,
46
46
  }) => {
47
47
  const ref = React.useRef<HTMLSpanElement>(null)
48
48
  const inView = useInView(ref, { once: false })
@@ -66,7 +66,7 @@ export const Counter: React.FC<CounterItem> = ({
66
66
  if (element && typeof value === 'number') {
67
67
  element.textContent = value.toFixed(0)
68
68
  }
69
- }
69
+ },
70
70
  })
71
71
 
72
72
  return () => {
@@ -75,13 +75,13 @@ export const Counter: React.FC<CounterItem> = ({
75
75
  }, [ref, inView, from, to, duration])
76
76
 
77
77
  return (
78
- <div className="counter-wrapper flex items-center">
78
+ <div className="counter-wrapper flex min-h-12 items-baseline justify-center gap-1 tabular-nums">
79
79
  {prefix && (
80
- <span className={prefixClass || 'text-xl font-bold sm:text-2xl md:text-3xl'}>{prefix}</span>
80
+ <span className={prefixClass || 'text-xl font-bold sm:text-2xl md:text-4xl'}>{prefix}</span>
81
81
  )}
82
82
  <span ref={ref} className={className || 'text-xl font-bold sm:text-2xl md:text-4xl'} />
83
83
  {suffix && (
84
- <span className={suffixClass || 'text-md font-base sm:text-lg md:text-xl ml-2'}>
84
+ <span className={suffixClass || 'text-base font-medium sm:text-lg md:text-xl'}>
85
85
  {suffix}
86
86
  </span>
87
87
  )}
@@ -99,7 +99,7 @@ export const DDCounter: React.FC<CounterBlockProps> = ({
99
99
  justifyContent = 'center',
100
100
  alignItems = 'center',
101
101
  gap = '6',
102
- gridColumns = '2'
102
+ gridColumns = '2',
103
103
  }) => {
104
104
  // Generate layout classes based on configuration
105
105
  const getLayoutClasses = () => {
@@ -117,7 +117,7 @@ export const DDCounter: React.FC<CounterBlockProps> = ({
117
117
  row: 'flex-row',
118
118
  column: 'flex-col',
119
119
  'row-reverse': 'flex-row-reverse',
120
- 'column-reverse': 'flex-col-reverse'
120
+ 'column-reverse': 'flex-col-reverse',
121
121
  }
122
122
  classes.push(flexDirMap[flexDirection])
123
123
 
@@ -131,7 +131,7 @@ export const DDCounter: React.FC<CounterBlockProps> = ({
131
131
  end: 'justify-end',
132
132
  between: 'justify-between',
133
133
  around: 'justify-around',
134
- evenly: 'justify-evenly'
134
+ evenly: 'justify-evenly',
135
135
  }
136
136
  classes.push(justifyMap[justifyContent])
137
137
 
@@ -141,7 +141,7 @@ export const DDCounter: React.FC<CounterBlockProps> = ({
141
141
  start: 'items-start',
142
142
  center: 'items-center',
143
143
  end: 'items-end',
144
- baseline: 'items-baseline'
144
+ baseline: 'items-baseline',
145
145
  }
146
146
  classes.push(alignMap[alignItems])
147
147
  } else if (layoutType === 'grid') {
@@ -166,13 +166,18 @@ export const DDCounter: React.FC<CounterBlockProps> = ({
166
166
  }
167
167
 
168
168
  return (
169
- <div className={getLayoutClasses()} id={counterContId}>
169
+ <div className={`w-full ${getLayoutClasses()}`} id={counterContId}>
170
170
  {counters.map((counter) => (
171
- <div key={counter.id} className="counter-item" id={counterId}>
171
+ <div
172
+ key={counter.id}
173
+ className="counter-item flex min-h-40 w-full flex-col items-center justify-center gap-3 rounded-2xl border border-border bg-card p-6 text-center shadow-sm"
174
+ id={counterId}
175
+ >
172
176
  <RichText
173
177
  data={counter.beforeCounter}
174
178
  enableGutter={false}
175
- className="counter-label -mt-2"
179
+ enableProse={false}
180
+ className="counter_label-before text-sm font-medium text-muted-foreground [&_*]:m-0 [&_*]:text-current"
176
181
  />
177
182
 
178
183
  <Counter {...counter} />
@@ -181,7 +186,7 @@ export const DDCounter: React.FC<CounterBlockProps> = ({
181
186
  data={counter.label}
182
187
  enableProse={false}
183
188
  enableGutter={false}
184
- className="counter-label -mt-2"
189
+ className="counter_label-after text-sm font-medium text-foreground [&_*]:m-0 [&_*]:text-current"
185
190
  />
186
191
  </div>
187
192
  ))}
@@ -0,0 +1,303 @@
1
+ 'use client'
2
+
3
+ import React, { useMemo, useState } from 'react'
4
+ import Image from 'next/image'
5
+ import * as LucideIcons from 'lucide-react'
6
+ import { Truck } from 'lucide-react'
7
+
8
+ import { cn } from '@/utilities/ui'
9
+ import RichText from '@/components/RichText'
10
+
11
+ type MediaObj = {
12
+ url?: string | null
13
+ alt?: string | null
14
+ }
15
+
16
+ type IconMedia = MediaObj | string | number | null | undefined
17
+
18
+ type Badge = {
19
+ icon?: string | null
20
+ customIcon?: IconMedia
21
+ title?: string | null
22
+ description?: string | null
23
+ }
24
+
25
+ type Feature = {
26
+ id?: string | null
27
+ icon?: string | null
28
+ customIcon?: IconMedia
29
+ title?: string | null
30
+ description?: string | null
31
+ }
32
+
33
+ type PricingItem = {
34
+ id?: string | null
35
+ price?: string | null
36
+ label?: string | null
37
+ }
38
+
39
+ export type DDDeliCardProps = {
40
+ blockType?: 'DD-DeliCard'
41
+ blockName?: string | null
42
+ sectionId?: string | null
43
+ customCSS?: string | null
44
+ image?: MediaObj | string | null
45
+ imageAlt?: string | null
46
+ badge?: Badge | null
47
+ heading?: string | null
48
+ description?: any | null
49
+ features?: Feature[] | null
50
+ pricingItems?: PricingItem[] | null
51
+ }
52
+
53
+ function getMediaUrl(media?: IconMedia): string | undefined {
54
+ if (!media) return undefined
55
+ if (typeof media === 'string') return media
56
+ if (typeof media === 'object' && 'url' in media && typeof media.url === 'string') return media.url
57
+ return undefined
58
+ }
59
+
60
+ function getMediaAlt(media?: MediaObj | string | null, fallback = ''): string {
61
+ if (!media || typeof media === 'string') return fallback
62
+ return media.alt ?? fallback
63
+ }
64
+
65
+ function IconRenderer({
66
+ icon,
67
+ customIcon,
68
+ className,
69
+ size = 22,
70
+ }: {
71
+ icon?: string | null
72
+ customIcon?: IconMedia
73
+ className?: string
74
+ size?: number
75
+ }) {
76
+ const customIconUrl = getMediaUrl(customIcon)
77
+
78
+ if (customIconUrl) {
79
+ return (
80
+ <Image
81
+ src={customIconUrl}
82
+ alt=""
83
+ width={size}
84
+ height={size}
85
+ className={cn('h-auto w-auto object-contain', className)}
86
+ />
87
+ )
88
+ }
89
+
90
+ const Icon = icon
91
+ ? ((LucideIcons as Record<string, any>)[icon] as
92
+ | React.ComponentType<{ size?: number; strokeWidth?: number; className?: string }>
93
+ | undefined)
94
+ : undefined
95
+
96
+ if (Icon) return <Icon size={size} strokeWidth={2.2} className={className} />
97
+
98
+ return <Truck size={size} strokeWidth={2.2} className={className} />
99
+ }
100
+
101
+ export const DDDeliCard: React.FC<DDDeliCardProps> = ({
102
+ sectionId,
103
+ customCSS,
104
+ image,
105
+ imageAlt,
106
+ badge,
107
+ heading = 'Reliable Delivery, Every Time',
108
+ description,
109
+ features = [],
110
+ pricingItems = [],
111
+ }) => {
112
+ const safeFeatures = useMemo(() => {
113
+ return features?.filter((feature) => feature?.title) ?? []
114
+ }, [features])
115
+
116
+ const safePricingItems = useMemo(() => {
117
+ return pricingItems?.filter((item) => item?.price || item?.label) ?? []
118
+ }, [pricingItems])
119
+
120
+ const [activeTab, setActiveTab] = useState(0)
121
+
122
+ const activeFeature = safeFeatures[activeTab] ?? safeFeatures[0]
123
+ const imageUrl = getMediaUrl(image)
124
+
125
+ return (
126
+ <div
127
+ id={sectionId || undefined}
128
+ data-block-type="DD-DeliCard"
129
+ className="delivery-card-block mx-auto w-full max-w-7xl bg-stone-50/50 text-stone-900 dark:bg-stone-950 dark:text-stone-100 rounded-3xl squircle"
130
+ >
131
+ {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
132
+
133
+ <div
134
+ data-container-type={`${sectionId}-container`}
135
+ className="overflow-hidden rounded-3xl border border-stone-200 dark:border-stone-800 squircle"
136
+ >
137
+ <div data-col-type={`${sectionId}-col`} className="grid grid-cols-1 lg:grid-cols-2">
138
+ <div className="relative min-h-[400px] overflow-hidden">
139
+ {imageUrl ? (
140
+ <>
141
+ <Image
142
+ data-image-type={`${sectionId}-img`}
143
+ src={imageUrl}
144
+ alt={imageAlt || getMediaAlt(image, 'BFS Plastics delivery truck')}
145
+ width={900}
146
+ height={600}
147
+ className="h-full min-h-[400px] w-full object-cover"
148
+ />
149
+ <img
150
+ data-image-type={`${sectionId}-bgimg`}
151
+ src="/assets/svg/lines.svg"
152
+ alt=""
153
+ aria-hidden="true"
154
+ className="pointer-events-none absolute inset-0 h-full min-h-[400px] w-full object-cover"
155
+ />
156
+ </>
157
+ ) : (
158
+ <div className="flex h-full min-h-[400px] w-full items-center justify-center border border-dashed border-stone-300 dark:border-stone-700 text-sm text-stone-600 dark:text-stone-300">
159
+ Upload a delivery image
160
+ </div>
161
+ )}
162
+
163
+ <div className="absolute inset-0 bg-gradient-to-r from-transparent to-stone-950/60 lg:bg-gradient-to-l lg:from-stone-950/60 lg:to-transparent" />
164
+
165
+ {(badge?.title || badge?.description) && (
166
+ <div
167
+ data-content-type={`${sectionId}-description`}
168
+ className="absolute bottom-6 left-6 rounded-xl bg-stone-50/95 dark:bg-stone-900/95 px-4 py-3 shadow-lg backdrop-blur-sm"
169
+ >
170
+ <div
171
+ data-container-type={`${sectionId}-container`}
172
+ className="flex items-center gap-2"
173
+ >
174
+ <span className="flex h-8 w-8 items-center justify-center rounded-full bg-indigo-100 dark:bg-indigo-950/70">
175
+ <IconRenderer
176
+ icon={badge?.icon || 'Truck'}
177
+ customIcon={badge?.customIcon}
178
+ size={18}
179
+ className="text-indigo-700 dark:text-indigo-300"
180
+ />
181
+ </span>
182
+
183
+ <div data-container-type={`${sectionId}-badge-desc`}>
184
+ {badge?.title && (
185
+ <div className="text-sm font-bold text-stone-900 dark:text-stone-100">
186
+ {badge.title}
187
+ </div>
188
+ )}
189
+ {badge?.description && (
190
+ <div className="text-xs text-stone-600 dark:text-stone-300">
191
+ {badge.description}
192
+ </div>
193
+ )}
194
+ </div>
195
+ </div>
196
+ </div>
197
+ )}
198
+ </div>
199
+
200
+ <div
201
+ data-container-type={`${sectionId}-heading`}
202
+ className="flex flex-col justify-center p-6 md:p-10 lg:p-12"
203
+ >
204
+ {heading && (
205
+ <h3 className="mb-0 text-2xl font-bold text-stone-900 dark:text-stone-100 md:text-3xl">
206
+ {heading}
207
+ </h3>
208
+ )}
209
+
210
+ {description && (
211
+ <RichText
212
+ data-content-type={`${sectionId}-richtext`}
213
+ data={description}
214
+ className="mb-8 max-w-none text-base leading-relaxed text-stone-600 dark:text-stone-300"
215
+ enableGutter={false}
216
+ enableProse={false}
217
+ />
218
+ )}
219
+
220
+ {safeFeatures.length > 0 && (
221
+ <>
222
+ <div
223
+ data-container-type="delivery-location-tags"
224
+ className="mb-6 mt-6 flex flex-wrap gap-2"
225
+ >
226
+ {safeFeatures.map((feature, index) => (
227
+ <button
228
+ key={feature.id || feature.title || index}
229
+ type="button"
230
+ onClick={() => setActiveTab(index)}
231
+ className={cn(
232
+ 'cursor-pointer whitespace-nowrap rounded-full px-4 py-2 text-sm font-medium transition-all duration-300 hover:duration-300',
233
+ activeTab === index
234
+ ? 'border border-solid border-indigo-600 bg-indigo-600 text-white dark:border-indigo-400 dark:bg-indigo-500 dark:text-stone-950'
235
+ : 'border border-stone-200 bg-stone-100 text-stone-700 hover:bg-stone-200 hover:text-stone-950 dark:border-stone-700 dark:bg-stone-900 dark:text-stone-300 dark:hover:bg-stone-800 dark:hover:text-stone-100',
236
+ )}
237
+ >
238
+ {feature.title}
239
+ </button>
240
+ ))}
241
+ </div>
242
+
243
+ {activeFeature && (
244
+ <div className="rounded-xl border border-stone-200 bg-stone-50 dark:border-stone-800 dark:bg-stone-900/80 p-5 md:p-6">
245
+ <div className="flex items-start gap-4">
246
+ <span className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl bg-indigo-100 dark:bg-indigo-950/70">
247
+ <IconRenderer
248
+ icon={activeFeature.icon || 'MapPin'}
249
+ customIcon={activeFeature.customIcon}
250
+ size={20}
251
+ className="text-indigo-700 dark:text-indigo-300"
252
+ />
253
+ </span>
254
+
255
+ <div>
256
+ {activeFeature.title && (
257
+ <h4 className="mb-1 text-lg font-bold text-foreground">
258
+ {activeFeature.title}
259
+ </h4>
260
+ )}
261
+
262
+ {activeFeature.description && (
263
+ <p className="text-sm leading-relaxed text-stone-600 dark:text-stone-300">
264
+ {activeFeature.description}
265
+ </p>
266
+ )}
267
+ </div>
268
+ </div>
269
+ </div>
270
+ )}
271
+ </>
272
+ )}
273
+
274
+ {safePricingItems.length > 0 && (
275
+ <div className="mt-6 grid grid-cols-1 gap-3 sm:grid-cols-3">
276
+ {safePricingItems.map((item, index) => (
277
+ <div
278
+ key={item.id || item.price || index}
279
+ className="rounded-xl border border-stone-200 bg-stone-50 dark:border-stone-800 dark:bg-stone-900/80 p-3 text-center"
280
+ >
281
+ {item.price && (
282
+ <div className="text-lg font-bold text-indigo-700 dark:text-indigo-300">
283
+ {item.price}
284
+ </div>
285
+ )}
286
+
287
+ {item.label && (
288
+ <div className="mt-0.5 text-xs text-stone-600 dark:text-stone-300">
289
+ {item.label}
290
+ </div>
291
+ )}
292
+ </div>
293
+ ))}
294
+ </div>
295
+ )}
296
+ </div>
297
+ </div>
298
+ </div>
299
+ </div>
300
+ )
301
+ }
302
+
303
+ export default DDDeliCard
@@ -0,0 +1,186 @@
1
+ import type { Block } from 'payload'
2
+
3
+ /**
4
+ * Author: Hadi Zainal | Designs Dusted
5
+ * Delivery Card Block - version 1.0 BFS variant
6
+ * Main delivery/freight card with editable image, badge, tabbed features,
7
+ * pricing summary, lucide icon picker defaults, and uploaded custom icon overrides.
8
+ */
9
+
10
+ import { iconPicker } from '@/fields/lucide-icon-picker/field'
11
+ import { defaultLexical } from '@/fields/defaultLexical'
12
+
13
+ const iconFields = [
14
+ iconPicker({
15
+ name: 'icon',
16
+ description: 'Choose a Lucide icon. Overridden by custom icon upload if provided.',
17
+ }),
18
+ {
19
+ name: 'customIcon',
20
+ type: 'upload' as const,
21
+ relationTo: 'media' as const,
22
+ required: false,
23
+ label: 'Custom Icon (overrides Lucide)',
24
+ admin: {
25
+ description: 'Upload a custom icon image. If set, this takes priority over the Lucide icon.',
26
+ },
27
+ },
28
+ ]
29
+
30
+ export const DDDeliCard: Block = {
31
+ slug: 'DD-DeliCard',
32
+ dbName: 'dd_deli_card',
33
+ interfaceName: 'DDDeliCard',
34
+ labels: {
35
+ singular: 'DD Deli Card',
36
+ plural: 'DD Deli Cards',
37
+ },
38
+ fields: [
39
+ {
40
+ type: 'tabs',
41
+ tabs: [
42
+ {
43
+ label: 'Content',
44
+ fields: [
45
+ {
46
+ name: 'image',
47
+ type: 'upload',
48
+ relationTo: 'media',
49
+ required: false,
50
+ label: 'Delivery Image',
51
+ },
52
+ {
53
+ name: 'imageAlt',
54
+ type: 'text',
55
+ label: 'Image Alt Text',
56
+ defaultValue: 'BFS Plastics delivery truck',
57
+ },
58
+ {
59
+ name: 'badge',
60
+ type: 'group',
61
+ label: 'Floating Badge',
62
+ fields: [
63
+ ...iconFields,
64
+ {
65
+ name: 'title',
66
+ type: 'text',
67
+ defaultValue: 'Australia-wide',
68
+ },
69
+ {
70
+ name: 'description',
71
+ type: 'text',
72
+ defaultValue: 'Delivery coverage',
73
+ },
74
+ ],
75
+ },
76
+ {
77
+ name: 'heading',
78
+ type: 'text',
79
+ defaultValue: 'Reliable Delivery, Every Time',
80
+ },
81
+ {
82
+ name: 'description',
83
+ type: 'richText',
84
+ label: 'Description',
85
+ editor: defaultLexical,
86
+ required: false,
87
+ },
88
+ {
89
+ name: 'features',
90
+ type: 'array',
91
+ label: 'Delivery Features',
92
+ minRows: 1,
93
+ defaultValue: [
94
+ {
95
+ icon: 'MapPin',
96
+ title: 'Brisbane Metro',
97
+ description:
98
+ 'From $35 + GST across all Brisbane metro areas. Fast, reliable local delivery.',
99
+ },
100
+ {
101
+ icon: 'Sun',
102
+ title: 'Gold & Sunshine Coast',
103
+ description:
104
+ 'From $40 + GST covering Sunshine Coast and Gold Coast regional areas.',
105
+ },
106
+ {
107
+ icon: 'Map',
108
+ title: 'Interstate Freight',
109
+ description:
110
+ 'Interstate charges quoted as per request. We ship Australia-wide with trusted freight partners.',
111
+ },
112
+ ],
113
+ fields: [
114
+ ...iconFields,
115
+ {
116
+ name: 'title',
117
+ type: 'text',
118
+ required: true,
119
+ },
120
+ {
121
+ name: 'description',
122
+ type: 'textarea',
123
+ },
124
+ ],
125
+ },
126
+ {
127
+ name: 'pricingItems',
128
+ type: 'array',
129
+ label: 'Pricing Summary',
130
+ minRows: 1,
131
+ defaultValue: [
132
+ {
133
+ price: '$35',
134
+ label: 'Brisbane Metro',
135
+ },
136
+ {
137
+ price: '$40',
138
+ label: 'Coast Regions',
139
+ },
140
+ {
141
+ price: 'Quote',
142
+ label: 'Interstate',
143
+ },
144
+ ],
145
+ fields: [
146
+ {
147
+ name: 'price',
148
+ type: 'text',
149
+ required: true,
150
+ },
151
+ {
152
+ name: 'label',
153
+ type: 'text',
154
+ required: true,
155
+ },
156
+ ],
157
+ },
158
+ ],
159
+ },
160
+ {
161
+ label: 'Settings',
162
+ fields: [
163
+ {
164
+ name: 'sectionId',
165
+ type: 'text',
166
+ label: 'Section ID',
167
+ admin: {
168
+ description: 'Optional ID for anchor linking',
169
+ },
170
+ },
171
+ {
172
+ name: 'customCSS',
173
+ type: 'code',
174
+ label: 'Custom CSS',
175
+ admin: {
176
+ language: 'css',
177
+ },
178
+ },
179
+ ],
180
+ },
181
+ ],
182
+ },
183
+ ],
184
+ }
185
+
186
+ export default DDDeliCard
@@ -0,0 +1,94 @@
1
+ {
2
+ "type": "block",
3
+ "name": "DD-DeliCard",
4
+ "label": "Delivery Card",
5
+ "sourceName": "DD-DeliCard",
6
+ "portableName": "DD-DeliCard",
7
+ "slug": "DD-DeliCard",
8
+ "version": "0.0.1",
9
+ "source": {
10
+ "repo": "Hadizainal/Dusted-Payload-Blocks",
11
+ "originalPath": "CLI-TestGround/dusted-blocks-demo/src/blocks/DD-DeliCard",
12
+ "originalSlug": "DD-DeliCard",
13
+ "originalConfigExport": "DDDeliCard",
14
+ "originalComponentExport": "DDDeliCard",
15
+ "originalInterfaceName": "DDDeliCard"
16
+ },
17
+ "files": [
18
+ {
19
+ "from": "Component.tsx",
20
+ "to": "src/blocks/DD-DeliCard/Component.tsx"
21
+ },
22
+ {
23
+ "from": "config.ts",
24
+ "to": "src/blocks/DD-DeliCard/config.ts"
25
+ }
26
+ ],
27
+ "dependencies": [
28
+ "lucide-react"
29
+ ],
30
+ "sharedFiles": [],
31
+ "requiredProjectFiles": [
32
+ {
33
+ "path": "src/fields/defaultLexical.ts",
34
+ "reason": "Payload config uses the project Lexical editor configuration."
35
+ },
36
+ {
37
+ "path": "src/fields/lucide-icon-picker/field.ts",
38
+ "reason": "Payload config uses the Lucide icon picker field."
39
+ },
40
+ {
41
+ "path": "src/components/RichText/index.tsx",
42
+ "reason": "Component renders Payload rich text."
43
+ },
44
+ {
45
+ "path": "src/utilities/ui.ts",
46
+ "reason": "Component imports cn from @/utilities/ui."
47
+ }
48
+ ],
49
+ "optionalProjectFiles": [],
50
+ "collectionRegistration": {
51
+ "enabled": true,
52
+ "collections": [
53
+ "Pages",
54
+ "Posts"
55
+ ]
56
+ },
57
+ "renderBlocksRegistration": {
58
+ "enabled": true
59
+ },
60
+ "renderBlocksBehavior": {
61
+ "wrapperless": false
62
+ },
63
+ "pageRouteIntegration": {
64
+ "required": false,
65
+ "manualSteps": []
66
+ },
67
+ "manual": [
68
+ "Register DDDeliCard in Pages/Posts block arrays if automatic registration was skipped.",
69
+ "Register DD-DeliCard in RenderBlocks if automatic registration was skipped.",
70
+ "Run payload generate:types after schema changes.",
71
+ "Run payload generate:importmap if admin/component imports changed."
72
+ ],
73
+ "manualSteps": [
74
+ "Register DDDeliCard in Pages/Posts block arrays if automatic registration was skipped.",
75
+ "Register DD-DeliCard in RenderBlocks if automatic registration was skipped.",
76
+ "Run payload generate:types after schema changes.",
77
+ "Run payload generate:importmap if admin/component imports changed."
78
+ ],
79
+ "patchTargets": {
80
+ "blockConfigRegistration": {
81
+ "description": "Installer can register DDDeliCard from @/blocks/DD-DeliCard/config."
82
+ },
83
+ "renderBlocksRegistration": {
84
+ "description": "Installer can register DD-DeliCard with component export DDDeliCard."
85
+ }
86
+ },
87
+ "notes": [
88
+ "Promoted from the verified Dusted Blocks demo implementation.",
89
+ "Installer preserves existing destination files and reports project requirements before writing."
90
+ ],
91
+ "exportName": "DDDeliCard",
92
+ "componentExportName": "DDDeliCard",
93
+ "interfaceName": "DDDeliCard"
94
+ }