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.
Files changed (148) hide show
  1. package/package.json +1 -1
  2. package/src/installers/patchRenderBlocks.js +1 -1
  3. package/src/registry/validateTemplateManifest.js +2 -1
  4. package/templates/blocks/BannerDD790432/Component.tsx +63 -0
  5. package/templates/blocks/BannerDD790432/config.ts +54 -0
  6. package/templates/blocks/BannerDD790432/manifest.json +80 -0
  7. package/templates/blocks/Button47/Component.tsx +124 -0
  8. package/templates/blocks/Button47/config.ts +75 -0
  9. package/templates/blocks/Button47/link.ts +22 -0
  10. package/templates/blocks/Button47/manifest.json +105 -0
  11. package/templates/blocks/Button47/styles.module.scss +75 -0
  12. package/templates/blocks/DD-Accordion/Component.tsx +55 -36
  13. package/templates/blocks/DD-Accordion/config.ts +71 -59
  14. package/templates/blocks/DD-AnimText/Component.tsx +50 -32
  15. package/templates/blocks/DD-AnimText/config.ts +296 -295
  16. package/templates/blocks/DD-AsideNav/Component.tsx +323 -319
  17. package/templates/blocks/DD-Banner/Component.tsx +54 -51
  18. package/templates/blocks/DD-BookCall/Component.tsx +535 -535
  19. package/templates/blocks/DD-ButtonPop/Component.tsx +118 -117
  20. package/templates/blocks/DD-Card/Component.tsx +4 -4
  21. package/templates/blocks/DD-CardTemplate01/Component.tsx +139 -140
  22. package/templates/blocks/DD-CardsWithVariantV3/Component.tsx +472 -0
  23. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/index.tsx +203 -0
  24. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/styles.ts +87 -0
  25. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/useInjectStyle.ts +39 -0
  26. package/templates/blocks/DD-CardsWithVariantV3/config.ts +326 -0
  27. package/templates/blocks/DD-CardsWithVariantV3/manifest.json +110 -0
  28. package/templates/blocks/DD-Carousel-A/Component.tsx +251 -249
  29. package/templates/blocks/DD-Carousel-B/Component.tsx +405 -403
  30. package/templates/blocks/DD-ComparisonTable/Component.tsx +255 -256
  31. package/templates/blocks/DD-Contact/Component.tsx +433 -435
  32. package/templates/blocks/DD-Counter/Component.tsx +11 -6
  33. package/templates/blocks/DD-DeliCard/Component.tsx +303 -0
  34. package/templates/blocks/DD-DeliCard/config.ts +186 -0
  35. package/templates/blocks/DD-DeliCard/manifest.json +94 -0
  36. package/templates/blocks/DD-FeatCat/Component.tsx +302 -302
  37. package/templates/blocks/DD-FeatProd/CircularGallery.tsx +1179 -0
  38. package/templates/blocks/DD-FeatProd/Component.tsx +484 -0
  39. package/templates/blocks/DD-FeatProd/config.ts +173 -0
  40. package/templates/blocks/DD-FeatProd/manifest.json +93 -0
  41. package/templates/blocks/DD-FeatServ/Component.tsx +607 -0
  42. package/templates/blocks/DD-FeatServ/config.ts +213 -0
  43. package/templates/blocks/DD-FeatServ/manifest.json +113 -0
  44. package/templates/blocks/DD-FeatStrip/Component.tsx +584 -585
  45. package/templates/blocks/DD-FluidBtn/ButtonsEyeCatching.tsx +627 -628
  46. package/templates/blocks/DD-FluidBtn/Component.tsx +46 -43
  47. package/templates/blocks/DD-GooeyInfo/Component.tsx +150 -149
  48. package/templates/blocks/DD-GooeyInfo/config.ts +136 -135
  49. package/templates/blocks/DD-Hero/Component.tsx +296 -297
  50. package/templates/blocks/DD-HeroPlas/Component.tsx +50 -0
  51. package/templates/blocks/DD-HeroPlas/ThreeScene.tsx +109 -0
  52. package/templates/blocks/DD-HeroPlas/config.ts +33 -0
  53. package/templates/blocks/DD-HeroPlas/manifest.json +88 -0
  54. package/templates/blocks/DD-HorizontalScroll/Component.tsx +937 -936
  55. package/templates/blocks/DD-Icon/Component.tsx +46 -81
  56. package/templates/blocks/DD-IconButton/Component.tsx +13 -20
  57. package/templates/blocks/DD-IdTag/Component.tsx +22 -21
  58. package/templates/blocks/DD-LineAnim/Component.tsx +42 -36
  59. package/templates/blocks/DD-LogoMarq/Component.tsx +38 -48
  60. package/templates/blocks/DD-Lottie/Component.tsx +11 -3
  61. package/templates/blocks/DD-MasonaryMedia/Component.tsx +544 -545
  62. package/templates/blocks/DD-MasonaryMedia/config.ts +389 -389
  63. package/templates/blocks/DD-Masonry/Component.tsx +654 -644
  64. package/templates/blocks/DD-Masonry/config.ts +475 -475
  65. package/templates/blocks/DD-MaterialSwatches/Component.tsx +85 -0
  66. package/templates/blocks/DD-MaterialSwatches/config.ts +232 -0
  67. package/templates/blocks/DD-MaterialSwatches/manifest.json +84 -0
  68. package/templates/blocks/DD-MaterialSwatches/material.ts +107 -0
  69. package/templates/blocks/DD-NineCanvas/Component.tsx +129 -0
  70. package/templates/blocks/DD-NineCanvas/RenderBlocks.tsx +244 -0
  71. package/templates/blocks/DD-NineCanvas/allowedCanvasBlocks.ts +6 -0
  72. package/templates/blocks/DD-NineCanvas/blockComponents.tsx +8 -0
  73. package/templates/blocks/DD-NineCanvas/canvas/CanvasGrid.tsx +316 -0
  74. package/templates/blocks/DD-NineCanvas/canvas/RadialNav.tsx +432 -0
  75. package/templates/blocks/DD-NineCanvas/canvas/canvas-panels.css +889 -0
  76. package/templates/blocks/DD-NineCanvas/canvas/icons/custom-icons.tsx +364 -0
  77. package/templates/blocks/DD-NineCanvas/config.ts +61 -0
  78. package/templates/blocks/DD-NineCanvas/manifest.json +165 -0
  79. package/templates/blocks/DD-NineCanvas/styles.scss +152 -0
  80. package/templates/blocks/DD-NineCanvas/tooltip/get-strict-context.tsx +26 -0
  81. package/templates/blocks/DD-NineCanvas/tooltip/primitives.tsx +178 -0
  82. package/templates/blocks/DD-NineCanvas/tooltip/tooltip.tsx +71 -0
  83. package/templates/blocks/DD-NineCanvas/tooltip/use-controlled-state.tsx +33 -0
  84. package/templates/blocks/DD-NineCanvas/utilities/FadeOnScroll.tsx +68 -0
  85. package/templates/blocks/DD-NineCanvas/utilities/ParallaxWrapper.tsx +157 -0
  86. package/templates/blocks/DD-NineCanvas/utilities/renderCustomCSS.ts +5 -0
  87. package/templates/blocks/DD-NineCanvas/utilities/webglCleanup.ts +27 -0
  88. package/templates/blocks/DD-PayloadAIAssistant/Component.tsx +78 -0
  89. package/templates/blocks/DD-PayloadAIAssistant/DustedBot.tsx +590 -0
  90. package/templates/blocks/DD-PayloadAIAssistant/config.ts +37 -0
  91. package/templates/blocks/DD-PayloadAIAssistant/demo.css +125 -0
  92. package/templates/blocks/DD-PayloadAIAssistant/manifest.json +93 -0
  93. package/templates/blocks/DD-PayloadAIAssistant/source-defaults.ts +17 -0
  94. package/templates/blocks/DD-PayloadAIAssistant/styles.ts +306 -0
  95. package/templates/blocks/DD-Pricing/Component.tsx +2 -3
  96. package/templates/blocks/DD-Pricing/config.ts +215 -215
  97. package/templates/blocks/DD-Process/Component.tsx +2 -2
  98. package/templates/blocks/DD-ProjHeader/Component.tsx +93 -91
  99. package/templates/blocks/DD-ProjSlider/Component.tsx +1 -1
  100. package/templates/blocks/DD-Section/Component.tsx +265 -266
  101. package/templates/blocks/DD-Separator/Component.tsx +66 -66
  102. package/templates/blocks/DD-Services/Component.tsx +176 -176
  103. package/templates/blocks/DD-ShowcaseGrid/Component.tsx +306 -307
  104. package/templates/blocks/DD-Slider-A/Component.tsx +2 -2
  105. package/templates/blocks/DD-StackCards/Component.tsx +172 -171
  106. package/templates/blocks/DD-Team/Component.tsx +247 -247
  107. package/templates/blocks/DD-TeamCards/Component.tsx +251 -251
  108. package/templates/blocks/DD-TechStack/Component.tsx +173 -174
  109. package/templates/blocks/DD-TestMarq/Component.tsx +158 -132
  110. package/templates/blocks/DD-Testim/Component.tsx +247 -247
  111. package/templates/blocks/DD-Testim2/Component.tsx +265 -256
  112. package/templates/blocks/DD-Testimonials/Component.tsx +139 -139
  113. package/templates/blocks/DD-Thanks/Component.tsx +1 -1
  114. package/templates/blocks/DD-Work/Component.tsx +318 -318
  115. package/templates/blocks/DD-Work-B/Component.tsx +294 -294
  116. package/templates/blocks/DDHG-CTASection2/Component.tsx +183 -184
  117. package/templates/blocks/DDHG-HighlightArc/Component.tsx +6 -3
  118. package/templates/blocks/DDHG-LogoMarquee/Component.tsx +45 -8
  119. package/templates/blocks/DDHG-NavCodeGrid/Component.tsx +1 -3
  120. package/templates/blocks/DDHG-ServiceDetails/ServiceDetailCard.tsx +9 -22
  121. package/templates/blocks/DDHG-ServiceHero/Component.tsx +10 -10
  122. package/templates/blocks/DDHG-TeamCards/Component.tsx +6 -35
  123. package/templates/blocks/Form/Checkbox/index.tsx +45 -0
  124. package/templates/blocks/Form/Component.tsx +163 -0
  125. package/templates/blocks/Form/Country/index.tsx +65 -0
  126. package/templates/blocks/Form/Country/options.ts +982 -0
  127. package/templates/blocks/Form/Email/index.tsx +38 -0
  128. package/templates/blocks/Form/Error/index.tsx +15 -0
  129. package/templates/blocks/Form/Message/index.tsx +13 -0
  130. package/templates/blocks/Form/Number/index.tsx +36 -0
  131. package/templates/blocks/Form/Select/index.tsx +63 -0
  132. package/templates/blocks/Form/State/index.tsx +64 -0
  133. package/templates/blocks/Form/State/options.ts +52 -0
  134. package/templates/blocks/Form/Text/index.tsx +32 -0
  135. package/templates/blocks/Form/Textarea/index.tsx +40 -0
  136. package/templates/blocks/Form/Width/index.tsx +13 -0
  137. package/templates/blocks/Form/config.ts +51 -0
  138. package/templates/blocks/Form/fields.tsx +21 -0
  139. package/templates/blocks/Form/manifest.json +168 -0
  140. package/templates/blocks/MediaBlock/Component.tsx +67 -0
  141. package/templates/blocks/MediaBlock/config.ts +14 -0
  142. package/templates/blocks/MediaBlock/manifest.json +92 -0
  143. package/templates/blocks/TransitionsDustedShowcase/Component.tsx +9 -0
  144. package/templates/blocks/TransitionsDustedShowcase/config.ts +13 -0
  145. package/templates/blocks/TransitionsDustedShowcase/manifest.json +80 -0
  146. package/templates/blocks/rawHtml/Component.tsx +25 -0
  147. package/templates/blocks/rawHtml/config.ts +56 -0
  148. package/templates/blocks/rawHtml/manifest.json +80 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blocks-dusted",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "Manifest-driven CLI for installing reusable Payload CMS blocks.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -46,7 +46,7 @@ async function exists(path) {
46
46
  }
47
47
 
48
48
  function patchRenderBlocksContent(content, manifest) {
49
- const exportName = manifest.exportName ?? manifest.name
49
+ const exportName = manifest.componentExportName ?? manifest.exportName ?? manifest.name
50
50
  if (content.includes(manifest.slug) || content.includes(`@/blocks/${manifest.name}/Component`)) {
51
51
  return { changed: false, reason: 'RenderBlocks already appears to contain this block' }
52
52
  }
@@ -26,6 +26,7 @@ export function validateTemplateManifest(manifest) {
26
26
  if (manifest.sourceName !== undefined && typeof manifest.sourceName !== 'string') errors.push('sourceName must be a string when present')
27
27
  if (manifest.portableName !== undefined && typeof manifest.portableName !== 'string') errors.push('portableName must be a string when present')
28
28
  if (manifest.exportName !== undefined && typeof manifest.exportName !== 'string') errors.push('exportName must be a string when present')
29
+ if (manifest.componentExportName !== undefined && typeof manifest.componentExportName !== 'string') errors.push('componentExportName must be a string when present')
29
30
  if (manifest.interfaceName !== undefined && typeof manifest.interfaceName !== 'string') errors.push('interfaceName must be a string when present')
30
31
 
31
32
  for (const key of OPTIONAL_ARRAYS) {
@@ -67,4 +68,4 @@ export function validateTemplateManifest(manifest) {
67
68
 
68
69
  if (errors.length > 0) throw new Error(`Invalid template manifest:\n- ${errors.join('\n- ')}`)
69
70
  return manifest
70
- }
71
+ }
@@ -0,0 +1,63 @@
1
+ import { cn } from '@/utilities/ui'
2
+ import React, { useId } from 'react'
3
+ // import RichText from '@/components/RichText'
4
+
5
+ const BANNER_BEFORE_CLS =
6
+ 'relative z-0 ' +
7
+ "before:content-[''] before:absolute before:inset-0 before:z-[-1] " +
8
+ 'before:bg-lines before:bg-[length:10px_10px] ' +
9
+ 'before:bg-repeat before:opacity-100 before:pointer-events-none'
10
+
11
+ export type BannerDD790432Props = {
12
+ className?: string
13
+ wrapperClassName?: string
14
+ content?: unknown
15
+ style?: 'info' | 'warning' | 'error' | 'success' | null
16
+ }
17
+
18
+ export const BannerDD790432: React.FC<BannerDD790432Props> = ({
19
+ className,
20
+ wrapperClassName,
21
+ content,
22
+ style,
23
+ }) => {
24
+ const reactId = useId()
25
+ const richtextKey = `banner-richtext-${reactId.replace(/:/g, '')}`
26
+
27
+ return (
28
+ <div className={cn('mx-auto my-8 w-full banner_container', className)}>
29
+ <div
30
+ className={cn(
31
+ wrapperClassName,
32
+ 'border py-3 pl-0 pr-3 flex items-center rounded shadow-2xl banner_content',
33
+ BANNER_BEFORE_CLS,
34
+ {
35
+ 'banner-info border-border bg-card text-(--black) [box-shadow:inset_0_0_2px_0px_var(--border),var(--shadow-xl)] [&_blockquote]:border-s-border!':
36
+ style === 'info',
37
+ 'banner-error border-(--error-border) bg-error text-(--white) [box-shadow:inset_0_0_2px_0px_var(--error),var(--shadow-xl)] [&_blockquote]:border-s-(--error-border)!':
38
+ style === 'error',
39
+ 'banner-success border-(--success-border) bg-success text-(--white) [box-shadow:inset_0_0_2px_0px_var(--success),var(--shadow-xl)] [&_blockquote]:border-s-(--success-border)!':
40
+ style === 'success',
41
+ 'banner-warning border-(--warning-border) bg-warning text-(--white) [box-shadow:inset_0_0_2px_0px_var(--warning),var(--shadow-xl)] [&_blockquote]:border-s-(--warning-border)!':
42
+ style === 'warning',
43
+ },
44
+ )}
45
+ >
46
+ {/* TODO: Reconnect this project's RichText renderer for `content`. */}
47
+ {content ? (
48
+ // Replace when RichText is configured.
49
+ <div className="banner_richtext">Rich text content is configured for this banner.</div>
50
+ ) : // Adjust RichText props if required.
51
+ // <RichText
52
+ // data={content}
53
+ // data-richtext-key={richtextKey}
54
+ // className="banner_richtext"
55
+ // enableGutter={false}
56
+ // enableProse={true}
57
+ // />
58
+ null}
59
+ </div>
60
+ </div>
61
+ )
62
+ }
63
+
@@ -0,0 +1,54 @@
1
+ import type { Block } from 'payload'
2
+
3
+ export const BannerDD790432: Block = {
4
+ slug: 'banner-dd-790432',
5
+ interfaceName: 'BannerDD790432',
6
+ fields: [
7
+ {
8
+ name: 'style',
9
+ type: 'select',
10
+ defaultValue: 'info',
11
+ options: [
12
+ { label: 'Info', value: 'info' },
13
+ { label: 'Warning', value: 'warning' },
14
+ { label: 'Error', value: 'error' },
15
+ { label: 'Success', value: 'success' },
16
+ ],
17
+ required: true,
18
+ },
19
+ {
20
+ name: 'content',
21
+ type: 'richText',
22
+ label: false,
23
+ required: true,
24
+ admin: {
25
+ description:
26
+ 'TODO: Add this project\'s RichText renderer and custom Lexical features manually after installation.',
27
+ },
28
+ },
29
+ {
30
+ name: 'useCustomClasses',
31
+ type: 'checkbox',
32
+ label: 'Override default classes',
33
+ defaultValue: false,
34
+ },
35
+ {
36
+ name: 'className',
37
+ type: 'text',
38
+ label: 'Banner container class',
39
+ admin: {
40
+ description: 'banner_container class',
41
+ condition: (_, siblingData) => Boolean(siblingData.useCustomClasses),
42
+ },
43
+ },
44
+ {
45
+ name: 'wrapperClassName',
46
+ type: 'text',
47
+ label: 'Banner content class',
48
+ admin: {
49
+ description: 'banner_content class',
50
+ condition: (_, siblingData) => Boolean(siblingData.useCustomClasses),
51
+ },
52
+ },
53
+ ],
54
+ }
@@ -0,0 +1,80 @@
1
+ {
2
+ "type": "block",
3
+ "name": "BannerDD790432",
4
+ "label": "Banner DD 790432",
5
+ "sourceName": "BannerDD790432",
6
+ "portableName": "BannerDD790432",
7
+ "slug": "banner-dd-790432",
8
+ "version": "0.0.1",
9
+ "source": {
10
+ "repo": "Hadizainal/Dusted-Payload-Blocks",
11
+ "originalPath": "CLI-TestGround/dusted-blocks-demo/src/blocks/BannerDD790432",
12
+ "originalSlug": "banner-dd-790432",
13
+ "originalConfigExport": "BannerDD790432",
14
+ "originalComponentExport": "BannerDD790432",
15
+ "originalInterfaceName": "BannerDD790432"
16
+ },
17
+ "files": [
18
+ {
19
+ "from": "Component.tsx",
20
+ "to": "src/blocks/BannerDD790432/Component.tsx"
21
+ },
22
+ {
23
+ "from": "config.ts",
24
+ "to": "src/blocks/BannerDD790432/config.ts"
25
+ }
26
+ ],
27
+ "dependencies": [],
28
+ "sharedFiles": [],
29
+ "requiredProjectFiles": [
30
+ {
31
+ "path": "src/utilities/ui.ts",
32
+ "reason": "Component imports cn from @/utilities/ui."
33
+ }
34
+ ],
35
+ "optionalProjectFiles": [],
36
+ "collectionRegistration": {
37
+ "enabled": true,
38
+ "collections": [
39
+ "Pages",
40
+ "Posts"
41
+ ]
42
+ },
43
+ "renderBlocksRegistration": {
44
+ "enabled": true
45
+ },
46
+ "renderBlocksBehavior": {
47
+ "wrapperless": false
48
+ },
49
+ "pageRouteIntegration": {
50
+ "required": false,
51
+ "manualSteps": []
52
+ },
53
+ "manual": [
54
+ "Register BannerDD790432 in Pages/Posts block arrays if automatic registration was skipped.",
55
+ "Register banner-dd-790432 in RenderBlocks if automatic registration was skipped.",
56
+ "Run payload generate:types after schema changes.",
57
+ "Run payload generate:importmap if admin/component imports changed."
58
+ ],
59
+ "manualSteps": [
60
+ "Register BannerDD790432 in Pages/Posts block arrays if automatic registration was skipped.",
61
+ "Register banner-dd-790432 in RenderBlocks if automatic registration was skipped.",
62
+ "Run payload generate:types after schema changes.",
63
+ "Run payload generate:importmap if admin/component imports changed."
64
+ ],
65
+ "patchTargets": {
66
+ "blockConfigRegistration": {
67
+ "description": "Installer can register BannerDD790432 from @/blocks/BannerDD790432/config."
68
+ },
69
+ "renderBlocksRegistration": {
70
+ "description": "Installer can register banner-dd-790432 with component export BannerDD790432."
71
+ }
72
+ },
73
+ "notes": [
74
+ "Promoted from the verified Dusted Blocks demo implementation.",
75
+ "Installer preserves existing destination files and reports project requirements before writing."
76
+ ],
77
+ "exportName": "BannerDD790432",
78
+ "componentExportName": "BannerDD790432",
79
+ "interfaceName": "BannerDD790432"
80
+ }
@@ -0,0 +1,124 @@
1
+ 'use client'
2
+
3
+ import Button from '@mui/material/Button'
4
+ import { icons } from 'lucide-react'
5
+ import { motion, useReducedMotion } from 'motion/react'
6
+ import Link from 'next/link'
7
+ import type { Button47Block } from '@/payload-types'
8
+ import { cn } from '@/utilities/ui'
9
+ import { resolveButton47Href } from './link'
10
+ import styles from './styles.module.scss'
11
+
12
+ const colors = {
13
+ contained: {
14
+ background: '#4338ca',
15
+ hover: '#3730a3',
16
+ foreground: '#d9f99d',
17
+ border: '#4338ca',
18
+ frame: 'var(--button47-frame)',
19
+ },
20
+ outlined: {
21
+ background: '#a3e635',
22
+ hover: '#bef264',
23
+ foreground: '#1e1b4b',
24
+ border: '#3f6212',
25
+ frame: 'var(--button47-frame)',
26
+ },
27
+ text: {
28
+ background: 'var(--button47-ghost-bg)',
29
+ hover: 'var(--button47-ghost-hover)',
30
+ foreground: 'var(--button47-ghost-text)',
31
+ border: 'var(--button47-frame)',
32
+ frame: 'transparent',
33
+ },
34
+ }
35
+ export function Button47({ buttons, position = 'center', customClassName }: Button47Block) {
36
+ const reduceMotion = useReducedMotion()
37
+ if (!buttons?.length) return null
38
+
39
+ return (
40
+ <div
41
+ className={cn(styles.root, customClassName)}
42
+ style={{
43
+ justifyContent:
44
+ position === 'left' ? 'flex-start' : position === 'right' ? 'flex-end' : 'center',
45
+ }}
46
+ >
47
+ {buttons.map((button, index) => {
48
+ const href = resolveButton47Href(button.link)
49
+ if (!href || !button.link.label?.trim()) return null
50
+ const variant = button.variant || 'contained'
51
+ const color = colors[variant]
52
+
53
+ const Icon = button.icon ? icons[button.icon as keyof typeof icons] : undefined
54
+ const icon = Icon ? <Icon size={18} aria-hidden="true" focusable="false" /> : undefined
55
+
56
+ return (
57
+ <motion.div
58
+ key={button.id || index}
59
+ className={styles.focusFrame}
60
+ initial={reduceMotion ? false : { opacity: 0, y: 16 }}
61
+ whileInView={{ opacity: 1, y: 0 }}
62
+ transition={{ duration: reduceMotion ? 0 : 0.3, delay: reduceMotion ? 0 : index * 0.1 }}
63
+ viewport={{ once: false, amount: 0.2 }}
64
+ >
65
+ <div
66
+ className={styles.bevel}
67
+ style={{ backgroundColor: '#ffffffe3', borderColor: '#cfcfcfa8' }}
68
+ >
69
+ <Button
70
+ component={Link}
71
+ href={href}
72
+ variant={variant}
73
+ target={button.link.newTab ? '_blank' : undefined}
74
+ rel={button.link.newTab ? 'noopener noreferrer' : undefined}
75
+ startIcon={button.iconPosition !== 'after' ? icon : undefined}
76
+ endIcon={button.iconPosition === 'after' ? icon : undefined}
77
+ className={cn(styles.button, 'group relative overflow-hidden')}
78
+ sx={{
79
+ minWidth: 160,
80
+ minHeight: 44,
81
+ maxWidth: '100%',
82
+ textTransform: 'none',
83
+ fontSize: 15,
84
+ fontFamily: 'inherit',
85
+ padding: '8px 16px',
86
+ border: '0px',
87
+ borderRadius: '4px',
88
+ lineHeight: 1.4,
89
+ color: color.foreground,
90
+ backgroundColor: color.background,
91
+ borderColor: color.border,
92
+ boxShadow: 'none',
93
+ '&:hover': {
94
+ backgroundColor: color.hover,
95
+ border: '0px',
96
+ borderColor: color.border,
97
+ boxShadow: '0 25px 50px -12px rgb(0 0 0 / 0.25)',
98
+ },
99
+ '&:active': { backgroundColor: color.background, border: '0px' },
100
+ }}
101
+ >
102
+ <motion.span
103
+ initial={reduceMotion ? false : { opacity: 0, y: -8 }}
104
+ whileInView={{ opacity: 1, y: 0 }}
105
+ transition={{ duration: reduceMotion ? 0 : 0.3 }}
106
+ viewport={{ once: false, amount: 0.2 }}
107
+ >
108
+ {button.link.label}
109
+ </motion.span>
110
+ <span
111
+ aria-hidden="true"
112
+ className="pointer-events-none absolute inset-0 flex size-full justify-center transition-transform [transform:skewX(-45deg)_translateX(-100%)] group-hover:duration-1000 group-hover:[transform:skewX(-45deg)_translateX(100%)] motion-reduce:hidden"
113
+ style={{ width: '200%' }}
114
+ >
115
+ <span className="relative h-full w-1/3 max-w-64 scale-200 bg-gradient-to-r from-transparent via-white/15 to-transparent" />
116
+ </span>
117
+ </Button>
118
+ </div>
119
+ </motion.div>
120
+ )
121
+ })}
122
+ </div>
123
+ )
124
+ }
@@ -0,0 +1,75 @@
1
+ import type { Block, Field, TextFieldSingleValidation } from 'payload'
2
+ import { link } from '@/fields/link'
3
+ import { iconPicker } from '@/fields/lucide-icon-picker/field'
4
+ import { isSupportedButtonURL } from './link'
5
+
6
+ const validateURL: TextFieldSingleValidation = (value, { siblingData }) =>
7
+ (siblingData as { type?: string })?.type !== 'custom' ||
8
+ isSupportedButtonURL(value) ||
9
+ 'Enter an https:// or http:// URL, /path, #anchor, mailto: or tel: link.'
10
+
11
+ // Apply URL validation only to this block's copy of the shared link field.
12
+ const validateCustomURL = (field: Field): Field => {
13
+ if ('name' in field && field.name === 'url' && field.type === 'text' && !field.hasMany) {
14
+ return {
15
+ ...field,
16
+ validate: validateURL,
17
+ }
18
+ }
19
+ if ('fields' in field) return { ...field, fields: field.fields.map(validateCustomURL) }
20
+ return field
21
+ }
22
+
23
+ export const Button47: Block = {
24
+ slug: 'button47',
25
+ dbName: 'button47',
26
+ interfaceName: 'Button47Block',
27
+ labels: { singular: 'Button47', plural: 'Button47 blocks' },
28
+ fields: [
29
+ {
30
+ name: 'position',
31
+ label: 'Button Alignment',
32
+ type: 'select',
33
+ required: true,
34
+ defaultValue: 'center',
35
+ options: [
36
+ { label: 'Left', value: 'left' },
37
+ { label: 'Center', value: 'center' },
38
+ { label: 'Right', value: 'right' },
39
+ ],
40
+ },
41
+ { name: 'customClassName', type: 'text', label: 'Wrapper CSS Class' },
42
+ {
43
+ name: 'buttons',
44
+ type: 'array',
45
+ required: true,
46
+ minRows: 1,
47
+ fields: [
48
+ validateCustomURL(link({ appearances: false })),
49
+ {
50
+ name: 'variant',
51
+ label: 'Button Style',
52
+ type: 'select',
53
+ required: true,
54
+ defaultValue: 'contained',
55
+ options: [
56
+ { label: 'Primary', value: 'contained' },
57
+ { label: 'Secondary', value: 'outlined' },
58
+ { label: 'Ghost', value: 'text' },
59
+ ],
60
+ },
61
+ iconPicker({ name: 'icon', description: 'Optional icon beside the button label.' }),
62
+ {
63
+ name: 'iconPosition',
64
+ type: 'select',
65
+ defaultValue: 'before',
66
+ options: [
67
+ { label: 'Before Label', value: 'before' },
68
+ { label: 'After Label', value: 'after' },
69
+ ],
70
+ admin: { condition: (_, siblingData) => Boolean(siblingData?.icon) },
71
+ },
72
+ ],
73
+ },
74
+ ],
75
+ }
@@ -0,0 +1,22 @@
1
+ import type { Button47Block } from '@/payload-types'
2
+
3
+ export const isSupportedButtonURL = (value: unknown): value is string => {
4
+ if (typeof value !== 'string' || !value.trim()) return false
5
+ const url = value.trim()
6
+ if (/[\u0000-\u0020\u007f\\]/.test(url)) return false
7
+ if (url.startsWith('#') || (url.startsWith('/') && !url.startsWith('//'))) return true
8
+ try {
9
+ return ['https:', 'http:', 'mailto:', 'tel:'].includes(new URL(url).protocol)
10
+ } catch {
11
+ return false
12
+ }
13
+ }
14
+
15
+ export const resolveButton47Href = (link: Button47Block['buttons'][number]['link']) => {
16
+ if (link.type === 'reference') {
17
+ const page = link.reference?.value
18
+ if (!page || typeof page !== 'object' || !page.slug) return null
19
+ return page.slug === 'home' ? '/' : `/${page.slug}`
20
+ }
21
+ return link.type === 'custom' && isSupportedButtonURL(link.url) ? link.url.trim() : null
22
+ }
@@ -0,0 +1,105 @@
1
+ {
2
+ "type": "block",
3
+ "name": "Button47",
4
+ "label": "Button 47",
5
+ "sourceName": "Button47",
6
+ "portableName": "Button47",
7
+ "slug": "button47",
8
+ "version": "0.0.1",
9
+ "source": {
10
+ "repo": "Hadizainal/Dusted-Payload-Blocks",
11
+ "originalPath": "CLI-TestGround/dusted-blocks-demo/src/blocks/Button47",
12
+ "originalSlug": "button47",
13
+ "originalConfigExport": "Button47",
14
+ "originalComponentExport": "Button47",
15
+ "originalInterfaceName": "Button47Block"
16
+ },
17
+ "files": [
18
+ {
19
+ "from": "Component.tsx",
20
+ "to": "src/blocks/Button47/Component.tsx"
21
+ },
22
+ {
23
+ "from": "config.ts",
24
+ "to": "src/blocks/Button47/config.ts"
25
+ },
26
+ {
27
+ "from": "link.ts",
28
+ "to": "src/blocks/Button47/link.ts"
29
+ },
30
+ {
31
+ "from": "styles.module.scss",
32
+ "to": "src/blocks/Button47/styles.module.scss"
33
+ }
34
+ ],
35
+ "dependencies": [
36
+ "@mui/material",
37
+ "lucide-react",
38
+ "motion",
39
+ "sass"
40
+ ],
41
+ "sharedFiles": [],
42
+ "requiredProjectFiles": [
43
+ {
44
+ "path": "src/payload-types.ts",
45
+ "reason": "Component and link resolver use generated Button47 types."
46
+ },
47
+ {
48
+ "path": "src/utilities/ui.ts",
49
+ "reason": "Component imports cn from @/utilities/ui."
50
+ },
51
+ {
52
+ "path": "src/fields/link.ts",
53
+ "reason": "Payload config uses the shared link field."
54
+ },
55
+ {
56
+ "path": "src/fields/lucide-icon-picker/field.ts",
57
+ "reason": "Payload config uses the Lucide icon picker field."
58
+ }
59
+ ],
60
+ "optionalProjectFiles": [],
61
+ "collectionRegistration": {
62
+ "enabled": true,
63
+ "collections": [
64
+ "Pages",
65
+ "Posts"
66
+ ]
67
+ },
68
+ "renderBlocksRegistration": {
69
+ "enabled": true
70
+ },
71
+ "renderBlocksBehavior": {
72
+ "wrapperless": false
73
+ },
74
+ "pageRouteIntegration": {
75
+ "required": false,
76
+ "manualSteps": []
77
+ },
78
+ "manual": [
79
+ "Register Button47 in Pages/Posts block arrays if automatic registration was skipped.",
80
+ "Register button47 in RenderBlocks if automatic registration was skipped.",
81
+ "Run payload generate:types after schema changes.",
82
+ "Run payload generate:importmap if admin/component imports changed."
83
+ ],
84
+ "manualSteps": [
85
+ "Register Button47 in Pages/Posts block arrays if automatic registration was skipped.",
86
+ "Register button47 in RenderBlocks if automatic registration was skipped.",
87
+ "Run payload generate:types after schema changes.",
88
+ "Run payload generate:importmap if admin/component imports changed."
89
+ ],
90
+ "patchTargets": {
91
+ "blockConfigRegistration": {
92
+ "description": "Installer can register Button47 from @/blocks/Button47/config."
93
+ },
94
+ "renderBlocksRegistration": {
95
+ "description": "Installer can register button47 with component export Button47."
96
+ }
97
+ },
98
+ "notes": [
99
+ "Promoted from the verified Dusted Blocks demo implementation.",
100
+ "Installer preserves existing destination files and reports project requirements before writing."
101
+ ],
102
+ "exportName": "Button47",
103
+ "componentExportName": "Button47",
104
+ "interfaceName": "Button47Block"
105
+ }
@@ -0,0 +1,75 @@
1
+ .root {
2
+ --button47-frame: #a3e635;
3
+ --button47-ghost-bg: #1e1b4b;
4
+ --button47-ghost-hover: #312e81;
5
+ --button47-ghost-text: #d9f99d;
6
+ --button47-focus: #a3e635;
7
+ display: flex;
8
+ flex-wrap: wrap;
9
+ gap: 1rem;
10
+ font-family: inherit;
11
+ }
12
+ :global([data-theme='light']) .root {
13
+ --button47-frame: #4338ca;
14
+ --button47-focus: #4338ca;
15
+ --button47-ghost-bg: #eef2ff;
16
+ --button47-ghost-hover: #e0e7ff;
17
+ --button47-ghost-text: #3730a3;
18
+ }
19
+
20
+ .focusFrame {
21
+ max-width: 100%;
22
+ min-width: 0;
23
+ border-radius: 4px;
24
+ corner-shape: squircle;
25
+ &:has(a:focus-visible) {
26
+ outline: 3px solid var(--button47-focus);
27
+ outline-offset: 4px;
28
+ }
29
+ }
30
+ .bevel,
31
+ .button {
32
+ clip-path: polygon(
33
+ 0 0,
34
+ calc(100% - 16px) 0,
35
+ 100% 16px,
36
+ 100% 100%,
37
+ 16px 100%,
38
+ 0 calc(100% - 16px)
39
+ );
40
+ transition:
41
+ background-color 0.3s,
42
+ border-color 0.3s,
43
+ clip-path 0.3s,
44
+ box-shadow 0.3s;
45
+ }
46
+ .bevel {
47
+ padding: 0;
48
+ border: 1px outset;
49
+ border-radius: 4px;
50
+ corner-shape: squircle;
51
+ &:hover {
52
+ clip-path: polygon(
53
+ 0 0,
54
+ calc(100% - 14px) 0,
55
+ 100% 14px,
56
+ 100% 100%,
57
+ 14px 100%,
58
+ 0 calc(100% - 14px)
59
+ );
60
+ }
61
+ }
62
+ .button {
63
+ border-radius: 4px;
64
+ corner-shape: squircle;
65
+ overflow-wrap: anywhere;
66
+ text-decoration: none !important;
67
+ }
68
+ @media (prefers-reduced-motion: reduce) {
69
+ .root *,
70
+ .root *::before,
71
+ .root *::after {
72
+ transition: none !important;
73
+ animation: none !important;
74
+ }
75
+ }