diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..05934b4
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,174 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+@design.estate/dees-catalog is a comprehensive web components library built with TypeScript and LitElement. It provides a large collection of UI components for building modern web applications with consistent design and behavior.
+
+## Build and Development Commands
+
+```bash
+# Install dependencies
+pnpm install
+
+# Build the project
+pnpm run build
+# This runs: tsbuild tsfolders --allowimplicitany && tsbundle element --production --bundler esbuild
+
+# Run development watch mode
+pnpm run watch
+# This runs: tswatch element
+
+# Run tests (browser tests)
+pnpm test
+# This runs: tstest test/ --web --verbose --timeout 30 --logfile
+
+# Run a specific test file
+tsx test/test.wysiwyg-basic.browser.ts --verbose
+
+# Build documentation
+pnpm run buildDocs
+```
+
+### Testing Notes
+- Test files follow the pattern: `test.*.browser.ts`, `test.*.node.ts`, or `test.*.both.ts`
+- Browser tests run in a headless browser environment
+- Use `--logfile` option to store logs in `.nogit/testlogs/`
+- For debugging, create files in `.nogit/debug/` and run with `tsx`
+
+## Architecture Overview
+
+### Component Structure
+The library is organized into several categories:
+
+1. **Core UI Components** (`dees-button`, `dees-badge`, `dees-icon`, etc.)
+ - Basic building blocks with consistent theming
+ - All support light/dark themes via `cssManager.bdTheme()`
+
+2. **Form Components** (`dees-form`, `dees-input-*`)
+ - Complete form system with validation
+ - Base class `DeesInputBase` provides common functionality
+ - Form data collection via `DeesForm` container
+
+3. **Layout Components** (`dees-appui-*`)
+ - Application shell components
+ - `DeesAppuiBase` orchestrates the entire layout
+ - Grid-based responsive design
+
+4. **Data Display** (`dees-table`, `dees-dataview-*`, `dees-statsgrid`)
+ - Complex data visualization components
+ - Interactive tables with sorting/filtering
+ - Chart components using ApexCharts
+
+5. **Overlays** (`dees-modal`, `dees-contextmenu`, `dees-toast`)
+ - Managed by central z-index registry
+ - Window layer system for proper stacking
+
+### Key Architectural Patterns
+
+#### Z-Index Management
+All overlay components use a centralized z-index registry system:
+- Definition in `ts_web/elements/00zindex.ts`
+- Dynamic z-index assignment via `ZIndexRegistry` class
+- Components get z-index from registry when showing
+- Ensures proper stacking order (dropdowns above modals, etc.)
+
+#### Theme System
+- All components support light/dark themes
+- Use `cssManager.bdTheme(lightValue, darkValue)` for theme-aware colors
+- Consistent color palette defined in `00colors.ts`
+
+#### Component Demo System
+- Each component has a static `demo` property
+- Demo functions in separate `.demo.ts` files
+- Showcase pages aggregate demos (e.g., `input-showcase.ts`)
+
+#### WYSIWYG Editor Architecture
+The WYSIWYG editor uses a sophisticated architecture with separated concerns:
+- **Main Component**: `dees-input-wysiwyg.ts` - Orchestrates the editor
+- **Handler Classes**:
+ - `WysiwygInputHandler` - Handles text input and block transformations
+ - `WysiwygKeyboardHandler` - Manages keyboard shortcuts and navigation
+ - `WysiwygDragDropHandler` - Manages block reordering
+ - `WysiwygModalManager` - Shows configuration modals
+ - `WysiwygBlockOperations` - Core block manipulation logic
+- **Global Menus**:
+ - `DeesSlashMenu` and `DeesFormattingMenu` render globally to avoid focus issues
+ - Singleton pattern ensures single instance
+- **Programmatic Rendering**: Uses manual DOM manipulation to prevent focus loss
+
+### Component Communication
+- Custom events for parent-child communication
+- Form components emit standardized events (`change`, `blur`, etc.)
+- Complex components like `DeesAppuiBase` re-emit child events
+
+### Build System
+- TypeScript compilation with decorators support
+- Web component bundling with esbuild
+- Element exports in `ts_web/elements/index.ts`
+- Distribution builds in `dist_ts_web/`
+
+## Important Implementation Details
+
+### When Creating New Components
+1. Extend `DeesElement` from `@design.estate/dees-element`
+2. Use `@customElement('dees-componentname')` decorator
+3. Implement theme support with `cssManager.bdTheme()`
+4. Create a demo function in a separate `.demo.ts` file
+5. Export from `elements/index.ts`
+
+### Form Input Components
+1. Extend `DeesInputBase` for form inputs
+2. Implement `getValue()` and `setValue()` methods
+3. Use `changeSubject.next(this)` to emit changes
+4. Support `disabled` and `required` properties
+
+### Overlay Components
+1. Import z-index from `00zindex.ts`
+2. Get z-index from registry when showing: `zIndexRegistry.getNextZIndex()`
+3. Register/unregister with the registry
+4. Use `DeesWindowLayer` for backdrop if needed
+
+### Testing Components
+1. Create test files in `test/` directory
+2. Use `@git.zone/tstest` with tap-bundle
+3. Test in browser environment for web components
+4. Use proper async/await for component lifecycle
+
+## Common Patterns and Pitfalls
+
+### Focus Management
+- WYSIWYG editor uses programmatic rendering to prevent focus loss
+- Use `requestAnimationFrame` for timing-sensitive focus operations
+- Avoid reactive re-renders during user input
+
+### Event Handling
+- Prevent event bubbling in nested interactive components
+- Use `pointer-events: none/auto` for click-through behavior
+- Handle both mouse and keyboard events for accessibility
+
+### Performance Considerations
+- Large components (editor, terminal) use lazy loading
+- Charts use debounced resize observers
+- Tables implement virtual scrolling for large datasets
+
+## File Organization
+```
+ts_web/
+├── elements/ # All component files
+│ ├── 00*.ts # Shared utilities (colors, z-index, plugins)
+│ ├── dees-*.ts # Component implementations
+│ ├── dees-*.demo.ts # Component demos
+│ ├── interfaces/ # Shared TypeScript interfaces
+│ ├── helperclasses/ # Utility classes (FormController)
+│ └── wysiwyg/ # WYSIWYG editor subsystem
+├── pages/ # Demo showcase pages
+└── index.ts # Main export file
+```
+
+## Recent Major Changes
+- Z-Index Registry System (2025-12-24): Dynamic stacking order management
+- WYSIWYG Refactoring (2025-06-24): Complete architecture overhaul with separated concerns
+- Form System Enhancement: Unified validation and data collection
+- Theme System: Consistent light/dark theme support across all components
\ No newline at end of file
diff --git a/package.json b/package.json
index 69b34a0..7137c5f 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"typings": "dist_ts_web/index.d.ts",
"type": "module",
"scripts": {
- "test": "tstest test/ --web --verbose --timeout 30",
+ "test": "tstest test/ --web --verbose --timeout 30 --logfile",
"build": "tsbuild tsfolders --allowimplicitany && tsbundle element --production --bundler esbuild",
"watch": "tswatch element",
"buildDocs": "tsdoc"
diff --git a/readme.hints.md b/readme.hints.md
index f8faf34..9146783 100644
--- a/readme.hints.md
+++ b/readme.hints.md
@@ -1,5 +1,5 @@
!!! Please pay attention to the following points when writing the readme: !!!
-* Give a short rundown of components and a few points abputspecific features on each.
+* Give a short rundown of components and a few points abput specific features on each.
* Try to list all components in a summary.
* Then list all components with a short description.
diff --git a/ts_web/elements/dees-button.demo.ts b/ts_web/elements/dees-button.demo.ts
index 8887a1a..373161d 100644
--- a/ts_web/elements/dees-button.demo.ts
+++ b/ts_web/elements/dees-button.demo.ts
@@ -1,77 +1,301 @@
-import { html, css } from '@design.estate/dees-element';
+import { html, css, cssManager } from '@design.estate/dees-element';
+import '@design.estate/dees-wcctools/demotools';
+import './dees-panel.js';
+import './dees-form.js';
+import './dees-form-submit.js';
+import './dees-input-text.js';
+import './dees-icon.js';
export const demoFunc = () => html`
-
+
+
+
+
+ Default
+ Secondary
+ Destructive
+ Outline
+ Ghost
+ Link Button
+
+
- .button-group {
- display: flex;
- gap: 16px;
- margin: 20px 0;
- }
- `}
-
-
-
Button Types
-
This is a slotted Text
-
- Highlighted
-
-
This is discreete button
-
This is a disabled button
-
This is a slotted Text
-
-
Button States
-
Normal Status
-
Pending Status
-
Success Status
-
Error Status
-
-
Buttons in Forms (Auto-spacing)
-
-
-
-
- Save Draft
- Save and Continue
- Submit Form
-
-
-
-
Buttons Outside Forms (No auto-spacing)
-
- Button 1
- Button 2
- Button 3
-
-
-
Manual Form Spacing
-
- Manually spaced button 1
- Manually spaced button 2
-
+
+
+ Small Button
+ Default Size
+ Large Button
+
+
+
+
+ Small Secondary
+ Default Destructive
+ Large Outline
+
+
+
+
+
+
+
+ Add Item
+
+
+
+ Delete
+
+
+
+ Download
+
+
+
+
+
+
+ Settings
+
+
+
+ Back
+
+
+ Next
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Normal
+ Processing...
+ Success!
+ Error!
+ Disabled
+
+
+
+ Small Loading
+ Default Loading
+ Large Loading
+
+
+
+
+
+ {
+ const output = document.querySelector('#click-output');
+ if (output) {
+ output.textContent = `Clicked: Default button at ${new Date().toLocaleTimeString()}`;
+ }
+ }}
+ >
+ Click Me
+
+
+ {
+ const output = document.querySelector('#click-output');
+ if (output) {
+ output.textContent = `Clicked: Secondary button with data: ${e.detail.data}`;
+ }
+ }}
+ >
+ Click with Data
+
+
+ {
+ const output = document.querySelector('#click-output');
+ if (output) {
+ output.textContent = 'Processing...';
+ await new Promise(resolve => setTimeout(resolve, 2000));
+ output.textContent = 'Action completed!';
+ }
+ }}
+ >
+ Async Action
+
+
+
+
+ Click a button to see the result...
+
+
+
+
+ {
+ const output = document.querySelector('#form-output');
+ if (output) {
+ output.innerHTML = 'Form submitted with data: ' +
+ JSON.stringify(e.detail.data, null, 2);
+ }
+ }}>
+
+
+
+
+ Save Draft
+ Cancel
+ Submit Form
+
+
+
+ Submit the form to see the data...
+
+
+
+
+
+ Normal → Default
+ Highlighted → Destructive
+ Discreet → Outline
+ Big → Large Size
+
+
+
+ These legacy type values are maintained for backward compatibility but we recommend using the new variant system.
+
+
+
+
+
+
+
Action Group
+
+
+ Save Changes
+
+
+
+ Discard
+
+
+
+ Help
+
+
+
+
+
Danger Zone
+
+
+ Delete Account
+
+
+
+ Archive Data
+
+
+
+ Not Available
+
+
+
+
+
+
Code Example:
+
+ <dees-button type="default" size="sm" @clicked="\${handleClick}">
+ <dees-icon iconFA="faSave"></dees-icon>
+ Save Changes
+ </dees-button>
+
+
+
+
+
`;
diff --git a/ts_web/elements/dees-button.ts b/ts_web/elements/dees-button.ts
index e65ba1f..862c999 100644
--- a/ts_web/elements/dees-button.ts
+++ b/ts_web/elements/dees-button.ts
@@ -48,7 +48,12 @@ export class DeesButton extends DeesElement {
@property({
type: String
})
- public type: 'normal' | 'highlighted' | 'discreet' | 'big' = 'normal';
+ public type: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link' | 'normal' | 'highlighted' | 'discreet' | 'big' = 'default';
+
+ @property({
+ type: String
+ })
+ public size: 'default' | 'sm' | 'lg' | 'icon' = 'default';
@property({
type: String
@@ -77,25 +82,23 @@ export class DeesButton extends DeesElement {
cssManager.defaultStyles,
css`
:host {
- display: block;
+ display: inline-block;
box-sizing: border-box;
- font-family: 'Geist Sans', 'monospace';
+ font-family: inherit;
}
:host([hidden]) {
display: none;
}
/* Form spacing styles */
- /* Default vertical form layout */
:host([inside-form]) {
- margin-bottom: 16px; /* Using standard 16px like inputs */
+ margin-bottom: 16px;
}
:host([inside-form]:last-child) {
margin-bottom: 0;
}
- /* Horizontal form layout - auto-detected via parent */
dees-form[horizontal-layout] :host([inside-form]) {
display: inline-block;
margin-right: 16px;
@@ -107,114 +110,260 @@ export class DeesButton extends DeesElement {
}
.button {
- transition: all 0.1s , color 0s;
position: relative;
- font-size: 14px;
- font-weight: 400;
- display: flex;
- justify-content: center;
+ display: inline-flex;
align-items: center;
- background: ${cssManager.bdTheme('#fff', '#333')};
- box-shadow: ${cssManager.bdTheme('0px 1px 3px rgba(0,0,0,0.3)', 'none')};
- border: 1px solid ${cssManager.bdTheme('#eee', '#333')};
- border-top: ${cssManager.bdTheme('1px solid #eee', '1px solid #444')};
- border-radius: 4px;
- height: 40px;
- padding: 0px 8px;
- min-width: 100px;
+ justify-content: center;
+ white-space: nowrap;
+ border-radius: 6px;
+ font-weight: 500;
+ transition: all 0.15s ease;
+ cursor: pointer;
user-select: none;
- color: ${cssManager.bdTheme('#333', ' #ccc')};
- max-width: 500px;
+ outline: none;
+ letter-spacing: -0.01em;
+ gap: 8px;
}
- .button:hover {
- background: #0050b9;
- color: #ffffff;
- border: 1px solid #0050b9;
- border-top: 1px solid #0050b9;
+ /* Size variants */
+ .button.size-default {
+ height: 36px;
+ padding: 0 16px;
+ font-size: 14px;
}
- .button:active {
- background: #0069f2;
- border-top: 1px solid #0069f2;
+ .button.size-sm {
+ height: 32px;
+ padding: 0 12px;
+ font-size: 13px;
}
- .button.highlighted {
- background: #e4002b;
+ .button.size-lg {
+ height: 44px;
+ padding: 0 24px;
+ font-size: 16px;
+ }
+
+ .button.size-icon {
+ height: 36px;
+ width: 36px;
+ padding: 0;
+ }
+
+ /* Default variant */
+ .button.default {
+ background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(215 20.2% 11.8%)')};
+ color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
+ border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(215 20.2% 16.8%)')};
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+ }
+
+ .button.default:hover:not(.disabled) {
+ background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 10.2%)')};
+ border-color: ${cssManager.bdTheme('hsl(214.3 31.8% 85%)', 'hsl(215 20.2% 20%)')};
+ }
+
+ .button.default:active:not(.disabled) {
+ background: ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(215 20.2% 9%)')};
+ }
+
+ /* Destructive variant */
+ .button.destructive {
+ background: hsl(0 84.2% 60.2%);
+ color: hsl(0 0% 98%);
+ border: 1px solid transparent;
+ }
+
+ .button.destructive:hover:not(.disabled) {
+ background: hsl(0 84.2% 56.2%);
+ }
+
+ .button.destructive:active:not(.disabled) {
+ background: hsl(0 84.2% 52.2%);
+ }
+
+ /* Outline variant */
+ .button.outline {
+ background: transparent;
+ color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
+ border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(215 20.2% 21.8%)')};
+ }
+
+ .button.outline:hover:not(.disabled) {
+ background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 16.8%)')};
+ border-color: ${cssManager.bdTheme('hsl(214.3 31.8% 85%)', 'hsl(215 20.2% 26.8%)')};
+ }
+
+ .button.outline:active:not(.disabled) {
+ background: ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(215 20.2% 13.8%)')};
+ }
+
+ /* Secondary variant */
+ .button.secondary {
+ background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 16.8%)')};
+ color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
+ border: 1px solid transparent;
+ }
+
+ .button.secondary:hover:not(.disabled) {
+ background: ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(215 20.2% 13.8%)')};
+ }
+
+ .button.secondary:active:not(.disabled) {
+ background: ${cssManager.bdTheme('hsl(214.3 31.8% 85%)', 'hsl(215 20.2% 11.8%)')};
+ }
+
+ /* Ghost variant */
+ .button.ghost {
+ background: transparent;
+ color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
+ border: 1px solid transparent;
+ }
+
+ .button.ghost:hover:not(.disabled) {
+ background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 16.8%)')};
+ }
+
+ .button.ghost:active:not(.disabled) {
+ background: ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(215 20.2% 13.8%)')};
+ }
+
+ /* Link variant */
+ .button.link {
+ background: transparent;
+ color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(213.1 93.9% 67.8%)')};
border: none;
- color: #fff;
+ text-decoration: underline;
+ text-decoration-color: transparent;
+ height: auto;
+ padding: 0;
}
- .button.highlighted:hover {
- background: #b50021;
- border: none;
- color: #fff;
+ .button.link:hover:not(.disabled) {
+ text-decoration-color: currentColor;
}
- .button.discreet {
- background: none;
- border: 1px solid #9b9b9e;
- color: ${cssManager.bdTheme('#000', '#fff')};
+ /* Status states */
+ .button.pending,
+ .button.success,
+ .button.error {
+ pointer-events: none;
+ padding-left: 36px; /* Space for spinner */
+ }
+
+ .button.size-sm.pending,
+ .button.size-sm.success,
+ .button.size-sm.error {
+ padding-left: 32px;
+ }
+
+ .button.size-lg.pending,
+ .button.size-lg.success,
+ .button.size-lg.error {
+ padding-left: 44px;
}
- .button.discreet:hover {
- background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.1)', 'rgba(255, 255, 255, 0.1)')};
+ .button.pending {
+ background: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(213.1 93.9% 67.8% / 0.2)')};
+ color: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(213.1 93.9% 67.8%)')};
+ border: 1px solid transparent;
}
+ .button.success {
+ background: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3%)', 'hsl(142.1 70.6% 45.3% / 0.2)')};
+ color: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(142.1 70.6% 45.3%)')};
+ border: 1px solid transparent;
+ }
+
+ .button.error {
+ background: ${cssManager.bdTheme('hsl(0 84.2% 60.2%)', 'hsl(0 62.8% 70.6% / 0.2)')};
+ color: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 62.8% 70.6%)')};
+ border: 1px solid transparent;
+ }
+
+ /* Disabled state */
.button.disabled {
- background: ${cssManager.bdTheme('#ffffff00', '#11111100')};
- border: 1px dashed ${cssManager.bdTheme('#666666', '#666666')};
- color: #9b9b9e;
- cursor: default;
+ opacity: 0.5;
+ cursor: not-allowed;
+ pointer-events: none;
}
+ /* Hidden state */
.button.hidden {
display: none;
}
- .button.big {
- width: 300px;
- line-height: 48px;
- font-size: 16px;
- padding: 0px 48px;
- margin-top: 32px;
- }
-
- .button.pending {
- border: 1px dashed ${cssManager.bdTheme('#0069f2', '#0069f270')};
- background: ${cssManager.bdTheme('#0069f2', '#0069f270')};
- color: #fff;
- }
-
- .button.success {
- border: 1px dashed ${cssManager.bdTheme('#689F38', '#8BC34A70')};
- background: ${cssManager.bdTheme('#689F38', '#8BC34A70')};
- color: #fff;
- }
-
- .button.error {
- border: 1px dashed ${cssManager.bdTheme('#B71C1C', '#E64A1970')};
- background: ${cssManager.bdTheme('#B71C1C', '#E64A1970')};
- color: #fff;
+ /* Focus state */
+ .button:focus-visible {
+ outline: 2px solid ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(213.1 93.9% 67.8%)')};
+ outline-offset: 2px;
}
+ /* Loading spinner */
dees-spinner {
position: absolute;
left: 10px;
+ width: 16px;
+ height: 16px;
}
+
+ .button.size-sm dees-spinner {
+ left: 8px;
+ width: 14px;
+ height: 14px;
+ }
+
+ .button.size-lg dees-spinner {
+ left: 14px;
+ width: 18px;
+ height: 18px;
+ }
+
+ /* Icon sizing within buttons */
+ .button dees-icon {
+ width: 16px;
+ height: 16px;
+ flex-shrink: 0;
+ }
+
+ .button.size-sm dees-icon {
+ width: 14px;
+ height: 14px;
+ }
+
+ .button.size-lg dees-icon {
+ width: 18px;
+ height: 18px;
+ }
+
`,
];
public render(): TemplateResult {
+ // Map old types to new types for backward compatibility
+ const typeMap: {[key: string]: string} = {
+ 'normal': 'default',
+ 'highlighted': 'destructive',
+ 'discreet': 'outline',
+ 'big': 'default' // Will use size instead
+ };
+
+ const actualType = typeMap[this.type] || this.type;
+ const actualSize = this.type === 'big' ? 'lg' : this.size;
+
return html`
diff --git a/ts_web/elements/dees-panel.ts b/ts_web/elements/dees-panel.ts
index 8979a56..4f0994a 100644
--- a/ts_web/elements/dees-panel.ts
+++ b/ts_web/elements/dees-panel.ts
@@ -30,28 +30,30 @@ export class DeesPanel extends DeesElement {
css`
:host {
display: block;
- background: ${cssManager.bdTheme('#ffffff', '#1a1a1a')};
+ background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(215 20.2% 11.8%)')};
border-radius: 8px;
padding: 24px;
- box-shadow: 0 2px 4px ${cssManager.bdTheme('rgba(0,0,0,0.1)', 'rgba(0,0,0,0.3)')};
- border: 1px solid ${cssManager.bdTheme('rgba(0,0,0,0.1)', 'rgba(255,255,255,0.1)')};
+ border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(215 20.2% 16.8%)')};
+ box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
}
.title {
- margin: 0 0 16px 0;
+ margin: 0 0 8px 0;
font-size: 18px;
- font-weight: 500;
- color: ${cssManager.bdTheme('#0069f2', '#0099ff')};
+ font-weight: 600;
+ color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
+ letter-spacing: -0.025em;
}
.subtitle {
- margin: -12px 0 16px 0;
+ margin: 0 0 16px 0;
font-size: 14px;
- color: ${cssManager.bdTheme('#666', '#999')};
+ color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
+ letter-spacing: -0.01em;
}
.content {
- color: ${cssManager.bdTheme('#333', '#ccc')};
+ color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
}
/* Remove margins from first and last children */
diff --git a/ts_web/elements/dees-statsgrid.demo.ts b/ts_web/elements/dees-statsgrid.demo.ts
index 67ce39a..a452b25 100644
--- a/ts_web/elements/dees-statsgrid.demo.ts
+++ b/ts_web/elements/dees-statsgrid.demo.ts
@@ -1,389 +1,516 @@
-import { html, cssManager } from '@design.estate/dees-element';
+import { html, css, cssManager } from '@design.estate/dees-element';
+import '@design.estate/dees-wcctools/demotools';
+import './dees-panel.js';
import type { IStatsTile } from './dees-statsgrid.js';
export const demoFunc = () => {
- // Demo data with different tile types
- const demoTiles: IStatsTile[] = [
- {
- id: 'revenue',
- title: 'Total Revenue',
- value: 125420,
- unit: '$',
- type: 'number',
- icon: 'faDollarSign',
- description: '+12.5% from last month',
- color: '#22c55e',
- actions: [
- {
- name: 'View Details',
- iconName: 'faChartLine',
- action: async () => {
- console.log('Viewing revenue details for tile:', 'revenue');
- console.log('Current value:', 125420);
- alert(`Revenue Details: $125,420 (+12.5%)`);
- }
- },
- {
- name: 'Export Data',
- iconName: 'faFileExport',
- action: async () => {
- console.log('Exporting revenue data');
- alert('Revenue data exported to CSV');
- }
- }
- ]
- },
- {
- id: 'users',
- title: 'Active Users',
- value: 3847,
- type: 'number',
- icon: 'faUsers',
- description: '324 new this week',
- actions: [
- {
- name: 'View User List',
- iconName: 'faList',
- action: async () => {
- console.log('Viewing user list');
- }
- }
- ]
- },
- {
- id: 'cpu',
- title: 'CPU Usage',
- value: 73,
- type: 'gauge',
- icon: 'faMicrochip',
- gaugeOptions: {
- min: 0,
- max: 100,
- thresholds: [
- { value: 0, color: '#22c55e' },
- { value: 60, color: '#f59e0b' },
- { value: 80, color: '#ef4444' }
- ]
- }
- },
- {
- id: 'storage',
- title: 'Storage Used',
- value: 65,
- type: 'percentage',
- icon: 'faHardDrive',
- description: '650 GB of 1 TB',
- color: '#3b82f6'
- },
- {
- id: 'memory',
- title: 'Memory Usage',
- value: 45,
- type: 'gauge',
- icon: 'faMemory',
- gaugeOptions: {
- min: 0,
- max: 100,
- thresholds: [
- { value: 0, color: '#22c55e' },
- { value: 70, color: '#f59e0b' },
- { value: 90, color: '#ef4444' }
- ]
- }
- },
- {
- id: 'requests',
- title: 'API Requests',
- value: '1.2k',
- unit: '/min',
- type: 'trend',
- icon: 'faServer',
- trendData: [45, 52, 38, 65, 72, 68, 75, 82, 79, 85, 88, 92]
- },
- {
- id: 'uptime',
- title: 'System Uptime',
- value: '99.95%',
- type: 'text',
- icon: 'faCheckCircle',
- color: '#22c55e',
- description: 'Last 30 days'
- },
- {
- id: 'latency',
- title: 'Response Time',
- value: 142,
- unit: 'ms',
- type: 'trend',
- icon: 'faClock',
- trendData: [150, 145, 148, 142, 138, 140, 135, 145, 142],
- description: 'P95 latency'
- },
- {
- id: 'errors',
- title: 'Error Rate',
- value: 0.03,
- unit: '%',
- type: 'number',
- icon: 'faExclamationTriangle',
- color: '#ef4444',
- actions: [
- {
- name: 'View Error Logs',
- iconName: 'faFileAlt',
- action: async () => {
- console.log('Viewing error logs');
- }
- }
- ]
- }
- ];
-
- // Grid actions for the demo
- const gridActions = [
- {
- name: 'Refresh',
- iconName: 'faSync',
- action: async () => {
- console.log('Refreshing stats...');
- // Simulate refresh animation
- const grid = document.querySelector('dees-statsgrid');
- if (grid) {
- grid.style.opacity = '0.5';
- setTimeout(() => {
- grid.style.opacity = '1';
- }, 500);
- }
- }
- },
- {
- name: 'Export Report',
- iconName: 'faFileExport',
- action: async () => {
- console.log('Exporting stats report...');
- }
- },
- {
- name: 'Settings',
- iconName: 'faCog',
- action: async () => {
- console.log('Opening settings...');
- }
- }
- ];
-
return html`
+
-
+
-
{
- document.body.classList.toggle('bright');
- }}>Toggle Theme
-
-
-
Full Featured Stats Grid
-
- A comprehensive dashboard with various tile types, actions, and real-time updates.
-
-
-
-
-
-
Compact Grid (Smaller Tiles)
-
- Same data displayed with smaller minimum tile width for more compact layouts.
-
-
-
-
-
-
Simple Metrics (No Actions)
-
- Clean display without interactive elements for pure visualization.
-
+
{
+ const output = document.querySelector('#action-output');
+ if (output) {
+ output.textContent = 'Viewing revenue details: $125,420 (+12.5%)';
+ }
+ }
+ },
+ {
+ name: 'Export Data',
+ iconName: 'lucide:download',
+ action: async () => {
+ const output = document.querySelector('#action-output');
+ if (output) {
+ output.textContent = 'Exporting revenue data to CSV...';
+ }
+ }
+ }
+ ]
},
{
- id: 'metric4',
- title: 'Customer Satisfaction',
- value: 92,
- type: 'percentage',
- icon: 'faSmile',
- color: '#22c55e'
- }
- ]}
- .minTileWidth=${220}
- .gap=${16}
- >
-
-
-
-
Performance Monitoring
-
- Real-time performance metrics with gauge visualizations and thresholds.
-
-
{
+ const output = document.querySelector('#action-output');
+ if (output) {
+ output.textContent = 'Opening user list...';
+ }
+ }
+ }
+ ]
+ },
{
- id: 'perf1',
- title: 'Database Load',
- value: 42,
+ id: 'cpu',
+ title: 'CPU Usage',
+ value: 73,
+ unit: '%',
type: 'gauge',
- icon: 'faDatabase',
+ icon: 'lucide:cpu',
gaugeOptions: {
min: 0,
max: 100,
thresholds: [
- { value: 0, color: '#10b981' },
- { value: 50, color: '#f59e0b' },
- { value: 75, color: '#ef4444' }
+ { value: 0, color: 'hsl(142.1 76.2% 36.3%)' },
+ { value: 60, color: 'hsl(45.4 93.4% 47.5%)' },
+ { value: 80, color: 'hsl(0 84.2% 60.2%)' }
]
}
},
{
- id: 'perf2',
- title: 'Network I/O',
- value: 856,
- unit: 'MB/s',
- type: 'trend',
- icon: 'faNetworkWired',
- trendData: [720, 780, 823, 845, 812, 876, 856]
- },
- {
- id: 'perf3',
- title: 'Cache Hit Rate',
- value: 94.2,
+ id: 'storage',
+ title: 'Storage Used',
+ value: 65,
type: 'percentage',
- icon: 'faBolt',
- color: '#3b82f6'
+ icon: 'lucide:hard-drive',
+ description: '650 GB of 1 TB',
},
{
- id: 'perf4',
- title: 'Active Connections',
- value: 1428,
- type: 'number',
- icon: 'faLink',
- description: 'Peak: 2,100'
+ id: 'latency',
+ title: 'Response Time',
+ value: 142,
+ unit: 'ms',
+ type: 'trend',
+ icon: 'lucide:activity',
+ trendData: [150, 145, 148, 142, 138, 140, 135, 145, 142],
+ description: 'P95'
+ },
+ {
+ id: 'uptime',
+ title: 'System Uptime',
+ value: '99.95%',
+ type: 'text',
+ icon: 'lucide:check-circle',
+ color: 'hsl(142.1 76.2% 36.3%)',
+ description: 'Last 30 days'
}
]}
.gridActions=${[
{
- name: 'Auto Refresh',
- iconName: 'faPlay',
+ name: 'Refresh',
+ iconName: 'lucide:refresh-cw',
action: async () => {
- console.log('Starting auto refresh...');
+ const grid = document.querySelector('dees-statsgrid');
+ if (grid) {
+ grid.style.opacity = '0.5';
+ setTimeout(() => {
+ grid.style.opacity = '1';
+ }, 300);
+ }
+ }
+ },
+ {
+ name: 'Export',
+ iconName: 'lucide:share',
+ action: async () => {
+ const output = document.querySelector('#action-output');
+ if (output) {
+ output.textContent = 'Exporting dashboard report...';
+ }
+ }
+ },
+ {
+ name: 'Settings',
+ iconName: 'lucide:settings',
+ action: async () => {
+ const output = document.querySelector('#action-output');
+ if (output) {
+ output.textContent = 'Opening dashboard settings...';
+ }
}
}
]}
+ .minTileWidth=${250}
+ .gap=${16}
+ >
+
+
+ Click on tile actions or grid actions to see the result...
+
+
+
+
+
+
+
+
+
Configuration Options
+
+ Each tile type supports different properties:
+
+ Number: value, unit, color, description
+ Gauge: value, unit, gaugeOptions (min, max, thresholds)
+ Percentage: value (0-100), color, description
+ Trend: value, unit, trendData array, description
+ Text: value (string), color, description
+
+
+
+
+
+
+
+ Compact Layout (180px tiles)
+
+
+ Spacious Layout (320px tiles)
+
-
-
-
+ ]}
+ .gridActions=${[
+ {
+ name: 'Start Live Updates',
+ iconName: 'lucide:play',
+ action: async function() {
+ // Toggle live updates
+ if (!window.liveUpdateInterval) {
+ window.liveUpdateInterval = setInterval(() => {
+ const grid = document.querySelector('#interactive-grid');
+ if (grid) {
+ const tiles = [...grid.tiles];
+
+ // Update CPU gauge
+ const cpuTile = tiles.find(t => t.id === 'live-cpu');
+ cpuTile.value = Math.max(0, Math.min(100, cpuTile.value + (Math.random() * 20 - 10)));
+
+ // Update requests trend
+ const requestsTile = tiles.find(t => t.id === 'live-requests');
+ const newValue = requestsTile.value + Math.round(Math.random() * 50 - 25);
+ requestsTile.value = Math.max(800, newValue);
+ requestsTile.trendData = [...requestsTile.trendData.slice(1), requestsTile.value];
+
+ // Update memory percentage
+ const memoryTile = tiles.find(t => t.id === 'live-memory');
+ memoryTile.value = Math.max(0, Math.min(100, memoryTile.value + (Math.random() * 10 - 5)));
+
+ grid.tiles = tiles;
+ }
+ }, 1000);
+
+ this.name = 'Stop Live Updates';
+ this.iconName = 'lucide:pause';
+ } else {
+ clearInterval(window.liveUpdateInterval);
+ window.liveUpdateInterval = null;
+ this.name = 'Start Live Updates';
+ this.iconName = 'lucide:play';
+ }
+ }
+ }
+ ]}
+ .minTileWidth=${250}
+ .gap=${16}
+ >
+
+
+
+ ${`const tiles: IStatsTile[] = [
+ {
+ id: 'revenue',
+ title: 'Total Revenue',
+ value: 125420,
+ unit: '$',
+ type: 'number',
+ icon: 'lucide:dollar-sign',
+ description: '+12.5% from last month',
+ actions: [
+ {
+ name: 'View Details',
+ iconName: 'lucide:trending-up',
+ action: async () => {
+ console.log('View revenue details');
+ }
+ }
+ ]
+ },
+ {
+ id: 'cpu',
+ title: 'CPU Usage',
+ value: 73,
+ unit: '%',
+ type: 'gauge',
+ icon: 'lucide:cpu',
+ gaugeOptions: {
+ min: 0,
+ max: 100,
+ thresholds: [
+ { value: 0, color: 'hsl(142.1 76.2% 36.3%)' },
+ { value: 60, color: 'hsl(45.4 93.4% 47.5%)' },
+ { value: 80, color: 'hsl(0 84.2% 60.2%)' }
+ ]
+ }
+ }
+];
+
+// Render the stats grid
+html\`
+ console.log('Refresh')
+ }
+ ]}
+ >
+\`;`}
+
+
+
+
`;
};
\ No newline at end of file
diff --git a/ts_web/elements/dees-statsgrid.ts b/ts_web/elements/dees-statsgrid.ts
index 383453f..ad5ec33 100644
--- a/ts_web/elements/dees-statsgrid.ts
+++ b/ts_web/elements/dees-statsgrid.ts
@@ -81,28 +81,44 @@ export class DeesStatsGrid extends DeesElement {
width: 100%;
}
+ /* CSS Variables for consistent spacing and sizing */
+ :host {
+ --grid-gap: 16px;
+ --tile-padding: 24px;
+ --header-spacing: 16px;
+ --content-min-height: 48px;
+ --value-font-size: 30px;
+ --unit-font-size: 16px;
+ --label-font-size: 13px;
+ --title-font-size: 14px;
+ --description-spacing: 12px;
+ --border-radius: 8px;
+ --transition-duration: 0.15s;
+ }
+
+ /* Grid Layout */
.grid-header {
display: flex;
justify-content: space-between;
align-items: center;
- margin-bottom: ${unsafeCSS(16)}px;
- min-height: 32px;
+ margin-bottom: calc(var(--grid-gap) * 1.5);
+ min-height: 40px;
}
.grid-title {
- font-size: 18px;
- font-weight: 600;
- color: ${cssManager.bdTheme('#333', '#fff')};
+ font-size: 16px;
+ font-weight: 500;
+ color: ${cssManager.bdTheme('#09090b', '#fafafa')};
+ letter-spacing: -0.01em;
}
.grid-actions {
display: flex;
- gap: 8px;
+ gap: 6px;
}
.grid-actions dees-button {
- font-size: 14px;
- min-width: auto;
+ font-size: var(--label-font-size);
}
.stats-grid {
@@ -112,86 +128,106 @@ export class DeesStatsGrid extends DeesElement {
width: 100%;
}
+ /* Tile Base Styles */
.stats-tile {
- background: ${cssManager.bdTheme('#fff', '#1a1a1a')};
- border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#2a2a2a')};
- border-radius: 12px;
- padding: 20px;
- transition: all 0.3s ease;
- cursor: pointer;
+ background: ${cssManager.bdTheme('#ffffff', '#09090b')};
+ border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(215 20.2% 11.8%)')};
+ border-radius: var(--border-radius);
+ padding: var(--tile-padding);
+ transition: all var(--transition-duration) ease;
+ cursor: default;
position: relative;
overflow: hidden;
+ display: flex;
+ flex-direction: column;
}
.stats-tile:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 12px ${cssManager.bdTheme('rgba(0,0,0,0.1)', 'rgba(0,0,0,0.3)')};
- border-color: ${cssManager.bdTheme('#d0d0d0', '#3a3a3a')};
+ background: ${cssManager.bdTheme('hsl(210 40% 98%)', 'hsl(215 20.2% 10.2%)')};
+ border-color: ${cssManager.bdTheme('hsl(214.3 31.8% 85%)', 'hsl(215 20.2% 16.8%)')};
}
.stats-tile.clickable {
cursor: pointer;
}
+ .stats-tile.clickable:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 2px 8px ${cssManager.bdTheme('rgba(0,0,0,0.04)', 'rgba(0,0,0,0.2)')};
+ }
+
+ /* Tile Header */
.tile-header {
display: flex;
justify-content: space-between;
- align-items: center;
- margin-bottom: 12px;
- width: 100%;
+ align-items: flex-start;
+ margin-bottom: var(--header-spacing);
+ flex-shrink: 0;
}
.tile-title {
- font-size: 14px;
+ font-size: var(--title-font-size);
font-weight: 500;
- color: ${cssManager.bdTheme('#666', '#aaa')};
+ color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
margin: 0;
+ letter-spacing: -0.01em;
+ line-height: 1.2;
}
.tile-icon {
- opacity: 0.6;
+ opacity: 0.7;
+ color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
+ font-size: 16px;
+ flex-shrink: 0;
}
+ /* Tile Content */
.tile-content {
- height: 90px;
+ min-height: var(--content-min-height);
display: flex;
flex-direction: column;
justify-content: center;
- align-items: center;
- position: relative;
+ flex: 1;
}
.tile-value {
- font-size: 32px;
+ font-size: var(--value-font-size);
font-weight: 600;
- color: ${cssManager.bdTheme('#333', '#fff')};
- line-height: 1.2;
+ color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
+ line-height: 1;
display: flex;
align-items: baseline;
- justify-content: center;
- gap: 6px;
- width: 100%;
+ gap: 4px;
+ letter-spacing: -0.025em;
}
.tile-unit {
- font-size: 18px;
+ font-size: var(--unit-font-size);
font-weight: 400;
- color: ${cssManager.bdTheme('#666', '#aaa')};
+ color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
+ letter-spacing: -0.01em;
}
.tile-description {
- font-size: 12px;
- color: ${cssManager.bdTheme('#888', '#777')};
- margin-top: 8px;
+ font-size: var(--label-font-size);
+ color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
+ margin-top: var(--description-spacing);
+ letter-spacing: -0.01em;
+ flex-shrink: 0;
+ }
+
+ /* Gauge Styles */
+ .gauge-wrapper {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
}
.gauge-container {
- width: 100%;
- height: 80px;
+ width: 140px;
+ height: 70px;
position: relative;
- display: flex;
- align-items: center;
- justify-content: center;
}
.gauge-svg {
@@ -201,96 +237,130 @@ export class DeesStatsGrid extends DeesElement {
.gauge-background {
fill: none;
- stroke: ${cssManager.bdTheme('#e0e0e0', '#2a2a2a')};
- stroke-width: 6;
+ stroke: ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(215 20.2% 21.8%)')};
+ stroke-width: 8;
}
.gauge-fill {
fill: none;
- stroke-width: 6;
+ stroke-width: 8;
stroke-linecap: round;
- transition: stroke-dashoffset 0.5s ease;
+ transition: stroke-dashoffset 0.6s cubic-bezier(0.4, 0, 0.2, 1);
}
.gauge-text {
- fill: ${cssManager.bdTheme('#333', '#fff')};
- font-size: 18px;
+ fill: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
+ font-size: var(--value-font-size);
font-weight: 600;
text-anchor: middle;
+ dominant-baseline: alphabetic;
+ letter-spacing: -0.025em;
+ }
+
+ .gauge-unit {
+ font-size: var(--unit-font-size);
+ fill: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
+ font-weight: 400;
}
- .percentage-container {
+ /* Percentage Styles */
+ .percentage-wrapper {
width: 100%;
- height: 24px;
- background: ${cssManager.bdTheme('#f0f0f0', '#2a2a2a')};
- border-radius: 12px;
- overflow: hidden;
position: relative;
}
+ .percentage-value {
+ font-size: var(--value-font-size);
+ font-weight: 600;
+ color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
+ letter-spacing: -0.025em;
+ margin-bottom: 8px;
+ }
+
+ .percentage-bar {
+ width: 100%;
+ height: 8px;
+ background: ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(215 20.2% 21.8%)')};
+ border-radius: 4px;
+ overflow: hidden;
+ }
+
.percentage-fill {
height: 100%;
- background: ${cssManager.bdTheme('#0084ff', '#0066cc')};
- transition: width 0.5s ease;
- border-radius: 12px;
- }
-
- .percentage-text {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- font-size: 12px;
- font-weight: 600;
- color: ${cssManager.bdTheme('#333', '#fff')};
+ background: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
+ transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
+ border-radius: 4px;
}
+ /* Trend Styles */
.trend-container {
width: 100%;
- height: 100%;
- position: relative;
display: flex;
flex-direction: column;
- justify-content: center;
- align-items: center;
- gap: 4px;
+ gap: 8px;
+ }
+
+ .trend-header {
+ display: flex;
+ align-items: baseline;
+ gap: 8px;
+ }
+
+ .trend-value {
+ font-size: var(--value-font-size);
+ font-weight: 600;
+ color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
+ letter-spacing: -0.025em;
+ }
+
+ .trend-unit {
+ font-size: var(--unit-font-size);
+ font-weight: 400;
+ color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
+ letter-spacing: -0.01em;
+ }
+
+ .trend-label {
+ font-size: var(--label-font-size);
+ font-weight: 500;
+ color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
+ letter-spacing: -0.01em;
+ margin-left: auto;
+ }
+
+ .trend-graph {
+ width: 100%;
+ height: 32px;
+ position: relative;
}
.trend-svg {
width: 100%;
- height: 40px;
- flex-shrink: 0;
+ height: 100%;
+ display: block;
}
.trend-line {
fill: none;
- stroke: ${cssManager.bdTheme('#0084ff', '#0066cc')};
+ stroke: ${cssManager.bdTheme('hsl(215.4 16.3% 66.9%)', 'hsl(215 20.2% 55.1%)')};
stroke-width: 2;
+ stroke-linejoin: round;
+ stroke-linecap: round;
}
.trend-area {
- fill: ${cssManager.bdTheme('rgba(0, 132, 255, 0.1)', 'rgba(0, 102, 204, 0.2)')};
+ fill: ${cssManager.bdTheme('hsl(215.4 16.3% 66.9% / 0.1)', 'hsl(215 20.2% 55.1% / 0.08)')};
}
+ /* Text Value Styles */
.text-value {
- font-size: 32px;
+ font-size: var(--value-font-size);
font-weight: 600;
- color: ${cssManager.bdTheme('#333', '#fff')};
- }
-
- .trend-value {
- font-size: 32px;
- font-weight: 600;
- color: ${cssManager.bdTheme('#333', '#fff')};
- display: flex;
- align-items: baseline;
- gap: 6px;
- }
-
- .trend-value .tile-unit {
- font-size: 18px;
+ color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
+ letter-spacing: -0.025em;
}
+ /* Context Menu */
dees-contextmenu {
position: fixed;
z-index: 1000;
@@ -306,10 +376,14 @@ export class DeesStatsGrid extends DeesElement {
return html`
${this.gridActions.length > 0 ? html`
@@ -396,12 +470,31 @@ export class DeesStatsGrid extends DeesElement {
const value = typeof tile.value === 'number' ? tile.value : parseFloat(tile.value);
const options = tile.gaugeOptions || { min: 0, max: 100 };
const percentage = ((value - options.min) / (options.max - options.min)) * 100;
- const strokeDasharray = 188.5; // Circumference of circle with r=30
- const strokeDashoffset = strokeDasharray - (strokeDasharray * percentage) / 100;
+
+ // SVG dimensions and calculations
+ const width = 140;
+ const height = 70;
+ const strokeWidth = 8;
+ const padding = strokeWidth / 2 + 2;
+ const radius = 40;
+ const centerX = width / 2;
+ const centerY = height - padding;
+
+ // Arc path
+ const startX = centerX - radius;
+ const startY = centerY;
+ const endX = centerX + radius;
+ const endY = centerY;
+ const arcPath = `M ${startX} ${startY} A ${radius} ${radius} 0 0 1 ${endX} ${endY}`;
+
+ // Calculate stroke dasharray and dashoffset
+ const circumference = Math.PI * radius;
+ const strokeDashoffset = circumference - (circumference * percentage) / 100;
- let strokeColor = tile.color || cssManager.bdTheme('#0084ff', '#0066cc');
+ let strokeColor = tile.color || cssManager.bdTheme('hsl(215.3 25% 28.8%)', 'hsl(210 40% 78%)');
if (options.thresholds) {
- for (const threshold of options.thresholds.reverse()) {
+ const sortedThresholds = [...options.thresholds].sort((a, b) => b.value - a.value);
+ for (const threshold of sortedThresholds) {
if (value >= threshold.value) {
strokeColor = threshold.color;
break;
@@ -410,29 +503,28 @@ export class DeesStatsGrid extends DeesElement {
}
return html`
-
-
-
-
-
- ${value}${tile.unit || ''}
-
-
+
+
+
+
+
+
+
+
+
+ ${value} ${tile.unit ? html`${tile.unit} ` : ''}
+
+
+
`;
}
@@ -442,12 +534,14 @@ export class DeesStatsGrid extends DeesElement {
const percentage = Math.min(100, Math.max(0, value));
return html`
-
-
-
${percentage}%
+
`;
}
@@ -461,11 +555,14 @@ export class DeesStatsGrid extends DeesElement {
const max = Math.max(...data);
const min = Math.min(...data);
const range = max - min || 1;
- const width = 200;
- const height = 40;
+ const width = 300;
+ const height = 32;
+
+ // Add padding to prevent clipping
+ const padding = 2;
const points = data.map((value, index) => {
const x = (index / (data.length - 1)) * width;
- const y = height - ((value - min) / range) * height;
+ const y = padding + (height - 2 * padding) - ((value - min) / range) * (height - 2 * padding);
return `${x},${y}`;
}).join(' ');
@@ -473,13 +570,16 @@ export class DeesStatsGrid extends DeesElement {
return html`
-
-
-
-
-
-
${tile.value}
- ${tile.unit ? html`
${tile.unit} ` : ''}
+
+
`;