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.
Files changed (161) hide show
  1. package/package.json +5 -2
  2. package/src/cli.js +39 -3
  3. package/src/commands/shop.js +59 -0
  4. package/src/commands/warehouse.js +73 -0
  5. package/src/installers/patchRenderBlocks.js +1 -1
  6. package/src/registry/validateTemplateManifest.js +2 -1
  7. package/templates/blocks/BannerDD790432/Component.tsx +63 -0
  8. package/templates/blocks/BannerDD790432/config.ts +54 -0
  9. package/templates/blocks/BannerDD790432/manifest.json +80 -0
  10. package/templates/blocks/Button47/Component.tsx +124 -0
  11. package/templates/blocks/Button47/config.ts +75 -0
  12. package/templates/blocks/Button47/link.ts +22 -0
  13. package/templates/blocks/Button47/manifest.json +105 -0
  14. package/templates/blocks/Button47/styles.module.scss +75 -0
  15. package/templates/blocks/DD-Accordion/Component.tsx +55 -36
  16. package/templates/blocks/DD-Accordion/config.ts +71 -59
  17. package/templates/blocks/DD-AnimText/Component.tsx +50 -32
  18. package/templates/blocks/DD-AnimText/config.ts +296 -295
  19. package/templates/blocks/DD-AsideNav/Component.tsx +323 -319
  20. package/templates/blocks/DD-Banner/Component.tsx +54 -51
  21. package/templates/blocks/DD-BookCall/Component.tsx +10 -9
  22. package/templates/blocks/DD-ButtonPop/Component.tsx +118 -117
  23. package/templates/blocks/DD-Card/Component.tsx +4 -4
  24. package/templates/blocks/DD-CardTemplate01/Component.tsx +5 -5
  25. package/templates/blocks/DD-CardsWithVariantV3/Component.tsx +472 -0
  26. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/index.tsx +203 -0
  27. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/styles.ts +87 -0
  28. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/useInjectStyle.ts +39 -0
  29. package/templates/blocks/DD-CardsWithVariantV3/config.ts +326 -0
  30. package/templates/blocks/DD-CardsWithVariantV3/manifest.json +110 -0
  31. package/templates/blocks/DD-Carousel-A/Component.tsx +251 -249
  32. package/templates/blocks/DD-Carousel-B/Component.tsx +405 -403
  33. package/templates/blocks/DD-Chip/Component.tsx +14 -14
  34. package/templates/blocks/DD-ComparisonTable/Component.tsx +255 -256
  35. package/templates/blocks/DD-Contact/Component.tsx +433 -434
  36. package/templates/blocks/DD-Counter/Component.tsx +18 -13
  37. package/templates/blocks/DD-DeliCard/Component.tsx +303 -0
  38. package/templates/blocks/DD-DeliCard/config.ts +186 -0
  39. package/templates/blocks/DD-DeliCard/manifest.json +94 -0
  40. package/templates/blocks/DD-FeatCat/Component.tsx +302 -302
  41. package/templates/blocks/DD-FeatProd/CircularGallery.tsx +1179 -0
  42. package/templates/blocks/DD-FeatProd/Component.tsx +484 -0
  43. package/templates/blocks/DD-FeatProd/config.ts +173 -0
  44. package/templates/blocks/DD-FeatProd/manifest.json +93 -0
  45. package/templates/blocks/DD-FeatServ/Component.tsx +607 -0
  46. package/templates/blocks/DD-FeatServ/config.ts +213 -0
  47. package/templates/blocks/DD-FeatServ/manifest.json +113 -0
  48. package/templates/blocks/DD-FeatStrip/Component.tsx +584 -585
  49. package/templates/blocks/DD-FluidBtn/ButtonsEyeCatching.tsx +627 -628
  50. package/templates/blocks/DD-FluidBtn/Component.tsx +46 -43
  51. package/templates/blocks/DD-GooeyInfo/Component.tsx +150 -149
  52. package/templates/blocks/DD-GooeyInfo/config.ts +136 -135
  53. package/templates/blocks/DD-Hero/Component.tsx +16 -40
  54. package/templates/blocks/DD-HeroPlas/Component.tsx +50 -0
  55. package/templates/blocks/DD-HeroPlas/ThreeScene.tsx +109 -0
  56. package/templates/blocks/DD-HeroPlas/config.ts +33 -0
  57. package/templates/blocks/DD-HeroPlas/manifest.json +88 -0
  58. package/templates/blocks/DD-HorizontalScroll/Component.tsx +4 -5
  59. package/templates/blocks/DD-Icon/Component.tsx +46 -81
  60. package/templates/blocks/DD-IconButton/Component.tsx +13 -20
  61. package/templates/blocks/DD-IdTag/Component.tsx +22 -21
  62. package/templates/blocks/DD-IframeMedia/Component.tsx +14 -10
  63. package/templates/blocks/DD-LineAnim/Component.tsx +42 -36
  64. package/templates/blocks/DD-LogoMarq/Component.tsx +38 -48
  65. package/templates/blocks/DD-Lottie/Component.tsx +11 -3
  66. package/templates/blocks/DD-MasonaryMedia/Component.tsx +544 -547
  67. package/templates/blocks/DD-MasonaryMedia/config.ts +389 -389
  68. package/templates/blocks/DD-Masonry/Component.tsx +654 -644
  69. package/templates/blocks/DD-Masonry/config.ts +475 -475
  70. package/templates/blocks/DD-MaterialSwatches/Component.tsx +85 -0
  71. package/templates/blocks/DD-MaterialSwatches/config.ts +232 -0
  72. package/templates/blocks/DD-MaterialSwatches/manifest.json +84 -0
  73. package/templates/blocks/DD-MaterialSwatches/material.ts +107 -0
  74. package/templates/blocks/DD-NineCanvas/Component.tsx +129 -0
  75. package/templates/blocks/DD-NineCanvas/RenderBlocks.tsx +244 -0
  76. package/templates/blocks/DD-NineCanvas/allowedCanvasBlocks.ts +6 -0
  77. package/templates/blocks/DD-NineCanvas/blockComponents.tsx +8 -0
  78. package/templates/blocks/DD-NineCanvas/canvas/CanvasGrid.tsx +316 -0
  79. package/templates/blocks/DD-NineCanvas/canvas/RadialNav.tsx +432 -0
  80. package/templates/blocks/DD-NineCanvas/canvas/canvas-panels.css +889 -0
  81. package/templates/blocks/DD-NineCanvas/canvas/icons/custom-icons.tsx +364 -0
  82. package/templates/blocks/DD-NineCanvas/config.ts +61 -0
  83. package/templates/blocks/DD-NineCanvas/manifest.json +165 -0
  84. package/templates/blocks/DD-NineCanvas/styles.scss +152 -0
  85. package/templates/blocks/DD-NineCanvas/tooltip/get-strict-context.tsx +26 -0
  86. package/templates/blocks/DD-NineCanvas/tooltip/primitives.tsx +178 -0
  87. package/templates/blocks/DD-NineCanvas/tooltip/tooltip.tsx +71 -0
  88. package/templates/blocks/DD-NineCanvas/tooltip/use-controlled-state.tsx +33 -0
  89. package/templates/blocks/DD-NineCanvas/utilities/FadeOnScroll.tsx +68 -0
  90. package/templates/blocks/DD-NineCanvas/utilities/ParallaxWrapper.tsx +157 -0
  91. package/templates/blocks/DD-NineCanvas/utilities/renderCustomCSS.ts +5 -0
  92. package/templates/blocks/DD-NineCanvas/utilities/webglCleanup.ts +27 -0
  93. package/templates/blocks/DD-PayloadAIAssistant/Component.tsx +78 -0
  94. package/templates/blocks/DD-PayloadAIAssistant/DustedBot.tsx +590 -0
  95. package/templates/blocks/DD-PayloadAIAssistant/config.ts +37 -0
  96. package/templates/blocks/DD-PayloadAIAssistant/demo.css +125 -0
  97. package/templates/blocks/DD-PayloadAIAssistant/manifest.json +93 -0
  98. package/templates/blocks/DD-PayloadAIAssistant/source-defaults.ts +17 -0
  99. package/templates/blocks/DD-PayloadAIAssistant/styles.ts +306 -0
  100. package/templates/blocks/DD-Pricing/Component.tsx +368 -372
  101. package/templates/blocks/DD-Pricing/config.ts +215 -215
  102. package/templates/blocks/DD-Process/Component.tsx +147 -149
  103. package/templates/blocks/DD-Progress/Component.tsx +156 -141
  104. package/templates/blocks/DD-ProjHeader/Component.tsx +93 -91
  105. package/templates/blocks/DD-ProjSlider/Component.tsx +1 -1
  106. package/templates/blocks/DD-ProjSlider/config.ts +2 -2
  107. package/templates/blocks/DD-Section/Component.tsx +265 -266
  108. package/templates/blocks/DD-Separator/Component.tsx +66 -66
  109. package/templates/blocks/DD-Services/Component.tsx +176 -176
  110. package/templates/blocks/DD-ShowcaseGrid/Component.tsx +306 -307
  111. package/templates/blocks/DD-Slider-A/Component.tsx +237 -237
  112. package/templates/blocks/DD-StackCards/Component.tsx +474 -473
  113. package/templates/blocks/DD-Team/Component.tsx +247 -247
  114. package/templates/blocks/DD-TeamCards/Component.tsx +251 -251
  115. package/templates/blocks/DD-TechStack/Component.tsx +161 -168
  116. package/templates/blocks/DD-TestMarq/Component.tsx +158 -132
  117. package/templates/blocks/DD-Testim/Component.tsx +247 -247
  118. package/templates/blocks/DD-Testim2/Component.tsx +265 -256
  119. package/templates/blocks/DD-Testimonials/Component.tsx +139 -147
  120. package/templates/blocks/DD-TextMarq/Component.tsx +157 -157
  121. package/templates/blocks/DD-Thanks/Component.tsx +501 -500
  122. package/templates/blocks/DD-Work/Component.tsx +315 -315
  123. package/templates/blocks/DD-Work-B/Component.tsx +294 -294
  124. package/templates/blocks/DDHG-CTASection2/Component.tsx +183 -184
  125. package/templates/blocks/DDHG-HighlightArc/Component.tsx +246 -243
  126. package/templates/blocks/DDHG-LogoMarquee/Component.tsx +180 -93
  127. package/templates/blocks/DDHG-LogoMarquee/config.ts +154 -66
  128. package/templates/blocks/DDHG-NavCodeGrid/Component.tsx +225 -213
  129. package/templates/blocks/DDHG-ProcessSec/Component.tsx +38 -39
  130. package/templates/blocks/DDHG-ServiceDetails/Component.tsx +11 -11
  131. package/templates/blocks/DDHG-ServiceDetails/ServiceDetailCard.tsx +30 -47
  132. package/templates/blocks/DDHG-ServiceHero/Component.tsx +10 -10
  133. package/templates/blocks/DDHG-ServiceOverview/Component.tsx +23 -23
  134. package/templates/blocks/DDHG-StatsFAQ/Component.tsx +40 -40
  135. package/templates/blocks/DDHG-TeamCards/Component.tsx +140 -131
  136. package/templates/blocks/Form/Checkbox/index.tsx +45 -0
  137. package/templates/blocks/Form/Component.tsx +163 -0
  138. package/templates/blocks/Form/Country/index.tsx +65 -0
  139. package/templates/blocks/Form/Country/options.ts +982 -0
  140. package/templates/blocks/Form/Email/index.tsx +38 -0
  141. package/templates/blocks/Form/Error/index.tsx +15 -0
  142. package/templates/blocks/Form/Message/index.tsx +13 -0
  143. package/templates/blocks/Form/Number/index.tsx +36 -0
  144. package/templates/blocks/Form/Select/index.tsx +63 -0
  145. package/templates/blocks/Form/State/index.tsx +64 -0
  146. package/templates/blocks/Form/State/options.ts +52 -0
  147. package/templates/blocks/Form/Text/index.tsx +32 -0
  148. package/templates/blocks/Form/Textarea/index.tsx +40 -0
  149. package/templates/blocks/Form/Width/index.tsx +13 -0
  150. package/templates/blocks/Form/config.ts +51 -0
  151. package/templates/blocks/Form/fields.tsx +21 -0
  152. package/templates/blocks/Form/manifest.json +168 -0
  153. package/templates/blocks/MediaBlock/Component.tsx +67 -0
  154. package/templates/blocks/MediaBlock/config.ts +14 -0
  155. package/templates/blocks/MediaBlock/manifest.json +92 -0
  156. package/templates/blocks/TransitionsDustedShowcase/Component.tsx +9 -0
  157. package/templates/blocks/TransitionsDustedShowcase/config.ts +13 -0
  158. package/templates/blocks/TransitionsDustedShowcase/manifest.json +80 -0
  159. package/templates/blocks/rawHtml/Component.tsx +25 -0
  160. package/templates/blocks/rawHtml/config.ts +56 -0
  161. package/templates/blocks/rawHtml/manifest.json +80 -0
@@ -47,21 +47,21 @@ export const ChipBlockComponent: React.FC<ChipBlockProps> = ({
47
47
  customClassName,
48
48
  animate = false,
49
49
  maxWidth,
50
- padding = 'none'
50
+ padding = 'none',
51
51
  }) => {
52
52
  // Gap mappings
53
53
  const gapClasses = {
54
54
  none: 'gap-0',
55
55
  small: 'gap-2',
56
56
  medium: 'gap-3',
57
- large: 'gap-4'
57
+ large: 'gap-4',
58
58
  }
59
59
 
60
60
  // Layout mappings
61
61
  const layoutClasses = {
62
62
  row: 'flex-row flex-nowrap overflow-x-auto',
63
63
  column: 'flex-col',
64
- 'row-wrap': 'flex-row flex-wrap'
64
+ 'row-wrap': 'flex-row flex-wrap',
65
65
  }
66
66
 
67
67
  // Alignment mappings
@@ -69,7 +69,7 @@ export const ChipBlockComponent: React.FC<ChipBlockProps> = ({
69
69
  start: 'justify-start',
70
70
  center: 'justify-center',
71
71
  end: 'justify-end',
72
- 'space-between': 'justify-between'
72
+ 'space-between': 'justify-between',
73
73
  }
74
74
 
75
75
  // Padding mappings
@@ -77,7 +77,7 @@ export const ChipBlockComponent: React.FC<ChipBlockProps> = ({
77
77
  none: '',
78
78
  small: 'p-2',
79
79
  medium: 'p-4',
80
- large: 'p-6'
80
+ large: 'p-6',
81
81
  }
82
82
 
83
83
  // Helper to determine if a color is a Tailwind class or CSS color
@@ -137,9 +137,9 @@ export const ChipBlockComponent: React.FC<ChipBlockProps> = ({
137
137
  visible: {
138
138
  opacity: 1,
139
139
  transition: {
140
- staggerChildren: 0.1
141
- }
142
- }
140
+ staggerChildren: 0.1,
141
+ },
142
+ },
143
143
  }
144
144
 
145
145
  const chipVariants: Variants = {
@@ -149,9 +149,9 @@ export const ChipBlockComponent: React.FC<ChipBlockProps> = ({
149
149
  y: 0,
150
150
  transition: {
151
151
  duration: 0.4,
152
- ease: 'easeOut'
153
- }
154
- }
152
+ ease: 'easeOut',
153
+ },
154
+ },
155
155
  }
156
156
 
157
157
  const ChipElement = ({ chip, index }: { chip: Chip; index: number }): React.ReactElement => {
@@ -165,7 +165,7 @@ export const ChipBlockComponent: React.FC<ChipBlockProps> = ({
165
165
  <span className="relative z-10 mx-auto flex max-w-md items-center bg-gradient-to-r from-transparent via-white/80 via-50% to-transparent bg-clip-text bg-no-repeat text-neutral-400/70 [background-position:0_0] [background-size:var(--shiny-width)_100%] [--shiny-width:100px] select-none">
166
166
  <span className="flex items-center text-white">
167
167
  {/* {chip.icon && (
168
- <span className="chip-icon mr-2 inline-flex h-3.5 w-3.5 items-center justify-center text-[10px] leading-none">
168
+ <span className="chip-icon mr-2 inline-flex h-3.5 w-3.5 items-center justify-center text-xs leading-none">
169
169
  {chip.icon}
170
170
  </span>
171
171
  )} */}
@@ -180,7 +180,7 @@ export const ChipBlockComponent: React.FC<ChipBlockProps> = ({
180
180
  '[&[href]]:cursor-pointer [&[href]]:no-underline',
181
181
  hoverEffect &&
182
182
  'hover:-translate-y-0.5 hover:shadow-[0_0_26px_rgba(186,85,211,0.35)] active:translate-y-0',
183
- getChipClasses(chip)
183
+ getChipClasses(chip),
184
184
  )
185
185
 
186
186
  const chipElement = chip.link ? (
@@ -216,7 +216,7 @@ export const ChipBlockComponent: React.FC<ChipBlockProps> = ({
216
216
  gapClasses[gap],
217
217
  alignmentClasses[alignment],
218
218
  paddingClasses[padding],
219
- customClassName
219
+ customClassName,
220
220
  )
221
221
 
222
222
  if (animate) {
@@ -1,256 +1,255 @@
1
- 'use client'
2
-
3
- import React, { Fragment, useState } from 'react'
4
- import { Check, Minus, Plus } from 'lucide-react'
5
-
6
- type TierKey = 'free' | 'pro' | 'team'
7
-
8
- type CellValue = boolean | string
9
-
10
- interface FeatureRow {
11
- id?: string | null
12
- label?: string | null
13
- free?: CellValue | null
14
- pro?: CellValue | null
15
- team?: CellValue | null
16
- }
17
-
18
- interface FeatureGroup {
19
- id?: string | null
20
- group?: string | null
21
- rows?: FeatureRow[] | null
22
- }
23
-
24
- interface TierHeader {
25
- key: TierKey
26
- label: string
27
- highlight: boolean
28
- }
29
-
30
- interface StudioComparisonTableProps {
31
- eyebrow?: string | null
32
- heading?: string | null
33
- defaultOpen?: boolean | null
34
- groups?: FeatureGroup[] | null
35
- sectionId?: string | null
36
- customCSS?: string | null
37
- [key: string]: unknown
38
- }
39
-
40
- const DEFAULT_GROUPS: FeatureGroup[] = [
41
- {
42
- group: 'Components & Templates',
43
- rows: [
44
- { label: 'Free components', free: true, pro: true, team: true },
45
- { label: 'Premium components', free: false, pro: '120+', team: '120+' },
46
- { label: 'Free templates', free: true, pro: true, team: true },
47
- { label: 'Premium templates', free: false, pro: '24+', team: '24+' },
48
- { label: 'Payload CMS v3 blocks', free: false, pro: true, team: true },
49
- { label: 'Figma source files', free: false, pro: true, team: true },
50
- ],
51
- },
52
- {
53
- group: 'Licensing',
54
- rows: [
55
- { label: 'Personal use', free: true, pro: true, team: true },
56
- {
57
- label: 'Commercial projects',
58
- free: false,
59
- pro: '1 project',
60
- team: 'Unlimited',
61
- },
62
- { label: 'White-label license', free: false, pro: false, team: true },
63
- { label: 'Client resale rights', free: false, pro: false, team: true },
64
- ],
65
- },
66
- {
67
- group: 'Updates & Access',
68
- rows: [
69
- { label: 'Lifetime updates', free: true, pro: true, team: true },
70
- {
71
- label: 'Early access to new releases',
72
- free: false,
73
- pro: true,
74
- team: true,
75
- },
76
- { label: 'Download source files', free: false, pro: true, team: true },
77
- ],
78
- },
79
- {
80
- group: 'Support',
81
- rows: [
82
- { label: 'Community Discord', free: true, pro: true, team: true },
83
- { label: 'Priority email support', free: false, pro: true, team: true },
84
- { label: 'Dedicated Slack channel', free: false, pro: false, team: true },
85
- { label: '1-on-1 onboarding call', free: false, pro: false, team: true },
86
- { label: 'SLA support guarantee', free: false, pro: false, team: true },
87
- ],
88
- },
89
- {
90
- group: 'Team',
91
- rows: [
92
- { label: 'Team seats', free: '1', pro: '1', team: 'Up to 10' },
93
- {
94
- label: 'Invoice & billing management',
95
- free: false,
96
- pro: false,
97
- team: true,
98
- },
99
- {
100
- label: 'Custom Payload CMS schemas',
101
- free: false,
102
- pro: false,
103
- team: true,
104
- },
105
- ],
106
- },
107
- ]
108
-
109
- function Cell({ value, highlight }: { value: CellValue | null | undefined; highlight: boolean }) {
110
- if (typeof value === 'string') {
111
- return (
112
- <span
113
- className={`text-sm font-medium ${highlight ? 'text-indigo-900 dark:text-lime-400' : 'text-stone-700 dark:text-stone-300'}`}
114
- >
115
- {value}
116
- </span>
117
- )
118
- }
119
-
120
- if (value === true) {
121
- return (
122
- <div
123
- className={`mx-auto flex h-5 w-5 items-center justify-center rounded-full ${
124
- highlight ? 'bg-stone-900' : 'bg-stone-800'
125
- }`}
126
- style={{ cornerShape: 'squircle' } as React.CSSProperties}
127
- >
128
- <Check className={`h-3 w-3 ${highlight ? 'text-stone-50' : 'text-stone-300'}`} />
129
- </div>
130
- )
131
- }
132
-
133
- return (
134
- <div className="mx-auto flex h-5 w-5 items-center justify-center opacity-30">
135
- <Minus className="h-4 w-4 text-stone-600" />
136
- </div>
137
- )
138
- }
139
-
140
- export function DDComparisonTable({
141
- eyebrow = 'Full breakdown',
142
- heading = 'Compare all features',
143
- defaultOpen = true,
144
- groups,
145
- sectionId,
146
- }: StudioComparisonTableProps) {
147
- const [open, setOpen] = useState(defaultOpen !== false)
148
- const featureGroups = groups && groups.length > 0 ? groups : DEFAULT_GROUPS
149
-
150
- const tiers: TierHeader[] = [
151
- { key: 'free', label: 'Free', highlight: false },
152
- { key: 'pro', label: 'Pro', highlight: true },
153
- { key: 'team', label: 'Team', highlight: false },
154
- ]
155
-
156
- return (
157
- <section
158
- id={sectionId ?? undefined}
159
- className="studio-comparison-table border-t border-stone-800 bg-transparent px-6 py-20"
160
- >
161
- <div className="mx-auto max-w-6xl">
162
-
163
- <button
164
- type="button"
165
- onClick={() => setOpen((current) => !current)}
166
- className="group mb-8 flex w-full cursor-pointer items-center justify-between gap-6 text-left"
167
- >
168
- <div>
169
- {eyebrow && (
170
- <p className="mb-1 text-xs font-semibold uppercase tracking-widest text-stone-500">
171
- {eyebrow}
172
- </p>
173
- )}
174
- {heading && (
175
- <h2 className="text-3xl font-semibold text-stone-900 dark:text-stone-50">
176
- {heading}
177
- </h2>
178
- )}
179
- </div>
180
-
181
- <div
182
- className="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl border border-stone-100 bg-stone-200 dark:border-stone-700 dark:bg-stone-800 transition-colors group-hover:border-stone-300 dark:group-hover:border-stone-500"
183
- style={{ cornerShape: 'squircle' } as React.CSSProperties}
184
- >
185
- {open ? (
186
- <Minus className="h-4 w-4 text-stone-700 dark:text-stone-400 transition-transform duration-300" />
187
- ) : (
188
- <Plus className="h-4 w-4 text-stone-700 dark:text-stone-300 transition-transform duration-300" />
189
- )}
190
- </div>
191
- </button>
192
-
193
- {open && (
194
- <div className="overflow-x-auto">
195
- <table className="w-full min-w-160">
196
- <thead>
197
- <tr>
198
- <th className="w-1/2 pb-0 text-left" />
199
- {tiers.map((tier) => (
200
- <th key={tier.key} className="pb-0 text-center">
201
- <span
202
- className={`inline-block rounded-lg px-4 py-1.5 text-sm font-semibold ${
203
- tier.highlight
204
- ? 'bg-stone-100 text-stone-900'
205
- : 'border-stone-100 bg-stone-200 dark:border-stone-700 dark:bg-stone-800/50 transition-colors group-hover:border-stone-300 dark:group-hover:border-stone-500'
206
- }`}
207
- style={{ cornerShape: 'squircle' } as React.CSSProperties}
208
- >
209
- {tier.label}
210
- </span>
211
- </th>
212
- ))}
213
- </tr>
214
- </thead>
215
-
216
- <tbody>
217
- {featureGroups.map((group, groupIndex) => (
218
- <Fragment key={group.id ?? group.group ?? groupIndex}>
219
- <tr>
220
- <td colSpan={4} className="pb-3 pt-0">
221
- <span className="text-xs font-bold uppercase tracking-widest text-stone-500">
222
- {group.group}
223
- </span>
224
- </td>
225
- </tr>
226
-
227
- {(group.rows ?? []).map((row, rowIndex) => (
228
- <tr
229
- key={row.id ?? row.label ?? rowIndex}
230
- className={`border-t transition-colors hover:bg-stone-200/40 dark:hover:bg-stone-900/40 ${
231
- rowIndex === 0 ? 'border-stone-800' : 'border-stone-800/50'
232
- }`}
233
- >
234
- <td className="py-3.5 pr-6">
235
- <span className="text-sm text-stone-400">{row.label}</span>
236
- </td>
237
-
238
- {tiers.map((tier) => (
239
- <td key={tier.key} className="py-3.5 text-center">
240
- <Cell value={row[tier.key]} highlight={tier.highlight} />
241
- </td>
242
- ))}
243
- </tr>
244
- ))}
245
- </Fragment>
246
- ))}
247
- </tbody>
248
- </table>
249
- </div>
250
- )}
251
- </div>
252
- </section>
253
- )
254
- }
255
-
256
- export default DDComparisonTable
1
+ 'use client'
2
+
3
+ import React, { Fragment, useState } from 'react'
4
+ import { Check, Minus, Plus } from 'lucide-react'
5
+
6
+ type TierKey = 'free' | 'pro' | 'team'
7
+
8
+ type CellValue = boolean | string
9
+
10
+ interface FeatureRow {
11
+ id?: string | null
12
+ label?: string | null
13
+ free?: CellValue | null
14
+ pro?: CellValue | null
15
+ team?: CellValue | null
16
+ }
17
+
18
+ interface FeatureGroup {
19
+ id?: string | null
20
+ group?: string | null
21
+ rows?: FeatureRow[] | null
22
+ }
23
+
24
+ interface TierHeader {
25
+ key: TierKey
26
+ label: string
27
+ highlight: boolean
28
+ }
29
+
30
+ interface StudioComparisonTableProps {
31
+ eyebrow?: string | null
32
+ heading?: string | null
33
+ defaultOpen?: boolean | null
34
+ groups?: FeatureGroup[] | null
35
+ sectionId?: string | null
36
+ customCSS?: string | null
37
+ [key: string]: unknown
38
+ }
39
+
40
+ const DEFAULT_GROUPS: FeatureGroup[] = [
41
+ {
42
+ group: 'Components & Templates',
43
+ rows: [
44
+ { label: 'Free components', free: true, pro: true, team: true },
45
+ { label: 'Premium components', free: false, pro: '120+', team: '120+' },
46
+ { label: 'Free templates', free: true, pro: true, team: true },
47
+ { label: 'Premium templates', free: false, pro: '24+', team: '24+' },
48
+ { label: 'Payload CMS v3 blocks', free: false, pro: true, team: true },
49
+ { label: 'Figma source files', free: false, pro: true, team: true },
50
+ ],
51
+ },
52
+ {
53
+ group: 'Licensing',
54
+ rows: [
55
+ { label: 'Personal use', free: true, pro: true, team: true },
56
+ {
57
+ label: 'Commercial projects',
58
+ free: false,
59
+ pro: '1 project',
60
+ team: 'Unlimited',
61
+ },
62
+ { label: 'White-label license', free: false, pro: false, team: true },
63
+ { label: 'Client resale rights', free: false, pro: false, team: true },
64
+ ],
65
+ },
66
+ {
67
+ group: 'Updates & Access',
68
+ rows: [
69
+ { label: 'Lifetime updates', free: true, pro: true, team: true },
70
+ {
71
+ label: 'Early access to new releases',
72
+ free: false,
73
+ pro: true,
74
+ team: true,
75
+ },
76
+ { label: 'Download source files', free: false, pro: true, team: true },
77
+ ],
78
+ },
79
+ {
80
+ group: 'Support',
81
+ rows: [
82
+ { label: 'Community Discord', free: true, pro: true, team: true },
83
+ { label: 'Priority email support', free: false, pro: true, team: true },
84
+ { label: 'Dedicated Slack channel', free: false, pro: false, team: true },
85
+ { label: '1-on-1 onboarding call', free: false, pro: false, team: true },
86
+ { label: 'SLA support guarantee', free: false, pro: false, team: true },
87
+ ],
88
+ },
89
+ {
90
+ group: 'Team',
91
+ rows: [
92
+ { label: 'Team seats', free: '1', pro: '1', team: 'Up to 10' },
93
+ {
94
+ label: 'Invoice & billing management',
95
+ free: false,
96
+ pro: false,
97
+ team: true,
98
+ },
99
+ {
100
+ label: 'Custom Payload CMS schemas',
101
+ free: false,
102
+ pro: false,
103
+ team: true,
104
+ },
105
+ ],
106
+ },
107
+ ]
108
+
109
+ function Cell({ value, highlight }: { value: CellValue | null | undefined; highlight: boolean }) {
110
+ if (typeof value === 'string') {
111
+ return (
112
+ <span
113
+ className={`text-sm font-medium ${highlight ? 'text-indigo-900 dark:text-lime-400' : 'text-stone-700 dark:text-stone-300'}`}
114
+ >
115
+ {value}
116
+ </span>
117
+ )
118
+ }
119
+
120
+ if (value === true) {
121
+ return (
122
+ <div
123
+ className={`mx-auto flex h-5 w-5 items-center justify-center rounded-full ${
124
+ highlight ? 'bg-stone-900' : 'bg-stone-800'
125
+ }`}
126
+ style={{ cornerShape: 'squircle' } as React.CSSProperties}
127
+ >
128
+ <Check className={`h-3 w-3 ${highlight ? 'text-stone-50' : 'text-stone-300'}`} />
129
+ </div>
130
+ )
131
+ }
132
+
133
+ return (
134
+ <div className="mx-auto flex h-5 w-5 items-center justify-center opacity-30">
135
+ <Minus className="h-4 w-4 text-stone-600" />
136
+ </div>
137
+ )
138
+ }
139
+
140
+ export function DDComparisonTable({
141
+ eyebrow = 'Full breakdown',
142
+ heading = 'Compare all features',
143
+ defaultOpen = true,
144
+ groups,
145
+ sectionId,
146
+ }: StudioComparisonTableProps) {
147
+ const [open, setOpen] = useState(defaultOpen !== false)
148
+ const featureGroups = groups && groups.length > 0 ? groups : DEFAULT_GROUPS
149
+
150
+ const tiers: TierHeader[] = [
151
+ { key: 'free', label: 'Free', highlight: false },
152
+ { key: 'pro', label: 'Pro', highlight: true },
153
+ { key: 'team', label: 'Team', highlight: false },
154
+ ]
155
+
156
+ return (
157
+ <section
158
+ id={sectionId ?? undefined}
159
+ className="studio-comparison-table w-full border-t border-stone-800 bg-transparent px-6 py-20"
160
+ >
161
+ <div className="mx-auto w-full max-w-7xl">
162
+ <button
163
+ type="button"
164
+ onClick={() => setOpen((current) => !current)}
165
+ className="group mb-8 flex w-full cursor-pointer items-center justify-between gap-6 text-left"
166
+ >
167
+ <div>
168
+ {eyebrow && (
169
+ <p className="mb-1 text-xs font-semibold uppercase tracking-widest text-stone-500">
170
+ {eyebrow}
171
+ </p>
172
+ )}
173
+ {heading && (
174
+ <h2 className="text-3xl font-semibold text-stone-900 dark:text-stone-50">
175
+ {heading}
176
+ </h2>
177
+ )}
178
+ </div>
179
+
180
+ <div
181
+ className="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl border border-stone-100 bg-stone-200 dark:border-stone-700 dark:bg-stone-800 transition-colors group-hover:border-stone-300 dark:group-hover:border-stone-500"
182
+ style={{ cornerShape: 'squircle' } as React.CSSProperties}
183
+ >
184
+ {open ? (
185
+ <Minus className="h-4 w-4 text-stone-700 dark:text-stone-400 transition-transform duration-300" />
186
+ ) : (
187
+ <Plus className="h-4 w-4 text-stone-700 dark:text-stone-300 transition-transform duration-300" />
188
+ )}
189
+ </div>
190
+ </button>
191
+
192
+ {open && (
193
+ <div className="overflow-x-auto">
194
+ <table className="w-full min-w-160">
195
+ <thead>
196
+ <tr>
197
+ <th className="w-1/2 pb-0 text-left" />
198
+ {tiers.map((tier) => (
199
+ <th key={tier.key} className="pb-0 text-center">
200
+ <span
201
+ className={`inline-block rounded-lg px-4 py-1.5 text-sm font-semibold ${
202
+ tier.highlight
203
+ ? 'bg-stone-100 text-stone-900'
204
+ : 'border-stone-100 bg-stone-200 dark:border-stone-700 dark:bg-stone-800/50 transition-colors group-hover:border-stone-300 dark:group-hover:border-stone-500'
205
+ }`}
206
+ style={{ cornerShape: 'squircle' } as React.CSSProperties}
207
+ >
208
+ {tier.label}
209
+ </span>
210
+ </th>
211
+ ))}
212
+ </tr>
213
+ </thead>
214
+
215
+ <tbody>
216
+ {featureGroups.map((group, groupIndex) => (
217
+ <Fragment key={group.id ?? group.group ?? groupIndex}>
218
+ <tr>
219
+ <td colSpan={4} className="pb-3 pt-0">
220
+ <span className="text-xs font-bold uppercase tracking-widest text-stone-500">
221
+ {group.group}
222
+ </span>
223
+ </td>
224
+ </tr>
225
+
226
+ {(group.rows ?? []).map((row, rowIndex) => (
227
+ <tr
228
+ key={row.id ?? row.label ?? rowIndex}
229
+ className={`border-t transition-colors hover:bg-stone-200/40 dark:hover:bg-stone-900/40 ${
230
+ rowIndex === 0 ? 'border-stone-800' : 'border-stone-800/50'
231
+ }`}
232
+ >
233
+ <td className="py-3.5 pr-6">
234
+ <span className="text-sm text-stone-400">{row.label}</span>
235
+ </td>
236
+
237
+ {tiers.map((tier) => (
238
+ <td key={tier.key} className="py-3.5 text-center">
239
+ <Cell value={row[tier.key]} highlight={tier.highlight} />
240
+ </td>
241
+ ))}
242
+ </tr>
243
+ ))}
244
+ </Fragment>
245
+ ))}
246
+ </tbody>
247
+ </table>
248
+ </div>
249
+ )}
250
+ </div>
251
+ </section>
252
+ )
253
+ }
254
+
255
+ export default DDComparisonTable