@mdxui/payload 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,95 @@
1
+ 'use client'
2
+
3
+ import { useState, useEffect, useCallback, useRef } from 'react'
4
+ import { compile } from '@mdx-js/mdx'
5
+
6
+ export interface MDXCompilerOptions {
7
+ /** MDX source code to compile */
8
+ source: string
9
+ /** Debounce delay in milliseconds (default: 300) */
10
+ debounceMs?: number
11
+ /** Whether to enable development mode (default: true) */
12
+ development?: boolean
13
+ }
14
+
15
+ export interface MDXCompilerResult {
16
+ /** Compiled MDX code (ES module string) */
17
+ code: string | null
18
+ /** Compilation error if any */
19
+ error: Error | null
20
+ /** Whether compilation is in progress */
21
+ isCompiling: boolean
22
+ }
23
+
24
+ /**
25
+ * Hook for compiling MDX source code with debouncing support.
26
+ *
27
+ * @example
28
+ * ```tsx
29
+ * const { code, error, isCompiling } = useMDXCompiler({
30
+ * source: mdxSource,
31
+ * debounceMs: 300,
32
+ * })
33
+ * ```
34
+ */
35
+ export function useMDXCompiler({
36
+ source,
37
+ debounceMs = 300,
38
+ development = true,
39
+ }: MDXCompilerOptions): MDXCompilerResult {
40
+ const [code, setCode] = useState<string | null>(null)
41
+ const [error, setError] = useState<Error | null>(null)
42
+ const [isCompiling, setIsCompiling] = useState(false)
43
+ const timeoutRef = useRef<NodeJS.Timeout | null>(null)
44
+
45
+ const compileMDX = useCallback(async (mdxSource: string) => {
46
+ if (!mdxSource.trim()) {
47
+ setCode(null)
48
+ setError(null)
49
+ setIsCompiling(false)
50
+ return
51
+ }
52
+
53
+ setIsCompiling(true)
54
+ setError(null)
55
+
56
+ try {
57
+ const compiled = await compile(mdxSource, {
58
+ outputFormat: 'function-body',
59
+ development,
60
+ /* @ts-ignore - MDX types are complex */
61
+ providerImportSource: '@mdx-js/react',
62
+ })
63
+
64
+ setCode(String(compiled))
65
+ setError(null)
66
+ } catch (err) {
67
+ console.error('MDX compilation error:', err)
68
+ setError(err instanceof Error ? err : new Error(String(err)))
69
+ setCode(null)
70
+ } finally {
71
+ setIsCompiling(false)
72
+ }
73
+ }, [development])
74
+
75
+ useEffect(() => {
76
+ // Clear existing timeout
77
+ if (timeoutRef.current) {
78
+ clearTimeout(timeoutRef.current)
79
+ }
80
+
81
+ // Set up debounced compilation
82
+ timeoutRef.current = setTimeout(() => {
83
+ compileMDX(source)
84
+ }, debounceMs)
85
+
86
+ // Cleanup on unmount or source change
87
+ return () => {
88
+ if (timeoutRef.current) {
89
+ clearTimeout(timeoutRef.current)
90
+ }
91
+ }
92
+ }, [source, debounceMs, compileMDX])
93
+
94
+ return { code, error, isCompiling }
95
+ }
@@ -0,0 +1,167 @@
1
+ 'use client'
2
+
3
+ import { useField } from '@payloadcms/ui'
4
+ import { SitePreview } from './SitePreview'
5
+
6
+ export interface PayloadSiteFieldProps {
7
+ /**
8
+ * Optional additional components to merge with default site components
9
+ */
10
+ components?: Record<string, any>
11
+
12
+ /**
13
+ * Whether to show the preview panel (default: true)
14
+ */
15
+ showPreview?: boolean
16
+
17
+ /**
18
+ * Custom label for the preview section
19
+ */
20
+ previewLabel?: string
21
+
22
+ /**
23
+ * Debounce delay in milliseconds (default: 300ms)
24
+ */
25
+ debounceMs?: number
26
+
27
+ /**
28
+ * Position of preview panel: 'below' | 'side' (default: 'below')
29
+ */
30
+ previewPosition?: 'below' | 'side'
31
+ }
32
+
33
+ /**
34
+ * Payload CMS field component with live Site preview.
35
+ *
36
+ * This component integrates SitePreview with Payload's admin UI, providing
37
+ * a preview panel that shows real-time rendering of Site components as users
38
+ * edit MDX content.
39
+ *
40
+ * Features:
41
+ * - Automatically reads field value from Payload form state
42
+ * - Shows preview panel below or beside the input field
43
+ * - Supports all SiteComponents (Hero, Features, Pricing, etc.)
44
+ * - Debounced updates for smooth editing experience
45
+ * - Error handling with user-friendly messages
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * // In your Payload collection config:
50
+ * import { PayloadSiteField } from '@mdxui/payload/site-preview'
51
+ *
52
+ * {
53
+ * name: 'content',
54
+ * type: 'textarea',
55
+ * admin: {
56
+ * components: {
57
+ * Field: PayloadSiteField,
58
+ * },
59
+ * },
60
+ * }
61
+ * ```
62
+ *
63
+ * @example
64
+ * ```tsx
65
+ * // With custom configuration:
66
+ * {
67
+ * name: 'heroSection',
68
+ * type: 'textarea',
69
+ * admin: {
70
+ * components: {
71
+ * Field: (props) => (
72
+ * <PayloadSiteField
73
+ * {...props}
74
+ * previewLabel="Hero Preview"
75
+ * debounceMs={500}
76
+ * />
77
+ * ),
78
+ * },
79
+ * },
80
+ * }
81
+ * ```
82
+ */
83
+ export function PayloadSiteField(props: PayloadSiteFieldProps & { path: string }) {
84
+ const {
85
+ path,
86
+ components,
87
+ showPreview = true,
88
+ previewLabel = 'Live Preview',
89
+ debounceMs = 300,
90
+ previewPosition = 'below',
91
+ } = props
92
+
93
+ const { value } = useField<string>({ path })
94
+
95
+ // Don't render if preview is disabled or no content
96
+ if (!showPreview || !value) {
97
+ return null
98
+ }
99
+
100
+ const previewPanel = (
101
+ <div className="site-preview-panel rounded-lg border border-gray-200 bg-white shadow-sm overflow-hidden">
102
+ <div className="flex items-center justify-between border-b border-gray-200 bg-gray-50 px-4 py-2">
103
+ <h3 className="text-sm font-semibold text-gray-700">
104
+ {previewLabel}
105
+ </h3>
106
+ <div className="flex items-center gap-2">
107
+ <span className="inline-flex items-center rounded-full bg-green-100 px-2 py-1 text-xs font-medium text-green-700">
108
+ <span className="mr-1 h-1.5 w-1.5 rounded-full bg-green-600"></span>
109
+ Live
110
+ </span>
111
+ </div>
112
+ </div>
113
+ <div className="p-4 bg-white">
114
+ <SitePreview
115
+ content={value}
116
+ components={components}
117
+ debounceMs={debounceMs}
118
+ />
119
+ </div>
120
+ </div>
121
+ )
122
+
123
+ // Render based on position
124
+ if (previewPosition === 'side') {
125
+ return (
126
+ <div className="grid grid-cols-2 gap-4">
127
+ <div className="col-span-1">{previewPanel}</div>
128
+ </div>
129
+ )
130
+ }
131
+
132
+ // Default: below
133
+ return <div className="mt-4">{previewPanel}</div>
134
+ }
135
+
136
+ /**
137
+ * Factory function to create a PayloadSiteField component with preset configuration.
138
+ *
139
+ * Useful for creating consistent field components across multiple collections.
140
+ *
141
+ * @example
142
+ * ```tsx
143
+ * // Create a hero field component
144
+ * const HeroSiteField = createPayloadSiteField({
145
+ * previewLabel: 'Hero Section Preview',
146
+ * debounceMs: 500,
147
+ * })
148
+ *
149
+ * // Use in collection config:
150
+ * {
151
+ * name: 'heroContent',
152
+ * type: 'textarea',
153
+ * admin: {
154
+ * components: {
155
+ * Field: HeroSiteField,
156
+ * },
157
+ * },
158
+ * }
159
+ * ```
160
+ */
161
+ export function createPayloadSiteField(
162
+ defaultProps: Omit<PayloadSiteFieldProps, 'path'>
163
+ ) {
164
+ return function PayloadSiteFieldComponent(props: { path: string }) {
165
+ return <PayloadSiteField {...defaultProps} {...props} />
166
+ }
167
+ }
@@ -0,0 +1,194 @@
1
+ 'use client'
2
+
3
+ import * as BeaconComponents from '@mdxui/beacon/components'
4
+ import { MDXLiveRenderer } from 'mdxui'
5
+ import { useMemo } from 'react'
6
+
7
+ const { Hero, Features, Pricing, FAQs, Header, Footer, LandingLayout, Directory } = BeaconComponents
8
+
9
+ /**
10
+ * Site components available for live preview
11
+ * These are the actual beacon implementations, not strictly typed to SiteComponents
12
+ * to allow for flexibility in MDX usage
13
+ */
14
+ const siteComponents = {
15
+ Hero,
16
+ Features,
17
+ Pricing,
18
+ FAQ: FAQs,
19
+ FAQs,
20
+ Header,
21
+ Footer,
22
+ LandingLayout,
23
+ LandingPage: LandingLayout,
24
+ Directory,
25
+ } as const
26
+
27
+ export interface SitePreviewProps {
28
+ /**
29
+ * MDX content to preview
30
+ */
31
+ content: string
32
+
33
+ /**
34
+ * Optional additional components to merge with default site components
35
+ */
36
+ components?: Record<string, any>
37
+
38
+ /**
39
+ * Debounce delay in milliseconds (default: 300ms)
40
+ */
41
+ debounceMs?: number
42
+
43
+ /**
44
+ * Optional className for the preview wrapper
45
+ */
46
+ className?: string
47
+
48
+ /**
49
+ * Show loading indicator
50
+ */
51
+ showLoading?: boolean
52
+
53
+ /**
54
+ * Show error messages
55
+ */
56
+ showErrors?: boolean
57
+ }
58
+
59
+ /**
60
+ * Default loading component for SitePreview
61
+ */
62
+ function SitePreviewLoading() {
63
+ return (
64
+ // biome-ignore lint/a11y/useSemanticElements: This is a loading indicator.
65
+ <div role='status' aria-live='polite' aria-label='Loading site preview' className='flex items-center justify-center p-8 text-gray-500'>
66
+ <div className='flex items-center gap-3'>
67
+ <div className='h-5 w-5 animate-spin rounded-full border-2 border-gray-300 border-t-gray-600' />
68
+ <span>Compiling preview...</span>
69
+ </div>
70
+ </div>
71
+ )
72
+ }
73
+
74
+ /**
75
+ * Default error component for SitePreview
76
+ */
77
+ function SitePreviewError({ error }: { error: Error }) {
78
+ return (
79
+ <div role='alert' aria-live='assertive' className='m-4 rounded-md border border-red-300 bg-red-50 p-4'>
80
+ <div className='flex items-start gap-3'>
81
+ <svg className='h-5 w-5 flex-shrink-0 text-red-600' fill='currentColor' viewBox='0 0 20 20' aria-hidden='true'>
82
+ <path
83
+ fillRule='evenodd'
84
+ d='M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z'
85
+ clipRule='evenodd'
86
+ />
87
+ </svg>
88
+ <div className='flex-1'>
89
+ <h3 className='text-sm font-semibold text-red-800'>Preview Compilation Error</h3>
90
+ <pre className='mt-2 whitespace-pre-wrap break-words text-xs text-red-700'>{error.message}</pre>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ )
95
+ }
96
+
97
+ /**
98
+ * SitePreview - Live preview component for Site templates in Payload admin.
99
+ *
100
+ * This component uses the MDXLiveRenderer to provide real-time rendering of
101
+ * mdxui Site components (Hero, Features, Pricing, Blog, Docs, etc.) as users
102
+ * edit content in Payload CMS.
103
+ *
104
+ * Features:
105
+ * - Real-time MDX compilation and preview
106
+ * - Full SiteComponents support (Hero, Features, Pricing, FAQ, etc.)
107
+ * - Debounced updates for performance
108
+ * - Error handling with user-friendly messages
109
+ * - Loading states
110
+ * - Integrates seamlessly with Payload admin UI
111
+ *
112
+ * @example
113
+ * ```tsx
114
+ * // In a Payload collection field configuration:
115
+ * {
116
+ * name: 'content',
117
+ * type: 'textarea',
118
+ * admin: {
119
+ * components: {
120
+ * Field: (props) => (
121
+ * <div>
122
+ * <TextareaField {...props} />
123
+ * <SitePreview content={props.value} />
124
+ * </div>
125
+ * ),
126
+ * },
127
+ * },
128
+ * }
129
+ * ```
130
+ *
131
+ * @example
132
+ * ```tsx
133
+ * // As a standalone preview panel:
134
+ * <SitePreview
135
+ * content={mdxContent}
136
+ * debounceMs={500}
137
+ * className="border rounded-lg"
138
+ * />
139
+ * ```
140
+ */
141
+ export function SitePreview({
142
+ content,
143
+ components = {},
144
+ debounceMs = 300,
145
+ className = '',
146
+ showLoading = true,
147
+ showErrors = true,
148
+ }: SitePreviewProps) {
149
+ // Merge provided components with default site components
150
+ const mergedComponents = useMemo(() => ({ ...siteComponents, ...components }), [components])
151
+
152
+ return (
153
+ <div className={`site-preview ${className}`}>
154
+ <MDXLiveRenderer
155
+ content={content}
156
+ components={mergedComponents as any}
157
+ debounceMs={debounceMs}
158
+ loadingComponent={showLoading ? <SitePreviewLoading /> : undefined}
159
+ errorComponent={showErrors ? SitePreviewError : undefined}
160
+ />
161
+ </div>
162
+ )
163
+ }
164
+
165
+ /**
166
+ * Factory function to create a SitePreview component with preset configuration.
167
+ *
168
+ * Useful for creating consistent preview components across multiple collections.
169
+ *
170
+ * @example
171
+ * ```tsx
172
+ * // Create a blog preview component
173
+ * const BlogPreview = createSitePreview({
174
+ * debounceMs: 500,
175
+ * className: 'blog-preview',
176
+ * })
177
+ *
178
+ * // Use in collection:
179
+ * {
180
+ * name: 'content',
181
+ * type: 'textarea',
182
+ * admin: {
183
+ * components: {
184
+ * AfterInput: [BlogPreview],
185
+ * },
186
+ * },
187
+ * }
188
+ * ```
189
+ */
190
+ export function createSitePreview(defaultProps: Partial<Omit<SitePreviewProps, 'content'>>) {
191
+ return function SitePreviewComponent({ content }: { content: string }) {
192
+ return <SitePreview content={content} {...defaultProps} />
193
+ }
194
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * SitePreview - Live preview for Site templates in Payload admin
3
+ *
4
+ * This module provides real-time MDX preview components for Site content,
5
+ * using the MDXLiveRenderer with SiteComponents from @mdxui/beacon.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * import { SitePreview, PayloadSiteField } from '@mdxui/payload/site-preview'
10
+ *
11
+ * // As a standalone preview:
12
+ * <SitePreview content={mdxContent} />
13
+ *
14
+ * // As a Payload field component:
15
+ * {
16
+ * name: 'content',
17
+ * type: 'textarea',
18
+ * admin: {
19
+ * components: {
20
+ * Field: PayloadSiteField,
21
+ * },
22
+ * },
23
+ * }
24
+ * ```
25
+ */
26
+
27
+ export { SitePreview, createSitePreview } from './SitePreview'
28
+ export type { SitePreviewProps } from './SitePreview'
29
+
30
+ export { PayloadSiteField, createPayloadSiteField } from './PayloadSiteField'
31
+ export type { PayloadSiteFieldProps } from './PayloadSiteField'
package/tsconfig.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "@mdxui/typescript-config/react-library.json",
3
+ "include": ["."],
4
+ "exclude": ["dist", "build", "node_modules"]
5
+ }