aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
309 lines (306 loc) • 11.5 kB
JavaScript
'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