blocks-dusted 0.1.11 → 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 +5 -2
- package/src/cli.js +39 -3
- package/src/commands/shop.js +59 -0
- package/src/commands/warehouse.js +73 -0
- 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 +10 -9
- 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 +5 -5
- 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-Chip/Component.tsx +14 -14
- package/templates/blocks/DD-ComparisonTable/Component.tsx +255 -256
- package/templates/blocks/DD-Contact/Component.tsx +433 -434
- package/templates/blocks/DD-Counter/Component.tsx +18 -13
- 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 +16 -40
- 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 +4 -5
- 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-IframeMedia/Component.tsx +14 -10
- 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 -547
- 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 +368 -372
- package/templates/blocks/DD-Pricing/config.ts +215 -215
- package/templates/blocks/DD-Process/Component.tsx +147 -149
- package/templates/blocks/DD-Progress/Component.tsx +156 -141
- package/templates/blocks/DD-ProjHeader/Component.tsx +93 -91
- package/templates/blocks/DD-ProjSlider/Component.tsx +1 -1
- package/templates/blocks/DD-ProjSlider/config.ts +2 -2
- 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 +237 -237
- package/templates/blocks/DD-StackCards/Component.tsx +474 -473
- 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 +161 -168
- 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 -147
- package/templates/blocks/DD-TextMarq/Component.tsx +157 -157
- package/templates/blocks/DD-Thanks/Component.tsx +501 -500
- package/templates/blocks/DD-Work/Component.tsx +315 -315
- 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 +246 -243
- package/templates/blocks/DDHG-LogoMarquee/Component.tsx +180 -93
- package/templates/blocks/DDHG-LogoMarquee/config.ts +154 -66
- package/templates/blocks/DDHG-NavCodeGrid/Component.tsx +225 -213
- package/templates/blocks/DDHG-ProcessSec/Component.tsx +38 -39
- package/templates/blocks/DDHG-ServiceDetails/Component.tsx +11 -11
- package/templates/blocks/DDHG-ServiceDetails/ServiceDetailCard.tsx +30 -47
- package/templates/blocks/DDHG-ServiceHero/Component.tsx +10 -10
- package/templates/blocks/DDHG-ServiceOverview/Component.tsx +23 -23
- package/templates/blocks/DDHG-StatsFAQ/Component.tsx +40 -40
- package/templates/blocks/DDHG-TeamCards/Component.tsx +140 -131
- 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,85 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { useEffect, useState } from 'react'
|
|
4
|
+
import { materialSources, previewDocument, type Swatch } from './material'
|
|
5
|
+
export type { Swatch, SwatchLayer } from './material'
|
|
6
|
+
export type MaterialSwatchesProps = { heading?: string; swatches: Swatch[] }
|
|
7
|
+
|
|
8
|
+
function CopyButton({ text, label }: { text: string; label: string }) {
|
|
9
|
+
const [status, setStatus] = useState('')
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (!status) return
|
|
12
|
+
const timer = setTimeout(() => setStatus(''), 2000)
|
|
13
|
+
return () => clearTimeout(timer)
|
|
14
|
+
}, [status])
|
|
15
|
+
return (
|
|
16
|
+
<button
|
|
17
|
+
type="button"
|
|
18
|
+
className="rounded border border-current/20 px-2 py-1 text-xs hover:bg-current/10"
|
|
19
|
+
onClick={async () => {
|
|
20
|
+
try {
|
|
21
|
+
await navigator.clipboard.writeText(text)
|
|
22
|
+
setStatus('Copied')
|
|
23
|
+
} catch {
|
|
24
|
+
setStatus('Copy failed')
|
|
25
|
+
}
|
|
26
|
+
}}
|
|
27
|
+
aria-label={label}
|
|
28
|
+
>
|
|
29
|
+
<span aria-live="polite">{status || label}</span>
|
|
30
|
+
</button>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function MaterialSample({ swatch, stylesheets }: { swatch: Swatch; stylesheets: string[] }) {
|
|
35
|
+
const source = materialSources(swatch)
|
|
36
|
+
const bundle = `${source.html}${source.css ? `\n\n<style>\n${source.css}\n</style>` : ''}`
|
|
37
|
+
return (
|
|
38
|
+
<div className="min-w-0">
|
|
39
|
+
<iframe
|
|
40
|
+
title={`${swatch.swatchName} material preview`}
|
|
41
|
+
sandbox=""
|
|
42
|
+
srcDoc={previewDocument(swatch, stylesheets)}
|
|
43
|
+
className="h-56 w-full rounded-lg border-0 bg-white/5"
|
|
44
|
+
/>
|
|
45
|
+
<div className="mt-3 flex flex-wrap items-center gap-2">
|
|
46
|
+
<span className="mr-auto text-sm font-medium">{swatch.swatchName}</span>
|
|
47
|
+
{source.color && <CopyButton text={source.color} label="Copy color" />}
|
|
48
|
+
{source.css && <CopyButton text={source.css} label="Copy CSS" />}
|
|
49
|
+
{source.classes && <CopyButton text={source.classes} label="Copy classes" />}
|
|
50
|
+
<CopyButton text={source.html} label="Copy HTML" />
|
|
51
|
+
<CopyButton text={bundle} label="Copy all" />
|
|
52
|
+
{source.textureURL && <CopyButton text={source.textureURL} label="Copy texture URL" />}
|
|
53
|
+
</div>
|
|
54
|
+
<details className="mt-2 text-xs opacity-80">
|
|
55
|
+
<summary className="cursor-pointer py-1">Source</summary>
|
|
56
|
+
<pre className="mt-2 max-h-64 overflow-auto rounded bg-black/10 p-3">
|
|
57
|
+
<code>{bundle}</code>
|
|
58
|
+
</pre>
|
|
59
|
+
</details>
|
|
60
|
+
</div>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function DDMaterialSwatches({ heading, swatches }: MaterialSwatchesProps) {
|
|
65
|
+
const [stylesheets, setStylesheets] = useState<string[]>([])
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
setStylesheets(
|
|
68
|
+
Array.from(document.querySelectorAll<HTMLLinkElement>('link[rel="stylesheet"]')).map(
|
|
69
|
+
(link) => link.href,
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
}, [])
|
|
73
|
+
if (!swatches?.length) return null
|
|
74
|
+
return (
|
|
75
|
+
<section className="w-full mt-16 px-4 py-10">
|
|
76
|
+
{heading && <h2 className="mb-6 text-lg font-semibold tracking-tight">{heading}</h2>}
|
|
77
|
+
<div className="grid gap-x-6 gap-y-10 sm:grid-cols-2 xl:grid-cols-4">
|
|
78
|
+
{swatches.map((swatch, index) => (
|
|
79
|
+
<MaterialSample key={index} swatch={swatch} stylesheets={stylesheets} />
|
|
80
|
+
))}
|
|
81
|
+
</div>
|
|
82
|
+
</section>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
export default DDMaterialSwatches
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import type { Block, Field } from 'payload'
|
|
2
|
+
|
|
3
|
+
// Recursive layer field — Payload doesn't support true infinite recursion,
|
|
4
|
+
// so we define three levels of depth which covers virtually all real swatch
|
|
5
|
+
// compositions (root → wrapper → leaf).
|
|
6
|
+
|
|
7
|
+
const layerFields = (suffix: string): Field[] => [
|
|
8
|
+
{
|
|
9
|
+
name: 'name',
|
|
10
|
+
type: 'text',
|
|
11
|
+
label: 'Layer name',
|
|
12
|
+
required: true,
|
|
13
|
+
admin: {
|
|
14
|
+
description: 'Human-readable label shown in the UI (e.g. "Blend Wrapper", "Top Glow").',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'element',
|
|
19
|
+
type: 'select',
|
|
20
|
+
dbName: `el_${suffix}`,
|
|
21
|
+
label: 'Element type',
|
|
22
|
+
required: true,
|
|
23
|
+
defaultValue: 'div',
|
|
24
|
+
options: [
|
|
25
|
+
{ label: 'div', value: 'div' },
|
|
26
|
+
{ label: 'span', value: 'span' },
|
|
27
|
+
{ label: 'button', value: 'button' },
|
|
28
|
+
{ label: 'a', value: 'a' },
|
|
29
|
+
{ label: 'section', value: 'section' },
|
|
30
|
+
{ label: 'article', value: 'article' },
|
|
31
|
+
{ label: 'p', value: 'p' },
|
|
32
|
+
{ label: 'li', value: 'li' },
|
|
33
|
+
{ label: 'ul', value: 'ul' },
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'className',
|
|
38
|
+
type: 'textarea',
|
|
39
|
+
label: 'Tailwind className',
|
|
40
|
+
admin: {
|
|
41
|
+
description:
|
|
42
|
+
'Paste the full class string. All modifiers (!, hover:, data-[...]:, arbitrary values) are preserved as-is.',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'attributes',
|
|
47
|
+
type: 'array',
|
|
48
|
+
label: 'HTML attributes',
|
|
49
|
+
admin: {
|
|
50
|
+
description: 'Extra attributes such as data-state="active" or aria-label="...".',
|
|
51
|
+
},
|
|
52
|
+
fields: [
|
|
53
|
+
{
|
|
54
|
+
name: 'key',
|
|
55
|
+
type: 'text',
|
|
56
|
+
label: 'Attribute name',
|
|
57
|
+
required: true,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'value',
|
|
61
|
+
type: 'text',
|
|
62
|
+
label: 'Attribute value',
|
|
63
|
+
required: true,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'content',
|
|
69
|
+
type: 'text',
|
|
70
|
+
label: 'Visible text content',
|
|
71
|
+
admin: {
|
|
72
|
+
description: 'Optional inner text for label/button/link layers.',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
// Level-3 leaf layers (no further children)
|
|
78
|
+
const level3LayerFields: Field[] = layerFields('l3')
|
|
79
|
+
|
|
80
|
+
// Level-2 layers — can contain level-3 children
|
|
81
|
+
const level2LayerFields: Field[] = [
|
|
82
|
+
...layerFields('l2'),
|
|
83
|
+
{
|
|
84
|
+
name: 'sub',
|
|
85
|
+
type: 'array',
|
|
86
|
+
label: 'Child layers',
|
|
87
|
+
fields: level3LayerFields,
|
|
88
|
+
},
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
// Level-1 layers — can contain level-2 children
|
|
92
|
+
const level1LayerFields: Field[] = [
|
|
93
|
+
...layerFields('l1'),
|
|
94
|
+
{
|
|
95
|
+
name: 'nodes',
|
|
96
|
+
type: 'array',
|
|
97
|
+
label: 'Child layers',
|
|
98
|
+
fields: level2LayerFields,
|
|
99
|
+
},
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
export const DDMaterialSwatches: Block = {
|
|
103
|
+
slug: 'DD-MaterialSwatches',
|
|
104
|
+
dbName: 'dd_ms',
|
|
105
|
+
interfaceName: 'DDMaterialSwatches',
|
|
106
|
+
labels: {
|
|
107
|
+
singular: 'Material Swatches',
|
|
108
|
+
plural: 'Material Swatches',
|
|
109
|
+
},
|
|
110
|
+
fields: [
|
|
111
|
+
{
|
|
112
|
+
name: 'heading',
|
|
113
|
+
type: 'text',
|
|
114
|
+
label: 'Section heading',
|
|
115
|
+
admin: {
|
|
116
|
+
description: 'Optional heading shown above the swatch grid.',
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'swatches',
|
|
121
|
+
type: 'array',
|
|
122
|
+
label: 'Swatches',
|
|
123
|
+
required: true,
|
|
124
|
+
minRows: 1,
|
|
125
|
+
fields: [
|
|
126
|
+
{
|
|
127
|
+
name: 'swatchName',
|
|
128
|
+
type: 'text',
|
|
129
|
+
label: 'Swatch name',
|
|
130
|
+
required: true,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'materialCSS',
|
|
134
|
+
type: 'code',
|
|
135
|
+
label: 'CSS',
|
|
136
|
+
admin: {
|
|
137
|
+
language: 'css',
|
|
138
|
+
description:
|
|
139
|
+
'Paste complete CSS rules, including hover states, pseudo-elements and keyframes. Use .material for the default surface.',
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'materialHTML',
|
|
144
|
+
type: 'code',
|
|
145
|
+
label: 'Layered HTML',
|
|
146
|
+
admin: {
|
|
147
|
+
language: 'html',
|
|
148
|
+
description:
|
|
149
|
+
'Optional HTML with all nested layers. Leave empty for a plain surface. Scripts are disabled in previews.',
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'colorValue',
|
|
154
|
+
type: 'text',
|
|
155
|
+
label: 'Color or background value',
|
|
156
|
+
admin: {
|
|
157
|
+
description:
|
|
158
|
+
'A CSS color, gradient, or comma-separated stack of gradients. Copied exactly as entered.',
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: 'texture',
|
|
163
|
+
type: 'upload',
|
|
164
|
+
relationTo: 'media',
|
|
165
|
+
label: 'Texture image',
|
|
166
|
+
},
|
|
167
|
+
// Root layer
|
|
168
|
+
{
|
|
169
|
+
name: 'rootElement',
|
|
170
|
+
type: 'select',
|
|
171
|
+
dbName: 'root_el',
|
|
172
|
+
label: 'Root element type',
|
|
173
|
+
required: true,
|
|
174
|
+
defaultValue: 'div',
|
|
175
|
+
options: [
|
|
176
|
+
{ label: 'div', value: 'div' },
|
|
177
|
+
{ label: 'span', value: 'span' },
|
|
178
|
+
{ label: 'button', value: 'button' },
|
|
179
|
+
{ label: 'a', value: 'a' },
|
|
180
|
+
{ label: 'section', value: 'section' },
|
|
181
|
+
{ label: 'article', value: 'article' },
|
|
182
|
+
{ label: 'p', value: 'p' },
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: 'rootClassName',
|
|
187
|
+
type: 'textarea',
|
|
188
|
+
label: 'Root className',
|
|
189
|
+
admin: {
|
|
190
|
+
description:
|
|
191
|
+
'Reusable classes for the root. Include their CSS above if they are not already compiled into this project.',
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: 'rootAttributes',
|
|
196
|
+
type: 'array',
|
|
197
|
+
label: 'Root attributes',
|
|
198
|
+
fields: [
|
|
199
|
+
{
|
|
200
|
+
name: 'key',
|
|
201
|
+
type: 'text',
|
|
202
|
+
label: 'Attribute name',
|
|
203
|
+
required: true,
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: 'value',
|
|
207
|
+
type: 'text',
|
|
208
|
+
label: 'Attribute value',
|
|
209
|
+
required: true,
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
name: 'rootContent',
|
|
215
|
+
type: 'text',
|
|
216
|
+
label: 'Root visible text (if any)',
|
|
217
|
+
},
|
|
218
|
+
// Direct children of root
|
|
219
|
+
{
|
|
220
|
+
name: 'layers',
|
|
221
|
+
type: 'array',
|
|
222
|
+
label: 'Child layers',
|
|
223
|
+
admin: {
|
|
224
|
+
description:
|
|
225
|
+
'Direct children of the root element. Each layer can contain its own children.',
|
|
226
|
+
},
|
|
227
|
+
fields: level1LayerFields,
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "block",
|
|
3
|
+
"name": "DD-MaterialSwatches",
|
|
4
|
+
"label": "Material Swatches",
|
|
5
|
+
"sourceName": "DD-MaterialSwatches",
|
|
6
|
+
"portableName": "DD-MaterialSwatches",
|
|
7
|
+
"slug": "DD-MaterialSwatches",
|
|
8
|
+
"version": "0.0.1",
|
|
9
|
+
"source": {
|
|
10
|
+
"repo": "Hadizainal/Dusted-Payload-Blocks",
|
|
11
|
+
"originalPath": "CLI-TestGround/dusted-blocks-demo/src/blocks/DD-MaterialSwatches",
|
|
12
|
+
"originalSlug": "DD-MaterialSwatches",
|
|
13
|
+
"originalConfigExport": "DDMaterialSwatches",
|
|
14
|
+
"originalComponentExport": "DDMaterialSwatches",
|
|
15
|
+
"originalInterfaceName": "DDMaterialSwatches"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
{
|
|
19
|
+
"from": "Component.tsx",
|
|
20
|
+
"to": "src/blocks/DD-MaterialSwatches/Component.tsx"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"from": "config.ts",
|
|
24
|
+
"to": "src/blocks/DD-MaterialSwatches/config.ts"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"from": "material.ts",
|
|
28
|
+
"to": "src/blocks/DD-MaterialSwatches/material.ts"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"dependencies": [],
|
|
32
|
+
"sharedFiles": [],
|
|
33
|
+
"requiredProjectFiles": [
|
|
34
|
+
{
|
|
35
|
+
"path": "src/payload-types.ts",
|
|
36
|
+
"reason": "Material helper uses the generated Media type."
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"optionalProjectFiles": [],
|
|
40
|
+
"collectionRegistration": {
|
|
41
|
+
"enabled": true,
|
|
42
|
+
"collections": [
|
|
43
|
+
"Pages",
|
|
44
|
+
"Posts"
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
"renderBlocksRegistration": {
|
|
48
|
+
"enabled": true
|
|
49
|
+
},
|
|
50
|
+
"renderBlocksBehavior": {
|
|
51
|
+
"wrapperless": false
|
|
52
|
+
},
|
|
53
|
+
"pageRouteIntegration": {
|
|
54
|
+
"required": false,
|
|
55
|
+
"manualSteps": []
|
|
56
|
+
},
|
|
57
|
+
"manual": [
|
|
58
|
+
"Register DDMaterialSwatches in Pages/Posts block arrays if automatic registration was skipped.",
|
|
59
|
+
"Register DD-MaterialSwatches in RenderBlocks if automatic registration was skipped.",
|
|
60
|
+
"Run payload generate:types after schema changes.",
|
|
61
|
+
"Run payload generate:importmap if admin/component imports changed."
|
|
62
|
+
],
|
|
63
|
+
"manualSteps": [
|
|
64
|
+
"Register DDMaterialSwatches in Pages/Posts block arrays if automatic registration was skipped.",
|
|
65
|
+
"Register DD-MaterialSwatches in RenderBlocks if automatic registration was skipped.",
|
|
66
|
+
"Run payload generate:types after schema changes.",
|
|
67
|
+
"Run payload generate:importmap if admin/component imports changed."
|
|
68
|
+
],
|
|
69
|
+
"patchTargets": {
|
|
70
|
+
"blockConfigRegistration": {
|
|
71
|
+
"description": "Installer can register DDMaterialSwatches from @/blocks/DD-MaterialSwatches/config."
|
|
72
|
+
},
|
|
73
|
+
"renderBlocksRegistration": {
|
|
74
|
+
"description": "Installer can register DD-MaterialSwatches with component export DDMaterialSwatches."
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"notes": [
|
|
78
|
+
"Promoted from the verified Dusted Blocks demo implementation.",
|
|
79
|
+
"Installer preserves existing destination files and reports project requirements before writing."
|
|
80
|
+
],
|
|
81
|
+
"exportName": "DDMaterialSwatches",
|
|
82
|
+
"componentExportName": "DDMaterialSwatches",
|
|
83
|
+
"interfaceName": "DDMaterialSwatches"
|
|
84
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { Media } from '@/payload-types'
|
|
2
|
+
|
|
3
|
+
export type Attr = { key: string; value: string }
|
|
4
|
+
export type SwatchLayer = {
|
|
5
|
+
name: string
|
|
6
|
+
element: string
|
|
7
|
+
className?: string
|
|
8
|
+
attributes?: Attr[]
|
|
9
|
+
content?: string
|
|
10
|
+
nodes?: SwatchLayer[]
|
|
11
|
+
sub?: SwatchLayer[]
|
|
12
|
+
}
|
|
13
|
+
export type Swatch = {
|
|
14
|
+
swatchName: string
|
|
15
|
+
rootElement: string
|
|
16
|
+
rootClassName?: string
|
|
17
|
+
rootAttributes?: Attr[] | null
|
|
18
|
+
rootContent?: string | null
|
|
19
|
+
layers?: SwatchLayer[]
|
|
20
|
+
materialCSS?: string | null
|
|
21
|
+
materialHTML?: string | null
|
|
22
|
+
colorValue?: string | null
|
|
23
|
+
texture?: number | Media | null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const escape = (value: string | null | undefined) =>
|
|
27
|
+
(value ?? '').replaceAll('&', '&')
|
|
28
|
+
.replaceAll('"', '"')
|
|
29
|
+
.replaceAll('<', '<')
|
|
30
|
+
.replaceAll('>', '>')
|
|
31
|
+
const children = (layer: SwatchLayer) => layer.nodes ?? layer.sub ?? []
|
|
32
|
+
const safeTag = (tag: string) =>
|
|
33
|
+
/^(div|span|button|a|section|article|p|li|ul)$/.test(tag) ? tag : 'div'
|
|
34
|
+
function elementHTML(
|
|
35
|
+
tag: string,
|
|
36
|
+
classes = '',
|
|
37
|
+
attributes: Attr[] | null = [],
|
|
38
|
+
content: string | null = '',
|
|
39
|
+
nested = '',
|
|
40
|
+
): string {
|
|
41
|
+
const name = safeTag(tag)
|
|
42
|
+
const attrs = (attributes ?? [])
|
|
43
|
+
.filter(({ key }) => /^(data-[\w-]+|aria-[\w-]+|title|role|style|type)$/.test(key))
|
|
44
|
+
.map(({ key, value }) => ` ${key}="${escape(value)}"`)
|
|
45
|
+
.join('')
|
|
46
|
+
return `<${name}${classes ? ` class="${escape(classes)}"` : ''}${attrs}>${nested}${escape(content)}</${name}>`
|
|
47
|
+
}
|
|
48
|
+
function layerHTML(layer: SwatchLayer): string {
|
|
49
|
+
return elementHTML(
|
|
50
|
+
layer.element,
|
|
51
|
+
layer.className,
|
|
52
|
+
layer.attributes,
|
|
53
|
+
layer.content,
|
|
54
|
+
children(layer).map(layerHTML).join('\n'),
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
export function materialSources(swatch: Swatch) {
|
|
58
|
+
const textureURL =
|
|
59
|
+
typeof swatch.texture === 'object' && swatch.texture ? swatch.texture.url : undefined
|
|
60
|
+
const hasLegacyLayers = Boolean(swatch.layers?.length || swatch.rootContent)
|
|
61
|
+
const classes = ['material', swatch.rootClassName].filter(Boolean).join(' ')
|
|
62
|
+
const html =
|
|
63
|
+
swatch.materialHTML ||
|
|
64
|
+
elementHTML(
|
|
65
|
+
swatch.rootElement || 'div',
|
|
66
|
+
classes,
|
|
67
|
+
swatch.rootAttributes,
|
|
68
|
+
swatch.rootContent,
|
|
69
|
+
swatch.layers?.map(layerHTML).join('\n'),
|
|
70
|
+
)
|
|
71
|
+
const declarations = [
|
|
72
|
+
!hasLegacyLayers && !swatch.materialHTML ? 'min-height: 180px; width: 100%;' : '',
|
|
73
|
+
swatch.colorValue ? `background: ${swatch.colorValue};` : '',
|
|
74
|
+
textureURL
|
|
75
|
+
? `background-image: url(${JSON.stringify(textureURL)}); background-size: cover;`
|
|
76
|
+
: '',
|
|
77
|
+
]
|
|
78
|
+
.filter(Boolean)
|
|
79
|
+
.join('\n ')
|
|
80
|
+
const css = [declarations ? `.material {\n ${declarations}\n}` : '', swatch.materialCSS]
|
|
81
|
+
.filter(Boolean)
|
|
82
|
+
.join('\n\n')
|
|
83
|
+
return {
|
|
84
|
+
html,
|
|
85
|
+
css,
|
|
86
|
+
classes: [
|
|
87
|
+
...new Set(
|
|
88
|
+
Array.from(html.matchAll(/\bclass=["']([^"']*)["']/g))
|
|
89
|
+
.flatMap((match) => match[1].split(/\s+/))
|
|
90
|
+
.filter(Boolean),
|
|
91
|
+
),
|
|
92
|
+
].join(' '),
|
|
93
|
+
color: swatch.colorValue || '',
|
|
94
|
+
textureURL,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function previewDocument(swatch: Swatch, stylesheets: string[] = []) {
|
|
99
|
+
const { html, css } = materialSources(swatch)
|
|
100
|
+
// Sandboxed srcdoc has no script or same-origin permission. CSS is kept inside its document.
|
|
101
|
+
const policy =
|
|
102
|
+
"default-src 'none'; img-src http: https: data: blob:; style-src 'unsafe-inline' http: https:; font-src http: https: data:; script-src 'none'; form-action 'none'; base-uri 'none'"
|
|
103
|
+
return `<!doctype html><html><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="${policy}">${stylesheets.map((url) => `<link rel="stylesheet" href="${escape(url)}">`).join('')}<style>html,body{margin:0;min-height:100%;opacity:1!important;visibility:visible!important;background:transparent!important;}body{box-sizing:border-box;display:grid;place-items:center;min-height:220px;padding:20px;overflow:hidden;}body>*{max-width:100%;}*,*::before,*::after{box-sizing:border-box;}</style><style>${css.replace(/<\/style/gi, '<\\/style')}</style></head><body>${html}</body></html>`
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/exhaustive-deps */
|
|
2
|
+
'use client'
|
|
3
|
+
|
|
4
|
+
import * as React from 'react'
|
|
5
|
+
|
|
6
|
+
import { RenderBlocks } from './RenderBlocks'
|
|
7
|
+
import { CanvasGrid, type CanvasPanel } from './canvas/CanvasGrid'
|
|
8
|
+
import { Circle } from 'lucide-react'
|
|
9
|
+
import { RadialNav, type RadialNavItem } from './canvas/RadialNav'
|
|
10
|
+
|
|
11
|
+
import './canvas/canvas-panels.css'
|
|
12
|
+
import './styles.scss'
|
|
13
|
+
|
|
14
|
+
type CanvasId =
|
|
15
|
+
| 'canvas_01'
|
|
16
|
+
| 'canvas_02'
|
|
17
|
+
| 'canvas_03'
|
|
18
|
+
| 'canvas_04'
|
|
19
|
+
| 'canvas_05'
|
|
20
|
+
| 'canvas_06'
|
|
21
|
+
| 'canvas_07'
|
|
22
|
+
| 'canvas_08'
|
|
23
|
+
| 'canvas_09'
|
|
24
|
+
|
|
25
|
+
type NineCanvasBlockProps = {
|
|
26
|
+
sectionId?: string
|
|
27
|
+
initialCanvas?: CanvasId
|
|
28
|
+
canvas01?: any[]
|
|
29
|
+
canvas02?: any[]
|
|
30
|
+
canvas03?: any[]
|
|
31
|
+
canvas04?: any[]
|
|
32
|
+
canvas05?: any[]
|
|
33
|
+
canvas06?: any[]
|
|
34
|
+
canvas07?: any[]
|
|
35
|
+
canvas08?: any[]
|
|
36
|
+
canvas09?: any[]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const canvasMeta = [
|
|
40
|
+
{ id: 'canvas_01', key: 'canvas01', x: -1, y: 1, label: 'Canvas 1' },
|
|
41
|
+
{ id: 'canvas_02', key: 'canvas02', x: 0, y: 1, label: 'Canvas 2' },
|
|
42
|
+
{ id: 'canvas_03', key: 'canvas03', x: 1, y: 1, label: 'Canvas 3' },
|
|
43
|
+
{ id: 'canvas_04', key: 'canvas04', x: -1, y: 0, label: 'Canvas 4' },
|
|
44
|
+
{ id: 'canvas_05', key: 'canvas05', x: 0, y: 0, label: 'Canvas 5' },
|
|
45
|
+
{ id: 'canvas_06', key: 'canvas06', x: 1, y: 0, label: 'Canvas 6' },
|
|
46
|
+
{ id: 'canvas_07', key: 'canvas07', x: -1, y: -1, label: 'Canvas 7' },
|
|
47
|
+
{ id: 'canvas_08', key: 'canvas08', x: 0, y: -1, label: 'Canvas 8' },
|
|
48
|
+
{ id: 'canvas_09', key: 'canvas09', x: 1, y: -1, label: 'Canvas 9' },
|
|
49
|
+
] as const
|
|
50
|
+
|
|
51
|
+
const radialItems: RadialNavItem[] = [
|
|
52
|
+
{ id: 1, icon: Circle, label: 'Canvas 1', angle: -56, canvasId: 'canvas_01' },
|
|
53
|
+
{ id: 2, icon: Circle, label: 'Canvas 2', angle: 0, canvasId: 'canvas_02' },
|
|
54
|
+
{ id: 3, icon: Circle, label: 'Canvas 3', angle: 56, canvasId: 'canvas_03' },
|
|
55
|
+
{ id: 4, icon: Circle, label: 'Canvas 4', angle: 270, canvasId: 'canvas_04' },
|
|
56
|
+
{ id: 6, icon: Circle, label: 'Canvas 6', angle: 90, canvasId: 'canvas_06' },
|
|
57
|
+
{ id: 7, icon: Circle, label: 'Canvas 7', angle: -115, canvasId: 'canvas_07' },
|
|
58
|
+
{ id: 8, icon: Circle, label: 'Canvas 8', angle: 180, canvasId: 'canvas_08' },
|
|
59
|
+
{ id: 9, icon: Circle, label: 'Canvas 9', angle: 115, canvasId: 'canvas_09' },
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
export function NineCanvasBlock({
|
|
63
|
+
sectionId,
|
|
64
|
+
initialCanvas = 'canvas_05',
|
|
65
|
+
...props
|
|
66
|
+
}: NineCanvasBlockProps) {
|
|
67
|
+
const initialPanel = canvasMeta.find((canvas) => canvas.id === initialCanvas) ?? canvasMeta[4]
|
|
68
|
+
const [activeCanvasId, setActiveCanvasId] = React.useState<CanvasId>(initialPanel.id)
|
|
69
|
+
const [mountedCanvasIds, setMountedCanvasIds] = React.useState(
|
|
70
|
+
() => new Set<CanvasId>([initialPanel.id]),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
const canvases = canvasMeta.map((canvas) => {
|
|
74
|
+
const blocks = props[canvas.key] || []
|
|
75
|
+
const hasBlocks = Array.isArray(blocks) && blocks.length > 0
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
id: canvas.id,
|
|
79
|
+
x: canvas.x,
|
|
80
|
+
y: canvas.y,
|
|
81
|
+
label: canvas.label,
|
|
82
|
+
content:
|
|
83
|
+
hasBlocks && mountedCanvasIds.has(canvas.id) ? (
|
|
84
|
+
<RenderBlocks blocks={blocks as any} />
|
|
85
|
+
) : undefined,
|
|
86
|
+
} satisfies CanvasPanel
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const activeCanvas = canvases.find((canvas) => canvas.id === activeCanvasId) ?? canvases[4]
|
|
90
|
+
|
|
91
|
+
const goToCanvas = (canvasId: string) => {
|
|
92
|
+
if (!canvases.some((canvas) => canvas.id === canvasId)) return
|
|
93
|
+
|
|
94
|
+
setMountedCanvasIds((current) => {
|
|
95
|
+
if (current.has(canvasId as CanvasId)) return current
|
|
96
|
+
|
|
97
|
+
const next = new Set(current)
|
|
98
|
+
next.add(canvasId as CanvasId)
|
|
99
|
+
return next
|
|
100
|
+
})
|
|
101
|
+
setActiveCanvasId(canvasId as CanvasId)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
React.useEffect(() => {
|
|
105
|
+
const handleCanvasChange = (event: Event) => {
|
|
106
|
+
const canvasId = (event as CustomEvent<{ canvasId?: string }>).detail?.canvasId
|
|
107
|
+
if (!canvasId) return
|
|
108
|
+
|
|
109
|
+
goToCanvas(canvasId)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
window.addEventListener('ninecanvas:change', handleCanvasChange)
|
|
113
|
+
|
|
114
|
+
return () => {
|
|
115
|
+
window.removeEventListener('ninecanvas:change', handleCanvasChange)
|
|
116
|
+
}
|
|
117
|
+
}, [canvases])
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<>
|
|
121
|
+
<RadialNav activeCanvasId={activeCanvas.id} items={radialItems} onCanvasChange={goToCanvas} />
|
|
122
|
+
<main className="hzNineCanvasBlock hzCanvasRoot overflow-hidden" data-section-id={sectionId}>
|
|
123
|
+
<CanvasGrid activeCanvas={activeCanvas} canvases={canvases} onCanvasChange={goToCanvas} />
|
|
124
|
+
</main>
|
|
125
|
+
</>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export default NineCanvasBlock
|