feat(dees-button-group): add new button group component with demo and styling

fix(dees-chart-area): improve real-time updates and chart element handling
fix(dees-chart-log): refactor demo to store log element reference
chore: update dependencies in package.json and pnpm-lock.yaml
This commit is contained in:
Juergen Kunz
2025-06-16 14:37:09 +00:00
parent 346abfa685
commit 48fbeb397d
10 changed files with 334 additions and 87 deletions

View File

@ -0,0 +1,114 @@
import { html, css } from '@design.estate/dees-element';
export const demoFunc = () => {
return html`
<style>
${css`
.demoBox {
background: #000000;
padding: 40px;
min-height: 100vh;
box-sizing: border-box;
}
.demo-section {
margin-bottom: 32px;
}
.demo-title {
color: #fff;
font-size: 20px;
font-weight: 600;
margin-bottom: 16px;
font-family: 'Geist Sans', sans-serif;
}
.demo-description {
color: #999;
font-size: 14px;
margin-bottom: 24px;
font-family: 'Geist Sans', sans-serif;
}
`}
</style>
<div class="demoBox">
<div class="demo-section">
<h2 class="demo-title">Basic Button Groups</h2>
<p class="demo-description">Button groups without labels for simple grouping</p>
<dees-button-group>
<dees-button>Option 1</dees-button>
<dees-button>Option 2</dees-button>
<dees-button>Option 3</dees-button>
</dees-button-group>
</div>
<div class="demo-section">
<h2 class="demo-title">Labeled Button Groups</h2>
<p class="demo-description">Button groups with descriptive labels</p>
<dees-button-group label="View Mode:">
<dees-button type="highlighted">Grid</dees-button>
<dees-button>List</dees-button>
<dees-button>Cards</dees-button>
</dees-button-group>
</div>
<div class="demo-section">
<h2 class="demo-title">Multiple Groups</h2>
<p class="demo-description">Multiple button groups used together</p>
<div style="display: flex; gap: 16px; flex-wrap: wrap;">
<dees-button-group label="Dataset:">
<dees-button type="highlighted">System</dees-button>
<dees-button>Network</dees-button>
<dees-button>Sales</dees-button>
</dees-button-group>
<dees-button-group label="Time Range:">
<dees-button>1H</dees-button>
<dees-button type="highlighted">24H</dees-button>
<dees-button>7D</dees-button>
<dees-button>30D</dees-button>
</dees-button-group>
<dees-button-group label="Actions:">
<dees-button>Refresh</dees-button>
<dees-button>Export</dees-button>
</dees-button-group>
</div>
</div>
<div class="demo-section">
<h2 class="demo-title">Vertical Button Groups</h2>
<p class="demo-description">Button groups with vertical layout</p>
<div style="display: flex; gap: 24px;">
<dees-button-group direction="vertical" label="Navigation:">
<dees-button>Dashboard</dees-button>
<dees-button type="highlighted">Analytics</dees-button>
<dees-button>Reports</dees-button>
<dees-button>Settings</dees-button>
</dees-button-group>
<dees-button-group direction="vertical">
<dees-button>Add Item</dees-button>
<dees-button>Edit Item</dees-button>
<dees-button>Delete Item</dees-button>
</dees-button-group>
</div>
</div>
<div class="demo-section">
<h2 class="demo-title">Mixed Button Types</h2>
<p class="demo-description">Different button types within groups</p>
<dees-button-group label="Status:">
<dees-button type="success">Active</dees-button>
<dees-button>Pending</dees-button>
<dees-button type="danger">Inactive</dees-button>
</dees-button-group>
</div>
</div>
`;
};

View File

@ -0,0 +1,83 @@
import {
DeesElement,
css,
cssManager,
customElement,
html,
property,
type TemplateResult,
} from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';
import { demoFunc } from './dees-button-group.demo.js';
declare global {
interface HTMLElementTagNameMap {
'dees-button-group': DeesButtonGroup;
}
}
@customElement('dees-button-group')
export class DeesButtonGroup extends DeesElement {
public static demo = demoFunc;
@property()
public label: string = '';
@property()
public direction: 'horizontal' | 'vertical' = 'horizontal';
constructor() {
super();
domtools.elementBasic.setup();
}
public static styles = [
cssManager.defaultStyles,
css`
:host {
display: inline-block;
}
.button-group {
display: flex;
gap: 8px;
align-items: center;
padding: 8px;
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.05)', 'rgba(255, 255, 255, 0.05)')};
border-radius: 6px;
}
.button-group.vertical {
flex-direction: column;
align-items: stretch;
}
.label {
color: ${cssManager.bdTheme('#666', '#999')};
font-size: 12px;
font-family: 'Geist Sans', sans-serif;
margin-right: 8px;
white-space: nowrap;
}
.button-group.vertical .label {
margin-right: 0;
margin-bottom: 8px;
}
::slotted(*) {
margin: 0 !important;
}
`,
];
public render(): TemplateResult {
return html`
<div class="button-group ${this.direction}">
${this.label ? html`<span class="label">${this.label}</span>` : ''}
<slot></slot>
</div>
`;
}
}

View File

@ -1,18 +1,11 @@
import { html, css } from '@design.estate/dees-element';
import type { DeesChartArea } from './dees-chart-area.js';
import '@design.estate/dees-wcctools';
export const demoFunc = () => {
let chartElement: DeesChartArea;
let intervalId: number;
let currentDataset = 'system';
// Get element reference after render
setTimeout(() => {
const charts = document.querySelectorAll('dees-chart-area');
if (charts.length > 0) {
chartElement = charts[charts.length - 1] as DeesChartArea;
}
}, 100);
// Y-axis formatters for different datasets
const formatters = {
@ -111,11 +104,15 @@ export const demoFunc = () => {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
// Get chart element
const getChartElement = () => {
return chartElement;
};
// Add real-time data
const addRealtimeData = () => {
if (!chartElement) return;
const dataset = datasets[currentDataset];
const chart = getChartElement();
if (!chart) return;
const newTimestamp = new Date().toISOString();
// Generate new data points based on dataset type
@ -133,29 +130,48 @@ export const demoFunc = () => {
];
}
// Keep only last 10 data points
const currentSeries = chartElement.series.map((series, index) => ({
// Keep only last 20 data points and update without animation
const currentSeries = chart.series.map((series, index) => ({
...series,
data: [...series.data.slice(-9), ...(newData[index] || [])],
data: [...series.data.slice(-19), ...(newData[index] || [])],
}));
chartElement.series = currentSeries;
// Update without animation for smoother real-time updates
chart.updateSeries(currentSeries, false);
};
// Switch dataset
const switchDataset = (name: string) => {
currentDataset = name;
if (chartElement) {
const dataset = datasets[name];
chartElement.label = dataset.label;
chartElement.series = dataset.series;
chartElement.yAxisFormatter = formatters[name];
}
const updateChart = () => {
const chart = getChartElement();
if (chart) {
const dataset = datasets[name];
chart.label = dataset.label;
chart.series = dataset.series;
chart.yAxisFormatter = formatters[name];
}
};
updateChart();
};
// Start/stop real-time updates
const startRealtime = () => {
if (!intervalId && (currentDataset === 'system' || currentDataset === 'network')) {
const chart = getChartElement();
if (chart) {
// Disable animations for real-time mode
chart.updateOptions({
chart: {
animations: {
enabled: false
}
}
}, false, false);
}
intervalId = window.setInterval(() => addRealtimeData(), 2000);
}
};
@ -164,29 +180,52 @@ export const demoFunc = () => {
if (intervalId) {
window.clearInterval(intervalId);
intervalId = null;
const chart = getChartElement();
if (chart) {
// Re-enable animations when stopping real-time
chart.updateOptions({
chart: {
animations: {
enabled: true,
speed: 400,
animateGradually: {
enabled: true,
delay: 150
}
}
}
}, false, true);
}
}
};
// Randomize current data
const randomizeData = () => {
if (!chartElement) return;
const chart = getChartElement();
if (!chart) return;
const currentSeries = chartElement.series.map(series => ({
const currentSeries = chart.series.map(series => ({
...series,
data: series.data.map(point => ({
data: series.data.map((point: any) => ({
...point,
y: typeof point.y === 'number'
? point.y * (0.8 + Math.random() * 0.4) // +/- 20% variation
? Math.round(point.y * (0.8 + Math.random() * 0.4)) // +/- 20% variation
: point.y,
})),
}));
chartElement.series = currentSeries;
// Update with animation for single updates
chart.updateSeries(currentSeries, true);
};
return html`
<style>
${css`
<dees-demowrapper .runAfterRender=${async (element: HTMLElement) => {
// Store the chart element reference
chartElement = element.querySelector('dees-chart-area') as DeesChartArea;
}}>
<style>
${css`
.demoBox {
position: relative;
background: #000000;
@ -206,22 +245,6 @@ export const demoFunc = () => {
margin-bottom: 8px;
}
.control-section {
display: flex;
gap: 8px;
align-items: center;
padding: 8px;
background: rgba(255, 255, 255, 0.05);
border-radius: 6px;
}
.section-label {
color: #999;
font-size: 12px;
font-family: 'Geist Sans', sans-serif;
margin-right: 8px;
}
.chart-container {
flex: 1;
min-height: 400px;
@ -238,8 +261,7 @@ export const demoFunc = () => {
</style>
<div class="demoBox">
<div class="controls">
<div class="control-section">
<span class="section-label">Dataset:</span>
<dees-button-group label="Dataset:">
<dees-button
@clicked=${() => switchDataset('system')}
type=${currentDataset === 'system' ? 'highlighted' : 'normal'}
@ -252,19 +274,17 @@ export const demoFunc = () => {
@clicked=${() => switchDataset('sales')}
type=${currentDataset === 'sales' ? 'highlighted' : 'normal'}
>Sales Data</dees-button>
</div>
</dees-button-group>
<div class="control-section">
<span class="section-label">Real-time:</span>
<dees-button-group label="Real-time:">
<dees-button @clicked=${() => startRealtime()}>Start Live</dees-button>
<dees-button @clicked=${() => stopRealtime()}>Stop Live</dees-button>
</div>
</dees-button-group>
<div class="control-section">
<span class="section-label">Actions:</span>
<dees-button-group label="Actions:">
<dees-button @clicked=${() => randomizeData()}>Randomize Values</dees-button>
<dees-button @clicked=${() => addRealtimeData()}>Add Point</dees-button>
</div>
</dees-button-group>
</div>
<div class="chart-container">
@ -280,6 +300,7 @@ export const demoFunc = () => {
Chart updates every 2 seconds when live mode is active •
Try switching datasets and randomizing values
</div>
</div>
</div>
</dees-demowrapper>
`;
};

View File

@ -6,7 +6,6 @@ import {
html,
property,
state,
type CSSResult,
type TemplateResult,
} from '@design.estate/dees-element';
@ -35,7 +34,7 @@ export class DeesChartArea extends DeesElement {
@property({ type: Array })
public series: ApexAxisChartSeries = [];
@property({ type: Function })
@property({ attribute: false })
public yAxisFormatter: (value: number) => string = (val) => `${val} Mbps`;
private resizeObserver: ResizeObserver;
@ -187,9 +186,13 @@ export class DeesChartArea extends DeesElement {
enabled: true,
speed: 400,
animateGradually: {
enabled: true,
delay: 150
enabled: false, // Disable gradual animation for cleaner updates
delay: 0
},
dynamicAnimation: {
enabled: true,
speed: 350
}
},
},
dataLabels: {
@ -312,24 +315,29 @@ export class DeesChartArea extends DeesElement {
}
}
public async updateSeries(newSeries: ApexAxisChartSeries) {
public async updateSeries(newSeries: ApexAxisChartSeries, animate: boolean = true) {
if (!this.chart) {
return;
}
await this.chart.updateSeries(newSeries, true);
this.chart.updateSeries(newSeries, animate);
}
public async appendData(seriesIndex: number, newData: any[]) {
public async appendData(newData: { data: any[] }[]) {
if (!this.chart) {
return;
}
const currentSeries = [...this.series];
if (currentSeries[seriesIndex]) {
currentSeries[seriesIndex].data = [...currentSeries[seriesIndex].data, ...newData];
await this.updateSeries(currentSeries);
// Use ApexCharts' appendData method for smoother real-time updates
await this.chart.appendData(newData);
}
public async updateOptions(options: ApexCharts.ApexOptions, redrawPaths?: boolean, animate?: boolean) {
if (!this.chart) {
return;
}
return this.chart.updateOptions(options, redrawPaths, animate);
}
public async resizeChart() {

View File

@ -1,7 +1,10 @@
import { html } from '@design.estate/dees-element';
import type { DeesChartLog } from './dees-chart-log.js';
import '@design.estate/dees-wcctools';
export const demoFunc = () => {
let intervalId: number;
let logElement: DeesChartLog;
const serverSources = ['Server', 'Database', 'API', 'Auth', 'Cache', 'Queue', 'WebSocket', 'Scheduler'];
@ -44,7 +47,6 @@ export const demoFunc = () => {
};
const generateRandomLog = () => {
const logElement = (window as any).__demoLogElement;
if (!logElement) {
console.warn('Log element not ready yet');
return;
@ -119,7 +121,11 @@ export const demoFunc = () => {
return html`
<style>
<dees-demowrapper .runAfterRender=${async (element: HTMLElement) => {
// Store the log element reference
logElement = element.querySelector('dees-chart-log') as DeesChartLog;
}}>
<style>
.demoBox {
position: relative;
background: #000000;
@ -152,6 +158,7 @@ export const demoFunc = () => {
<dees-chart-log
.label=${'Production Server Logs'}
></dees-chart-log>
</div>
</div>
</dees-demowrapper>
`;
};

View File

@ -46,7 +46,7 @@ export class DeesChartLog extends DeesElement {
constructor() {
super();
domtools.elementBasic.setup();
}
public static styles = [
@ -274,11 +274,6 @@ export class DeesChartLog extends DeesElement {
this.logEntries = demoLogs;
this.scrollToBottom();
// For demo purposes, store reference globally
if ((window as any).__demoLogElement === undefined) {
(window as any).__demoLogElement = this;
}
}
public async updateLog(entries?: ILogEntry[]) {

View File

@ -6,6 +6,7 @@ export * from './dees-appui-mainmenu.js';
export * from './dees-appui-mainselector.js';
export * from './dees-badge.js';
export * from './dees-button-exit.js';
export * from './dees-button-group.js';
export * from './dees-button.js';
export * from './dees-chart-area.js';
export * from './dees-chart-log.js';