aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
363 lines (360 loc) • 14.7 kB
JavaScript
'use client';
import { jsx, jsxs } from 'react/jsx-runtime';
import { useState } from 'react';
import { GlassCore } from '../../primitives/GlassCore.js';
import '../../primitives/glass/GlassAdvanced.js';
import '../../primitives/OptimizedGlassCore.js';
import '../../primitives/glass/OptimizedGlassAdvanced.js';
import '../../primitives/MotionNative.js';
import '../../primitives/motion/MotionFramer.js';
import { cn } from '../../lib/utilsComprehensive.js';
import { useDragDrop } from './GlassDragDropProvider.js';
const TreeItem = ({
component,
level,
isSelected,
onSelect,
onToggleExpand,
expandedItems,
componentDefinition
}) => {
const {
deleteComponent,
duplicateComponent,
onDragStart,
dragDropState,
componentLibrary
} = useDragDrop();
const [showActions, setShowActions] = useState(false);
const isExpanded = expandedItems.has(component.id);
const hasChildren = component.children.length > 0;
const handleDragStart = e => {
e.stopPropagation();
onDragStart(component, "element");
};
const handleClick = e => {
e.stopPropagation();
onSelect(component.id);
};
const handleToggleExpand = e => {
e.stopPropagation();
if (hasChildren) {
onToggleExpand(component.id);
}
};
const getComponentIcon = type => {
const definition = componentLibrary.find(def => def.type === type);
return definition?.icon || "📦";
};
return jsxs("div", {
"data-glass-component": true,
children: [jsxs("div", {
draggable: true,
onDragStart: handleDragStart,
onClick: handleClick,
onMouseEnter: () => setShowActions(true),
onMouseLeave: () => setShowActions(false),
className: cn("flex items-center gap-2 py-1 px-2 rounded-md cursor-pointer transition-colors relative group", isSelected ? "bg-blue-100 text-blue-900" : "hover:bg-gray-50", dragDropState.draggedItem?.id === component.id && "opacity-50"),
style: {
paddingLeft: `${level * 16 + 8}px`
},
children: [jsx("button", {
onClick: handleToggleExpand,
className: cn("w-4 h-4 flex items-center justify-center text-xs transition-transform glass-focus glass-touch-target", hasChildren ? "text-gray-500 hover:text-gray-700" : "text-transparent", isExpanded ? "rotate-90" : "rotate-0"),
children: hasChildren && "▶"
}), jsx("span", {
className: "glass-text-sm",
children: getComponentIcon(component.type)
}), jsxs("div", {
className: 'glass-flex-1 min-glass-w-0',
children: [jsx("span", {
className: 'glass-text-sm font-medium truncate',
children: componentDefinition?.name || component.type
}), component.props.content && jsxs("span", {
className: 'glass-text-xs glass-text-secondary ml-1 truncate',
children: ["\"", component.props.content.substring(0, 20), component.props.content.length > 20 ? "..." : "", "\""]
})]
}), hasChildren && jsx("span", {
className: "glass-text-xs glass-text-secondary glass-surface-subtle glass-px-1.5 glass-py-0.5 glass-radius",
children: component.children.length
}), jsxs("div", {
className: "glass-flex glass-items-center glass-gap-1",
children: [component.locked && jsx("span", {
className: 'glass-text-xs text-primary',
title: "Locked",
children: "\uD83D\uDD12"
}), !component.props.visible && jsx("span", {
className: "glass-text-xs glass-text-secondary",
title: "Hidden",
children: "\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8\uFE0F"
})]
}), showActions && jsxs("div", {
className: 'glass-flex glass-items-center glass-gap-1 ml-2',
children: [jsx("button", {
onClick: e => {
e.stopPropagation();
duplicateComponent(component.id);
},
className: 'w-6 h-6 glass-flex glass-items-center glass-justify-center glass-text-xs glass-text-secondary hover:text-primary hover:glass-surface-subtle glass-radius glass-focus glass-touch-target',
title: "Duplicate",
children: "\uD83D\uDCCB"
}), jsx("button", {
onClick: e => {
e.stopPropagation();
deleteComponent(component.id);
},
className: 'w-6 h-6 glass-flex glass-items-center glass-justify-center glass-text-xs glass-text-secondary hover:text-primary hover:glass-surface-subtle glass-radius glass-focus glass-touch-target',
title: "Delete",
children: "\uD83D\uDDD1\uFE0F"
})]
})]
}), hasChildren && isExpanded && jsx("div", {
children: component.children.map(child => jsx(TreeItem, {
component: child,
level: level + 1,
isSelected: isSelected,
onSelect: onSelect,
onToggleExpand: onToggleExpand,
expandedItems: expandedItems,
componentDefinition: componentLibrary.find(def => def.type === child.type)
}, child.id))
})]
});
};
const GlassPageStructure = ({
className,
collapsed = false,
onToggleCollapse
}) => {
const {
pageState,
componentLibrary,
selectComponent,
getSelectedComponent,
clearPage,
exportPage,
importPage
} = useDragDrop();
const [expandedItems, setExpandedItems] = useState(new Set());
const [searchQuery, setSearchQuery] = useState("");
const selectedComponent = getSelectedComponent();
const toggleExpanded = id => {
setExpandedItems(prev => {
const newSet = new Set(prev);
if (newSet.has(id)) {
newSet.delete(id);
} else {
newSet.add(id);
}
return newSet;
});
};
const expandAll = () => {
const allIds = new Set();
const addIds = components => {
components.forEach(component => {
allIds.add(component.id);
addIds(component.children);
});
};
addIds(pageState.components);
setExpandedItems(allIds);
};
const collapseAll = () => {
setExpandedItems(new Set());
};
const handleExport = () => {
const data = exportPage();
const blob = new Blob([JSON.stringify(data, null, 2)], {
type: "application/json"
});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "page-structure.json";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
const handleImport = () => {
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
input.onchange = e => {
const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = e => {
try {
const data = JSON.parse(e.target?.result);
importPage(data);
} catch (error) {
alert("Error importing page structure: Invalid JSON file");
}
};
reader.readAsText(file);
}
};
input.click();
};
// Filter components based on search
const filteredComponents = searchQuery ? pageState.components.filter(component => {
const searchInComponent = comp => {
const definition = componentLibrary.find(def => def.type === comp.type);
const name = definition?.name || comp.type;
const content = comp.props.content || "";
const matches = name.toLowerCase().includes(searchQuery.toLowerCase()) || content.toLowerCase().includes(searchQuery.toLowerCase()) || comp.type.toLowerCase().includes(searchQuery.toLowerCase());
return matches || comp.children.some(searchInComponent);
};
return searchInComponent(component);
}) : pageState.components;
if (collapsed) {
return jsx("div", {
className: cn("w-12", className),
children: jsx(GlassCore, {
className: "glass-h-full glass-contrast-guard",
children: jsx("button", {
onClick: onToggleCollapse,
className: 'glass-flex glass-items-center glass-justify-center glass-w-full h-12 glass-text-secondary hover:glass-text-secondary transition-colors glass-focus glass-touch-target glass-focus glass-touch-target glass-contrast-guard',
title: "Expand Page Structure",
children: jsx("div", {
className: "glass-text-lg",
children: "\uD83C\uDF33"
})
})
})
});
}
return jsx("div", {
className: cn("w-80 h-full flex flex-col", className),
children: jsxs(GlassCore, {
className: "glass-h-full glass-flex glass-flex-col glass-contrast-guard",
children: [jsxs("div", {
className: "glass-flex glass-items-center glass-justify-between glass-p-4 glass-border-b glass-border-subtle",
children: [jsx("h2", {
className: 'glass-text-lg font-semibold glass-text-secondary',
children: "Structure"
}), jsx("button", {
onClick: onToggleCollapse,
className: 'glass-p-2 glass-text-secondary hover:glass-text-secondary transition-colors glass-focus glass-touch-target glass-focus glass-touch-target glass-contrast-guard',
title: "Collapse Structure",
children: "\u25C0"
})]
}), jsxs("div", {
className: 'glass-p-4 glass-border-b glass-border-subtle space-y-3',
children: [jsxs("div", {
className: 'relative',
children: [jsx("input", {
type: "text",
placeholder: "Search components...",
value: searchQuery,
onChange: e => setSearchQuery(e.target.value),
className: 'glass-w-full glass-px-3 glass-py-2 pl-10 glass-text-sm glass-border glass-border-subtle glass-radius-lg focus:outline-none focus:ring-2 focus:ring-blue-500'
}), jsx("div", {
className: 'absolute left-3 glass-top-1/2 transform -translate-y-1/2 glass-text-secondary',
children: "\uD83D\uDD0D"
}), searchQuery && jsx("button", {
onClick: () => setSearchQuery(""),
className: 'absolute right-3 glass-top-1/2 transform -translate-y-1/2 glass-text-secondary hover:glass-text-secondary glass-focus glass-touch-target',
children: "\u2715"
})]
}), jsxs("div", {
className: "glass-flex glass-items-center glass-justify-between glass-text-xs",
children: [jsxs("div", {
className: "glass-flex glass-items-center glass-gap-2",
children: [jsx("button", {
onClick: expandAll,
className: 'text-primary hover:text-primary glass-focus glass-touch-target glass-focus glass-touch-target glass-contrast-guard',
children: "Expand All"
}), jsx("span", {
className: 'text-gray-300',
children: "|"
}), jsx("button", {
onClick: collapseAll,
className: 'text-primary hover:text-primary glass-focus glass-touch-target glass-focus glass-touch-target glass-contrast-guard',
children: "Collapse All"
})]
}), jsxs("div", {
className: "glass-flex glass-items-center glass-gap-2",
children: [jsx("button", {
onClick: handleExport,
className: 'text-primary hover:text-primary glass-focus glass-touch-target glass-focus glass-touch-target glass-contrast-guard',
title: "Export Structure",
children: "\uD83D\uDCBE"
}), jsx("button", {
onClick: handleImport,
className: 'text-primary hover:text-primary glass-focus glass-touch-target glass-focus glass-touch-target glass-contrast-guard',
title: "Import Structure",
children: "\uD83D\uDCC1"
}), jsx("button", {
onClick: clearPage,
className: 'text-primary hover:text-primary glass-focus glass-touch-target glass-focus glass-touch-target glass-contrast-guard',
title: "Clear All",
children: "\uD83D\uDDD1\uFE0F"
})]
})]
})]
}), jsx("div", {
className: 'glass-flex-1 overflow-y-auto glass-p-2',
children: filteredComponents.length === 0 ? jsx("div", {
className: "glass-flex glass-items-center glass-justify-center glass-h-full",
children: jsxs("div", {
className: 'text-center',
children: [jsx("div", {
className: 'glass-text-4xl mb-4',
children: "\uD83C\uDF33"
}), jsx("h3", {
className: 'glass-text-lg font-medium glass-text-secondary mb-2',
children: searchQuery ? "No matches found" : "Empty Page"
}), jsx("p", {
className: "glass-text-secondary glass-text-sm",
children: searchQuery ? "Try a different search term" : "Start by adding components to your page"
})]
})
}) : jsx("div", {
className: 'space-y-1',
children: filteredComponents.filter(component => !component.parent) // Only show root components
.map(component => jsx(TreeItem, {
component: component,
level: 0,
isSelected: selectedComponent?.id === component.id,
onSelect: selectComponent,
onToggleExpand: toggleExpanded,
expandedItems: expandedItems,
componentDefinition: componentLibrary.find(def => def.type === component.type)
}, component.id))
})
}), jsx("div", {
className: "glass-p-4 glass-surface-subtle glass-border-t glass-border-subtle",
children: jsxs("div", {
className: 'glass-text-xs glass-text-secondary space-y-1',
children: [jsxs("div", {
className: "glass-flex glass-justify-between",
children: [jsx("span", {
children: "Total Components:"
}), jsx("span", {
children: pageState.components.length
})]
}), jsxs("div", {
className: "glass-flex glass-justify-between",
children: [jsx("span", {
children: "Root Components:"
}), jsx("span", {
children: pageState.components.filter(c => !c.parent).length
})]
}), jsxs("div", {
className: "glass-flex glass-justify-between",
children: [jsx("span", {
children: "Selected:"
}), jsx("span", {
children: selectedComponent ? selectedComponent.type : "None"
})]
})]
})
})]
})
});
};
export { GlassPageStructure };
//# sourceMappingURL=GlassPageStructure.js.map