Initial commit: dashboard-elements-ui library
Angular library providing dashboard components including grid layout, drag-and-drop widgets, resize handles, toolbar, config panel, layout presets, and persistence services.
This commit is contained in:
35
demo/angular.json
Normal file
35
demo/angular.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"demo": {
|
||||
"projectType": "application",
|
||||
"schematics": {},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:application",
|
||||
"options": {
|
||||
"outputPath": "dist/demo",
|
||||
"index": "src/index.html",
|
||||
"browser": "src/main.ts",
|
||||
"polyfills": ["zone.js"],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"assets": ["src/favicon.ico", "src/assets"],
|
||||
"styles": ["src/styles.scss"],
|
||||
"preserveSymlinks": true
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"buildTarget": "demo:build"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13963
demo/package-lock.json
generated
Normal file
13963
demo/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
demo/package.json
Normal file
29
demo/package.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "dashboard-elements-ui-demo",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "ng serve --port 4200",
|
||||
"build": "ng build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "^19.1.0",
|
||||
"@angular/common": "^19.1.0",
|
||||
"@angular/compiler": "^19.1.0",
|
||||
"@angular/core": "^19.1.0",
|
||||
"@angular/forms": "^19.1.0",
|
||||
"@angular/platform-browser": "^19.1.0",
|
||||
"@angular/platform-browser-dynamic": "^19.1.0",
|
||||
"@sda/base-ui": "file:../../base-ui",
|
||||
"@sda/dashboard-elements-ui": "file:../dist",
|
||||
"rxjs": "^7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "^0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^19.1.0",
|
||||
"@angular/cli": "^19.1.0",
|
||||
"@angular/compiler-cli": "^19.1.0",
|
||||
"typescript": "~5.7.2"
|
||||
}
|
||||
}
|
||||
462
demo/src/app/app.component.ts
Normal file
462
demo/src/app/app.component.ts
Normal file
@@ -0,0 +1,462 @@
|
||||
import { Component, signal } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import {
|
||||
DbDashboardComponent,
|
||||
DbWidgetContentDefDirective,
|
||||
type DbDashboard,
|
||||
type DbWidgetType,
|
||||
} from '@sda/dashboard-elements-ui';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
imports: [CommonModule, DbDashboardComponent, DbWidgetContentDefDirective],
|
||||
template: `
|
||||
<div class="demo-container">
|
||||
<db-dashboard
|
||||
[dashboard]="dashboard()"
|
||||
[widgetTypes]="widgetTypes"
|
||||
[showToolbar]="true"
|
||||
title="Analytics Dashboard"
|
||||
(widgetAdd)="onWidgetAdd($event)"
|
||||
(widgetRemove)="onWidgetRemove($event)"
|
||||
(widgetMove)="onWidgetMove($event)"
|
||||
(dashboardSave)="onSave($event)"
|
||||
>
|
||||
<ng-template dbWidgetContentDef="stats" let-widget>
|
||||
<div class="widget-stats">
|
||||
<div class="stat-value">{{ widget.config?.['value'] ?? 0 }}</div>
|
||||
<div class="stat-label">{{ widget.config?.['label'] ?? 'Metric' }}</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template dbWidgetContentDef="chart" let-widget>
|
||||
<div class="widget-chart">
|
||||
<div class="chart-title">{{ widget.title }}</div>
|
||||
<div class="chart-placeholder">
|
||||
<svg viewBox="0 0 400 200" class="chart-svg">
|
||||
<polyline
|
||||
points="0,150 50,120 100,140 150,80 200,100 250,60 300,90 350,40 400,70"
|
||||
fill="none"
|
||||
stroke="#3b82f6"
|
||||
stroke-width="3"
|
||||
/>
|
||||
<polyline
|
||||
points="0,150 50,120 100,140 150,80 200,100 250,60 300,90 350,40 400,70"
|
||||
fill="url(#gradient)"
|
||||
opacity="0.2"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient id="gradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#3b82f6;stop-opacity:0.8" />
|
||||
<stop offset="100%" style="stop-color:#3b82f6;stop-opacity:0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template dbWidgetContentDef="table" let-widget>
|
||||
<div class="widget-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Product</th>
|
||||
<th>Sales</th>
|
||||
<th>Status</th>
|
||||
<th>Change</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Product A</strong></td>
|
||||
<td>$12,450</td>
|
||||
<td><span class="status status--success">Active</span></td>
|
||||
<td class="positive">+12.5%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Product B</strong></td>
|
||||
<td>$8,320</td>
|
||||
<td><span class="status status--success">Active</span></td>
|
||||
<td class="positive">+8.3%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Product C</strong></td>
|
||||
<td>$5,680</td>
|
||||
<td><span class="status status--warning">Pending</span></td>
|
||||
<td class="negative">-3.2%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Product D</strong></td>
|
||||
<td>$4,120</td>
|
||||
<td><span class="status status--success">Active</span></td>
|
||||
<td class="positive">+15.7%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template dbWidgetContentDef="list" let-widget>
|
||||
<div class="widget-list">
|
||||
@for (item of sampleListItems; track item.initial) {
|
||||
<div class="list-item">
|
||||
<div class="list-item__icon">
|
||||
<div class="avatar">{{ item.initial }}</div>
|
||||
</div>
|
||||
<div class="list-item__content">
|
||||
<div class="list-item__title">{{ item.title }}</div>
|
||||
<div class="list-item__subtitle">{{ item.subtitle }}</div>
|
||||
</div>
|
||||
<div class="list-item__meta">{{ item.time }}</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</ng-template>
|
||||
</db-dashboard>
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
.demo-container {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Stats Widget */
|
||||
.widget-stats {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: #3b82f6;
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.75rem;
|
||||
color: #6b7280;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Chart Widget */
|
||||
.widget-chart {
|
||||
padding: 1rem;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #6b7280;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.chart-placeholder {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chart-svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
/* Table Widget */
|
||||
.widget-table {
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 0.75rem 1rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #f9fafb;
|
||||
font-weight: 600;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: #6b7280;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
td {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.status {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.625rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status--success {
|
||||
background: #d1fae5;
|
||||
color: #065f46;
|
||||
}
|
||||
|
||||
.status--warning {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.positive {
|
||||
color: #059669;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.negative {
|
||||
color: #dc2626;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* List Widget */
|
||||
.widget-list {
|
||||
padding: 0.5rem 0;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
gap: 0.75rem;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
transition: background 150ms;
|
||||
}
|
||||
|
||||
.list-item:hover {
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.list-item__content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.list-item__title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #111827;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.list-item__subtitle {
|
||||
font-size: 0.75rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.list-item__meta {
|
||||
font-size: 0.75rem;
|
||||
color: #9ca3af;
|
||||
}
|
||||
`],
|
||||
})
|
||||
export class AppComponent {
|
||||
dashboard = signal<DbDashboard>({
|
||||
id: 'demo-dashboard',
|
||||
title: 'Analytics Dashboard',
|
||||
layouts: [
|
||||
{
|
||||
breakpoint: 'lg',
|
||||
columns: 12,
|
||||
rowHeight: 80,
|
||||
gap: 16,
|
||||
widgets: [
|
||||
{ id: 'widget-1', col: 1, row: 1, colSpan: 3, rowSpan: 2 },
|
||||
{ id: 'widget-2', col: 4, row: 1, colSpan: 3, rowSpan: 2 },
|
||||
{ id: 'widget-3', col: 7, row: 1, colSpan: 3, rowSpan: 2 },
|
||||
{ id: 'widget-4', col: 10, row: 1, colSpan: 3, rowSpan: 2 },
|
||||
{ id: 'widget-5', col: 1, row: 3, colSpan: 8, rowSpan: 4 },
|
||||
{ id: 'widget-6', col: 9, row: 3, colSpan: 4, rowSpan: 4 },
|
||||
],
|
||||
},
|
||||
],
|
||||
widgets: [
|
||||
{
|
||||
id: 'widget-1',
|
||||
type: 'stats',
|
||||
title: 'Total Users',
|
||||
icon: 'users',
|
||||
layout: { id: 'widget-1', col: 1, row: 1, colSpan: 3, rowSpan: 2 },
|
||||
config: { value: '2,845', label: 'Total Users' },
|
||||
},
|
||||
{
|
||||
id: 'widget-2',
|
||||
type: 'stats',
|
||||
title: 'Revenue',
|
||||
icon: 'dollar-sign',
|
||||
layout: { id: 'widget-2', col: 4, row: 1, colSpan: 3, rowSpan: 2 },
|
||||
config: { value: '$54.2K', label: 'Monthly Revenue' },
|
||||
},
|
||||
{
|
||||
id: 'widget-3',
|
||||
type: 'stats',
|
||||
title: 'Conversion',
|
||||
icon: 'trending-up',
|
||||
layout: { id: 'widget-3', col: 7, row: 1, colSpan: 3, rowSpan: 2 },
|
||||
config: { value: '28.5%', label: 'Conversion Rate' },
|
||||
},
|
||||
{
|
||||
id: 'widget-4',
|
||||
type: 'stats',
|
||||
title: 'Active Now',
|
||||
icon: 'activity',
|
||||
layout: { id: 'widget-4', col: 10, row: 1, colSpan: 3, rowSpan: 2 },
|
||||
config: { value: '124', label: 'Active Sessions' },
|
||||
},
|
||||
{
|
||||
id: 'widget-5',
|
||||
type: 'chart',
|
||||
title: 'Revenue Trend',
|
||||
icon: 'bar-chart-2',
|
||||
layout: { id: 'widget-5', col: 1, row: 3, colSpan: 8, rowSpan: 4 },
|
||||
},
|
||||
{
|
||||
id: 'widget-6',
|
||||
type: 'list',
|
||||
title: 'Recent Activity',
|
||||
icon: 'activity',
|
||||
layout: { id: 'widget-6', col: 9, row: 3, colSpan: 4, rowSpan: 4 },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
widgetTypes: DbWidgetType[] = [
|
||||
{
|
||||
type: 'stats',
|
||||
label: 'Stats Card',
|
||||
icon: 'bar-chart',
|
||||
description: 'Display key metrics and statistics',
|
||||
category: 'analytics',
|
||||
defaultLayout: {
|
||||
colSpan: 3,
|
||||
rowSpan: 2,
|
||||
minColSpan: 2,
|
||||
minRowSpan: 2,
|
||||
maxColSpan: 4,
|
||||
maxRowSpan: 3,
|
||||
},
|
||||
component: class {},
|
||||
},
|
||||
{
|
||||
type: 'chart',
|
||||
label: 'Chart',
|
||||
icon: 'line-chart',
|
||||
description: 'Visualize data with charts',
|
||||
category: 'analytics',
|
||||
defaultLayout: {
|
||||
colSpan: 6,
|
||||
rowSpan: 4,
|
||||
minColSpan: 4,
|
||||
minRowSpan: 3,
|
||||
},
|
||||
component: class {},
|
||||
},
|
||||
{
|
||||
type: 'table',
|
||||
label: 'Data Table',
|
||||
icon: 'table',
|
||||
description: 'Display tabular data',
|
||||
category: 'data',
|
||||
defaultLayout: {
|
||||
colSpan: 6,
|
||||
rowSpan: 4,
|
||||
minColSpan: 4,
|
||||
minRowSpan: 3,
|
||||
},
|
||||
component: class {},
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
label: 'Activity List',
|
||||
icon: 'list',
|
||||
description: 'Show recent activities or notifications',
|
||||
category: 'data',
|
||||
defaultLayout: {
|
||||
colSpan: 4,
|
||||
rowSpan: 4,
|
||||
minColSpan: 3,
|
||||
minRowSpan: 3,
|
||||
},
|
||||
component: class {},
|
||||
},
|
||||
];
|
||||
|
||||
sampleListItems = [
|
||||
{ initial: 'JD', title: 'John Doe completed a task', subtitle: 'Project Alpha', time: '2m ago' },
|
||||
{ initial: 'SM', title: 'Sarah Miller uploaded a file', subtitle: 'Document.pdf', time: '15m ago' },
|
||||
{ initial: 'RJ', title: 'Robert Johnson commented', subtitle: 'Great work on this!', time: '1h ago' },
|
||||
{ initial: 'EW', title: 'Emily White created an issue', subtitle: 'Bug #234', time: '2h ago' },
|
||||
{ initial: 'MC', title: 'Michael Chen merged PR #45', subtitle: 'Feature: Add dashboard', time: '3h ago' },
|
||||
];
|
||||
|
||||
onWidgetAdd(event: any) {
|
||||
console.log('Widget added:', event);
|
||||
const newWidgets = [...this.dashboard().widgets, event.widget];
|
||||
this.dashboard.update(d => ({ ...d, widgets: newWidgets }));
|
||||
}
|
||||
|
||||
onWidgetRemove(event: any) {
|
||||
console.log('Widget removed:', event);
|
||||
const filtered = this.dashboard().widgets.filter((w: any) => w.id !== event.widget.id);
|
||||
this.dashboard.update(d => ({ ...d, widgets: filtered }));
|
||||
}
|
||||
|
||||
onWidgetMove(event: any) {
|
||||
console.log('Widget moved:', event);
|
||||
}
|
||||
|
||||
onSave(event: any) {
|
||||
console.log('Dashboard saved:', event);
|
||||
alert('Dashboard saved successfully!');
|
||||
}
|
||||
}
|
||||
0
demo/src/favicon.ico
Normal file
0
demo/src/favicon.ico
Normal file
13
demo/src/index.html
Normal file
13
demo/src/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Dashboard Elements UI Demo</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
6
demo/src/main.ts
Normal file
6
demo/src/main.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { AppComponent } from './app/app.component';
|
||||
|
||||
bootstrapApplication(AppComponent, {
|
||||
providers: [],
|
||||
}).catch((err) => console.error(err));
|
||||
18
demo/src/styles.scss
Normal file
18
demo/src/styles.scss
Normal file
@@ -0,0 +1,18 @@
|
||||
@use '@sda/dashboard-elements-ui/src/styles' as db;
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
background: #f5f5f7;
|
||||
color: #111827;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
9
demo/tsconfig.app.json
Normal file
9
demo/tsconfig.app.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"files": ["src/main.ts"],
|
||||
"include": ["src/**/*.d.ts"]
|
||||
}
|
||||
29
demo/tsconfig.json
Normal file
29
demo/tsconfig.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/out-tsc",
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "bundler",
|
||||
"importHelpers": true,
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"lib": ["ES2022", "dom"],
|
||||
"useDefineForClassFields": false,
|
||||
"preserveSymlinks": true
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user