aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
715 lines (712 loc) • 17.5 kB
JavaScript
'use client';
import { jsx } from 'react/jsx-runtime';
import { forwardRef, useState, useRef, useCallback, createContext, useContext } from 'react';
import { cn } from '../../lib/utilsComprehensive.js';
const DragDropContext = /*#__PURE__*/createContext(null);
// Default component library
const defaultComponentLibrary = [
// Layout Components
{
id: 'container',
type: 'container',
name: 'Container',
icon: '📦',
category: 'layout',
defaultProps: {
padding: '16px',
margin: '0px',
backgroundColor: 'transparent',
borderRadius: '0px',
maxWidth: '100%',
display: 'block'
},
editableProps: {
padding: {
type: 'text',
label: 'Padding'
},
margin: {
type: 'text',
label: 'Margin'
},
backgroundColor: {
type: 'color',
label: 'Background Color'
},
borderRadius: {
type: 'text',
label: 'Border Radius'
},
maxWidth: {
type: 'text',
label: 'Max Width'
},
display: {
type: 'select',
label: 'Display',
options: ['block', 'flex', 'grid', 'inline-block']
}
},
allowChildren: true
}, {
id: 'row',
type: 'row',
name: 'Row',
icon: '↔️',
category: 'layout',
defaultProps: {
gap: '16px',
justifyContent: 'flex-start',
alignItems: 'stretch',
wrap: 'nowrap',
padding: '0px'
},
editableProps: {
gap: {
type: 'text',
label: 'Gap'
},
justifyContent: {
type: 'select',
label: 'Justify Content',
options: ['flex-start', 'center', 'flex-end', 'space-between', 'space-around']
},
alignItems: {
type: 'select',
label: 'Align Items',
options: ['stretch', 'flex-start', 'center', 'flex-end']
},
wrap: {
type: 'select',
label: 'Flex Wrap',
options: ['nowrap', 'wrap', 'wrap-reverse']
},
padding: {
type: 'text',
label: 'Padding'
}
},
allowChildren: true,
acceptsTypes: ['column', 'container', 'text', 'image', 'button']
}, {
id: 'column',
type: 'column',
name: 'Column',
icon: '↕️',
category: 'layout',
defaultProps: {
flex: '1',
padding: '16px',
gap: '8px',
alignItems: 'stretch'
},
editableProps: {
flex: {
type: 'text',
label: 'Flex'
},
padding: {
type: 'text',
label: 'Padding'
},
gap: {
type: 'text',
label: 'Gap'
},
alignItems: {
type: 'select',
label: 'Align Items',
options: ['stretch', 'flex-start', 'center', 'flex-end']
}
},
allowChildren: true
},
// Content Components
{
id: 'text',
type: 'text',
name: 'Text',
icon: '📝',
category: 'content',
defaultProps: {
content: 'Edit this text',
fontSize: '16px',
fontWeight: '400',
color: '#333333',
textAlign: 'left',
lineHeight: '1.5',
fontFamily: 'system-ui, -apple-system, sans-serif'
},
editableProps: {
content: {
type: 'text',
label: 'Content'
},
fontSize: {
type: 'text',
label: 'Font Size'
},
fontWeight: {
type: 'select',
label: 'Font Weight',
options: ['100', '200', '300', '400', '500', '600', '700', '800', '900']
},
color: {
type: 'color',
label: 'Text Color'
},
textAlign: {
type: 'select',
label: 'Text Align',
options: ['left', 'center', 'right', 'justify']
},
lineHeight: {
type: 'text',
label: 'Line Height'
},
fontFamily: {
type: 'text',
label: 'Font Family'
}
},
allowChildren: false
}, {
id: 'heading',
type: 'heading',
name: 'Heading',
icon: '🏷️',
category: 'content',
defaultProps: {
content: 'Heading Text',
level: 'h2',
fontSize: '32px',
fontWeight: '600',
color: 'var(--glass-gray-900)',
textAlign: 'left',
margin: '16px 0'
},
editableProps: {
content: {
type: 'text',
label: 'Content'
},
level: {
type: 'select',
label: 'Heading Level',
options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
},
fontSize: {
type: 'text',
label: 'Font Size'
},
fontWeight: {
type: 'select',
label: 'Font Weight',
options: ['300', '400', '500', '600', '700', '800', '900']
},
color: {
type: 'color',
label: 'Text Color'
},
textAlign: {
type: 'select',
label: 'Text Align',
options: ['left', 'center', 'right']
},
margin: {
type: 'text',
label: 'Margin'
}
},
allowChildren: false
},
// Media Components
{
id: 'image',
type: 'image',
name: 'Image',
icon: '🖼️',
category: 'media',
defaultProps: {
src: 'https://via.placeholder.com/400x300',
alt: 'Placeholder image',
width: '100%',
height: 'auto',
borderRadius: '0px',
objectFit: 'cover'
},
editableProps: {
src: {
type: 'image',
label: 'Image Source'
},
alt: {
type: 'text',
label: 'Alt Text'
},
width: {
type: 'text',
label: 'Width'
},
height: {
type: 'text',
label: 'Height'
},
borderRadius: {
type: 'text',
label: 'Border Radius'
},
objectFit: {
type: 'select',
label: 'Object Fit',
options: ['cover', 'contain', 'fill', 'scale-down', 'none']
}
},
allowChildren: false
},
// Interactive Components
{
id: 'button',
type: 'button',
name: 'Button',
icon: '🔘',
category: 'interactive',
defaultProps: {
text: 'Click me',
variant: 'primary',
size: 'medium',
disabled: false,
href: '',
onClick: 'alert("Button clicked!")'
},
editableProps: {
text: {
type: 'text',
label: 'Button Text'
},
variant: {
type: 'select',
label: 'Variant',
options: ['primary', 'secondary', 'outline', 'ghost']
},
size: {
type: 'select',
label: 'Size',
options: ['small', 'medium', 'large']
},
disabled: {
type: 'boolean',
label: 'Disabled'
},
href: {
type: 'text',
label: 'Link URL'
},
onClick: {
type: 'text',
label: 'On Click (JavaScript)'
}
},
allowChildren: false
},
// Advanced Components
{
id: 'card',
type: 'card',
name: 'Card',
icon: '🃏',
category: 'advanced',
defaultProps: {
padding: '24px',
borderRadius: '12px',
backgroundColor: 'var(--glass-white)',
boxShadow: 'var(--glass-elev-2)',
border: '1px solid var(--glass-gray-200)'
},
editableProps: {
padding: {
type: 'text',
label: 'Padding'
},
borderRadius: {
type: 'text',
label: 'Border Radius'
},
backgroundColor: {
type: 'color',
label: 'Background Color'
},
boxShadow: {
type: 'text',
label: 'Box Shadow'
},
border: {
type: 'text',
label: 'Border'
}
},
allowChildren: true
}];
const DragDropProvider = /*#__PURE__*/forwardRef(({
children,
className,
...props
}, ref) => {
const [dragDropState, setDragDropState] = useState({
isDragging: false,
draggedType: null
});
const [pageState, setPageState] = useState({
components: [],
history: [[]],
historyIndex: 0,
previewMode: false,
activeBreakpoint: 'desktop',
showGrid: false,
snapToGrid: true
});
const componentCounter = useRef(0);
const generateId = useCallback(() => {
return `comp_${Date.now()}_${++componentCounter.current}`;
}, []);
const saveToHistory = useCallback(() => {
setPageState(prev => {
const newHistory = prev.history.slice(0, prev.historyIndex + 1);
newHistory.push([...prev.components]);
return {
...prev,
history: newHistory.slice(-50),
// Keep last 50 states
historyIndex: newHistory.length - 1
};
});
}, []);
const onDragStart = useCallback((item, type) => {
setDragDropState({
isDragging: true,
draggedItem: item,
draggedType: type
});
}, []);
const onDragEnd = useCallback(() => {
setDragDropState({
isDragging: false,
draggedType: null
});
}, []);
const onDragOver = useCallback((targetId, position) => {
setDragDropState(prev => ({
...prev,
dropTarget: targetId,
dropPosition: position
}));
}, []);
const onDrop = useCallback((targetId, position) => {
if (!dragDropState.draggedItem || !dragDropState.draggedType) return;
if (dragDropState.draggedType === 'component') {
addComponent(dragDropState.draggedItem, targetId, position);
} else if (dragDropState.draggedType === 'element') {
const element = dragDropState.draggedItem;
if (targetId && element.id !== targetId) {
moveComponent(element.id, targetId, position || 'inside');
}
}
onDragEnd();
}, [dragDropState]);
const addComponent = useCallback((definition, targetId, position) => {
const newComponent = {
id: generateId(),
type: definition.type,
props: {
...definition.defaultProps
},
children: [],
parent: targetId && position === 'inside' ? targetId : undefined,
order: 0
};
setPageState(prev => {
const components = [...prev.components];
if (targetId && position) {
const targetIndex = components.findIndex(c => c.id === targetId);
if (targetIndex !== -1) {
switch (position) {
case 'before':
components.splice(targetIndex, 0, newComponent);
break;
case 'after':
components.splice(targetIndex + 1, 0, newComponent);
break;
case 'inside':
newComponent.parent = targetId;
const target = components[targetIndex];
if (target) {
target.children.push(newComponent);
}
break;
}
}
} else {
components.push(newComponent);
}
return {
...prev,
components,
selectedComponent: newComponent.id
};
});
saveToHistory();
}, [generateId, saveToHistory]);
const updateComponent = useCallback((id, props) => {
setPageState(prev => {
const updateInArray = components => {
return components.map(component => {
if (component.id === id) {
return {
...component,
props: {
...component.props,
...props
}
};
}
if (component.children.length > 0) {
return {
...component,
children: updateInArray(component.children)
};
}
return component;
});
};
return {
...prev,
components: updateInArray(prev.components)
};
});
}, []);
const deleteComponent = useCallback(id => {
setPageState(prev => {
const removeFromArray = components => {
return components.filter(component => {
if (component.id === id) return false;
component.children = removeFromArray(component.children);
return true;
});
};
return {
...prev,
components: removeFromArray(prev.components),
selectedComponent: prev.selectedComponent === id ? undefined : prev.selectedComponent
};
});
saveToHistory();
}, [saveToHistory]);
const duplicateComponent = useCallback(id => {
setPageState(prev => {
const findComponent = components => {
for (const component of components) {
if (component.id === id) return component;
const found = findComponent(component.children);
if (found) return found;
}
return null;
};
const original = findComponent(prev.components);
if (!original) return prev;
const duplicateRecursive = comp => ({
...comp,
id: generateId(),
children: comp.children.map(duplicateRecursive)
});
const duplicate = duplicateRecursive(original);
return {
...prev,
components: [...prev.components, duplicate],
selectedComponent: duplicate.id
};
});
saveToHistory();
}, [generateId, saveToHistory]);
const moveComponent = useCallback((id, targetId, position) => {
// Implementation for moving components within the tree
saveToHistory();
}, [saveToHistory]);
const selectComponent = useCallback(id => {
setPageState(prev => ({
...prev,
selectedComponent: id
}));
}, []);
const getSelectedComponent = useCallback(() => {
if (!pageState.selectedComponent) return undefined;
const findInArray = components => {
for (const component of components) {
if (component.id === pageState.selectedComponent) return component;
const found = findInArray(component.children);
if (found) return found;
}
return undefined;
};
return findInArray(pageState.components);
}, [pageState.selectedComponent, pageState.components]);
const getComponentById = useCallback(id => {
const findInArray = components => {
for (const component of components) {
if (component.id === id) return component;
const found = findInArray(component.children);
if (found) return found;
}
return undefined;
};
return findInArray(pageState.components);
}, [pageState.components]);
const undo = useCallback(() => {
setPageState(prev => {
if (prev.historyIndex > 0) {
return {
...prev,
components: prev.history[prev.historyIndex - 1],
historyIndex: prev.historyIndex - 1
};
}
return prev;
});
}, []);
const redo = useCallback(() => {
setPageState(prev => {
if (prev.historyIndex < prev.history.length - 1) {
return {
...prev,
components: prev.history[prev.historyIndex + 1],
historyIndex: prev.historyIndex + 1
};
}
return prev;
});
}, []);
const canUndo = useCallback(() => pageState.historyIndex > 0, [pageState.historyIndex]);
const canRedo = useCallback(() => pageState.historyIndex < pageState.history.length - 1, [pageState.historyIndex, pageState.history.length]);
const copyComponent = useCallback(id => {
const component = getComponentById(id);
if (component) {
setPageState(prev => ({
...prev,
clipboardComponent: component
}));
}
}, [getComponentById]);
const pasteComponent = useCallback(targetId => {
if (pageState.clipboardComponent) {
const definition = {
id: pageState.clipboardComponent.type,
type: pageState.clipboardComponent.type,
name: pageState.clipboardComponent.type,
icon: '📋',
category: 'content',
defaultProps: pageState.clipboardComponent.props,
editableProps: {}
};
addComponent(definition, targetId);
}
}, [pageState.clipboardComponent, addComponent]);
const clearPage = useCallback(() => {
setPageState(prev => ({
...prev,
components: [],
selectedComponent: undefined
}));
saveToHistory();
}, [saveToHistory]);
const exportPage = useCallback(() => {
return {
components: pageState.components,
timestamp: new Date().toISOString(),
version: '1.0.0'
};
}, [pageState.components]);
const importPage = useCallback(data => {
if (data.components) {
setPageState(prev => ({
...prev,
components: data.components,
selectedComponent: undefined
}));
saveToHistory();
}
}, [saveToHistory]);
const togglePreviewMode = useCallback(() => {
setPageState(prev => ({
...prev,
previewMode: !prev.previewMode
}));
}, []);
const setActiveBreakpoint = useCallback(breakpoint => {
setPageState(prev => ({
...prev,
activeBreakpoint: breakpoint
}));
}, []);
const toggleGrid = useCallback(() => {
setPageState(prev => ({
...prev,
showGrid: !prev.showGrid
}));
}, []);
const toggleSnapToGrid = useCallback(() => {
setPageState(prev => ({
...prev,
snapToGrid: !prev.snapToGrid
}));
}, []);
const value = {
pageState,
componentLibrary: defaultComponentLibrary,
dragDropState,
onDragStart,
onDragEnd,
onDragOver,
onDrop,
addComponent,
updateComponent,
deleteComponent,
duplicateComponent,
moveComponent,
selectComponent,
getSelectedComponent,
getComponentById,
undo,
redo,
canUndo,
canRedo,
saveToHistory,
copyComponent,
pasteComponent,
clearPage,
exportPage,
importPage,
togglePreviewMode,
setActiveBreakpoint,
toggleGrid,
toggleSnapToGrid
};
return jsx("div", {
ref: ref,
className: cn('glass-dragdrop-provider', className),
"data-glass-component": true,
...props,
children: jsx(DragDropContext.Provider, {
value: value,
children: children
})
});
});
DragDropProvider.displayName = 'GlassDragDropProvider';
const useDragDrop = () => {
const context = useContext(DragDropContext);
if (!context) {
throw new Error('useDragDrop must be used within a DragDropProvider');
}
return context;
};
export { DragDropProvider, DragDropProvider as GlassDragDropProvider, useDragDrop };
//# sourceMappingURL=GlassDragDropProvider.js.map