blocks-dusted 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +68 -0
  3. package/bin/index.js +8 -0
  4. package/package.json +40 -0
  5. package/src/cli.js +79 -0
  6. package/src/commands/add.js +327 -0
  7. package/src/commands/doctor.js +21 -0
  8. package/src/commands/list.js +18 -0
  9. package/src/installers/checkRequirements.js +101 -0
  10. package/src/installers/copyTemplateFiles.js +79 -0
  11. package/src/installers/installDependencies.js +69 -0
  12. package/src/installers/patchPayloadBlocksArray.js +122 -0
  13. package/src/installers/patchRenderBlocks.js +110 -0
  14. package/src/installers/runGenerators.js +3 -0
  15. package/src/installers/writeInstalledBlockReadme.js +117 -0
  16. package/src/registry/listTemplates.js +17 -0
  17. package/src/registry/loadTemplateManifest.js +20 -0
  18. package/src/registry/validateTemplateManifest.js +69 -0
  19. package/src/utils/logger.js +8 -0
  20. package/src/utils/packageManager.js +42 -0
  21. package/src/utils/paths.js +7 -0
  22. package/src/utils/project.js +122 -0
  23. package/src/utils/strings.js +7 -0
  24. package/templates/blocks/DD-BookCall/Component.tsx +573 -0
  25. package/templates/blocks/DD-BookCall/config.ts +239 -0
  26. package/templates/blocks/DD-BookCall/manifest.json +112 -0
  27. package/templates/blocks/DD-CardTemplate01/Component.tsx +144 -0
  28. package/templates/blocks/DD-CardTemplate01/config.ts +106 -0
  29. package/templates/blocks/DD-CardTemplate01/manifest.json +84 -0
  30. package/templates/blocks/DD-Carousel-A/Component.tsx +266 -0
  31. package/templates/blocks/DD-Carousel-A/config.ts +163 -0
  32. package/templates/blocks/DD-Carousel-A/manifest.json +86 -0
  33. package/templates/blocks/DD-Carousel-B/Component.tsx +432 -0
  34. package/templates/blocks/DD-Carousel-B/config.ts +176 -0
  35. package/templates/blocks/DD-Carousel-B/manifest.json +86 -0
  36. package/templates/blocks/DD-ComparisonTable/Component.tsx +272 -0
  37. package/templates/blocks/DD-ComparisonTable/config.ts +105 -0
  38. package/templates/blocks/DD-ComparisonTable/manifest.json +78 -0
  39. package/templates/blocks/DD-Contact/Component.tsx +439 -0
  40. package/templates/blocks/DD-Contact/config.ts +217 -0
  41. package/templates/blocks/DD-Contact/manifest.json +107 -0
  42. package/templates/blocks/DD-FeatCat/Component.tsx +361 -0
  43. package/templates/blocks/DD-FeatCat/config.ts +204 -0
  44. package/templates/blocks/DD-FeatCat/manifest.json +87 -0
  45. package/templates/blocks/DD-FeatStrip/Component.tsx +615 -0
  46. package/templates/blocks/DD-FeatStrip/config.ts +134 -0
  47. package/templates/blocks/DD-FeatStrip/manifest.json +82 -0
  48. package/templates/blocks/DD-Hero/Component.tsx +317 -0
  49. package/templates/blocks/DD-Hero/config.ts +267 -0
  50. package/templates/blocks/DD-Hero/manifest.json +92 -0
  51. package/templates/blocks/DD-HorizontalScroll/Component.tsx +995 -0
  52. package/templates/blocks/DD-HorizontalScroll/config.ts +231 -0
  53. package/templates/blocks/DD-HorizontalScroll/manifest.json +85 -0
  54. package/templates/blocks/DD-MasonaryMedia/Component.tsx +571 -0
  55. package/templates/blocks/DD-MasonaryMedia/config.ts +390 -0
  56. package/templates/blocks/DD-MasonaryMedia/manifest.json +101 -0
  57. package/templates/blocks/DD-MasonaryMedia/vendor.d.ts +2 -0
  58. package/templates/blocks/DD-Pricing/Component.tsx +380 -0
  59. package/templates/blocks/DD-Pricing/config.ts +215 -0
  60. package/templates/blocks/DD-Pricing/manifest.json +78 -0
  61. package/templates/blocks/DD-Process/Component.tsx +155 -0
  62. package/templates/blocks/DD-Process/config.ts +150 -0
  63. package/templates/blocks/DD-Process/manifest.json +82 -0
  64. package/templates/blocks/DD-Section/Component.tsx +327 -0
  65. package/templates/blocks/DD-Section/config.ts +214 -0
  66. package/templates/blocks/DD-Section/manifest.json +85 -0
  67. package/templates/blocks/DD-Services/Component.tsx +188 -0
  68. package/templates/blocks/DD-Services/config.ts +149 -0
  69. package/templates/blocks/DD-Services/manifest.json +95 -0
  70. package/templates/blocks/DD-ShowcaseGrid/Component.tsx +317 -0
  71. package/templates/blocks/DD-ShowcaseGrid/config.ts +197 -0
  72. package/templates/blocks/DD-ShowcaseGrid/manifest.json +80 -0
  73. package/templates/blocks/DD-Slider-A/Component.tsx +252 -0
  74. package/templates/blocks/DD-Slider-A/config.ts +118 -0
  75. package/templates/blocks/DD-Slider-A/manifest.json +86 -0
  76. package/templates/blocks/DD-StackCards/Component.tsx +496 -0
  77. package/templates/blocks/DD-StackCards/config.ts +399 -0
  78. package/templates/blocks/DD-StackCards/manifest.json +87 -0
  79. package/templates/blocks/DD-Team/Component.tsx +265 -0
  80. package/templates/blocks/DD-Team/config.ts +173 -0
  81. package/templates/blocks/DD-Team/manifest.json +82 -0
  82. package/templates/blocks/DD-TechStack/Component.tsx +189 -0
  83. package/templates/blocks/DD-TechStack/config.ts +153 -0
  84. package/templates/blocks/DD-TechStack/manifest.json +118 -0
  85. package/templates/blocks/DD-Testimonials/Component.tsx +147 -0
  86. package/templates/blocks/DD-Testimonials/config.ts +120 -0
  87. package/templates/blocks/DD-Testimonials/manifest.json +87 -0
  88. package/templates/blocks/DD-Work/Component.tsx +328 -0
  89. package/templates/blocks/DD-Work/config.ts +165 -0
  90. package/templates/blocks/DD-Work/manifest.json +87 -0
  91. package/templates/blocks/DD-Work-B/Component.tsx +320 -0
  92. package/templates/blocks/DD-Work-B/config.ts +165 -0
  93. package/templates/blocks/DD-Work-B/manifest.json +75 -0
  94. package/templates/shared/components/ui/anims/fade.tsx +83 -0
  95. package/templates/shared/components/ui/button.tsx +233 -0
  96. package/templates/shared/components/ui/input.tsx +41 -0
  97. package/templates/shared/fields/lucide-icon-picker/Cell.tsx +24 -0
  98. package/templates/shared/fields/lucide-icon-picker/README.md +160 -0
  99. package/templates/shared/fields/lucide-icon-picker/field.ts +40 -0
  100. package/templates/shared/fields/lucide-icon-picker/index.tsx +203 -0
  101. package/templates/shared/utilities/canUseDOM.ts +1 -0
  102. package/templates/shared/utilities/getURL.ts +31 -0
@@ -0,0 +1,252 @@
1
+ "use client";
2
+
3
+ import React, { useState, useCallback, useEffect } from "react";
4
+ import Image from "next/image";
5
+ import { ChevronLeft, ChevronRight } from "lucide-react";
6
+ import { cn } from "@/utilities/ui";
7
+ import useEmblaCarousel from "embla-carousel-react";
8
+
9
+ type MediaObj = { url?: string | null; alt?: string | null };
10
+
11
+ type Slide = {
12
+ id?: string | null;
13
+ title: string;
14
+ category?: string | null;
15
+ description?: string | null;
16
+ image?: MediaObj | string | null;
17
+ imageFallbackUrl?: string | null;
18
+ };
19
+
20
+ export type DDSliderAProps = {
21
+ blockType: "DD-Slider-A";
22
+ blockName?: string | null;
23
+ eyebrow?: string | null;
24
+ heading?: string | null;
25
+ subtext?: string | null;
26
+ slides?: Slide[] | null;
27
+ sectionId?: string | null;
28
+ customCSS?: string | null;
29
+ };
30
+
31
+ function isValidImageSrc(src?: string | null): src is string {
32
+ return Boolean(src && src.startsWith("/api/media/file/"));
33
+ }
34
+
35
+ function getImageSrc(slide: Slide): string {
36
+ const candidateSrc =
37
+ slide.image &&
38
+ typeof slide.image === "object" &&
39
+ (slide.image as MediaObj).url
40
+ ? (slide.image as MediaObj).url!
41
+ : (slide.imageFallbackUrl ?? "");
42
+
43
+ return isValidImageSrc(candidateSrc) ? candidateSrc : "";
44
+ }
45
+
46
+ export const DDSliderA: React.FC<DDSliderAProps> = ({
47
+ eyebrow,
48
+ heading,
49
+ subtext,
50
+ slides: slidesProp,
51
+ sectionId,
52
+ customCSS,
53
+ }) => {
54
+ const slides = slidesProp ?? [];
55
+ const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true });
56
+ const [activeIdx, setActiveIdx] = useState(0);
57
+ const [loadedSlides, setLoadedSlides] = useState<Record<number, boolean>>({});
58
+ const [loadedThumbs, setLoadedThumbs] = useState<Record<number, boolean>>({});
59
+
60
+ const onSelect = useCallback(() => {
61
+ if (!emblaApi) return;
62
+ setActiveIdx(emblaApi.selectedScrollSnap());
63
+ }, [emblaApi]);
64
+
65
+ useEffect(() => {
66
+ if (!emblaApi) return;
67
+ onSelect();
68
+ emblaApi.on("select", onSelect);
69
+ emblaApi.on("reInit", onSelect);
70
+ return () => {
71
+ emblaApi.off("select", onSelect);
72
+ emblaApi.off("reInit", onSelect);
73
+ };
74
+ }, [emblaApi, onSelect]);
75
+
76
+ const scrollPrev = useCallback(() => emblaApi?.scrollPrev(), [emblaApi]);
77
+ const scrollNext = useCallback(() => emblaApi?.scrollNext(), [emblaApi]);
78
+ const scrollTo = useCallback(
79
+ (i: number) => emblaApi?.scrollTo(i),
80
+ [emblaApi],
81
+ );
82
+
83
+ if (!slides.length) return null;
84
+
85
+ return (
86
+ <section id={sectionId ?? undefined} className="py-20 overflow-hidden">
87
+ {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
88
+ <div className="max-w-6xl lg:max-w-7xl mx-auto px-4 md:px-6">
89
+ {/* Optional section header */}
90
+ {(eyebrow || heading || subtext) && (
91
+ <div className="mb-10 text-center">
92
+ {eyebrow && (
93
+ <p className="text-lime-500 text-xs font-bold uppercase tracking-widest mb-2">
94
+ {eyebrow}
95
+ </p>
96
+ )}
97
+ {heading && (
98
+ <h2 className="text-4xl lg:text-5xl font-bold text-stone-900 dark:text-stone-50 leading-tight">
99
+ {heading}
100
+ </h2>
101
+ )}
102
+ {subtext && (
103
+ <p className="text-stone-400 mt-3 text-base max-w-2xl mx-auto">
104
+ {subtext}
105
+ </p>
106
+ )}
107
+ </div>
108
+ )}
109
+
110
+ <div className="relative rounded-3xl overflow-hidden">
111
+ {/* Embla viewport */}
112
+ <div
113
+ ref={emblaRef}
114
+ className="overflow-hidden select-none cursor-grab active:cursor-grabbing h-[480px] lg:h-[560px]"
115
+ >
116
+ <div className="flex h-full">
117
+ {slides.map((slide, i) => {
118
+ const imgSrc = getImageSrc(slide);
119
+ return (
120
+ <div
121
+ key={slide.id ?? i}
122
+ className="flex-none w-full h-full relative"
123
+ >
124
+ {imgSrc ? (
125
+ <>
126
+ {!loadedSlides[i] && (
127
+ <div className="absolute inset-0 bg-stone-900 animate-pulse" />
128
+ )}
129
+ <Image
130
+ src={imgSrc}
131
+ alt={slide.title}
132
+ fill
133
+ sizes="100vw"
134
+ priority={i === 0}
135
+ className="w-full h-full object-cover object-top"
136
+ draggable={false}
137
+ onLoad={() =>
138
+ setLoadedSlides((prev) => ({ ...prev, [i]: true }))
139
+ }
140
+ />
141
+ </>
142
+ ) : (
143
+ <div className="w-full h-full bg-gradient-to-br from-violet-950 to-indigo-950" />
144
+ )}
145
+
146
+ {/* Gradient overlay */}
147
+ <div className="absolute inset-0 bg-gradient-to-r from-stone-950/80 via-stone-950/40 to-transparent pointer-events-none" />
148
+
149
+ {/* Slide content */}
150
+ <div className="absolute bottom-0 left-0 right-0 p-8 lg:p-12 pointer-events-none">
151
+ <div className="max-w-lg">
152
+ {slide.category && (
153
+ <span className="inline-block bg-lime-500/50 border-lime-500 border text-stone-200 text-xs font-bold px-3 py-1 rounded-full mb-3 uppercase tracking-widest scale-90 origin-top-left">
154
+ {slide.category}
155
+ </span>
156
+ )}
157
+ <h3 className="text-stone-50 font-black text-3xl lg:text-4xl mb-3">
158
+ {slide.title}
159
+ </h3>
160
+ {slide.description && (
161
+ <p className="text-stone-50/70 text-base leading-relaxed">
162
+ {slide.description}
163
+ </p>
164
+ )}
165
+ </div>
166
+ </div>
167
+ </div>
168
+ );
169
+ })}
170
+ </div>
171
+ </div>
172
+
173
+ {/* Navigation arrows */}
174
+ <button
175
+ onClick={scrollPrev}
176
+ aria-label="Previous slide"
177
+ className="absolute left-1 top-1/2 -translate-y-1/2 z-20 w-12 h-12 flex items-center justify-center rounded-full bg-stone-50/5 hover:bg-lime-500 text-stone-50 backdrop-blur-sm transition-all cursor-pointer"
178
+ >
179
+ <ChevronLeft className="w-5 h-5" />
180
+ </button>
181
+ <button
182
+ onClick={scrollNext}
183
+ aria-label="Next slide"
184
+ className="absolute right-1 top-1/2 -translate-y-1/2 z-20 w-12 h-12 flex items-center justify-center rounded-full bg-stone-50/5 hover:bg-lime-500 text-stone-50 backdrop-blur-sm transition-all cursor-pointer"
185
+ >
186
+ <ChevronRight className="w-5 h-5" />
187
+ </button>
188
+
189
+ {/* Thumbnail strip */}
190
+ <div className="absolute bottom-4 right-4 lg:right-8 z-20 flex gap-2">
191
+ {slides.map((slide, i) => {
192
+ const imgSrc = getImageSrc(slide);
193
+ return (
194
+ <button
195
+ key={slide.id ?? i}
196
+ onClick={() => scrollTo(i)}
197
+ aria-label={`Go to slide: ${slide.title}`}
198
+ className={cn(
199
+ "relative w-16 h-12 rounded-lg overflow-hidden border-2 transition-all cursor-pointer",
200
+ i === activeIdx
201
+ ? "border-lime-500 scale-110"
202
+ : "border-white/30 opacity-60 hover:opacity-100",
203
+ )}
204
+ >
205
+ {imgSrc ? (
206
+ <>
207
+ {!loadedThumbs[i] && (
208
+ <div className="absolute inset-0 bg-stone-800 animate-pulse" />
209
+ )}
210
+ <Image
211
+ src={imgSrc}
212
+ alt={slide.title}
213
+ fill
214
+ sizes="64px"
215
+ className="w-full h-full object-cover object-top"
216
+ draggable={false}
217
+ onLoad={() =>
218
+ setLoadedThumbs((prev) => ({ ...prev, [i]: true }))
219
+ }
220
+ />
221
+ </>
222
+ ) : (
223
+ <div className="w-full h-full bg-stone-800" />
224
+ )}
225
+ </button>
226
+ );
227
+ })}
228
+ </div>
229
+ </div>
230
+
231
+ {/* Dot indicators */}
232
+ {slides.length > 1 && (
233
+ <div className="flex justify-center gap-2 mt-6">
234
+ {slides.map((_, i) => (
235
+ <button
236
+ key={i}
237
+ onClick={() => scrollTo(i)}
238
+ aria-label={`Go to slide ${i + 1}`}
239
+ className={cn(
240
+ "rounded-full transition-all duration-300 cursor-pointer",
241
+ i === activeIdx
242
+ ? "w-8 h-2.5 bg-lime-500"
243
+ : "w-2.5 h-2.5 bg-stone-600 hover:bg-stone-400",
244
+ )}
245
+ />
246
+ ))}
247
+ </div>
248
+ )}
249
+ </div>
250
+ </section>
251
+ );
252
+ };
@@ -0,0 +1,118 @@
1
+ import type { Block } from 'payload'
2
+
3
+ export const DDSliderA: Block = {
4
+ slug: 'DD-Slider-A',
5
+ dbName: 'ss_blk_a',
6
+ interfaceName: 'DDSliderA',
7
+ labels: {
8
+ singular: 'Studio Slider A',
9
+ plural: 'Studio Slider A'
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: {
23
+ description: 'Optional small label above the heading',
24
+ placeholder: 'Applications'
25
+ }
26
+ },
27
+ {
28
+ name: 'heading',
29
+ type: 'text',
30
+ label: 'Heading',
31
+ admin: {
32
+ placeholder: 'Where Our Products Are Used'
33
+ }
34
+ },
35
+ {
36
+ name: 'subtext',
37
+ type: 'textarea',
38
+ label: 'Subtext',
39
+ admin: {
40
+ placeholder: 'From signage to architecture — see how our materials perform.'
41
+ }
42
+ }
43
+ ]
44
+ },
45
+ {
46
+ label: 'Slides',
47
+ fields: [
48
+ {
49
+ name: 'slides',
50
+ type: 'array',
51
+ label: 'Slides',
52
+ minRows: 1,
53
+ labels: {
54
+ singular: 'Slide',
55
+ plural: 'Slides'
56
+ },
57
+ fields: [
58
+ {
59
+ name: 'title',
60
+ type: 'text',
61
+ label: 'Title',
62
+ required: true
63
+ },
64
+ {
65
+ name: 'category',
66
+ type: 'text',
67
+ label: 'Category Badge',
68
+ admin: {
69
+ description: 'Short label shown as a badge e.g. "Signage", "Retail"'
70
+ }
71
+ },
72
+ {
73
+ name: 'description',
74
+ type: 'textarea',
75
+ label: 'Description'
76
+ },
77
+ {
78
+ name: 'image',
79
+ type: 'upload',
80
+ label: 'Slide Image',
81
+ relationTo: 'media'
82
+ },
83
+ {
84
+ name: 'imageFallbackUrl',
85
+ type: 'text',
86
+ label: 'Image Fallback URL',
87
+ admin: {
88
+ placeholder: '/assets/placeholder/01.png',
89
+ description: 'Used when no uploaded image is set'
90
+ }
91
+ }
92
+ ]
93
+ }
94
+ ]
95
+ },
96
+ {
97
+ label: 'Settings',
98
+ fields: [
99
+ {
100
+ name: 'sectionId',
101
+ type: 'text',
102
+ label: 'Section ID',
103
+ admin: {
104
+ description: 'Optional ID for anchor linking'
105
+ }
106
+ },
107
+ {
108
+ name: 'customCSS',
109
+ type: 'code',
110
+ label: 'Custom CSS',
111
+ admin: { language: 'css' }
112
+ }
113
+ ]
114
+ }
115
+ ]
116
+ }
117
+ ]
118
+ }
@@ -0,0 +1,86 @@
1
+ {
2
+ "type": "block",
3
+ "name": "DD-Slider-A",
4
+ "label": "Studio Slider Block A",
5
+ "sourceName": "StudioSliderBlock-A",
6
+ "portableName": "DD-Slider-A",
7
+ "slug": "DD-Slider-A",
8
+ "version": "0.0.1",
9
+ "source": {
10
+ "repo": "Hadizainal/DesignsDustedv3",
11
+ "originalPath": "src/blocks/StudioSliderBlock-A",
12
+ "originalSlug": "studioSliderBlockA",
13
+ "originalConfigExport": "StudioSliderBlockA",
14
+ "originalComponentExport": "StudioSliderBlockA",
15
+ "originalInterfaceName": "StudioSliderBlockAProps"
16
+ },
17
+ "files": [
18
+ {
19
+ "from": "Component.tsx",
20
+ "to": "src/blocks/DD-Slider-A/Component.tsx"
21
+ },
22
+ {
23
+ "from": "config.ts",
24
+ "to": "src/blocks/DD-Slider-A/config.ts"
25
+ }
26
+ ],
27
+ "dependencies": [
28
+ {
29
+ "name": "embla-carousel-react"
30
+ },
31
+ {
32
+ "name": "lucide-react"
33
+ }
34
+ ],
35
+ "sharedFiles": [],
36
+ "requiredProjectFiles": [
37
+ {
38
+ "path": "src/utilities/ui.ts",
39
+ "reason": "Block uses the cn utility from @/utilities/ui."
40
+ }
41
+ ],
42
+ "optionalProjectFiles": [],
43
+ "collectionRegistration": {
44
+ "enabled": true,
45
+ "collections": [
46
+ "Pages",
47
+ "Posts"
48
+ ]
49
+ },
50
+ "renderBlocksRegistration": {
51
+ "enabled": true
52
+ },
53
+ "renderBlocksBehavior": {
54
+ "wrapperless": false
55
+ },
56
+ "pageRouteIntegration": {
57
+ "required": false,
58
+ "manualSteps": []
59
+ },
60
+ "manual": [
61
+ "Register DDSliderA in Pages/Posts blocks arrays if automatic registration was skipped.",
62
+ "Register DD-Slider-A in RenderBlocks if automatic registration was skipped.",
63
+ "Run payload generate:types after schema changes.",
64
+ "Run payload generate:importmap if admin/component imports changed."
65
+ ],
66
+ "manualSteps": [
67
+ "Register DDSliderA in Pages/Posts blocks arrays if automatic registration was skipped.",
68
+ "Register DD-Slider-A in RenderBlocks if automatic registration was skipped.",
69
+ "Run payload generate:types after schema changes.",
70
+ "Run payload generate:importmap if admin/component imports changed."
71
+ ],
72
+ "patchTargets": {
73
+ "blockConfigRegistration": {
74
+ "description": "Installer can add import { DDSliderA } from '@/blocks/DD-Slider-A/config' and add DDSliderA to clear Pages/Posts blocks arrays."
75
+ },
76
+ "renderBlocksRegistration": {
77
+ "description": "Installer can register DD-Slider-A in src/blocks/**/RenderBlocks.tsx when the blockComponents map is clear."
78
+ }
79
+ },
80
+ "notes": [
81
+ "Converted from DesignsDustedv3 StudioSliderBlock-A.",
82
+ "Installer must not overwrite existing target files."
83
+ ],
84
+ "exportName": "DDSliderA",
85
+ "interfaceName": "DDSliderA"
86
+ }