mono/packages/ui/src/modules/layout/LayoutContainerView.tsx
2026-03-21 20:18:25 +01:00

234 lines
7.5 KiB
TypeScript

import React from 'react';
import { cn } from '@/lib/utils';
import { LayoutContainer as LayoutContainerType, WidgetInstance } from '@/modules/layout/LayoutManager';
import { widgetRegistry } from '@/lib/widgetRegistry';
import CollapsibleSection from '@/components/CollapsibleSection';
import { LayoutContainer } from './LayoutContainer';
import type { LayoutContainerEditProps } from './LayoutContainerEdit';
export type LayoutContainerViewProps = Omit<
LayoutContainerEditProps,
| 'onAddWidget'
| 'onRemoveWidget'
| 'onMoveWidget'
| 'onUpdateColumns'
| 'onUpdateSettings'
| 'onAddContainer'
| 'onRemoveContainer'
| 'onMoveContainer'
| 'onEditWidget'
| 'onFilesDrop'
| 'canMoveContainerUp'
| 'canMoveContainerDown'
>;
const LayoutContainerView: React.FC<LayoutContainerViewProps> = ({
container,
isEditMode = false,
pageId,
selectedContainerId,
onSelect,
selectedWidgetId,
selectedWidgetIds,
onSelectWidget,
depth = 0,
isCompactMode = false,
contextVariables,
pageContext,
}) => {
const maxDepth = 3;
const getGridClasses = (columns: number) => {
const baseClass = "grid";
switch (columns) {
case 1: return `${baseClass} grid-cols-1`;
case 2: return `${baseClass} grid-cols-1 md:grid-cols-2`;
case 3: return `${baseClass} grid-cols-1 md:grid-cols-2 lg:grid-cols-3`;
case 4: return `${baseClass} grid-cols-1 md:grid-cols-2 lg:grid-cols-4`;
case 5: return `${baseClass} grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5`;
case 6: return `${baseClass} grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6`;
default:
return `${baseClass} grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-${Math.min(columns, 12)}`;
}
};
const gridClasses = getGridClasses(container.columns);
const renderContainerContent = () => (
<>
{container.widgets
.sort((a, b) => (a.order || 0) - (b.order || 0))
.map((widget, index) => (
<WidgetItemView
key={widget.id}
widget={widget}
index={index}
containerId={container.id}
pageId={pageId}
selectedWidgetId={selectedWidgetId}
selectedWidgetIds={selectedWidgetIds}
onSelectWidget={onSelectWidget}
contextVariables={contextVariables}
pageContext={pageContext}
selectedContainerId={selectedContainerId}
onSelectContainer={onSelect}
/>
))}
{container.children
.sort((a, b) => (a.order || 0) - (b.order || 0))
.map((childContainer) => (
<div key={childContainer.id} className="col-span-full">
<LayoutContainer
container={childContainer}
isEditMode={false}
pageId={pageId}
selectedContainerId={selectedContainerId}
onSelect={onSelect}
selectedWidgetId={selectedWidgetId}
selectedWidgetIds={selectedWidgetIds}
onSelectWidget={onSelectWidget}
depth={depth + 1}
isCompactMode={isCompactMode}
contextVariables={contextVariables}
pageContext={pageContext}
/>
</div>
))}
{container.widgets.length === 0 && container.children.length === 0 && (
<div className="col-span-full flex items-center justify-center min-h-[80px] text-slate-500 dark:text-slate-400">
<p className="text-sm"></p>
</div>
)}
</>
);
const isContainerEnabled = container.settings?.enabled !== false;
if (!isContainerEnabled) {
return null;
}
return (
<div className={cn("space-y-0", container.settings?.customClassName)}>
<div className="relative transition-all duration-200 min-w-0 overflow-hidden border-transparent">
{container.settings?.collapsible ? (
<CollapsibleSection
title={
container.settings?.showTitle
? (container.settings?.title || `Container (${container.columns} col${container.columns !== 1 ? 's' : ''})`)
: `Container (${container.columns} col${container.columns !== 1 ? 's' : ''})`
}
initiallyOpen={!container.settings?.collapsed}
storageKey={`container-${container.id}-collapsed`}
className="border-0 rounded-none shadow-none bg-transparent"
minimal={true}
>
<div
className={cn(isCompactMode ? "p-1 sm:p-2 min-h-[80px]" : "min-h-[120px]", gridClasses)}
style={{ gap: `${container.gap}px` }}
>
{renderContainerContent()}
</div>
</CollapsibleSection>
) : (
<div>
{container.settings?.showTitle && (
<div className="px-4 pt-3 pb-1 border-b border-slate-300/30 dark:border-white/10">
<h3 className="text-sm font-medium text-slate-700 dark:text-white">
{container.settings?.title || `Container (${container.columns} col${container.columns !== 1 ? 's' : ''})`}
</h3>
</div>
)}
<div
className={cn(isCompactMode ? "p-1 sm:p-2 min-h-[80px]" : "min-h-[120px]", gridClasses)}
style={{ gap: `${container.gap}px` }}
>
{renderContainerContent()}
</div>
</div>
)}
</div>
</div>
);
};
interface WidgetItemViewProps {
widget: WidgetInstance;
index: number;
containerId: string;
pageId: string;
selectedWidgetId?: string | null;
selectedWidgetIds?: Set<string>;
onSelectWidget?: (widgetId: string, pageId?: string) => void;
contextVariables?: Record<string, any>;
pageContext?: Record<string, any>;
selectedContainerId?: string | null;
onSelectContainer?: (containerId: string, pageId?: string) => void;
}
const WidgetItemView: React.FC<WidgetItemViewProps> = ({
widget,
index,
containerId,
pageId,
selectedWidgetId,
selectedWidgetIds,
onSelectWidget,
contextVariables,
pageContext,
selectedContainerId,
onSelectContainer,
}) => {
const widgetDefinition = widgetRegistry.get(widget.widgetId);
if (!widgetDefinition) {
return (
<div className="relative group border-red-500 bg-red-100 dark:bg-red-900/20 rounded-lg">
<p className="text-red-600 dark:text-red-400 text-sm">
Widget "{widget.widgetId}" not found in registry
</p>
</div>
);
}
const WidgetComponent = widgetDefinition.component;
const isEnabled = widget.props?.enabled !== false;
if (!isEnabled) {
return null;
}
return (
<div className="relative group" id={`widget-item-${widget.id}`}>
<div
className={cn(
"w-full dark:bg-slate-900/10 overflow-hidden transition-all duration-200",
widget.props?.customClassName || `widget-${widget.id.toLowerCase().replace(/[^a-z0-9]+/g, '-')}`,
`widget-type-${widget.widgetId}`,
"border-transparent"
)}
>
<WidgetComponent
{...(widget.props || {})}
widgetInstanceId={widget.id}
widgetDefId={widget.widgetId}
isEditMode={false}
onPropsChange={async () => { }}
selectedWidgetId={selectedWidgetId}
onSelectWidget={onSelectWidget}
editingWidgetId={null}
onEditWidget={() => { }}
contextVariables={contextVariables}
pageContext={pageContext}
selectedContainerId={selectedContainerId}
onSelectContainer={onSelectContainer}
/>
</div>
</div>
);
};
export default LayoutContainerView;