blocks-dusted 0.1.1 → 0.1.2
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.
- package/README.md +967 -3
- package/package.json +2 -2
- package/src/commands/add.js +143 -45
- package/src/commands/list.js +2 -2
- package/src/installers/checkRequirements.js +1 -0
- package/src/installers/copyTemplateFiles.js +23 -2
- package/src/installers/patchPayloadConfigCollections.js +92 -0
- package/src/installers/writeInstalledBlockReadme.js +17 -4
- package/src/registry/listTemplates.js +16 -7
- package/src/registry/loadTemplateManifest.js +20 -11
- package/src/registry/validateTemplateManifest.js +6 -5
- package/src/utils/paths.js +7 -0
- package/templates/blocks/DD-BookCall/Component.tsx +535 -573
- package/templates/blocks/DD-CardTemplate01/Component.tsx +45 -49
- package/templates/blocks/DD-Carousel-A/Component.tsx +249 -266
- package/templates/blocks/DD-Carousel-B/Component.tsx +403 -432
- package/templates/blocks/DD-ComparisonTable/Component.tsx +256 -272
- package/templates/blocks/DD-Contact/Component.tsx +434 -439
- package/templates/blocks/DD-Contact/config.ts +8 -3
- package/templates/blocks/DD-FeatCat/Component.tsx +302 -361
- package/templates/blocks/DD-FeatCat/config.ts +201 -204
- package/templates/blocks/DD-FeatStrip/Component.tsx +171 -201
- package/templates/blocks/DD-Hero/Component.tsx +297 -317
- package/templates/blocks/DD-HorizontalScroll/Component.tsx +244 -303
- package/templates/blocks/DD-MasonaryMedia/Component.tsx +202 -228
- package/templates/blocks/DD-MasonaryMedia/config.ts +13 -14
- package/templates/blocks/DD-Pricing/Component.tsx +372 -380
- package/templates/blocks/DD-Process/Component.tsx +149 -155
- package/templates/blocks/DD-Section/Component.tsx +266 -327
- package/templates/blocks/DD-Services/Component.tsx +176 -188
- package/templates/blocks/DD-ShowcaseGrid/Component.tsx +307 -317
- package/templates/blocks/DD-Slider-A/Component.tsx +237 -252
- package/templates/blocks/DD-StackCards/Component.tsx +137 -160
- package/templates/blocks/DD-Team/Component.tsx +247 -265
- package/templates/blocks/DD-TechStack/Component.tsx +57 -72
- package/templates/blocks/DD-Testimonials/Component.tsx +147 -147
- package/templates/blocks/DD-Testimonials/config.ts +66 -66
- package/templates/blocks/DD-Work/Component.tsx +315 -328
- package/templates/blocks/DD-Work-B/Component.tsx +294 -320
- package/templates/collections/Testimonials/README.md +11 -0
- package/templates/collections/Testimonials/Testimonials.ts +161 -0
- package/templates/collections/Testimonials/manifest.json +55 -0
- package/templates/components/RichText/README.md +13 -0
- package/templates/components/RichText/converter/componentConverter/blocks.tsx +43 -0
- package/templates/components/RichText/converter/componentConverter/types.ts +11 -0
- package/templates/components/RichText/converter/index.tsx +18 -0
- package/templates/components/RichText/converter/internalLinks.tsx +45 -0
- package/templates/components/RichText/converter/textConverter.tsx +27 -0
- package/templates/components/RichText/index.tsx +35 -0
- package/templates/components/RichText/manifest.json +80 -0
|
@@ -1,127 +1,118 @@
|
|
|
1
|
-
|
|
1
|
+
'use client'
|
|
2
2
|
|
|
3
|
-
import React, { useEffect, useMemo, useRef } from
|
|
4
|
-
import Link from
|
|
5
|
-
import { motion, type Variants } from
|
|
6
|
-
import { cn } from
|
|
3
|
+
import React, { useEffect, useMemo, useRef } from 'react'
|
|
4
|
+
import Link from 'next/link'
|
|
5
|
+
import { motion, type Variants } from 'framer-motion'
|
|
6
|
+
import { cn } from '@/utilities/ui'
|
|
7
7
|
|
|
8
8
|
type MediaLike = {
|
|
9
|
-
url?: string | null
|
|
10
|
-
alt?: string | null
|
|
11
|
-
mimeType?: string | null
|
|
12
|
-
}
|
|
9
|
+
url?: string | null
|
|
10
|
+
alt?: string | null
|
|
11
|
+
mimeType?: string | null
|
|
12
|
+
}
|
|
13
13
|
|
|
14
14
|
type ShowcaseItem = {
|
|
15
|
-
id?: string | null
|
|
16
|
-
title?: string | null
|
|
17
|
-
category?: string | null
|
|
18
|
-
framework?: string | null
|
|
19
|
-
description?: string | null
|
|
20
|
-
href?: string | null
|
|
21
|
-
linkLabel?: string | null
|
|
22
|
-
free?: boolean | null
|
|
23
|
-
priceLabel?: string | null
|
|
24
|
-
aspectRatio?:
|
|
25
|
-
image?: number | string | MediaLike | null
|
|
26
|
-
hoverVideo?: number | string | MediaLike | null
|
|
27
|
-
posterFallbackUrl?: string | null
|
|
28
|
-
videoFallbackUrl?: string | null
|
|
29
|
-
}
|
|
15
|
+
id?: string | null
|
|
16
|
+
title?: string | null
|
|
17
|
+
category?: string | null
|
|
18
|
+
framework?: string | null
|
|
19
|
+
description?: string | null
|
|
20
|
+
href?: string | null
|
|
21
|
+
linkLabel?: string | null
|
|
22
|
+
free?: boolean | null
|
|
23
|
+
priceLabel?: string | null
|
|
24
|
+
aspectRatio?: '16/9' | '3/2' | '1/1' | '2/3' | string | null
|
|
25
|
+
image?: number | string | MediaLike | null
|
|
26
|
+
hoverVideo?: number | string | MediaLike | null
|
|
27
|
+
posterFallbackUrl?: string | null
|
|
28
|
+
videoFallbackUrl?: string | null
|
|
29
|
+
}
|
|
30
30
|
|
|
31
31
|
type DDMasonaryMediaProps = {
|
|
32
|
-
eyebrow?: string | null
|
|
33
|
-
headlineMain?: string | null
|
|
34
|
-
headlineAccent?: string | null
|
|
35
|
-
subtext?: string | null
|
|
36
|
-
items?: ShowcaseItem[] | null
|
|
37
|
-
columnsDesktop?: number | null
|
|
38
|
-
columnsTablet?: number | null
|
|
39
|
-
gapClass?: string | null
|
|
40
|
-
containerClassName?: string | null
|
|
41
|
-
sectionClassName?: string | null
|
|
42
|
-
sectionId?: string | null
|
|
43
|
-
customCSS?: string | null
|
|
44
|
-
showCta?: boolean | null
|
|
45
|
-
ctaAlign?:
|
|
46
|
-
ctaDisplayMode?:
|
|
47
|
-
ctaIconPosition?:
|
|
48
|
-
ctaButtonText?: string | null
|
|
49
|
-
ctaButtonStyle?: string | null
|
|
50
|
-
ctaSize?:
|
|
51
|
-
ctaIcon?: string | null
|
|
52
|
-
ctaLink?: any | null
|
|
53
|
-
ctaContainerClass?: string | null
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const getMediaUrl = (
|
|
57
|
-
media
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const getMediaAlt = (
|
|
67
|
-
media?: number | string | MediaLike | null,
|
|
68
|
-
fallback = "",
|
|
69
|
-
) => {
|
|
70
|
-
if (media && typeof media === "object" && typeof media.alt === "string")
|
|
71
|
-
return media.alt;
|
|
72
|
-
return fallback;
|
|
73
|
-
};
|
|
32
|
+
eyebrow?: string | null
|
|
33
|
+
headlineMain?: string | null
|
|
34
|
+
headlineAccent?: string | null
|
|
35
|
+
subtext?: string | null
|
|
36
|
+
items?: ShowcaseItem[] | null
|
|
37
|
+
columnsDesktop?: number | null
|
|
38
|
+
columnsTablet?: number | null
|
|
39
|
+
gapClass?: string | null
|
|
40
|
+
containerClassName?: string | null
|
|
41
|
+
sectionClassName?: string | null
|
|
42
|
+
sectionId?: string | null
|
|
43
|
+
customCSS?: string | null
|
|
44
|
+
showCta?: boolean | null
|
|
45
|
+
ctaAlign?: 'left' | 'center' | 'right' | null
|
|
46
|
+
ctaDisplayMode?: 'iconWithLabel' | 'iconOnly' | 'labelOnly' | null
|
|
47
|
+
ctaIconPosition?: 'before' | 'after' | null
|
|
48
|
+
ctaButtonText?: string | null
|
|
49
|
+
ctaButtonStyle?: string | null
|
|
50
|
+
ctaSize?: 'small' | 'medium' | 'large' | null
|
|
51
|
+
ctaIcon?: string | null
|
|
52
|
+
ctaLink?: any | null
|
|
53
|
+
ctaContainerClass?: string | null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const getMediaUrl = (media?: number | string | MediaLike | null, fallback?: string | null) => {
|
|
57
|
+
if (media && typeof media === 'object' && typeof media.url === 'string') return media.url
|
|
58
|
+
if (typeof media === 'string') return media
|
|
59
|
+
return fallback || ''
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const getMediaAlt = (media?: number | string | MediaLike | null, fallback = '') => {
|
|
63
|
+
if (media && typeof media === 'object' && typeof media.alt === 'string') return media.alt
|
|
64
|
+
return fallback
|
|
65
|
+
}
|
|
74
66
|
|
|
75
67
|
const getMediaMimeType = (media?: number | string | MediaLike | null) => {
|
|
76
|
-
if (media && typeof media ===
|
|
77
|
-
return media.mimeType
|
|
78
|
-
return
|
|
79
|
-
}
|
|
68
|
+
if (media && typeof media === 'object' && typeof media.mimeType === 'string')
|
|
69
|
+
return media.mimeType
|
|
70
|
+
return ''
|
|
71
|
+
}
|
|
80
72
|
|
|
81
73
|
const getFileExtension = (url: string) => {
|
|
82
|
-
const clean = url.split(
|
|
83
|
-
return clean.split(
|
|
84
|
-
}
|
|
74
|
+
const clean = url.split('?')[0]?.split('#')[0] || ''
|
|
75
|
+
return clean.split('.').pop()?.toLowerCase() || ''
|
|
76
|
+
}
|
|
85
77
|
|
|
86
78
|
const isVideoMedia = (url: string, mimeType?: string | null) => {
|
|
87
|
-
const ext = getFileExtension(url)
|
|
79
|
+
const ext = getFileExtension(url)
|
|
88
80
|
|
|
89
81
|
return Boolean(
|
|
90
|
-
mimeType?.startsWith(
|
|
91
|
-
ext ===
|
|
92
|
-
ext ===
|
|
93
|
-
ext ===
|
|
94
|
-
ext ===
|
|
95
|
-
)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const isWideCard = (ratio?: string | null) =>
|
|
99
|
-
|
|
100
|
-
const isSquareCard = (ratio?: string | null) => ratio === "1/1";
|
|
82
|
+
mimeType?.startsWith('video/') ||
|
|
83
|
+
ext === 'webm' ||
|
|
84
|
+
ext === 'mp4' ||
|
|
85
|
+
ext === 'mov' ||
|
|
86
|
+
ext === 'm4v',
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const isWideCard = (ratio?: string | null) => ratio === '16/9' || ratio === '3/2'
|
|
91
|
+
const isSquareCard = (ratio?: string | null) => ratio === '1/1'
|
|
101
92
|
|
|
102
93
|
const getGapPx = (gapClass?: string | null) => {
|
|
103
|
-
if (!gapClass) return 20
|
|
104
|
-
if (gapClass.includes(
|
|
105
|
-
if (gapClass.includes(
|
|
106
|
-
if (gapClass.includes(
|
|
107
|
-
if (gapClass.includes(
|
|
108
|
-
if (gapClass.includes(
|
|
109
|
-
if (gapClass.includes(
|
|
110
|
-
if (gapClass.includes(
|
|
111
|
-
if (gapClass.includes(
|
|
112
|
-
if (gapClass.includes(
|
|
113
|
-
return 20
|
|
114
|
-
}
|
|
94
|
+
if (!gapClass) return 20
|
|
95
|
+
if (gapClass.includes('gap-0')) return 0
|
|
96
|
+
if (gapClass.includes('gap-1')) return 4
|
|
97
|
+
if (gapClass.includes('gap-2')) return 8
|
|
98
|
+
if (gapClass.includes('gap-3')) return 12
|
|
99
|
+
if (gapClass.includes('gap-4')) return 16
|
|
100
|
+
if (gapClass.includes('gap-5')) return 20
|
|
101
|
+
if (gapClass.includes('gap-6')) return 24
|
|
102
|
+
if (gapClass.includes('gap-8')) return 32
|
|
103
|
+
if (gapClass.includes('gap-10')) return 40
|
|
104
|
+
return 20
|
|
105
|
+
}
|
|
115
106
|
|
|
116
107
|
const parseAspectRatio = (ratio?: string | null) => {
|
|
117
|
-
if (!ratio || !ratio.includes(
|
|
108
|
+
if (!ratio || !ratio.includes('/')) return '1 / 1'
|
|
118
109
|
|
|
119
|
-
const [rawWidth, rawHeight] = ratio.split(
|
|
120
|
-
const width = rawWidth && !Number.isNaN(rawWidth) ? rawWidth : 1
|
|
121
|
-
const height = rawHeight && !Number.isNaN(rawHeight) ? rawHeight : 1
|
|
110
|
+
const [rawWidth, rawHeight] = ratio.split('/').map(Number)
|
|
111
|
+
const width = rawWidth && !Number.isNaN(rawWidth) ? rawWidth : 1
|
|
112
|
+
const height = rawHeight && !Number.isNaN(rawHeight) ? rawHeight : 1
|
|
122
113
|
|
|
123
|
-
return `${width} / ${height}
|
|
124
|
-
}
|
|
114
|
+
return `${width} / ${height}`
|
|
115
|
+
}
|
|
125
116
|
|
|
126
117
|
const containerVariants: Variants = {
|
|
127
118
|
hidden: {},
|
|
@@ -131,78 +122,67 @@ const containerVariants: Variants = {
|
|
|
131
122
|
delayChildren: 0.06,
|
|
132
123
|
},
|
|
133
124
|
},
|
|
134
|
-
}
|
|
125
|
+
}
|
|
135
126
|
|
|
136
127
|
const itemVariants: Variants = {
|
|
137
128
|
hidden: {
|
|
138
129
|
opacity: 0,
|
|
139
130
|
y: 22,
|
|
140
|
-
filter:
|
|
131
|
+
filter: 'blur(6px)',
|
|
141
132
|
},
|
|
142
133
|
show: {
|
|
143
134
|
opacity: 1,
|
|
144
135
|
y: 0,
|
|
145
|
-
filter:
|
|
136
|
+
filter: 'none',
|
|
146
137
|
transition: {
|
|
147
138
|
duration: 0.45,
|
|
148
|
-
ease:
|
|
139
|
+
ease: 'easeOut',
|
|
149
140
|
},
|
|
150
141
|
},
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function StudioMasonaryCard({
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
item
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
const href = item.href || "#";
|
|
171
|
-
const aspectRatio = parseAspectRatio(item.aspectRatio || "16/9");
|
|
172
|
-
const wide = isWideCard(item.aspectRatio);
|
|
173
|
-
const square = isSquareCard(item.aspectRatio);
|
|
174
|
-
const badgeLabel =
|
|
175
|
-
item.category || (item.free ? "Free" : item.priceLabel || "");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function StudioMasonaryCard({ item, index }: { item: ShowcaseItem; index: number }) {
|
|
145
|
+
const videoRef = useRef<HTMLVideoElement | null>(null)
|
|
146
|
+
|
|
147
|
+
const imageUrl = getMediaUrl(item.image, item.posterFallbackUrl)
|
|
148
|
+
const imageAlt = getMediaAlt(item.image, item.title || 'Studio preview')
|
|
149
|
+
|
|
150
|
+
const hoverUrl = getMediaUrl(item.hoverVideo, item.videoFallbackUrl)
|
|
151
|
+
const hoverMimeType = getMediaMimeType(item.hoverVideo)
|
|
152
|
+
const hoverIsVideo = isVideoMedia(hoverUrl, hoverMimeType)
|
|
153
|
+
const hoverIsImage = Boolean(hoverUrl && !hoverIsVideo)
|
|
154
|
+
|
|
155
|
+
const href = item.href || '#'
|
|
156
|
+
const aspectRatio = parseAspectRatio(item.aspectRatio || '16/9')
|
|
157
|
+
const wide = isWideCard(item.aspectRatio)
|
|
158
|
+
const square = isSquareCard(item.aspectRatio)
|
|
159
|
+
const badgeLabel = item.category || (item.free ? 'Free' : item.priceLabel || '')
|
|
176
160
|
|
|
177
161
|
const handleMouseEnter = () => {
|
|
178
|
-
const video = videoRef.current
|
|
179
|
-
if (!video || !hoverIsVideo) return
|
|
162
|
+
const video = videoRef.current
|
|
163
|
+
if (!video || !hoverIsVideo) return
|
|
180
164
|
|
|
181
|
-
if (video.readyState === 0) video.load()
|
|
165
|
+
if (video.readyState === 0) video.load()
|
|
182
166
|
|
|
183
|
-
video.currentTime = 0
|
|
184
|
-
const playPromise = video.play()
|
|
167
|
+
video.currentTime = 0
|
|
168
|
+
const playPromise = video.play()
|
|
185
169
|
|
|
186
|
-
if (playPromise && typeof playPromise.catch ===
|
|
187
|
-
playPromise.catch(() => undefined)
|
|
170
|
+
if (playPromise && typeof playPromise.catch === 'function') {
|
|
171
|
+
playPromise.catch(() => undefined)
|
|
188
172
|
}
|
|
189
|
-
}
|
|
173
|
+
}
|
|
190
174
|
|
|
191
175
|
const handleMouseLeave = () => {
|
|
192
|
-
const video = videoRef.current
|
|
193
|
-
if (!video) return
|
|
176
|
+
const video = videoRef.current
|
|
177
|
+
if (!video) return
|
|
194
178
|
|
|
195
|
-
video.pause()
|
|
196
|
-
video.currentTime = 0
|
|
197
|
-
}
|
|
179
|
+
video.pause()
|
|
180
|
+
video.currentTime = 0
|
|
181
|
+
}
|
|
198
182
|
|
|
199
183
|
return (
|
|
200
184
|
<article
|
|
201
|
-
className={cn(
|
|
202
|
-
"studio-masonry-item card-item",
|
|
203
|
-
wide && "is-wide",
|
|
204
|
-
square && "is-square",
|
|
205
|
-
)}
|
|
185
|
+
className={cn('studio-masonry-item card-item', wide && 'is-wide', square && 'is-square')}
|
|
206
186
|
data-index={index}
|
|
207
187
|
onMouseEnter={handleMouseEnter}
|
|
208
188
|
onMouseLeave={handleMouseLeave}
|
|
@@ -211,15 +191,11 @@ function StudioMasonaryCard({
|
|
|
211
191
|
<article
|
|
212
192
|
data-theme="dark"
|
|
213
193
|
className="masonary-article-card outline-2 outline-stone-500/50 outline-solid group relative h-full w-full overflow-clip rounded-[3rem]! border border-border bg-[#55526b85]! backdrop-blur-xl! shadow-2xl transition-all duration-300 hover:cursor-pointer hover:border-lime-500 hover:shadow-2xl"
|
|
214
|
-
style={
|
|
215
|
-
{ cornerShape: "squircle", aspectRatio } as React.CSSProperties
|
|
216
|
-
}
|
|
194
|
+
style={{ cornerShape: 'squircle', aspectRatio } as React.CSSProperties}
|
|
217
195
|
>
|
|
218
196
|
<div
|
|
219
197
|
className="card-media relative w-full overflow-hidden rounded-[3rem]! border border-border bg-linear-to-br from-violet-950 to-indigo-950 shadow-2xl!"
|
|
220
|
-
style={
|
|
221
|
-
{ cornerShape: "squircle", aspectRatio } as React.CSSProperties
|
|
222
|
-
}
|
|
198
|
+
style={{ cornerShape: 'squircle', aspectRatio } as React.CSSProperties}
|
|
223
199
|
>
|
|
224
200
|
{imageUrl ? (
|
|
225
201
|
<img
|
|
@@ -228,10 +204,10 @@ function StudioMasonaryCard({
|
|
|
228
204
|
loading="lazy"
|
|
229
205
|
decoding="async"
|
|
230
206
|
className={cn(
|
|
231
|
-
|
|
232
|
-
hoverUrl &&
|
|
207
|
+
'absolute inset-0 h-full w-full object-cover object-top rounded-[3rem]! transition-all duration-500 group-hover:scale-105',
|
|
208
|
+
hoverUrl && 'group-hover:opacity-0',
|
|
233
209
|
)}
|
|
234
|
-
style={{ cornerShape:
|
|
210
|
+
style={{ cornerShape: 'squircle' } as React.CSSProperties}
|
|
235
211
|
/>
|
|
236
212
|
) : (
|
|
237
213
|
<div className="absolute inset-0 flex items-center justify-center bg-linear-to-br from-violet-950 to-indigo-950 text-xs text-stone-500">
|
|
@@ -248,7 +224,7 @@ function StudioMasonaryCard({
|
|
|
248
224
|
playsInline
|
|
249
225
|
preload="metadata"
|
|
250
226
|
className="absolute inset-0 h-full w-full object-cover object-top rounded-[3rem]! opacity-0 transition-all duration-500 group-hover:scale-105 group-hover:opacity-100"
|
|
251
|
-
style={{ cornerShape:
|
|
227
|
+
style={{ cornerShape: 'squircle' } as React.CSSProperties}
|
|
252
228
|
/>
|
|
253
229
|
)}
|
|
254
230
|
|
|
@@ -259,7 +235,7 @@ function StudioMasonaryCard({
|
|
|
259
235
|
loading="lazy"
|
|
260
236
|
decoding="async"
|
|
261
237
|
className="absolute inset-0 h-full w-full object-cover object-top !rounded-4xl opacity-0 transition-all duration-500 group-hover:scale-105 group-hover:opacity-100"
|
|
262
|
-
style={{ cornerShape:
|
|
238
|
+
style={{ cornerShape: 'squircle' } as React.CSSProperties}
|
|
263
239
|
/>
|
|
264
240
|
)}
|
|
265
241
|
|
|
@@ -267,7 +243,7 @@ function StudioMasonaryCard({
|
|
|
267
243
|
className="pointer-events-none absolute inset-0 z-10 transition-opacity duration-300 group-hover:opacity-90"
|
|
268
244
|
style={{
|
|
269
245
|
background:
|
|
270
|
-
|
|
246
|
+
'linear-gradient(180deg, rgba(0,0,0,0.62) 0%, rgba(0,0,0,0.08) 32%, rgba(0,0,0,0.12) 52%, rgba(0,0,0,0.88) 100%)',
|
|
271
247
|
}}
|
|
272
248
|
/>
|
|
273
249
|
|
|
@@ -276,7 +252,7 @@ function StudioMasonaryCard({
|
|
|
276
252
|
<div className="flex items-start justify-start">
|
|
277
253
|
<span
|
|
278
254
|
className="inline-flex rounded-full border border-lime-500/80 bg-lime-500/15 px-2 py-1 text-[11.5px] font-medium tracking-widest text-stone-50 backdrop-blur-md sm:px-2 sm:py-1"
|
|
279
|
-
style={{ boxShadow:
|
|
255
|
+
style={{ boxShadow: '0 0 24px rgba(132,204,22,0.35)' }}
|
|
280
256
|
>
|
|
281
257
|
{badgeLabel}
|
|
282
258
|
</span>
|
|
@@ -288,7 +264,7 @@ function StudioMasonaryCard({
|
|
|
288
264
|
{item.title && (
|
|
289
265
|
<h3
|
|
290
266
|
className="max-w-[86%] text-lg font-semibold tracking-[0.08em] text-stone-50"
|
|
291
|
-
style={{ textShadow:
|
|
267
|
+
style={{ textShadow: '0 4px 16px rgba(0,0,0,0.9)' }}
|
|
292
268
|
>
|
|
293
269
|
{item.title}
|
|
294
270
|
</h3>
|
|
@@ -298,96 +274,96 @@ function StudioMasonaryCard({
|
|
|
298
274
|
</article>
|
|
299
275
|
</Link>
|
|
300
276
|
</article>
|
|
301
|
-
)
|
|
277
|
+
)
|
|
302
278
|
}
|
|
303
279
|
|
|
304
280
|
export const DDMasonaryMedia: React.FC<DDMasonaryMediaProps> = ({
|
|
305
|
-
eyebrow =
|
|
306
|
-
headlineMain =
|
|
307
|
-
headlineAccent =
|
|
308
|
-
subtext =
|
|
281
|
+
eyebrow = 'Featured systems',
|
|
282
|
+
headlineMain = 'Components, blocks, and templates',
|
|
283
|
+
headlineAccent = 'built to feel alive',
|
|
284
|
+
subtext = 'A curated showcase of production-ready interface pieces, with static previews and motion demos on hover.',
|
|
309
285
|
items = [],
|
|
310
286
|
columnsDesktop = 12,
|
|
311
287
|
columnsTablet = 2,
|
|
312
|
-
gapClass =
|
|
313
|
-
containerClassName =
|
|
314
|
-
sectionClassName =
|
|
315
|
-
sectionId =
|
|
288
|
+
gapClass = 'gap-5',
|
|
289
|
+
containerClassName = 'max-w-7xl mx-auto px-4 md:px-6',
|
|
290
|
+
sectionClassName = 'py-24',
|
|
291
|
+
sectionId = 'studio-masonary-media',
|
|
316
292
|
customCSS,
|
|
317
293
|
showCta = false,
|
|
318
|
-
ctaAlign =
|
|
319
|
-
ctaDisplayMode =
|
|
320
|
-
ctaIconPosition =
|
|
294
|
+
ctaAlign = 'center',
|
|
295
|
+
ctaDisplayMode = 'iconWithLabel',
|
|
296
|
+
ctaIconPosition = 'before',
|
|
321
297
|
ctaButtonText,
|
|
322
|
-
ctaButtonStyle =
|
|
323
|
-
ctaSize =
|
|
298
|
+
ctaButtonStyle = 'primary',
|
|
299
|
+
ctaSize = 'medium',
|
|
324
300
|
ctaIcon,
|
|
325
301
|
ctaLink,
|
|
326
302
|
ctaContainerClass,
|
|
327
303
|
}) => {
|
|
328
|
-
const gridRef = useRef<HTMLDivElement | null>(null)
|
|
329
|
-
const isotopeRef = useRef<any>(null)
|
|
304
|
+
const gridRef = useRef<HTMLDivElement | null>(null)
|
|
305
|
+
const isotopeRef = useRef<any>(null)
|
|
330
306
|
|
|
331
|
-
const safeSectionId = sectionId ||
|
|
332
|
-
const gap = useMemo(() => getGapPx(gapClass), [gapClass])
|
|
333
|
-
const desktopColumns = Math.min(Math.max(columnsDesktop || 6, 5), 6)
|
|
334
|
-
const tabletColumns = Math.min(Math.max(columnsTablet || 2, 1), 3)
|
|
307
|
+
const safeSectionId = sectionId || 'studio-masonary-media'
|
|
308
|
+
const gap = useMemo(() => getGapPx(gapClass), [gapClass])
|
|
309
|
+
const desktopColumns = Math.min(Math.max(columnsDesktop || 6, 5), 6)
|
|
310
|
+
const tabletColumns = Math.min(Math.max(columnsTablet || 2, 1), 3)
|
|
335
311
|
|
|
336
312
|
useEffect(() => {
|
|
337
|
-
if (!gridRef.current || !items || items.length === 0) return
|
|
313
|
+
if (!gridRef.current || !items || items.length === 0) return
|
|
338
314
|
|
|
339
|
-
let destroyed = false
|
|
315
|
+
let destroyed = false
|
|
340
316
|
|
|
341
317
|
const init = async () => {
|
|
342
|
-
const Isotope = (await import(
|
|
343
|
-
const imagesLoaded = (await import(
|
|
318
|
+
const Isotope = (await import('isotope-layout')).default
|
|
319
|
+
const imagesLoaded = (await import('imagesloaded')).default
|
|
344
320
|
|
|
345
|
-
if (!gridRef.current || destroyed) return
|
|
321
|
+
if (!gridRef.current || destroyed) return
|
|
346
322
|
|
|
347
323
|
const iso = new Isotope(gridRef.current, {
|
|
348
|
-
itemSelector:
|
|
324
|
+
itemSelector: '.studio-masonry-item',
|
|
349
325
|
percentPosition: true,
|
|
350
|
-
transitionDuration:
|
|
326
|
+
transitionDuration: '0.35s',
|
|
351
327
|
masonry: {
|
|
352
|
-
columnWidth:
|
|
353
|
-
gutter:
|
|
328
|
+
columnWidth: '.studio-masonry-sizer',
|
|
329
|
+
gutter: '.studio-masonry-gutter',
|
|
354
330
|
fitWidth: false,
|
|
355
331
|
},
|
|
356
|
-
})
|
|
332
|
+
})
|
|
357
333
|
|
|
358
|
-
isotopeRef.current = iso
|
|
334
|
+
isotopeRef.current = iso
|
|
359
335
|
|
|
360
336
|
imagesLoaded(gridRef.current, () => {
|
|
361
|
-
if (!destroyed) iso.layout()
|
|
362
|
-
})
|
|
337
|
+
if (!destroyed) iso.layout()
|
|
338
|
+
})
|
|
363
339
|
|
|
364
340
|
window.setTimeout(() => {
|
|
365
|
-
if (!destroyed) iso.layout()
|
|
366
|
-
}, 250)
|
|
367
|
-
}
|
|
341
|
+
if (!destroyed) iso.layout()
|
|
342
|
+
}, 250)
|
|
343
|
+
}
|
|
368
344
|
|
|
369
|
-
init()
|
|
345
|
+
init()
|
|
370
346
|
|
|
371
347
|
const handleResize = () => {
|
|
372
|
-
isotopeRef.current?.layout()
|
|
373
|
-
}
|
|
348
|
+
isotopeRef.current?.layout()
|
|
349
|
+
}
|
|
374
350
|
|
|
375
|
-
window.addEventListener(
|
|
351
|
+
window.addEventListener('resize', handleResize)
|
|
376
352
|
|
|
377
353
|
return () => {
|
|
378
|
-
destroyed = true
|
|
379
|
-
window.removeEventListener(
|
|
380
|
-
isotopeRef.current?.destroy()
|
|
381
|
-
isotopeRef.current = null
|
|
382
|
-
}
|
|
383
|
-
}, [items])
|
|
354
|
+
destroyed = true
|
|
355
|
+
window.removeEventListener('resize', handleResize)
|
|
356
|
+
isotopeRef.current?.destroy()
|
|
357
|
+
isotopeRef.current = null
|
|
358
|
+
}
|
|
359
|
+
}, [items])
|
|
384
360
|
|
|
385
361
|
useEffect(() => {
|
|
386
|
-
isotopeRef.current?.reloadItems()
|
|
387
|
-
isotopeRef.current?.layout()
|
|
388
|
-
}, [items, columnsDesktop, columnsTablet, gap])
|
|
362
|
+
isotopeRef.current?.reloadItems()
|
|
363
|
+
isotopeRef.current?.layout()
|
|
364
|
+
}, [items, columnsDesktop, columnsTablet, gap])
|
|
389
365
|
|
|
390
|
-
if (!items || items.length === 0) return null
|
|
366
|
+
if (!items || items.length === 0) return null
|
|
391
367
|
|
|
392
368
|
return (
|
|
393
369
|
<>
|
|
@@ -487,11 +463,9 @@ export const DDMasonaryMedia: React.FC<DDMasonaryMediaProps> = ({
|
|
|
487
463
|
|
|
488
464
|
{customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
|
|
489
465
|
|
|
490
|
-
<section
|
|
491
|
-
id={safeSectionId}
|
|
492
|
-
className={cn("studio-masonary-media", sectionClassName)}
|
|
493
|
-
>
|
|
466
|
+
<section id={safeSectionId} className={cn('studio-masonary-media', sectionClassName)}>
|
|
494
467
|
<div className={cn(containerClassName)}>
|
|
468
|
+
#BlockName-DDMasonaryMedia
|
|
495
469
|
<motion.div
|
|
496
470
|
variants={containerVariants}
|
|
497
471
|
initial="hidden"
|
|
@@ -546,26 +520,26 @@ export const DDMasonaryMedia: React.FC<DDMasonaryMediaProps> = ({
|
|
|
546
520
|
{showCta && (
|
|
547
521
|
<div
|
|
548
522
|
className={cn(
|
|
549
|
-
|
|
550
|
-
ctaAlign ===
|
|
551
|
-
ctaAlign ===
|
|
552
|
-
ctaAlign ===
|
|
523
|
+
'mt-10 flex',
|
|
524
|
+
ctaAlign === 'left' && 'justify-start',
|
|
525
|
+
ctaAlign === 'center' && 'justify-center',
|
|
526
|
+
ctaAlign === 'right' && 'justify-end',
|
|
553
527
|
ctaContainerClass,
|
|
554
528
|
)}
|
|
555
529
|
>
|
|
556
530
|
<a
|
|
557
|
-
href={(ctaLink as any)?.url ??
|
|
531
|
+
href={(ctaLink as any)?.url ?? '#'}
|
|
558
532
|
className="inline-flex items-center gap-1.5 bg-indigo-700 text-stone-50 text-xs font-semibold px-3 py-1.5 rounded-lg group-hover:bg-indigo-600 group-hover:scale-[1.05] group-hover:shadow-xl transition-all duration-150 whitespace-nowrap"
|
|
559
|
-
style={{ cornerShape:
|
|
533
|
+
style={{ cornerShape: 'squircle' } as React.CSSProperties}
|
|
560
534
|
>
|
|
561
535
|
{/* TODO: Reconnect ButtonWithIcon if this project provides it. */}
|
|
562
|
-
{ctaButtonText ?? (ctaLink as any)?.label ??
|
|
536
|
+
{ctaButtonText ?? (ctaLink as any)?.label ?? 'Learn more'}
|
|
563
537
|
</a>
|
|
564
538
|
</div>
|
|
565
539
|
)}
|
|
566
540
|
</section>
|
|
567
541
|
</>
|
|
568
|
-
)
|
|
569
|
-
}
|
|
542
|
+
)
|
|
543
|
+
}
|
|
570
544
|
|
|
571
|
-
export default DDMasonaryMedia
|
|
545
|
+
export default DDMasonaryMedia
|
|
@@ -151,17 +151,16 @@ export const DDMasonaryMedia: Block = {
|
|
|
151
151
|
}
|
|
152
152
|
]
|
|
153
153
|
},
|
|
154
|
-
{
|
|
155
|
-
name: 'image',
|
|
156
|
-
type: 'upload',
|
|
157
|
-
relationTo: 'media',
|
|
158
|
-
label: 'Static Preview Image',
|
|
154
|
+
{
|
|
155
|
+
name: 'image',
|
|
156
|
+
type: 'upload',
|
|
157
|
+
relationTo: 'media',
|
|
158
|
+
label: 'Static Preview Image',
|
|
159
159
|
required: false,
|
|
160
|
-
admin: {
|
|
161
|
-
description:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
},
|
|
160
|
+
admin: {
|
|
161
|
+
description: 'Static image shown by default before hover.'
|
|
162
|
+
}
|
|
163
|
+
},
|
|
165
164
|
{
|
|
166
165
|
name: 'hoverVideo',
|
|
167
166
|
type: 'upload',
|
|
@@ -372,10 +371,10 @@ export const DDMasonaryMedia: Block = {
|
|
|
372
371
|
]
|
|
373
372
|
}
|
|
374
373
|
]
|
|
375
|
-
},
|
|
376
|
-
{
|
|
377
|
-
name: 'ctaContainerClass',
|
|
378
|
-
type: 'text',
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
name: 'ctaContainerClass',
|
|
377
|
+
type: 'text',
|
|
379
378
|
label: 'Extra Container Class',
|
|
380
379
|
admin: {
|
|
381
380
|
condition: (_, siblingData) => siblingData?.showCta,
|