blocks-dusted 0.1.12 → 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.
- package/package.json +1 -1
- package/src/installers/patchRenderBlocks.js +1 -1
- package/src/registry/validateTemplateManifest.js +2 -1
- package/templates/blocks/BannerDD790432/Component.tsx +63 -0
- package/templates/blocks/BannerDD790432/config.ts +54 -0
- package/templates/blocks/BannerDD790432/manifest.json +80 -0
- package/templates/blocks/Button47/Component.tsx +124 -0
- package/templates/blocks/Button47/config.ts +75 -0
- package/templates/blocks/Button47/link.ts +22 -0
- package/templates/blocks/Button47/manifest.json +105 -0
- package/templates/blocks/Button47/styles.module.scss +75 -0
- package/templates/blocks/DD-Accordion/Component.tsx +55 -36
- package/templates/blocks/DD-Accordion/config.ts +71 -59
- package/templates/blocks/DD-AnimText/Component.tsx +50 -32
- package/templates/blocks/DD-AnimText/config.ts +296 -295
- package/templates/blocks/DD-AsideNav/Component.tsx +323 -319
- package/templates/blocks/DD-Banner/Component.tsx +54 -51
- package/templates/blocks/DD-BookCall/Component.tsx +535 -535
- package/templates/blocks/DD-ButtonPop/Component.tsx +118 -117
- package/templates/blocks/DD-Card/Component.tsx +4 -4
- package/templates/blocks/DD-CardTemplate01/Component.tsx +139 -140
- package/templates/blocks/DD-CardsWithVariantV3/Component.tsx +472 -0
- package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/index.tsx +203 -0
- package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/styles.ts +87 -0
- package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/useInjectStyle.ts +39 -0
- package/templates/blocks/DD-CardsWithVariantV3/config.ts +326 -0
- package/templates/blocks/DD-CardsWithVariantV3/manifest.json +110 -0
- package/templates/blocks/DD-Carousel-A/Component.tsx +251 -249
- package/templates/blocks/DD-Carousel-B/Component.tsx +405 -403
- package/templates/blocks/DD-ComparisonTable/Component.tsx +255 -256
- package/templates/blocks/DD-Contact/Component.tsx +433 -435
- package/templates/blocks/DD-Counter/Component.tsx +11 -6
- package/templates/blocks/DD-DeliCard/Component.tsx +303 -0
- package/templates/blocks/DD-DeliCard/config.ts +186 -0
- package/templates/blocks/DD-DeliCard/manifest.json +94 -0
- package/templates/blocks/DD-FeatCat/Component.tsx +302 -302
- package/templates/blocks/DD-FeatProd/CircularGallery.tsx +1179 -0
- package/templates/blocks/DD-FeatProd/Component.tsx +484 -0
- package/templates/blocks/DD-FeatProd/config.ts +173 -0
- package/templates/blocks/DD-FeatProd/manifest.json +93 -0
- package/templates/blocks/DD-FeatServ/Component.tsx +607 -0
- package/templates/blocks/DD-FeatServ/config.ts +213 -0
- package/templates/blocks/DD-FeatServ/manifest.json +113 -0
- package/templates/blocks/DD-FeatStrip/Component.tsx +584 -585
- package/templates/blocks/DD-FluidBtn/ButtonsEyeCatching.tsx +627 -628
- package/templates/blocks/DD-FluidBtn/Component.tsx +46 -43
- package/templates/blocks/DD-GooeyInfo/Component.tsx +150 -149
- package/templates/blocks/DD-GooeyInfo/config.ts +136 -135
- package/templates/blocks/DD-Hero/Component.tsx +296 -297
- package/templates/blocks/DD-HeroPlas/Component.tsx +50 -0
- package/templates/blocks/DD-HeroPlas/ThreeScene.tsx +109 -0
- package/templates/blocks/DD-HeroPlas/config.ts +33 -0
- package/templates/blocks/DD-HeroPlas/manifest.json +88 -0
- package/templates/blocks/DD-HorizontalScroll/Component.tsx +937 -936
- package/templates/blocks/DD-Icon/Component.tsx +46 -81
- package/templates/blocks/DD-IconButton/Component.tsx +13 -20
- package/templates/blocks/DD-IdTag/Component.tsx +22 -21
- package/templates/blocks/DD-LineAnim/Component.tsx +42 -36
- package/templates/blocks/DD-LogoMarq/Component.tsx +38 -48
- package/templates/blocks/DD-Lottie/Component.tsx +11 -3
- package/templates/blocks/DD-MasonaryMedia/Component.tsx +544 -545
- package/templates/blocks/DD-MasonaryMedia/config.ts +389 -389
- package/templates/blocks/DD-Masonry/Component.tsx +654 -644
- package/templates/blocks/DD-Masonry/config.ts +475 -475
- package/templates/blocks/DD-MaterialSwatches/Component.tsx +85 -0
- package/templates/blocks/DD-MaterialSwatches/config.ts +232 -0
- package/templates/blocks/DD-MaterialSwatches/manifest.json +84 -0
- package/templates/blocks/DD-MaterialSwatches/material.ts +107 -0
- package/templates/blocks/DD-NineCanvas/Component.tsx +129 -0
- package/templates/blocks/DD-NineCanvas/RenderBlocks.tsx +244 -0
- package/templates/blocks/DD-NineCanvas/allowedCanvasBlocks.ts +6 -0
- package/templates/blocks/DD-NineCanvas/blockComponents.tsx +8 -0
- package/templates/blocks/DD-NineCanvas/canvas/CanvasGrid.tsx +316 -0
- package/templates/blocks/DD-NineCanvas/canvas/RadialNav.tsx +432 -0
- package/templates/blocks/DD-NineCanvas/canvas/canvas-panels.css +889 -0
- package/templates/blocks/DD-NineCanvas/canvas/icons/custom-icons.tsx +364 -0
- package/templates/blocks/DD-NineCanvas/config.ts +61 -0
- package/templates/blocks/DD-NineCanvas/manifest.json +165 -0
- package/templates/blocks/DD-NineCanvas/styles.scss +152 -0
- package/templates/blocks/DD-NineCanvas/tooltip/get-strict-context.tsx +26 -0
- package/templates/blocks/DD-NineCanvas/tooltip/primitives.tsx +178 -0
- package/templates/blocks/DD-NineCanvas/tooltip/tooltip.tsx +71 -0
- package/templates/blocks/DD-NineCanvas/tooltip/use-controlled-state.tsx +33 -0
- package/templates/blocks/DD-NineCanvas/utilities/FadeOnScroll.tsx +68 -0
- package/templates/blocks/DD-NineCanvas/utilities/ParallaxWrapper.tsx +157 -0
- package/templates/blocks/DD-NineCanvas/utilities/renderCustomCSS.ts +5 -0
- package/templates/blocks/DD-NineCanvas/utilities/webglCleanup.ts +27 -0
- package/templates/blocks/DD-PayloadAIAssistant/Component.tsx +78 -0
- package/templates/blocks/DD-PayloadAIAssistant/DustedBot.tsx +590 -0
- package/templates/blocks/DD-PayloadAIAssistant/config.ts +37 -0
- package/templates/blocks/DD-PayloadAIAssistant/demo.css +125 -0
- package/templates/blocks/DD-PayloadAIAssistant/manifest.json +93 -0
- package/templates/blocks/DD-PayloadAIAssistant/source-defaults.ts +17 -0
- package/templates/blocks/DD-PayloadAIAssistant/styles.ts +306 -0
- package/templates/blocks/DD-Pricing/Component.tsx +2 -3
- package/templates/blocks/DD-Pricing/config.ts +215 -215
- package/templates/blocks/DD-Process/Component.tsx +2 -2
- package/templates/blocks/DD-ProjHeader/Component.tsx +93 -91
- package/templates/blocks/DD-ProjSlider/Component.tsx +1 -1
- package/templates/blocks/DD-Section/Component.tsx +265 -266
- package/templates/blocks/DD-Separator/Component.tsx +66 -66
- package/templates/blocks/DD-Services/Component.tsx +176 -176
- package/templates/blocks/DD-ShowcaseGrid/Component.tsx +306 -307
- package/templates/blocks/DD-Slider-A/Component.tsx +2 -2
- package/templates/blocks/DD-StackCards/Component.tsx +172 -171
- package/templates/blocks/DD-Team/Component.tsx +247 -247
- package/templates/blocks/DD-TeamCards/Component.tsx +251 -251
- package/templates/blocks/DD-TechStack/Component.tsx +173 -174
- package/templates/blocks/DD-TestMarq/Component.tsx +158 -132
- package/templates/blocks/DD-Testim/Component.tsx +247 -247
- package/templates/blocks/DD-Testim2/Component.tsx +265 -256
- package/templates/blocks/DD-Testimonials/Component.tsx +139 -139
- package/templates/blocks/DD-Thanks/Component.tsx +1 -1
- package/templates/blocks/DD-Work/Component.tsx +318 -318
- package/templates/blocks/DD-Work-B/Component.tsx +294 -294
- package/templates/blocks/DDHG-CTASection2/Component.tsx +183 -184
- package/templates/blocks/DDHG-HighlightArc/Component.tsx +6 -3
- package/templates/blocks/DDHG-LogoMarquee/Component.tsx +45 -8
- package/templates/blocks/DDHG-NavCodeGrid/Component.tsx +1 -3
- package/templates/blocks/DDHG-ServiceDetails/ServiceDetailCard.tsx +9 -22
- package/templates/blocks/DDHG-ServiceHero/Component.tsx +10 -10
- package/templates/blocks/DDHG-TeamCards/Component.tsx +6 -35
- package/templates/blocks/Form/Checkbox/index.tsx +45 -0
- package/templates/blocks/Form/Component.tsx +163 -0
- package/templates/blocks/Form/Country/index.tsx +65 -0
- package/templates/blocks/Form/Country/options.ts +982 -0
- package/templates/blocks/Form/Email/index.tsx +38 -0
- package/templates/blocks/Form/Error/index.tsx +15 -0
- package/templates/blocks/Form/Message/index.tsx +13 -0
- package/templates/blocks/Form/Number/index.tsx +36 -0
- package/templates/blocks/Form/Select/index.tsx +63 -0
- package/templates/blocks/Form/State/index.tsx +64 -0
- package/templates/blocks/Form/State/options.ts +52 -0
- package/templates/blocks/Form/Text/index.tsx +32 -0
- package/templates/blocks/Form/Textarea/index.tsx +40 -0
- package/templates/blocks/Form/Width/index.tsx +13 -0
- package/templates/blocks/Form/config.ts +51 -0
- package/templates/blocks/Form/fields.tsx +21 -0
- package/templates/blocks/Form/manifest.json +168 -0
- package/templates/blocks/MediaBlock/Component.tsx +67 -0
- package/templates/blocks/MediaBlock/config.ts +14 -0
- package/templates/blocks/MediaBlock/manifest.json +92 -0
- package/templates/blocks/TransitionsDustedShowcase/Component.tsx +9 -0
- package/templates/blocks/TransitionsDustedShowcase/config.ts +13 -0
- package/templates/blocks/TransitionsDustedShowcase/manifest.json +80 -0
- package/templates/blocks/rawHtml/Component.tsx +25 -0
- package/templates/blocks/rawHtml/config.ts +56 -0
- package/templates/blocks/rawHtml/manifest.json +80 -0
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
|
4
|
+
import Image from 'next/image'
|
|
5
|
+
import Link from 'next/link'
|
|
6
|
+
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
|
7
|
+
import type { LucideIcon } from 'lucide-react'
|
|
8
|
+
import * as LucideIcons from 'lucide-react'
|
|
9
|
+
import useEmblaCarousel from 'embla-carousel-react'
|
|
10
|
+
import { motion } from 'framer-motion'
|
|
11
|
+
import { cn } from '@/utilities/ui'
|
|
12
|
+
import dynamic from 'next/dynamic'
|
|
13
|
+
|
|
14
|
+
/* -- Inlined CSS (converted from the original SCSS) -- */
|
|
15
|
+
|
|
16
|
+
const featuredProductsBlockStyles = `
|
|
17
|
+
.viewAllHref-link {
|
|
18
|
+
margin-left: 4rem;
|
|
19
|
+
padding: 1rem;
|
|
20
|
+
border-radius: 1.5rem;
|
|
21
|
+
corner-shape: squircle;
|
|
22
|
+
transition: all 0.3s;
|
|
23
|
+
|
|
24
|
+
@media (max-width: 768px) {
|
|
25
|
+
right: 1rem;
|
|
26
|
+
position: absolute;
|
|
27
|
+
float: right;
|
|
28
|
+
background: transparent;
|
|
29
|
+
border: 1px solid transparent;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
.viewAllHref-link:hover {
|
|
33
|
+
gap: 0.75rem;
|
|
34
|
+
transition: all 0.3s;
|
|
35
|
+
}
|
|
36
|
+
.viewAllHref-link:hover svg {
|
|
37
|
+
scale: 1.5;
|
|
38
|
+
transform-origin: right;
|
|
39
|
+
transition: all 0.2s;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
[data-container-type="productCarouselContainer"] .productCardWrapper.isSelected {
|
|
43
|
+
[data-content-type="featuredProductCard"] {
|
|
44
|
+
outline: 3px solid indigo;
|
|
45
|
+
transition: all 0.3s;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
`
|
|
50
|
+
const CircularGallery = dynamic(() => import('./CircularGallery'), { ssr: false })
|
|
51
|
+
|
|
52
|
+
const fadeUp = {
|
|
53
|
+
hidden: { opacity: 0, y: 40 },
|
|
54
|
+
visible: (i: number) => ({
|
|
55
|
+
opacity: 1,
|
|
56
|
+
y: 0,
|
|
57
|
+
transition: { duration: 0.5, ease: 'easeOut', delay: i * 0.1 },
|
|
58
|
+
}),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type MediaObj = {
|
|
62
|
+
url?: string | null
|
|
63
|
+
alt?: string | null
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
type LinkGroup = {
|
|
67
|
+
type?: 'custom' | 'reference' | null
|
|
68
|
+
newTab?: boolean | null
|
|
69
|
+
url?: string | null
|
|
70
|
+
label?: string | null
|
|
71
|
+
icon?: string | null
|
|
72
|
+
iconPosition?: 'before' | 'after' | 'only' | null
|
|
73
|
+
reference?: {
|
|
74
|
+
relationTo?: 'pages' | 'posts'
|
|
75
|
+
value?: { slug?: string | null } | string | number | null
|
|
76
|
+
} | null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type ProductItem = {
|
|
80
|
+
id?: string | null
|
|
81
|
+
name: string
|
|
82
|
+
slug: string
|
|
83
|
+
category?: string | null
|
|
84
|
+
shortDesc?: string | null
|
|
85
|
+
badge?: string | null
|
|
86
|
+
image?: MediaObj | string | null
|
|
87
|
+
imageFallbackUrl?: string | null
|
|
88
|
+
features?:
|
|
89
|
+
| {
|
|
90
|
+
id?: string | null
|
|
91
|
+
feature: string
|
|
92
|
+
}[]
|
|
93
|
+
| null
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export type DDFeatProdProps = {
|
|
97
|
+
blockType?: 'DD-FeatProd'
|
|
98
|
+
blockName?: string | null
|
|
99
|
+
eyebrow?: string | null
|
|
100
|
+
heading?: string | null
|
|
101
|
+
description?: string | null
|
|
102
|
+
viewAllLabel?: string | null
|
|
103
|
+
viewAllUrl?: LinkGroup | string | null
|
|
104
|
+
products?: ProductItem[] | null
|
|
105
|
+
sectionId?: string | null
|
|
106
|
+
customCSS?: string | null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getLinkHref(linkGroup?: LinkGroup | string | null): string | null {
|
|
110
|
+
if (typeof linkGroup === 'string') return linkGroup
|
|
111
|
+
|
|
112
|
+
if (
|
|
113
|
+
linkGroup?.type === 'reference' &&
|
|
114
|
+
linkGroup.reference?.value &&
|
|
115
|
+
typeof linkGroup.reference.value === 'object' &&
|
|
116
|
+
linkGroup.reference.value.slug
|
|
117
|
+
) {
|
|
118
|
+
const collectionPrefix = linkGroup.reference.relationTo === 'posts' ? '/posts' : ''
|
|
119
|
+
return `${collectionPrefix}/${linkGroup.reference.value.slug}`
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return linkGroup?.url || null
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function getImageSrc(product: ProductItem): string {
|
|
126
|
+
if (product.image && typeof product.image === 'object' && (product.image as MediaObj).url) {
|
|
127
|
+
return (product.image as MediaObj).url!
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (typeof product.image === 'string') {
|
|
131
|
+
return product.image
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return product.imageFallbackUrl ?? ''
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function useSlidesPerView() {
|
|
138
|
+
const [spv, setSpv] = useState(3)
|
|
139
|
+
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
const update = () => {
|
|
142
|
+
const width = window.innerWidth
|
|
143
|
+
|
|
144
|
+
if (width < 640) setSpv(1)
|
|
145
|
+
else if (width < 1024) setSpv(2)
|
|
146
|
+
else setSpv(3)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
update()
|
|
150
|
+
window.addEventListener('resize', update)
|
|
151
|
+
|
|
152
|
+
return () => window.removeEventListener('resize', update)
|
|
153
|
+
}, [])
|
|
154
|
+
|
|
155
|
+
return spv
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export const DDFeatProd: React.FC<DDFeatProdProps> = ({
|
|
159
|
+
eyebrow = 'What We Supply',
|
|
160
|
+
heading = 'Our Product Range',
|
|
161
|
+
description = 'Industry-leading plastic sheet materials for every application - from signage to industrial packaging.',
|
|
162
|
+
viewAllLabel = 'View All',
|
|
163
|
+
viewAllUrl,
|
|
164
|
+
products: productsProp,
|
|
165
|
+
sectionId,
|
|
166
|
+
customCSS,
|
|
167
|
+
}) => {
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
if (document.getElementById('featured-products-block-inline-styles')) return
|
|
170
|
+
|
|
171
|
+
const style = document.createElement('style')
|
|
172
|
+
style.id = 'featured-products-block-inline-styles'
|
|
173
|
+
style.textContent = featuredProductsBlockStyles
|
|
174
|
+
document.head.appendChild(style)
|
|
175
|
+
|
|
176
|
+
return () => style.remove()
|
|
177
|
+
}, [])
|
|
178
|
+
|
|
179
|
+
const products = productsProp ?? []
|
|
180
|
+
const viewAllLink = typeof viewAllUrl === 'object' ? viewAllUrl : null
|
|
181
|
+
const viewAllHref = getLinkHref(viewAllUrl)
|
|
182
|
+
const resolvedViewAllLabel = viewAllLink?.label || viewAllLabel || 'View All'
|
|
183
|
+
const ViewAllIcon = viewAllLink?.icon
|
|
184
|
+
? (LucideIcons[viewAllLink.icon as keyof typeof LucideIcons] as LucideIcon | undefined)
|
|
185
|
+
: undefined
|
|
186
|
+
const viewAllIconPosition = viewAllLink?.iconPosition ?? 'after'
|
|
187
|
+
const slidesPerView = useSlidesPerView()
|
|
188
|
+
|
|
189
|
+
const [mainEmblaRef, mainEmblaApi] = useEmblaCarousel({
|
|
190
|
+
align: 'start',
|
|
191
|
+
slidesToScroll: 1,
|
|
192
|
+
containScroll: 'trimSnaps',
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
196
|
+
const [loadedImages, setLoadedImages] = useState<Record<number, boolean>>({})
|
|
197
|
+
|
|
198
|
+
const onSelect = useCallback(() => {
|
|
199
|
+
if (!mainEmblaApi) return
|
|
200
|
+
const index = mainEmblaApi.selectedScrollSnap()
|
|
201
|
+
setSelectedIndex(index)
|
|
202
|
+
}, [mainEmblaApi])
|
|
203
|
+
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
if (!mainEmblaApi) return
|
|
206
|
+
|
|
207
|
+
onSelect()
|
|
208
|
+
mainEmblaApi.on('select', onSelect)
|
|
209
|
+
mainEmblaApi.on('reInit', onSelect)
|
|
210
|
+
|
|
211
|
+
return () => {
|
|
212
|
+
mainEmblaApi.off('select', onSelect)
|
|
213
|
+
mainEmblaApi.off('reInit', onSelect)
|
|
214
|
+
}
|
|
215
|
+
}, [mainEmblaApi, onSelect])
|
|
216
|
+
|
|
217
|
+
const scrollPrev = useCallback(() => mainEmblaApi?.scrollPrev(), [mainEmblaApi])
|
|
218
|
+
const scrollNext = useCallback(() => mainEmblaApi?.scrollNext(), [mainEmblaApi])
|
|
219
|
+
const scrollTo = useCallback(
|
|
220
|
+
(index: number) => {
|
|
221
|
+
setSelectedIndex(index)
|
|
222
|
+
mainEmblaApi?.scrollTo(index)
|
|
223
|
+
},
|
|
224
|
+
[mainEmblaApi],
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
const galleryItems = useMemo(
|
|
228
|
+
() =>
|
|
229
|
+
products.map((p) => ({
|
|
230
|
+
image: getImageSrc(p),
|
|
231
|
+
text: p.name.length > 24 ? p.name.slice(0, 24) + '\u2026' : p.name,
|
|
232
|
+
})),
|
|
233
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
234
|
+
[products.map((p) => p.slug).join(',')],
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if (!products.length) return null
|
|
238
|
+
|
|
239
|
+
return (
|
|
240
|
+
<div
|
|
241
|
+
id={sectionId ?? undefined}
|
|
242
|
+
className="featured-products-block relative left-0 mx-auto w-screen max-w-none overflow-hidden bg-stone-50 py-20 text-stone-900 dark:bg-stone-950 dark:text-stone-100"
|
|
243
|
+
>
|
|
244
|
+
{customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
|
|
245
|
+
|
|
246
|
+
<div className="container-fluid">
|
|
247
|
+
<div className="mx-auto max-w-7xl px-4 md:px-6">
|
|
248
|
+
{(eyebrow || heading || description || viewAllHref) && (
|
|
249
|
+
<motion.div
|
|
250
|
+
className="mb-0 flex flex-col justify-between gap-4 md:flex-row md:items-end"
|
|
251
|
+
initial="hidden"
|
|
252
|
+
whileInView="visible"
|
|
253
|
+
viewport={{ once: true, amount: 0.3 }}
|
|
254
|
+
custom={0}
|
|
255
|
+
// variants={fadeUp}
|
|
256
|
+
>
|
|
257
|
+
<div>
|
|
258
|
+
{eyebrow && (
|
|
259
|
+
<div
|
|
260
|
+
data-type={`eyebrow-${eyebrow}`}
|
|
261
|
+
className="text-xs tracking-widest text-indigo-700 dark:text-indigo-300"
|
|
262
|
+
>
|
|
263
|
+
{eyebrow}
|
|
264
|
+
</div>
|
|
265
|
+
)}
|
|
266
|
+
|
|
267
|
+
{heading && (
|
|
268
|
+
<h2 className="text-3xl font-black leading-tight text-stone-900 dark:text-stone-100 md:text-4xl lg:text-5xl">
|
|
269
|
+
{heading}
|
|
270
|
+
</h2>
|
|
271
|
+
)}
|
|
272
|
+
|
|
273
|
+
{description && (
|
|
274
|
+
<p className="mt-3 max-w-xl text-sm text-stone-600 dark:text-stone-300 md:text-base">
|
|
275
|
+
{description}
|
|
276
|
+
</p>
|
|
277
|
+
)}
|
|
278
|
+
</div>
|
|
279
|
+
|
|
280
|
+
<div className="flex items-center gap-3">
|
|
281
|
+
<button
|
|
282
|
+
type="button"
|
|
283
|
+
onClick={scrollPrev}
|
|
284
|
+
aria-label="Previous product"
|
|
285
|
+
className="flex h-11 w-11 cursor-pointer items-center justify-center rounded-full border-2 border-stone-800 text-stone-900 transition-all hover:bg-stone-900 hover:text-stone-50 dark:border-stone-200 dark:text-stone-100 dark:hover:bg-stone-100 dark:hover:text-stone-950 disabled:cursor-not-allowed disabled:opacity-30"
|
|
286
|
+
>
|
|
287
|
+
<ChevronLeft className="h-5 w-5" />
|
|
288
|
+
</button>
|
|
289
|
+
|
|
290
|
+
<button
|
|
291
|
+
type="button"
|
|
292
|
+
onClick={scrollNext}
|
|
293
|
+
aria-label="Next product"
|
|
294
|
+
className="flex h-11 w-11 cursor-pointer items-center justify-center rounded-full bg-indigo-600 text-white transition-all hover:bg-indigo-700 dark:bg-indigo-500 dark:text-stone-950 dark:hover:bg-indigo-300 disabled:cursor-not-allowed disabled:opacity-30"
|
|
295
|
+
>
|
|
296
|
+
<ChevronRight className="h-5 w-5" />
|
|
297
|
+
</button>
|
|
298
|
+
|
|
299
|
+
{viewAllHref && resolvedViewAllLabel && (
|
|
300
|
+
<Link
|
|
301
|
+
href={viewAllHref}
|
|
302
|
+
aria-label={viewAllIconPosition === 'only' ? resolvedViewAllLabel : undefined}
|
|
303
|
+
className="viewAllHref-link ml-2 inline-flex items-center gap-1.5 whitespace-nowrap text-sm font-semibold text-stone-900 dark:text-stone-100 transition-colors hover:text-indigo-700 dark:text-stone-100 dark:hover:text-indigo-300"
|
|
304
|
+
{...(viewAllLink?.newTab
|
|
305
|
+
? { rel: 'noopener noreferrer', target: '_blank' }
|
|
306
|
+
: {})}
|
|
307
|
+
>
|
|
308
|
+
{ViewAllIcon && viewAllIconPosition === 'before' && (
|
|
309
|
+
<ViewAllIcon aria-hidden="true" className="h-4 w-4 shrink-0" />
|
|
310
|
+
)}
|
|
311
|
+
{viewAllIconPosition !== 'only' && resolvedViewAllLabel}
|
|
312
|
+
{ViewAllIcon && ['after', 'only'].includes(viewAllIconPosition) && (
|
|
313
|
+
<ViewAllIcon aria-hidden="true" className="h-4 w-4 shrink-0" />
|
|
314
|
+
)}
|
|
315
|
+
{!ViewAllIcon && viewAllIconPosition !== 'only' && (
|
|
316
|
+
<span aria-hidden="true">
|
|
317
|
+
<svg
|
|
318
|
+
width="9"
|
|
319
|
+
height="18"
|
|
320
|
+
viewBox="0 0 9 18"
|
|
321
|
+
fill="none"
|
|
322
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
323
|
+
>
|
|
324
|
+
<path
|
|
325
|
+
d="M5.53846 0H0L3.46154 8.86957L0 18H5.53846L9 8.86957L5.53846 0Z"
|
|
326
|
+
fill="currentColor"
|
|
327
|
+
/>
|
|
328
|
+
</svg>
|
|
329
|
+
</span>
|
|
330
|
+
)}
|
|
331
|
+
</Link>
|
|
332
|
+
)}
|
|
333
|
+
</div>
|
|
334
|
+
</motion.div>
|
|
335
|
+
)}
|
|
336
|
+
|
|
337
|
+
<div
|
|
338
|
+
ref={mainEmblaRef}
|
|
339
|
+
data-container-type="productCarouselContainer"
|
|
340
|
+
className="overflow-x-visible overflow-y-visible pb-8"
|
|
341
|
+
>
|
|
342
|
+
<div className="flex py-4">
|
|
343
|
+
{products.map((product, index) => {
|
|
344
|
+
const imgSrc = getImageSrc(product)
|
|
345
|
+
|
|
346
|
+
return (
|
|
347
|
+
<motion.div
|
|
348
|
+
key={product.id ?? product.slug ?? index}
|
|
349
|
+
data-content-type="productCardWrapper"
|
|
350
|
+
className={cn(
|
|
351
|
+
'productCardWrapper',
|
|
352
|
+
'min-w-0 flex-none px-2',
|
|
353
|
+
selectedIndex === index && 'isSelected',
|
|
354
|
+
slidesPerView === 1 && 'w-full',
|
|
355
|
+
slidesPerView === 2 && 'w-1/2',
|
|
356
|
+
slidesPerView === 3 && 'w-1/3',
|
|
357
|
+
)}
|
|
358
|
+
initial="hidden"
|
|
359
|
+
whileInView="visible"
|
|
360
|
+
viewport={{ once: true, amount: 0.2 }}
|
|
361
|
+
custom={index}
|
|
362
|
+
// variants={fadeUp}
|
|
363
|
+
>
|
|
364
|
+
<Link
|
|
365
|
+
href={`${product.slug}`}
|
|
366
|
+
// href={`/products/${product.slug}`}
|
|
367
|
+
className="group block h-full"
|
|
368
|
+
data-link-type="featuredProductCard"
|
|
369
|
+
>
|
|
370
|
+
<div
|
|
371
|
+
data-content-type="featuredProductCard"
|
|
372
|
+
className="flex h-full flex-col overflow-hidden rounded-2xl border border-stone-200 dark:border-stone-700 bg-stone-50 text-stone-900 transition-all duration-300 dark:bg-stone-900 dark:text-stone-100 hover:-translate-y-2 hover:border-indigo-500/50 hover:border-[2px] hover:shadow-xl"
|
|
373
|
+
>
|
|
374
|
+
<div className="relative h-52 flex-shrink-0 overflow-hidden bg-stone-100 dark:bg-stone-800">
|
|
375
|
+
{imgSrc ? (
|
|
376
|
+
<>
|
|
377
|
+
{!loadedImages[index] && (
|
|
378
|
+
<div className="absolute shimmer-effect inset-0 z-1 flex items-center justify-center bg-steel rounded-sm">
|
|
379
|
+
<div className="flex flex-col items-center gap-3">
|
|
380
|
+
<div className="text-stone-500 text-sm font-medium px-4 py-2 rounded-md">
|
|
381
|
+
Loading video...
|
|
382
|
+
</div>
|
|
383
|
+
</div>
|
|
384
|
+
</div>
|
|
385
|
+
// <div className="absolute inset-0 animate-pulse bg-stone-200 dark:bg-stone-800" />
|
|
386
|
+
)}
|
|
387
|
+
|
|
388
|
+
<Image
|
|
389
|
+
src={imgSrc}
|
|
390
|
+
alt={
|
|
391
|
+
product.image && typeof product.image === 'object'
|
|
392
|
+
? product.image.alt || product.name
|
|
393
|
+
: product.name
|
|
394
|
+
}
|
|
395
|
+
fill
|
|
396
|
+
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
|
|
397
|
+
priority={index === 0}
|
|
398
|
+
className="h-full w-full object-cover object-top transition-transform duration-500 group-hover:scale-105"
|
|
399
|
+
draggable={false}
|
|
400
|
+
onLoad={() =>
|
|
401
|
+
setLoadedImages((prev) => ({
|
|
402
|
+
...prev,
|
|
403
|
+
[index]: true,
|
|
404
|
+
}))
|
|
405
|
+
}
|
|
406
|
+
/>
|
|
407
|
+
</>
|
|
408
|
+
) : (
|
|
409
|
+
<div className="h-full w-full bg-stone-200 dark:bg-stone-800" />
|
|
410
|
+
)}
|
|
411
|
+
|
|
412
|
+
{product.badge && (
|
|
413
|
+
<div className="absolute left-3 top-3 rounded-full bg-indigo-600 px-3 py-1 text-xs font-bold text-white">
|
|
414
|
+
{product.badge}
|
|
415
|
+
</div>
|
|
416
|
+
)}
|
|
417
|
+
|
|
418
|
+
<div className="absolute inset-0 bg-gradient-to-t from-[#292524]/60 via-transparent to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
|
|
419
|
+
|
|
420
|
+
<div className="absolute bottom-3 left-3 right-3 opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
|
421
|
+
<span className="text-sm font-semibold text-white">
|
|
422
|
+
View Details >
|
|
423
|
+
</span>
|
|
424
|
+
</div>
|
|
425
|
+
</div>
|
|
426
|
+
|
|
427
|
+
<div className="card-content prod-card flex flex-1 flex-col px-4 pb-5 pt-2">
|
|
428
|
+
{product.category && (
|
|
429
|
+
<div className="mb-1 text-xs uppercase tracking-widest text-indigo-700 dark:text-indigo-300">
|
|
430
|
+
{product.category}
|
|
431
|
+
</div>
|
|
432
|
+
)}
|
|
433
|
+
|
|
434
|
+
<h3 className="mb-2 line-clamp-2 text-base font-bold leading-snug text-stone-900 dark:text-stone-100">
|
|
435
|
+
{product.name}
|
|
436
|
+
</h3>
|
|
437
|
+
|
|
438
|
+
{product.shortDesc && (
|
|
439
|
+
<p className="mb-4 line-clamp-2 flex-1 text-sm leading-relaxed text-stone-600 dark:text-stone-300">
|
|
440
|
+
{product.shortDesc}
|
|
441
|
+
</p>
|
|
442
|
+
)}
|
|
443
|
+
|
|
444
|
+
{!!product.features?.length && (
|
|
445
|
+
<div className="flex flex-wrap gap-1.5">
|
|
446
|
+
{product.features.slice(0, 2).map((item) => (
|
|
447
|
+
<span
|
|
448
|
+
key={item.id ?? item.feature}
|
|
449
|
+
className="rounded-full bg-indigo-100 dark:bg-indigo-950/70 px-2.5 py-1 text-xs text-indigo-700 dark:text-indigo-300"
|
|
450
|
+
>
|
|
451
|
+
{item.feature}
|
|
452
|
+
</span>
|
|
453
|
+
))}
|
|
454
|
+
</div>
|
|
455
|
+
)}
|
|
456
|
+
</div>
|
|
457
|
+
</div>
|
|
458
|
+
</Link>
|
|
459
|
+
</motion.div>
|
|
460
|
+
)
|
|
461
|
+
})}
|
|
462
|
+
</div>
|
|
463
|
+
</div>
|
|
464
|
+
</div>
|
|
465
|
+
|
|
466
|
+
{products.length > 1 && (
|
|
467
|
+
<div className="mt-2 h-64 w-full overflow-visible relative top-[-4rem]">
|
|
468
|
+
<CircularGallery
|
|
469
|
+
items={galleryItems}
|
|
470
|
+
bend={2}
|
|
471
|
+
textColor="#432dd7"
|
|
472
|
+
borderRadius={0.05}
|
|
473
|
+
scrollSpeed={2}
|
|
474
|
+
scrollEase={0.05}
|
|
475
|
+
onItemClick={(idx) => scrollTo(idx)}
|
|
476
|
+
/>
|
|
477
|
+
</div>
|
|
478
|
+
)}
|
|
479
|
+
</div>
|
|
480
|
+
</div>
|
|
481
|
+
)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export default DDFeatProd
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { Block } from 'payload'
|
|
2
|
+
import { link } from '@/fields/link'
|
|
3
|
+
|
|
4
|
+
export const DDFeatProd: Block = {
|
|
5
|
+
slug: 'DD-FeatProd',
|
|
6
|
+
dbName: 'dd_feat_prod',
|
|
7
|
+
interfaceName: 'DDFeatProd',
|
|
8
|
+
labels: {
|
|
9
|
+
singular: 'DD Featured Products',
|
|
10
|
+
plural: 'DD Featured Products',
|
|
11
|
+
},
|
|
12
|
+
fields: [
|
|
13
|
+
{
|
|
14
|
+
type: 'tabs',
|
|
15
|
+
tabs: [
|
|
16
|
+
{
|
|
17
|
+
label: 'Header',
|
|
18
|
+
fields: [
|
|
19
|
+
{
|
|
20
|
+
name: 'eyebrow',
|
|
21
|
+
type: 'text',
|
|
22
|
+
label: 'Eyebrow Label',
|
|
23
|
+
defaultValue: 'What We Supply',
|
|
24
|
+
admin: {
|
|
25
|
+
description: 'Optional small label above the heading',
|
|
26
|
+
placeholder: 'What We Supply',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'heading',
|
|
31
|
+
type: 'text',
|
|
32
|
+
label: 'Heading',
|
|
33
|
+
defaultValue: 'Our Product Range',
|
|
34
|
+
admin: {
|
|
35
|
+
placeholder: 'Our Product Range',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'description',
|
|
40
|
+
type: 'textarea',
|
|
41
|
+
label: 'Description',
|
|
42
|
+
defaultValue:
|
|
43
|
+
'Industry-leading plastic sheet materials for every application — from signage to industrial packaging.',
|
|
44
|
+
admin: {
|
|
45
|
+
placeholder:
|
|
46
|
+
'Industry-leading plastic sheet materials for every application — from signage to industrial packaging.',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
link({
|
|
51
|
+
appearances: false,
|
|
52
|
+
overrides: {
|
|
53
|
+
name: 'viewAllUrl',
|
|
54
|
+
label: 'View All Link',
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
label: 'Products',
|
|
61
|
+
fields: [
|
|
62
|
+
{
|
|
63
|
+
name: 'products',
|
|
64
|
+
type: 'array',
|
|
65
|
+
label: 'Products',
|
|
66
|
+
minRows: 1,
|
|
67
|
+
labels: {
|
|
68
|
+
singular: 'Product',
|
|
69
|
+
plural: 'Products',
|
|
70
|
+
},
|
|
71
|
+
fields: [
|
|
72
|
+
{
|
|
73
|
+
name: 'name',
|
|
74
|
+
type: 'text',
|
|
75
|
+
label: 'Product Name',
|
|
76
|
+
required: true,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'slug',
|
|
80
|
+
type: 'text',
|
|
81
|
+
label: 'Product Slug',
|
|
82
|
+
required: true,
|
|
83
|
+
admin: {
|
|
84
|
+
placeholder: 'acrylic-sheets',
|
|
85
|
+
description: 'Used for the product detail URL e.g. /products/acrylic-sheets',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'category',
|
|
90
|
+
type: 'text',
|
|
91
|
+
label: 'Category',
|
|
92
|
+
admin: {
|
|
93
|
+
placeholder: 'Acrylic',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'shortDesc',
|
|
98
|
+
type: 'textarea',
|
|
99
|
+
label: 'Short Description',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'badge',
|
|
103
|
+
type: 'text',
|
|
104
|
+
label: 'Badge',
|
|
105
|
+
admin: {
|
|
106
|
+
placeholder: 'Popular',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'image',
|
|
111
|
+
type: 'upload',
|
|
112
|
+
label: 'Product Image',
|
|
113
|
+
relationTo: 'media',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: 'imageFallbackUrl',
|
|
117
|
+
type: 'text',
|
|
118
|
+
label: 'Image Fallback URL',
|
|
119
|
+
admin: {
|
|
120
|
+
placeholder: '/assets/placeholder/product.png',
|
|
121
|
+
description: 'Used when no uploaded image is set',
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'features',
|
|
126
|
+
type: 'array',
|
|
127
|
+
label: 'Feature Tags',
|
|
128
|
+
maxRows: 4,
|
|
129
|
+
labels: {
|
|
130
|
+
singular: 'Feature',
|
|
131
|
+
plural: 'Features',
|
|
132
|
+
},
|
|
133
|
+
fields: [
|
|
134
|
+
{
|
|
135
|
+
name: 'feature',
|
|
136
|
+
type: 'text',
|
|
137
|
+
label: 'Feature',
|
|
138
|
+
required: true,
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
label: 'Settings',
|
|
148
|
+
fields: [
|
|
149
|
+
{
|
|
150
|
+
name: 'sectionId',
|
|
151
|
+
type: 'text',
|
|
152
|
+
label: 'Section ID',
|
|
153
|
+
admin: {
|
|
154
|
+
description: 'Optional ID for anchor linking',
|
|
155
|
+
placeholder: 'featured-products',
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: 'customCSS',
|
|
160
|
+
type: 'code',
|
|
161
|
+
label: 'Custom CSS',
|
|
162
|
+
admin: {
|
|
163
|
+
language: 'css',
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export default DDFeatProd
|