@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,216 @@
1
+ 'use client'
2
+
3
+ import React, { useState, useRef, useEffect, useCallback } from 'react'
4
+
5
+ interface TerminalProps {
6
+ /** Initial working directory */
7
+ cwd?: string
8
+ /** API endpoint base URL */
9
+ apiUrl?: string
10
+ /** Session ID for persistent sessions */
11
+ sessionId?: string
12
+ /** Height of the terminal */
13
+ height?: string | number
14
+ /** Called when command is executed */
15
+ onCommand?: (command: string, output: string) => void
16
+ }
17
+
18
+ interface TerminalLine {
19
+ type: 'input' | 'output' | 'error'
20
+ content: string
21
+ timestamp: Date
22
+ }
23
+
24
+ export const Terminal: React.FC<TerminalProps> = ({
25
+ cwd = '~',
26
+ apiUrl = 'https://api.sb',
27
+ sessionId,
28
+ height = 400,
29
+ onCommand,
30
+ }) => {
31
+ const [lines, setLines] = useState<TerminalLine[]>([
32
+ { type: 'output', content: `Connected to sandbox. Type 'help' for commands.`, timestamp: new Date() },
33
+ ])
34
+ const [input, setInput] = useState('')
35
+ const [currentDir, _setCurrentDir] = useState(cwd)
36
+ const [isLoading, setIsLoading] = useState(false)
37
+ const [history, setHistory] = useState<string[]>([])
38
+ const [historyIndex, setHistoryIndex] = useState(-1)
39
+ const inputRef = useRef<HTMLInputElement>(null)
40
+ const containerRef = useRef<HTMLDivElement>(null)
41
+ const [currentSessionId, setCurrentSessionId] = useState(sessionId)
42
+
43
+ // Auto-scroll to bottom
44
+ useEffect(() => {
45
+ if (containerRef.current) {
46
+ containerRef.current.scrollTop = containerRef.current.scrollHeight
47
+ }
48
+ }, [lines])
49
+
50
+ // Focus input on click
51
+ const handleContainerClick = useCallback(() => {
52
+ inputRef.current?.focus()
53
+ }, [])
54
+
55
+ const executeCommand = useCallback(async (command: string) => {
56
+ if (!command.trim()) return
57
+
58
+ // Add to history
59
+ setHistory(prev => [...prev, command])
60
+ setHistoryIndex(-1)
61
+
62
+ // Show input line
63
+ setLines(prev => [...prev, { type: 'input', content: `${currentDir} $ ${command}`, timestamp: new Date() }])
64
+ setInput('')
65
+ setIsLoading(true)
66
+
67
+ try {
68
+ // Handle built-in commands
69
+ if (command === 'clear') {
70
+ setLines([])
71
+ setIsLoading(false)
72
+ return
73
+ }
74
+
75
+ if (command === 'help') {
76
+ setLines(prev => [...prev, {
77
+ type: 'output',
78
+ content: `Available commands:
79
+ clear - Clear terminal
80
+ help - Show this help
81
+ exit - Close session
82
+
83
+ All other commands are executed in the sandbox environment.`,
84
+ timestamp: new Date(),
85
+ }])
86
+ setIsLoading(false)
87
+ return
88
+ }
89
+
90
+ // Execute via API
91
+ const response = await fetch(`${apiUrl}/rpc`, {
92
+ method: 'POST',
93
+ headers: { 'Content-Type': 'application/json' },
94
+ body: JSON.stringify({
95
+ method: 'executeSandbox',
96
+ params: {
97
+ sessionId: currentSessionId,
98
+ command,
99
+ cwd: currentDir,
100
+ },
101
+ }),
102
+ })
103
+
104
+ const result = await response.json()
105
+
106
+ if (result.error) {
107
+ setLines(prev => [...prev, { type: 'error', content: result.error.message || 'Command failed', timestamp: new Date() }])
108
+ } else {
109
+ const output = result.result?.stdout || result.result?.stderr || ''
110
+ if (output) {
111
+ setLines(prev => [...prev, { type: 'output', content: output, timestamp: new Date() }])
112
+ }
113
+
114
+ // Update session ID if new session was created
115
+ if (result.result?.sessionId && !currentSessionId) {
116
+ setCurrentSessionId(result.result.sessionId)
117
+ }
118
+ }
119
+
120
+ onCommand?.(command, result.result?.stdout || '')
121
+ } catch (error) {
122
+ setLines(prev => [...prev, {
123
+ type: 'error',
124
+ content: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
125
+ timestamp: new Date()
126
+ }])
127
+ } finally {
128
+ setIsLoading(false)
129
+ }
130
+ }, [apiUrl, currentDir, currentSessionId, onCommand])
131
+
132
+ const handleKeyDown = useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
133
+ if (e.key === 'Enter' && !isLoading) {
134
+ executeCommand(input)
135
+ } else if (e.key === 'ArrowUp') {
136
+ e.preventDefault()
137
+ if (history.length > 0) {
138
+ const newIndex = historyIndex === -1 ? history.length - 1 : Math.max(0, historyIndex - 1)
139
+ setHistoryIndex(newIndex)
140
+ setInput(history[newIndex])
141
+ }
142
+ } else if (e.key === 'ArrowDown') {
143
+ e.preventDefault()
144
+ if (historyIndex !== -1) {
145
+ const newIndex = historyIndex + 1
146
+ if (newIndex >= history.length) {
147
+ setHistoryIndex(-1)
148
+ setInput('')
149
+ } else {
150
+ setHistoryIndex(newIndex)
151
+ setInput(history[newIndex])
152
+ }
153
+ }
154
+ } else if (e.key === 'c' && e.ctrlKey) {
155
+ setInput('')
156
+ setLines(prev => [...prev, { type: 'input', content: `${currentDir} $ ^C`, timestamp: new Date() }])
157
+ }
158
+ }, [input, isLoading, executeCommand, history, historyIndex, currentDir])
159
+
160
+ return (
161
+ <div
162
+ ref={containerRef}
163
+ onClick={handleContainerClick}
164
+ style={{
165
+ backgroundColor: '#1e1e1e',
166
+ color: '#d4d4d4',
167
+ fontFamily: 'Monaco, Menlo, "Ubuntu Mono", monospace',
168
+ fontSize: '13px',
169
+ lineHeight: '1.5',
170
+ padding: '12px',
171
+ borderRadius: '8px',
172
+ height: typeof height === 'number' ? `${height}px` : height,
173
+ overflowY: 'auto',
174
+ cursor: 'text',
175
+ }}
176
+ >
177
+ {lines.map((line, i) => (
178
+ <div
179
+ key={i}
180
+ style={{
181
+ color: line.type === 'error' ? '#f14c4c' : line.type === 'input' ? '#569cd6' : '#d4d4d4',
182
+ whiteSpace: 'pre-wrap',
183
+ wordBreak: 'break-all',
184
+ }}
185
+ >
186
+ {line.content}
187
+ </div>
188
+ ))}
189
+ <div style={{ display: 'flex', alignItems: 'center' }}>
190
+ <span style={{ color: '#569cd6' }}>{currentDir} $&nbsp;</span>
191
+ <input
192
+ ref={inputRef}
193
+ type="text"
194
+ value={input}
195
+ onChange={(e) => setInput(e.target.value)}
196
+ onKeyDown={handleKeyDown}
197
+ disabled={isLoading}
198
+ autoFocus
199
+ style={{
200
+ backgroundColor: 'transparent',
201
+ border: 'none',
202
+ color: '#d4d4d4',
203
+ fontFamily: 'inherit',
204
+ fontSize: 'inherit',
205
+ outline: 'none',
206
+ flex: 1,
207
+ caretColor: '#d4d4d4',
208
+ }}
209
+ />
210
+ {isLoading && <span style={{ color: '#6a9955' }}>⏳</span>}
211
+ </div>
212
+ </div>
213
+ )
214
+ }
215
+
216
+ export default Terminal
@@ -0,0 +1,4 @@
1
+ // Dev tools components for Payload CMS admin
2
+ export { Terminal } from './Terminal'
3
+ export { Browser } from './Browser'
4
+ export { DevTools } from './DevTools'
package/src/index.ts ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @mdxui/payload - Payload CMS admin components and field UI
3
+ *
4
+ * This package provides components for extending Payload CMS admin UI:
5
+ * - Authentication wrappers and auto-login
6
+ * - WorkOS AuthKit integration widgets
7
+ * - Dev tools (Terminal, Browser)
8
+ * - MDX preview components
9
+ * - Site and App preview components
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * // Auth components
14
+ * import { AdminAuthWrapper, useAuth } from '@mdxui/payload/auth'
15
+ *
16
+ * // WorkOS AuthKit widgets
17
+ * import { UserProfile, UserSecurity, ApiKeys } from '@mdxui/payload/authkit'
18
+ *
19
+ * // Dev tools
20
+ * import { DevTools, Terminal, Browser } from '@mdxui/payload/dev-tools'
21
+ *
22
+ * // MDX preview
23
+ * import { MDXPreview, MDXProvider } from '@mdxui/payload/mdx-preview'
24
+ *
25
+ * // Site preview (uses @mdxui/beacon components)
26
+ * import { SitePreview, PayloadSiteField } from '@mdxui/payload/site-preview'
27
+ *
28
+ * // App preview (uses @mdxui/cockpit components)
29
+ * import { AppPreview, createAppPreviewField } from '@mdxui/payload/app-preview'
30
+ * ```
31
+ */
32
+
33
+ // Re-export from submodules for convenience
34
+ export * from './auth'
35
+ export * from './authkit'
36
+ export * from './dev-tools'
37
+ export * from './mdx-preview'
38
+ export * from './site-preview'
39
+ export * from './app-preview'
@@ -0,0 +1,183 @@
1
+ 'use client'
2
+
3
+ import React, { useMemo } from 'react'
4
+ import { ErrorBoundary } from 'react-error-boundary'
5
+ import { useMDXCompiler } from './useMDXCompiler'
6
+ import { useMDXComponents } from './MDXProvider'
7
+ import type { MDXComponents } from './MDXProvider'
8
+
9
+ export interface MDXPreviewProps {
10
+ /** MDX source code to render */
11
+ source: string
12
+ /** Component overrides (merged with provider components) */
13
+ components?: MDXComponents
14
+ /** Debounce delay in milliseconds (default: 300) */
15
+ debounceMs?: number
16
+ /** Custom error fallback component */
17
+ errorFallback?: React.ComponentType<{ error: Error; resetErrorBoundary: () => void }>
18
+ /** Custom loading component */
19
+ loadingComponent?: React.ReactNode
20
+ /** Whether to show compilation status (default: false) */
21
+ showStatus?: boolean
22
+ /** Development mode (default: true) */
23
+ development?: boolean
24
+ }
25
+
26
+ /**
27
+ * Default error fallback component
28
+ */
29
+ function DefaultErrorFallback({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }) {
30
+ return (
31
+ <div
32
+ style={{
33
+ padding: '1rem',
34
+ backgroundColor: '#fee',
35
+ border: '1px solid #c00',
36
+ borderRadius: '0.25rem',
37
+ fontFamily: 'monospace',
38
+ }}
39
+ >
40
+ <h3 style={{ margin: '0 0 0.5rem 0', color: '#c00' }}>
41
+ MDX Compilation Error
42
+ </h3>
43
+ <pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
44
+ {error.message}
45
+ </pre>
46
+ <button
47
+ onClick={resetErrorBoundary}
48
+ style={{
49
+ marginTop: '0.5rem',
50
+ padding: '0.25rem 0.5rem',
51
+ backgroundColor: '#fff',
52
+ border: '1px solid #c00',
53
+ borderRadius: '0.25rem',
54
+ cursor: 'pointer',
55
+ }}
56
+ >
57
+ Retry
58
+ </button>
59
+ </div>
60
+ )
61
+ }
62
+
63
+ /**
64
+ * Default loading component
65
+ */
66
+ function DefaultLoadingComponent() {
67
+ return (
68
+ <div
69
+ style={{
70
+ padding: '1rem',
71
+ color: '#666',
72
+ fontStyle: 'italic',
73
+ }}
74
+ >
75
+ Compiling MDX...
76
+ </div>
77
+ )
78
+ }
79
+
80
+ /**
81
+ * MDX live preview component that renders MDX source in real-time.
82
+ *
83
+ * @example
84
+ * ```tsx
85
+ * import { MDXPreview } from '@mdxui/payload/mdx-preview'
86
+ * import { Hero, Features } from '@mdxui/beacon'
87
+ *
88
+ * <MDXPreview
89
+ * source={mdxSource}
90
+ * components={{ Hero, Features }}
91
+ * debounceMs={300}
92
+ * />
93
+ * ```
94
+ */
95
+ export function MDXPreview({
96
+ source,
97
+ components: propsComponents = {},
98
+ debounceMs = 300,
99
+ errorFallback: ErrorFallback = DefaultErrorFallback,
100
+ loadingComponent = <DefaultLoadingComponent />,
101
+ showStatus = false,
102
+ development = true,
103
+ }: MDXPreviewProps) {
104
+ const contextComponents = useMDXComponents()
105
+ const { code, error: compileError, isCompiling } = useMDXCompiler({
106
+ source,
107
+ debounceMs,
108
+ development,
109
+ })
110
+
111
+ // Merge components from context and props
112
+ const mergedComponents = useMemo(
113
+ () => ({ ...contextComponents, ...propsComponents }),
114
+ [contextComponents, propsComponents]
115
+ )
116
+
117
+ // Render compilation error
118
+ if (compileError) {
119
+ return <ErrorFallback error={compileError} resetErrorBoundary={() => {}} />
120
+ }
121
+
122
+ // Render loading state
123
+ if (isCompiling) {
124
+ return <>{loadingComponent}</>
125
+ }
126
+
127
+ // Render empty state
128
+ if (!code) {
129
+ return null
130
+ }
131
+
132
+ // Render compiled MDX
133
+ return (
134
+ <div>
135
+ {showStatus && (
136
+ <div
137
+ style={{
138
+ padding: '0.25rem 0.5rem',
139
+ fontSize: '0.75rem',
140
+ color: '#666',
141
+ borderBottom: '1px solid #e0e0e0',
142
+ }}
143
+ >
144
+ {isCompiling ? 'Compiling...' : 'Ready'}
145
+ </div>
146
+ )}
147
+ <ErrorBoundary FallbackComponent={ErrorFallback}>
148
+ <MDXContent code={code} components={mergedComponents} />
149
+ </ErrorBoundary>
150
+ </div>
151
+ )
152
+ }
153
+
154
+ /**
155
+ * Component that executes and renders compiled MDX code
156
+ */
157
+ function MDXContent({ code, components }: { code: string; components: MDXComponents }) {
158
+ const Content = useMemo(() => {
159
+ try {
160
+ // Create a function from the compiled code
161
+ // The compiled code expects certain globals to be available
162
+ const fn = new Function(
163
+ 'React',
164
+ ...Object.keys(components),
165
+ code
166
+ )
167
+
168
+ // Execute the function with React and components as arguments
169
+ const { default: MDXComponent } = fn(React, ...Object.values(components))
170
+
171
+ return MDXComponent
172
+ } catch (error) {
173
+ console.error('Error executing MDX code:', error)
174
+ throw error
175
+ }
176
+ }, [code, components])
177
+
178
+ if (!Content) {
179
+ return null
180
+ }
181
+
182
+ return <Content components={components} />
183
+ }
@@ -0,0 +1,62 @@
1
+ 'use client'
2
+
3
+ import React, { createContext, useContext, useMemo } from 'react'
4
+ import { MDXProvider as BaseMDXProvider } from '@mdx-js/react'
5
+ import type { SiteComponents, AppComponents } from 'mdxui'
6
+
7
+ /**
8
+ * Combined components type that accepts both Site and App components
9
+ */
10
+ export type MDXComponents = Partial<SiteComponents & AppComponents> & {
11
+ // Allow additional custom components
12
+ [key: string]: React.ComponentType<any>
13
+ }
14
+
15
+ interface MDXContextValue {
16
+ components: MDXComponents
17
+ }
18
+
19
+ const MDXContext = createContext<MDXContextValue | null>(null)
20
+
21
+ export interface MDXProviderProps {
22
+ /** Component overrides for MDX rendering */
23
+ components?: MDXComponents
24
+ /** Child elements */
25
+ children: React.ReactNode
26
+ }
27
+
28
+ /**
29
+ * Provider for MDX components.
30
+ * Wraps the application and provides component overrides to MDX content.
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * import { Hero, Features } from '@mdxui/beacon'
35
+ *
36
+ * <MDXProvider components={{ Hero, Features }}>
37
+ * <MDXPreview source={mdxSource} />
38
+ * </MDXProvider>
39
+ * ```
40
+ */
41
+ export function MDXProvider({ components = {}, children }: MDXProviderProps) {
42
+ const value = useMemo(() => ({ components }), [components])
43
+
44
+ return (
45
+ <MDXContext.Provider value={value}>
46
+ <BaseMDXProvider components={components}>
47
+ {children}
48
+ </BaseMDXProvider>
49
+ </MDXContext.Provider>
50
+ )
51
+ }
52
+
53
+ /**
54
+ * Hook to access MDX components from context.
55
+ */
56
+ export function useMDXComponents(): MDXComponents {
57
+ const context = useContext(MDXContext)
58
+ if (!context) {
59
+ return {}
60
+ }
61
+ return context.components
62
+ }
@@ -0,0 +1,120 @@
1
+ 'use client'
2
+
3
+ import { useField } from '@payloadcms/ui'
4
+ import { MDXPreview } from './MDXPreview'
5
+ import type { MDXComponents } from './MDXProvider'
6
+
7
+ export interface PayloadMDXFieldProps {
8
+ /** Field path in Payload document */
9
+ path: string
10
+ /** Component overrides for MDX rendering */
11
+ components?: MDXComponents
12
+ /** Whether to show the preview (default: true) */
13
+ showPreview?: boolean
14
+ /** Custom label for the preview section */
15
+ previewLabel?: string
16
+ }
17
+
18
+ /**
19
+ * Payload CMS custom field component with live MDX preview.
20
+ *
21
+ * Use this as a custom component in your Payload field configuration.
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * // In your Payload collection config:
26
+ * {
27
+ * name: 'content',
28
+ * type: 'textarea',
29
+ * admin: {
30
+ * components: {
31
+ * Field: (props) => (
32
+ * <PayloadMDXField
33
+ * {...props}
34
+ * components={{ Hero, Features }}
35
+ * />
36
+ * ),
37
+ * },
38
+ * },
39
+ * }
40
+ * ```
41
+ */
42
+ export function PayloadMDXField({
43
+ path,
44
+ components,
45
+ showPreview = true,
46
+ previewLabel = 'Preview',
47
+ }: PayloadMDXFieldProps) {
48
+ const { value } = useField<string>({ path })
49
+
50
+ if (!showPreview || !value) {
51
+ return null
52
+ }
53
+
54
+ return (
55
+ <div
56
+ style={{
57
+ marginTop: '1rem',
58
+ border: '1px solid #e0e0e0',
59
+ borderRadius: '0.25rem',
60
+ overflow: 'hidden',
61
+ }}
62
+ >
63
+ <div
64
+ style={{
65
+ padding: '0.5rem 1rem',
66
+ backgroundColor: '#f5f5f5',
67
+ borderBottom: '1px solid #e0e0e0',
68
+ fontWeight: 600,
69
+ fontSize: '0.875rem',
70
+ }}
71
+ >
72
+ {previewLabel}
73
+ </div>
74
+ <div
75
+ style={{
76
+ padding: '1rem',
77
+ backgroundColor: '#fff',
78
+ }}
79
+ >
80
+ <MDXPreview
81
+ source={value}
82
+ components={components}
83
+ showStatus={true}
84
+ />
85
+ </div>
86
+ </div>
87
+ )
88
+ }
89
+
90
+ /**
91
+ * Factory function to create a Payload field component with preset components.
92
+ *
93
+ * @example
94
+ * ```tsx
95
+ * import { Hero, Features } from '@mdxui/beacon'
96
+ *
97
+ * const SiteMDXField = createPayloadMDXField({
98
+ * components: { Hero, Features },
99
+ * previewLabel: 'Site Preview',
100
+ * })
101
+ *
102
+ * // Use in collection config:
103
+ * {
104
+ * name: 'content',
105
+ * type: 'textarea',
106
+ * admin: {
107
+ * components: {
108
+ * Field: SiteMDXField,
109
+ * },
110
+ * },
111
+ * }
112
+ * ```
113
+ */
114
+ export function createPayloadMDXField(
115
+ defaultProps: Omit<PayloadMDXFieldProps, 'path'>
116
+ ) {
117
+ return function PayloadMDXFieldComponent(props: { path: string }) {
118
+ return <PayloadMDXField {...defaultProps} {...props} />
119
+ }
120
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * MDX Live Preview Components
3
+ *
4
+ * Real-time MDX rendering for Payload CMS admin panel.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * import { MDXProvider, MDXPreview } from '@mdxui/payload/mdx-preview'
9
+ * import { Hero, Features } from '@mdxui/beacon'
10
+ *
11
+ * // Wrap your app with MDXProvider
12
+ * <MDXProvider components={{ Hero, Features }}>
13
+ * <MDXPreview source={mdxSource} />
14
+ * </MDXProvider>
15
+ * ```
16
+ */
17
+
18
+ export { MDXPreview } from './MDXPreview'
19
+ export type { MDXPreviewProps } from './MDXPreview'
20
+
21
+ export { MDXProvider, useMDXComponents } from './MDXProvider'
22
+ export type { MDXProviderProps, MDXComponents } from './MDXProvider'
23
+
24
+ export { useMDXCompiler } from './useMDXCompiler'
25
+ export type { MDXCompilerOptions, MDXCompilerResult } from './useMDXCompiler'
26
+
27
+ export { PayloadMDXField, createPayloadMDXField } from './PayloadMDXField'
28
+ export type { PayloadMDXFieldProps } from './PayloadMDXField'