blocks-dusted 0.1.1 → 0.1.3

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 (56) hide show
  1. package/README.md +967 -3
  2. package/package.json +2 -2
  3. package/src/commands/add.js +143 -45
  4. package/src/commands/list.js +2 -2
  5. package/src/installers/checkRequirements.js +1 -0
  6. package/src/installers/copyTemplateFiles.js +23 -2
  7. package/src/installers/patchPayloadConfigCollections.js +92 -0
  8. package/src/installers/writeInstalledBlockReadme.js +17 -4
  9. package/src/registry/listTemplates.js +16 -7
  10. package/src/registry/loadTemplateManifest.js +20 -11
  11. package/src/registry/validateTemplateManifest.js +6 -5
  12. package/src/utils/paths.js +7 -0
  13. package/templates/blocks/DD-BookCall/Component.tsx +535 -573
  14. package/templates/blocks/DD-CardTemplate01/Component.tsx +45 -49
  15. package/templates/blocks/DD-Carousel-A/Component.tsx +249 -266
  16. package/templates/blocks/DD-Carousel-B/Component.tsx +403 -432
  17. package/templates/blocks/DD-ComparisonTable/Component.tsx +256 -272
  18. package/templates/blocks/DD-Contact/Component.tsx +434 -439
  19. package/templates/blocks/DD-Contact/config.ts +8 -3
  20. package/templates/blocks/DD-FeatCat/Component.tsx +302 -361
  21. package/templates/blocks/DD-FeatCat/config.ts +201 -204
  22. package/templates/blocks/DD-FeatStrip/Component.tsx +171 -201
  23. package/templates/blocks/DD-Hero/Component.tsx +297 -317
  24. package/templates/blocks/DD-HorizontalScroll/Component.tsx +244 -303
  25. package/templates/blocks/DD-MasonaryMedia/Component.tsx +202 -228
  26. package/templates/blocks/DD-MasonaryMedia/config.ts +13 -14
  27. package/templates/blocks/DD-Pricing/Component.tsx +372 -380
  28. package/templates/blocks/DD-Process/Component.tsx +149 -155
  29. package/templates/blocks/DD-Section/Component.tsx +266 -327
  30. package/templates/blocks/DD-Services/Component.tsx +176 -188
  31. package/templates/blocks/DD-ShowcaseGrid/Component.tsx +307 -317
  32. package/templates/blocks/DD-Slider-A/Component.tsx +237 -252
  33. package/templates/blocks/DD-StackCards/Component.tsx +137 -160
  34. package/templates/blocks/DD-Team/Component.tsx +247 -265
  35. package/templates/blocks/DD-TechStack/Component.tsx +57 -72
  36. package/templates/blocks/DD-Testimonials/Component.tsx +147 -147
  37. package/templates/blocks/DD-Testimonials/config.ts +66 -66
  38. package/templates/blocks/DD-Work/Component.tsx +315 -328
  39. package/templates/blocks/DD-Work-B/Component.tsx +294 -320
  40. package/templates/collections/Testimonials/README.md +11 -0
  41. package/templates/collections/Testimonials/Testimonials.ts +161 -0
  42. package/templates/collections/Testimonials/manifest.json +55 -0
  43. package/templates/components/HeaderDD02/MobileBottomNav/index.tsx +545 -0
  44. package/templates/components/HeaderDD02/README.md +45 -0
  45. package/templates/components/HeaderDD02/RowLabel.tsx +33 -0
  46. package/templates/components/HeaderDD02/config.ts +302 -0
  47. package/templates/components/HeaderDD02/index.tsx +517 -0
  48. package/templates/components/HeaderDD02/manifest.json +123 -0
  49. package/templates/components/RichText/README.md +13 -0
  50. package/templates/components/RichText/converter/componentConverter/blocks.tsx +43 -0
  51. package/templates/components/RichText/converter/componentConverter/types.ts +11 -0
  52. package/templates/components/RichText/converter/index.tsx +18 -0
  53. package/templates/components/RichText/converter/internalLinks.tsx +45 -0
  54. package/templates/components/RichText/converter/textConverter.tsx +27 -0
  55. package/templates/components/RichText/index.tsx +35 -0
  56. package/templates/components/RichText/manifest.json +80 -0
@@ -1,55 +1,49 @@
1
- "use client";
1
+ 'use client'
2
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>;
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
7
 
8
8
  // ─── Types ────────────────────────────────────────────────────────────────────
9
- type MediaObj = { url?: string | null; alt?: string | null };
9
+ type MediaObj = { url?: string | null; alt?: string | null }
10
10
  type TechStackItem = {
11
- image?: string | MediaObj | null;
12
- icon?: string | null;
13
- label?: string | null;
14
- showLabel?: boolean | null;
15
- };
11
+ image?: string | MediaObj | null
12
+ icon?: string | null
13
+ label?: string | null
14
+ showLabel?: boolean | null
15
+ }
16
16
  type TechStackRow = {
17
- duration?: number | null;
18
- direction?: "left" | "right" | null;
19
- items?: TechStackItem[] | null;
20
- };
21
-
22
- function getMediaUrl(
23
- media: string | MediaObj | null | undefined,
24
- ): string | null {
25
- if (!media) return null;
26
- if (typeof media === "string") return media;
27
- return media.url ?? null;
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
28
26
  }
29
27
 
30
- function getMediaAlt(
31
- media: string | MediaObj | null | undefined,
32
- ): string | null {
33
- if (!media || typeof media === "string") return null;
34
- return (media as MediaObj).alt ?? null;
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
35
31
  }
36
32
 
37
33
  // ─── Dynamic Lucide Icon ──────────────────────────────────────────────────────
38
34
  function isLocalImageSrc(src?: string | null): src is string {
39
- return typeof src === "string" && src.startsWith("/api/media/file/");
35
+ return typeof src === 'string' && src.startsWith('/api/media/file/')
40
36
  }
41
37
 
42
38
  function DynamicIcon({
43
39
  name,
44
40
  size = 36,
45
41
  ...props
46
- }: { name?: string | null } & Omit<LucideIcons.LucideProps, "name">) {
47
- if (!name) return null;
48
- const Icon = (
49
- LucideIcons as unknown as Record<string, React.FC<LucideIcons.LucideProps>>
50
- )[name];
51
- if (!Icon) return null;
52
- return <Icon size={size} {...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} />
53
47
  }
54
48
 
55
49
  // ─── Tech Item Card ───────────────────────────────────────────────────────────
@@ -60,13 +54,13 @@ function TechItem({
60
54
  label,
61
55
  showLabel,
62
56
  }: {
63
- imageUrl?: string | null;
64
- imageAlt?: string | null;
65
- icon?: string | null;
66
- label?: string | null;
67
- showLabel?: boolean | null;
57
+ imageUrl?: string | null
58
+ imageAlt?: string | null
59
+ icon?: string | null
60
+ label?: string | null
61
+ showLabel?: boolean | null
68
62
  }) {
69
- const safeImageUrl = isLocalImageSrc(imageUrl) ? imageUrl : null;
63
+ const safeImageUrl = isLocalImageSrc(imageUrl) ? imageUrl : null
70
64
 
71
65
  return (
72
66
  <div className="relative group/tech text-center shrink-0">
@@ -74,7 +68,7 @@ function TechItem({
74
68
  {safeImageUrl ? (
75
69
  <Image
76
70
  src={safeImageUrl}
77
- alt={imageAlt ?? label ?? "Tech logo"}
71
+ alt={imageAlt ?? label ?? 'Tech logo'}
78
72
  width={40}
79
73
  height={40}
80
74
  loading="lazy"
@@ -85,12 +79,10 @@ function TechItem({
85
79
  )}
86
80
  </div>
87
81
  {showLabel !== false && label && (
88
- <span className="text-sm whitespace-nowrap text-[#9A9790] mt-2 block">
89
- {label}
90
- </span>
82
+ <span className="text-sm whitespace-nowrap text-[#9A9790] mt-2 block">{label}</span>
91
83
  )}
92
84
  </div>
93
- );
85
+ )
94
86
  }
95
87
 
96
88
  // ─── Component ────────────────────────────────────────────────────────────────
@@ -101,15 +93,14 @@ export const DDTechStack: React.FC<DDTechStackProps> = ({
101
93
  sectionId,
102
94
  customCSS,
103
95
  }) => {
104
- const rows = (techRows ?? []) as TechStackRow[];
105
- if (!rows.length) return null;
96
+ const rows = (techRows ?? []) as TechStackRow[]
97
+ if (!rows.length) return null
106
98
 
107
99
  const rowMaskStyle: React.CSSProperties = {
108
- maskImage:
109
- "linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%)",
100
+ maskImage: 'linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%)',
110
101
  WebkitMaskImage:
111
- "linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%)",
112
- };
102
+ 'linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%)',
103
+ }
113
104
 
114
105
  return (
115
106
  <section
@@ -120,10 +111,9 @@ export const DDTechStack: React.FC<DDTechStackProps> = ({
120
111
 
121
112
  {(eyebrow || heading) && (
122
113
  <div className="mb-12">
114
+ #BlockName-DDTechStack
123
115
  {eyebrow && (
124
- <p className="text-sm uppercase tracking-widest text-[#9A9790] mb-2">
125
- {eyebrow}
126
- </p>
116
+ <p className="text-sm uppercase tracking-widest text-[#9A9790] mb-2">{eyebrow}</p>
127
117
  )}
128
118
  {heading && (
129
119
  <h2
@@ -138,14 +128,14 @@ export const DDTechStack: React.FC<DDTechStackProps> = ({
138
128
 
139
129
  <div className="relative">
140
130
  {rows.map((row, rowIndex) => {
141
- const duration = row.duration ?? 40;
142
- const direction = row.direction ?? "left";
143
- const items = row.items ?? [];
131
+ const duration = row.duration ?? 40
132
+ const direction = row.direction ?? 'left'
133
+ const items = row.items ?? []
144
134
 
145
- if (items.length === 0) return null;
135
+ if (items.length === 0) return null
146
136
 
147
137
  // Quadruple items for a seamless infinite loop
148
- const duplicated = [...items, ...items, ...items, ...items];
138
+ const duplicated = [...items, ...items, ...items, ...items]
149
139
 
150
140
  return (
151
141
  <div
@@ -157,17 +147,12 @@ export const DDTechStack: React.FC<DDTechStackProps> = ({
157
147
  className="flex shrink-0 justify-around gap-8 flex-row animate-marquee"
158
148
  style={{
159
149
  animationDuration: `${duration}s`,
160
- animationDirection:
161
- direction === "right" ? "reverse" : "normal",
150
+ animationDirection: direction === 'right' ? 'reverse' : 'normal',
162
151
  }}
163
152
  >
164
153
  {duplicated.map((item, idx) => {
165
- const imgUrl = getMediaUrl(
166
- item.image as string | MediaObj | null,
167
- );
168
- const imgAlt = getMediaAlt(
169
- item.image as string | MediaObj | null,
170
- );
154
+ const imgUrl = getMediaUrl(item.image as string | MediaObj | null)
155
+ const imgAlt = getMediaAlt(item.image as string | MediaObj | null)
171
156
  return (
172
157
  <TechItem
173
158
  key={idx}
@@ -177,13 +162,13 @@ export const DDTechStack: React.FC<DDTechStackProps> = ({
177
162
  label={item.label}
178
163
  showLabel={item.showLabel}
179
164
  />
180
- );
165
+ )
181
166
  })}
182
167
  </div>
183
168
  </div>
184
- );
169
+ )
185
170
  })}
186
171
  </div>
187
172
  </section>
188
- );
189
- };
173
+ )
174
+ }
@@ -1,147 +1,147 @@
1
- "use client";
2
-
3
- import React from "react";
4
- import Image from "next/image";
5
- import { Star } from "lucide-react";
6
- import { motion, type Variants } from "framer-motion";
7
- export type DDTestimonialsProps = Record<string, any>;
8
- type Media = { url?: string | null };
9
- type Testimonial = {
10
- id?: string | null;
11
- title?: string | null;
12
- content?: unknown;
13
- designation?: unknown;
14
- companyLogo?: Media | string | null;
15
- fallbackAvatarUrl?: string | null;
16
- rating?: number | null;
17
- };
18
-
19
- function isLocalImageSrc(src?: string | null): src is string {
20
- return typeof src === "string" && src.startsWith("/api/media/file/");
21
- }
22
-
23
- const containerVariants: Variants = {
24
- hidden: {},
25
- show: { transition: { staggerChildren: 0.12, delayChildren: 0.05 } },
26
- };
27
-
28
- const cardVariants: Variants = {
29
- hidden: { opacity: 0, y: 24 },
30
- show: { opacity: 1, y: 0, transition: { duration: 0.5, ease: "easeOut" } },
31
- };
32
-
33
- export const DDTestimonials: React.FC<DDTestimonialsProps> = ({
34
- eyebrow,
35
- headline,
36
- testimonials,
37
- sectionId,
38
- customCSS,
39
- }) => {
40
- const populated = ((testimonials ?? []) as unknown[]).filter(
41
- (t): t is Testimonial => typeof t === "object" && t !== null,
42
- );
43
-
44
- return (
45
- <>
46
- {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
47
-
48
- <div id={sectionId ?? "testimonials"} className="py-24">
49
- <div className="max-w-6xl lg:max-w-7xl mx-auto px-4 md:px-6">
50
- {/* Header */}
51
- <div className="text-center max-w-xl mx-auto mb-14">
52
- {eyebrow && (
53
- <p className="text-[11.5px] font-semibold text-stone-400 tracking-widest uppercase mb-3 top-caption">
54
- {eyebrow}
55
- </p>
56
- )}
57
- <h2 className="text-3xl font-semibold text-stone-900 dark:text-stone-50 leading-tight">
58
- {headline}
59
- </h2>
60
- </div>
61
-
62
- {/* Testimonials */}
63
- {populated.length > 0 && (
64
- <motion.div
65
- className="grid grid-cols-1 md:grid-cols-3 gap-6"
66
- variants={containerVariants}
67
- initial="hidden"
68
- whileInView="show"
69
- viewport={{ once: false, amount: 0.15 }}
70
- >
71
- {populated.map((t, i) => {
72
- const avatarUrl =
73
- typeof t.companyLogo === "object" &&
74
- t.companyLogo !== null &&
75
- "url" in t.companyLogo
76
- ? t.companyLogo.url
77
- : (t.fallbackAvatarUrl ?? null);
78
- const safeAvatarUrl = isLocalImageSrc(avatarUrl)
79
- ? avatarUrl
80
- : null;
81
-
82
- const stars = Math.min(5, Math.max(1, t.rating ?? 5));
83
-
84
- return (
85
- <motion.div
86
- key={t.id ?? i}
87
- className="rounded-2xl bg-stone-100 dark:bg-stone-900 border border-stone-100 dark:border-stone-800 p-8 flex flex-col"
88
- variants={cardVariants}
89
- style={{ cornerShape: "squircle" } as React.CSSProperties}
90
- >
91
- {/* Stars */}
92
- <div className="flex items-center gap-1 mb-5">
93
- {Array.from({ length: stars }).map((_, si) => (
94
- <Star
95
- key={si}
96
- size={14}
97
- className="text-amber-400"
98
- fill="currentColor"
99
- />
100
- ))}
101
- </div>
102
-
103
- {/* Quote */}
104
- {t.content ? (
105
- <div className="text-sm text-stone-400 leading-relaxed flex-1 mb-6">
106
- {/* TODO: Reconnect this project's RichText renderer for testimonial content. */}
107
- <span>
108
- Rich text content is configured for this testimonial.
109
- </span>
110
- </div>
111
- ) : null}
112
-
113
- {/* Author / Company */}
114
- <div className="flex items-center gap-3 pt-5 border-t border-stone-800">
115
- {safeAvatarUrl && (
116
- <div className="w-10 h-10 rounded-full overflow-hidden shrink-0 relative">
117
- <Image
118
- src={safeAvatarUrl}
119
- alt={t.title ?? ""}
120
- fill
121
- sizes="40px"
122
- className="object-cover object-top"
123
- />
124
- </div>
125
- )}
126
- <div>
127
- <div className="text-sm font-semibold text-stone-800 dark:text-stone-50">
128
- {t.title}
129
- </div>
130
- {t.designation ? (
131
- <div className="text-xs text-stone-300">
132
- {/* TODO: Reconnect RichText for designation if needed. */}
133
- <span>Designation content configured.</span>
134
- </div>
135
- ) : null}
136
- </div>
137
- </div>
138
- </motion.div>
139
- );
140
- })}
141
- </motion.div>
142
- )}
143
- </div>
144
- </div>
145
- </>
146
- );
147
- };
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import Image from "next/image";
5
+ import { Star } from "lucide-react";
6
+ import { motion, type Variants } from "framer-motion";
7
+ export type DDTestimonialsProps = Record<string, any>;
8
+ type Media = { url?: string | null };
9
+ type Testimonial = {
10
+ id?: string | null;
11
+ title?: string | null;
12
+ content?: unknown;
13
+ designation?: unknown;
14
+ companyLogo?: Media | string | null;
15
+ fallbackAvatarUrl?: string | null;
16
+ rating?: number | null;
17
+ };
18
+
19
+ function isLocalImageSrc(src?: string | null): src is string {
20
+ return typeof src === "string" && src.startsWith("/api/media/file/");
21
+ }
22
+
23
+ const containerVariants: Variants = {
24
+ hidden: {},
25
+ show: { transition: { staggerChildren: 0.12, delayChildren: 0.05 } },
26
+ };
27
+
28
+ const cardVariants: Variants = {
29
+ hidden: { opacity: 0, y: 24 },
30
+ show: { opacity: 1, y: 0, transition: { duration: 0.5, ease: "easeOut" } },
31
+ };
32
+
33
+ export const DDTestimonials: React.FC<DDTestimonialsProps> = ({
34
+ eyebrow,
35
+ headline,
36
+ testimonials,
37
+ sectionId,
38
+ customCSS,
39
+ }) => {
40
+ const populated = ((testimonials ?? []) as unknown[]).filter(
41
+ (t): t is Testimonial => typeof t === "object" && t !== null,
42
+ );
43
+
44
+ return (
45
+ <>
46
+ {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
47
+
48
+ <div id={sectionId ?? "testimonials"} className="py-24">
49
+ <div className="max-w-6xl lg:max-w-7xl mx-auto px-4 md:px-6">
50
+ {/* Header */}#BlockName-DDTestimonials
51
+ <div className="text-center max-w-xl mx-auto mb-14">
52
+ {eyebrow && (
53
+ <p className="text-[11.5px] font-semibold text-stone-400 tracking-widest uppercase mb-3 top-caption">
54
+ {eyebrow}
55
+ </p>
56
+ )}
57
+ <h2 className="text-3xl font-semibold text-stone-900 dark:text-stone-50 leading-tight">
58
+ {headline}
59
+ </h2>
60
+ </div>
61
+ {/* Testimonials */}
62
+ {populated.length > 0 && (
63
+ <motion.div
64
+ className="grid grid-cols-1 md:grid-cols-3 gap-6"
65
+ variants={containerVariants}
66
+ initial="hidden"
67
+ whileInView="show"
68
+ viewport={{ once: false, amount: 0.15 }}
69
+ >
70
+ {populated.map((t, i) => {
71
+ const avatarUrl =
72
+ typeof t.companyLogo === "object" &&
73
+ t.companyLogo !== null &&
74
+ "url" in t.companyLogo
75
+ ? t.companyLogo.url
76
+ : (t.fallbackAvatarUrl ?? null);
77
+ const safeAvatarUrl = isLocalImageSrc(avatarUrl)
78
+ ? avatarUrl
79
+ : null;
80
+
81
+ const stars = Math.min(5, Math.max(1, t.rating ?? 5));
82
+
83
+ return (
84
+ <motion.div
85
+ key={t.id ?? i}
86
+ className="rounded-2xl bg-stone-100 dark:bg-stone-900 border border-stone-100 dark:border-stone-800 p-8 flex flex-col"
87
+ variants={cardVariants}
88
+ style={{ cornerShape: "squircle" } as React.CSSProperties}
89
+ >
90
+ {/* Stars */}
91
+ <div className="flex items-center gap-1 mb-5">
92
+ {Array.from({ length: stars }).map((_, si) => (
93
+ <Star
94
+ key={si}
95
+ size={14}
96
+ className="text-amber-400"
97
+ fill="currentColor"
98
+ />
99
+ ))}
100
+ </div>
101
+
102
+ {/* Quote */}
103
+ {t.content ? (
104
+ <div className="text-sm text-stone-400 leading-relaxed flex-1 mb-6">
105
+ {/* TODO: Reconnect this project's RichText renderer for testimonial content. */}
106
+ <span>
107
+ Reconnect this project's RichText renderer for
108
+ testimonial content.
109
+ </span>
110
+ </div>
111
+ ) : null}
112
+
113
+ {/* Author / Company */}
114
+ <div className="flex items-center gap-3 pt-5 border-t border-stone-800">
115
+ {safeAvatarUrl && (
116
+ <div className="w-10 h-10 rounded-full overflow-hidden shrink-0 relative">
117
+ <Image
118
+ src={safeAvatarUrl}
119
+ alt={t.title ?? ""}
120
+ fill
121
+ sizes="40px"
122
+ className="object-cover object-top"
123
+ />
124
+ </div>
125
+ )}
126
+ <div>
127
+ <div className="text-sm font-semibold text-stone-800 dark:text-stone-50">
128
+ {t.title}
129
+ </div>
130
+ {t.designation ? (
131
+ <div className="text-xs text-stone-300">
132
+ {/* TODO: Reconnect RichText for designation if needed. */}
133
+ <span>Reconnect RichText for designation</span>
134
+ </div>
135
+ ) : null}
136
+ </div>
137
+ </div>
138
+ </motion.div>
139
+ );
140
+ })}
141
+ </motion.div>
142
+ )}
143
+ </div>
144
+ </div>
145
+ </>
146
+ );
147
+ };