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
package/package.json
CHANGED
|
@@ -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
|
+
}
|