feat: implement DeesDashboardgrid component with drag-and-drop functionality
- Added DeesDashboardgrid class for managing a grid of dashboard widgets. - Implemented widget dragging and resizing capabilities. - Introduced layout management with collision detection and margin resolution. - Created styles for grid layout, widget appearance, and animations. - Added support for customizable margins, cell height, and grid lines. - Included methods for adding, removing, and updating widgets dynamically. - Implemented context menu for widget actions and keyboard navigation support. - Established a responsive design with breakpoint handling for different layouts.
This commit is contained in:
255
ts_web/elements/dees-dashboardgrid/dees-dashboardgrid.demo.ts
Normal file
255
ts_web/elements/dees-dashboardgrid/dees-dashboardgrid.demo.ts
Normal file
@@ -0,0 +1,255 @@
|
||||
import { html, css, cssManager } from '@design.estate/dees-element';
|
||||
import type { DeesDashboardgrid } from './dees-dashboardgrid.js';
|
||||
import '@design.estate/dees-wcctools/demotools';
|
||||
|
||||
export const demoFunc = () => {
|
||||
return html`
|
||||
<dees-demowrapper .runAfterRender=${async (elementArg: HTMLElement) => {
|
||||
const grid = elementArg.querySelector('#dashboardGrid') as DeesDashboardgrid;
|
||||
|
||||
const seedWidgets = [
|
||||
{
|
||||
id: 'metrics1',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 3,
|
||||
h: 2,
|
||||
title: 'Revenue',
|
||||
icon: 'lucide:dollarSign',
|
||||
content: html`
|
||||
<div style="padding: 20px;">
|
||||
<div style="font-size: 32px; font-weight: 700; color: ${cssManager.bdTheme('#09090b', '#fafafa')};">$124,563</div>
|
||||
<div style="color: #22c55e; font-size: 14px; margin-top: 8px;">↑ 12.5% from last month</div>
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: 'metrics2',
|
||||
x: 3,
|
||||
y: 0,
|
||||
w: 3,
|
||||
h: 2,
|
||||
title: 'Users',
|
||||
icon: 'lucide:users',
|
||||
content: html`
|
||||
<div style="padding: 20px;">
|
||||
<div style="font-size: 32px; font-weight: 700; color: ${cssManager.bdTheme('#09090b', '#fafafa')};">8,234</div>
|
||||
<div style="color: #3b82f6; font-size: 14px; margin-top: 8px;">↑ 5.2% from last week</div>
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: 'chart1',
|
||||
x: 6,
|
||||
y: 0,
|
||||
w: 6,
|
||||
h: 4,
|
||||
title: 'Analytics',
|
||||
icon: 'lucide:lineChart',
|
||||
content: html`
|
||||
<div style="padding: 20px; height: 100%; display: flex; align-items: center; justify-content: center;">
|
||||
<div style="text-align: center; color: #71717a;">
|
||||
<dees-icon .icon=${'lucide:lineChart'} style="font-size: 48px; margin-bottom: 16px;"></dees-icon>
|
||||
<div>Chart visualization area</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
grid.widgets = seedWidgets.map(widget => ({ ...widget }));
|
||||
grid.cellHeight = 80;
|
||||
grid.margin = { top: 10, right: 10, bottom: 10, left: 10 };
|
||||
grid.enableAnimation = true;
|
||||
grid.showGridLines = false;
|
||||
|
||||
const baseLayout = grid.getLayout().map(item => ({ ...item }));
|
||||
const mobileLayout = grid.widgets.map((widget, index) => ({
|
||||
id: widget.id,
|
||||
x: 0,
|
||||
y: index === 0 ? 0 : grid.widgets.slice(0, index).reduce((acc, prev) => acc + prev.h, 0),
|
||||
w: grid.columns,
|
||||
h: widget.h,
|
||||
}));
|
||||
|
||||
grid.layouts = {
|
||||
base: baseLayout,
|
||||
mobile: mobileLayout,
|
||||
};
|
||||
|
||||
const statusEl = elementArg.querySelector('#dashboardLayoutStatus') as HTMLElement;
|
||||
const updateStatus = () => {
|
||||
const layout = grid.getLayout();
|
||||
statusEl.textContent = `Active breakpoint: ${grid.activeBreakpoint} • Tiles: ${layout.length}`;
|
||||
};
|
||||
|
||||
const mediaQuery = window.matchMedia('(max-width: 768px)');
|
||||
const handleBreakpoint = () => {
|
||||
const target = mediaQuery.matches ? 'mobile' : 'base';
|
||||
grid.applyBreakpointLayout(target);
|
||||
updateStatus();
|
||||
};
|
||||
if ('addEventListener' in mediaQuery) {
|
||||
mediaQuery.addEventListener('change', handleBreakpoint);
|
||||
} else {
|
||||
mediaQuery.addListener(handleBreakpoint);
|
||||
}
|
||||
handleBreakpoint();
|
||||
|
||||
let widgetCounter = 4;
|
||||
|
||||
const buttons = elementArg.querySelectorAll('dees-button');
|
||||
buttons.forEach(button => {
|
||||
const text = button.textContent?.trim();
|
||||
|
||||
switch (text) {
|
||||
case 'Toggle Animation':
|
||||
button.addEventListener('click', () => {
|
||||
grid.enableAnimation = !grid.enableAnimation;
|
||||
});
|
||||
break;
|
||||
case 'Toggle Grid Lines':
|
||||
button.addEventListener('click', () => {
|
||||
grid.showGridLines = !grid.showGridLines;
|
||||
});
|
||||
break;
|
||||
case 'Add Widget':
|
||||
button.addEventListener('click', () => {
|
||||
const newWidget = {
|
||||
id: `widget${widgetCounter++}`,
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 3,
|
||||
h: 2,
|
||||
autoPosition: true,
|
||||
title: `Widget ${widgetCounter - 1}`,
|
||||
icon: 'lucide:package',
|
||||
content: html`
|
||||
<div style="padding: 20px; text-align: center;">
|
||||
<div style="color: #71717a;">New widget content</div>
|
||||
<div style="margin-top: 8px; font-size: 24px; font-weight: 600; color: ${cssManager.bdTheme('#09090b', '#fafafa')};">${Math.floor(
|
||||
Math.random() * 1000,
|
||||
)}</div>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
grid.addWidget(newWidget, true);
|
||||
});
|
||||
break;
|
||||
case 'Compact Grid':
|
||||
button.addEventListener('click', () => {
|
||||
grid.compact();
|
||||
});
|
||||
break;
|
||||
case 'Toggle Edit Mode':
|
||||
button.addEventListener('click', () => {
|
||||
grid.editable = !grid.editable;
|
||||
button.textContent = grid.editable ? 'Lock Grid' : 'Unlock Grid';
|
||||
});
|
||||
break;
|
||||
case 'Reset Layout':
|
||||
button.addEventListener('click', () => {
|
||||
grid.applyBreakpointLayout(grid.activeBreakpoint);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
grid.addEventListener('widget-move', (e: CustomEvent) => {
|
||||
console.log('Widget moved:', e.detail.widget, 'Displaced:', e.detail.displaced);
|
||||
});
|
||||
grid.addEventListener('widget-resize', (e: CustomEvent) => {
|
||||
console.log('Widget resized:', e.detail.widget, 'Displaced:', e.detail.displaced);
|
||||
});
|
||||
grid.addEventListener('widget-remove', (e: CustomEvent) => {
|
||||
console.log('Widget removed:', e.detail.widget);
|
||||
updateStatus();
|
||||
});
|
||||
grid.addEventListener('layout-change', () => {
|
||||
console.log('Layout changed:', grid.getLayout());
|
||||
updateStatus();
|
||||
});
|
||||
|
||||
updateStatus();
|
||||
}}>
|
||||
<style>
|
||||
${css`
|
||||
.demoBox {
|
||||
position: relative;
|
||||
background: ${cssManager.bdTheme('#f4f4f5', '#09090b')};
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 40px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.demo-controls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.demo-controls dees-button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.grid-container-wrapper {
|
||||
flex: 1;
|
||||
min-height: 600px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.info {
|
||||
color: ${cssManager.bdTheme('#71717a', '#71717a')};
|
||||
font-size: 12px;
|
||||
font-family: 'Geist Sans', sans-serif;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
#dashboardLayoutStatus {
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<div class="demoBox">
|
||||
<div class="demo-controls">
|
||||
<dees-button-group label="Animation:">
|
||||
<dees-button>Toggle Animation</dees-button>
|
||||
</dees-button-group>
|
||||
|
||||
<dees-button-group label="Display:">
|
||||
<dees-button>Toggle Grid Lines</dees-button>
|
||||
</dees-button-group>
|
||||
|
||||
<dees-button-group label="Actions:">
|
||||
<dees-button>Add Widget</dees-button>
|
||||
<dees-button>Compact Grid</dees-button>
|
||||
<dees-button>Reset Layout</dees-button>
|
||||
</dees-button-group>
|
||||
|
||||
<dees-button-group label="Mode:">
|
||||
<dees-button>Toggle Edit Mode</dees-button>
|
||||
</dees-button-group>
|
||||
</div>
|
||||
|
||||
<div class="grid-container-wrapper">
|
||||
<dees-dashboardgrid id="dashboardGrid"></dees-dashboardgrid>
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
<div>Drag to reposition, resize from handles, or right-click a header to delete a tile.</div>
|
||||
<div id="dashboardLayoutStatus"></div>
|
||||
</div>
|
||||
</div>
|
||||
</dees-demowrapper>
|
||||
`;
|
||||
};
|
Reference in New Issue
Block a user