UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

309 lines (306 loc) 11.5 kB
'use client'; import { jsxs, jsx, Fragment } from 'react/jsx-runtime'; import { cn } from '../../lib/utilsComprehensive.js'; import { useState, useRef, useCallback, useEffect } from 'react'; import '../../primitives/GlassCore.js'; import '../../primitives/glass/GlassAdvanced.js'; import { OptimizedGlassCore } from '../../primitives/OptimizedGlassCore.js'; import '../../primitives/glass/OptimizedGlassAdvanced.js'; import '../../primitives/MotionNative.js'; import '../../primitives/motion/MotionFramer.js'; // Simple syntax highlighting patterns for common languages const syntaxPatterns = { javascript: { keywords: /\b(const|let|var|function|return|if|else|for|while|do|switch|case|default|try|catch|finally|throw|new|class|extends|super|import|export|from|async|await|yield|typeof|instanceof|in|of)\b/g, strings: /(["'`])(.*?)\1/g, comments: /(\/\/.*$|\/\*[\s\S]*?\*\/)/gm, numbers: /\b\d+(\.\d+)?\b/g, functions: /\b\w+(?=\()/g }, typescript: { keywords: /\b(const|let|var|function|return|if|else|for|while|do|switch|case|default|try|catch|finally|throw|new|class|extends|super|import|export|from|async|await|yield|typeof|instanceof|in|of|interface|type|enum|namespace|module)\b/g, strings: /(["'`])(.*?)\1/g, comments: /(\/\/.*$|\/\*[\s\S]*?\*\/)/gm, numbers: /\b\d+(\.\d+)?\b/g, types: /\b[A-Z]\w*\b/g }, python: { keywords: /\b(def|class|if|elif|else|for|while|try|except|finally|with|as|import|from|return|yield|lambda|and|or|not|in|is|None|True|False)\b/g, strings: /(["'`])(.*?)\1/g, comments: /(#.*$)/gm, numbers: /\b\d+(\.\d+)?\b/g, functions: /\b\w+(?=\()/g }, css: { properties: /([a-z-]+)(?=\s*:)/g, values: /:(.+?);/g, selectors: /([.#]?[\w-]+)(?=\s*\{)/g, comments: /(\/\*[\s\S]*?\*\/)/g }, json: { keys: /"([^"]+)":/g, strings: /"([^"]+)"/g, numbers: /\b\d+(\.\d+)?\b/g } }; const GlassCodeEditor = ({ value = '', language = 'plaintext', readOnly = false, placeholder = 'Enter your code here...', fontSize = 14, lineNumbers = true, minimap = false, wordWrap = true, tabSize = 2, autoComplete = false, theme = 'dark', maxHeight = '400px', minHeight = '200px', className = '', onChange, onFocus, onBlur, onMount }) => { const [internalValue, setInternalValue] = useState(value); const [isFocused, setIsFocused] = useState(false); const [cursorPosition, setCursorPosition] = useState({ line: 1, column: 1 }); const textareaRef = useRef(null); const preRef = useRef(null); const currentValue = value !== undefined ? value : internalValue; // Handle value changes const handleChange = useCallback(e => { const newValue = e.target.value; setInternalValue(newValue); onChange?.(newValue); }, [onChange]); // Handle focus/blur const handleFocus = useCallback(() => { setIsFocused(true); onFocus?.(); }, [onFocus]); const handleBlur = useCallback(() => { setIsFocused(false); onBlur?.(); }, [onBlur]); // Syntax highlighting function const highlightCode = useCallback((code, lang) => { if (lang === 'plaintext') return code; const patterns = syntaxPatterns[lang]; if (!patterns) return code; let highlighted = code; // Apply highlighting based on patterns Object.entries(patterns).forEach(([type, pattern]) => { highlighted = highlighted.replace(pattern, (match, ...groups) => { const colorClass = getHighlightColor(type); return `<span class="${colorClass}">${match}</span>`; }); }); return highlighted; }, []); // Get highlight color classes const getHighlightColor = type => { const colors = { keywords: 'glass-text-blue-400', strings: 'glass-text-green-400', comments: 'glass-text-secondary', numbers: 'glass-text-purple-400', functions: 'glass-text-yellow-400', types: 'glass-text-cyan-400', properties: 'glass-text-blue-300', values: 'glass-text-green-300', selectors: 'glass-text-orange-400', keys: 'glass-text-blue-300' }; return colors[type] || 'glass-text-primary'; }; // Handle keyboard shortcuts const handleKeyDown = useCallback(e => { if (e.key === 'Tab' && !readOnly) { e.preventDefault(); const textarea = textareaRef.current; if (!textarea) return; const start = textarea.selectionStart; const end = textarea.selectionEnd; const spaces = ' '.repeat(tabSize); const newValue = currentValue.substring(0, start) + spaces + currentValue.substring(end); setInternalValue(newValue); onChange?.(newValue); // Set cursor position after the inserted spaces setTimeout(() => { textarea.selectionStart = textarea.selectionEnd = start + tabSize; }, 0); } }, [currentValue, tabSize, readOnly, onChange]); // Update cursor position const updateCursorPosition = useCallback(() => { const textarea = textareaRef.current; if (!textarea) return; const text = textarea.value; const cursorPos = textarea.selectionStart; let line = 1; let column = 1; for (let i = 0; i < cursorPos; i++) { if (text[i] === '\n') { line++; column = 1; } else { column++; } } setCursorPosition({ line, column }); }, []); // Sync scroll positions useEffect(() => { const textarea = textareaRef.current; const pre = preRef.current; if (!textarea || !pre) return; const syncScroll = () => { pre.scrollTop = textarea.scrollTop; pre.scrollLeft = textarea.scrollLeft; }; textarea.addEventListener('scroll', syncScroll); return () => textarea.removeEventListener('scroll', syncScroll); }, []); // Handle mount useEffect(() => { if (textareaRef.current && onMount) { onMount(textareaRef.current); } }, [onMount]); const highlightedCode = highlightCode(currentValue, language); const lines = currentValue.split('\n'); return jsxs(OptimizedGlassCore, { "data-glass-component": true, className: cn('glass-relative glass-overflow-hidden', className), style: { maxHeight, minHeight }, blur: "medium", elevation: 'level1', children: [jsxs("div", { className: cn('glass-flex glass-items-center glass-justify-between glass-p-3 glass-border-b glass-border-white-10'), children: [jsxs("div", { className: cn('glass-flex glass-items-center glass-gap-4'), children: [jsx("span", { className: cn('glass-text-sm glass-text-primary-70 glass-font-medium'), children: language.toUpperCase() }), lineNumbers && jsxs("span", { className: cn('glass-text-xs glass-text-primary-50'), children: ["Ln ", cursorPosition.line, ", Col ", cursorPosition.column] })] }), jsx("div", { className: cn('glass-flex glass-items-center glass-gap-2'), children: !readOnly && jsxs(Fragment, { children: [jsx("button", { className: cn('glass-px-2 glass-py-1 glass-text-xs glass-text-primary-70 glass-hover-text-primary glass-hover-surface-subtle glass-radius-md glass-transition-colors'), onClick: e => { const textarea = textareaRef.current; if (textarea) { textarea.value = ''; setInternalValue(''); onChange?.(''); } }, children: "Clear" }), jsx("button", { className: cn('glass-px-2 glass-py-1 glass-text-xs glass-text-primary-70 glass-hover-text-primary glass-hover-surface-subtle glass-radius-md glass-transition-colors'), onClick: e => { navigator.clipboard?.writeText(currentValue); }, children: "Copy" })] }) })] }), jsxs("div", { className: cn('glass-relative'), children: [jsx("pre", { ref: preRef, className: cn('glass-absolute glass-inset-0 glass-p-4 glass-font-mono glass-text-sm glass-overflow-auto glass-pointer-events-none glass-whitespace-pre-wrap glass-break-words', wordWrap ? 'glass-break-words' : 'glass-whitespace-pre'), style: { fontSize, lineHeight: '1.5' }, children: jsx("code", { dangerouslySetInnerHTML: { __html: highlightedCode || placeholder }, className: cn(currentValue ? '' : 'glass-text-primary-30') }) }), lineNumbers && jsx("div", { className: cn('glass-absolute glass-left-0 glass-top-0 glass-bottom-0 glass-w-12 glass-surface-black-20 glass-border-r glass-border-white-10 glass-p-4 glass-text-right glass-text-primary-50 glass-text-sm glass-font-mono glass-select-none'), children: lines.map((_, index) => jsx("div", { className: cn('glass-leading-6'), children: index + 1 }, index)) }), jsx("textarea", { ref: textareaRef, value: currentValue, onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown, onSelect: updateCursorPosition, onClick: updateCursorPosition, readOnly: readOnly, placeholder: placeholder, className: cn('glass-w-full glass-p-4 glass-font-mono glass-text-sm glass-bg-transparent glass-text-transparent glass-caret-white glass-outline-none glass-resize-none glass-overflow-auto', wordWrap ? 'glass-break-words' : 'glass-whitespace-pre', lineNumbers ? 'glass-pl-16' : ''), style: { fontSize, lineHeight: '1.5', minHeight: '100%', height: '100%' }, spellCheck: false, autoComplete: autoComplete ? 'on' : 'off', autoCorrect: "off", autoCapitalize: "off" })] })] }); }; // Compound component for code editor with file management const GlassCodeEditorWithFiles = ({ files, onFileChange, className = '' }) => { const [activeFile, setActiveFile] = useState(files[0]?.name || ''); const currentFile = files.find(f => f.name === activeFile) || files[0]; return jsxs("div", { className: cn('glass-grid glass-grid-cols-4 glass-gap-4', className), children: [jsxs(OptimizedGlassCore, { className: cn('glass-col-span-1 glass-p-4'), blur: "medium", elevation: 'level1', children: [jsx("h3", { className: cn('glass-text-sm glass-font-semibold glass-text-primary glass-mb-4'), children: "Files" }), jsx("div", { className: cn('glass-gap-2'), children: files.map(file => jsx("button", { onClick: e => setActiveFile(file.name), className: cn('glass-w-full glass-text-left glass-px-3 glass-py-2 glass-radius-md glass-text-sm glass-transition-colors', activeFile === file.name ? 'glass-surface-elevated glass-text-primary' : 'glass-text-primary-70 glass-hover-text-primary glass-hover-surface-subtle'), children: file.name }, file.name)) })] }), jsx("div", { className: cn('glass-col-span-3'), children: currentFile && jsx(GlassCodeEditor, { value: currentFile.content, language: currentFile.language, onChange: content => onFileChange?.(currentFile.name, content) }) })] }); }; export { GlassCodeEditor, GlassCodeEditorWithFiles }; //# sourceMappingURL=GlassCodeEditor.js.map