diff --git a/docs/src/components/editable-code-block.tsx b/docs/src/components/editable-code-block.tsx new file mode 100644 index 00000000..d7a93df4 --- /dev/null +++ b/docs/src/components/editable-code-block.tsx @@ -0,0 +1,246 @@ +'use client'; + +import React, { createContext, useContext, useState, ReactNode } from 'react'; +import * as Base from 'fumadocs-ui/components/codeblock'; +import { cn } from 'fumadocs-ui/utils/cn'; + +/** + * Context for managing editable values within code blocks + */ +interface EditableCodeContextValue { + values: Record; + updateValue: (key: string, value: string) => void; +} + +const EditableCodeContext = createContext(null); + +/** + * Hook to access the editable code context + */ +function useEditableCode() { + const context = useContext(EditableCodeContext); + if (!context) { + throw new Error('useEditableCode must be used within EditableCodeBlock'); + } + return context; +} + +/** + * Props for EditableCodeBlock component + */ +interface EditableCodeBlockProps { + /** Programming language for styling */ + lang?: string; + /** Initial values for placeholders */ + defaultValues?: Record; + /** Code content with embedded EditableValue components */ + children: ReactNode; + /** Additional CSS classes */ + className?: string; + /** Title for the code block */ + title?: string; +} + +/** + * Code block component that supports inline editable values + * Uses fumadocs-ui styling with interactive input fields + */ +export function EditableCodeBlock({ + lang = 'python', + defaultValues = {}, + children, + className, + title, +}: EditableCodeBlockProps) { + const [values, setValues] = useState>(defaultValues); + + const updateValue = (key: string, value: string) => { + setValues(prev => ({ ...prev, [key]: value })); + }; + + return ( + + + + + {children} + + + + + ); +} + +/** + * Props for EditableValue component + */ +interface EditableValueProps { + /** Unique identifier for this value */ + placeholder: string; + /** Display width in characters (default: auto) */ + width?: number; + /** Optional default value */ + defaultValue?: string; + /** Input type */ + type?: 'text' | 'password'; +} + +/** + * Inline editable input that blends with code styling + * Appears as an underlined, hoverable value within code + */ +export function EditableValue({ + placeholder, + width: explicitWidth, + defaultValue = '', + type = 'text', +}: EditableValueProps) { + const { values, updateValue } = useEditableCode(); + const value = values[placeholder] ?? defaultValue; + const spanRef = React.useRef(null); + const placeholderSpanRef = React.useRef(null); + const [measuredWidth, setMeasuredWidth] = React.useState(0); + const [placeholderWidth, setPlaceholderWidth] = React.useState(0); + + // Measure the actual text width using a hidden span + React.useEffect(() => { + if (spanRef.current) { + setMeasuredWidth(spanRef.current.offsetWidth); + } + }, [value]); + + // Measure placeholder width once on mount + React.useEffect(() => { + if (placeholderSpanRef.current) { + setPlaceholderWidth(placeholderSpanRef.current.offsetWidth); + } + }, [placeholder]); + + const inputWidth = explicitWidth + ? `${explicitWidth}ch` + : `${Math.max(placeholderWidth, measuredWidth, 50)}px`; + + return ( + + {/* Hidden span to measure current value width */} + + + {/* Hidden span to measure placeholder width */} + + + updateValue(placeholder, e.target.value)} + placeholder={placeholder} + className={cn( + type === 'password' && value && 'text-security-disc' + )} + style={{ + display: 'inline', + width: inputWidth, + verticalAlign: 'baseline', + lineHeight: 'inherit', + fontSize: 'inherit', + fontFamily: 'inherit', + height: 'auto', + padding: 0, + margin: 0, + background: 'transparent', + border: 'none', + borderBottom: '1px dashed rgba(96, 165, 250, 0.5)', + outline: 'none', + color: 'inherit', + }} + /> + + ); +} + +/** + * Container for form inputs outside the code block + */ +export function EditableForm({ + children, + className = '', +}: { + children: ReactNode; + className?: string; +}) { + return ( +
+

Configuration

+ {children} +
+ ); +} + +/** + * Form input for editing values outside code block + */ +interface EditableInputProps { + /** Placeholder key to bind to */ + placeholder: string; + /** Label text */ + label: string; + /** Input type */ + type?: 'text' | 'email' | 'password'; + /** Custom class name */ + className?: string; +} + +export function EditableInput({ + placeholder, + label, + type = 'text', + className = '', +}: EditableInputProps) { + const { values, updateValue } = useEditableCode(); + const value = values[placeholder] || ''; + + return ( +
+ + updateValue(placeholder, e.target.value)} + placeholder={placeholder} + className={cn( + 'w-full px-3 py-2 border rounded-md', + 'focus:outline-none focus:ring-2 focus:ring-blue-500', + 'bg-fd-background border-fd-border' + )} + /> +
+ ); +}