blocks-dusted 0.1.2 → 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.
@@ -0,0 +1,517 @@
1
+ 'use client'
2
+
3
+ import { usePathname } from 'next/navigation'
4
+ import React, { useEffect, useMemo, useState, useRef } from 'react'
5
+ import Link from 'next/link'
6
+ import {
7
+ NavigationMenu,
8
+ NavigationMenuContent,
9
+ NavigationMenuItem,
10
+ NavigationMenuLink,
11
+ NavigationMenuList,
12
+ NavigationMenuTrigger
13
+ } from '@/components/ui/navigation-menu'
14
+ import { CircleCheck, CircleHelp, Circle, Home, User, Settings, Mail, Phone } from 'lucide-react'
15
+ import type { Header as HeaderType } from '@/payload-types'
16
+ import RichText from '@/components/RichText'
17
+ import { cn } from '@/utilities/ui'
18
+
19
+ import * as LucideIcons from 'lucide-react'
20
+ import { motion } from 'framer-motion'
21
+
22
+ import MobileBottomNav from './MobileBottomNav'
23
+
24
+ interface HeaderDD02Props {
25
+ data?: HeaderType
26
+ variant?: 'default' | 'centered' | 'fullwidth'
27
+ sticky?: boolean
28
+ }
29
+
30
+ const iconMap = {
31
+ CircleHelp,
32
+ Circle,
33
+ CircleCheck,
34
+ Home,
35
+ User,
36
+ Settings,
37
+ Mail,
38
+ Phone
39
+ }
40
+
41
+ type CartVisibilityMode = 'always' | 'hide' | 'show'
42
+
43
+ type CartVisibilityEntry = {
44
+ path?: string | null
45
+ }
46
+
47
+ type CartVisibilityConfig = {
48
+ mode?: CartVisibilityMode
49
+ paths?: CartVisibilityEntry[]
50
+ }
51
+
52
+ type HeaderDD02Data = HeaderType['header02'] & {
53
+ cartButtonVisibility?: CartVisibilityConfig
54
+ }
55
+
56
+ const normalizePath = (value?: string | null) => {
57
+ if (!value) return '/'
58
+ const [base] = value.split(/[?#]/)
59
+ if (!base) return '/'
60
+ if (base !== '/' && base.endsWith('/')) {
61
+ return base.slice(0, -1)
62
+ }
63
+ return base || '/'
64
+ }
65
+
66
+ // Helper to extract href from link field
67
+ const getLinkHref = (link: any): string => {
68
+ if (!link) return '#'
69
+ if (link.type === 'reference') {
70
+ const value = link.reference?.value
71
+ if (typeof value === 'object' && 'slug' in value) {
72
+ return `/${value.slug}`
73
+ }
74
+ }
75
+ return link.url || '#'
76
+ }
77
+
78
+ const resolveCustomIconUrl = (customIcon?: any): string | null => {
79
+ if (!customIcon) return null
80
+ if (typeof customIcon === 'string') return customIcon
81
+ if (typeof customIcon === 'number') return null
82
+ if (typeof customIcon === 'object' && 'url' in customIcon && typeof customIcon.url === 'string') {
83
+ return customIcon.url
84
+ }
85
+ return null
86
+ }
87
+
88
+ // Helper to render icon from link field or fallback
89
+ const renderIcon = (iconName?: string | null, className?: string, customIcon?: any) => {
90
+ const customUrl = resolveCustomIconUrl(customIcon)
91
+ if (customUrl) {
92
+ return (
93
+ <img
94
+ src={customUrl}
95
+ className={className || 'size-4'}
96
+ alt=""
97
+ aria-hidden="true"
98
+ loading="lazy"
99
+ />
100
+ )
101
+ }
102
+
103
+ if (!iconName) return null
104
+ const IconComponent = (LucideIcons as any)[iconName]
105
+ if (!IconComponent) return null
106
+ return <IconComponent className={className || 'size-4'} />
107
+ }
108
+
109
+ const dropdownStyles = `
110
+ .dropdown-richtext-content {
111
+ max-height: 80vh;
112
+ overflow-y: auto;
113
+ }
114
+
115
+ :where(.navigation-menu-viewport) {
116
+ position: absolute;
117
+ top: 100%;
118
+ left: 0;
119
+ width: 100%;
120
+ perspective: 2000px;
121
+ transform-origin: top center;
122
+ }
123
+
124
+ .NavigationMenuContent {
125
+ position: relative;
126
+ top: 0;
127
+ left: 0;
128
+ animation-duration: 250ms;
129
+ max-height: var(--radix-navigation-menu-viewport-height);
130
+ overflow: auto;
131
+ }
132
+ `
133
+
134
+ function ListItem({
135
+ title,
136
+ children,
137
+ href,
138
+ className,
139
+ icon,
140
+ iconPosition = 'before'
141
+ }: {
142
+ title: string
143
+ children?: React.ReactNode
144
+ href: string
145
+ className?: string
146
+ icon?: React.ReactNode
147
+ iconPosition?: 'before' | 'after'
148
+ }) {
149
+ return (
150
+ <li>
151
+ <NavigationMenuLink asChild>
152
+ <Link
153
+ href={href}
154
+ className={cn(
155
+ 'block select-none space-y-1 no-underline outline-none transition-colors hover:bg-zinc-800/50 duration-300 rounded-sm hover:text-accent focus:bg-accent focus:text-stone-200 py-2 px-3',
156
+ className
157
+ )}
158
+ >
159
+ <div className="text-sm font-medium leading-none flex items-center gap-2">
160
+ {icon && iconPosition === 'before' && icon}
161
+ {title}
162
+ {icon && iconPosition === 'after' && icon}
163
+ </div>
164
+ {children && (
165
+ <p
166
+ className="line-clamp-2 text-xs leading-snug text-stone-400 mt-0 !pt-1 -translate-y-2"
167
+ style={{ lineHeight: '1.3' }}
168
+ >
169
+ {children}
170
+ </p>
171
+ )}
172
+ </Link>
173
+ </NavigationMenuLink>
174
+ </li>
175
+ )
176
+ }
177
+
178
+ function ListItemWithIcon({
179
+ title,
180
+ href,
181
+ icon,
182
+ className
183
+ }: {
184
+ title: string
185
+ href: string
186
+ icon: string
187
+ className?: string
188
+ }) {
189
+ const IconComponent = iconMap[icon as keyof typeof iconMap]
190
+
191
+ return (
192
+ <li>
193
+ <NavigationMenuLink asChild>
194
+ <Link
195
+ href={href}
196
+ className={cn(
197
+ 'flex items-center gap-2 select-none no-underline outline-none transition-colors hover:bg-accent hover:text-stone-200 focus:bg-accent focus:text-stone-200 py-2 px-3',
198
+ className
199
+ )}
200
+ >
201
+ {IconComponent && <IconComponent className="size-4" />}
202
+ <span className="text-sm font-medium leading-none">{title}</span>
203
+ </Link>
204
+ </NavigationMenuLink>
205
+ </li>
206
+ )
207
+ }
208
+
209
+ export default function HeaderDD02({ data, variant = 'default', sticky = true }: HeaderDD02Props) {
210
+ // Get scroll settings from data with defaults
211
+ const enableHideOnScroll = data?.header02?.enableHideOnScroll ?? true
212
+ const scrollThreshold = data?.header02?.scrollThreshold ?? 100
213
+ const animationDuration = data?.header02?.animationDuration ?? 0.3
214
+ const [forceOpen, setForceOpen] = useState(true)
215
+
216
+ // State for handling scroll behavior
217
+ const [isHidden, setIsHidden] = useState(false)
218
+ const lastScrollY = useRef(0)
219
+ const pathname = usePathname()
220
+ const normalizedCurrentPathname = useMemo(() => normalizePath(pathname), [pathname])
221
+ const header02Data = data?.header02 as HeaderDD02Data | undefined
222
+ const cartVisibilityConfig =
223
+ header02Data?.cartButtonVisibility ?? (data as any)?.header02CartButtonVisibility
224
+ const normalizedPathname = useMemo(() => normalizePath(pathname), [pathname])
225
+ const cartVisibilityPaths = useMemo(
226
+ () =>
227
+ (cartVisibilityConfig?.paths ?? [])
228
+ .map((entry) => normalizePath(entry?.path))
229
+ .filter((path): path is string => Boolean(path)),
230
+ [cartVisibilityConfig?.paths]
231
+ )
232
+ const cartVisibilityMode = cartVisibilityConfig?.mode ?? 'always'
233
+ const showCartButton = useMemo(() => {
234
+ if (cartVisibilityMode === 'always') return true
235
+ const matches = cartVisibilityPaths.includes(normalizedPathname)
236
+ return cartVisibilityMode === 'hide' ? !matches : matches
237
+ }, [cartVisibilityMode, cartVisibilityPaths, normalizedPathname])
238
+
239
+ // Effect for scroll hide/show behavior
240
+ useEffect(() => {
241
+ if (!enableHideOnScroll) return
242
+
243
+ const handleScroll = () => {
244
+ const currentScrollY = window.scrollY
245
+ setIsHidden(currentScrollY > lastScrollY.current && currentScrollY > scrollThreshold)
246
+ lastScrollY.current = currentScrollY
247
+ }
248
+
249
+ window.addEventListener('scroll', handleScroll, { passive: true })
250
+ return () => window.removeEventListener('scroll', handleScroll)
251
+ }, [enableHideOnScroll, scrollThreshold])
252
+
253
+ // Extract navItems from header02 data
254
+ const navItems = data?.header02?.navItems || []
255
+
256
+ return (
257
+ <>
258
+ <style jsx global>
259
+ {dropdownStyles}
260
+ </style>
261
+ {/* Desktop Navigation - Hidden on mobile */}
262
+ <header className="header-02 fixed container left-0 right-0 mx-auto w-screen mt-2 justify-between hidden md:flex">
263
+ <motion.nav
264
+ initial={{ opacity: 1, y: 0 }}
265
+ animate={{ opacity: 1, y: enableHideOnScroll && isHidden ? -100 : 0 }}
266
+ transition={{ duration: animationDuration }}
267
+ className={`navbar fixed top-0 z-50 right-0 ${variant === 'default' ? 'text-left' : ''} ${sticky ? 'sticky top-0 z-50' : ''} transition-transform duration-300 ease-out`}
268
+ id="topnav"
269
+ >
270
+ <div className="nav">
271
+ <NavigationMenu
272
+ viewport={false}
273
+ className="header02-navigation-menu shadow-glass dark:border-neutral-700 !flex w-full items-center justify-between rounded-2xl border px-5 py-0 pr-2.5 backdrop-blur-md relative"
274
+ >
275
+ <NavigationMenuList className="ml-2 gap-4 md:gap-6 flex">
276
+ {navItems.map((navItem, index) => {
277
+ // Simple Link - works
278
+ if (navItem.type === 'link') {
279
+ const href = getLinkHref(navItem.link)
280
+ const normalizedHref = normalizePath(href)
281
+ const isActive = normalizedHref === normalizedCurrentPathname
282
+ const icon = renderIcon(navItem.link?.icon, undefined, navItem.link?.customIcon)
283
+ const iconPosition = navItem.link?.iconPosition || 'before'
284
+ const showLabel = navItem.showLabel ?? true
285
+ return (
286
+ <NavigationMenuItem key={index}>
287
+ <NavigationMenuLink
288
+ asChild
289
+ className={cn(
290
+ 'header02-nav-btn !flex flex-row items-center justify-center px-0 bg-transparent text-stone-200 dark:text-stone-200 gap-0 text-sm transition-colors focus:outline-none',
291
+ isActive &&
292
+ 'header02-nav-btn-active text-stone-200 dark:text-stone-200 font-semibold'
293
+ )}
294
+ >
295
+ <Link
296
+ href={href}
297
+ {...(navItem.link?.newTab
298
+ ? { target: '_blank', rel: 'noopener noreferrer' }
299
+ : {})}
300
+ className="flex items-center gap-1 h-[2.5rem] card-bottom-lit"
301
+ >
302
+ {icon && iconPosition === 'before' && icon}
303
+ {showLabel && navItem.link?.label}
304
+ {icon && iconPosition === 'after' && icon}
305
+ </Link>
306
+ </NavigationMenuLink>
307
+ </NavigationMenuItem>
308
+ )
309
+ }
310
+
311
+ // Dropdown with RichText + Links (Variant 1) - works
312
+ if (navItem.type === 'dropdown-richtext') {
313
+ const triggerIcon = renderIcon(navItem.triggerIcon)
314
+ const triggerIconPosition = navItem.triggerIconPosition || 'before'
315
+ return (
316
+ <NavigationMenuItem
317
+ key={index}
318
+ className="header02-nav-btn card-bottom-lit bg-transparent text-stone-200 dark:text-stone-200"
319
+ >
320
+ <NavigationMenuTrigger className="flex items-center gap-2">
321
+ {triggerIcon && triggerIconPosition === 'before' && triggerIcon}
322
+ {navItem.label}
323
+ {triggerIcon && triggerIconPosition === 'after' && triggerIcon}
324
+ </NavigationMenuTrigger>
325
+ <NavigationMenuContent className="header02-dropdown">
326
+ <motion.div
327
+ initial={{ opacity: 0, x: -50 }}
328
+ animate={{ opacity: 1, x: 0 }}
329
+ transition={{ duration: 0.2, ease: 'easeOut' }}
330
+ className="border border-border rounded-md overflow-hidden grid gap-3 pt-0 md:w-[400px] lg:w-[500px] lg:grid-cols-[.75fr_1fr]"
331
+ >
332
+ <div className="row-span-4">
333
+ <NavigationMenuLink asChild>
334
+ <div className="bg-card flex h-full w-full select-none flex-col justify-end rounded-none p-4 no-underline outline-none focus:shadow-md">
335
+ {navItem.richTextContent && (
336
+ <RichText
337
+ data={navItem.richTextContent}
338
+ enableGutter={false}
339
+ enableProse={false}
340
+ className="text-sm"
341
+ />
342
+ )}
343
+ </div>
344
+ </NavigationMenuLink>
345
+ </div>
346
+ <ul className="flex flex-col gap-2 pt-4 p-1">
347
+ {navItem.v1_children?.map((child, childIndex) => (
348
+ <ListItem
349
+ key={childIndex}
350
+ title={child.link?.label || ''}
351
+ href={getLinkHref(child.link)}
352
+ icon={renderIcon(
353
+ child.link?.icon,
354
+ undefined,
355
+ child.link?.customIcon
356
+ )}
357
+ iconPosition={child.link?.iconPosition || 'before'}
358
+ className="text-sm"
359
+ >
360
+ {child.description}
361
+ </ListItem>
362
+ ))}
363
+ </ul>
364
+ </motion.div>
365
+ </NavigationMenuContent>
366
+ </NavigationMenuItem>
367
+ )
368
+ }
369
+
370
+ // Dropdown - Single Column with Description (Variant 4) - works
371
+ if (navItem.type === 'dropdown-single-desc') {
372
+ const triggerIcon = renderIcon(navItem.triggerIcon)
373
+ const triggerIconPosition = navItem.triggerIconPosition || 'before'
374
+ return (
375
+ <NavigationMenuItem
376
+ key={index}
377
+ className="header02-nav-btn card-bottom-lit text-stone-200 dark:text-stone-200"
378
+ >
379
+ <NavigationMenuTrigger className="flex items-center gap-2">
380
+ {triggerIcon && triggerIconPosition === 'before' && triggerIcon}
381
+ {navItem.label}
382
+ {triggerIcon && triggerIconPosition === 'after' && triggerIcon}
383
+ </NavigationMenuTrigger>
384
+ <NavigationMenuContent className="p-0">
385
+ <motion.ul
386
+ initial={{ opacity: 0, x: -50 }}
387
+ animate={{ opacity: 1, x: 0 }}
388
+ transition={{ duration: 0.2, ease: 'easeOut' }}
389
+ className="bg-7 border border-border rounded-md overflow-hidden grid gap-3 p-1 w-fit md:w-[14rem]"
390
+ >
391
+ {navItem.v1_children?.map((child, childIndex) => (
392
+ <ListItem
393
+ key={childIndex}
394
+ title={child.link?.label || ''}
395
+ href={getLinkHref(child.link)}
396
+ icon={renderIcon(
397
+ child.link?.icon,
398
+ undefined,
399
+ child.link?.customIcon
400
+ )}
401
+ iconPosition={child.link?.iconPosition || 'before'}
402
+ >
403
+ {child.description}
404
+ </ListItem>
405
+ ))}
406
+ </motion.ul>
407
+ </NavigationMenuContent>
408
+ </NavigationMenuItem>
409
+ )
410
+ }
411
+
412
+ // Dropdown - Single Column no Description (Variant 5) - works
413
+ if (navItem.type === 'dropdown-single') {
414
+ const triggerIcon = renderIcon(navItem.triggerIcon)
415
+ const triggerIconPosition = navItem.triggerIconPosition || 'before'
416
+ return (
417
+ <NavigationMenuItem
418
+ key={index}
419
+ className="header02-nav-btn card-bottom-lit bg-transparent text-stone-200 dark:text-stone-200"
420
+ >
421
+ <NavigationMenuTrigger className="flex items-center gap-2">
422
+ {triggerIcon && triggerIconPosition === 'before' && triggerIcon}
423
+ {navItem.label}
424
+ {triggerIcon && triggerIconPosition === 'after' && triggerIcon}
425
+ </NavigationMenuTrigger>
426
+ <NavigationMenuContent className="p-0">
427
+ <motion.ul
428
+ initial={{ opacity: 0, x: -50 }}
429
+ animate={{ opacity: 1, x: 0 }}
430
+ transition={{ duration: 0.2, ease: 'easeOut' }}
431
+ className="bg-7 border border-border rounded-md overflow-hidden flex flex-col gap-2 p-1 w-fit"
432
+ >
433
+ {navItem.v5_children?.map((child, childIndex) => (
434
+ <ListItem
435
+ key={childIndex}
436
+ title={child.link?.label || ''}
437
+ href={getLinkHref(child.link)}
438
+ icon={renderIcon(
439
+ child.link?.icon,
440
+ undefined,
441
+ child.link?.customIcon
442
+ )}
443
+ iconPosition={child.link?.iconPosition || 'before'}
444
+ />
445
+ ))}
446
+ </motion.ul>
447
+ </NavigationMenuContent>
448
+ </NavigationMenuItem>
449
+ )
450
+ }
451
+
452
+ // Dropdown - Single Column with Icons (Variant 6) - works
453
+ if (navItem.type === 'dropdown-icons') {
454
+ const triggerIcon = renderIcon(navItem.triggerIcon)
455
+ const triggerIconPosition = navItem.triggerIconPosition || 'before'
456
+ return (
457
+ <NavigationMenuItem
458
+ key={index}
459
+ className="header02-nav-btn card-bottom-lit bg-transparent text-stone-200 dark:text-stone-200"
460
+ >
461
+ <NavigationMenuTrigger className="flex items-center gap-2">
462
+ {triggerIcon && triggerIconPosition === 'before' && triggerIcon}
463
+ {navItem.label}
464
+ {triggerIcon && triggerIconPosition === 'after' && triggerIcon}
465
+ </NavigationMenuTrigger>
466
+ <NavigationMenuContent className="p-0">
467
+ <motion.ul
468
+ initial={{ opacity: 0, x: -50 }}
469
+ animate={{ opacity: 1, x: 0 }}
470
+ transition={{ duration: 0.2, ease: 'easeOut' }}
471
+ className="bg-7 border border-border rounded-md overflow-hidden flex flex-col gap-2 p-1 w-fit"
472
+ >
473
+ {navItem.v6_children?.map((child, childIndex) => {
474
+ const icon = renderIcon(
475
+ child.link?.icon,
476
+ 'size-4',
477
+ child.link?.customIcon
478
+ )
479
+ const iconPosition = child.link?.iconPosition || 'before'
480
+ return (
481
+ <li key={childIndex}>
482
+ <NavigationMenuLink asChild>
483
+ <Link
484
+ href={getLinkHref(child.link)}
485
+ className={cn(
486
+ 'flex items-center gap-2 select-none no-underline outline-none transition-colors rounded-sm group bg-8 hover:bg-zinc-600/50 !hover:text-stone-200 focus:bg-accent focus:text-stone-200 py-2 px-3'
487
+ )}
488
+ >
489
+ {' '}
490
+ {icon && iconPosition === 'before' && icon}
491
+ <span className="!text-xs group font-medium leading-none scale-75 !hover:text-stone-200 ">
492
+ {child.link?.label}
493
+ </span>
494
+ {icon && iconPosition === 'after' && icon}
495
+ </Link>
496
+ </NavigationMenuLink>
497
+ </li>
498
+ )
499
+ })}
500
+ </motion.ul>
501
+ </NavigationMenuContent>
502
+ </NavigationMenuItem>
503
+ )
504
+ }
505
+ return null
506
+ })}
507
+ </NavigationMenuList>
508
+ </NavigationMenu>
509
+ </div>
510
+ </motion.nav>
511
+ </header>
512
+
513
+ {/* Mobile Bottom Navigation - Visible only on mobile */}
514
+ <MobileBottomNav data={data} />
515
+ </>
516
+ )
517
+ }
@@ -0,0 +1,123 @@
1
+ {
2
+ "type": "component",
3
+ "name": "HeaderDD02",
4
+ "label": "Header DD 02",
5
+ "sourceName": "Header02",
6
+ "portableName": "HeaderDD02",
7
+ "slug": "HeaderDD02",
8
+ "version": "0.0.1",
9
+ "source": {
10
+ "repo": "Hadizainal/DesignsDustedv3",
11
+ "originalPath": "src/Header/variants/Header02",
12
+ "originalConfigExport": "header02Fields",
13
+ "originalComponentExport": "Header02",
14
+ "removedLexicalBlockImports": [
15
+ "@/blocks/AnimatedText/config",
16
+ "@/blocks/ButtonWithIcon/config",
17
+ "@/blocks/CounterBlock/config",
18
+ "@/blocks/MediaBlock/config"
19
+ ],
20
+ "excludedHeaderFiles": [
21
+ "src/Header/Component.tsx",
22
+ "src/Header/Component.client.tsx",
23
+ "src/Header/config.ts",
24
+ "src/Header/Nav/index.tsx",
25
+ "src/Header/hooks/revalidateHeader.ts"
26
+ ]
27
+ },
28
+ "files": [
29
+ {
30
+ "from": "index.tsx",
31
+ "to": "src/Header/variants/HeaderDD02/index.tsx"
32
+ },
33
+ {
34
+ "from": "MobileBottomNav/index.tsx",
35
+ "to": "src/Header/variants/HeaderDD02/MobileBottomNav/index.tsx"
36
+ },
37
+ {
38
+ "from": "config.ts",
39
+ "to": "src/Header/variants/HeaderDD02/config.ts"
40
+ },
41
+ {
42
+ "from": "RowLabel.tsx",
43
+ "to": "src/Header/RowLabel.tsx"
44
+ },
45
+ {
46
+ "from": "README.md",
47
+ "to": "src/Header/variants/HeaderDD02/README.md"
48
+ }
49
+ ],
50
+ "dependencies": [
51
+ {
52
+ "name": "@payloadcms/richtext-lexical"
53
+ },
54
+ {
55
+ "name": "framer-motion"
56
+ },
57
+ {
58
+ "name": "lucide-react"
59
+ }
60
+ ],
61
+ "requiredProjectFiles": [
62
+ {
63
+ "path": "src/fields/defaultLexical.ts",
64
+ "reason": "HeaderDD02 richText fields use the target project's shared defaultLexical helper."
65
+ },
66
+ {
67
+ "path": "src/fields/link.ts",
68
+ "reason": "HeaderDD02 navigation fields reuse the target project's link field helper."
69
+ },
70
+ {
71
+ "path": "src/fields/lucide-icon-picker/field.ts",
72
+ "reason": "HeaderDD02 dropdown trigger icons reuse the shared icon picker field."
73
+ },
74
+ {
75
+ "path": "src/components/ui/navigation-menu.tsx",
76
+ "reason": "HeaderDD02 desktop navigation renders the target project's NavigationMenu primitives."
77
+ },
78
+ {
79
+ "path": "src/components/RichText/index.tsx",
80
+ "reason": "HeaderDD02 renders richTextContent and floatingContent through the target project's RichText renderer."
81
+ },
82
+ {
83
+ "path": "src/utilities/ui.ts",
84
+ "reason": "HeaderDD02 uses cn from @/utilities/ui."
85
+ },
86
+ {
87
+ "path": "src/components/animate-ui/components/animate/tooltip.tsx",
88
+ "reason": "HeaderDD02 MobileBottomNav uses the target tooltip primitives."
89
+ }
90
+ ],
91
+ "collectionRegistration": {
92
+ "enabled": false
93
+ },
94
+ "renderBlocksRegistration": {
95
+ "enabled": false
96
+ },
97
+ "manual": [
98
+ "This header uses the shared `defaultLexical` editor standard from `@/fields/defaultLexical`. Project-specific Lexical blocks are intentionally not bundled. The nested `BlocksFeature` uses an empty `blocks` array so each target project can register only the blocks it requires.",
99
+ "Removed source Lexical block imports: `@/blocks/AnimatedText/config`, `@/blocks/ButtonWithIcon/config`, `@/blocks/CounterBlock/config`, `@/blocks/MediaBlock/config`.",
100
+ "HeaderDD02 installs the Header02 runtime component, MobileBottomNav, Header02 field config, and the RowLabel admin helper only. Header01 and Header03 are not bundled.",
101
+ "This component template does not yet patch an existing active Header global, replace Header/Component.tsx, or inspect database-stored Header content automatically. Review the existing Header global before activation and back up active files before wiring the variant into the target project.",
102
+ "Inspect any existing target Header richText data before replacing an active Header schema. Do not apply the empty nested blocks extension point to stored content that already depends on registered block node types.",
103
+ "Verify the target `src/fields/defaultLexical.ts` exports a compatible `defaultLexical` helper that accepts `defaultLexical({ features: [] })` and does not inject unrelated project-specific blocks.",
104
+ "Wire `HeaderDD02` into the target Header global or render it directly after confirming the target project will not render two headers."
105
+ ],
106
+ "manualSteps": [
107
+ "This header uses the shared `defaultLexical` editor standard from `@/fields/defaultLexical`. Project-specific Lexical blocks are intentionally not bundled. The nested `BlocksFeature` uses an empty `blocks` array so each target project can register only the blocks it requires.",
108
+ "Removed source Lexical block imports: `@/blocks/AnimatedText/config`, `@/blocks/ButtonWithIcon/config`, `@/blocks/CounterBlock/config`, `@/blocks/MediaBlock/config`.",
109
+ "HeaderDD02 installs the Header02 runtime component, MobileBottomNav, Header02 field config, and the RowLabel admin helper only. Header01 and Header03 are not bundled.",
110
+ "This component template does not yet patch an existing active Header global, replace Header/Component.tsx, or inspect database-stored Header content automatically. Review the existing Header global before activation and back up active files before wiring the variant into the target project.",
111
+ "Inspect any existing target Header richText data before replacing an active Header schema. Do not apply the empty nested blocks extension point to stored content that already depends on registered block node types.",
112
+ "Verify the target `src/fields/defaultLexical.ts` exports a compatible `defaultLexical` helper that accepts `defaultLexical({ features: [] })` and does not inject unrelated project-specific blocks.",
113
+ "Wire `HeaderDD02` into the target Header global or render it directly after confirming the target project will not render two headers."
114
+ ],
115
+ "notes": [
116
+ "Converted from DesignsDustedv3 Header02 after tracing Header/variants/Header02/index.tsx and its direct imports.",
117
+ "Header02 styling is inline Tailwind class usage plus style jsx global blocks in index.tsx and MobileBottomNav/index.tsx; no separate Header02 stylesheet was imported.",
118
+ "Parent Header files were audited but excluded because the reusable export targets Header02 only, not the multi-variant app header shell."
119
+ ],
120
+ "backupExistingDestination": true,
121
+ "exportName": "HeaderDD02",
122
+ "interfaceName": "HeaderDD02"
123
+ }